@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.cjs CHANGED
@@ -42,7 +42,7 @@ let _geekmidas_constructs_subscribers = require("@geekmidas/constructs/subscribe
42
42
  let _geekmidas_constructs_topic = require("@geekmidas/constructs/topic");
43
43
  let node_dns_promises = require("node:dns/promises");
44
44
  //#region package.json
45
- var version = "10.0.0-alpha.31";
45
+ var version = "10.0.0-alpha.32";
46
46
  //#endregion
47
47
  //#region src/auth/credentials.ts
48
48
  /**
@@ -1091,7 +1091,10 @@ async function workspaceDevCommand(workspace, options) {
1091
1091
  ...rawSecrets,
1092
1092
  ...reconciled.env
1093
1093
  };
1094
- await Promise.resolve().then(() => require("./migrate-BuCx7tIv.cjs")).then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1094
+ if (options.migrate || options.seed) {
1095
+ const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-BiasgAIN.cjs"));
1096
+ await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
1097
+ } else await Promise.resolve().then(() => require("./migrate-BiasgAIN.cjs")).then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1095
1098
  logger$10.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
1096
1099
  });
1097
1100
  const appUrls = {};
@@ -8304,7 +8307,7 @@ function layoutBlock(options, template) {
8304
8307
  const { database } = options.constructs;
8305
8308
  const isWorker = template.name === "worker";
8306
8309
  if (monorepo) return `constructs/ # every construct: the surface, the database, topics
8307
- ${database ? "db/<construct>/ # migrations, one folder per database construct\n" : ""}${apiPath}/ # the API app
8310
+ ${database ? "db/<construct>/ # migrations/ and seeds/, one folder per database construct\n" : ""}${apiPath}/ # the API app
8308
8311
  gkm.config.ts # name, constructs glob, secrets`;
8309
8312
  const lines = ["src/constructs/ # the surface, and anything it declares", "src/config/ # env parser, logger"];
8310
8313
  if (!isWorker) lines.push("src/endpoints/ # HTTP handlers");
@@ -8312,7 +8315,7 @@ gkm.config.ts # name, constructs glob, secrets`;
8312
8315
  lines.push("src/crons/ # scheduled work");
8313
8316
  lines.push("src/subscribers/ # topic consumers");
8314
8317
  }
8315
- if (database) lines.push("db/<construct>/ # migrations, one folder per database construct");
8318
+ if (database) lines.push("db/<construct>/ # migrations/ and seeds/, one folder per database construct");
8316
8319
  lines.push("gkm.config.ts # name, constructs glob, secrets");
8317
8320
  return lines.join("\n");
8318
8321
  }
@@ -8441,32 +8444,52 @@ Queries go through Kysely. Never open your own connection pool — the one the
8441
8444
  construct provides is the one that gets credentials, pooling and shutdown.`;
8442
8445
  }
8443
8446
  function migrationsSection() {
8444
- return `## Migrations
8447
+ return `## Migrations and seeds
8445
8448
 
8446
8449
  Each database construct has its own folder, named after the construct:
8447
8450
  \`Database\` is \`db/database/\`, a tenant \`AuthDatabase\` is
8448
- \`db/auth-database/\`. Each keeps its own history, in its own schema, and is
8449
- migrated as its own **owner** role — the role a handler runs as cannot create a
8451
+ \`db/auth-database/\`. It holds two folders and nothing else:
8452
+
8453
+ \`\`\`
8454
+ db/database/
8455
+ migrations/ # the schema's history, applied once each
8456
+ seeds/ # the reference data the app needs, run on every pass
8457
+ \`\`\`
8458
+
8459
+ Each construct keeps its own history, in its own schema, and is migrated and
8460
+ seeded as its own **owner** role — the role a handler runs as cannot create a
8450
8461
  table.
8451
8462
 
8452
8463
  \`\`\`bash
8453
- gkm migration database add_workouts # db/database/20260930143012_add_workouts.ts
8464
+ gkm migration database add_workouts # db/database/migrations/20260930143012_add_workouts.ts
8454
8465
  gkm migration auth # Better Auth's schema change, as SQL
8455
- gkm migrate # apply every folder, parents first
8466
+ gkm migrate # apply every construct's migrations, parents first
8456
8467
  gkm migrate database # apply one construct's
8468
+ gkm seed # migrate, then run every seed
8469
+ gkm dev --migrate # migrate before the apps start
8470
+ gkm dev --seed # migrate and seed before the apps start
8457
8471
  \`\`\`
8458
8472
 
8459
- - Files are \`<UTC timestamp>_<name>.ts\` exporting \`up\` (and \`down\`), or
8460
- \`.sql\`, which runs whole. Never rename or edit one that has been applied —
8461
- it is recorded by name. Change the schema with a new migration.
8473
+ - Migrations are \`<UTC timestamp>_<name>.ts\` exporting \`up\` (and
8474
+ \`down\`), or \`.sql\`, which runs whole. Never rename or edit one that has
8475
+ been applied — it is recorded by name. Change the schema with a new
8476
+ migration.
8462
8477
  - Write migrations with \`Kysely<unknown>\` and snake_case names: they are the
8463
8478
  SQL, and the camelCase plugin is the app's, not theirs.
8464
8479
  - \`gkm migration auth\` compares Better Auth's schema to its tenant and writes
8465
8480
  only the difference. Run it after adding a Better Auth plugin, and commit the
8466
8481
  file.
8467
- - Tests migrate themselves: the root vitest config's
8468
- \`globalSetup: ['@geekmidas/cli/vitest']\` applies every folder to the test
8469
- stage before any test runs. \`gkm dev\` only reports what is pending.
8482
+ - Seeds always run after migrations, never instead of them. Each is a
8483
+ \`.ts\` exporting \`seed(db, { stage })\`, or \`.sql\`, run in name
8484
+ order, in a transaction of its own.
8485
+ - **Seeds run on every stage, production included.** Write them to be safe
8486
+ there. A seed that belongs only somewhere checks the stage it is handed:
8487
+ \`if (stage === 'production') return;\`. A \`.sql\` seed has no stage, so
8488
+ it must be right everywhere.
8489
+ - Tests migrate and seed themselves: the root vitest config's
8490
+ \`globalSetup: ['@geekmidas/cli/vitest']\` does both for the test stage
8491
+ before any test runs. \`gkm dev\` without a flag only reports what is
8492
+ pending.
8470
8493
  - A deploy migrates before the new version goes live, so the old version runs
8471
8494
  against the new schema until it is replaced. Keep each migration compatible
8472
8495
  with the code before it: add a column in one release, stop using the old one,
@@ -8475,29 +8498,26 @@ gkm migrate database # apply one construct's
8475
8498
  **Migrations hold schema, never data.** Tables, columns, constraints, indexes.
8476
8499
  No inserts, updates or deletes against the application's rows.
8477
8500
 
8478
- - **Data the code defines lives in the code.** A permission catalogue, the
8479
- system roles, a list of statuses — if a constant in the source already says
8480
- what they are, a table holding them is a copy, and a copy drifts. Keep the
8481
- constant; store only what users create (a custom role, which permission keys
8482
- it grants), checked against the constant in code.
8483
- - **Define it once, typed, where every app can import it.** A catalogue in
8484
- code belongs in a shared package (\`packages/models\`), declared \`as const\`
8485
- so its keys are a union type. The API's guards, the web app's gates and a
8486
- mobile app's gates then all name the same keys, and a typo — or a removed
8501
+ - **Reference data is a seed.** A permission catalogue, the roles and what
8502
+ they grant, a list of statuses — rows the app needs in order to run — are
8503
+ written by a seed in \`db/<construct>/seeds/\`, never by a migration.
8504
+ - **A seed is an upsert, run every time.** There is no history: every seed
8505
+ runs on every \`gkm seed\`, so changing what it writes and running it again
8506
+ is how the change is applied. Write \`onConflict(…).doUpdateSet(…)\` (or
8507
+ \`doNothing()\`), never a bare insert.
8508
+ - **Define it once, typed, where every app can import it.** A catalogue
8509
+ belongs in code, declared \`as const\` so its keys are a union type, and the
8510
+ seed writes the table from that constant — so the two cannot drift, and no
8511
+ test is needed to keep them in step. The API's guards, the web app's gates
8512
+ and a mobile app's gates all name the same keys, and a typo — or a removed
8487
8513
  permission still checked somewhere — fails to compile instead of failing
8488
8514
  closed at runtime.
8489
- - **A rule is not a row.** "Every user is a member", "a super admin has every
8490
- permission" are logic. A row per user, or a grant per permission, is a
8491
- snapshot of the rule that goes stale the moment the rule's inputs change.
8492
- - **Nothing needs seeding to run.** If the app fails without some rows present,
8493
- those rows are code in the wrong place. Sample and demo data comes from test
8494
- factories — never a migration, never a deploy.
8495
- - **A backfill is the one exception.** When a schema change needs existing rows
8496
- transformed — a new non-null column filled from an old one — do it in the
8497
- same migration, keyed on the rows it transforms. It never inserts rows that
8498
- were not there.
8499
- - **A test that only keeps two copies in step is the smell.** Delete a copy,
8500
- and the test with it.`;
8515
+ - **Sample data is factories'.** Demo users, a tournament for a test — those
8516
+ come from test factories, never a seed and never a migration.
8517
+ - **A backfill is the one data change a migration makes.** When a schema
8518
+ change needs existing rows transformed — a new non-null column filled from
8519
+ an old one — do it in the same migration, keyed on the rows it transforms.
8520
+ It never inserts rows that were not there.`;
8501
8521
  }
8502
8522
  function queriesSection() {
8503
8523
  return `## Writing queries and relations
@@ -8754,26 +8774,26 @@ function loadPackageJson() {
8754
8774
  * Run: pnpm --filter @geekmidas/cli sync-versions
8755
8775
  */
8756
8776
  const GEEKMIDAS_VERSIONS = {
8757
- "@geekmidas/audit": "~10.0.0-alpha.31",
8758
- "@geekmidas/auth": "~10.0.0-alpha.31",
8759
- "@geekmidas/cache": "~10.0.0-alpha.31",
8760
- "@geekmidas/client": "~10.0.0-alpha.31",
8761
- "@geekmidas/cloud": "~10.0.0-alpha.31",
8762
- "@geekmidas/constructs": "~10.0.0-alpha.31",
8763
- "@geekmidas/db": "~10.0.0-alpha.31",
8764
- "@geekmidas/emailkit": "~10.0.0-alpha.31",
8765
- "@geekmidas/envkit": "~10.0.0-alpha.31",
8766
- "@geekmidas/errors": "~10.0.0-alpha.31",
8767
- "@geekmidas/events": "~10.0.0-alpha.31",
8768
- "@geekmidas/logger": "~10.0.0-alpha.31",
8769
- "@geekmidas/manifest": "~10.0.0-alpha.31",
8770
- "@geekmidas/rate-limit": "~10.0.0-alpha.31",
8771
- "@geekmidas/schema": "~10.0.0-alpha.31",
8772
- "@geekmidas/services": "~10.0.0-alpha.31",
8773
- "@geekmidas/storage": "~10.0.0-alpha.31",
8774
- "@geekmidas/studio": "~10.0.0-alpha.31",
8775
- "@geekmidas/telescope": "~10.0.0-alpha.31",
8776
- "@geekmidas/testkit": "~10.0.0-alpha.31",
8777
+ "@geekmidas/audit": "~10.0.0-alpha.32",
8778
+ "@geekmidas/auth": "~10.0.0-alpha.32",
8779
+ "@geekmidas/cache": "~10.0.0-alpha.32",
8780
+ "@geekmidas/client": "~10.0.0-alpha.32",
8781
+ "@geekmidas/cloud": "~10.0.0-alpha.32",
8782
+ "@geekmidas/constructs": "~10.0.0-alpha.32",
8783
+ "@geekmidas/db": "~10.0.0-alpha.32",
8784
+ "@geekmidas/emailkit": "~10.0.0-alpha.32",
8785
+ "@geekmidas/envkit": "~10.0.0-alpha.32",
8786
+ "@geekmidas/errors": "~10.0.0-alpha.32",
8787
+ "@geekmidas/events": "~10.0.0-alpha.32",
8788
+ "@geekmidas/logger": "~10.0.0-alpha.32",
8789
+ "@geekmidas/manifest": "~10.0.0-alpha.32",
8790
+ "@geekmidas/rate-limit": "~10.0.0-alpha.32",
8791
+ "@geekmidas/schema": "~10.0.0-alpha.32",
8792
+ "@geekmidas/services": "~10.0.0-alpha.32",
8793
+ "@geekmidas/storage": "~10.0.0-alpha.32",
8794
+ "@geekmidas/studio": "~10.0.0-alpha.32",
8795
+ "@geekmidas/telescope": "~10.0.0-alpha.32",
8796
+ "@geekmidas/testkit": "~10.0.0-alpha.32",
8777
8797
  "@geekmidas/cli": `~${loadPackageJson().version}`
8778
8798
  };
8779
8799
  //#endregion
@@ -15368,9 +15388,12 @@ async function testCommand(options = {}) {
15368
15388
  });
