@geekmidas/cli 10.0.0-alpha.35 → 10.0.0-alpha.37

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
@@ -4,7 +4,7 @@ import { c as isPartitionedRoutes, i as ConstructGenerator, t as discover } from
4
4
  import { a as loadConfig, c as parseModuleConfig, i as loadAppConfig, l as appKey, r as getAppNameFromCwd, s as loadWorkspaceConfig, u as derivedApps } from "./config-Dss1WwtH.mjs";
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-CsL76tpc.mjs";
6
6
  import { c as getKeyPath, i as setCustomSecret, n as readStageSecrets, o as toEmbeddableSecrets, r as secretsExist, s as writeStageSecrets, t as maskPassword } from "./storage-BjTLjDDp.mjs";
7
- import { _ as findAvailablePort, b as loadSecretsForApp, c as applyPostgres, d as cacheBackendFor, f as eventsBackendFor, g as createEntryWrapper, h as createCredentialsPreload, i as reconcileWorkspace, l as resourceName, m as storageBackendFor, n as constructGlobs, o as COMPOSE_PATH, p as providerOf, r as derivedContainers, s as LOCAL_CA_PATH, t as backendsOf, u as rootSite, v as isPortAvailable, x as prepareEntryCredentials, y as loadEnvFiles } from "./workspace-CIiGijNi.mjs";
7
+ import { C as loadEnvFiles, S as isPortAvailable, T as prepareEntryCredentials, _ as providerOf, b as createEntryWrapper, c as forgetState, d as pgClient, f as resourceName, g as eventsBackendFor, h as cacheBackendFor, i as reconcileWorkspace, l as applyPostgres, m as primaryPortKey, n as constructGlobs, o as COMPOSE_PATH, p as rootSite, r as derivedContainers, s as LOCAL_CA_PATH, t as backendsOf, u as quoteIdentifier, v as storageBackendFor, w as loadSecretsForApp, x as findAvailablePort, y as createCredentialsPreload } from "./workspace-BtXCL1C8.mjs";
8
8
  import { n as EndpointGenerator, r as runtimeFor } from "./OpenApiTsGenerator-hDINScnz.mjs";
