@dragonmastery/tamer 0.36.0 → 0.36.2
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/tamer.mjs +79 -2
- package/dist/tamer.mjs.map +1 -1
- package/package.json +1 -1
package/dist/tamer.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
2
|
import { c as TAMER_OVERLAY_ENV_KEY, f as getDispatchNamespaces, n as materializeTamerResolvable, r as materializeVars, t as materializeCloudflareBindings } from "./normalize-DVSTRZhO.mjs";
|
|
3
3
|
import { basename, dirname, relative, resolve } from "path";
|
|
4
4
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -5579,6 +5579,50 @@ async function deleteEnvSecretRows(api, env) {
|
|
|
5579
5579
|
await api.d1Query(uuid$1, `DELETE FROM secret_history WHERE name LIKE ?`, [`${env}:%`]);
|
|
5580
5580
|
return true;
|
|
5581
5581
|
}
|
|
5582
|
+
/**
|
|
5583
|
+
* Copy all secret rows from one env namespace to another in the shared D1.
|
|
5584
|
+
* Copies ciphertext + hashes directly — no decryption/re-encryption. Requires
|
|
5585
|
+
* both envs to use the same master key (or the target to accept the source's
|
|
5586
|
+
* wrapped DEK, which it does since each row carries its own `wrapped_dek`).
|
|
5587
|
+
*
|
|
5588
|
+
* Used by `tamer secrets copy --from dev --to pr-42` for ephemeral PR envs.
|
|
5589
|
+
* Returns the number of secrets copied.
|
|
5590
|
+
*/
|
|
5591
|
+
async function copyEnvSecretRows(api, fromEnv, toEnv) {
|
|
5592
|
+
const uuid$1 = await findTamerSecretsDatabaseUuid(api);
|
|
5593
|
+
if (!uuid$1) throw new Error(`secrets copy: vault not found. Run "tamer bootstrap" first.`);
|
|
5594
|
+
const { rows } = await api.d1Query(uuid$1, `SELECT name, ciphertext, iv, wrapped_dek, dek_iv, value_hash, updated_at, updated_by
|
|
5595
|
+
FROM secrets WHERE name LIKE ?`, [`${fromEnv}:%`]);
|
|
5596
|
+
if (rows.length === 0) return 0;
|
|
5597
|
+
const fromPrefix = `${fromEnv}:`;
|
|
5598
|
+
const toPrefix = `${toEnv}:`;
|
|
5599
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5600
|
+
for (const row of rows) {
|
|
5601
|
+
const sourceName = String(row.name);
|
|
5602
|
+
const targetName = `${toPrefix}${sourceName.startsWith(fromPrefix) ? sourceName.slice(fromPrefix.length) : sourceName}`;
|
|
5603
|
+
await api.d1Query(uuid$1, `INSERT INTO secrets (
|
|
5604
|
+
name, ciphertext, iv, wrapped_dek, dek_iv, value_hash, updated_at, updated_by
|
|
5605
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
5606
|
+
ON CONFLICT(name) DO UPDATE SET
|
|
5607
|
+
ciphertext = excluded.ciphertext,
|
|
5608
|
+
iv = excluded.iv,
|
|
5609
|
+
wrapped_dek = excluded.wrapped_dek,
|
|
5610
|
+
dek_iv = excluded.dek_iv,
|
|
5611
|
+
value_hash = excluded.value_hash,
|
|
5612
|
+
updated_at = excluded.updated_at,
|
|
5613
|
+
updated_by = excluded.updated_by`, [
|
|
5614
|
+
targetName,
|
|
5615
|
+
row.ciphertext,
|
|
5616
|
+
row.iv,
|
|
5617
|
+
row.wrapped_dek,
|
|
5618
|
+
row.dek_iv,
|
|
5619
|
+
row.value_hash,
|
|
5620
|
+
now,
|
|
5621
|
+
`copy:${fromEnv}`
|
|
5622
|
+
]);
|
|
5623
|
+
}
|
|
5624
|
+
return rows.length;
|
|
5625
|
+
}
|
|
5582
5626
|
|
|
5583
5627
|
//#endregion
|
|
5584
5628
|
//#region src/core/secrets/masterKey.ts
|
|
@@ -8363,6 +8407,29 @@ async function runSecretsPush(options) {
|
|
|
8363
8407
|
console.log(`secrets push (${ctx.env}): ${pushed} pushed, ${skipped} already current`);
|
|
8364
8408
|
}
|
|
8365
8409
|
|
|
8410
|
+
//#endregion
|
|
8411
|
+
//#region src/cli/commands/secrets/copy.ts
|
|
8412
|
+
/**
|
|
8413
|
+
* Copy all secrets from one env's vault namespace to another. Copies
|
|
8414
|
+
* ciphertext directly — no decryption/re-encryption. Each row carries its
|
|
8415
|
+
* own wrapped DEK, so the target env doesn't even need the same master key.
|
|
8416
|
+
*
|
|
8417
|
+
* Usage: `tamer secrets copy --from dev --to pr-42`
|
|
8418
|
+
*/
|
|
8419
|
+
async function runSecretsCopy(options) {
|
|
8420
|
+
const { from, to } = options;
|
|
8421
|
+
if (!from || !to) throw new Error("secrets copy: --from and --to are required (e.g. --from dev --to pr-42)");
|
|
8422
|
+
if (from === to) throw new Error("secrets copy: --from and --to must be different");
|
|
8423
|
+
const accountId = cloudflareAccountIdFromEnv();
|
|
8424
|
+
if (!accountId) throw new Error("CLOUDFLARE_ACCOUNT_ID required (env var or config account_id)");
|
|
8425
|
+
const count = await copyEnvSecretRows(new CFApiClient(accountId), from, to);
|
|
8426
|
+
if (count === 0) {
|
|
8427
|
+
console.log(`secrets copy: no secrets found in vault for env "${from}".`);
|
|
8428
|
+
return;
|
|
8429
|
+
}
|
|
8430
|
+
console.log(`secrets copy: copied ${count} secret(s) from "${from}" to "${to}".`);
|
|
8431
|
+
}
|
|
8432
|
+
|
|
8366
8433
|
//#endregion
|
|
8367
8434
|
//#region src/cli/commands/secrets/index.ts
|
|
8368
8435
|
const SECRETS_USAGE = `usage:
|
|
@@ -8370,6 +8437,7 @@ const SECRETS_USAGE = `usage:
|
|
|
8370
8437
|
tamer secrets set <NAME> --env <env> [--config <path>] # value on stdin (pipe only)
|
|
8371
8438
|
tamer secrets load --env <env> [--worker <name>] [--file <path>] [--config <path>]
|
|
8372
8439
|
# each worker: {workerDir}/.dev.vars.{env}; all declared workers when --worker omitted
|
|
8440
|
+
tamer secrets copy --from <env> --to <env> # copy vault between envs (e.g. dev → pr-42)
|
|
8373
8441
|
tamer secrets get <NAME> --env <env> [--config <path>] # confirmation + audit log
|
|
8374
8442
|
tamer secrets list --env <env> [--config <path>]
|
|
8375
8443
|
tamer secrets rm <NAME> --env <env> [--config <path>]
|
|
@@ -8396,7 +8464,9 @@ function parseSecretsArgs(argv) {
|
|
|
8396
8464
|
configPath: opts.config,
|
|
8397
8465
|
file: opts.file,
|
|
8398
8466
|
worker: opts.worker,
|
|
8399
|
-
yes: opts.yes === true
|
|
8467
|
+
yes: opts.yes === true,
|
|
8468
|
+
from: opts.from,
|
|
8469
|
+
to: opts.to
|
|
8400
8470
|
};
|
|
8401
8471
|
}
|
|
8402
8472
|
async function runSecrets(argv) {
|
|
@@ -8456,6 +8526,13 @@ async function runSecrets(argv) {
|
|
|
8456
8526
|
worker: parsed.worker
|
|
8457
8527
|
});
|
|
8458
8528
|
return 0;
|
|
8529
|
+
case "copy":
|
|
8530
|
+
await runSecretsCopy({
|
|
8531
|
+
from: parsed.from,
|
|
8532
|
+
to: parsed.to,
|
|
8533
|
+
configPath: parsed.configPath
|
|
8534
|
+
});
|
|
8535
|
+
return 0;
|
|
8459
8536
|
default:
|
|
8460
8537
|
console.error(SECRETS_USAGE);
|
|
8461
8538
|
return 1;
|