@mesh-tech/mesh-cli 0.20.0 → 0.20.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.
Files changed (41) hide show
  1. package/README.md +7 -3
  2. package/dist/bin/mesh.js +186 -15
  3. package/dist/bin/mesh.js.map +4 -4
  4. package/dist/build-info.json +2 -2
  5. package/dist/src/commands/app-check.d.ts +1 -1
  6. package/dist/src/commands/app-check.d.ts.map +1 -1
  7. package/dist/src/commands/app-check.js +13 -0
  8. package/dist/src/commands/app-check.js.map +1 -1
  9. package/dist/src/commands/local/hub-local.d.ts +35 -0
  10. package/dist/src/commands/local/hub-local.d.ts.map +1 -1
  11. package/dist/src/commands/local/hub-local.js +61 -1
  12. package/dist/src/commands/local/hub-local.js.map +1 -1
  13. package/dist/src/commands/local/index.d.ts.map +1 -1
  14. package/dist/src/commands/local/index.js +15 -1
  15. package/dist/src/commands/local/index.js.map +1 -1
  16. package/dist/src/commands/local/seed-hub-catalog.d.ts +33 -0
  17. package/dist/src/commands/local/seed-hub-catalog.d.ts.map +1 -0
  18. package/dist/src/commands/local/seed-hub-catalog.js +90 -0
  19. package/dist/src/commands/local/seed-hub-catalog.js.map +1 -0
  20. package/dist/src/commands/login.d.ts +12 -0
  21. package/dist/src/commands/login.d.ts.map +1 -1
  22. package/dist/src/commands/login.js +52 -7
  23. package/dist/src/commands/login.js.map +1 -1
  24. package/dist/src/commands/registry.d.ts +22 -3
  25. package/dist/src/commands/registry.d.ts.map +1 -1
  26. package/dist/src/commands/registry.js +36 -4
  27. package/dist/src/commands/registry.js.map +1 -1
  28. package/dist/src/commands/temporal.js +20 -6
  29. package/dist/src/commands/temporal.js.map +2 -2
  30. package/dist/src/utils/aws-auth.d.ts +19 -0
  31. package/dist/src/utils/aws-auth.d.ts.map +1 -1
  32. package/dist/src/utils/aws-auth.js +30 -9
  33. package/dist/src/utils/aws-auth.js.map +1 -1
  34. package/dist/src/utils/registry-identity.d.ts +35 -0
  35. package/dist/src/utils/registry-identity.d.ts.map +1 -1
  36. package/dist/src/utils/registry-identity.js +47 -1
  37. package/dist/src/utils/registry-identity.js.map +1 -1
  38. package/package.json +2 -2
  39. package/skills/core/SKILL.md +1 -1
  40. package/templates/api-auth/index.ts.hbs +3 -1
  41. package/templates/api-role-gating/index.ts.hbs +3 -1
package/README.md CHANGED
@@ -64,7 +64,9 @@ trip).
64
64
  **If your account does not hold the grant yet**, the command tells you so and
65
65
  prints the access-request URL the platform returned (or names the admin to
66
66
  ask). Contact your Mesh contact to have **registry access granted to your
67
- user**, then run `mesh registry login` again.
67
+ user**, then run `mesh registry login` again — it re-mints your sign-in so the
68
+ new role is in the token the broker sees. If it still says not authorized,
69
+ start a fresh sign-in: `mesh registry logout && mesh registry login`.
68
70
 
69
71
  Everything after this point — installing `@mesh-tech/*` packages, scaffolding
70
72
  an app, and the local docs server — depends on this step. `mesh init` (next)
@@ -241,8 +243,10 @@ everywhere else.
241
243
 
242
244
  Your account and session are fine; you don't hold the registry grant yet. The
243
245
  message names the request URL (or the admin to ask) — see
