@topolo/cli 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generate-wrangler.mjs +908 -0
- package/dist/index.js +308 -706
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -61,13 +61,13 @@ var TopoloHttpError = class extends TopoloSdkError {
|
|
|
61
61
|
body;
|
|
62
62
|
service;
|
|
63
63
|
path;
|
|
64
|
-
constructor(service,
|
|
65
|
-
super("http_error", message ?? `HTTP ${status} from ${service}${
|
|
64
|
+
constructor(service, path6, status, body, message) {
|
|
65
|
+
super("http_error", message ?? `HTTP ${status} from ${service}${path6}`);
|
|
66
66
|
this.name = "TopoloHttpError";
|
|
67
67
|
this.status = status;
|
|
68
68
|
this.body = body;
|
|
69
69
|
this.service = service;
|
|
70
|
-
this.path =
|
|
70
|
+
this.path = path6;
|
|
71
71
|
}
|
|
72
72
|
};
|
|
73
73
|
var APP_API_URLS = {
|
|
@@ -185,7 +185,7 @@ var APPLICATIONS = {
|
|
|
185
185
|
"appDir": "TopoloAuth",
|
|
186
186
|
"name": "topolo-auth",
|
|
187
187
|
"packageName": "topolo-auth",
|
|
188
|
-
"productionUrl": "https://auth.topolo.app
|
|
188
|
+
"productionUrl": "https://auth.topolo.app",
|
|
189
189
|
"services": [
|
|
190
190
|
"auth"
|
|
191
191
|
],
|
|
@@ -273,9 +273,7 @@ var APPLICATIONS = {
|
|
|
273
273
|
"domains": [
|
|
274
274
|
"brand.topolo.app"
|
|
275
275
|
],
|
|
276
|
-
"routes": [
|
|
277
|
-
"brand.topolo.app"
|
|
278
|
-
],
|
|
276
|
+
"routes": [],
|
|
279
277
|
"appId": "brand"
|
|
280
278
|
},
|
|
281
279
|
{
|
|
@@ -387,9 +385,7 @@ var APPLICATIONS = {
|
|
|
387
385
|
"domains": [
|
|
388
386
|
"campaigns.topolo.app"
|
|
389
387
|
],
|
|
390
|
-
"routes": [
|
|
391
|
-
"campaigns.topolo.app"
|
|
392
|
-
],
|
|
388
|
+
"routes": [],
|
|
393
389
|
"appId": "campaigns"
|
|
394
390
|
},
|
|
395
391
|
{
|
|
@@ -1077,11 +1073,13 @@ var APPLICATIONS = {
|
|
|
1077
1073
|
{
|
|
1078
1074
|
"name": "topolo-nexus-dashboard",
|
|
1079
1075
|
"kind": "worker",
|
|
1080
|
-
"apiUrl":
|
|
1076
|
+
"apiUrl": "https://nexus.topolo.app",
|
|
1081
1077
|
"domains": [
|
|
1082
1078
|
"nexus.topolo.app"
|
|
1083
1079
|
],
|
|
1084
|
-
"routes": [
|
|
1080
|
+
"routes": [
|
|
1081
|
+
"nexus.topolo.app"
|
|
1082
|
+
],
|
|
1085
1083
|
"appId": null
|
|
1086
1084
|
},
|
|
1087
1085
|
{
|
|
@@ -2178,17 +2176,17 @@ function validateTopoloActionInput(schema, input3) {
|
|
|
2178
2176
|
validateNode(schema, input3, "$", schema, errors, 0);
|
|
2179
2177
|
return { valid: errors.length === 0, errors };
|
|
2180
2178
|
}
|
|
2181
|
-
function inspectSchema(schema,
|
|
2182
|
-
if (depth > 128) return { path:
|
|
2179
|
+
function inspectSchema(schema, path6, root, depth) {
|
|
2180
|
+
if (depth > 128) return { path: path6, code: "schema_depth", message: "Schema recursion exceeded 128 levels." };
|
|
2183
2181
|
const unsupportedKeyword = Object.keys(schema).find((keyword) => !SUPPORTED_SCHEMA_KEYWORDS.has(keyword));
|
|
2184
2182
|
if (unsupportedKeyword) {
|
|
2185
|
-
return { path:
|
|
2183
|
+
return { path: path6, code: "unsupported_schema_keyword", message: `Action schema uses unsupported keyword ${unsupportedKeyword}.` };
|
|
2186
2184
|
}
|
|
2187
2185
|
if (typeof schema.format === "string" && !["uri", "email", "date-time"].includes(schema.format)) {
|
|
2188
|
-
return { path:
|
|
2186
|
+
return { path: path6, code: "unsupported_schema_format", message: `Action schema uses unsupported format ${schema.format}.` };
|
|
2189
2187
|
}
|
|
2190
2188
|
if (typeof schema.$ref === "string" && !resolveLocalReference(root, schema.$ref)) {
|
|
2191
|
-
return { path:
|
|
2189
|
+
return { path: path6, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` };
|
|
2192
2190
|
}
|
|
2193
2191
|
const children = [];
|
|
2194
2192
|
if (isRecord(schema.properties)) children.push(...Object.entries(schema.properties));
|
|
@@ -2204,7 +2202,7 @@ function inspectSchema(schema, path5, root, depth) {
|
|
|
2204
2202
|
);
|
|
2205
2203
|
for (const [key, child] of children) {
|
|
2206
2204
|
if (!isRecord(child)) continue;
|
|
2207
|
-
const issue = inspectSchema(child, `${
|
|
2205
|
+
const issue = inspectSchema(child, `${path6}.${key}`, root, depth + 1);
|
|
2208
2206
|
if (issue) return issue;
|
|
2209
2207
|
}
|
|
2210
2208
|
return null;
|
|
@@ -2212,129 +2210,129 @@ function inspectSchema(schema, path5, root, depth) {
|
|
|
2212
2210
|
function arraySchemaEntries(keyword, value) {
|
|
2213
2211
|
return Array.isArray(value) ? value.map((candidate, index) => [`${keyword}[${index}]`, candidate]) : [];
|
|
2214
2212
|
}
|
|
2215
|
-
function validateNode(schema, value,
|
|
2213
|
+
function validateNode(schema, value, path6, root, errors, depth) {
|
|
2216
2214
|
if (depth > 128) {
|
|
2217
|
-
errors.push({ path:
|
|
2215
|
+
errors.push({ path: path6, code: "schema_depth", message: "Schema recursion exceeded 128 levels." });
|
|
2218
2216
|
return;
|
|
2219
2217
|
}
|
|
2220
2218
|
if (typeof schema.$ref === "string") {
|
|
2221
2219
|
const resolved = resolveLocalReference(root, schema.$ref);
|
|
2222
2220
|
if (!resolved) {
|
|
2223
|
-
errors.push({ path:
|
|
2221
|
+
errors.push({ path: path6, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` });
|
|
2224
2222
|
return;
|
|
2225
2223
|
}
|
|
2226
|
-
validateNode(resolved, value,
|
|
2224
|
+
validateNode(resolved, value, path6, root, errors, depth + 1);
|
|
2227
2225
|
}
|
|
2228
2226
|
if (Array.isArray(schema.allOf)) {
|
|
2229
2227
|
for (const candidate of schema.allOf) {
|
|
2230
|
-
if (isRecord(candidate)) validateNode(candidate, value,
|
|
2228
|
+
if (isRecord(candidate)) validateNode(candidate, value, path6, root, errors, depth + 1);
|
|
2231
2229
|
}
|
|
2232
2230
|
}
|
|
2233
2231
|
if (Array.isArray(schema.anyOf) && !schema.anyOf.some((candidate) => candidateMatches(candidate, value, root, depth))) {
|
|
2234
|
-
errors.push({ path:
|
|
2232
|
+
errors.push({ path: path6, code: "any_of", message: "Value does not match any allowed schema." });
|
|
2235
2233
|
return;
|
|
2236
2234
|
}
|
|
2237
2235
|
if (Array.isArray(schema.oneOf)) {
|
|
2238
2236
|
const matches = schema.oneOf.filter((candidate) => candidateMatches(candidate, value, root, depth)).length;
|
|
2239
2237
|
if (matches !== 1) {
|
|
2240
|
-
errors.push({ path:
|
|
2238
|
+
errors.push({ path: path6, code: "one_of", message: "Value must match exactly one allowed schema." });
|
|
2241
2239
|
return;
|
|
2242
2240
|
}
|
|
2243
2241
|
}
|
|
2244
2242
|
if (value === null && schema.nullable === true) return;
|
|
2245
2243
|
if (Object.prototype.hasOwnProperty.call(schema, "const") && !sameJsonValue(value, schema.const)) {
|
|
2246
|
-
errors.push({ path:
|
|
2244
|
+
errors.push({ path: path6, code: "const", message: `Value must equal ${JSON.stringify(schema.const)}.` });
|
|
2247
2245
|
return;
|
|
2248
2246
|
}
|
|
2249
2247
|
if (Array.isArray(schema.enum) && !schema.enum.some((candidate) => sameJsonValue(value, candidate))) {
|
|
2250
|
-
errors.push({ path:
|
|
2248
|
+
errors.push({ path: path6, code: "enum", message: `Value must be one of ${schema.enum.map((item) => JSON.stringify(item)).join(", ")}.` });
|
|
2251
2249
|
return;
|
|
2252
2250
|
}
|
|
2253
2251
|
if (!matchesType(schema.type, value)) {
|
|
2254
|
-
errors.push({ path:
|
|
2252
|
+
errors.push({ path: path6, code: "type", message: `Expected ${String(schema.type)}.` });
|
|
2255
2253
|
return;
|
|
2256
2254
|
}
|
|
2257
|
-
if (typeof value === "string") validateString(schema, value,
|
|
2258
|
-
if (typeof value === "number") validateNumber(schema, value,
|
|
2259
|
-
if (Array.isArray(value)) validateArray(schema, value,
|
|
2260
|
-
if (isRecord(value)) validateObject(schema, value,
|
|
2255
|
+
if (typeof value === "string") validateString(schema, value, path6, errors);
|
|
2256
|
+
if (typeof value === "number") validateNumber(schema, value, path6, errors);
|
|
2257
|
+
if (Array.isArray(value)) validateArray(schema, value, path6, root, errors, depth);
|
|
2258
|
+
if (isRecord(value)) validateObject(schema, value, path6, root, errors, depth);
|
|
2261
2259
|
}
|
|
2262
|
-
function validateObject(schema, value,
|
|
2260
|
+
function validateObject(schema, value, path6, root, errors, depth) {
|
|
2263
2261
|
if (typeof schema.minProperties === "number" && Object.keys(value).length < schema.minProperties) {
|
|
2264
|
-
errors.push({ path:
|
|
2262
|
+
errors.push({ path: path6, code: "min_properties", message: `Expected at least ${schema.minProperties} properties.` });
|
|
2265
2263
|
}
|
|
2266
2264
|
if (typeof schema.maxProperties === "number" && Object.keys(value).length > schema.maxProperties) {
|
|
2267
|
-
errors.push({ path:
|
|
2265
|
+
errors.push({ path: path6, code: "max_properties", message: `Expected at most ${schema.maxProperties} properties.` });
|
|
2268
2266
|
}
|
|
2269
2267
|
const properties = isRecord(schema.properties) ? schema.properties : {};
|
|
2270
2268
|
const required = Array.isArray(schema.required) ? schema.required.map(String) : [];
|
|
2271
2269
|
for (const key of required) {
|
|
2272
2270
|
if (!Object.prototype.hasOwnProperty.call(value, key)) {
|
|
2273
|
-
errors.push({ path: `${
|
|
2271
|
+
errors.push({ path: `${path6}.${key}`, code: "required", message: "Required property is missing." });
|
|
2274
2272
|
}
|
|
2275
2273
|
}
|
|
2276
2274
|
if (isRecord(schema.propertyNames)) {
|
|
2277
2275
|
for (const key of Object.keys(value)) {
|
|
2278
|
-
validateNode(schema.propertyNames, key, `${
|
|
2276
|
+
validateNode(schema.propertyNames, key, `${path6}.${key}`, root, errors, depth + 1);
|
|
2279
2277
|
}
|
|
2280
2278
|
}
|
|
2281
2279
|
for (const [key, child] of Object.entries(value)) {
|
|
2282
2280
|
const childSchema = properties[key];
|
|
2283
2281
|
if (isRecord(childSchema)) {
|
|
2284
|
-
validateNode(childSchema, child, `${
|
|
2282
|
+
validateNode(childSchema, child, `${path6}.${key}`, root, errors, depth + 1);
|
|
2285
2283
|
} else if (schema.additionalProperties === false) {
|
|
2286
|
-
errors.push({ path: `${
|
|
2284
|
+
errors.push({ path: `${path6}.${key}`, code: "additional_property", message: "Property is not declared by the action schema." });
|
|
2287
2285
|
} else if (isRecord(schema.additionalProperties)) {
|
|
2288
|
-
validateNode(schema.additionalProperties, child, `${
|
|
2286
|
+
validateNode(schema.additionalProperties, child, `${path6}.${key}`, root, errors, depth + 1);
|
|
2289
2287
|
}
|
|
2290
2288
|
}
|
|
2291
2289
|
}
|
|
2292
|
-
function validateArray(schema, value,
|
|
2290
|
+
function validateArray(schema, value, path6, root, errors, depth) {
|
|
2293
2291
|
if (typeof schema.minItems === "number" && value.length < schema.minItems) {
|
|
2294
|
-
errors.push({ path:
|
|
2292
|
+
errors.push({ path: path6, code: "min_items", message: `Expected at least ${schema.minItems} items.` });
|
|
2295
2293
|
}
|
|
2296
2294
|
if (typeof schema.maxItems === "number" && value.length > schema.maxItems) {
|
|
2297
|
-
errors.push({ path:
|
|
2295
|
+
errors.push({ path: path6, code: "max_items", message: `Expected at most ${schema.maxItems} items.` });
|
|
2298
2296
|
}
|
|
2299
2297
|
if (schema.uniqueItems === true) {
|
|
2300
2298
|
const unique2 = new Set(value.map((item) => JSON.stringify(item)));
|
|
2301
|
-
if (unique2.size !== value.length) errors.push({ path:
|
|
2299
|
+
if (unique2.size !== value.length) errors.push({ path: path6, code: "unique_items", message: "Expected every item to be unique." });
|
|
2302
2300
|
}
|
|
2303
2301
|
if (isRecord(schema.items)) {
|
|
2304
|
-
value.forEach((item, index) => validateNode(schema.items, item, `${
|
|
2302
|
+
value.forEach((item, index) => validateNode(schema.items, item, `${path6}[${index}]`, root, errors, depth + 1));
|
|
2305
2303
|
}
|
|
2306
2304
|
}
|
|
2307
|
-
function validateString(schema, value,
|
|
2305
|
+
function validateString(schema, value, path6, errors) {
|
|
2308
2306
|
if (typeof schema.minLength === "number" && value.length < schema.minLength) {
|
|
2309
|
-
errors.push({ path:
|
|
2307
|
+
errors.push({ path: path6, code: "min_length", message: `Expected at least ${schema.minLength} characters.` });
|
|
2310
2308
|
}
|
|
2311
2309
|
if (typeof schema.maxLength === "number" && value.length > schema.maxLength) {
|
|
2312
|
-
errors.push({ path:
|
|
2310
|
+
errors.push({ path: path6, code: "max_length", message: `Expected at most ${schema.maxLength} characters.` });
|
|
2313
2311
|
}
|
|
2314
2312
|
if (typeof schema.pattern === "string") {
|
|
2315
2313
|
try {
|
|
2316
|
-
if (!new RegExp(schema.pattern).test(value)) errors.push({ path:
|
|
2314
|
+
if (!new RegExp(schema.pattern).test(value)) errors.push({ path: path6, code: "pattern", message: "Value does not match the required pattern." });
|
|
2317
2315
|
} catch {
|
|
2318
|
-
errors.push({ path:
|
|
2316
|
+
errors.push({ path: path6, code: "invalid_schema_pattern", message: "Action schema contains an invalid pattern." });
|
|
2319
2317
|
}
|
|
2320
2318
|
}
|
|
2321
|
-
if (schema.format === "uri" && !isUrl(value)) errors.push({ path:
|
|
2322
|
-
if (schema.format === "email" && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value)) errors.push({ path:
|
|
2323
|
-
if (schema.format === "date-time" && !isDateTime(value)) errors.push({ path:
|
|
2319
|
+
if (schema.format === "uri" && !isUrl(value)) errors.push({ path: path6, code: "format_uri", message: "Expected an absolute URL." });
|
|
2320
|
+
if (schema.format === "email" && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value)) errors.push({ path: path6, code: "format_email", message: "Expected an email address." });
|
|
2321
|
+
if (schema.format === "date-time" && !isDateTime(value)) errors.push({ path: path6, code: "format_date_time", message: "Expected an ISO-8601 date-time." });
|
|
2324
2322
|
}
|
|
2325
|
-
function validateNumber(schema, value,
|
|
2326
|
-
if (typeof schema.minimum === "number" && value < schema.minimum) errors.push({ path:
|
|
2327
|
-
if (typeof schema.maximum === "number" && value > schema.maximum) errors.push({ path:
|
|
2323
|
+
function validateNumber(schema, value, path6, errors) {
|
|
2324
|
+
if (typeof schema.minimum === "number" && value < schema.minimum) errors.push({ path: path6, code: "minimum", message: `Expected a value of at least ${schema.minimum}.` });
|
|
2325
|
+
if (typeof schema.maximum === "number" && value > schema.maximum) errors.push({ path: path6, code: "maximum", message: `Expected a value of at most ${schema.maximum}.` });
|
|
2328
2326
|
const exclusiveMinimum = typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : schema.exclusiveMinimum === true && typeof schema.minimum === "number" ? schema.minimum : null;
|
|
2329
2327
|
const exclusiveMaximum = typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : schema.exclusiveMaximum === true && typeof schema.maximum === "number" ? schema.maximum : null;
|
|
2330
2328
|
if (exclusiveMinimum !== null && value <= exclusiveMinimum) {
|
|
2331
|
-
errors.push({ path:
|
|
2329
|
+
errors.push({ path: path6, code: "exclusive_minimum", message: `Expected a value greater than ${exclusiveMinimum}.` });
|
|
2332
2330
|
}
|
|
2333
2331
|
if (exclusiveMaximum !== null && value >= exclusiveMaximum) {
|
|
2334
|
-
errors.push({ path:
|
|
2332
|
+
errors.push({ path: path6, code: "exclusive_maximum", message: `Expected a value less than ${exclusiveMaximum}.` });
|
|
2335
2333
|
}
|
|
2336
2334
|
if (typeof schema.multipleOf === "number" && !isMultipleOf(value, schema.multipleOf)) {
|
|
2337
|
-
errors.push({ path:
|
|
2335
|
+
errors.push({ path: path6, code: "multiple_of", message: `Expected a multiple of ${schema.multipleOf}.` });
|
|
2338
2336
|
}
|
|
2339
2337
|
}
|
|
2340
2338
|
function candidateMatches(candidate, value, root, depth) {
|
|
@@ -3227,8 +3225,8 @@ function normalizeActionCatalogEntry(item) {
|
|
|
3227
3225
|
const name = pickActionString(a.name);
|
|
3228
3226
|
const appId = pickActionString(a.app_id) ?? pickActionString(a.appId);
|
|
3229
3227
|
const method = pickActionString(a.method)?.toUpperCase();
|
|
3230
|
-
const
|
|
3231
|
-
if (!actionId || !name || !appId || !method || !
|
|
3228
|
+
const path6 = pickActionString(a.path);
|
|
3229
|
+
if (!actionId || !name || !appId || !method || !path6 || !isHttpMethod(method)) {
|
|
3232
3230
|
return null;
|
|
3233
3231
|
}
|
|
3234
3232
|
return {
|
|
@@ -3243,7 +3241,7 @@ function normalizeActionCatalogEntry(item) {
|
|
|
3243
3241
|
appSlug: pickActionString(a.app_slug) ?? pickActionString(a.appSlug) ?? null,
|
|
3244
3242
|
appName: pickActionString(a.app_name) ?? pickActionString(a.appName) ?? null,
|
|
3245
3243
|
method,
|
|
3246
|
-
path:
|
|
3244
|
+
path: path6,
|
|
3247
3245
|
permissionName: pickActionString(a.permission_name) ?? pickActionString(a.permissionName) ?? "",
|
|
3248
3246
|
requiredPermission: pickActionString(a.required_permission) ?? pickActionString(a.requiredPermission) ?? "",
|
|
3249
3247
|
authorizationMode: pickAuthorizationMode(a.authorization_mode ?? a.authorizationMode),
|
|
@@ -3279,7 +3277,7 @@ function stringArray(value) {
|
|
|
3279
3277
|
}
|
|
3280
3278
|
function mapActionInput(action, input3) {
|
|
3281
3279
|
const remaining = { ...input3 || {} };
|
|
3282
|
-
const
|
|
3280
|
+
const path6 = action.path.replace(/\{([a-zA-Z0-9_]+)\}/g, (_match, key) => {
|
|
3283
3281
|
const value = remaining[key];
|
|
3284
3282
|
if (value === void 0 || value === null || value === "") {
|
|
3285
3283
|
throw new TopoloSdkError(
|
|
@@ -3296,14 +3294,14 @@ function mapActionInput(action, input3) {
|
|
|
3296
3294
|
delete remaining.body;
|
|
3297
3295
|
if (action.method === "GET") {
|
|
3298
3296
|
return {
|
|
3299
|
-
path:
|
|
3297
|
+
path: path6,
|
|
3300
3298
|
query: objectToQuery({ ...remaining, ...explicitQuery })
|
|
3301
3299
|
};
|
|
3302
3300
|
}
|
|
3303
3301
|
const query = objectToQuery(explicitQuery);
|
|
3304
3302
|
const body = explicitBody !== void 0 ? explicitBody : remaining;
|
|
3305
3303
|
return {
|
|
3306
|
-
path:
|
|
3304
|
+
path: path6,
|
|
3307
3305
|
...query ? { query } : {},
|
|
3308
3306
|
body
|
|
3309
3307
|
};
|
|
@@ -3473,8 +3471,8 @@ var TopoloOAuth = class {
|
|
|
3473
3471
|
if (params.clientSecret) body.set("client_secret", params.clientSecret);
|
|
3474
3472
|
return this.#postForm("/api/developer-oauth/token", body);
|
|
3475
3473
|
}
|
|
3476
|
-
async #postForm(
|
|
3477
|
-
const url = new URL(
|
|
3474
|
+
async #postForm(path6, body) {
|
|
3475
|
+
const url = new URL(path6, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
|
|
3478
3476
|
const res = await this.fetchImpl(url.toString(), {
|
|
3479
3477
|
method: "POST",
|
|
3480
3478
|
headers: {
|
|
@@ -3490,7 +3488,7 @@ var TopoloOAuth = class {
|
|
|
3490
3488
|
const err = parsed;
|
|
3491
3489
|
throw new TopoloAuthError(err.error_description || err.error, err.error);
|
|
3492
3490
|
}
|
|
3493
|
-
throw new TopoloHttpError("auth",
|
|
3491
|
+
throw new TopoloHttpError("auth", path6, res.status, parsed);
|
|
3494
3492
|
}
|
|
3495
3493
|
return parsed;
|
|
3496
3494
|
}
|
|
@@ -3528,13 +3526,13 @@ var TopoloHttpError2 = class extends TopoloSdkError2 {
|
|
|
3528
3526
|
body;
|
|
3529
3527
|
service;
|
|
3530
3528
|
path;
|
|
3531
|
-
constructor(service,
|
|
3532
|
-
super("http_error", message ?? `HTTP ${status} from ${service}${
|
|
3529
|
+
constructor(service, path6, status, body, message) {
|
|
3530
|
+
super("http_error", message ?? `HTTP ${status} from ${service}${path6}`);
|
|
3533
3531
|
this.name = "TopoloHttpError";
|
|
3534
3532
|
this.status = status;
|
|
3535
3533
|
this.body = body;
|
|
3536
3534
|
this.service = service;
|
|
3537
|
-
this.path =
|
|
3535
|
+
this.path = path6;
|
|
3538
3536
|
}
|
|
3539
3537
|
};
|
|
3540
3538
|
var APP_API_URLS2 = {
|
|
@@ -3665,8 +3663,8 @@ var TopoloOAuth2 = class {
|
|
|
3665
3663
|
if (params.clientSecret) body.set("client_secret", params.clientSecret);
|
|
3666
3664
|
return this.#postForm("/api/developer-oauth/token", body);
|
|
3667
3665
|
}
|
|
3668
|
-
async #postForm(
|
|
3669
|
-
const url = new URL(
|
|
3666
|
+
async #postForm(path6, body) {
|
|
3667
|
+
const url = new URL(path6, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
|
|
3670
3668
|
const res = await this.fetchImpl(url.toString(), {
|
|
3671
3669
|
method: "POST",
|
|
3672
3670
|
headers: {
|
|
@@ -3682,7 +3680,7 @@ var TopoloOAuth2 = class {
|
|
|
3682
3680
|
const err = parsed;
|
|
3683
3681
|
throw new TopoloAuthError2(err.error_description || err.error, err.error);
|
|
3684
3682
|
}
|
|
3685
|
-
throw new TopoloHttpError2("auth",
|
|
3683
|
+
throw new TopoloHttpError2("auth", path6, res.status, parsed);
|
|
3686
3684
|
}
|
|
3687
3685
|
return parsed;
|
|
3688
3686
|
}
|
|
@@ -3712,22 +3710,22 @@ function configPath() {
|
|
|
3712
3710
|
return join(base, "topolo", "config.json");
|
|
3713
3711
|
}
|
|
3714
3712
|
async function readConfig() {
|
|
3715
|
-
const
|
|
3716
|
-
if (!existsSync(
|
|
3713
|
+
const path6 = configPath();
|
|
3714
|
+
if (!existsSync(path6)) return {};
|
|
3717
3715
|
try {
|
|
3718
|
-
const raw = await readFile(
|
|
3716
|
+
const raw = await readFile(path6, "utf8");
|
|
3719
3717
|
return JSON.parse(raw);
|
|
3720
3718
|
} catch {
|
|
3721
3719
|
return {};
|
|
3722
3720
|
}
|
|
3723
3721
|
}
|
|
3724
3722
|
async function writeConfig(config) {
|
|
3725
|
-
const
|
|
3726
|
-
await mkdir(dirname(
|
|
3727
|
-
await writeFile(
|
|
3723
|
+
const path6 = configPath();
|
|
3724
|
+
await mkdir(dirname(path6), { recursive: true });
|
|
3725
|
+
await writeFile(path6, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
3728
3726
|
if (platform() !== "win32") {
|
|
3729
3727
|
try {
|
|
3730
|
-
await chmod(
|
|
3728
|
+
await chmod(path6, 384);
|
|
3731
3729
|
} catch {
|
|
3732
3730
|
}
|
|
3733
3731
|
}
|
|
@@ -4047,9 +4045,9 @@ function readCliVersion() {
|
|
|
4047
4045
|
join2(here, "package.json"),
|
|
4048
4046
|
join2(here, "..", "package.json")
|
|
4049
4047
|
];
|
|
4050
|
-
for (const
|
|
4048
|
+
for (const path6 of candidates) {
|
|
4051
4049
|
try {
|
|
4052
|
-
const pkg = JSON.parse(readFileSync(
|
|
4050
|
+
const pkg = JSON.parse(readFileSync(path6, "utf8"));
|
|
4053
4051
|
if (typeof pkg.version === "string" && pkg.version) return pkg.version;
|
|
4054
4052
|
} catch {
|
|
4055
4053
|
}
|
|
@@ -4408,7 +4406,7 @@ function registerApiCommand(program2) {
|
|
|
4408
4406
|
"-q, --query <kv...>",
|
|
4409
4407
|
"Query params as key=value (repeatable), e.g. --query source=cli page=2"
|
|
4410
4408
|
).option("--resource-type <type>", "Cataloged app-local workspace resource alias for resource-scoped API keys").option("--resource-id <id>", "Workspace resource id for resource-scoped API keys").option("--confirm", "Acknowledge a mutating request").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(
|
|
4411
|
-
async (service, method,
|
|
4409
|
+
async (service, method, path6, opts) => {
|
|
4412
4410
|
const mode = resolveOutputMode(opts.json);
|
|
4413
4411
|
try {
|
|
4414
4412
|
const upperMethod = method.toUpperCase();
|
|
@@ -4447,7 +4445,7 @@ function registerApiCommand(program2) {
|
|
|
4447
4445
|
const result = await topolo.client.request({
|
|
4448
4446
|
service,
|
|
4449
4447
|
method: httpMethod,
|
|
4450
|
-
path:
|
|
4448
|
+
path: path6,
|
|
4451
4449
|
body,
|
|
4452
4450
|
...query ? { query } : {},
|
|
4453
4451
|
...resource ? { resource } : {},
|
|
@@ -4611,13 +4609,13 @@ async function requiredClient() {
|
|
|
4611
4609
|
function parseJsonObject(value) {
|
|
4612
4610
|
let source = value;
|
|
4613
4611
|
if (value.startsWith("@")) {
|
|
4614
|
-
const
|
|
4615
|
-
if (!
|
|
4612
|
+
const path6 = value.slice(1);
|
|
4613
|
+
if (!path6) throw new Error("--data @file path must not be empty");
|
|
4616
4614
|
try {
|
|
4617
|
-
source = readFileSync2(
|
|
4615
|
+
source = readFileSync2(path6, "utf8");
|
|
4618
4616
|
} catch (err) {
|
|
4619
4617
|
const detail = err instanceof Error ? err.message : String(err);
|
|
4620
|
-
throw new Error(`Could not read --data file ${
|
|
4618
|
+
throw new Error(`Could not read --data file ${path6}: ${detail}`);
|
|
4621
4619
|
}
|
|
4622
4620
|
}
|
|
4623
4621
|
let parsed;
|
|
@@ -4691,8 +4689,8 @@ function formatActionPlan(plan) {
|
|
|
4691
4689
|
|
|
4692
4690
|
// src/commands/apps.ts
|
|
4693
4691
|
import { execSync } from "child_process";
|
|
4694
|
-
import { existsSync as
|
|
4695
|
-
import
|
|
4692
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
4693
|
+
import path4 from "path";
|
|
4696
4694
|
|
|
4697
4695
|
// src/commands/app-audit-local.ts
|
|
4698
4696
|
import {
|
|
@@ -5794,14 +5792,14 @@ import { constants } from "fs";
|
|
|
5794
5792
|
import { access } from "fs/promises";
|
|
5795
5793
|
import { relative, resolve } from "path";
|
|
5796
5794
|
function normalizeHomePath(value) {
|
|
5797
|
-
const
|
|
5798
|
-
if (!
|
|
5795
|
+
const path6 = String(value || "/dashboard").trim();
|
|
5796
|
+
if (!path6.startsWith("/") || path6.startsWith("//") || /[?#]/.test(path6)) {
|
|
5799
5797
|
throw new Error("Invalid home path. Use an absolute application path such as /dashboard.");
|
|
5800
5798
|
}
|
|
5801
|
-
if (["/", "/login", "/auth/callback", "/getting-started"].includes(
|
|
5799
|
+
if (["/", "/login", "/auth/callback", "/getting-started"].includes(path6)) {
|
|
5802
5800
|
throw new Error("Home path must be a protected product route, not a reserved entry route.");
|
|
5803
5801
|
}
|
|
5804
|
-
return
|
|
5802
|
+
return path6.replace(/\/$/, "") || "/dashboard";
|
|
5805
5803
|
}
|
|
5806
5804
|
function normalizeHomeLabel(value) {
|
|
5807
5805
|
const label = String(value || "Dashboard").trim();
|
|
@@ -5942,9 +5940,9 @@ function toTitle(value) {
|
|
|
5942
5940
|
function json(value) {
|
|
5943
5941
|
return JSON.stringify(value, null, 2);
|
|
5944
5942
|
}
|
|
5945
|
-
async function pathExists(
|
|
5943
|
+
async function pathExists(path6) {
|
|
5946
5944
|
try {
|
|
5947
|
-
await access(
|
|
5945
|
+
await access(path6, constants.F_OK);
|
|
5948
5946
|
return true;
|
|
5949
5947
|
} catch {
|
|
5950
5948
|
return false;
|
|
@@ -6480,11 +6478,20 @@ function appIdentityTs(application) {
|
|
|
6480
6478
|
"}",
|
|
6481
6479
|
"",
|
|
6482
6480
|
"export function getBrowserAuthBaseUrl(): string {",
|
|
6483
|
-
"
|
|
6484
|
-
"
|
|
6485
|
-
"
|
|
6486
|
-
"
|
|
6487
|
-
"
|
|
6481
|
+
" // A declared URL always wins; deployed builds set VITE_TOPOLO_AUTH_URL in",
|
|
6482
|
+
" // their per-environment build commands, so the host map below only covers",
|
|
6483
|
+
" // platform domains. Anything else fails loudly \u2014 an unknown host",
|
|
6484
|
+
" // (localhost, workers.dev previews) must never silently authenticate",
|
|
6485
|
+
" // against production Auth.",
|
|
6486
|
+
" const declared = import.meta.env.VITE_TOPOLO_AUTH_URL;",
|
|
6487
|
+
" if (declared) return declared.replace(/\\/+$/, '');",
|
|
6488
|
+
" const hostname = typeof window !== 'undefined' ? window.location.hostname : '';",
|
|
6489
|
+
" if (hostname.endsWith('.stg.topolo.us')) return 'https://auth.stg.topolo.us';",
|
|
6490
|
+
" if (hostname.endsWith('.topolo.dev')) return 'https://auth.topolo.dev';",
|
|
6491
|
+
" if (hostname.endsWith('.topolo.app')) return 'https://auth.topolo.app';",
|
|
6492
|
+
" throw new Error(",
|
|
6493
|
+
" `VITE_TOPOLO_AUTH_URL must be set for host \"${hostname || 'non-browser context'}\" \u2014 refusing to default to production Auth.`,",
|
|
6494
|
+
" );",
|
|
6488
6495
|
"}",
|
|
6489
6496
|
"",
|
|
6490
6497
|
"// Canonical browser app id. index.html bakes %VITE_TOPOLO_APP_ID% into",
|
|
@@ -7219,7 +7226,12 @@ import { createAccountLinkPayload, createTopoloAuthorizationUrl, exchangeTopoloC
|
|
|
7219
7226
|
|
|
7220
7227
|
type AuthState = 'ready' | 'missing_client' | 'authorizing' | 'authenticated' | 'error';
|
|
7221
7228
|
const APP_NAME = '${escapeSingleQuoted(appName)}';
|
|
7222
|
-
|
|
7229
|
+
// Environment-aware default: hardcoding the production host would point a
|
|
7230
|
+
// staging build at prod Auth whenever the VITE_ vars were dropped.
|
|
7231
|
+
const DEFAULT_AUTH_URL = window.location.hostname.endsWith('.stg.topolo.us')
|
|
7232
|
+
? 'https://auth.stg.topolo.us'
|
|
7233
|
+
: 'https://auth.topolo.app';
|
|
7234
|
+
const AUTH_URL = import.meta.env.VITE_TOPOLO_AUTH_URL ?? import.meta.env.VITE_TOPOLO_AUTH_BASE_URL ?? DEFAULT_AUTH_URL;
|
|
7223
7235
|
const CLIENT_ID = import.meta.env.VITE_TOPOLO_CLIENT_ID || '${defaultClientId}';
|
|
7224
7236
|
const REDIRECT_URI = import.meta.env.VITE_TOPOLO_REDIRECT_URI || \`\${window.location.origin}/auth/callback\`;
|
|
7225
7237
|
const REQUESTED_SCOPES = (import.meta.env.VITE_TOPOLO_SCOPES || 'openid profile email organization:read entitlements:read').split(/[\\s,]+/).filter(Boolean);
|
|
@@ -7318,235 +7330,6 @@ function webWorkerTs(application, api) {
|
|
|
7318
7330
|
""
|
|
7319
7331
|
].join("\n");
|
|
7320
7332
|
}
|
|
7321
|
-
function platformWebWranglerToml(application, api) {
|
|
7322
|
-
const slug = toSlug(application.id);
|
|
7323
|
-
const host = application.productionUrl ? new URL(application.productionUrl).host : `${slug}.topolo.app`;
|
|
7324
|
-
return [
|
|
7325
|
-
`name = "topolo-${slug}"`,
|
|
7326
|
-
'main = "src/worker.ts"',
|
|
7327
|
-
`compatibility_date = "${CLOUDFLARE_COMPATIBILITY_DATE}"`,
|
|
7328
|
-
'compatibility_flags = ["nodejs_compat"]',
|
|
7329
|
-
`account_id = "${CLOUDFLARE_ACCOUNT_ID}"`,
|
|
7330
|
-
"workers_dev = false",
|
|
7331
|
-
"",
|
|
7332
|
-
"[assets]",
|
|
7333
|
-
'directory = "./dist"',
|
|
7334
|
-
'binding = "ASSETS"',
|
|
7335
|
-
'not_found_handling = "single-page-application"',
|
|
7336
|
-
...api ? ['run_worker_first = ["/api/*"]'] : [],
|
|
7337
|
-
"",
|
|
7338
|
-
"[observability]",
|
|
7339
|
-
"enabled = true",
|
|
7340
|
-
"",
|
|
7341
|
-
"[[routes]]",
|
|
7342
|
-
`pattern = "${host}"`,
|
|
7343
|
-
"custom_domain = true",
|
|
7344
|
-
"",
|
|
7345
|
-
"[vars]",
|
|
7346
|
-
`APP_ID = "${application.appId ?? ""}"`,
|
|
7347
|
-
`VITE_TOPOLO_APP_ID = "${application.appId ?? ""}"`,
|
|
7348
|
-
"",
|
|
7349
|
-
...api ? [
|
|
7350
|
-
"[[services]]",
|
|
7351
|
-
'binding = "API"',
|
|
7352
|
-
`service = "topolo-${slug}-api"`,
|
|
7353
|
-
""
|
|
7354
|
-
] : [],
|
|
7355
|
-
"[env.development]",
|
|
7356
|
-
`name = "topolo-${slug}-development"`,
|
|
7357
|
-
`account_id = "${DEVELOPMENT_CLOUDFLARE_ACCOUNT_ID}"`,
|
|
7358
|
-
"workers_dev = true",
|
|
7359
|
-
"",
|
|
7360
|
-
"[env.development.vars]",
|
|
7361
|
-
`APP_ID = "${application.appId ?? ""}"`,
|
|
7362
|
-
`VITE_TOPOLO_APP_ID = "${application.appId ?? ""}"`,
|
|
7363
|
-
"",
|
|
7364
|
-
"[[env.development.routes]]",
|
|
7365
|
-
`pattern = "${slug}.topolo.dev"`,
|
|
7366
|
-
"custom_domain = true",
|
|
7367
|
-
"",
|
|
7368
|
-
"[env.development.assets]",
|
|
7369
|
-
'directory = "./dist"',
|
|
7370
|
-
'binding = "ASSETS"',
|
|
7371
|
-
'not_found_handling = "single-page-application"',
|
|
7372
|
-
...api ? ['run_worker_first = ["/api/*"]'] : [],
|
|
7373
|
-
"",
|
|
7374
|
-
...api ? [
|
|
7375
|
-
"[[env.development.services]]",
|
|
7376
|
-
'binding = "API"',
|
|
7377
|
-
`service = "topolo-${slug}-api-development"`,
|
|
7378
|
-
""
|
|
7379
|
-
] : [],
|
|
7380
|
-
"[env.staging]",
|
|
7381
|
-
`name = "topolo-${slug}-staging"`,
|
|
7382
|
-
`account_id = "${STAGING_CLOUDFLARE_ACCOUNT_ID}"`,
|
|
7383
|
-
"workers_dev = true",
|
|
7384
|
-
"",
|
|
7385
|
-
"[env.staging.vars]",
|
|
7386
|
-
`APP_ID = "${application.appId ?? ""}"`,
|
|
7387
|
-
`VITE_TOPOLO_APP_ID = "${application.appId ?? ""}"`,
|
|
7388
|
-
"",
|
|
7389
|
-
"[[env.staging.routes]]",
|
|
7390
|
-
`pattern = "${slug}.stg.topolo.us"`,
|
|
7391
|
-
"custom_domain = true",
|
|
7392
|
-
"",
|
|
7393
|
-
"[env.staging.assets]",
|
|
7394
|
-
'directory = "./dist"',
|
|
7395
|
-
'binding = "ASSETS"',
|
|
7396
|
-
'not_found_handling = "single-page-application"',
|
|
7397
|
-
...api ? ['run_worker_first = ["/api/*"]'] : [],
|
|
7398
|
-
"",
|
|
7399
|
-
...api ? [
|
|
7400
|
-
"[[env.staging.services]]",
|
|
7401
|
-
'binding = "API"',
|
|
7402
|
-
`service = "topolo-${slug}-api-staging"`,
|
|
7403
|
-
""
|
|
7404
|
-
] : [],
|
|
7405
|
-
"[env.production]",
|
|
7406
|
-
`name = "topolo-${slug}-production"`,
|
|
7407
|
-
`account_id = "${CLOUDFLARE_ACCOUNT_ID}"`,
|
|
7408
|
-
"workers_dev = false",
|
|
7409
|
-
"",
|
|
7410
|
-
"[env.production.vars]",
|
|
7411
|
-
`APP_ID = "${application.appId ?? ""}"`,
|
|
7412
|
-
`VITE_TOPOLO_APP_ID = "${application.appId ?? ""}"`,
|
|
7413
|
-
"",
|
|
7414
|
-
"[[env.production.routes]]",
|
|
7415
|
-
`pattern = "${host}"`,
|
|
7416
|
-
"custom_domain = true",
|
|
7417
|
-
"",
|
|
7418
|
-
"[env.production.assets]",
|
|
7419
|
-
'directory = "./dist"',
|
|
7420
|
-
'binding = "ASSETS"',
|
|
7421
|
-
'not_found_handling = "single-page-application"',
|
|
7422
|
-
...api ? ['run_worker_first = ["/api/*"]'] : [],
|
|
7423
|
-
"",
|
|
7424
|
-
...api ? [
|
|
7425
|
-
"[[env.production.services]]",
|
|
7426
|
-
'binding = "API"',
|
|
7427
|
-
`service = "topolo-${slug}-api-production"`,
|
|
7428
|
-
""
|
|
7429
|
-
] : []
|
|
7430
|
-
].join("\n");
|
|
7431
|
-
}
|
|
7432
|
-
function platformApiWranglerToml(application, resources, profile) {
|
|
7433
|
-
const slug = toSlug(application.id);
|
|
7434
|
-
const resource = resourceConfig(application, profile);
|
|
7435
|
-
const lines = [
|
|
7436
|
-
`name = "topolo-${slug}-api"`,
|
|
7437
|
-
'main = "src/worker.ts"',
|
|
7438
|
-
`compatibility_date = "${CLOUDFLARE_COMPATIBILITY_DATE}"`,
|
|
7439
|
-
'compatibility_flags = ["nodejs_compat"]',
|
|
7440
|
-
`account_id = "${CLOUDFLARE_ACCOUNT_ID}"`,
|
|
7441
|
-
"workers_dev = true",
|
|
7442
|
-
"",
|
|
7443
|
-
"[observability]",
|
|
7444
|
-
"enabled = true",
|
|
7445
|
-
"",
|
|
7446
|
-
"[vars]",
|
|
7447
|
-
'AUTH_BASE_URL = "https://auth.topolo.app"',
|
|
7448
|
-
'NEXUS_GATEWAY_URL = "https://nexus.topolo.app"',
|
|
7449
|
-
`NEXUS_APP_ID = "${slug}"`,
|
|
7450
|
-
`APP_ID = "${application.appId ?? ""}"`,
|
|
7451
|
-
`VITE_TOPOLO_APP_ID = "${application.appId ?? ""}"`,
|
|
7452
|
-
""
|
|
7453
|
-
];
|
|
7454
|
-
appendResourceWranglerBlocks(lines, resources, resource, "production");
|
|
7455
|
-
lines.push(
|
|
7456
|
-
"[env.development]",
|
|
7457
|
-
`name = "topolo-${slug}-api-development"`,
|
|
7458
|
-
`account_id = "${DEVELOPMENT_CLOUDFLARE_ACCOUNT_ID}"`,
|
|
7459
|
-
"workers_dev = true",
|
|
7460
|
-
"",
|
|
7461
|
-
"[env.development.vars]",
|
|
7462
|
-
'AUTH_BASE_URL = "https://auth.topolo.dev"',
|
|
7463
|
-
'NEXUS_GATEWAY_URL = "https://nexus.topolo.dev"',
|
|
7464
|
-
`NEXUS_APP_ID = "${slug}"`,
|
|
7465
|
-
`APP_ID = "${application.appId ?? ""}"`,
|
|
7466
|
-
`VITE_TOPOLO_APP_ID = "${application.appId ?? ""}"`,
|
|
7467
|
-
'ENVIRONMENT = "development"',
|
|
7468
|
-
""
|
|
7469
|
-
);
|
|
7470
|
-
appendResourceWranglerBlocks(lines, resources, resource, "development");
|
|
7471
|
-
lines.push(
|
|
7472
|
-
"[env.staging]",
|
|
7473
|
-
`name = "topolo-${slug}-api-staging"`,
|
|
7474
|
-
`account_id = "${STAGING_CLOUDFLARE_ACCOUNT_ID}"`,
|
|
7475
|
-
"workers_dev = true",
|
|
7476
|
-
"",
|
|
7477
|
-
"[env.staging.vars]",
|
|
7478
|
-
'AUTH_BASE_URL = "https://auth.stg.topolo.us"',
|
|
7479
|
-
'NEXUS_GATEWAY_URL = "https://nexus.stg.topolo.us"',
|
|
7480
|
-
`NEXUS_APP_ID = "${slug}"`,
|
|
7481
|
-
`APP_ID = "${application.appId ?? ""}"`,
|
|
7482
|
-
`VITE_TOPOLO_APP_ID = "${application.appId ?? ""}"`,
|
|
7483
|
-
""
|
|
7484
|
-
);
|
|
7485
|
-
appendResourceWranglerBlocks(lines, resources, resource, "staging");
|
|
7486
|
-
lines.push(
|
|
7487
|
-
"[env.production]",
|
|
7488
|
-
`name = "topolo-${slug}-api-production"`,
|
|
7489
|
-
`account_id = "${CLOUDFLARE_ACCOUNT_ID}"`,
|
|
7490
|
-
"workers_dev = false",
|
|
7491
|
-
"",
|
|
7492
|
-
"[env.production.vars]",
|
|
7493
|
-
'AUTH_BASE_URL = "https://auth.topolo.app"',
|
|
7494
|
-
'NEXUS_GATEWAY_URL = "https://nexus.topolo.app"',
|
|
7495
|
-
`NEXUS_APP_ID = "${slug}"`,
|
|
7496
|
-
`APP_ID = "${application.appId ?? ""}"`,
|
|
7497
|
-
`VITE_TOPOLO_APP_ID = "${application.appId ?? ""}"`,
|
|
7498
|
-
""
|
|
7499
|
-
);
|
|
7500
|
-
appendResourceWranglerBlocks(lines, resources, resource, "production-env");
|
|
7501
|
-
return lines.join("\n");
|
|
7502
|
-
}
|
|
7503
|
-
function appendResourceWranglerBlocks(lines, resources, resource, environment) {
|
|
7504
|
-
const prefix = environment === "staging" ? "env.staging." : environment === "development" ? "env.development." : environment === "production-env" ? "env.production." : "";
|
|
7505
|
-
const envName = environment === "staging" ? "Staging" : environment === "development" ? "Development" : "Production";
|
|
7506
|
-
const pick = (dev, staging, prod) => environment === "development" ? dev : environment === "staging" ? staging : prod;
|
|
7507
|
-
if (resources.d1) {
|
|
7508
|
-
lines.push(
|
|
7509
|
-
`[[${prefix}d1_databases]]`,
|
|
7510
|
-
`binding = "${resource.d1Binding}"`,
|
|
7511
|
-
`database_name = "${pick(resource.d1DevelopmentName, resource.d1StagingName, resource.d1ProductionName)}"`,
|
|
7512
|
-
'database_id = "provision-after-scaffold"',
|
|
7513
|
-
'migrations_dir = "migrations"',
|
|
7514
|
-
""
|
|
7515
|
-
);
|
|
7516
|
-
}
|
|
7517
|
-
if (resources.r2) {
|
|
7518
|
-
lines.push(
|
|
7519
|
-
`[[${prefix}r2_buckets]]`,
|
|
7520
|
-
`binding = "${resource.r2Binding}"`,
|
|
7521
|
-
`bucket_name = "${pick(resource.r2DevelopmentName, resource.r2StagingName, resource.r2ProductionName)}"`,
|
|
7522
|
-
""
|
|
7523
|
-
);
|
|
7524
|
-
}
|
|
7525
|
-
if (resources.queue) {
|
|
7526
|
-
lines.push(
|
|
7527
|
-
`[[${prefix}queues.producers]]`,
|
|
7528
|
-
`binding = "${resource.queueBinding}"`,
|
|
7529
|
-
`queue = "${pick(resource.queueDevelopmentName, resource.queueStagingName, resource.queueProductionName)}"`,
|
|
7530
|
-
"",
|
|
7531
|
-
`[[${prefix}queues.consumers]]`,
|
|
7532
|
-
`queue = "${pick(resource.queueDevelopmentName, resource.queueStagingName, resource.queueProductionName)}"`,
|
|
7533
|
-
""
|
|
7534
|
-
);
|
|
7535
|
-
}
|
|
7536
|
-
if (resources.workflow) {
|
|
7537
|
-
lines.push(
|
|
7538
|
-
`[[${prefix}workflows]]`,
|
|
7539
|
-
`binding = "${resource.workflowBinding}"`,
|
|
7540
|
-
`name = "${pick(resource.workflowDevelopmentName, resource.workflowStagingName, resource.workflowProductionName)}"`,
|
|
7541
|
-
`class_name = "${resource.workflowClassName}"`,
|
|
7542
|
-
""
|
|
7543
|
-
);
|
|
7544
|
-
}
|
|
7545
|
-
if (resources.containerProcessing) {
|
|
7546
|
-
lines.push(`# ${envName} container metadata is declared in topolo.app.json and topolo.cloudcontrol.json.`);
|
|
7547
|
-
lines.push("");
|
|
7548
|
-
}
|
|
7549
|
-
}
|
|
7550
7333
|
function wranglerToml(application, target, resources, profile) {
|
|
7551
7334
|
const slug = toSlug(application.id);
|
|
7552
7335
|
const workerName = target === "platform" ? `topolo-${slug}` : application.packageName;
|
|
@@ -7766,7 +7549,10 @@ function platformWorkerTs(application, options) {
|
|
|
7766
7549
|
" // Auth verification base URL is environment-driven (staging/prod/dev),",
|
|
7767
7550
|
" // so rt.verifyAuth() targets the correct Auth service per deploy.",
|
|
7768
7551
|
" authBaseUrl: (env) => env.AUTH_BASE_URL,",
|
|
7769
|
-
"
|
|
7552
|
+
" // The runtime's /api/auth/* gateway serves the app-switcher catalog,",
|
|
7553
|
+
" // preferences, and theme for the shared app shell \u2014 it must run before",
|
|
7554
|
+
" // the app's own auth so unentitled users still get the switcher.",
|
|
7555
|
+
" authGateway: { enabled: true },",
|
|
7770
7556
|
" actionContracts: topoloActionContracts,",
|
|
7771
7557
|
"});",
|
|
7772
7558
|
"",
|
|
@@ -7919,8 +7705,13 @@ function developerWorkerTs(application) {
|
|
|
7919
7705
|
" const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : null;",
|
|
7920
7706
|
" if (!token) return { ok: false, status: 401, error: 'Bearer token required' };",
|
|
7921
7707
|
"",
|
|
7708
|
+
" // Fail loudly: defaulting to the production Auth host here would let a",
|
|
7709
|
+
" // misconfigured staging/dev worker validate tokens against prod. The",
|
|
7710
|
+
" // manifest declares AUTH_BASE_URL for every environment.",
|
|
7711
|
+
" if (!env.AUTH_BASE_URL) return { ok: false, status: 500, error: 'AUTH_BASE_URL not configured' };",
|
|
7712
|
+
"",
|
|
7922
7713
|
" try {",
|
|
7923
|
-
" const authBaseUrl = env.AUTH_BASE_URL
|
|
7714
|
+
" const authBaseUrl = env.AUTH_BASE_URL;",
|
|
7924
7715
|
" const response = await fetch(`${authBaseUrl}/validate`, {",
|
|
7925
7716
|
" method: 'POST',",
|
|
7926
7717
|
" headers: {",
|
|
@@ -7950,6 +7741,72 @@ function developerWorkerTs(application) {
|
|
|
7950
7741
|
].join("\n");
|
|
7951
7742
|
}
|
|
7952
7743
|
|
|
7744
|
+
// src/commands/app-scaffold-generate.ts
|
|
7745
|
+
import { existsSync as existsSync3, mkdirSync, mkdtempSync, readFileSync as readFileSync4, rmSync, writeFileSync } from "fs";
|
|
7746
|
+
import { spawnSync } from "child_process";
|
|
7747
|
+
import { tmpdir } from "os";
|
|
7748
|
+
import path2 from "path";
|
|
7749
|
+
function resolveGeneratorPath() {
|
|
7750
|
+
const candidates = [
|
|
7751
|
+
// Published CLI: vendored alongside dist/index.js at build time.
|
|
7752
|
+
new URL("./generate-wrangler.mjs", import.meta.url),
|
|
7753
|
+
// In-repo (tests, `pnpm dev`): the platform's canonical copy, which is the
|
|
7754
|
+
// very file the vendor step copies.
|
|
7755
|
+
new URL("../../../../tooling/generate-wrangler.mjs", import.meta.url)
|
|
7756
|
+
];
|
|
7757
|
+
for (const candidate of candidates) {
|
|
7758
|
+
const p = decodeURIComponent(candidate.pathname);
|
|
7759
|
+
if (existsSync3(p)) return p;
|
|
7760
|
+
}
|
|
7761
|
+
throw new Error(
|
|
7762
|
+
`topolo: could not locate generate-wrangler.mjs. A published CLI vendors it into dist/ at build time; in-repo it is read from topolo-platform/tooling/. Looked in:
|
|
7763
|
+
${candidates.map((c) => ` ${decodeURIComponent(c.pathname)}`).join("\n")}`
|
|
7764
|
+
);
|
|
7765
|
+
}
|
|
7766
|
+
var GENERATED = {
|
|
7767
|
+
purpose: "GENERATED from topolo.app.json by topolo-platform/tooling/generate-wrangler.mjs \u2014 never hand-edit.",
|
|
7768
|
+
requiredSignals: [
|
|
7769
|
+
"byte-identical to what generate-wrangler emits for this manifest",
|
|
7770
|
+
"every change is made in topolo.app.json, never here"
|
|
7771
|
+
]
|
|
7772
|
+
};
|
|
7773
|
+
function deriveGeneratedConfigs(files, base) {
|
|
7774
|
+
const generator = resolveGeneratorPath();
|
|
7775
|
+
const root = mkdtempSync(path2.join(tmpdir(), "topolo-scaffold-generate-"));
|
|
7776
|
+
try {
|
|
7777
|
+
const prefix = `${base}/`;
|
|
7778
|
+
for (const file of files) {
|
|
7779
|
+
if (!file.path.startsWith(prefix)) continue;
|
|
7780
|
+
const abs = path2.join(root, file.path.slice(prefix.length));
|
|
7781
|
+
mkdirSync(path2.dirname(abs), { recursive: true });
|
|
7782
|
+
writeFileSync(abs, file.content, "utf8");
|
|
7783
|
+
}
|
|
7784
|
+
const result = spawnSync(
|
|
7785
|
+
process.execPath,
|
|
7786
|
+
[generator, `--manifest=${path2.join(root, "topolo.app.json")}`],
|
|
7787
|
+
{ cwd: root, encoding: "utf8" }
|
|
7788
|
+
);
|
|
7789
|
+
if (result.status !== 0) {
|
|
7790
|
+
throw new Error(
|
|
7791
|
+
`topolo: generate-wrangler failed for the scaffolded manifest (exit ${result.status}).
|
|
7792
|
+
${result.stdout ?? ""}${result.stderr ?? ""}`
|
|
7793
|
+
);
|
|
7794
|
+
}
|
|
7795
|
+
const written = [...(result.stdout ?? "").matchAll(/^\[generate\] wrote (.+)$/gm)].map((m) => (m[1] ?? "").trim()).filter(Boolean);
|
|
7796
|
+
if (!written.length) {
|
|
7797
|
+
throw new Error(`topolo: generate-wrangler wrote no files.
|
|
7798
|
+
${result.stdout ?? ""}`);
|
|
7799
|
+
}
|
|
7800
|
+
return written.map((rel) => ({
|
|
7801
|
+
path: `${base}/${rel}`,
|
|
7802
|
+
...GENERATED,
|
|
7803
|
+
content: readFileSync4(path2.join(root, rel), "utf8")
|
|
7804
|
+
}));
|
|
7805
|
+
} finally {
|
|
7806
|
+
rmSync(root, { recursive: true, force: true });
|
|
7807
|
+
}
|
|
7808
|
+
}
|
|
7809
|
+
|
|
7953
7810
|
// src/commands/app-scaffold-workflows.ts
|
|
7954
7811
|
function scaffoldCommands(options) {
|
|
7955
7812
|
const scaffoldId = toSlug(options.id);
|
|
@@ -8113,6 +7970,10 @@ function topoloAppManifestJson(application, options) {
|
|
|
8113
7970
|
bindings.d1 = [
|
|
8114
7971
|
{
|
|
8115
7972
|
binding: resource.d1Binding,
|
|
7973
|
+
development: {
|
|
7974
|
+
databaseName: resource.d1DevelopmentName,
|
|
7975
|
+
databaseId: "provision-after-scaffold"
|
|
7976
|
+
},
|
|
8116
7977
|
staging: {
|
|
8117
7978
|
databaseName: resource.d1StagingName,
|
|
8118
7979
|
databaseId: "provision-after-scaffold"
|
|
@@ -8129,26 +7990,29 @@ function topoloAppManifestJson(application, options) {
|
|
|
8129
7990
|
bindings.r2 = [
|
|
8130
7991
|
{
|
|
8131
7992
|
binding: resource.r2Binding,
|
|
7993
|
+
development: { bucketName: resource.r2DevelopmentName },
|
|
8132
7994
|
staging: { bucketName: resource.r2StagingName },
|
|
8133
7995
|
production: { bucketName: resource.r2ProductionName }
|
|
8134
7996
|
}
|
|
8135
7997
|
];
|
|
8136
7998
|
}
|
|
8137
7999
|
if (options.resources.queue) {
|
|
8138
|
-
|
|
8139
|
-
{
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
}
|
|
8145
|
-
|
|
8000
|
+
const queue = {
|
|
8001
|
+
development: { queue: resource.queueDevelopmentName },
|
|
8002
|
+
staging: { queue: resource.queueStagingName },
|
|
8003
|
+
production: { queue: resource.queueProductionName }
|
|
8004
|
+
};
|
|
8005
|
+
bindings.queues = {
|
|
8006
|
+
producers: [{ binding: resource.queueBinding, ...queue }],
|
|
8007
|
+
consumers: [{ ...queue }]
|
|
8008
|
+
};
|
|
8146
8009
|
}
|
|
8147
8010
|
if (options.resources.workflow) {
|
|
8148
8011
|
bindings.workflows = [
|
|
8149
8012
|
{
|
|
8150
8013
|
binding: resource.workflowBinding,
|
|
8151
8014
|
className: resource.workflowClassName,
|
|
8015
|
+
development: { name: resource.workflowDevelopmentName },
|
|
8152
8016
|
staging: { name: resource.workflowStagingName },
|
|
8153
8017
|
production: { name: resource.workflowProductionName }
|
|
8154
8018
|
}
|
|
@@ -8162,53 +8026,67 @@ function topoloAppManifestJson(application, options) {
|
|
|
8162
8026
|
compatibilityFlags: ["nodejs_compat"],
|
|
8163
8027
|
healthcheckPath: "/api/health",
|
|
8164
8028
|
vars: {
|
|
8029
|
+
// Every environment the manifest declares must be listed. A per-env map
|
|
8030
|
+
// that omits one leaves that env with no value, and both of these fall
|
|
8031
|
+
// back to PRODUCTION when unset (@topolo-io/nexus-client defaults
|
|
8032
|
+
// gatewayUrl to https://nexus.topolo.app) — a staging or dev worker
|
|
8033
|
+
// silently talking to prod.
|
|
8165
8034
|
AUTH_BASE_URL: {
|
|
8035
|
+
development: "https://auth.topolo.dev",
|
|
8166
8036
|
staging: "https://auth.stg.topolo.us",
|
|
8167
8037
|
production: "https://auth.topolo.app"
|
|
8168
8038
|
},
|
|
8169
8039
|
NEXUS_GATEWAY_URL: {
|
|
8040
|
+
development: "https://nexus.topolo.dev",
|
|
8170
8041
|
staging: "https://nexus.stg.topolo.us",
|
|
8171
8042
|
production: "https://nexus.topolo.app"
|
|
8172
|
-
}
|
|
8173
|
-
NEXUS_APP_ID:
|
|
8043
|
+
}
|
|
8044
|
+
// No NEXUS_APP_ID: generate-wrangler binds APP_ID (the app_id) into every
|
|
8045
|
+
// env, and worker-runtime reads env.APP_ID for Nexus usage attribution,
|
|
8046
|
+
// so a second copy of the same identity would only be a chance to drift.
|
|
8047
|
+
// It previously carried the SLUG, which reads as a working identity while
|
|
8048
|
+
// Nexus attributes by app_id.
|
|
8174
8049
|
},
|
|
8175
8050
|
secrets: ["TOPOLO_SERVICE_CLIENT_SECRET"],
|
|
8176
8051
|
...Object.keys(bindings).length ? { bindings } : {}
|
|
8177
8052
|
};
|
|
8178
8053
|
}
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8054
|
+
if (options.browser) {
|
|
8055
|
+
targets.web = {
|
|
8056
|
+
kind: "worker",
|
|
8057
|
+
dir: "apps/web",
|
|
8058
|
+
wranglerName: `topolo-${slug}`,
|
|
8059
|
+
subdomain: slug,
|
|
8060
|
+
main: "src/worker.ts",
|
|
8061
|
+
workersDev: false,
|
|
8062
|
+
assets: {
|
|
8063
|
+
directory: "./dist",
|
|
8064
|
+
binding: "ASSETS",
|
|
8065
|
+
notFoundHandling: "single-page-application",
|
|
8066
|
+
// Only meaningful when there is an API Worker to run first.
|
|
8067
|
+
...options.api ? { runWorkerFirst: ["/api/*"] } : {}
|
|
8068
|
+
},
|
|
8069
|
+
compatibilityFlags: ["nodejs_compat"],
|
|
8070
|
+
buildCommand: {
|
|
8071
|
+
staging: "cd ../.. && DEPLOY_ENVIRONMENT=staging VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us pnpm run build",
|
|
8072
|
+
production: "cd ../.. && DEPLOY_ENVIRONMENT=production VITE_TOPOLO_AUTH_URL=https://auth.topolo.app pnpm run build"
|
|
8073
|
+
},
|
|
8074
|
+
healthcheckPath: options.api ? "/api/health" : "/",
|
|
8075
|
+
...options.api ? {
|
|
8076
|
+
bindings: {
|
|
8077
|
+
services: [
|
|
8078
|
+
{
|
|
8079
|
+
binding: "API",
|
|
8080
|
+
service: {
|
|
8081
|
+
staging: `topolo-${slug}-api-staging`,
|
|
8082
|
+
production: `topolo-${slug}-api-production`
|
|
8083
|
+
}
|
|
8206
8084
|
}
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
}
|
|
8210
|
-
}
|
|
8211
|
-
}
|
|
8085
|
+
]
|
|
8086
|
+
}
|
|
8087
|
+
} : {}
|
|
8088
|
+
};
|
|
8089
|
+
}
|
|
8212
8090
|
const actions = options.api ? baselineApiActions() : [];
|
|
8213
8091
|
return `${json({
|
|
8214
8092
|
$schema: "./../../topolo-platform/tooling/app-manifest-schema.json",
|
|
@@ -8352,263 +8230,6 @@ function baselineApiActions() {
|
|
|
8352
8230
|
}
|
|
8353
8231
|
];
|
|
8354
8232
|
}
|
|
8355
|
-
function cloudcontrolJson(application, options) {
|
|
8356
|
-
const slug = toSlug(application.id);
|
|
8357
|
-
const appPath = firstPartyAppPath(application.appDir, options.audience);
|
|
8358
|
-
const host = application.productionUrl ? new URL(application.productionUrl).host : `${slug}.topolo.app`;
|
|
8359
|
-
const stagingHost = `${slug}.stg.topolo.us`;
|
|
8360
|
-
const serviceSlug = application.serviceSlug ?? `topolo-${slug}`;
|
|
8361
|
-
const resources = resourceConfig(application, options.profile);
|
|
8362
|
-
const resourceBindings = [];
|
|
8363
|
-
if (options.resources.d1) {
|
|
8364
|
-
resourceBindings.push(
|
|
8365
|
-
{
|
|
8366
|
-
resource_type: "d1",
|
|
8367
|
-
binding: resources.d1Binding,
|
|
8368
|
-
environment: "production",
|
|
8369
|
-
account_id: CLOUDFLARE_ACCOUNT_ID,
|
|
8370
|
-
resource_name: resources.d1ProductionName,
|
|
8371
|
-
resource_id: "provision-after-scaffold",
|
|
8372
|
-
migrations_dir: "migrations"
|
|
8373
|
-
},
|
|
8374
|
-
{
|
|
8375
|
-
resource_type: "d1",
|
|
8376
|
-
binding: resources.d1Binding,
|
|
8377
|
-
environment: "staging",
|
|
8378
|
-
account_id: STAGING_CLOUDFLARE_ACCOUNT_ID,
|
|
8379
|
-
resource_name: resources.d1StagingName,
|
|
8380
|
-
resource_id: "provision-after-scaffold",
|
|
8381
|
-
migrations_dir: "migrations"
|
|
8382
|
-
},
|
|
8383
|
-
{
|
|
8384
|
-
resource_type: "d1",
|
|
8385
|
-
binding: resources.d1Binding,
|
|
8386
|
-
environment: "development",
|
|
8387
|
-
account_id: DEVELOPMENT_CLOUDFLARE_ACCOUNT_ID,
|
|
8388
|
-
resource_name: resources.d1DevelopmentName,
|
|
8389
|
-
resource_id: "provision-after-scaffold",
|
|
8390
|
-
migrations_dir: "migrations"
|
|
8391
|
-
}
|
|
8392
|
-
);
|
|
8393
|
-
}
|
|
8394
|
-
if (options.resources.r2) {
|
|
8395
|
-
resourceBindings.push(
|
|
8396
|
-
{
|
|
8397
|
-
resource_type: "r2",
|
|
8398
|
-
binding: resources.r2Binding,
|
|
8399
|
-
environment: "production",
|
|
8400
|
-
account_id: CLOUDFLARE_ACCOUNT_ID,
|
|
8401
|
-
resource_name: resources.r2ProductionName
|
|
8402
|
-
},
|
|
8403
|
-
{
|
|
8404
|
-
resource_type: "r2",
|
|
8405
|
-
binding: resources.r2Binding,
|
|
8406
|
-
environment: "staging",
|
|
8407
|
-
account_id: STAGING_CLOUDFLARE_ACCOUNT_ID,
|
|
8408
|
-
resource_name: resources.r2StagingName
|
|
8409
|
-
}
|
|
8410
|
-
);
|
|
8411
|
-
}
|
|
8412
|
-
if (options.resources.queue) {
|
|
8413
|
-
resourceBindings.push(
|
|
8414
|
-
{
|
|
8415
|
-
resource_type: "queue",
|
|
8416
|
-
binding: resources.queueBinding,
|
|
8417
|
-
environment: "production",
|
|
8418
|
-
account_id: CLOUDFLARE_ACCOUNT_ID,
|
|
8419
|
-
resource_name: resources.queueProductionName
|
|
8420
|
-
},
|
|
8421
|
-
{
|
|
8422
|
-
resource_type: "queue",
|
|
8423
|
-
binding: resources.queueBinding,
|
|
8424
|
-
environment: "staging",
|
|
8425
|
-
account_id: STAGING_CLOUDFLARE_ACCOUNT_ID,
|
|
8426
|
-
resource_name: resources.queueStagingName
|
|
8427
|
-
}
|
|
8428
|
-
);
|
|
8429
|
-
}
|
|
8430
|
-
if (options.resources.workflow) {
|
|
8431
|
-
resourceBindings.push(
|
|
8432
|
-
{
|
|
8433
|
-
resource_type: "workflow",
|
|
8434
|
-
binding: resources.workflowBinding,
|
|
8435
|
-
environment: "production",
|
|
8436
|
-
account_id: CLOUDFLARE_ACCOUNT_ID,
|
|
8437
|
-
resource_name: resources.workflowProductionName,
|
|
8438
|
-
class_name: resources.workflowClassName
|
|
8439
|
-
},
|
|
8440
|
-
{
|
|
8441
|
-
resource_type: "workflow",
|
|
8442
|
-
binding: resources.workflowBinding,
|
|
8443
|
-
environment: "staging",
|
|
8444
|
-
account_id: STAGING_CLOUDFLARE_ACCOUNT_ID,
|
|
8445
|
-
resource_name: resources.workflowStagingName,
|
|
8446
|
-
class_name: resources.workflowClassName
|
|
8447
|
-
}
|
|
8448
|
-
);
|
|
8449
|
-
}
|
|
8450
|
-
if (options.resources.containerProcessing) {
|
|
8451
|
-
resourceBindings.push(
|
|
8452
|
-
{
|
|
8453
|
-
resource_type: "container",
|
|
8454
|
-
environment: "production",
|
|
8455
|
-
account_id: CLOUDFLARE_ACCOUNT_ID,
|
|
8456
|
-
resource_name: resources.containerProductionName,
|
|
8457
|
-
purpose: "processing"
|
|
8458
|
-
},
|
|
8459
|
-
{
|
|
8460
|
-
resource_type: "container",
|
|
8461
|
-
environment: "staging",
|
|
8462
|
-
account_id: STAGING_CLOUDFLARE_ACCOUNT_ID,
|
|
8463
|
-
resource_name: resources.containerStagingName,
|
|
8464
|
-
purpose: "processing"
|
|
8465
|
-
}
|
|
8466
|
-
);
|
|
8467
|
-
}
|
|
8468
|
-
const deployTargets = options.api ? [
|
|
8469
|
-
{
|
|
8470
|
-
kind: "worker",
|
|
8471
|
-
environment: "production",
|
|
8472
|
-
target_name: `topolo-${slug}-api-production`,
|
|
8473
|
-
cloudflare_account_id: CLOUDFLARE_ACCOUNT_ID,
|
|
8474
|
-
config_path: "apps/api/wrangler.toml",
|
|
8475
|
-
workdir: "apps/api",
|
|
8476
|
-
build_command: null,
|
|
8477
|
-
output_dir: null,
|
|
8478
|
-
production_branch: "main",
|
|
8479
|
-
healthcheck_path: "/api/health",
|
|
8480
|
-
domains: [],
|
|
8481
|
-
routes: [],
|
|
8482
|
-
service_slug: serviceSlug
|
|
8483
|
-
},
|
|
8484
|
-
{
|
|
8485
|
-
kind: "worker",
|
|
8486
|
-
environment: "staging",
|
|
8487
|
-
target_name: `topolo-${slug}-staging`,
|
|
8488
|
-
cloudflare_account_id: STAGING_CLOUDFLARE_ACCOUNT_ID,
|
|
8489
|
-
config_path: "apps/web/wrangler.toml",
|
|
8490
|
-
workdir: "apps/web",
|
|
8491
|
-
build_command: "cd ../.. && DEPLOY_ENVIRONMENT=staging VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us pnpm run build",
|
|
8492
|
-
output_dir: null,
|
|
8493
|
-
production_branch: "staging",
|
|
8494
|
-
healthcheck_path: "/api/health",
|
|
8495
|
-
domains: [stagingHost],
|
|
8496
|
-
routes: [stagingHost],
|
|
8497
|
-
api_url: `https://${stagingHost}`,
|
|
8498
|
-
service_slug: serviceSlug,
|
|
8499
|
-
sdk_app_id: application.id,
|
|
8500
|
-
agent_callable: true
|
|
8501
|
-
},
|
|
8502
|
-
{
|
|
8503
|
-
kind: "worker",
|
|
8504
|
-
environment: "staging",
|
|
8505
|
-
target_name: `topolo-${slug}-api-staging`,
|
|
8506
|
-
cloudflare_account_id: STAGING_CLOUDFLARE_ACCOUNT_ID,
|
|
8507
|
-
config_path: "apps/api/wrangler.toml",
|
|
8508
|
-
workdir: "apps/api",
|
|
8509
|
-
build_command: null,
|
|
8510
|
-
output_dir: null,
|
|
8511
|
-
production_branch: "staging",
|
|
8512
|
-
healthcheck_path: "/api/health",
|
|
8513
|
-
domains: [],
|
|
8514
|
-
routes: [],
|
|
8515
|
-
service_slug: serviceSlug
|
|
8516
|
-
},
|
|
8517
|
-
{
|
|
8518
|
-
kind: "worker",
|
|
8519
|
-
environment: "production",
|
|
8520
|
-
target_name: `topolo-${slug}-production`,
|
|
8521
|
-
cloudflare_account_id: CLOUDFLARE_ACCOUNT_ID,
|
|
8522
|
-
config_path: "apps/web/wrangler.toml",
|
|
8523
|
-
workdir: "apps/web",
|
|
8524
|
-
build_command: "cd ../.. && DEPLOY_ENVIRONMENT=production VITE_TOPOLO_AUTH_URL=https://auth.topolo.app pnpm run build",
|
|
8525
|
-
output_dir: null,
|
|
8526
|
-
production_branch: "main",
|
|
8527
|
-
healthcheck_path: "/api/health",
|
|
8528
|
-
domains: [host],
|
|
8529
|
-
routes: [host],
|
|
8530
|
-
api_url: application.productionUrl,
|
|
8531
|
-
service_slug: serviceSlug,
|
|
8532
|
-
sdk_app_id: application.id,
|
|
8533
|
-
agent_callable: true
|
|
8534
|
-
}
|
|
8535
|
-
] : [
|
|
8536
|
-
{
|
|
8537
|
-
kind: "worker",
|
|
8538
|
-
environment: "production",
|
|
8539
|
-
target_name: `topolo-${slug}`,
|
|
8540
|
-
cloudflare_account_id: CLOUDFLARE_ACCOUNT_ID,
|
|
8541
|
-
config_path: "wrangler.toml",
|
|
8542
|
-
build_command: "npm run build",
|
|
8543
|
-
output_dir: "dist",
|
|
8544
|
-
production_branch: "main",
|
|
8545
|
-
healthcheck_path: "/",
|
|
8546
|
-
domains: [host],
|
|
8547
|
-
routes: [host],
|
|
8548
|
-
api_url: application.productionUrl,
|
|
8549
|
-
agent_callable: false
|
|
8550
|
-
}
|
|
8551
|
-
];
|
|
8552
|
-
return `${json({
|
|
8553
|
-
name: `topolo-${slug}`,
|
|
8554
|
-
description: `Canonical documentation for ${toTitle(application.name)} lives in system-apps/TopoloDocs.`,
|
|
8555
|
-
deploy_method: "wrangler",
|
|
8556
|
-
ci_provider: "github_actions",
|
|
8557
|
-
repo_identity: {
|
|
8558
|
-
provider: "github",
|
|
8559
|
-
owner: "Topolo-io",
|
|
8560
|
-
repo: application.appDir,
|
|
8561
|
-
remote_url: `git@github.com:Topolo-io/${application.appDir}.git`,
|
|
8562
|
-
default_branch: "main"
|
|
8563
|
-
},
|
|
8564
|
-
environments: application.productionUrl ? [
|
|
8565
|
-
{
|
|
8566
|
-
name: "production",
|
|
8567
|
-
url: application.productionUrl,
|
|
8568
|
-
branch: "main",
|
|
8569
|
-
status: "unknown"
|
|
8570
|
-
},
|
|
8571
|
-
{
|
|
8572
|
-
name: "staging",
|
|
8573
|
-
url: `https://${stagingHost}`,
|
|
8574
|
-
branch: "main",
|
|
8575
|
-
status: "unknown"
|
|
8576
|
-
}
|
|
8577
|
-
] : [],
|
|
8578
|
-
deploy_targets: deployTargets,
|
|
8579
|
-
resource_bindings: resourceBindings,
|
|
8580
|
-
ci_workflows: [
|
|
8581
|
-
{
|
|
8582
|
-
provider: "github_actions",
|
|
8583
|
-
workflow_name: "topolo-conformance.yml",
|
|
8584
|
-
path: ".github/workflows/topolo-conformance.yml",
|
|
8585
|
-
branch: null,
|
|
8586
|
-
required: true
|
|
8587
|
-
}
|
|
8588
|
-
],
|
|
8589
|
-
ci_targets: [
|
|
8590
|
-
{
|
|
8591
|
-
name: "typecheck",
|
|
8592
|
-
command: "pnpm run typecheck",
|
|
8593
|
-
required: true
|
|
8594
|
-
},
|
|
8595
|
-
{
|
|
8596
|
-
name: "test",
|
|
8597
|
-
command: "pnpm run test",
|
|
8598
|
-
required: true
|
|
8599
|
-
},
|
|
8600
|
-
{
|
|
8601
|
-
name: "build",
|
|
8602
|
-
command: "pnpm run build",
|
|
8603
|
-
required: true
|
|
8604
|
-
}
|
|
8605
|
-
],
|
|
8606
|
-
tags: ["application", "cloudflare", "typescript", ...hasScaffoldResources(options.resources) ? ["resource-backed"] : []],
|
|
8607
|
-
source_paths: [appPath],
|
|
8608
|
-
cloudflare_account_id: CLOUDFLARE_ACCOUNT_ID
|
|
8609
|
-
})}
|
|
8610
|
-
`;
|
|
8611
|
-
}
|
|
8612
8233
|
function systemJson(application, options) {
|
|
8613
8234
|
const slug = toSlug(application.id);
|
|
8614
8235
|
const suiteLabel = options.audience === "personal" ? "personal" : "platform";
|
|
@@ -9686,6 +9307,7 @@ function scaffoldFiles(options) {
|
|
|
9686
9307
|
],
|
|
9687
9308
|
content: topoloAppManifestJson(application, {
|
|
9688
9309
|
api: options.api,
|
|
9310
|
+
browser: options.browser,
|
|
9689
9311
|
resources: options.payload.resources,
|
|
9690
9312
|
profile: options.payload.profile,
|
|
9691
9313
|
payload: options.payload
|
|
@@ -9711,20 +9333,6 @@ function scaffoldFiles(options) {
|
|
|
9711
9333
|
content: topoloActionContractsDmts()
|
|
9712
9334
|
}
|
|
9713
9335
|
] : [],
|
|
9714
|
-
{
|
|
9715
|
-
path: `${base}/topolo.cloudcontrol.json`,
|
|
9716
|
-
purpose: "CloudControl source of truth for deploy targets, routes, bindings, and CI metadata.",
|
|
9717
|
-
requiredSignals: [
|
|
9718
|
-
"production URLs and Cloudflare targets are explicit",
|
|
9719
|
-
"resource bindings and required secrets are declared without secret values"
|
|
9720
|
-
],
|
|
9721
|
-
content: cloudcontrolJson(application, {
|
|
9722
|
-
api: options.api,
|
|
9723
|
-
audience: options.payload.audience,
|
|
9724
|
-
profile: options.payload.profile,
|
|
9725
|
-
resources: options.payload.resources
|
|
9726
|
-
})
|
|
9727
|
-
},
|
|
9728
9336
|
{
|
|
9729
9337
|
path: `${base}/.github/workflows/topolo-conformance.yml`,
|
|
9730
9338
|
purpose: "PR/push gate that runs the local Topolo app audit with docs evidence.",
|
|
@@ -9933,15 +9541,6 @@ export default defineConfig({
|
|
|
9933
9541
|
"only declares an API service binding for API-backed scaffolds"
|
|
9934
9542
|
],
|
|
9935
9543
|
content: webWorkerTs(application, options.api)
|
|
9936
|
-
},
|
|
9937
|
-
{
|
|
9938
|
-
path: `${browserBase}/wrangler.toml`,
|
|
9939
|
-
purpose: "Cloudflare Worker static-assets configuration for the browser surface.",
|
|
9940
|
-
requiredSignals: [
|
|
9941
|
-
"serves Vite assets through a Worker with assets",
|
|
9942
|
-
"only binds an API Worker when the scaffold includes one"
|
|
9943
|
-
],
|
|
9944
|
-
content: platformWebWranglerToml(application, options.api)
|
|
9945
9544
|
}
|
|
9946
9545
|
);
|
|
9947
9546
|
}
|
|
@@ -9949,15 +9548,15 @@ export default defineConfig({
|
|
|
9949
9548
|
}
|
|
9950
9549
|
if (options.api) {
|
|
9951
9550
|
files.push(
|
|
9952
|
-
{
|
|
9551
|
+
...target === "platform" ? [] : [{
|
|
9953
9552
|
path: `${apiBase}/wrangler.toml`,
|
|
9954
9553
|
purpose: "Cloudflare Worker runtime configuration.",
|
|
9955
9554
|
requiredSignals: [
|
|
9956
|
-
"bindings match
|
|
9555
|
+
"bindings match the declared resources",
|
|
9957
9556
|
"secrets are referenced by name only"
|
|
9958
9557
|
],
|
|
9959
|
-
content:
|
|
9960
|
-
},
|
|
9558
|
+
content: wranglerToml(application, target, options.payload.resources, options.payload.profile)
|
|
9559
|
+
}],
|
|
9961
9560
|
{
|
|
9962
9561
|
path: `${apiBase}/src/worker.ts`,
|
|
9963
9562
|
purpose: "Protected service boundary.",
|
|
@@ -10025,6 +9624,9 @@ export default defineConfig({
|
|
|
10025
9624
|
}
|
|
10026
9625
|
}
|
|
10027
9626
|
}
|
|
9627
|
+
if (target === "platform") {
|
|
9628
|
+
files.push(...deriveGeneratedConfigs(files, base));
|
|
9629
|
+
}
|
|
10028
9630
|
return files;
|
|
10029
9631
|
}
|
|
10030
9632
|
|
|
@@ -10271,7 +9873,7 @@ import { join as join3 } from "path";
|
|
|
10271
9873
|
|
|
10272
9874
|
// src/commands/app-reconcile.ts
|
|
10273
9875
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
10274
|
-
import
|
|
9876
|
+
import path3 from "path";
|
|
10275
9877
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
10276
9878
|
import { stdin as input2, stdout as output } from "process";
|
|
10277
9879
|
function record(value) {
|
|
@@ -10330,7 +9932,7 @@ function titleFromSlug(value) {
|
|
|
10330
9932
|
return value.replace(/^topolo[-_]/, "").split(/[-_]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
10331
9933
|
}
|
|
10332
9934
|
async function completeApplicationRegistrationInteractively(options) {
|
|
10333
|
-
const manifestPath =
|
|
9935
|
+
const manifestPath = path3.resolve(options.manifestPath || "topolo.app.json");
|
|
10334
9936
|
const manifest = JSON.parse(await readFile3(manifestPath, "utf8"));
|
|
10335
9937
|
const existing = record(manifest.registration) || {};
|
|
10336
9938
|
const appId = String(manifest.appId || manifest.app_id || "").trim();
|
|
@@ -10410,7 +10012,7 @@ async function completeApplicationRegistrationInteractively(options) {
|
|
|
10410
10012
|
}
|
|
10411
10013
|
}
|
|
10412
10014
|
async function loadApplicationReconcileRequest(options) {
|
|
10413
|
-
const manifestPath =
|
|
10015
|
+
const manifestPath = path3.resolve(options.manifestPath || "topolo.app.json");
|
|
10414
10016
|
const raw = await readFile3(manifestPath, "utf8");
|
|
10415
10017
|
const manifest = JSON.parse(raw);
|
|
10416
10018
|
assertRegistration(manifest.registration);
|
|
@@ -10796,8 +10398,8 @@ function applicationAuditPayload(application, options = {}) {
|
|
|
10796
10398
|
}
|
|
10797
10399
|
function recordApplicationEvidence(application, options = {}) {
|
|
10798
10400
|
const appId = resolveApplication(application).id;
|
|
10799
|
-
const root =
|
|
10800
|
-
if (!
|
|
10401
|
+
const root = path4.resolve(options.appRoot ?? process.cwd());
|
|
10402
|
+
if (!existsSync4(root)) {
|
|
10801
10403
|
throw new Error(`Application root does not exist: ${root}`);
|
|
10802
10404
|
}
|
|
10803
10405
|
let commit = options.commit;
|
|
@@ -10808,12 +10410,12 @@ function recordApplicationEvidence(application, options = {}) {
|
|
|
10808
10410
|
commit = void 0;
|
|
10809
10411
|
}
|
|
10810
10412
|
}
|
|
10811
|
-
const evidenceDir =
|
|
10812
|
-
const evidencePath =
|
|
10413
|
+
const evidenceDir = path4.join(root, ".topolo");
|
|
10414
|
+
const evidencePath = path4.join(evidenceDir, "evidence.json");
|
|
10813
10415
|
let before = null;
|
|
10814
|
-
if (
|
|
10416
|
+
if (existsSync4(evidencePath)) {
|
|
10815
10417
|
try {
|
|
10816
|
-
const raw = JSON.parse(
|
|
10418
|
+
const raw = JSON.parse(readFileSync5(evidencePath, "utf8"));
|
|
10817
10419
|
if (raw && typeof raw === "object" && raw.version === 1) {
|
|
10818
10420
|
before = raw;
|
|
10819
10421
|
}
|
|
@@ -10851,8 +10453,8 @@ function recordApplicationEvidence(application, options = {}) {
|
|
|
10851
10453
|
"record-evidence: pass at least one of --build, --typecheck, --test, --docs-validate, --smoke <url>"
|
|
10852
10454
|
);
|
|
10853
10455
|
}
|
|
10854
|
-
|
|
10855
|
-
|
|
10456
|
+
mkdirSync2(evidenceDir, { recursive: true });
|
|
10457
|
+
writeFileSync2(evidencePath, JSON.stringify(after, null, 2) + "\n");
|
|
10856
10458
|
return { appId, evidencePath, before, after, updatedFields };
|
|
10857
10459
|
}
|
|
10858
10460
|
function formatEvidence(result) {
|
|
@@ -11108,8 +10710,8 @@ function statusesAtOrWorse(threshold) {
|
|
|
11108
10710
|
}
|
|
11109
10711
|
|
|
11110
10712
|
// src/commands/platform.ts
|
|
11111
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
11112
|
-
import
|
|
10713
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync2 } from "fs";
|
|
10714
|
+
import path5 from "path";
|
|
11113
10715
|
function registerPlatformCommand(program2) {
|
|
11114
10716
|
const platform3 = program2.command("platform").description("Internal Topolo first-party platform and personal app commands");
|
|
11115
10717
|
const platformApps = platform3.command("apps").description("Scaffold and operate first-party Topolo platform and personal applications");
|
|
@@ -11227,12 +10829,12 @@ var SKIPPED_SCAN_DIRS = /* @__PURE__ */ new Set([
|
|
|
11227
10829
|
"output"
|
|
11228
10830
|
]);
|
|
11229
10831
|
function scanStagingProductionUrls(rootInput, allowPatterns) {
|
|
11230
|
-
const root =
|
|
10832
|
+
const root = path5.resolve(rootInput);
|
|
11231
10833
|
const allowlist = allowPatterns.map((pattern) => new RegExp(pattern));
|
|
11232
10834
|
const violations = [];
|
|
11233
10835
|
function shouldSkipFile(filePath) {
|
|
11234
|
-
const basename =
|
|
11235
|
-
const relativePath =
|
|
10836
|
+
const basename = path5.basename(filePath);
|
|
10837
|
+
const relativePath = path5.relative(root, filePath);
|
|
11236
10838
|
if (basename === "package-lock.json" || basename === "pnpm-lock.yaml" || basename === "yarn.lock") return true;
|
|
11237
10839
|
if (basename === "topolo.cloudcontrol.json") return true;
|
|
11238
10840
|
if (basename === "wrangler.toml" || basename === "wrangler.jsonc") return true;
|
|
@@ -11241,15 +10843,15 @@ function scanStagingProductionUrls(rootInput, allowPatterns) {
|
|
|
11241
10843
|
if (basename.endsWith(".generated.ts")) return true;
|
|
11242
10844
|
if (basename.endsWith(".test.ts") || basename.endsWith(".test.tsx") || basename.endsWith(".spec.ts")) return true;
|
|
11243
10845
|
if (basename === ".env.production") return true;
|
|
11244
|
-
if (relativePath ===
|
|
11245
|
-
if (relativePath.includes(`${
|
|
11246
|
-
if (relativePath.startsWith(`scripts${
|
|
11247
|
-
return !RUNTIME_EXTENSIONS.has(
|
|
10846
|
+
if (relativePath === path5.join("src", "environment.ts") || relativePath === path5.join("src", "environmentUrls.ts") || relativePath.endsWith(path5.join("src", "environment.ts")) || relativePath.endsWith(path5.join("src", "lib", "environment.ts")) || relativePath === path5.join("packages", "topolo-ui-kit", "src", "environmentUrls.ts")) return true;
|
|
10847
|
+
if (relativePath.includes(`${path5.sep}test${path5.sep}`) || relativePath.includes(`${path5.sep}tests${path5.sep}`) || relativePath.includes(`${path5.sep}e2e${path5.sep}`)) return true;
|
|
10848
|
+
if (relativePath.startsWith(`scripts${path5.sep}`) || relativePath.includes(`${path5.sep}scripts${path5.sep}`)) return true;
|
|
10849
|
+
return !RUNTIME_EXTENSIONS.has(path5.extname(filePath));
|
|
11248
10850
|
}
|
|
11249
10851
|
function scanFile(filePath) {
|
|
11250
10852
|
if (shouldSkipFile(filePath)) return;
|
|
11251
|
-
const relativePath =
|
|
11252
|
-
const content =
|
|
10853
|
+
const relativePath = path5.relative(root, filePath);
|
|
10854
|
+
const content = readFileSync6(filePath, "utf8");
|
|
11253
10855
|
const lines = content.split(/\r?\n/);
|
|
11254
10856
|
lines.forEach((line, index) => {
|
|
11255
10857
|
const matches = [...line.matchAll(PRODUCTION_TOPOLO_URL_PATTERN)].map((match) => match[0]);
|
|
@@ -11263,7 +10865,7 @@ function scanStagingProductionUrls(rootInput, allowPatterns) {
|
|
|
11263
10865
|
}
|
|
11264
10866
|
function walk(dir) {
|
|
11265
10867
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
11266
|
-
const fullPath =
|
|
10868
|
+
const fullPath = path5.join(dir, entry.name);
|
|
11267
10869
|
if (entry.isDirectory()) {
|
|
11268
10870
|
if (!SKIPPED_SCAN_DIRS.has(entry.name)) walk(fullPath);
|
|
11269
10871
|
continue;
|
|
@@ -11431,7 +11033,7 @@ var DEFAULT_SEED_BASE_URL = "https://seed.stg.topolo.us";
|
|
|
11431
11033
|
function seedBaseUrl() {
|
|
11432
11034
|
return process.env.TOPOLO_SEED_URL || DEFAULT_SEED_BASE_URL;
|
|
11433
11035
|
}
|
|
11434
|
-
async function authedFetch(
|
|
11036
|
+
async function authedFetch(path6, init = {}) {
|
|
11435
11037
|
const credential = await refreshCredentialIfNeeded("staging");
|
|
11436
11038
|
if (!credential) {
|
|
11437
11039
|
throw new Error("No staging credential configured. Run `topolo auth login --env staging` or set TOPOLO_STAGING_API_KEY.");
|
|
@@ -11441,7 +11043,7 @@ async function authedFetch(path5, init = {}) {
|
|
|
11441
11043
|
else headers.set("Authorization", `Bearer ${credential.accessToken}`);
|
|
11442
11044
|
headers.set("X-Topolo-Client", `topolo-cli/${CLI_VERSION}`);
|
|
11443
11045
|
if (init.body && !headers.has("Content-Type")) headers.set("Content-Type", "application/json");
|
|
11444
|
-
const response = await fetch(`${seedBaseUrl()}${
|
|
11046
|
+
const response = await fetch(`${seedBaseUrl()}${path6}`, { ...init, headers });
|
|
11445
11047
|
const text = await response.text();
|
|
11446
11048
|
let body;
|
|
11447
11049
|
try {
|
|
@@ -11718,7 +11320,7 @@ function createOAuthClientPayload(options) {
|
|
|
11718
11320
|
logo_url: options.logoUrl
|
|
11719
11321
|
};
|
|
11720
11322
|
}
|
|
11721
|
-
async function authFetchJson(
|
|
11323
|
+
async function authFetchJson(path6, init = {}) {
|
|
11722
11324
|
const environment = selectedEnvironment();
|
|
11723
11325
|
const credential = await refreshCredentialIfNeeded(environment);
|
|
11724
11326
|
if (!credential) {
|
|
@@ -11740,7 +11342,7 @@ async function authFetchJson(path5, init = {}) {
|
|
|
11740
11342
|
throw err;
|
|
11741
11343
|
}
|
|
11742
11344
|
headers.set("Authorization", `Bearer ${credential.accessToken}`);
|
|
11743
|
-
const response = await fetch(`${authBaseUrl}${
|
|
11345
|
+
const response = await fetch(`${authBaseUrl}${path6}`, { ...init, headers });
|
|
11744
11346
|
const text = await response.text();
|
|
11745
11347
|
const payload = text ? JSON.parse(text) : {};
|
|
11746
11348
|
if (!response.ok || payload.success === false) {
|
|
@@ -11939,8 +11541,8 @@ function formatCheckResult(result) {
|
|
|
11939
11541
|
}
|
|
11940
11542
|
|
|
11941
11543
|
// src/commands/skills.ts
|
|
11942
|
-
import { spawnSync } from "child_process";
|
|
11943
|
-
import { existsSync as
|
|
11544
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
11545
|
+
import { existsSync as existsSync5 } from "fs";
|
|
11944
11546
|
import { mkdir as mkdir3, copyFile, readFile as readFile5, writeFile as writeFile5, rm } from "fs/promises";
|
|
11945
11547
|
import { homedir as homedir2 } from "os";
|
|
11946
11548
|
import { dirname as dirname4, join as join5, resolve as resolve3 } from "path";
|
|
@@ -11952,19 +11554,19 @@ function skillsSourceDir() {
|
|
|
11952
11554
|
const distCandidate = resolve3(here, "../skills");
|
|
11953
11555
|
const srcCandidate = resolve3(here, "../../skills");
|
|
11954
11556
|
for (const candidate of [distCandidate, srcCandidate]) {
|
|
11955
|
-
if (
|
|
11557
|
+
if (existsSync5(join5(candidate, "topolo", "SKILL.md"))) return candidate;
|
|
11956
11558
|
}
|
|
11957
11559
|
return distCandidate;
|
|
11958
11560
|
}
|
|
11959
11561
|
function resolveMcpCommand() {
|
|
11960
|
-
const probe =
|
|
11562
|
+
const probe = spawnSync2(process.platform === "win32" ? "where" : "which", ["topolo-mcp"], {
|
|
11961
11563
|
stdio: "ignore"
|
|
11962
11564
|
});
|
|
11963
11565
|
if (probe.status === 0) return { command: "topolo-mcp", args: [] };
|
|
11964
11566
|
return { command: "npx", args: ["-y", "@topolo/mcp"] };
|
|
11965
11567
|
}
|
|
11966
11568
|
function agentBinaryExists(bin) {
|
|
11967
|
-
const probe =
|
|
11569
|
+
const probe = spawnSync2(process.platform === "win32" ? "where" : "which", [bin], {
|
|
11968
11570
|
stdio: "ignore"
|
|
11969
11571
|
});
|
|
11970
11572
|
return probe.status === 0;
|
|
@@ -11985,9 +11587,9 @@ async function installSkillFile(sourceDir, targetDir) {
|
|
|
11985
11587
|
function registerViaOfficialCli(cli, mcp, agentName) {
|
|
11986
11588
|
if (!agentBinaryExists(cli)) return false;
|
|
11987
11589
|
const scopeArgs = cli === "claude" ? ["-s", "user"] : [];
|
|
11988
|
-
|
|
11590
|
+
spawnSync2(cli, ["mcp", "remove", "topolo", ...scopeArgs], { stdio: "ignore" });
|
|
11989
11591
|
const envArgs = cli === "claude" ? ["-e", `TOPOLO_AGENT_NAME=${agentName}`] : ["--env", `TOPOLO_AGENT_NAME=${agentName}`];
|
|
11990
|
-
const result =
|
|
11592
|
+
const result = spawnSync2(
|
|
11991
11593
|
cli,
|
|
11992
11594
|
["mcp", "add", "topolo", ...scopeArgs, ...envArgs, "--", mcp.command, ...mcp.args],
|
|
11993
11595
|
{ stdio: "ignore" }
|
|
@@ -11995,11 +11597,11 @@ function registerViaOfficialCli(cli, mcp, agentName) {
|
|
|
11995
11597
|
return result.status === 0;
|
|
11996
11598
|
}
|
|
11997
11599
|
async function registerClaudeViaConfig(mcp) {
|
|
11998
|
-
const
|
|
11600
|
+
const path6 = join5(homedir2(), ".claude.json");
|
|
11999
11601
|
let config = {};
|
|
12000
|
-
if (
|
|
11602
|
+
if (existsSync5(path6)) {
|
|
12001
11603
|
try {
|
|
12002
|
-
config = JSON.parse(await readFile5(
|
|
11604
|
+
config = JSON.parse(await readFile5(path6, "utf8"));
|
|
12003
11605
|
} catch {
|
|
12004
11606
|
config = {};
|
|
12005
11607
|
}
|
|
@@ -12012,13 +11614,13 @@ async function registerClaudeViaConfig(mcp) {
|
|
|
12012
11614
|
env: { TOPOLO_AGENT_NAME: AGENT_NAME_FOR.claude }
|
|
12013
11615
|
};
|
|
12014
11616
|
config.mcpServers = servers;
|
|
12015
|
-
await writeFile5(
|
|
11617
|
+
await writeFile5(path6, `${JSON.stringify(config, null, 2)}
|
|
12016
11618
|
`, "utf8");
|
|
12017
11619
|
}
|
|
12018
11620
|
async function registerCodexViaConfig(mcp) {
|
|
12019
|
-
const
|
|
11621
|
+
const path6 = join5(homedir2(), ".codex", "config.toml");
|
|
12020
11622
|
let text = "";
|
|
12021
|
-
if (
|
|
11623
|
+
if (existsSync5(path6)) text = await readFile5(path6, "utf8");
|
|
12022
11624
|
text = text.replace(
|
|
12023
11625
|
/(^|\n)\[mcp_servers\.topolo(?:\.[a-z]+)?\][^\[]*/g,
|
|
12024
11626
|
(match) => match.startsWith("\n") ? "\n" : ""
|
|
@@ -12034,8 +11636,8 @@ command = "${mcp.command}"${argsToml}
|
|
|
12034
11636
|
[mcp_servers.topolo.env]
|
|
12035
11637
|
TOPOLO_AGENT_NAME = "${AGENT_NAME_FOR.codex}"
|
|
12036
11638
|
`;
|
|
12037
|
-
await mkdir3(dirname4(
|
|
12038
|
-
await writeFile5(
|
|
11639
|
+
await mkdir3(dirname4(path6), { recursive: true });
|
|
11640
|
+
await writeFile5(path6, `${text}${block}`, "utf8");
|
|
12039
11641
|
}
|
|
12040
11642
|
async function installForAgent(agent, sourceDir, mcp, opts) {
|
|
12041
11643
|
const skillDir = agent === "claude" ? claudeSkillDir(opts.projectDir) : codexSkillDir();
|
|
@@ -12073,7 +11675,7 @@ function registerSkillsCommand(program2) {
|
|
|
12073
11675
|
const mode = resolveOutputMode(opts.json);
|
|
12074
11676
|
try {
|
|
12075
11677
|
const sourceDir = skillsSourceDir();
|
|
12076
|
-
if (!
|
|
11678
|
+
if (!existsSync5(join5(sourceDir, "topolo", "SKILL.md"))) {
|
|
12077
11679
|
throw new Error(
|
|
12078
11680
|
"Bundled skill file not found. Reinstall the CLI: npm install -g @topolo/cli"
|
|
12079
11681
|
);
|
|
@@ -12102,7 +11704,7 @@ function registerSkillsCommand(program2) {
|
|
|
12102
11704
|
const skillDir = agent === "claude" ? claudeSkillDir() : codexSkillDir();
|
|
12103
11705
|
return {
|
|
12104
11706
|
agent,
|
|
12105
|
-
skillInstalled:
|
|
11707
|
+
skillInstalled: existsSync5(join5(skillDir, "SKILL.md")),
|
|
12106
11708
|
skillPath: join5(skillDir, "SKILL.md"),
|
|
12107
11709
|
cliAvailable: agentBinaryExists(agent)
|
|
12108
11710
|
};
|
|
@@ -12119,10 +11721,10 @@ function registerSkillsCommand(program2) {
|
|
|
12119
11721
|
for (const agent of resolveTargets(opts)) {
|
|
12120
11722
|
const skillDir = agent === "claude" ? claudeSkillDir() : codexSkillDir();
|
|
12121
11723
|
const skillFile = join5(skillDir, "SKILL.md");
|
|
12122
|
-
const skillRemoved =
|
|
11724
|
+
const skillRemoved = existsSync5(skillFile);
|
|
12123
11725
|
if (skillRemoved) await rm(skillFile, { force: true });
|
|
12124
11726
|
if (agentBinaryExists(agent)) {
|
|
12125
|
-
|
|
11727
|
+
spawnSync2(agent, ["mcp", "remove", "topolo"], { stdio: "ignore" });
|
|
12126
11728
|
}
|
|
12127
11729
|
removed.push({ agent, skillRemoved });
|
|
12128
11730
|
}
|