@geekmidas/cli 10.0.0-alpha.21 → 10.0.0-alpha.23

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
@@ -2,24 +2,24 @@
2
2
  import { n as __require, t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
3
3
  import { i as DEFAULT_CACHE, r as ConstructGenerator, s as isPartitionedRoutes, t as discover } from "./discover-CK-n3w6b.mjs";
4
4
  import { i as loadConfig, l as derivedApps, n as getAppNameFromCwd, o as loadWorkspaceConfig, r as loadAppConfig, s as parseModuleConfig } from "./config-DdKkoUE1.mjs";
5
- import { A as stripPublicPrefix, C as InvalidStages, D as stageProblems, E as deployedProblems, T as assertDeployedStage, a as getAppBuildOrder, k as getPublicEnvPrefix, o as getAppGkmConfig, s as getDependencyEnvVars, t as allConstructGlobs, v as getDeployTargetError, y as isDeployTargetSupported } from "./workspace-Bu3xNCej.mjs";
6
- import { C as storageBackendFor, S as providerOf, _ as LOCAL_CA_PATH, a as loadEnvFiles, b as cacheBackendFor, c as resolveServicePorts, d as constructGlobs, f as derivedContainers, g as COMPOSE_PATH, i as isPortAvailable, l as rewriteUrlsWithPorts, m as usesConstructs, n as createEntryWrapper, o as loadSecretsForApp, p as reconcileWorkspace, r as findAvailablePort, s as prepareEntryCredentials, t as createCredentialsPreload, u as startWorkspaceServices, v as applyPostgres, x as eventsBackendFor, y as resourceName } from "./credentials-CD8ftUS6.mjs";
5
+ import { A as stripPublicPrefix, C as InvalidStages, D as stageProblems, E as deployedProblems, T as assertDeployedStage, a as getAppBuildOrder, k as getPublicEnvPrefix, o as getAppGkmConfig, s as getDependencyEnvVars, t as allConstructGlobs, v as getDeployTargetError, w as TEST_STAGE, y as isDeployTargetSupported } from "./workspace-Bu3xNCej.mjs";
6
+ import { C as providerOf, S as eventsBackendFor, _ as COMPOSE_PATH, a as loadEnvFiles, b as resourceName, c as resolveServicePorts, d as backendsOf, f as constructGlobs, h as usesConstructs, i as isPortAvailable, l as rewriteUrlsWithPorts, m as reconcileWorkspace, n as createEntryWrapper, o as loadSecretsForApp, p as derivedContainers, r as findAvailablePort, s as prepareEntryCredentials, t as createCredentialsPreload, u as startWorkspaceServices, v as LOCAL_CA_PATH, w as storageBackendFor, x as cacheBackendFor, y as applyPostgres } from "./credentials-D9TKJJZc.mjs";
7
7
  import { a as generateServiceCredentials, i as generateSecurePassword, n as generateConnectionUrls, o as rotateServicePassword, t as createStageSecrets } from "./generator-LfZU7NlU.mjs";
8
8
  import { c as getKeyPath, i as setCustomSecret, n as readStageSecrets, o as toEmbeddableSecrets, r as secretsExist, s as writeStageSecrets, t as maskPassword } from "./storage-TP0x118M.mjs";
9
- import { a as openapiCommand, c as runtimeFor, n as generateOpenApi, o as resolveOpenApiConfig, r as generateOpenApiFrom, s as EndpointGenerator, t as OPENAPI_OUTPUT_PATH$1 } from "./openapi-DfjbhrsM.mjs";
9
+ import { a as openapiCommand, c as EndpointGenerator, l as runtimeFor, n as generateOpenApi, o as resolveOpenApiConfig, r as generateOpenApiFrom, s as OpenApiTsGenerator, t as OPENAPI_OUTPUT_PATH$1 } from "./openapi-B5PSuLym.mjs";
10
10
  import { i as withRoutes, r as withCompute } from "./emit-Bzq7JsMP.mjs";
11
11
  import { i as writeDockerEnvFromSecrets, r as generateFullstackCustomSecrets } from "./reconcile-gb1lz1Dk.mjs";
12
- import { a as isRemoteStore, n as pullStageSecrets, r as pushStageSecrets, t as NoStoredSecrets } from "./transfer-Gsxfy6m-.mjs";
12
+ import { a as isRemoteStore, n as pullStageSecrets, r as pushStageSecrets, t as NoStoredSecrets } from "./transfer-Bk5YlRyB.mjs";
13
13
  import { createRequire } from "node:module";
14
14
  import { Command } from "commander";
15
15
  import { stdin, stdout } from "node:process";
16
16
  import * as readline from "node:readline/promises";
17
17
  import { copyFileSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
18
- import { mkdir, readFile, writeFile } from "node:fs/promises";
18
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
19
19
  import { homedir } from "node:os";
20
20
  import { basename, dirname, isAbsolute, join, parse, relative, resolve } from "node:path";
21
21
  import { execFile, execSync, spawn } from "node:child_process";
22
- import { cacheTable, canonicalId, cloudName, cookieDomain, dependentsOf, provideKey, provisionOrder, scopedName, serviceKey } from "@geekmidas/manifest";
22
+ import { cacheTable, canonicalId, cloudName, cookieDomain, dependentsOf, kebabCase, provideKey, provisionOrder, scopedName, serviceKey } from "@geekmidas/manifest";
23
23
  import fg from "fast-glob";
24
24
  import chokidar from "chokidar";
25
25
  import { parse as parse$1, parseDocument } from "yaml";
@@ -36,8 +36,10 @@ import { Topic } from "@geekmidas/constructs/topic";
36
36
  import { fileURLToPath, pathToFileURL } from "node:url";
37
37
  import { lookup } from "node:dns/promises";
38
38
  import prompts from "prompts";
39
+ import { EnvironmentParser } from "@geekmidas/envkit";
40
+ import { serviceContext } from "@geekmidas/services";
39
41
  //#region package.json
40
- var version = "10.0.0-alpha.21";
42
+ var version = "10.0.0-alpha.23";
41
43
  //#endregion
42
44
  //#region src/auth/credentials.ts
43
45
  /**
@@ -8407,26 +8409,26 @@ function loadPackageJson() {
8407
8409
  * Run: pnpm --filter @geekmidas/cli sync-versions
8408
8410
  */
8409
8411
  const GEEKMIDAS_VERSIONS = {
8410
- "@geekmidas/audit": "~10.0.0-alpha.21",
8411
- "@geekmidas/auth": "~10.0.0-alpha.21",
8412
- "@geekmidas/cache": "~10.0.0-alpha.21",
8413
- "@geekmidas/client": "~10.0.0-alpha.21",
8414
- "@geekmidas/cloud": "~10.0.0-alpha.21",
8415
- "@geekmidas/constructs": "~10.0.0-alpha.21",
8416
- "@geekmidas/db": "~10.0.0-alpha.21",
8417
- "@geekmidas/emailkit": "~10.0.0-alpha.21",
8418
- "@geekmidas/envkit": "~10.0.0-alpha.21",
8419
- "@geekmidas/errors": "~10.0.0-alpha.21",
8420
- "@geekmidas/events": "~10.0.0-alpha.21",
8421
- "@geekmidas/logger": "~10.0.0-alpha.21",
8422
- "@geekmidas/manifest": "~10.0.0-alpha.21",
8423
- "@geekmidas/rate-limit": "~10.0.0-alpha.21",
8424
- "@geekmidas/schema": "~10.0.0-alpha.21",
8425
- "@geekmidas/services": "~10.0.0-alpha.21",
8426
- "@geekmidas/storage": "~10.0.0-alpha.21",
8427
- "@geekmidas/studio": "~10.0.0-alpha.21",
8428
- "@geekmidas/telescope": "~10.0.0-alpha.21",
8429
- "@geekmidas/testkit": "~10.0.0-alpha.21",
8412
+ "@geekmidas/audit": "~10.0.0-alpha.23",
8413
+ "@geekmidas/auth": "~10.0.0-alpha.23",
8414
+ "@geekmidas/cache": "~10.0.0-alpha.23",
8415
+ "@geekmidas/client": "~10.0.0-alpha.23",
8416
+ "@geekmidas/cloud": "~10.0.0-alpha.23",
8417
+ "@geekmidas/constructs": "~10.0.0-alpha.23",
8418
+ "@geekmidas/db": "~10.0.0-alpha.23",
8419
+ "@geekmidas/emailkit": "~10.0.0-alpha.23",
8420
+ "@geekmidas/envkit": "~10.0.0-alpha.23",
8421
+ "@geekmidas/errors": "~10.0.0-alpha.23",
8422
+ "@geekmidas/events": "~10.0.0-alpha.23",
8423
+ "@geekmidas/logger": "~10.0.0-alpha.23",
8424
+ "@geekmidas/manifest": "~10.0.0-alpha.23",
8425
+ "@geekmidas/rate-limit": "~10.0.0-alpha.23",
8426
+ "@geekmidas/schema": "~10.0.0-alpha.23",
8427
+ "@geekmidas/services": "~10.0.0-alpha.23",
8428
+ "@geekmidas/storage": "~10.0.0-alpha.23",
8429
+ "@geekmidas/studio": "~10.0.0-alpha.23",
8430
+ "@geekmidas/telescope": "~10.0.0-alpha.23",
8431
+ "@geekmidas/testkit": "~10.0.0-alpha.23",
8430
8432
  "@geekmidas/cli": `~${loadPackageJson().version}`
8431
8433
  };
8432
8434
  //#endregion
@@ -15127,6 +15129,231 @@ function printSummary(workspace, stage) {
15127
15129
  logger$1.log("");
15128
15130
  }
15129
15131
  //#endregion
15132
+ //#region src/test/harness.ts
15133
+ /**
15134
+ * What `gkm test` writes for a feature test to be built from.
15135
+ *
15136
+ * `gkm test` already discovers an app's constructs and resolves the test
15137
+ * stage's environment; this keeps both, under `.gkm/test/`:
15138
+ *
15139
+ * - `manifest.json` — which construct and endpoint is exported where, and the
15140
+ * environment the test stage resolved, keyed as the constructs derive keys.
15141
+ * - `clients/<surface>.ts` — each surface's typed client, from the same
15142
+ * generator `gkm build` uses for the app's own.
15143
+ * - `index.ts` — the configured `it`, and a `Browser` holding one client per
15144
+ * surface and one per auth server, each on the browser's `fetch`.
15145
+ *
15146
+ * A test imports `it` from there and declares nothing — not a construct, not an
15147
+ * environment key, not a client.
15148
+ */
15149
+ /**
15150
+ * The variable the manifest's path travels in — what
15151
+ * `@geekmidas/constructs/testing` reads. Its own constant rather than an import:
15152
+ * that entry carries the whole harness (MSW, Hono, Vitest), and importing it
15153
+ * here put all of it on the start-up path of every `gkm` command.
15154
+ */
15155
+ const TEST_MANIFEST_ENV = "GKM_TEST_MANIFEST";
15156
+ /** Where the harness lives, relative to the workspace root. */
15157
+ const TEST_HARNESS_DIR = ".gkm/test";
15158
+ /**
15159
+ * Better-auth's client plugin for each server plugin id.
15160
+ *
15161
+ * Written down rather than derived: the ids and the client names do not follow
15162
+ * one rule (`admin` pairs with `adminClient`, `email-otp` with
15163
+ * `emailOTPClient`), and a guess that missed would hand a test a client
15164
+ * silently lacking every method of that plugin. A server plugin not listed has
15165
+ * no client counterpart — `bearer`, `open-api` — or needs one from another
15166
+ * package, and is left out.
15167
+ */
15168
+ const CLIENT_PLUGINS = {
15169
+ admin: "adminClient",
15170
+ anonymous: "anonymousClient",
15171
+ "device-authorization": "deviceAuthorizationClient",
15172
+ "email-otp": "emailOTPClient",
15173
+ jwt: "jwtClient",
15174
+ "last-login-method": "lastLoginMethodClient",
15175
+ "magic-link": "magicLinkClient",
15176
+ "multi-session": "multiSessionClient",
15177
+ "oauth-popup": "oauthPopupClient",
15178
+ "one-tap": "oneTapClient",
15179
+ "one-time-token": "oneTimeTokenClient",
15180
+ organization: "organizationClient",
15181
+ "phone-number": "phoneNumberClient",
15182
+ siwe: "siweClient",
15183
+ "two-factor": "twoFactorClient",
15184
+ username: "usernameClient"
15185
+ };
15186
+ /** Write the manifest, the clients and the harness; return the manifest path. */
15187
+ async function writeTestHarness(options) {
15188
+ const sources = {};
15189
+ const declared = await discover({
15190
+ patterns: options.patterns,
15191
+ cwd: options.root,
15192
+ sources
15193
+ });
15194
+ const endpoints = (await new EndpointGenerator().load(options.patterns, options.root)).map(({ key, construct, path }) => ({
15195
+ surface: construct.owner ?? construct.surface?.id,
15196
+ construct,
15197
+ source: {
15198
+ file: path.absolute,
15199
+ export: key
15200
+ }
15201
+ })).filter((endpoint) => endpoint.surface !== void 0);
15202
+ const manifest = {
15203
+ stage: options.stage,
15204
+ constructs: Object.fromEntries(Object.entries(declared).flatMap(([id, declaration]) => {
15205
+ const source = sources[id];
15206
+ return source ? [[id, {
15207
+ kind: declaration.kind,
15208
+ source: {
15209
+ file: source.file,
15210
+ export: source.exportName
15211
+ }
15212
+ }]] : [];
15213
+ })),
15214
+ endpoints: endpoints.map(({ surface, source }) => ({
15215
+ surface,
15216
+ source
15217
+ })),
15218
+ env: options.env
15219
+ };
15220
+ const surfaces = [...new Set(endpoints.map(({ surface }) => surface))];
15221
+ const generator = new OpenApiTsGenerator();
15222
+ const clients = await Promise.all(surfaces.map(async (surface) => ({
15223
+ file: `${kebabCase(surface)}.ts`,
15224
+ content: await generator.generate(endpoints.filter((endpoint) => endpoint.surface === surface).map(({ construct }) => construct), {
15225
+ title: surface,
15226
+ version: "1.0.0"
15227
+ })
15228
+ })));
15229
+ const auths = await authClients(declared, sources, options.env);
15230
+ const drivers = driversFor({
15231
+ appRoot: options.root,
15232
+ cache: cacheBackendsIn(declared, options.cacheBackend)
15233
+ });
15234
+ const database = await databaseSource(declared, sources, endpoints.map(({ construct }) => construct));
15235
+ const files = [.../* @__PURE__ */ new Set([...Object.values(manifest.constructs).map(({ source }) => source.file), ...manifest.endpoints.map(({ source }) => source.file)])];
15236
+ const harnessFor = (dir) => harnessModule({
15237
+ dir,
15238
+ files,
15239
+ database,
15240
+ surfaces: surfaces.map((id, index) => ({
15241
+ id,
15242
+ secured: clients[index].content.includes("authStrategies:")
15243
+ })),
15244
+ auths,
15245
+ drivers
15246
+ });
15247
+ const json = `${JSON.stringify(manifest, null, 2)}\n`;
15248
+ return (await Promise.all([...new Set(options.targets)].map(async (target) => {
15249
+ const dir = join(target, TEST_HARNESS_DIR);
15250
+ await rm(dir, {
15251
+ recursive: true,
15252
+ force: true
15253
+ });
15254
+ await mkdir(join(dir, "clients"), { recursive: true });
15255
+ await writeFile(join(dir, "manifest.json"), json);
15256
+ await writeFile(join(dir, "index.ts"), harnessFor(dir));
15257
+ for (const client of clients) await writeFile(join(dir, "clients", client.file), client.content);
15258
+ return join(dir, "manifest.json");
15259
+ })))[0];
15260
+ }
15261
+ /** Each auth server, with the client plugins its server plugins pair with. */
15262
+ async function authClients(declared, sources, env) {
15263
+ const clients = [];
15264
+ const envParser = new EnvironmentParser({ ...env });
15265
+ for (const [id, declaration] of Object.entries(declared)) {
15266
+ const source = sources[id];
15267
+ if (declaration.kind !== "rest-api" || !source) continue;
15268
+ const exported = (await import(source.file))[source.exportName];
15269
+ if (!isBetterAuth(exported) || exported.id !== id) continue;
15270
+ const ids = await exported.pluginIds({
15271
+ envParser,
15272
+ context: serviceContext
15273
+ });
15274
+ clients.push({
15275
+ id,
15276
+ basePath: exported.basePath,
15277
+ plugins: ids.flatMap((plugin) => CLIENT_PLUGINS[plugin] ?? [])
15278
+ });
15279
+ }
15280
+ return clients;
15281
+ }
15282
+ function isBetterAuth(value) {
15283
+ return typeof value === "object" && value !== null && "pluginIds" in value && "basePath" in value;
15284
+ }
15285
+ /** `Api` → `api`, `AdminApi` → `adminApi`: a construct id as a property name. */
15286
+ function propertyOf(id) {
15287
+ return id.charAt(0).toLowerCase() + id.slice(1);
15288
+ }
15289
+ /**
15290
+ * The database a test's `db` is — the one the endpoints were given with
15291
+ * `.database()`, found by the service they hold, which is the construct's own.
15292
+ * The same rule `featureTest` applies at runtime, applied here so `db` can be
15293
+ * typed by the construct's schema.
15294
+ */
15295
+ async function databaseSource(declared, sources, endpoints) {
15296
+ const services = new Set(endpoints.map((endpoint) => endpoint.databaseService).filter(Boolean));
15297
+ if (services.size === 0) return void 0;
15298
+ for (const [id, declaration] of Object.entries(declared)) {
15299
+ const source = sources[id];
15300
+ if (!source || !declaration.kind.startsWith("database")) continue;
15301
+ const exported = (await import(source.file))[source.exportName];
15302
+ if (services.has(exported?.service)) return source;
15303
+ }
15304
+ }
15305
+ /** An import specifier for `file`, from the harness written into `dir`. */
15306
+ function specifierFrom(dir, file) {
15307
+ const path = relative(dir, file).replace(/\.tsx?$/, ".js");
15308
+ return path.startsWith(".") ? path : `./${path}`;
15309
+ }
15310
+ function harnessModule(options) {
15311
+ const { surfaces, auths, drivers, database, dir, files } = options;
15312
+ const plugins = [...new Set(auths.flatMap(({ plugins }) => plugins))].sort();
15313
+ const imports = [
15314
+ `import {${database ? " type DatabaseOf," : ""} featureTest, loadTestManifest } from '@geekmidas/constructs/testing';`,
15315
+ ...database ? [`import type { ${database.exportName} as __database } from '${specifierFrom(dir, database.file)}';`] : [],
15316
+ `import { Browser as TestBrowser } from '@geekmidas/testkit/browser';`,
15317
+ ...surfaces.map(({ id }) => `import { createApi as create${id} } from './clients/${kebabCase(id)}.js';`),
15318
+ ...auths.length ? [`import { createAuthClient } from 'better-auth/client';`] : [],
15319
+ ...plugins.length ? [`import { ${plugins.join(", ")} } from 'better-auth/client/plugins';`] : [],
15320
+ ...drivers.imports ? [drivers.imports] : [],
15321
+ ...files.map((file, index) => `import * as __module${index} from '${specifierFrom(dir, file)}';`)
15322
+ ];
15323
+ const url = (id) => `manifest.env[${JSON.stringify(provideKey(id, "url"))}]!`;
15324
+ const members = [...surfaces.map(({ id, secured }) => ` /** \`${id}\`, at ${provideKey(id, "url")}. */
15325
+ readonly ${propertyOf(id)} = create${id}({
15326
+ baseURL: ${url(id)},
15327
+ fetch: this.fetch,${secured ? `
15328
+ // Served in-process: no authorizer runs, so nothing is signed. A scheme
15329
+ // with no strategy sends its request without credentials.
15330
+ authStrategies: {} as never,` : ""}
15331
+ });`), ...auths.map((auth) => ` /** \`${auth.id}\`, at ${provideKey(auth.id, "url")}. */
15332
+ readonly ${propertyOf(auth.id)} = createAuthClient({
15333
+ baseURL: \`\${${url(auth.id)}}${auth.basePath}\`,
15334
+ plugins: [${auth.plugins.map((plugin) => `${plugin}()`).join(", ")}],
15335
+ fetchOptions: { customFetchImpl: this.fetch },
15336
+ });`)];
15337
+ return `// Generated by \`gkm test\` from the app's constructs — do not edit.
15338
+ ${imports.join("\n")}
15339
+
15340
+ ${drivers.setup ? `// The drivers the app's server entry registers, registered the same way.\n${drivers.setup}\n\n` : ""}const manifest = loadTestManifest(new URL('./manifest.json', import.meta.url));
15341
+
15342
+ /** A browser with a client for every surface the app declares. */
15343
+ export class Browser extends TestBrowser {
15344
+ ${members.join("\n\n")}
15345
+ }
15346
+
15347
+ /** The app's modules, by the path the manifest records for each. */
15348
+ const modules = {
15349
+ ${files.map((file, index) => `\t${JSON.stringify(file)}: __module${index},`).join("\n")}
15350
+ };
15351
+
15352
+ /** \`it\`, for a test that drives the app the way it runs deployed. */
15353
+ export const it = featureTest${database ? "<Browser, DatabaseOf<typeof __database>>" : ""}({ manifest, modules, browser: Browser });
15354
+ `;
15355
+ }
15356
+ //#endregion
15130
15357
  //#region src/test/index.ts
15131
15358
  /**
15132
15359
  * Run tests with secrets, dependency URLs, and .env files loaded.
@@ -15145,7 +15372,7 @@ async function testCommand(options = {}) {
15145
15372
  if (defaultEnv.loaded.length > 0) console.log(` 📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
15146
15373
  const result = await prepareEntryCredentials({
15147
15374
  stage,
15148
- startDocker: true,
15375
+ startDocker: !options.prepare,
15149
15376
  secretsFileName: "test-secrets.json",
15150
15377
  resolveDockerPorts: "full",
15151
15378
  reconcileStage: "test"
@@ -15167,6 +15394,19 @@ async function testCommand(options = {}) {
15167
15394
  const gkmDir = join(cwd, ".gkm");
15168
15395
  const preloadPath = join(gkmDir, "test-credentials-preload.ts");
15169
15396
  await createCredentialsPreload(preloadPath, result.secretsJsonPath);
15397
+ const workspace = await loadWorkspaceConfig(cwd).then((loaded) => loaded.workspace).catch(() => void 0);
15398
+ const manifestPath = workspace && usesConstructs(workspace) ? await writeTestHarness({
15399
+ root: workspace.root,
15400
+ targets: [workspace.root, ...Object.values(workspace.apps).map((app) => isAbsolute(app.path) ? app.path : join(workspace.root, app.path))],
15401
+ patterns: constructGlobs(workspace),
15402
+ cacheBackend: backendsOf(workspace).cache,
15403
+ stage: TEST_STAGE,
15404
+ env: finalCredentials
15405
+ }) : void 0;
15406
+ if (options.prepare) {
15407
+ console.log(manifestPath ? ` 🧪 Test harness written beside ${manifestPath}` : " 🧪 Nothing to prepare: this project declares no constructs");
15408
+ return;
15409
+ }
15170
15410
  const nodeOptions = [
15171
15411
  process.env.NODE_OPTIONS ?? "",
15172
15412
  "--import=tsx",
@@ -15184,6 +15424,7 @@ async function testCommand(options = {}) {
15184
15424
  env: {
15185
15425
  ...process.env,
15186
15426
  ...finalCredentials,
15427
+ ...manifestPath ? { [TEST_MANIFEST_ENV]: manifestPath } : {},
15187
15428
  NODE_ENV: "test",
15188
15429
  NODE_OPTIONS: nodeOptions
15189
15430
  }
@@ -15629,7 +15870,7 @@ program.command("exec").description("Run a command with secrets injected into Cr
15629
15870
  process.exit(1);
15630
15871
  }
15631
15872
  });
15632
- 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)").argument("[pattern]", "Pattern to filter tests").action(async (pattern, options) => {
15873
+ 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").argument("[pattern]", "Pattern to filter tests").action(async (pattern, options) => {
15633
15874
  try {
15634
15875
  const globalOptions = program.opts();
15635
15876
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
@@ -15761,7 +16002,7 @@ program.command("secrets:push").description("Send a deployed stage's secrets to
15761
16002
  const globalOptions = program.opts();
15762
16003
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
15763
16004
  const { loadWorkspaceConfig } = await import("./config.mjs");
15764
- const { pushStageSecrets } = await import("./transfer-Gsxfy6m-.mjs").then((n) => n.i);
16005
+ const { pushStageSecrets } = await import("./transfer-Bk5YlRyB.mjs").then((n) => n.i);
15765
16006
  const { workspace } = await loadWorkspaceConfig();
15766
16007
  const { addedKeys } = await pushStageSecrets(workspace, options.stage, { profile: options.profile });
15767
16008
  for (const key of addedKeys) console.log(` + ${key} (reconciled)`);
@@ -15776,7 +16017,7 @@ program.command("secrets:pull").description("Bring a deployed stage's secrets fr
15776
16017
  const globalOptions = program.opts();
15777
16018
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
15778
16019
  const { loadWorkspaceConfig } = await import("./config.mjs");
15779
- const { pullStageSecrets } = await import("./transfer-Gsxfy6m-.mjs").then((n) => n.i);
16020
+ const { pullStageSecrets } = await import("./transfer-Bk5YlRyB.mjs").then((n) => n.i);
15780
16021
  const { workspace } = await loadWorkspaceConfig();
15781
16022
  const { addedKeys } = await pullStageSecrets(workspace, options.stage, { profile: options.profile });
15782
16023
  for (const key of addedKeys) console.log(` + ${key} (reconciled)`);
@@ -15800,7 +16041,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
15800
16041
  console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
15801
16042
  process.exit(1);
15802
16043
  }
15803
- const { derivedContainers } = await import("./credentials-CD8ftUS6.mjs").then((n) => n.h);
16044
+ const { derivedContainers } = await import("./credentials-D9TKJJZc.mjs").then((n) => n.g);
15804
16045
  const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
15805
16046
  if (!result) {
15806
16047
  console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
@@ -15859,7 +16100,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
15859
16100
  try {
15860
16101
  const globalOptions = program.opts();
15861
16102
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
15862
- const { deployGithubCommand } = await import("./github-De-yMwVt.mjs");
16103
+ const { deployGithubCommand } = await import("./github-B4OQQZBV.mjs");
15863
16104
  await deployGithubCommand(options);
15864
16105
  } catch (error) {
15865
16106
  console.error(formatError(error));