@geekmidas/cli 10.0.0-alpha.31 → 10.0.0-alpha.32

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
@@ -37,7 +37,7 @@ import { Subscriber } from "@geekmidas/constructs/subscribers";
37
37
  import { Topic } from "@geekmidas/constructs/topic";
38
38
  import { lookup } from "node:dns/promises";
39
39
  //#region package.json
40
- var version = "10.0.0-alpha.31";
40
+ var version = "10.0.0-alpha.32";
41
41
  //#endregion
42
42
  //#region src/auth/credentials.ts
43
43
  /**
@@ -1086,7 +1086,10 @@ async function workspaceDevCommand(workspace, options) {
1086
1086
  ...rawSecrets,
1087
1087
  ...reconciled.env
1088
1088
  };
1089
- await import("./migrate-CBICVMAi.mjs").then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1089
+ if (options.migrate || options.seed) {
1090
+ const { prepareDevDatabases } = await import("./migrate-CnPtWnkR.mjs");
1091
+ await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
1092
+ } else await import("./migrate-CnPtWnkR.mjs").then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1090
1093
  logger$10.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
1091
1094
  });
1092
1095
  const appUrls = {};
@@ -8293,7 +8296,7 @@ function layoutBlock(options, template) {
8293
8296
  const { database } = options.constructs;
8294
8297
  const isWorker = template.name === "worker";
8295
8298
  if (monorepo) return `constructs/ # every construct: the surface, the database, topics
8296
- ${database ? "db/<construct>/ # migrations, one folder per database construct\n" : ""}${apiPath}/ # the API app
8299
+ ${database ? "db/<construct>/ # migrations/ and seeds/, one folder per database construct\n" : ""}${apiPath}/ # the API app
8297
8300
  gkm.config.ts # name, constructs glob, secrets`;
8298
8301
  const lines = ["src/constructs/ # the surface, and anything it declares", "src/config/ # env parser, logger"];
8299
8302
  if (!isWorker) lines.push("src/endpoints/ # HTTP handlers");
@@ -8301,7 +8304,7 @@ gkm.config.ts # name, constructs glob, secrets`;
8301
8304
  lines.push("src/crons/ # scheduled work");
8302
8305
  lines.push("src/subscribers/ # topic consumers");
8303
8306
  }
8304
- if (database) lines.push("db/<construct>/ # migrations, one folder per database construct");
8307
+ if (database) lines.push("db/<construct>/ # migrations/ and seeds/, one folder per database construct");
8305
8308
  lines.push("gkm.config.ts # name, constructs glob, secrets");
8306
8309
  return lines.join("\n");
8307
8310
  }
@@ -8430,32 +8433,52 @@ Queries go through Kysely. Never open your own connection pool — the one the
8430
8433
  construct provides is the one that gets credentials, pooling and shutdown.`;
8431
8434
  }
8432
8435
  function migrationsSection() {
8433
- return `## Migrations
8436
+ return `## Migrations and seeds
8434
8437
 
8435
8438
  Each database construct has its own folder, named after the construct:
8436
8439
  \`Database\` is \`db/database/\`, a tenant \`AuthDatabase\` is
8437
- \`db/auth-database/\`. Each keeps its own history, in its own schema, and is
8438
- migrated as its own **owner** role — the role a handler runs as cannot create a
8440
+ \`db/auth-database/\`. It holds two folders and nothing else:
8441
+
8442
+ \`\`\`
8443
+ db/database/
8444
+ migrations/ # the schema's history, applied once each
8445
+ seeds/ # the reference data the app needs, run on every pass
8446
+ \`\`\`
8447
+
8448
+ Each construct keeps its own history, in its own schema, and is migrated and
8449
+ seeded as its own **owner** role — the role a handler runs as cannot create a
8439
8450
  table.
8440
8451
 
8441
8452
  \`\`\`bash
8442
- gkm migration database add_workouts # db/database/20260930143012_add_workouts.ts
8453
+ gkm migration database add_workouts # db/database/migrations/20260930143012_add_workouts.ts
8443
8454
  gkm migration auth # Better Auth's schema change, as SQL
8444
- gkm migrate # apply every folder, parents first
8455
+ gkm migrate # apply every construct's migrations, parents first
8445
8456
  gkm migrate database # apply one construct's
8457
+ gkm seed # migrate, then run every seed
8458
+ gkm dev --migrate # migrate before the apps start
8459
+ gkm dev --seed # migrate and seed before the apps start
8446
8460
  \`\`\`
8447
8461
 
8448
- - Files are \`<UTC timestamp>_<name>.ts\` exporting \`up\` (and \`down\`), or
8449
- \`.sql\`, which runs whole. Never rename or edit one that has been applied —
8450
- it is recorded by name. Change the schema with a new migration.
8462
+ - Migrations are \`<UTC timestamp>_<name>.ts\` exporting \`up\` (and
8463
+ \`down\`), or \`.sql\`, which runs whole. Never rename or edit one that has
8464
+ been applied — it is recorded by name. Change the schema with a new
8465
+ migration.
8451
8466
  - Write migrations with \`Kysely<unknown>\` and snake_case names: they are the
8452
8467
  SQL, and the camelCase plugin is the app's, not theirs.
8453
8468
  - \`gkm migration auth\` compares Better Auth's schema to its tenant and writes
8454
8469
  only the difference. Run it after adding a Better Auth plugin, and commit the
8455
8470
  file.
8456
- - Tests migrate themselves: the root vitest config's
8457
- \`globalSetup: ['@geekmidas/cli/vitest']\` applies every folder to the test
8458
- stage before any test runs. \`gkm dev\` only reports what is pending.
8471
+ - Seeds always run after migrations, never instead of them. Each is a
8472
+ \`.ts\` exporting \`seed(db, { stage })\`, or \`.sql\`, run in name
8473
+ order, in a transaction of its own.
8474
+ - **Seeds run on every stage, production included.** Write them to be safe
8475
+ there. A seed that belongs only somewhere checks the stage it is handed:
8476
+ \`if (stage === 'production') return;\`. A \`.sql\` seed has no stage, so
8477
+ it must be right everywhere.
8478
+ - Tests migrate and seed themselves: the root vitest config's
8479
+ \`globalSetup: ['@geekmidas/cli/vitest']\` does both for the test stage
8480
+ before any test runs. \`gkm dev\` without a flag only reports what is
8481
+ pending.
8459
8482
  - A deploy migrates before the new version goes live, so the old version runs
8460
8483
  against the new schema until it is replaced. Keep each migration compatible
8461
8484
  with the code before it: add a column in one release, stop using the old one,
@@ -8464,29 +8487,26 @@ gkm migrate database # apply one construct's
8464
8487
  **Migrations hold schema, never data.** Tables, columns, constraints, indexes.
8465
8488
  No inserts, updates or deletes against the application's rows.
8466
8489
 
8467
- - **Data the code defines lives in the code.** A permission catalogue, the
8468
- system roles, a list of statuses — if a constant in the source already says
8469
- what they are, a table holding them is a copy, and a copy drifts. Keep the
8470
- constant; store only what users create (a custom role, which permission keys
8471
- it grants), checked against the constant in code.
8472
- - **Define it once, typed, where every app can import it.** A catalogue in
8473
- code belongs in a shared package (\`packages/models\`), declared \`as const\`
8474
- so its keys are a union type. The API's guards, the web app's gates and a
8475
- mobile app's gates then all name the same keys, and a typo — or a removed
8490
+ - **Reference data is a seed.** A permission catalogue, the roles and what
8491
+ they grant, a list of statuses — rows the app needs in order to run — are
8492
+ written by a seed in \`db/<construct>/seeds/\`, never by a migration.
8493
+ - **A seed is an upsert, run every time.** There is no history: every seed
8494
+ runs on every \`gkm seed\`, so changing what it writes and running it again
8495
+ is how the change is applied. Write \`onConflict(…).doUpdateSet(…)\` (or
8496
+ \`doNothing()\`), never a bare insert.
8497
+ - **Define it once, typed, where every app can import it.** A catalogue
8498
+ belongs in code, declared \`as const\` so its keys are a union type, and the
8499
+ seed writes the table from that constant — so the two cannot drift, and no
8500
+ test is needed to keep them in step. The API's guards, the web app's gates
8501
+ and a mobile app's gates all name the same keys, and a typo — or a removed
8476
8502
  permission still checked somewhere — fails to compile instead of failing
8477
8503
  closed at runtime.
8478
- - **A rule is not a row.** "Every user is a member", "a super admin has every
8479
- permission" are logic. A row per user, or a grant per permission, is a
8480
- snapshot of the rule that goes stale the moment the rule's inputs change.
8481
- - **Nothing needs seeding to run.** If the app fails without some rows present,
8482
- those rows are code in the wrong place. Sample and demo data comes from test
8483
- factories — never a migration, never a deploy.
8484
- - **A backfill is the one exception.** When a schema change needs existing rows
8485
- transformed — a new non-null column filled from an old one — do it in the
8486
- same migration, keyed on the rows it transforms. It never inserts rows that
8487
- were not there.
8488
- - **A test that only keeps two copies in step is the smell.** Delete a copy,
8489
- and the test with it.`;
8504
+ - **Sample data is factories'.** Demo users, a tournament for a test — those
8505
+ come from test factories, never a seed and never a migration.
8506
+ - **A backfill is the one data change a migration makes.** When a schema
8507
+ change needs existing rows transformed — a new non-null column filled from
8508
+ an old one — do it in the same migration, keyed on the rows it transforms.
8509
+ It never inserts rows that were not there.`;
8490
8510
  }
8491
8511
  function queriesSection() {
8492
8512
  return `## Writing queries and relations
@@ -8743,26 +8763,26 @@ function loadPackageJson() {
8743
8763
  * Run: pnpm --filter @geekmidas/cli sync-versions
8744
8764
  */