15369
15389
  });
15370
15390
  }
15371
- /** Apply every construct's migrations to the test stage just reconciled. */
15391
+ /**
15392
+ * Migrate the test stage just reconciled, then seed it: a test run never
15393
+ * starts without the reference data the app needs.
15394
+ */
15372
15395
  async function migrateTestStage(workspace, env) {
15373
- const { migrateDatabases } = await Promise.resolve().then(() => require("./migrate-BuCx7tIv.cjs"));
15396
+ const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-BiasgAIN.cjs"));
15374
15397
  const { discover } = await Promise.resolve().then(() => require("./discover-CroTIm0K.cjs")).then((n) => n.discover_exports);
15375
15398
  const sources = {};
15376
15399
  const manifest = await discover({
@@ -15378,13 +15401,15 @@ async function migrateTestStage(workspace, env) {
15378
15401
  cwd: workspace.root,
15379
15402
  sources
15380
15403
  });
15381
- const runs = await migrateDatabases({
15404
+ const { migrations, seeds } = await migrateAndSeed({
15382
15405
  root: workspace.root,
15383
15406
  manifest,
15384
15407
  sources,
15385
- env
15408
+ env,
15409
+ stage: require_workspace.TEST_STAGE
15386
15410
  });
15387
- for (const { target, applied } of runs) if (applied.length > 0) console.log(` 🗄️ ${target.folder}: applied ${applied.join(", ")}`);
15411
+ for (const { target, applied } of migrations) if (applied.length > 0) console.log(` 🗄️ ${target.migrations}: applied ${applied.join(", ")}`);
15412
+ for (const { target, seeded } of seeds) if (seeded.length > 0) console.log(` 🌱 ${target.seeds}: ran ${seeded.join(", ")}`);
15388
15413
  }
15389
15414
  /** `gkm test` outside a project that declares stages, with none named. */
15390
15415
  var NoStageToTest = class extends Error {
@@ -15768,7 +15793,7 @@ program.command("build").description("Build handlers and the manifest from decla
15768
15793
  process.exit(1);
15769
15794
  }
15770
15795
  });
15771
- 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) => {
15796
+ 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) => {
15772
15797
  try {
15773
15798
  const globalOptions = program.opts();
15774
15799
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
@@ -15777,7 +15802,9 @@ program.command("dev").description("Reconcile constructs and start the dev serve
15777
15802
  portExplicit: !!options.port,
15778
15803
  enableOpenApi: options.enableOpenapi ?? true,
15779
15804
  entry: options.entry,
15780
- watch: options.watch
15805
+ watch: options.watch,
15806
+ migrate: options.migrate,
15807
+ seed: options.seed
15781
15808
  });
15782
15809
  } catch (error) {
15783
15810
  console.error(formatError(error));
@@ -15794,7 +15821,7 @@ program.command("exec").description("Run a command with secrets injected into Cr
15794
15821
  process.exit(1);
15795
15822
  }
15796
15823
  });
