@mesh-tech/mesh-cli 0.19.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 (61) hide show
  1. package/README.md +7 -3
  2. package/dist/bin/mesh.js +289 -28
  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/dev.d.ts +7 -0
  10. package/dist/src/commands/dev.d.ts.map +1 -1
  11. package/dist/src/commands/dev.js +1 -0
  12. package/dist/src/commands/dev.js.map +1 -1
  13. package/dist/src/commands/hub/index.d.ts.map +1 -1
  14. package/dist/src/commands/hub/index.js +5 -0
  15. package/dist/src/commands/hub/index.js.map +1 -1
  16. package/dist/src/commands/local/auth-provision.d.ts +4 -0
  17. package/dist/src/commands/local/auth-provision.d.ts.map +1 -1
  18. package/dist/src/commands/local/auth-provision.js +20 -0
  19. package/dist/src/commands/local/auth-provision.js.map +1 -1
  20. package/dist/src/commands/local/dev-local.d.ts +32 -0
  21. package/dist/src/commands/local/dev-local.d.ts.map +1 -1
  22. package/dist/src/commands/local/dev-local.js +82 -2
  23. package/dist/src/commands/local/dev-local.js.map +1 -1
  24. package/dist/src/commands/local/hub-local.d.ts +35 -0
  25. package/dist/src/commands/local/hub-local.d.ts.map +1 -1
  26. package/dist/src/commands/local/hub-local.js +61 -1
  27. package/dist/src/commands/local/hub-local.js.map +1 -1
  28. package/dist/src/commands/local/index.d.ts.map +1 -1
  29. package/dist/src/commands/local/index.js +15 -1
  30. package/dist/src/commands/local/index.js.map +1 -1
  31. package/dist/src/commands/local/seed-hub-catalog.d.ts +33 -0
  32. package/dist/src/commands/local/seed-hub-catalog.d.ts.map +1 -0
  33. package/dist/src/commands/local/seed-hub-catalog.js +90 -0
  34. package/dist/src/commands/local/seed-hub-catalog.js.map +1 -0
  35. package/dist/src/commands/local/seed.d.ts +9 -0
  36. package/dist/src/commands/local/seed.d.ts.map +1 -1
  37. package/dist/src/commands/local/seed.js +49 -10
  38. package/dist/src/commands/local/seed.js.map +1 -1
  39. package/dist/src/commands/login.d.ts +12 -0
  40. package/dist/src/commands/login.d.ts.map +1 -1
  41. package/dist/src/commands/login.js +52 -7
  42. package/dist/src/commands/login.js.map +1 -1
  43. package/dist/src/commands/registry.d.ts +22 -3
  44. package/dist/src/commands/registry.d.ts.map +1 -1
  45. package/dist/src/commands/registry.js +36 -4
  46. package/dist/src/commands/registry.js.map +1 -1
  47. package/dist/src/commands/temporal.js +37 -7
  48. package/dist/src/commands/temporal.js.map +2 -2
  49. package/dist/src/utils/aws-auth.d.ts +19 -0
  50. package/dist/src/utils/aws-auth.d.ts.map +1 -1
  51. package/dist/src/utils/aws-auth.js +30 -9
  52. package/dist/src/utils/aws-auth.js.map +1 -1
  53. package/dist/src/utils/registry-identity.d.ts +35 -0
  54. package/dist/src/utils/registry-identity.d.ts.map +1 -1
  55. package/dist/src/utils/registry-identity.js +47 -1
  56. package/dist/src/utils/registry-identity.js.map +1 -1
  57. package/package.json +2 -2
  58. package/skills/core/SKILL.md +3 -3
  59. package/templates/api-auth/index.ts.hbs +3 -1
  60. package/templates/api-role-gating/index.ts.hbs +3 -1
  61. package/templates/apps-repo/gitignore +4 -0
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
@@ -767,22 +767,35 @@ async function seedLocalPlatform() {
767
767
  );
768
768
  await registerTenantEnv(LOCAL_TENANT, { tier: "hub" });
769
769
  logSuccess(`Registered tenant '${LOCAL_TENANT}' env '${LOCAL_ENV}' \u2192 ${APP_TENANTS_PARAM}`);
770
- const { S3Client, CreateBucketCommand } = await import("@aws-sdk/client-s3");
770
+ const { S3Client, CreateBucketCommand, PutObjectCommand } = await import("@aws-sdk/client-s3");
771
771
  const s3 = new S3Client({ ...AWS_CONFIG, forcePathStyle: true });
