@mesh-tech/mesh-cli 0.34.3 → 0.35.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
@@ -1712,8 +1712,10 @@ var init_stack = __esm({
1712
1712
  var skills_exports = {};
1713
1713
  __export(skills_exports, {
1714
1714
  INTENT_RANGE: () => INTENT_RANGE,
1715
+ installedSkillName: () => installedSkillName,
1715
1716
  listPackageSkills: () => listPackageSkills,
1716
1717
  registerSkillsCommands: () => registerSkillsCommands,
1718
+ renderManagedSkill: () => renderManagedSkill,
1717
1719
  resolveTargetRoot: () => resolveTargetRoot,
1718
1720
  seedAppSkill: () => seedAppSkill,
1719
1721
  syncSkills: () => syncSkills
@@ -1738,15 +1740,36 @@ function cliAsset(...segments) {
1738
1740
  function listBaseSkills() {
1739
1741
  const dir = cliAsset("skills");
1740
1742
  if (!fs6.existsSync(dir)) return [];
1741
- return fs6.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && fs6.existsSync(path6.join(dir, entry.name, "SKILL.md"))).map((entry) => ({ name: entry.name, source: path6.join(dir, entry.name, "SKILL.md") }));
1743
+ return fs6.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && fs6.existsSync(path6.join(dir, entry.name, "SKILL.md"))).map((entry) => ({
1744
+ name: installedSkillName([entry.name]),
1745
+ // The CLI's own skill shipped as `skills/core/` and installed as `mesh-core/`.
1746
+ legacyName: entry.name === "mesh" ? "mesh-core" : `mesh-${entry.name}`,
1747
+ source: path6.join(dir, entry.name, "SKILL.md")
1748
+ }));
1749
+ }
1750
+ function installedSkillName(segments) {
1751
+ const parts = ["mesh"];
1752
+ for (const segment of segments) {
1753
+ if (segment === "core") continue;
1754
+ if (segment === parts[parts.length - 1]) continue;
1755
+ parts.push(segment);
1756
+ }
1757
+ return parts.join("-");
1742
1758
  }
1743
- function renderManagedSkill(source) {
1759
+ function renderManagedSkill(source, name) {
1744
1760
  const raw = fs6.readFileSync(source, "utf-8");
1745
1761
  const frontmatter = raw.match(/^(---\n[\s\S]*?\n---\n)/)?.[1];
1746
- if (!frontmatter) return `${MANAGED_MARKER}
1762
+ if (!frontmatter) return `---
1763
+ name: ${name}
1764
+ ---
1765
+
1766
+ ${MANAGED_MARKER}
1747
1767
 
1748
1768
  ${raw}`;
1749
- return `${frontmatter}
1769
+ const named = /^name:.*$/m.test(frontmatter) ? frontmatter.replace(/^name:.*$/m, `name: ${name}`) : frontmatter.replace(/^---\n/, `---
1770
+ name: ${name}
1771
+ `);
1772
+ return `${named}
1750
1773
  ${MANAGED_MARKER}
1751
1774
  ${raw.slice(frontmatter.length)}`;
1752
1775
  }
@@ -1785,13 +1808,31 @@ function listPackageSkills(root) {
1785
1808
  for (const domain of fs6.readdirSync(skillsDir, { withFileTypes: true })) {
1786
1809
  const source = path6.join(skillsDir, domain.name, "SKILL.md");
1787
1810
  if (!fs6.existsSync(source)) continue;
1788
- const name = `mesh-${pkg.name}-${domain.name}`;
1789
- if (!byName.has(name)) byName.set(name, { name, dir: path6.join(skillsDir, domain.name), source });
1811
+ const name = installedSkillName([pkg.name, domain.name]);
1812
+ if (!byName.has(name)) {
1813
+ byName.set(name, {
1814
+ name,
1815
+ legacyName: `mesh-${pkg.name}-${domain.name}`,
1816
+ dir: path6.join(skillsDir, domain.name),
1817
+ source
1818
+ });
1819
+ }
1790
1820
  }
1791
1821
  }
1792
1822
  }
1793
1823
  return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
1794
1824
  }
1825
+ function extrasInSync(sourceDir, targetDir) {
1826
+ for (const entry of fs6.readdirSync(sourceDir, { withFileTypes: true, recursive: true })) {
1827
+ if (!entry.isFile()) continue;
1828
+ const rel2 = path6.relative(sourceDir, path6.join(entry.parentPath, entry.name));
1829
+ if (rel2 === "SKILL.md") continue;
1830
+ const installed = path6.join(targetDir, rel2);
1831
+ if (!fs6.existsSync(installed)) return false;
1832
+ if (!fs6.readFileSync(installed).equals(fs6.readFileSync(path6.join(sourceDir, rel2)))) return false;
1833
+ }
1834
+ return true;
1835
+ }
1795
1836
  function copySkillExtras(sourceDir, targetDir) {
1796
1837
  for (const entry of fs6.readdirSync(sourceDir, { withFileTypes: true })) {
1797
1838
  if (entry.name === "SKILL.md") continue;
@@ -1809,25 +1850,36 @@ function seedAppSkill(root, appName, appRelPath) {
1809
1850
  }
1810
1851
  function planSync(root) {
1811
1852
  const items = [];
1812
- for (const skill of listBaseSkills()) {
1813
- const target = path6.join(root, ".claude", "skills", `mesh-${skill.name}`, "SKILL.md");
1814
- const desired = renderManagedSkill(skill.source);
1815
- const label = `.claude/skills/mesh-${skill.name}/SKILL.md (base skill)`;
1853
+ const baseSkills = listBaseSkills();
1854
+ for (const skill of baseSkills) {
1855
+ const targetDir = path6.join(root, ".claude", "skills", skill.name);
1856
+ const target = path6.join(targetDir, "SKILL.md");
1857
+ const sourceDir = path6.dirname(skill.source);
1858
+ const desired = renderManagedSkill(skill.source, skill.name);
1859
+ const label = `.claude/skills/${skill.name}/SKILL.md (base skill)`;
1816
1860
  if (!fs6.existsSync(target)) {
1817
1861
  items.push({
1818
1862
  label,
1819
1863
  state: "write",
1820
1864
  apply: () => {
1821
- fs6.mkdirSync(path6.dirname(target), { recursive: true });
1865
+ fs6.mkdirSync(targetDir, { recursive: true });
1822
1866
  fs6.writeFileSync(target, desired);
1867
+ copySkillExtras(sourceDir, targetDir);
1823
1868
  }
1824
1869
  });
1825
1870
  } else {
1826
1871
  const current = fs6.readFileSync(target, "utf-8");
1827
- if (current === desired) {
1872
+ if (current === desired && extrasInSync(sourceDir, targetDir)) {
1828
1873
  items.push({ label, state: "ok" });
1829
1874
  } else if (current.includes(MANAGED_MARKER)) {
1830
- items.push({ label, state: "write", apply: () => fs6.writeFileSync(target, desired) });
1875
+ items.push({
1876
+ label,
1877
+ state: "write",
1878
+ apply: () => {
1879
+ fs6.writeFileSync(target, desired);
1880
+ copySkillExtras(sourceDir, targetDir);
1881
+ }
1882
+ });
1831
1883
  } else {
1832
1884
  items.push({ label: `${label} \u2014 exists without the managed marker, leaving as-is`, state: "skip" });
1833
1885
  }
@@ -1837,7 +1889,7 @@ function planSync(root) {
1837
1889
  for (const skill of packageSkills) {
1838
1890
  const targetDir = path6.join(root, ".claude", "skills", skill.name);
1839
1891
  const target = path6.join(targetDir, "SKILL.md");
1840
- const desired = renderManagedSkill(skill.source);
1892
+ const desired = renderManagedSkill(skill.source, skill.name);
1841
1893
  const label = `.claude/skills/${skill.name}/SKILL.md (platform skill)`;
1842
1894
  if (!fs6.existsSync(target)) {
1843
1895
  items.push({
@@ -1870,6 +1922,22 @@ function planSync(root) {
1870
1922
  if (packageSkills.length === 0 && meshTechScopeDirs(root).length === 0) {
1871
1923
  logInfo("No @mesh-tech packages installed yet \u2014 run `pnpm install`, then `mesh skills sync` again for the platform skills.");
1872
1924
  }
1925
+ for (const skill of [...baseSkills, ...packageSkills]) {
1926
+ if (skill.legacyName === skill.name) continue;
1927
+ const legacyDir = path6.join(root, ".claude", "skills", skill.legacyName);
1928
+ const legacyFile = path6.join(legacyDir, "SKILL.md");
1929
+ if (!fs6.existsSync(legacyFile)) continue;
1930
+ const label = `.claude/skills/${skill.legacyName}/ (renamed to ${skill.name})`;
1931
+ if (fs6.readFileSync(legacyFile, "utf-8").includes(MANAGED_MARKER)) {
1932
+ items.push({
1933
+ label: `${label} \u2014 removing the old copy`,
1934
+ state: "write",
1935
+ apply: () => fs6.rmSync(legacyDir, { recursive: true, force: true })
1936
+ });
1937
+ } else {
1938
+ items.push({ label: `${label} \u2014 exists without the managed marker, leaving as-is`, state: "skip" });
1939
+ }
1940
+ }
1873
1941
  const hookSource = cliAsset("assets", "intent", "intent-claude-gate.mjs");
1874
1942
  const hookTarget = path6.join(root, HOOK_RELATIVE);
1875
1943
  const hookDesired = fs6.readFileSync(hookSource, "utf-8");
@@ -2260,12 +2328,19 @@ var init_app_check_resolve = __esm({
2260
2328
  });
2261
2329
 
2262
2330
  // packages/mesh-cli/src/utils/first-party-contexts.ts
2331
+ function loginContextForPlatform(platform) {
2332
+ if (!Object.hasOwn(FIRST_PARTY_PLATFORM_CONTEXTS, platform)) return platform;
2333
+ return FIRST_PARTY_PLATFORM_CONTEXTS[platform] || platform;
2334
+ }
2263
2335
  function firstPartyDomainFor(context) {
2264
2336
  if (!Object.hasOwn(FIRST_PARTY_CONTEXTS, context)) return null;
2265
2337
  const domain = FIRST_PARTY_CONTEXTS[context];
2266
2338
  return typeof domain === "string" && domain ? domain : null;
2267
2339
  }
2268
- var FIRST_PARTY_CONTEXTS;
2340
+ function isPlatformLoginAlias(context) {
2341
+ return Object.values(FIRST_PARTY_PLATFORM_CONTEXTS).includes(context);
2342
+ }
2343
+ var FIRST_PARTY_CONTEXTS, FIRST_PARTY_PLATFORM_CONTEXTS;
2269
2344
  var init_first_party_contexts = __esm({
2270
2345
  "packages/mesh-cli/src/utils/first-party-contexts.ts"() {
2271
2346
  "use strict";
@@ -2276,7 +2351,15 @@ var init_first_party_contexts = __esm({
2276
2351
  // platform lives in a different AWS account with no registry; the
2277
2352
  // platform-dev stack in the registry-owning account is the developer front
2278
2353
  // door. mesh-sandbox#32 / #3587.)
2279
- "mesh.dev": "dev.platform.meshtech.io"
2354
+ "mesh.dev": "dev.platform.meshtech.io",
2355
+ // Verified 2026-09-25: https://cli.dev.mesh-platform.trabian.com/.well-known/mesh.json
2356
+ // answers anonymously with the `mesh` tenant's dev platform issuer
2357
+ // (identity.dev.mesh-platform.trabian.com) — the platform every `mesh`/dev
2358
+ // tenant environment and its `*-apps-deployer` role live on.
2359
+ "mesh-platform.dev": "dev.mesh-platform.trabian.com"
2360
+ });
2361
+ FIRST_PARTY_PLATFORM_CONTEXTS = Object.freeze({
2362
+ "mesh.dev": "mesh-platform.dev"
2280
2363
  });
2281
2364
  }
2282
2365
  });
@@ -3909,6 +3992,7 @@ __export(login_exports, {
3909
3992
  clearContextConfig: () => clearContextConfig,
3910
3993
  clearCredentials: () => clearCredentials,
3911
3994
  decodeJwtPayload: () => decodeJwtPayload,
3995
+ deployableTenants: () => deployableTenants,
3912
3996
  deviceLoginWithReissue: () => deviceLoginWithReissue,
3913
3997
  discoverConfigForContext: () => discoverConfigForContext,
3914
3998
  discoverConfigFromWellKnown: () => discoverConfigFromWellKnown,
@@ -4090,11 +4174,11 @@ function readAllCredentials() {
4090
4174
  function readCredentials(context) {
4091
4175
  return readAllCredentials()[context] ?? null;
4092
4176
  }
4093
- function atomicWriteFileSync(path49, data, mode) {
4094
- const tmpPath = `${path49}.${process.pid}.${atomicWriteCounter++}.tmp`;
4177
+ function atomicWriteFileSync(path50, data, mode) {
4178
+ const tmpPath = `${path50}.${process.pid}.${atomicWriteCounter++}.tmp`;
4095
4179
  try {
4096
4180
  fs11.writeFileSync(tmpPath, data, { mode });
4097
- fs11.renameSync(tmpPath, path49);
4181
+ fs11.renameSync(tmpPath, path50);
4098
4182
  } catch (err) {
4099
4183
  try {
4100
4184
  fs11.unlinkSync(tmpPath);
@@ -4225,12 +4309,7 @@ function login(context, config) {
4225
4309
  const idPayload = decodeJwtPayload(tokens.id_token);
4226
4310
  const email = idPayload.email ?? idPayload.preferred_username ?? "unknown";
4227
4311
  const sub = idPayload.sub;
4228
- let tenants = [];
4229
- try {
4230
- const accessPayload = decodeJwtPayload(tokens.access_token);
4231
- tenants = accessPayload["urn:mesh:tenants"] ?? [];
4232
- } catch {
4233
- }
4312
+ const tenants = deployableTenants(tokens.id_token, tokens.access_token);
4234
4313
  writeCredentials(context, {
4235
4314
  idToken: tokens.id_token,
4236
4315
  accessToken: tokens.access_token,
@@ -4346,12 +4425,7 @@ async function attemptDeviceCode(context, config) {
4346
4425
  const idPayload = decodeJwtPayload(tokens.id_token);
4347
4426
  const email = idPayload.email ?? idPayload.preferred_username ?? "unknown";
4348
4427
  const sub = idPayload.sub;
4349
- let tenants = [];
4350
- try {
4351
- const accessPayload = decodeJwtPayload(tokens.access_token);
4352
- tenants = accessPayload["urn:mesh:tenants"] ?? [];
4353
- } catch {
4354
- }
4428
+ const tenants = deployableTenants(tokens.id_token, tokens.access_token);
4355
4429
  writeCredentials(context, {
4356
4430
  idToken: tokens.id_token,
4357
4431
  accessToken: tokens.access_token,
@@ -4524,12 +4598,10 @@ async function showStatus(context, config) {
4524
4598
  );
4525
4599
  return;
4526
4600
  }
4527
- let tenants = [];
4601
+ const tenants = deployableTenants(creds.idToken, creds.accessToken);
4528
4602
  let audience = [];
4529
4603
  try {
4530
- const accessPayload = decodeJwtPayload(creds.accessToken);
4531
- tenants = accessPayload["urn:mesh:tenants"] ?? [];
4532
- audience = tokenAudience(accessPayload);
4604
+ audience = tokenAudience(decodeJwtPayload(creds.accessToken));
4533
4605
  } catch {
4534
4606
  }
4535
4607
  logSuccess(`Logged in as ${creds.email ?? "unknown"} (${context})`);
@@ -4552,6 +4624,19 @@ async function showStatus(context, config) {
4552
4624
  );
4553
4625
  }
4554
4626
  }
4627
+ function deployableTenants(idToken, accessToken) {
4628
+ for (const token of [idToken, accessToken]) {
4629
+ if (!token) continue;
4630
+ let claim;
4631
+ try {
4632
+ claim = decodeJwtPayload(token)[DEPLOY_TENANTS_CLAIM];
4633
+ } catch {
4634
+ continue;
4635
+ }
4636
+ if (Array.isArray(claim)) return claim.filter((t) => typeof t === "string");
4637
+ }
4638
+ return [];
4639
+ }
4555
4640
  function tokenAudience(payload) {
4556
4641
  const aud = payload.aud;
4557
4642
  if (typeof aud === "string") return [aud];
@@ -4828,7 +4913,7 @@ async function ensureLogin(context, opts = {}) {
4828
4913
  }
4829
4914
  async function discoverConfigGuarded(context, opts = {}) {
4830
4915
  let config = null;
4831
- if (parseTenantEnv(context)) {
4916
+ if (parseTenantEnv(context) && !isPlatformLoginAlias(context)) {
4832
4917
  config = await discoverConfigFromSsm(context, opts);
4833
4918
  }
4834
4919
  if (!config) {
@@ -4974,7 +5059,7 @@ function readAllContextConfigs() {
4974
5059
  async function discoverConfigForContext(context) {
4975
5060
  return getContextConfig(context) ?? await discoverConfigGuarded(context);
4976
5061
  }
4977
- var CONFIG_DIR, CONFIG_FILE, CREDENTIALS_FILE, REDIRECT_PORT, REDIRECT_URI, BASE_SCOPES, atomicWriteCounter, MAX_DEVICE_CODES, RESERVED_REGISTRY_CONTEXT, NO_REGISTRY_DEPRECATED, REGISTRY_SEPARATE_HINT;
5062
+ var CONFIG_DIR, CONFIG_FILE, CREDENTIALS_FILE, REDIRECT_PORT, REDIRECT_URI, BASE_SCOPES, atomicWriteCounter, MAX_DEVICE_CODES, DEPLOY_TENANTS_CLAIM, RESERVED_REGISTRY_CONTEXT, NO_REGISTRY_DEPRECATED, REGISTRY_SEPARATE_HINT;
4978
5063
  var init_login = __esm({
4979
5064
  "packages/mesh-cli/src/commands/login.ts"() {
4980
5065
  "use strict";
@@ -4994,6 +5079,7 @@ var init_login = __esm({
4994
5079
  BASE_SCOPES = "openid email profile offline_access urn:zitadel:iam:org:project:id:zitadel:aud";
4995
5080
  atomicWriteCounter = 0;
4996
5081
  MAX_DEVICE_CODES = 3;
5082
+ DEPLOY_TENANTS_CLAIM = "urn:mesh:tenants";
4997
5083
  RESERVED_REGISTRY_CONTEXT = "registry";
4998
5084
  NO_REGISTRY_DEPRECATED = "--no-registry is deprecated and ignored: mesh login no longer touches the registry.";
4999
5085
  REGISTRY_SEPARATE_HINT = "Package registry access is separate from platform sign-in \u2014 run: mesh registry login";
@@ -5498,7 +5584,7 @@ import {
5498
5584
  GetCallerIdentityCommand,
5499
5585
  STSClient
5500
5586
  } from "@aws-sdk/client-sts";
5501
- function derivePlatformContext(appRoot, stack) {
5587
+ function readStackPlatform(appRoot, stack) {
5502
5588
  const configFile = path13.join(appRoot, `Pulumi.${stack}.yaml`);
5503
5589
  if (!fs13.existsSync(configFile)) return null;
5504
5590
  const content = fs13.readFileSync(configFile, "utf-8");
@@ -5517,6 +5603,12 @@ function derivePlatformContext(appRoot, stack) {
5517
5603
  }
5518
5604
  return null;
5519
5605
  }
5606
+ function derivePlatformContext(appRoot, stack) {
5607
+ const explicit = readStackConfig(appRoot, stack, "mesh:loginContext");
5608
+ if (explicit) return explicit;
5609
+ const platform = readStackPlatform(appRoot, stack);
5610
+ return platform ? loginContextForPlatform(platform) : null;
5611
+ }
5520
5612
  function quoteCredProcessToken(token) {
5521
5613
  return /\s/.test(token) ? `"${token}"` : token;
5522
5614
  }
@@ -5818,6 +5910,8 @@ var init_aws_auth = __esm({
5818
5910
  init_log();
5819
5911
  init_auth_preflight();
5820
5912
  init_login();
5913
+ init_first_party_contexts();
5914
+ init_pulumi();
5821
5915
  DEFAULT_REGION = process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? "us-east-2";
5822
5916
  MANAGED_START_PREFIX = `# >>> mesh-cli managed profile `;
5823
5917
  MANAGED_START = (context) => `${MANAGED_START_PREFIX}${context} >>>`;
@@ -5868,7 +5962,9 @@ var init_vpn_join = __esm({
5868
5962
  });
5869
5963
 
5870
5964
  // packages/mesh-cli/src/utils/pulumi-run.ts
5871
- function credProbeToPreflightError(probe, context) {
5965
+ function credProbeToPreflightError(probe, context, platform) {
5966
+ const aliasNote = platform && platform !== context && loginContextForPlatform(platform) === context ? `
5967
+ This stack deploys to platform ${platform}, which signs in as ${context} \u2014 a \`mesh login ${platform}\` session is a different platform and is not trusted here.` : "";
5872
5968
  switch (probe.state) {
5873
5969
  case "ok":
5874
5970
  return null;
@@ -5876,19 +5972,21 @@ function credProbeToPreflightError(probe, context) {
5876
5972
  return {
5877
5973
  fatal: true,
5878
5974
  message: `Not logged in for ${context}. Run:
5879
- mesh login ${context} --device`
5975
+ mesh login ${context} --device` + aliasNote
5880
5976
  };
5881
5977
  case "expired-session":
5882
5978
  return {
5883
5979
  fatal: true,
5884
5980
  message: `Your mesh login session for ${context} expired. Run:
5885
- mesh login ${context} --device`
5981
+ mesh login ${context} --device` + aliasNote
5886
5982
  };
5887
5983
  case "assume-denied":
5888
5984
  return {
5889
5985
  fatal: true,
5890
5986
  message: `Logged in, but couldn't assume the deployer role \u2014 ${probe.detail}
5891
- Check mesh:deployerRole in the stack config and your IAM access.`
5987
+ If you signed in to another platform, sign in to this stack's:
5988
+ mesh login ${context} --device
5989
+ Otherwise check mesh:deployerRole in the stack config and your IAM access.` + aliasNote
5892
5990
  };
5893
5991
  case "stale-env-override":
5894
5992
  return {
@@ -5918,7 +6016,7 @@ async function resolvePulumiEnv(opts) {
5918
6016
  const context = derivePlatformContext(appRoot, stack);
5919
6017
  if (!hasAmbientCreds && context) {
5920
6018
  const probe = await probeCredentials(context, roleArn);
5921
- const preflight = credProbeToPreflightError(probe, context);
6019
+ const preflight = credProbeToPreflightError(probe, context, readStackPlatform(appRoot, stack));
5922
6020
  if (preflight?.fatal) {
5923
6021
  logError(preflight.message);
5924
6022
  process.exit(1);
@@ -5942,6 +6040,7 @@ var init_pulumi_run = __esm({
5942
6040
  init_log();
5943
6041
  init_pulumi();
5944
6042
  init_aws_auth();
6043
+ init_first_party_contexts();
5945
6044
  init_login();
5946
6045
  }
5947
6046
  });
@@ -10777,7 +10876,7 @@ function registerDevCommand(program2) {
10777
10876
  }
10778
10877
  const resolved = deployerRole ? await resolveAwsCredentials(deployerRole, appRoot, stack) : null;
10779
10878
  if (deployerRole && !resolved && !process.env.AWS_ACCESS_KEY_ID) {
10780
- const pf = ctx ? credProbeToPreflightError(await probeCredentials(ctx, deployerRole), ctx) : null;
10879
+ const pf = ctx ? credProbeToPreflightError(await probeCredentials(ctx, deployerRole), ctx, readStackPlatform(appRoot, stack)) : null;
10781
10880
  logError(
10782
10881
  pf?.message ?? `Couldn't resolve AWS credentials to restart '${service}'. Run: mesh login ${ctx ?? "mesh.dev"} --device`
10783
10882
  );
@@ -12870,11 +12969,11 @@ function createVcsFolderReader(opts) {
12870
12969
  }
12871
12970
  return p;
12872
12971
  };
12873
- const readPath = async (repo, path49) => {
12972
+ const readPath = async (repo, path50) => {
12874
12973
  const dir = await cloneRepo(repo);
12875
- const ref = await resolveFolderRef(dir, path49);
12876
- if (!ref) throw new Error(`path "${path49}" not found on any ref of ${repo}`);
12877
- const files = await readFolderAtRef(dir, ref, path49);
12974
+ const ref = await resolveFolderRef(dir, path50);
12975
+ if (!ref) throw new Error(`path "${path50}" not found on any ref of ${repo}`);
12976
+ const files = await readFolderAtRef(dir, ref, path50);
12878
12977
  return { ref, files };
12879
12978
  };
12880
12979
  const cleanup = async () => {
@@ -13194,7 +13293,7 @@ Use --stack <name> (the deployed stack, e.g. dev).` : "No stack configs found in
13194
13293
  const ctx = derivePlatformContext(appRoot, stack);
13195
13294
  const resolved = deployerRole ? await resolveAwsCredentials(deployerRole, appRoot, stack) : null;
13196
13295
  if (deployerRole && !resolved && !process.env.AWS_ACCESS_KEY_ID) {
13197
- const pf = ctx ? credProbeToPreflightError(await probeCredentials(ctx, deployerRole), ctx) : null;
13296
+ const pf = ctx ? credProbeToPreflightError(await probeCredentials(ctx, deployerRole), ctx, readStackPlatform(appRoot, stack)) : null;
13198
13297
  throw new Error(
13199
13298
  pf?.message ?? `Couldn't resolve AWS credentials. Run: mesh login ${ctx ?? "mesh.dev"} --device`
13200
13299
  );
@@ -15249,7 +15348,7 @@ var init_discover = __esm({
15249
15348
 
15250
15349
  // packages/mesh-cli/src/docs/assemble.ts
15251
15350
  import { execFileSync as execFileSync20 } from "node:child_process";
15252
- import { mkdirSync as mkdirSync12, readFileSync as readFileSync25, rmSync as rmSync5, writeFileSync as writeFileSync15 } from "node:fs";
15351
+ import { mkdirSync as mkdirSync12, readFileSync as readFileSync25, rmSync as rmSync6, writeFileSync as writeFileSync15 } from "node:fs";
15253
15352
  import path30 from "node:path";
15254
15353
  import { parse as parseYaml5 } from "yaml";
15255
15354
  function splitFrontMatter(markdown) {
@@ -15531,7 +15630,7 @@ function assemblePortal(options) {
15531
15630
  const routes = new Set(pages.map((p) => p.route));
15532
15631
  const brokenLinks = [];
15533
15632
  const externalLinks = [];
15534
- rmSync5(outDir, { recursive: true, force: true });
15633
+ rmSync6(outDir, { recursive: true, force: true });
15535
15634
  mkdirSync12(outDir, { recursive: true });
15536
15635
  for (const page of pages) {
15537
15636
  const raw = readFileSync25(path30.join(repoRoot2, page.source), "utf-8");
@@ -15869,8 +15968,8 @@ function formatDefault(value, paths = currentMachinePaths()) {
15869
15968
  if (typeof value === "boolean" || typeof value === "number") return String(value);
15870
15969
  return normalizeMachinePaths(JSON.stringify(value), paths);
15871
15970
  }
15872
- function slugifyCommandPath(path49) {
15873
- return path49.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
15971
+ function slugifyCommandPath(path50) {
15972
+ return path50.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
15874
15973
  }
15875
15974
  function extractOptions(command) {
15876
15975
  const options = command.options ?? [];
@@ -15904,17 +16003,17 @@ function isUndocumented(command) {
15904
16003
  function extractCommand(command, parentPath, depth) {
15905
16004
  const internals = command;
15906
16005
  const name = internals._name ?? "";
15907
- const path49 = parentPath ? `${parentPath} ${name}` : name;
16006
+ const path50 = parentPath ? `${parentPath} ${name}` : name;
15908
16007
  return {
15909
- path: path49,
16008
+ path: path50,
15910
16009
  name,
15911
16010
  aliases: [...internals._aliases ?? []],
15912
16011
  description: internals._description ?? "",
15913
16012
  args: extractArgs(command),
15914
16013
  options: extractOptions(command),
15915
- slug: slugifyCommandPath(path49),
16014
+ slug: slugifyCommandPath(path50),
15916
16015
  depth,
15917
- subcommands: (internals.commands ?? []).filter((child) => !isUndocumented(child)).map((child) => extractCommand(child, path49, depth + 1))
16016
+ subcommands: (internals.commands ?? []).filter((child) => !isUndocumented(child)).map((child) => extractCommand(child, path50, depth + 1))
15918
16017
  };
15919
16018
  }
15920
16019
  function extractCliReference(program2) {
@@ -16402,7 +16501,7 @@ var init_serve = __esm({
16402
16501
 
16403
16502
  // packages/mesh-cli/src/docs/registry-docs.ts
16404
16503
  import { execFileSync as execFileSync22 } from "node:child_process";
16405
- import { existsSync as existsSync25, mkdirSync as mkdirSync14, readFileSync as readFileSync28, renameSync as renameSync3, rmSync as rmSync6, writeFileSync as writeFileSync17 } from "node:fs";
16504
+ import { existsSync as existsSync25, mkdirSync as mkdirSync14, readFileSync as readFileSync28, renameSync as renameSync3, rmSync as rmSync7, writeFileSync as writeFileSync17 } from "node:fs";
16406
16505
  import { tmpdir as tmpdir7 } from "node:os";
16407
16506
  import path33 from "node:path";
16408
16507
  function readDocsRegistryAuth(npmrcPath2 = homeNpmrcPath()) {
@@ -16490,20 +16589,20 @@ async function fetchDocsArtifact(auth, version, cacheRoot = docsCacheRoot(), fet
16490
16589
  const tgzPath = path33.join(cacheRoot, `.${version}.tgz`);
16491
16590
  writeFileSync17(tgzPath, Buffer.from(await tgzResponse.arrayBuffer()));
16492
16591
  const staging = path33.join(cacheRoot, `.staging-${version}`);
16493
- rmSync6(staging, { recursive: true, force: true });
16592
+ rmSync7(staging, { recursive: true, force: true });
16494
16593
  mkdirSync14(staging, { recursive: true });
16495
16594
  try {
16496
16595
  execFileSync22("tar", ["-xzf", tgzPath, "-C", staging, "--strip-components", "1"], {
16497
16596
  stdio: ["pipe", "pipe", "pipe"]
16498
16597
  });
16499
16598
  } finally {
16500
- rmSync6(tgzPath, { force: true });
16599
+ rmSync7(tgzPath, { force: true });
16501
16600
  }
16502
16601
  if (!existsSync25(path33.join(staging, "dist", "index.html"))) {
16503
- rmSync6(staging, { recursive: true, force: true });
16602
+ rmSync7(staging, { recursive: true, force: true });
16504
16603
  throw new Error(`${DOCS_PACKAGE}@${version} unpacked without a dist/index.html \u2014 the artifact is malformed`);
16505
16604
  }
16506
- rmSync6(targetDir, { recursive: true, force: true });
16605
+ rmSync7(targetDir, { recursive: true, force: true });
16507
16606
  renameSync3(staging, targetDir);
16508
16607
  return targetDir;
16509
16608
  }
@@ -21455,24 +21554,290 @@ var init_site = __esm({
21455
21554
  }
21456
21555
  });
21457
21556
 
21458
- // packages/mesh-cli/src/commands/stack.ts
21459
- import { execFileSync as execFileSync31 } from "child_process";
21460
- import * as path45 from "path";
21557
+ // packages/mesh-cli/src/commands/stack-status.ts
21461
21558
  import * as fs38 from "fs";
21559
+ import * as path45 from "path";
21560
+ import { execFileSync as execFileSync31 } from "child_process";
21561
+ function splitLayerStack(stack, tenant) {
21562
+ if (tenant && stack.startsWith(`${tenant}-`)) return { tenant, env: stack.slice(tenant.length + 1) };
21563
+ const cut = stack.indexOf("-");
21564
+ if (cut <= 0) return { tenant: tenant ?? stack, env: stack };
21565
+ return { tenant: tenant ?? stack.slice(0, cut), env: stack.slice(cut + 1) };
21566
+ }
21567
+ function targetForStack(projectDir, stack) {
21568
+ const platform = readStackPlatform(projectDir, stack);
21569
+ if (platform) {
21570
+ const [tenant2, env2] = platform.split(".");
21571
+ return { stack, kind: "app", tenant: tenant2, env: env2, coreTenant: tenant2, coreEnv: env2 };
21572
+ }
21573
+ const declared = readStackConfig(projectDir, stack, "mesh:tenant");
21574
+ const layerDir = ["core", "platform"].includes(path45.basename(projectDir));
21575
+ if (!declared && !layerDir) return null;
21576
+ const { tenant, env } = splitLayerStack(stack, declared);
21577
+ const isCore = path45.basename(projectDir) === "core";
21578
+ return {
21579
+ stack,
21580
+ kind: "layer",
21581
+ tenant,
21582
+ env,
21583
+ coreTenant: readStackConfig(projectDir, stack, "mesh:coreTenant") ?? tenant,
21584
+ coreEnv: readStackConfig(projectDir, stack, "mesh:coreEnv") ?? env,
21585
+ ...isCore ? { coreDir: projectDir } : { platformDir: projectDir }
21586
+ };
21587
+ }
21588
+ function findStatusTargets(cwd, stack) {
21589
+ const project = findAppRoot(cwd);
21590
+ let projectDir = project;
21591
+ let repoRoot2 = null;
21592
+ if (!projectDir) {
21593
+ const platformDir = path45.join(cwd, "platform");
21594
+ if (fs38.existsSync(path45.join(platformDir, "Pulumi.yaml"))) {
21595
+ projectDir = platformDir;
21596
+ repoRoot2 = cwd;
21597
+ }
21598
+ } else if (["core", "platform"].includes(path45.basename(projectDir))) {
21599
+ repoRoot2 = path45.dirname(projectDir);
21600
+ }
21601
+ if (!projectDir) return [];
21602
+ const stacks = stack ? [stack] : findStackConfigs(projectDir);
21603
+ const targets = [];
21604
+ for (const name of stacks) {
21605
+ const target = targetForStack(projectDir, name);
21606
+ if (!target) continue;
21607
+ if (repoRoot2 && target.kind === "layer") {
21608
+ const core = path45.join(repoRoot2, "core");
21609
+ const platform = path45.join(repoRoot2, "platform");
21610
+ if (fs38.existsSync(path45.join(core, "package.json"))) target.coreDir = core;
21611
+ if (fs38.existsSync(path45.join(platform, "package.json"))) target.platformDir = platform;
21612
+ }
21613
+ targets.push(target);
21614
+ }
21615
+ return targets;
21616
+ }
21617
+ function pinnedVersion(dir, pkg) {
21618
+ if (!dir) return null;
21619
+ try {
21620
+ const manifest = JSON.parse(fs38.readFileSync(path45.join(dir, "package.json"), "utf-8"));
21621
+ const spec = manifest.dependencies?.[pkg] ?? manifest.devDependencies?.[pkg];
21622
+ return typeof spec === "string" ? spec.replace(/^[\^~=v]+/, "") : null;
21623
+ } catch {
21624
+ return null;
21625
+ }
21626
+ }
21627
+ function compareVersions2(a, b) {
21628
+ const parse4 = (v) => v.split(/[.+-]/).slice(0, 3).map((n) => Number.parseInt(n, 10) || 0);
21629
+ const [x, y] = [parse4(a), parse4(b)];
21630
+ for (let i = 0; i < 3; i++) {
21631
+ const diff = (x[i] ?? 0) - (y[i] ?? 0);
21632
+ if (diff) return diff;
21633
+ }
21634
+ return 0;
21635
+ }
21636
+ function layerState(deployed, latest) {
21637
+ if (!deployed) return "not-deployed";
21638
+ if (!latest) return "unknown";
21639
+ return compareVersions2(deployed, latest) < 0 ? "behind" : "current";
21640
+ }
21641
+ function parseRegistration(raw) {
21642
+ try {
21643
+ const data = JSON.parse(raw);
21644
+ const out = {};
21645
+ if (typeof data.build?.infraVersion === "string") out.infraVersion = data.build.infraVersion;
21646
+ if (typeof data.hub?.hubVersion === "string") out.hubVersion = data.hub.hubVersion;
21647
+ return out;
21648
+ } catch {
21649
+ return {};
21650
+ }
21651
+ }
21652
+ function buildRows(target, deployed, latest) {
21653
+ const corePath = `/mesh-platform/${target.coreTenant}/${target.coreEnv}/core`;
21654
+ const platformPath = `/mesh-platform/${target.tenant}/${target.env}/platform`;
21655
+ const row = (layer, registration, source, field, pinned, latestVersion) => {
21656
+ const value = source && !(source instanceof Error) ? source[field] ?? null : null;
21657
+ const predatesExport = !!source && !(source instanceof Error) && value === null;
21658
+ const note = source instanceof Error ? source.message : source === null ? "no registration \u2014 never deployed, or not readable with these credentials" : predatesExport ? "registration predates version export \u2014 last deployed with an infra-components older than any current release" : void 0;
21659
+ return {
21660
+ layer,
21661
+ registration,
21662
+ pinned,
21663
+ deployed: value,
21664
+ latest: latestVersion,
21665
+ state: source instanceof Error ? "unknown" : predatesExport ? "behind" : layerState(value, latestVersion),
21666
+ undeployedPin: !!pinned && !!value && pinned !== value,
21667
+ ...note ? { note } : {}
21668
+ };
21669
+ };
21670
+ return [
21671
+ row("core", corePath, deployed.core, "infraVersion", pinnedVersion(target.coreDir, INFRA_PACKAGE), latest.infra),
21672
+ row(
21673
+ "platform",
21674
+ platformPath,
21675
+ deployed.platform,
21676
+ "infraVersion",
21677
+ pinnedVersion(target.platformDir, INFRA_PACKAGE),
21678
+ latest.infra
21679
+ ),
21680
+ row("hub", platformPath, deployed.platform, "hubVersion", pinnedVersion(target.platformDir, HUB_PACKAGE2), latest.hub)
21681
+ ];
21682
+ }
21683
+ function renderStatus(reports, cli) {
21684
+ const lines = [];
21685
+ const show = (v) => v ?? "\u2014";
21686
+ for (const { target, rows } of reports) {
21687
+ const what = target.kind === "app" ? `App stack ${target.stack} \u2192 platform ${target.tenant}/${target.env}` : `Platform ${target.tenant}/${target.env} (stack ${target.stack}) on core ${target.coreTenant}/${target.coreEnv}`;
21688
+ lines.push(what);
21689
+ lines.push(` ${"layer".padEnd(9)} ${"pinned".padEnd(10)} ${"deployed".padEnd(10)} ${"latest".padEnd(10)} state`);
21690
+ for (const r of rows) {
21691
+ const flag = r.undeployedPin ? " (pin not deployed)" : "";
21692
+ lines.push(
21693
+ ` ${r.layer.padEnd(9)} ${show(r.pinned).padEnd(10)} ${show(r.deployed).padEnd(10)} ${show(r.latest).padEnd(10)} ${r.state}${flag}`
21694
+ );
21695
+ if (r.note) lines.push(` ${r.registration}: ${r.note}`);
21696
+ }
21697
+ if (rows.some((r) => r.state === "behind" || r.undeployedPin)) {
21698
+ lines.push(...upgradeSteps(target, rows));
21699
+ }
21700
+ lines.push("");
21701
+ }
21702
+ lines.push(
21703
+ `CLI ${CLI_PACKAGE} ${cli.running}` + (cli.latest ? ` (latest ${cli.latest})${cli.behind ? " \u2014 behind: pnpm add -D @mesh-tech/mesh-cli@latest" : ""}` : " (latest unknown)")
21704
+ );
21705
+ return lines.join("\n");
21706
+ }
21707
+ function upgradeSteps(target, rows) {
21708
+ const latest = rows.find((r) => r.latest)?.latest;
21709
+ if (target.kind === "app") {
21710
+ return [
21711
+ " This platform is behind. Its operator upgrades it from the org's platform repo:",
21712
+ " run `mesh stack status` there, then upgrade core and platform together."
21713
+ ];
21714
+ }
21715
+ const to = latest ? `@${latest}` : "@latest";
21716
+ return [
21717
+ " To upgrade (both layers \u2014 deploying platform/ alone leaves core on the old version):",
21718
+ ` 1. core/ and platform/: set ${INFRA_PACKAGE}${to} (and ${HUB_PACKAGE2}${to} in platform/), then pnpm install`,
21719
+ ` 2. cd core && mesh deploy preview --stack ${target.coreTenant}-${target.coreEnv} # review: this layer is the VPC/EKS/RDS`,
21720
+ ` 3. mesh deploy up --stack ${target.coreTenant}-${target.coreEnv} && cd ../platform && mesh deploy up --stack ${target.stack}`,
21721
+ " 4. mesh stack status # both layers now report the new version"
21722
+ ];
21723
+ }
21724
+ function latestPublished(pkg, cwd) {
21725
+ try {
21726
+ const out = execFileSync31("npm", ["view", pkg, "version"], {
21727
+ cwd,
21728
+ encoding: "utf-8",
21729
+ timeout: 2e4,
21730
+ stdio: ["ignore", "pipe", "ignore"]
21731
+ }).trim();
21732
+ return /^\d+\.\d+\.\d+$/.test(out) ? out : null;
21733
+ } catch {
21734
+ return null;
21735
+ }
21736
+ }
21737
+ async function readRegistration(name, region, auth) {
21738
+ const { credentials, profile, signInHint } = auth;
21739
+ try {
21740
+ const { SSMClient: SSMClient5, GetParameterCommand: GetParameterCommand2 } = await import("@aws-sdk/client-ssm");
21741
+ const client = new SSMClient5({
21742
+ region,
21743
+ ...profile && !credentials?.AWS_ACCESS_KEY_ID ? { profile } : {},
21744
+ ...credentials?.AWS_ACCESS_KEY_ID ? {
21745
+ credentials: {
21746
+ accessKeyId: credentials.AWS_ACCESS_KEY_ID,
21747
+ secretAccessKey: credentials.AWS_SECRET_ACCESS_KEY ?? "",
21748
+ sessionToken: credentials.AWS_SESSION_TOKEN
21749
+ }
21750
+ } : {}
21751
+ });
21752
+ const resp = await client.send(new GetParameterCommand2({ Name: name }));
21753
+ return resp.Parameter?.Value ? parseRegistration(resp.Parameter.Value) : null;
21754
+ } catch (err) {
21755
+ const e = err;
21756
+ const kind = e.name || e.__type || e.message || String(err);
21757
+ if (/ParameterNotFound/.test(kind)) return null;
21758
+ if (/credential|ExpiredToken|AccessDenied|UnrecognizedClient|security token/i.test(`${kind} ${e.message ?? ""}`)) {
21759
+ return new Error(`could not read \u2014 no AWS credentials for this account (${signInHint})`);
21760
+ }
21761
+ return new Error(`could not read \u2014 ${e.message ?? kind}`);
21762
+ }
21763
+ }
21764
+ async function runStackStatus(cwd, opts) {
21765
+ const targets = findStatusTargets(cwd, opts.stack);
21766
+ if (targets.length === 0) {
21767
+ logWarn(
21768
+ "No Mesh stack found here. Run from an app directory (a Pulumi.<stack>.yaml naming mesh:platform),\n or from your org's Mesh platform repo (its root, core/ or platform/)."
21769
+ );
21770
+ return true;
21771
+ }
21772
+ const infraLatest = latestPublished(INFRA_PACKAGE, cwd);
21773
+ const hubLatest = latestPublished(HUB_PACKAGE2, cwd);
21774
+ const running = resolveCliRuntime({ vcs: false }).version;
21775
+ const cliLatest = latestPublished(CLI_PACKAGE, cwd);
21776
+ const cli = { running, latest: cliLatest, behind: !!cliLatest && compareVersions2(running, cliLatest) < 0 };
21777
+ if (!infraLatest) logInfo("Registry lookup failed \u2014 `latest` is unknown. Run `mesh registry login` and retry.");
21778
+ const reports = [];
21779
+ for (const target of targets) {
21780
+ const projectDir = findAppRoot(cwd) ?? target.platformDir ?? cwd;
21781
+ const region = opts.region ?? readStackConfig(projectDir, target.stack, "aws:region") ?? "us-east-2";
21782
+ const ambient = !!process.env.AWS_ACCESS_KEY_ID || !!process.env.AWS_PROFILE;
21783
+ let profile = opts.profile;
21784
+ let credentials;
21785
+ if (!profile && !ambient) {
21786
+ profile = readStackConfig(projectDir, target.stack, "aws:profile") ?? void 0;
21787
+ if (!profile && target.kind === "app") {
21788
+ const role = readStackConfig(projectDir, target.stack, "mesh:deployerRole");
21789
+ if (role) credentials = (await resolveAwsCredentials(role, projectDir, target.stack))?.env;
21790
+ }
21791
+ }
21792
+ const loginContext = target.kind === "app" ? derivePlatformContext(projectDir, target.stack) : null;
21793
+ const signInHint = loginContext ? `sign in with \`mesh login ${loginContext} --device\`, or pass --profile` : "set AWS_PROFILE or pass --profile with access to the core's account";
21794
+ const auth = { credentials, profile, signInHint };
21795
+ const [core, platform] = await Promise.all([
21796
+ readRegistration(`/mesh-platform/${target.coreTenant}/${target.coreEnv}/core`, region, auth),
21797
+ readRegistration(`/mesh-platform/${target.tenant}/${target.env}/platform`, region, auth)
21798
+ ]);
21799
+ reports.push({ target, rows: buildRows(target, { core, platform }, { infra: infraLatest, hub: hubLatest }) });
21800
+ }
21801
+ if (opts.json) {
21802
+ process.stdout.write(JSON.stringify({ platforms: reports, cli }, null, 2) + "\n");
21803
+ } else {
21804
+ process.stdout.write(renderStatus(reports, cli) + "\n");
21805
+ }
21806
+ const behind = cli.behind || reports.some((r) => r.rows.some((row) => row.state === "behind" || row.undeployedPin));
21807
+ return !behind;
21808
+ }
21809
+ var INFRA_PACKAGE, HUB_PACKAGE2, CLI_PACKAGE;
21810
+ var init_stack_status = __esm({
21811
+ "packages/mesh-cli/src/commands/stack-status.ts"() {
21812
+ "use strict";
21813
+ init_log();
21814
+ init_pulumi();
21815
+ init_aws_auth();
21816
+ init_build_info();
21817
+ INFRA_PACKAGE = "@mesh-tech/infra-components";
21818
+ HUB_PACKAGE2 = "@mesh-tech/hub";
21819
+ CLI_PACKAGE = "@mesh-tech/mesh-cli";
21820
+ }
21821
+ });
21822
+
21823
+ // packages/mesh-cli/src/commands/stack.ts
21824
+ import { execFileSync as execFileSync32 } from "child_process";
21825
+ import * as path46 from "path";
21826
+ import * as fs39 from "fs";
21462
21827
  import { parse as parseYaml6 } from "yaml";
21463
21828
  function readTopLevelYamlKey(appRoot, stack, key) {
21464
- const configFile = path45.join(appRoot, `Pulumi.${stack}.yaml`);
21465
- if (!fs38.existsSync(configFile)) return null;
21466
- const content = fs38.readFileSync(configFile, "utf-8");
21829
+ const configFile = path46.join(appRoot, `Pulumi.${stack}.yaml`);
21830
+ if (!fs39.existsSync(configFile)) return null;
21831
+ const content = fs39.readFileSync(configFile, "utf-8");
21467
21832
  const pattern = new RegExp(`^${key}:\\s*(.+)$`, "m");
21468
21833
  const match = content.match(pattern);
21469
21834
  if (!match) return null;
21470
21835
  return match[1].trim().replace(/^["']|["']$/g, "");
21471
21836
  }
21472
21837
  function readConfigBlockKey(appRoot, stack, key) {
21473
- const configFile = path45.join(appRoot, `Pulumi.${stack}.yaml`);
21474
- if (!fs38.existsSync(configFile)) return null;
21475
- const content = fs38.readFileSync(configFile, "utf-8");
21838
+ const configFile = path46.join(appRoot, `Pulumi.${stack}.yaml`);
21839
+ if (!fs39.existsSync(configFile)) return null;
21840
+ const content = fs39.readFileSync(configFile, "utf-8");
21476
21841
  const pattern = new RegExp(`^\\s{2}${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}:\\s*(.+)$`, "m");
21477
21842
  const match = content.match(pattern);
21478
21843
  if (!match) return null;
@@ -21532,11 +21897,11 @@ ${records}
21532
21897
  }
21533
21898
  }
21534
21899
  function readBaseConfigFromYaml(appRoot, stack) {
21535
- const file = path45.join(appRoot, `Pulumi.${stack}.yaml`);
21536
- if (!fs38.existsSync(file)) return {};
21900
+ const file = path46.join(appRoot, `Pulumi.${stack}.yaml`);
21901
+ if (!fs39.existsSync(file)) return {};
21537
21902
  let doc;
21538
21903
  try {
21539
- doc = parseYaml6(fs38.readFileSync(file, "utf-8"));
21904
+ doc = parseYaml6(fs39.readFileSync(file, "utf-8"));
21540
21905
  } catch {
21541
21906
  return {};
21542
21907
  }
@@ -21549,7 +21914,7 @@ function readBaseConfigFromYaml(appRoot, stack) {
21549
21914
  }
21550
21915
  function getGitHubUsername() {
21551
21916
  try {
21552
- const result = execFileSync31("gh", ["api", "user", "--jq", ".login"], {
21917
+ const result = execFileSync32("gh", ["api", "user", "--jq", ".login"], {
21553
21918
  encoding: "utf-8",
21554
21919
  stdio: ["pipe", "pipe", "pipe"]
21555
21920
  });
@@ -21558,7 +21923,7 @@ function getGitHubUsername() {
21558
21923
  } catch {
21559
21924
  }
21560
21925
  try {
21561
- const result = execFileSync31("git", ["config", "user.email"], {
21926
+ const result = execFileSync32("git", ["config", "user.email"], {
21562
21927
  encoding: "utf-8",
21563
21928
  stdio: ["pipe", "pipe", "pipe"]
21564
21929
  });
@@ -21570,7 +21935,7 @@ function getGitHubUsername() {
21570
21935
  return null;
21571
21936
  }
21572
21937
  function registerStackCommand(program2) {
21573
- const stack = program2.command("stack").description("Manage personal Pulumi stacks");
21938
+ const stack = program2.command("stack").description("Manage personal Pulumi stacks and check a Mesh instance against the latest release");
21574
21939
  stack.command("init").description("Create a personal dev stack (dev-{github-username})").option("--from <stack>", "Base stack to copy config from").option("--name <name>", "Override stack name (default: dev-{username})").option("--adopt", "resume an EXISTING stack with this name (skips the availability check)", false).option(
21575
21940
  "--worktree",
21576
21941
  "Isolate this stack per git worktree \u2014 append the worktree token to the name so concurrent worktrees deploy to distinct stacks (no SSM/namespace collisions)"
@@ -21613,7 +21978,7 @@ Specify which to base on: mesh stack init --from <stack>`
21613
21978
  }
21614
21979
  }
21615
21980
  const baseConfigPath = `${appRoot}/Pulumi.${baseStack}.yaml`;
21616
- if (!fs38.existsSync(baseConfigPath)) {
21981
+ if (!fs39.existsSync(baseConfigPath)) {
21617
21982
  logError(`Stack config not found: Pulumi.${baseStack}.yaml`);
21618
21983
  process.exit(1);
21619
21984
  }
@@ -21635,8 +22000,8 @@ Specify which to base on: mesh stack init --from <stack>`
21635
22000
  );
21636
22001
  }
21637
22002
  }
21638
- const newConfigPath = path45.join(appRoot, `Pulumi.${newStack}.yaml`);
21639
- const configExists = fs38.existsSync(newConfigPath);
22003
+ const newConfigPath = path46.join(appRoot, `Pulumi.${newStack}.yaml`);
22004
+ const configExists = fs39.existsSync(newConfigPath);
21640
22005
  const secretsProvider = readTopLevelYamlKey(appRoot, baseStack, "secretsprovider");
21641
22006
  const credEnv = await resolvePulumiEnv({ appRoot, stack: baseStack });
21642
22007
  const pulumiEnv = { ...process.env, ...credEnv };
@@ -21644,7 +22009,7 @@ Specify which to base on: mesh stack init --from <stack>`
21644
22009
  if (!opts.adopt) {
21645
22010
  let existing = [];
21646
22011
  try {
21647
- const raw = execFileSync31("pulumi", ["stack", "ls", "--json"], {
22012
+ const raw = execFileSync32("pulumi", ["stack", "ls", "--json"], {
21648
22013
  cwd: appRoot,
21649
22014
  encoding: "utf-8",
21650
22015
  env: pulumiEnv,
@@ -21668,7 +22033,7 @@ Specify which to base on: mesh stack init --from <stack>`
21668
22033
  logInfo(`Using KMS secrets provider: ${secretsProvider}`);
21669
22034
  }
21670
22035
  try {
21671
- execFileSync31("pulumi", initArgs, {
22036
+ execFileSync32("pulumi", initArgs, {
21672
22037
  cwd: appRoot,
21673
22038
  env: pulumiEnv,
21674
22039
  stdio: "inherit"
@@ -21685,7 +22050,7 @@ Specify which to base on: mesh stack init --from <stack>`
21685
22050
  }
21686
22051
  }
21687
22052
  try {
21688
- execFileSync31("pulumi", ["stack", "select", newStack], {
22053
+ execFileSync32("pulumi", ["stack", "select", newStack], {
21689
22054
  cwd: appRoot,
21690
22055
  env: pulumiEnv,
21691
22056
  stdio: ["pipe", "pipe", "pipe"]
@@ -21695,7 +22060,7 @@ Specify which to base on: mesh stack init --from <stack>`
21695
22060
  if (!configExists) {
21696
22061
  let baseConfig = {};
21697
22062
  try {
21698
- const raw = execFileSync31(
22063
+ const raw = execFileSync32(
21699
22064
  "pulumi",
21700
22065
  ["config", "--json", "--stack", baseStack],
21701
22066
  { cwd: appRoot, env: pulumiEnv, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
@@ -21713,19 +22078,19 @@ Specify which to base on: mesh stack init --from <stack>`
21713
22078
  if (key === "mesh:deploy") continue;
21714
22079
  try {
21715
22080
  if (entry.objectValue !== void 0) {
21716
- execFileSync31(
22081
+ execFileSync32(
21717
22082
  "pulumi",
21718
22083
  ["config", "set", key, JSON.stringify(entry.objectValue)],
21719
22084
  { cwd: appRoot, env: pulumiEnv, stdio: ["pipe", "pipe", "pipe"] }
21720
22085
  );
21721
22086
  } else if (entry.value === "true" || entry.value === "false") {
21722
- execFileSync31(
22087
+ execFileSync32(
21723
22088
  "pulumi",
21724
22089
  ["config", "set", "--type", "bool", key, entry.value],
21725
22090
  { cwd: appRoot, env: pulumiEnv, stdio: ["pipe", "pipe", "pipe"] }
21726
22091
  );
21727
22092
  } else {
21728
- execFileSync31(
22093
+ execFileSync32(
21729
22094
  "pulumi",
21730
22095
  ["config", "set", key, entry.value],
21731
22096
  { cwd: appRoot, env: pulumiEnv, stdio: ["pipe", "pipe", "pipe"] }
@@ -21738,7 +22103,7 @@ Specify which to base on: mesh stack init --from <stack>`
21738
22103
  const baseEnv = readConfigBlockKey(appRoot, baseStack, "mesh:coreEnv") ?? (baseTenant && baseStack.startsWith(`${baseTenant}-`) ? baseStack.slice(baseTenant.length + 1) : baseStack);
21739
22104
  const setCfg = (args) => {
21740
22105
  try {
21741
- execFileSync31("pulumi", ["config", "set", ...args], {
22106
+ execFileSync32("pulumi", ["config", "set", ...args], {
21742
22107
  cwd: appRoot,
21743
22108
  env: pulumiEnv,
21744
22109
  stdio: ["pipe", "pipe", "pipe"]
@@ -21787,7 +22152,7 @@ Specify which to base on: mesh stack init --from <stack>`
21787
22152
  `Configured Pulumi.${newStack}.yaml (personal platform env on core '${baseEnv}'${opts.parentZone ? `, DNS zone ${newStack.startsWith(`${baseTenant}-`) ? newStack.slice(baseTenant.length + 1) : newStack}.${opts.parentZone}` : ""})`
21788
22153
  );
21789
22154
  } else {
21790
- execFileSync31("pulumi", ["config", "set", "--type", "bool", "mesh:deploy", "false"], {
22155
+ execFileSync32("pulumi", ["config", "set", "--type", "bool", "mesh:deploy", "false"], {
21791
22156
  cwd: appRoot,
21792
22157
  env: pulumiEnv,
21793
22158
  stdio: ["pipe", "pipe", "pipe"]
@@ -21817,12 +22182,18 @@ Specify which to base on: mesh stack init --from <stack>`
21817
22182
  const args = ["stack", "rm", name];
21818
22183
  if (opts.yes) args.push("--yes");
21819
22184
  try {
21820
- execFileSync31("pulumi", args, { cwd: appRoot, env: pulumiEnv, stdio: "inherit" });
22185
+ execFileSync32("pulumi", args, { cwd: appRoot, env: pulumiEnv, stdio: "inherit" });
21821
22186
  logSuccess(`Removed stack ${name}`);
21822
22187
  } catch (err) {
21823
22188
  process.exit(err.status ?? 1);
21824
22189
  }
21825
22190
  });
22191
+ stack.command("status").description(
22192
+ "Read-only drift check: for each platform this directory deploys (a platform repo's core/platform stacks) or deploys apps to (an app's mesh:platform), compare the pinned, deployed (the core and platform SSM registrations written by the last successful deploy) and latest published @mesh-tech/infra-components and @mesh-tech/hub, plus this CLI against the latest @mesh-tech/mesh-cli, and print the upgrade steps when anything is behind. Changes nothing."
22193
+ ).option("--stack <name>", "check one stack (default: every Pulumi.<stack>.yaml here)").option("--profile <profile>", "AWS profile to read the SSM registrations with (default: AWS_PROFILE, else the stack's aws:profile, else an app stack's deployer role)").option("--region <region>", "AWS region of the registrations (default: the stack's aws:region, else us-east-2)").option("--json", "print the report as JSON", false).option("--check", "exit 1 when any layer or the CLI is behind, or a pin is not deployed", false).action(async (opts) => {
22194
+ const ok = await runStackStatus(process.cwd(), opts);
22195
+ if (opts.check && !ok) process.exit(1);
22196
+ });
21826
22197
  }
21827
22198
  var init_stack2 = __esm({
21828
22199
  "packages/mesh-cli/src/commands/stack.ts"() {
@@ -21831,6 +22202,7 @@ var init_stack2 = __esm({
21831
22202
  init_pulumi();
21832
22203
  init_worktree_identity();
21833
22204
  init_pulumi_run();
22205
+ init_stack_status();
21834
22206
  }
21835
22207
  });
21836
22208
 
@@ -22029,12 +22401,12 @@ var init_recover_conversation = __esm({
22029
22401
  });
22030
22402
 
22031
22403
  // packages/mesh-cli/src/utils/temporal-codec.ts
22032
- import { execFileSync as execFileSync32 } from "child_process";
22404
+ import { execFileSync as execFileSync33 } from "child_process";
22033
22405
  import { webcrypto as crypto4 } from "node:crypto";
22034
22406
  function resolveTemporalEncodingKeyFromK8s(namespace) {
22035
22407
  const secretName = `${namespace}-temporal-encoding-key`;
22036
22408
  try {
22037
- const b64 = execFileSync32(
22409
+ const b64 = execFileSync33(
22038
22410
  "kubectl",
22039
22411
  [
22040
22412
  "get",
@@ -22285,7 +22657,7 @@ var init_capture_history = __esm({
22285
22657
  import { spawnSync as spawnSync4 } from "node:child_process";
22286
22658
  import { writeFileSync as writeFileSync24, mkdirSync as mkdirSync20 } from "node:fs";
22287
22659
  import { homedir as homedir6 } from "node:os";
22288
- import { dirname as dirname29, join as join42, resolve as resolve19 } from "node:path";
22660
+ import { dirname as dirname30, join as join43, resolve as resolve19 } from "node:path";
22289
22661
  async function resolveConnection(options) {
22290
22662
  if (options.address && options.namespace) {
22291
22663
  return { address: options.address, namespace: options.namespace };
@@ -22713,8 +23085,8 @@ async function captureHistory(workflowId, runId, options) {
22713
23085
  }
22714
23086
  const { serializeHistoryToFixture: serializeHistoryToFixture2 } = await Promise.resolve().then(() => (init_capture_history(), capture_history_exports));
22715
23087
  const { fixture, eventCount, decryptedPayloads } = await serializeHistoryToFixture2(events, decrypter);
22716
- const outPath = options.out ?? join42(homedir6(), ".mesh", "replay-histories", `${sanitizeFileId(workflowId)}.json`);
22717
- mkdirSync20(dirname29(outPath), { recursive: true });
23088
+ const outPath = options.out ?? join43(homedir6(), ".mesh", "replay-histories", `${sanitizeFileId(workflowId)}.json`);
23089
+ mkdirSync20(dirname30(outPath), { recursive: true });
22718
23090
  if (options.out) warnIfNotGitIgnored(options.out);
22719
23091
  writeFileSync24(outPath, JSON.stringify(fixture, null, 2) + "\n");
22720
23092
  logSuccess(
@@ -22733,7 +23105,7 @@ function sanitizeFileId(id) {
22733
23105
  function warnIfNotGitIgnored(outPath) {
22734
23106
  const abs = resolve19(outPath);
22735
23107
  try {
22736
- const res = spawnSync4("git", ["-C", dirname29(abs), "check-ignore", "-q", abs], {
23108
+ const res = spawnSync4("git", ["-C", dirname30(abs), "check-ignore", "-q", abs], {
22737
23109
  stdio: "ignore"
22738
23110
  });
22739
23111
  if (res.status !== 1) return;
@@ -22905,8 +23277,8 @@ var init_temporal = __esm({
22905
23277
 
22906
23278
  // packages/mesh-cli/src/commands/tenant.ts
22907
23279
  import chalk7 from "chalk";
22908
- import * as fs39 from "fs";
22909
- import * as path46 from "path";
23280
+ import * as fs40 from "fs";
23281
+ import * as path47 from "path";
22910
23282
  import { parseDocument, YAMLMap, isMap } from "yaml";
22911
23283
  function validateTenantName(name) {
22912
23284
  return /^[a-z][a-z0-9-]*$/.test(name) && !name.endsWith("-");
@@ -22974,16 +23346,16 @@ function resolveStackConfig(explicitStack) {
22974
23346
  { remediation: { command: "mesh tenant add <name> --stack <stack>" } }
22975
23347
  );
22976
23348
  }
22977
- const file = path46.join(appRoot, `Pulumi.${stack}.yaml`);
22978
- if (!fs39.existsSync(file)) {
23349
+ const file = path47.join(appRoot, `Pulumi.${stack}.yaml`);
23350
+ if (!fs40.existsSync(file)) {
22979
23351
  throw new MeshCliError(`Stack config not found: ${file}`, {
22980
23352
  remediation: { command: "mesh tenant add <name> --stack <stack>" }
22981
23353
  });
22982
23354
  }
22983
- return { appRoot, stack, file, content: fs39.readFileSync(file, "utf-8") };
23355
+ return { appRoot, stack, file, content: fs40.readFileSync(file, "utf-8") };
22984
23356
  }
22985
23357
  function readProjectName(appRoot) {
22986
- const projectDoc = parseDocument(fs39.readFileSync(path46.join(appRoot, "Pulumi.yaml"), "utf-8"));
23358
+ const projectDoc = parseDocument(fs40.readFileSync(path47.join(appRoot, "Pulumi.yaml"), "utf-8"));
22987
23359
  return String(projectDoc.get("name") ?? "");
22988
23360
  }
22989
23361
  function looksLikePlatformLayer(projectName) {
@@ -23026,12 +23398,12 @@ function registerTenantCommands(program2) {
23026
23398
  if (!result.ok) {
23027
23399
  if (result.reason === "exists") {
23028
23400
  throw new MeshCliError(
23029
- `${result.detail} (${path46.basename(resolved.file)}). Edit the existing entry instead of re-adding it.`
23401
+ `${result.detail} (${path47.basename(resolved.file)}). Edit the existing entry instead of re-adding it.`
23030
23402
  );
23031
23403
  }
23032
23404
  throw new MeshCliError(`${resolved.file}: ${result.detail}`);
23033
23405
  }
23034
- atomicWriteFileSync(resolved.file, result.yaml, fs39.statSync(resolved.file).mode & 511);
23406
+ atomicWriteFileSync(resolved.file, result.yaml, fs40.statSync(resolved.file).mode & 511);
23035
23407
  if (opts.json) {
23036
23408
  emitJsonPayload({
23037
23409
  ok: true,
@@ -23044,7 +23416,7 @@ function registerTenantCommands(program2) {
23044
23416
  return;
23045
23417
  }
23046
23418
  logInfo(
23047
- `Tenant '${name}' registered in ${path46.basename(resolved.file)} (mesh:tenants, subdomain '${subdomain}')${result.createdTenantsBlock ? " \u2014 created the mesh:tenants block" : ""}`
23419
+ `Tenant '${name}' registered in ${path47.basename(resolved.file)} (mesh:tenants, subdomain '${subdomain}')${result.createdTenantsBlock ? " \u2014 created the mesh:tenants block" : ""}`
23048
23420
  );
23049
23421
  console.log("");
23050
23422
  console.log("Next steps:");
@@ -23070,7 +23442,7 @@ function registerTenantCommands(program2) {
23070
23442
  `Project '${projectName}' does not look like a platform layer \u2014 tenants are declared on the tenant platform repo's PLATFORM stack (e.g. mesh-sandbox/platform).`
23071
23443
  );
23072
23444
  }
23073
- logInfo(`No tenants declared in ${path46.basename(resolved.file)}.`);
23445
+ logInfo(`No tenants declared in ${path47.basename(resolved.file)}.`);
23074
23446
  return;
23075
23447
  }
23076
23448
  logInfo(`Tenants on stack '${resolved.stack}':`);
@@ -23092,9 +23464,9 @@ var init_tenant = __esm({
23092
23464
 
23093
23465
  // packages/mesh-cli/src/commands/tunnel/index.ts
23094
23466
  import { spawn as spawn9 } from "child_process";
23095
- import * as fs40 from "fs";
23467
+ import * as fs41 from "fs";
23096
23468
  import * as os12 from "os";
23097
- import * as path47 from "path";
23469
+ import * as path48 from "path";
23098
23470
  import {
23099
23471
  SecretsManagerClient as SecretsManagerClient9,
23100
23472
  GetSecretValueCommand as GetSecretValueCommand9
@@ -23114,9 +23486,9 @@ function readPulumiAwsRegion(stage) {
23114
23486
  const preferred = stage ? `Pulumi.${stage}.yaml` : void 0;
23115
23487
  const rank = (f) => f === preferred ? 0 : f === "Pulumi.yaml" ? 2 : 1;
23116
23488
  try {
23117
- const files = fs40.readdirSync(".").filter((f) => f.startsWith("Pulumi.") && f.endsWith(".yaml")).sort((a, b) => rank(a) - rank(b));
23489
+ const files = fs41.readdirSync(".").filter((f) => f.startsWith("Pulumi.") && f.endsWith(".yaml")).sort((a, b) => rank(a) - rank(b));
23118
23490
  for (const f of files) {
23119
- const m = fs40.readFileSync(f, "utf-8").match(/^\s*aws:region:\s*["']?([^"'\n]+)["']?/m);
23491
+ const m = fs41.readFileSync(f, "utf-8").match(/^\s*aws:region:\s*["']?([^"'\n]+)["']?/m);
23120
23492
  if (m?.[1]) return m[1].trim();
23121
23493
  }
23122
23494
  } catch {
@@ -23125,14 +23497,14 @@ function readPulumiAwsRegion(stage) {
23125
23497
  }
23126
23498
  function awsProfileSections(name) {
23127
23499
  return [
23128
- [path47.join(os12.homedir(), ".aws", "config"), `[profile ${name}]`],
23129
- [path47.join(os12.homedir(), ".aws", "credentials"), `[${name}]`]
23500
+ [path48.join(os12.homedir(), ".aws", "config"), `[profile ${name}]`],
23501
+ [path48.join(os12.homedir(), ".aws", "credentials"), `[${name}]`]
23130
23502
  ];
23131
23503
  }
23132
23504
  function awsProfileExists(name) {
23133
23505
  for (const [file, header] of awsProfileSections(name)) {
23134
23506
  try {
23135
- const lines = fs40.readFileSync(file, "utf-8").split("\n");
23507
+ const lines = fs41.readFileSync(file, "utf-8").split("\n");
23136
23508
  if (lines.some((l) => l.trim() === header)) return true;
23137
23509
  } catch {
23138
23510
  }
@@ -23158,7 +23530,7 @@ function awsProfileRegion(profile) {
23158
23530
  if (!profile) return void 0;
23159
23531
  for (const [file, header] of awsProfileSections(profile)) {
23160
23532
  try {
23161
- const region = parseProfileRegion(fs40.readFileSync(file, "utf-8"), header);
23533
+ const region = parseProfileRegion(fs41.readFileSync(file, "utf-8"), header);
23162
23534
  if (region) return region;
23163
23535
  } catch {
23164
23536
  }
@@ -23167,14 +23539,14 @@ function awsProfileRegion(profile) {
23167
23539
  }
23168
23540
  function awsDefaultProfileSections() {
23169
23541
  return [
23170
- [path47.join(os12.homedir(), ".aws", "config"), "[default]"],
23171
- [path47.join(os12.homedir(), ".aws", "credentials"), "[default]"]
23542
+ [path48.join(os12.homedir(), ".aws", "config"), "[default]"],
23543
+ [path48.join(os12.homedir(), ".aws", "credentials"), "[default]"]
23172
23544
  ];
23173
23545
  }
23174
23546
  function awsDefaultProfileRegion() {
23175
23547
  for (const [file, header] of awsDefaultProfileSections()) {
23176
23548
  try {
23177
- const region = parseProfileRegion(fs40.readFileSync(file, "utf-8"), header);
23549
+ const region = parseProfileRegion(fs41.readFileSync(file, "utf-8"), header);
23178
23550
  if (region) return region;
23179
23551
  } catch {
23180
23552
  }
@@ -23561,8 +23933,8 @@ async function resolveToken(opts) {
23561
23933
  "no token: pass --token, set VCS_TOKEN, or pass --context <platform-context> (after mesh login)"
23562
23934
  );
23563
23935
  }
23564
- async function vcsApi(baseUrl, token, path49, method = "GET", body) {
23565
- const res = await fetch(`${baseUrl}${path49}`, {
23936
+ async function vcsApi(baseUrl, token, path50, method = "GET", body) {
23937
+ const res = await fetch(`${baseUrl}${path50}`, {
23566
23938
  method,
23567
23939
  headers: {
23568
23940
  authorization: `Bearer ${token}`,
@@ -23573,7 +23945,7 @@ async function vcsApi(baseUrl, token, path49, method = "GET", body) {
23573
23945
  const data = await res.json().catch(() => ({}));
23574
23946
  if (!res.ok) {
23575
23947
  throw new Error(
23576
- `${method} ${path49} failed (${res.status}): ${String(data.error ?? "unknown error")}`
23948
+ `${method} ${path50} failed (${res.status}): ${String(data.error ?? "unknown error")}`
23577
23949
  );
23578
23950
  }
23579
23951
  return data;
@@ -23619,8 +23991,8 @@ var init_clone = __esm({
23619
23991
 
23620
23992
  // packages/mesh-cli/src/commands/vcs/get.ts
23621
23993
  import { writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
23622
- import { join as join45, dirname as dirname30 } from "node:path";
23623
- async function getCommand(repo, path49, opts) {
23994
+ import { join as join46, dirname as dirname31 } from "node:path";
23995
+ async function getCommand(repo, path50, opts) {
23624
23996
  let vcsBaseUrl;
23625
23997
  let token;
23626
23998
  if (opts.target) {
@@ -23635,18 +24007,18 @@ async function getCommand(repo, path49, opts) {
23635
24007
  if (!token) throw new Error("no token: pass --target (after mesh login), or --token / --context");
23636
24008
  const reader = createVcsFolderReader({ vcsBaseUrl, token });
23637
24009
  try {
23638
- const { ref, files } = await reader.readPath(repo, path49);
24010
+ const { ref, files } = await reader.readPath(repo, path50);
23639
24011
  if (files.length === 0) {
23640
- logInfo(`no files at ${repo}:${path49}`);
24012
+ logInfo(`no files at ${repo}:${path50}`);
23641
24013
  return;
23642
24014
  }
23643
24015
  if (opts.output) {
23644
24016
  for (const f of files) {
23645
- const abs = join45(opts.output, f.path);
23646
- await mkdir2(dirname30(abs), { recursive: true });
24017
+ const abs = join46(opts.output, f.path);
24018
+ await mkdir2(dirname31(abs), { recursive: true });
23647
24019
  await writeFile2(abs, f.contents);
23648
24020
  }
23649
- logInfo(`wrote ${files.length} file(s) from ${repo}:${path49} (${ref}) \u2192 ${opts.output}`);
24021
+ logInfo(`wrote ${files.length} file(s) from ${repo}:${path50} (${ref}) \u2192 ${opts.output}`);
23650
24022
  } else {
23651
24023
  for (const f of files) {
23652
24024
  if (files.length > 1) process.stdout.write(`
@@ -23671,10 +24043,10 @@ var init_get = __esm({
23671
24043
  });
23672
24044
 
23673
24045
  // packages/mesh-cli/src/commands/vcs/drafts.ts
23674
- async function call(opts, repo, path49, method = "GET", body) {
24046
+ async function call(opts, repo, path50, method = "GET", body) {
23675
24047
  const target = await resolveTarget2({ ...opts, repo });
23676
24048
  const token = await resolveToken(opts);
23677
- return vcsApi(target.baseUrl, token, `/v1/repos/${repo}${path49}`, method, body);
24049
+ return vcsApi(target.baseUrl, token, `/v1/repos/${repo}${path50}`, method, body);
23678
24050
  }
23679
24051
  async function draftsListCommand(repo, opts) {
23680
24052
  const data = await call(opts, repo, `/drafts${opts.all ? "?all=true" : ""}`);
@@ -23729,7 +24101,7 @@ var init_drafts = __esm({
23729
24101
 
23730
24102
  // packages/mesh-cli/src/commands/vcs/propose.ts
23731
24103
  import { readFile as readFile3 } from "node:fs/promises";
23732
- import { join as join46 } from "node:path";
24104
+ import { join as join47 } from "node:path";
23733
24105
  function parseStatus(out) {
23734
24106
  const tokens = out.split("\0");
23735
24107
  const changes = [];
@@ -23762,13 +24134,13 @@ function parseStatus(out) {
23762
24134
  }
23763
24135
  return changes;
23764
24136
  }
23765
- async function writeOp(cwd, path49) {
23766
- const buf = await readFile3(join46(cwd, path49));
24137
+ async function writeOp(cwd, path50) {
24138
+ const buf = await readFile3(join47(cwd, path50));
23767
24139
  const asUtf8 = buf.toString("utf8");
23768
24140
  if (Buffer.from(asUtf8, "utf8").equals(buf)) {
23769
- return { op: "write", path: path49, content: asUtf8 };
24141
+ return { op: "write", path: path50, content: asUtf8 };
23770
24142
  }
23771
- return { op: "write", path: path49, content: buf.toString("base64"), encoding: "base64" };
24143
+ return { op: "write", path: path50, content: buf.toString("base64"), encoding: "base64" };
23772
24144
  }
23773
24145
  async function proposeCommand(opts) {
23774
24146
  const cwd = process.cwd();
@@ -23812,8 +24184,8 @@ async function proposeCommand(opts) {
23812
24184
  operations,
23813
24185
  ...opts.mergeParent ? { mergeParent: opts.mergeParent } : {}
23814
24186
  };
23815
- const path49 = opts.revise ? `/v1/repos/${target.repo}/proposals/${opts.revise}/revisions` : `/v1/repos/${target.repo}/proposals`;
23816
- const data = await vcsApi(target.baseUrl, token, path49, "POST", body);
24187
+ const path50 = opts.revise ? `/v1/repos/${target.repo}/proposals/${opts.revise}/revisions` : `/v1/repos/${target.repo}/proposals`;
24188
+ const data = await vcsApi(target.baseUrl, token, path50, "POST", body);
23817
24189
  console.log(
23818
24190
  `proposal ${data.proposalId} @ ${data.sha.slice(0, 8)} \u2014 review: mesh vcs show ${target.repo} ${data.proposalId} --url ${target.baseUrl}`
23819
24191
  );
@@ -23828,28 +24200,28 @@ var init_propose = __esm({
23828
24200
  // packages/mesh-cli/src/commands/vcs/rm.ts
23829
24201
  import { mkdtemp as mkdtemp2, rm as rmrf } from "node:fs/promises";
23830
24202
  import { tmpdir as tmpdir10 } from "node:os";
23831
- import { join as join47 } from "node:path";
24203
+ import { join as join48 } from "node:path";
23832
24204
  function normalize2(p) {
23833
24205
  return p.trim().replace(/^\.\//, "").replace(/\/+$/, "");
23834
24206
  }
23835
24207
  function expandPathsToDeletions(repoFiles, requested) {
23836
24208
  const out = /* @__PURE__ */ new Set();
23837
24209
  for (const raw of requested) {
23838
- const path49 = normalize2(raw);
23839
- if (path49.startsWith("/") || path49.split("/").includes("..")) {
24210
+ const path50 = normalize2(raw);
24211
+ if (path50.startsWith("/") || path50.split("/").includes("..")) {
23840
24212
  throw new Error(
23841
24213
  `"${raw}" is not a repo-relative path \u2014 vcs paths never start with "/" or traverse with "..".`
23842
24214
  );
23843
24215
  }
23844
- const exact = repoFiles.filter((f) => f === path49);
24216
+ const exact = repoFiles.filter((f) => f === path50);
23845
24217
  if (exact.length > 0) {
23846
24218
  for (const f of exact) out.add(f);
23847
24219
  continue;
23848
24220
  }
23849
- const under = repoFiles.filter((f) => f.startsWith(`${path49}/`));
24221
+ const under = repoFiles.filter((f) => f.startsWith(`${path50}/`));
23850
24222
  if (under.length === 0) {
23851
24223
  throw new Error(
23852
- `"${raw}" matched no files in the repo \u2014 nothing was proposed. Check the path with: mesh vcs get <repo> ${path49}`
24224
+ `"${raw}" matched no files in the repo \u2014 nothing was proposed. Check the path with: mesh vcs get <repo> ${path50}`
23853
24225
  );
23854
24226
  }
23855
24227
  for (const f of under) out.add(f);
@@ -23860,7 +24232,7 @@ async function rmCommand(repo, paths, opts) {
23860
24232
  const baseUrl = (opts.url ?? process.env.VCS_URL)?.replace(/\/$/, "");
23861
24233
  if (!baseUrl) throw new Error("rm requires --url or VCS_URL");
23862
24234
  const token = await resolveToken(opts);
23863
- const dir = await mkdtemp2(join47(tmpdir10(), "mesh-vcs-rm-"));
24235
+ const dir = await mkdtemp2(join48(tmpdir10(), "mesh-vcs-rm-"));
23864
24236
  try {
23865
24237
  const git = (args, cwd) => gitWithAuth(args, token, cwd);
23866
24238
  await git([
@@ -23885,7 +24257,7 @@ async function rmCommand(repo, paths, opts) {
23885
24257
  const body = {
23886
24258
  baseCommit: head.trim(),
23887
24259
  message: opts.message ?? `Delete ${doomed.length} file(s)`,
23888
- operations: doomed.map((path49) => ({ op: "delete", path: path49 }))
24260
+ operations: doomed.map((path50) => ({ op: "delete", path: path50 }))
23889
24261
  };
23890
24262
  const data = await vcsApi(
23891
24263
  baseUrl,
@@ -23911,10 +24283,10 @@ var init_rm = __esm({
23911
24283
  });
23912
24284
 
23913
24285
  // packages/mesh-cli/src/commands/vcs/review.ts
23914
- async function call2(opts, repo, path49, method = "GET", body) {
24286
+ async function call2(opts, repo, path50, method = "GET", body) {
23915
24287
  const target = await resolveTarget2({ ...opts, repo });
23916
24288
  const token = await resolveToken(opts);
23917
- return vcsApi(target.baseUrl, token, `/v1/repos/${repo}${path49}`, method, body);
24289
+ return vcsApi(target.baseUrl, token, `/v1/repos/${repo}${path50}`, method, body);
23918
24290
  }
23919
24291
  async function proposalsCommand(repo, opts) {
23920
24292
  console.log(JSON.stringify(await call2(opts, repo, "/proposals"), null, 2));
@@ -24061,8 +24433,8 @@ function computeHappyPath(nodes, edges) {
24061
24433
  ];
24062
24434
  const visited = /* @__PURE__ */ new Set([startNode.id]);
24063
24435
  while (queue.length > 0) {
24064
- const { id, path: path49 } = queue.shift();
24065
- if (targetIds.has(id)) return path49;
24436
+ const { id, path: path50 } = queue.shift();
24437
+ if (targetIds.has(id)) return path50;
24066
24438
  const neighbors = adjacency.get(id) ?? [];
24067
24439
  const hasMainEdge = neighbors.some((e) => e.isMainPath);
24068
24440
  const candidates = hasMainEdge ? neighbors.filter((e) => e.isMainPath) : neighbors;
@@ -24082,7 +24454,7 @@ function computeHappyPath(nodes, edges) {
24082
24454
  if (!allowExceptional && (neighbor.isExceptional || neighbor.isTimeout || failureEndIds.has(neighbor.to)))
24083
24455
  continue;
24084
24456
  visited.add(neighbor.to);
24085
- queue.push({ id: neighbor.to, path: [...path49, neighbor.to] });
24457
+ queue.push({ id: neighbor.to, path: [...path50, neighbor.to] });
24086
24458
  }
24087
24459
  }
24088
24460
  return null;
@@ -24367,11 +24739,11 @@ function wrapIntoGroup(workflow, nodeIds, groupId, label, groupType) {
24367
24739
  }
24368
24740
  }
24369
24741
  if (found) {
24370
- const path49 = [];
24371
- for (let c = found; c !== void 0; c = prev.get(c)) path49.unshift(c);
24372
- for (let i = 0; i < path49.length - 1; i++) mainEdgeKeys.add(edgeKey(path49[i], path49[i + 1]));
24742
+ const path50 = [];
24743
+ for (let c = found; c !== void 0; c = prev.get(c)) path50.unshift(c);
24744
+ for (let i = 0; i < path50.length - 1; i++) mainEdgeKeys.add(edgeKey(path50[i], path50[i + 1]));
24373
24745
  mainEdgeKeys.add(edgeKey(found, successExit.id));
24374
- mainBody = path49;
24746
+ mainBody = path50;
24375
24747
  mainTail = [successExit.id];
24376
24748
  }
24377
24749
  }
@@ -24907,11 +25279,11 @@ function buildProcessManifest(artifact, outcomeNodeIds) {
24907
25279
  ...pointOfNoReturnStage ? { pointOfNoReturnStageId: pointOfNoReturnStage.id } : {}
24908
25280
  };
24909
25281
  }
24910
- function formatPath(path49) {
24911
- return path49.length > 0 ? z4.core.toDotPath(path49) : "(root)";
25282
+ function formatPath(path50) {
25283
+ return path50.length > 0 ? z4.core.toDotPath(path50) : "(root)";
24912
25284
  }
24913
25285
  function issueToDiagnostic(issue) {
24914
- const path49 = formatPath(issue.path);
25286
+ const path50 = formatPath(issue.path);
24915
25287
  let code = "SCHEMA_INVALID";
24916
25288
  if (issue.code === "custom") {
24917
25289
  const paramCode = issue.params?.code;
@@ -24922,8 +25294,8 @@ function issueToDiagnostic(issue) {
24922
25294
  return {
24923
25295
  severity: "error",
24924
25296
  code,
24925
- message: `${path49}: ${issue.message}`,
24926
- path: path49
25297
+ message: `${path50}: ${issue.message}`,
25298
+ path: path50
24927
25299
  };
24928
25300
  }
24929
25301
  function upgradeV1(artifact) {
@@ -25090,24 +25462,24 @@ var init_process_artifact = __esm({
25090
25462
  });
25091
25463
  }
25092
25464
  const declaredSelectors = new Set(Object.keys(process2.selectors ?? {}));
25093
- const checkActor = (actorId, path49) => {
25465
+ const checkActor = (actorId, path50) => {
25094
25466
  if (actorId === void 0) return;
25095
25467
  if (!Object.prototype.hasOwnProperty.call(process2.actors, actorId)) {
25096
25468
  ctx.addIssue({
25097
25469
  code: "custom",
25098
- path: path49,
25099
- message: `Actor "${actorId}" is referenced at ${z4.core.toDotPath(path49)} but not declared in process.actors. Declare the actor (label, and optionally category/selector) or fix the reference.`,
25470
+ path: path50,
25471
+ message: `Actor "${actorId}" is referenced at ${z4.core.toDotPath(path50)} but not declared in process.actors. Declare the actor (label, and optionally category/selector) or fix the reference.`,
25100
25472
  params: { code: "UNDECLARED_ACTOR" }
25101
25473
  });
25102
25474
  }
25103
25475
  };
25104
- const checkSelector = (selector, path49) => {
25476
+ const checkSelector = (selector, path50) => {
25105
25477
  if (selector === void 0) return;
25106
25478
  if (!declaredSelectors.has(selector)) {
25107
25479
  ctx.addIssue({
25108
25480
  code: "custom",
25109
- path: path49,
25110
- message: `Selector "${selector}" is referenced at ${z4.core.toDotPath(path49)} but not declared in process.selectors. Declare the selector or fix the reference.`,
25481
+ path: path50,
25482
+ message: `Selector "${selector}" is referenced at ${z4.core.toDotPath(path50)} but not declared in process.selectors. Declare the selector or fix the reference.`,
25111
25483
  params: { code: "UNDECLARED_SELECTOR" }
25112
25484
  });
25113
25485
  }
@@ -25121,20 +25493,20 @@ var init_process_artifact = __esm({
25121
25493
  outcome: "DUPLICATE_OUTCOME_ID"
25122
25494
  };
25123
25495
  const idFirstSeenAt = /* @__PURE__ */ new Map();
25124
- const checkId = (id, kind, path49) => {
25496
+ const checkId = (id, kind, path50) => {
25125
25497
  const first = idFirstSeenAt.get(id);
25126
25498
  if (first) {
25127
25499
  const kindLabel = kind === first.kind ? kind : "process";
25128
25500
  ctx.addIssue({
25129
25501
  code: "custom",
25130
- path: path49,
25131
- message: `Duplicate ${kindLabel} id "${id}": ${kind} at ${z4.core.toDotPath(path49)} collides with ${first.kind} at ${z4.core.toDotPath(first.path)}.`,
25502
+ path: path50,
25503
+ message: `Duplicate ${kindLabel} id "${id}": ${kind} at ${z4.core.toDotPath(path50)} collides with ${first.kind} at ${z4.core.toDotPath(first.path)}.`,
25132
25504
  params: {
25133
25505
  code: kind === first.kind ? SAME_KIND_CODE[kind] : "DUPLICATE_PROCESS_ID"
25134
25506
  }
25135
25507
  });
25136
25508
  } else {
25137
- idFirstSeenAt.set(id, { kind, path: path49 });
25509
+ idFirstSeenAt.set(id, { kind, path: path50 });
25138
25510
  }
25139
25511
  };
25140
25512
  process2.stages.forEach((stage, stageIndex) => {
@@ -25181,13 +25553,13 @@ function lintProcess(artifact, inventory, predicateNames, options = {}) {
25181
25553
  const internalCommands = process2.internalCommands ?? [];
25182
25554
  const internalSet = new Set(internalCommands);
25183
25555
  const boundCommandPaths = /* @__PURE__ */ new Map();
25184
- const checkPredicate = (predicate, path49) => {
25556
+ const checkPredicate = (predicate, path50) => {
25185
25557
  if (!predicateSet.has(predicate)) {
25186
25558
  diagnostics.push({
25187
25559
  severity: "error",
25188
25560
  code: "UNKNOWN_PREDICATE",
25189
- message: `Predicate "${predicate}" referenced at ${path49} is not in the predicate registry.`,
25190
- path: path49
25561
+ message: `Predicate "${predicate}" referenced at ${path50} is not in the predicate registry.`,
25562
+ path: path50
25191
25563
  });
25192
25564
  }
25193
25565
  };
@@ -25247,19 +25619,19 @@ function lintProcess(artifact, inventory, predicateNames, options = {}) {
25247
25619
  process2.outcomes.forEach((outcome, outcomeIndex) => {
25248
25620
  const whenPath = `process.outcomes[${outcomeIndex}].when`;
25249
25621
  outcomeTriggers(outcome).forEach((trigger, triggerIndex) => {
25250
- const path49 = Array.isArray(outcome.when) ? `${whenPath}[${triggerIndex}]` : whenPath;
25622
+ const path50 = Array.isArray(outcome.when) ? `${whenPath}[${triggerIndex}]` : whenPath;
25251
25623
  if (trigger.kind === "predicate") {
25252
- checkPredicate(trigger.name, `${path49}.predicate`);
25624
+ checkPredicate(trigger.name, `${path50}.predicate`);
25253
25625
  } else if (trigger.kind === "command") {
25254
25626
  if (!boundCommandPaths.has(trigger.name)) {
25255
- boundCommandPaths.set(trigger.name, `${path49}.command`);
25627
+ boundCommandPaths.set(trigger.name, `${path50}.command`);
25256
25628
  }
25257
25629
  if (!commandNames.has(trigger.name)) {
25258
25630
  diagnostics.push({
25259
25631
  severity: "error",
25260
25632
  code: "BOUND_COMMAND_NOT_IN_INVENTORY",
25261
- message: `Command "${trigger.name}" triggering outcome "${outcome.id}" (${path49}.command) does not exist in the inventory.`,
25262
- path: `${path49}.command`
25633
+ message: `Command "${trigger.name}" triggering outcome "${outcome.id}" (${path50}.command) does not exist in the inventory.`,
25634
+ path: `${path50}.command`
25263
25635
  });
25264
25636
  }
25265
25637
  }
@@ -25371,10 +25743,10 @@ var init_src4 = __esm({
25371
25743
  });
25372
25744
 
25373
25745
  // packages/mesh-cli/src/commands/workflow.ts
25374
- import * as fs41 from "fs";
25375
- import * as path48 from "path";
25746
+ import * as fs42 from "fs";
25747
+ import * as path49 from "path";
25376
25748
  import { createRequire as createRequire2 } from "module";
25377
- import { execFileSync as execFileSync33 } from "child_process";
25749
+ import { execFileSync as execFileSync34 } from "child_process";
25378
25750
  function resolveExtractorPath() {
25379
25751
  try {
25380
25752
  const require2 = createRequire2(import.meta.url);
@@ -25388,7 +25760,7 @@ function runExtraction(targetPath, extractorPath, explicitProcessPath) {
25388
25760
  let explicitProcessArtifact;
25389
25761
  if (explicitProcessPath) {
25390
25762
  try {
25391
- explicitProcessArtifact = JSON.parse(fs41.readFileSync(explicitProcessPath, "utf-8"));
25763
+ explicitProcessArtifact = JSON.parse(fs42.readFileSync(explicitProcessPath, "utf-8"));
25392
25764
  } catch (err) {
25393
25765
  logError(
25394
25766
  `Could not read/parse --process ${explicitProcessPath}: ${err instanceof Error ? err.message : String(err)}`
@@ -25429,7 +25801,7 @@ for (const file of files) {
25429
25801
  process.stdout.write(JSON.stringify(results));
25430
25802
  `;
25431
25803
  try {
25432
- const result = execFileSync33("npx", ["tsx", "--eval", script], {
25804
+ const result = execFileSync34("npx", ["tsx", "--eval", script], {
25433
25805
  encoding: "utf-8",
25434
25806
  stdio: ["pipe", "pipe", "inherit"],
25435
25807
  maxBuffer: 10 * 1024 * 1024
@@ -25471,7 +25843,7 @@ for (const file of files) {
25471
25843
  process.stdout.write(JSON.stringify(results));
25472
25844
  `;
25473
25845
  try {
25474
- const result = execFileSync33("npx", ["tsx", "--eval", script], {
25846
+ const result = execFileSync34("npx", ["tsx", "--eval", script], {
25475
25847
  encoding: "utf-8",
25476
25848
  stdio: ["pipe", "pipe", "inherit"],
25477
25849
  maxBuffer: 10 * 1024 * 1024
@@ -25488,7 +25860,7 @@ function runLintExtraction(targetPath, extractorPath, explicitProcessPath) {
25488
25860
  let explicitProcessArtifact;
25489
25861
  if (explicitProcessPath) {
25490
25862
  try {
25491
- explicitProcessArtifact = JSON.parse(fs41.readFileSync(explicitProcessPath, "utf-8"));
25863
+ explicitProcessArtifact = JSON.parse(fs42.readFileSync(explicitProcessPath, "utf-8"));
25492
25864
  } catch (err) {
25493
25865
  logError(
25494
25866
  `Could not read/parse --process ${explicitProcessPath}: ${err instanceof Error ? err.message : String(err)}`
@@ -25526,7 +25898,7 @@ for (const file of files) {
25526
25898
  process.stdout.write(JSON.stringify(results));
25527
25899
  `;
25528
25900
  try {
25529
- const result = execFileSync33("npx", ["tsx", "--eval", script], {
25901
+ const result = execFileSync34("npx", ["tsx", "--eval", script], {
25530
25902
  encoding: "utf-8",
25531
25903
  stdio: ["pipe", "pipe", "inherit"],
25532
25904
  maxBuffer: 10 * 1024 * 1024
@@ -25596,16 +25968,16 @@ function registerWorkflowCommands(program2) {
25596
25968
  ).action(
25597
25969
  async (targetPath, opts) => {
25598
25970
  try {
25599
- const resolvedPath = path48.resolve(targetPath);
25600
- if (!fs41.existsSync(resolvedPath)) {
25971
+ const resolvedPath = path49.resolve(targetPath);
25972
+ if (!fs42.existsSync(resolvedPath)) {
25601
25973
  logError(`Path does not exist: ${resolvedPath}`);
25602
25974
  process.exitCode = 1;
25603
25975
  return;
25604
25976
  }
25605
25977
  let resolvedProcessPath;
25606
25978
  if (opts.process) {
25607
- resolvedProcessPath = path48.resolve(opts.process);
25608
- if (!fs41.existsSync(resolvedProcessPath)) {
25979
+ resolvedProcessPath = path49.resolve(opts.process);
25980
+ if (!fs42.existsSync(resolvedProcessPath)) {
25609
25981
  logError(`--process path does not exist: ${resolvedProcessPath}`);
25610
25982
  process.exitCode = 1;
25611
25983
  return;
@@ -25654,8 +26026,8 @@ function registerWorkflowCommands(program2) {
25654
26026
  "Extract the as-built command/query/activity inventory from Temporal workflow source\n\nThe inventory ({ workflowType, commands, queries, activities }) is the process-\nconformance lint's code-side input \u2014 it is never hand-edited. Supports single\nfiles or directories."
25655
26027
  ).action(async (targetPath) => {
25656
26028
  try {
25657
- const resolvedPath = path48.resolve(targetPath);
25658
- if (!fs41.existsSync(resolvedPath)) {
26029
+ const resolvedPath = path49.resolve(targetPath);
26030
+ if (!fs42.existsSync(resolvedPath)) {
25659
26031
  logError(`Path does not exist: ${resolvedPath}`);
25660
26032
  process.exitCode = 1;
25661
26033
  return;
@@ -25701,16 +26073,16 @@ function registerWorkflowCommands(program2) {
25701
26073
  "Comma-separated named-selector registry (enables UNKNOWN_SELECTOR checks)"
25702
26074
  ).action(async (targetPath, opts) => {
25703
26075
  try {
25704
- const resolvedPath = path48.resolve(targetPath);
25705
- if (!fs41.existsSync(resolvedPath)) {
26076
+ const resolvedPath = path49.resolve(targetPath);
26077
+ if (!fs42.existsSync(resolvedPath)) {
25706
26078
  logError(`Path does not exist: ${resolvedPath}`);
25707
26079
  process.exitCode = 1;
25708
26080
  return;
25709
26081
  }
25710
26082
  let resolvedProcessPath;
25711
26083
  if (opts.process) {
25712
- resolvedProcessPath = path48.resolve(opts.process);
25713
- if (!fs41.existsSync(resolvedProcessPath)) {
26084
+ resolvedProcessPath = path49.resolve(opts.process);
26085
+ if (!fs42.existsSync(resolvedProcessPath)) {
25714
26086
  logError(`--process path does not exist: ${resolvedProcessPath}`);
25715
26087
  process.exitCode = 1;
25716
26088
  return;
@@ -25964,7 +26336,7 @@ var init_program = __esm({
25964
26336
  init_vpn2();
25965
26337
  init_workflow();
25966
26338
  CLI_NAME = "mesh";
25967
- CLI_DESCRIPTION = "CLI for Mesh Lab development utilities";
26339
+ CLI_DESCRIPTION = "Build, run and deploy apps on a Mesh platform";
25968
26340
  }
25969
26341
  });
25970
26342