@mesh-tech/mesh-cli 0.23.1 → 0.24.1
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/bin/mesh.js +43 -41
- package/dist/bin/mesh.js.map +1 -1
- package/dist/build-info.json +2 -2
- package/dist/src/commands/app-check.d.ts +1 -1
- package/dist/src/commands/app-check.js +32 -38
- package/dist/src/commands/create-app.js +1 -1
- package/dist/src/commands/temporal.js +7 -3
- package/dist/src/commands/temporal.js.map +1 -1
- package/fragments/agents/AGENTS.md.hbs +10 -7
- package/fragments/base/index.ts.hbs +12 -10
- package/fragments/service/docs/pages/getting-started.md.hbs +4 -2
- package/package.json +2 -2
- package/skills/core/SKILL.md +1 -1
package/dist/bin/mesh.js
CHANGED
|
@@ -6258,7 +6258,7 @@ var init_hub_roles = __esm({
|
|
|
6258
6258
|
"libs/api-registry/src/hub-roles.ts"() {
|
|
6259
6259
|
"use strict";
|
|
6260
6260
|
HUB_STAFF_ROLES = ["ops", "admin"];
|
|
6261
|
-
HUB_RESTRICTED_ROLES = ["developer", "auditor", "vendor"];
|
|
6261
|
+
HUB_RESTRICTED_ROLES = ["developer", "auditor", "vendor", "docs"];
|
|
6262
6262
|
HUB_BASE_ROLES = [
|
|
6263
6263
|
...HUB_STAFF_ROLES,
|
|
6264
6264
|
...HUB_RESTRICTED_ROLES
|
|
@@ -6271,7 +6271,7 @@ var init_hub_roles = __esm({
|
|
|
6271
6271
|
|
|
6272
6272
|
// libs/api-registry/src/index.ts
|
|
6273
6273
|
import { z } from "zod";
|
|
6274
|
-
var rateLimitSpecSchema, rateLimitDefaultsSchema, integrationHealthSchema, apiSurfaceKindSchema, apiSurfaceSchema, apiRegistryEntrySchema, integrationStatusSchema;
|
|
6274
|
+
var rateLimitSpecSchema, rateLimitDefaultsSchema, integrationHealthSchema, apiSurfaceKindSchema, apiDocsSiteAudienceSchema, apiSurfaceSchema, apiRegistryEntrySchema, integrationStatusSchema;
|
|
6275
6275
|
var init_src = __esm({
|
|
6276
6276
|
"libs/api-registry/src/index.ts"() {
|
|
6277
6277
|
"use strict";
|
|
@@ -6286,6 +6286,10 @@ var init_src = __esm({
|
|
|
6286
6286
|
z.object({ unavailable: z.string().trim().min(1) })
|
|
6287
6287
|
]);
|
|
6288
6288
|
apiSurfaceKindSchema = z.enum(["app", "vendor"]);
|
|
6289
|
+
apiDocsSiteAudienceSchema = z.discriminatedUnion("readers", [
|
|
6290
|
+
z.object({ readers: z.literal("partner"), roles: z.array(z.string().min(1)) }),
|
|
6291
|
+
z.object({ readers: z.literal("operator") })
|
|
6292
|
+
]);
|
|
6289
6293
|
apiSurfaceSchema = z.object({
|
|
6290
6294
|
http: z.object({ url: z.string().min(1) }).optional(),
|
|
6291
6295
|
nexus: z.object({ endpoint: z.string().min(1), taskQueue: z.string().min(1) }).optional()
|
|
@@ -6304,7 +6308,7 @@ var init_src = __esm({
|
|
|
6304
6308
|
url: z.string().min(1),
|
|
6305
6309
|
contentHash: z.string().min(1),
|
|
6306
6310
|
siteUrl: z.string().min(1).optional(),
|
|
6307
|
-
|
|
6311
|
+
audience: apiDocsSiteAudienceSchema.optional()
|
|
6308
6312
|
}),
|
|
6309
6313
|
enabledOps: z.array(z.string()),
|
|
6310
6314
|
// The consumer schema strips unknown keys, so a producer-side field that is
|
|
@@ -10953,10 +10957,14 @@ function objectLiteralOf(value, program2) {
|
|
|
10953
10957
|
const decl = new RegExp(`\\bconst\\s+${id}\\s*(?::[^=]+)?=\\s*\\{`).exec(program2);
|
|
10954
10958
|
return decl ? balancedSpan(program2, decl.index + decl[0].length - 1) || void 0 : void 0;
|
|
10955
10959
|
}
|
|
10956
|
-
function optionsObject(body) {
|
|
10960
|
+
function optionsObject(body, program2) {
|
|
10957
10961
|
for (const arg of splitTopLevel(body.slice(1, -1))) {
|
|
10958
|
-
const trimmed = arg.trim();
|
|
10962
|
+
const trimmed = stripTrailingComments(stripLeadingComments(arg).trim());
|
|
10959
10963
|
if (trimmed.startsWith("{")) return balancedSpan(trimmed, 0) || void 0;
|
|
10964
|
+
if (/^[A-Za-z_$][\w$]*$/.test(trimmed)) {
|
|
10965
|
+
const resolved = objectLiteralOf(trimmed, program2);
|
|
10966
|
+
if (resolved) return resolved;
|
|
10967
|
+
}
|
|
10960
10968
|
}
|
|
10961
10969
|
return void 0;
|
|
10962
10970
|
}
|
|
@@ -10969,13 +10977,16 @@ function surfaceDeclarations(text, pattern = RE_SURFACE_NEW) {
|
|
|
10969
10977
|
}
|
|
10970
10978
|
return out;
|
|
10971
10979
|
}
|
|
10972
|
-
function
|
|
10973
|
-
return value !== void 0 && value !== "undefined"
|
|
10980
|
+
function isWritten(value) {
|
|
10981
|
+
return value !== void 0 && value !== "undefined";
|
|
10982
|
+
}
|
|
10983
|
+
function hasIngress(value) {
|
|
10984
|
+
return isWritten(value) && !/^(null|false)$/.test(value);
|
|
10974
10985
|
}
|
|
10975
10986
|
function docsSiteFindings(text) {
|
|
10976
10987
|
const findings = [];
|
|
10977
10988
|
for (const decl of surfaceDeclarations(text)) {
|
|
10978
|
-
const options = optionsObject(decl.body);
|
|
10989
|
+
const options = optionsObject(decl.body, text);
|
|
10979
10990
|
if (!options) continue;
|
|
10980
10991
|
const args = topLevelProperties(options);
|
|
10981
10992
|
const docsText = objectLiteralOf(args.get("docs"), text);
|
|
@@ -10983,13 +10994,13 @@ function docsSiteFindings(text) {
|
|
|
10983
10994
|
const docs = topLevelProperties(docsText);
|
|
10984
10995
|
if (docs.get("site") === "false") continue;
|
|
10985
10996
|
if (docs.get("site") !== "true" && !DOCS_SITE_FIELDS.some((f) => docs.has(f))) continue;
|
|
10986
|
-
const
|
|
10987
|
-
|
|
10988
|
-
|
|
10997
|
+
const retired = DOCS_SITE_RETIRED_FIELDS.filter((f) => isWritten(docs.get(f)));
|
|
10998
|
+
if (retired.length > 0) {
|
|
10999
|
+
const named = retired.map((f) => `docs.${f}`).join(", ");
|
|
10989
11000
|
findings.push({
|
|
10990
|
-
code: "
|
|
11001
|
+
code: "DOCS_SITE_PUBLIC",
|
|
10991
11002
|
line: decl.line,
|
|
10992
|
-
why: `mesh.apps.${decl.ctor} declares a docs site with
|
|
11003
|
+
why: `mesh.apps.${decl.ctor} declares a docs site with ${named} \u2014 retired: a docs site has no hostname, no sign-in and no door switch of its own; it is read inside the Hub through the docs door, and the primitive ignores ${retired.length > 1 ? "them" : "it"}`
|
|
10993
11004
|
});
|
|
10994
11005
|
}
|
|
10995
11006
|
const kindStated = decl.ctor === "AppApiSurface" || decl.ctor === "VendorApiSurface" || args.has("kind");
|
|
@@ -10997,21 +11008,21 @@ function docsSiteFindings(text) {
|
|
|
10997
11008
|
findings.push({
|
|
10998
11009
|
code: "DOCS_AUDIENCE_IMPLICIT",
|
|
10999
11010
|
line: decl.line,
|
|
11000
|
-
why: `mesh.apps.${decl.ctor} declares a docs site but neither its kind nor its audience \u2014 who
|
|
11011
|
+
why: `mesh.apps.${decl.ctor} declares a docs site but neither its kind nor its audience \u2014 who the Hub admits through the door would fall out of the name heuristic, and a vendor surface must never default to partner-readable that way`
|
|
11001
11012
|
});
|
|
11002
11013
|
}
|
|
11014
|
+
const httpText = objectLiteralOf(topLevelProperties(objectLiteralOf(args.get("surfaces"), text) ?? "{}").get("http"), text);
|
|
11003
11015
|
for (const f of docsAudienceUngrantable(text, decl, args, docs, httpText)) findings.push(f);
|
|
11004
11016
|
}
|
|
11005
11017
|
for (const decl of surfaceDeclarations(text, RE_APIDOCS_NEW)) {
|
|
11006
|
-
const options = optionsObject(decl.body);
|
|
11018
|
+
const options = optionsObject(decl.body, text);
|
|
11007
11019
|
if (!options) continue;
|
|
11008
11020
|
const args = topLevelProperties(options);
|
|
11009
|
-
if (!
|
|
11010
|
-
if (isClientAuth(args.get("auth"))) continue;
|
|
11021
|
+
if (!hasIngress(args.get("ingress"))) continue;
|
|
11011
11022
|
findings.push({
|
|
11012
|
-
code: "
|
|
11023
|
+
code: "DOCS_SITE_PUBLIC",
|
|
11013
11024
|
line: decl.line,
|
|
11014
|
-
why: `mesh.apps.ApiDocs is published on an ingress
|
|
11025
|
+
why: `mesh.apps.ApiDocs is published on an ingress \u2014 a docs site has no hostname of its own; it is read inside the Hub through the docs door of the API surface that declares it, and the ingress is retired (it still deploys, with a preview warning, until a coming minor release refuses it)`
|
|
11015
11026
|
});
|
|
11016
11027
|
}
|
|
11017
11028
|
return findings;
|
|
@@ -11029,7 +11040,7 @@ function docsAudienceUngrantable(text, decl, args, docs, httpText) {
|
|
|
11029
11040
|
if (!roles || roles.length === 0) return findings;
|
|
11030
11041
|
const identities = [];
|
|
11031
11042
|
for (const d of surfaceDeclarations(text, RE_IDENTITY_NEW)) {
|
|
11032
|
-
const options = optionsObject(d.body);
|
|
11043
|
+
const options = optionsObject(d.body, text);
|
|
11033
11044
|
if (!options) continue;
|
|
11034
11045
|
const binding = [...text.matchAll(RE_IDENTITY_BINDING)].find((m) => text.indexOf(d.body, m.index) === m.index + m[0].length - 1)?.[1];
|
|
11035
11046
|
identities.push({ binding, options: topLevelProperties(options) });
|
|
@@ -11037,8 +11048,7 @@ function docsAudienceUngrantable(text, decl, args, docs, httpText) {
|
|
|
11037
11048
|
if (identities.length === 0) return findings;
|
|
11038
11049
|
const docsAuth = objectLiteralOf(docs.get("auth"), text);
|
|
11039
11050
|
const clientExpr = (docsAuth ? topLevelProperties(docsAuth).get("clientId") : void 0) ?? (httpText ? topLevelProperties(objectLiteralOf(topLevelProperties(httpText).get("auth"), text) ?? "{}").get("clientId") : void 0);
|
|
11040
|
-
const
|
|
11041
|
-
const clientRoot = clientExpr ? /^([A-Za-z_$][\w$]*)\.applications\./.exec(clientExpr.trim())?.[1] : void 0;
|
|
11051
|
+
const clientRoot = clientExpr ? /^([A-Za-z_$][\w$]*)\.(?:clientIds|applications)\./.exec(clientExpr.trim())?.[1] : void 0;
|
|
11042
11052
|
const identity = (clientRoot ? identities.find((id) => id.binding === clientRoot) : void 0) ?? (identities.length === 1 ? identities[0] : void 0);
|
|
11043
11053
|
if (!identity) return findings;
|
|
11044
11054
|
const rolesText = identity.options.get("roles");
|
|
@@ -11054,16 +11064,6 @@ function docsAudienceUngrantable(text, decl, args, docs, httpText) {
|
|
|
11054
11064
|
});
|
|
11055
11065
|
}
|
|
11056
11066
|
}
|
|
11057
|
-
if (!appName) return findings;
|
|
11058
|
-
const apps = objectLiteralOf(identity.options.get("applications"), text);
|
|
11059
|
-
const app = apps ? objectLiteralOf(topLevelProperties(apps).get(appName), text) : void 0;
|
|
11060
|
-
if (app && topLevelProperties(app).get("idTokenRoleAssertion") === "false") {
|
|
11061
|
-
findings.push({
|
|
11062
|
-
code: "DOCS_AUDIENCE_UNGRANTABLE",
|
|
11063
|
-
line: decl.line,
|
|
11064
|
-
why: `the docs sign-in client is applications.${appName} on the ZitadelAppIdentity, which sets idTokenRoleAssertion: false \u2014 the proxy's ID token then carries no roles and the site's gate admits nobody`
|
|
11065
|
-
});
|
|
11066
|
-
}
|
|
11067
11067
|
return findings;
|
|
11068
11068
|
}
|
|
11069
11069
|
function listApps(root) {
|
|
@@ -11271,7 +11271,7 @@ function registerAppCommands(program2) {
|
|
|
11271
11271
|
}
|
|
11272
11272
|
});
|
|
11273
11273
|
}
|
|
11274
|
-
var APP_CHECK_RULES, APP_CHECK_CODES, SRC_EXT, SKIP_DIRS, NON_SERVICE_DIRS, RE_HTML, RE_OIDC, RE_COOKIE, RE_LOGGER, RE_EXPOSURE, RE_POINTER, RE_METADATA, RE_IAC_GRANT, RE_POINTER_OVERRIDE, RE_PASSWORD, RE_ALLOWLIST, RE_USERS_TABLE, RE_SURFACE_NEW, RE_APIDOCS_NEW, RE_IDENTITY_NEW, RE_IDENTITY_BINDING, DEFAULT_PARTNER_DOCS_ROLES,
|
|
11274
|
+
var APP_CHECK_RULES, APP_CHECK_CODES, SRC_EXT, SKIP_DIRS, NON_SERVICE_DIRS, RE_HTML, RE_OIDC, RE_COOKIE, RE_LOGGER, RE_EXPOSURE, RE_POINTER, RE_METADATA, RE_IAC_GRANT, RE_POINTER_OVERRIDE, RE_PASSWORD, RE_ALLOWLIST, RE_USERS_TABLE, RE_SURFACE_NEW, RE_APIDOCS_NEW, RE_IDENTITY_NEW, RE_IDENTITY_BINDING, DEFAULT_PARTNER_DOCS_ROLES, DOCS_SITE_FIELDS, DOCS_SITE_RETIRED_FIELDS, RE_PROPERTY;
|
|
11275
11275
|
var init_app_check = __esm({
|
|
11276
11276
|
"libs/mesh-cli/src/commands/app-check.ts"() {
|
|
11277
11277
|
"use strict";
|
|
@@ -11371,11 +11371,14 @@ var init_app_check = __esm({
|
|
|
11371
11371
|
remediation: "drop the table; people and roles live in Zitadel and are administered from the Hub",
|
|
11372
11372
|
scope: "app"
|
|
11373
11373
|
},
|
|
11374
|
-
|
|
11374
|
+
DOCS_SITE_PUBLIC: {
|
|
11375
11375
|
gate: "0.3",
|
|
11376
|
-
title: "Docs site
|
|
11377
|
-
|
|
11378
|
-
|
|
11376
|
+
title: "Docs site read through the Hub, not on a hostname of its own",
|
|
11377
|
+
// Advisory while the hostname is deprecated rather than refused: a site
|
|
11378
|
+
// that still deploys on one keeps deploying this release, with a preview
|
|
11379
|
+
// warning, and its tenant CI should say so without going red.
|
|
11380
|
+
severity: "advisory",
|
|
11381
|
+
remediation: "drop the docs hostname \u2014 a docs site is read inside the Hub through the docs door, which signs every reader in: on an API surface remove docs.ingress (docs.auth, docs.basePath and docs.hubDoor are retired with it); on a mesh.apps.ApiDocs remove the ingress (and its auth / authentication) and declare the API as a mesh.apps.AppApiSurface whose docs block builds the site",
|
|
11379
11382
|
scope: "app"
|
|
11380
11383
|
},
|
|
11381
11384
|
DOCS_AUDIENCE_IMPLICIT: {
|
|
@@ -11389,7 +11392,7 @@ var init_app_check = __esm({
|
|
|
11389
11392
|
gate: "H",
|
|
11390
11393
|
title: "Docs audience grantable from the Hub",
|
|
11391
11394
|
severity: "block",
|
|
11392
|
-
remediation: `declare every audience role on the ZitadelAppIdentity (roles: ["docs"]) so the Hub can grant it
|
|
11395
|
+
remediation: `declare every audience role on the ZitadelAppIdentity (roles: ["docs"]) so the Hub can grant it from the app's Access tab \u2014 the audience is published on the surface's registry entry as the reader set for the Hub to admit by, and a role nobody can be granted admits nobody`,
|
|
11393
11396
|
scope: "app"
|
|
11394
11397
|
}
|
|
11395
11398
|
};
|
|
@@ -11427,9 +11430,8 @@ var init_app_check = __esm({
|
|
|
11427
11430
|
RE_IDENTITY_NEW = /new\s+mesh\.auth\.(ZitadelAppIdentity)\s*\(/g;
|
|
11428
11431
|
RE_IDENTITY_BINDING = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*(?::[^=]+)?=\s*new\s+mesh\.auth\.ZitadelAppIdentity\s*\(/g;
|
|
11429
11432
|
DEFAULT_PARTNER_DOCS_ROLES = ["docs"];
|
|
11430
|
-
RE_IDENTITY_APP_CLIENT = /\bapplications\.([A-Za-z_$][\w$]*)\.clientId\b/;
|
|
11431
11433
|
DOCS_SITE_FIELDS = ["pages", "playground", "audience", "basePath", "ingress", "auth"];
|
|
11432
|
-
|
|
11434
|
+
DOCS_SITE_RETIRED_FIELDS = ["auth", "ingress", "basePath", "hubDoor"];
|
|
11433
11435
|
RE_PROPERTY = /^(?:"([^"]+)"|'([^']+)'|([A-Za-z_$][\w$]*))\s*(?::\s*([\s\S]*))?$/;
|
|
11434
11436
|
}
|
|
11435
11437
|
});
|
|
@@ -13076,7 +13078,7 @@ async function checkScaffoldContract(appDir) {
|
|
|
13076
13078
|
}
|
|
13077
13079
|
console.log(renderReport(report, { verbose: false }));
|
|
13078
13080
|
if (report.status === "warn") {
|
|
13079
|
-
logSuccess("app contract: met \u2014 the \u26A0 lines above are advisories
|
|
13081
|
+
logSuccess("app contract: met \u2014 the \u26A0 lines above are advisories, not blocking findings (mesh app check)");
|
|
13080
13082
|
return;
|
|
13081
13083
|
}
|
|
13082
13084
|
if (report.status === "error") {
|