@topolo/cli 0.1.30 → 0.1.32
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/README.md +9 -4
- package/dist/index.js +933 -805
- package/package.json +14 -14
package/dist/index.js
CHANGED
|
@@ -92,7 +92,7 @@ var SERVICES = {
|
|
|
92
92
|
mail: "https://mail.topolo.app",
|
|
93
93
|
mdm: "https://mdm.topolo.app",
|
|
94
94
|
messages: "https://messages.topolo.app",
|
|
95
|
-
nexus: "https://nexus
|
|
95
|
+
nexus: "https://nexus.topolo.app",
|
|
96
96
|
notify: "https://notify.topolo.app",
|
|
97
97
|
observability_watch: "https://observability-watch.topolo.app",
|
|
98
98
|
one: "https://www.topolo.app",
|
|
@@ -641,8 +641,10 @@ var APPLICATIONS = {
|
|
|
641
641
|
{
|
|
642
642
|
"name": "topolo-feed-api",
|
|
643
643
|
"kind": "worker",
|
|
644
|
-
"apiUrl":
|
|
645
|
-
"domains": [
|
|
644
|
+
"apiUrl": "https://feed-api.topolo.app",
|
|
645
|
+
"domains": [
|
|
646
|
+
"feed-api.topolo.app"
|
|
647
|
+
],
|
|
646
648
|
"routes": [],
|
|
647
649
|
"serviceId": null
|
|
648
650
|
},
|
|
@@ -1013,9 +1015,9 @@ var APPLICATIONS = {
|
|
|
1013
1015
|
{
|
|
1014
1016
|
"name": "topolo-nexus-gateway",
|
|
1015
1017
|
"kind": "worker",
|
|
1016
|
-
"apiUrl": "https://nexus
|
|
1018
|
+
"apiUrl": "https://nexus.topolo.app",
|
|
1017
1019
|
"domains": [
|
|
1018
|
-
"nexus
|
|
1020
|
+
"nexus.topolo.app"
|
|
1019
1021
|
],
|
|
1020
1022
|
"routes": [],
|
|
1021
1023
|
"serviceId": "nexus"
|
|
@@ -1375,8 +1377,10 @@ var APPLICATIONS = {
|
|
|
1375
1377
|
{
|
|
1376
1378
|
"name": "topolo-socialize-api",
|
|
1377
1379
|
"kind": "worker",
|
|
1378
|
-
"apiUrl":
|
|
1379
|
-
"domains": [
|
|
1380
|
+
"apiUrl": "https://socialize-api.topolo.app",
|
|
1381
|
+
"domains": [
|
|
1382
|
+
"socialize-api.topolo.app"
|
|
1383
|
+
],
|
|
1380
1384
|
"routes": [],
|
|
1381
1385
|
"serviceId": null
|
|
1382
1386
|
},
|
|
@@ -2220,10 +2224,14 @@ var TopoloClient = class _TopoloClient {
|
|
|
2220
2224
|
if (!serviceKey) {
|
|
2221
2225
|
throw new TopoloSdkError("unknown_service", "service is required");
|
|
2222
2226
|
}
|
|
2223
|
-
if (serviceKey === "auth") {
|
|
2227
|
+
if (serviceKey === "auth" || serviceKey === "developers") {
|
|
2224
2228
|
const overrideUrl = resolveConfiguredServiceUrl(serviceKey, this.serviceUrls);
|
|
2225
2229
|
if (overrideUrl) return { serviceKey, baseUrl: overrideUrl };
|
|
2226
|
-
|
|
2230
|
+
if (serviceKey === "developers") {
|
|
2231
|
+
const derived = deriveDevelopersFromAuth(resolveConfiguredServiceUrl("auth", this.serviceUrls));
|
|
2232
|
+
if (derived) return { serviceKey, baseUrl: derived };
|
|
2233
|
+
}
|
|
2234
|
+
return { serviceKey, baseUrl: DEFAULT_SERVICE_URLS[serviceKey] };
|
|
2227
2235
|
}
|
|
2228
2236
|
const live = await this.resolveLiveCallableService(serviceKey);
|
|
2229
2237
|
if (live) {
|
|
@@ -2263,7 +2271,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2263
2271
|
if (!normalized) return null;
|
|
2264
2272
|
const configuredServiceId = resolveConfiguredServiceId(normalized, this.serviceIds);
|
|
2265
2273
|
if (configuredServiceId) return configuredServiceId;
|
|
2266
|
-
const authBase = resolveServiceUrl("
|
|
2274
|
+
const authBase = resolveServiceUrl("developers", this.serviceUrls);
|
|
2267
2275
|
const cacheKey = `${authBase}|${normalized}`;
|
|
2268
2276
|
const now = Date.now();
|
|
2269
2277
|
const cached = _TopoloClient.serviceIdCache.get(cacheKey);
|
|
@@ -2317,7 +2325,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2317
2325
|
if (this.serviceCatalogCache && this.serviceCatalogCache.expiresAt > now) {
|
|
2318
2326
|
return this.serviceCatalogCache.entries;
|
|
2319
2327
|
}
|
|
2320
|
-
const res = await this.request({ service: "
|
|
2328
|
+
const res = await this.request({ service: "developers", method: "GET", path: "/api/services/catalog" });
|
|
2321
2329
|
const raw = res.data?.services ?? res.services;
|
|
2322
2330
|
if (!Array.isArray(raw)) {
|
|
2323
2331
|
throw new TopoloSdkError(
|
|
@@ -2548,6 +2556,17 @@ function serviceKeyCandidates2(slug) {
|
|
|
2548
2556
|
function normalizeLookupKey(value) {
|
|
2549
2557
|
return String(value || "").trim().toLowerCase().replace(/\s+/g, "-");
|
|
2550
2558
|
}
|
|
2559
|
+
function deriveDevelopersFromAuth(authUrl) {
|
|
2560
|
+
if (!authUrl) return null;
|
|
2561
|
+
try {
|
|
2562
|
+
const url = new URL(authUrl);
|
|
2563
|
+
if (!/^auth\./i.test(url.hostname)) return null;
|
|
2564
|
+
url.hostname = url.hostname.replace(/^auth\./i, "developers.");
|
|
2565
|
+
return url.origin;
|
|
2566
|
+
} catch {
|
|
2567
|
+
return null;
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2551
2570
|
function catalogEntryMatches(entry, needle) {
|
|
2552
2571
|
return catalogEntryLookupKeys(entry).includes(needle);
|
|
2553
2572
|
}
|
|
@@ -2693,7 +2712,6 @@ var CrmModule = class {
|
|
|
2693
2712
|
constructor(client) {
|
|
2694
2713
|
this.client = client;
|
|
2695
2714
|
}
|
|
2696
|
-
client;
|
|
2697
2715
|
async listContacts(options = {}) {
|
|
2698
2716
|
const res = await this.client.request({
|
|
2699
2717
|
service: "crm",
|
|
@@ -2739,7 +2757,6 @@ var IdentityModule = class {
|
|
|
2739
2757
|
constructor(client) {
|
|
2740
2758
|
this.client = client;
|
|
2741
2759
|
}
|
|
2742
|
-
client;
|
|
2743
2760
|
/** Returns the resolved user, organization, and permission set for the
|
|
2744
2761
|
* current credential. Equivalent to `TopoloClient.introspect()` — exposed
|
|
2745
2762
|
* on this module for symmetry with the other domain modules. */
|
|
@@ -3311,7 +3328,7 @@ async function pollUntilApproved(params) {
|
|
|
3311
3328
|
throw new Error("Device code expired. Run `topolo auth login` again.");
|
|
3312
3329
|
}
|
|
3313
3330
|
function sleep(ms) {
|
|
3314
|
-
return new Promise((
|
|
3331
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
3315
3332
|
}
|
|
3316
3333
|
|
|
3317
3334
|
// src/version.ts
|
|
@@ -3665,7 +3682,7 @@ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
3665
3682
|
function enrichApplicationAuditWithLocalEvidence(report, options = {}) {
|
|
3666
3683
|
const repoRoot = resolveTopoloRepoRoot(options.repoRoot, options.cwd ?? process.cwd());
|
|
3667
3684
|
if (!repoRoot) {
|
|
3668
|
-
throw new Error("Could not locate the Topolo checkout. Pass --repo-root with the path that contains
|
|
3685
|
+
throw new Error("Could not locate the Topolo checkout. Pass --repo-root with the path that contains apps/ and system-apps/.");
|
|
3669
3686
|
}
|
|
3670
3687
|
const applications = report.applications.map(
|
|
3671
3688
|
(audit) => enrichApplicationAudit(audit, collectEvidence(repoRoot, audit.application))
|
|
@@ -3694,8 +3711,10 @@ function resolveTopoloRepoRoot(explicitRoot, cwd = process.cwd()) {
|
|
|
3694
3711
|
}
|
|
3695
3712
|
}
|
|
3696
3713
|
function normalizeTopoloRoot(candidate) {
|
|
3697
|
-
if (isDirectory(path.join(candidate, "
|
|
3698
|
-
|
|
3714
|
+
if (isDirectory(path.join(candidate, "apps")) && isDirectory(path.join(candidate, "system-apps"))) {
|
|
3715
|
+
return candidate;
|
|
3716
|
+
}
|
|
3717
|
+
if (path.basename(candidate) === "apps" && isDirectory(path.join(path.dirname(candidate), "system-apps"))) {
|
|
3699
3718
|
return path.dirname(candidate);
|
|
3700
3719
|
}
|
|
3701
3720
|
return null;
|
|
@@ -4279,7 +4298,8 @@ function nativeDashboardWidgetEvidence(evidence) {
|
|
|
4279
4298
|
return unique(items);
|
|
4280
4299
|
}
|
|
4281
4300
|
function collectEvidence(repoRoot, application) {
|
|
4282
|
-
const
|
|
4301
|
+
const appRepoPath = applicationSourcePath(application.appDir);
|
|
4302
|
+
const appRoot = path.join(repoRoot, appRepoPath);
|
|
4283
4303
|
const packageJsonPath = path.join(appRoot, "package.json");
|
|
4284
4304
|
const packageJson2 = readPackageJson(packageJsonPath);
|
|
4285
4305
|
const cloudcontrolPath = path.join(appRoot, "topolo.cloudcontrol.json");
|
|
@@ -4520,7 +4540,7 @@ function matchingSystems(repoRoot, application) {
|
|
|
4520
4540
|
const docsRoot = topoloDocsRoot(repoRoot);
|
|
4521
4541
|
const systemsRoot = path.join(docsRoot, "src", "content", "systems");
|
|
4522
4542
|
if (!isDirectory(systemsRoot)) return [];
|
|
4523
|
-
const appRepoPath =
|
|
4543
|
+
const appRepoPath = applicationSourcePath(application.appDir);
|
|
4524
4544
|
return readdirSync(systemsRoot).filter((entry) => entry.endsWith(".json")).map((entry) => readJson(path.join(systemsRoot, entry))).filter((system) => Boolean(system)).filter((system) => {
|
|
4525
4545
|
const repoPaths = [...system.repo_paths ?? [], ...system.repoPaths ?? []];
|
|
4526
4546
|
return repoPaths.some(
|
|
@@ -4541,9 +4561,14 @@ function existingDocs(repoRoot, systems) {
|
|
|
4541
4561
|
);
|
|
4542
4562
|
}
|
|
4543
4563
|
function topoloDocsRoot(repoRoot) {
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4564
|
+
return path.join(repoRoot, "system-apps", "TopoloDocs", "apps", "web");
|
|
4565
|
+
}
|
|
4566
|
+
function applicationSourcePath(appDir) {
|
|
4567
|
+
if (appDir === "TopoloCli" || appDir === "TopoloCLI") return "topolo-platform/packages/topolo-cli";
|
|
4568
|
+
if (appDir === "TopoloDocs") return "system-apps/TopoloDocs";
|
|
4569
|
+
if (appDir === "TopoloAuth") return "topolo-platform/core/TopoloAuth";
|
|
4570
|
+
if (appDir === "TopoloCloudControl") return "topolo-platform/core/TopoloCloudControl";
|
|
4571
|
+
return `apps/${appDir}`;
|
|
4547
4572
|
}
|
|
4548
4573
|
function docExists(contentRoot, docPath) {
|
|
4549
4574
|
return [
|
|
@@ -4671,10 +4696,12 @@ function unique(items) {
|
|
|
4671
4696
|
return Array.from(new Set(items));
|
|
4672
4697
|
}
|
|
4673
4698
|
function relativeTopoloPath(filePath) {
|
|
4674
|
-
const index = filePath.indexOf("
|
|
4699
|
+
const index = filePath.indexOf("apps/");
|
|
4675
4700
|
if (index >= 0) return filePath.slice(index);
|
|
4676
|
-
const docsIndex = filePath.indexOf("TopoloDocs/");
|
|
4701
|
+
const docsIndex = filePath.indexOf("system-apps/TopoloDocs/");
|
|
4677
4702
|
if (docsIndex >= 0) return filePath.slice(docsIndex);
|
|
4703
|
+
const platformIndex = filePath.indexOf("topolo-platform/");
|
|
4704
|
+
if (platformIndex >= 0) return filePath.slice(platformIndex);
|
|
4678
4705
|
return filePath;
|
|
4679
4706
|
}
|
|
4680
4707
|
function isDirectory(filePath) {
|
|
@@ -4694,640 +4721,150 @@ function isFile(filePath) {
|
|
|
4694
4721
|
|
|
4695
4722
|
// src/commands/app-scaffold.ts
|
|
4696
4723
|
import { randomBytes } from "crypto";
|
|
4724
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
4725
|
+
import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
4726
|
+
|
|
4727
|
+
// src/commands/app-scaffold-normalizers.ts
|
|
4697
4728
|
import { constants } from "fs";
|
|
4698
|
-
import { access
|
|
4699
|
-
import {
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
function generateOpaqueServiceId() {
|
|
4705
|
-
return `srv_${randomBytes(9).toString("base64url")}`;
|
|
4729
|
+
import { access } from "fs/promises";
|
|
4730
|
+
import { relative, resolve } from "path";
|
|
4731
|
+
function normalizeTarget(value) {
|
|
4732
|
+
if (!value) return "platform";
|
|
4733
|
+
if (value === "platform" || value === "developer") return value;
|
|
4734
|
+
throw new Error("Invalid scaffold target. Use platform or developer.");
|
|
4706
4735
|
}
|
|
4707
|
-
function
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
const id = target === "platform" ? normalizeFirstPartyApplicationId(requestedId) : requestedId;
|
|
4712
|
-
const slug = toSlug(id);
|
|
4713
|
-
const catalogApplication = APPLICATIONS[id];
|
|
4714
|
-
const browser = options.browser !== false;
|
|
4715
|
-
const api = options.api ?? target === "platform";
|
|
4716
|
-
const audience = normalizeAudience(
|
|
4717
|
-
options.audience,
|
|
4718
|
-
target === "platform" ? portfolio : null
|
|
4719
|
-
);
|
|
4720
|
-
const tenancy = normalizeTenancy(options.tenancy, audience);
|
|
4721
|
-
const surfaceModel = normalizeSurfaceModel(options.surfaceModel);
|
|
4722
|
-
const containerAppId = normalizeContainerAppId(options.containerAppId, surfaceModel);
|
|
4723
|
-
const profile = normalizeScaffoldProfile(options.profile);
|
|
4724
|
-
const resources = resolveScaffoldResources(profile, options);
|
|
4725
|
-
const ownerType = target === "platform" ? "topolo_first_party" : "third_party";
|
|
4726
|
-
const distributionModel = target === "platform" ? "marketplace" : "organization_internal";
|
|
4727
|
-
const appDir = options.appDir ?? catalogApplication?.appDir ?? (target === "platform" ? `Topolo${toPascalCase(id)}` : `topolo-${slug}`);
|
|
4728
|
-
const name = options.name ?? catalogApplication?.name ?? `topolo-${slug}`;
|
|
4729
|
-
const packageName = options.packageName ?? catalogApplication?.packageName ?? `topolo-${slug}`;
|
|
4730
|
-
const serviceSlug = api ? target === "platform" ? `topolo-${slug}` : id : null;
|
|
4731
|
-
const defaultServiceId = target === "platform" ? generateOpaqueServiceId() : catalogApplication?.services[0] ?? id;
|
|
4732
|
-
const serviceId = api ? options.service ?? defaultServiceId : null;
|
|
4733
|
-
const productionUrl = browser ? options.productionUrl ?? catalogApplication?.productionUrl ?? `https://${slug}.topolo.app` : null;
|
|
4734
|
-
const scopes = scaffoldScopes({ browser, api });
|
|
4735
|
-
const capabilities = scaffoldCapabilities({ browser, api, distributionModel });
|
|
4736
|
-
const ciWorkflowPath = target === "platform" ? ".github/workflows/topolo-conformance.yml" : null;
|
|
4737
|
-
const ciWorkflow = target === "platform" ? topoloConformanceWorkflow({ id, appDir, audience }) : null;
|
|
4738
|
-
const applicationPayload = {
|
|
4739
|
-
id,
|
|
4740
|
-
name,
|
|
4741
|
-
appDir,
|
|
4742
|
-
packageName,
|
|
4743
|
-
productionUrl,
|
|
4744
|
-
serviceSlug,
|
|
4745
|
-
serviceId
|
|
4746
|
-
};
|
|
4747
|
-
return {
|
|
4748
|
-
version: APPLICATION_REQUIREMENTS_VERSION,
|
|
4749
|
-
target,
|
|
4750
|
-
ownerType,
|
|
4751
|
-
portfolio,
|
|
4752
|
-
audience,
|
|
4753
|
-
tenancy,
|
|
4754
|
-
surfaceModel,
|
|
4755
|
-
containerAppId,
|
|
4756
|
-
profile,
|
|
4757
|
-
resources,
|
|
4758
|
-
distributionModel,
|
|
4759
|
-
capabilities,
|
|
4760
|
-
application: applicationPayload,
|
|
4761
|
-
scopes,
|
|
4762
|
-
requirements: APPLICATION_REQUIREMENTS.filter(
|
|
4763
|
-
(requirement) => requirement.appliesTo.some((scope) => scopes.includes(scope))
|
|
4764
|
-
),
|
|
4765
|
-
packages: scaffoldPackages({ browser, api, target }),
|
|
4766
|
-
files: scaffoldFiles({
|
|
4767
|
-
payload: {
|
|
4768
|
-
ownerType,
|
|
4769
|
-
portfolio,
|
|
4770
|
-
audience,
|
|
4771
|
-
tenancy,
|
|
4772
|
-
surfaceModel,
|
|
4773
|
-
containerAppId,
|
|
4774
|
-
profile,
|
|
4775
|
-
resources,
|
|
4776
|
-
distributionModel,
|
|
4777
|
-
capabilities
|
|
4778
|
-
},
|
|
4779
|
-
application: applicationPayload,
|
|
4780
|
-
target,
|
|
4781
|
-
browser,
|
|
4782
|
-
api,
|
|
4783
|
-
ciWorkflow
|
|
4784
|
-
}),
|
|
4785
|
-
commands: scaffoldCommands({
|
|
4786
|
-
id,
|
|
4787
|
-
target,
|
|
4788
|
-
audience,
|
|
4789
|
-
tenancy,
|
|
4790
|
-
portfolio,
|
|
4791
|
-
profile
|
|
4792
|
-
}),
|
|
4793
|
-
ciWorkflowPath,
|
|
4794
|
-
ciWorkflow
|
|
4795
|
-
};
|
|
4736
|
+
function normalizePortfolio(value) {
|
|
4737
|
+
if (!value) return "platform";
|
|
4738
|
+
if (value === "platform" || value === "personal") return value;
|
|
4739
|
+
throw new Error("Invalid scaffold portfolio. Use platform or personal.");
|
|
4796
4740
|
}
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
file,
|
|
4801
|
-
absolutePath: resolveInside(root, file.path)
|
|
4802
|
-
}));
|
|
4803
|
-
const existingFiles = [];
|
|
4804
|
-
for (const planned of plannedFiles) {
|
|
4805
|
-
if (await pathExists(planned.absolutePath)) {
|
|
4806
|
-
existingFiles.push(planned.file.path);
|
|
4807
|
-
}
|
|
4741
|
+
function normalizeAudience(value, portfolio) {
|
|
4742
|
+
if (!value) {
|
|
4743
|
+
return portfolio === "personal" ? "personal" : "business";
|
|
4808
4744
|
}
|
|
4809
|
-
if (
|
|
4810
|
-
|
|
4811
|
-
[
|
|
4812
|
-
"Refusing to overwrite existing scaffold files:",
|
|
4813
|
-
...existingFiles.map((filePath) => `- ${filePath}`),
|
|
4814
|
-
"Pass --force to overwrite these files."
|
|
4815
|
-
].join("\n")
|
|
4816
|
-
);
|
|
4745
|
+
if (value === "business" || value === "personal") {
|
|
4746
|
+
return value;
|
|
4817
4747
|
}
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
path: planned.file.path,
|
|
4825
|
-
status: existed ? "overwritten" : "created"
|
|
4826
|
-
});
|
|
4748
|
+
throw new Error("Invalid scaffold audience. Use business or personal.");
|
|
4749
|
+
}
|
|
4750
|
+
function normalizeTenancy(value, audience) {
|
|
4751
|
+
const resolved = value ?? (audience === "personal" ? "household" : "organization");
|
|
4752
|
+
if (resolved !== "organization" && resolved !== "household") {
|
|
4753
|
+
throw new Error("Invalid scaffold tenancy. Use organization or household.");
|
|
4827
4754
|
}
|
|
4828
|
-
|
|
4755
|
+
if (audience === "business" && resolved !== "organization") {
|
|
4756
|
+
throw new Error("Business scaffolds must use organization tenancy.");
|
|
4757
|
+
}
|
|
4758
|
+
if (audience === "personal" && resolved !== "household") {
|
|
4759
|
+
throw new Error("Personal scaffolds must use household tenancy.");
|
|
4760
|
+
}
|
|
4761
|
+
return resolved;
|
|
4829
4762
|
}
|
|
4830
|
-
function
|
|
4831
|
-
|
|
4832
|
-
if (
|
|
4833
|
-
|
|
4834
|
-
scopes.push("api", "agent_surface");
|
|
4763
|
+
function normalizeSurfaceModel(value) {
|
|
4764
|
+
if (!value) return "web_app";
|
|
4765
|
+
if (value === "web_app" || value === "mobile_container" || value === "mobile_module" || value === "messaging_surface") {
|
|
4766
|
+
return value;
|
|
4835
4767
|
}
|
|
4836
|
-
|
|
4768
|
+
throw new Error(
|
|
4769
|
+
"Invalid scaffold surface model. Use web_app, mobile_container, mobile_module, or messaging_surface."
|
|
4770
|
+
);
|
|
4837
4771
|
}
|
|
4838
|
-
function
|
|
4839
|
-
if (
|
|
4840
|
-
|
|
4772
|
+
function normalizeScaffoldProfile(value) {
|
|
4773
|
+
if (!value) return null;
|
|
4774
|
+
if (value === "spatial-assets") return value;
|
|
4775
|
+
throw new Error("Invalid scaffold profile. Use spatial-assets.");
|
|
4776
|
+
}
|
|
4777
|
+
function resolveScaffoldResources(profile, options) {
|
|
4778
|
+
const profileResources = profile === "spatial-assets" ? { d1: true, r2: true, queue: true, workflow: true, containerProcessing: true } : { d1: false, r2: false, queue: false, workflow: false, containerProcessing: false };
|
|
4779
|
+
return {
|
|
4780
|
+
d1: Boolean(profileResources.d1 || options.withD1),
|
|
4781
|
+
r2: Boolean(profileResources.r2 || options.withR2),
|
|
4782
|
+
queue: Boolean(profileResources.queue || options.withQueue),
|
|
4783
|
+
workflow: Boolean(profileResources.workflow || options.withWorkflow),
|
|
4784
|
+
containerProcessing: Boolean(profileResources.containerProcessing || options.withContainerProcessing)
|
|
4785
|
+
};
|
|
4786
|
+
}
|
|
4787
|
+
function normalizeContainerAppId(value, surfaceModel) {
|
|
4788
|
+
const trimmed = String(value || "").trim();
|
|
4789
|
+
if (surfaceModel === "mobile_module") {
|
|
4790
|
+
if (!trimmed) {
|
|
4791
|
+
throw new Error("Mobile-module scaffolds require --container-app-id.");
|
|
4792
|
+
}
|
|
4793
|
+
return trimmed;
|
|
4841
4794
|
}
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4795
|
+
return trimmed || null;
|
|
4796
|
+
}
|
|
4797
|
+
function normalizeApplicationId(application) {
|
|
4798
|
+
const raw = application.trim().toLowerCase();
|
|
4799
|
+
const normalized = raw.replace(/-/g, "_");
|
|
4800
|
+
if (Object.prototype.hasOwnProperty.call(APPLICATIONS, raw)) {
|
|
4801
|
+
return raw;
|
|
4849
4802
|
}
|
|
4850
|
-
if (
|
|
4851
|
-
|
|
4852
|
-
packages.add("@topolo/sdk");
|
|
4803
|
+
if (Object.prototype.hasOwnProperty.call(APPLICATIONS, normalized)) {
|
|
4804
|
+
return normalized;
|
|
4853
4805
|
}
|
|
4854
|
-
|
|
4806
|
+
if (/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(raw) || /^[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$/.test(raw)) {
|
|
4807
|
+
return normalized;
|
|
4808
|
+
}
|
|
4809
|
+
throw new Error(
|
|
4810
|
+
"Application id must be kebab-case or snake_case using lowercase letters, numbers, hyphens, and underscores."
|
|
4811
|
+
);
|
|
4855
4812
|
}
|
|
4856
|
-
function
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
capabilities.push("login", "launcher");
|
|
4813
|
+
function normalizeFirstPartyApplicationId(applicationId) {
|
|
4814
|
+
if (applicationId.startsWith("topolo_") && applicationId.length > "topolo_".length) {
|
|
4815
|
+
return applicationId.slice("topolo_".length);
|
|
4860
4816
|
}
|
|
4861
|
-
|
|
4862
|
-
if (options.api) capabilities.push("api", "widget");
|
|
4863
|
-
return capabilities;
|
|
4817
|
+
return applicationId;
|
|
4864
4818
|
}
|
|
4865
|
-
function
|
|
4866
|
-
|
|
4867
|
-
if (appDir === "TopoloDocs") return "Apps/system/TopoloDocs";
|
|
4868
|
-
if (appDir === "TopoloAuth") return "Apps/system/TopoloAuth";
|
|
4869
|
-
return `${audience === "personal" ? "Apps/personal" : "Apps/business"}/${appDir}`;
|
|
4819
|
+
function toSlug(value) {
|
|
4820
|
+
return value.replace(/_/g, "-");
|
|
4870
4821
|
}
|
|
4871
|
-
function
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
const
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
},
|
|
4891
|
-
{
|
|
4892
|
-
path: `${base}/README.md`,
|
|
4893
|
-
purpose: "Starter README with install, run, and verification commands.",
|
|
4894
|
-
requiredSignals: [
|
|
4895
|
-
"documents the generated app target",
|
|
4896
|
-
"points maintainers to the shared Topolo packages used by the scaffold"
|
|
4897
|
-
],
|
|
4898
|
-
content: readme(application, target, {
|
|
4899
|
-
api: options.api,
|
|
4900
|
-
audience: options.payload.audience,
|
|
4901
|
-
tenancy: options.payload.tenancy,
|
|
4902
|
-
portfolio: options.payload.portfolio,
|
|
4903
|
-
profile: options.payload.profile,
|
|
4904
|
-
resources: options.payload.resources
|
|
4905
|
-
})
|
|
4906
|
-
},
|
|
4907
|
-
{
|
|
4908
|
-
path: `${base}/.gitignore`,
|
|
4909
|
-
purpose: "Generated-app ignore rules.",
|
|
4910
|
-
requiredSignals: [
|
|
4911
|
-
"ignores dependency installs and build output",
|
|
4912
|
-
"keeps local environment files out of source control"
|
|
4913
|
-
],
|
|
4914
|
-
content: gitignore()
|
|
4915
|
-
},
|
|
4916
|
-
{
|
|
4917
|
-
path: `${base}/tsconfig.json`,
|
|
4918
|
-
purpose: "TypeScript configuration for the generated app.",
|
|
4919
|
-
requiredSignals: [
|
|
4920
|
-
"uses strict mode",
|
|
4921
|
-
"includes browser DOM types for React and Worker types for API-backed apps"
|
|
4922
|
-
],
|
|
4923
|
-
content: tsconfigJson(options.api, target)
|
|
4924
|
-
},
|
|
4925
|
-
{
|
|
4926
|
-
path: `${browserBase}/vite.config.ts`,
|
|
4927
|
-
purpose: "Vite browser build configuration.",
|
|
4928
|
-
requiredSignals: [
|
|
4929
|
-
"builds the authenticated browser surface",
|
|
4930
|
-
"keeps generated apps on the standard React/Vite baseline"
|
|
4931
|
-
],
|
|
4932
|
-
content: viteConfig(target)
|
|
4933
|
-
},
|
|
4934
|
-
{
|
|
4935
|
-
path: `${browserBase}/index.html`,
|
|
4936
|
-
purpose: "Browser entry document.",
|
|
4937
|
-
requiredSignals: [
|
|
4938
|
-
"mounts the React app at #root",
|
|
4939
|
-
"uses the generated application display name"
|
|
4940
|
-
],
|
|
4941
|
-
content: indexHtml(application.name)
|
|
4942
|
-
},
|
|
4943
|
-
{
|
|
4944
|
-
path: `${browserBase}/src/main.tsx`,
|
|
4945
|
-
purpose: "React app entrypoint.",
|
|
4946
|
-
requiredSignals: [
|
|
4947
|
-
"renders the generated app component",
|
|
4948
|
-
"imports the generated app stylesheet"
|
|
4949
|
-
],
|
|
4950
|
-
content: mainTsx()
|
|
4951
|
-
},
|
|
4952
|
-
{
|
|
4953
|
-
path: `${browserBase}/src/env.d.ts`,
|
|
4954
|
-
purpose: "Vite environment variable typing.",
|
|
4955
|
-
requiredSignals: [
|
|
4956
|
-
"loads vite/client import.meta.env types",
|
|
4957
|
-
"declares the Topolo environment variables consumed by the starter"
|
|
4958
|
-
],
|
|
4959
|
-
content: envDts(options.api)
|
|
4960
|
-
},
|
|
4961
|
-
...options.payload.resources.workflow ? [
|
|
4962
|
-
{
|
|
4963
|
-
path: `${base}/src/cloudflare-workers.d.ts`,
|
|
4964
|
-
purpose: "Minimal Cloudflare Workflow entrypoint typing for scaffolded Workers.",
|
|
4965
|
-
requiredSignals: [
|
|
4966
|
-
"declares cloudflare:workers WorkflowEntrypoint for TypeScript",
|
|
4967
|
-
"keeps Workflow imports deployable without adding a new dependency"
|
|
4968
|
-
],
|
|
4969
|
-
content: cloudflareWorkersDts()
|
|
4970
|
-
}
|
|
4971
|
-
] : [],
|
|
4972
|
-
{
|
|
4973
|
-
path: `${browserBase}/src/styles.css`,
|
|
4974
|
-
purpose: "Minimal app-local styles outside the shared Topolo shell.",
|
|
4975
|
-
requiredSignals: [
|
|
4976
|
-
"does not fork the shared shell chrome",
|
|
4977
|
-
"keeps app-specific layout scoped under generated classes"
|
|
4978
|
-
],
|
|
4979
|
-
content: stylesCss()
|
|
4980
|
-
}
|
|
4981
|
-
];
|
|
4982
|
-
if (target === "platform") {
|
|
4983
|
-
files.push(
|
|
4984
|
-
{
|
|
4985
|
-
path: `${base}/topolo.app.json`,
|
|
4986
|
-
purpose: "Canonical app manifest that drives generated Wrangler and CloudControl metadata.",
|
|
4987
|
-
requiredSignals: [
|
|
4988
|
-
"declares web/api targets with canonical names and app-local target directories",
|
|
4989
|
-
"keeps Cloudflare account, domain, service id, and bindings in one manifest source of truth"
|
|
4990
|
-
],
|
|
4991
|
-
content: topoloAppManifestJson(application, {
|
|
4992
|
-
api: options.api,
|
|
4993
|
-
resources: options.payload.resources,
|
|
4994
|
-
profile: options.payload.profile
|
|
4995
|
-
})
|
|
4996
|
-
},
|
|
4997
|
-
{
|
|
4998
|
-
path: `${base}/topolo.cloudcontrol.json`,
|
|
4999
|
-
purpose: "CloudControl source of truth for deploy targets, routes, bindings, and CI metadata.",
|
|
5000
|
-
requiredSignals: [
|
|
5001
|
-
"production URLs and Cloudflare targets are explicit",
|
|
5002
|
-
"resource bindings and required secrets are declared without secret values"
|
|
5003
|
-
],
|
|
5004
|
-
content: cloudcontrolJson(application, {
|
|
5005
|
-
api: options.api,
|
|
5006
|
-
audience: options.payload.audience,
|
|
5007
|
-
profile: options.payload.profile,
|
|
5008
|
-
resources: options.payload.resources
|
|
5009
|
-
})
|
|
5010
|
-
},
|
|
5011
|
-
{
|
|
5012
|
-
path: `${base}/.github/workflows/topolo-conformance.yml`,
|
|
5013
|
-
purpose: "PR/push gate that runs the local Topolo app audit with docs evidence.",
|
|
5014
|
-
requiredSignals: [
|
|
5015
|
-
"checks out this repo under its Apps lane directory",
|
|
5016
|
-
"checks out TopoloDocs under Apps/system/TopoloDocs",
|
|
5017
|
-
"checks out topolo-sdk and TopoloCli from main so the gate does not wait for an npm publish",
|
|
5018
|
-
"runs topolo apps audit with --local and --fail-on needs_review"
|
|
5019
|
-
],
|
|
5020
|
-
content: options.ciWorkflow ?? ""
|
|
5021
|
-
},
|
|
5022
|
-
{
|
|
5023
|
-
path: `Apps/system/TopoloDocs/src/content/systems/topolo-${slug}.json`,
|
|
5024
|
-
purpose: "Canonical system registry entry.",
|
|
5025
|
-
requiredSignals: [
|
|
5026
|
-
`repo_paths includes ${base}`,
|
|
5027
|
-
"doc_paths includes internal handbook coverage",
|
|
5028
|
-
"security_assurance records the auth, tenancy, deployment, and verification boundary"
|
|
5029
|
-
],
|
|
5030
|
-
content: systemJson(application, {
|
|
5031
|
-
api: options.api,
|
|
5032
|
-
audience: options.payload.audience,
|
|
5033
|
-
tenancy: options.payload.tenancy,
|
|
5034
|
-
portfolio: options.payload.portfolio,
|
|
5035
|
-
profile: options.payload.profile,
|
|
5036
|
-
resources: options.payload.resources
|
|
5037
|
-
})
|
|
5038
|
-
},
|
|
5039
|
-
{
|
|
5040
|
-
path: `Apps/system/TopoloDocs/src/content/internal/apps/${slug}.mdx`,
|
|
5041
|
-
purpose: "Internal operator handbook.",
|
|
5042
|
-
requiredSignals: [
|
|
5043
|
-
"documents ownership, routes, auth boundary, deployment, operations, and verification",
|
|
5044
|
-
"last_verified is updated when behavior changes"
|
|
5045
|
-
],
|
|
5046
|
-
content: internalDocs(application, {
|
|
5047
|
-
api: options.api,
|
|
5048
|
-
audience: options.payload.audience,
|
|
5049
|
-
tenancy: options.payload.tenancy,
|
|
5050
|
-
portfolio: options.payload.portfolio,
|
|
5051
|
-
profile: options.payload.profile,
|
|
5052
|
-
resources: options.payload.resources
|
|
5053
|
-
})
|
|
5054
|
-
},
|
|
5055
|
-
{
|
|
5056
|
-
path: `Apps/system/TopoloDocs/src/content/public/applications/${slug}.mdx`,
|
|
5057
|
-
purpose: "Public application overview.",
|
|
5058
|
-
requiredSignals: [
|
|
5059
|
-
"documents the product surface without internal-only details",
|
|
5060
|
-
"links back to the canonical system registry entry"
|
|
5061
|
-
],
|
|
5062
|
-
content: publicDocs(application, {
|
|
5063
|
-
api: options.api,
|
|
5064
|
-
audience: options.payload.audience,
|
|
5065
|
-
tenancy: options.payload.tenancy,
|
|
5066
|
-
portfolio: options.payload.portfolio,
|
|
5067
|
-
profile: options.payload.profile,
|
|
5068
|
-
resources: options.payload.resources
|
|
5069
|
-
})
|
|
5070
|
-
}
|
|
5071
|
-
);
|
|
5072
|
-
if (options.api && application.serviceId) {
|
|
5073
|
-
files.push(
|
|
5074
|
-
{
|
|
5075
|
-
path: `Apps/system/TopoloAuth/catalog/service-manifests/${application.serviceId}.json`,
|
|
5076
|
-
purpose: "Auth service manifest for first-party permissions, role bundles, and API-key scopes.",
|
|
5077
|
-
requiredSignals: [
|
|
5078
|
-
"serviceId matches the generated Worker service id",
|
|
5079
|
-
"permissions, role bundles, and API-key scopes exist before the app is catalog-visible",
|
|
5080
|
-
"systems metadata maps the service back to the generated TopoloDocs entries"
|
|
5081
|
-
],
|
|
5082
|
-
content: serviceManifestJson(application, {
|
|
5083
|
-
audience: options.payload.audience,
|
|
5084
|
-
tenancy: options.payload.tenancy
|
|
5085
|
-
})
|
|
5086
|
-
}
|
|
5087
|
-
);
|
|
5088
|
-
}
|
|
5089
|
-
} else {
|
|
5090
|
-
files.push(
|
|
5091
|
-
{
|
|
5092
|
-
path: `${base}/topolo.app.json`,
|
|
5093
|
-
purpose: "Third-party Topolo application manifest.",
|
|
5094
|
-
requiredSignals: [
|
|
5095
|
-
"declares the external app id, callback URLs, and requested platform scopes",
|
|
5096
|
-
"does not include internal Topolo CloudControl or canonical-docs ownership"
|
|
5097
|
-
],
|
|
5098
|
-
content: developerManifest(application, {
|
|
5099
|
-
api: options.api,
|
|
5100
|
-
audience: options.payload.audience,
|
|
5101
|
-
tenancy: options.payload.tenancy,
|
|
5102
|
-
surfaceModel: options.payload.surfaceModel,
|
|
5103
|
-
containerAppId: options.payload.containerAppId,
|
|
5104
|
-
distributionModel: options.payload.distributionModel,
|
|
5105
|
-
capabilities: options.payload.capabilities
|
|
5106
|
-
})
|
|
5107
|
-
},
|
|
5108
|
-
{
|
|
5109
|
-
path: `${base}/.env.example`,
|
|
5110
|
-
purpose: "Developer environment template for local auth configuration.",
|
|
5111
|
-
requiredSignals: [
|
|
5112
|
-
"names the Topolo Auth URL",
|
|
5113
|
-
"keeps OAuth client ids and service ids out of source control by default"
|
|
5114
|
-
],
|
|
5115
|
-
content: envExample(application)
|
|
5116
|
-
}
|
|
5117
|
-
);
|
|
4822
|
+
function toConstantCase(value) {
|
|
4823
|
+
return value.split(/[-_]/).filter(Boolean).join("_").toUpperCase();
|
|
4824
|
+
}
|
|
4825
|
+
function toPascalCase(value) {
|
|
4826
|
+
const acronyms = /* @__PURE__ */ new Set(["api", "cli", "crm", "mcp", "mdm", "p2p"]);
|
|
4827
|
+
return value.split(/[-_]/).filter(Boolean).map((part) => acronyms.has(part.toLowerCase()) ? part.toUpperCase() : part.slice(0, 1).toUpperCase() + part.slice(1)).join("");
|
|
4828
|
+
}
|
|
4829
|
+
function toTitle(value) {
|
|
4830
|
+
return value.replace(/^topolo[-_\s]*/i, "Topolo ").split(/[-_\s]+/).filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
|
|
4831
|
+
}
|
|
4832
|
+
function json(value) {
|
|
4833
|
+
return JSON.stringify(value, null, 2);
|
|
4834
|
+
}
|
|
4835
|
+
async function pathExists(path4) {
|
|
4836
|
+
try {
|
|
4837
|
+
await access(path4, constants.F_OK);
|
|
4838
|
+
return true;
|
|
4839
|
+
} catch {
|
|
4840
|
+
return false;
|
|
5118
4841
|
}
|
|
5119
|
-
if (options.browser) {
|
|
5120
|
-
if (target === "platform") {
|
|
5121
|
-
files.push({
|
|
5122
|
-
path: `${browserBase}/src/service-identity.ts`,
|
|
5123
|
-
purpose: "Central service identity resolver shared by browser and Worker runtime surfaces.",
|
|
5124
|
-
requiredSignals: [
|
|
5125
|
-
"exports the generated app id, display name, and stable Auth service slug",
|
|
5126
|
-
"requires the provisioned Auth service id from the Worker deployment config"
|
|
5127
|
-
],
|
|
5128
|
-
content: serviceIdentityTs(application)
|
|
5129
|
-
});
|
|
5130
|
-
}
|
|
5131
|
-
files.push({
|
|
5132
|
-
path: `${browserBase}/src/App.tsx`,
|
|
5133
|
-
purpose: "Authenticated browser surface.",
|
|
5134
|
-
requiredSignals: [
|
|
5135
|
-
"bootstraps routes, auth provider, and shared shell through @topolo-io/app-shell",
|
|
5136
|
-
"uses @topolo-io/auth-client through the app-shell wrapper for app-origin auth flows",
|
|
5137
|
-
"keeps launcher and account-menu behavior on the shared platform surface",
|
|
5138
|
-
"passes app-specific account actions through accountMenuItems or TopoloAccountMenu"
|
|
5139
|
-
],
|
|
5140
|
-
content: appTsx(application, target, {
|
|
5141
|
-
audience: options.payload.audience,
|
|
5142
|
-
portfolio: options.payload.portfolio
|
|
5143
|
-
})
|
|
5144
|
-
});
|
|
5145
|
-
}
|
|
5146
|
-
if (options.api) {
|
|
5147
|
-
files.push(
|
|
5148
|
-
{
|
|
5149
|
-
path: `${apiBase}/wrangler.toml`,
|
|
5150
|
-
purpose: "Cloudflare Worker runtime configuration.",
|
|
5151
|
-
requiredSignals: [
|
|
5152
|
-
"bindings match topolo.cloudcontrol.json or topolo.app.json",
|
|
5153
|
-
"secrets are referenced by name only"
|
|
5154
|
-
],
|
|
5155
|
-
content: target === "platform" ? platformApiWranglerToml(application, options.payload.resources, options.payload.profile) : wranglerToml(application, target, options.payload.resources, options.payload.profile)
|
|
5156
|
-
},
|
|
5157
|
-
{
|
|
5158
|
-
path: `${apiBase}/src/worker.ts`,
|
|
5159
|
-
purpose: "Protected service boundary.",
|
|
5160
|
-
requiredSignals: [
|
|
5161
|
-
"uses @topolo-io/worker-runtime for Worker auth, gatewaying, headers, and request helpers",
|
|
5162
|
-
"derives organization from the credential rather than client-supplied orgId",
|
|
5163
|
-
"emits or propagates X-Topolo-Request-Id and X-Topolo-Client"
|
|
5164
|
-
],
|
|
5165
|
-
content: workerTs(application, target, {
|
|
5166
|
-
audience: options.payload.audience,
|
|
5167
|
-
tenancy: options.payload.tenancy,
|
|
5168
|
-
profile: options.payload.profile,
|
|
5169
|
-
resources: options.payload.resources
|
|
5170
|
-
})
|
|
5171
|
-
}
|
|
5172
|
-
);
|
|
5173
|
-
if (target === "platform") {
|
|
5174
|
-
files.push(
|
|
5175
|
-
{
|
|
5176
|
-
path: `${apiBase}/src/service-identity.ts`,
|
|
5177
|
-
purpose: "Runtime service identity resolver for the API Worker.",
|
|
5178
|
-
requiredSignals: [
|
|
5179
|
-
"resolves the concrete Auth service id from Worker vars",
|
|
5180
|
-
"keeps the API Worker independent from browser-only import.meta usage"
|
|
5181
|
-
],
|
|
5182
|
-
content: apiServiceIdentityTs(application)
|
|
5183
|
-
},
|
|
5184
|
-
{
|
|
5185
|
-
path: `${browserBase}/src/worker.ts`,
|
|
5186
|
-
purpose: "Browser Worker that serves assets and forwards API paths to the API Worker binding.",
|
|
5187
|
-
requiredSignals: [
|
|
5188
|
-
"keeps the frontend Worker thin",
|
|
5189
|
-
"routes /api/* to the API service binding instead of owning backend routes locally"
|
|
5190
|
-
],
|
|
5191
|
-
content: webWorkerTs(application)
|
|
5192
|
-
},
|
|
5193
|
-
{
|
|
5194
|
-
path: `${browserBase}/wrangler.toml`,
|
|
5195
|
-
purpose: "Cloudflare Worker static-assets configuration for the browser surface.",
|
|
5196
|
-
requiredSignals: [
|
|
5197
|
-
"serves Vite assets through a Worker with assets",
|
|
5198
|
-
"binds the frontend Worker to the API Worker for /api/* requests"
|
|
5199
|
-
],
|
|
5200
|
-
content: platformWebWranglerToml(application)
|
|
5201
|
-
}
|
|
5202
|
-
);
|
|
5203
|
-
}
|
|
5204
|
-
}
|
|
5205
|
-
return files;
|
|
5206
4842
|
}
|
|
5207
|
-
function
|
|
5208
|
-
const
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
`topolo apps scaffold ${scaffoldId}${audienceFlags} --write --repo-root .`,
|
|
5213
|
-
`cd topolo-${scaffoldId}`,
|
|
5214
|
-
"npm install",
|
|
5215
|
-
"npm run typecheck",
|
|
5216
|
-
"npm run build"
|
|
5217
|
-
];
|
|
4843
|
+
function resolveInside(root, filePath) {
|
|
4844
|
+
const resolved = resolve(root, filePath);
|
|
4845
|
+
const relativePath = relative(root, resolved);
|
|
4846
|
+
if (relativePath.startsWith("..") || relativePath === "" || relativePath.startsWith("/")) {
|
|
4847
|
+
throw new Error(`Scaffold file path escapes the target root: ${filePath}`);
|
|
5218
4848
|
}
|
|
5219
|
-
|
|
5220
|
-
const profileFlags = options.profile ? ` --profile ${options.profile}` : "";
|
|
5221
|
-
return [
|
|
5222
|
-
`topolo platform apps scaffold ${scaffoldId}${portfolioFlags}${profileFlags} --write --provision --repo-root /Users/ashes/Projects/Topolo/Platform --json`,
|
|
5223
|
-
"topolo apps requirements --json",
|
|
5224
|
-
`topolo apps audit ${options.id} --local --repo-root /Users/ashes/Projects/Topolo/Platform --fail-on needs_review --json`,
|
|
5225
|
-
`topolo apps audit ${options.id} --local --repo-root /Users/ashes/Projects/Topolo/Platform --fail-on partial --json`
|
|
5226
|
-
];
|
|
4849
|
+
return resolved;
|
|
5227
4850
|
}
|
|
5228
|
-
function
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
" repository: Topolo-io/TopoloDocs",
|
|
5237
|
-
" path: Apps/system/TopoloDocs",
|
|
5238
|
-
" token: ${{ steps.app-token.outputs.token || secrets.TOPOLO_CI_REPO_TOKEN }}"
|
|
5239
|
-
];
|
|
5240
|
-
const cliCheckoutStep = options.appDir === "TopoloCLI" || options.appDir === "TopoloCli" ? [] : [
|
|
5241
|
-
"",
|
|
5242
|
-
" - name: Checkout Topolo CLI",
|
|
5243
|
-
` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
|
|
5244
|
-
" uses: actions/checkout@v4",
|
|
5245
|
-
" with:",
|
|
5246
|
-
" repository: Topolo-io/TopoloCli",
|
|
5247
|
-
" path: Apps/system/TopoloCLI",
|
|
5248
|
-
" token: ${{ steps.app-token.outputs.token || secrets.TOPOLO_CI_REPO_TOKEN }}"
|
|
5249
|
-
];
|
|
5250
|
-
return [
|
|
5251
|
-
"name: Topolo App Conformance",
|
|
5252
|
-
"",
|
|
5253
|
-
"on:",
|
|
5254
|
-
" pull_request:",
|
|
5255
|
-
" push:",
|
|
5256
|
-
" branches:",
|
|
5257
|
-
" - main",
|
|
5258
|
-
" workflow_dispatch:",
|
|
5259
|
-
"",
|
|
5260
|
-
"permissions:",
|
|
5261
|
-
" contents: read",
|
|
5262
|
-
"",
|
|
5263
|
-
"jobs:",
|
|
5264
|
-
" conformance:",
|
|
5265
|
-
" runs-on: ubuntu-latest",
|
|
5266
|
-
" env:",
|
|
5267
|
-
` HAS_TOPOLO_PACKAGES_APP: \${{ secrets.TOPOLO_PACKAGES_APP_ID != '' && secrets.TOPOLO_PACKAGES_APP_PRIVATE_KEY != '' }}`,
|
|
5268
|
-
` HAS_TOPOLO_CI_REPO_TOKEN: \${{ secrets.TOPOLO_CI_REPO_TOKEN != '' }}`,
|
|
5269
|
-
" steps:",
|
|
5270
|
-
" - name: Checkout application",
|
|
5271
|
-
" uses: actions/checkout@v4",
|
|
5272
|
-
" with:",
|
|
5273
|
-
` path: ${appPath}`,
|
|
5274
|
-
"",
|
|
5275
|
-
" - name: Mint Topolo cross-repo token",
|
|
5276
|
-
" id: app-token",
|
|
5277
|
-
` if: env.HAS_TOPOLO_PACKAGES_APP == 'true'`,
|
|
5278
|
-
" uses: actions/create-github-app-token@v1",
|
|
5279
|
-
" with:",
|
|
5280
|
-
` app-id: \${{ secrets.TOPOLO_PACKAGES_APP_ID }}`,
|
|
5281
|
-
` private-key: \${{ secrets.TOPOLO_PACKAGES_APP_PRIVATE_KEY }}`,
|
|
5282
|
-
" owner: Topolo-io",
|
|
5283
|
-
...docsCheckoutStep,
|
|
5284
|
-
"",
|
|
5285
|
-
" - name: Checkout Topolo SDK",
|
|
5286
|
-
` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
|
|
5287
|
-
" uses: actions/checkout@v4",
|
|
5288
|
-
" with:",
|
|
5289
|
-
" repository: Topolo-io/topolo-sdk",
|
|
5290
|
-
" path: Apps/packages/topolo-sdk",
|
|
5291
|
-
" token: ${{ steps.app-token.outputs.token || secrets.TOPOLO_CI_REPO_TOKEN }}",
|
|
5292
|
-
...cliCheckoutStep,
|
|
5293
|
-
"",
|
|
5294
|
-
" - name: Skip conformance without cross-repo token",
|
|
5295
|
-
` if: steps.app-token.outputs.token == '' && env.HAS_TOPOLO_CI_REPO_TOKEN != 'true'`,
|
|
5296
|
-
" run: |",
|
|
5297
|
-
' echo "::warning::Skipping Topolo app conformance because no Topolo cross-repo token is available to this workflow context."',
|
|
5298
|
-
"",
|
|
5299
|
-
" - name: Setup Node.js",
|
|
5300
|
-
` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
|
|
5301
|
-
" uses: actions/setup-node@v4",
|
|
5302
|
-
" with:",
|
|
5303
|
-
" node-version: 20",
|
|
5304
|
-
"",
|
|
5305
|
-
" - name: Build Topolo SDK",
|
|
5306
|
-
` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
|
|
5307
|
-
" working-directory: Apps/packages/topolo-sdk",
|
|
5308
|
-
" run: |",
|
|
5309
|
-
" npm install --no-audit --no-fund",
|
|
5310
|
-
" npm run build",
|
|
5311
|
-
"",
|
|
5312
|
-
" - name: Build Topolo CLI",
|
|
5313
|
-
` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
|
|
5314
|
-
" working-directory: Apps/system/TopoloCLI",
|
|
5315
|
-
" run: |",
|
|
5316
|
-
" npm install --no-audit --no-fund",
|
|
5317
|
-
" npm run build",
|
|
5318
|
-
"",
|
|
5319
|
-
" - name: Run Topolo app conformance audit",
|
|
5320
|
-
` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
|
|
5321
|
-
" run: |",
|
|
5322
|
-
` node Apps/system/TopoloCLI/dist/index.js apps audit ${options.id} --local --repo-root "$GITHUB_WORKSPACE" --fail-on needs_review --json`,
|
|
5323
|
-
"",
|
|
5324
|
-
" - name: Record green CI evidence",
|
|
5325
|
-
` if: \${{ success() && (steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true') }}`,
|
|
5326
|
-
` working-directory: ${appPath}`,
|
|
5327
|
-
" run: |",
|
|
5328
|
-
` node "$GITHUB_WORKSPACE/Apps/system/TopoloCLI/dist/index.js" apps record-evidence ${options.id} --build --typecheck --test --commit "$GITHUB_SHA"`
|
|
5329
|
-
].join("\n");
|
|
4851
|
+
function escapeHtml(value) {
|
|
4852
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
4853
|
+
}
|
|
4854
|
+
function escapeSingleQuoted(value) {
|
|
4855
|
+
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
4856
|
+
}
|
|
4857
|
+
function escapeFrontmatter(value) {
|
|
4858
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
5330
4859
|
}
|
|
4860
|
+
function firstPartyAppPath(appDir, _audience) {
|
|
4861
|
+
if (appDir === "TopoloCli" || appDir === "TopoloCLI") return "topolo-platform/packages/topolo-cli";
|
|
4862
|
+
if (appDir === "TopoloDocs") return "system-apps/TopoloDocs";
|
|
4863
|
+
if (appDir === "TopoloAuth") return "topolo-platform/core/TopoloAuth";
|
|
4864
|
+
return `apps/${appDir}`;
|
|
4865
|
+
}
|
|
4866
|
+
|
|
4867
|
+
// src/commands/app-scaffold-package-template.ts
|
|
5331
4868
|
function packageJson(application, target, options) {
|
|
5332
4869
|
const dependencies = {};
|
|
5333
4870
|
const devDependencies = {
|
|
@@ -5342,10 +4879,10 @@ function packageJson(application, target, options) {
|
|
|
5342
4879
|
typecheck: "tsc --noEmit"
|
|
5343
4880
|
};
|
|
5344
4881
|
if (options.browser) {
|
|
5345
|
-
dependencies.react = "^
|
|
5346
|
-
dependencies["react-dom"] = "^
|
|
5347
|
-
devDependencies["@types/react"] = "^
|
|
5348
|
-
devDependencies["@types/react-dom"] = "^
|
|
4882
|
+
dependencies.react = "^19.2.0";
|
|
4883
|
+
dependencies["react-dom"] = "^19.2.0";
|
|
4884
|
+
devDependencies["@types/react"] = "^19.2.2";
|
|
4885
|
+
devDependencies["@types/react-dom"] = "^19.2.2";
|
|
5349
4886
|
devDependencies["@vitejs/plugin-react"] = "^4.7.0";
|
|
5350
4887
|
if (target === "platform") {
|
|
5351
4888
|
dependencies["@topolo-io/auth-client"] = topoloPackageVersion("@topolo-io/auth-client", target);
|
|
@@ -5394,28 +4931,28 @@ function topoloPackageVersion(packageName, target) {
|
|
|
5394
4931
|
if (target === "developer") {
|
|
5395
4932
|
if (packageName === "@topolo/login") return "^0.1.0";
|
|
5396
4933
|
if (packageName === "@topolo-io/auth-client") return "^1.0.0";
|
|
5397
|
-
if (packageName === "@topolo/sdk") return "^0.4.
|
|
4934
|
+
if (packageName === "@topolo/sdk") return "^0.4.9";
|
|
5398
4935
|
return "^0.1.0";
|
|
5399
4936
|
}
|
|
5400
|
-
const
|
|
5401
|
-
"@topolo/sdk": "
|
|
5402
|
-
"@topolo/login": "
|
|
5403
|
-
"@topolo-io/app-funnel": "
|
|
5404
|
-
"@topolo-io/app-launcher": "
|
|
5405
|
-
"@topolo-io/app-shell": "
|
|
5406
|
-
"@topolo-io/auth-client": "
|
|
5407
|
-
"@topolo-io/auth-middleware": "
|
|
5408
|
-
"@topolo-io/worker-runtime": "
|
|
5409
|
-
"@topolo-io/design-tokens": "
|
|
5410
|
-
"@topolo-io/onboarding": "
|
|
5411
|
-
"@topolo-io/tour": "
|
|
5412
|
-
"@topolo-io/ui-kit": "
|
|
4937
|
+
const platformPackageVersions = {
|
|
4938
|
+
"@topolo/sdk": "^0.4.9",
|
|
4939
|
+
"@topolo/login": "^0.1.0",
|
|
4940
|
+
"@topolo-io/app-funnel": "^0.1.6",
|
|
4941
|
+
"@topolo-io/app-launcher": "^0.1.15",
|
|
4942
|
+
"@topolo-io/app-shell": "^0.5.54",
|
|
4943
|
+
"@topolo-io/auth-client": "^1.2.1",
|
|
4944
|
+
"@topolo-io/auth-middleware": "^0.3.4",
|
|
4945
|
+
"@topolo-io/worker-runtime": "^0.1.11",
|
|
4946
|
+
"@topolo-io/design-tokens": "^0.1.8",
|
|
4947
|
+
"@topolo-io/onboarding": "^0.9.5",
|
|
4948
|
+
"@topolo-io/tour": "^0.3.4",
|
|
4949
|
+
"@topolo-io/ui-kit": "^0.4.233"
|
|
5413
4950
|
};
|
|
5414
|
-
const
|
|
5415
|
-
if (!
|
|
4951
|
+
const version = platformPackageVersions[packageName];
|
|
4952
|
+
if (!version) {
|
|
5416
4953
|
throw new Error(`Unknown Topolo platform package: ${packageName}`);
|
|
5417
4954
|
}
|
|
5418
|
-
return
|
|
4955
|
+
return version;
|
|
5419
4956
|
}
|
|
5420
4957
|
function hasScaffoldResources(resources) {
|
|
5421
4958
|
return resources.d1 || resources.r2 || resources.queue || resources.workflow || resources.containerProcessing;
|
|
@@ -5423,11 +4960,12 @@ function hasScaffoldResources(resources) {
|
|
|
5423
4960
|
function resourceConfig(application, profile) {
|
|
5424
4961
|
const slug = toSlug(application.id);
|
|
5425
4962
|
const pascal = toPascalCase(application.id);
|
|
4963
|
+
const constant = toConstantCase(application.id);
|
|
5426
4964
|
if (profile === "spatial-assets") {
|
|
5427
4965
|
return {
|
|
5428
|
-
d1Binding:
|
|
5429
|
-
d1ProductionName: `topolo-${slug}-
|
|
5430
|
-
d1StagingName: `topolo-${slug}-staging
|
|
4966
|
+
d1Binding: `${constant}_CORE_DB`,
|
|
4967
|
+
d1ProductionName: `topolo-${slug}-core-prod`,
|
|
4968
|
+
d1StagingName: `topolo-${slug}-core-staging`,
|
|
5431
4969
|
r2Binding: "SPACES_ASSETS",
|
|
5432
4970
|
r2ProductionName: `topolo-${slug}-assets`,
|
|
5433
4971
|
r2StagingName: `topolo-${slug}-assets-staging`,
|
|
@@ -5443,9 +4981,9 @@ function resourceConfig(application, profile) {
|
|
|
5443
4981
|
};
|
|
5444
4982
|
}
|
|
5445
4983
|
return {
|
|
5446
|
-
d1Binding:
|
|
5447
|
-
d1ProductionName: `topolo-${slug}-prod`,
|
|
5448
|
-
d1StagingName: `topolo-${slug}-staging`,
|
|
4984
|
+
d1Binding: `${constant}_CORE_DB`,
|
|
4985
|
+
d1ProductionName: `topolo-${slug}-core-prod`,
|
|
4986
|
+
d1StagingName: `topolo-${slug}-core-staging`,
|
|
5449
4987
|
r2Binding: "APP_ASSETS",
|
|
5450
4988
|
r2ProductionName: `topolo-${slug}-assets`,
|
|
5451
4989
|
r2StagingName: `topolo-${slug}-assets-staging`,
|
|
@@ -5460,6 +4998,8 @@ function resourceConfig(application, profile) {
|
|
|
5460
4998
|
containerStagingName: `topolo-${slug}-processor-staging`
|
|
5461
4999
|
};
|
|
5462
5000
|
}
|
|
5001
|
+
|
|
5002
|
+
// src/commands/app-scaffold-browser-template.ts
|
|
5463
5003
|
function readme(application, target, options) {
|
|
5464
5004
|
const displayName = toTitle(application.name);
|
|
5465
5005
|
const slug = toSlug(application.id);
|
|
@@ -5541,6 +5081,67 @@ function readme(application, target, options) {
|
|
|
5541
5081
|
return `${lines.join("\n")}
|
|
5542
5082
|
`;
|
|
5543
5083
|
}
|
|
5084
|
+
function agentRules(application, target, options) {
|
|
5085
|
+
const displayName = toTitle(application.name);
|
|
5086
|
+
if (target === "developer") {
|
|
5087
|
+
return [
|
|
5088
|
+
`# AGENTS.md - ${displayName}`,
|
|
5089
|
+
"",
|
|
5090
|
+
"These rules apply to this scaffolded Topolo developer application.",
|
|
5091
|
+
"",
|
|
5092
|
+
"## Scope First",
|
|
5093
|
+
"",
|
|
5094
|
+
"- Confirm whether a change belongs in this app, the public Topolo SDK/Login integration boundary, or the registered `topolo.app.json` manifest before editing.",
|
|
5095
|
+
"- Keep public developer scaffolds independent from private first-party Topolo packages.",
|
|
5096
|
+
"- Keep `topolo.app.json`, callback URLs, requested scopes, and README verification steps aligned with runtime behavior.",
|
|
5097
|
+
"",
|
|
5098
|
+
"## Implementation Rules",
|
|
5099
|
+
"",
|
|
5100
|
+
"- Make focused changes only; do not rewrite generated structure unless the task requires it.",
|
|
5101
|
+
"- For API-backed changes, validate bearer tokens through Topolo Auth and derive tenant context from the verified credential.",
|
|
5102
|
+
"- For browser changes, keep the OAuth authorization-code + PKCE flow intact and do not hardcode user, organization, or household ids.",
|
|
5103
|
+
"- Add or update tests for changed behavior before handoff.",
|
|
5104
|
+
"",
|
|
5105
|
+
"## Verification",
|
|
5106
|
+
"",
|
|
5107
|
+
"- Run `npm run typecheck` and `npm run build` before handoff.",
|
|
5108
|
+
...options.api ? ["- Run Worker deploy dry-runs or the project deploy check after changing API bindings."] : [],
|
|
5109
|
+
"- Flag any required Topolo Developers registration or provisioning follow-up explicitly.",
|
|
5110
|
+
""
|
|
5111
|
+
].join("\n");
|
|
5112
|
+
}
|
|
5113
|
+
const portfolioLabel = options.portfolio === "personal" ? "personal" : "platform";
|
|
5114
|
+
const tenantLabel = options.tenancy === "household" ? "household" : "organization";
|
|
5115
|
+
return [
|
|
5116
|
+
`# AGENTS.md - ${displayName}`,
|
|
5117
|
+
"",
|
|
5118
|
+
"These rules apply to this first-party Topolo scaffold.",
|
|
5119
|
+
"",
|
|
5120
|
+
"## Canonical Ownership",
|
|
5121
|
+
"",
|
|
5122
|
+
"- The workspace-level `/Users/ashes/Projects/Topolo/AGENTS.md` rules still apply.",
|
|
5123
|
+
`- This app is a first-party ${portfolioLabel} app under \`apps/${application.appDir}\` with ${tenantLabel}-scoped product behavior.`,
|
|
5124
|
+
"- Put reusable UI in shared platform packages and reusable API/runtime behavior in shared platform packages; keep this app local only for app-specific product logic.",
|
|
5125
|
+
"- Keep `topolo.app.json`, `topolo.cloudcontrol.json`, TopoloDocs system/public/internal docs, and Auth service manifests aligned with behavior changes.",
|
|
5126
|
+
"- Keep `topolo.app.json` actions current for callable product operations and mark mutating, external-send, billing, export, destructive, and provisioning actions for confirmation.",
|
|
5127
|
+
"",
|
|
5128
|
+
"## Implementation Rules",
|
|
5129
|
+
"",
|
|
5130
|
+
"- Start from `staging`, preserve other agents' work, and do not revert unrelated local changes.",
|
|
5131
|
+
"- Make contained changes at the real ownership boundary; do not patch around shared runtime, auth, launcher, shell, or resource-context issues locally.",
|
|
5132
|
+
"- Browser code should stay on the shared landing/login/callback/AppShell baseline generated by the scaffold.",
|
|
5133
|
+
...options.api ? ["- API code should stay on `@topolo-io/worker-runtime`, derive auth context from verified credentials, and preserve Nexus usage monitoring."] : [],
|
|
5134
|
+
...hasScaffoldResources(options.resources) ? ["- Keep Cloudflare resource bindings in Wrangler, CloudControl, Env typing, docs, and README verification output in sync."] : [],
|
|
5135
|
+
"",
|
|
5136
|
+
"## Verification",
|
|
5137
|
+
"",
|
|
5138
|
+
"- Add or update tests for changed behavior; newly added code should be covered.",
|
|
5139
|
+
"- Run the most specific affected tests plus `npm run typecheck` and `npm run build` before handoff.",
|
|
5140
|
+
"- Run `npm run check:deploy-config` after resource binding changes when the script exists.",
|
|
5141
|
+
"- Deploy and verify staging before any production promotion.",
|
|
5142
|
+
""
|
|
5143
|
+
].join("\n");
|
|
5144
|
+
}
|
|
5544
5145
|
function tsconfigJson(api, target) {
|
|
5545
5146
|
const include = target === "platform" ? ["apps/web/src", ...api ? ["apps/api/src"] : []] : ["src"];
|
|
5546
5147
|
return `${json({
|
|
@@ -5904,6 +5505,8 @@ function stylesCss() {
|
|
|
5904
5505
|
""
|
|
5905
5506
|
].join("\n");
|
|
5906
5507
|
}
|
|
5508
|
+
|
|
5509
|
+
// src/commands/app-scaffold-app-template.ts
|
|
5907
5510
|
function appTsx(application, target, options) {
|
|
5908
5511
|
if (target === "developer") return developerAppTsx(application);
|
|
5909
5512
|
return platformAppTsx(application, options);
|
|
@@ -6331,6 +5934,13 @@ function developerAppTsx(application) {
|
|
|
6331
5934
|
""
|
|
6332
5935
|
].join("\n");
|
|
6333
5936
|
}
|
|
5937
|
+
|
|
5938
|
+
// src/commands/app-scaffold-cloudflare-constants.ts
|
|
5939
|
+
var CLOUDFLARE_ACCOUNT_ID = "49ef1ba682ad8cfd720c86699ae17521";
|
|
5940
|
+
var STAGING_CLOUDFLARE_ACCOUNT_ID = "4f4e1c69a3830946f9fea7b1eb7531ac";
|
|
5941
|
+
var CLOUDFLARE_COMPATIBILITY_DATE = "2026-05-05";
|
|
5942
|
+
|
|
5943
|
+
// src/commands/app-scaffold-worker-template.ts
|
|
6334
5944
|
function webWorkerTs(application) {
|
|
6335
5945
|
const serviceName = toTitle(application.name);
|
|
6336
5946
|
return [
|
|
@@ -6448,7 +6058,7 @@ function platformApiWranglerToml(application, resources, profile) {
|
|
|
6448
6058
|
"",
|
|
6449
6059
|
"[vars]",
|
|
6450
6060
|
'AUTH_BASE_URL = "https://auth.topolo.app"',
|
|
6451
|
-
'NEXUS_GATEWAY_URL = "https://nexus
|
|
6061
|
+
'NEXUS_GATEWAY_URL = "https://nexus.topolo.app"',
|
|
6452
6062
|
`NEXUS_APP_ID = "${slug}"`,
|
|
6453
6063
|
`SERVICE_ID = "${application.serviceId ?? ""}"`,
|
|
6454
6064
|
`VITE_TOPOLO_SERVICE_ID = "${application.serviceId ?? ""}"`,
|
|
@@ -6463,7 +6073,7 @@ function platformApiWranglerToml(application, resources, profile) {
|
|
|
6463
6073
|
"",
|
|
6464
6074
|
"[env.staging.vars]",
|
|
6465
6075
|
'AUTH_BASE_URL = "https://auth.stg.topolo.us"',
|
|
6466
|
-
'NEXUS_GATEWAY_URL = "https://nexus
|
|
6076
|
+
'NEXUS_GATEWAY_URL = "https://nexus.stg.topolo.us"',
|
|
6467
6077
|
`NEXUS_APP_ID = "${slug}"`,
|
|
6468
6078
|
`SERVICE_ID = "${application.serviceId ?? ""}"`,
|
|
6469
6079
|
`VITE_TOPOLO_SERVICE_ID = "${application.serviceId ?? ""}"`,
|
|
@@ -6478,7 +6088,7 @@ function platformApiWranglerToml(application, resources, profile) {
|
|
|
6478
6088
|
"",
|
|
6479
6089
|
"[env.production.vars]",
|
|
6480
6090
|
'AUTH_BASE_URL = "https://auth.topolo.app"',
|
|
6481
|
-
'NEXUS_GATEWAY_URL = "https://nexus
|
|
6091
|
+
'NEXUS_GATEWAY_URL = "https://nexus.topolo.app"',
|
|
6482
6092
|
`NEXUS_APP_ID = "${slug}"`,
|
|
6483
6093
|
`SERVICE_ID = "${application.serviceId ?? ""}"`,
|
|
6484
6094
|
`VITE_TOPOLO_SERVICE_ID = "${application.serviceId ?? ""}"`,
|
|
@@ -6574,7 +6184,7 @@ function wranglerToml(application, target, resources, profile) {
|
|
|
6574
6184
|
"",
|
|
6575
6185
|
"[vars]",
|
|
6576
6186
|
'AUTH_BASE_URL = "https://auth.topolo.app"',
|
|
6577
|
-
'NEXUS_GATEWAY_URL = "https://nexus
|
|
6187
|
+
'NEXUS_GATEWAY_URL = "https://nexus.topolo.app"',
|
|
6578
6188
|
runtimeIdentityVar,
|
|
6579
6189
|
""
|
|
6580
6190
|
);
|
|
@@ -6629,7 +6239,7 @@ function wranglerToml(application, target, resources, profile) {
|
|
|
6629
6239
|
"",
|
|
6630
6240
|
"[env.staging.vars]",
|
|
6631
6241
|
'AUTH_BASE_URL = "https://auth.stg.topolo.us"',
|
|
6632
|
-
'NEXUS_GATEWAY_URL = "https://nexus
|
|
6242
|
+
'NEXUS_GATEWAY_URL = "https://nexus.stg.topolo.us"',
|
|
6633
6243
|
runtimeIdentityVar,
|
|
6634
6244
|
""
|
|
6635
6245
|
);
|
|
@@ -6929,6 +6539,650 @@ function developerWorkerTs(application) {
|
|
|
6929
6539
|
""
|
|
6930
6540
|
].join("\n");
|
|
6931
6541
|
}
|
|
6542
|
+
|
|
6543
|
+
// src/commands/app-scaffold-workflows.ts
|
|
6544
|
+
function scaffoldCommands(options) {
|
|
6545
|
+
const scaffoldId = toSlug(options.id);
|
|
6546
|
+
if (options.target === "developer") {
|
|
6547
|
+
const audienceFlags = options.audience === "personal" ? " --audience personal --tenancy household" : "";
|
|
6548
|
+
return [
|
|
6549
|
+
`topolo apps scaffold ${scaffoldId}${audienceFlags} --write --repo-root .`,
|
|
6550
|
+
`cd topolo-${scaffoldId}`,
|
|
6551
|
+
"npm install",
|
|
6552
|
+
"npm run typecheck",
|
|
6553
|
+
"npm run build"
|
|
6554
|
+
];
|
|
6555
|
+
}
|
|
6556
|
+
const portfolioFlags = options.portfolio === "personal" ? " --portfolio personal" : "";
|
|
6557
|
+
const profileFlags = options.profile ? ` --profile ${options.profile}` : "";
|
|
6558
|
+
return [
|
|
6559
|
+
`topolo platform apps scaffold ${scaffoldId}${portfolioFlags}${profileFlags} --write --provision --repo-root /Users/ashes/Projects/Topolo --json`,
|
|
6560
|
+
"topolo apps requirements --json",
|
|
6561
|
+
`topolo apps audit ${options.id} --local --repo-root /Users/ashes/Projects/Topolo --fail-on needs_review --json`,
|
|
6562
|
+
`topolo apps audit ${options.id} --local --repo-root /Users/ashes/Projects/Topolo --fail-on partial --json`
|
|
6563
|
+
];
|
|
6564
|
+
}
|
|
6565
|
+
function topoloConformanceWorkflow(options) {
|
|
6566
|
+
const appPath = firstPartyAppPath(options.appDir, options.audience);
|
|
6567
|
+
const docsCheckoutStep = options.appDir === "TopoloDocs" ? [] : [
|
|
6568
|
+
"",
|
|
6569
|
+
" - name: Checkout TopoloDocs",
|
|
6570
|
+
` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
|
|
6571
|
+
" uses: actions/checkout@v4",
|
|
6572
|
+
" with:",
|
|
6573
|
+
" repository: Topolo-io/TopoloDocs",
|
|
6574
|
+
" path: system-apps/TopoloDocs",
|
|
6575
|
+
" token: ${{ steps.app-token.outputs.token || secrets.TOPOLO_CI_REPO_TOKEN }}"
|
|
6576
|
+
];
|
|
6577
|
+
const cliCheckoutStep = options.appDir === "TopoloCLI" || options.appDir === "TopoloCli" ? [] : [
|
|
6578
|
+
"",
|
|
6579
|
+
" - name: Checkout Topolo CLI",
|
|
6580
|
+
` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
|
|
6581
|
+
" uses: actions/checkout@v4",
|
|
6582
|
+
" with:",
|
|
6583
|
+
" repository: Topolo-io/TopoloCli",
|
|
6584
|
+
" path: topolo-platform/packages/topolo-cli",
|
|
6585
|
+
" token: ${{ steps.app-token.outputs.token || secrets.TOPOLO_CI_REPO_TOKEN }}"
|
|
6586
|
+
];
|
|
6587
|
+
return [
|
|
6588
|
+
"name: Topolo App Conformance",
|
|
6589
|
+
"",
|
|
6590
|
+
"on:",
|
|
6591
|
+
" pull_request:",
|
|
6592
|
+
" push:",
|
|
6593
|
+
" branches:",
|
|
6594
|
+
" - main",
|
|
6595
|
+
" workflow_dispatch:",
|
|
6596
|
+
"",
|
|
6597
|
+
"permissions:",
|
|
6598
|
+
" contents: read",
|
|
6599
|
+
"",
|
|
6600
|
+
"jobs:",
|
|
6601
|
+
" conformance:",
|
|
6602
|
+
" runs-on: ubuntu-latest",
|
|
6603
|
+
" env:",
|
|
6604
|
+
` HAS_TOPOLO_PACKAGES_APP: \${{ secrets.TOPOLO_PACKAGES_APP_ID != '' && secrets.TOPOLO_PACKAGES_APP_PRIVATE_KEY != '' }}`,
|
|
6605
|
+
` HAS_TOPOLO_CI_REPO_TOKEN: \${{ secrets.TOPOLO_CI_REPO_TOKEN != '' }}`,
|
|
6606
|
+
" steps:",
|
|
6607
|
+
" - name: Checkout application",
|
|
6608
|
+
" uses: actions/checkout@v4",
|
|
6609
|
+
" with:",
|
|
6610
|
+
` path: ${appPath}`,
|
|
6611
|
+
"",
|
|
6612
|
+
" - name: Mint Topolo cross-repo token",
|
|
6613
|
+
" id: app-token",
|
|
6614
|
+
` if: env.HAS_TOPOLO_PACKAGES_APP == 'true'`,
|
|
6615
|
+
" uses: actions/create-github-app-token@v1",
|
|
6616
|
+
" with:",
|
|
6617
|
+
` app-id: \${{ secrets.TOPOLO_PACKAGES_APP_ID }}`,
|
|
6618
|
+
` private-key: \${{ secrets.TOPOLO_PACKAGES_APP_PRIVATE_KEY }}`,
|
|
6619
|
+
" owner: Topolo-io",
|
|
6620
|
+
...docsCheckoutStep,
|
|
6621
|
+
"",
|
|
6622
|
+
" - name: Checkout Topolo SDK",
|
|
6623
|
+
` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
|
|
6624
|
+
" uses: actions/checkout@v4",
|
|
6625
|
+
" with:",
|
|
6626
|
+
" repository: Topolo-io/topolo-sdk",
|
|
6627
|
+
" path: topolo-platform/packages/topolo-sdk",
|
|
6628
|
+
" token: ${{ steps.app-token.outputs.token || secrets.TOPOLO_CI_REPO_TOKEN }}",
|
|
6629
|
+
...cliCheckoutStep,
|
|
6630
|
+
"",
|
|
6631
|
+
" - name: Skip conformance without cross-repo token",
|
|
6632
|
+
` if: steps.app-token.outputs.token == '' && env.HAS_TOPOLO_CI_REPO_TOKEN != 'true'`,
|
|
6633
|
+
" run: |",
|
|
6634
|
+
' echo "::warning::Skipping Topolo app conformance because no Topolo cross-repo token is available to this workflow context."',
|
|
6635
|
+
"",
|
|
6636
|
+
" - name: Setup Node.js",
|
|
6637
|
+
` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
|
|
6638
|
+
" uses: actions/setup-node@v4",
|
|
6639
|
+
" with:",
|
|
6640
|
+
" node-version: 20",
|
|
6641
|
+
"",
|
|
6642
|
+
" - name: Build Topolo SDK",
|
|
6643
|
+
` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
|
|
6644
|
+
" working-directory: topolo-platform/packages/topolo-sdk",
|
|
6645
|
+
" run: |",
|
|
6646
|
+
" npm install --no-audit --no-fund",
|
|
6647
|
+
" npm run build",
|
|
6648
|
+
"",
|
|
6649
|
+
" - name: Build Topolo CLI",
|
|
6650
|
+
` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
|
|
6651
|
+
" working-directory: topolo-platform/packages/topolo-cli",
|
|
6652
|
+
" run: |",
|
|
6653
|
+
" npm install --no-audit --no-fund",
|
|
6654
|
+
" npm run build",
|
|
6655
|
+
"",
|
|
6656
|
+
" - name: Run Topolo app conformance audit",
|
|
6657
|
+
` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
|
|
6658
|
+
" run: |",
|
|
6659
|
+
` node topolo-platform/packages/topolo-cli/dist/index.js apps audit ${options.id} --local --repo-root "$GITHUB_WORKSPACE" --fail-on needs_review --json`,
|
|
6660
|
+
"",
|
|
6661
|
+
" - name: Record green CI evidence",
|
|
6662
|
+
` if: \${{ success() && (steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true') }}`,
|
|
6663
|
+
` working-directory: ${appPath}`,
|
|
6664
|
+
" run: |",
|
|
6665
|
+
` node "$GITHUB_WORKSPACE/topolo-platform/packages/topolo-cli/dist/index.js" apps record-evidence ${options.id} --build --typecheck --test --commit "$GITHUB_SHA"`
|
|
6666
|
+
].join("\n");
|
|
6667
|
+
}
|
|
6668
|
+
|
|
6669
|
+
// src/commands/app-scaffold.ts
|
|
6670
|
+
var CURRENT_VERIFICATION_DATE = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
6671
|
+
function generateOpaqueServiceId() {
|
|
6672
|
+
return `srv_${randomBytes(9).toString("base64url")}`;
|
|
6673
|
+
}
|
|
6674
|
+
function applicationScaffoldPayload(application, options = {}) {
|
|
6675
|
+
const target = normalizeTarget(options.target);
|
|
6676
|
+
const portfolio = target === "platform" ? normalizePortfolio(options.portfolio) : null;
|
|
6677
|
+
const requestedId = normalizeApplicationId(application);
|
|
6678
|
+
const id = target === "platform" ? normalizeFirstPartyApplicationId(requestedId) : requestedId;
|
|
6679
|
+
const slug = toSlug(id);
|
|
6680
|
+
const catalogApplication = APPLICATIONS[id];
|
|
6681
|
+
const browser = options.browser !== false;
|
|
6682
|
+
const api = options.api ?? target === "platform";
|
|
6683
|
+
const audience = normalizeAudience(
|
|
6684
|
+
options.audience,
|
|
6685
|
+
target === "platform" ? portfolio : null
|
|
6686
|
+
);
|
|
6687
|
+
const tenancy = normalizeTenancy(options.tenancy, audience);
|
|
6688
|
+
const surfaceModel = normalizeSurfaceModel(options.surfaceModel);
|
|
6689
|
+
const containerAppId = normalizeContainerAppId(options.containerAppId, surfaceModel);
|
|
6690
|
+
const profile = normalizeScaffoldProfile(options.profile);
|
|
6691
|
+
const resources = resolveScaffoldResources(profile, options);
|
|
6692
|
+
const ownerType = target === "platform" ? "topolo_first_party" : "third_party";
|
|
6693
|
+
const distributionModel = target === "platform" ? "marketplace" : "organization_internal";
|
|
6694
|
+
const appDir = options.appDir ?? catalogApplication?.appDir ?? (target === "platform" ? `Topolo${toPascalCase(id)}` : `topolo-${slug}`);
|
|
6695
|
+
const name = options.name ?? catalogApplication?.name ?? `topolo-${slug}`;
|
|
6696
|
+
const packageName = options.packageName ?? catalogApplication?.packageName ?? `topolo-${slug}`;
|
|
6697
|
+
const serviceSlug = api ? target === "platform" ? `topolo-${slug}` : id : null;
|
|
6698
|
+
const defaultServiceId = target === "platform" ? generateOpaqueServiceId() : catalogApplication?.services[0] ?? id;
|
|
6699
|
+
const serviceId = api ? options.service ?? defaultServiceId : null;
|
|
6700
|
+
const productionUrl = browser ? options.productionUrl ?? catalogApplication?.productionUrl ?? `https://${slug}.topolo.app` : null;
|
|
6701
|
+
const scopes = scaffoldScopes({ browser, api });
|
|
6702
|
+
const capabilities = scaffoldCapabilities({ browser, api, distributionModel });
|
|
6703
|
+
const ciWorkflowPath = target === "platform" ? ".github/workflows/topolo-conformance.yml" : null;
|
|
6704
|
+
const ciWorkflow = target === "platform" ? topoloConformanceWorkflow({ id, appDir, audience }) : null;
|
|
6705
|
+
const applicationPayload = {
|
|
6706
|
+
id,
|
|
6707
|
+
name,
|
|
6708
|
+
appDir,
|
|
6709
|
+
packageName,
|
|
6710
|
+
productionUrl,
|
|
6711
|
+
serviceSlug,
|
|
6712
|
+
serviceId
|
|
6713
|
+
};
|
|
6714
|
+
return {
|
|
6715
|
+
version: APPLICATION_REQUIREMENTS_VERSION,
|
|
6716
|
+
target,
|
|
6717
|
+
ownerType,
|
|
6718
|
+
portfolio,
|
|
6719
|
+
audience,
|
|
6720
|
+
tenancy,
|
|
6721
|
+
surfaceModel,
|
|
6722
|
+
containerAppId,
|
|
6723
|
+
profile,
|
|
6724
|
+
resources,
|
|
6725
|
+
distributionModel,
|
|
6726
|
+
capabilities,
|
|
6727
|
+
application: applicationPayload,
|
|
6728
|
+
scopes,
|
|
6729
|
+
requirements: APPLICATION_REQUIREMENTS.filter(
|
|
6730
|
+
(requirement) => requirement.appliesTo.some((scope) => scopes.includes(scope))
|
|
6731
|
+
),
|
|
6732
|
+
packages: scaffoldPackages({ browser, api, target }),
|
|
6733
|
+
files: scaffoldFiles({
|
|
6734
|
+
payload: {
|
|
6735
|
+
ownerType,
|
|
6736
|
+
portfolio,
|
|
6737
|
+
audience,
|
|
6738
|
+
tenancy,
|
|
6739
|
+
surfaceModel,
|
|
6740
|
+
containerAppId,
|
|
6741
|
+
profile,
|
|
6742
|
+
resources,
|
|
6743
|
+
distributionModel,
|
|
6744
|
+
capabilities
|
|
6745
|
+
},
|
|
6746
|
+
application: applicationPayload,
|
|
6747
|
+
target,
|
|
6748
|
+
browser,
|
|
6749
|
+
api,
|
|
6750
|
+
ciWorkflow
|
|
6751
|
+
}),
|
|
6752
|
+
commands: scaffoldCommands({
|
|
6753
|
+
id,
|
|
6754
|
+
target,
|
|
6755
|
+
audience,
|
|
6756
|
+
tenancy,
|
|
6757
|
+
portfolio,
|
|
6758
|
+
profile
|
|
6759
|
+
}),
|
|
6760
|
+
ciWorkflowPath,
|
|
6761
|
+
ciWorkflow
|
|
6762
|
+
};
|
|
6763
|
+
}
|
|
6764
|
+
async function writeApplicationScaffold(payload, options = {}) {
|
|
6765
|
+
const root = resolve2(options.repoRoot ?? process.cwd());
|
|
6766
|
+
const plannedFiles = payload.files.map((file) => ({
|
|
6767
|
+
file,
|
|
6768
|
+
absolutePath: resolveInside(root, file.path)
|
|
6769
|
+
}));
|
|
6770
|
+
const existingFiles = [];
|
|
6771
|
+
for (const planned of plannedFiles) {
|
|
6772
|
+
if (await pathExists(planned.absolutePath)) {
|
|
6773
|
+
existingFiles.push(planned.file.path);
|
|
6774
|
+
}
|
|
6775
|
+
}
|
|
6776
|
+
if (existingFiles.length > 0 && !options.force) {
|
|
6777
|
+
throw new Error(
|
|
6778
|
+
[
|
|
6779
|
+
"Refusing to overwrite existing scaffold files:",
|
|
6780
|
+
...existingFiles.map((filePath) => `- ${filePath}`),
|
|
6781
|
+
"Pass --force to overwrite these files."
|
|
6782
|
+
].join("\n")
|
|
6783
|
+
);
|
|
6784
|
+
}
|
|
6785
|
+
const files = [];
|
|
6786
|
+
for (const planned of plannedFiles) {
|
|
6787
|
+
const existed = await pathExists(planned.absolutePath);
|
|
6788
|
+
await mkdir2(dirname3(planned.absolutePath), { recursive: true });
|
|
6789
|
+
await writeFile2(planned.absolutePath, planned.file.content, "utf8");
|
|
6790
|
+
files.push({
|
|
6791
|
+
path: planned.file.path,
|
|
6792
|
+
status: existed ? "overwritten" : "created"
|
|
6793
|
+
});
|
|
6794
|
+
}
|
|
6795
|
+
return { root, files };
|
|
6796
|
+
}
|
|
6797
|
+
function scaffoldScopes(options) {
|
|
6798
|
+
const scopes = ["all"];
|
|
6799
|
+
if (options.browser) scopes.push("browser");
|
|
6800
|
+
if (options.api) {
|
|
6801
|
+
scopes.push("api", "agent_surface");
|
|
6802
|
+
}
|
|
6803
|
+
return scopes;
|
|
6804
|
+
}
|
|
6805
|
+
function scaffoldPackages(options) {
|
|
6806
|
+
if (options.target === "developer") {
|
|
6807
|
+
return options.browser ? ["@topolo/login"] : [];
|
|
6808
|
+
}
|
|
6809
|
+
const packages = /* @__PURE__ */ new Set();
|
|
6810
|
+
if (options.browser) {
|
|
6811
|
+
packages.add("@topolo-io/app-funnel");
|
|
6812
|
+
packages.add("@topolo-io/app-launcher");
|
|
6813
|
+
packages.add("@topolo-io/app-shell");
|
|
6814
|
+
packages.add("@topolo-io/auth-client");
|
|
6815
|
+
packages.add("@topolo-io/design-tokens");
|
|
6816
|
+
packages.add("@topolo-io/onboarding");
|
|
6817
|
+
packages.add("@topolo-io/tour");
|
|
6818
|
+
packages.add("@topolo-io/ui-kit");
|
|
6819
|
+
}
|
|
6820
|
+
if (options.api) {
|
|
6821
|
+
packages.add("@topolo-io/worker-runtime");
|
|
6822
|
+
packages.add("@topolo/sdk");
|
|
6823
|
+
}
|
|
6824
|
+
return Array.from(packages);
|
|
6825
|
+
}
|
|
6826
|
+
function scaffoldCapabilities(options) {
|
|
6827
|
+
const capabilities = [];
|
|
6828
|
+
if (options.browser) {
|
|
6829
|
+
capabilities.push("login", "launcher");
|
|
6830
|
+
}
|
|
6831
|
+
if (options.distributionModel === "marketplace") capabilities.push("marketplace");
|
|
6832
|
+
if (options.api) capabilities.push("api", "widget");
|
|
6833
|
+
return capabilities;
|
|
6834
|
+
}
|
|
6835
|
+
function scaffoldFiles(options) {
|
|
6836
|
+
const { application, target } = options;
|
|
6837
|
+
const slug = toSlug(application.id);
|
|
6838
|
+
const base = target === "platform" ? firstPartyAppPath(application.appDir, options.payload.audience) : application.appDir;
|
|
6839
|
+
const browserBase = target === "platform" ? `${base}/apps/web` : base;
|
|
6840
|
+
const apiBase = target === "platform" ? `${base}/apps/api` : base;
|
|
6841
|
+
const files = [
|
|
6842
|
+
{
|
|
6843
|
+
path: `${base}/package.json`,
|
|
6844
|
+
purpose: "Package metadata and verification commands.",
|
|
6845
|
+
requiredSignals: [
|
|
6846
|
+
"name matches the packageName in the SDK application catalog",
|
|
6847
|
+
"scripts include build plus typecheck where the stack supports them"
|
|
6848
|
+
],
|
|
6849
|
+
content: packageJson(application, target, {
|
|
6850
|
+
browser: options.browser,
|
|
6851
|
+
api: options.api,
|
|
6852
|
+
resources: options.payload.resources
|
|
6853
|
+
})
|
|
6854
|
+
},
|
|
6855
|
+
{
|
|
6856
|
+
path: `${base}/README.md`,
|
|
6857
|
+
purpose: "Starter README with install, run, and verification commands.",
|
|
6858
|
+
requiredSignals: [
|
|
6859
|
+
"documents the generated app target",
|
|
6860
|
+
"points maintainers to the shared Topolo packages used by the scaffold"
|
|
6861
|
+
],
|
|
6862
|
+
content: readme(application, target, {
|
|
6863
|
+
api: options.api,
|
|
6864
|
+
audience: options.payload.audience,
|
|
6865
|
+
tenancy: options.payload.tenancy,
|
|
6866
|
+
portfolio: options.payload.portfolio,
|
|
6867
|
+
profile: options.payload.profile,
|
|
6868
|
+
resources: options.payload.resources
|
|
6869
|
+
})
|
|
6870
|
+
},
|
|
6871
|
+
{
|
|
6872
|
+
path: `${base}/.gitignore`,
|
|
6873
|
+
purpose: "Generated-app ignore rules.",
|
|
6874
|
+
requiredSignals: [
|
|
6875
|
+
"ignores dependency installs and build output",
|
|
6876
|
+
"keeps local environment files out of source control"
|
|
6877
|
+
],
|
|
6878
|
+
content: gitignore()
|
|
6879
|
+
},
|
|
6880
|
+
{
|
|
6881
|
+
path: `${base}/AGENTS.md`,
|
|
6882
|
+
purpose: "Local agent rules for safe work inside the generated project.",
|
|
6883
|
+
requiredSignals: [
|
|
6884
|
+
"states the app-specific ownership boundary",
|
|
6885
|
+
"keeps future agents aligned with Topolo docs, manifest, testing, and staging expectations"
|
|
6886
|
+
],
|
|
6887
|
+
content: agentRules(application, target, {
|
|
6888
|
+
api: options.api,
|
|
6889
|
+
audience: options.payload.audience,
|
|
6890
|
+
tenancy: options.payload.tenancy,
|
|
6891
|
+
portfolio: options.payload.portfolio,
|
|
6892
|
+
resources: options.payload.resources
|
|
6893
|
+
})
|
|
6894
|
+
},
|
|
6895
|
+
{
|
|
6896
|
+
path: `${base}/tsconfig.json`,
|
|
6897
|
+
purpose: "TypeScript configuration for the generated app.",
|
|
6898
|
+
requiredSignals: [
|
|
6899
|
+
"uses strict mode",
|
|
6900
|
+
"includes browser DOM types for React and Worker types for API-backed apps"
|
|
6901
|
+
],
|
|
6902
|
+
content: tsconfigJson(options.api, target)
|
|
6903
|
+
},
|
|
6904
|
+
{
|
|
6905
|
+
path: `${browserBase}/vite.config.ts`,
|
|
6906
|
+
purpose: "Vite browser build configuration.",
|
|
6907
|
+
requiredSignals: [
|
|
6908
|
+
"builds the authenticated browser surface",
|
|
6909
|
+
"keeps generated apps on the standard React/Vite baseline"
|
|
6910
|
+
],
|
|
6911
|
+
content: viteConfig(target)
|
|
6912
|
+
},
|
|
6913
|
+
{
|
|
6914
|
+
path: `${browserBase}/index.html`,
|
|
6915
|
+
purpose: "Browser entry document.",
|
|
6916
|
+
requiredSignals: [
|
|
6917
|
+
"mounts the React app at #root",
|
|
6918
|
+
"uses the generated application display name"
|
|
6919
|
+
],
|
|
6920
|
+
content: indexHtml(application.name)
|
|
6921
|
+
},
|
|
6922
|
+
{
|
|
6923
|
+
path: `${browserBase}/src/main.tsx`,
|
|
6924
|
+
purpose: "React app entrypoint.",
|
|
6925
|
+
requiredSignals: [
|
|
6926
|
+
"renders the generated app component",
|
|
6927
|
+
"imports the generated app stylesheet"
|
|
6928
|
+
],
|
|
6929
|
+
content: mainTsx()
|
|
6930
|
+
},
|
|
6931
|
+
{
|
|
6932
|
+
path: `${browserBase}/src/env.d.ts`,
|
|
6933
|
+
purpose: "Vite environment variable typing.",
|
|
6934
|
+
requiredSignals: [
|
|
6935
|
+
"loads vite/client import.meta.env types",
|
|
6936
|
+
"declares the Topolo environment variables consumed by the starter"
|
|
6937
|
+
],
|
|
6938
|
+
content: envDts(options.api)
|
|
6939
|
+
},
|
|
6940
|
+
...options.payload.resources.workflow ? [
|
|
6941
|
+
{
|
|
6942
|
+
path: `${base}/src/cloudflare-workers.d.ts`,
|
|
6943
|
+
purpose: "Minimal Cloudflare Workflow entrypoint typing for scaffolded Workers.",
|
|
6944
|
+
requiredSignals: [
|
|
6945
|
+
"declares cloudflare:workers WorkflowEntrypoint for TypeScript",
|
|
6946
|
+
"keeps Workflow imports deployable without adding a new dependency"
|
|
6947
|
+
],
|
|
6948
|
+
content: cloudflareWorkersDts()
|
|
6949
|
+
}
|
|
6950
|
+
] : [],
|
|
6951
|
+
{
|
|
6952
|
+
path: `${browserBase}/src/styles.css`,
|
|
6953
|
+
purpose: "Minimal app-local styles outside the shared Topolo shell.",
|
|
6954
|
+
requiredSignals: [
|
|
6955
|
+
"does not fork the shared shell chrome",
|
|
6956
|
+
"keeps app-specific layout scoped under generated classes"
|
|
6957
|
+
],
|
|
6958
|
+
content: stylesCss()
|
|
6959
|
+
}
|
|
6960
|
+
];
|
|
6961
|
+
if (target === "platform") {
|
|
6962
|
+
files.push(
|
|
6963
|
+
{
|
|
6964
|
+
path: `${base}/topolo.app.json`,
|
|
6965
|
+
purpose: "Canonical app manifest that drives generated Wrangler and CloudControl metadata.",
|
|
6966
|
+
requiredSignals: [
|
|
6967
|
+
"declares web/api targets with canonical names and app-local target directories",
|
|
6968
|
+
"keeps Cloudflare account, domain, service id, and bindings in one manifest source of truth"
|
|
6969
|
+
],
|
|
6970
|
+
content: topoloAppManifestJson(application, {
|
|
6971
|
+
api: options.api,
|
|
6972
|
+
resources: options.payload.resources,
|
|
6973
|
+
profile: options.payload.profile
|
|
6974
|
+
})
|
|
6975
|
+
},
|
|
6976
|
+
{
|
|
6977
|
+
path: `${base}/topolo.cloudcontrol.json`,
|
|
6978
|
+
purpose: "CloudControl source of truth for deploy targets, routes, bindings, and CI metadata.",
|
|
6979
|
+
requiredSignals: [
|
|
6980
|
+
"production URLs and Cloudflare targets are explicit",
|
|
6981
|
+
"resource bindings and required secrets are declared without secret values"
|
|
6982
|
+
],
|
|
6983
|
+
content: cloudcontrolJson(application, {
|
|
6984
|
+
api: options.api,
|
|
6985
|
+
audience: options.payload.audience,
|
|
6986
|
+
profile: options.payload.profile,
|
|
6987
|
+
resources: options.payload.resources
|
|
6988
|
+
})
|
|
6989
|
+
},
|
|
6990
|
+
{
|
|
6991
|
+
path: `${base}/.github/workflows/topolo-conformance.yml`,
|
|
6992
|
+
purpose: "PR/push gate that runs the local Topolo app audit with docs evidence.",
|
|
6993
|
+
requiredSignals: [
|
|
6994
|
+
"checks out this repo under its workspace app directory",
|
|
6995
|
+
"checks out TopoloDocs under system-apps/TopoloDocs",
|
|
6996
|
+
"checks out topolo-sdk and TopoloCli from main so the gate does not wait for an npm publish",
|
|
6997
|
+
"runs topolo apps audit with --local and --fail-on needs_review"
|
|
6998
|
+
],
|
|
6999
|
+
content: options.ciWorkflow ?? ""
|
|
7000
|
+
},
|
|
7001
|
+
{
|
|
7002
|
+
path: `system-apps/TopoloDocs/apps/web/src/content/systems/topolo-${slug}.json`,
|
|
7003
|
+
purpose: "Canonical system registry entry.",
|
|
7004
|
+
requiredSignals: [
|
|
7005
|
+
`repo_paths includes ${base}`,
|
|
7006
|
+
"doc_paths includes internal handbook coverage",
|
|
7007
|
+
"security_assurance records the auth, tenancy, deployment, and verification boundary"
|
|
7008
|
+
],
|
|
7009
|
+
content: systemJson(application, {
|
|
7010
|
+
api: options.api,
|
|
7011
|
+
audience: options.payload.audience,
|
|
7012
|
+
tenancy: options.payload.tenancy,
|
|
7013
|
+
portfolio: options.payload.portfolio,
|
|
7014
|
+
profile: options.payload.profile,
|
|
7015
|
+
resources: options.payload.resources
|
|
7016
|
+
})
|
|
7017
|
+
},
|
|
7018
|
+
{
|
|
7019
|
+
path: `system-apps/TopoloDocs/apps/web/src/content/internal/apps/${slug}.mdx`,
|
|
7020
|
+
purpose: "Internal operator handbook.",
|
|
7021
|
+
requiredSignals: [
|
|
7022
|
+
"documents ownership, routes, auth boundary, deployment, operations, and verification",
|
|
7023
|
+
"last_verified is updated when behavior changes"
|
|
7024
|
+
],
|
|
7025
|
+
content: internalDocs(application, {
|
|
7026
|
+
api: options.api,
|
|
7027
|
+
audience: options.payload.audience,
|
|
7028
|
+
tenancy: options.payload.tenancy,
|
|
7029
|
+
portfolio: options.payload.portfolio,
|
|
7030
|
+
profile: options.payload.profile,
|
|
7031
|
+
resources: options.payload.resources
|
|
7032
|
+
})
|
|
7033
|
+
},
|
|
7034
|
+
{
|
|
7035
|
+
path: `system-apps/TopoloDocs/apps/web/src/content/public/applications/${slug}.mdx`,
|
|
7036
|
+
purpose: "Public application overview.",
|
|
7037
|
+
requiredSignals: [
|
|
7038
|
+
"documents the product surface without internal-only details",
|
|
7039
|
+
"links back to the canonical system registry entry"
|
|
7040
|
+
],
|
|
7041
|
+
content: publicDocs(application, {
|
|
7042
|
+
api: options.api,
|
|
7043
|
+
audience: options.payload.audience,
|
|
7044
|
+
tenancy: options.payload.tenancy,
|
|
7045
|
+
portfolio: options.payload.portfolio,
|
|
7046
|
+
profile: options.payload.profile,
|
|
7047
|
+
resources: options.payload.resources
|
|
7048
|
+
})
|
|
7049
|
+
}
|
|
7050
|
+
);
|
|
7051
|
+
if (options.api && application.serviceId) {
|
|
7052
|
+
files.push(
|
|
7053
|
+
{
|
|
7054
|
+
path: `topolo-platform/core/TopoloAuth/catalog/service-manifests/${application.serviceId}.json`,
|
|
7055
|
+
purpose: "Auth service manifest for first-party permissions, role bundles, and API-key scopes.",
|
|
7056
|
+
requiredSignals: [
|
|
7057
|
+
"serviceId matches the generated Worker service id",
|
|
7058
|
+
"permissions, role bundles, and API-key scopes exist before the app is catalog-visible",
|
|
7059
|
+
"systems metadata maps the service back to the generated TopoloDocs entries"
|
|
7060
|
+
],
|
|
7061
|
+
content: serviceManifestJson(application, {
|
|
7062
|
+
audience: options.payload.audience,
|
|
7063
|
+
tenancy: options.payload.tenancy
|
|
7064
|
+
})
|
|
7065
|
+
}
|
|
7066
|
+
);
|
|
7067
|
+
}
|
|
7068
|
+
} else {
|
|
7069
|
+
files.push(
|
|
7070
|
+
{
|
|
7071
|
+
path: `${base}/topolo.app.json`,
|
|
7072
|
+
purpose: "Third-party Topolo application manifest.",
|
|
7073
|
+
requiredSignals: [
|
|
7074
|
+
"declares the external app id, callback URLs, and requested platform scopes",
|
|
7075
|
+
"does not include internal Topolo CloudControl or canonical-docs ownership"
|
|
7076
|
+
],
|
|
7077
|
+
content: developerManifest(application, {
|
|
7078
|
+
api: options.api,
|
|
7079
|
+
audience: options.payload.audience,
|
|
7080
|
+
tenancy: options.payload.tenancy,
|
|
7081
|
+
surfaceModel: options.payload.surfaceModel,
|
|
7082
|
+
containerAppId: options.payload.containerAppId,
|
|
7083
|
+
distributionModel: options.payload.distributionModel,
|
|
7084
|
+
capabilities: options.payload.capabilities
|
|
7085
|
+
})
|
|
7086
|
+
},
|
|
7087
|
+
{
|
|
7088
|
+
path: `${base}/.env.example`,
|
|
7089
|
+
purpose: "Developer environment template for local auth configuration.",
|
|
7090
|
+
requiredSignals: [
|
|
7091
|
+
"names the Topolo Auth URL",
|
|
7092
|
+
"keeps OAuth client ids and service ids out of source control by default"
|
|
7093
|
+
],
|
|
7094
|
+
content: envExample(application)
|
|
7095
|
+
}
|
|
7096
|
+
);
|
|
7097
|
+
}
|
|
7098
|
+
if (options.browser) {
|
|
7099
|
+
if (target === "platform") {
|
|
7100
|
+
files.push({
|
|
7101
|
+
path: `${browserBase}/src/service-identity.ts`,
|
|
7102
|
+
purpose: "Central service identity resolver shared by browser and Worker runtime surfaces.",
|
|
7103
|
+
requiredSignals: [
|
|
7104
|
+
"exports the generated app id, display name, and stable Auth service slug",
|
|
7105
|
+
"requires the provisioned Auth service id from the Worker deployment config"
|
|
7106
|
+
],
|
|
7107
|
+
content: serviceIdentityTs(application)
|
|
7108
|
+
});
|
|
7109
|
+
}
|
|
7110
|
+
files.push({
|
|
7111
|
+
path: `${browserBase}/src/App.tsx`,
|
|
7112
|
+
purpose: "Authenticated browser surface.",
|
|
7113
|
+
requiredSignals: [
|
|
7114
|
+
"bootstraps routes, auth provider, and shared shell through @topolo-io/app-shell",
|
|
7115
|
+
"uses @topolo-io/auth-client through the app-shell wrapper for app-origin auth flows",
|
|
7116
|
+
"keeps launcher and account-menu behavior on the shared platform surface",
|
|
7117
|
+
"passes app-specific account actions through accountMenuItems or TopoloAccountMenu"
|
|
7118
|
+
],
|
|
7119
|
+
content: appTsx(application, target, {
|
|
7120
|
+
audience: options.payload.audience,
|
|
7121
|
+
portfolio: options.payload.portfolio
|
|
7122
|
+
})
|
|
7123
|
+
});
|
|
7124
|
+
}
|
|
7125
|
+
if (options.api) {
|
|
7126
|
+
files.push(
|
|
7127
|
+
{
|
|
7128
|
+
path: `${apiBase}/wrangler.toml`,
|
|
7129
|
+
purpose: "Cloudflare Worker runtime configuration.",
|
|
7130
|
+
requiredSignals: [
|
|
7131
|
+
"bindings match topolo.cloudcontrol.json or topolo.app.json",
|
|
7132
|
+
"secrets are referenced by name only"
|
|
7133
|
+
],
|
|
7134
|
+
content: target === "platform" ? platformApiWranglerToml(application, options.payload.resources, options.payload.profile) : wranglerToml(application, target, options.payload.resources, options.payload.profile)
|
|
7135
|
+
},
|
|
7136
|
+
{
|
|
7137
|
+
path: `${apiBase}/src/worker.ts`,
|
|
7138
|
+
purpose: "Protected service boundary.",
|
|
7139
|
+
requiredSignals: [
|
|
7140
|
+
"uses @topolo-io/worker-runtime for Worker auth, gatewaying, headers, and request helpers",
|
|
7141
|
+
"derives organization from the credential rather than client-supplied orgId",
|
|
7142
|
+
"emits or propagates X-Topolo-Request-Id and X-Topolo-Client"
|
|
7143
|
+
],
|
|
7144
|
+
content: workerTs(application, target, {
|
|
7145
|
+
audience: options.payload.audience,
|
|
7146
|
+
tenancy: options.payload.tenancy,
|
|
7147
|
+
profile: options.payload.profile,
|
|
7148
|
+
resources: options.payload.resources
|
|
7149
|
+
})
|
|
7150
|
+
}
|
|
7151
|
+
);
|
|
7152
|
+
if (target === "platform") {
|
|
7153
|
+
files.push(
|
|
7154
|
+
{
|
|
7155
|
+
path: `${apiBase}/src/service-identity.ts`,
|
|
7156
|
+
purpose: "Runtime service identity resolver for the API Worker.",
|
|
7157
|
+
requiredSignals: [
|
|
7158
|
+
"resolves the concrete Auth service id from Worker vars",
|
|
7159
|
+
"keeps the API Worker independent from browser-only import.meta usage"
|
|
7160
|
+
],
|
|
7161
|
+
content: apiServiceIdentityTs(application)
|
|
7162
|
+
},
|
|
7163
|
+
{
|
|
7164
|
+
path: `${browserBase}/src/worker.ts`,
|
|
7165
|
+
purpose: "Browser Worker that serves assets and forwards API paths to the API Worker binding.",
|
|
7166
|
+
requiredSignals: [
|
|
7167
|
+
"keeps the frontend Worker thin",
|
|
7168
|
+
"routes /api/* to the API service binding instead of owning backend routes locally"
|
|
7169
|
+
],
|
|
7170
|
+
content: webWorkerTs(application)
|
|
7171
|
+
},
|
|
7172
|
+
{
|
|
7173
|
+
path: `${browserBase}/wrangler.toml`,
|
|
7174
|
+
purpose: "Cloudflare Worker static-assets configuration for the browser surface.",
|
|
7175
|
+
requiredSignals: [
|
|
7176
|
+
"serves Vite assets through a Worker with assets",
|
|
7177
|
+
"binds the frontend Worker to the API Worker for /api/* requests"
|
|
7178
|
+
],
|
|
7179
|
+
content: platformWebWranglerToml(application)
|
|
7180
|
+
}
|
|
7181
|
+
);
|
|
7182
|
+
}
|
|
7183
|
+
}
|
|
7184
|
+
return files;
|
|
7185
|
+
}
|
|
6932
7186
|
function topoloAppManifestJson(application, options) {
|
|
6933
7187
|
const slug = toSlug(application.id);
|
|
6934
7188
|
const resource = resourceConfig(application, options.profile);
|
|
@@ -6993,8 +7247,8 @@ function topoloAppManifestJson(application, options) {
|
|
|
6993
7247
|
production: "https://auth.topolo.app"
|
|
6994
7248
|
},
|
|
6995
7249
|
NEXUS_GATEWAY_URL: {
|
|
6996
|
-
staging: "https://nexus
|
|
6997
|
-
production: "https://nexus
|
|
7250
|
+
staging: "https://nexus.stg.topolo.us",
|
|
7251
|
+
production: "https://nexus.topolo.app"
|
|
6998
7252
|
},
|
|
6999
7253
|
NEXUS_APP_ID: slug
|
|
7000
7254
|
},
|
|
@@ -7245,7 +7499,7 @@ function cloudcontrolJson(application, options) {
|
|
|
7245
7499
|
];
|
|
7246
7500
|
return `${json({
|
|
7247
7501
|
name: `topolo-${slug}`,
|
|
7248
|
-
description: `Canonical documentation for ${toTitle(application.name)} lives in
|
|
7502
|
+
description: `Canonical documentation for ${toTitle(application.name)} lives in system-apps/TopoloDocs.`,
|
|
7249
7503
|
deploy_method: "wrangler",
|
|
7250
7504
|
ci_provider: "github_actions",
|
|
7251
7505
|
repo_identity: {
|
|
@@ -7396,7 +7650,7 @@ function internalDocs(application, options) {
|
|
|
7396
7650
|
`last_verified: "${CURRENT_VERIFICATION_DATE}"`,
|
|
7397
7651
|
"source_repos:",
|
|
7398
7652
|
` - "${appPath}"`,
|
|
7399
|
-
' - "
|
|
7653
|
+
' - "system-apps/TopoloDocs"',
|
|
7400
7654
|
'audience: "internal"',
|
|
7401
7655
|
"tags:",
|
|
7402
7656
|
` - "${slug}"`,
|
|
@@ -7429,7 +7683,7 @@ function internalDocs(application, options) {
|
|
|
7429
7683
|
...options.api ? [`- \`${appPath}/apps/api/src/worker.ts\``, `- \`${appPath}/apps/api/wrangler.toml\``] : [],
|
|
7430
7684
|
`- \`${appPath}/topolo.app.json\``,
|
|
7431
7685
|
`- \`${appPath}/topolo.cloudcontrol.json\``,
|
|
7432
|
-
...options.api && application.serviceId ? [`- \`
|
|
7686
|
+
...options.api && application.serviceId ? [`- \`topolo-platform/core/TopoloAuth/catalog/service-manifests/${application.serviceId}.json\``] : [],
|
|
7433
7687
|
"",
|
|
7434
7688
|
"## API Reference",
|
|
7435
7689
|
"",
|
|
@@ -7458,7 +7712,7 @@ function internalDocs(application, options) {
|
|
|
7458
7712
|
"## Debugging",
|
|
7459
7713
|
"",
|
|
7460
7714
|
"- run `npm run typecheck` and `npm run build` in the application directory",
|
|
7461
|
-
"- run `topolo apps audit <app> --local --repo-root /Users/ashes/Projects/Topolo
|
|
7715
|
+
"- run `topolo apps audit <app> --local --repo-root /Users/ashes/Projects/Topolo --fail-on needs_review --json` after SDK catalog registration",
|
|
7462
7716
|
"- inspect `topolo.cloudcontrol.json` before deployment changes",
|
|
7463
7717
|
...options.api ? ["- inspect the Worker `SERVICE_ID` binding and matching Auth service manifest when permission grants fail"] : [],
|
|
7464
7718
|
...options.api && application.serviceId ? [`- re-run \`topolo platform apps scaffold ${slug} --write --provision\` when embedded app login, launcher visibility, or callback origins drift`] : [],
|
|
@@ -7697,132 +7951,6 @@ function envExample(application) {
|
|
|
7697
7951
|
""
|
|
7698
7952
|
].join("\n");
|
|
7699
7953
|
}
|
|
7700
|
-
function normalizeTarget(value) {
|
|
7701
|
-
if (!value) return "platform";
|
|
7702
|
-
if (value === "platform" || value === "developer") return value;
|
|
7703
|
-
throw new Error("Invalid scaffold target. Use platform or developer.");
|
|
7704
|
-
}
|
|
7705
|
-
function normalizePortfolio(value) {
|
|
7706
|
-
if (!value) return "platform";
|
|
7707
|
-
if (value === "platform" || value === "personal") return value;
|
|
7708
|
-
throw new Error("Invalid scaffold portfolio. Use platform or personal.");
|
|
7709
|
-
}
|
|
7710
|
-
function normalizeAudience(value, portfolio) {
|
|
7711
|
-
if (!value) {
|
|
7712
|
-
return portfolio === "personal" ? "personal" : "business";
|
|
7713
|
-
}
|
|
7714
|
-
if (value === "business" || value === "personal") {
|
|
7715
|
-
return value;
|
|
7716
|
-
}
|
|
7717
|
-
throw new Error("Invalid scaffold audience. Use business or personal.");
|
|
7718
|
-
}
|
|
7719
|
-
function normalizeTenancy(value, audience) {
|
|
7720
|
-
const resolved = value ?? (audience === "personal" ? "household" : "organization");
|
|
7721
|
-
if (resolved !== "organization" && resolved !== "household") {
|
|
7722
|
-
throw new Error("Invalid scaffold tenancy. Use organization or household.");
|
|
7723
|
-
}
|
|
7724
|
-
if (audience === "business" && resolved !== "organization") {
|
|
7725
|
-
throw new Error("Business scaffolds must use organization tenancy.");
|
|
7726
|
-
}
|
|
7727
|
-
if (audience === "personal" && resolved !== "household") {
|
|
7728
|
-
throw new Error("Personal scaffolds must use household tenancy.");
|
|
7729
|
-
}
|
|
7730
|
-
return resolved;
|
|
7731
|
-
}
|
|
7732
|
-
function normalizeSurfaceModel(value) {
|
|
7733
|
-
if (!value) return "web_app";
|
|
7734
|
-
if (value === "web_app" || value === "mobile_container" || value === "mobile_module" || value === "messaging_surface") {
|
|
7735
|
-
return value;
|
|
7736
|
-
}
|
|
7737
|
-
throw new Error(
|
|
7738
|
-
"Invalid scaffold surface model. Use web_app, mobile_container, mobile_module, or messaging_surface."
|
|
7739
|
-
);
|
|
7740
|
-
}
|
|
7741
|
-
function normalizeScaffoldProfile(value) {
|
|
7742
|
-
if (!value) return null;
|
|
7743
|
-
if (value === "spatial-assets") return value;
|
|
7744
|
-
throw new Error("Invalid scaffold profile. Use spatial-assets.");
|
|
7745
|
-
}
|
|
7746
|
-
function resolveScaffoldResources(profile, options) {
|
|
7747
|
-
const profileResources = profile === "spatial-assets" ? { d1: true, r2: true, queue: true, workflow: true, containerProcessing: true } : { d1: false, r2: false, queue: false, workflow: false, containerProcessing: false };
|
|
7748
|
-
return {
|
|
7749
|
-
d1: Boolean(profileResources.d1 || options.withD1),
|
|
7750
|
-
r2: Boolean(profileResources.r2 || options.withR2),
|
|
7751
|
-
queue: Boolean(profileResources.queue || options.withQueue),
|
|
7752
|
-
workflow: Boolean(profileResources.workflow || options.withWorkflow),
|
|
7753
|
-
containerProcessing: Boolean(profileResources.containerProcessing || options.withContainerProcessing)
|
|
7754
|
-
};
|
|
7755
|
-
}
|
|
7756
|
-
function normalizeContainerAppId(value, surfaceModel) {
|
|
7757
|
-
const trimmed = String(value || "").trim();
|
|
7758
|
-
if (surfaceModel === "mobile_module") {
|
|
7759
|
-
if (!trimmed) {
|
|
7760
|
-
throw new Error("Mobile-module scaffolds require --container-app-id.");
|
|
7761
|
-
}
|
|
7762
|
-
return trimmed;
|
|
7763
|
-
}
|
|
7764
|
-
return trimmed || null;
|
|
7765
|
-
}
|
|
7766
|
-
function normalizeApplicationId(application) {
|
|
7767
|
-
const raw = application.trim().toLowerCase();
|
|
7768
|
-
const normalized = raw.replace(/-/g, "_");
|
|
7769
|
-
if (Object.prototype.hasOwnProperty.call(APPLICATIONS, raw)) {
|
|
7770
|
-
return raw;
|
|
7771
|
-
}
|
|
7772
|
-
if (Object.prototype.hasOwnProperty.call(APPLICATIONS, normalized)) {
|
|
7773
|
-
return normalized;
|
|
7774
|
-
}
|
|
7775
|
-
if (/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(raw) || /^[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$/.test(raw)) {
|
|
7776
|
-
return normalized;
|
|
7777
|
-
}
|
|
7778
|
-
throw new Error(
|
|
7779
|
-
"Application id must be kebab-case or snake_case using lowercase letters, numbers, hyphens, and underscores."
|
|
7780
|
-
);
|
|
7781
|
-
}
|
|
7782
|
-
function normalizeFirstPartyApplicationId(applicationId) {
|
|
7783
|
-
if (applicationId.startsWith("topolo_") && applicationId.length > "topolo_".length) {
|
|
7784
|
-
return applicationId.slice("topolo_".length);
|
|
7785
|
-
}
|
|
7786
|
-
return applicationId;
|
|
7787
|
-
}
|
|
7788
|
-
function toSlug(value) {
|
|
7789
|
-
return value.replace(/_/g, "-");
|
|
7790
|
-
}
|
|
7791
|
-
function toPascalCase(value) {
|
|
7792
|
-
const acronyms = /* @__PURE__ */ new Set(["api", "cli", "crm", "mcp", "mdm", "p2p"]);
|
|
7793
|
-
return value.split(/[-_]/).filter(Boolean).map((part) => acronyms.has(part.toLowerCase()) ? part.toUpperCase() : part.slice(0, 1).toUpperCase() + part.slice(1)).join("");
|
|
7794
|
-
}
|
|
7795
|
-
function toTitle(value) {
|
|
7796
|
-
return value.replace(/^topolo[-_\s]*/i, "Topolo ").split(/[-_\s]+/).filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
|
|
7797
|
-
}
|
|
7798
|
-
function json(value) {
|
|
7799
|
-
return JSON.stringify(value, null, 2);
|
|
7800
|
-
}
|
|
7801
|
-
async function pathExists(path4) {
|
|
7802
|
-
try {
|
|
7803
|
-
await access(path4, constants.F_OK);
|
|
7804
|
-
return true;
|
|
7805
|
-
} catch {
|
|
7806
|
-
return false;
|
|
7807
|
-
}
|
|
7808
|
-
}
|
|
7809
|
-
function resolveInside(root, filePath) {
|
|
7810
|
-
const resolved = resolve(root, filePath);
|
|
7811
|
-
const relativePath = relative(root, resolved);
|
|
7812
|
-
if (relativePath.startsWith("..") || relativePath === "" || relativePath.startsWith("/")) {
|
|
7813
|
-
throw new Error(`Scaffold file path escapes the target root: ${filePath}`);
|
|
7814
|
-
}
|
|
7815
|
-
return resolved;
|
|
7816
|
-
}
|
|
7817
|
-
function escapeHtml(value) {
|
|
7818
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
7819
|
-
}
|
|
7820
|
-
function escapeSingleQuoted(value) {
|
|
7821
|
-
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
7822
|
-
}
|
|
7823
|
-
function escapeFrontmatter(value) {
|
|
7824
|
-
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
7825
|
-
}
|
|
7826
7954
|
|
|
7827
7955
|
// src/commands/app-scaffold-provision.ts
|
|
7828
7956
|
import { readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
|
|
@@ -8137,7 +8265,7 @@ function registerAppsCommand(program2) {
|
|
|
8137
8265
|
});
|
|
8138
8266
|
apps.command("scaffold <application>").description(
|
|
8139
8267
|
"Generate a third-party Topolo application scaffold for business or personal/household use."
|
|
8140
|
-
).option("--json", "Output JSON").option("--no-json", "Output human-readable checklist").option("--app-dir <path>", "Application directory under
|
|
8268
|
+
).option("--json", "Output JSON").option("--no-json", "Output human-readable checklist").option("--app-dir <path>", "Application directory under the scaffold root").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 service id for API-backed apps").option("--audience <audience>", "Developer app audience: business or personal").option("--tenancy <tenancy>", "Developer app tenancy: organization or household").option(
|
|
8141
8269
|
"--surface-model <surfaceModel>",
|
|
8142
8270
|
"Developer app surface model: web_app, mobile_container, mobile_module, or messaging_surface"
|
|
8143
8271
|
).option("--container-app-id <appId>", "Container app id when --surface-model mobile_module is used").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 API/service files for developer scaffolds").option("--no-api", "Do not include API/service requirements").option("--no-browser", "Do not include browser-shell requirements").option(
|
|
@@ -8561,7 +8689,7 @@ function registerPlatformCommand(program2) {
|
|
|
8561
8689
|
const platformApps = platform3.command("apps").description("Scaffold and operate first-party Topolo platform and personal applications");
|
|
8562
8690
|
platformApps.command("scaffold <application>").description(
|
|
8563
8691
|
"Generate a first-party Topolo scaffold for the platform or personal portfolio."
|
|
8564
|
-
).option("--json", "Output JSON").option("--no-json", "Output human-readable checklist").option("--app-dir <path>", "Application directory under
|
|
8692
|
+
).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 service id for API-backed apps").option("--portfolio <portfolio>", "First-party portfolio: platform or personal").option(
|
|
8565
8693
|
"--surface-model <surfaceModel>",
|
|
8566
8694
|
"First-party surface model: web_app, mobile_container, mobile_module, or messaging_surface"
|
|
8567
8695
|
).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(
|
|
@@ -9182,7 +9310,7 @@ function normalizeList(values, fallback = []) {
|
|
|
9182
9310
|
|
|
9183
9311
|
// src/commands/integrations.ts
|
|
9184
9312
|
import { readdir, readFile as readFile3, stat } from "fs/promises";
|
|
9185
|
-
import { join as join4, relative as
|
|
9313
|
+
import { join as join4, relative as relative3 } from "path";
|
|
9186
9314
|
function registerIntegrationsCommand(program2) {
|
|
9187
9315
|
const integrations = program2.command("integrations").description("Generate and validate Topolo integration handoffs.");
|
|
9188
9316
|
integrations.command("prompt").description("Print an agent prompt for adding a Topolo integration.").argument("<integration>", "Integration prompt id, currently login-with-topolo").option("--framework <framework>", "auto, authjs, express, or generic", "auto").option("--json", "Emit JSON output").action((integration, options) => {
|
|
@@ -9347,7 +9475,7 @@ async function collectFiles(repoRoot) {
|
|
|
9347
9475
|
const info = await stat(fullPath);
|
|
9348
9476
|
if (info.size > 25e4) continue;
|
|
9349
9477
|
result.push({
|
|
9350
|
-
path:
|
|
9478
|
+
path: relative3(root, fullPath),
|
|
9351
9479
|
content: await readFile3(fullPath, "utf8")
|
|
9352
9480
|
});
|
|
9353
9481
|
}
|