9
9
  import { generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi.mjs";
10
10
  import { a as writeTestHarness, i as TEST_MANIFEST_ENV, n as TEST_READY_FILE, o as cacheBackendsIn, s as driversFor, t as TEST_READY_ENV } from "./ready-D1BCiaYU.mjs";
@@ -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.35";
40
+ var version = "10.0.0-alpha.37";
41
41
  //#endregion
42
42
  //#region src/auth/credentials.ts
43
43
  /**
@@ -1087,9 +1087,9 @@ async function workspaceDevCommand(workspace, options) {
1087
1087
  ...reconciled.env
1088
1088
  };
1089
1089
  if (options.migrate || options.seed) {
1090
- const { prepareDevDatabases } = await import("./migrate-C-D9xIbb.mjs");
1090
+ const { prepareDevDatabases } = await import("./migrate-_2J3lFT5.mjs");
1091
1091
  await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
1092
- } else await import("./migrate-C-D9xIbb.mjs").then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1092
+ } else await import("./migrate-_2J3lFT5.mjs").then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1093
1093
  logger$10.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
1094
1094
  });
1095
1095
  const appUrls = {};
@@ -8798,26 +8798,26 @@ function loadPackageJson() {
8798
8798
  * Run: pnpm --filter @geekmidas/cli sync-versions
8799
8799
  */
8800
8800
  const GEEKMIDAS_VERSIONS = {
8801
- "@geekmidas/audit": "~10.0.0-alpha.35",
8802
- "@geekmidas/auth": "~10.0.0-alpha.35",
8803
- "@geekmidas/cache": "~10.0.0-alpha.35",
8804
- "@geekmidas/client": "~10.0.0-alpha.35",
8805
- "@geekmidas/cloud": "~10.0.0-alpha.35",
8806
- "@geekmidas/constructs": "~10.0.0-alpha.35",
8807
- "@geekmidas/db": "~10.0.0-alpha.35",
8808
- "@geekmidas/emailkit": "~10.0.0-alpha.35",
8809
- "@geekmidas/envkit": "~10.0.0-alpha.35",
8810
- "@geekmidas/errors": "~10.0.0-alpha.35",
8811
- "@geekmidas/events": "~10.0.0-alpha.35",
8812
- "@geekmidas/logger": "~10.0.0-alpha.35",
8813
- "@geekmidas/manifest": "~10.0.0-alpha.35",
8814
- "@geekmidas/rate-limit": "~10.0.0-alpha.35",
8815
- "@geekmidas/schema": "~10.0.0-alpha.35",
8816
- "@geekmidas/services": "~10.0.0-alpha.35",
8817
- "@geekmidas/storage": "~10.0.0-alpha.35",
8818
- "@geekmidas/studio": "~10.0.0-alpha.35",
8819
- "@geekmidas/telescope": "~10.0.0-alpha.35",
8820
- "@geekmidas/testkit": "~10.0.0-alpha.35",
8801
+ "@geekmidas/audit": "~10.0.0-alpha.37",
8802
+ "@geekmidas/auth": "~10.0.0-alpha.37",
8803
+ "@geekmidas/cache": "~10.0.0-alpha.37",
8804
+ "@geekmidas/client": "~10.0.0-alpha.37",
8805
+ "@geekmidas/cloud": "~10.0.0-alpha.37",
8806
+ "@geekmidas/constructs": "~10.0.0-alpha.37",
8807
+ "@geekmidas/db": "~10.0.0-alpha.37",
8808
+ "@geekmidas/emailkit": "~10.0.0-alpha.37",
8809
+ "@geekmidas/envkit": "~10.0.0-alpha.37",
8810
+ "@geekmidas/errors": "~10.0.0-alpha.37",
8811
+ "@geekmidas/events": "~10.0.0-alpha.37",
8812
+ "@geekmidas/logger": "~10.0.0-alpha.37",
8813
+ "@geekmidas/manifest": "~10.0.0-alpha.37",
8814
+ "@geekmidas/rate-limit": "~10.0.0-alpha.37",
8815
+ "@geekmidas/schema": "~10.0.0-alpha.37",
8816
+ "@geekmidas/services": "~10.0.0-alpha.37",
8817
+ "@geekmidas/storage": "~10.0.0-alpha.37",
8818
+ "@geekmidas/studio": "~10.0.0-alpha.37",
8819
+ "@geekmidas/telescope": "~10.0.0-alpha.37",
8820
+ "@geekmidas/testkit": "~10.0.0-alpha.37",
8821
8821
  "@geekmidas/cli": `~${loadPackageJson().version}`
8822
8822
  };
8823
8823
  //#endregion
@@ -9596,9 +9596,7 @@ import { EnvironmentParser } from '@geekmidas/envkit';
9596
9596
  * \`MobileApp\` construct's \`.dependsOn([api, auth])\` is where every value
9597
9597
  * here comes from, so none of it is written down.
9598
9598
  *
9599
- * - \`APP_SCHEME\`: the URL scheme for this stage — \`${scheme}\` deployed,
9600
- * \`${scheme}-dev\` locally, so a development build and the store build on
9601
- * one phone never answer each other's links.
9599
+ * - \`APP_SCHEME\`: the URL scheme — \`${scheme}\`, the same on every stage.
9602
9600
  * - \`EXPO_PUBLIC_API_URL\` / \`EXPO_PUBLIC_AUTH_URL\`: where the servers
9603
9601
  * answer. Locally that is their own port on \`localhost\`, which
9604
9602
  * \`config.ts\` points at the machine Metro was served from.
@@ -10909,7 +10907,7 @@ export const authDb = database.schema<Record<string, never>, 'AuthDb'>('AuthDb')
10909
10907
  files.push(usersMigration());
10910
10908
  files.push({
10911
10909
  path: "constructs/auth.ts",
10912
- content: `import { BetterAuth } from '@geekmidas/constructs/auth';
10910
+ content: `${frontendFramework === "expo" ? "import { expo } from '@better-auth/expo';\n" : ""}import { BetterAuth } from '@geekmidas/constructs/auth';
10913
10911
  import { authDb } from './database.ts';
10914
10912
 
10915
10913
  /**
@@ -10924,7 +10922,11 @@ import { authDb } from './database.ts';
10924
10922
  export const auth = new BetterAuth('Auth', {
10925
10923
  path: 'apps/auth',
10926
10924
  database: authDb,
10927
- basePath: '/api/auth',
10925
+ basePath: '/api/auth',${frontendFramework === "expo" ? `
10926
+ // The mobile app signs in through Better Auth's Expo plugin: it sends its
10927
+ // scheme as the origin, and this is what reads it. The construct refuses to
10928
+ // start without it once a MobileApp depends on this server.
10929
+ options: { plugins: [expo()] },` : ""}
10928
10930
  });
10929
10931
  `
10930
10932
  });
@@ -10963,7 +10965,7 @@ import { auth } from './auth.ts';
10963
10965
  * The mobile app — a construct like a site, which is what makes it an app.
10964
10966
  *
