spaps
Version:
Sweet Potato Authentication & Payment Service CLI - Docker Compose orchestrator for local Python/FastAPI SPAPS server with built-in admin middleware
250 lines (221 loc) • 7.21 kB
JavaScript
const { DEFAULT_PORT } = require('./config');
const { getServerRuntime, readSelfServicePassword } = require('./local-runtime');
const { resolveAuthApiKey } = require('./auth/api-key');
function normalizeRuntimeArgs(input = DEFAULT_PORT) {
if (typeof input === 'number') {
return {
port: input,
serverUrl: null,
cwd: process.cwd(),
};
}
if (input && typeof input === 'object') {
return {
port: Number(input.port) || DEFAULT_PORT,
serverUrl: input.serverUrl || resolveEnvServerUrl(),
cwd: input.cwd || process.cwd(),
};
}
return {
port: DEFAULT_PORT,
serverUrl: null,
cwd: process.cwd(),
};
}
function resolveEnvServerUrl() {
const raw = String(process.env.SPAPS_API_URL || '').trim();
return raw ? raw : null;
}
function buildProvisioningCommand(template, port) {
return `SELF_SERVICE_PASSWORD=your-password npx spaps create demo-${template === 'node' ? 'api' : 'app'} --template ${template} --port ${port}`;
}
function buildLocalModeInstructions(runtime) {
return {
success: true,
server: {
running: true,
url: runtime.url,
docs: runtime.docs,
local_mode_active: true,
environment: runtime.local_mode.environment,
spaps_local_mode_env: runtime.local_mode.spaps_local_mode_env,
},
auth: {
local_mode: true,
api_key_required: false,
mode_source: '/health/local-mode',
test_users: runtime.local_mode.test_users,
test_application: runtime.local_mode.test_application,
hints: runtime.local_mode.hints,
},
summary: [
'Install the SDK with `npm install spaps-sdk`.',
`Point the client at ${runtime.url}.`,
'Use the local-mode hints from `/health/local-mode` if you need a specific test persona.',
],
instructions: {
install_sdk: {
description: 'Install the SDK',
command: 'npm install spaps-sdk',
},
create_client: {
description: 'Create a client for local mode',
filename: 'test-spaps.js',
content: `const { SPAPSClient } = require('spaps-sdk');
const spaps = new SPAPSClient({ apiUrl: '${runtime.url}' });
async function main() {
const status = await fetch('${runtime.url}/health/local-mode').then((res) => res.json());
console.log(status.data);
}
main().catch(console.error);
`,
},
verify_server: {
description: 'Confirm local mode status',
command: `curl -s ${runtime.url}/health/local-mode | jq '.'`,
},
},
};
}
function buildProvisionedModeInstructions(runtime, port) {
const hasPassword = Boolean(readSelfServicePassword());
return {
success: true,
server: {
running: true,
url: runtime.url,
docs: runtime.docs,
local_mode_active: false,
environment: runtime.local_mode.environment,
spaps_local_mode_env: runtime.local_mode.spaps_local_mode_env,
},
auth: {
local_mode: false,
api_key_required: true,
mode_source: '/health/local-mode',
header: 'X-API-Key',
env: 'SPAPS_API_KEY',
self_service_password_env: 'SELF_SERVICE_PASSWORD',
},
summary: [
'Install the SDK with `npm install spaps-sdk`.',
'Provision a local application before testing authenticated flows.',
`Send \`X-API-Key\` when /health/local-mode reports \`local_mode_active: false\`.`,
],
instructions: {
install_sdk: {
description: 'Install the SDK',
command: 'npm install spaps-sdk',
},
provision_app: {
description: hasPassword
? 'Provision a local browser app'
: 'Set SELF_SERVICE_PASSWORD, then provision a local browser app',
command: buildProvisioningCommand('react', port),
},
create_client: {
description: 'Create a client that uses a provisioned key',
filename: 'test-spaps.js',
content: `const { createServerClient } = require('spaps-sdk');
const apiUrl = process.env.SPAPS_API_URL || '${runtime.url}';
const apiKey = process.env.SPAPS_API_KEY;
if (!apiKey) {
throw new Error('Set SPAPS_API_KEY before testing against a non-local-mode server.');
}
const spaps = createServerClient(apiKey, { apiUrl });
async function main() {
const result = await spaps.auth.signInWithPassword({
email: 'user@example.com',
password: 'correct-horse-battery-staple',
});
console.log(result.user.id);
}
main().catch(console.error);
`,
},
verify_server: {
description: 'Confirm whether the server is in local mode',
command: `curl -s ${runtime.url}/health/local-mode | jq '.'`,
},
},
};
}
async function getQuickStartInstructions(input = DEFAULT_PORT) {
const { port, serverUrl } = normalizeRuntimeArgs(input);
const runtime = await getServerRuntime({ port, serverUrl });
if (!runtime.running) {
return {
success: false,
server: runtime,
summary: [
`Start the local server with \`npx spaps local --port ${port}\`.`,
'Provision a starter application after the server is healthy.',
],
instructions: {
start_server: {
description: 'Start the local server',
command: `npx spaps local --port ${port}`,
},
},
};
}
if (runtime.local_mode.active) {
return buildLocalModeInstructions(runtime);
}
return buildProvisionedModeInstructions(runtime, port);
}
async function getServerStatus(input = DEFAULT_PORT) {
const { port, serverUrl } = normalizeRuntimeArgs(input);
return getServerRuntime({ port, serverUrl });
}
async function runQuickTest(input = DEFAULT_PORT) {
const { port, serverUrl, cwd } = normalizeRuntimeArgs(input);
const runtime = await getServerRuntime({ port, serverUrl });
const results = [
{
test: 'server_status',
success: runtime.running,
details: runtime,
},
];
if (!runtime.running) {
return {
success: false,
summary: '0/1 tests passed',
results,
next_steps: [runtime.start_command || `Check the server at ${runtime.url} and rerun spaps verify.`],
};
}
results.push({
test: 'local_mode_contract',
success: runtime.local_mode.known,
details: runtime.local_mode,
fix: runtime.local_mode.known ? null : `curl -s ${runtime.url}/health/local-mode`,
});
if (!runtime.local_mode.active) {
const apiKey = resolveAuthApiKey({ cwd });
const hasKey = Boolean(apiKey.apiKey);
results.push({
test: 'api_key_wiring',
success: hasKey,
message: hasKey
? `API key is configured via ${apiKey.source}`
: 'SPAPS_API_KEY is required when local mode is disabled',
fix: hasKey ? null : buildProvisioningCommand('node', port),
});
}
const allSuccess = results.every((result) => result.success);
return {
success: allSuccess,
summary: `${results.filter((result) => result.success).length}/${results.length} tests passed`,
results,
next_steps: allSuccess
? [`See docs at ${runtime.docs}`]
: ['Fix the failing checks above and re-run: spaps verify --json'],
};
}
module.exports = {
getQuickStartInstructions,
getServerStatus,
runQuickTest,
};