@mesh-tech/mesh-cli 0.13.0 → 0.14.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
@@ -1859,8 +1859,8 @@ function resolveHubPlatformName(platform) {
1859
1859
  return platform?.name ?? "mesh";
1860
1860
  }
1861
1861
  async function ssmGetParameter(name) {
1862
- const { SSMClient: SSMClient4, GetParameterCommand: GetParameterCommand2 } = await import("@aws-sdk/client-ssm");
1863
- const ssm = new SSMClient4({ region: process.env.AWS_REGION || "us-east-2" });
1862
+ const { SSMClient: SSMClient5, GetParameterCommand: GetParameterCommand2 } = await import("@aws-sdk/client-ssm");
1863
+ const ssm = new SSMClient5({ region: process.env.AWS_REGION || "us-east-2" });
1864
1864
  const resp = await ssm.send(new GetParameterCommand2({ Name: name, WithDecryption: true }));
1865
1865
  return resp.Parameter?.Value;
1866
1866
  }
@@ -1901,8 +1901,8 @@ var init_kubeconfig = __esm({
1901
1901
  // libs/mesh-cli/src/utils/temporal-auth.ts
1902
1902
  import { execFileSync as execFileSync3 } from "node:child_process";
1903
1903
  async function resolveTemporalAuth(tenant, env, platformName = tenant) {
1904
- const { SSMClient: SSMClient4, GetParameterCommand: GetParameterCommand2 } = await import("@aws-sdk/client-ssm");
1905
- const ssm = new SSMClient4({ region: process.env.AWS_REGION || "us-east-2" });
1904
+ const { SSMClient: SSMClient5, GetParameterCommand: GetParameterCommand2 } = await import("@aws-sdk/client-ssm");
1905
+ const ssm = new SSMClient5({ region: process.env.AWS_REGION || "us-east-2" });
1906
1906
  const results = {};
1907
1907
  async function trySSM(name) {
1908
1908
  try {
@@ -2297,8 +2297,8 @@ function buildFabricCheckPayload(targetBytes = 8e3) {
2297
2297
  return JSON.stringify({ ...base, pad: "x".repeat(Math.max(0, targetBytes - overhead)) });
2298
2298
  }
2299
2299
  async function verifyFabric() {
2300
- const { SSMClient: SSMClient4, PutParameterCommand, GetParameterCommand: GetParameterCommand2, GetParametersByPathCommand: GetParametersByPathCommand3, DeleteParametersCommand } = await import("@aws-sdk/client-ssm");
2301
- const ssm = new SSMClient4(AWS_CONFIG);
2300
+ const { SSMClient: SSMClient5, PutParameterCommand, GetParameterCommand: GetParameterCommand2, GetParametersByPathCommand: GetParametersByPathCommand3, DeleteParametersCommand } = await import("@aws-sdk/client-ssm");
2301
+ const ssm = new SSMClient5(AWS_CONFIG);
2302
2302
  const payload = buildFabricCheckPayload();
2303
2303
  const mainParam = FABRIC_CHECK_PATH;
2304
2304
  const childParams = [`${FABRIC_CHECK_PATH}/vpc`, `${FABRIC_CHECK_PATH}/eks`];
@@ -2350,8 +2350,8 @@ async function verifyFabric() {
2350
2350
  }
2351
2351
  }
2352
2352
  async function registerTenantEnv(tenant, opts = {}) {
2353
- const { SSMClient: SSMClient4, PutParameterCommand } = await import("@aws-sdk/client-ssm");
2354
- const ssm = new SSMClient4(AWS_CONFIG);
2353
+ const { SSMClient: SSMClient5, PutParameterCommand } = await import("@aws-sdk/client-ssm");
2354
+ const ssm = new SSMClient5(AWS_CONFIG);
2355
2355
  await ssm.send(
2356
2356
  new PutParameterCommand({
2357
2357
  Name: `/mesh-platform/${tenant}`,
@@ -2379,8 +2379,8 @@ async function registerTenantEnv(tenant, opts = {}) {
2379
2379
  );
2380
2380
  }
2381
2381
  async function seedLocalPlatform() {
2382
- const { SSMClient: SSMClient4, PutParameterCommand } = await import("@aws-sdk/client-ssm");
2383
- const ssm = new SSMClient4(AWS_CONFIG);
2382
+ const { SSMClient: SSMClient5, PutParameterCommand } = await import("@aws-sdk/client-ssm");
2383
+ const ssm = new SSMClient5(AWS_CONFIG);
2384
2384
  await ssm.send(
2385
2385
  new PutParameterCommand({
2386
2386
  Name: APP_TENANTS_PARAM,
@@ -2434,14 +2434,14 @@ var init_seed = __esm({
2434
2434
  // libs/mesh-cli/src/commands/local/helpers.ts
2435
2435
  import * as net2 from "net";
2436
2436
  async function upsertLocalSecret(secretId, value, client) {
2437
- const { SecretsManagerClient: SecretsManagerClient8, CreateSecretCommand: CreateSecretCommand3, PutSecretValueCommand: PutSecretValueCommand3 } = await import("@aws-sdk/client-secrets-manager");
2438
- const sm = client ?? new SecretsManagerClient8(LOCAL_AWS_CONFIG);
2437
+ const { SecretsManagerClient: SecretsManagerClient9, CreateSecretCommand: CreateSecretCommand4, PutSecretValueCommand: PutSecretValueCommand4 } = await import("@aws-sdk/client-secrets-manager");
2438
+ const sm = client ?? new SecretsManagerClient9(LOCAL_AWS_CONFIG);
2439
2439
  const secretString = JSON.stringify(value);
2440
2440
  try {
2441
- await sm.send(new CreateSecretCommand3({ Name: secretId, SecretString: secretString }));
2441
+ await sm.send(new CreateSecretCommand4({ Name: secretId, SecretString: secretString }));
2442
2442
  } catch (err) {
2443
2443
  if (err?.name === "ResourceExistsException") {
2444
- await sm.send(new PutSecretValueCommand3({ SecretId: secretId, SecretString: secretString }));
2444
+ await sm.send(new PutSecretValueCommand4({ SecretId: secretId, SecretString: secretString }));
2445
2445
  } else {
2446
2446
  throw err;
2447
2447
  }
@@ -3158,12 +3158,12 @@ async function fetchRemoteExternalCredentials(tenant, env, external, profile) {
3158
3158
  const region = process.env.MESH_PLATFORM_REGION ?? process.env.AWS_REGION ?? "us-east-2";
3159
3159
  const fallbackProfile = !process.env.AWS_ACCESS_KEY_ID && !process.env.AWS_PROFILE ? process.env.MESH_AWS_PROFILE ?? profile : void 0;
3160
3160
  try {
3161
- const { SecretsManagerClient: SecretsManagerClient8, GetSecretValueCommand: GetSecretValueCommand8 } = await import("@aws-sdk/client-secrets-manager");
3161
+ const { SecretsManagerClient: SecretsManagerClient9, GetSecretValueCommand: GetSecretValueCommand9 } = await import("@aws-sdk/client-secrets-manager");
3162
3162
  if (fallbackProfile) process.env.AWS_PROFILE = fallbackProfile;
3163
- const sm = new SecretsManagerClient8({ region });
3163
+ const sm = new SecretsManagerClient9({ region });
3164
3164
  let res;
3165
3165
  try {
3166
- res = await sm.send(new GetSecretValueCommand8({ SecretId: secretId }));
3166
+ res = await sm.send(new GetSecretValueCommand9({ SecretId: secretId }));
3167
3167
  } finally {
3168
3168
  if (fallbackProfile) delete process.env.AWS_PROFILE;
3169
3169
  }
@@ -3277,8 +3277,8 @@ async function seedLocalMock(args) {
3277
3277
  `${secretName}/.config`,
3278
3278
  Object.fromEntries(Object.entries(value).filter(([key]) => !SECRET_KEY_RE.test(key)))
3279
3279
  );
3280
- const { SSMClient: SSMClient4, PutParameterCommand } = await import("@aws-sdk/client-ssm");
3281
- const ssm = new SSMClient4(LOCAL_AWS_CONFIG);
3280
+ const { SSMClient: SSMClient5, PutParameterCommand } = await import("@aws-sdk/client-ssm");
3281
+ const ssm = new SSMClient5(LOCAL_AWS_CONFIG);
3282
3282
  const base = `/mesh-platform/${tenant}/${LOCAL_ENV}/apps/${app}/stacks/local/external-services/${decl.external}`;
3283
3283
  await ssm.send(
3284
3284
  new PutParameterCommand({
@@ -3720,8 +3720,8 @@ async function seedHubAuth(pat) {
3720
3720
  return config;
3721
3721
  }
3722
3722
  async function writeRegistryParams(cliClientId, awsConfig) {
3723
- const { SSMClient: SSMClient4, PutParameterCommand } = await import("@aws-sdk/client-ssm");
3724
- const ssm = new SSMClient4(awsConfig);
3723
+ const { SSMClient: SSMClient5, PutParameterCommand } = await import("@aws-sdk/client-ssm");
3724
+ const ssm = new SSMClient5(awsConfig);
3725
3725
  await ssm.send(
3726
3726
  new PutParameterCommand({
3727
3727
  Name: ZITADEL_SSM_PARAM,
@@ -3911,9 +3911,9 @@ async function ensureM2mCaller(pat, tenant, app, orgId, projectId, roles) {
3911
3911
  return false;
3912
3912
  }
3913
3913
  async function authSecretExists(tenant, app, service) {
3914
- const { SecretsManagerClient: SecretsManagerClient8, GetSecretValueCommand: GetSecretValueCommand8 } = await import("@aws-sdk/client-secrets-manager");
3915
- const sm = new SecretsManagerClient8(LOCAL_AWS_CONFIG);
3916
- return sm.send(new GetSecretValueCommand8({ SecretId: authSecretPath(tenant, app, service) })).then(
3914
+ const { SecretsManagerClient: SecretsManagerClient9, GetSecretValueCommand: GetSecretValueCommand9 } = await import("@aws-sdk/client-secrets-manager");
3915
+ const sm = new SecretsManagerClient9(LOCAL_AWS_CONFIG);
3916
+ return sm.send(new GetSecretValueCommand9({ SecretId: authSecretPath(tenant, app, service) })).then(
3917
3917
  () => true,
3918
3918
  () => false
3919
3919
  );
@@ -3924,8 +3924,8 @@ async function writeAuthSecret(tenant, app, service, value) {
3924
3924
  logSuccess(`Stored service credentials \u2192 ${name}`);
3925
3925
  }
3926
3926
  async function registerLocalApp(args) {
3927
- const { SSMClient: SSMClient4, PutParameterCommand } = await import("@aws-sdk/client-ssm");
3928
- const ssm = new SSMClient4(LOCAL_AWS_CONFIG);
3927
+ const { SSMClient: SSMClient5, PutParameterCommand } = await import("@aws-sdk/client-ssm");
3928
+ const ssm = new SSMClient5(LOCAL_AWS_CONFIG);
3929
3929
  const put2 = (name, value, description) => ssm.send(
3930
3930
  new PutParameterCommand({ Name: name, Type: "String", Overwrite: true, Value: JSON.stringify(value), Description: description })
3931
3931
  );
@@ -3975,8 +3975,8 @@ async function registerLocalApp(args) {
3975
3975
  }
3976
3976
  async function reconcileRegistryFromZitadel() {
3977
3977
  const pat = readSeederPat();
3978
- const { SSMClient: SSMClient4, GetParameterCommand: GetParameterCommand2, PutParameterCommand } = await import("@aws-sdk/client-ssm");
3979
- const ssm = new SSMClient4(LOCAL_AWS_CONFIG);
3978
+ const { SSMClient: SSMClient5, GetParameterCommand: GetParameterCommand2, PutParameterCommand } = await import("@aws-sdk/client-ssm");
3979
+ const ssm = new SSMClient5(LOCAL_AWS_CONFIG);
3980
3980
  const orgs = await api(pat, "POST", "/admin/v1/orgs/_search", { query: { limit: 200 } });
3981
3981
  const tenants = [];
3982
3982
  let apps = 0;
@@ -7297,8 +7297,8 @@ function registerDevCommand(program2) {
7297
7297
  });
7298
7298
  dev.command("test-user [name]").description("Get Temporal test user credentials (from Pulumi-managed test users)").option("--tenant <tenant>", "Tenant name", "mesh").option("--env <env>", "Environment", "dev").option("--region <region>", "AWS region", "us-east-2").action(
7299
7299
  async (name, opts) => {
7300
- const { SSMClient: SSMClient4, GetParameterCommand: GetParameterCommand2, GetParametersByPathCommand: GetParametersByPathCommand3 } = await import("@aws-sdk/client-ssm");
7301
- const ssm = new SSMClient4({ region: opts.region });
7300
+ const { SSMClient: SSMClient5, GetParameterCommand: GetParameterCommand2, GetParametersByPathCommand: GetParametersByPathCommand3 } = await import("@aws-sdk/client-ssm");
7301
+ const ssm = new SSMClient5({ region: opts.region });
7302
7302
  const basePath = `/mesh-platform/${opts.tenant}/${opts.env}/temporal/test-users`;
7303
7303
  if (!name) {
7304
7304
  try {
@@ -9291,9 +9291,9 @@ function parseTenantEnv(context) {
9291
9291
  }
9292
9292
  async function getRegistrySsmExport(tenant, env) {
9293
9293
  try {
9294
- const { SSMClient: SSMClient4, GetParameterCommand: GetParameterCommand2 } = await import("@aws-sdk/client-ssm");
9294
+ const { SSMClient: SSMClient5, GetParameterCommand: GetParameterCommand2 } = await import("@aws-sdk/client-ssm");
9295
9295
  const region = process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? CA_REGION;
9296
- const ssm = new SSMClient4({ region });
9296
+ const ssm = new SSMClient5({ region });
9297
9297
  const ssmPath = `/mesh-platform/${tenant}/${env}/registry`;
9298
9298
  const response = await ssm.send(new GetParameterCommand2({ Name: ssmPath }));
9299
9299
  if (!response.Parameter?.Value) return null;
@@ -9883,9 +9883,9 @@ async function discoverConfigFromSsm(context) {
9883
9883
  const ssmPath = `/mesh-platform/${tenant}/${env}/platform/zitadel`;
9884
9884
  logInfo(`Attempting SSM discovery from ${ssmPath}...`);
9885
9885
  try {
9886
- const { SSMClient: SSMClient4, GetParameterCommand: GetParameterCommand2 } = await import("@aws-sdk/client-ssm");
9886
+ const { SSMClient: SSMClient5, GetParameterCommand: GetParameterCommand2 } = await import("@aws-sdk/client-ssm");
9887
9887
  const region = process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? "us-east-2";
9888
- const ssm = new SSMClient4({ region });
9888
+ const ssm = new SSMClient5({ region });
9889
9889
  const resp = await ssm.send(new GetParameterCommand2({ Name: ssmPath }));
9890
9890
  const raw = resp.Parameter?.Value;
9891
9891
  if (!raw) {
@@ -16521,6 +16521,12 @@ import {
16521
16521
  } from "@aws-sdk/client-secrets-manager";
16522
16522
  import input from "@inquirer/input";
16523
16523
  import password from "@inquirer/password";
16524
+ import {
16525
+ buildInstanceIndexValue,
16526
+ instanceIndexSecretId,
16527
+ isInstanceKey,
16528
+ parseInstanceIndex
16529
+ } from "@mesh-tech/secrets";
16524
16530
  async function ensureAwsCredentialsForStack(stack) {
16525
16531
  if (process.env.AWS_ACCESS_KEY_ID || process.env.AWS_SESSION_TOKEN) return;
16526
16532
  if (!stack) return;
@@ -16705,6 +16711,12 @@ Usage: mesh secrets set external/<name>`);
16705
16711
  );
16706
16712
  process.exit(1);
16707
16713
  }
16714
+ if (opts.key && !isInstanceKey(opts.key)) {
16715
+ logError(
16716
+ `Invalid instance key "${opts.key}": keys must not contain "/", start with ".", or end with ".config" \u2014 those names are reserved for the index and config mirrors.`
16717
+ );
16718
+ process.exit(1);
16719
+ }
16708
16720
  let existing = {};
16709
16721
  try {
16710
16722
  const response = await smClient.send(
@@ -16729,6 +16741,9 @@ Usage: mesh secrets set external/<name>`);
16729
16741
  const merged2 = deepMerge(existing, values2);
16730
16742
  await writeSecret(smClient, secretId, merged2, svc.meta);
16731
16743
  await writeConfigMirror(smClient, secretId, schema, merged2, svc.meta);
16744
+ if (opts.key) {
16745
+ await addKeyToInstanceIndex(smClient, secretPrefix, opts.key, serviceName);
16746
+ }
16732
16747
  logSuccess(`Credentials written to ${secretId}`);
16733
16748
  return;
16734
16749
  }
@@ -16744,6 +16759,9 @@ Usage: mesh secrets set external/<name>`);
16744
16759
  const merged = deepMerge(existing, values);
16745
16760
  await writeSecret(smClient, secretId, merged, svc.meta);
16746
16761
  await writeConfigMirror(smClient, secretId, schema, merged, svc.meta);
16762
+ if (opts.key) {
16763
+ await addKeyToInstanceIndex(smClient, secretPrefix, opts.key, serviceName);
16764
+ }
16747
16765
  logSuccess(`Credentials written to ${secretId}`);
16748
16766
  console.error("\nStored fields:");
16749
16767
  displaySummary(schema.fields, merged);
@@ -16809,6 +16827,45 @@ function extractNonSecretFields(fields, values) {
16809
16827
  }
16810
16828
  return result;
16811
16829
  }
16830
+ async function addKeyToInstanceIndex(client, secretPrefix, key, serviceName) {
16831
+ const indexId = instanceIndexSecretId(secretPrefix);
16832
+ try {
16833
+ let keys = [];
16834
+ try {
16835
+ const res = await client.send(new GetSecretValueCommand5({ SecretId: indexId }));
16836
+ keys = parseInstanceIndex(res.SecretString ?? "");
16837
+ } catch (err) {
16838
+ if (!isResourceNotFound(err)) throw err;
16839
+ }
16840
+ if (keys.includes(key)) return;
16841
+ const secretString = buildInstanceIndexValue([...keys, key]);
16842
+ try {
16843
+ await client.send(
16844
+ new PutSecretValueCommand({ SecretId: indexId, SecretString: secretString })
16845
+ );
16846
+ } catch (err) {
16847
+ if (!isResourceNotFound(err)) throw err;
16848
+ await client.send(
16849
+ new CreateSecretCommand({
16850
+ Name: indexId,
16851
+ Description: `Instance-key index for ${serviceName} (read by listInstances(); maintained by mesh secrets set/reindex and the Hub)`,
16852
+ SecretString: secretString,
16853
+ Tags: [
16854
+ { Key: "mesh:type", Value: "external-service-index" },
16855
+ { Key: "mesh:service", Value: serviceName }
16856
+ ]
16857
+ })
16858
+ );
16859
+ logInfo(
16860
+ `Created ${indexId} OUT OF BAND \u2014 the declaring stack does not manage it. Deploy the declaring stack (it creates the index) before writing keys, or import this secret into the stack before its next deploy.`
16861
+ );
16862
+ }
16863
+ } catch (err) {
16864
+ logInfo(
16865
+ `Warning: failed to record instance "${key}" in ${indexId} \u2014 listInstances() will not see it until you run: mesh secrets reindex external/${serviceName} (${err})`
16866
+ );
16867
+ }
16868
+ }
16812
16869
  async function writeConfigMirror(client, secretId, schema, merged, meta) {
16813
16870
  const configId = `${secretId}/.config`;
16814
16871
  const configValues = extractNonSecretFields(schema.fields, merged);
@@ -16850,17 +16907,157 @@ var init_set = __esm({
16850
16907
  }
16851
16908
  });
16852
16909
 
16910
+ // libs/mesh-cli/src/commands/secrets/reindex.ts
16911
+ import { SSMClient as SSMClient3 } from "@aws-sdk/client-ssm";
16912
+ import {
16913
+ SecretsManagerClient as SecretsManagerClient6,
16914
+ ListSecretsCommand,
16915
+ GetSecretValueCommand as GetSecretValueCommand6,
16916
+ PutSecretValueCommand as PutSecretValueCommand2,
16917
+ CreateSecretCommand as CreateSecretCommand2
16918
+ } from "@aws-sdk/client-secrets-manager";
16919
+ import {
16920
+ buildInstanceIndexValue as buildInstanceIndexValue2,
16921
+ instanceIndexSecretId as instanceIndexSecretId2,
16922
+ instanceKeyFromSecretName,
16923
+ parseInstanceIndex as parseInstanceIndex2
16924
+ } from "@mesh-tech/secrets";
16925
+ async function scanInstanceKeys(client, secretPrefix) {
16926
+ const keys = [];
16927
+ let nextToken;
16928
+ do {
16929
+ const response = await client.send(
16930
+ new ListSecretsCommand({
16931
+ Filters: [{ Key: "name", Values: [`${secretPrefix}/`] }],
16932
+ NextToken: nextToken
16933
+ })
16934
+ );
16935
+ for (const secret of response.SecretList ?? []) {
16936
+ const key = secret.Name ? instanceKeyFromSecretName(secretPrefix, secret.Name) : null;
16937
+ if (key) keys.push(key);
16938
+ }
16939
+ nextToken = response.NextToken;
16940
+ } while (nextToken);
16941
+ return keys;
16942
+ }
16943
+ function indexAlreadyMatches(missing, stale, indexExists, malformed) {
16944
+ return missing.length === 0 && stale.length === 0 && indexExists && !malformed;
16945
+ }
16946
+ async function reindexCommand(servicePath, opts) {
16947
+ const stackOpt = resolveStackOption(opts);
16948
+ const context = detectContext(stackOpt);
16949
+ const region = opts.region ?? process.env.AWS_REGION ?? "us-east-2";
16950
+ await ensureAwsCredentialsForStack(stackOpt ?? context.stage);
16951
+ const ssmClient = new SSMClient3({ region });
16952
+ const smClient = new SecretsManagerClient6({ region });
16953
+ const services = await discoverExternalServices(
16954
+ ssmClient,
16955
+ context.tenant,
16956
+ context.platformEnv
16957
+ );
16958
+ const serviceName = servicePath?.replace(/^external\//, "");
16959
+ if (!serviceName || !services.has(serviceName)) {
16960
+ if (serviceName) {
16961
+ logError(`External service "${serviceName}" not found.`);
16962
+ }
16963
+ const keyed = [...services.entries()].filter(([, s]) => s.schema?.keyedBy);
16964
+ console.error("\nMulti-instance external services:");
16965
+ for (const [name, svc2] of keyed) {
16966
+ console.error(` ${name} (keyed by ${svc2.schema.keyedBy})`);
16967
+ }
16968
+ if (keyed.length === 0) console.error(" (none)");
16969
+ console.error(`
16970
+ Usage: mesh secrets reindex external/<name>`);
16971
+ process.exit(serviceName ? 1 : 0);
16972
+ }
16973
+ const svc = services.get(serviceName);
16974
+ if (!svc.schema?.keyedBy) {
16975
+ logError(
16976
+ `"${serviceName}" is single-instance (no keyedBy) \u2014 it has no instance index.`
16977
+ );
16978
+ process.exit(1);
16979
+ }
16980
+ const secretPrefix = svc.meta.secretPrefix;
16981
+ const indexId = instanceIndexSecretId2(secretPrefix);
16982
+ const actual = [...new Set(await scanInstanceKeys(smClient, secretPrefix))].sort();
16983
+ let listed = [];
16984
+ let indexExists = true;
16985
+ let malformed = false;
16986
+ try {
16987
+ const res = await smClient.send(new GetSecretValueCommand6({ SecretId: indexId }));
16988
+ try {
16989
+ listed = parseInstanceIndex2(res.SecretString ?? "");
16990
+ } catch {
16991
+ malformed = true;
16992
+ logInfo(`Existing index at ${indexId} is malformed \u2014 rebuilding from scratch.`);
16993
+ }
16994
+ } catch (err) {
16995
+ if (!isResourceNotFound(err)) throw err;
16996
+ indexExists = false;
16997
+ logInfo(`No index at ${indexId} yet \u2014 it will be created.`);
16998
+ }
16999
+ const missing = actual.filter((k) => !listed.includes(k));
17000
+ const stale = listed.filter((k) => !actual.includes(k));
17001
+ logInfo(`Instances in Secrets Manager: ${actual.length ? actual.join(", ") : "(none)"}`);
17002
+ if (missing.length > 0) logInfo(`Missing from index: ${missing.join(", ")}`);
17003
+ if (stale.length > 0) logInfo(`Stale in index (secret gone): ${stale.join(", ")}`);
17004
+ if (indexAlreadyMatches(missing, stale, indexExists, malformed)) {
17005
+ logSuccess(`Index at ${indexId} already matches \u2014 nothing to do.`);
17006
+ return;
17007
+ }
17008
+ if (opts.dryRun) {
17009
+ logInfo(`Dry run \u2014 would write ${indexId} with ${actual.length} instance(s).`);
17010
+ return;
17011
+ }
17012
+ await writeInstanceIndex(smClient, secretPrefix, serviceName, actual);
17013
+ logSuccess(`Index rebuilt: ${indexId} now lists ${actual.length} instance(s).`);
17014
+ }
17015
+ async function writeInstanceIndex(client, secretPrefix, serviceName, keys) {
17016
+ const indexId = instanceIndexSecretId2(secretPrefix);
17017
+ const secretString = buildInstanceIndexValue2(keys);
17018
+ try {
17019
+ await client.send(
17020
+ new PutSecretValueCommand2({ SecretId: indexId, SecretString: secretString })
17021
+ );
17022
+ } catch (err) {
17023
+ if (!isResourceNotFound(err)) throw err;
17024
+ await client.send(
17025
+ new CreateSecretCommand2({
17026
+ Name: indexId,
17027
+ Description: `Instance-key index for ${serviceName} (read by listInstances(); maintained by mesh secrets set/reindex and the Hub)`,
17028
+ SecretString: secretString,
17029
+ Tags: [
17030
+ { Key: "mesh:type", Value: "external-service-index" },
17031
+ { Key: "mesh:service", Value: serviceName }
17032
+ ]
17033
+ })
17034
+ );
17035
+ logInfo(
17036
+ `Created ${indexId} OUT OF BAND \u2014 the declaring stack does not manage it. Deploy the declaring stack BEFORE reindex next time, or import this secret into the stack before its next deploy.`
17037
+ );
17038
+ }
17039
+ }
17040
+ var init_reindex = __esm({
17041
+ "libs/mesh-cli/src/commands/secrets/reindex.ts"() {
17042
+ "use strict";
17043
+ init_context();
17044
+ init_log();
17045
+ init_stack_flag();
17046
+ init_set();
17047
+ }
17048
+ });
17049
+
16853
17050
  // libs/mesh-cli/src/commands/secrets/migrate-config.ts
16854
17051
  import {
16855
- SSMClient as SSMClient3,
17052
+ SSMClient as SSMClient4,
16856
17053
  GetParametersByPathCommand as GetParametersByPathCommand2
16857
17054
  } from "@aws-sdk/client-ssm";
16858
17055
  import {
16859
- SecretsManagerClient as SecretsManagerClient6,
16860
- GetSecretValueCommand as GetSecretValueCommand6,
16861
- PutSecretValueCommand as PutSecretValueCommand2,
16862
- CreateSecretCommand as CreateSecretCommand2,
16863
- ListSecretsCommand
17056
+ SecretsManagerClient as SecretsManagerClient7,
17057
+ GetSecretValueCommand as GetSecretValueCommand7,
17058
+ PutSecretValueCommand as PutSecretValueCommand3,
17059
+ CreateSecretCommand as CreateSecretCommand3,
17060
+ ListSecretsCommand as ListSecretsCommand2
16864
17061
  } from "@aws-sdk/client-secrets-manager";
16865
17062
  function normalizeEntry2(raw) {
16866
17063
  if (raw.type === "group" && raw.fields) {
@@ -16946,7 +17143,7 @@ function isResourceNotFound2(err) {
16946
17143
  }
16947
17144
  async function readSecret(client, secretId) {
16948
17145
  try {
16949
- const res = await client.send(new GetSecretValueCommand6({ SecretId: secretId }));
17146
+ const res = await client.send(new GetSecretValueCommand7({ SecretId: secretId }));
16950
17147
  return res.SecretString ? JSON.parse(res.SecretString) : null;
16951
17148
  } catch (err) {
16952
17149
  if (isResourceNotFound2(err)) return null;
@@ -16956,10 +17153,10 @@ async function readSecret(client, secretId) {
16956
17153
  async function writeSecret2(client, secretId, values, description) {
16957
17154
  const secretString = JSON.stringify(values);
16958
17155
  try {
16959
- await client.send(new PutSecretValueCommand2({ SecretId: secretId, SecretString: secretString }));
17156
+ await client.send(new PutSecretValueCommand3({ SecretId: secretId, SecretString: secretString }));
16960
17157
  } catch (err) {
16961
17158
  if (isResourceNotFound2(err)) {
16962
- await client.send(new CreateSecretCommand2({
17159
+ await client.send(new CreateSecretCommand3({
16963
17160
  Name: secretId,
16964
17161
  Description: description,
16965
17162
  SecretString: secretString,
@@ -16972,7 +17169,7 @@ async function writeSecret2(client, secretId, values, description) {
16972
17169
  }
16973
17170
  async function secretExists(client, secretId) {
16974
17171
  try {
16975
- await client.send(new GetSecretValueCommand6({ SecretId: secretId }));
17172
+ await client.send(new GetSecretValueCommand7({ SecretId: secretId }));
16976
17173
  return true;
16977
17174
  } catch (err) {
16978
17175
  if (isResourceNotFound2(err)) return false;
@@ -16985,7 +17182,7 @@ async function listInstanceKeys(client, secretPrefix) {
16985
17182
  let nextToken;
16986
17183
  do {
16987
17184
  const response = await client.send(
16988
- new ListSecretsCommand({
17185
+ new ListSecretsCommand2({
16989
17186
  Filters: [{ Key: "name", Values: [prefix2] }],
16990
17187
  NextToken: nextToken
16991
17188
  })
@@ -17051,8 +17248,8 @@ async function migrateService(smClient, meta, schema, opts) {
17051
17248
  async function migrateConfigCommand(opts) {
17052
17249
  const context = detectContext(resolveStackOption(opts));
17053
17250
  const region = opts.region ?? process.env.AWS_REGION ?? "us-east-2";
17054
- const ssmClient = new SSMClient3({ region });
17055
- const smClient = new SecretsManagerClient6({ region });
17251
+ const ssmClient = new SSMClient4({ region });
17252
+ const smClient = new SecretsManagerClient7({ region });
17056
17253
  logInfo(`Migrating .config mirrors for ${context.tenant}/${context.platformEnv}`);
17057
17254
  if (opts.dryRun) logInfo("DRY-RUN mode \u2014 no writes");
17058
17255
  if (opts.force) logInfo("FORCE mode \u2014 overwrite existing .config");
@@ -17105,6 +17302,11 @@ function registerSecretsCommands(program2) {
17105
17302
  secrets.command("set [service]").description("Set credentials for an external service (e.g., mesh secrets set external/symitar)").option("--key <key>", "Instance key for multi-instance services (e.g., FI ID)").option("--all", "Prompt for all fields including optional ones").option("--json <json>", "Non-interactive: provide values as JSON").option("--stack <stack>", "Pulumi stack name (override detection)").addOption(new Option2("--stage <stack>", "Deprecated alias for --stack").hideHelp()).option("--region <region>", "AWS region (default: us-east-2)").action(async (service, opts) => {
17106
17303
  await setCommand(service, opts);
17107
17304
  });
17305
+ secrets.command("reindex [service]").description(
17306
+ "Rebuild a multi-instance external service's instance index (the {prefix}/.index secret listInstances() reads) from the per-key secrets actually in Secrets Manager \u2014 the repair for an instance secret created or deleted out of band"
17307
+ ).option("--dry-run", "Show what would change without writing").option("--stack <stack>", "Pulumi stack name (override detection)").addOption(new Option2("--stage <stack>", "Deprecated alias for --stack").hideHelp()).option("--region <region>", "AWS region (default: us-east-2)").action(async (service, opts) => {
17308
+ await reindexCommand(service, opts);
17309
+ });
17108
17310
  secrets.command("migrate-config").description("Backfill .config mirror secrets for all external services").option("--force", "Overwrite existing .config mirrors").option("--dry-run", "Show what would be written without writing").option("--stack <stack>", "Pulumi stack name (override detection)").addOption(new Option2("--stage <stack>", "Deprecated alias for --stack").hideHelp()).option("--region <region>", "AWS region (default: us-east-2)").action(async (opts) => {
17109
17311
  await migrateConfigCommand(opts);
17110
17312
  });
@@ -17114,6 +17316,7 @@ var init_secrets = __esm({
17114
17316
  "use strict";
17115
17317
  init_exec2();
17116
17318
  init_set();
17319
+ init_reindex();
17117
17320
  init_migrate_config();
17118
17321
  }
17119
17322
  });
@@ -18755,8 +18958,8 @@ import * as fs32 from "fs";
18755
18958
  import * as os13 from "os";
18756
18959
  import * as path39 from "path";
18757
18960
  import {
18758
- SecretsManagerClient as SecretsManagerClient7,
18759
- GetSecretValueCommand as GetSecretValueCommand7
18961
+ SecretsManagerClient as SecretsManagerClient8,
18962
+ GetSecretValueCommand as GetSecretValueCommand8
18760
18963
  } from "@aws-sdk/client-secrets-manager";
18761
18964
  function resolveTenantEnv(options) {
18762
18965
  if (options.tenant && options.env) {
@@ -19019,10 +19222,10 @@ async function tunnelExternal(name, options) {
19019
19222
  const { appTenant, appStage } = resolveCredentialAxis(options);
19020
19223
  const secretId = externalSecretId(appTenant, appStage, name, key);
19021
19224
  const setHint = `mesh secrets set external/${name}${key ? ` --key=${key}` : ""}`;
19022
- const sm = new SecretsManagerClient7({});
19225
+ const sm = new SecretsManagerClient8({});
19023
19226
  let creds;
19024
19227
  try {
19025
- const out = await sm.send(new GetSecretValueCommand7({ SecretId: secretId }));
19228
+ const out = await sm.send(new GetSecretValueCommand8({ SecretId: secretId }));
19026
19229
  creds = JSON.parse(out.SecretString ?? "{}");
19027
19230
  } catch (err) {
19028
19231
  logError(