spaps
Version:
Sweet Potato Authentication & Payment Service CLI - Docker Compose orchestrator for local Python/FastAPI SPAPS server with built-in admin middleware
201 lines (179 loc) • 5.98 kB
JavaScript
const chalk = require('chalk');
const { DEFAULT_PORT } = require('./config');
const { getServerRuntime } = require('./local-runtime');
const { resolveServerUrl } = require('./auth/client');
const { getCredentials, CREDENTIALS_PATH } = require('./auth/credentials');
const { findUpContract } = require('./auth/client-id');
const { resolveAuthApiKey } = require('./auth/api-key');
function resolveAppContext({ cwd, runtime }) {
if (typeof process.env.SPAPS_CLI_CLIENT_ID === 'string' && process.env.SPAPS_CLI_CLIENT_ID.trim()) {
return {
clientId: process.env.SPAPS_CLI_CLIENT_ID.trim(),
source: 'SPAPS_CLI_CLIENT_ID',
path: null,
};
}
const contractHit = findUpContract(cwd);
if (contractHit) {
return contractHit;
}
const runtimeClientId = runtime?.local_mode?.test_application?.slug || null;
if (runtimeClientId) {
return {
clientId: runtimeClientId,
source: '/health/local-mode',
path: null,
};
}
return {
clientId: null,
source: null,
path: null,
};
}
function isLocalhostUrl(url) {
try {
const parsed = new URL(url);
return parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1';
} catch {
return false;
}
}
function formatSource(source, filePath) {
if (!source) {
return null;
}
return filePath ? `${source} (${filePath})` : source;
}
function formatMode(runtime) {
if (!runtime.running) {
return 'server unreachable';
}
if (!runtime.local_mode?.known) {
return 'mode unknown';
}
return runtime.local_mode.active ? 'local mode active' : 'application key required';
}
function buildRecommendedNext({ operator, runtime, port }) {
if (!operator.connected) {
return {
command: 'spaps connect',
reason: `No saved operator session for ${operator.server_url}.`,
};
}
if (!runtime.running) {
return {
command: isLocalhostUrl(runtime.url)
? `spaps local${port !== DEFAULT_PORT ? ` --port ${port}` : ''}`
: 'spaps verify',
reason: isLocalhostUrl(runtime.url)
? `No SPAPS runtime is reachable at ${runtime.url}.`
: `No SPAPS runtime is reachable at ${runtime.url}; verify the configured server URL.`,
};
}
return {
command: 'spaps verify',
reason: runtime.local_mode?.active
? 'Confirm the local auth path and runtime hints end to end.'
: 'Confirm the provisioned auth path and API key wiring end to end.',
};
}
async function buildHomeView({ port = DEFAULT_PORT, serverUrl = null, cwd = process.cwd() } = {}) {
const resolvedPort = Number(port) || DEFAULT_PORT;
const resolvedServerUrl = resolveServerUrl({ port: resolvedPort, serverUrl });
const runtime = await getServerRuntime({ port: resolvedPort, serverUrl: resolvedServerUrl });
const storedCreds = getCredentials(resolvedServerUrl);
const envAccessToken =
typeof process.env.SPAPS_ACCESS_TOKEN === 'string' && process.env.SPAPS_ACCESS_TOKEN.trim()
? process.env.SPAPS_ACCESS_TOKEN.trim()
: null;
const appContext = resolveAppContext({ cwd, runtime });
const apiKey = resolveAuthApiKey({ cwd });
const operator = {
server_url: resolvedServerUrl,
connected: Boolean(envAccessToken || storedCreds?.access_token),
source: envAccessToken ? 'SPAPS_ACCESS_TOKEN' : storedCreds?.access_token ? 'credentials' : null,
credentials_path: CREDENTIALS_PATH,
user_id: storedCreds?.user_id || null,
client_id: storedCreds?.client_id || null,
expires_at: storedCreds?.expires_at || null,
};
const app = {
client_id: appContext.clientId,
client_id_source: appContext.source,
client_id_path: appContext.path,
api_key_configured: Boolean(apiKey.apiKey),
api_key_source: apiKey.source,
api_key_path: apiKey.path,
api_key_env: apiKey.envVar || null,
};
return {
success: true,
command: 'home',
operator,
app,
runtime,
recommended_next: buildRecommendedNext({
operator,
runtime,
port: resolvedPort,
}),
};
}
function renderHomeView(view, { logo = '' } = {}) {
if (logo) {
console.log(logo);
}
console.log(chalk.bold('Operator'));
console.log(' Server:', chalk.cyan(view.operator.server_url));
console.log(
' Session:',
view.operator.connected
? chalk.green(`connected via ${view.operator.source || 'credentials'}`)
: chalk.yellow('not connected')
);
if (view.operator.client_id) {
console.log(' Session app:', chalk.cyan(view.operator.client_id));
}
if (view.operator.user_id) {
console.log(' User:', chalk.cyan(view.operator.user_id));
}
console.log();
console.log(chalk.bold('App'));
if (view.app.client_id) {
console.log(' Client id:', chalk.cyan(view.app.client_id));
if (view.app.client_id_source) {
console.log(chalk.gray(` source: ${formatSource(view.app.client_id_source, view.app.client_id_path)}`));
}
} else {
console.log(' Client id:', chalk.yellow('not detected'));
}
console.log(
' API key:',
view.app.api_key_configured
? chalk.green(`configured via ${formatSource(view.app.api_key_source, view.app.api_key_path)}`)
: chalk.yellow('not found')
);
console.log();
console.log(chalk.bold('Runtime'));
if (!view.runtime.running) {
console.log(' Status:', chalk.red('unreachable'));
console.log(' Target:', chalk.cyan(view.runtime.url));
if (view.runtime.error?.message) {
console.log(chalk.gray(` ${view.runtime.error.message}`));
}
} else {
console.log(' Status:', chalk.green('running'));
console.log(' URL:', chalk.cyan(view.runtime.url));
console.log(' Mode:', chalk.cyan(formatMode(view.runtime)));
}
console.log();
console.log(chalk.bold('Next'));
console.log(' ' + chalk.cyan(view.recommended_next.command));
console.log(chalk.gray(` ${view.recommended_next.reason}`));
console.log();
}
module.exports = {
buildHomeView,
renderHomeView,
};