@topolo/cli 0.4.5 → 0.4.8
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 +921 -631
- package/package.json +2 -6
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { platform as platform2 } from "os";
|
|
|
9
9
|
import { createInterface } from "readline/promises";
|
|
10
10
|
import { stdin as input } from "process";
|
|
11
11
|
|
|
12
|
-
//
|
|
12
|
+
// node_modules/@topolo/sdk/dist/index.js
|
|
13
13
|
function applyAuthHeaders(headers, credential) {
|
|
14
14
|
if (credential.kind === "api_key") {
|
|
15
15
|
headers.set("X-Api-Key", credential.apiKey);
|
|
@@ -75,6 +75,7 @@ var APP_API_URLS = {
|
|
|
75
75
|
auth: "https://auth.topolo.app",
|
|
76
76
|
backup: "https://backup.topolo.app",
|
|
77
77
|
books: "https://books.topolo.app",
|
|
78
|
+
brand: "https://brand.topolo.app",
|
|
78
79
|
bugfix: "https://bugfix.topolo.app",
|
|
79
80
|
bytes: "https://bytes.topolo.app",
|
|
80
81
|
calendar: "https://calendar.topolo.app",
|
|
@@ -255,6 +256,38 @@ var APPLICATIONS = {
|
|
|
255
256
|
}
|
|
256
257
|
]
|
|
257
258
|
},
|
|
259
|
+
"brand": {
|
|
260
|
+
"id": "brand",
|
|
261
|
+
"appDir": "TopoloBrand",
|
|
262
|
+
"name": "topolo-brand",
|
|
263
|
+
"packageName": "@topolo/topolo-brand",
|
|
264
|
+
"productionUrl": "https://brand.topolo.app",
|
|
265
|
+
"services": [
|
|
266
|
+
"brand"
|
|
267
|
+
],
|
|
268
|
+
"deployTargets": [
|
|
269
|
+
{
|
|
270
|
+
"name": "topolo-brand",
|
|
271
|
+
"kind": "worker",
|
|
272
|
+
"apiUrl": "https://brand.topolo.app",
|
|
273
|
+
"domains": [
|
|
274
|
+
"brand.topolo.app"
|
|
275
|
+
],
|
|
276
|
+
"routes": [
|
|
277
|
+
"brand.topolo.app"
|
|
278
|
+
],
|
|
279
|
+
"appId": "brand"
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
"name": "topolo-brand-api",
|
|
283
|
+
"kind": "worker",
|
|
284
|
+
"apiUrl": null,
|
|
285
|
+
"domains": [],
|
|
286
|
+
"routes": [],
|
|
287
|
+
"appId": null
|
|
288
|
+
}
|
|
289
|
+
]
|
|
290
|
+
},
|
|
258
291
|
"bugfix": {
|
|
259
292
|
"id": "bugfix",
|
|
260
293
|
"appDir": "TopoloBugFix",
|
|
@@ -2369,6 +2402,8 @@ var TopoloClient = class _TopoloClient {
|
|
|
2369
2402
|
requireConfirmForWrites;
|
|
2370
2403
|
timeoutMs;
|
|
2371
2404
|
fetchImpl;
|
|
2405
|
+
credentialProvider;
|
|
2406
|
+
credentialLoadInFlight = null;
|
|
2372
2407
|
refreshCredential;
|
|
2373
2408
|
refreshInFlight = null;
|
|
2374
2409
|
debug;
|
|
@@ -2396,6 +2431,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2396
2431
|
this.requireConfirmForWrites = options.requireConfirmForWrites !== false;
|
|
2397
2432
|
this.timeoutMs = options.timeoutMs ?? 3e4;
|
|
2398
2433
|
this.fetchImpl = options.fetch ?? fetch;
|
|
2434
|
+
this.credentialProvider = options.credentialProvider;
|
|
2399
2435
|
this.refreshCredential = options.refreshCredential;
|
|
2400
2436
|
this.debug = options.debug;
|
|
2401
2437
|
}
|
|
@@ -2418,6 +2454,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2418
2454
|
`Mutating ${method} requests require { confirm: true }. This guardrail prevents agents from issuing writes without an explicit human-in-the-loop acknowledgement.`
|
|
2419
2455
|
);
|
|
2420
2456
|
}
|
|
2457
|
+
await this.loadCurrentCredential();
|
|
2421
2458
|
const resolvedService = opts.baseUrl ? { serviceKey: normalizeLookupKey(opts.service), baseUrl: requireHttpOrigin(opts.baseUrl) } : await this.resolveRequestService(opts.service);
|
|
2422
2459
|
const baseUrl = resolvedService.baseUrl;
|
|
2423
2460
|
const url = new URL(opts.path, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
|
|
@@ -2529,6 +2566,19 @@ var TopoloClient = class _TopoloClient {
|
|
|
2529
2566
|
});
|
|
2530
2567
|
return parsed;
|
|
2531
2568
|
}
|
|
2569
|
+
async loadCurrentCredential() {
|
|
2570
|
+
if (!this.credentialProvider) return;
|
|
2571
|
+
this.credentialLoadInFlight ??= this.credentialProvider().then((credential) => {
|
|
2572
|
+
if (!credential) {
|
|
2573
|
+
throw new TopoloAuthError("The configured Topolo credential is no longer available.");
|
|
2574
|
+
}
|
|
2575
|
+
this.credential = credential;
|
|
2576
|
+
return credential;
|
|
2577
|
+
}).finally(() => {
|
|
2578
|
+
this.credentialLoadInFlight = null;
|
|
2579
|
+
});
|
|
2580
|
+
await this.credentialLoadInFlight;
|
|
2581
|
+
}
|
|
2532
2582
|
async refreshRejectedAccessToken(attemptedAccessToken) {
|
|
2533
2583
|
if (this.credential.kind === "access_token" && this.credential.accessToken !== attemptedAccessToken) {
|
|
2534
2584
|
return this.credential;
|
|
@@ -3197,6 +3247,7 @@ function normalizeActionCatalogEntry(item) {
|
|
|
3197
3247
|
permissionName: pickActionString(a.permission_name) ?? pickActionString(a.permissionName) ?? "",
|
|
3198
3248
|
requiredPermission: pickActionString(a.required_permission) ?? pickActionString(a.requiredPermission) ?? "",
|
|
3199
3249
|
authorizationMode: pickAuthorizationMode(a.authorization_mode ?? a.authorizationMode),
|
|
3250
|
+
allowedServices: stringArray(a.allowed_services ?? a.allowedServices),
|
|
3200
3251
|
inputSchema: plainObject(a.input_schema ?? a.inputSchema),
|
|
3201
3252
|
outputSchema: plainObject(a.output_schema ?? a.outputSchema),
|
|
3202
3253
|
agentContract: plainObject(a.agent_contract ?? a.agentContract),
|
|
@@ -3218,7 +3269,7 @@ function pickAgentAccess(value) {
|
|
|
3218
3269
|
return value === "auto" || value === "confirm" || value === "off" ? value : null;
|
|
3219
3270
|
}
|
|
3220
3271
|
function pickAuthorizationMode(value) {
|
|
3221
|
-
return value === "runtime"
|
|
3272
|
+
return value === "runtime" || value === "public" || value === "service" ? value : "static";
|
|
3222
3273
|
}
|
|
3223
3274
|
function plainObject(value) {
|
|
3224
3275
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
@@ -3312,6 +3363,7 @@ var CrmModule = class {
|
|
|
3312
3363
|
constructor(client) {
|
|
3313
3364
|
this.client = client;
|
|
3314
3365
|
}
|
|
3366
|
+
client;
|
|
3315
3367
|
async listContacts(options = {}) {
|
|
3316
3368
|
const res = await this.client.request({
|
|
3317
3369
|
service: "crm",
|
|
@@ -3357,6 +3409,7 @@ var IdentityModule = class {
|
|
|
3357
3409
|
constructor(client) {
|
|
3358
3410
|
this.client = client;
|
|
3359
3411
|
}
|
|
3412
|
+
client;
|
|
3360
3413
|
/** Returns the resolved user, organization, and permission set for the
|
|
3361
3414
|
* current credential. Equivalent to `TopoloClient.introspect()` — exposed
|
|
3362
3415
|
* on this module for symmetry with the other domain modules. */
|
|
@@ -3451,7 +3504,7 @@ function createTopolo(options) {
|
|
|
3451
3504
|
};
|
|
3452
3505
|
}
|
|
3453
3506
|
|
|
3454
|
-
//
|
|
3507
|
+
// node_modules/@topolo/sdk/dist/node/credential-store.js
|
|
3455
3508
|
import { mkdir, readFile, writeFile, chmod } from "fs/promises";
|
|
3456
3509
|
import { existsSync } from "fs";
|
|
3457
3510
|
import { homedir, platform } from "os";
|
|
@@ -3489,6 +3542,7 @@ var APP_API_URLS2 = {
|
|
|
3489
3542
|
auth: "https://auth.topolo.app",
|
|
3490
3543
|
backup: "https://backup.topolo.app",
|
|
3491
3544
|
books: "https://books.topolo.app",
|
|
3545
|
+
brand: "https://brand.topolo.app",
|
|
3492
3546
|
bugfix: "https://bugfix.topolo.app",
|
|
3493
3547
|
bytes: "https://bytes.topolo.app",
|
|
3494
3548
|
calendar: "https://calendar.topolo.app",
|
|
@@ -5738,6 +5792,33 @@ import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
|
5738
5792
|
import { constants } from "fs";
|
|
5739
5793
|
import { access } from "fs/promises";
|
|
5740
5794
|
import { relative, resolve } from "path";
|
|
5795
|
+
function normalizeHomePath(value) {
|
|
5796
|
+
const path4 = String(value || "/dashboard").trim();
|
|
5797
|
+
if (!path4.startsWith("/") || path4.startsWith("//") || /[?#]/.test(path4)) {
|
|
5798
|
+
throw new Error("Invalid home path. Use an absolute application path such as /dashboard.");
|
|
5799
|
+
}
|
|
5800
|
+
if (["/", "/login", "/auth/callback", "/getting-started"].includes(path4)) {
|
|
5801
|
+
throw new Error("Home path must be a protected product route, not a reserved entry route.");
|
|
5802
|
+
}
|
|
5803
|
+
return path4.replace(/\/$/, "") || "/dashboard";
|
|
5804
|
+
}
|
|
5805
|
+
function normalizeHomeLabel(value) {
|
|
5806
|
+
const label = String(value || "Dashboard").trim();
|
|
5807
|
+
if (!label || label.length > 80) {
|
|
5808
|
+
throw new Error("Home label must contain between 1 and 80 characters.");
|
|
5809
|
+
}
|
|
5810
|
+
return label;
|
|
5811
|
+
}
|
|
5812
|
+
function normalizeNavigationMode(value) {
|
|
5813
|
+
const mode = String(value || "sidebar").trim();
|
|
5814
|
+
if (mode === "sidebar" || mode === "topbar" || mode === "none") return mode;
|
|
5815
|
+
throw new Error("Invalid navigation mode. Use sidebar, topbar, or none.");
|
|
5816
|
+
}
|
|
5817
|
+
function normalizeWorkspaceSwitcherMode(value) {
|
|
5818
|
+
const mode = String(value || "required").trim();
|
|
5819
|
+
if (mode === "required" || mode === "optional" || mode === "off") return mode;
|
|
5820
|
+
throw new Error("Invalid workspace switcher mode. Use required, optional, or off.");
|
|
5821
|
+
}
|
|
5741
5822
|
function normalizeTarget(value) {
|
|
5742
5823
|
if (!value) return "platform";
|
|
5743
5824
|
if (value === "platform" || value === "developer") return value;
|
|
@@ -5880,18 +5961,60 @@ var STAGING_CLOUDFLARE_ACCOUNT_ID = "4f4e1c69a3830946f9fea7b1eb7531ac";
|
|
|
5880
5961
|
var DEVELOPMENT_CLOUDFLARE_ACCOUNT_ID = "0ebdeec262a245481a721fc4f9d6d798";
|
|
5881
5962
|
var CLOUDFLARE_COMPATIBILITY_DATE = "2026-05-05";
|
|
5882
5963
|
|
|
5964
|
+
// src/commands/app-scaffold-package-policy.ts
|
|
5965
|
+
var TOPOLO_PACKAGE_SCAFFOLD_POLICY = {
|
|
5966
|
+
"@topolo-io/api-key-management": "browser-baseline",
|
|
5967
|
+
"@topolo-io/app-funnel": "browser-baseline",
|
|
5968
|
+
"@topolo-io/app-launcher": "browser-baseline",
|
|
5969
|
+
"@topolo-io/app-manifest": "domain-opt-in",
|
|
5970
|
+
"@topolo-io/app-shell": "browser-baseline",
|
|
5971
|
+
"@topolo-io/auth-client": "browser-baseline",
|
|
5972
|
+
"@topolo-io/auth-middleware": "api-baseline",
|
|
5973
|
+
"@topolo-io/billing-manifest": "domain-opt-in",
|
|
5974
|
+
"@topolo-io/billing-ui": "domain-opt-in",
|
|
5975
|
+
"@topolo-io/bridge": "domain-opt-in",
|
|
5976
|
+
"@topolo-io/calendar": "domain-opt-in",
|
|
5977
|
+
"@topolo-io/chat-widget": "browser-baseline",
|
|
5978
|
+
"@topolo-io/data": "domain-opt-in",
|
|
5979
|
+
"@topolo-io/design-tokens": "browser-baseline",
|
|
5980
|
+
"@topolo-io/error-middleware": "api-baseline",
|
|
5981
|
+
"@topolo-io/mailbox": "domain-opt-in",
|
|
5982
|
+
"@topolo-io/mobile-error-middleware": "domain-opt-in",
|
|
5983
|
+
"@topolo-io/mobile-experience-contracts": "domain-opt-in",
|
|
5984
|
+
"@topolo-io/nexus-client": "api-baseline",
|
|
5985
|
+
"@topolo-io/node-canvas": "domain-opt-in",
|
|
5986
|
+
"@topolo-io/notifications": "browser-baseline",
|
|
5987
|
+
"@topolo-io/onboarding": "browser-baseline",
|
|
5988
|
+
"@topolo-io/platform-mcp": "tooling",
|
|
5989
|
+
"@topolo-io/resource-context": "browser-baseline",
|
|
5990
|
+
"@topolo-io/security-headers": "api-baseline",
|
|
5991
|
+
"@topolo-io/tour": "browser-baseline",
|
|
5992
|
+
"@topolo-io/ui-kit": "browser-baseline",
|
|
5993
|
+
"@topolo-io/widget-server": "api-baseline",
|
|
5994
|
+
"@topolo-io/worker-runtime": "api-baseline",
|
|
5995
|
+
"@topolo/cli": "tooling",
|
|
5996
|
+
"@topolo/login": "developer-baseline",
|
|
5997
|
+
"@topolo/mcp": "tooling",
|
|
5998
|
+
"@topolo/sdk": "api-baseline"
|
|
5999
|
+
};
|
|
6000
|
+
function packagesForScaffoldClass(scaffoldClass) {
|
|
6001
|
+
return Object.entries(TOPOLO_PACKAGE_SCAFFOLD_POLICY).filter(([, value]) => value === scaffoldClass).map(([name]) => name).sort();
|
|
6002
|
+
}
|
|
6003
|
+
var PLATFORM_BROWSER_TOPOLO_PACKAGES = packagesForScaffoldClass("browser-baseline");
|
|
6004
|
+
var PLATFORM_API_TOPOLO_PACKAGES = packagesForScaffoldClass("api-baseline");
|
|
6005
|
+
|
|
5883
6006
|
// src/commands/app-scaffold-package-template.ts
|
|
5884
6007
|
function packageJson(application, target, options) {
|
|
5885
6008
|
const dependencies = {};
|
|
5886
6009
|
const devDependencies = {
|
|
5887
6010
|
"@types/node": "^20.12.0",
|
|
5888
6011
|
typescript: "^5.4.0",
|
|
5889
|
-
vite: "^5.4.0"
|
|
6012
|
+
...options.browser ? { vite: "^5.4.0" } : {}
|
|
5890
6013
|
};
|
|
5891
6014
|
const scripts = {
|
|
5892
|
-
dev: target === "platform" ? "vite --config apps/web/vite.config.ts --host 0.0.0.0" : "vite --host 0.0.0.0",
|
|
5893
|
-
build: target === "platform" ? "vite build --config apps/web/vite.config.ts" : "vite build",
|
|
5894
|
-
preview: target === "platform" ? "vite preview --config apps/web/vite.config.ts --host 0.0.0.0" : "vite preview --host 0.0.0.0",
|
|
6015
|
+
dev: target === "platform" ? options.browser ? "vite --config apps/web/vite.config.ts --host 0.0.0.0" : "wrangler dev --config apps/api/wrangler.toml" : "vite --host 0.0.0.0",
|
|
6016
|
+
build: target === "platform" ? options.browser ? "vite build --config apps/web/vite.config.ts" : 'wrangler deploy --config apps/api/wrangler.toml --env="" --dry-run --outdir dist' : "vite build",
|
|
6017
|
+
preview: target === "platform" ? options.browser ? "vite preview --config apps/web/vite.config.ts --host 0.0.0.0" : "wrangler dev --config apps/api/wrangler.toml" : "vite preview --host 0.0.0.0",
|
|
5895
6018
|
typecheck: "tsc --noEmit"
|
|
5896
6019
|
};
|
|
5897
6020
|
if (options.browser) {
|
|
@@ -5901,29 +6024,20 @@ function packageJson(application, target, options) {
|
|
|
5901
6024
|
devDependencies["@types/react-dom"] = "^19.2.2";
|
|
5902
6025
|
devDependencies["@vitejs/plugin-react"] = "^4.7.0";
|
|
5903
6026
|
if (target === "platform") {
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
dependencies["@topolo-io/app-shell"] = topoloPackageVersion("@topolo-io/app-shell", target);
|
|
5908
|
-
dependencies["@topolo-io/design-tokens"] = topoloPackageVersion("@topolo-io/design-tokens", target);
|
|
5909
|
-
dependencies["@topolo-io/onboarding"] = topoloPackageVersion("@topolo-io/onboarding", target);
|
|
5910
|
-
dependencies["@topolo-io/tour"] = topoloPackageVersion("@topolo-io/tour", target);
|
|
5911
|
-
dependencies["@topolo-io/ui-kit"] = topoloPackageVersion("@topolo-io/ui-kit", target);
|
|
6027
|
+
for (const packageName of PLATFORM_BROWSER_TOPOLO_PACKAGES) {
|
|
6028
|
+
dependencies[packageName] = topoloPackageVersion(packageName, target);
|
|
6029
|
+
}
|
|
5912
6030
|
dependencies["react-router-dom"] = "^7.6.2";
|
|
5913
6031
|
} else {
|
|
5914
6032
|
dependencies["@topolo/login"] = topoloPackageVersion("@topolo/login", target);
|
|
5915
6033
|
}
|
|
5916
6034
|
}
|
|
5917
6035
|
if (options.api) {
|
|
5918
|
-
devDependencies["@cloudflare/workers-types"] = "^
|
|
6036
|
+
devDependencies["@cloudflare/workers-types"] = "^5.20260716.1";
|
|
5919
6037
|
if (target === "platform") {
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
dependencies["@topolo-io/security-headers"] = topoloPackageVersion("@topolo-io/security-headers", target);
|
|
5924
|
-
dependencies["@topolo-io/resource-context"] = topoloPackageVersion("@topolo-io/resource-context", target);
|
|
5925
|
-
dependencies["@topolo-io/data"] = topoloPackageVersion("@topolo-io/data", target);
|
|
5926
|
-
dependencies["@topolo/sdk"] = topoloPackageVersion("@topolo/sdk", target);
|
|
6038
|
+
for (const packageName of PLATFORM_API_TOPOLO_PACKAGES) {
|
|
6039
|
+
dependencies[packageName] = topoloPackageVersion(packageName, target);
|
|
6040
|
+
}
|
|
5927
6041
|
dependencies.hono = "^4";
|
|
5928
6042
|
dependencies.zod = "^4.4.3";
|
|
5929
6043
|
scripts.deploy = `node -e 'console.error("\\nX Bare deploy is disabled. Use deploy:staging or deploy:production.\\n"); process.exit(1)'`;
|
|
@@ -5942,7 +6056,7 @@ function packageJson(application, target, options) {
|
|
|
5942
6056
|
if (testDirs.length > 0) {
|
|
5943
6057
|
const chain = (withCoverage) => testDirs.map((dir, index) => {
|
|
5944
6058
|
const enter = index === 0 ? `cd ${dir}` : `cd ../${dir.split("/").pop()}`;
|
|
5945
|
-
return `${enter} && vitest run --config vitest.config.ts
|
|
6059
|
+
return `${enter} && vitest run --config vitest.config.ts${withCoverage ? " --coverage" : ""}`;
|
|
5946
6060
|
}).join(" && ");
|
|
5947
6061
|
scripts.test = chain(false);
|
|
5948
6062
|
scripts.coverage = chain(true);
|
|
@@ -5957,13 +6071,18 @@ function packageJson(application, target, options) {
|
|
|
5957
6071
|
}
|
|
5958
6072
|
}
|
|
5959
6073
|
if (target === "platform" && hasScaffoldResources(options.resources)) {
|
|
5960
|
-
|
|
6074
|
+
const deployChecks = [
|
|
6075
|
+
...options.api ? ["wrangler deploy --config apps/api/wrangler.toml --env staging --dry-run --outdir .wrangler/api-build"] : [],
|
|
6076
|
+
...options.browser ? ["wrangler deploy --config apps/web/wrangler.toml --env staging --dry-run --outdir .wrangler/web-build"] : []
|
|
6077
|
+
];
|
|
6078
|
+
scripts["check:deploy-config"] = deployChecks.join(" && ");
|
|
5961
6079
|
}
|
|
5962
6080
|
return `${json({
|
|
5963
6081
|
name: application.packageName,
|
|
5964
6082
|
version: "0.1.0",
|
|
5965
6083
|
private: true,
|
|
5966
6084
|
type: "module",
|
|
6085
|
+
...target === "platform" ? { packageManager: "pnpm@10.17.1" } : {},
|
|
5967
6086
|
scripts,
|
|
5968
6087
|
dependencies,
|
|
5969
6088
|
devDependencies
|
|
@@ -5977,29 +6096,8 @@ function topoloPackageVersion(packageName, target) {
|
|
|
5977
6096
|
if (packageName === "@topolo/sdk") return "^0.8.1";
|
|
5978
6097
|
return "^0.1.0";
|
|
5979
6098
|
}
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
"@topolo/login": "^0.1.0",
|
|
5983
|
-
"@topolo-io/app-funnel": "^0.1.6",
|
|
5984
|
-
"@topolo-io/app-launcher": "^0.1.15",
|
|
5985
|
-
"@topolo-io/app-shell": "^0.5.54",
|
|
5986
|
-
"@topolo-io/auth-client": "^1.2.1",
|
|
5987
|
-
"@topolo-io/auth-middleware": "^0.4.0",
|
|
5988
|
-
"@topolo-io/error-middleware": "^0.6.0",
|
|
5989
|
-
"@topolo-io/security-headers": "^0.2.2",
|
|
5990
|
-
"@topolo-io/resource-context": "^0.3.0",
|
|
5991
|
-
"@topolo-io/data": "^0.1.2",
|
|
5992
|
-
"@topolo-io/worker-runtime": "^0.1.11",
|
|
5993
|
-
"@topolo-io/design-tokens": "^0.1.8",
|
|
5994
|
-
"@topolo-io/onboarding": "^0.9.5",
|
|
5995
|
-
"@topolo-io/tour": "^0.3.4",
|
|
5996
|
-
"@topolo-io/ui-kit": "^0.4.233"
|
|
5997
|
-
};
|
|
5998
|
-
const version = platformPackageVersions[packageName];
|
|
5999
|
-
if (!version) {
|
|
6000
|
-
throw new Error(`Unknown Topolo platform package: ${packageName}`);
|
|
6001
|
-
}
|
|
6002
|
-
return version;
|
|
6099
|
+
if (packageName.startsWith("@topolo/") || packageName.startsWith("@topolo-io/")) return "latest";
|
|
6100
|
+
throw new Error(`Unknown Topolo platform package: ${packageName}`);
|
|
6003
6101
|
}
|
|
6004
6102
|
function hasScaffoldResources(resources) {
|
|
6005
6103
|
return resources.d1 || resources.r2 || resources.queue || resources.workflow || resources.containerProcessing;
|
|
@@ -6074,10 +6172,11 @@ function readme(application, target, options) {
|
|
|
6074
6172
|
"## Development",
|
|
6075
6173
|
"",
|
|
6076
6174
|
"```bash",
|
|
6077
|
-
"npm install",
|
|
6078
|
-
"npm run typecheck",
|
|
6079
|
-
"npm run build",
|
|
6080
|
-
"npm run dev",
|
|
6175
|
+
target === "platform" ? "pnpm install" : "npm install",
|
|
6176
|
+
target === "platform" ? "pnpm run typecheck" : "npm run typecheck",
|
|
6177
|
+
target === "platform" ? "pnpm run coverage" : "npm run build",
|
|
6178
|
+
target === "platform" ? "pnpm run build" : "npm run dev",
|
|
6179
|
+
...target === "platform" ? ["pnpm run dev"] : [],
|
|
6081
6180
|
"```",
|
|
6082
6181
|
""
|
|
6083
6182
|
];
|
|
@@ -6090,11 +6189,15 @@ function readme(application, target, options) {
|
|
|
6090
6189
|
"- `@topolo-io/app-launcher` provides shared app switching and handoff helpers.",
|
|
6091
6190
|
"- `@topolo-io/ui-kit` provides primitive account, command palette, icon, transition, and bug reporter UI.",
|
|
6092
6191
|
"- `@topolo-io/auth-client` owns browser auth, callback exchange, token refresh, and logout.",
|
|
6093
|
-
"-
|
|
6192
|
+
"- `@topolo-io/api-key-management`, `@topolo-io/chat-widget`, `@topolo-io/notifications`, and `@topolo-io/resource-context` complete the AppShell capability baseline.",
|
|
6193
|
+
"- Every Topolo-owned browser dependency is emitted at `latest`; product-domain packages are added only when the app actually adopts that domain.",
|
|
6194
|
+
"- `.npmrc` routes private `@topolo-io` packages to GitHub Packages without storing credentials; public `@topolo` packages remain on npm. Local and CI environments must provide a GitHub Packages token.",
|
|
6195
|
+
"- Generated first-party browser apps start on the shared landing page at `/`, use the shared login page at `/login`, and complete sign-in on `/auth/callback` before entering the configured authenticated home.",
|
|
6196
|
+
"- The workspace selector renders in the top utility bar; Getting Started is an account-menu action and is not part of the left product rail.",
|
|
6094
6197
|
"- `vite.config.ts` includes the standard React plugin plus React runtime dedupe so linked Topolo packages resolve one browser React runtime."
|
|
6095
6198
|
);
|
|
6096
6199
|
if (options.api) {
|
|
6097
|
-
lines.push("- `@topolo-io/worker-runtime`
|
|
6200
|
+
lines.push("- `@topolo-io/worker-runtime` plus its auth, error, Nexus, security-header, and widget-server suite own Worker validation, request ids, usage monitoring, widget responses, and BugFix reporting.");
|
|
6098
6201
|
}
|
|
6099
6202
|
if (hasScaffoldResources(options.resources)) {
|
|
6100
6203
|
lines.push(
|
|
@@ -6297,6 +6400,15 @@ function gitignore() {
|
|
|
6297
6400
|
""
|
|
6298
6401
|
].join("\n");
|
|
6299
6402
|
}
|
|
6403
|
+
function npmrc() {
|
|
6404
|
+
return [
|
|
6405
|
+
"@topolo-io:registry=https://npm.pkg.github.com",
|
|
6406
|
+
"strict-peer-dependencies=true",
|
|
6407
|
+
"minimum-release-age-exclude[]=@topolo-io/*",
|
|
6408
|
+
"minimum-release-age-exclude[]=@topolo/*",
|
|
6409
|
+
""
|
|
6410
|
+
].join("\n");
|
|
6411
|
+
}
|
|
6300
6412
|
function envDts(api) {
|
|
6301
6413
|
const lines = [
|
|
6302
6414
|
'/// <reference types="vite/client" />',
|
|
@@ -6557,46 +6669,6 @@ function stylesCss() {
|
|
|
6557
6669
|
"textarea {",
|
|
6558
6670
|
" font: inherit;",
|
|
6559
6671
|
"}",
|
|
6560
|
-
"",
|
|
6561
|
-
".topolo-starter-workspace {",
|
|
6562
|
-
" display: grid;",
|
|
6563
|
-
" gap: 18px;",
|
|
6564
|
-
" min-width: 0;",
|
|
6565
|
-
" width: auto;",
|
|
6566
|
-
" max-width: min(100%, calc(100vw - 56px));",
|
|
6567
|
-
" overflow-x: clip;",
|
|
6568
|
-
"}",
|
|
6569
|
-
"",
|
|
6570
|
-
".topolo-starter-card-stack {",
|
|
6571
|
-
" display: grid;",
|
|
6572
|
-
" gap: 18px;",
|
|
6573
|
-
" min-width: 0;",
|
|
6574
|
-
"}",
|
|
6575
|
-
"",
|
|
6576
|
-
".topolo-starter-workspace > *,",
|
|
6577
|
-
".topolo-starter-card-stack > * {",
|
|
6578
|
-
" min-width: 0;",
|
|
6579
|
-
"}",
|
|
6580
|
-
"",
|
|
6581
|
-
".topolo-starter-card h2 {",
|
|
6582
|
-
" margin: 0 0 12px;",
|
|
6583
|
-
" font-size: 24px;",
|
|
6584
|
-
" line-height: 1.2;",
|
|
6585
|
-
"}",
|
|
6586
|
-
"",
|
|
6587
|
-
".topolo-starter-card p {",
|
|
6588
|
-
" margin: 0;",
|
|
6589
|
-
" color: hsl(var(--muted-foreground));",
|
|
6590
|
-
" line-height: 1.55;",
|
|
6591
|
-
"}",
|
|
6592
|
-
"",
|
|
6593
|
-
".topolo-starter-card {",
|
|
6594
|
-
" border: 1px solid hsl(var(--border));",
|
|
6595
|
-
" border-radius: var(--radius);",
|
|
6596
|
-
" padding: 20px;",
|
|
6597
|
-
" background: hsl(var(--card));",
|
|
6598
|
-
" color: hsl(var(--card-foreground));",
|
|
6599
|
-
"}",
|
|
6600
6672
|
""
|
|
6601
6673
|
].join("\n");
|
|
6602
6674
|
}
|
|
@@ -6604,450 +6676,429 @@ function stylesCss() {
|
|
|
6604
6676
|
// src/commands/app-scaffold-app-template.ts
|
|
6605
6677
|
function appTsx(application, target, options) {
|
|
6606
6678
|
if (target === "developer") return developerAppTsx(application);
|
|
6607
|
-
return platformAppTsx(
|
|
6608
|
-
}
|
|
6609
|
-
function platformAppTsx(
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
|
|
6679
|
+
return platformAppTsx();
|
|
6680
|
+
}
|
|
6681
|
+
function platformAppTsx() {
|
|
6682
|
+
return `import { useEffect, useMemo, useState } from 'react';
|
|
6683
|
+
import { LandingPage } from '@topolo-io/app-funnel';
|
|
6684
|
+
import { LoginPage, TopoloAppLoadingScaffold, TopoloAppRoot, TopoloAuthFlowScreen, createTopoloAuth } from '@topolo-io/app-shell';
|
|
6685
|
+
import { setupBugReporterErrorCapture } from '@topolo-io/ui-kit';
|
|
6686
|
+
import AppErrorBoundary from './AppErrorBoundary';
|
|
6687
|
+
import { APP_NAME, AUTH_URL, HOME_PATH, LANDING_CONFIG, LOGIN_CONFIG, NAVIGATION_MODE, WORKSPACE_RESOURCE } from './app-config';
|
|
6688
|
+
import { createAppRoutes } from './app-routes';
|
|
6689
|
+
import { resolveAppId } from './app-identity';
|
|
6690
|
+
|
|
6691
|
+
setupBugReporterErrorCapture();
|
|
6692
|
+
|
|
6693
|
+
export default function App() {
|
|
6694
|
+
return (
|
|
6695
|
+
<AppErrorBoundary>
|
|
6696
|
+
<CanonicalAppRoot />
|
|
6697
|
+
</AppErrorBoundary>
|
|
6698
|
+
);
|
|
6699
|
+
}
|
|
6700
|
+
|
|
6701
|
+
export function CanonicalAppRoot() {
|
|
6702
|
+
const [appId, setAppId] = useState('');
|
|
6703
|
+
const [appIdentityError, setAppIdentityError] = useState('');
|
|
6704
|
+
|
|
6705
|
+
useEffect(() => {
|
|
6706
|
+
let active = true;
|
|
6707
|
+
setAppIdentityError('');
|
|
6708
|
+
void resolveAppId()
|
|
6709
|
+
.then((nextAppId) => {
|
|
6710
|
+
if (active) setAppId(nextAppId);
|
|
6711
|
+
})
|
|
6712
|
+
.catch((error) => {
|
|
6713
|
+
if (active) setAppIdentityError(errorMessage(error));
|
|
6714
|
+
});
|
|
6715
|
+
return () => {
|
|
6716
|
+
active = false;
|
|
6717
|
+
};
|
|
6718
|
+
}, []);
|
|
6719
|
+
|
|
6720
|
+
const auth = useMemo(() => appId ? createTopoloAuth({
|
|
6721
|
+
authServiceUrl: AUTH_URL,
|
|
6722
|
+
appId,
|
|
6723
|
+
devSessionKey: \`\${appId}_session\`,
|
|
6724
|
+
callbackPath: '/auth/callback',
|
|
6725
|
+
sessionStorageRestoreEnabled: import.meta.env.DEV,
|
|
6726
|
+
debug: import.meta.env.DEV,
|
|
6727
|
+
}) : null, [appId]);
|
|
6728
|
+
const routes = useMemo(() => appId ? createAppRoutes(appId) : [], [appId]);
|
|
6729
|
+
|
|
6730
|
+
if (appIdentityError) {
|
|
6731
|
+
return (
|
|
6732
|
+
<TopoloAuthFlowScreen
|
|
6733
|
+
appName={APP_NAME}
|
|
6734
|
+
status="error"
|
|
6735
|
+
title="App identity unavailable"
|
|
6736
|
+
message={appIdentityError}
|
|
6737
|
+
actionLabel="Retry"
|
|
6738
|
+
onAction={() => window.location.reload()}
|
|
6739
|
+
/>
|
|
6740
|
+
);
|
|
6741
|
+
}
|
|
6742
|
+
|
|
6743
|
+
if (!auth) {
|
|
6744
|
+
return (
|
|
6745
|
+
<TopoloAppLoadingScaffold
|
|
6746
|
+
appId={appId}
|
|
6747
|
+
appName={APP_NAME}
|
|
6748
|
+
layout={{ shell: NAVIGATION_MODE, content: 'workspace' }}
|
|
6749
|
+
/>
|
|
6750
|
+
);
|
|
6751
|
+
}
|
|
6752
|
+
|
|
6753
|
+
return (
|
|
6754
|
+
<TopoloAppRoot
|
|
6755
|
+
appName={APP_NAME}
|
|
6756
|
+
appId={appId}
|
|
6757
|
+
auth={auth}
|
|
6758
|
+
routes={routes}
|
|
6759
|
+
home={HOME_PATH}
|
|
6760
|
+
workspaceResource={WORKSPACE_RESOURCE}
|
|
6761
|
+
gettingStarted={{ path: '/getting-started' }}
|
|
6762
|
+
publicLanding={
|
|
6763
|
+
<LandingPage
|
|
6764
|
+
appId={appId}
|
|
6765
|
+
authApiUrl={AUTH_URL}
|
|
6766
|
+
config={LANDING_CONFIG}
|
|
6767
|
+
onCTAClick={() => window.location.assign('/login')}
|
|
6768
|
+
/>
|
|
6769
|
+
}
|
|
6770
|
+
loginElement={
|
|
6771
|
+
<LoginPage
|
|
6772
|
+
appId={appId}
|
|
6773
|
+
authApiUrl={AUTH_URL}
|
|
6774
|
+
config={LOGIN_CONFIG}
|
|
6775
|
+
onLogin={async (provider) => {
|
|
6776
|
+
await auth.login(provider || 'topolo');
|
|
6777
|
+
}}
|
|
6778
|
+
onPasswordLogin={async (request) => {
|
|
6779
|
+
const result = await auth.loginWithPassword(request);
|
|
6780
|
+
if (!result.authenticated) return result;
|
|
6781
|
+
window.dispatchEvent(new CustomEvent('auth:session-updated', { detail: { tokens: result.tokens, user: result.user } }));
|
|
6782
|
+
return undefined;
|
|
6783
|
+
}}
|
|
6784
|
+
/>
|
|
6785
|
+
}
|
|
6786
|
+
loadingFallback={({ loading }) => (
|
|
6787
|
+
<TopoloAppLoadingScaffold
|
|
6788
|
+
appId={appId}
|
|
6789
|
+
appName={APP_NAME}
|
|
6790
|
+
layout={{ shell: loading?.shell || NAVIGATION_MODE, content: loading?.content || 'workspace' }}
|
|
6791
|
+
/>
|
|
6792
|
+
)}
|
|
6793
|
+
shell={{
|
|
6794
|
+
appId,
|
|
6795
|
+
authUrl: AUTH_URL,
|
|
6796
|
+
navigationMode: NAVIGATION_MODE,
|
|
6797
|
+
title: APP_NAME,
|
|
6798
|
+
commandPlaceholder: \`Search \${APP_NAME}\`,
|
|
6799
|
+
bugReporterAppName: APP_NAME,
|
|
6800
|
+
enableCommandPalette: true,
|
|
6801
|
+
enableBugReporter: true,
|
|
6802
|
+
}}
|
|
6803
|
+
/>
|
|
6804
|
+
);
|
|
6805
|
+
}
|
|
6806
|
+
|
|
6807
|
+
export function errorMessage(error: unknown): string {
|
|
6808
|
+
if (error instanceof Error && error.message) return error.message;
|
|
6809
|
+
return 'Unexpected authentication error.';
|
|
6810
|
+
}
|
|
6811
|
+
`;
|
|
6812
|
+
}
|
|
6813
|
+
function platformAppConfigTs(application, experience) {
|
|
6814
|
+
const appName = toTitle(application.name);
|
|
6815
|
+
const workspaceResource = experience.workspaceSwitcher === "off" ? "{ enabled: false }" : `{ enabled: true, required: ${experience.workspaceSwitcher === "required"}, label: '${escapeSingleQuoted(experience.workspaceLabel)}' }`;
|
|
6816
|
+
return `import { SCAFFOLD_APP_NAME, getBrowserAuthBaseUrl } from './app-identity';
|
|
6817
|
+
|
|
6818
|
+
export const APP_NAME = SCAFFOLD_APP_NAME;
|
|
6819
|
+
export const AUTH_URL = getBrowserAuthBaseUrl();
|
|
6820
|
+
export const HOME_PATH = '${escapeSingleQuoted(experience.homePath)}';
|
|
6821
|
+
export const HOME_LABEL = '${escapeSingleQuoted(experience.homeLabel)}';
|
|
6822
|
+
export const NAVIGATION_MODE = '${experience.navigationMode}' as const;
|
|
6823
|
+
export const WORKSPACE_RESOURCE = ${workspaceResource} as const;
|
|
6824
|
+
export const PRODUCT_TAGLINE = '${escapeSingleQuoted(experience.tagline)}';
|
|
6825
|
+
|
|
6826
|
+
export const LANDING_CONFIG = {
|
|
6827
|
+
app_name: APP_NAME,
|
|
6828
|
+
app_tagline: PRODUCT_TAGLINE,
|
|
6829
|
+
hero_title: APP_NAME,
|
|
6830
|
+
hero_subtitle: PRODUCT_TAGLINE,
|
|
6831
|
+
hero_cta_text: 'Sign in',
|
|
6832
|
+
hero_cta_url: '/login',
|
|
6833
|
+
cta_title: 'Ready to continue?',
|
|
6834
|
+
cta_subtitle: 'Sign in with your Topolo account.',
|
|
6835
|
+
};
|
|
6836
|
+
|
|
6837
|
+
export const LOGIN_CONFIG = {
|
|
6838
|
+
app_name: APP_NAME,
|
|
6839
|
+
app_tagline: PRODUCT_TAGLINE,
|
|
6840
|
+
hero_title: \`Sign in to \${APP_NAME}\`,
|
|
6841
|
+
hero_subtitle: PRODUCT_TAGLINE,
|
|
6842
|
+
authenticated_home_path: HOME_PATH,
|
|
6843
|
+
};
|
|
6844
|
+
|
|
6845
|
+
export const APP_DISPLAY_NAME = '${escapeSingleQuoted(appName)}';
|
|
6846
|
+
`;
|
|
6847
|
+
}
|
|
6848
|
+
function platformAppRoutesTsx(experience) {
|
|
6849
|
+
const nav = experience.navigationMode === "none" ? "" : " nav: { id: 'home', label: HOME_LABEL },\n";
|
|
6850
|
+
return `import type { AppRoute } from '@topolo-io/app-shell';
|
|
6851
|
+
import { HOME_LABEL, HOME_PATH, NAVIGATION_MODE } from './app-config';
|
|
6852
|
+
import GettingStartedRoute from './routes/GettingStartedRoute';
|
|
6853
|
+
import HomeRoute from './routes/HomeRoute';
|
|
6854
|
+
|
|
6855
|
+
export function createAppRoutes(appId: string): AppRoute[] {
|
|
6616
6856
|
return [
|
|
6617
|
-
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
'
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
" const result = await auth.loginWithPassword(request);",
|
|
6754
|
-
" if (!result.authenticated) return result;",
|
|
6755
|
-
" window.dispatchEvent(new CustomEvent('auth:session-updated', { detail: { tokens: result.tokens, user: result.user } }));",
|
|
6756
|
-
" return undefined;",
|
|
6757
|
-
" }}",
|
|
6758
|
-
" />",
|
|
6759
|
-
" }",
|
|
6760
|
-
" loadingFallback={({ loading }) => (",
|
|
6761
|
-
" <TopoloAppLoadingScaffold",
|
|
6762
|
-
" appId={appId}",
|
|
6763
|
-
" appName={APP_NAME}",
|
|
6764
|
-
" layout={{ shell: loading?.shell || 'topbar', content: loading?.content || 'workspace' }}",
|
|
6765
|
-
" />",
|
|
6766
|
-
" )}",
|
|
6767
|
-
" shell={{",
|
|
6768
|
-
" appId: appId,",
|
|
6769
|
-
" authUrl: AUTH_URL,",
|
|
6770
|
-
" navigationMode: 'topbar',",
|
|
6771
|
-
" title: APP_NAME,",
|
|
6772
|
-
" commandPlaceholder: `Search ${APP_NAME}`,",
|
|
6773
|
-
" bugReporterAppName: APP_NAME,",
|
|
6774
|
-
" enableCommandPalette: true,",
|
|
6775
|
-
" enableBugReporter: true,",
|
|
6776
|
-
" }}",
|
|
6777
|
-
" />",
|
|
6778
|
-
" );",
|
|
6779
|
-
"}",
|
|
6780
|
-
"",
|
|
6781
|
-
"function ScaffoldWorkspace() {",
|
|
6782
|
-
" const { user } = useAuth();",
|
|
6783
|
-
" const openWorkspace = useCallback(() => {",
|
|
6784
|
-
" window.history.pushState({}, '', '/workspace');",
|
|
6785
|
-
" window.dispatchEvent(new PopStateEvent('popstate'));",
|
|
6786
|
-
" }, []);",
|
|
6787
|
-
" const openGettingStarted = useCallback(() => {",
|
|
6788
|
-
" window.history.pushState({}, '', '/getting-started');",
|
|
6789
|
-
" window.dispatchEvent(new PopStateEvent('popstate'));",
|
|
6790
|
-
" }, []);",
|
|
6791
|
-
" return (",
|
|
6792
|
-
' <div className="topolo-starter-workspace">',
|
|
6793
|
-
' <section className="topolo-starter-card" data-tour="workspace-card">',
|
|
6794
|
-
` <h2>${baselineHeading}</h2>`,
|
|
6795
|
-
" <p>",
|
|
6796
|
-
` ${baselineDescription}`,
|
|
6797
|
-
" </p>",
|
|
6798
|
-
" </section>",
|
|
6799
|
-
" <IntroTour",
|
|
6800
|
-
" appName={APP_NAME}",
|
|
6801
|
-
" role={user?.role}",
|
|
6802
|
-
" onOpenGettingStarted={openGettingStarted}",
|
|
6803
|
-
" extraSteps={[",
|
|
6804
|
-
" {",
|
|
6805
|
-
" id: 'workspace-card',",
|
|
6806
|
-
` target: '[data-tour="workspace-card"]',`,
|
|
6807
|
-
" title: 'Your workspace',",
|
|
6808
|
-
" content: 'Everything you build renders here, inside the shared Topolo shell.',",
|
|
6809
|
-
" placement: 'bottom' as const,",
|
|
6810
|
-
" beforeStep: () => openWorkspace(),",
|
|
6811
|
-
" },",
|
|
6812
|
-
" ]}",
|
|
6813
|
-
" />",
|
|
6814
|
-
" </div>",
|
|
6815
|
-
" );",
|
|
6816
|
-
"}",
|
|
6817
|
-
"",
|
|
6818
|
-
"function ScaffoldGettingStartedRoute({ appId }: { appId: string }) {",
|
|
6819
|
-
" const { user, tokens } = useAuth();",
|
|
6820
|
-
" return (",
|
|
6821
|
-
" <ScaffoldGettingStarted",
|
|
6822
|
-
" appId={appId}",
|
|
6823
|
-
" organizationId={String((user as any)?.organization?.id ?? user?.orgId ?? '')}",
|
|
6824
|
-
" role={user?.role}",
|
|
6825
|
-
" getAccessToken={() => tokens?.accessToken ?? null}",
|
|
6826
|
-
" onOpenWorkspace={() => window.location.assign('/workspace')}",
|
|
6827
|
-
" />",
|
|
6828
|
-
" );",
|
|
6829
|
-
"}",
|
|
6830
|
-
"",
|
|
6831
|
-
"function ScaffoldGettingStarted(props: {",
|
|
6832
|
-
" appId: string;",
|
|
6833
|
-
" organizationId: string;",
|
|
6834
|
-
" role?: string;",
|
|
6835
|
-
" getAccessToken: () => string | null;",
|
|
6836
|
-
" onOpenWorkspace: () => void;",
|
|
6837
|
-
"}) {",
|
|
6838
|
-
" const { appId, organizationId, role, getAccessToken, onOpenWorkspace } = props;",
|
|
6839
|
-
" const steps: OnboardingGuideStep[] = [",
|
|
6840
|
-
" {",
|
|
6841
|
-
" id: 'explore-workspace',",
|
|
6842
|
-
" title: 'Explore your workspace',",
|
|
6843
|
-
" description:",
|
|
6844
|
-
" 'Your workspace lives in the Topolo shell \u2014 the launcher, command palette, and account menu are all one click away.',",
|
|
6845
|
-
" actionLabel: 'Open workspace',",
|
|
6846
|
-
" onAction: onOpenWorkspace,",
|
|
6847
|
-
" },",
|
|
6848
|
-
" {",
|
|
6849
|
-
" id: 'discover-platform',",
|
|
6850
|
-
" title: 'Move between your Topolo apps',",
|
|
6851
|
-
" description:",
|
|
6852
|
-
" 'Use the launcher to switch between your Topolo apps, and the command palette (Cmd/Ctrl-K) to jump anywhere fast.',",
|
|
6853
|
-
" optional: true,",
|
|
6854
|
-
" },",
|
|
6855
|
-
" {",
|
|
6856
|
-
" id: 'build-feature',",
|
|
6857
|
-
" title: 'Make it your own',",
|
|
6858
|
-
" description:",
|
|
6859
|
-
" 'Open the account menu to adjust your settings and theme \u2014 your preferences stay in sync across every Topolo app.',",
|
|
6860
|
-
" },",
|
|
6861
|
-
" ];",
|
|
6862
|
-
" return (",
|
|
6863
|
-
' <div className="topolo-starter-workspace">',
|
|
6864
|
-
" <GettingStarted",
|
|
6865
|
-
" authApiBaseUrl={`${AUTH_URL}/api`}",
|
|
6866
|
-
" organizationId={organizationId}",
|
|
6867
|
-
" appId={appId}",
|
|
6868
|
-
" getAccessToken={getAccessToken}",
|
|
6869
|
-
" role={role || undefined}",
|
|
6870
|
-
" steps={steps}",
|
|
6871
|
-
" eyebrow={`Welcome to ${APP_NAME}`}",
|
|
6872
|
-
" title={`Get started with ${APP_NAME}`}",
|
|
6873
|
-
' subtitle="A short checklist to orient you in your new Topolo workspace."',
|
|
6874
|
-
' completeLabel="Mark done"',
|
|
6875
|
-
' dismissLabel="Skip for now"',
|
|
6876
|
-
" completedContent={`You're all set \u2014 ${APP_NAME} is ready to build on.`}",
|
|
6877
|
-
" completeAction={{ label: 'Go to workspace', onClick: onOpenWorkspace }}",
|
|
6878
|
-
" onDismiss={onOpenWorkspace}",
|
|
6879
|
-
" />",
|
|
6880
|
-
" </div>",
|
|
6881
|
-
" );",
|
|
6882
|
-
"}",
|
|
6883
|
-
"",
|
|
6884
|
-
"function errorMessage(error: unknown): string {",
|
|
6885
|
-
" if (error instanceof Error && error.message) return error.message;",
|
|
6886
|
-
" return 'Unexpected authentication error.';",
|
|
6887
|
-
"}",
|
|
6888
|
-
"",
|
|
6889
|
-
"// Minimal error boundary so a render-time crash falls back to a recoverable surface",
|
|
6890
|
-
"// instead of a blank page. The shared TopoloAppShell BugReporter still has access to",
|
|
6891
|
-
"// the captured stack via setupBugReporterErrorCapture().",
|
|
6892
|
-
"class AppErrorBoundary extends Component<{ children: ReactNode }, { error: Error | null }> {",
|
|
6893
|
-
" state = { error: null as Error | null };",
|
|
6894
|
-
" static getDerivedStateFromError(error: Error) {",
|
|
6895
|
-
" return { error };",
|
|
6896
|
-
" }",
|
|
6897
|
-
" componentDidCatch(error: Error, info: ErrorInfo) {",
|
|
6898
|
-
" // Surface to the platform error pipeline. setupBugReporterErrorCapture() also",
|
|
6899
|
-
" // records this via the console.error hook for BugReporter attachments.",
|
|
6900
|
-
" console.error('[' + APP_NAME + '] render error:', error, info.componentStack);",
|
|
6901
|
-
" }",
|
|
6902
|
-
" render() {",
|
|
6903
|
-
" if (!this.state.error) return this.props.children;",
|
|
6904
|
-
" return (",
|
|
6905
|
-
" <TopoloAuthFlowScreen",
|
|
6906
|
-
" appName={APP_NAME}",
|
|
6907
|
-
' status="error"',
|
|
6908
|
-
" title={APP_NAME + ' hit an unexpected error'}",
|
|
6909
|
-
" message={this.state.error.message}",
|
|
6910
|
-
' actionLabel="Reload"',
|
|
6911
|
-
" onAction={() => window.location.reload()}",
|
|
6912
|
-
" />",
|
|
6913
|
-
" );",
|
|
6914
|
-
" }",
|
|
6915
|
-
"}",
|
|
6916
|
-
""
|
|
6917
|
-
].join("\n");
|
|
6857
|
+
{
|
|
6858
|
+
path: HOME_PATH,
|
|
6859
|
+
${nav} loading: { shell: NAVIGATION_MODE, content: 'workspace', title: HOME_LABEL },
|
|
6860
|
+
element: <HomeRoute />,
|
|
6861
|
+
},
|
|
6862
|
+
{
|
|
6863
|
+
path: '/getting-started',
|
|
6864
|
+
loading: { shell: NAVIGATION_MODE, content: 'card-grid', title: 'Getting started' },
|
|
6865
|
+
element: <GettingStartedRoute appId={appId} />,
|
|
6866
|
+
},
|
|
6867
|
+
];
|
|
6868
|
+
}
|
|
6869
|
+
`;
|
|
6870
|
+
}
|
|
6871
|
+
function platformHomeRouteTsx() {
|
|
6872
|
+
return `import { TopoloAppPage, TopoloAppPageHeader, useAuth } from '@topolo-io/app-shell';
|
|
6873
|
+
import { IntroTour } from '@topolo-io/tour';
|
|
6874
|
+
import { useTranslation } from '@topolo-io/ui-kit';
|
|
6875
|
+
import { useNavigate } from 'react-router-dom';
|
|
6876
|
+
import { APP_NAME, HOME_LABEL, HOME_PATH, PRODUCT_TAGLINE } from '../app-config';
|
|
6877
|
+
|
|
6878
|
+
export default function HomeRoute() {
|
|
6879
|
+
const { user } = useAuth();
|
|
6880
|
+
const { t } = useTranslation();
|
|
6881
|
+
const navigate = useNavigate();
|
|
6882
|
+
return (
|
|
6883
|
+
<TopoloAppPage data-tour="home-page">
|
|
6884
|
+
<TopoloAppPageHeader
|
|
6885
|
+
eyebrow={APP_NAME}
|
|
6886
|
+
title={t('app.home.title', { defaultValue: HOME_LABEL })}
|
|
6887
|
+
description={t('app.home.description', { defaultValue: PRODUCT_TAGLINE })}
|
|
6888
|
+
/>
|
|
6889
|
+
<IntroTour
|
|
6890
|
+
appName={APP_NAME}
|
|
6891
|
+
role={user?.role}
|
|
6892
|
+
onOpenGettingStarted={() => navigate('/getting-started')}
|
|
6893
|
+
extraSteps={[{
|
|
6894
|
+
id: 'home-page',
|
|
6895
|
+
target: '[data-tour="home-page"]',
|
|
6896
|
+
title: HOME_LABEL,
|
|
6897
|
+
content: PRODUCT_TAGLINE,
|
|
6898
|
+
placement: 'bottom' as const,
|
|
6899
|
+
beforeStep: () => navigate(HOME_PATH),
|
|
6900
|
+
}]}
|
|
6901
|
+
/>
|
|
6902
|
+
</TopoloAppPage>
|
|
6903
|
+
);
|
|
6904
|
+
}
|
|
6905
|
+
`;
|
|
6906
|
+
}
|
|
6907
|
+
function platformGettingStartedRouteTsx() {
|
|
6908
|
+
return `import { TopoloAppPage, useAuth } from '@topolo-io/app-shell';
|
|
6909
|
+
import { GettingStarted, type OnboardingGuideStep } from '@topolo-io/onboarding';
|
|
6910
|
+
import { useTranslation } from '@topolo-io/ui-kit';
|
|
6911
|
+
import { useNavigate } from 'react-router-dom';
|
|
6912
|
+
import { APP_NAME, AUTH_URL, HOME_LABEL, HOME_PATH } from '../app-config';
|
|
6913
|
+
|
|
6914
|
+
export default function GettingStartedRoute({ appId }: { appId: string }) {
|
|
6915
|
+
const { user, tokens } = useAuth();
|
|
6916
|
+
const { t } = useTranslation();
|
|
6917
|
+
const navigate = useNavigate();
|
|
6918
|
+
const openHome = () => navigate(HOME_PATH);
|
|
6919
|
+
const steps: OnboardingGuideStep[] = [
|
|
6920
|
+
{
|
|
6921
|
+
id: 'explore-home',
|
|
6922
|
+
title: t('app.gettingStarted.explore.title', { defaultValue: \`Explore \${HOME_LABEL.toLowerCase()}\` }),
|
|
6923
|
+
description: t('app.gettingStarted.explore.description', { defaultValue: 'Start with the main product surface and the actions available to you.' }),
|
|
6924
|
+
actionLabel: t('app.gettingStarted.explore.action', { defaultValue: \`Open \${HOME_LABEL.toLowerCase()}\` }),
|
|
6925
|
+
onAction: openHome,
|
|
6926
|
+
},
|
|
6927
|
+
{
|
|
6928
|
+
id: 'discover-platform',
|
|
6929
|
+
title: t('app.gettingStarted.platform.title', { defaultValue: 'Move between Topolo apps' }),
|
|
6930
|
+
description: t('app.gettingStarted.platform.description', { defaultValue: 'Use the app launcher and command palette to move through your work.' }),
|
|
6931
|
+
optional: true,
|
|
6932
|
+
},
|
|
6933
|
+
{
|
|
6934
|
+
id: 'personalize',
|
|
6935
|
+
title: t('app.gettingStarted.personalize.title', { defaultValue: 'Set your preferences' }),
|
|
6936
|
+
description: t('app.gettingStarted.personalize.description', { defaultValue: 'Open the account menu to manage your settings and theme.' }),
|
|
6937
|
+
},
|
|
6938
|
+
];
|
|
6939
|
+
return (
|
|
6940
|
+
<TopoloAppPage>
|
|
6941
|
+
<GettingStarted
|
|
6942
|
+
authApiBaseUrl={\`\${AUTH_URL}/api\`}
|
|
6943
|
+
organizationId={String((user as { organization?: { id?: string } } | null)?.organization?.id ?? user?.orgId ?? '')}
|
|
6944
|
+
appId={appId}
|
|
6945
|
+
getAccessToken={() => tokens?.accessToken ?? null}
|
|
6946
|
+
role={user?.role}
|
|
6947
|
+
steps={steps}
|
|
6948
|
+
eyebrow={t('app.gettingStarted.eyebrow', { defaultValue: \`Welcome to \${APP_NAME}\` })}
|
|
6949
|
+
title={t('app.gettingStarted.title', { defaultValue: \`Get started with \${APP_NAME}\` })}
|
|
6950
|
+
subtitle={t('app.gettingStarted.subtitle', { defaultValue: 'A short checklist to help you find your way around.' })}
|
|
6951
|
+
completeLabel={t('app.gettingStarted.complete', { defaultValue: 'Mark done' })}
|
|
6952
|
+
dismissLabel={t('app.gettingStarted.dismiss', { defaultValue: 'Skip for now' })}
|
|
6953
|
+
completedContent={t('app.gettingStarted.completed', { defaultValue: \`You're all set \u2014 \${APP_NAME} is ready.\` })}
|
|
6954
|
+
completeAction={{ label: t('app.gettingStarted.openHome', { defaultValue: \`Go to \${HOME_LABEL.toLowerCase()}\` }), onClick: openHome }}
|
|
6955
|
+
onDismiss={openHome}
|
|
6956
|
+
/>
|
|
6957
|
+
</TopoloAppPage>
|
|
6958
|
+
);
|
|
6959
|
+
}
|
|
6960
|
+
`;
|
|
6961
|
+
}
|
|
6962
|
+
function platformAppErrorBoundaryTsx() {
|
|
6963
|
+
return `import { Component, type ErrorInfo, type ReactNode } from 'react';
|
|
6964
|
+
import { TopoloAuthFlowScreen } from '@topolo-io/app-shell';
|
|
6965
|
+
import { APP_NAME } from './app-config';
|
|
6966
|
+
|
|
6967
|
+
export default class AppErrorBoundary extends Component<{ children: ReactNode }, { error: Error | null }> {
|
|
6968
|
+
state = { error: null as Error | null };
|
|
6969
|
+
|
|
6970
|
+
static getDerivedStateFromError(error: Error) {
|
|
6971
|
+
return { error };
|
|
6972
|
+
}
|
|
6973
|
+
|
|
6974
|
+
componentDidCatch(error: Error, info: ErrorInfo) {
|
|
6975
|
+
console.error('[' + APP_NAME + '] render error:', error, info.componentStack);
|
|
6976
|
+
}
|
|
6977
|
+
|
|
6978
|
+
render() {
|
|
6979
|
+
if (!this.state.error) return this.props.children;
|
|
6980
|
+
return (
|
|
6981
|
+
<TopoloAuthFlowScreen
|
|
6982
|
+
appName={APP_NAME}
|
|
6983
|
+
status="error"
|
|
6984
|
+
title={APP_NAME + ' hit an unexpected error'}
|
|
6985
|
+
message={this.state.error.message}
|
|
6986
|
+
actionLabel="Reload"
|
|
6987
|
+
onAction={() => window.location.reload()}
|
|
6988
|
+
/>
|
|
6989
|
+
);
|
|
6990
|
+
}
|
|
6991
|
+
}
|
|
6992
|
+
`;
|
|
6918
6993
|
}
|
|
6919
6994
|
function developerAppTsx(application) {
|
|
6920
6995
|
const appName = toTitle(application.name);
|
|
6921
6996
|
const defaultClientId = `${toSlug(application.id)}_local_client`;
|
|
6922
|
-
return
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
6985
|
-
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
|
|
6998
|
-
" clientId: CLIENT_ID,",
|
|
6999
|
-
" redirectUri: REDIRECT_URI,",
|
|
7000
|
-
" scopes: REQUESTED_SCOPES,",
|
|
7001
|
-
" });",
|
|
7002
|
-
" sessionStorage.setItem(`${CLIENT_ID}:topolo_pkce_verifier`, authorization.codeVerifier);",
|
|
7003
|
-
" sessionStorage.setItem(`${CLIENT_ID}:topolo_oauth_state`, authorization.state);",
|
|
7004
|
-
" window.location.assign(authorization.url);",
|
|
7005
|
-
"}",
|
|
7006
|
-
"",
|
|
7007
|
-
"async function exchangeAuthorizationCode(code: string): Promise<{ access_token: string; refresh_token?: string }> {",
|
|
7008
|
-
" const params = new URLSearchParams(window.location.search);",
|
|
7009
|
-
" const expectedState = sessionStorage.getItem(`${CLIENT_ID}:topolo_oauth_state`);",
|
|
7010
|
-
" if (expectedState && params.get('state') !== expectedState) {",
|
|
7011
|
-
" throw new Error('OAuth state mismatch. Restart sign-in.');",
|
|
7012
|
-
" }",
|
|
7013
|
-
" const verifier = sessionStorage.getItem(`${CLIENT_ID}:topolo_pkce_verifier`);",
|
|
7014
|
-
" if (!verifier) throw new Error('Missing PKCE verifier. Restart sign-in.');",
|
|
7015
|
-
"",
|
|
7016
|
-
" return exchangeTopoloCode({",
|
|
7017
|
-
" authBaseUrl: AUTH_URL,",
|
|
7018
|
-
" clientId: CLIENT_ID,",
|
|
7019
|
-
" code,",
|
|
7020
|
-
" redirectUri: REDIRECT_URI,",
|
|
7021
|
-
" codeVerifier: verifier,",
|
|
7022
|
-
" });",
|
|
7023
|
-
"}",
|
|
7024
|
-
""
|
|
7025
|
-
].join("\n");
|
|
6997
|
+
return `import { useEffect, useMemo, useState } from 'react';
|
|
6998
|
+
import { createAccountLinkPayload, createTopoloAuthorizationUrl, exchangeTopoloCode, fetchTopoloUserInfo, type TopoloUserInfo } from '@topolo/login';
|
|
6999
|
+
|
|
7000
|
+
type AuthState = 'ready' | 'missing_client' | 'authorizing' | 'authenticated' | 'error';
|
|
7001
|
+
const APP_NAME = '${escapeSingleQuoted(appName)}';
|
|
7002
|
+
const AUTH_URL = import.meta.env.VITE_TOPOLO_AUTH_URL ?? import.meta.env.VITE_TOPOLO_AUTH_BASE_URL ?? 'https://auth.topolo.app';
|
|
7003
|
+
const CLIENT_ID = import.meta.env.VITE_TOPOLO_CLIENT_ID || '${defaultClientId}';
|
|
7004
|
+
const REDIRECT_URI = import.meta.env.VITE_TOPOLO_REDIRECT_URI || \`\${window.location.origin}/auth/callback\`;
|
|
7005
|
+
const REQUESTED_SCOPES = (import.meta.env.VITE_TOPOLO_SCOPES || 'openid profile email organization:read entitlements:read').split(/[\\s,]+/).filter(Boolean);
|
|
7006
|
+
|
|
7007
|
+
export default function App() {
|
|
7008
|
+
const [authState, setAuthState] = useState<AuthState>(() => CLIENT_ID ? 'ready' : 'missing_client');
|
|
7009
|
+
const [message, setMessage] = useState<string | null>(null);
|
|
7010
|
+
const [tokenPreview, setTokenPreview] = useState<string | null>(null);
|
|
7011
|
+
const [userInfo, setUserInfo] = useState<TopoloUserInfo | null>(null);
|
|
7012
|
+
const callbackCode = useMemo(() => new URLSearchParams(window.location.search).get('code'), []);
|
|
7013
|
+
const callbackError = useMemo(() => new URLSearchParams(window.location.search).get('error'), []);
|
|
7014
|
+
|
|
7015
|
+
useEffect(() => {
|
|
7016
|
+
if (callbackError) {
|
|
7017
|
+
setAuthState('error');
|
|
7018
|
+
setMessage(callbackError);
|
|
7019
|
+
return;
|
|
7020
|
+
}
|
|
7021
|
+
if (!callbackCode) return;
|
|
7022
|
+
setAuthState('authorizing');
|
|
7023
|
+
void exchangeAuthorizationCode(callbackCode)
|
|
7024
|
+
.then(async (tokens) => {
|
|
7025
|
+
sessionStorage.setItem(\`\${CLIENT_ID}:topolo_access_token\`, tokens.access_token);
|
|
7026
|
+
if (tokens.refresh_token) sessionStorage.setItem(\`\${CLIENT_ID}:topolo_refresh_token\`, tokens.refresh_token);
|
|
7027
|
+
const profile = await fetchTopoloUserInfo({ authBaseUrl: AUTH_URL, accessToken: tokens.access_token });
|
|
7028
|
+
const link = createAccountLinkPayload({ localUserId: profile.email ? \`local:\${profile.email}\` : \`local:\${profile.sub}\`, userInfo: profile });
|
|
7029
|
+
sessionStorage.setItem(\`\${CLIENT_ID}:topolo_account_link\`, JSON.stringify(link));
|
|
7030
|
+
setUserInfo(profile);
|
|
7031
|
+
setTokenPreview(\`\${tokens.access_token.slice(0, 18)}...\`);
|
|
7032
|
+
setAuthState('authenticated');
|
|
7033
|
+
window.history.replaceState({}, document.title, '/');
|
|
7034
|
+
})
|
|
7035
|
+
.catch((error: Error) => {
|
|
7036
|
+
setAuthState('error');
|
|
7037
|
+
setMessage(error.message);
|
|
7038
|
+
});
|
|
7039
|
+
}, [callbackCode, callbackError]);
|
|
7040
|
+
|
|
7041
|
+
return (
|
|
7042
|
+
<main className="topolo-starter-login">
|
|
7043
|
+
<section className="topolo-starter-login__panel">
|
|
7044
|
+
<h1>{APP_NAME}</h1>
|
|
7045
|
+
<p>Public Topolo OAuth starter using authorization-code + PKCE.</p>
|
|
7046
|
+
{authState === 'missing_client' && <p>Set VITE_TOPOLO_CLIENT_ID after registering this app in Topolo Developers.</p>}
|
|
7047
|
+
{authState === 'authenticated' && <p>Topolo sign-in complete for {userInfo?.email ?? userInfo?.sub}. Access token: {tokenPreview}</p>}
|
|
7048
|
+
{authState === 'error' && <p>{message}</p>}
|
|
7049
|
+
<button type="button" disabled={!CLIENT_ID || authState === 'authorizing'} onClick={() => void startTopoloLogin()}>
|
|
7050
|
+
{authState === 'authorizing' ? 'Completing sign-in' : 'Sign in with Topolo'}
|
|
7051
|
+
</button>
|
|
7052
|
+
</section>
|
|
7053
|
+
</main>
|
|
7054
|
+
);
|
|
7055
|
+
}
|
|
7056
|
+
|
|
7057
|
+
async function startTopoloLogin(): Promise<void> {
|
|
7058
|
+
const authorization = await createTopoloAuthorizationUrl({ authBaseUrl: AUTH_URL, clientId: CLIENT_ID, redirectUri: REDIRECT_URI, scopes: REQUESTED_SCOPES });
|
|
7059
|
+
sessionStorage.setItem(\`\${CLIENT_ID}:topolo_pkce_verifier\`, authorization.codeVerifier);
|
|
7060
|
+
sessionStorage.setItem(\`\${CLIENT_ID}:topolo_oauth_state\`, authorization.state);
|
|
7061
|
+
window.location.assign(authorization.url);
|
|
7062
|
+
}
|
|
7063
|
+
|
|
7064
|
+
async function exchangeAuthorizationCode(code: string): Promise<{ access_token: string; refresh_token?: string }> {
|
|
7065
|
+
const params = new URLSearchParams(window.location.search);
|
|
7066
|
+
const expectedState = sessionStorage.getItem(\`\${CLIENT_ID}:topolo_oauth_state\`);
|
|
7067
|
+
if (expectedState && params.get('state') !== expectedState) throw new Error('OAuth state mismatch. Restart sign-in.');
|
|
7068
|
+
const verifier = sessionStorage.getItem(\`\${CLIENT_ID}:topolo_pkce_verifier\`);
|
|
7069
|
+
if (!verifier) throw new Error('Missing PKCE verifier. Restart sign-in.');
|
|
7070
|
+
return exchangeTopoloCode({ authBaseUrl: AUTH_URL, clientId: CLIENT_ID, code, redirectUri: REDIRECT_URI, codeVerifier: verifier });
|
|
7071
|
+
}
|
|
7072
|
+
`;
|
|
7026
7073
|
}
|
|
7027
7074
|
|
|
7028
7075
|
// src/commands/app-scaffold-worker-template.ts
|
|
7029
|
-
function webWorkerTs(application) {
|
|
7076
|
+
function webWorkerTs(application, api) {
|
|
7030
7077
|
const serviceName = toTitle(application.name);
|
|
7031
7078
|
return [
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7079
|
+
...api ? [
|
|
7080
|
+
"interface Env {",
|
|
7081
|
+
" API: { fetch(request: Request): Promise<Response> };",
|
|
7082
|
+
"}"
|
|
7083
|
+
] : ["type Env = Record<string, never>;"],
|
|
7035
7084
|
"",
|
|
7036
7085
|
`const SERVICE_NAME = '${escapeSingleQuoted(serviceName)}';`,
|
|
7037
7086
|
"",
|
|
7038
7087
|
"export default {",
|
|
7039
|
-
" async fetch(request: Request, env: Env): Promise<Response> {",
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7088
|
+
api ? " async fetch(request: Request, env: Env): Promise<Response> {" : " async fetch(): Promise<Response> {",
|
|
7089
|
+
...api ? [
|
|
7090
|
+
" const url = new URL(request.url);",
|
|
7091
|
+
" if (url.pathname.startsWith('/api/')) {",
|
|
7092
|
+
" return env.API.fetch(request);",
|
|
7093
|
+
" }"
|
|
7094
|
+
] : [],
|
|
7044
7095
|
" return Response.json({ ok: true, service: SERVICE_NAME }, { status: 200 });",
|
|
7045
7096
|
" },",
|
|
7046
7097
|
"};",
|
|
7047
7098
|
""
|
|
7048
7099
|
].join("\n");
|
|
7049
7100
|
}
|
|
7050
|
-
function platformWebWranglerToml(application) {
|
|
7101
|
+
function platformWebWranglerToml(application, api) {
|
|
7051
7102
|
const slug = toSlug(application.id);
|
|
7052
7103
|
const host = application.productionUrl ? new URL(application.productionUrl).host : `${slug}.topolo.app`;
|
|
7053
7104
|
return [
|
|
@@ -7062,7 +7113,7 @@ function platformWebWranglerToml(application) {
|
|
|
7062
7113
|
'directory = "./dist"',
|
|
7063
7114
|
'binding = "ASSETS"',
|
|
7064
7115
|
'not_found_handling = "single-page-application"',
|
|
7065
|
-
'run_worker_first = ["/api/*"]',
|
|
7116
|
+
...api ? ['run_worker_first = ["/api/*"]'] : [],
|
|
7066
7117
|
"",
|
|
7067
7118
|
"[observability]",
|
|
7068
7119
|
"enabled = true",
|
|
@@ -7075,10 +7126,12 @@ function platformWebWranglerToml(application) {
|
|
|
7075
7126
|
`APP_ID = "${application.appId ?? ""}"`,
|
|
7076
7127
|
`VITE_TOPOLO_APP_ID = "${application.appId ?? ""}"`,
|
|
7077
7128
|
"",
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7129
|
+
...api ? [
|
|
7130
|
+
"[[services]]",
|
|
7131
|
+
'binding = "API"',
|
|
7132
|
+
`service = "topolo-${slug}-api"`,
|
|
7133
|
+
""
|
|
7134
|
+
] : [],
|
|
7082
7135
|
"[env.development]",
|
|
7083
7136
|
`name = "topolo-${slug}-development"`,
|
|
7084
7137
|
`account_id = "${DEVELOPMENT_CLOUDFLARE_ACCOUNT_ID}"`,
|
|
@@ -7096,12 +7149,14 @@ function platformWebWranglerToml(application) {
|
|
|
7096
7149
|
'directory = "./dist"',
|
|
7097
7150
|
'binding = "ASSETS"',
|
|
7098
7151
|
'not_found_handling = "single-page-application"',
|
|
7099
|
-
'run_worker_first = ["/api/*"]',
|
|
7100
|
-
"",
|
|
7101
|
-
"[[env.development.services]]",
|
|
7102
|
-
'binding = "API"',
|
|
7103
|
-
`service = "topolo-${slug}-api-development"`,
|
|
7152
|
+
...api ? ['run_worker_first = ["/api/*"]'] : [],
|
|
7104
7153
|
"",
|
|
7154
|
+
...api ? [
|
|
7155
|
+
"[[env.development.services]]",
|
|
7156
|
+
'binding = "API"',
|
|
7157
|
+
`service = "topolo-${slug}-api-development"`,
|
|
7158
|
+
""
|
|
7159
|
+
] : [],
|
|
7105
7160
|
"[env.staging]",
|
|
7106
7161
|
`name = "topolo-${slug}-staging"`,
|
|
7107
7162
|
`account_id = "${STAGING_CLOUDFLARE_ACCOUNT_ID}"`,
|
|
@@ -7119,12 +7174,14 @@ function platformWebWranglerToml(application) {
|
|
|
7119
7174
|
'directory = "./dist"',
|
|
7120
7175
|
'binding = "ASSETS"',
|
|
7121
7176
|
'not_found_handling = "single-page-application"',
|
|
7122
|
-
'run_worker_first = ["/api/*"]',
|
|
7123
|
-
"",
|
|
7124
|
-
"[[env.staging.services]]",
|
|
7125
|
-
'binding = "API"',
|
|
7126
|
-
`service = "topolo-${slug}-api-staging"`,
|
|
7177
|
+
...api ? ['run_worker_first = ["/api/*"]'] : [],
|
|
7127
7178
|
"",
|
|
7179
|
+
...api ? [
|
|
7180
|
+
"[[env.staging.services]]",
|
|
7181
|
+
'binding = "API"',
|
|
7182
|
+
`service = "topolo-${slug}-api-staging"`,
|
|
7183
|
+
""
|
|
7184
|
+
] : [],
|
|
7128
7185
|
"[env.production]",
|
|
7129
7186
|
`name = "topolo-${slug}-production"`,
|
|
7130
7187
|
`account_id = "${CLOUDFLARE_ACCOUNT_ID}"`,
|
|
@@ -7142,12 +7199,14 @@ function platformWebWranglerToml(application) {
|
|
|
7142
7199
|
'directory = "./dist"',
|
|
7143
7200
|
'binding = "ASSETS"',
|
|
7144
7201
|
'not_found_handling = "single-page-application"',
|
|
7145
|
-
'run_worker_first = ["/api/*"]',
|
|
7202
|
+
...api ? ['run_worker_first = ["/api/*"]'] : [],
|
|
7146
7203
|
"",
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
|
|
7204
|
+
...api ? [
|
|
7205
|
+
"[[env.production.services]]",
|
|
7206
|
+
'binding = "API"',
|
|
7207
|
+
`service = "topolo-${slug}-api-production"`,
|
|
7208
|
+
""
|
|
7209
|
+
] : []
|
|
7151
7210
|
].join("\n");
|
|
7152
7211
|
}
|
|
7153
7212
|
function platformApiWranglerToml(application, resources, profile) {
|
|
@@ -7459,6 +7518,7 @@ function platformWorkerTs(application, options) {
|
|
|
7459
7518
|
"import { withSecurityHeaders } from '@topolo-io/security-headers';",
|
|
7460
7519
|
"import { withErrorReporting } from '@topolo-io/error-middleware';",
|
|
7461
7520
|
"import { SCAFFOLD_APP_NAME, SCAFFOLD_SERVICE_SLUG, resolveRuntimeAppId } from './app-identity';",
|
|
7521
|
+
"import { topoloActionContracts } from '../../../topolo.action-contracts.mjs';",
|
|
7462
7522
|
...options.resources.workflow ? ["import { WorkflowEntrypoint, type WorkflowEvent, type WorkflowStep } from 'cloudflare:workers';"] : [],
|
|
7463
7523
|
"",
|
|
7464
7524
|
"export interface Env {",
|
|
@@ -7487,6 +7547,7 @@ function platformWorkerTs(application, options) {
|
|
|
7487
7547
|
" // so rt.verifyAuth() targets the correct Auth service per deploy.",
|
|
7488
7548
|
" authBaseUrl: (env) => env.AUTH_BASE_URL,",
|
|
7489
7549
|
" authGateway: { enabled: false },",
|
|
7550
|
+
" actionContracts: topoloActionContracts,",
|
|
7490
7551
|
"});",
|
|
7491
7552
|
"",
|
|
7492
7553
|
"async function handleRequest(",
|
|
@@ -7516,9 +7577,10 @@ function platformWorkerTs(application, options) {
|
|
|
7516
7577
|
" }, { status: 200 });",
|
|
7517
7578
|
" }",
|
|
7518
7579
|
"",
|
|
7580
|
+
" const auth = await rt.verifyAuth({ appId });",
|
|
7581
|
+
" if (!auth.ok) return rt.authError(auth);",
|
|
7582
|
+
"",
|
|
7519
7583
|
" if (url.pathname === '/api/widget') {",
|
|
7520
|
-
" const auth = await rt.verifyAuth({ appId });",
|
|
7521
|
-
" if (!auth.ok) return rt.authError(auth);",
|
|
7522
7584
|
" return rt.json(createTopoloWidgetResponse({",
|
|
7523
7585
|
" appId,",
|
|
7524
7586
|
" serviceName: SCAFFOLD_APP_NAME,",
|
|
@@ -7526,12 +7588,6 @@ function platformWorkerTs(application, options) {
|
|
|
7526
7588
|
" }), { status: 200, headers: { 'Cache-Control': 'public, max-age=15' } });",
|
|
7527
7589
|
" }",
|
|
7528
7590
|
"",
|
|
7529
|
-
" const auth = await rt.verifyAuth({ appId });",
|
|
7530
|
-
"",
|
|
7531
|
-
" if (!auth.ok) {",
|
|
7532
|
-
" return rt.authError(auth);",
|
|
7533
|
-
" }",
|
|
7534
|
-
"",
|
|
7535
7591
|
" if (url.pathname === '/api/bootstrap') {",
|
|
7536
7592
|
...bootstrapBody,
|
|
7537
7593
|
" }",
|
|
@@ -7725,6 +7781,8 @@ function topoloConformanceWorkflow(options) {
|
|
|
7725
7781
|
" pull_request:",
|
|
7726
7782
|
" push:",
|
|
7727
7783
|
" branches:",
|
|
7784
|
+
" - staging",
|
|
7785
|
+
" - development",
|
|
7728
7786
|
" - main",
|
|
7729
7787
|
" workflow_dispatch:",
|
|
7730
7788
|
"",
|
|
@@ -7762,10 +7820,11 @@ function topoloConformanceWorkflow(options) {
|
|
|
7762
7820
|
" token: ${{ steps.app-token.outputs.token || secrets.TOPOLO_CI_REPO_TOKEN }}",
|
|
7763
7821
|
...cliCheckoutStep,
|
|
7764
7822
|
"",
|
|
7765
|
-
" - name:
|
|
7823
|
+
" - name: Require Topolo package and repository credentials",
|
|
7766
7824
|
` if: steps.app-token.outputs.token == '' && env.HAS_TOPOLO_CI_REPO_TOKEN != 'true'`,
|
|
7767
7825
|
" run: |",
|
|
7768
|
-
' echo "::
|
|
7826
|
+
' echo "::error::Topolo conformance requires TOPOLO_PACKAGES_APP credentials or TOPOLO_CI_REPO_TOKEN."',
|
|
7827
|
+
" exit 1",
|
|
7769
7828
|
"",
|
|
7770
7829
|
" - name: Setup Node.js",
|
|
7771
7830
|
` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
|
|
@@ -7773,6 +7832,14 @@ function topoloConformanceWorkflow(options) {
|
|
|
7773
7832
|
" with:",
|
|
7774
7833
|
" node-version: 20",
|
|
7775
7834
|
"",
|
|
7835
|
+
" - name: Enable pnpm and authenticate Topolo packages",
|
|
7836
|
+
` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
|
|
7837
|
+
" env:",
|
|
7838
|
+
` NODE_AUTH_TOKEN: \${{ steps.app-token.outputs.token || secrets.TOPOLO_CI_REPO_TOKEN }}`,
|
|
7839
|
+
" run: |",
|
|
7840
|
+
" corepack enable",
|
|
7841
|
+
' printf "//npm.pkg.github.com/:_authToken=%s\\n" "$NODE_AUTH_TOKEN" >> "$HOME/.npmrc"',
|
|
7842
|
+
"",
|
|
7776
7843
|
" - name: Build Topolo SDK",
|
|
7777
7844
|
` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
|
|
7778
7845
|
" working-directory: topolo-platform/packages/topolo-sdk",
|
|
@@ -7787,16 +7854,30 @@ function topoloConformanceWorkflow(options) {
|
|
|
7787
7854
|
" npm install --no-audit --no-fund",
|
|
7788
7855
|
" npm run build",
|
|
7789
7856
|
"",
|
|
7790
|
-
" - name:
|
|
7857
|
+
" - name: Install generated application",
|
|
7858
|
+
` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
|
|
7859
|
+
` working-directory: ${appPath}`,
|
|
7860
|
+
" run: pnpm install --frozen-lockfile",
|
|
7861
|
+
"",
|
|
7862
|
+
" - name: Run generated application quality gates",
|
|
7791
7863
|
` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
|
|
7864
|
+
` working-directory: ${appPath}`,
|
|
7792
7865
|
" run: |",
|
|
7793
|
-
|
|
7866
|
+
" pnpm run typecheck",
|
|
7867
|
+
" pnpm run coverage",
|
|
7868
|
+
" pnpm run build",
|
|
7869
|
+
' if pnpm run | grep -q "check:deploy-config"; then pnpm run check:deploy-config; fi',
|
|
7794
7870
|
"",
|
|
7795
|
-
" - name: Record
|
|
7871
|
+
" - name: Record verified CI evidence",
|
|
7796
7872
|
` if: \${{ success() && (steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true') }}`,
|
|
7797
7873
|
` working-directory: ${appPath}`,
|
|
7798
7874
|
" run: |",
|
|
7799
|
-
` node "$GITHUB_WORKSPACE/topolo-platform/packages/topolo-cli/dist/index.js" apps record-evidence ${options.id} --build --typecheck --test --commit "$GITHUB_SHA"
|
|
7875
|
+
` node "$GITHUB_WORKSPACE/topolo-platform/packages/topolo-cli/dist/index.js" apps record-evidence ${options.id} --build --typecheck --test --commit "$GITHUB_SHA"`,
|
|
7876
|
+
"",
|
|
7877
|
+
" - name: Run Topolo app conformance audit",
|
|
7878
|
+
` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
|
|
7879
|
+
" run: |",
|
|
7880
|
+
` node topolo-platform/packages/topolo-cli/dist/index.js apps audit ${options.id} --local --repo-root "$GITHUB_WORKSPACE" --fail-on needs_review --json`
|
|
7800
7881
|
].join("\n");
|
|
7801
7882
|
}
|
|
7802
7883
|
|
|
@@ -7890,8 +7971,8 @@ function topoloAppManifestJson(application, options) {
|
|
|
7890
7971
|
},
|
|
7891
7972
|
compatibilityFlags: ["nodejs_compat"],
|
|
7892
7973
|
buildCommand: {
|
|
7893
|
-
staging: "cd ../.. && DEPLOY_ENVIRONMENT=staging VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us
|
|
7894
|
-
production: "cd ../.. && DEPLOY_ENVIRONMENT=production VITE_TOPOLO_AUTH_URL=https://auth.topolo.app
|
|
7974
|
+
staging: "cd ../.. && DEPLOY_ENVIRONMENT=staging VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us pnpm run build",
|
|
7975
|
+
production: "cd ../.. && DEPLOY_ENVIRONMENT=production VITE_TOPOLO_AUTH_URL=https://auth.topolo.app pnpm run build"
|
|
7895
7976
|
},
|
|
7896
7977
|
healthcheckPath: options.api ? "/api/health" : "/",
|
|
7897
7978
|
...options.api ? {
|
|
@@ -7949,12 +8030,14 @@ function topoloActionContractsMjs() {
|
|
|
7949
8030
|
" name: 'widget.get',",
|
|
7950
8031
|
" method: 'GET',",
|
|
7951
8032
|
" path: '/api/widget',",
|
|
8033
|
+
" permission: 'workspace:read',",
|
|
7952
8034
|
" input: emptyInput,",
|
|
7953
8035
|
" },",
|
|
7954
8036
|
" 'workspace.bootstrap': {",
|
|
7955
8037
|
" name: 'workspace.bootstrap',",
|
|
7956
8038
|
" method: 'GET',",
|
|
7957
8039
|
" path: '/api/bootstrap',",
|
|
8040
|
+
" permission: 'workspace:read',",
|
|
7958
8041
|
" input: emptyInput,",
|
|
7959
8042
|
" },",
|
|
7960
8043
|
"};",
|
|
@@ -7968,6 +8051,15 @@ function topoloActionContractsMjs() {
|
|
|
7968
8051
|
""
|
|
7969
8052
|
].join("\n");
|
|
7970
8053
|
}
|
|
8054
|
+
function topoloActionContractsDmts() {
|
|
8055
|
+
return [
|
|
8056
|
+
"import type { TopoloActionContracts } from '@topolo-io/worker-runtime/actions';",
|
|
8057
|
+
"",
|
|
8058
|
+
"export const topoloActionContracts: TopoloActionContracts;",
|
|
8059
|
+
"export const topoloActionInputSchemas: Record<string, unknown>;",
|
|
8060
|
+
""
|
|
8061
|
+
].join("\n");
|
|
8062
|
+
}
|
|
7971
8063
|
function baselineApiActions() {
|
|
7972
8064
|
const emptyInputSchema = {
|
|
7973
8065
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -8145,7 +8237,7 @@ function cloudcontrolJson(application, options) {
|
|
|
8145
8237
|
cloudflare_account_id: STAGING_CLOUDFLARE_ACCOUNT_ID,
|
|
8146
8238
|
config_path: "apps/web/wrangler.toml",
|
|
8147
8239
|
workdir: "apps/web",
|
|
8148
|
-
build_command: "DEPLOY_ENVIRONMENT=staging VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us
|
|
8240
|
+
build_command: "cd ../.. && DEPLOY_ENVIRONMENT=staging VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us pnpm run build",
|
|
8149
8241
|
output_dir: null,
|
|
8150
8242
|
production_branch: "staging",
|
|
8151
8243
|
healthcheck_path: "/api/health",
|
|
@@ -8178,7 +8270,7 @@ function cloudcontrolJson(application, options) {
|
|
|
8178
8270
|
cloudflare_account_id: CLOUDFLARE_ACCOUNT_ID,
|
|
8179
8271
|
config_path: "apps/web/wrangler.toml",
|
|
8180
8272
|
workdir: "apps/web",
|
|
8181
|
-
build_command: "DEPLOY_ENVIRONMENT=production VITE_TOPOLO_AUTH_URL=https://auth.topolo.app
|
|
8273
|
+
build_command: "cd ../.. && DEPLOY_ENVIRONMENT=production VITE_TOPOLO_AUTH_URL=https://auth.topolo.app pnpm run build",
|
|
8182
8274
|
output_dir: null,
|
|
8183
8275
|
production_branch: "main",
|
|
8184
8276
|
healthcheck_path: "/api/health",
|
|
@@ -8246,17 +8338,17 @@ function cloudcontrolJson(application, options) {
|
|
|
8246
8338
|
ci_targets: [
|
|
8247
8339
|
{
|
|
8248
8340
|
name: "typecheck",
|
|
8249
|
-
command: "
|
|
8341
|
+
command: "pnpm run typecheck",
|
|
8250
8342
|
required: true
|
|
8251
8343
|
},
|
|
8252
8344
|
{
|
|
8253
8345
|
name: "test",
|
|
8254
|
-
command: "
|
|
8346
|
+
command: "pnpm run test",
|
|
8255
8347
|
required: true
|
|
8256
8348
|
},
|
|
8257
8349
|
{
|
|
8258
8350
|
name: "build",
|
|
8259
|
-
command: "
|
|
8351
|
+
command: "pnpm run build",
|
|
8260
8352
|
required: true
|
|
8261
8353
|
}
|
|
8262
8354
|
],
|
|
@@ -8661,6 +8753,131 @@ function envExample(application) {
|
|
|
8661
8753
|
].join("\n");
|
|
8662
8754
|
}
|
|
8663
8755
|
|
|
8756
|
+
// src/commands/app-scaffold-test-template.ts
|
|
8757
|
+
var coverageThresholds = ` thresholds: {
|
|
8758
|
+
statements: 100,
|
|
8759
|
+
branches: 100,
|
|
8760
|
+
functions: 100,
|
|
8761
|
+
lines: 100,
|
|
8762
|
+
},`;
|
|
8763
|
+
function platformBrowserVitestConfig() {
|
|
8764
|
+
return `import { defineConfig } from 'vitest/config';
|
|
8765
|
+
|
|
8766
|
+
export default defineConfig({
|
|
8767
|
+
test: {
|
|
8768
|
+
include: ['test/**/*.test.{ts,tsx}'],
|
|
8769
|
+
environment: 'jsdom',
|
|
8770
|
+
coverage: {
|
|
8771
|
+
provider: 'v8',
|
|
8772
|
+
include: ['src/app-config.ts', 'src/app-routes.tsx', 'src/worker.ts'],
|
|
8773
|
+
${coverageThresholds}
|
|
8774
|
+
},
|
|
8775
|
+
},
|
|
8776
|
+
});
|
|
8777
|
+
`;
|
|
8778
|
+
}
|
|
8779
|
+
function platformApiVitestConfig() {
|
|
8780
|
+
return `import { defineConfig } from 'vitest/config';
|
|
8781
|
+
|
|
8782
|
+
export default defineConfig({
|
|
8783
|
+
test: {
|
|
8784
|
+
include: ['test/**/*.test.{js,mjs,mts,ts,tsx}'],
|
|
8785
|
+
environment: 'node',
|
|
8786
|
+
coverage: {
|
|
8787
|
+
provider: 'v8',
|
|
8788
|
+
include: ['src/app-identity.ts'],
|
|
8789
|
+
${coverageThresholds}
|
|
8790
|
+
},
|
|
8791
|
+
},
|
|
8792
|
+
});
|
|
8793
|
+
`;
|
|
8794
|
+
}
|
|
8795
|
+
function platformBrowserContractTest(application, experience) {
|
|
8796
|
+
const required = experience.workspaceSwitcher === "required";
|
|
8797
|
+
const enabled = experience.workspaceSwitcher !== "off";
|
|
8798
|
+
const navExpectation = experience.navigationMode === "none" ? " expect(home.nav).toBeUndefined();" : ` expect(home.nav).toEqual({ id: 'home', label: '${escapeSingleQuoted(experience.homeLabel)}' });`;
|
|
8799
|
+
return `import { describe, expect, it, vi } from 'vitest';
|
|
8800
|
+
|
|
8801
|
+
vi.mock('../src/app-identity', () => ({
|
|
8802
|
+
SCAFFOLD_APP_NAME: '${escapeSingleQuoted(application.name)}',
|
|
8803
|
+
getBrowserAuthBaseUrl: () => 'https://auth.stg.topolo.us',
|
|
8804
|
+
}));
|
|
8805
|
+
|
|
8806
|
+
import { HOME_LABEL, HOME_PATH, NAVIGATION_MODE, WORKSPACE_RESOURCE } from '../src/app-config';
|
|
8807
|
+
import { createAppRoutes } from '../src/app-routes';
|
|
8808
|
+
|
|
8809
|
+
describe('generated application contract', () => {
|
|
8810
|
+
it('uses the selected home, navigation, and context-switcher shape', () => {
|
|
8811
|
+
expect(HOME_PATH).toBe('${escapeSingleQuoted(experience.homePath)}');
|
|
8812
|
+
expect(HOME_LABEL).toBe('${escapeSingleQuoted(experience.homeLabel)}');
|
|
8813
|
+
expect(NAVIGATION_MODE).toBe('${experience.navigationMode}');
|
|
8814
|
+
expect(WORKSPACE_RESOURCE.enabled).toBe(${enabled});
|
|
8815
|
+
expect('required' in WORKSPACE_RESOURCE ? WORKSPACE_RESOURCE.required : false).toBe(${required});
|
|
8816
|
+
});
|
|
8817
|
+
|
|
8818
|
+
it('keeps Getting Started out of product navigation', () => {
|
|
8819
|
+
const [home, gettingStarted] = createAppRoutes('app_test');
|
|
8820
|
+
expect(home.path).toBe('${escapeSingleQuoted(experience.homePath)}');
|
|
8821
|
+
${navExpectation}
|
|
8822
|
+
expect(gettingStarted.path).toBe('/getting-started');
|
|
8823
|
+
expect(gettingStarted.nav).toBeUndefined();
|
|
8824
|
+
});
|
|
8825
|
+
});
|
|
8826
|
+
`;
|
|
8827
|
+
}
|
|
8828
|
+
function platformWebWorkerTest(application, api) {
|
|
8829
|
+
const apiTest = api ? ` it('proxies API paths to the API service binding', async () => {
|
|
8830
|
+
const response = new Response('proxied', { status: 202 });
|
|
8831
|
+
const fetch = vi.fn().mockResolvedValue(response);
|
|
8832
|
+
await expect(worker.fetch(new Request('https://app.test/api/health'), { API: { fetch } })).resolves.toBe(response);
|
|
8833
|
+
expect(fetch).toHaveBeenCalledOnce();
|
|
8834
|
+
});
|
|
8835
|
+
|
|
8836
|
+
` : "";
|
|
8837
|
+
const env = api ? "{ API: { fetch: vi.fn() } }" : "{}";
|
|
8838
|
+
return `import { describe, expect, it, vi } from 'vitest';
|
|
8839
|
+
import worker from '../src/worker';
|
|
8840
|
+
|
|
8841
|
+
describe('browser worker routing', () => {
|
|
8842
|
+
${apiTest}
|
|
8843
|
+
it('returns the browser service descriptor for asset paths', async () => {
|
|
8844
|
+
const response = await worker.fetch(new Request('https://app.test/dashboard'), ${env});
|
|
8845
|
+
expect(response.status).toBe(200);
|
|
8846
|
+
await expect(response.json()).resolves.toEqual({ ok: true, service: '${escapeSingleQuoted(toTitle(application.name))}' });
|
|
8847
|
+
});
|
|
8848
|
+
});
|
|
8849
|
+
`;
|
|
8850
|
+
}
|
|
8851
|
+
function platformApiHealthTest(application) {
|
|
8852
|
+
return `import { describe, expect, it } from 'vitest';
|
|
8853
|
+
import worker from '../src/worker';
|
|
8854
|
+
import { resolveRuntimeAppId } from '../src/app-identity';
|
|
8855
|
+
|
|
8856
|
+
const executionContext = {
|
|
8857
|
+
waitUntil() {},
|
|
8858
|
+
passThroughOnException() {},
|
|
8859
|
+
props: {},
|
|
8860
|
+
} as unknown as ExecutionContext;
|
|
8861
|
+
|
|
8862
|
+
describe('API runtime baseline', () => {
|
|
8863
|
+
it('serves a real health response through the generated worker stack', async () => {
|
|
8864
|
+
const response = await worker.fetch(
|
|
8865
|
+
new Request('https://api.test/api/health'),
|
|
8866
|
+
{ APP_ID: '${escapeSingleQuoted(application.appId ?? application.id)}' },
|
|
8867
|
+
executionContext,
|
|
8868
|
+
);
|
|
8869
|
+
expect(response.status).toBe(200);
|
|
8870
|
+
await expect(response.json()).resolves.toMatchObject({ ok: true, app: '${escapeSingleQuoted(application.appId ?? application.id)}' });
|
|
8871
|
+
});
|
|
8872
|
+
|
|
8873
|
+
it('requires runtime app identity', async () => {
|
|
8874
|
+
await expect(resolveRuntimeAppId({ APP_ID: ' app_runtime ' })).resolves.toBe('app_runtime');
|
|
8875
|
+
expect(() => resolveRuntimeAppId({})).toThrow('APP_ID is required');
|
|
8876
|
+
});
|
|
8877
|
+
});
|
|
8878
|
+
`;
|
|
8879
|
+
}
|
|
8880
|
+
|
|
8664
8881
|
// src/commands/app-scaffold.ts
|
|
8665
8882
|
function standardScriptStub() {
|
|
8666
8883
|
return `#!/usr/bin/env python3
|
|
@@ -8756,6 +8973,14 @@ function applicationScaffoldPayload(application, options = {}) {
|
|
|
8756
8973
|
const appDir = options.appDir ?? catalogApplication?.appDir ?? (target === "platform" ? `Topolo${toPascalCase(id)}` : `topolo-${slug}`);
|
|
8757
8974
|
const name = options.name ?? catalogApplication?.name ?? `topolo-${slug}`;
|
|
8758
8975
|
const packageName = options.packageName ?? catalogApplication?.packageName ?? `topolo-${slug}`;
|
|
8976
|
+
const experience = {
|
|
8977
|
+
homePath: normalizeHomePath(options.homePath),
|
|
8978
|
+
homeLabel: normalizeHomeLabel(options.homeLabel),
|
|
8979
|
+
navigationMode: normalizeNavigationMode(options.navigationMode),
|
|
8980
|
+
workspaceSwitcher: normalizeWorkspaceSwitcherMode(options.workspaceSwitcher),
|
|
8981
|
+
workspaceLabel: tenancy === "household" ? "Households" : "Workspaces",
|
|
8982
|
+
tagline: String(options.tagline || "").trim() || `Manage your work in ${toTitle(name)}.`
|
|
8983
|
+
};
|
|
8759
8984
|
const serviceSlug = api ? target === "platform" ? `topolo-${slug}` : id : null;
|
|
8760
8985
|
const defaultAppId = target === "platform" ? `app_topolo_${id}` : catalogApplication?.services[0] ?? id;
|
|
8761
8986
|
const appId = api ? options.appId ?? defaultAppId : null;
|
|
@@ -8784,6 +9009,7 @@ function applicationScaffoldPayload(application, options = {}) {
|
|
|
8784
9009
|
containerAppId,
|
|
8785
9010
|
profile,
|
|
8786
9011
|
resources,
|
|
9012
|
+
experience,
|
|
8787
9013
|
distributionModel,
|
|
8788
9014
|
capabilities,
|
|
8789
9015
|
application: applicationPayload,
|
|
@@ -8802,6 +9028,7 @@ function applicationScaffoldPayload(application, options = {}) {
|
|
|
8802
9028
|
containerAppId,
|
|
8803
9029
|
profile,
|
|
8804
9030
|
resources,
|
|
9031
|
+
experience,
|
|
8805
9032
|
distributionModel,
|
|
8806
9033
|
capabilities
|
|
8807
9034
|
},
|
|
@@ -8870,18 +9097,10 @@ function scaffoldPackages(options) {
|
|
|
8870
9097
|
}
|
|
8871
9098
|
const packages = /* @__PURE__ */ new Set();
|
|
8872
9099
|
if (options.browser) {
|
|
8873
|
-
packages.add(
|
|
8874
|
-
packages.add("@topolo-io/app-launcher");
|
|
8875
|
-
packages.add("@topolo-io/app-shell");
|
|
8876
|
-
packages.add("@topolo-io/auth-client");
|
|
8877
|
-
packages.add("@topolo-io/design-tokens");
|
|
8878
|
-
packages.add("@topolo-io/onboarding");
|
|
8879
|
-
packages.add("@topolo-io/tour");
|
|
8880
|
-
packages.add("@topolo-io/ui-kit");
|
|
9100
|
+
for (const packageName of PLATFORM_BROWSER_TOPOLO_PACKAGES) packages.add(packageName);
|
|
8881
9101
|
}
|
|
8882
9102
|
if (options.api) {
|
|
8883
|
-
packages.add(
|
|
8884
|
-
packages.add("@topolo/sdk");
|
|
9103
|
+
for (const packageName of PLATFORM_API_TOPOLO_PACKAGES) packages.add(packageName);
|
|
8885
9104
|
}
|
|
8886
9105
|
return Array.from(packages);
|
|
8887
9106
|
}
|
|
@@ -8961,6 +9180,12 @@ function scaffoldFiles(options) {
|
|
|
8961
9180
|
],
|
|
8962
9181
|
content: gitignore()
|
|
8963
9182
|
},
|
|
9183
|
+
...target === "platform" ? [{
|
|
9184
|
+
path: `${base}/.npmrc`,
|
|
9185
|
+
purpose: "Scoped package registry routing without persisted credentials.",
|
|
9186
|
+
requiredSignals: ["routes private @topolo-io packages to GitHub Packages", "keeps public @topolo packages on npm", "does not contain an authentication token"],
|
|
9187
|
+
content: npmrc()
|
|
9188
|
+
}] : [],
|
|
8964
9189
|
{
|
|
8965
9190
|
path: `${base}/AGENTS.md`,
|
|
8966
9191
|
purpose: "Local agent rules for safe work inside the generated project.",
|
|
@@ -8976,42 +9201,44 @@ function scaffoldFiles(options) {
|
|
|
8976
9201
|
resources: options.payload.resources
|
|
8977
9202
|
})
|
|
8978
9203
|
},
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
|
|
9003
|
-
|
|
9004
|
-
|
|
9005
|
-
|
|
9006
|
-
|
|
9007
|
-
|
|
9008
|
-
|
|
9009
|
-
|
|
9010
|
-
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9204
|
+
...options.browser ? [
|
|
9205
|
+
{
|
|
9206
|
+
path: `${browserBase}/vite.config.ts`,
|
|
9207
|
+
purpose: "Vite browser build configuration.",
|
|
9208
|
+
requiredSignals: [
|
|
9209
|
+
"builds the authenticated browser surface",
|
|
9210
|
+
"keeps generated apps on the standard React/Vite baseline"
|
|
9211
|
+
],
|
|
9212
|
+
content: viteConfig(target)
|
|
9213
|
+
},
|
|
9214
|
+
{
|
|
9215
|
+
path: `${browserBase}/index.html`,
|
|
9216
|
+
purpose: "Browser entry document.",
|
|
9217
|
+
requiredSignals: [
|
|
9218
|
+
"mounts the React app at #root",
|
|
9219
|
+
"uses the generated application display name"
|
|
9220
|
+
],
|
|
9221
|
+
content: indexHtml(application.name)
|
|
9222
|
+
},
|
|
9223
|
+
{
|
|
9224
|
+
path: `${browserBase}/src/main.tsx`,
|
|
9225
|
+
purpose: "React app entrypoint.",
|
|
9226
|
+
requiredSignals: [
|
|
9227
|
+
"renders the generated app component",
|
|
9228
|
+
"imports the generated app stylesheet"
|
|
9229
|
+
],
|
|
9230
|
+
content: mainTsx()
|
|
9231
|
+
},
|
|
9232
|
+
{
|
|
9233
|
+
path: `${browserBase}/src/env.d.ts`,
|
|
9234
|
+
purpose: "Vite environment variable typing.",
|
|
9235
|
+
requiredSignals: [
|
|
9236
|
+
"loads vite/client import.meta.env types",
|
|
9237
|
+
"declares the Topolo environment variables consumed by the starter"
|
|
9238
|
+
],
|
|
9239
|
+
content: envDts(options.api)
|
|
9240
|
+
}
|
|
9241
|
+
] : [],
|
|
9015
9242
|
...options.payload.resources.workflow ? [
|
|
9016
9243
|
{
|
|
9017
9244
|
path: `${base}/src/cloudflare-workers.d.ts`,
|
|
@@ -9023,7 +9250,7 @@ function scaffoldFiles(options) {
|
|
|
9023
9250
|
content: cloudflareWorkersDts()
|
|
9024
9251
|
}
|
|
9025
9252
|
] : [],
|
|
9026
|
-
{
|
|
9253
|
+
...options.browser ? [{
|
|
9027
9254
|
path: `${browserBase}/src/styles.css`,
|
|
9028
9255
|
purpose: "Minimal app-local styles outside the shared Topolo shell.",
|
|
9029
9256
|
requiredSignals: [
|
|
@@ -9031,7 +9258,39 @@ function scaffoldFiles(options) {
|
|
|
9031
9258
|
"keeps app-specific layout scoped under generated classes"
|
|
9032
9259
|
],
|
|
9033
9260
|
content: stylesCss()
|
|
9034
|
-
}
|
|
9261
|
+
}] : [],
|
|
9262
|
+
...target === "platform" && options.browser ? [
|
|
9263
|
+
{
|
|
9264
|
+
path: `${browserBase}/src/app-config.ts`,
|
|
9265
|
+
purpose: "Product route, navigation, context-switcher, and entry-surface configuration.",
|
|
9266
|
+
requiredSignals: ["defaults the authenticated home to /dashboard", "keeps product copy separate from shell wiring"],
|
|
9267
|
+
content: platformAppConfigTs(application, options.payload.experience)
|
|
9268
|
+
},
|
|
9269
|
+
{
|
|
9270
|
+
path: `${browserBase}/src/app-routes.tsx`,
|
|
9271
|
+
purpose: "Canonical protected route manifest and product navigation metadata.",
|
|
9272
|
+
requiredSignals: ["uses the configured home route", "keeps Getting Started out of product navigation"],
|
|
9273
|
+
content: platformAppRoutesTsx(options.payload.experience)
|
|
9274
|
+
},
|
|
9275
|
+
{
|
|
9276
|
+
path: `${browserBase}/src/routes/HomeRoute.tsx`,
|
|
9277
|
+
purpose: "Focused authenticated home product surface.",
|
|
9278
|
+
requiredSignals: ["uses shared page primitives", "uses translation-safe product copy"],
|
|
9279
|
+
content: platformHomeRouteTsx()
|
|
9280
|
+
},
|
|
9281
|
+
{
|
|
9282
|
+
path: `${browserBase}/src/routes/GettingStartedRoute.tsx`,
|
|
9283
|
+
purpose: "Focused role-aware onboarding route reached from the account menu.",
|
|
9284
|
+
requiredSignals: ["uses shared onboarding", "returns to the configured home route"],
|
|
9285
|
+
content: platformGettingStartedRouteTsx()
|
|
9286
|
+
},
|
|
9287
|
+
{
|
|
9288
|
+
path: `${browserBase}/src/AppErrorBoundary.tsx`,
|
|
9289
|
+
purpose: "Recoverable render-error boundary for the product root.",
|
|
9290
|
+
requiredSignals: ["uses the shared auth flow error surface", "forwards render errors to shared capture"],
|
|
9291
|
+
content: platformAppErrorBoundaryTsx()
|
|
9292
|
+
}
|
|
9293
|
+
] : []
|
|
9035
9294
|
];
|
|
9036
9295
|
if (target === "platform") {
|
|
9037
9296
|
if (options.browser) {
|
|
@@ -9082,6 +9341,15 @@ function scaffoldFiles(options) {
|
|
|
9082
9341
|
"each contract has a zod input validator, keeping the manifest actions[] honest"
|
|
9083
9342
|
],
|
|
9084
9343
|
content: topoloActionContractsMjs()
|
|
9344
|
+
},
|
|
9345
|
+
{
|
|
9346
|
+
path: `${base}/topolo.action-contracts.d.mts`,
|
|
9347
|
+
purpose: "TypeScript declaration for the generated JavaScript action-contract module.",
|
|
9348
|
+
requiredSignals: [
|
|
9349
|
+
"types topoloActionContracts against the canonical worker-runtime action contract",
|
|
9350
|
+
"keeps generated API typecheck strict without enabling allowJs"
|
|
9351
|
+
],
|
|
9352
|
+
content: topoloActionContractsDmts()
|
|
9085
9353
|
}
|
|
9086
9354
|
] : [],
|
|
9087
9355
|
{
|
|
@@ -9256,7 +9524,8 @@ function scaffoldFiles(options) {
|
|
|
9256
9524
|
],
|
|
9257
9525
|
content: appTsx(application, target, {
|
|
9258
9526
|
audience: options.payload.audience,
|
|
9259
|
-
portfolio: options.payload.portfolio
|
|
9527
|
+
portfolio: options.payload.portfolio,
|
|
9528
|
+
experience: options.payload.experience
|
|
9260
9529
|
})
|
|
9261
9530
|
});
|
|
9262
9531
|
if (target === "platform") {
|
|
@@ -9267,7 +9536,7 @@ function scaffoldFiles(options) {
|
|
|
9267
9536
|
"runs component/unit tests under jsdom",
|
|
9268
9537
|
"keeps the generated browser surface testable from day one"
|
|
9269
9538
|
],
|
|
9270
|
-
content: `import { defineConfig } from 'vitest/config';
|
|
9539
|
+
content: target === "platform" ? platformBrowserVitestConfig() : `import { defineConfig } from 'vitest/config';
|
|
9271
9540
|
|
|
9272
9541
|
export default defineConfig({
|
|
9273
9542
|
test: {
|
|
@@ -9277,6 +9546,40 @@ export default defineConfig({
|
|
|
9277
9546
|
});
|
|
9278
9547
|
`
|
|
9279
9548
|
});
|
|
9549
|
+
if (target === "platform") {
|
|
9550
|
+
files.push(
|
|
9551
|
+
{
|
|
9552
|
+
path: `${browserBase}/test/application-contract.test.tsx`,
|
|
9553
|
+
purpose: "Executable route, navigation, and resource-switcher contract.",
|
|
9554
|
+
requiredSignals: ["asserts the configured product shape", "proves Getting Started is absent from product navigation"],
|
|
9555
|
+
content: platformBrowserContractTest(application, options.payload.experience)
|
|
9556
|
+
},
|
|
9557
|
+
{
|
|
9558
|
+
path: `${browserBase}/test/worker.test.ts`,
|
|
9559
|
+
purpose: "Executable browser Worker routing tests.",
|
|
9560
|
+
requiredSignals: ["proves API proxying", "proves non-API browser fallback"],
|
|
9561
|
+
content: platformWebWorkerTest(application, options.api)
|
|
9562
|
+
},
|
|
9563
|
+
{
|
|
9564
|
+
path: `${browserBase}/src/worker.ts`,
|
|
9565
|
+
purpose: "Browser Worker that serves assets and forwards API paths when an API target exists.",
|
|
9566
|
+
requiredSignals: [
|
|
9567
|
+
"keeps the frontend Worker thin",
|
|
9568
|
+
"only declares an API service binding for API-backed scaffolds"
|
|
9569
|
+
],
|
|
9570
|
+
content: webWorkerTs(application, options.api)
|
|
9571
|
+
},
|
|
9572
|
+
{
|
|
9573
|
+
path: `${browserBase}/wrangler.toml`,
|
|
9574
|
+
purpose: "Cloudflare Worker static-assets configuration for the browser surface.",
|
|
9575
|
+
requiredSignals: [
|
|
9576
|
+
"serves Vite assets through a Worker with assets",
|
|
9577
|
+
"only binds an API Worker when the scaffold includes one"
|
|
9578
|
+
],
|
|
9579
|
+
content: platformWebWranglerToml(application, options.api)
|
|
9580
|
+
}
|
|
9581
|
+
);
|
|
9582
|
+
}
|
|
9280
9583
|
}
|
|
9281
9584
|
}
|
|
9282
9585
|
if (options.api) {
|
|
@@ -9317,24 +9620,6 @@ export default defineConfig({
|
|
|
9317
9620
|
],
|
|
9318
9621
|
content: apiAppIdentityTs(application)
|
|
9319
9622
|
},
|
|
9320
|
-
{
|
|
9321
|
-
path: `${browserBase}/src/worker.ts`,
|
|
9322
|
-
purpose: "Browser Worker that serves assets and forwards API paths to the API Worker binding.",
|
|
9323
|
-
requiredSignals: [
|
|
9324
|
-
"keeps the frontend Worker thin",
|
|
9325
|
-
"routes /api/* to the API service binding instead of owning backend routes locally"
|
|
9326
|
-
],
|
|
9327
|
-
content: webWorkerTs(application)
|
|
9328
|
-
},
|
|
9329
|
-
{
|
|
9330
|
-
path: `${browserBase}/wrangler.toml`,
|
|
9331
|
-
purpose: "Cloudflare Worker static-assets configuration for the browser surface.",
|
|
9332
|
-
requiredSignals: [
|
|
9333
|
-
"serves Vite assets through a Worker with assets",
|
|
9334
|
-
"binds the frontend Worker to the API Worker for /api/* requests"
|
|
9335
|
-
],
|
|
9336
|
-
content: platformWebWranglerToml(application)
|
|
9337
|
-
},
|
|
9338
9623
|
{
|
|
9339
9624
|
path: `${apiBase}/vitest.config.ts`,
|
|
9340
9625
|
purpose: "Vitest config for the API worker surface.",
|
|
@@ -9342,7 +9627,7 @@ export default defineConfig({
|
|
|
9342
9627
|
"runs worker/unit tests under the node environment",
|
|
9343
9628
|
"keeps the generated service testable from day one"
|
|
9344
9629
|
],
|
|
9345
|
-
content: `import { defineConfig } from 'vitest/config';
|
|
9630
|
+
content: target === "platform" ? platformApiVitestConfig() : `import { defineConfig } from 'vitest/config';
|
|
9346
9631
|
|
|
9347
9632
|
export default defineConfig({
|
|
9348
9633
|
test: {
|
|
@@ -9359,7 +9644,7 @@ export default defineConfig({
|
|
|
9359
9644
|
"asserts the scaffold boots",
|
|
9360
9645
|
"is replaced by real route/repository tests as the surface grows"
|
|
9361
9646
|
],
|
|
9362
|
-
content: "import { describe, expect, it } from 'vitest';\n\
|
|
9647
|
+
content: target === "platform" ? platformApiHealthTest(application) : "import { describe, expect, it } from 'vitest';\n\ndescribe('api scaffold', () => {\n it('boots', () => {\n expect(true).toBe(true);\n });\n});\n"
|
|
9363
9648
|
}
|
|
9364
9649
|
);
|
|
9365
9650
|
if (options.payload.resources.d1) {
|
|
@@ -10125,7 +10410,7 @@ function registerPlatformCommand(program2) {
|
|
|
10125
10410
|
).option("--json", "Output JSON").option("--no-json", "Output human-readable checklist").option("--app-dir <path>", "Application directory under apps/").option("--name <name>", "Application display/package source name").option("--package-name <name>", "package.json name").option("--production-url <url>", "Production browser URL").option("--service <id>", "Stable SDK/Auth app id for API-backed apps").option("--portfolio <portfolio>", "First-party portfolio: platform or personal").option(
|
|
10126
10411
|
"--surface-model <surfaceModel>",
|
|
10127
10412
|
"First-party surface model: web_app, mobile_container, mobile_module, or messaging_surface"
|
|
10128
|
-
).option("--container-app-id <appId>", "Container app id when --surface-model mobile_module is used").option("--profile <profile>", "First-party scaffold profile such as spatial-assets").option("--with-d1", "Declare a D1 binding in the first-party scaffold").option("--with-r2", "Declare an R2 binding in the first-party scaffold").option("--with-queue", "Declare a Queue binding in the first-party scaffold").option("--with-workflow", "Declare a Workflow binding in the first-party scaffold").option("--with-container-processing", "Declare container-processing metadata in CloudControl").option("--write", "Write the generated scaffold files to disk").option("--repo-root <path>", "Directory where scaffold files should be written").option("--force", "Overwrite existing scaffold files when --write is set").option("--with-api", "Include the API/service baseline").option("--no-api", "Do not include API/service requirements").option("--no-browser", "Do not include browser-shell requirements").option(
|
|
10413
|
+
).option("--container-app-id <appId>", "Container app id when --surface-model mobile_module is used").option("--profile <profile>", "First-party scaffold profile such as spatial-assets").option("--with-d1", "Declare a D1 binding in the first-party scaffold").option("--with-r2", "Declare an R2 binding in the first-party scaffold").option("--with-queue", "Declare a Queue binding in the first-party scaffold").option("--with-workflow", "Declare a Workflow binding in the first-party scaffold").option("--with-container-processing", "Declare container-processing metadata in CloudControl").option("--home-path <path>", "Authenticated home route (default: /dashboard)").option("--home-label <label>", "Home navigation label (default: Dashboard)").option("--navigation-mode <mode>", "Product navigation: sidebar, topbar, or none").option("--workspace-switcher <mode>", "Workspace context selector: required, optional, or off").option("--tagline <text>", "Product-facing landing and home description").option("--write", "Write the generated scaffold files to disk").option("--repo-root <path>", "Directory where scaffold files should be written").option("--force", "Overwrite existing scaffold files when --write is set").option("--with-api", "Include the API/service baseline").option("--no-api", "Do not include API/service requirements").option("--no-browser", "Do not include browser-shell requirements").option(
|
|
10129
10414
|
"--provision",
|
|
10130
10415
|
"Provision the generated first-party app into Topolo Developers and Auth using the authenticated Topolo admin session"
|
|
10131
10416
|
).option(
|
|
@@ -10160,7 +10445,12 @@ function registerPlatformCommand(program2) {
|
|
|
10160
10445
|
withR2: opts.withR2,
|
|
10161
10446
|
withQueue: opts.withQueue,
|
|
10162
10447
|
withWorkflow: opts.withWorkflow,
|
|
10163
|
-
withContainerProcessing: opts.withContainerProcessing
|
|
10448
|
+
withContainerProcessing: opts.withContainerProcessing,
|
|
10449
|
+
homePath: opts.homePath,
|
|
10450
|
+
homeLabel: opts.homeLabel,
|
|
10451
|
+
navigationMode: opts.navigationMode,
|
|
10452
|
+
workspaceSwitcher: opts.workspaceSwitcher,
|
|
10453
|
+
tagline: opts.tagline
|
|
10164
10454
|
});
|
|
10165
10455
|
if (opts.workflowOnly && !payload.ciWorkflow) {
|
|
10166
10456
|
throw new Error("--workflow-only is only available for first-party scaffolds.");
|