15797
- 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) => {
15824
+ 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) => {
15798
15825
  try {
15799
15826
  const globalOptions = program.opts();
15800
15827
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
@@ -15807,11 +15834,11 @@ program.command("test").description("Run tests with secrets loaded from environm
15807
15834
  process.exit(1);
15808
15835
  }
15809
15836
  });
15810
- 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) => {
15837
+ 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) => {
15811
15838
  try {
15812
15839
  const globalOptions = program.opts();
15813
15840
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
15814
- const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-BuCx7tIv.cjs"));
15841
+ const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-BiasgAIN.cjs"));
15815
15842
  await migrateCommand({
15816
15843
  ...options,
15817
15844
  ...construct ? { construct } : {}
@@ -15821,11 +15848,25 @@ program.command("migrate").description("Apply each database construct's migratio
15821
15848
  process.exit(1);
15822
15849
  }
15823
15850
  });
15851
+ 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) => {
15852
+ try {
15853
+ const globalOptions = program.opts();
15854
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
15855
+ const { seedCommand } = await Promise.resolve().then(() => require("./migrate-BiasgAIN.cjs"));
15856
+ await seedCommand({
15857
+ ...options,
15858
+ ...construct ? { construct } : {}
15859
+ });
15860
+ } catch (error) {
15861
+ console.error(formatError(error));
15862
+ process.exit(1);
15863
+ }
15864
+ });
15824
15865
  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) => {
15825
15866
  try {
15826
15867
  const globalOptions = program.opts();
15827
15868
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
15828
- const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-BuCx7tIv.cjs"));
15869
+ const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-BiasgAIN.cjs"));
15829
15870
  await migrationCommand({
15830
15871
  construct,
15831
15872
  ...name ? { name } : {}