@topolo/cli 0.2.1 → 0.2.2
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 +390 -50
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3312,16 +3312,6 @@ function writeContextCredential(config, environment, ctx, makeActive = true) {
|
|
|
3312
3312
|
}
|
|
3313
3313
|
return writeProfileConfig(config, environment, next);
|
|
3314
3314
|
}
|
|
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
3315
|
function clearProfileCredential(config, environment) {
|
|
3326
3316
|
const next = { ...config };
|
|
3327
3317
|
if (environment === "production") {
|
|
@@ -5323,6 +5313,12 @@ function firstPartyAppPath(appDir, _audience) {
|
|
|
5323
5313
|
return `apps/${appDir}`;
|
|
5324
5314
|
}
|
|
5325
5315
|
|
|
5316
|
+
// src/commands/app-scaffold-cloudflare-constants.ts
|
|
5317
|
+
var CLOUDFLARE_ACCOUNT_ID = "49ef1ba682ad8cfd720c86699ae17521";
|
|
5318
|
+
var STAGING_CLOUDFLARE_ACCOUNT_ID = "4f4e1c69a3830946f9fea7b1eb7531ac";
|
|
5319
|
+
var DEVELOPMENT_CLOUDFLARE_ACCOUNT_ID = "0ebdeec262a245481a721fc4f9d6d798";
|
|
5320
|
+
var CLOUDFLARE_COMPATIBILITY_DATE = "2026-05-05";
|
|
5321
|
+
|
|
5326
5322
|
// src/commands/app-scaffold-package-template.ts
|
|
5327
5323
|
function packageJson(application, target, options) {
|
|
5328
5324
|
const dependencies = {};
|
|
@@ -5361,17 +5357,44 @@ function packageJson(application, target, options) {
|
|
|
5361
5357
|
devDependencies["@cloudflare/workers-types"] = "^4.20260413.1";
|
|
5362
5358
|
if (target === "platform") {
|
|
5363
5359
|
dependencies["@topolo-io/worker-runtime"] = topoloPackageVersion("@topolo-io/worker-runtime", target);
|
|
5360
|
+
dependencies["@topolo-io/auth-middleware"] = topoloPackageVersion("@topolo-io/auth-middleware", target);
|
|
5361
|
+
dependencies["@topolo-io/error-middleware"] = topoloPackageVersion("@topolo-io/error-middleware", target);
|
|
5362
|
+
dependencies["@topolo-io/security-headers"] = topoloPackageVersion("@topolo-io/security-headers", target);
|
|
5363
|
+
dependencies["@topolo-io/resource-context"] = topoloPackageVersion("@topolo-io/resource-context", target);
|
|
5364
|
+
dependencies["@topolo-io/data"] = topoloPackageVersion("@topolo-io/data", target);
|
|
5364
5365
|
dependencies["@topolo/sdk"] = topoloPackageVersion("@topolo/sdk", target);
|
|
5365
5366
|
dependencies.hono = "^4";
|
|
5367
|
+
dependencies.zod = "^4.4.3";
|
|
5366
5368
|
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"] =
|
|
5369
|
+
scripts["deploy:staging"] = `DEPLOY_ENVIRONMENT=staging CLOUDFLARE_ACCOUNT_ID=${STAGING_CLOUDFLARE_ACCOUNT_ID} python3 .github/scripts/standard_deploy.py local-deploy`;
|
|
5370
|
+
scripts["deploy:production"] = `DEPLOY_ENVIRONMENT=production CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID} python3 .github/scripts/standard_deploy.py local-deploy`;
|
|
5369
5371
|
} else {
|
|
5370
5372
|
dependencies["@topolo/sdk"] = topoloPackageVersion("@topolo/sdk", target);
|
|
5371
5373
|
scripts.deploy = "npm run build && wrangler deploy";
|
|
5372
5374
|
}
|
|
5373
5375
|
devDependencies.wrangler = "^4.91.0";
|
|
5374
5376
|
}
|
|
5377
|
+
if (target === "platform") {
|
|
5378
|
+
const testDirs = [];
|
|
5379
|
+
if (options.browser) testDirs.push("apps/web");
|
|
5380
|
+
if (options.api) testDirs.push("apps/api");
|
|
5381
|
+
if (testDirs.length > 0) {
|
|
5382
|
+
const chain = (withCoverage) => testDirs.map((dir, index) => {
|
|
5383
|
+
const enter = index === 0 ? `cd ${dir}` : `cd ../${dir.split("/").pop()}`;
|
|
5384
|
+
return `${enter} && vitest run --config vitest.config.ts --passWithNoTests${withCoverage ? " --coverage" : ""}`;
|
|
5385
|
+
}).join(" && ");
|
|
5386
|
+
scripts.test = chain(false);
|
|
5387
|
+
scripts.coverage = chain(true);
|
|
5388
|
+
scripts.typecheck = testDirs.map((dir) => `tsc --noEmit -p ${dir}/tsconfig.json`).join(" && ");
|
|
5389
|
+
devDependencies.vitest = "^3.2.4";
|
|
5390
|
+
devDependencies["@vitest/coverage-v8"] = "^3.2.4";
|
|
5391
|
+
if (options.browser) {
|
|
5392
|
+
devDependencies.jsdom = "^29.1.1";
|
|
5393
|
+
devDependencies["@testing-library/react"] = "^16.3.2";
|
|
5394
|
+
devDependencies["@testing-library/user-event"] = "^14.6.1";
|
|
5395
|
+
}
|
|
5396
|
+
}
|
|
5397
|
+
}
|
|
5375
5398
|
if (target === "platform" && hasScaffoldResources(options.resources)) {
|
|
5376
5399
|
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
5400
|
}
|
|
@@ -5400,7 +5423,11 @@ function topoloPackageVersion(packageName, target) {
|
|
|
5400
5423
|
"@topolo-io/app-launcher": "^0.1.15",
|
|
5401
5424
|
"@topolo-io/app-shell": "^0.5.54",
|
|
5402
5425
|
"@topolo-io/auth-client": "^1.2.1",
|
|
5403
|
-
"@topolo-io/auth-middleware": "^0.
|
|
5426
|
+
"@topolo-io/auth-middleware": "^0.4.0",
|
|
5427
|
+
"@topolo-io/error-middleware": "^0.6.0",
|
|
5428
|
+
"@topolo-io/security-headers": "^0.2.2",
|
|
5429
|
+
"@topolo-io/resource-context": "^0.3.0",
|
|
5430
|
+
"@topolo-io/data": "^0.1.2",
|
|
5404
5431
|
"@topolo-io/worker-runtime": "^0.1.11",
|
|
5405
5432
|
"@topolo-io/design-tokens": "^0.1.8",
|
|
5406
5433
|
"@topolo-io/onboarding": "^0.9.5",
|
|
@@ -5436,7 +5463,12 @@ function resourceConfig(application, profile) {
|
|
|
5436
5463
|
workflowProductionName: `topolo-${slug}-import`,
|
|
5437
5464
|
workflowStagingName: `topolo-${slug}-import-staging`,
|
|
5438
5465
|
containerProductionName: `topolo-${slug}-processor`,
|
|
5439
|
-
containerStagingName: `topolo-${slug}-processor-staging
|
|
5466
|
+
containerStagingName: `topolo-${slug}-processor-staging`,
|
|
5467
|
+
d1DevelopmentName: `topolo-${slug}-core-development`,
|
|
5468
|
+
r2DevelopmentName: `topolo-${slug}-assets-development`,
|
|
5469
|
+
queueDevelopmentName: `topolo-${slug}-processing-development`,
|
|
5470
|
+
workflowDevelopmentName: `topolo-${slug}-import-development`,
|
|
5471
|
+
containerDevelopmentName: `topolo-${slug}-processor-development`
|
|
5440
5472
|
};
|
|
5441
5473
|
}
|
|
5442
5474
|
return {
|
|
@@ -5454,7 +5486,12 @@ function resourceConfig(application, profile) {
|
|
|
5454
5486
|
workflowProductionName: `topolo-${slug}-workflow`,
|
|
5455
5487
|
workflowStagingName: `topolo-${slug}-workflow-staging`,
|
|
5456
5488
|
containerProductionName: `topolo-${slug}-processor`,
|
|
5457
|
-
containerStagingName: `topolo-${slug}-processor-staging
|
|
5489
|
+
containerStagingName: `topolo-${slug}-processor-staging`,
|
|
5490
|
+
d1DevelopmentName: `topolo-${slug}-core-development`,
|
|
5491
|
+
r2DevelopmentName: `topolo-${slug}-assets-development`,
|
|
5492
|
+
queueDevelopmentName: `topolo-${slug}-queue-development`,
|
|
5493
|
+
workflowDevelopmentName: `topolo-${slug}-workflow-development`,
|
|
5494
|
+
containerDevelopmentName: `topolo-${slug}-processor-development`
|
|
5458
5495
|
};
|
|
5459
5496
|
}
|
|
5460
5497
|
|
|
@@ -5627,6 +5664,64 @@ function tsconfigJson(api, target) {
|
|
|
5627
5664
|
})}
|
|
5628
5665
|
`;
|
|
5629
5666
|
}
|
|
5667
|
+
var TSCONFIG_TEST_EXCLUDE = [
|
|
5668
|
+
"src/**/*.test.ts",
|
|
5669
|
+
"src/**/*.test.tsx",
|
|
5670
|
+
"src/**/*.test.mts",
|
|
5671
|
+
"src/**/*.spec.ts",
|
|
5672
|
+
"src/**/*.spec.tsx",
|
|
5673
|
+
"test"
|
|
5674
|
+
];
|
|
5675
|
+
function webTsconfigJson() {
|
|
5676
|
+
return `${json({
|
|
5677
|
+
compilerOptions: {
|
|
5678
|
+
target: "ES2022",
|
|
5679
|
+
useDefineForClassFields: true,
|
|
5680
|
+
lib: ["ES2022", "DOM", "DOM.Iterable"],
|
|
5681
|
+
allowJs: false,
|
|
5682
|
+
skipLibCheck: true,
|
|
5683
|
+
esModuleInterop: true,
|
|
5684
|
+
allowSyntheticDefaultImports: true,
|
|
5685
|
+
strict: true,
|
|
5686
|
+
forceConsistentCasingInFileNames: true,
|
|
5687
|
+
module: "ESNext",
|
|
5688
|
+
moduleResolution: "Bundler",
|
|
5689
|
+
resolveJsonModule: true,
|
|
5690
|
+
isolatedModules: true,
|
|
5691
|
+
noEmit: true,
|
|
5692
|
+
jsx: "react-jsx",
|
|
5693
|
+
types: ["vite/client"]
|
|
5694
|
+
},
|
|
5695
|
+
include: ["src"],
|
|
5696
|
+
references: [],
|
|
5697
|
+
exclude: TSCONFIG_TEST_EXCLUDE
|
|
5698
|
+
})}
|
|
5699
|
+
`;
|
|
5700
|
+
}
|
|
5701
|
+
function apiTsconfigJson() {
|
|
5702
|
+
return `${json({
|
|
5703
|
+
compilerOptions: {
|
|
5704
|
+
target: "ES2022",
|
|
5705
|
+
lib: ["ES2022", "DOM", "DOM.Iterable", "WebWorker"],
|
|
5706
|
+
allowJs: false,
|
|
5707
|
+
skipLibCheck: true,
|
|
5708
|
+
esModuleInterop: true,
|
|
5709
|
+
allowSyntheticDefaultImports: true,
|
|
5710
|
+
strict: true,
|
|
5711
|
+
forceConsistentCasingInFileNames: true,
|
|
5712
|
+
module: "ESNext",
|
|
5713
|
+
moduleResolution: "Bundler",
|
|
5714
|
+
resolveJsonModule: true,
|
|
5715
|
+
isolatedModules: true,
|
|
5716
|
+
noEmit: true,
|
|
5717
|
+
types: ["@cloudflare/workers-types", "node"]
|
|
5718
|
+
},
|
|
5719
|
+
include: ["src"],
|
|
5720
|
+
references: [],
|
|
5721
|
+
exclude: TSCONFIG_TEST_EXCLUDE
|
|
5722
|
+
})}
|
|
5723
|
+
`;
|
|
5724
|
+
}
|
|
5630
5725
|
function gitignore() {
|
|
5631
5726
|
return [
|
|
5632
5727
|
"node_modules/",
|
|
@@ -6368,11 +6463,6 @@ function developerAppTsx(application) {
|
|
|
6368
6463
|
].join("\n");
|
|
6369
6464
|
}
|
|
6370
6465
|
|
|
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
6466
|
// src/commands/app-scaffold-worker-template.ts
|
|
6377
6467
|
function webWorkerTs(application) {
|
|
6378
6468
|
const serviceName = toTitle(application.name);
|
|
@@ -6427,6 +6517,29 @@ function platformWebWranglerToml(application) {
|
|
|
6427
6517
|
'binding = "API"',
|
|
6428
6518
|
`service = "topolo-${slug}-api"`,
|
|
6429
6519
|
"",
|
|
6520
|
+
"[env.development]",
|
|
6521
|
+
`name = "topolo-${slug}-development"`,
|
|
6522
|
+
`account_id = "${DEVELOPMENT_CLOUDFLARE_ACCOUNT_ID}"`,
|
|
6523
|
+
"workers_dev = true",
|
|
6524
|
+
"",
|
|
6525
|
+
"[env.development.vars]",
|
|
6526
|
+
`APP_ID = "${application.appId ?? ""}"`,
|
|
6527
|
+
`VITE_TOPOLO_APP_ID = "${application.appId ?? ""}"`,
|
|
6528
|
+
"",
|
|
6529
|
+
"[[env.development.routes]]",
|
|
6530
|
+
`pattern = "${slug}.topolo.dev"`,
|
|
6531
|
+
"custom_domain = true",
|
|
6532
|
+
"",
|
|
6533
|
+
"[env.development.assets]",
|
|
6534
|
+
'directory = "./dist"',
|
|
6535
|
+
'binding = "ASSETS"',
|
|
6536
|
+
'not_found_handling = "single-page-application"',
|
|
6537
|
+
'run_worker_first = ["/api/*"]',
|
|
6538
|
+
"",
|
|
6539
|
+
"[[env.development.services]]",
|
|
6540
|
+
'binding = "API"',
|
|
6541
|
+
`service = "topolo-${slug}-api-development"`,
|
|
6542
|
+
"",
|
|
6430
6543
|
"[env.staging]",
|
|
6431
6544
|
`name = "topolo-${slug}-staging"`,
|
|
6432
6545
|
`account_id = "${STAGING_CLOUDFLARE_ACCOUNT_ID}"`,
|
|
@@ -6498,6 +6611,22 @@ function platformApiWranglerToml(application, resources, profile) {
|
|
|
6498
6611
|
""
|
|
6499
6612
|
];
|
|
6500
6613
|
appendResourceWranglerBlocks(lines, resources, resource, "production");
|
|
6614
|
+
lines.push(
|
|
6615
|
+
"[env.development]",
|
|
6616
|
+
`name = "topolo-${slug}-api-development"`,
|
|
6617
|
+
`account_id = "${DEVELOPMENT_CLOUDFLARE_ACCOUNT_ID}"`,
|
|
6618
|
+
"workers_dev = true",
|
|
6619
|
+
"",
|
|
6620
|
+
"[env.development.vars]",
|
|
6621
|
+
'AUTH_BASE_URL = "https://auth.topolo.dev"',
|
|
6622
|
+
'NEXUS_GATEWAY_URL = "https://nexus.topolo.dev"',
|
|
6623
|
+
`NEXUS_APP_ID = "${slug}"`,
|
|
6624
|
+
`APP_ID = "${application.appId ?? ""}"`,
|
|
6625
|
+
`VITE_TOPOLO_APP_ID = "${application.appId ?? ""}"`,
|
|
6626
|
+
'ENVIRONMENT = "development"',
|
|
6627
|
+
""
|
|
6628
|
+
);
|
|
6629
|
+
appendResourceWranglerBlocks(lines, resources, resource, "development");
|
|
6501
6630
|
lines.push(
|
|
6502
6631
|
"[env.staging]",
|
|
6503
6632
|
`name = "topolo-${slug}-api-staging"`,
|
|
@@ -6531,13 +6660,14 @@ function platformApiWranglerToml(application, resources, profile) {
|
|
|
6531
6660
|
return lines.join("\n");
|
|
6532
6661
|
}
|
|
6533
6662
|
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";
|
|
6663
|
+
const prefix = environment === "staging" ? "env.staging." : environment === "development" ? "env.development." : environment === "production-env" ? "env.production." : "";
|
|
6664
|
+
const envName = environment === "staging" ? "Staging" : environment === "development" ? "Development" : "Production";
|
|
6665
|
+
const pick = (dev, staging, prod) => environment === "development" ? dev : environment === "staging" ? staging : prod;
|
|
6536
6666
|
if (resources.d1) {
|
|
6537
6667
|
lines.push(
|
|
6538
6668
|
`[[${prefix}d1_databases]]`,
|
|
6539
6669
|
`binding = "${resource.d1Binding}"`,
|
|
6540
|
-
`database_name = "${
|
|
6670
|
+
`database_name = "${pick(resource.d1DevelopmentName, resource.d1StagingName, resource.d1ProductionName)}"`,
|
|
6541
6671
|
'database_id = "provision-after-scaffold"',
|
|
6542
6672
|
'migrations_dir = "migrations"',
|
|
6543
6673
|
""
|
|
@@ -6547,7 +6677,7 @@ function appendResourceWranglerBlocks(lines, resources, resource, environment) {
|
|
|
6547
6677
|
lines.push(
|
|
6548
6678
|
`[[${prefix}r2_buckets]]`,
|
|
6549
6679
|
`binding = "${resource.r2Binding}"`,
|
|
6550
|
-
`bucket_name = "${
|
|
6680
|
+
`bucket_name = "${pick(resource.r2DevelopmentName, resource.r2StagingName, resource.r2ProductionName)}"`,
|
|
6551
6681
|
""
|
|
6552
6682
|
);
|
|
6553
6683
|
}
|
|
@@ -6555,10 +6685,10 @@ function appendResourceWranglerBlocks(lines, resources, resource, environment) {
|
|
|
6555
6685
|
lines.push(
|
|
6556
6686
|
`[[${prefix}queues.producers]]`,
|
|
6557
6687
|
`binding = "${resource.queueBinding}"`,
|
|
6558
|
-
`queue = "${
|
|
6688
|
+
`queue = "${pick(resource.queueDevelopmentName, resource.queueStagingName, resource.queueProductionName)}"`,
|
|
6559
6689
|
"",
|
|
6560
6690
|
`[[${prefix}queues.consumers]]`,
|
|
6561
|
-
`queue = "${
|
|
6691
|
+
`queue = "${pick(resource.queueDevelopmentName, resource.queueStagingName, resource.queueProductionName)}"`,
|
|
6562
6692
|
""
|
|
6563
6693
|
);
|
|
6564
6694
|
}
|
|
@@ -6566,7 +6696,7 @@ function appendResourceWranglerBlocks(lines, resources, resource, environment) {
|
|
|
6566
6696
|
lines.push(
|
|
6567
6697
|
`[[${prefix}workflows]]`,
|
|
6568
6698
|
`binding = "${resource.workflowBinding}"`,
|
|
6569
|
-
`name = "${
|
|
6699
|
+
`name = "${pick(resource.workflowDevelopmentName, resource.workflowStagingName, resource.workflowProductionName)}"`,
|
|
6570
6700
|
`class_name = "${resource.workflowClassName}"`,
|
|
6571
6701
|
""
|
|
6572
6702
|
);
|
|
@@ -6764,6 +6894,8 @@ function platformWorkerTs(application, options) {
|
|
|
6764
6894
|
"import { Hono } from 'hono';",
|
|
6765
6895
|
"import { createTopoloWidgetResponse, type TopoloWidgetDraft } from '@topolo/sdk';",
|
|
6766
6896
|
"import { createTopoloWorkerRuntime, type TopoloRuntimeContext } from '@topolo-io/worker-runtime';",
|
|
6897
|
+
"import { withSecurityHeaders } from '@topolo-io/security-headers';",
|
|
6898
|
+
"import { withErrorReporting } from '@topolo-io/error-middleware';",
|
|
6767
6899
|
"import { SCAFFOLD_APP_ID, SCAFFOLD_APP_NAME, SCAFFOLD_SERVICE_SLUG, resolveRuntimeAppId } from './app-identity';",
|
|
6768
6900
|
...options.resources.workflow ? ["import { WorkflowEntrypoint, type WorkflowEvent, type WorkflowStep } from 'cloudflare:workers';"] : [],
|
|
6769
6901
|
"",
|
|
@@ -6854,11 +6986,20 @@ function platformWorkerTs(application, options) {
|
|
|
6854
6986
|
" return handleRequest(c.req.raw, c.env, rt);",
|
|
6855
6987
|
"});",
|
|
6856
6988
|
"",
|
|
6989
|
+
"// Platform security headers + structured error reporting wrap the whole app",
|
|
6990
|
+
"// (outermost), so headers are stamped even on error-middleware 5xx responses.",
|
|
6991
|
+
"const securedFetch = withSecurityHeaders(",
|
|
6992
|
+
" withErrorReporting(",
|
|
6993
|
+
" (request: Request, env: Env, ctx: ExecutionContext) => api.fetch(request, env, ctx),",
|
|
6994
|
+
" { appName: SCAFFOLD_APP_NAME, appId: SCAFFOLD_SERVICE_SLUG },",
|
|
6995
|
+
" ),",
|
|
6996
|
+
") as (request: Request, env: Env, ctx: ExecutionContext) => Response | Promise<Response>;",
|
|
6997
|
+
"",
|
|
6857
6998
|
"export default {",
|
|
6858
6999
|
" fetch: runtime.worker((request, env, ctx, rt) => {",
|
|
6859
7000
|
" const bindings = env as Env & { __rt?: TopoloRuntimeContext<Env> };",
|
|
6860
7001
|
" bindings.__rt = rt;",
|
|
6861
|
-
" return
|
|
7002
|
+
" return securedFetch(request, bindings, ctx as ExecutionContext);",
|
|
6862
7003
|
" }),",
|
|
6863
7004
|
"};",
|
|
6864
7005
|
"",
|
|
@@ -7211,9 +7352,12 @@ function topoloAppManifestJson(application, options) {
|
|
|
7211
7352
|
$schema: "./../../topolo-platform/tooling/app-manifest-schema.json",
|
|
7212
7353
|
name: `topolo-${slug}`,
|
|
7213
7354
|
appId: application.appId,
|
|
7214
|
-
primaryTarget: "web",
|
|
7215
7355
|
compatibilityDate: CLOUDFLARE_COMPATIBILITY_DATE,
|
|
7216
7356
|
environments: {
|
|
7357
|
+
development: {
|
|
7358
|
+
accountId: DEVELOPMENT_CLOUDFLARE_ACCOUNT_ID,
|
|
7359
|
+
domain: "topolo.dev"
|
|
7360
|
+
},
|
|
7217
7361
|
staging: {
|
|
7218
7362
|
accountId: STAGING_CLOUDFLARE_ACCOUNT_ID,
|
|
7219
7363
|
domain: "stg.topolo.us"
|
|
@@ -7254,6 +7398,15 @@ function cloudcontrolJson(application, options) {
|
|
|
7254
7398
|
resource_name: resources.d1StagingName,
|
|
7255
7399
|
resource_id: "provision-after-scaffold",
|
|
7256
7400
|
migrations_dir: "migrations"
|
|
7401
|
+
},
|
|
7402
|
+
{
|
|
7403
|
+
resource_type: "d1",
|
|
7404
|
+
binding: resources.d1Binding,
|
|
7405
|
+
environment: "development",
|
|
7406
|
+
account_id: DEVELOPMENT_CLOUDFLARE_ACCOUNT_ID,
|
|
7407
|
+
resource_name: resources.d1DevelopmentName,
|
|
7408
|
+
resource_id: "provision-after-scaffold",
|
|
7409
|
+
migrations_dir: "migrations"
|
|
7257
7410
|
}
|
|
7258
7411
|
);
|
|
7259
7412
|
}
|
|
@@ -7874,6 +8027,74 @@ function envExample(application) {
|
|
|
7874
8027
|
function generateOpaqueAppId() {
|
|
7875
8028
|
return `app_${randomBytes(9).toString("base64url")}`;
|
|
7876
8029
|
}
|
|
8030
|
+
function standardScriptStub() {
|
|
8031
|
+
return `#!/usr/bin/env python3
|
|
8032
|
+
"""Canonical-fetching stub for .github/scripts/standard_*.py.
|
|
8033
|
+
|
|
8034
|
+
Replaces a vendored copy. At runtime, finds the canonical sibling in
|
|
8035
|
+
\`<repo-parent>/topolo-platform/tooling/<name>.py\` and execs it, passing argv
|
|
8036
|
+
through, so every consumer runs the current platform script on every
|
|
8037
|
+
invocation (no drifted per-app copies).
|
|
8038
|
+
|
|
8039
|
+
Override: set TOPOLO_PLATFORM_DIR to the absolute path of the topolo-platform
|
|
8040
|
+
repo when the canonical is in a non-default checkout layout (e.g. CI). If no
|
|
8041
|
+
canonical can be found, exits 2 with a clear message rather than silently
|
|
8042
|
+
falling back.
|
|
8043
|
+
"""
|
|
8044
|
+
from __future__ import annotations
|
|
8045
|
+
|
|
8046
|
+
import os
|
|
8047
|
+
import sys
|
|
8048
|
+
|
|
8049
|
+
STUB_NAME = os.path.basename(__file__) # standard_ci.py OR standard_deploy.py
|
|
8050
|
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
8051
|
+
|
|
8052
|
+
|
|
8053
|
+
def find_canonical() -> str | None:
|
|
8054
|
+
# 1. Explicit override (CI runners with non-standard layouts).
|
|
8055
|
+
override = os.environ.get('TOPOLO_PLATFORM_DIR')
|
|
8056
|
+
if override:
|
|
8057
|
+
path = os.path.join(override, 'tooling', STUB_NAME)
|
|
8058
|
+
if os.path.exists(path):
|
|
8059
|
+
return path
|
|
8060
|
+
# 2. Walk up looking for a sibling topolo-platform/tooling/<name>.py.
|
|
8061
|
+
cursor = HERE
|
|
8062
|
+
for _ in range(10):
|
|
8063
|
+
candidate = os.path.normpath(os.path.join(cursor, '..', 'topolo-platform', 'tooling', STUB_NAME))
|
|
8064
|
+
if os.path.exists(candidate):
|
|
8065
|
+
return candidate
|
|
8066
|
+
parent = os.path.dirname(cursor)
|
|
8067
|
+
if parent == cursor:
|
|
8068
|
+
break
|
|
8069
|
+
cursor = parent
|
|
8070
|
+
# 3. node_modules-based delivery (future): @topolo-io/standard-ci.
|
|
8071
|
+
cursor = HERE
|
|
8072
|
+
for _ in range(10):
|
|
8073
|
+
candidate = os.path.normpath(os.path.join(cursor, 'node_modules', '@topolo-io', 'standard-ci', 'bin', STUB_NAME))
|
|
8074
|
+
if os.path.exists(candidate):
|
|
8075
|
+
return candidate
|
|
8076
|
+
parent = os.path.dirname(cursor)
|
|
8077
|
+
if parent == cursor:
|
|
8078
|
+
break
|
|
8079
|
+
cursor = parent
|
|
8080
|
+
return None
|
|
8081
|
+
|
|
8082
|
+
|
|
8083
|
+
def main() -> None:
|
|
8084
|
+
canonical = find_canonical()
|
|
8085
|
+
if not canonical:
|
|
8086
|
+
sys.stderr.write(
|
|
8087
|
+
f'[topolo-canonical-stub] could not locate canonical {STUB_NAME}.\\n'
|
|
8088
|
+
f' Set TOPOLO_PLATFORM_DIR or checkout topolo-platform as a sibling of this app.\\n'
|
|
8089
|
+
)
|
|
8090
|
+
sys.exit(2)
|
|
8091
|
+
os.execv(sys.executable, [sys.executable, canonical, *sys.argv[1:]])
|
|
8092
|
+
|
|
8093
|
+
|
|
8094
|
+
if __name__ == '__main__':
|
|
8095
|
+
main()
|
|
8096
|
+
`;
|
|
8097
|
+
}
|
|
7877
8098
|
function applicationScaffoldPayload(application, options = {}) {
|
|
7878
8099
|
const target = normalizeTarget(options.target);
|
|
7879
8100
|
const portfolio = target === "platform" ? normalizePortfolio(options.portfolio) : null;
|
|
@@ -7892,6 +8113,9 @@ function applicationScaffoldPayload(application, options = {}) {
|
|
|
7892
8113
|
const containerAppId = normalizeContainerAppId(options.containerAppId, surfaceModel);
|
|
7893
8114
|
const profile = normalizeScaffoldProfile(options.profile);
|
|
7894
8115
|
const resources = resolveScaffoldResources(profile, options);
|
|
8116
|
+
if (target === "platform" && api && !profile) {
|
|
8117
|
+
resources.d1 = true;
|
|
8118
|
+
}
|
|
7895
8119
|
const ownerType = target === "platform" ? "topolo_first_party" : "third_party";
|
|
7896
8120
|
const distributionModel = target === "platform" ? "marketplace" : "organization_internal";
|
|
7897
8121
|
const appDir = options.appDir ?? catalogApplication?.appDir ?? (target === "platform" ? `Topolo${toPascalCase(id)}` : `topolo-${slug}`);
|
|
@@ -8038,6 +8262,28 @@ function scaffoldCapabilities(options) {
|
|
|
8038
8262
|
function scaffoldFiles(options) {
|
|
8039
8263
|
const { application, target } = options;
|
|
8040
8264
|
const slug = toSlug(application.id);
|
|
8265
|
+
const tablePrefix = slug.replace(/-/g, "_");
|
|
8266
|
+
const tenantColumn = options.payload.tenancy === "household" ? "household_id" : "organization_id";
|
|
8267
|
+
const workspacesMigrationSql = [
|
|
8268
|
+
`CREATE TABLE IF NOT EXISTS ${tablePrefix}_workspaces (`,
|
|
8269
|
+
" id TEXT PRIMARY KEY,",
|
|
8270
|
+
` ${tenantColumn} TEXT NOT NULL,`,
|
|
8271
|
+
" name TEXT NOT NULL,",
|
|
8272
|
+
" slug TEXT NOT NULL,",
|
|
8273
|
+
" created_by_user_id TEXT,",
|
|
8274
|
+
" created_by_email TEXT,",
|
|
8275
|
+
" created_at TEXT NOT NULL,",
|
|
8276
|
+
" updated_at TEXT NOT NULL,",
|
|
8277
|
+
` UNIQUE(${tenantColumn}, slug)`,
|
|
8278
|
+
");",
|
|
8279
|
+
"",
|
|
8280
|
+
`CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_workspaces_${tenantColumn}`,
|
|
8281
|
+
` ON ${tablePrefix}_workspaces(${tenantColumn});`,
|
|
8282
|
+
"",
|
|
8283
|
+
`CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_workspaces_slug`,
|
|
8284
|
+
` ON ${tablePrefix}_workspaces(${tenantColumn}, slug);`,
|
|
8285
|
+
""
|
|
8286
|
+
].join("\n");
|
|
8041
8287
|
const base = target === "platform" ? firstPartyAppPath(application.appDir, options.payload.audience) : application.appDir;
|
|
8042
8288
|
const browserBase = target === "platform" ? `${base}/apps/web` : base;
|
|
8043
8289
|
const apiBase = target === "platform" ? `${base}/apps/api` : base;
|
|
@@ -8095,15 +8341,6 @@ function scaffoldFiles(options) {
|
|
|
8095
8341
|
resources: options.payload.resources
|
|
8096
8342
|
})
|
|
8097
8343
|
},
|
|
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
8344
|
{
|
|
8108
8345
|
path: `${browserBase}/vite.config.ts`,
|
|
8109
8346
|
purpose: "Vite browser build configuration.",
|
|
@@ -8161,6 +8398,31 @@ function scaffoldFiles(options) {
|
|
|
8161
8398
|
content: stylesCss()
|
|
8162
8399
|
}
|
|
8163
8400
|
];
|
|
8401
|
+
if (target === "platform") {
|
|
8402
|
+
if (options.browser) {
|
|
8403
|
+
files.push({
|
|
8404
|
+
path: `${browserBase}/tsconfig.json`,
|
|
8405
|
+
purpose: "TypeScript configuration for the browser surface (DOM + vite types).",
|
|
8406
|
+
requiredSignals: ["uses strict mode", "includes browser DOM types for React"],
|
|
8407
|
+
content: webTsconfigJson()
|
|
8408
|
+
});
|
|
8409
|
+
}
|
|
8410
|
+
if (options.api) {
|
|
8411
|
+
files.push({
|
|
8412
|
+
path: `${apiBase}/tsconfig.json`,
|
|
8413
|
+
purpose: "TypeScript configuration for the API worker surface (Workers types).",
|
|
8414
|
+
requiredSignals: ["uses strict mode", "includes Cloudflare Workers types"],
|
|
8415
|
+
content: apiTsconfigJson()
|
|
8416
|
+
});
|
|
8417
|
+
}
|
|
8418
|
+
} else {
|
|
8419
|
+
files.push({
|
|
8420
|
+
path: `${base}/tsconfig.json`,
|
|
8421
|
+
purpose: "TypeScript configuration for the generated app.",
|
|
8422
|
+
requiredSignals: ["uses strict mode", "includes browser DOM types for React and Worker types for API-backed apps"],
|
|
8423
|
+
content: tsconfigJson(options.api, target)
|
|
8424
|
+
});
|
|
8425
|
+
}
|
|
8164
8426
|
if (target === "platform") {
|
|
8165
8427
|
files.push(
|
|
8166
8428
|
{
|
|
@@ -8201,6 +8463,33 @@ function scaffoldFiles(options) {
|
|
|
8201
8463
|
],
|
|
8202
8464
|
content: options.ciWorkflow ?? ""
|
|
8203
8465
|
},
|
|
8466
|
+
{
|
|
8467
|
+
path: `${base}/.github/scripts/standard_deploy.py`,
|
|
8468
|
+
purpose: "Canonical-fetching deploy stub (execs topolo-platform/tooling/standard_deploy.py).",
|
|
8469
|
+
requiredSignals: [
|
|
8470
|
+
"never a vendored copy of the canonical script",
|
|
8471
|
+
"runs the current platform standard_deploy.py on every invocation"
|
|
8472
|
+
],
|
|
8473
|
+
content: standardScriptStub()
|
|
8474
|
+
},
|
|
8475
|
+
{
|
|
8476
|
+
path: `${base}/.github/scripts/standard_ci.py`,
|
|
8477
|
+
purpose: "Canonical-fetching CI stub (execs topolo-platform/tooling/standard_ci.py).",
|
|
8478
|
+
requiredSignals: [
|
|
8479
|
+
"never a vendored copy of the canonical script",
|
|
8480
|
+
"runs the current platform standard_ci.py on every invocation"
|
|
8481
|
+
],
|
|
8482
|
+
content: standardScriptStub()
|
|
8483
|
+
},
|
|
8484
|
+
{
|
|
8485
|
+
path: `${base}/.github/scripts/standard_ops.py`,
|
|
8486
|
+
purpose: "Canonical-fetching ops stub (execs topolo-platform/tooling/standard_ops.py).",
|
|
8487
|
+
requiredSignals: [
|
|
8488
|
+
"never a vendored copy of the canonical script",
|
|
8489
|
+
"runs the current platform standard_ops.py on every invocation"
|
|
8490
|
+
],
|
|
8491
|
+
content: standardScriptStub()
|
|
8492
|
+
},
|
|
8204
8493
|
{
|
|
8205
8494
|
path: `system-apps/TopoloDocs/apps/web/src/content/systems/topolo-${slug}.json`,
|
|
8206
8495
|
purpose: "Canonical system registry entry.",
|
|
@@ -8324,6 +8613,25 @@ function scaffoldFiles(options) {
|
|
|
8324
8613
|
portfolio: options.payload.portfolio
|
|
8325
8614
|
})
|
|
8326
8615
|
});
|
|
8616
|
+
if (target === "platform") {
|
|
8617
|
+
files.push({
|
|
8618
|
+
path: `${browserBase}/vitest.config.ts`,
|
|
8619
|
+
purpose: "Vitest config for the browser surface.",
|
|
8620
|
+
requiredSignals: [
|
|
8621
|
+
"runs component/unit tests under jsdom",
|
|
8622
|
+
"keeps the generated browser surface testable from day one"
|
|
8623
|
+
],
|
|
8624
|
+
content: `import { defineConfig } from 'vitest/config';
|
|
8625
|
+
|
|
8626
|
+
export default defineConfig({
|
|
8627
|
+
test: {
|
|
8628
|
+
include: ['test/**/*.test.{ts,tsx}'],
|
|
8629
|
+
environment: 'jsdom',
|
|
8630
|
+
},
|
|
8631
|
+
});
|
|
8632
|
+
`
|
|
8633
|
+
});
|
|
8634
|
+
}
|
|
8327
8635
|
}
|
|
8328
8636
|
if (options.api) {
|
|
8329
8637
|
files.push(
|
|
@@ -8380,8 +8688,45 @@ function scaffoldFiles(options) {
|
|
|
8380
8688
|
"binds the frontend Worker to the API Worker for /api/* requests"
|
|
8381
8689
|
],
|
|
8382
8690
|
content: platformWebWranglerToml(application)
|
|
8691
|
+
},
|
|
8692
|
+
{
|
|
8693
|
+
path: `${apiBase}/vitest.config.ts`,
|
|
8694
|
+
purpose: "Vitest config for the API worker surface.",
|
|
8695
|
+
requiredSignals: [
|
|
8696
|
+
"runs worker/unit tests under the node environment",
|
|
8697
|
+
"keeps the generated service testable from day one"
|
|
8698
|
+
],
|
|
8699
|
+
content: `import { defineConfig } from 'vitest/config';
|
|
8700
|
+
|
|
8701
|
+
export default defineConfig({
|
|
8702
|
+
test: {
|
|
8703
|
+
include: ['test/**/*.test.{js,mjs,mts,ts,tsx}'],
|
|
8704
|
+
environment: 'node',
|
|
8705
|
+
},
|
|
8706
|
+
});
|
|
8707
|
+
`
|
|
8708
|
+
},
|
|
8709
|
+
{
|
|
8710
|
+
path: `${apiBase}/test/health.test.ts`,
|
|
8711
|
+
purpose: "Smoke test so the API test suite is non-empty from scaffold.",
|
|
8712
|
+
requiredSignals: [
|
|
8713
|
+
"asserts the scaffold boots",
|
|
8714
|
+
"is replaced by real route/repository tests as the surface grows"
|
|
8715
|
+
],
|
|
8716
|
+
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
8717
|
}
|
|
8384
8718
|
);
|
|
8719
|
+
if (options.payload.resources.d1) {
|
|
8720
|
+
files.push({
|
|
8721
|
+
path: `${apiBase}/migrations/0001_initial.sql`,
|
|
8722
|
+
purpose: "Canonical multi-tenant baseline: the app workspaces table.",
|
|
8723
|
+
requiredSignals: [
|
|
8724
|
+
`${tablePrefix}_workspaces carries ${tenantColumn} on every row`,
|
|
8725
|
+
"every domain table added later carries the tenant + workspace columns"
|
|
8726
|
+
],
|
|
8727
|
+
content: workspacesMigrationSql
|
|
8728
|
+
});
|
|
8729
|
+
}
|
|
8385
8730
|
}
|
|
8386
8731
|
}
|
|
8387
8732
|
return files;
|
|
@@ -10299,18 +10644,13 @@ function registerContextCommand(program2) {
|
|
|
10299
10644
|
}
|
|
10300
10645
|
const key = contextKey(resolved.contextType, resolved.organizationId);
|
|
10301
10646
|
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
|
-
}
|
|
10647
|
+
const newCredential = await exchangeForContext(cred, serviceUrls, {
|
|
10648
|
+
type: resolved.contextType,
|
|
10649
|
+
id: resolved.organizationId
|
|
10650
|
+
});
|
|
10651
|
+
resolved.credential = newCredential;
|
|
10652
|
+
resolved.oauthClientId = existing?.oauthClientId ?? profile.oauthClientId;
|
|
10653
|
+
const updated = writeContextCredential(config, environment, resolved, true);
|
|
10314
10654
|
await writeConfig(updated);
|
|
10315
10655
|
emit(
|
|
10316
10656
|
{ 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.2",
|
|
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": {
|