244
- [step 2](#2-get-registry-access). Once granted, `mesh registry login` again;
245
- your sign-in is kept.
246
+ [step 2](#2-get-registry-access). Once granted, `mesh registry login` again
247
+ it re-mints your sign-in so the new role reaches the broker (a role only rides
248
+ in tokens issued after it was granted). Still refused?
249
+ `mesh registry logout && mesh registry login` starts a fresh sign-in.
246
250
 
247
251
  ### `No registry access on this machine — @mesh-tech packages cannot be installed.`
248
252
 
package/dist/bin/mesh.js CHANGED
@@ -3274,6 +3274,7 @@ __export(login_exports, {
3274
3274
  discoverConfigForContext: () => discoverConfigForContext,
3275
3275
  discoverConfigFromWellKnown: () => discoverConfigFromWellKnown,
3276
3276
  ensureLogin: () => ensureLogin,
3277
+ forceRefreshToken: () => forceRefreshToken,
3277
3278
  getContextConfig: () => getContextConfig,
3278
3279
  getValidToken: () => getValidToken,
3279
3280
  isRemoteEnvironment: () => isRemoteEnvironment,
@@ -3993,22 +3994,43 @@ async function getValidToken(context, opts = {}) {
3993
3994
  if (tokenStillValid(creds.expiresAt, opts.marginMs ?? 0)) {
3994
3995
  return creds.idToken;
3995
3996
  }
3997
+ return remintToken(context, config, creds);
3998
+ }
3999
+ async function remintToken(context, config, creds) {
3996
4000
  if (!creds.refreshToken) return null;
4001
+ let tokens;
4002
+ try {
4003
+ tokens = await refreshTokens(config.issuer, config.clientId, creds.refreshToken);
4004
+ } catch {
4005
+ return null;
4006
+ }
4007
+ let email = creds.email;
4008
+ try {
4009
+ email = decodeJwtPayload(tokens.id_token).email ?? creds.email;
4010
+ } catch {
4011
+ }
3997
4012
  try {
3998
- const tokens = await refreshTokens(config.issuer, config.clientId, creds.refreshToken);
3999
- const idPayload = decodeJwtPayload(tokens.id_token);
4000
4013
  writeCredentials(context, {
4001
4014
  ...creds,
4002
4015
  idToken: tokens.id_token,
4003
4016
  accessToken: tokens.access_token,
4004
4017
  refreshToken: tokens.refresh_token ?? creds.refreshToken,
4005
4018
  expiresAt: new Date(Date.now() + tokens.expires_in * 1e3).toISOString(),
4006
- email: idPayload.email ?? creds.email
4019
+ email
4007
4020
  });
4008
- return tokens.id_token;
4009
- } catch {
4010
- return null;
4021
+ } catch (err) {
4022
+ logWarn(
4023
+ `The refreshed ${context} session could not be cached (${err instanceof Error ? err.message : String(err)}) \u2014 this run continues on the new token, but the next one may need a fresh sign-in.`
4024
+ );
4011
4025
  }
4026
+ return tokens.id_token;
4027
+ }
4028
+ async function forceRefreshToken(context) {
4029
+ const config = getContextConfig(context);
4030
+ if (!config) return null;
4031
+ const creds = readCredentials(context);
4032
+ if (!creds) return null;
4033
+ return remintToken(context, config, creds);
4012
4034
  }
4013
4035
  async function probeCredentials(context, roleArn) {
4014
4036
  const cached = readCredentials(context);
@@ -4376,7 +4398,9 @@ __export(registry_identity_exports, {
4376
4398
  isReservedRegistryContext: () => isReservedRegistryContext,
4377
4399
  logRegistryIdentity: () => logRegistryIdentity,
4378
4400
  readRegistrySession: () => readRegistrySession,
4401
+ refreshRegistrySession: () => refreshRegistrySession,
4379
4402
  registrySessionCandidates: () => registrySessionCandidates,
4403
+ registryTokenRoles: () => registryTokenRoles,
4380
4404
  resolveRegistryIdentity: () => resolveRegistryIdentity
4381
4405
  });
4382
4406
  function isReservedRegistryContext(context) {
@@ -4480,6 +4504,22 @@ async function ensureRegistrySession(opts) {
4480
4504
  expiresAt: creds.expiresAt
4481
4505
  };
4482
4506
  }
4507
+ function registryTokenRoles(idToken) {
4508
+ if (!idToken) return [];
4509
+ try {
4510
+ return projectRoleKeys(decodeJwtPayload(idToken)).sort();
4511
+ } catch {
4512
+ return [];
4513
+ }
4514
+ }
4515
+ async function refreshRegistrySession(context) {
4516
+ const before = registryTokenRoles(readCredentials(context)?.idToken);
4517
+ const idToken = await forceRefreshToken(context);
4518
+ if (!idToken) return { reminted: false, rolesChanged: false };
4519
+ const after = registryTokenRoles(idToken);
4520
+ const same = after.length === before.length && after.every((role, i) => role === before[i]);
4521
+ return { reminted: true, rolesChanged: !same };
4522
+ }
4483
4523
  function clearRegistrySession() {
4484
4524
  clearCredentials(REGISTRY_CREDENTIAL_KEY);
4485
4525
  clearContextConfig(REGISTRY_CREDENTIAL_KEY);
@@ -4508,6 +4548,7 @@ var init_registry_identity = __esm({
4508
4548
  "libs/mesh-cli/src/utils/registry-identity.ts"() {
4509
4549
  "use strict";
4510
4550
  init_login();
4551
+ init_aws_auth();
4511
4552
  init_first_party_contexts();
4512
4553
  init_registry_broker();
4513
4554
  init_errors();
@@ -4985,16 +5026,20 @@ function selectRoleForCaller(idToken, options) {
4985
5026
  const adminRoles = options.adminClaimRoles ?? ["mesh.platform:admin"];
4986
5027
  const claims = decodeJwtPayload2(idToken);
4987
5028
  if (!claims) return options.defaultRole;
5029
+ const claimedRoles = projectRoleKeys(claims);
5030
+ return adminRoles.some((target) => claimedRoles.includes(target)) ? options.adminRole : options.defaultRole;
5031
+ }
5032
+ function projectRoleKeys(claims) {
5033
+ const names = /* @__PURE__ */ new Set();
4988
5034
  for (const [key, value] of Object.entries(claims)) {
4989
5035
  if (!key.startsWith("urn:zitadel:iam:org:project:")) continue;
4990
5036
  if (!key.endsWith(":roles")) continue;
4991
- if (!value || typeof value !== "object") continue;
4992
- const claimedRoles = Object.keys(value);
4993
- for (const target of adminRoles) {
4994
- if (claimedRoles.includes(target)) return options.adminRole;
5037
+ if (!value || typeof value !== "object" || Array.isArray(value)) continue;
5038
+ for (const name of Object.keys(value)) {
5039
+ if (name) names.add(name);
4995
5040
  }
4996
5041
  }
4997
- return options.defaultRole;
5042
+ return [...names];
4998
5043
  }
4999
5044
  async function resolveAwsCredentials(roleArn, appRoot, stack) {
5000
5045
  const roleName = roleArn.split("/").pop() ?? roleArn;
@@ -10748,6 +10793,11 @@ function scanApp(root, appRel) {
10748
10793
  for (const f of files) hit(code, rel(root, f), why);
10749
10794
  };
10750
10795
  anti("IAC_GRANTS", RE_IAC_GRANT, "per-user role grant baked into IaC \u2014 grants belong in the Hub (Access \u2192 People), not in a config redeploy");
10796
+ anti(
10797
+ "POINTER_PROJECT_OVERRIDE",
10798
+ RE_POINTER_OVERRIDE,
10799
+ "AppAuthzPointer is given a `zitadel:` override \u2014 an app binds its project with AppEnvironment's zitadelAppProjectId so every consumer of env.zitadel agrees; the override is reserved for the Hub"
10800
+ );
10751
10801
  anti(
10752
10802
  "PASSWORD_STORE",
10753
10803
  RE_PASSWORD,
@@ -10857,7 +10907,7 @@ function registerAppCommands(program2) {
10857
10907
  }
10858
10908
  });
10859
10909
  }
10860
- 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_PASSWORD, RE_ALLOWLIST, RE_USERS_TABLE;
10910
+ 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;
10861
10911
  var init_app_check = __esm({
10862
10912
  "libs/mesh-cli/src/commands/app-check.ts"() {
10863
10913
  "use strict";
@@ -10929,6 +10979,13 @@ var init_app_check = __esm({
10929
10979
  remediation: "remove the per-user grant map; grant roles from the Hub (App \u2192 Access \u2192 People)",
10930
10980
  scope: "app"
10931
10981
  },
10982
+ POINTER_PROJECT_OVERRIDE: {
10983
+ gate: "H",
10984
+ title: "Pointer publishes the env's project",
10985
+ severity: "advisory",
10986
+ remediation: "drop `zitadel:` from AppAuthzPointer and pass `zitadelAppProjectId: identity.projectId` to AppEnvironment \u2014 the override is the Hub's",
10987
+ scope: "app"
10988
+ },
10932
10989
  PASSWORD_STORE: {
10933
10990
  gate: "H",
10934
10991
  title: "No app-side credentials",
@@ -10976,6 +11033,7 @@ var init_app_check = __esm({
10976
11033
  RE_POINTER = /AppAuthzPointer/;
10977
11034
  RE_METADATA = /compileOpsHubMetadata|ops-hub-metadata|SpiceDBSchema/;
10978
11035
  RE_IAC_GRANT = /userId: *"[0-9]{6,}"/;
11036
+ RE_POINTER_OVERRIDE = /\bzitadel: *\{\s*projectId\b/;
10979
11037
  RE_PASSWORD = /(^|[^a-z])(bcrypt|argon2)|password_hash|passwordHash|hashPassword|temporaryPassword.*(send|mail)|(send|mail).*temporaryPassword/;
10980
11038
  RE_ALLOWLIST = /ADMIN_EMAILS|ALLOWED_EMAILS|allowedEmails|adminEmails/;
10981
11039
  RE_USERS_TABLE = /create table (if not exists )?"?(users|roles|user_roles|permissions)"?/i;
@@ -16159,6 +16217,7 @@ __export(registry_exports, {
16159
16217
  resolvePublisherRoleArn: () => resolvePublisherRoleArn,
16160
16218
  runRegistryLogin: () => runRegistryLogin,
16161
16219
  runRegistryStatus: () => runRegistryStatus,
16220
+ shouldRemintAfter: () => shouldRemintAfter,
16162
16221
  shouldRepairUserNpmrc: () => shouldRepairUserNpmrc
16163
16222
  });
16164
16223
  import { execFileSync as execFileSync24 } from "child_process";
@@ -16422,6 +16481,9 @@ function describeBrokerFailure(failure) {
16422
16481
  function registrySignInAllowed(opts, hasTty = Boolean(process.stdin.isTTY && process.stdout.isTTY)) {
16423
16482
  return Boolean(opts.device) || hasTty;
16424
16483
  }
16484
+ function shouldRemintAfter(failure) {
16485
+ return failure.kind === "not-authorized";
16486
+ }
16425
16487
  async function tryBrokerLogin(opts) {
16426
16488
  if (opts.broker === false) return { outcome: { kind: "skipped-flag", flag: "--no-broker" } };
16427
16489
  if (opts.ci) return { outcome: { kind: "skipped-flag", flag: "--ci" } };
@@ -16461,6 +16523,13 @@ async function tryBrokerLogin(opts) {
16461
16523
  result = await requestGrant(session.context, identity);
16462
16524
  }
16463
16525
  }
16526
+ if (!result.ok && shouldRemintAfter(result.failure)) {
16527
+ const { rolesChanged } = await refreshRegistrySession(session.context);
16528
+ if (rolesChanged) {
16529
+ logInfo("Your sign-in now carries a role it did not have \u2014 retrying\u2026");
16530
+ result = await requestGrant(session.context, identity);
16531
+ }
16532
+ }
16464
16533
  if (result.ok) return { grant: result.grant };
16465
16534
  const { message, hint, fatal } = describeBrokerFailure(result.failure);
16466
16535
  if (fatal) {
@@ -16810,7 +16879,7 @@ var init_registry = __esm({
16810
16879
  this.name = "RegistryNotAuthorizedError";
16811
16880
  }
16812
16881
  };
16813
- NOT_AUTHORIZED_RERUN = "Once granted, run: mesh registry login (your sign-in is kept; no second browser trip)";
16882
+ NOT_AUTHORIZED_RERUN = "Once granted, run: mesh registry login (it re-mints your sign-in; still refused? mesh registry logout && mesh registry login)";
16814
16883
  CONTEXT_IGNORED_NOTICE = "The registry is global \u2014 the context argument is no longer needed (ignored).";
16815
16884
  }
16816
16885
  });
@@ -17776,6 +17845,32 @@ async function ensureHubImages() {
17776
17845
  }
17777
17846
  return version;
17778
17847
  }
17848
+ function tarballNameForImageVersion(version) {
17849
+ if (version.endsWith("-src")) return null;
17850
+ return `mesh-tech-hub-${version.replace(/-r\d+$/, "")}.tgz`;
17851
+ }
17852
+ function readHubCompiledAuthz(version) {
17853
+ const context = path40.join(cacheDir(), `context-${version}`);
17854
+ const file = path40.join(context, HUB_COMPILED_AUTHZ_REL);
17855
+ if (!fs33.existsSync(file)) {
17856
+ const tarball = tarballNameForImageVersion(version);
17857
+ if (!tarball || !fs33.existsSync(path40.join(cacheDir(), tarball))) return null;
17858
+ fs33.mkdirSync(context, { recursive: true });
17859
+ try {
17860
+ execFileSync26(
17861
+ "tar",
17862
+ ["-xzf", path40.join(cacheDir(), tarball), "-C", context, "--strip-components", "1", `package/${HUB_COMPILED_AUTHZ_REL}`],
17863
+ { stdio: ["ignore", "pipe", "pipe"] }
17864
+ );
17865
+ } catch {
17866
+ return null;
17867
+ }
17868
+ if (!fs33.existsSync(file)) return null;
17869
+ }
17870
+ const parsed = JSON.parse(fs33.readFileSync(file, "utf-8"));
17871
+ if (typeof parsed.zed !== "string" || !parsed.metadata || typeof parsed.metadata !== "object") return null;
17872
+ return { zed: parsed.zed, metadata: parsed.metadata };
17873
+ }
17779
17874
  function readWorkspaceCatalog(repoRoot2) {
17780
17875
  const text = fs33.readFileSync(path40.join(repoRoot2, "pnpm-workspace.yaml"), "utf-8");
17781
17876
  const marker = "\ncatalog:\n";
@@ -17790,7 +17885,7 @@ function readWorkspaceCatalog(repoRoot2) {
17790
17885
  return catalog;
17791
17886
  }
17792
17887
  function normalizeHubManifests(contextDir, catalog) {
17793
- const DEP_FIELDS = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"];
17888
+ const DEP_FIELDS = ["dependencies", "optionalDependencies", "peerDependencies"];
17794
17889
  const touched = [];
17795
17890
  for (const entry of fs33.readdirSync(contextDir, { withFileTypes: true })) {
17796
17891
  if (!entry.isDirectory()) continue;
@@ -17798,6 +17893,10 @@ function normalizeHubManifests(contextDir, catalog) {
17798
17893
  if (!fs33.existsSync(manifest)) continue;
17799
17894
  const pkg = JSON.parse(fs33.readFileSync(manifest, "utf-8"));
17800
17895
  let changed = false;
17896
+ if (pkg.devDependencies) {
17897
+ delete pkg.devDependencies;
17898
+ changed = true;
17899
+ }
17801
17900
  for (const field of DEP_FIELDS) {
17802
17901
  const deps = pkg[field];
17803
17902
  if (!deps) continue;
@@ -17873,7 +17972,7 @@ async function buildHubImagesFromSource(repoRoot2) {
17873
17972
  }
17874
17973
  return version;
17875
17974
  }
17876
- var execFileAsync2, HUB_PACKAGE, HUB_IMAGES, LOCAL_IMAGE_REV, HUB_AUTH_IMAGE;
17975
+ var execFileAsync2, HUB_PACKAGE, HUB_IMAGES, LOCAL_IMAGE_REV, HUB_AUTH_IMAGE, HUB_COMPILED_AUTHZ_REL;
17877
17976
  var init_hub_local = __esm({
17878
17977
  "libs/mesh-cli/src/commands/local/hub-local.ts"() {
17879
17978
  "use strict";
@@ -17887,6 +17986,66 @@ var init_hub_local = __esm({
17887
17986
  HUB_IMAGES = ["mesh-local-hub-api", "mesh-local-hub-ui"];
17888
17987
  LOCAL_IMAGE_REV = "r2";
17889
17988
  HUB_AUTH_IMAGE = "mesh-local-hub-auth:v7.7.1-r1";
17989
+ HUB_COMPILED_AUTHZ_REL = "api/dist/authz/compiled.json";
17990
+ }
17991
+ });
17992
+
17993
+ // libs/mesh-cli/src/commands/local/seed-hub-catalog.ts
17994
+ function withHubCatalogRefs(pointer, metadataRef) {
17995
+ return {
17996
+ spicedb: { instanceRefs: [] },
17997
+ ...pointer,
17998
+ opsHubMetadataRef: metadataRef,
17999
+ mode: typeof pointer.mode === "string" ? pointer.mode : "policy-engine"
18000
+ };
18001
+ }
18002
+ async function publishHubAuthzCatalog(compiled, awsConfig) {
18003
+ const { SSMClient: SSMClient5, GetParameterCommand: GetParameterCommand2, PutParameterCommand } = await import("@aws-sdk/client-ssm");
18004
+ const ssm = new SSMClient5(awsConfig);
18005
+ await ssm.send(
18006
+ new PutParameterCommand({
18007
+ Name: HUB_OPS_HUB_METADATA_PARAM,
18008
+ Type: "String",
18009
+ // The blob is ~7.5 KB — past the 4 KB standard tier, within Advanced (8 KB),
18010
+ // the tier the platform's Export uses for the same param.
18011
+ Tier: "Advanced",
18012
+ Overwrite: true,
18013
+ Value: JSON.stringify(compiled.metadata),
18014
+ Description: "Hub role catalog \u2014 ops-hub-metadata (local analog of MeshHub's SpiceDBSchema export)"
18015
+ })
18016
+ );
18017
+ let current = {};
18018
+ try {
18019
+ const existing = await ssm.send(new GetParameterCommand2({ Name: HUB_AUTHZ_POINTER_PARAM }));
18020
+ current = JSON.parse(existing.Parameter?.Value ?? "{}");
18021
+ } catch {
18022
+ }
18023
+ await ssm.send(
18024
+ new PutParameterCommand({
18025
+ Name: HUB_AUTHZ_POINTER_PARAM,
18026
+ Type: "String",
18027
+ Overwrite: true,
18028
+ Value: JSON.stringify(withHubCatalogRefs(current, HUB_OPS_HUB_METADATA_PARAM)),
18029
+ Description: "Hub authz pointer (local analog of the platform Pulumi program)"
18030
+ })
18031
+ );
18032
+ return { bundles: catalogBundleNames(compiled.metadata) };
18033
+ }
18034
+ function catalogBundleNames(metadata) {
18035
+ const bundles = metadata.bundles;
18036
+ if (Array.isArray(bundles)) {
18037
+ return bundles.map((b) => b && typeof b === "object" && typeof b.name === "string" ? b.name : null).filter((n) => n !== null);
18038
+ }
18039
+ if (bundles && typeof bundles === "object") return Object.keys(bundles);
18040
+ return [];
18041
+ }
18042
+ var HUB_AUTHZ_POINTER_PARAM, HUB_OPS_HUB_METADATA_PARAM;
18043
+ var init_seed_hub_catalog = __esm({
18044
+ "libs/mesh-cli/src/commands/local/seed-hub-catalog.ts"() {
18045
+ "use strict";
18046
+ init_seed();
18047
+ HUB_AUTHZ_POINTER_PARAM = `/mesh-platform/${LOCAL_TENANT}/${LOCAL_ENV}/apps/hub/stacks/local/authz`;
18048
+ HUB_OPS_HUB_METADATA_PARAM = `${HUB_AUTHZ_POINTER_PARAM}/ops-hub-metadata`;
17890
18049
  }
17891
18050
  });
17892
18051
 
@@ -18063,6 +18222,17 @@ Re-running start from here may recreate shared containers with this checkout's c
18063
18222
  logInfo("Seeding Zitadel (Platform project, Mesh CLI app, Hub auth, test users)\u2026");
18064
18223
  const zitadel = await seedZitadel(LOCAL_AWS_CONFIG);
18065
18224
  hubAuth = zitadel.hubAuth;
18225
+ if (hubVersion) {
18226
+ const compiled = readHubCompiledAuthz(hubVersion);
18227
+ if (compiled) {
18228
+ const { bundles } = await publishHubAuthzCatalog(compiled, LOCAL_AWS_CONFIG);
18229
+ logSuccess(`Hub role catalog seeded: ${bundles.length} role(s) (${bundles.join(", ")})`);
18230
+ } else {
18231
+ logWarn(
18232
+ `Hub v${hubVersion} ships no compiled role catalog (api/dist/authz/compiled.json) \u2014 Users \u2192 Roles will be empty. Hub \u2265 2.1.0 publishes one.`
18233
+ );
18234
+ }
18235
+ }
18066
18236
  try {
18067
18237
  const reconciled = await reconcileRegistryFromZitadel();
18068
18238
  if (reconciled.tenants.length > 0) {
@@ -18230,6 +18400,7 @@ var init_local = __esm({
18230
18400
  init_auth_provision();
18231
18401
  init_seed();
18232
18402
  init_seed_zitadel();
18403
+ init_seed_hub_catalog();
18233
18404
  WAIT_TIMEOUT_MS = 18e4;
18234
18405
  WAIT_POLL_MS = 3e3;
18235
18406
  }