@geekmidas/cli 10.0.0-alpha.46 → 10.0.0-alpha.47

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/index.mjs CHANGED
@@ -5,10 +5,11 @@ import { d as parseModuleConfig, f as appKey, l as loadWorkspaceConfig, n as Not
5
5
  import { D as assertDeployedStage, E as TEST_STAGE, O as deployedProblems, T as InvalidStages, a as getAppBuildOrder, b as getDeployTargetError, k as stageProblems, m as stripPublicPrefix, o as getAppGkmConfig, p as getPublicEnvPrefix, t as allConstructGlobs, x as isDeployTargetSupported } from "./workspace-DNzWgM1J.mjs";
6
6
  import { a as withCustomSecret, i as toEmbeddableSecrets, n as maskPassword, t as initStageSecrets } from "./storage-BzaUxmqe.mjs";
7
7
  import { a as getKeyPath, i as FileSecretsStore, n as secretsStoreFor } from "./store-DeGATCS-.mjs";
8
- import { C as createEntryWrapper, D as loadSecretsForApp, E as loadEnvFiles, O as prepareEntryCredentials, S as createCredentialsPreload, T as isPortAvailable, _ as primaryPortKey, a as reconcileWorkspace, b as providerOf, c as LOCAL_CA_PATH, d as postgresDatabaseNames, f as quoteIdentifier, g as fakeKey, h as rootSite, i as derivedContainers, l as forgetState, m as resourceName, n as backendsOf$1, p as pgClient, r as constructGlobs, s as COMPOSE_PATH, t as FAKE_ENV, u as applyPostgres, v as cacheBackendFor, w as findAvailablePort, x as storageBackendFor, y as eventsBackendFor } from "./workspace-CHEC80sp.mjs";
8
+ import { C as loadEnvFiles, S as isPortAvailable, T as prepareEntryCredentials, _ as fakeKey, a as reconcileWorkspace, b as createEntryWrapper, c as LOCAL_CA_PATH, d as postgresDatabaseNames, f as quoteIdentifier, g as describeServices, h as rootSite, i as derivedContainers, l as forgetState, m as resourceName, n as backendsOf$1, p as pgClient, r as constructGlobs, s as COMPOSE_PATH, t as FAKE_ENV, u as applyPostgres, v as primaryPortKey, w as loadSecretsForApp, x as findAvailablePort, y as createCredentialsPreload } from "./workspace-cb6U29EL.mjs";
9
9
  import { n as EndpointGenerator, r as runtimeFor } from "./OpenApiTsGenerator-BzIGy8KE.mjs";
10
10
  import { generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi.mjs";
11
- import { a as writeTestHarness, c as driversFor, i as TEST_MANIFEST_ENV, n as TEST_READY_FILE, o as SubscriberGenerator, s as cacheBackendsIn, t as TEST_READY_ENV } from "./ready-V6Ou6-B6.mjs";
11
+ import { a as writeTestHarness, c as driversFor, i as TEST_MANIFEST_ENV, n as TEST_READY_FILE, o as SubscriberGenerator, s as cacheBackendsIn, t as TEST_READY_ENV } from "./ready-WfT-eWlm.mjs";
12
+ import { i as storageBackendFor, n as eventsBackendFor, r as providerOf, t as cacheBackendFor } from "./backends-CSuLl0YP.mjs";
12
13
  import { n as withCompute, r as withRoutes, t as manifestModule } from "./emit-C1e8qD08.mjs";
13
14
  import { createRequire } from "node:module";
14
15
  import { Command } from "commander";
@@ -24,6 +25,7 @@ import fg from "fast-glob";
24
25
  import chokidar from "chokidar";
25
26
  import { createCipheriv, createHash, randomBytes } from "node:crypto";
26
27
  import { fileURLToPath, pathToFileURL } from "node:url";
28
+ import { generateKeyring } from "@geekmidas/constructs/encryption";
27
29
  import { ownerRole, readerRole, roleStatements } from "@geekmidas/db/pg/roles";
28
30
  import { Client } from "pg";
29
31
  import { parse as parse$1, parseDocument } from "yaml";
@@ -40,7 +42,7 @@ import { Queue } from "@geekmidas/constructs/queue";
40
42
  import { Topic } from "@geekmidas/constructs/topic";
41
43
  import { lookup } from "node:dns/promises";
42
44
  //#region package.json
43
- var version = "10.0.0-alpha.46";
45
+ var version = "10.0.0-alpha.47";
44
46
  //#endregion
45
47
  //#region src/auth/credentials.ts
46
48
  /**
@@ -1196,9 +1198,9 @@ async function devCommand(options) {
1196
1198
  stage: workspace.stages.local,
1197
1199
  ...options.fake ? { fake: true } : {}
1198
1200
  });
1199
- if (reconciled.changed && reconciled.plan.containers.length > 0) {
1200
- logger$9.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
1201
- for (const [container, address] of Object.entries(reconciled.addresses)) logger$9.log(` ${container}: ${address}`);
1201
+ if (reconciled.services.length > 0) {
1202
+ logger$9.log("🐳 Services");
1203
+ for (const line of describeServices(reconciled.services)) logger$9.log(line);
1202
1204
  }
1203
1205
  Object.assign(appSecrets, reconciled.env);
1204
1206
  const own = reconciled.plan.resources.find((r) => (r.kind === "rest-api" || r.kind === "site") && appKey(r.id) === workspaceAppName);
@@ -1479,9 +1481,9 @@ async function workspaceDevCommand(workspace, options) {
1479
1481
  stage: workspace.stages.local,
1480
1482
  ...options.fake ? { fake: true } : {}
1481
1483
  });
1482
- if (reconciled.changed && reconciled.plan.containers.length > 0) {
1483
- logger$9.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
1484
- for (const [container, address] of Object.entries(reconciled.addresses)) logger$9.log(` ${container}: ${address}`);
1484
+ if (reconciled.services.length > 0) {
1485
+ logger$9.log("🐳 Services");
1486
+ for (const line of describeServices(reconciled.services)) logger$9.log(line);
1485
1487
  }
1486
1488
  const secretsEnv = {
1487
1489
  ...rawSecrets,
@@ -1490,9 +1492,9 @@ async function workspaceDevCommand(workspace, options) {
1490
1492
  const fakes = options.fake ? await serveFakes(reconciled.fakes, reconciled.ports) : [];
1491
1493
  for (const { id, port } of fakes) logger$9.log(`🎭 ${id} fake: http://localhost:${port}`);
1492
1494
  if (options.migrate || options.seed) {
1493
- const { prepareDevDatabases } = await import("./migrate-B3opyC3o.mjs");
1495
+ const { prepareDevDatabases } = await import("./migrate-DhxTIA0z.mjs");
1494
1496
  await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
1495
- } else await import("./migrate-B3opyC3o.mjs").then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1497
+ } else await import("./migrate-DhxTIA0z.mjs").then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1496
1498
  logger$9.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
1497
1499
  });
1498
1500
  const appUrls = {};
@@ -3370,6 +3372,19 @@ const PROVISIONERS = {
3370
3372
  return { provides: { [key]: supplied(declaration.id, key, context) } };
3371
3373
  },
3372
3374
  /**
3375
+ * The keyring the stage keeps in its secrets.
3376
+ *
3377
+ * Generated on the first deploy (`withGeneratedSecrets`) and rotated only
3378
+ * by `gkm encryption:rotate`, so a redeploy never changes a key and nothing
3379
+ * written becomes unreadable. Nothing is created in Dokploy: the process
3380
+ * holds the keyring.
3381
+ */
3382
+ encryption: async (declaration, context) => {
3383
+ if (declaration.kind !== "encryption") throw new WrongKind(declaration.kind);
3384
+ const key = provideKey(declaration.id, "url");
3385
+ return { provides: { [key]: supplied(declaration.id, key, context) } };
3386
+ },
3387
+ /**
3373
3388
  * A third party's credentials, as the stage was given them.
3374
3389
  *
3375
3390
  * Nothing is created and nothing derived: the value was issued by somebody
@@ -6373,6 +6388,8 @@ function validateEnvVars(requiredVars, context) {
6373
6388
  * who could read the repo.
6374
6389
  * - **Each `secret` construct's value** — an auth server's signing secret —
6375
6390
  * random, rather than derived.
6391
+ * - **Each `encryption` construct's keyring** — one random key and an index
6392
+ * key. Later keys are added by `gkm encryption:rotate`, never by a deploy.
6376
6393
  *
6377
6394
  * Both are generated on the first deploy that needs them and written to the
6378
6395
  * stage's store, so every later deploy reads the same values: sessions survive
@@ -6388,10 +6405,10 @@ function withGeneratedSecrets(secrets, manifest) {
6388
6405
  const generated = [];
6389
6406
  const custom = { ...secrets.custom };
6390
6407
  for (const declaration of Object.values(manifest)) {
6391
- if (declaration?.kind !== "secret") continue;
6408
+ if (declaration?.kind !== "secret" && declaration?.kind !== "encryption") continue;
6392
6409
  const key = declaration.provides?.[0];
6393
6410
  if (!key || custom[key]) continue;
6394
- custom[key] = random();
6411
+ custom[key] = declaration.kind === "encryption" ? generateKeyring() : random();
6395
6412
  generated.push(key);
6396
6413
  }
6397
6414
  const seed = secrets.seed ?? random();
@@ -9065,26 +9082,26 @@ function loadPackageJson() {
9065
9082
  * Run: pnpm --filter @geekmidas/cli sync-versions
9066
9083
  */
9067
9084
  const GEEKMIDAS_VERSIONS = {
9068
- "@geekmidas/audit": "~10.0.0-alpha.46",
9069
- "@geekmidas/auth": "~10.0.0-alpha.46",
9070
- "@geekmidas/cache": "~10.0.0-alpha.46",
9071
- "@geekmidas/client": "~10.0.0-alpha.46",
9072
- "@geekmidas/cloud": "~10.0.0-alpha.46",
9073
- "@geekmidas/constructs": "~10.0.0-alpha.46",
9074
- "@geekmidas/db": "~10.0.0-alpha.46",
9075
- "@geekmidas/emailkit": "~10.0.0-alpha.46",
9076
- "@geekmidas/envkit": "~10.0.0-alpha.46",
9077
- "@geekmidas/errors": "~10.0.0-alpha.46",
9078
- "@geekmidas/events": "~10.0.0-alpha.46",
9079
- "@geekmidas/logger": "~10.0.0-alpha.46",
9080
- "@geekmidas/manifest": "~10.0.0-alpha.46",
9081
- "@geekmidas/rate-limit": "~10.0.0-alpha.46",
9082
- "@geekmidas/schema": "~10.0.0-alpha.46",
9083
- "@geekmidas/services": "~10.0.0-alpha.46",
9084
- "@geekmidas/storage": "~10.0.0-alpha.46",
9085
- "@geekmidas/studio": "~10.0.0-alpha.46",
9086
- "@geekmidas/telescope": "~10.0.0-alpha.46",
9087
- "@geekmidas/testkit": "~10.0.0-alpha.46",
9085
+ "@geekmidas/audit": "~10.0.0-alpha.47",
9086
+ "@geekmidas/auth": "~10.0.0-alpha.47",
9087
+ "@geekmidas/cache": "~10.0.0-alpha.47",
9088
+ "@geekmidas/client": "~10.0.0-alpha.47",
9089
+ "@geekmidas/cloud": "~10.0.0-alpha.47",
9090
+ "@geekmidas/constructs": "~10.0.0-alpha.47",
9091
+ "@geekmidas/db": "~10.0.0-alpha.47",
9092
+ "@geekmidas/emailkit": "~10.0.0-alpha.47",
9093
+ "@geekmidas/envkit": "~10.0.0-alpha.47",
9094
+ "@geekmidas/errors": "~10.0.0-alpha.47",
9095
+ "@geekmidas/events": "~10.0.0-alpha.47",
9096
+ "@geekmidas/logger": "~10.0.0-alpha.47",
9097
+ "@geekmidas/manifest": "~10.0.0-alpha.47",
9098
+ "@geekmidas/rate-limit": "~10.0.0-alpha.47",
9099
+ "@geekmidas/schema": "~10.0.0-alpha.47",
9100
+ "@geekmidas/services": "~10.0.0-alpha.47",
9101
+ "@geekmidas/storage": "~10.0.0-alpha.47",
9102
+ "@geekmidas/studio": "~10.0.0-alpha.47",
9103
+ "@geekmidas/telescope": "~10.0.0-alpha.47",
9104
+ "@geekmidas/testkit": "~10.0.0-alpha.47",
9088
9105
  "@geekmidas/cli": `~${loadPackageJson().version}`
9089
9106
  };
9090
9107
  //#endregion
@@ -15493,8 +15510,8 @@ async function reconcileLocal(workspace, stage, options) {
15493
15510
  logger$1.log("🐳 Services already up to date");
15494
15511
  return;
15495
15512
  }
15496
- logger$1.log(`🐳 Services: ${result.plan.containers.join(", ")}`);
15497
- for (const [container, address] of Object.entries(result.addresses)) logger$1.log(` ${container}: ${address}`);
15513
+ logger$1.log("🐳 Services");
15514
+ for (const line of describeServices(result.services)) logger$1.log(line);
15498
15515
  const url = result.plan.resources.filter((r) => r.kind === "rest-api" || r.kind === "site" || r.kind === "file-server").map((r) => result.env[r.envKey]).find((address) => address?.startsWith("https://"));
15499
15516
  if (url) await ensureTrusted(workspace.root, url, { ...options.yes ? { assumeYes: true } : {} });
15500
15517
  }
@@ -15841,7 +15858,7 @@ function testDatabases(reconciled) {
15841
15858
  * starts without the reference data the app needs.
15842
15859
  */
15843
15860
  async function migrateTestStage(workspace, env) {
15844
- const { migrateAndSeed } = await import("./migrate-B3opyC3o.mjs");
15861
+ const { migrateAndSeed } = await import("./migrate-DhxTIA0z.mjs");
15845
15862
  const { discover } = await import("./discover-Cf2jAcgF.mjs").then((n) => n.n);
15846
15863
  const sources = {};
15847
15864
  const manifest = await discover({
@@ -16272,7 +16289,7 @@ program.command("migrate").description("Apply each database construct's migratio
16272
16289
  try {
16273
16290
  const globalOptions = program.opts();
16274
16291
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16275
- const { migrateCommand } = await import("./migrate-B3opyC3o.mjs");
16292
+ const { migrateCommand } = await import("./migrate-DhxTIA0z.mjs");
16276
16293
  await migrateCommand({
16277
16294
  ...options,
16278
16295
  ...construct ? { construct } : {}
@@ -16286,7 +16303,7 @@ program.command("seed").description("Migrate, then run each database construct's
16286
16303
  try {
16287
16304
  const globalOptions = program.opts();
16288
16305
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16289
- const { seedCommand } = await import("./migrate-B3opyC3o.mjs");
16306
+ const { seedCommand } = await import("./migrate-DhxTIA0z.mjs");
16290
16307
  await seedCommand({
16291
16308
  ...options,
16292
16309
  ...construct ? { construct } : {}
@@ -16300,7 +16317,7 @@ program.command("migration").description("Write a construct's next migration: an
16300
16317
  try {
16301
16318
  const globalOptions = program.opts();
16302
16319
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16303
- const { migrationCommand } = await import("./migrate-B3opyC3o.mjs");
16320
+ const { migrationCommand } = await import("./migrate-DhxTIA0z.mjs");
16304
16321
  await migrationCommand({
16305
16322
  construct,
16306
16323
  ...name ? { name } : {}
@@ -16394,6 +16411,28 @@ program.command("secrets:set").description("Set a custom secret for a stage").ar
16394
16411
  process.exit(1);
16395
16412
  }
16396
16413
  });
16414
+ program.command("encryption:rotate").description("Add a new current key to an Encryption construct's keyring on a server stage").argument("<construct>", "The Encryption construct (e.g. Pii)").requiredOption("--stage <stage>", "A deployed stage").action(async (construct, options) => {
16415
+ try {
16416
+ const globalOptions = program.opts();
16417
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16418
+ const { encryptionRotateCommand } = await import("./encryption-DyQnPsZN.mjs");
16419
+ await encryptionRotateCommand(construct, options);
16420
+ } catch (error) {
16421
+ console.error(formatError(error));
16422
+ process.exit(1);
16423
+ }
16424
+ });
16425
+ program.command("encryption:retire").description("Remove an old key from an Encryption construct's keyring, once nothing is under it").argument("<construct>", "The Encryption construct (e.g. Pii)").argument("<key>", "The key to retire (e.g. k1)").requiredOption("--stage <stage>", "A deployed stage").action(async (construct, key, options) => {
16426
+ try {
16427
+ const globalOptions = program.opts();
16428
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16429
+ const { encryptionRetireCommand } = await import("./encryption-DyQnPsZN.mjs");
16430
+ await encryptionRetireCommand(construct, key, options);
16431
+ } catch (error) {
16432
+ console.error(formatError(error));
16433
+ process.exit(1);
16434
+ }
16435
+ });
16397
16436
  program.command("secrets:show").description("Show secrets for a stage").requiredOption("--stage <stage>", "Stage name").option("--reveal", "Show actual secret values (not masked)").action(async (options) => {
16398
16437
  try {
16399
16438
  const globalOptions = program.opts();
@@ -16439,7 +16478,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
16439
16478
  console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
16440
16479
  process.exit(1);
16441
16480
  }
16442
- const { derivedContainers } = await import("./workspace-CHEC80sp.mjs").then((n) => n.o);
16481
+ const { derivedContainers } = await import("./workspace-cb6U29EL.mjs").then((n) => n.o);
16443
16482
  const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
16444
16483
  if (!result) {
16445
16484
  console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);