@mesh-tech/mesh-cli 0.32.0 → 0.33.0

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
@@ -6912,7 +6912,7 @@ var init_src = __esm({
6912
6912
  z.object({ op: z.string().trim().min(1) }),
6913
6913
  z.object({ unavailable: z.string().trim().min(1) })
6914
6914
  ]);
6915
- apiSurfaceKindSchema = z.enum(["app", "vendor"]);
6915
+ apiSurfaceKindSchema = z.enum(["app", "vendor", "events"]);
6916
6916
  apiDocsSiteAudienceSchema = z.discriminatedUnion("readers", [
6917
6917
  z.object({ readers: z.literal("partner"), roles: z.array(z.string().min(1)) }),
6918
6918
  z.object({ readers: z.literal("operator") })
@@ -11846,6 +11846,53 @@ function statedKind(decl, args) {
11846
11846
  if (decl.ctor === "VendorApiSurface") return "vendor";
11847
11847
  return /^["']([a-z]+)["']$/.exec(args.get("kind") ?? "")?.[1];
11848
11848
  }
11849
+ function programIdentities(text) {
11850
+ const identities = [];
11851
+ for (const d of surfaceDeclarations(text, RE_IDENTITY_NEW)) {
11852
+ const options = optionsObject(d.body, text);
11853
+ if (!options) continue;
11854
+ const binding = [...text.matchAll(RE_IDENTITY_BINDING)].find(
11855
+ (m) => text.indexOf(d.body, m.index) === m.index + m[0].length - 1
11856
+ )?.[1];
11857
+ identities.push({ binding, options: topLevelProperties(options) });
11858
+ }
11859
+ return identities;
11860
+ }
11861
+ function declaredProjectRoles(identity) {
11862
+ const rolesText = identity.options.get("roles");
11863
+ const coarseText = identity.options.get("coarseRoles");
11864
+ if (rolesText !== void 0) return stringList(rolesText);
11865
+ if (coarseText !== void 0) return stringList(coarseText);
11866
+ return [];
11867
+ }
11868
+ function eventsRoleFindings(text) {
11869
+ const findings = [];
11870
+ const identities = programIdentities(text);
11871
+ for (const decl of surfaceDeclarations(text, RE_EVENTS_NEW)) {
11872
+ const options = optionsObject(decl.body, text);
11873
+ if (!options) continue;
11874
+ const args = topLevelProperties(options);
11875
+ if (args.get("enabled") === "false") continue;
11876
+ if (identities.length === 0) {
11877
+ continue;
11878
+ }
11879
+ const identityText = objectLiteralOf(args.get("identity"), text);
11880
+ const projectExpr = identityText ? topLevelProperties(identityText).get("projectId") : void 0;
11881
+ const projectRoot = projectExpr ? /^([A-Za-z_$][\w$]*)\.projectId\b/.exec(projectExpr.trim())?.[1] : void 0;
11882
+ const identity = (projectRoot ? identities.find((id) => id.binding === projectRoot) : void 0) ?? (identities.length === 1 ? identities[0] : void 0);
11883
+ if (!identity) continue;
11884
+ const declared = declaredProjectRoles(identity);
11885
+ if (!declared) continue;
11886
+ const missing = EVENTS_PROJECT_ROLES.filter((r) => !declared.includes(r));
11887
+ if (missing.length === 0) continue;
11888
+ findings.push({
11889
+ code: "EVENTS_ROLES_UNDECLARED",
11890
+ line: decl.line,
11891
+ why: `mesh.apps.EventsService is declared but the app's ZitadelAppIdentity does not declare ${missing.map((r) => `"${r}"`).join(" or ")} \u2014 the events service authorizes bearer callers by these project roles, and a role the identity never declares cannot be granted from the Hub's Access tab, so no subscriber can be admitted`
11892
+ });
11893
+ }
11894
+ return findings;
11895
+ }
11849
11896
  function docsAudienceUngrantable(text, decl, args, docs, httpText) {
11850
11897
  const findings = [];
11851
11898
  let roles;
@@ -11856,22 +11903,14 @@ function docsAudienceUngrantable(text, decl, args, docs, httpText) {
11856
11903
  roles = DEFAULT_PARTNER_DOCS_ROLES;
11857
11904
  }
11858
11905
  if (!roles || roles.length === 0) return findings;
11859
- const identities = [];
11860
- for (const d of surfaceDeclarations(text, RE_IDENTITY_NEW)) {
11861
- const options = optionsObject(d.body, text);
11862
- if (!options) continue;
11863
- const binding = [...text.matchAll(RE_IDENTITY_BINDING)].find((m) => text.indexOf(d.body, m.index) === m.index + m[0].length - 1)?.[1];
11864
- identities.push({ binding, options: topLevelProperties(options) });
11865
- }
11906
+ const identities = programIdentities(text);
11866
11907
  if (identities.length === 0) return findings;
11867
11908
  const docsAuth = objectLiteralOf(docs.get("auth"), text);
11868
11909
  const clientExpr = (docsAuth ? topLevelProperties(docsAuth).get("clientId") : void 0) ?? (httpText ? topLevelProperties(objectLiteralOf(topLevelProperties(httpText).get("auth"), text) ?? "{}").get("clientId") : void 0);
11869
11910
  const clientRoot = clientExpr ? /^([A-Za-z_$][\w$]*)\.(?:clientIds|applications)\./.exec(clientExpr.trim())?.[1] : void 0;
11870
11911
  const identity = (clientRoot ? identities.find((id) => id.binding === clientRoot) : void 0) ?? (identities.length === 1 ? identities[0] : void 0);
11871
11912
  if (!identity) return findings;
11872
- const rolesText = identity.options.get("roles");
11873
- const coarseText = identity.options.get("coarseRoles");
11874
- const declared = rolesText !== void 0 ? stringList(rolesText) : coarseText !== void 0 ? stringList(coarseText) : [];
11913
+ const declared = declaredProjectRoles(identity);
11875
11914
  if (declared) {
11876
11915
  for (const role of roles) {
11877
11916
  if (declared.includes(role)) continue;
@@ -11942,6 +11981,7 @@ function scanApp(root, appRel, resolution = probeResolution(root, appRel)) {
11942
11981
  hit("NO_REGISTER", indexRel, "never calls env.register({\u2026}) \u2014 the Hub cannot list this app");
11943
11982
  }
11944
11983
  for (const f of docsSiteFindings(readText(index))) hit(f.code, `${indexRel}:${f.line}`, f.why);
11984
+ for (const f of eventsRoleFindings(readText(index))) hit(f.code, `${indexRel}:${f.line}`, f.why);
11945
11985
  for (const svc of serviceDirs(app)) {
11946
11986
  for (const f of srcFiles(svc)) {
11947
11987
  const text = readText(f);
@@ -12103,7 +12143,7 @@ function registerAppCommands(program2) {
12103
12143
  }
12104
12144
  });
12105
12145
  }
12106
- 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;
12146
+ 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_EVENTS_NEW, EVENTS_PROJECT_ROLES, RE_IDENTITY_BINDING, DEFAULT_PARTNER_DOCS_ROLES, DOCS_SITE_FIELDS, DOCS_SITE_RETIRED_FIELDS, RE_PROPERTY;
12107
12147
  var init_app_check = __esm({
12108
12148
  "libs/mesh-cli/src/commands/app-check.ts"() {
12109
12149
  "use strict";
@@ -12242,6 +12282,13 @@ var init_app_check = __esm({
12242
12282
  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`,
12243
12283
  scope: "app"
12244
12284
  },
12285
+ EVENTS_ROLES_UNDECLARED: {
12286
+ gate: "H",
12287
+ title: "Events roles grantable from the Hub",
12288
+ severity: "block",
12289
+ remediation: "spread mesh.apps.EVENTS_PROJECT_ROLES into the ZitadelAppIdentity's roles (roles: [...schemaDef.coarseRoles, ...mesh.apps.EVENTS_PROJECT_ROLES]) so events:subscriber and events:admin are project roles the Hub's Access tab can grant \u2014 the primitive cannot add them, because the identity builds its roles before the events service exists and a role added afterwards is absent from the coarseRoles the Hub reads",
12290
+ scope: "app"
12291
+ },
12245
12292
  DOCS_VENDOR_AUDIENCE_PARTNER: {
12246
12293
  gate: "H",
12247
12294
  title: "Vendor reference never partner-readable",
@@ -12273,7 +12320,7 @@ var init_app_check = __esm({
12273
12320
  RE_OIDC = /openid-client|code_verifier|authorization_code|buildAuthorizeUrl|end_session/;
12274
12321
  RE_COOKIE = /setCookie\(|hono\/cookie|Set-Cookie/;
12275
12322
  RE_LOGGER = /from ["'](pino|hono\/logger|winston|bunyan)["']|require\(["'](pino|winston|bunyan)["']\)|^[^/*]*\bconsole\.(log|info|warn|error)\(/;
12276
- RE_EXPOSURE = /ZitadelAppIdentity|createOAuthMiddleware|createAuthorizationMiddleware|zitadelJwtScheme|createAuthzMiddleware|bearerAuth|type: *"oidc"|^\s+auth: *\{|_ROLES *= *\{|coarseRoles|ZITADEL_(AUDIENCE|CLIENT_ID)|OIDC_CLIENT_ID|zitadel\.(ApplicationOidc|ApplicationApi|Project|UserGrant)\(/;
12323
+ RE_EXPOSURE = /ZitadelAppIdentity|createOAuthMiddleware|createAuthorizationMiddleware|zitadelJwtScheme|createAuthzMiddleware|bearerAuth|type: *"oidc"|^\s+auth: *\{|_ROLES *= *\{|coarseRoles|ZITADEL_(AUDIENCE|CLIENT_ID)|OIDC_CLIENT_ID|zitadel\.(ApplicationOidc|ApplicationApi|Project|UserGrant)\(|mesh\.apps\.EventsService\(/;
12277
12324
  RE_POINTER = /AppAuthzPointer/;
12278
12325
  RE_METADATA = /compileOpsHubMetadata|ops-hub-metadata|SpiceDBSchema/;
12279
12326
  RE_IAC_GRANT = /userId: *"[0-9]{6,}"/;
@@ -12284,6 +12331,8 @@ var init_app_check = __esm({
12284
12331
  RE_SURFACE_NEW = /new\s+mesh\.apps\.(ApiSurface|AppApiSurface|VendorApiSurface|Integration)\s*\(/g;
12285
12332
  RE_APIDOCS_NEW = /new\s+mesh\.apps\.ApiDocs\s*\(/g;
12286
12333
  RE_IDENTITY_NEW = /new\s+mesh\.auth\.(ZitadelAppIdentity)\s*\(/g;
12334
+ RE_EVENTS_NEW = /new\s+mesh\.apps\.(EventsService)\s*\(/g;
12335
+ EVENTS_PROJECT_ROLES = ["events:subscriber", "events:admin"];
12287
12336
  RE_IDENTITY_BINDING = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*(?::[^=]+)?=\s*new\s+mesh\.auth\.ZitadelAppIdentity\s*\(/g;
12288
12337
  DEFAULT_PARTNER_DOCS_ROLES = ["docs"];
12289
12338
  DOCS_SITE_FIELDS = ["pages", "playground", "audience", "basePath", "ingress", "auth"];
@@ -13507,7 +13556,13 @@ var init_scaffold_versions = __esm({
13507
13556
  "use strict";
13508
13557
  SCAFFOLD_PACKAGE_LINES = {
13509
13558
  "@mesh-tech/app-kit": "^1.45.0",
13510
- "@mesh-tech/esm-bundle": "^0.1.1"
13559
+ "@mesh-tech/esm-bundle": "^0.1.1",
13560
+ // The events service versions with the primitive that deploys it, not with
13561
+ // the lockstep group, so it carries its own floor.
13562
+ "@mesh-tech/events-service": "^0.1.0",
13563
+ // Likewise authz: it is not in the `fixed` group in .changeset/config.json,
13564
+ // so the lockstep range resolves to a major line it has never published.
13565
+ "@mesh-tech/authz": "^1.19.0"
13511
13566
  };
13512
13567
  SCAFFOLD_TOOLCHAIN = {
13513
13568
  "@types/node": "^24.2.1",
@@ -13676,6 +13731,10 @@ async function promptForOptions(options) {
13676
13731
  primitives.push("service");
13677
13732
  logInfo("Added 'service' \u2014 bucket requires an API service for S3 helpers.");
13678
13733
  }
13734
+ if (primitives.includes("events") && !primitives.includes("service")) {
13735
+ primitives.push("service");
13736
+ logInfo("Added 'service' \u2014 the events publisher lives in the api/ directory.");
13737
+ }
13679
13738
  return { tenant, name, primitives };
13680
13739
  }
13681
13740
  function copyTemplate(srcDir, destDir, context) {
@@ -13787,6 +13846,13 @@ function generateComposableApp(appDir, context) {
13787
13846
  }
13788
13847
  copyTemplate(bucketDir, appDir, context);
13789
13848
  }
13849
+ if (context.events) {
13850
+ const eventsDir = path27.join(fragmentsDir, "events");
13851
+ if (!fs25.existsSync(eventsDir)) {
13852
+ throw new Error(`Fragment directory not found: ${eventsDir}`);
13853
+ }
13854
+ copyTemplate(eventsDir, appDir, context);
13855
+ }
13790
13856
  const agentsDir = path27.join(fragmentsDir, "agents");
13791
13857
  if (fs25.existsSync(agentsDir)) {
13792
13858
  copyTemplate(agentsDir, appDir, context);
@@ -13853,6 +13919,10 @@ function registerCreateAppCommand(program2) {
13853
13919
  primitives.push("service");
13854
13920
  logInfo("Added 'service' \u2014 bucket requires an API service for S3 helpers.");
13855
13921
  }
13922
+ if (primitives.includes("events") && !primitives.includes("service")) {
13923
+ primitives.push("service");
13924
+ logInfo("Added 'service' \u2014 the events publisher lives in the api/ directory.");
13925
+ }
13856
13926
  await runComposable(tenant, name, primitives, options.test);
13857
13927
  });
13858
13928
  }
@@ -13961,6 +14031,8 @@ async function runComposable(tenant, name, primitives, test) {
13961
14031
  const repoRoot2 = resolveTargetRoot(process.cwd());
13962
14032
  const mesh = workspaceDeps ? { range: "", resolved: true } : resolvePublishedRange("@mesh-tech/app-kit", repoRoot2);
13963
14033
  const esmBundle = workspaceDeps ? { range: "", resolved: true } : resolvePublishedRange("@mesh-tech/esm-bundle", repoRoot2);
14034
+ const eventsService = workspaceDeps || !primitives.includes("events") ? { range: "", resolved: true } : resolvePublishedRange("@mesh-tech/events-service", repoRoot2);
14035
+ const authz = workspaceDeps || !primitives.includes("events") ? { range: "", resolved: true } : resolvePublishedRange("@mesh-tech/authz", repoRoot2);
13964
14036
  if (!workspaceDeps) {
13965
14037
  logInfo(
13966
14038
  mesh.resolved ? `@mesh-tech/* pinned to ${mesh.range} (current published line)` : `Registry lookup unavailable \u2014 pinning @mesh-tech/* to ${mesh.range}; run \`pnpm up @mesh-tech/*\` once you have registry auth`
@@ -13973,10 +14045,13 @@ async function runComposable(tenant, name, primitives, test) {
13973
14045
  database: primitives.includes("database"),
13974
14046
  temporal: primitives.includes("temporal"),
13975
14047
  bucket: primitives.includes("bucket"),
14048
+ events: primitives.includes("events"),
13976
14049
  region,
13977
14050
  workspaceDeps,
13978
14051
  meshRange: mesh.range,
13979
14052
  esmBundleRange: esmBundle.range,
14053
+ eventsServiceRange: eventsService.range,
14054
+ authzRange: authz.range,
13980
14055
  typesNodeRange: SCAFFOLD_TOOLCHAIN["@types/node"],
13981
14056
  tsxRange: SCAFFOLD_TOOLCHAIN.tsx,
13982
14057
  typescriptRange: SCAFFOLD_TOOLCHAIN.typescript,
@@ -14091,7 +14166,8 @@ var init_create_app = __esm({
14091
14166
  service: "HTTP Service (API with Hono)",
14092
14167
  database: "Database (PostgreSQL via Prisma)",
14093
14168
  temporal: "Temporal (workflow orchestration)",
14094
- bucket: "S3 Bucket (file storage)"
14169
+ bucket: "S3 Bucket (file storage)",
14170
+ events: "Events and webhooks (publish, subscriptions, signed delivery)"
14095
14171
  };
14096
14172
  HUB_ACCOUNTS = {
14097
14173
  mesh: "159923586610"