@tinycloud/cli 1.5.0-beta.11 → 1.5.0-beta.13

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.
@@ -5259,6 +5259,9 @@ function assertReadable(authority, now) {
5259
5259
  function contentHash(bytes) {
5260
5260
  return bytesToHex(blake3(bytes));
5261
5261
  }
5262
+ function repairId(entry) {
5263
+ return `${entry.hash}\0${entry.key}`;
5264
+ }
5262
5265
  function sameEtag(a, b) {
5263
5266
  return a.replace(/^"|"$/g, "") === b.replace(/^"|"$/g, "");
5264
5267
  }
@@ -5628,7 +5631,7 @@ function kvSyncTransport(kv, options = {}) {
5628
5631
  }
5629
5632
  };
5630
5633
  }
5631
- var ReplicaErrorCode, REPLICA_ERROR, ReplicaError, RFC3339, DEFAULT_PAGE_LIMIT, DEFAULT_LEASE_TTL_MS, FETCH_CHUNK, REPAIR_BATCH, RETENTION_REVOKED, PENDING_REFUSALS, Replica, BASE58, BASE32, featureCache, REVOKED_CODES, REVOKED_BODY, encoder;
5634
+ var ReplicaErrorCode, REPLICA_ERROR, ReplicaError, RFC3339, DEFAULT_PAGE_LIMIT, DEFAULT_LEASE_TTL_MS, FETCH_CHUNK, REPAIR_BATCH, REPAIR_MAX_TRIES, REPAIR_BACKOFF_BASE_MS, RETENTION_REVOKED, PENDING_REFUSALS, Replica, BASE58, BASE32, featureCache, REVOKED_CODES, REVOKED_BODY, encoder;
5632
5635
  var init_dist = __esm({
5633
5636
  "../replica/dist/index.js"() {
5634
5637
  "use strict";
@@ -5716,6 +5719,8 @@ var init_dist = __esm({
5716
5719
  DEFAULT_LEASE_TTL_MS = 12e4;
5717
5720
  FETCH_CHUNK = 100;
5718
5721
  REPAIR_BATCH = 100;
5722
+ REPAIR_MAX_TRIES = 5;
5723
+ REPAIR_BACKOFF_BASE_MS = 6e4;
5719
5724
  RETENTION_REVOKED = /* @__PURE__ */ new Set(["retention-grant-revoked", "retention-grant-ancestor-revoked"]);
5720
5725
  PENDING_REFUSALS = /* @__PURE__ */ new Set([
5721
5726
  ReplicaErrorCode.GRANT_UNAUTHORIZED,
@@ -5728,6 +5733,8 @@ var init_dist = __esm({
5728
5733
  #now;
5729
5734
  #leaseTtlMs;
5730
5735
  #onCommit;
5736
+ /** Failed repair fetches of this session, by key and ETag hash. Never persisted: a new `Replica` retries afresh. */
5737
+ #repairTries = /* @__PURE__ */ new Map();
5731
5738
  constructor(options) {
5732
5739
  this.#store = options.store;
5733
5740
  const transport = options.transport;
@@ -6082,7 +6089,14 @@ var init_dist = __esm({
6082
6089
  return { changes, blobs, fetched: fetched2 };
6083
6090
  }
6084
6091
  async #repair(attempt, cursor, nodeDid, coverage, retentionGrantCid) {
6085
- const pending = await this.#store.pendingRepairs(Number.MAX_SAFE_INTEGER);
6092
+ const all = await this.#store.pendingRepairs(Number.MAX_SAFE_INTEGER);
6093
+ const now = this.#now();
6094
+ const pendingIds = new Set(all.map((entry) => repairId(entry)));
6095
+ for (const id of this.#repairTries.keys()) if (!pendingIds.has(id)) this.#repairTries.delete(id);
6096
+ const pending = all.filter((entry) => {
6097
+ const state2 = this.#repairTries.get(repairId(entry));
6098
+ return state2 === void 0 || state2.tries < REPAIR_MAX_TRIES && now >= state2.nextAttemptAt;
6099
+ });
6086
6100
  let repaired = 0;
6087
6101
  for (let start = 0; start < pending.length; start += REPAIR_BATCH) {
6088
6102
  const chunk = pending.slice(start, start + REPAIR_BATCH);
@@ -6094,7 +6108,14 @@ var init_dist = __esm({
6094
6108
  const changes = [];
6095
6109
  for (const entry of chunk) {
6096
6110
  const bytes = acceptContent(entry, contents.get(entry.key));
6097
- if (bytes === void 0) continue;
6111
+ const id = repairId(entry);
6112
+ if (bytes === void 0) {
6113
+ const tries = (this.#repairTries.get(id)?.tries ?? 0) + 1;
6114
+ const wait = tries === 1 ? 0 : REPAIR_BACKOFF_BASE_MS * 2 ** (tries - 2);
6115
+ this.#repairTries.set(id, { tries, nextAttemptAt: this.#now() + wait });
6116
+ continue;
6117
+ }
6118
+ this.#repairTries.delete(id);
6098
6119
  blobs.set(entry.hash, bytes);
6099
6120
  changes.push({ ...entry, content: true });
6100
6121
  }
@@ -39167,7 +39188,7 @@ init_constants();
39167
39188
  import { CLI_NAME as CLI_NAME32 } from "@tinycloud/operations/cli-name";
39168
39189
  import { readFile as readFile14 } from "fs/promises";
39169
39190
  import { join as join17 } from "path";
39170
- import { homedir, hostname, userInfo } from "os";
39191
+ import { constants as osConstants, homedir, hostname, userInfo } from "os";
39171
39192
  import { appSecretScope } from "@tinycloud/sdk-core";
39172
39193
  import { invokeOperation as invokeOperation3 } from "@tinycloud/operations";
39173
39194
  import { isLocalOwnerProfile as isLocalOwnerProfile4, ownerSessionLacksProof as ownerSessionLacksProof5 } from "@tinycloud/operations/profile";
@@ -39188,6 +39209,7 @@ import {
39188
39209
  } from "@tinycloud/operations/secret-capabilities";
39189
39210
  import { invokeSecretsGetWithLocalAuthorityRetry } from "@tinycloud/operations/cli-runtime";
39190
39211
  init_sdk();
39212
+ init_replication_registry();
39191
39213
  init_session_expired();
39192
39214
  init_space();
39193
39215
 
@@ -39253,14 +39275,14 @@ function runProbeChild(request, options = {}) {
39253
39275
  const args = options.argv ?? probeChildArgv(request);
39254
39276
  const budgetMs = options.budgetMs ?? ATTEMPT_BUDGET_MS;
39255
39277
  const killGraceMs = options.killGraceMs ?? KILL_GRACE_MS;
39256
- const spawn2 = options.spawn ?? nodeSpawn;
39278
+ const spawn3 = options.spawn ?? nodeSpawn;
39257
39279
  const signal = options.signal;
39258
39280
  return new Promise((resolve9) => {
39259
39281
  if (signal?.aborted) {
39260
39282
  resolve9({ kind: "cancelled" });
39261
39283
  return;
39262
39284
  }
39263
- const child = spawn2(command, args, { env: process.env, stdio: ["ignore", "pipe", "pipe"] });
39285
+ const child = spawn3(command, args, { env: process.env, stdio: ["ignore", "pipe", "pipe"] });
39264
39286
  let outcome;
39265
39287
  let stdout = Buffer.alloc(0);
39266
39288
  let stderr = Buffer.alloc(0);
@@ -39372,6 +39394,75 @@ function parseStatusEnvelope(line) {
39372
39394
  return void 0;
39373
39395
  }
39374
39396
 
39397
+ // src/lib/run-command.ts
39398
+ import { execFileSync, spawn as spawn2 } from "child_process";
39399
+ import { existsSync, readFileSync as readFileSync2 } from "fs";
39400
+ var FORWARDED_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"];
39401
+ var TERMINAL_SIGNALS = { SIGINT: true, SIGQUIT: true, SIGHUP: true };
39402
+ function psGroupArgs(pid) {
39403
+ return ["-o", "pgid=", "-o", "tpgid=", "-p", String(pid)];
39404
+ }
39405
+ function parsePsGroups(output2) {
39406
+ const columns = output2.trim().split(/\s+/);
39407
+ if (columns.length !== 2) return void 0;
39408
+ const group = Number(columns[0]);
39409
+ const foreground = Number(columns[1]);
39410
+ return Number.isSafeInteger(group) && Number.isSafeInteger(foreground) ? { group, foreground } : void 0;
39411
+ }
39412
+ function processGroups() {
39413
+ try {
39414
+ const stat8 = readFileSync2("/proc/self/stat", "utf8");
39415
+ const fields = stat8.slice(stat8.lastIndexOf(")") + 2).split(" ");
39416
+ return { group: Number(fields[2]), foreground: Number(fields[5]) };
39417
+ } catch {
39418
+ }
39419
+ try {
39420
+ const ps = existsSync("/bin/ps") ? "/bin/ps" : "/usr/bin/ps";
39421
+ return parsePsGroups(execFileSync(ps, psGroupArgs(process.pid), {
39422
+ env: {},
39423
+ encoding: "utf8",
39424
+ stdio: ["ignore", "pipe", "ignore"],
39425
+ timeout: 2e3
39426
+ }));
39427
+ } catch {
39428
+ return void 0;
39429
+ }
39430
+ }
39431
+ function inTerminalForeground() {
39432
+ const groups = processGroups();
39433
+ return groups !== void 0 && groups.foreground > 0 && groups.group === groups.foreground;
39434
+ }
39435
+ function runCommand(argv, env) {
39436
+ return new Promise((resolve9) => {
39437
+ let child;
39438
+ try {
39439
+ child = spawn2(argv[0], argv.slice(1), { stdio: "inherit", env, shell: false });
39440
+ } catch (error) {
39441
+ const code5 = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
39442
+ resolve9({ kind: "not_started", ...code5 === void 0 ? {} : { errno: code5 } });
39443
+ return;
39444
+ }
39445
+ const forward = (signal) => {
39446
+ if (TERMINAL_SIGNALS[signal] && inTerminalForeground()) return;
39447
+ child.kill(signal);
39448
+ };
39449
+ for (const signal of FORWARDED_SIGNALS) process.on(signal, forward);
39450
+ const settle = (outcome) => {
39451
+ for (const signal of FORWARDED_SIGNALS) process.off(signal, forward);
39452
+ resolve9(outcome);
39453
+ };
39454
+ child.on("error", (error) => {
39455
+ if (child.pid === void 0) settle({ kind: "not_started", ...error.code === void 0 ? {} : { errno: error.code } });
39456
+ });
39457
+ child.once("exit", (code5, signal) => {
39458
+ settle(signal !== null ? { kind: "signaled", signal } : { kind: "exited", code: code5 ?? 1 });
39459
+ });
39460
+ });
39461
+ }
39462
+
39463
+ // src/commands/secrets.ts
39464
+ init_safe_text();
39465
+
39375
39466
  // src/lib/secrets-wait.ts
39376
39467
  init_constants();
39377
39468
  init_errors();
@@ -40326,6 +40417,153 @@ function outputSecretDoctor(result) {
40326
40417
  process.stdout.write(theme.warn(`${failed} secrets check${failed > 1 ? "s" : ""} need attention.`) + "\n");
40327
40418
  }
40328
40419
  }
40420
+ async function resolveSecretReadTarget(ctx, name, options) {
40421
+ const scopeOptions = resolveSecretScope(options);
40422
+ const legacySpaceUri = await resolveSecretSpace(options.space, ctx.profile);
40423
+ const resolved = resolveSecretPath2(name, scopeOptions);
40424
+ return { name, scopeOptions, scope: resolved.scope, legacySpaceUri, secretPath: resolved.permissionPaths.vault };
40425
+ }
40426
+ async function readSecret(params) {
40427
+ const { ctx, target, options } = params;
40428
+ const { name } = target;
40429
+ if (options.delegation) {
40430
+ const delegated = await resolveDelegatedSecretSource(
40431
+ options.delegation,
40432
+ target.secretPath,
40433
+ target.legacySpaceUri ?? SECRETS_SPACE2
40434
+ );
40435
+ const effectiveHost = params.host ?? delegated.delegation.host ?? ctx.host;
40436
+ const delegatedCtx = { ...ctx, host: effectiveHost };
40437
+ const local = effectiveLocal(await ProfileManager.getProfile(ctx.profile).catch(() => null), options);
40438
+ const node = await ensureSecretsNode(delegatedCtx, options);
40439
+ const value = await withSpinner(
40440
+ `Getting secret ${name}...`,
40441
+ () => readDelegatedSecretValue({
40442
+ node,
40443
+ delegation: delegated.delegation,
40444
+ delegationCid: delegated.delegation.cid,
40445
+ permissions: delegated.permissions,
40446
+ secretPath: target.secretPath,
40447
+ space: target.legacySpaceUri ?? SECRETS_SPACE2,
40448
+ name,
40449
+ ...target.scope === void 0 ? {} : { scope: target.scope },
40450
+ local
40451
+ })
40452
+ );
40453
+ return { value };
40454
+ }
40455
+ const privateKey = authOptions(options)?.privateKey;
40456
+ const spaceUri = privateKey !== void 0 && options.space !== void 0 && !options.space.startsWith("tinycloud:") ? options.space : target.legacySpaceUri;
40457
+ const result = await invokeCanonicalSecretGet({
40458
+ ctx,
40459
+ name,
40460
+ ...target.scopeOptions?.scope === void 0 ? {} : { scope: target.scopeOptions.scope },
40461
+ ...spaceUri === void 0 ? {} : { space: spaceUri },
40462
+ options,
40463
+ label: `Getting secret ${name}...`,
40464
+ openKeyAcquisition: params.openKeyAcquisition
40465
+ });
40466
+ if (result.status !== "ok") {
40467
+ try {
40468
+ throwCanonicalSecretGetError(result, name);
40469
+ } catch (error) {
40470
+ throw withOperationWarnings(error, result.warnings);
40471
+ }
40472
+ }
40473
+ return { value: result.output.value, warnings: result.warnings };
40474
+ }
40475
+ var ENV_VAR_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
40476
+ function argvAfterSeparator(cmd) {
40477
+ let root = cmd;
40478
+ while (root.parent) root = root.parent;
40479
+ const raw = "rawArgs" in root ? root.rawArgs : void 0;
40480
+ if (!Array.isArray(raw)) return void 0;
40481
+ const args = raw.map(String);
40482
+ const withValue = (flag) => root.options.some((option) => option.required && (option.long === flag || option.short === flag));
40483
+ for (let index = 0; index < args.length; index += 1) {
40484
+ const arg = args[index];
40485
+ if (arg === "--") return args.slice(index + 1);
40486
+ if (arg.startsWith("--")) {
40487
+ if (withValue(arg)) index += 1;
40488
+ continue;
40489
+ }
40490
+ if (arg.length < 2 || !arg.startsWith("-")) continue;
40491
+ for (let char = 1; char < arg.length; char += 1) {
40492
+ const option = root.options.find((candidate) => candidate.short === `-${arg[char]}`);
40493
+ if (option === void 0) break;
40494
+ if (option.required) {
40495
+ if (char === arg.length - 1) index += 1;
40496
+ break;
40497
+ }
40498
+ }
40499
+ }
40500
+ return void 0;
40501
+ }
40502
+ function secretRunPlan(cmd, operands, maps) {
40503
+ const usage3 = `${CLI_NAME32} secrets run NAME... [--map NAME=ENV_VAR] -- <command> [args...]`;
40504
+ const after = argvAfterSeparator(cmd);
40505
+ if (after === void 0 || after.length > operands.length) {
40506
+ throw new CLIError("USAGE_ERROR", `Put the command after \`--\`: ${usage3}`, ExitCode.USAGE_ERROR);
40507
+ }
40508
+ const [program, ...args] = after;
40509
+ if (program === void 0) {
40510
+ throw new CLIError("USAGE_ERROR", `Add the command to run after \`--\`: ${usage3}`, ExitCode.USAGE_ERROR);
40511
+ }
40512
+ const exports = [];
40513
+ for (const name of operands.slice(0, operands.length - after.length)) {
40514
+ if (!ENV_VAR_RE.test(name)) {
40515
+ throw new CLIError(
40516
+ "USAGE_ERROR",
40517
+ `${JSON.stringify(name)} can't be used as an environment variable name. Use \`--map ${name}=ENV_VAR\` to choose one.`,
40518
+ ExitCode.USAGE_ERROR
40519
+ );
40520
+ }
40521
+ exports.push({ name, variable: name });
40522
+ }
40523
+ for (const map of maps) {
40524
+ const equals7 = map.indexOf("=");
40525
+ const variable = map.slice(equals7 + 1);
40526
+ if (equals7 <= 0 || !ENV_VAR_RE.test(variable)) {
40527
+ throw new CLIError(
40528
+ "USAGE_ERROR",
40529
+ `Invalid \`--map ${JSON.stringify(map)}\`: use NAME=ENV_VAR, where ENV_VAR starts with a letter or _ and uses only letters, digits and _.`,
40530
+ ExitCode.USAGE_ERROR
40531
+ );
40532
+ }
40533
+ exports.push({ name: map.slice(0, equals7), variable });
40534
+ }
40535
+ if (exports.length === 0) {
40536
+ throw new CLIError("USAGE_ERROR", `Name at least one secret: ${usage3}`, ExitCode.USAGE_ERROR);
40537
+ }
40538
+ const variables = /* @__PURE__ */ new Set();
40539
+ for (const { variable } of exports) {
40540
+ if (variables.has(variable)) {
40541
+ throw new CLIError("USAGE_ERROR", `Two secrets are mapped to ${variable}. Give each its own variable.`, ExitCode.USAGE_ERROR);
40542
+ }
40543
+ variables.add(variable);
40544
+ }
40545
+ return { command: [program, ...args], exports };
40546
+ }
40547
+ var RERAISED_SIGNALS = {
40548
+ SIGINT: true,
40549
+ SIGTERM: true,
40550
+ SIGHUP: true,
40551
+ SIGKILL: true,
40552
+ SIGALRM: true,
40553
+ SIGUSR2: true
40554
+ };
40555
+ var TC_CREDENTIAL_VARIABLES = ["TC_PRIVATE_KEY"];
40556
+ function exitAsCommand(outcome, program) {
40557
+ if (outcome.kind === "exited") return process.exit(outcome.code);
40558
+ if (outcome.kind === "signaled") {
40559
+ if (RERAISED_SIGNALS[outcome.signal]) process.kill(process.pid, outcome.signal);
40560
+ return process.exit(128 + (osConstants.signals[outcome.signal] ?? 0));
40561
+ }
40562
+ const reason = outcome.errno === "ENOENT" ? "command not found" : outcome.errno === "EACCES" ? "permission denied" : `cannot run (${outcome.errno ?? "unknown error"})`;
40563
+ process.stderr.write(`${CLI_NAME32} secrets run: ${safeInline(program)}: ${reason}
40564
+ `);
40565
+ return process.exit(outcome.errno === "ENOENT" ? 127 : 126);
40566
+ }
40329
40567
  function registerSecretsCommand(program, openKeyAcquisition) {
40330
40568
  const secrets = program.command("secrets").description("Encrypted secrets management");
40331
40569
  const network = secrets.command("network").description("Manage the default secrets encryption network");
@@ -40493,66 +40731,23 @@ function registerSecretsCommand(program, openKeyAcquisition) {
40493
40731
  try {
40494
40732
  const globalOpts = cmd.optsWithGlobals();
40495
40733
  const ctx = await ProfileManager.resolveContext(globalOpts);
40496
- const scopeOptions = resolveSecretScope(options);
40497
- const legacySpaceUri = await resolveSecretSpace(options.space, ctx.profile);
40498
- const secretPath = resolveSecretPath2(name, scopeOptions).permissionPaths.vault;
40734
+ const target = await resolveSecretReadTarget(ctx, name, options);
40499
40735
  if (options.output) await validatePrivateOutput(options.output, "Secret output");
40736
+ const secret = await readSecret({ ctx, host: globalOpts.host, target, options, openKeyAcquisition });
40737
+ const value = secret.value;
40500
40738
  if (options.delegation) {
40501
- const delegated = await resolveDelegatedSecretSource(
40502
- options.delegation,
40503
- secretPath,
40504
- legacySpaceUri ?? SECRETS_SPACE2
40505
- );
40506
- const effectiveHost = globalOpts.host ?? delegated.delegation.host ?? ctx.host;
40507
- const delegatedCtx = { ...ctx, host: effectiveHost };
40508
- const local = effectiveLocal(await ProfileManager.getProfile(ctx.profile).catch(() => null), options);
40509
- const scope = resolveSecretPath2(name, scopeOptions).scope;
40510
- const node = await ensureSecretsNode(delegatedCtx, options);
40511
- const value2 = await withSpinner(
40512
- `Getting secret ${name}...`,
40513
- () => readDelegatedSecretValue({
40514
- node,
40515
- delegation: delegated.delegation,
40516
- delegationCid: delegated.delegation.cid,
40517
- permissions: delegated.permissions,
40518
- secretPath,
40519
- space: legacySpaceUri ?? SECRETS_SPACE2,
40520
- name,
40521
- ...scope === void 0 ? {} : { scope },
40522
- local
40523
- })
40524
- );
40525
40739
  if (options.output) {
40526
- await writePrivateOutput(options.output, value2, "Secret output");
40740
+ await writePrivateOutput(options.output, value, "Secret output");
40527
40741
  outputJson({ name, written: options.output });
40528
40742
  return;
40529
40743
  }
40530
40744
  if (options.raw) {
40531
- process.stdout.write(value2);
40745
+ process.stdout.write(value);
40532
40746
  return;
40533
40747
  }
40534
- outputJson({ name, value: value2 });
40748
+ outputJson({ name, value });
40535
40749
  return;
40536
40750
  }
40537
- const privateKey = authOptions(options)?.privateKey;
40538
- const spaceUri = privateKey !== void 0 && options.space !== void 0 && !options.space.startsWith("tinycloud:") ? options.space : legacySpaceUri;
40539
- const result = await invokeCanonicalSecretGet({
40540
- ctx,
40541
- name,
40542
- ...scopeOptions?.scope === void 0 ? {} : { scope: scopeOptions.scope },
40543
- ...spaceUri === void 0 ? {} : { space: spaceUri },
40544
- options,
40545
- label: `Getting secret ${name}...`,
40546
- openKeyAcquisition
40547
- });
40548
- if (result.status !== "ok") {
40549
- try {
40550
- throwCanonicalSecretGetError(result, name);
40551
- } catch (error) {
40552
- throw withOperationWarnings(error, result.warnings);
40553
- }
40554
- }
40555
- const value = result.output.value;
40556
40751
  try {
40557
40752
  if (options.output) {
40558
40753
  await writePrivateOutput(options.output, value, "Secret output");
@@ -40563,9 +40758,47 @@ function registerSecretsCommand(program, openKeyAcquisition) {
40563
40758
  outputJson({ name, value });
40564
40759
  }
40565
40760
  } catch (error) {
40566
- throw withOperationWarnings(error, result.warnings);
40761
+ throw withOperationWarnings(error, secret.warnings);
40567
40762
  }
40568
- outputWarnings(operationWarnings(result.warnings));
40763
+ outputWarnings(operationWarnings(secret.warnings));
40764
+ } catch (error) {
40765
+ return handleError(error);
40766
+ }
40767
+ });
40768
+ secrets.command("run [names...]").description("Run a command with secrets in its environment").usage("[options] [names...] -- <command> [args...]").option(
40769
+ "--map <NAME=ENV_VAR>",
40770
+ "Give secret NAME to the command as environment variable ENV_VAR (repeatable)",
40771
+ (value, previous) => [...previous ?? [], value]
40772
+ ).option("--scope <scope>", "Logical secret scope").option("--space <name|uri>", "Target a non-default secrets space (short name or full URI)").option("--delegation <source>", "Delegation file path or imported profile name").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (operands, options, cmd) => {
40773
+ try {
40774
+ const plan = secretRunPlan(cmd, operands, options.map ?? []);
40775
+ const globalOpts = cmd.optsWithGlobals();
40776
+ const ctx = await ProfileManager.resolveContext(globalOpts);
40777
+ const targets = [];
40778
+ for (const name of new Set(plan.exports.map((entry) => entry.name))) {
40779
+ targets.push(await resolveSecretReadTarget(ctx, name, options));
40780
+ }
40781
+ const values = /* @__PURE__ */ new Map();
40782
+ let warnings = [];
40783
+ for (const target of targets) {
40784
+ const secret = await readSecret({ ctx, host: globalOpts.host, target, options, openKeyAcquisition });
40785
+ if (secret.value.includes("\0")) {
40786
+ throw new CLIError(
40787
+ "SECRET_NOT_EXPORTABLE",
40788
+ `Secret "${target.name}" contains a NUL character, so it can't be put in an environment variable. The command wasn't started.`,
40789
+ ExitCode.ERROR
40790
+ );
40791
+ }
40792
+ values.set(target.name, secret.value);
40793
+ warnings = mergeWarnings(warnings, secret.warnings ?? []);
40794
+ }
40795
+ outputWarnings(operationWarnings(warnings));
40796
+ await closeReplication();
40797
+ const env = { ...process.env };
40798
+ for (const variable of TC_CREDENTIAL_VARIABLES) delete env[variable];
40799
+ for (const entry of plan.exports) env[entry.variable] = values.get(entry.name);
40800
+ values.clear();
40801
+ exitAsCommand(await runCommand(plan.command, env), plan.command[0]);
40569
40802
  } catch (error) {
40570
40803
  return handleError(error);
40571
40804
  }
@@ -41027,7 +41260,7 @@ async function writeShareOutput(directory, filename, bytes, force) {
41027
41260
  init_profiles();
41028
41261
 
41029
41262
  // src/share/html-folder.ts
41030
- import { constants as constants2, existsSync } from "fs";
41263
+ import { constants as constants2, existsSync as existsSync2 } from "fs";
41031
41264
  import { lstat as lstat11, open as open14, readdir as readdir7, realpath as realpath3, stat as stat7 } from "fs/promises";
41032
41265
  import { basename as basename5, dirname as dirname9, isAbsolute, join as join19, relative as relativePath, resolve as resolve7, sep as sep4 } from "path";
41033
41266
 
@@ -41965,7 +42198,7 @@ function isRaceError(error) {
41965
42198
  }
41966
42199
  var PROC_FD = "/proc/self/fd";
41967
42200
  async function linuxDescriptorPath(fd) {
41968
- if (process.platform !== "linux" || !existsSync(PROC_FD)) return void 0;
42201
+ if (process.platform !== "linux" || !existsSync2(PROC_FD)) return void 0;
41969
42202
  return realpath3(`${PROC_FD}/${fd}`);
41970
42203
  }
41971
42204
  async function parentChainIsRealDirectories(root, path) {
@@ -43602,11 +43835,11 @@ function plural(count, label) {
43602
43835
  init_theme();
43603
43836
  init_errors();
43604
43837
  import { execSync } from "child_process";
43605
- import { readFileSync as readFileSync2 } from "fs";
43838
+ import { readFileSync as readFileSync3 } from "fs";
43606
43839
  var PACKAGE_NAME = "@tinycloud/cli";
43607
43840
  function getCurrentVersion() {
43608
43841
  const pkg = JSON.parse(
43609
- readFileSync2(new URL("../package.json", import.meta.url), "utf-8")
43842
+ readFileSync3(new URL("../package.json", import.meta.url), "utf-8")
43610
43843
  );
43611
43844
  return pkg.version;
43612
43845
  }