8745
8765
  const GEEKMIDAS_VERSIONS = {
8746
- "@geekmidas/audit": "~10.0.0-alpha.31",
8747
- "@geekmidas/auth": "~10.0.0-alpha.31",
8748
- "@geekmidas/cache": "~10.0.0-alpha.31",
8749
- "@geekmidas/client": "~10.0.0-alpha.31",
8750
- "@geekmidas/cloud": "~10.0.0-alpha.31",
8751
- "@geekmidas/constructs": "~10.0.0-alpha.31",
8752
- "@geekmidas/db": "~10.0.0-alpha.31",
8753
- "@geekmidas/emailkit": "~10.0.0-alpha.31",
8754
- "@geekmidas/envkit": "~10.0.0-alpha.31",
8755
- "@geekmidas/errors": "~10.0.0-alpha.31",
8756
- "@geekmidas/events": "~10.0.0-alpha.31",
8757
- "@geekmidas/logger": "~10.0.0-alpha.31",
8758
- "@geekmidas/manifest": "~10.0.0-alpha.31",
8759
- "@geekmidas/rate-limit": "~10.0.0-alpha.31",
8760
- "@geekmidas/schema": "~10.0.0-alpha.31",
8761
- "@geekmidas/services": "~10.0.0-alpha.31",
8762
- "@geekmidas/storage": "~10.0.0-alpha.31",
8763
- "@geekmidas/studio": "~10.0.0-alpha.31",
8764
- "@geekmidas/telescope": "~10.0.0-alpha.31",
8765
- "@geekmidas/testkit": "~10.0.0-alpha.31",
8766
+ "@geekmidas/audit": "~10.0.0-alpha.32",
8767
+ "@geekmidas/auth": "~10.0.0-alpha.32",
8768
+ "@geekmidas/cache": "~10.0.0-alpha.32",
8769
+ "@geekmidas/client": "~10.0.0-alpha.32",
8770
+ "@geekmidas/cloud": "~10.0.0-alpha.32",
8771
+ "@geekmidas/constructs": "~10.0.0-alpha.32",
8772
+ "@geekmidas/db": "~10.0.0-alpha.32",
8773
+ "@geekmidas/emailkit": "~10.0.0-alpha.32",
8774
+ "@geekmidas/envkit": "~10.0.0-alpha.32",
8775
+ "@geekmidas/errors": "~10.0.0-alpha.32",
8776
+ "@geekmidas/events": "~10.0.0-alpha.32",
8777
+ "@geekmidas/logger": "~10.0.0-alpha.32",
8778
+ "@geekmidas/manifest": "~10.0.0-alpha.32",
8779
+ "@geekmidas/rate-limit": "~10.0.0-alpha.32",
8780
+ "@geekmidas/schema": "~10.0.0-alpha.32",
8781
+ "@geekmidas/services": "~10.0.0-alpha.32",
8782
+ "@geekmidas/storage": "~10.0.0-alpha.32",
8783
+ "@geekmidas/studio": "~10.0.0-alpha.32",
8784
+ "@geekmidas/telescope": "~10.0.0-alpha.32",
8785
+ "@geekmidas/testkit": "~10.0.0-alpha.32",
8766
8786
  "@geekmidas/cli": `~${loadPackageJson().version}`
8767
8787
  };
