spaps 0.7.3 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI_TOOLS.json +10 -11
- package/README.md +216 -36
- package/assets/local-runtime/Dockerfile +28 -0
- package/assets/local-runtime/alembic/env.py +101 -0
- package/assets/local-runtime/alembic/path_bootstrap.py +71 -0
- package/assets/local-runtime/alembic/versions/000000000001_baseline_consolidated_schema.py +1076 -0
- package/assets/local-runtime/alembic/versions/000000000002_fix_column_types_to_match_prod.py +83 -0
- package/assets/local-runtime/alembic/versions/000000000003_fix_email_template_key_uniqueness.py +49 -0
- package/assets/local-runtime/alembic/versions/000000000004_add_hold_duration_minutes_to_dayrate_config.py +30 -0
- package/assets/local-runtime/alembic/versions/000000000005_resource_scoped_entitlements.py +77 -0
- package/assets/local-runtime/alembic/versions/000000000006_cfo_rbac_add_is_admin.py +37 -0
- package/assets/local-runtime/alembic/versions/000000000007_agent_approvals.py +158 -0
- package/assets/local-runtime/alembic/versions/000000000008_add_company_id_to_cfo_connections.py +35 -0
- package/assets/local-runtime/alembic/versions/000000000009_tx_signing.py +62 -0
- package/assets/local-runtime/alembic/versions/000000000010_affiliate_referrals.py +235 -0
- package/assets/local-runtime/alembic/versions/000000000011_checkin_call_booking.py +137 -0
- package/assets/local-runtime/alembic/versions/000000000012_subscription_application_scoping.py +55 -0
- package/assets/local-runtime/alembic/versions/000000000013_refresh_token_anomaly_context.py +61 -0
- package/assets/local-runtime/alembic/versions/000000000014_buildooor_dayrate_hire_schedule.py +39 -0
- package/assets/local-runtime/alembic/versions/000000000015_support_telemetry_platform.py +112 -0
- package/assets/local-runtime/alembic/versions/000000000016_issue_reporting_platform.py +54 -0
- package/assets/local-runtime/alembic/versions/000000000017_issue_reporting_platform_import_tracking.py +44 -0
- package/assets/local-runtime/alembic/versions/000000000018_authorization_policy_engine.py +76 -0
- package/assets/local-runtime/alembic.ini +47 -0
- package/assets/local-runtime/docker-compose.yml +61 -0
- package/assets/local-runtime/manifest.json +8 -0
- package/assets/local-runtime/scripts/container-entrypoint.sh +13 -0
- package/assets/local-runtime/scripts/fetch-prod-db.sh +112 -0
- package/assets/local-runtime/scripts/run-migrations.sh +96 -0
- package/package.json +2 -1
- package/src/ai-helper.js +176 -234
- package/src/ai-tool-spec.js +52 -20
- package/src/auth/api-key.js +119 -0
- package/src/auth/client-id.js +136 -0
- package/src/auth/client.js +169 -0
- package/src/auth/credentials.js +110 -0
- package/src/auth/device-flow.js +159 -0
- package/src/auth/env.js +57 -0
- package/src/auth/handlers.js +462 -0
- package/src/auth/http.js +74 -0
- package/src/cli-dispatcher.js +134 -21
- package/src/docs-system.js +7 -7
- package/src/fixture-kernel.js +1143 -0
- package/src/handlers.js +202 -11
- package/src/help-system.js +2 -0
- package/src/local-runtime.js +258 -0
- package/src/local-server.js +597 -199
- package/src/project-scaffolder.js +185 -45
|
@@ -2,18 +2,27 @@ const fs = require('node:fs');
|
|
|
2
2
|
const path = require('node:path');
|
|
3
3
|
|
|
4
4
|
const { DEFAULT_PORT } = require('./config');
|
|
5
|
+
const { provisionStarterApplication } = require('./local-runtime');
|
|
5
6
|
|
|
6
7
|
const SUPPORTED_TEMPLATES = {
|
|
7
8
|
nextjs: {
|
|
8
9
|
label: 'Next.js starter',
|
|
9
10
|
blueprintKey: 'browser_auth',
|
|
10
11
|
allowedOrigins: ['http://localhost:3000'],
|
|
12
|
+
publicApiUrlEnv: 'NEXT_PUBLIC_SPAPS_API_URL',
|
|
13
|
+
publicApiKeyEnv: 'NEXT_PUBLIC_SPAPS_API_KEY',
|
|
11
14
|
files: ({ apiUrl }) => ({
|
|
12
|
-
'lib/spaps.ts': `import { SPAPSClient } from 'spaps-sdk';
|
|
15
|
+
'lib/spaps.ts': `import { createBrowserClient, SPAPSClient } from 'spaps-sdk';
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
const apiUrl = process.env.NEXT_PUBLIC_SPAPS_API_URL || '${apiUrl}';
|
|
18
|
+
const apiKey = process.env.NEXT_PUBLIC_SPAPS_API_KEY;
|
|
19
|
+
|
|
20
|
+
export const spaps = apiKey && apiKey.startsWith('spaps_pub_')
|
|
21
|
+
? createBrowserClient(apiKey, { apiUrl })
|
|
22
|
+
: new SPAPSClient({
|
|
23
|
+
apiUrl,
|
|
24
|
+
...(apiKey ? { apiKey } : {}),
|
|
25
|
+
});
|
|
17
26
|
`,
|
|
18
27
|
'app/providers.tsx': `'use client';
|
|
19
28
|
|
|
@@ -31,12 +40,20 @@ export function Providers({ children }: PropsWithChildren) {
|
|
|
31
40
|
label: 'React + Vite starter',
|
|
32
41
|
blueprintKey: 'browser_auth',
|
|
33
42
|
allowedOrigins: ['http://localhost:5173'],
|
|
43
|
+
publicApiUrlEnv: 'VITE_SPAPS_API_URL',
|
|
44
|
+
publicApiKeyEnv: 'VITE_SPAPS_API_KEY',
|
|
34
45
|
files: ({ apiUrl }) => ({
|
|
35
|
-
'src/lib/spaps.ts': `import { SPAPSClient } from 'spaps-sdk';
|
|
46
|
+
'src/lib/spaps.ts': `import { createBrowserClient, SPAPSClient } from 'spaps-sdk';
|
|
47
|
+
|
|
48
|
+
const apiUrl = import.meta.env.VITE_SPAPS_API_URL || '${apiUrl}';
|
|
49
|
+
const apiKey = import.meta.env.VITE_SPAPS_API_KEY;
|
|
36
50
|
|
|
37
|
-
export const spaps =
|
|
38
|
-
|
|
39
|
-
|
|
51
|
+
export const spaps = apiKey && apiKey.startsWith('spaps_pub_')
|
|
52
|
+
? createBrowserClient(apiKey, { apiUrl })
|
|
53
|
+
: new SPAPSClient({
|
|
54
|
+
apiUrl,
|
|
55
|
+
...(apiKey ? { apiKey } : {}),
|
|
56
|
+
});
|
|
40
57
|
`,
|
|
41
58
|
}),
|
|
42
59
|
},
|
|
@@ -44,13 +61,17 @@ export const spaps = new SPAPSClient({
|
|
|
44
61
|
label: 'Node.js starter',
|
|
45
62
|
blueprintKey: 'default',
|
|
46
63
|
allowedOrigins: [],
|
|
64
|
+
publicApiUrlEnv: 'SPAPS_API_URL',
|
|
65
|
+
publicApiKeyEnv: 'SPAPS_API_KEY',
|
|
47
66
|
files: ({ apiUrl }) => ({
|
|
48
|
-
'src/spaps.js': `const { SPAPSClient } = require('spaps-sdk');
|
|
67
|
+
'src/spaps.js': `const { createServerClient, SPAPSClient } = require('spaps-sdk');
|
|
68
|
+
|
|
69
|
+
const apiUrl = process.env.SPAPS_API_URL || '${apiUrl}';
|
|
70
|
+
const apiKey = process.env.SPAPS_API_KEY;
|
|
49
71
|
|
|
50
|
-
const spaps =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
});
|
|
72
|
+
const spaps = apiKey
|
|
73
|
+
? createServerClient(apiKey, { apiUrl })
|
|
74
|
+
: new SPAPSClient({ apiUrl });
|
|
54
75
|
|
|
55
76
|
module.exports = { spaps };
|
|
56
77
|
`,
|
|
@@ -60,12 +81,20 @@ module.exports = { spaps };
|
|
|
60
81
|
label: 'Vanilla JavaScript starter',
|
|
61
82
|
blueprintKey: 'browser_auth',
|
|
62
83
|
allowedOrigins: ['http://localhost:8080'],
|
|
84
|
+
publicApiUrlEnv: 'SPAPS_API_URL',
|
|
85
|
+
publicApiKeyEnv: 'SPAPS_API_KEY',
|
|
63
86
|
files: ({ apiUrl }) => ({
|
|
64
|
-
'src/spaps.js': `import { SPAPSClient } from 'spaps-sdk';
|
|
87
|
+
'src/spaps.js': `import { createBrowserClient, SPAPSClient } from 'spaps-sdk';
|
|
88
|
+
|
|
89
|
+
const apiUrl = window.SPAPS_API_URL || '${apiUrl}';
|
|
90
|
+
const apiKey = window.SPAPS_API_KEY;
|
|
65
91
|
|
|
66
|
-
export const spaps =
|
|
67
|
-
|
|
68
|
-
|
|
92
|
+
export const spaps = apiKey && apiKey.startsWith('spaps_pub_')
|
|
93
|
+
? createBrowserClient(apiKey, { apiUrl })
|
|
94
|
+
: new SPAPSClient({
|
|
95
|
+
apiUrl,
|
|
96
|
+
...(apiKey ? { apiKey } : {}),
|
|
97
|
+
});
|
|
69
98
|
`,
|
|
70
99
|
}),
|
|
71
100
|
},
|
|
@@ -115,6 +144,12 @@ function ensureWritableTarget(targetDir, force) {
|
|
|
115
144
|
}
|
|
116
145
|
}
|
|
117
146
|
|
|
147
|
+
function prepareWritableTarget(targetDir, force) {
|
|
148
|
+
ensureWritableTarget(targetDir, force);
|
|
149
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
150
|
+
fs.accessSync(targetDir, fs.constants.W_OK);
|
|
151
|
+
}
|
|
152
|
+
|
|
118
153
|
function writeManagedFile(targetDir, relativePath, content, bookkeeping) {
|
|
119
154
|
const fullPath = path.join(targetDir, relativePath);
|
|
120
155
|
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
@@ -146,7 +181,7 @@ function buildPackageJson(name, template) {
|
|
|
146
181
|
return `${JSON.stringify(pkg, null, 2)}\n`;
|
|
147
182
|
}
|
|
148
183
|
|
|
149
|
-
function buildContract({ name, slug, template, version, apiUrl, docsUrl }) {
|
|
184
|
+
function buildContract({ name, slug, template, version, apiUrl, docsUrl, provisioning }) {
|
|
150
185
|
const templateDef = SUPPORTED_TEMPLATES[template];
|
|
151
186
|
const contract = {
|
|
152
187
|
name,
|
|
@@ -157,12 +192,20 @@ function buildContract({ name, slug, template, version, apiUrl, docsUrl }) {
|
|
|
157
192
|
local: {
|
|
158
193
|
api_url: apiUrl,
|
|
159
194
|
docs_url: docsUrl,
|
|
195
|
+
local_mode_active: provisioning.runtime?.local_mode?.active ?? null,
|
|
160
196
|
},
|
|
161
197
|
application: {
|
|
198
|
+
id: provisioning.application?.id || null,
|
|
162
199
|
slug,
|
|
163
200
|
blueprint_key: templateDef.blueprintKey,
|
|
164
201
|
allowed_origins: templateDef.allowedOrigins,
|
|
165
|
-
provisioning_status:
|
|
202
|
+
provisioning_status: provisioning.status,
|
|
203
|
+
provisioned_via:
|
|
204
|
+
provisioning.status === 'provisioned'
|
|
205
|
+
? 'self_service'
|
|
206
|
+
: provisioning.status === 'local_mode'
|
|
207
|
+
? 'local_mode'
|
|
208
|
+
: null,
|
|
166
209
|
},
|
|
167
210
|
},
|
|
168
211
|
};
|
|
@@ -170,25 +213,73 @@ function buildContract({ name, slug, template, version, apiUrl, docsUrl }) {
|
|
|
170
213
|
return `${JSON.stringify(contract, null, 2)}\n`;
|
|
171
214
|
}
|
|
172
215
|
|
|
173
|
-
function buildEnvFile(template, apiUrl) {
|
|
216
|
+
function buildEnvFile(template, apiUrl, provisioning) {
|
|
217
|
+
const templateDef = SUPPORTED_TEMPLATES[template];
|
|
174
218
|
const lines = [
|
|
175
219
|
'# SPAPS local development',
|
|
176
220
|
`SPAPS_API_URL=${apiUrl}`,
|
|
177
|
-
'# SPAPS_API_KEY=',
|
|
178
221
|
];
|
|
179
222
|
|
|
180
|
-
if (
|
|
181
|
-
lines.push(
|
|
223
|
+
if (templateDef.publicApiUrlEnv !== 'SPAPS_API_URL') {
|
|
224
|
+
lines.push(`${templateDef.publicApiUrlEnv}=${apiUrl}`);
|
|
182
225
|
}
|
|
183
226
|
|
|
184
|
-
if (
|
|
185
|
-
|
|
227
|
+
if (provisioning.status === 'provisioned') {
|
|
228
|
+
const keyValue = template === 'node' ? provisioning.keys?.secret : provisioning.keys?.publishable;
|
|
229
|
+
if (keyValue) {
|
|
230
|
+
lines.push(`${templateDef.publicApiKeyEnv}=${keyValue}`);
|
|
231
|
+
} else {
|
|
232
|
+
lines.push(`# ${templateDef.publicApiKeyEnv}=`);
|
|
233
|
+
}
|
|
234
|
+
} else {
|
|
235
|
+
lines.push(`# ${templateDef.publicApiKeyEnv}=`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (template !== 'node' && templateDef.publicApiKeyEnv !== 'SPAPS_API_KEY') {
|
|
239
|
+
lines.push('# SPAPS_API_KEY=');
|
|
186
240
|
}
|
|
187
241
|
|
|
188
242
|
return `${lines.join('\n')}\n`;
|
|
189
243
|
}
|
|
190
244
|
|
|
191
|
-
function
|
|
245
|
+
function buildProvisioningNote({ provisioning, targetDir, template }) {
|
|
246
|
+
if (provisioning.status === 'provisioned') {
|
|
247
|
+
return [
|
|
248
|
+
'## Provisioning Status',
|
|
249
|
+
'',
|
|
250
|
+
`This starter was provisioned against \`${provisioning.runtime.url}\`. The generated \`.env.local\` includes a working ${template === 'node' ? 'server' : 'browser'} key for this template.`,
|
|
251
|
+
'',
|
|
252
|
+
].join('\n');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (provisioning.status === 'local_mode') {
|
|
256
|
+
return [
|
|
257
|
+
'## Provisioning Status',
|
|
258
|
+
'',
|
|
259
|
+
`The server at \`${provisioning.runtime.url}\` is currently in local mode, so authenticated flows can run without provisioning while that mode stays enabled.`,
|
|
260
|
+
'',
|
|
261
|
+
'Use `npx spaps quickstart --json` if you need the current local-mode hints and test personas.',
|
|
262
|
+
'',
|
|
263
|
+
].join('\n');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const rerunCommand = `SELF_SERVICE_PASSWORD=your-password npx spaps create ${path.basename(targetDir)} --template ${template} --dir ${targetDir} --force`;
|
|
267
|
+
|
|
268
|
+
return [
|
|
269
|
+
'## Provisioning Status',
|
|
270
|
+
'',
|
|
271
|
+
'This run only scaffolded files. Authenticated flows will not work until you provision a real SPAPS application or enable server local mode.',
|
|
272
|
+
'',
|
|
273
|
+
'To provision automatically:',
|
|
274
|
+
'',
|
|
275
|
+
'```bash',
|
|
276
|
+
rerunCommand,
|
|
277
|
+
'```',
|
|
278
|
+
'',
|
|
279
|
+
].join('\n');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function buildReadme({ name, template, apiUrl, targetDir, provisioning }) {
|
|
192
283
|
const templateDef = SUPPORTED_TEMPLATES[template];
|
|
193
284
|
|
|
194
285
|
return `# ${name}
|
|
@@ -203,22 +294,48 @@ It gives you three things immediately:
|
|
|
203
294
|
|
|
204
295
|
This is not a full framework generator. It does not run \`create-next-app\`, Vite, or Express setup for you.
|
|
205
296
|
|
|
206
|
-
## Next Steps
|
|
297
|
+
${buildProvisioningNote({ provisioning, targetDir, template })}## Next Steps
|
|
207
298
|
|
|
208
|
-
1.
|
|
209
|
-
2.
|
|
210
|
-
3.
|
|
211
|
-
4. Point your app at \`${apiUrl}\`
|
|
299
|
+
1. Install dependencies in this project with \`npm install\`
|
|
300
|
+
2. Copy the generated starter files into your real ${templateDef.label.toLowerCase()} or keep extending this directory
|
|
301
|
+
3. Point your app at \`${apiUrl}\`
|
|
212
302
|
|
|
213
303
|
## Generated Files
|
|
214
304
|
|
|
215
|
-
- \`spaps.app.json\`: local
|
|
216
|
-
- \`.env.local\`: SPAPS API URL wiring
|
|
305
|
+
- \`spaps.app.json\`: local app contract and provisioning metadata
|
|
306
|
+
- \`.env.local\`: SPAPS API URL wiring and, when available, a working key for this template
|
|
217
307
|
- \`package.json\`: minimal dependency declaration for \`spaps-sdk\`
|
|
218
308
|
`;
|
|
219
309
|
}
|
|
220
310
|
|
|
221
|
-
function
|
|
311
|
+
function buildNextSteps({ targetDir, provisioning, port, name, template }) {
|
|
312
|
+
const steps = [
|
|
313
|
+
`cd ${targetDir}`,
|
|
314
|
+
'npm install',
|
|
315
|
+
];
|
|
316
|
+
|
|
317
|
+
if (provisioning.status === 'provisioned') {
|
|
318
|
+
steps.push('Review .env.local and start your app');
|
|
319
|
+
return steps;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (provisioning.status === 'local_mode') {
|
|
323
|
+
steps.push(`Use the starter against http://localhost:${port} while local mode stays enabled`);
|
|
324
|
+
return steps;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (provisioning.reason === 'server_unreachable') {
|
|
328
|
+
steps.push(`npx spaps local --port ${port}`);
|
|
329
|
+
} else {
|
|
330
|
+
steps.push(
|
|
331
|
+
`SELF_SERVICE_PASSWORD=your-password npx spaps create ${name} --template ${template} --dir ${targetDir} --force`
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return steps;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function createProjectStarter({
|
|
222
339
|
name,
|
|
223
340
|
template,
|
|
224
341
|
dir = null,
|
|
@@ -241,9 +358,15 @@ function createProjectStarter({
|
|
|
241
358
|
files_created: [],
|
|
242
359
|
files_overwritten: [],
|
|
243
360
|
};
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
361
|
+
const templateDef = SUPPORTED_TEMPLATES[template];
|
|
362
|
+
prepareWritableTarget(targetDir, force);
|
|
363
|
+
const provisioning = await provisionStarterApplication({
|
|
364
|
+
port,
|
|
365
|
+
name: normalizedName,
|
|
366
|
+
slug,
|
|
367
|
+
blueprintKey: templateDef.blueprintKey,
|
|
368
|
+
allowedOrigins: templateDef.allowedOrigins,
|
|
369
|
+
});
|
|
247
370
|
|
|
248
371
|
writeManagedFile(
|
|
249
372
|
targetDir,
|
|
@@ -255,11 +378,17 @@ function createProjectStarter({
|
|
|
255
378
|
version,
|
|
256
379
|
apiUrl,
|
|
257
380
|
docsUrl,
|
|
381
|
+
provisioning,
|
|
258
382
|
}),
|
|
259
383
|
bookkeeping
|
|
260
384
|
);
|
|
261
|
-
writeManagedFile(targetDir, '.env.local', buildEnvFile(template, apiUrl), bookkeeping);
|
|
262
|
-
writeManagedFile(
|
|
385
|
+
writeManagedFile(targetDir, '.env.local', buildEnvFile(template, apiUrl, provisioning), bookkeeping);
|
|
386
|
+
writeManagedFile(
|
|
387
|
+
targetDir,
|
|
388
|
+
'README.md',
|
|
389
|
+
buildReadme({ name: normalizedName, template, apiUrl, targetDir, provisioning }),
|
|
390
|
+
bookkeeping
|
|
391
|
+
);
|
|
263
392
|
writeManagedFile(targetDir, 'package.json', buildPackageJson(normalizedName, template), bookkeeping);
|
|
264
393
|
writeManagedFile(
|
|
265
394
|
targetDir,
|
|
@@ -268,7 +397,7 @@ function createProjectStarter({
|
|
|
268
397
|
bookkeeping
|
|
269
398
|
);
|
|
270
399
|
|
|
271
|
-
const templateFiles =
|
|
400
|
+
const templateFiles = templateDef.files({
|
|
272
401
|
name: normalizedName,
|
|
273
402
|
slug,
|
|
274
403
|
apiUrl,
|
|
@@ -287,11 +416,22 @@ function createProjectStarter({
|
|
|
287
416
|
contract_path: path.join(targetDir, 'spaps.app.json'),
|
|
288
417
|
files_created: bookkeeping.files_created,
|
|
289
418
|
files_overwritten: bookkeeping.files_overwritten,
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
419
|
+
provisioning: {
|
|
420
|
+
status: provisioning.status,
|
|
421
|
+
reason: provisioning.reason || null,
|
|
422
|
+
application_id: provisioning.application?.id || null,
|
|
423
|
+
application_slug: provisioning.application?.slug || slug,
|
|
424
|
+
local_mode_active: provisioning.runtime?.local_mode?.active ?? null,
|
|
425
|
+
server_url: provisioning.runtime?.url || apiUrl,
|
|
426
|
+
},
|
|
427
|
+
warnings: provisioning.warnings || [],
|
|
428
|
+
next_steps: buildNextSteps({
|
|
429
|
+
targetDir,
|
|
430
|
+
provisioning,
|
|
431
|
+
port,
|
|
432
|
+
name: normalizedName,
|
|
433
|
+
template,
|
|
434
|
+
}),
|
|
295
435
|
};
|
|
296
436
|
}
|
|
297
437
|
|