@topolo/cli 0.2.1 → 0.2.3
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/dist/index.js +500 -67
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2582,6 +2582,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2582
2582
|
const kind = payload.credentialType === "api_key" ? "api_key" : "access_token";
|
|
2583
2583
|
return {
|
|
2584
2584
|
kind,
|
|
2585
|
+
authorizationMode: normalizeCredentialAuthorizationMode(payload.authorizationMode, kind),
|
|
2585
2586
|
user: {
|
|
2586
2587
|
id: user.id,
|
|
2587
2588
|
email: user.email,
|
|
@@ -2844,6 +2845,11 @@ function objectToQuery(value) {
|
|
|
2844
2845
|
}
|
|
2845
2846
|
return Object.keys(query).length > 0 ? query : void 0;
|
|
2846
2847
|
}
|
|
2848
|
+
function normalizeCredentialAuthorizationMode(value, kind) {
|
|
2849
|
+
if (kind === "api_key") return "api_key";
|
|
2850
|
+
if (value === "context_rbac" || value === "oauth_scopes" || value === "session_rbac") return value;
|
|
2851
|
+
return "unknown";
|
|
2852
|
+
}
|
|
2847
2853
|
function describeError(body, fallback) {
|
|
2848
2854
|
if (body && typeof body === "object") {
|
|
2849
2855
|
const maybe = body;
|
|
@@ -3312,16 +3318,6 @@ function writeContextCredential(config, environment, ctx, makeActive = true) {
|
|
|
3312
3318
|
}
|
|
3313
3319
|
return writeProfileConfig(config, environment, next);
|
|
3314
3320
|
}
|
|
3315
|
-
function activateContext(config, environment, key) {
|
|
3316
|
-
const profile = profileForEnvironment(config, environment);
|
|
3317
|
-
const ctx = profile.contexts?.[key];
|
|
3318
|
-
if (!ctx) return config;
|
|
3319
|
-
return writeProfileConfig(config, environment, {
|
|
3320
|
-
activeContext: key,
|
|
3321
|
-
credential: ctx.credential,
|
|
3322
|
-
oauthClientId: ctx.oauthClientId ?? profile.oauthClientId
|
|
3323
|
-
});
|
|
3324
|
-
}
|
|
3325
3321
|
function clearProfileCredential(config, environment) {
|
|
3326
3322
|
const next = { ...config };
|
|
3327
3323
|
if (environment === "production") {
|
|
@@ -3456,7 +3452,7 @@ function resolveOAuthClientId(config, environment = selectedEnvironment()) {
|
|
|
3456
3452
|
const envSpecific = process.env[`${envVarPrefix(environment)}_CLI_CLIENT_ID`];
|
|
3457
3453
|
if (envSpecific) return envSpecific;
|
|
3458
3454
|
if (process.env.TOPOLO_CLI_CLIENT_ID) return process.env.TOPOLO_CLI_CLIENT_ID;
|
|
3459
|
-
return profileForEnvironment(config, environment).oauthClientId
|
|
3455
|
+
return DEFAULT_CLI_CLIENT_IDS[environment] ?? profileForEnvironment(config, environment).oauthClientId;
|
|
3460
3456
|
}
|
|
3461
3457
|
var NON_PROD_URL_MARKERS = {
|
|
3462
3458
|
staging: [".stg.topolo.us", "topolo-staging"],
|
|
@@ -3842,7 +3838,7 @@ function stderrDebugLogger(event) {
|
|
|
3842
3838
|
|
|
3843
3839
|
// src/commands/whoami.ts
|
|
3844
3840
|
function registerWhoamiCommand(program2) {
|
|
3845
|
-
program2.command("whoami").description("Show the user, organization, and
|
|
3841
|
+
program2.command("whoami").description("Show the current user, organization, and authorization mode").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (opts) => {
|
|
3846
3842
|
const mode = resolveOutputMode(opts.json);
|
|
3847
3843
|
try {
|
|
3848
3844
|
const environment = selectedEnvironment();
|
|
@@ -3867,7 +3863,10 @@ function formatPretty(info) {
|
|
|
3867
3863
|
`Organization: ${info.organization ? `${info.organization.name} (${info.organization.slug})` : "(none)"}`
|
|
3868
3864
|
);
|
|
3869
3865
|
lines.push(`Credential: ${info.kind}`);
|
|
3870
|
-
|
|
3866
|
+
const dynamicRbac = info.authorizationMode === "context_rbac";
|
|
3867
|
+
const authorization = dynamicRbac ? "Dynamic RBAC (checked per application action)" : info.authorizationMode === "oauth_scopes" ? "Delegated OAuth scopes" : info.authorizationMode === "api_key" ? "API key scopes" : info.authorizationMode === "session_rbac" ? "Session RBAC" : "Credential scopes";
|
|
3868
|
+
lines.push(`Authorization: ${authorization}`);
|
|
3869
|
+
lines.push(`${dynamicRbac ? "Identity scopes" : "Scopes"}: ${info.user.permissions.length === 0 ? "(none)" : ""}`);
|
|
3871
3870
|
for (const p of info.user.permissions) lines.push(` - ${p}`);
|
|
3872
3871
|
return lines.join("\n");
|
|
3873
3872
|
}
|
|
@@ -5179,7 +5178,6 @@ function isFile(filePath) {
|
|
|
5179
5178
|
}
|
|
5180
5179
|
|
|
5181
5180
|
// src/commands/app-scaffold.ts
|
|
5182
|
-
import { randomBytes } from "crypto";
|
|
5183
5181
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
5184
5182
|
import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
5185
5183
|
|
|
@@ -5323,6 +5321,12 @@ function firstPartyAppPath(appDir, _audience) {
|
|
|
5323
5321
|
return `apps/${appDir}`;
|
|
5324
5322
|
}
|
|
5325
5323
|
|
|
5324
|
+
// src/commands/app-scaffold-cloudflare-constants.ts
|
|
5325
|
+
var CLOUDFLARE_ACCOUNT_ID = "49ef1ba682ad8cfd720c86699ae17521";
|
|
5326
|
+
var STAGING_CLOUDFLARE_ACCOUNT_ID = "4f4e1c69a3830946f9fea7b1eb7531ac";
|
|
5327
|
+
var DEVELOPMENT_CLOUDFLARE_ACCOUNT_ID = "0ebdeec262a245481a721fc4f9d6d798";
|
|
5328
|
+
var CLOUDFLARE_COMPATIBILITY_DATE = "2026-05-05";
|
|
5329
|
+
|
|
5326
5330
|
// src/commands/app-scaffold-package-template.ts
|
|
5327
5331
|
function packageJson(application, target, options) {
|
|
5328
5332
|
const dependencies = {};
|
|
@@ -5361,17 +5365,44 @@ function packageJson(application, target, options) {
|
|
|
5361
5365
|
devDependencies["@cloudflare/workers-types"] = "^4.20260413.1";
|
|
5362
5366
|
if (target === "platform") {
|
|
5363
5367
|
dependencies["@topolo-io/worker-runtime"] = topoloPackageVersion("@topolo-io/worker-runtime", target);
|
|
5368
|
+
dependencies["@topolo-io/auth-middleware"] = topoloPackageVersion("@topolo-io/auth-middleware", target);
|
|
5369
|
+
dependencies["@topolo-io/error-middleware"] = topoloPackageVersion("@topolo-io/error-middleware", target);
|
|
5370
|
+
dependencies["@topolo-io/security-headers"] = topoloPackageVersion("@topolo-io/security-headers", target);
|
|
5371
|
+
dependencies["@topolo-io/resource-context"] = topoloPackageVersion("@topolo-io/resource-context", target);
|
|
5372
|
+
dependencies["@topolo-io/data"] = topoloPackageVersion("@topolo-io/data", target);
|
|
5364
5373
|
dependencies["@topolo/sdk"] = topoloPackageVersion("@topolo/sdk", target);
|
|
5365
5374
|
dependencies.hono = "^4";
|
|
5375
|
+
dependencies.zod = "^4.4.3";
|
|
5366
5376
|
scripts.deploy = `node -e 'console.error("\\nX Bare deploy is disabled. Use deploy:staging or deploy:production.\\n"); process.exit(1)'`;
|
|
5367
|
-
scripts["deploy:staging"] =
|
|
5368
|
-
scripts["deploy:production"] =
|
|
5377
|
+
scripts["deploy:staging"] = `DEPLOY_ENVIRONMENT=staging CLOUDFLARE_ACCOUNT_ID=${STAGING_CLOUDFLARE_ACCOUNT_ID} python3 .github/scripts/standard_deploy.py local-deploy`;
|
|
5378
|
+
scripts["deploy:production"] = `DEPLOY_ENVIRONMENT=production CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID} python3 .github/scripts/standard_deploy.py local-deploy`;
|
|
5369
5379
|
} else {
|
|
5370
5380
|
dependencies["@topolo/sdk"] = topoloPackageVersion("@topolo/sdk", target);
|
|
5371
5381
|
scripts.deploy = "npm run build && wrangler deploy";
|
|
5372
5382
|
}
|
|
5373
5383
|
devDependencies.wrangler = "^4.91.0";
|
|
5374
5384
|
}
|
|
5385
|
+
if (target === "platform") {
|
|
5386
|
+
const testDirs = [];
|
|
5387
|
+
if (options.browser) testDirs.push("apps/web");
|
|
5388
|
+
if (options.api) testDirs.push("apps/api");
|
|
5389
|
+
if (testDirs.length > 0) {
|
|
5390
|
+
const chain = (withCoverage) => testDirs.map((dir, index) => {
|
|
5391
|
+
const enter = index === 0 ? `cd ${dir}` : `cd ../${dir.split("/").pop()}`;
|
|
5392
|
+
return `${enter} && vitest run --config vitest.config.ts --passWithNoTests${withCoverage ? " --coverage" : ""}`;
|
|
5393
|
+
}).join(" && ");
|
|
5394
|
+
scripts.test = chain(false);
|
|
5395
|
+
scripts.coverage = chain(true);
|
|
5396
|
+
scripts.typecheck = testDirs.map((dir) => `tsc --noEmit -p ${dir}/tsconfig.json`).join(" && ");
|
|
5397
|
+
devDependencies.vitest = "^3.2.4";
|
|
5398
|
+
devDependencies["@vitest/coverage-v8"] = "^3.2.4";
|
|
5399
|
+
if (options.browser) {
|
|
5400
|
+
devDependencies.jsdom = "^29.1.1";
|
|
5401
|
+
devDependencies["@testing-library/react"] = "^16.3.2";
|
|
5402
|
+
devDependencies["@testing-library/user-event"] = "^14.6.1";
|
|
5403
|
+
}
|
|
5404
|
+
}
|
|
5405
|
+
}
|
|
5375
5406
|
if (target === "platform" && hasScaffoldResources(options.resources)) {
|
|
5376
5407
|
scripts["check:deploy-config"] = "wrangler deploy --config apps/api/wrangler.toml --env staging --dry-run --outdir .wrangler/api-build && wrangler deploy --config apps/web/wrangler.toml --env staging --dry-run --outdir .wrangler/web-build";
|
|
5377
5408
|
}
|
|
@@ -5400,7 +5431,11 @@ function topoloPackageVersion(packageName, target) {
|
|
|
5400
5431
|
"@topolo-io/app-launcher": "^0.1.15",
|
|
5401
5432
|
"@topolo-io/app-shell": "^0.5.54",
|
|
5402
5433
|
"@topolo-io/auth-client": "^1.2.1",
|
|
5403
|
-
"@topolo-io/auth-middleware": "^0.
|
|
5434
|
+
"@topolo-io/auth-middleware": "^0.4.0",
|
|
5435
|
+
"@topolo-io/error-middleware": "^0.6.0",
|
|
5436
|
+
"@topolo-io/security-headers": "^0.2.2",
|
|
5437
|
+
"@topolo-io/resource-context": "^0.3.0",
|
|
5438
|
+
"@topolo-io/data": "^0.1.2",
|
|
5404
5439
|
"@topolo-io/worker-runtime": "^0.1.11",
|
|
5405
5440
|
"@topolo-io/design-tokens": "^0.1.8",
|
|
5406
5441
|
"@topolo-io/onboarding": "^0.9.5",
|
|
@@ -5436,7 +5471,12 @@ function resourceConfig(application, profile) {
|
|
|
5436
5471
|
workflowProductionName: `topolo-${slug}-import`,
|
|
5437
5472
|
workflowStagingName: `topolo-${slug}-import-staging`,
|
|
5438
5473
|
containerProductionName: `topolo-${slug}-processor`,
|
|
5439
|
-
containerStagingName: `topolo-${slug}-processor-staging
|
|
5474
|
+
containerStagingName: `topolo-${slug}-processor-staging`,
|
|
5475
|
+
d1DevelopmentName: `topolo-${slug}-core-development`,
|
|
5476
|
+
r2DevelopmentName: `topolo-${slug}-assets-development`,
|
|
5477
|
+
queueDevelopmentName: `topolo-${slug}-processing-development`,
|
|
5478
|
+
workflowDevelopmentName: `topolo-${slug}-import-development`,
|
|
5479
|
+
containerDevelopmentName: `topolo-${slug}-processor-development`
|
|
5440
5480
|
};
|
|
5441
5481
|
}
|
|
5442
5482
|
return {
|
|
@@ -5454,7 +5494,12 @@ function resourceConfig(application, profile) {
|
|
|
5454
5494
|
workflowProductionName: `topolo-${slug}-workflow`,
|
|
5455
5495
|
workflowStagingName: `topolo-${slug}-workflow-staging`,
|
|
5456
5496
|
containerProductionName: `topolo-${slug}-processor`,
|
|
5457
|
-
containerStagingName: `topolo-${slug}-processor-staging
|
|
5497
|
+
containerStagingName: `topolo-${slug}-processor-staging`,
|
|
5498
|
+
d1DevelopmentName: `topolo-${slug}-core-development`,
|
|
5499
|
+
r2DevelopmentName: `topolo-${slug}-assets-development`,
|
|
5500
|
+
queueDevelopmentName: `topolo-${slug}-queue-development`,
|
|
5501
|
+
workflowDevelopmentName: `topolo-${slug}-workflow-development`,
|
|
5502
|
+
containerDevelopmentName: `topolo-${slug}-processor-development`
|
|
5458
5503
|
};
|
|
5459
5504
|
}
|
|
5460
5505
|
|
|
@@ -5627,6 +5672,64 @@ function tsconfigJson(api, target) {
|
|
|
5627
5672
|
})}
|
|
5628
5673
|
`;
|
|
5629
5674
|
}
|
|
5675
|
+
var TSCONFIG_TEST_EXCLUDE = [
|
|
5676
|
+
"src/**/*.test.ts",
|
|
5677
|
+
"src/**/*.test.tsx",
|
|
5678
|
+
"src/**/*.test.mts",
|
|
5679
|
+
"src/**/*.spec.ts",
|
|
5680
|
+
"src/**/*.spec.tsx",
|
|
5681
|
+
"test"
|
|
5682
|
+
];
|
|
5683
|
+
function webTsconfigJson() {
|
|
5684
|
+
return `${json({
|
|
5685
|
+
compilerOptions: {
|
|
5686
|
+
target: "ES2022",
|
|
5687
|
+
useDefineForClassFields: true,
|
|
5688
|
+
lib: ["ES2022", "DOM", "DOM.Iterable"],
|
|
5689
|
+
allowJs: false,
|
|
5690
|
+
skipLibCheck: true,
|
|
5691
|
+
esModuleInterop: true,
|
|
5692
|
+
allowSyntheticDefaultImports: true,
|
|
5693
|
+
strict: true,
|
|
5694
|
+
forceConsistentCasingInFileNames: true,
|
|
5695
|
+
module: "ESNext",
|
|
5696
|
+
moduleResolution: "Bundler",
|
|
5697
|
+
resolveJsonModule: true,
|
|
5698
|
+
isolatedModules: true,
|
|
5699
|
+
noEmit: true,
|
|
5700
|
+
jsx: "react-jsx",
|
|
5701
|
+
types: ["vite/client"]
|
|
5702
|
+
},
|
|
5703
|
+
include: ["src"],
|
|
5704
|
+
references: [],
|
|
5705
|
+
exclude: TSCONFIG_TEST_EXCLUDE
|
|
5706
|
+
})}
|
|
5707
|
+
`;
|
|
5708
|
+
}
|
|
5709
|
+
function apiTsconfigJson() {
|
|
5710
|
+
return `${json({
|
|
5711
|
+
compilerOptions: {
|
|
5712
|
+
target: "ES2022",
|
|
5713
|
+
lib: ["ES2022", "DOM", "DOM.Iterable", "WebWorker"],
|
|
5714
|
+
allowJs: false,
|
|
5715
|
+
skipLibCheck: true,
|
|
5716
|
+
esModuleInterop: true,
|
|
5717
|
+
allowSyntheticDefaultImports: true,
|
|
5718
|
+
strict: true,
|
|
5719
|
+
forceConsistentCasingInFileNames: true,
|
|
5720
|
+
module: "ESNext",
|
|
5721
|
+
moduleResolution: "Bundler",
|
|
5722
|
+
resolveJsonModule: true,
|
|
5723
|
+
isolatedModules: true,
|
|
5724
|
+
noEmit: true,
|
|
5725
|
+
types: ["@cloudflare/workers-types", "node"]
|
|
5726
|
+
},
|
|
5727
|
+
include: ["src"],
|
|
5728
|
+
references: [],
|
|
5729
|
+
exclude: TSCONFIG_TEST_EXCLUDE
|
|
5730
|
+
})}
|
|
5731
|
+
`;
|
|
5732
|
+
}
|
|
5630
5733
|
function gitignore() {
|
|
5631
5734
|
return [
|
|
5632
5735
|
"node_modules/",
|
|
@@ -6057,12 +6160,15 @@ function platformAppTsx(application, options) {
|
|
|
6057
6160
|
" }",
|
|
6058
6161
|
"",
|
|
6059
6162
|
" if (!auth) {",
|
|
6163
|
+
" // Pre-AppRoot: appId is still resolving, so auth can't be built yet.",
|
|
6164
|
+
" // Render the SAME scaffold layout AppRoot uses below so this phase and",
|
|
6165
|
+
" // the auth-gate fallback read as one continuous loading state (the",
|
|
6166
|
+
" // scaffold resolves theme + rail width on its own to match the shell).",
|
|
6060
6167
|
" return (",
|
|
6061
6168
|
" <TopoloAppLoadingScaffold",
|
|
6062
6169
|
" appId={appId}",
|
|
6063
6170
|
" appName={APP_NAME}",
|
|
6064
6171
|
" layout={{ shell: 'topbar', content: 'workspace' }}",
|
|
6065
|
-
" subtitle={`Loading ${APP_NAME}...`}",
|
|
6066
6172
|
" />",
|
|
6067
6173
|
" );",
|
|
6068
6174
|
" }",
|
|
@@ -6103,8 +6209,6 @@ function platformAppTsx(application, options) {
|
|
|
6103
6209
|
" appId={appId}",
|
|
6104
6210
|
" appName={APP_NAME}",
|
|
6105
6211
|
" layout={{ shell: loading?.shell || 'topbar', content: loading?.content || 'workspace' }}",
|
|
6106
|
-
" title={loading?.title}",
|
|
6107
|
-
" subtitle={`Loading ${APP_NAME}...`}",
|
|
6108
6212
|
" />",
|
|
6109
6213
|
" )}",
|
|
6110
6214
|
" shell={{",
|
|
@@ -6368,11 +6472,6 @@ function developerAppTsx(application) {
|
|
|
6368
6472
|
].join("\n");
|
|
6369
6473
|
}
|
|
6370
6474
|
|
|
6371
|
-
// src/commands/app-scaffold-cloudflare-constants.ts
|
|
6372
|
-
var CLOUDFLARE_ACCOUNT_ID = "49ef1ba682ad8cfd720c86699ae17521";
|
|
6373
|
-
var STAGING_CLOUDFLARE_ACCOUNT_ID = "4f4e1c69a3830946f9fea7b1eb7531ac";
|
|
6374
|
-
var CLOUDFLARE_COMPATIBILITY_DATE = "2026-05-05";
|
|
6375
|
-
|
|
6376
6475
|
// src/commands/app-scaffold-worker-template.ts
|
|
6377
6476
|
function webWorkerTs(application) {
|
|
6378
6477
|
const serviceName = toTitle(application.name);
|
|
@@ -6427,6 +6526,29 @@ function platformWebWranglerToml(application) {
|
|
|
6427
6526
|
'binding = "API"',
|
|
6428
6527
|
`service = "topolo-${slug}-api"`,
|
|
6429
6528
|
"",
|
|
6529
|
+
"[env.development]",
|
|
6530
|
+
`name = "topolo-${slug}-development"`,
|
|
6531
|
+
`account_id = "${DEVELOPMENT_CLOUDFLARE_ACCOUNT_ID}"`,
|
|
6532
|
+
"workers_dev = true",
|
|
6533
|
+
"",
|
|
6534
|
+
"[env.development.vars]",
|
|
6535
|
+
`APP_ID = "${application.appId ?? ""}"`,
|
|
6536
|
+
`VITE_TOPOLO_APP_ID = "${application.appId ?? ""}"`,
|
|
6537
|
+
"",
|
|
6538
|
+
"[[env.development.routes]]",
|
|
6539
|
+
`pattern = "${slug}.topolo.dev"`,
|
|
6540
|
+
"custom_domain = true",
|
|
6541
|
+
"",
|
|
6542
|
+
"[env.development.assets]",
|
|
6543
|
+
'directory = "./dist"',
|
|
6544
|
+
'binding = "ASSETS"',
|
|
6545
|
+
'not_found_handling = "single-page-application"',
|
|
6546
|
+
'run_worker_first = ["/api/*"]',
|
|
6547
|
+
"",
|
|
6548
|
+
"[[env.development.services]]",
|
|
6549
|
+
'binding = "API"',
|
|
6550
|
+
`service = "topolo-${slug}-api-development"`,
|
|
6551
|
+
"",
|
|
6430
6552
|
"[env.staging]",
|
|
6431
6553
|
`name = "topolo-${slug}-staging"`,
|
|
6432
6554
|
`account_id = "${STAGING_CLOUDFLARE_ACCOUNT_ID}"`,
|
|
@@ -6498,6 +6620,22 @@ function platformApiWranglerToml(application, resources, profile) {
|
|
|
6498
6620
|
""
|
|
6499
6621
|
];
|
|
6500
6622
|
appendResourceWranglerBlocks(lines, resources, resource, "production");
|
|
6623
|
+
lines.push(
|
|
6624
|
+
"[env.development]",
|
|
6625
|
+
`name = "topolo-${slug}-api-development"`,
|
|
6626
|
+
`account_id = "${DEVELOPMENT_CLOUDFLARE_ACCOUNT_ID}"`,
|
|
6627
|
+
"workers_dev = true",
|
|
6628
|
+
"",
|
|
6629
|
+
"[env.development.vars]",
|
|
6630
|
+
'AUTH_BASE_URL = "https://auth.topolo.dev"',
|
|
6631
|
+
'NEXUS_GATEWAY_URL = "https://nexus.topolo.dev"',
|
|
6632
|
+
`NEXUS_APP_ID = "${slug}"`,
|
|
6633
|
+
`APP_ID = "${application.appId ?? ""}"`,
|
|
6634
|
+
`VITE_TOPOLO_APP_ID = "${application.appId ?? ""}"`,
|
|
6635
|
+
'ENVIRONMENT = "development"',
|
|
6636
|
+
""
|
|
6637
|
+
);
|
|
6638
|
+
appendResourceWranglerBlocks(lines, resources, resource, "development");
|
|
6501
6639
|
lines.push(
|
|
6502
6640
|
"[env.staging]",
|
|
6503
6641
|
`name = "topolo-${slug}-api-staging"`,
|
|
@@ -6531,13 +6669,14 @@ function platformApiWranglerToml(application, resources, profile) {
|
|
|
6531
6669
|
return lines.join("\n");
|
|
6532
6670
|
}
|
|
6533
6671
|
function appendResourceWranglerBlocks(lines, resources, resource, environment) {
|
|
6534
|
-
const prefix = environment === "staging" ? "env.staging." : environment === "production-env" ? "env.production." : "";
|
|
6535
|
-
const envName = environment === "staging" ? "Staging" : "Production";
|
|
6672
|
+
const prefix = environment === "staging" ? "env.staging." : environment === "development" ? "env.development." : environment === "production-env" ? "env.production." : "";
|
|
6673
|
+
const envName = environment === "staging" ? "Staging" : environment === "development" ? "Development" : "Production";
|
|
6674
|
+
const pick = (dev, staging, prod) => environment === "development" ? dev : environment === "staging" ? staging : prod;
|
|
6536
6675
|
if (resources.d1) {
|
|
6537
6676
|
lines.push(
|
|
6538
6677
|
`[[${prefix}d1_databases]]`,
|
|
6539
6678
|
`binding = "${resource.d1Binding}"`,
|
|
6540
|
-
`database_name = "${
|
|
6679
|
+
`database_name = "${pick(resource.d1DevelopmentName, resource.d1StagingName, resource.d1ProductionName)}"`,
|
|
6541
6680
|
'database_id = "provision-after-scaffold"',
|
|
6542
6681
|
'migrations_dir = "migrations"',
|
|
6543
6682
|
""
|
|
@@ -6547,7 +6686,7 @@ function appendResourceWranglerBlocks(lines, resources, resource, environment) {
|
|
|
6547
6686
|
lines.push(
|
|
6548
6687
|
`[[${prefix}r2_buckets]]`,
|
|
6549
6688
|
`binding = "${resource.r2Binding}"`,
|
|
6550
|
-
`bucket_name = "${
|
|
6689
|
+
`bucket_name = "${pick(resource.r2DevelopmentName, resource.r2StagingName, resource.r2ProductionName)}"`,
|
|
6551
6690
|
""
|
|
6552
6691
|
);
|
|
6553
6692
|
}
|
|
@@ -6555,10 +6694,10 @@ function appendResourceWranglerBlocks(lines, resources, resource, environment) {
|
|
|
6555
6694
|
lines.push(
|
|
6556
6695
|
`[[${prefix}queues.producers]]`,
|
|
6557
6696
|
`binding = "${resource.queueBinding}"`,
|
|
6558
|
-
`queue = "${
|
|
6697
|
+
`queue = "${pick(resource.queueDevelopmentName, resource.queueStagingName, resource.queueProductionName)}"`,
|
|
6559
6698
|
"",
|
|
6560
6699
|
`[[${prefix}queues.consumers]]`,
|
|
6561
|
-
`queue = "${
|
|
6700
|
+
`queue = "${pick(resource.queueDevelopmentName, resource.queueStagingName, resource.queueProductionName)}"`,
|
|
6562
6701
|
""
|
|
6563
6702
|
);
|
|
6564
6703
|
}
|
|
@@ -6566,7 +6705,7 @@ function appendResourceWranglerBlocks(lines, resources, resource, environment) {
|
|
|
6566
6705
|
lines.push(
|
|
6567
6706
|
`[[${prefix}workflows]]`,
|
|
6568
6707
|
`binding = "${resource.workflowBinding}"`,
|
|
6569
|
-
`name = "${
|
|
6708
|
+
`name = "${pick(resource.workflowDevelopmentName, resource.workflowStagingName, resource.workflowProductionName)}"`,
|
|
6570
6709
|
`class_name = "${resource.workflowClassName}"`,
|
|
6571
6710
|
""
|
|
6572
6711
|
);
|
|
@@ -6764,7 +6903,9 @@ function platformWorkerTs(application, options) {
|
|
|
6764
6903
|
"import { Hono } from 'hono';",
|
|
6765
6904
|
"import { createTopoloWidgetResponse, type TopoloWidgetDraft } from '@topolo/sdk';",
|
|
6766
6905
|
"import { createTopoloWorkerRuntime, type TopoloRuntimeContext } from '@topolo-io/worker-runtime';",
|
|
6767
|
-
"import {
|
|
6906
|
+
"import { withSecurityHeaders } from '@topolo-io/security-headers';",
|
|
6907
|
+
"import { withErrorReporting } from '@topolo-io/error-middleware';",
|
|
6908
|
+
"import { SCAFFOLD_APP_NAME, SCAFFOLD_SERVICE_SLUG, resolveRuntimeAppId } from './app-identity';",
|
|
6768
6909
|
...options.resources.workflow ? ["import { WorkflowEntrypoint, type WorkflowEvent, type WorkflowStep } from 'cloudflare:workers';"] : [],
|
|
6769
6910
|
"",
|
|
6770
6911
|
"export interface Env {",
|
|
@@ -6789,12 +6930,10 @@ function platformWorkerTs(application, options) {
|
|
|
6789
6930
|
"const runtime = createTopoloWorkerRuntime<Env>({",
|
|
6790
6931
|
" appName: SCAFFOLD_APP_NAME,",
|
|
6791
6932
|
" serviceSlug: SCAFFOLD_SERVICE_SLUG,",
|
|
6792
|
-
"
|
|
6933
|
+
" // Auth verification base URL is environment-driven (staging/prod/dev),",
|
|
6934
|
+
" // so rt.verifyAuth() targets the correct Auth service per deploy.",
|
|
6793
6935
|
" authBaseUrl: (env) => env.AUTH_BASE_URL,",
|
|
6794
|
-
"
|
|
6795
|
-
" appId: SCAFFOLD_APP_ID,",
|
|
6796
|
-
" nexusGatewayUrl: (env) => env.NEXUS_GATEWAY_URL,",
|
|
6797
|
-
" },",
|
|
6936
|
+
" authGateway: { enabled: false },",
|
|
6798
6937
|
"});",
|
|
6799
6938
|
"",
|
|
6800
6939
|
"async function handleRequest(",
|
|
@@ -6854,11 +6993,20 @@ function platformWorkerTs(application, options) {
|
|
|
6854
6993
|
" return handleRequest(c.req.raw, c.env, rt);",
|
|
6855
6994
|
"});",
|
|
6856
6995
|
"",
|
|
6996
|
+
"// Platform security headers + structured error reporting wrap the whole app",
|
|
6997
|
+
"// (outermost), so headers are stamped even on error-middleware 5xx responses.",
|
|
6998
|
+
"const securedFetch = withSecurityHeaders(",
|
|
6999
|
+
" withErrorReporting(",
|
|
7000
|
+
" (request: Request, env: Env, ctx: ExecutionContext) => api.fetch(request, env, ctx),",
|
|
7001
|
+
" { appName: SCAFFOLD_APP_NAME, appId: SCAFFOLD_SERVICE_SLUG },",
|
|
7002
|
+
" ),",
|
|
7003
|
+
") as (request: Request, env: Env, ctx: ExecutionContext) => Response | Promise<Response>;",
|
|
7004
|
+
"",
|
|
6857
7005
|
"export default {",
|
|
6858
7006
|
" fetch: runtime.worker((request, env, ctx, rt) => {",
|
|
6859
7007
|
" const bindings = env as Env & { __rt?: TopoloRuntimeContext<Env> };",
|
|
6860
7008
|
" bindings.__rt = rt;",
|
|
6861
|
-
" return
|
|
7009
|
+
" return securedFetch(request, bindings, ctx as ExecutionContext);",
|
|
6862
7010
|
" }),",
|
|
6863
7011
|
"};",
|
|
6864
7012
|
"",
|
|
@@ -7207,13 +7355,17 @@ function topoloAppManifestJson(application, options) {
|
|
|
7207
7355
|
}
|
|
7208
7356
|
} : {}
|
|
7209
7357
|
};
|
|
7358
|
+
const actions = options.api ? baselineApiActions() : [];
|
|
7210
7359
|
return `${json({
|
|
7211
7360
|
$schema: "./../../topolo-platform/tooling/app-manifest-schema.json",
|
|
7212
7361
|
name: `topolo-${slug}`,
|
|
7213
7362
|
appId: application.appId,
|
|
7214
|
-
primaryTarget: "web",
|
|
7215
7363
|
compatibilityDate: CLOUDFLARE_COMPATIBILITY_DATE,
|
|
7216
7364
|
environments: {
|
|
7365
|
+
development: {
|
|
7366
|
+
accountId: DEVELOPMENT_CLOUDFLARE_ACCOUNT_ID,
|
|
7367
|
+
domain: "topolo.dev"
|
|
7368
|
+
},
|
|
7217
7369
|
staging: {
|
|
7218
7370
|
accountId: STAGING_CLOUDFLARE_ACCOUNT_ID,
|
|
7219
7371
|
domain: "stg.topolo.us"
|
|
@@ -7223,10 +7375,87 @@ function topoloAppManifestJson(application, options) {
|
|
|
7223
7375
|
domain: "topolo.app"
|
|
7224
7376
|
}
|
|
7225
7377
|
},
|
|
7226
|
-
targets
|
|
7378
|
+
targets,
|
|
7379
|
+
...actions.length ? { actions } : {}
|
|
7227
7380
|
})}
|
|
7228
7381
|
`;
|
|
7229
7382
|
}
|
|
7383
|
+
function topoloActionContractsMjs() {
|
|
7384
|
+
return [
|
|
7385
|
+
"// Canonical action contracts \u2014 the source of truth for the agent/SDK/MCP",
|
|
7386
|
+
"// surface. Each entry maps 1:1 to a worker route (method + path) and validates",
|
|
7387
|
+
"// its input with zod. Add product actions here and mirror them into",
|
|
7388
|
+
"// topolo.app.json actions[]. Mirrors the normalized fleet convention.",
|
|
7389
|
+
"",
|
|
7390
|
+
"import { z } from 'zod';",
|
|
7391
|
+
"",
|
|
7392
|
+
"const emptyInput = z.object({}).strict();",
|
|
7393
|
+
"",
|
|
7394
|
+
"export const topoloActionContracts = {",
|
|
7395
|
+
" 'widget.get': {",
|
|
7396
|
+
" name: 'widget.get',",
|
|
7397
|
+
" method: 'GET',",
|
|
7398
|
+
" path: '/api/widget',",
|
|
7399
|
+
" input: emptyInput,",
|
|
7400
|
+
" },",
|
|
7401
|
+
" 'workspace.bootstrap': {",
|
|
7402
|
+
" name: 'workspace.bootstrap',",
|
|
7403
|
+
" method: 'GET',",
|
|
7404
|
+
" path: '/api/bootstrap',",
|
|
7405
|
+
" input: emptyInput,",
|
|
7406
|
+
" },",
|
|
7407
|
+
"};",
|
|
7408
|
+
"",
|
|
7409
|
+
"export const topoloActionInputSchemas = Object.fromEntries(",
|
|
7410
|
+
" Object.entries(topoloActionContracts).map(([name, contract]) => [",
|
|
7411
|
+
" name,",
|
|
7412
|
+
" z.toJSONSchema(contract.input),",
|
|
7413
|
+
" ]),",
|
|
7414
|
+
");",
|
|
7415
|
+
""
|
|
7416
|
+
].join("\n");
|
|
7417
|
+
}
|
|
7418
|
+
function baselineApiActions() {
|
|
7419
|
+
const emptyInputSchema = {
|
|
7420
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
7421
|
+
type: "object",
|
|
7422
|
+
properties: {},
|
|
7423
|
+
additionalProperties: false
|
|
7424
|
+
};
|
|
7425
|
+
const openOutputSchema = { type: "object", additionalProperties: true };
|
|
7426
|
+
return [
|
|
7427
|
+
{
|
|
7428
|
+
name: "widget.get",
|
|
7429
|
+
title: "Get widget summary",
|
|
7430
|
+
description: "Get the TopoloOne widget summary for this service.",
|
|
7431
|
+
method: "GET",
|
|
7432
|
+
path: "/api/widget",
|
|
7433
|
+
permission: "workspace:read",
|
|
7434
|
+
readOnly: true,
|
|
7435
|
+
inputSchema: emptyInputSchema,
|
|
7436
|
+
outputSchema: openOutputSchema,
|
|
7437
|
+
target: "api",
|
|
7438
|
+
destructive: false,
|
|
7439
|
+
requiresConfirmation: false,
|
|
7440
|
+
agentAccess: "auto"
|
|
7441
|
+
},
|
|
7442
|
+
{
|
|
7443
|
+
name: "workspace.bootstrap",
|
|
7444
|
+
title: "Get workspace bootstrap",
|
|
7445
|
+
description: "Get the signed workspace bootstrap payload for this service.",
|
|
7446
|
+
method: "GET",
|
|
7447
|
+
path: "/api/bootstrap",
|
|
7448
|
+
permission: "workspace:read",
|
|
7449
|
+
readOnly: true,
|
|
7450
|
+
inputSchema: emptyInputSchema,
|
|
7451
|
+
outputSchema: openOutputSchema,
|
|
7452
|
+
target: "api",
|
|
7453
|
+
destructive: false,
|
|
7454
|
+
requiresConfirmation: false,
|
|
7455
|
+
agentAccess: "auto"
|
|
7456
|
+
}
|
|
7457
|
+
];
|
|
7458
|
+
}
|
|
7230
7459
|
function cloudcontrolJson(application, options) {
|
|
7231
7460
|
const slug = toSlug(application.id);
|
|
7232
7461
|
const appPath = firstPartyAppPath(application.appDir, options.audience);
|
|
@@ -7254,6 +7483,15 @@ function cloudcontrolJson(application, options) {
|
|
|
7254
7483
|
resource_name: resources.d1StagingName,
|
|
7255
7484
|
resource_id: "provision-after-scaffold",
|
|
7256
7485
|
migrations_dir: "migrations"
|
|
7486
|
+
},
|
|
7487
|
+
{
|
|
7488
|
+
resource_type: "d1",
|
|
7489
|
+
binding: resources.d1Binding,
|
|
7490
|
+
environment: "development",
|
|
7491
|
+
account_id: DEVELOPMENT_CLOUDFLARE_ACCOUNT_ID,
|
|
7492
|
+
resource_name: resources.d1DevelopmentName,
|
|
7493
|
+
resource_id: "provision-after-scaffold",
|
|
7494
|
+
migrations_dir: "migrations"
|
|
7257
7495
|
}
|
|
7258
7496
|
);
|
|
7259
7497
|
}
|
|
@@ -7871,8 +8109,73 @@ function envExample(application) {
|
|
|
7871
8109
|
}
|
|
7872
8110
|
|
|
7873
8111
|
// src/commands/app-scaffold.ts
|
|
7874
|
-
function
|
|
7875
|
-
return
|
|
8112
|
+
function standardScriptStub() {
|
|
8113
|
+
return `#!/usr/bin/env python3
|
|
8114
|
+
"""Canonical-fetching stub for .github/scripts/standard_*.py.
|
|
8115
|
+
|
|
8116
|
+
Replaces a vendored copy. At runtime, finds the canonical sibling in
|
|
8117
|
+
\`<repo-parent>/topolo-platform/tooling/<name>.py\` and execs it, passing argv
|
|
8118
|
+
through, so every consumer runs the current platform script on every
|
|
8119
|
+
invocation (no drifted per-app copies).
|
|
8120
|
+
|
|
8121
|
+
Override: set TOPOLO_PLATFORM_DIR to the absolute path of the topolo-platform
|
|
8122
|
+
repo when the canonical is in a non-default checkout layout (e.g. CI). If no
|
|
8123
|
+
canonical can be found, exits 2 with a clear message rather than silently
|
|
8124
|
+
falling back.
|
|
8125
|
+
"""
|
|
8126
|
+
from __future__ import annotations
|
|
8127
|
+
|
|
8128
|
+
import os
|
|
8129
|
+
import sys
|
|
8130
|
+
|
|
8131
|
+
STUB_NAME = os.path.basename(__file__) # standard_ci.py OR standard_deploy.py
|
|
8132
|
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
8133
|
+
|
|
8134
|
+
|
|
8135
|
+
def find_canonical() -> str | None:
|
|
8136
|
+
# 1. Explicit override (CI runners with non-standard layouts).
|
|
8137
|
+
override = os.environ.get('TOPOLO_PLATFORM_DIR')
|
|
8138
|
+
if override:
|
|
8139
|
+
path = os.path.join(override, 'tooling', STUB_NAME)
|
|
8140
|
+
if os.path.exists(path):
|
|
8141
|
+
return path
|
|
8142
|
+
# 2. Walk up looking for a sibling topolo-platform/tooling/<name>.py.
|
|
8143
|
+
cursor = HERE
|
|
8144
|
+
for _ in range(10):
|
|
8145
|
+
candidate = os.path.normpath(os.path.join(cursor, '..', 'topolo-platform', 'tooling', STUB_NAME))
|
|
8146
|
+
if os.path.exists(candidate):
|
|
8147
|
+
return candidate
|
|
8148
|
+
parent = os.path.dirname(cursor)
|
|
8149
|
+
if parent == cursor:
|
|
8150
|
+
break
|
|
8151
|
+
cursor = parent
|
|
8152
|
+
# 3. node_modules-based delivery (future): @topolo-io/standard-ci.
|
|
8153
|
+
cursor = HERE
|
|
8154
|
+
for _ in range(10):
|
|
8155
|
+
candidate = os.path.normpath(os.path.join(cursor, 'node_modules', '@topolo-io', 'standard-ci', 'bin', STUB_NAME))
|
|
8156
|
+
if os.path.exists(candidate):
|
|
8157
|
+
return candidate
|
|
8158
|
+
parent = os.path.dirname(cursor)
|
|
8159
|
+
if parent == cursor:
|
|
8160
|
+
break
|
|
8161
|
+
cursor = parent
|
|
8162
|
+
return None
|
|
8163
|
+
|
|
8164
|
+
|
|
8165
|
+
def main() -> None:
|
|
8166
|
+
canonical = find_canonical()
|
|
8167
|
+
if not canonical:
|
|
8168
|
+
sys.stderr.write(
|
|
8169
|
+
f'[topolo-canonical-stub] could not locate canonical {STUB_NAME}.\\n'
|
|
8170
|
+
f' Set TOPOLO_PLATFORM_DIR or checkout topolo-platform as a sibling of this app.\\n'
|
|
8171
|
+
)
|
|
8172
|
+
sys.exit(2)
|
|
8173
|
+
os.execv(sys.executable, [sys.executable, canonical, *sys.argv[1:]])
|
|
8174
|
+
|
|
8175
|
+
|
|
8176
|
+
if __name__ == '__main__':
|
|
8177
|
+
main()
|
|
8178
|
+
`;
|
|
7876
8179
|
}
|
|
7877
8180
|
function applicationScaffoldPayload(application, options = {}) {
|
|
7878
8181
|
const target = normalizeTarget(options.target);
|
|
@@ -7892,13 +8195,16 @@ function applicationScaffoldPayload(application, options = {}) {
|
|
|
7892
8195
|
const containerAppId = normalizeContainerAppId(options.containerAppId, surfaceModel);
|
|
7893
8196
|
const profile = normalizeScaffoldProfile(options.profile);
|
|
7894
8197
|
const resources = resolveScaffoldResources(profile, options);
|
|
8198
|
+
if (target === "platform" && api && !profile) {
|
|
8199
|
+
resources.d1 = true;
|
|
8200
|
+
}
|
|
7895
8201
|
const ownerType = target === "platform" ? "topolo_first_party" : "third_party";
|
|
7896
8202
|
const distributionModel = target === "platform" ? "marketplace" : "organization_internal";
|
|
7897
8203
|
const appDir = options.appDir ?? catalogApplication?.appDir ?? (target === "platform" ? `Topolo${toPascalCase(id)}` : `topolo-${slug}`);
|
|
7898
8204
|
const name = options.name ?? catalogApplication?.name ?? `topolo-${slug}`;
|
|
7899
8205
|
const packageName = options.packageName ?? catalogApplication?.packageName ?? `topolo-${slug}`;
|
|
7900
8206
|
const serviceSlug = api ? target === "platform" ? `topolo-${slug}` : id : null;
|
|
7901
|
-
const defaultAppId = target === "platform" ?
|
|
8207
|
+
const defaultAppId = target === "platform" ? `app_topolo_${id}` : catalogApplication?.services[0] ?? id;
|
|
7902
8208
|
const appId = api ? options.appId ?? defaultAppId : null;
|
|
7903
8209
|
const productionUrl = browser ? options.productionUrl ?? catalogApplication?.productionUrl ?? `https://${slug}.topolo.app` : null;
|
|
7904
8210
|
const scopes = scaffoldScopes({ browser, api });
|
|
@@ -8038,6 +8344,28 @@ function scaffoldCapabilities(options) {
|
|
|
8038
8344
|
function scaffoldFiles(options) {
|
|
8039
8345
|
const { application, target } = options;
|
|
8040
8346
|
const slug = toSlug(application.id);
|
|
8347
|
+
const tablePrefix = slug.replace(/-/g, "_");
|
|
8348
|
+
const tenantColumn = options.payload.tenancy === "household" ? "household_id" : "organization_id";
|
|
8349
|
+
const workspacesMigrationSql = [
|
|
8350
|
+
`CREATE TABLE IF NOT EXISTS ${tablePrefix}_workspaces (`,
|
|
8351
|
+
" id TEXT PRIMARY KEY,",
|
|
8352
|
+
` ${tenantColumn} TEXT NOT NULL,`,
|
|
8353
|
+
" name TEXT NOT NULL,",
|
|
8354
|
+
" slug TEXT NOT NULL,",
|
|
8355
|
+
" created_by_user_id TEXT,",
|
|
8356
|
+
" created_by_email TEXT,",
|
|
8357
|
+
" created_at TEXT NOT NULL,",
|
|
8358
|
+
" updated_at TEXT NOT NULL,",
|
|
8359
|
+
` UNIQUE(${tenantColumn}, slug)`,
|
|
8360
|
+
");",
|
|
8361
|
+
"",
|
|
8362
|
+
`CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_workspaces_${tenantColumn}`,
|
|
8363
|
+
` ON ${tablePrefix}_workspaces(${tenantColumn});`,
|
|
8364
|
+
"",
|
|
8365
|
+
`CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_workspaces_slug`,
|
|
8366
|
+
` ON ${tablePrefix}_workspaces(${tenantColumn}, slug);`,
|
|
8367
|
+
""
|
|
8368
|
+
].join("\n");
|
|
8041
8369
|
const base = target === "platform" ? firstPartyAppPath(application.appDir, options.payload.audience) : application.appDir;
|
|
8042
8370
|
const browserBase = target === "platform" ? `${base}/apps/web` : base;
|
|
8043
8371
|
const apiBase = target === "platform" ? `${base}/apps/api` : base;
|
|
@@ -8095,15 +8423,6 @@ function scaffoldFiles(options) {
|
|
|
8095
8423
|
resources: options.payload.resources
|
|
8096
8424
|
})
|
|
8097
8425
|
},
|
|
8098
|
-
{
|
|
8099
|
-
path: `${base}/tsconfig.json`,
|
|
8100
|
-
purpose: "TypeScript configuration for the generated app.",
|
|
8101
|
-
requiredSignals: [
|
|
8102
|
-
"uses strict mode",
|
|
8103
|
-
"includes browser DOM types for React and Worker types for API-backed apps"
|
|
8104
|
-
],
|
|
8105
|
-
content: tsconfigJson(options.api, target)
|
|
8106
|
-
},
|
|
8107
8426
|
{
|
|
8108
8427
|
path: `${browserBase}/vite.config.ts`,
|
|
8109
8428
|
purpose: "Vite browser build configuration.",
|
|
@@ -8161,6 +8480,31 @@ function scaffoldFiles(options) {
|
|
|
8161
8480
|
content: stylesCss()
|
|
8162
8481
|
}
|
|
8163
8482
|
];
|
|
8483
|
+
if (target === "platform") {
|
|
8484
|
+
if (options.browser) {
|
|
8485
|
+
files.push({
|
|
8486
|
+
path: `${browserBase}/tsconfig.json`,
|
|
8487
|
+
purpose: "TypeScript configuration for the browser surface (DOM + vite types).",
|
|
8488
|
+
requiredSignals: ["uses strict mode", "includes browser DOM types for React"],
|
|
8489
|
+
content: webTsconfigJson()
|
|
8490
|
+
});
|
|
8491
|
+
}
|
|
8492
|
+
if (options.api) {
|
|
8493
|
+
files.push({
|
|
8494
|
+
path: `${apiBase}/tsconfig.json`,
|
|
8495
|
+
purpose: "TypeScript configuration for the API worker surface (Workers types).",
|
|
8496
|
+
requiredSignals: ["uses strict mode", "includes Cloudflare Workers types"],
|
|
8497
|
+
content: apiTsconfigJson()
|
|
8498
|
+
});
|
|
8499
|
+
}
|
|
8500
|
+
} else {
|
|
8501
|
+
files.push({
|
|
8502
|
+
path: `${base}/tsconfig.json`,
|
|
8503
|
+
purpose: "TypeScript configuration for the generated app.",
|
|
8504
|
+
requiredSignals: ["uses strict mode", "includes browser DOM types for React and Worker types for API-backed apps"],
|
|
8505
|
+
content: tsconfigJson(options.api, target)
|
|
8506
|
+
});
|
|
8507
|
+
}
|
|
8164
8508
|
if (target === "platform") {
|
|
8165
8509
|
files.push(
|
|
8166
8510
|
{
|
|
@@ -8176,6 +8520,17 @@ function scaffoldFiles(options) {
|
|
|
8176
8520
|
profile: options.payload.profile
|
|
8177
8521
|
})
|
|
8178
8522
|
},
|
|
8523
|
+
...options.api ? [
|
|
8524
|
+
{
|
|
8525
|
+
path: `${base}/topolo.action-contracts.mjs`,
|
|
8526
|
+
purpose: "Zod source of truth for the agent/SDK/MCP action catalog, mirrored into topolo.app.json actions[].",
|
|
8527
|
+
requiredSignals: [
|
|
8528
|
+
"declares topoloActionContracts with name/method/path per action",
|
|
8529
|
+
"each contract has a zod input validator, keeping the manifest actions[] honest"
|
|
8530
|
+
],
|
|
8531
|
+
content: topoloActionContractsMjs()
|
|
8532
|
+
}
|
|
8533
|
+
] : [],
|
|
8179
8534
|
{
|
|
8180
8535
|
path: `${base}/topolo.cloudcontrol.json`,
|
|
8181
8536
|
purpose: "CloudControl source of truth for deploy targets, routes, bindings, and CI metadata.",
|
|
@@ -8201,6 +8556,33 @@ function scaffoldFiles(options) {
|
|
|
8201
8556
|
],
|
|
8202
8557
|
content: options.ciWorkflow ?? ""
|
|
8203
8558
|
},
|
|
8559
|
+
{
|
|
8560
|
+
path: `${base}/.github/scripts/standard_deploy.py`,
|
|
8561
|
+
purpose: "Canonical-fetching deploy stub (execs topolo-platform/tooling/standard_deploy.py).",
|
|
8562
|
+
requiredSignals: [
|
|
8563
|
+
"never a vendored copy of the canonical script",
|
|
8564
|
+
"runs the current platform standard_deploy.py on every invocation"
|
|
8565
|
+
],
|
|
8566
|
+
content: standardScriptStub()
|
|
8567
|
+
},
|
|
8568
|
+
{
|
|
8569
|
+
path: `${base}/.github/scripts/standard_ci.py`,
|
|
8570
|
+
purpose: "Canonical-fetching CI stub (execs topolo-platform/tooling/standard_ci.py).",
|
|
8571
|
+
requiredSignals: [
|
|
8572
|
+
"never a vendored copy of the canonical script",
|
|
8573
|
+
"runs the current platform standard_ci.py on every invocation"
|
|
8574
|
+
],
|
|
8575
|
+
content: standardScriptStub()
|
|
8576
|
+
},
|
|
8577
|
+
{
|
|
8578
|
+
path: `${base}/.github/scripts/standard_ops.py`,
|
|
8579
|
+
purpose: "Canonical-fetching ops stub (execs topolo-platform/tooling/standard_ops.py).",
|
|
8580
|
+
requiredSignals: [
|
|
8581
|
+
"never a vendored copy of the canonical script",
|
|
8582
|
+
"runs the current platform standard_ops.py on every invocation"
|
|
8583
|
+
],
|
|
8584
|
+
content: standardScriptStub()
|
|
8585
|
+
},
|
|
8204
8586
|
{
|
|
8205
8587
|
path: `system-apps/TopoloDocs/apps/web/src/content/systems/topolo-${slug}.json`,
|
|
8206
8588
|
purpose: "Canonical system registry entry.",
|
|
@@ -8324,6 +8706,25 @@ function scaffoldFiles(options) {
|
|
|
8324
8706
|
portfolio: options.payload.portfolio
|
|
8325
8707
|
})
|
|
8326
8708
|
});
|
|
8709
|
+
if (target === "platform") {
|
|
8710
|
+
files.push({
|
|
8711
|
+
path: `${browserBase}/vitest.config.ts`,
|
|
8712
|
+
purpose: "Vitest config for the browser surface.",
|
|
8713
|
+
requiredSignals: [
|
|
8714
|
+
"runs component/unit tests under jsdom",
|
|
8715
|
+
"keeps the generated browser surface testable from day one"
|
|
8716
|
+
],
|
|
8717
|
+
content: `import { defineConfig } from 'vitest/config';
|
|
8718
|
+
|
|
8719
|
+
export default defineConfig({
|
|
8720
|
+
test: {
|
|
8721
|
+
include: ['test/**/*.test.{ts,tsx}'],
|
|
8722
|
+
environment: 'jsdom',
|
|
8723
|
+
},
|
|
8724
|
+
});
|
|
8725
|
+
`
|
|
8726
|
+
});
|
|
8727
|
+
}
|
|
8327
8728
|
}
|
|
8328
8729
|
if (options.api) {
|
|
8329
8730
|
files.push(
|
|
@@ -8380,8 +8781,45 @@ function scaffoldFiles(options) {
|
|
|
8380
8781
|
"binds the frontend Worker to the API Worker for /api/* requests"
|
|
8381
8782
|
],
|
|
8382
8783
|
content: platformWebWranglerToml(application)
|
|
8784
|
+
},
|
|
8785
|
+
{
|
|
8786
|
+
path: `${apiBase}/vitest.config.ts`,
|
|
8787
|
+
purpose: "Vitest config for the API worker surface.",
|
|
8788
|
+
requiredSignals: [
|
|
8789
|
+
"runs worker/unit tests under the node environment",
|
|
8790
|
+
"keeps the generated service testable from day one"
|
|
8791
|
+
],
|
|
8792
|
+
content: `import { defineConfig } from 'vitest/config';
|
|
8793
|
+
|
|
8794
|
+
export default defineConfig({
|
|
8795
|
+
test: {
|
|
8796
|
+
include: ['test/**/*.test.{js,mjs,mts,ts,tsx}'],
|
|
8797
|
+
environment: 'node',
|
|
8798
|
+
},
|
|
8799
|
+
});
|
|
8800
|
+
`
|
|
8801
|
+
},
|
|
8802
|
+
{
|
|
8803
|
+
path: `${apiBase}/test/health.test.ts`,
|
|
8804
|
+
purpose: "Smoke test so the API test suite is non-empty from scaffold.",
|
|
8805
|
+
requiredSignals: [
|
|
8806
|
+
"asserts the scaffold boots",
|
|
8807
|
+
"is replaced by real route/repository tests as the surface grows"
|
|
8808
|
+
],
|
|
8809
|
+
content: "import { describe, expect, it } from 'vitest';\n\n// Smoke test so the API test suite is non-empty from day one. Replace with\n// real route/repository tests as the surface grows.\ndescribe('api scaffold', () => {\n it('boots', () => {\n expect(true).toBe(true);\n });\n});\n"
|
|
8383
8810
|
}
|
|
8384
8811
|
);
|
|
8812
|
+
if (options.payload.resources.d1) {
|
|
8813
|
+
files.push({
|
|
8814
|
+
path: `${apiBase}/migrations/0001_initial.sql`,
|
|
8815
|
+
purpose: "Canonical multi-tenant baseline: the app workspaces table.",
|
|
8816
|
+
requiredSignals: [
|
|
8817
|
+
`${tablePrefix}_workspaces carries ${tenantColumn} on every row`,
|
|
8818
|
+
"every domain table added later carries the tenant + workspace columns"
|
|
8819
|
+
],
|
|
8820
|
+
content: workspacesMigrationSql
|
|
8821
|
+
});
|
|
8822
|
+
}
|
|
8385
8823
|
}
|
|
8386
8824
|
}
|
|
8387
8825
|
return files;
|
|
@@ -10299,18 +10737,13 @@ function registerContextCommand(program2) {
|
|
|
10299
10737
|
}
|
|
10300
10738
|
const key = contextKey(resolved.contextType, resolved.organizationId);
|
|
10301
10739
|
const existing = profile.contexts?.[key];
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
}
|
|
10306
|
-
|
|
10307
|
-
|
|
10308
|
-
|
|
10309
|
-
});
|
|
10310
|
-
resolved.credential = newCredential;
|
|
10311
|
-
resolved.oauthClientId = existing?.oauthClientId ?? profile.oauthClientId;
|
|
10312
|
-
updated = writeContextCredential(config, environment, resolved, true);
|
|
10313
|
-
}
|
|
10740
|
+
const newCredential = await exchangeForContext(cred, serviceUrls, {
|
|
10741
|
+
type: resolved.contextType,
|
|
10742
|
+
id: resolved.organizationId
|
|
10743
|
+
});
|
|
10744
|
+
resolved.credential = newCredential;
|
|
10745
|
+
resolved.oauthClientId = existing?.oauthClientId ?? profile.oauthClientId;
|
|
10746
|
+
const updated = writeContextCredential(config, environment, resolved, true);
|
|
10314
10747
|
await writeConfig(updated);
|
|
10315
10748
|
emit(
|
|
10316
10749
|
{ environment, active: key, context: { type: resolved.contextType, label: labelFor(resolved) } },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topolo/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Command-line interface for Topolo developers, operators, and agents, with internal first-party scaffolding available under the topolo platform namespace.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|