10965
10967
  * \`.dependsOn()\` is the single fact behind what a mobile app otherwise has
10966
- * written down by hand: its API and auth URLs, its URL scheme per stage, the
10968
+ * written down by hand: its API and auth URLs, its URL scheme, the
10967
10969
  * scheme in the auth server's trusted origins, Better Auth's Expo plugin, and
10968
10970
  * sign-in links a phone can open.
10969
10971
  */
@@ -15147,7 +15149,7 @@ var NoStoredSecrets = class extends Error {
15147
15149
  }
15148
15150
  };
15149
15151
  async function reconcile(secrets, workspace, stage) {
15150
- const { derivedContainers } = await import("./workspace-CIiGijNi.mjs").then((n) => n.a);
15152
+ const { derivedContainers } = await import("./workspace-BtXCL1C8.mjs").then((n) => n.a);
15151
15153
  const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
15152
15154
  return result ? {
15153
15155
  secrets: result.secrets,
@@ -15446,6 +15448,56 @@ function printSummary(workspace, stage) {
15446
15448
  logger$1.log("");
15447
15449
  }
15448
15450
  //#endregion
15451
+ //#region src/test/teardown.ts
15452
+ /**
15453
+ * Dropping the test stage's databases when a suite ends.
15454
+ *
15455
+ * Reconcile never drops — the line it draws is at data. The test stage's data
15456
+ * is the exception: every test runs in a transaction rolled back after it, so
15457
+ * nothing in these databases is anyone's, and a database kept between runs is
15458
+ * one whose migrations already ran — an edited migration is never applied to
15459
+ * it again. So each run starts from a database it created, and drops it when
15460
+ * the suite ends: the Vitest global setup's teardown.
15461
+ */
15462
+ /**
15463
+ * Drop the databases the last setup recorded, and forget the test stage's
15464
+ * reconcile, so the next run provisions them from nothing.
15465
+ *
15466
+ * A no-op when nothing is recorded — no run yet, or one already torn down —
15467
+ * so running it twice, or before a setup, is harmless.
15468
+ */
15469
+ async function dropTestDatabases(cwd, root, sql = pgClient) {
15470
+ const readyPath = join(cwd, TEST_READY_FILE);
15471
+ const ready = await readReady(readyPath);
15472
+ const recorded = ready?.databases;
15473
+ if (!ready || !recorded) return [];
15474
+ const client = sql(recorded.port);
15475
+ for (const name of recorded.names) {
15476
+ if (!name.endsWith(`_test`)) throw new NotATestDatabase(name);
15477
+ await client.query(void 0, `DROP DATABASE IF EXISTS ${quoteIdentifier(name)} WITH (FORCE)`);
15478
+ }
15479
+ await forgetState(root, TEST_STAGE);
15480
+ const { databases: _dropped, ...rest } = ready;
15481
+ await writeFile(readyPath, JSON.stringify(rest, null, 2));
15482
+ return recorded.names;
15483
+ }
15484
+ async function readReady(path) {
15485
+ try {
15486
+ return JSON.parse(await readFile(path, "utf-8"));
15487
+ } catch {
15488
+ return;
15489
+ }
15490
+ }
15491
+ /** A name recorded for the test stage without the test stage's suffix. */
15492
+ var NotATestDatabase = class extends Error {
15493
+ database;
15494
+ constructor(database) {
15495
+ super(`Refusing to drop '${database}': only the test stage's databases (named '…_${TEST_STAGE}') are dropped after a suite. Delete ${TEST_READY_FILE} if it was edited by hand.`);
15496
+ this.database = database;
15497
+ this.name = "NotATestDatabase";
15498
+ }
15499
+ };
15500
+ //#endregion
15449
15501
  //#region src/test/index.ts
