@topolo/cli 0.1.22 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +174 -76
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { platform as platform2 } from "os";
|
|
|
9
9
|
import { createInterface } from "readline/promises";
|
|
10
10
|
import { stdin as input } from "process";
|
|
11
11
|
|
|
12
|
-
//
|
|
12
|
+
// ../topolo-sdk/dist/index.js
|
|
13
13
|
function applyAuthHeaders(headers, credential) {
|
|
14
14
|
if (credential.kind === "api_key") {
|
|
15
15
|
headers.set("X-Api-Key", credential.apiKey);
|
|
@@ -1522,12 +1522,12 @@ var APPLICATION_REQUIREMENTS = [
|
|
|
1522
1522
|
summary: "Applications must derive identity, organization, scopes, and service entitlement from TopoloAuth rather than inventing parallel auth state.",
|
|
1523
1523
|
appliesTo: ["browser", "api", "tooling", "agent_surface"],
|
|
1524
1524
|
evidence: [
|
|
1525
|
-
"@topolo/auth-client or @topolo
|
|
1525
|
+
"@topolo/auth-client or @topolo/auth-middleware is used where applicable",
|
|
1526
1526
|
"No public command, tool, or SDK method accepts orgId",
|
|
1527
1527
|
"Service slug and Auth service registration are documented for protected services"
|
|
1528
1528
|
],
|
|
1529
1529
|
implementation: [
|
|
1530
|
-
"Use shared auth
|
|
1530
|
+
"Use shared auth packages instead of copying token parsing or browser storage flows",
|
|
1531
1531
|
"Derive organization from the credential on every request",
|
|
1532
1532
|
"Route OAuth, refresh, logout, and SSO paths according to the shared Auth contract"
|
|
1533
1533
|
]
|
|
@@ -2068,58 +2068,6 @@ var TopoloClient = class _TopoloClient {
|
|
|
2068
2068
|
});
|
|
2069
2069
|
return value;
|
|
2070
2070
|
}
|
|
2071
|
-
/**
|
|
2072
|
-
* List the platform service catalog from the live registry
|
|
2073
|
-
* (`GET /api/services/catalog` on TopoloAuth). Credential-aware: the
|
|
2074
|
-
* caller's credential is attached, so an authenticated caller in an
|
|
2075
|
-
* entitled org also receives the `developer_only` services its role
|
|
2076
|
-
* permits; an unauthenticated/agent caller receives `active` only.
|
|
2077
|
-
*
|
|
2078
|
-
* Resilient: on any network/HTTP failure this falls back to the
|
|
2079
|
-
* build-time generated `APPLICATIONS` snapshot so offline/degraded
|
|
2080
|
-
* callers still get a usable (if static) catalog. The live result is
|
|
2081
|
-
* authoritative; the snapshot is a floor, not a cap.
|
|
2082
|
-
*/
|
|
2083
|
-
async listServices() {
|
|
2084
|
-
try {
|
|
2085
|
-
const res = await this.request({ service: "auth", method: "GET", path: "/api/services/catalog" });
|
|
2086
|
-
const raw = res.data?.services ?? res.services;
|
|
2087
|
-
if (Array.isArray(raw) && raw.length > 0) {
|
|
2088
|
-
const entries = [];
|
|
2089
|
-
for (const item of raw) {
|
|
2090
|
-
const s = item;
|
|
2091
|
-
const serviceId = typeof s.service_id === "string" ? s.service_id : typeof s.id === "string" ? s.id : null;
|
|
2092
|
-
if (!serviceId) continue;
|
|
2093
|
-
entries.push({
|
|
2094
|
-
serviceId,
|
|
2095
|
-
slug: typeof s.app_slug === "string" ? s.app_slug : null,
|
|
2096
|
-
name: typeof s.name === "string" ? s.name : null,
|
|
2097
|
-
description: typeof s.description === "string" ? s.description : null,
|
|
2098
|
-
baseUrl: typeof s.base_url === "string" ? s.base_url : null,
|
|
2099
|
-
status: typeof s.status === "string" ? s.status : null,
|
|
2100
|
-
surfaceKind: typeof s.surface_kind === "string" ? s.surface_kind : null,
|
|
2101
|
-
launchable: s.launchable === true
|
|
2102
|
-
});
|
|
2103
|
-
}
|
|
2104
|
-
if (entries.length > 0) return entries;
|
|
2105
|
-
}
|
|
2106
|
-
} catch {
|
|
2107
|
-
}
|
|
2108
|
-
return this.generatedCatalogFallback();
|
|
2109
|
-
}
|
|
2110
|
-
generatedCatalogFallback() {
|
|
2111
|
-
const apps = Object.values(APPLICATIONS);
|
|
2112
|
-
return apps.map((app) => ({
|
|
2113
|
-
serviceId: app.id,
|
|
2114
|
-
slug: app.id,
|
|
2115
|
-
name: app.name ?? app.id,
|
|
2116
|
-
description: null,
|
|
2117
|
-
baseUrl: app.productionUrl ?? null,
|
|
2118
|
-
status: "active",
|
|
2119
|
-
surfaceKind: "application",
|
|
2120
|
-
launchable: Boolean(app.productionUrl)
|
|
2121
|
-
}));
|
|
2122
|
-
}
|
|
2123
2071
|
/**
|
|
2124
2072
|
* Introspect the current credential. Returns the caller's resolved identity,
|
|
2125
2073
|
* organization, and permission set. Used by CLI `whoami` and by MCP to gate
|
|
@@ -3108,6 +3056,8 @@ import {
|
|
|
3108
3056
|
} from "fs";
|
|
3109
3057
|
import path from "path";
|
|
3110
3058
|
var EVIDENCE_FRESHNESS_MS = 36 * 60 * 60 * 1e3;
|
|
3059
|
+
var REQUIRED_AUTH_CLIENT_VERSION = "1.1.8";
|
|
3060
|
+
var REQUIRED_UI_KIT_VERSION = "0.4.63";
|
|
3111
3061
|
function readEvidenceFile(appRoot) {
|
|
3112
3062
|
const candidate = path.join(appRoot, ".topolo", "evidence.json");
|
|
3113
3063
|
if (!isFile(candidate)) return null;
|
|
@@ -3269,32 +3219,56 @@ function enrichFinding(application, finding2, evidence) {
|
|
|
3269
3219
|
evidence: ["service=auth"]
|
|
3270
3220
|
});
|
|
3271
3221
|
}
|
|
3222
|
+
const usesWorkerRuntime = authEvidence.includes("source=@topolo-io/worker-runtime") || authEvidence.includes("source=topolo-worker-runtime") || authEvidence.includes("source=createTopoloWorkerRuntime");
|
|
3272
3223
|
const usesAuthMiddleware = authEvidence.includes("source=@topolo-io/auth-middleware") || authEvidence.includes("source=topolo-auth-middleware") || authEvidence.includes("source=TopoloAuthMiddleware");
|
|
3273
3224
|
const usesAuthClient = authEvidence.includes("source=@topolo-io/auth-client") || authEvidence.includes("source=topolo-auth-client");
|
|
3274
3225
|
const usesTopoloClient = authEvidence.includes("source=TopoloClient");
|
|
3275
3226
|
const usesAuthValidateFetch = authEvidence.includes("source=auth-validate-fetch");
|
|
3276
|
-
if (
|
|
3227
|
+
if (authEvidence.includes(`dependency-version=@topolo-io/auth-client<${REQUIRED_AUTH_CLIENT_VERSION}`)) {
|
|
3228
|
+
return mergeFinding(finding2, {
|
|
3229
|
+
status: "needs_review",
|
|
3230
|
+
reason: `The app depends on @topolo-io/auth-client below ${REQUIRED_AUTH_CLIENT_VERSION}; public-route auth boot and session-channel fixes require the current shared client.`,
|
|
3231
|
+
evidence: authEvidence,
|
|
3232
|
+
nextActions: [
|
|
3233
|
+
`Update @topolo-io/auth-client to ${REQUIRED_AUTH_CLIENT_VERSION} or the workspace package that resolves to it`,
|
|
3234
|
+
"Regenerate the lockfile without reverting unrelated app changes",
|
|
3235
|
+
"Rerun this audit after the package update"
|
|
3236
|
+
]
|
|
3237
|
+
});
|
|
3238
|
+
}
|
|
3239
|
+
if (authEvidence.includes("auth-boot=blocking-cookie-refresh") && !authEvidence.includes("auth-boot=background-cookie-refresh")) {
|
|
3240
|
+
return mergeFinding(finding2, {
|
|
3241
|
+
status: "needs_review",
|
|
3242
|
+
reason: "The app appears to block all browser routes on auth cookie refresh. Public landing and login routes must render immediately and restore the cookie session in the background.",
|
|
3243
|
+
evidence: authEvidence,
|
|
3244
|
+
nextActions: [
|
|
3245
|
+
"Use authClient.initialize({ cookieRefresh: 'background' }) or initializeSession({ cookieRefresh: 'background' }) on public landing/login routes",
|
|
3246
|
+
"Keep cookieRefresh: 'blocking' for protected workspace routes",
|
|
3247
|
+
"Subscribe to auth:session-updated so background restore updates the visible user state"
|
|
3248
|
+
]
|
|
3249
|
+
});
|
|
3250
|
+
}
|
|
3251
|
+
if (usesWorkerRuntime || usesAuthMiddleware || usesAuthClient || usesTopoloClient || usesAuthValidateFetch) {
|
|
3277
3252
|
return mergeFinding(finding2, {
|
|
3278
3253
|
status: "met",
|
|
3279
|
-
reason: "Canonical Topolo auth wiring is present (auth-middleware verifyRequest, auth-client browser
|
|
3254
|
+
reason: "Canonical Topolo auth wiring is present (worker-runtime, auth-middleware verifyRequest, auth-client browser session controller, or TopoloClient).",
|
|
3280
3255
|
evidence: authEvidence
|
|
3281
3256
|
});
|
|
3282
3257
|
}
|
|
3283
3258
|
return mergeFinding(finding2, {
|
|
3284
3259
|
status: "partial",
|
|
3285
|
-
reason: "Shared Topolo auth dependency is declared but the canonical wiring (verifyRequest /
|
|
3260
|
+
reason: "Shared Topolo auth dependency is declared but the canonical wiring (verifyRequest / createBrowserAuthSessionController / useTopoloAuth / TopoloClient) is not visible.",
|
|
3286
3261
|
evidence: authEvidence
|
|
3287
3262
|
});
|
|
3288
3263
|
}
|
|
3289
3264
|
case "service-registration-and-scopes": {
|
|
3290
|
-
const applicationId = String(application.id);
|
|
3291
3265
|
const declaredServices = application.services ?? [];
|
|
3292
3266
|
if (declaredServices.length === 0) {
|
|
3293
|
-
if (
|
|
3267
|
+
if (application.id === "cloudcontrol") {
|
|
3294
3268
|
return mergeFinding(finding2, {
|
|
3295
3269
|
status: "met",
|
|
3296
3270
|
reason: "This application is itself a tooling/agent surface and does not register a callable platform service.",
|
|
3297
|
-
evidence: [`tooling=${
|
|
3271
|
+
evidence: [`tooling=${application.id}`],
|
|
3298
3272
|
nextActions: []
|
|
3299
3273
|
});
|
|
3300
3274
|
}
|
|
@@ -3373,6 +3347,18 @@ function enrichFinding(application, finding2, evidence) {
|
|
|
3373
3347
|
});
|
|
3374
3348
|
}
|
|
3375
3349
|
if (shellEvidence.length === 0) return finding2;
|
|
3350
|
+
if (shellEvidence.includes(`dependency-version=@topolo-io/ui-kit<${REQUIRED_UI_KIT_VERSION}`)) {
|
|
3351
|
+
return mergeFinding(finding2, {
|
|
3352
|
+
status: "needs_review",
|
|
3353
|
+
reason: `The app depends on @topolo-io/ui-kit below ${REQUIRED_UI_KIT_VERSION}; launcher catalog cache/dedupe fixes require the current shared kit.`,
|
|
3354
|
+
evidence: shellEvidence,
|
|
3355
|
+
nextActions: [
|
|
3356
|
+
`Update @topolo-io/ui-kit to ${REQUIRED_UI_KIT_VERSION}`,
|
|
3357
|
+
"Regenerate the lockfile without reverting unrelated app changes",
|
|
3358
|
+
"Rerun this audit after the package update"
|
|
3359
|
+
]
|
|
3360
|
+
});
|
|
3361
|
+
}
|
|
3376
3362
|
if (shellEvidence.includes("forbidden=workspace-shell")) {
|
|
3377
3363
|
return mergeFinding(finding2, {
|
|
3378
3364
|
status: "needs_review",
|
|
@@ -3439,7 +3425,7 @@ function enrichFinding(application, finding2, evidence) {
|
|
|
3439
3425
|
}
|
|
3440
3426
|
const text = evidence.source.text;
|
|
3441
3427
|
const hasOrganizationIdColumn = orgEvidence.includes("source=organization_id") || orgEvidence.includes("source=organizationId") || orgEvidence.includes("source=tenantId") || orgEvidence.includes("source=tenant_id");
|
|
3442
|
-
const hasCredentialBoundary = orgEvidence.includes("source=organization-auth-boundary") || text.includes("verifyRequest") || text.includes("
|
|
3428
|
+
const hasCredentialBoundary = orgEvidence.includes("source=organization-auth-boundary") || text.includes("verifyRequest") || text.includes("createBrowserAuthSessionController") || text.includes("useTopoloAuth");
|
|
3443
3429
|
if (hasOrganizationIdColumn && hasCredentialBoundary) {
|
|
3444
3430
|
return mergeFinding(finding2, {
|
|
3445
3431
|
status: "met",
|
|
@@ -3454,7 +3440,6 @@ function enrichFinding(application, finding2, evidence) {
|
|
|
3454
3440
|
});
|
|
3455
3441
|
}
|
|
3456
3442
|
case "agent-and-operator-surfaces": {
|
|
3457
|
-
const applicationId = String(application.id);
|
|
3458
3443
|
const declaredServices = application.services ?? [];
|
|
3459
3444
|
const text = evidence.source.text;
|
|
3460
3445
|
const items = [];
|
|
@@ -3464,11 +3449,11 @@ function enrichFinding(application, finding2, evidence) {
|
|
|
3464
3449
|
items.push("source=confirm-write-gate");
|
|
3465
3450
|
}
|
|
3466
3451
|
if (text.includes("@topolo/sdk")) items.push("source=@topolo/sdk");
|
|
3467
|
-
if (
|
|
3452
|
+
if (application.id === "cloudcontrol") {
|
|
3468
3453
|
return mergeFinding(finding2, {
|
|
3469
3454
|
status: "met",
|
|
3470
|
-
reason: "This application is itself an agent/operator surface (
|
|
3471
|
-
evidence: [`tooling=${
|
|
3455
|
+
reason: "This application is itself an agent/operator surface (CloudControl).",
|
|
3456
|
+
evidence: [`tooling=${application.id}`, ...items],
|
|
3472
3457
|
nextActions: []
|
|
3473
3458
|
});
|
|
3474
3459
|
}
|
|
@@ -3715,15 +3700,24 @@ function sharedAuthEvidence(evidence) {
|
|
|
3715
3700
|
const text = evidence.source.text;
|
|
3716
3701
|
const items = [];
|
|
3717
3702
|
if (deps.has("@topolo-io/auth-client")) items.push("dependency=@topolo-io/auth-client");
|
|
3703
|
+
if (packageDependencyVersions(evidence, "@topolo-io/auth-client").some((version) => isStaleDependencyVersion(version, REQUIRED_AUTH_CLIENT_VERSION))) {
|
|
3704
|
+
items.push(`dependency-version=@topolo-io/auth-client<${REQUIRED_AUTH_CLIENT_VERSION}`);
|
|
3705
|
+
}
|
|
3718
3706
|
if (deps.has("@topolo-io/auth-middleware")) items.push("dependency=@topolo-io/auth-middleware");
|
|
3707
|
+
if (deps.has("@topolo-io/worker-runtime")) items.push("dependency=@topolo-io/worker-runtime");
|
|
3719
3708
|
if (deps.has("@topolo/sdk")) items.push("dependency=@topolo/sdk");
|
|
3720
3709
|
if (text.includes("@topolo-io/auth-client")) items.push("source=@topolo-io/auth-client");
|
|
3721
3710
|
if (text.includes("@topolo-io/auth-middleware")) items.push("source=@topolo-io/auth-middleware");
|
|
3711
|
+
if (text.includes("@topolo-io/worker-runtime")) items.push("source=@topolo-io/worker-runtime");
|
|
3712
|
+
if (text.includes("topolo-worker-runtime")) items.push("source=topolo-worker-runtime");
|
|
3713
|
+
if (text.includes("createTopoloWorkerRuntime")) items.push("source=createTopoloWorkerRuntime");
|
|
3722
3714
|
if (text.includes("topolo-auth-client")) items.push("source=topolo-auth-client");
|
|
3723
3715
|
if (text.includes("topolo-auth-middleware")) items.push("source=topolo-auth-middleware");
|
|
3724
3716
|
if (text.includes("TopoloAuthMiddleware")) items.push("source=TopoloAuthMiddleware");
|
|
3725
3717
|
if (text.includes("TopoloClient")) items.push("source=TopoloClient");
|
|
3726
3718
|
if (text.includes("TopoloOAuth")) items.push("source=TopoloOAuth");
|
|
3719
|
+
if (/\.initialize(?:Session)?\(\s*\)/.test(text)) items.push("auth-boot=blocking-cookie-refresh");
|
|
3720
|
+
if (/cookieRefresh:\s*['"]background['"]/.test(text)) items.push("auth-boot=background-cookie-refresh");
|
|
3727
3721
|
if (/Authorization.*Bearer/i.test(text) && (text.includes("/validate") || text.includes("TOPOLO_AUTH_URL") || text.includes("AUTH_BASE_URL") || text.includes("auth.topolo.app"))) {
|
|
3728
3722
|
items.push("source=auth-validate-fetch");
|
|
3729
3723
|
}
|
|
@@ -3745,6 +3739,9 @@ function sharedShellEvidence(evidence) {
|
|
|
3745
3739
|
const text = evidence.source.text;
|
|
3746
3740
|
const items = [];
|
|
3747
3741
|
if (deps.has("@topolo-io/ui-kit")) items.push("dependency=@topolo-io/ui-kit");
|
|
3742
|
+
if (packageDependencyVersions(evidence, "@topolo-io/ui-kit").some((version) => isStaleDependencyVersion(version, REQUIRED_UI_KIT_VERSION))) {
|
|
3743
|
+
items.push(`dependency-version=@topolo-io/ui-kit<${REQUIRED_UI_KIT_VERSION}`);
|
|
3744
|
+
}
|
|
3748
3745
|
if (text.includes("@topolo-io/ui-kit")) items.push("source=@topolo-io/ui-kit");
|
|
3749
3746
|
if (text.includes("TopoloAppShell")) items.push("source=TopoloAppShell");
|
|
3750
3747
|
if (text.includes(["Topolo", "Workspace", "Shell"].join(""))) items.push("forbidden=workspace-shell");
|
|
@@ -3851,6 +3848,36 @@ function dependencyNames(evidence) {
|
|
|
3851
3848
|
])
|
|
3852
3849
|
);
|
|
3853
3850
|
}
|
|
3851
|
+
function packageDependencyVersions(evidence, packageName) {
|
|
3852
|
+
return unique(
|
|
3853
|
+
evidence.packageJsons.flatMap((record) => {
|
|
3854
|
+
const specs = [
|
|
3855
|
+
record.packageJson.dependencies?.[packageName],
|
|
3856
|
+
record.packageJson.devDependencies?.[packageName],
|
|
3857
|
+
record.packageJson.peerDependencies?.[packageName]
|
|
3858
|
+
];
|
|
3859
|
+
return specs.filter((spec) => typeof spec === "string");
|
|
3860
|
+
})
|
|
3861
|
+
);
|
|
3862
|
+
}
|
|
3863
|
+
function isStaleDependencyVersion(spec, minimumVersion) {
|
|
3864
|
+
if (spec.startsWith("workspace:")) return false;
|
|
3865
|
+
const current = parseDependencyVersion(spec);
|
|
3866
|
+
const minimum = parseDependencyVersion(minimumVersion);
|
|
3867
|
+
if (!current || !minimum) return false;
|
|
3868
|
+
const currentParts = current;
|
|
3869
|
+
const minimumParts = minimum;
|
|
3870
|
+
for (const index of [0, 1, 2]) {
|
|
3871
|
+
if (currentParts[index] < minimumParts[index]) return true;
|
|
3872
|
+
if (currentParts[index] > minimumParts[index]) return false;
|
|
3873
|
+
}
|
|
3874
|
+
return false;
|
|
3875
|
+
}
|
|
3876
|
+
function parseDependencyVersion(spec) {
|
|
3877
|
+
const match = spec.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
3878
|
+
if (!match) return null;
|
|
3879
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
3880
|
+
}
|
|
3854
3881
|
function matchingSystems(repoRoot, application) {
|
|
3855
3882
|
const docsRoot = topoloDocsRoot(repoRoot);
|
|
3856
3883
|
const systemsRoot = path.join(docsRoot, "src", "content", "systems");
|
|
@@ -4624,7 +4651,7 @@ function packageJson(application, target, options) {
|
|
|
4624
4651
|
dependencies["@topolo/sdk"] = topoloPackageVersion("@topolo/sdk", target);
|
|
4625
4652
|
scripts.deploy = `node -e 'console.error("\\nX Bare deploy is disabled. Use deploy:staging or deploy:production.\\n"); process.exit(1)'`;
|
|
4626
4653
|
scripts["deploy:staging"] = "DEPLOY_ENVIRONMENT=staging VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us npm run build && wrangler deploy --env staging";
|
|
4627
|
-
scripts["deploy:production"] = "DEPLOY_ENVIRONMENT=production npm run build && wrangler deploy";
|
|
4654
|
+
scripts["deploy:production"] = "DEPLOY_ENVIRONMENT=production VITE_TOPOLO_AUTH_URL=https://auth.topolo.app npm run build && wrangler deploy";
|
|
4628
4655
|
} else {
|
|
4629
4656
|
dependencies["@topolo-io/widget-server"] = topoloPackageVersion("@topolo-io/widget-server", target);
|
|
4630
4657
|
scripts.deploy = "npm run build && wrangler deploy";
|
|
@@ -5244,7 +5271,9 @@ function platformAppTsx(application, options) {
|
|
|
5244
5271
|
" if (!auth) return;",
|
|
5245
5272
|
" let active = true;",
|
|
5246
5273
|
" const syncAuth = async () => {",
|
|
5247
|
-
"
|
|
5274
|
+
" const publicRoute = route.kind === 'root' || route.kind === 'login';",
|
|
5275
|
+
" if (publicRoute) setLoading(false);",
|
|
5276
|
+
" else setLoading(true);",
|
|
5248
5277
|
" setAuthError('');",
|
|
5249
5278
|
"",
|
|
5250
5279
|
" if (route.kind === 'callback') {",
|
|
@@ -5260,7 +5289,9 @@ function platformAppTsx(application, options) {
|
|
|
5260
5289
|
" return;",
|
|
5261
5290
|
" }",
|
|
5262
5291
|
"",
|
|
5263
|
-
" const nextUser = await auth.authClient.initialize(
|
|
5292
|
+
" const nextUser = await auth.authClient.initialize({",
|
|
5293
|
+
" cookieRefresh: publicRoute ? 'background' : 'blocking',",
|
|
5294
|
+
" });",
|
|
5264
5295
|
" if (!active) return;",
|
|
5265
5296
|
" setUser(nextUser);",
|
|
5266
5297
|
" setLoading(false);",
|
|
@@ -5277,6 +5308,16 @@ function platformAppTsx(application, options) {
|
|
|
5277
5308
|
" }, [auth, route.kind]);",
|
|
5278
5309
|
"",
|
|
5279
5310
|
" useEffect(() => {",
|
|
5311
|
+
" if (!auth) return;",
|
|
5312
|
+
" const handleSessionUpdated = () => {",
|
|
5313
|
+
" const nextUser = auth.authClient.getHydratedCurrentUser() || auth.authClient.getTokenIdentity();",
|
|
5314
|
+
" if (nextUser) setUser(nextUser);",
|
|
5315
|
+
" };",
|
|
5316
|
+
" window.addEventListener('auth:session-updated', handleSessionUpdated);",
|
|
5317
|
+
" return () => window.removeEventListener('auth:session-updated', handleSessionUpdated);",
|
|
5318
|
+
" }, [auth]);",
|
|
5319
|
+
"",
|
|
5320
|
+
" useEffect(() => {",
|
|
5280
5321
|
" if (!loading && user && route.kind === 'login') {",
|
|
5281
5322
|
" window.location.replace('/');",
|
|
5282
5323
|
" }",
|
|
@@ -6881,8 +6922,31 @@ function readRequiredString(value, message) {
|
|
|
6881
6922
|
}
|
|
6882
6923
|
return resolved;
|
|
6883
6924
|
}
|
|
6925
|
+
async function applyProvisionedServiceId(manifestPath, serviceId) {
|
|
6926
|
+
let raw;
|
|
6927
|
+
try {
|
|
6928
|
+
raw = await readFile2(manifestPath, "utf8");
|
|
6929
|
+
} catch {
|
|
6930
|
+
return { changed: false, previous: null };
|
|
6931
|
+
}
|
|
6932
|
+
let json2;
|
|
6933
|
+
try {
|
|
6934
|
+
json2 = JSON.parse(raw);
|
|
6935
|
+
} catch {
|
|
6936
|
+
return { changed: false, previous: null };
|
|
6937
|
+
}
|
|
6938
|
+
const previous = typeof json2.serviceId === "string" ? json2.serviceId : null;
|
|
6939
|
+
if (previous === serviceId) {
|
|
6940
|
+
return { changed: false, previous };
|
|
6941
|
+
}
|
|
6942
|
+
json2.serviceId = serviceId;
|
|
6943
|
+
await writeFile3(manifestPath, `${JSON.stringify(json2, null, 2)}
|
|
6944
|
+
`, "utf8");
|
|
6945
|
+
return { changed: true, previous };
|
|
6946
|
+
}
|
|
6884
6947
|
|
|
6885
6948
|
// src/commands/apps.ts
|
|
6949
|
+
import { join as join3 } from "path";
|
|
6886
6950
|
var APPLICATION_IDS = Object.keys(APPLICATIONS).sort();
|
|
6887
6951
|
function registerAppsCommand(program2) {
|
|
6888
6952
|
const apps = program2.command("apps").description("List every Topolo platform application known to the SDK catalog").option("--json", "Output JSON", true).option("--no-json", "Output human-readable table").action((opts) => {
|
|
@@ -6981,15 +7045,25 @@ function registerAppsCommand(program2) {
|
|
|
6981
7045
|
});
|
|
6982
7046
|
const writeResult = opts.write ? await writeApplicationScaffold(payload, { repoRoot: opts.repoRoot, force: opts.force }) : null;
|
|
6983
7047
|
const provisionResult = opts.provision ? await provisionApplicationScaffold(payload) : null;
|
|
7048
|
+
let serviceIdWriteBack = null;
|
|
7049
|
+
if (writeResult && provisionResult?.authService?.id) {
|
|
7050
|
+
const manifestFile = writeResult.files.find((f) => f.path.endsWith("topolo.app.json"));
|
|
7051
|
+
if (manifestFile) {
|
|
7052
|
+
const manifestPath = join3(writeResult.root, manifestFile.path);
|
|
7053
|
+
const result = await applyProvisionedServiceId(manifestPath, provisionResult.authService.id);
|
|
7054
|
+
serviceIdWriteBack = { ...result, path: manifestFile.path };
|
|
7055
|
+
}
|
|
7056
|
+
}
|
|
6984
7057
|
const output = {
|
|
6985
7058
|
...payload,
|
|
6986
7059
|
...writeResult ? { write: writeResult } : {},
|
|
6987
|
-
...provisionResult ? { provision: provisionResult } : {}
|
|
7060
|
+
...provisionResult ? { provision: provisionResult } : {},
|
|
7061
|
+
...serviceIdWriteBack ? { serviceIdWriteBack } : {}
|
|
6988
7062
|
};
|
|
6989
7063
|
emit(
|
|
6990
7064
|
output,
|
|
6991
7065
|
mode,
|
|
6992
|
-
() => formatScaffold(payload, writeResult, provisionResult)
|
|
7066
|
+
() => formatScaffold(payload, writeResult, provisionResult, serviceIdWriteBack)
|
|
6993
7067
|
);
|
|
6994
7068
|
} catch (err) {
|
|
6995
7069
|
fail(err, mode);
|
|
@@ -7223,7 +7297,7 @@ function formatAudit(payload) {
|
|
|
7223
7297
|
}
|
|
7224
7298
|
return lines.join("\n");
|
|
7225
7299
|
}
|
|
7226
|
-
function formatScaffold(payload, writeResult = null, provisionResult = null) {
|
|
7300
|
+
function formatScaffold(payload, writeResult = null, provisionResult = null, serviceIdWriteBack = null) {
|
|
7227
7301
|
const lines = [
|
|
7228
7302
|
`Version: ${payload.version}`,
|
|
7229
7303
|
`Target: ${payload.target}`,
|
|
@@ -7283,6 +7357,11 @@ function formatScaffold(payload, writeResult = null, provisionResult = null) {
|
|
|
7283
7357
|
);
|
|
7284
7358
|
}
|
|
7285
7359
|
}
|
|
7360
|
+
if (serviceIdWriteBack) {
|
|
7361
|
+
lines.push(
|
|
7362
|
+
serviceIdWriteBack.changed ? ` - service id written back to ${serviceIdWriteBack.path} (was ${serviceIdWriteBack.previous ?? "unset"})` : ` - service id already current in ${serviceIdWriteBack.path}`
|
|
7363
|
+
);
|
|
7364
|
+
}
|
|
7286
7365
|
return lines.join("\n");
|
|
7287
7366
|
}
|
|
7288
7367
|
function resolveScaffoldAudience(value) {
|
|
@@ -7915,7 +7994,7 @@ function normalizeList(values, fallback = []) {
|
|
|
7915
7994
|
|
|
7916
7995
|
// src/commands/integrations.ts
|
|
7917
7996
|
import { readdir, readFile as readFile3, stat } from "fs/promises";
|
|
7918
|
-
import { join as
|
|
7997
|
+
import { join as join4, relative as relative2 } from "path";
|
|
7919
7998
|
function registerIntegrationsCommand(program2) {
|
|
7920
7999
|
const integrations = program2.command("integrations").description("Generate and validate Topolo integration handoffs.");
|
|
7921
8000
|
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) => {
|
|
@@ -7973,7 +8052,7 @@ ${file.content}`).join("\n");
|
|
|
7973
8052
|
makeCheck(
|
|
7974
8053
|
"package",
|
|
7975
8054
|
"@topolo/login dependency",
|
|
7976
|
-
packageJson2
|
|
8055
|
+
topoloLoginPackageEvidence(packageJson2, files),
|
|
7977
8056
|
true
|
|
7978
8057
|
),
|
|
7979
8058
|
makeCheck(
|
|
@@ -8025,6 +8104,25 @@ function makeCheck(id, label, evidence, required) {
|
|
|
8025
8104
|
function matchEvidence(haystack, needles) {
|
|
8026
8105
|
return needles.filter((needle) => haystack.toLowerCase().includes(needle.toLowerCase()));
|
|
8027
8106
|
}
|
|
8107
|
+
function topoloLoginPackageEvidence(rootPackageJson, files) {
|
|
8108
|
+
const evidence = [];
|
|
8109
|
+
if (declaresTopoloLogin(rootPackageJson)) evidence.push("package.json declares @topolo/login");
|
|
8110
|
+
for (const file of files) {
|
|
8111
|
+
if (!file.path.endsWith("package.json") || file.path === "package.json") continue;
|
|
8112
|
+
try {
|
|
8113
|
+
if (declaresTopoloLogin(JSON.parse(file.content))) {
|
|
8114
|
+
evidence.push(`${file.path} declares @topolo/login`);
|
|
8115
|
+
}
|
|
8116
|
+
} catch {
|
|
8117
|
+
}
|
|
8118
|
+
}
|
|
8119
|
+
return evidence;
|
|
8120
|
+
}
|
|
8121
|
+
function declaresTopoloLogin(packageJson2) {
|
|
8122
|
+
return Boolean(
|
|
8123
|
+
packageJson2?.dependencies?.["@topolo/login"] || packageJson2?.devDependencies?.["@topolo/login"] || packageJson2?.peerDependencies?.["@topolo/login"]
|
|
8124
|
+
);
|
|
8125
|
+
}
|
|
8028
8126
|
function detectFramework(packageJson2, haystack) {
|
|
8029
8127
|
const deps = {
|
|
8030
8128
|
...packageJson2?.dependencies ?? {},
|
|
@@ -8039,7 +8137,7 @@ function detectFramework(packageJson2, haystack) {
|
|
|
8039
8137
|
}
|
|
8040
8138
|
async function readPackageJson2(repoRoot) {
|
|
8041
8139
|
try {
|
|
8042
|
-
return JSON.parse(await readFile3(
|
|
8140
|
+
return JSON.parse(await readFile3(join4(repoRoot, "package.json"), "utf8"));
|
|
8043
8141
|
} catch {
|
|
8044
8142
|
return null;
|
|
8045
8143
|
}
|
|
@@ -8051,7 +8149,7 @@ async function collectFiles(repoRoot) {
|
|
|
8051
8149
|
if (result.length > 2e3) return;
|
|
8052
8150
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
8053
8151
|
for (const entry of entries) {
|
|
8054
|
-
const fullPath =
|
|
8152
|
+
const fullPath = join4(dir, entry.name);
|
|
8055
8153
|
if (entry.isDirectory()) {
|
|
8056
8154
|
if ([".git", "node_modules", "dist", "build", ".next", "coverage"].includes(entry.name)) continue;
|
|
8057
8155
|
await walk(fullPath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topolo/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.23",
|
|
4
4
|
"description": "Command-line interface for Topolo developers, operators, and agents, with internal first-party scaffolding available under the topolo platform namespace.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"commander": "^12.1.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@topolo/sdk": "
|
|
25
|
+
"@topolo/sdk": "workspace:*",
|
|
26
26
|
"@types/node": "^20.12.0",
|
|
27
27
|
"@vitest/coverage-v8": "^2.1.0",
|
|
28
28
|
"tsup": "^8.0.0",
|