8768
8788
  //#endregion
@@ -15357,9 +15377,12 @@ async function testCommand(options = {}) {
15357
15377
  });
15358
15378
  });
15359
15379
  }
15360
- /** Apply every construct's migrations to the test stage just reconciled. */
15380
+ /**
15381
+ * Migrate the test stage just reconciled, then seed it: a test run never
15382
+ * starts without the reference data the app needs.
15383
+ */
15361
15384
  async function migrateTestStage(workspace, env) {
15362
- const { migrateDatabases } = await import("./migrate-CBICVMAi.mjs");
15385
+ const { migrateAndSeed } = await import("./migrate-CnPtWnkR.mjs");
15363
15386
  const { discover } = await import("./discover-CMxesskG.mjs").then((n) => n.n);
15364
15387
  const sources = {};
15365
15388
  const manifest = await discover({
@@ -15367,13 +15390,15 @@ async function migrateTestStage(workspace, env) {
15367
15390
  cwd: workspace.root,
15368
15391
  sources
15369
15392
  });
15370
- const runs = await migrateDatabases({
15393
+ const { migrations, seeds } = await migrateAndSeed({
15371
15394
  root: workspace.root,
15372
15395
  manifest,
15373
15396
  sources,
15374
- env
15397
+ env,
15398
+ stage: TEST_STAGE
15375
15399
  });
15376
- for (const { target, applied } of runs) if (applied.length > 0) console.log(` 🗄️ ${target.folder}: applied ${applied.join(", ")}`);
15400
+ for (const { target, applied } of migrations) if (applied.length > 0) console.log(` 🗄️ ${target.migrations}: applied ${applied.join(", ")}`);
15401
+ for (const { target, seeded } of seeds) if (seeded.length > 0) console.log(` 🌱 ${target.seeds}: ran ${seeded.join(", ")}`);
15377
15402
  }
15378
15403
  /** `gkm test` outside a project that declares stages, with none named. */
15379
15404
  var NoStageToTest = class extends Error {
@@ -15757,7 +15782,7 @@ program.command("build").description("Build handlers and the manifest from decla
15757
15782
  process.exit(1);
15758
15783
  }
15759
15784
  });
15760
- program.command("dev").description("Reconcile constructs and start the dev server with automatic reload").option("-p, --port <port>", "Port to run the development server on").option("--entry <file>", "Entry file to run (bypasses gkm config)").option("--watch", "Watch for file changes (default: true with --entry)").option("--no-watch", "Disable file watching").option("--enable-openapi", "Enable OpenAPI documentation for development server", true).action(async (options) => {
15785
+ program.command("dev").description("Reconcile constructs and start the dev server with automatic reload").option("-p, --port <port>", "Port to run the development server on").option("--entry <file>", "Entry file to run (bypasses gkm config)").option("--watch", "Watch for file changes (default: true with --entry)").option("--no-watch", "Disable file watching").option("--enable-openapi", "Enable OpenAPI documentation for development server", true).option("--migrate", "Apply pending migrations before the apps start").option("--seed", "Migrate, then run the seeds, before the apps start").action(async (options) => {
15761
15786
  try {
15762
15787
  const globalOptions = program.opts();
15763
15788
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
@@ -15766,7 +15791,9 @@ program.command("dev").description("Reconcile constructs and start the dev serve
15766
15791
  portExplicit: !!options.port,
15767
15792
  enableOpenApi: options.enableOpenapi ?? true,
15768
15793
  entry: options.entry,
15769
- watch: options.watch
15794
+ watch: options.watch,
15795
+ migrate: options.migrate,
15796
+ seed: options.seed
15770
15797
  });
15771
15798
  } catch (error) {
15772
15799
  console.error(formatError(error));
@@ -15783,7 +15810,7 @@ program.command("exec").description("Run a command with secrets injected into Cr
15783
15810
  process.exit(1);
15784
15811
  }
15785
15812
  });
15786
- program.command("test").description("Run tests with secrets loaded from environment").option("--stage <stage>", "Stage to load secrets from (default: stages.local)").option("--run", "Run tests once without watch mode").option("--watch", "Enable watch mode").option("--coverage", "Generate coverage report").option("--ui", "Open Vitest UI").option("--auto-setup", "Generate a fresh stage (secrets + key) from the workspace config when none exists (for CI; also via GKM_AUTO_SETUP)").option("--prepare", "Write the test manifest and #test harness, then stop — for a typecheck that runs before the suite").option("--setup", "Reconcile and migrate the test stage and write the harness, then stop — what @geekmidas/cli/vitest runs").argument("[pattern]", "Pattern to filter tests").action(async (pattern, options) => {
15813
+ program.command("test").description("Run tests with secrets loaded from environment").option("--stage <stage>", "Stage to load secrets from (default: stages.local)").option("--run", "Run tests once without watch mode").option("--watch", "Enable watch mode").option("--coverage", "Generate coverage report").option("--ui", "Open Vitest UI").option("--auto-setup", "Generate a fresh stage (secrets + key) from the workspace config when none exists (for CI; also via GKM_AUTO_SETUP)").option("--prepare", "Write the test manifest and #test harness, then stop — for a typecheck that runs before the suite").option("--setup", "Reconcile, migrate and seed the test stage and write the harness, then stop — what @geekmidas/cli/vitest runs").argument("[pattern]", "Pattern to filter tests").action(async (pattern, options) => {
15787
15814
  try {
15788
15815
  const globalOptions = program.opts();
15789
15816
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
@@ -15796,11 +15823,11 @@ program.command("test").description("Run tests with secrets loaded from environm
15796
15823
  process.exit(1);
15797
15824
  }
15798
15825
  });
15799
- program.command("migrate").description("Apply each database construct's migrations from db/<construct>/, as its owner").argument("[construct]", "Only this construct, by name").option("--stage <stage>", "The local stage (default) or test — a deploy migrates its own stage").action(async (construct, options) => {
15826
+ program.command("migrate").description("Apply each database construct's migrations from db/<construct>/migrations/, as its owner").argument("[construct]", "Only this construct, by name").option("--stage <stage>", "The local stage (default) or test — a deploy migrates its own stage").action(async (construct, options) => {
15800
15827
  try {
15801
15828
  const globalOptions = program.opts();
15802
15829
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
15803
- const { migrateCommand } = await import("./migrate-CBICVMAi.mjs");
15830
+ const { migrateCommand } = await import("./migrate-CnPtWnkR.mjs");
15804
15831
  await migrateCommand({
15805
15832
  ...options,
15806
15833
  ...construct ? { construct } : {}
@@ -15810,11 +15837,25 @@ program.command("migrate").description("Apply each database construct's migratio
15810
15837
  process.exit(1);
15811
15838
  }
15812
15839
  });
15840
+ program.command("seed").description("Migrate, then run each database construct's seeds from db/<construct>/seeds/, as its owner").argument("[construct]", "Only this construct, by name").option("--stage <stage>", "The local stage (default) or test — a deploy seeds its own stage").action(async (construct, options) => {
15841
+ try {
15842
+ const globalOptions = program.opts();
15843
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
15844
+ const { seedCommand } = await import("./migrate-CnPtWnkR.mjs");
15845
+ await seedCommand({
15846
+ ...options,
15847
+ ...construct ? { construct } : {}
15848
+ });
15849
+ } catch (error) {
15850
+ console.error(formatError(error));
15851
+ process.exit(1);
15852
+ }
15853
+ });
15813
15854
  program.command("migration").description("Write a construct's next migration: an empty one for a database, the schema change for an auth server").argument("<construct>", "The construct, by name").argument("[name]", "What the migration is called").action(async (construct, name) => {
15814
15855
  try {
15815
15856
  const globalOptions = program.opts();
15816
15857
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
15817
- const { migrationCommand } = await import("./migrate-CBICVMAi.mjs");
15858
+ const { migrationCommand } = await import("./migrate-CnPtWnkR.mjs");
15818
15859
  await migrationCommand({
15819
15860
  construct,
15820
15861
  ...name ? { name } : {}