772
- try {
773
- await s3.send(new CreateBucketCommand({ Bucket: ARTIFACTS_BUCKET }));
774
- logSuccess(`Created artifacts bucket s3://${ARTIFACTS_BUCKET}`);
775
- } catch (err) {
776
- if (err?.name === "BucketAlreadyOwnedByYou" || err?.name === "BucketAlreadyExists") {
777
- logInfo(`Artifacts bucket s3://${ARTIFACTS_BUCKET} already exists`);
778
- } else {
779
- throw err;
772
+ for (const bucket of [ARTIFACTS_BUCKET, DATA_BUCKET]) {
773
+ try {
774
+ await s3.send(new CreateBucketCommand({ Bucket: bucket }));
775
+ logSuccess(`Created bucket s3://${bucket}`);
776
+ } catch (err) {
777
+ if (err?.name === "BucketAlreadyOwnedByYou" || err?.name === "BucketAlreadyExists") {
778
+ logInfo(`Bucket s3://${bucket} already exists`);
779
+ } else {
780
+ throw err;
781
+ }
780
782
  }
781
783
  }
784
+ for (const seed of DATA_BUCKET_SEEDS) {
785
+ await s3.send(
786
+ new PutObjectCommand({
787
+ Bucket: DATA_BUCKET,
788
+ Key: seed.key,
789
+ Body: seed.body,
790
+ ContentType: "application/json"
791
+ })
792
+ );
793
+ }
794
+ logSuccess(`Seeded s3://${DATA_BUCKET} with ${DATA_BUCKET_SEEDS.length} sample object(s)`);
782
795
  await seedTemporalNamespace();
783
796
  return { parameter: APP_TENANTS_PARAM, bucket: ARTIFACTS_BUCKET };
784
797
  }
785
- var LOCAL_TENANT, LOCAL_ENV, LOCAL_AWS_ENDPOINT, LOCAL_AWS_REGION, ARTIFACTS_BUCKET, APP_TENANTS_PARAM, LOCAL_AWS_CONFIG, AWS_CONFIG, FABRIC_CHECK_PATH, TEMPORAL_NAMESPACE, TEMPORAL_ADDRESS;
798
+ var LOCAL_TENANT, LOCAL_ENV, LOCAL_AWS_ENDPOINT, LOCAL_AWS_REGION, ARTIFACTS_BUCKET, DATA_BUCKET, APP_TENANTS_PARAM, DATA_BUCKET_SEEDS, LOCAL_AWS_CONFIG, AWS_CONFIG, FABRIC_CHECK_PATH, TEMPORAL_NAMESPACE, TEMPORAL_ADDRESS;
786
799
  var init_seed = __esm({
787
800
  "libs/mesh-cli/src/commands/local/seed.ts"() {
788
801
  "use strict";
@@ -793,7 +806,24 @@ var init_seed = __esm({
793
806
  LOCAL_AWS_ENDPOINT = "http://localhost:4566";
794
807
  LOCAL_AWS_REGION = "us-east-2";
795
808
  ARTIFACTS_BUCKET = "mesh-local-artifacts";
809
+ DATA_BUCKET = "mesh-local-data";
796
810
  APP_TENANTS_PARAM = `/mesh-platform/${LOCAL_TENANT}/${LOCAL_ENV}/app-tenants`;
811
+ DATA_BUCKET_SEEDS = [
812
+ {
813
+ key: "samples/pipes/fragment-0001.json",
814
+ body: JSON.stringify(
815
+ {
816
+ id: "sample-0001",
817
+ kind: "pipes-fragment",
818
+ account: "0001",
819
+ asOf: "2026-01-01",
820
+ lines: [{ amount: "125.00", currency: "USD", description: "Sample credit" }]
821
+ },
822
+ null,
823
+ 2
824
+ )
825
+ }
826
+ ];
797
827
  LOCAL_AWS_CONFIG = {
798
828
  endpoint: LOCAL_AWS_ENDPOINT,
799
829
  region: LOCAL_AWS_REGION,
@@ -3244,6 +3274,7 @@ __export(login_exports, {
3244
3274
  discoverConfigForContext: () => discoverConfigForContext,
3245
3275
  discoverConfigFromWellKnown: () => discoverConfigFromWellKnown,
3246
3276
  ensureLogin: () => ensureLogin,
3277
+ forceRefreshToken: () => forceRefreshToken,
3247
3278
  getContextConfig: () => getContextConfig,
3248
3279
  getValidToken: () => getValidToken,
3249
3280
  isRemoteEnvironment: () => isRemoteEnvironment,
@@ -3963,22 +3994,43 @@ async function getValidToken(context, opts = {}) {
3963
3994
  if (tokenStillValid(creds.expiresAt, opts.marginMs ?? 0)) {
3964
3995
  return creds.idToken;
3965
3996
  }
3997
+ return remintToken(context, config, creds);
3998
+ }
3999
+ async function remintToken(context, config, creds) {
3966
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
+ }
3967
4012
  try {
3968
- const tokens = await refreshTokens(config.issuer, config.clientId, creds.refreshToken);
3969
- const idPayload = decodeJwtPayload(tokens.id_token);
3970
4013
  writeCredentials(context, {
3971
4014
  ...creds,
3972
4015
  idToken: tokens.id_token,
3973
4016
  accessToken: tokens.access_token,
3974
4017
  refreshToken: tokens.refresh_token ?? creds.refreshToken,
3975
4018
  expiresAt: new Date(Date.now() + tokens.expires_in * 1e3).toISOString(),
3976
- email: idPayload.email ?? creds.email
4019
+ email
3977
4020
  });
3978
- return tokens.id_token;
3979
- } catch {
3980
- 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
+ );
3981
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);
3982
4034
  }
3983
4035
  async function probeCredentials(context, roleArn) {
3984
4036
  const cached = readCredentials(context);
@@ -4346,7 +4398,9 @@ __export(registry_identity_exports, {
4346
4398
  isReservedRegistryContext: () => isReservedRegistryContext,
4347
4399
  logRegistryIdentity: () => logRegistryIdentity,
4348
4400
  readRegistrySession: () => readRegistrySession,
4401
+ refreshRegistrySession: () => refreshRegistrySession,
4349
4402
  registrySessionCandidates: () => registrySessionCandidates,
4403
+ registryTokenRoles: () => registryTokenRoles,
4350
4404
  resolveRegistryIdentity: () => resolveRegistryIdentity
4351
4405
  });
4352
4406
  function isReservedRegistryContext(context) {
@@ -4450,6 +4504,22 @@ async function ensureRegistrySession(opts) {
4450
4504
  expiresAt: creds.expiresAt
4451
4505
  };
4452
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
+ }
4453
4523
  function clearRegistrySession() {
4454
4524
  clearCredentials(REGISTRY_CREDENTIAL_KEY);
4455
4525
  clearContextConfig(REGISTRY_CREDENTIAL_KEY);
@@ -4478,6 +4548,7 @@ var init_registry_identity = __esm({
4478
4548
  "libs/mesh-cli/src/utils/registry-identity.ts"() {
4479
4549
  "use strict";
4480
4550
  init_login();
4551
+ init_aws_auth();
4481
4552
  init_first_party_contexts();
4482
4553
  init_registry_broker();
4483
4554
  init_errors();
@@ -4955,16 +5026,20 @@ function selectRoleForCaller(idToken, options) {
4955
5026
  const adminRoles = options.adminClaimRoles ?? ["mesh.platform:admin"];
4956
5027
  const claims = decodeJwtPayload2(idToken);
4957
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();
4958
5034
  for (const [key, value] of Object.entries(claims)) {
4959
5035
  if (!key.startsWith("urn:zitadel:iam:org:project:")) continue;
4960
5036
  if (!key.endsWith(":roles")) continue;
4961
- if (!value || typeof value !== "object") continue;
4962
- const claimedRoles = Object.keys(value);
4963
- for (const target of adminRoles) {
4964
- 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);
4965
5040
  }
4966
5041
  }
4967
- return options.defaultRole;
5042
+ return [...names];
4968
5043
  }
4969
5044
  async function resolveAwsCredentials(roleArn, appRoot, stack) {
4970
5045
  const roleName = roleArn.split("/").pop() ?? roleArn;
@@ -6808,6 +6883,20 @@ async function registerLocalApp(args) {
6808
6883
  `Local service registration (${service})`
6809
6884
  );
6810
6885
  }
6886
+ for (const service of args.services) {
6887
+ if (args.kinds?.[service] !== "dagster") continue;
6888
+ const port = args.ports?.[service];
6889
+ if (!port) continue;
6890
+ await put2(
6891
+ `${base}/dagster`,
6892
+ {
6893
+ kind: "dagster",
6894
+ graphqlUrl: `http://host.docker.internal:${port}/graphql`,
6895
+ uiUrl: `http://localhost:${port}`
6896
+ },
6897
+ `Local Dagster discovery record (${service})`
6898
+ );
6899
+ }
6811
6900
  for (const service of args.services) {
6812
6901
  if (!/worker/i.test(service)) continue;
6813
6902
  await put2(
@@ -7127,6 +7216,22 @@ function localPlatformEnv(tenant, appName, serviceName) {
7127
7216
  }
7128
7217
  return env;
7129
7218
  }
7219
+ function isDagsterWorkspaceRoot(appRoot) {
7220
+ if (fs17.existsSync(path17.join(appRoot, "dg.toml"))) return true;
7221
+ const pyproject = path17.join(appRoot, "pyproject.toml");
7222
+ if (!fs17.existsSync(pyproject)) return false;
7223
+ try {
7224
+ return /^\s*\[tool\.dg[\].]/m.test(fs17.readFileSync(pyproject, "utf-8"));
7225
+ } catch {
7226
+ return false;
7227
+ }
7228
+ }
7229
+ function resolveDgBinary(appRoot) {
7230
+ for (const candidate of ["deployments/local/.venv/bin/dg", ".venv/bin/dg"]) {
7231
+ if (fs17.existsSync(path17.join(appRoot, candidate))) return candidate;
7232
+ }
7233
+ return "dg";
7234
+ }
7130
7235
  function hasDevScript(dir) {
7131
7236
  const pkgPath = path17.join(dir, "package.json");
7132
7237
  if (!fs17.existsSync(pkgPath)) return false;
@@ -7153,9 +7258,10 @@ function detectLocalServices(appRoot) {
7153
7258
  function buildLocalDevOutput(appRoot, tenant, opts = {}) {
7154
7259
  const detected = detectLocalServices(appRoot);
7155
7260
  const names = Object.keys(detected);
7156
- if (names.length === 0) {
7261
+ const isDagsterWorkspace = isDagsterWorkspaceRoot(appRoot);
7262
+ if (names.length === 0 && !isDagsterWorkspace) {
7157
7263
  throw new MeshCliError(
7158
- `No runnable services found under ${appRoot} \u2014 expected subdirectories (api/, worker/, \u2026) with a package.json 'dev' script.`,
7264
+ `No runnable services found under ${appRoot} \u2014 expected subdirectories (api/, worker/, \u2026) with a package.json 'dev' script, or a Dagster workspace (dg.toml, or [tool.dg] in pyproject.toml).`,
7159
7265
  { remediation: { command: "mesh create-app" } }
7160
7266
  );
7161
7267
  }
@@ -7203,6 +7309,25 @@ function buildLocalDevOutput(appRoot, tenant, opts = {}) {
7203
7309
  services[other].env[key] = { value: url };
7204
7310
  }
7205
7311
  }
7312
+ if (isDagsterWorkspace) {
7313
+ const port = BASE_PORT + names.length + mockNames.length;
7314
+ services["dagster"] = {
7315
+ // Absolute on purpose: a relative src is MONOREPO-ROOT-relative by the
7316
+ // ServiceDevOutput contract (rebaseServiceSrc), which for an app nested
7317
+ // in the monorepo would cd the launch shell to the repo root — where
7318
+ // neither dg.toml nor the venv path resolves. The absolute app root
7319
+ // survives rebasing in both the monorepo and standalone-repo shapes.
7320
+ src: appRoot,
7321
+ kind: "dagster",
7322
+ port,
7323
+ command: [resolveDgBinary(appRoot), "dev", "-h", "127.0.0.1", "-p", '"$PORT"'],
7324
+ env: {
7325
+ PORT: { value: String(port) },
7326
+ DAGSTER_HOME: { value: path17.join(appRoot, ".dagster") },
7327
+ ...localPlatformEnv(tenant, appName, "dagster")
7328
+ }
7329
+ };
7330
+ }
7206
7331
  mockNames.forEach((name, i) => {
7207
7332
  const decl = mocks[name];
7208
7333
  const serviceName = `mock-${name}`;
@@ -9349,6 +9474,11 @@ function registerDevCommand(program2) {
9349
9474
  ports: Object.fromEntries(
9350
9475
  Object.entries(devOutput2.services).map(([name, svc]) => [name, svc.port])
9351
9476
  ),
9477
+ kinds: Object.fromEntries(
9478
+ Object.entries(devOutput2.services).flatMap(
9479
+ ([name, svc]) => svc.kind ? [[name, svc.kind]] : []
9480
+ )
9481
+ ),
9352
9482
  // ExternalService.link() parity: consuming services carry the
9353
9483
  // external names, so the Hub shows consumers + per-app uptime.
9354
9484
  links: Object.values(selectedMocks).map((decl) => decl.external).filter((n) => !!n)
@@ -10663,6 +10793,11 @@ function scanApp(root, appRel) {
10663
10793
  for (const f of files) hit(code, rel(root, f), why);
10664
10794
  };
10665
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
+ );
10666
10801
  anti(
10667
10802
  "PASSWORD_STORE",
10668
10803
  RE_PASSWORD,
@@ -10772,7 +10907,7 @@ function registerAppCommands(program2) {
10772
10907
  }
10773
10908
  });
10774
10909
  }
10775
- 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;
10776
10911
  var init_app_check = __esm({
10777
10912
  "libs/mesh-cli/src/commands/app-check.ts"() {
10778
10913
  "use strict";
@@ -10844,6 +10979,13 @@ var init_app_check = __esm({
10844
10979
  remediation: "remove the per-user grant map; grant roles from the Hub (App \u2192 Access \u2192 People)",
10845
10980
  scope: "app"
10846
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
+ },
10847
10989
  PASSWORD_STORE: {
10848
10990
  gate: "H",
10849
10991
  title: "No app-side credentials",
@@ -10891,6 +11033,7 @@ var init_app_check = __esm({
10891
11033
  RE_POINTER = /AppAuthzPointer/;
10892
11034
  RE_METADATA = /compileOpsHubMetadata|ops-hub-metadata|SpiceDBSchema/;
10893
11035
  RE_IAC_GRANT = /userId: *"[0-9]{6,}"/;
11036
+ RE_POINTER_OVERRIDE = /\bzitadel: *\{\s*projectId\b/;
10894
11037
  RE_PASSWORD = /(^|[^a-z])(bcrypt|argon2)|password_hash|passwordHash|hashPassword|temporaryPassword.*(send|mail)|(send|mail).*temporaryPassword/;
10895
11038
  RE_ALLOWLIST = /ADMIN_EMAILS|ALLOWED_EMAILS|allowedEmails|adminEmails/;
10896
11039
  RE_USERS_TABLE = /create table (if not exists )?"?(users|roles|user_roles|permissions)"?/i;
@@ -15922,7 +16065,12 @@ var init_hub = __esm({
15922
16065
  TEMPORAL_UI_URL: "http://localhost:8233",
15923
16066
  // The stack's namespace. Absent, the Hub's compliance schedules fail to
15924
16067
  // register with "Temporal namespace is required".
15925
- TEMPORAL_NAMESPACE: "local-dev"
16068
+ TEMPORAL_NAMESPACE: "local-dev",
16069
+ // Dagster discovery records written by `mesh dev --local` carry
16070
+ // `host.docker.internal` (for the containerized `mesh start` hub). This hub
16071
+ // runs on the host, where that name does not resolve — tell the Data
16072
+ // section's resolver to swap it for localhost.
16073
+ MESH_DAGSTER_LOCAL_HOST: "localhost"
15926
16074
  });
15927
16075
  SENSITIVE_RE = /TOKEN|SECRET|KEY|PASSWORD/i;
15928
16076
  }
@@ -16069,6 +16217,7 @@ __export(registry_exports, {
16069
16217
  resolvePublisherRoleArn: () => resolvePublisherRoleArn,
16070
16218
  runRegistryLogin: () => runRegistryLogin,
16071
16219
  runRegistryStatus: () => runRegistryStatus,
16220
+ shouldRemintAfter: () => shouldRemintAfter,
16072
16221
  shouldRepairUserNpmrc: () => shouldRepairUserNpmrc
16073
16222
  });
16074
16223
  import { execFileSync as execFileSync24 } from "child_process";
@@ -16332,6 +16481,9 @@ function describeBrokerFailure(failure) {
16332
16481
  function registrySignInAllowed(opts, hasTty = Boolean(process.stdin.isTTY && process.stdout.isTTY)) {
16333
16482
  return Boolean(opts.device) || hasTty;
16334
16483
  }
16484
+ function shouldRemintAfter(failure) {
16485
+ return failure.kind === "not-authorized";
16486
+ }
16335
16487
  async function tryBrokerLogin(opts) {
16336
16488
  if (opts.broker === false) return { outcome: { kind: "skipped-flag", flag: "--no-broker" } };
16337
16489
  if (opts.ci) return { outcome: { kind: "skipped-flag", flag: "--ci" } };
@@ -16371,6 +16523,13 @@ async function tryBrokerLogin(opts) {
16371
16523
  result = await requestGrant(session.context, identity);
16372
16524
  }
16373
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
+ }
16374
16533
  if (result.ok) return { grant: result.grant };
16375
16534
  const { message, hint, fatal } = describeBrokerFailure(result.failure);
16376
16535
  if (fatal) {
@@ -16720,7 +16879,7 @@ var init_registry = __esm({
16720
16879
  this.name = "RegistryNotAuthorizedError";
16721
16880
  }
16722
16881
  };
16723
- 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)";
16724
16883
  CONTEXT_IGNORED_NOTICE = "The registry is global \u2014 the context argument is no longer needed (ignored).";
16725
16884
  }
16726
16885
  });
@@ -17686,6 +17845,32 @@ async function ensureHubImages() {
17686
17845
  }
17687
17846
  return version;
17688
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
+ }
17689
17874
  function readWorkspaceCatalog(repoRoot2) {
17690
17875
  const text = fs33.readFileSync(path40.join(repoRoot2, "pnpm-workspace.yaml"), "utf-8");
17691
17876
  const marker = "\ncatalog:\n";
@@ -17700,7 +17885,7 @@ function readWorkspaceCatalog(repoRoot2) {
17700
17885
  return catalog;
17701
17886
  }
17702
17887
  function normalizeHubManifests(contextDir, catalog) {
17703
- const DEP_FIELDS = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"];
17888
+ const DEP_FIELDS = ["dependencies", "optionalDependencies", "peerDependencies"];
17704
17889
  const touched = [];
17705
17890
  for (const entry of fs33.readdirSync(contextDir, { withFileTypes: true })) {
17706
17891
  if (!entry.isDirectory()) continue;
@@ -17708,6 +17893,10 @@ function normalizeHubManifests(contextDir, catalog) {
17708
17893
  if (!fs33.existsSync(manifest)) continue;
17709
17894
  const pkg = JSON.parse(fs33.readFileSync(manifest, "utf-8"));
17710
17895
  let changed = false;
17896
+ if (pkg.devDependencies) {
17897
+ delete pkg.devDependencies;
17898
+ changed = true;
17899
+ }
17711
17900
  for (const field of DEP_FIELDS) {
17712
17901
  const deps = pkg[field];
17713
17902
  if (!deps) continue;
@@ -17783,7 +17972,7 @@ async function buildHubImagesFromSource(repoRoot2) {
17783
17972
  }
17784
17973
  return version;
17785
17974
  }
17786
- 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;
17787
17976
  var init_hub_local = __esm({
17788
17977
  "libs/mesh-cli/src/commands/local/hub-local.ts"() {
17789
17978
  "use strict";
@@ -17797,6 +17986,66 @@ var init_hub_local = __esm({
17797
17986
  HUB_IMAGES = ["mesh-local-hub-api", "mesh-local-hub-ui"];
17798
17987
  LOCAL_IMAGE_REV = "r2";
17799
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`;
17800
18049
  }
17801
18050
  });
17802
18051
 
@@ -17973,6 +18222,17 @@ Re-running start from here may recreate shared containers with this checkout's c
17973
18222
  logInfo("Seeding Zitadel (Platform project, Mesh CLI app, Hub auth, test users)\u2026");
17974
18223
  const zitadel = await seedZitadel(LOCAL_AWS_CONFIG);
17975
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
+ }
17976
18236
  try {
17977
18237
  const reconciled = await reconcileRegistryFromZitadel();
17978
18238
  if (reconciled.tenants.length > 0) {
@@ -18140,6 +18400,7 @@ var init_local = __esm({
18140
18400
  init_auth_provision();
18141
18401
  init_seed();
18142
18402
  init_seed_zitadel();
18403
+ init_seed_hub_catalog();
18143
18404
  WAIT_TIMEOUT_MS = 18e4;
18144
18405
  WAIT_POLL_MS = 3e3;
18145
18406
  }