@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 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
- siteInternalUrl: z.string().min(1).optional()
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 isClientAuth(value) {
10973
- return value !== void 0 && value !== "undefined" && !RE_DEV_BYPASS.test(value);
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 httpText = objectLiteralOf(topLevelProperties(objectLiteralOf(args.get("surfaces"), text) ?? "{}").get("http"), text);
10987
- const hasClient = isClientAuth(docs.get("auth")) || isClientAuth(topLevelProperties(httpText ?? "{}").get("auth"));
10988
- if (!hasClient) {
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: "DOCS_SITE_UNAUTHED",
11001
+ code: "DOCS_SITE_PUBLIC",
10991
11002
  line: decl.line,
10992
- why: `mesh.apps.${decl.ctor} declares a docs site with no OIDC client \u2014 the site is served behind the platform sign-in proxy, which needs one (docs.auth, or a client on surfaces.http.auth)`
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 may read it would fall out of the name heuristic, and a vendor surface must never default to partner-readable that way`
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 (!args.has("ingress") || args.get("ingress") === "undefined") continue;
11010
- if (isClientAuth(args.get("auth"))) continue;
11021
+ if (!hasIngress(args.get("ingress"))) continue;
11011
11022
  findings.push({
11012
- code: "DOCS_SITE_UNAUTHED",
11023
+ code: "DOCS_SITE_PUBLIC",
11013
11024
  line: decl.line,
11014
- why: `mesh.apps.ApiDocs is published on an ingress with no auth \u2014 the site (reference and playground) is reachable by anyone; put the platform sign-in proxy in front of it, or drop the 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 appName = clientExpr ? RE_IDENTITY_APP_CLIENT.exec(clientExpr)?.[1] : void 0;
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, RE_IDENTITY_APP_CLIENT, DOCS_SITE_FIELDS, RE_DEV_BYPASS, RE_PROPERTY;
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
- DOCS_SITE_UNAUTHED: {
11374
+ DOCS_SITE_PUBLIC: {
11375
11375
  gate: "0.3",
11376
- title: "Docs site behind the platform sign-in",
11377
- severity: "block",
11378
- remediation: 'on an API surface: docs: { auth: { clientId, clientSecret } } (or a client on surfaces.http.auth), or opt out with docs: { site: false }; on a mesh.apps.ApiDocs: auth: { provider: "mesh", clientId, clientSecret }, or drop the ingress (an in-cluster site is reachable through mesh dev)',
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, and do not set idTokenRoleAssertion: false on the application the docs sign-in proxy uses (applications.<name>) \u2014 the identity asserts roles into the ID token by default, and the site's gate reads that token`,
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
- RE_DEV_BYPASS = /^["']dev-bypass["']$/;
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 on the repo, not on this app (mesh app check)");
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") {