15450
15502
  /**
15451
15503
  * Run tests with secrets, dependency URLs, and .env files loaded.
@@ -15454,6 +15506,10 @@ function printSummary(workspace, stage) {
15454
15506
  async function testCommand(options = {}) {
15455
15507
  const cwd = process.cwd();
15456
15508
  const stage = options.stage ?? await loadWorkspaceConfig(cwd).then((loaded) => loaded.workspace.stages.local).catch(() => void 0);
15509
+ if (options.teardown) {
15510
+ await teardown(cwd);
15511
+ return;
15512
+ }
15457
15513
  if (!stage) throw new NoStageToTest();
15458
15514
  console.log(`\n🧪 Running tests with ${stage} environment...\n`);
15459
15515
  if (options.autoSetup || Boolean(process.env.GKM_AUTO_SETUP)) {
@@ -15462,6 +15518,7 @@ async function testCommand(options = {}) {
15462
15518
  }
15463
15519
  const defaultEnv = loadEnvFiles(".env");
15464
15520
  if (defaultEnv.loaded.length > 0) console.log(` 📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
15521
+ if (!options.prepare) await teardown(cwd);
15465
15522
  const result = await prepareEntryCredentials({
15466
15523
  stage,
15467
15524
  startDocker: !options.prepare,
@@ -15502,7 +15559,8 @@ async function testCommand(options = {}) {
15502
15559
  const readyPath = join(cwd, TEST_READY_FILE);
15503
15560
  await writeFile(readyPath, JSON.stringify({
15504
15561
  env: result.secretsJsonPath,
15505
- ...manifestPath ? { manifest: manifestPath } : {}
15562
+ ...manifestPath ? { manifest: manifestPath } : {},
15563
+ ...testDatabases(result.reconciled)
15506
15564
  }, null, 2));
15507
15565
  if (options.setup) {
15508
15566
  console.log(` 🧪 Test stage ready: ${TEST_READY_FILE}`);
@@ -15541,12 +15599,29 @@ async function testCommand(options = {}) {
15541
15599
  });
15542
15600
  });
15543
15601
  }
15602
+ /** Drop what the last setup created — see `dropTestDatabases`. */
15603
+ async function teardown(cwd) {
15604
+ const dropped = await dropTestDatabases(cwd, await loadWorkspaceConfig(cwd).then((loaded) => loaded.workspace.root).catch(() => cwd));
15605
+ if (dropped.length > 0) console.log(` 🧹 Dropped ${dropped.join(", ")}`);
15606
+ }
15607
+ /**
15608
+ * The test stage's Postgres databases, to drop when the suite ends — those
15609
+ * the plan provisions, on the port the shared container answers.
15610
+ */
15611
+ function testDatabases(reconciled) {
15612
+ const port = reconciled?.ports[primaryPortKey("postgres")];
15613
+ const names = (reconciled?.plan.resources ?? []).filter((resource) => resource.kind === "database" && resource.provisions && resource.container === "postgres").map((resource) => resource.name);
15614
+ return port !== void 0 && names.length > 0 ? { databases: {
15615
+ port,
15616
+ names
15617
+ } } : {};
15618
+ }
15544
15619
  /**
15545
15620
  * Migrate the test stage just reconciled, then seed it: a test run never
15546
15621
  * starts without the reference data the app needs.
15547
15622
  */
15548
15623
  async function migrateTestStage(workspace, env) {
15549
- const { migrateAndSeed } = await import("./migrate-C-D9xIbb.mjs");
15624
+ const { migrateAndSeed } = await import("./migrate-_2J3lFT5.mjs");
15550
15625
  const { discover } = await import("./discover-CMxesskG.mjs").then((n) => n.n);
15551
15626
  const sources = {};
15552
15627
  const manifest = await discover({
@@ -15974,7 +16049,7 @@ program.command("exec").description("Run a command with secrets injected into Cr
15974
16049
  process.exit(1);
15975
16050
  }
15976
16051
  });
15977
- 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) => {
16052
+ 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").option("--teardown", "Drop the test stage's databases the last setup created, then stop — what @geekmidas/cli/vitest runs when the suite ends").argument("[pattern]", "Pattern to filter tests").action(async (pattern, options) => {
15978
16053
  try {
15979
16054
  const globalOptions = program.opts();
15980
16055
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
@@ -15991,7 +16066,7 @@ program.command("migrate").description("Apply each database construct's migratio
15991
16066
  try {
15992
16067
  const globalOptions = program.opts();
15993
16068
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
15994
- const { migrateCommand } = await import("./migrate-C-D9xIbb.mjs");
16069
+ const { migrateCommand } = await import("./migrate-_2J3lFT5.mjs");
15995
16070
  await migrateCommand({
15996
16071
  ...options,
15997
16072
  ...construct ? { construct } : {}
@@ -16005,7 +16080,7 @@ program.command("seed").description("Migrate, then run each database construct's
16005
16080
  try {
16006
16081
  const globalOptions = program.opts();
16007
16082
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16008
- const { seedCommand } = await import("./migrate-C-D9xIbb.mjs");
16083
+ const { seedCommand } = await import("./migrate-_2J3lFT5.mjs");
16009
16084
  await seedCommand({
16010
16085
  ...options,
16011
16086
  ...construct ? { construct } : {}
@@ -16019,7 +16094,7 @@ program.command("migration").description("Write a construct's next migration: an
16019
16094
  try {
16020
16095
  const globalOptions = program.opts();
16021
16096
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16022
- const { migrationCommand } = await import("./migrate-C-D9xIbb.mjs");
16097
+ const { migrationCommand } = await import("./migrate-_2J3lFT5.mjs");
16023
16098
  await migrationCommand({
16024
16099
  construct,
16025
16100
  ...name ? { name } : {}
@@ -16187,7 +16262,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
16187
16262
  console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
16188
16263
  process.exit(1);
16189
16264
  }
16190
- const { derivedContainers } = await import("./workspace-CIiGijNi.mjs").then((n) => n.a);
16265
+ const { derivedContainers } = await import("./workspace-BtXCL1C8.mjs").then((n) => n.a);
16191
16266
  const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
16192
16267
  if (!result) {
16193
16268
  console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);