@geekmidas/cli 10.0.0-alpha.29 → 10.0.0-alpha.30
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/{HostingerProvider-Btxpto71.mjs → HostingerProvider-GF6NAZAK.mjs} +2 -2
- package/dist/{HostingerProvider-Btxpto71.mjs.map → HostingerProvider-GF6NAZAK.mjs.map} +1 -1
- package/dist/{openapi-K4H5tHzA.cjs → OpenApiTsGenerator-D1nhN98m.cjs} +2 -224
- package/dist/OpenApiTsGenerator-D1nhN98m.cjs.map +1 -0
- package/dist/{openapi-D-Lk5Hr5.mjs → OpenApiTsGenerator-hDINScnz.mjs} +4 -190
- package/dist/OpenApiTsGenerator-hDINScnz.mjs.map +1 -0
- package/dist/{bundler-7iaO93G8.mjs → bundler-D9IPNvDt.mjs} +2 -2
- package/dist/{bundler-7iaO93G8.mjs.map → bundler-D9IPNvDt.mjs.map} +1 -1
- package/dist/{config-Dqxa-_Ge.mjs → config-BH3Ki69F.mjs} +2 -2
- package/dist/{config-Dqxa-_Ge.mjs.map → config-BH3Ki69F.mjs.map} +1 -1
- package/dist/{config-BTFLHfRb.cjs → config-URVD4bTj.cjs} +2 -2
- package/dist/{config-BTFLHfRb.cjs.map → config-URVD4bTj.cjs.map} +1 -1
- package/dist/config.cjs +1 -1
- package/dist/config.mjs +1 -1
- package/dist/{discover-CXGlQHm1.mjs → discover-CMxesskG.mjs} +8 -2
- package/dist/{discover-CXGlQHm1.mjs.map → discover-CMxesskG.mjs.map} +1 -1
- package/dist/{discover-BTh87DOO.cjs → discover-CroTIm0K.cjs} +12 -1
- package/dist/{discover-BTh87DOO.cjs.map → discover-CroTIm0K.cjs.map} +1 -1
- package/dist/{github-ZL3RcUVD.cjs → github-B2fis34P.cjs} +2 -2
- package/dist/{github-ZL3RcUVD.cjs.map → github-B2fis34P.cjs.map} +1 -1
- package/dist/{github-DPpNfzu4.mjs → github-BrDmlm1x.mjs} +2 -2
- package/dist/{github-DPpNfzu4.mjs.map → github-BrDmlm1x.mjs.map} +1 -1
- package/dist/index.cjs +187 -455
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +181 -451
- package/dist/index.mjs.map +1 -1
- package/dist/migrate-BuCx7tIv.cjs +498 -0
- package/dist/migrate-BuCx7tIv.cjs.map +1 -0
- package/dist/migrate-CBICVMAi.mjs +493 -0
- package/dist/migrate-CBICVMAi.mjs.map +1 -0
- package/dist/openapi.cjs +197 -7
- package/dist/openapi.cjs.map +1 -0
- package/dist/openapi.mjs +191 -1
- package/dist/openapi.mjs.map +1 -0
- package/dist/ready-CvP3JVEw.mjs +355 -0
- package/dist/ready-CvP3JVEw.mjs.map +1 -0
- package/dist/ready-DXtNfk7v.cjs +390 -0
- package/dist/ready-DXtNfk7v.cjs.map +1 -0
- package/dist/reconcile/public.cjs +1 -1
- package/dist/reconcile/public.mjs +1 -1
- package/dist/vitest.cjs +61 -0
- package/dist/vitest.cjs.map +1 -0
- package/dist/vitest.d.cts +28 -0
- package/dist/vitest.d.cts.map +1 -0
- package/dist/vitest.d.mts +28 -0
- package/dist/vitest.d.mts.map +1 -0
- package/dist/vitest.mjs +61 -0
- package/dist/vitest.mjs.map +1 -0
- package/dist/{workspace-B3ApLW6p.cjs → workspace-BhiMkRuA.cjs} +3 -3
- package/dist/{workspace-B3ApLW6p.cjs.map → workspace-BhiMkRuA.cjs.map} +1 -1
- package/dist/{workspace-DnHW8ooI.mjs → workspace-C97QUCIf.mjs} +3 -3
- package/dist/{workspace-DnHW8ooI.mjs.map → workspace-C97QUCIf.mjs.map} +1 -1
- package/package.json +24 -14
- package/dist/openapi-D-Lk5Hr5.mjs.map +0 -1
- package/dist/openapi-K4H5tHzA.cjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
import { n as __require, t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
|
|
3
|
-
import {
|
|
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-
|
|
3
|
+
import { c as isPartitionedRoutes, i as ConstructGenerator, t as discover } from "./discover-CMxesskG.mjs";
|
|
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-BH3Ki69F.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-T4IVGSoB.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-
|
|
8
|
-
import {
|
|
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-C97QUCIf.mjs";
|
|
8
|
+
import { n as EndpointGenerator, r as runtimeFor } from "./OpenApiTsGenerator-hDINScnz.mjs";
|
|
9
|
+
import { generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi.mjs";
|
|
10
|
+
import { a as cacheBackendsIn, i as writeTestHarness, n as TEST_READY_FILE, o as driversFor, r as TEST_MANIFEST_ENV, t as TEST_READY_ENV } from "./ready-CvP3JVEw.mjs";
|
|
9
11
|
import { i as withRoutes, r as withCompute } from "./emit-Bzq7JsMP.mjs";
|
|
10
12
|
import { a as createStageSecrets, c as generateServiceCredentials, i as writeDockerEnvFromSecrets, l as rotateServicePassword, o as generateConnectionUrls, r as generateFullstackCustomSecrets, s as generateSecurePassword, t as reconcileMissingSecrets } from "./reconcile-yNq6Shau.mjs";
|
|
11
13
|
import { createRequire } from "node:module";
|
|
@@ -13,11 +15,11 @@ import { Command } from "commander";
|
|
|
13
15
|
import { stdin, stdout } from "node:process";
|
|
14
16
|
import * as readline from "node:readline/promises";
|
|
15
17
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
16
|
-
import { mkdir, readFile,
|
|
18
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
17
19
|
import { homedir } from "node:os";
|
|
18
20
|
import { basename, dirname, isAbsolute, join, parse, relative, resolve } from "node:path";
|
|
19
21
|
import { execFile, execSync, spawn } from "node:child_process";
|
|
20
|
-
import { cacheTable, canonicalId, cloudName, cookieDomain, dependentsOf,
|
|
22
|
+
import { cacheTable, canonicalId, cloudName, cookieDomain, dependentsOf, migrationFolder, provideKey, provisionOrder, scopedName, serviceKey } from "@geekmidas/manifest";
|
|
21
23
|
import fg from "fast-glob";
|
|
22
24
|
import chokidar from "chokidar";
|
|
23
25
|
import { createCipheriv, createHash, randomBytes } from "node:crypto";
|
|
@@ -34,10 +36,8 @@ import { Queue } from "@geekmidas/constructs/queue";
|
|
|
34
36
|
import { Subscriber } from "@geekmidas/constructs/subscribers";
|
|
35
37
|
import { Topic } from "@geekmidas/constructs/topic";
|
|
36
38
|
import { lookup } from "node:dns/promises";
|
|
37
|
-
import { EnvironmentParser } from "@geekmidas/envkit";
|
|
38
|
-
import { serviceContext } from "@geekmidas/services";
|
|
39
39
|
//#region package.json
|
|
40
|
-
var version = "10.0.0-alpha.
|
|
40
|
+
var version = "10.0.0-alpha.30";
|
|
41
41
|
//#endregion
|
|
42
42
|
//#region src/auth/credentials.ts
|
|
43
43
|
/**
|
|
@@ -1086,6 +1086,9 @@ 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) => {
|
|
1090
|
+
logger$10.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
|
|
1091
|
+
});
|
|
1089
1092
|
const appUrls = {};
|
|
1090
1093
|
for (const resource of reconciled.plan.resources) {
|
|
1091
1094
|
if (resource.kind !== "rest-api" && resource.kind !== "site") continue;
|
|
@@ -1794,111 +1797,6 @@ export const handler = adapter.handler;
|
|
|
1794
1797
|
}
|
|
1795
1798
|
};
|
|
1796
1799
|
//#endregion
|
|
1797
|
-
//#region src/generators/drivers.ts
|
|
1798
|
-
/**
|
|
1799
|
-
* Driver registration for generated entry points.
|
|
1800
|
-
*
|
|
1801
|
-
* A construct declares that it needs object storage and is handed one URL; the
|
|
1802
|
-
* scheme in that URL picks the driver that builds the client. Which drivers
|
|
1803
|
-
* exist is therefore the *entry point's* decision, not the construct's and not
|
|
1804
|
-
* the application's — an app that never talks to S3 should never resolve the AWS
|
|
1805
|
-
* SDK, and application code that registers a driver has named a provider in the
|
|
1806
|
-
* one layer this design keeps provider-free.
|
|
1807
|
-
*
|
|
1808
|
-
* So the generated entries register: `gkm dev`'s server for local development,
|
|
1809
|
-
* and each Lambda handler for its own target. Registration is an explicit call
|
|
1810
|
-
* rather than an import side effect, because a side-effecting module is exactly
|
|
1811
|
-
* what a bundler is entitled to drop.
|
|
1812
|
-
*/
|
|
1813
|
-
/** The `s3://` driver, which serves MinIO locally and S3 deployed. */
|
|
1814
|
-
const S3 = {
|
|
1815
|
-
imports: `import { registerStorageDriver } from '@geekmidas/storage';\nimport { s3Driver } from '@geekmidas/storage/aws';`,
|
|
1816
|
-
setup: "registerStorageDriver(s3Driver);"
|
|
1817
|
-
};
|
|
1818
|
-
const NONE = {
|
|
1819
|
-
imports: "",
|
|
1820
|
-
setup: ""
|
|
1821
|
-
};
|
|
1822
|
-
/**
|
|
1823
|
-
* The cache driver for a backend — exactly one, never all three.
|
|
1824
|
-
*
|
|
1825
|
-
* Keyed off the *backend* rather than off a dependency, because unlike storage
|
|
1826
|
-
* the backend already says which driver is right: a project caching in Postgres
|
|
1827
|
-
* resolves `pg` and never `ioredis`, and one caching in Upstash resolves
|
|
1828
|
-
* neither. That is the whole reason the drivers live behind separate subpaths.
|
|
1829
|
-
*
|
|
1830
|
-
* Both HTTP schemes are registered for Upstash, because the local proxy speaks
|
|
1831
|
-
* `http://` and Upstash speaks `https://`, and an entry that registered one
|
|
1832
|
-
* would work in exactly one of the two places.
|
|
1833
|
-
*/
|
|
1834
|
-
const CACHE_DRIVERS = {
|
|
1835
|
-
upstash: {
|
|
1836
|
-
imports: `import { registerCacheDriver } from '@geekmidas/cache';\nimport { upstashCacheDriver, upstashInsecureCacheDriver } from '@geekmidas/cache/upstash';`,
|
|
1837
|
-
setup: "registerCacheDriver(upstashCacheDriver);\nregisterCacheDriver(upstashInsecureCacheDriver);"
|
|
1838
|
-
},
|
|
1839
|
-
elasticache: {
|
|
1840
|
-
imports: `import { registerCacheDriver } from '@geekmidas/cache';\nimport { redisCacheDriver, redissCacheDriver } from '@geekmidas/cache/redis';`,
|
|
1841
|
-
setup: "registerCacheDriver(redisCacheDriver);\nregisterCacheDriver(redissCacheDriver);"
|
|
1842
|
-
},
|
|
1843
|
-
db: {
|
|
1844
|
-
imports: `import { registerCacheDriver } from '@geekmidas/cache';\nimport { postgresCacheDriver } from '@geekmidas/cache/postgres';`,
|
|
1845
|
-
setup: "registerCacheDriver(postgresCacheDriver);"
|
|
1846
|
-
}
|
|
1847
|
-
};
|
|
1848
|
-
/**
|
|
1849
|
-
* Everything a generated entry should register: object storage, and the cache.
|
|
1850
|
-
*
|
|
1851
|
-
* Merged here rather than threaded separately, because a generated file has one
|
|
1852
|
-
* import block and one setup block whatever fills them.
|
|
1853
|
-
*/
|
|
1854
|
-
function driversFor(options) {
|
|
1855
|
-
const backends = options.cache === false || options.cache === void 0 ? [] : typeof options.cache === "string" ? [options.cache] : options.cache;
|
|
1856
|
-
const parts = [storageDriversFor(options.appRoot), ...[...new Set(backends)].map((backend) => CACHE_DRIVERS[backend] ?? CACHE_DRIVERS[DEFAULT_CACHE.aws])].filter((part) => part.imports || part.setup);
|
|
1857
|
-
if (parts.length === 0) return NONE;
|
|
1858
|
-
return {
|
|
1859
|
-
imports: parts.map((part) => part.imports).join("\n"),
|
|
1860
|
-
setup: parts.map((part) => part.setup).join("\n")
|
|
1861
|
-
};
|
|
1862
|
-
}
|
|
1863
|
-
/**
|
|
1864
|
-
* Which cache backends an app's entry actually has to speak.
|
|
1865
|
-
*
|
|
1866
|
-
* The config answers this for a cache that named nowhere, and the *declaration*
|
|
1867
|
-
* answers it for one that named a database — `orders.cache('Sessions')` is a
|
|
1868
|
-
* statement about the application, so a deployment cannot move that cache and
|
|
1869
|
-
* the entry cannot register a driver for somewhere else.
|
|
1870
|
-
*
|
|
1871
|
-
* Reading only the config is how an entry ends up registering the Upstash
|
|
1872
|
-
* driver for a URL the target composed as `postgres://`, which fails at the
|
|
1873
|
-
* first request with `UnregisteredCacheScheme` and a stack that points at the
|
|
1874
|
-
* cache rather than at the config that disagreed.
|
|
1875
|
-
*/
|
|
1876
|
-
function cacheBackendsIn(manifest, configured) {
|
|
1877
|
-
const caches = Object.values(manifest).filter((d) => d.kind === "cache");
|
|
1878
|
-
return [...new Set(caches.map((c) => c.of ? "db" : configured))];
|
|
1879
|
-
}
|
|
1880
|
-
/**
|
|
1881
|
-
* The drivers an app's entry points should register.
|
|
1882
|
-
*
|
|
1883
|
-
* Keyed off the dependency rather than off a declared bucket, because the entry
|
|
1884
|
-
* is generated before discovery has run on the watcher's rebuild path — and an
|
|
1885
|
-
* app that installed `@geekmidas/storage` has already paid for the SDK it would
|
|
1886
|
-
* otherwise resolve lazily.
|
|
1887
|
-
*/
|
|
1888
|
-
function storageDriversFor(appRoot) {
|
|
1889
|
-
return dependsOnStorage(appRoot) ? S3 : NONE;
|
|
1890
|
-
}
|
|
1891
|
-
function dependsOnStorage(appRoot) {
|
|
1892
|
-
const manifest = join(appRoot, "package.json");
|
|
1893
|
-
if (!existsSync(manifest)) return false;
|
|
1894
|
-
try {
|
|
1895
|
-
const pkg = JSON.parse(readFileSync(manifest, "utf8"));
|
|
1896
|
-
return Boolean(pkg.dependencies?.["@geekmidas/storage"] ?? pkg.devDependencies?.["@geekmidas/storage"]);
|
|
1897
|
-
} catch {
|
|
1898
|
-
return false;
|
|
1899
|
-
}
|
|
1900
|
-
}
|
|
1901
|
-
//#endregion
|
|
1902
1800
|
//#region src/generators/FunctionGenerator.ts
|
|
1903
1801
|
var FunctionGenerator = class extends ConstructGenerator {
|
|
1904
1802
|
isConstruct(value) {
|
|
@@ -2901,7 +2799,7 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
|
|
|
2901
2799
|
let masterKey;
|
|
2902
2800
|
if (context.production?.bundle && !skipBundle) {
|
|
2903
2801
|
logger$8.log(`\n📦 Bundling production server...`);
|
|
2904
|
-
const { bundleServer } = await import("./bundler-
|
|
2802
|
+
const { bundleServer } = await import("./bundler-D9IPNvDt.mjs");
|
|
2905
2803
|
const allConstructs = [
|
|
2906
2804
|
...endpoints.map((e) => e.construct),
|
|
2907
2805
|
...functions.map((f) => f.construct),
|
|
@@ -3912,7 +3810,7 @@ async function createDnsProvider(options) {
|
|
|
3912
3810
|
if (isDnsProvider(config.provider)) return config.provider;
|
|
3913
3811
|
const provider = config.provider;
|
|
3914
3812
|
if (provider === "hostinger") {
|
|
3915
|
-
const { HostingerProvider } = await import("./HostingerProvider-
|
|
3813
|
+
const { HostingerProvider } = await import("./HostingerProvider-GF6NAZAK.mjs");
|
|
3916
3814
|
return new HostingerProvider();
|
|
3917
3815
|
}
|
|
3918
3816
|
if (provider === "route53") {
|
|
@@ -8049,6 +7947,22 @@ function printStateDetails(state) {
|
|
|
8049
7947
|
}
|
|
8050
7948
|
}
|
|
8051
7949
|
//#endregion
|
|
7950
|
+
//#region src/migrate/names.ts
|
|
7951
|
+
/**
|
|
7952
|
+
* What a migration file is called: a UTC timestamp, then a name.
|
|
7953
|
+
*
|
|
7954
|
+
* Kept apart from the commands so the scaffold can name the migration it
|
|
7955
|
+
* writes the same way `gkm migration` does, without loading a migrator.
|
|
7956
|
+
*/
|
|
7957
|
+
/** UTC `YYYYMMDDHHmmss` — sorts as text, and reads as a date. */
|
|
7958
|
+
function stamp(now = /* @__PURE__ */ new Date()) {
|
|
7959
|
+
return now.toISOString().replace(/\.\d+Z$/, "").replace(/\D/g, "");
|
|
7960
|
+
}
|
|
7961
|
+
/** `Add workouts` → `add_workouts`: the part of a file name after its stamp. */
|
|
7962
|
+
function slug(name) {
|
|
7963
|
+
return name.trim().replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
7964
|
+
}
|
|
7965
|
+
//#endregion
|
|
8052
7966
|
//#region src/init/constructs.ts
|
|
8053
7967
|
/**
|
|
8054
7968
|
* What a scaffolded project declares, and the keys that derive from it.
|
|
@@ -8165,7 +8079,7 @@ export const database = new KyselyDatabase<Database, '${db.id}'>('${db.id}', {
|
|
|
8165
8079
|
*/
|
|
8166
8080
|
function usersMigration() {
|
|
8167
8081
|
return {
|
|
8168
|
-
path:
|
|
8082
|
+
path: `${migrationFolder(databaseFor().id)}/${stamp()}_create_users.ts`,
|
|
8169
8083
|
content: `import type { Kysely } from 'kysely';
|
|
8170
8084
|
|
|
8171
8085
|
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
@@ -8328,7 +8242,7 @@ environment and fail in a way that looks like a config bug. Go through
|
|
|
8328
8242
|
\`gkm exec --\`, which is what injects them.`);
|
|
8329
8243
|
if (!isWorker) sections.push(endpointSection(options));
|
|
8330
8244
|
if (isWorker || isServerless) sections.push(backgroundSection(template));
|
|
8331
|
-
if (database) sections.push(databaseSection(), queriesSection());
|
|
8245
|
+
if (database) sections.push(databaseSection(), migrationsSection(), queriesSection());
|
|
8332
8246
|
sections.push(`## Environment
|
|
8333
8247
|
|
|
8334
8248
|
Environment is parsed once, through \`envkit\`, and never read from
|
|
@@ -8379,7 +8293,7 @@ function layoutBlock(options, template) {
|
|
|
8379
8293
|
const { database } = options.constructs;
|
|
8380
8294
|
const isWorker = template.name === "worker";
|
|
8381
8295
|
if (monorepo) return `constructs/ # every construct: the surface, the database, topics
|
|
8382
|
-
${apiPath}/ # the API app
|
|
8296
|
+
${database ? "db/<construct>/ # migrations, one folder per database construct\n" : ""}${apiPath}/ # the API app
|
|
8383
8297
|
gkm.config.ts # name, constructs glob, secrets`;
|
|
8384
8298
|
const lines = ["src/constructs/ # the surface, and anything it declares", "src/config/ # env parser, logger"];
|
|
8385
8299
|
if (!isWorker) lines.push("src/endpoints/ # HTTP handlers");
|
|
@@ -8387,7 +8301,7 @@ gkm.config.ts # name, constructs glob, secrets`;
|
|
|
8387
8301
|
lines.push("src/crons/ # scheduled work");
|
|
8388
8302
|
lines.push("src/subscribers/ # topic consumers");
|
|
8389
8303
|
}
|
|
8390
|
-
if (database) lines.push("
|
|
8304
|
+
if (database) lines.push("db/<construct>/ # migrations, one folder per database construct");
|
|
8391
8305
|
lines.push("gkm.config.ts # name, constructs glob, secrets");
|
|
8392
8306
|
return lines.join("\n");
|
|
8393
8307
|
}
|
|
@@ -8515,6 +8429,38 @@ export const monthly = router
|
|
|
8515
8429
|
Queries go through Kysely. Never open your own connection pool — the one the
|
|
8516
8430
|
construct provides is the one that gets credentials, pooling and shutdown.`;
|
|
8517
8431
|
}
|
|
8432
|
+
function migrationsSection() {
|
|
8433
|
+
return `## Migrations
|
|
8434
|
+
|
|
8435
|
+
Each database construct has its own folder, named after the construct:
|
|
8436
|
+
\`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
|
|
8439
|
+
table.
|
|
8440
|
+
|
|
8441
|
+
\`\`\`bash
|
|
8442
|
+
gkm migration database add_workouts # db/database/20260930143012_add_workouts.ts
|
|
8443
|
+
gkm migration auth # Better Auth's schema change, as SQL
|
|
8444
|
+
gkm migrate # apply every folder, parents first
|
|
8445
|
+
gkm migrate database # apply one construct's
|
|
8446
|
+
\`\`\`
|
|
8447
|
+
|
|
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.
|
|
8451
|
+
- Write migrations with \`Kysely<unknown>\` and snake_case names: they are the
|
|
8452
|
+
SQL, and the camelCase plugin is the app's, not theirs.
|
|
8453
|
+
- \`gkm migration auth\` compares Better Auth's schema to its tenant and writes
|
|
8454
|
+
only the difference. Run it after adding a Better Auth plugin, and commit the
|
|
8455
|
+
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.
|
|
8459
|
+
- A deploy migrates before the new version goes live, so the old version runs
|
|
8460
|
+
against the new schema until it is replaced. Keep each migration compatible
|
|
8461
|
+
with the code before it: add a column in one release, stop using the old one,
|
|
8462
|
+
drop it in a later release.`;
|
|
8463
|
+
}
|
|
8518
8464
|
function queriesSection() {
|
|
8519
8465
|
return `## Writing queries and relations
|
|
8520
8466
|
|
|
@@ -8664,7 +8610,6 @@ const DEPENDENCY_VERSIONS = {
|
|
|
8664
8610
|
hono: "~4.13.9",
|
|
8665
8611
|
"@hono/node-server": "~1.19.17",
|
|
8666
8612
|
kysely: "~0.29.6",
|
|
8667
|
-
"kysely-ctl": "~0.21.0",
|
|
8668
8613
|
pg: "~8.23.0",
|
|
8669
8614
|
pino: "~10.3.1",
|
|
8670
8615
|
zod: "~4.6.5",
|
|
@@ -8771,26 +8716,26 @@ function loadPackageJson() {
|
|
|
8771
8716
|
* Run: pnpm --filter @geekmidas/cli sync-versions
|
|
8772
8717
|
*/
|
|
8773
8718
|
const GEEKMIDAS_VERSIONS = {
|
|
8774
|
-
"@geekmidas/audit": "~10.0.0-alpha.
|
|
8775
|
-
"@geekmidas/auth": "~10.0.0-alpha.
|
|
8776
|
-
"@geekmidas/cache": "~10.0.0-alpha.
|
|
8777
|
-
"@geekmidas/client": "~10.0.0-alpha.
|
|
8778
|
-
"@geekmidas/cloud": "~10.0.0-alpha.
|
|
8779
|
-
"@geekmidas/constructs": "~10.0.0-alpha.
|
|
8780
|
-
"@geekmidas/db": "~10.0.0-alpha.
|
|
8781
|
-
"@geekmidas/emailkit": "~10.0.0-alpha.
|
|
8782
|
-
"@geekmidas/envkit": "~10.0.0-alpha.
|
|
8783
|
-
"@geekmidas/errors": "~10.0.0-alpha.
|
|
8784
|
-
"@geekmidas/events": "~10.0.0-alpha.
|
|
8785
|
-
"@geekmidas/logger": "~10.0.0-alpha.
|
|
8786
|
-
"@geekmidas/manifest": "~10.0.0-alpha.
|
|
8787
|
-
"@geekmidas/rate-limit": "~10.0.0-alpha.
|
|
8788
|
-
"@geekmidas/schema": "~10.0.0-alpha.
|
|
8789
|
-
"@geekmidas/services": "~10.0.0-alpha.
|
|
8790
|
-
"@geekmidas/storage": "~10.0.0-alpha.
|
|
8791
|
-
"@geekmidas/studio": "~10.0.0-alpha.
|
|
8792
|
-
"@geekmidas/telescope": "~10.0.0-alpha.
|
|
8793
|
-
"@geekmidas/testkit": "~10.0.0-alpha.
|
|
8719
|
+
"@geekmidas/audit": "~10.0.0-alpha.30",
|
|
8720
|
+
"@geekmidas/auth": "~10.0.0-alpha.30",
|
|
8721
|
+
"@geekmidas/cache": "~10.0.0-alpha.30",
|
|
8722
|
+
"@geekmidas/client": "~10.0.0-alpha.30",
|
|
8723
|
+
"@geekmidas/cloud": "~10.0.0-alpha.30",
|
|
8724
|
+
"@geekmidas/constructs": "~10.0.0-alpha.30",
|
|
8725
|
+
"@geekmidas/db": "~10.0.0-alpha.30",
|
|
8726
|
+
"@geekmidas/emailkit": "~10.0.0-alpha.30",
|
|
8727
|
+
"@geekmidas/envkit": "~10.0.0-alpha.30",
|
|
8728
|
+
"@geekmidas/errors": "~10.0.0-alpha.30",
|
|
8729
|
+
"@geekmidas/events": "~10.0.0-alpha.30",
|
|
8730
|
+
"@geekmidas/logger": "~10.0.0-alpha.30",
|
|
8731
|
+
"@geekmidas/manifest": "~10.0.0-alpha.30",
|
|
8732
|
+
"@geekmidas/rate-limit": "~10.0.0-alpha.30",
|
|
8733
|
+
"@geekmidas/schema": "~10.0.0-alpha.30",
|
|
8734
|
+
"@geekmidas/services": "~10.0.0-alpha.30",
|
|
8735
|
+
"@geekmidas/storage": "~10.0.0-alpha.30",
|
|
8736
|
+
"@geekmidas/studio": "~10.0.0-alpha.30",
|
|
8737
|
+
"@geekmidas/telescope": "~10.0.0-alpha.30",
|
|
8738
|
+
"@geekmidas/testkit": "~10.0.0-alpha.30",
|
|
8794
8739
|
"@geekmidas/cli": `~${loadPackageJson().version}`
|
|
8795
8740
|
};
|
|
8796
8741
|
//#endregion
|
|
@@ -8808,9 +8753,7 @@ function generateAuthAppFiles(options) {
|
|
|
8808
8753
|
type: "module",
|
|
8809
8754
|
scripts: {
|
|
8810
8755
|
dev: "gkm dev",
|
|
8811
|
-
build: "gkm build"
|
|
8812
|
-
"db:migrate": "gkm exec -- npx @better-auth/cli migrate",
|
|
8813
|
-
"db:generate": "gkm exec -- npx @better-auth/cli generate"
|
|
8756
|
+
build: "gkm build"
|
|
8814
8757
|
},
|
|
8815
8758
|
dependencies: {
|
|
8816
8759
|
[`@${options.name}/models`]: "workspace:*",
|
|
@@ -8890,16 +8833,24 @@ function stageEnv(stages) {
|
|
|
8890
8833
|
//#endregion
|
|
8891
8834
|
//#region src/init/generators/config.ts
|
|
8892
8835
|
/**
|
|
8893
|
-
* Vitest config
|
|
8836
|
+
* Vitest config for a database-enabled app.
|
|
8837
|
+
*
|
|
8838
|
+
* On its own, it is the root config and carries gkm's setup: the test stage
|
|
8839
|
+
* reconciled and every construct migrated, however the suite starts. In a
|
|
8840
|
+
* workspace the root config carries it instead — once, so no project filter
|
|
8841
|
+
* can skip it and nothing runs it twice.
|
|
8894
8842
|
*/
|
|
8895
|
-
const
|
|
8843
|
+
const vitestConfigFor = (monorepo) => `import { defineConfig } from 'vitest/config';
|
|
8896
8844
|
|
|
8897
8845
|
export default defineConfig({
|
|
8898
8846
|
// Vite resolves tsconfig \`paths\` itself; no plugin needed.
|
|
8899
8847
|
resolve: { tsconfigPaths: true },
|
|
8900
8848
|
test: {
|
|
8901
|
-
environment: 'node'
|
|
8902
|
-
|
|
8849
|
+
environment: 'node',${monorepo ? "" : `
|
|
8850
|
+
// The test stage, reconciled and every database construct's migrations
|
|
8851
|
+
// applied, before any test runs — whether \`gkm test\` or plain
|
|
8852
|
+
// \`vitest\` started it.
|
|
8853
|
+
globalSetup: ['@geekmidas/cli/vitest'],`}
|
|
8903
8854
|
},
|
|
8904
8855
|
});
|
|
8905
8856
|
`;
|
|
@@ -8977,7 +8928,7 @@ export default defineConfig({${stagesBlock(options.stages)}${options.monorepo ?
|
|
|
8977
8928
|
}];
|
|
8978
8929
|
if (options.constructs.database) files.push({
|
|
8979
8930
|
path: "vitest.config.ts",
|
|
8980
|
-
content:
|
|
8931
|
+
content: vitestConfigFor(Boolean(options.monorepo))
|
|
8981
8932
|
});
|
|
8982
8933
|
return files;
|
|
8983
8934
|
}
|
|
@@ -9067,7 +9018,7 @@ export default defineConfig({${stagesBlock(options.stages)}${options.monorepo ?
|
|
|
9067
9018
|
];
|
|
9068
9019
|
if (options.constructs.database) files.push({
|
|
9069
9020
|
path: "vitest.config.ts",
|
|
9070
|
-
content:
|
|
9021
|
+
content: vitestConfigFor(Boolean(options.monorepo))
|
|
9071
9022
|
});
|
|
9072
9023
|
return files;
|
|
9073
9024
|
}
|
|
@@ -9103,7 +9054,7 @@ function generateSingleAppConfigFiles(options, _template, _helpers) {
|
|
|
9103
9054
|
}];
|
|
9104
9055
|
if (options.constructs.database) files.push({
|
|
9105
9056
|
path: "vitest.config.ts",
|
|
9106
|
-
content:
|
|
9057
|
+
content: vitestConfigFor(Boolean(options.monorepo))
|
|
9107
9058
|
});
|
|
9108
9059
|
return files;
|
|
9109
9060
|
}
|
|
@@ -10550,8 +10501,12 @@ ${isSst ? "\n# SST\n.sst/\n" : ""}`;
|
|
|
10550
10501
|
|
|
10551
10502
|
export default defineConfig({
|
|
10552
10503
|
test: {
|
|
10553
|
-
//
|
|
10554
|
-
//
|
|
10504
|
+
// The test stage, reconciled and every database construct's migrations
|
|
10505
|
+
// applied, before any project's tests start — declared here, once, so no
|
|
10506
|
+
// project filter can skip it and nothing runs it twice.
|
|
10507
|
+
globalSetup: ['@geekmidas/cli/vitest'],
|
|
10508
|
+
// Each app and package is its own project, so its own config — its path
|
|
10509
|
+
// aliases — applies to its own tests.
|
|
10555
10510
|
projects: ['apps/*', 'packages/*'],
|
|
10556
10511
|
// The shared packages ship without tests of their own.
|
|
10557
10512
|
passWithNoTests: true,
|
|
@@ -10769,6 +10724,7 @@ export const database = new KyselyDatabase<Database, '${db.id}'>('${db.id}', {
|
|
|
10769
10724
|
export const authDb = database.schema<Record<string, never>, 'AuthDb'>('AuthDb');
|
|
10770
10725
|
`
|
|
10771
10726
|
});
|
|
10727
|
+
files.push(usersMigration());
|
|
10772
10728
|
files.push({
|
|
10773
10729
|
path: "constructs/auth.ts",
|
|
10774
10730
|
content: `import { BetterAuth } from '@geekmidas/constructs/auth';
|
|
@@ -11157,7 +11113,6 @@ export const router = api${options.constructs.database ? ".database(database)" :
|
|
|
11157
11113
|
`
|
|
11158
11114
|
});
|
|
11159
11115
|
if (options.constructs.database && declares) files.push(...databaseFiles());
|
|
11160
|
-
else if (options.constructs.database) files.push(usersMigration());
|
|
11161
11116
|
if (uploads && declares) files.push({
|
|
11162
11117
|
path: "src/constructs/storage.ts",
|
|
11163
11118
|
content: `import { ObjectStorage } from '@geekmidas/constructs/object-storage';
|
|
@@ -11925,7 +11880,6 @@ function generatePackageJson(options, template) {
|
|
|
11925
11880
|
devDependencies["@types/pg"] = DEPENDENCY_VERSIONS["@types/pg"];
|
|
11926
11881
|
devDependencies["@geekmidas/testkit"] = GEEKMIDAS_VERSIONS["@geekmidas/testkit"];
|
|
11927
11882
|
devDependencies["@faker-js/faker"] = DEPENDENCY_VERSIONS["@faker-js/faker"];
|
|
11928
|
-
devDependencies["kysely-ctl"] = DEPENDENCY_VERSIONS["kysely-ctl"];
|
|
11929
11883
|
}
|
|
11930
11884
|
if (monorepo) {
|
|
11931
11885
|
delete devDependencies["@biomejs/biome"];
|
|
@@ -11976,38 +11930,16 @@ function generateSourceFiles(options, template) {
|
|
|
11976
11930
|
//#region src/init/generators/test.ts
|
|
11977
11931
|
/**
|
|
11978
11932
|
* Generate test infrastructure files when database is enabled.
|
|
11979
|
-
* Includes transaction-isolated test config,
|
|
11933
|
+
* Includes transaction-isolated test config,
|
|
11980
11934
|
* factory system with builders/seeds, and an example spec.
|
|
11981
11935
|
*/
|
|
11982
11936
|
function generateTestFiles(options, _template) {
|
|
11983
11937
|
if (!options.constructs.database) return [];
|
|
11984
11938
|
const db = databaseFor();
|
|
11985
11939
|
const schema = !options.monorepo ? "../src/constructs/database.ts" : `@${options.name}/constructs/database.ts`;
|
|
11986
|
-
|
|
11987
|
-
|
|
11940
|
+
db.urlKey;
|
|
11941
|
+
db.ownerUrlKey;
|
|
11988
11942
|
return [
|
|
11989
|
-
{
|
|
11990
|
-
path: "kysely.config.ts",
|
|
11991
|
-
content: `import { Credentials } from '@geekmidas/envkit/credentials';
|
|
11992
|
-
import { PostgresDialect } from 'kysely';
|
|
11993
|
-
import { defineConfig } from 'kysely-ctl';
|
|
11994
|
-
import pg from 'pg';
|
|
11995
|
-
|
|
11996
|
-
// The owner role's URL — the one that may create, alter, and drop. Both keys
|
|
11997
|
-
// are published by the declared database construct; run this under
|
|
11998
|
-
// \`gkm exec -- pnpm kysely migrate:latest\` so they are injected.
|
|
11999
|
-
const url = Credentials.${ownerUrl} ?? Credentials.${runtimeUrl};
|
|
12000
|
-
|
|
12001
|
-
export default defineConfig({
|
|
12002
|
-
dialect: new PostgresDialect({
|
|
12003
|
-
pool: new pg.Pool({ connectionString: url }),
|
|
12004
|
-
}),
|
|
12005
|
-
migrations: {
|
|
12006
|
-
migrationFolder: './src/db/migrations',
|
|
12007
|
-
},
|
|
12008
|
-
});
|
|
12009
|
-
`
|
|
12010
|
-
},
|
|
12011
11943
|
{
|
|
12012
11944
|
path: "test/config.ts",
|
|
12013
11945
|
content: `import { it as itVitest } from 'vitest';
|
|
@@ -12019,44 +11951,6 @@ export const it = wrapVitestKyselyTransaction<Database>(itVitest, {
|
|
|
12019
11951
|
// the plugins it was given, \`CamelCasePlugin\` included.
|
|
12020
11952
|
connection: database,
|
|
12021
11953
|
});
|
|
12022
|
-
`
|
|
12023
|
-
},
|
|
12024
|
-
{
|
|
12025
|
-
path: "test/globalSetup.ts",
|
|
12026
|
-
content: `import fs from 'node:fs/promises';
|
|
12027
|
-
import path from 'node:path';
|
|
12028
|
-
import { Credentials } from '@geekmidas/envkit/credentials';
|
|
12029
|
-
import { PostgresKyselyMigrator } from '@geekmidas/testkit/kysely';
|
|
12030
|
-
import { Kysely, PostgresDialect } from 'kysely';
|
|
12031
|
-
import { FileMigrationProvider } from 'kysely/migration';
|
|
12032
|
-
import pg from 'pg';
|
|
12033
|
-
|
|
12034
|
-
export default async function globalSetup() {
|
|
12035
|
-
// \`gkm test\` reconciles the test stage before this runs: the container is
|
|
12036
|
-
// up, the database exists, and its roles are created. What is left is the
|
|
12037
|
-
// schema, which is what migrations are for.
|
|
12038
|
-
const databaseUrl = Credentials.${ownerUrl} ?? Credentials.${runtimeUrl}!;
|
|
12039
|
-
const migrationFolder = path.resolve(import.meta.dirname, '../src/db/migrations');
|
|
12040
|
-
|
|
12041
|
-
const db = new Kysely({
|
|
12042
|
-
dialect: new PostgresDialect({
|
|
12043
|
-
pool: new pg.Pool({ connectionString: databaseUrl }),
|
|
12044
|
-
}),
|
|
12045
|
-
});
|
|
12046
|
-
|
|
12047
|
-
const migrator = new PostgresKyselyMigrator({
|
|
12048
|
-
uri: databaseUrl,
|
|
12049
|
-
db,
|
|
12050
|
-
provider: new FileMigrationProvider({
|
|
12051
|
-
fs,
|
|
12052
|
-
path,
|
|
12053
|
-
migrationFolder,
|
|
12054
|
-
}),
|
|
12055
|
-
});
|
|
12056
|
-
|
|
12057
|
-
const teardown = await migrator.start();
|
|
12058
|
-
return teardown;
|
|
12059
|
-
}
|
|
12060
11954
|
`
|
|
12061
11955
|
},
|
|
12062
11956
|
{
|
|
@@ -14684,10 +14578,14 @@ function printNextSteps(projectName, options, pkgManager) {
|
|
|
14684
14578
|
console.log("Next steps:\n");
|
|
14685
14579
|
console.log(` ${cdCommand}`);
|
|
14686
14580
|
console.log(` ${devCommand}`);
|
|
14687
|
-
if (
|
|
14581
|
+
if (options.constructs.database) {
|
|
14688
14582
|
console.log("");
|
|
14689
|
-
console.log(" #
|
|
14690
|
-
console.log(
|
|
14583
|
+
console.log(" # Apply each database construct's db/<construct>/ migrations:");
|
|
14584
|
+
console.log(" gkm migrate");
|
|
14585
|
+
if (options.monorepo) {
|
|
14586
|
+
console.log(" # Better Auth's tables, as its tenant's first migration:");
|
|
14587
|
+
console.log(" gkm migration auth && gkm migrate");
|
|
14588
|
+
}
|
|
14691
14589
|
}
|
|
14692
14590
|
console.log("");
|
|
14693
14591
|
if (options.monorepo) {
|
|
@@ -15039,7 +14937,7 @@ var NoStoredSecrets = class extends Error {
|
|
|
15039
14937
|
}
|
|
15040
14938
|
};
|
|
15041
14939
|
async function reconcile(secrets, workspace, stage) {
|
|
15042
|
-
const { derivedContainers } = await import("./workspace-
|
|
14940
|
+
const { derivedContainers } = await import("./workspace-C97QUCIf.mjs").then((n) => n.a);
|
|
15043
14941
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
15044
14942
|
return result ? {
|
|
15045
14943
|
secrets: result.secrets,
|
|
@@ -15338,231 +15236,6 @@ function printSummary(workspace, stage) {
|
|
|
15338
15236
|
logger$1.log("");
|
|
15339
15237
|
}
|
|
15340
15238
|
//#endregion
|
|
15341
|
-
//#region src/test/harness.ts
|
|
15342
|
-
/**
|
|
15343
|
-
* What `gkm test` writes for a feature test to be built from.
|
|
15344
|
-
*
|
|
15345
|
-
* `gkm test` already discovers an app's constructs and resolves the test
|
|
15346
|
-
* stage's environment; this keeps both, under `.gkm/test/`:
|
|
15347
|
-
*
|
|
15348
|
-
* - `manifest.json` — which construct and endpoint is exported where, and the
|
|
15349
|
-
* environment the test stage resolved, keyed as the constructs derive keys.
|
|
15350
|
-
* - `clients/<surface>.ts` — each surface's typed client, from the same
|
|
15351
|
-
* generator `gkm build` uses for the app's own.
|
|
15352
|
-
* - `index.ts` — the configured `it`, and a `Browser` holding one client per
|
|
15353
|
-
* surface and one per auth server, each on the browser's `fetch`.
|
|
15354
|
-
*
|
|
15355
|
-
* A test imports `it` from there and declares nothing — not a construct, not an
|
|
15356
|
-
* environment key, not a client.
|
|
15357
|
-
*/
|
|
15358
|
-
/**
|
|
15359
|
-
* The variable the manifest's path travels in — what
|
|
15360
|
-
* `@geekmidas/constructs/testing` reads. Its own constant rather than an import:
|
|
15361
|
-
* that entry carries the whole harness (MSW, Hono, Vitest), and importing it
|
|
15362
|
-
* here put all of it on the start-up path of every `gkm` command.
|
|
15363
|
-
*/
|
|
15364
|
-
const TEST_MANIFEST_ENV = "GKM_TEST_MANIFEST";
|
|
15365
|
-
/** Where the harness lives, relative to the workspace root. */
|
|
15366
|
-
const TEST_HARNESS_DIR = ".gkm/test";
|
|
15367
|
-
/**
|
|
15368
|
-
* Better-auth's client plugin for each server plugin id.
|
|
15369
|
-
*
|
|
15370
|
-
* Written down rather than derived: the ids and the client names do not follow
|
|
15371
|
-
* one rule (`admin` pairs with `adminClient`, `email-otp` with
|
|
15372
|
-
* `emailOTPClient`), and a guess that missed would hand a test a client
|
|
15373
|
-
* silently lacking every method of that plugin. A server plugin not listed has
|
|
15374
|
-
* no client counterpart — `bearer`, `open-api` — or needs one from another
|
|
15375
|
-
* package, and is left out.
|
|
15376
|
-
*/
|
|
15377
|
-
const CLIENT_PLUGINS = {
|
|
15378
|
-
admin: "adminClient",
|
|
15379
|
-
anonymous: "anonymousClient",
|
|
15380
|
-
"device-authorization": "deviceAuthorizationClient",
|
|
15381
|
-
"email-otp": "emailOTPClient",
|
|
15382
|
-
jwt: "jwtClient",
|
|
15383
|
-
"last-login-method": "lastLoginMethodClient",
|
|
15384
|
-
"magic-link": "magicLinkClient",
|
|
15385
|
-
"multi-session": "multiSessionClient",
|
|
15386
|
-
"oauth-popup": "oauthPopupClient",
|
|
15387
|
-
"one-tap": "oneTapClient",
|
|
15388
|
-
"one-time-token": "oneTimeTokenClient",
|
|
15389
|
-
organization: "organizationClient",
|
|
15390
|
-
"phone-number": "phoneNumberClient",
|
|
15391
|
-
siwe: "siweClient",
|
|
15392
|
-
"two-factor": "twoFactorClient",
|
|
15393
|
-
username: "usernameClient"
|
|
15394
|
-
};
|
|
15395
|
-
/** Write the manifest, the clients and the harness; return the manifest path. */
|
|
15396
|
-
async function writeTestHarness(options) {
|
|
15397
|
-
const sources = {};
|
|
15398
|
-
const declared = await discover({
|
|
15399
|
-
patterns: options.patterns,
|
|
15400
|
-
cwd: options.root,
|
|
15401
|
-
sources
|
|
15402
|
-
});
|
|
15403
|
-
const endpoints = (await new EndpointGenerator().load(options.patterns, options.root)).map(({ key, construct, path }) => ({
|
|
15404
|
-
surface: construct.owner ?? construct.surface?.id,
|
|
15405
|
-
construct,
|
|
15406
|
-
source: {
|
|
15407
|
-
file: path.absolute,
|
|
15408
|
-
export: key
|
|
15409
|
-
}
|
|
15410
|
-
})).filter((endpoint) => endpoint.surface !== void 0);
|
|
15411
|
-
const manifest = {
|
|
15412
|
-
stage: options.stage,
|
|
15413
|
-
constructs: Object.fromEntries(Object.entries(declared).flatMap(([id, declaration]) => {
|
|
15414
|
-
const source = sources[id];
|
|
15415
|
-
return source ? [[id, {
|
|
15416
|
-
kind: declaration.kind,
|
|
15417
|
-
source: {
|
|
15418
|
-
file: source.file,
|
|
15419
|
-
export: source.exportName
|
|
15420
|
-
}
|
|
15421
|
-
}]] : [];
|
|
15422
|
-
})),
|
|
15423
|
-
endpoints: endpoints.map(({ surface, source }) => ({
|
|
15424
|
-
surface,
|
|
15425
|
-
source
|
|
15426
|
-
})),
|
|
15427
|
-
env: options.env
|
|
15428
|
-
};
|
|
15429
|
-
const surfaces = [...new Set(endpoints.map(({ surface }) => surface))];
|
|
15430
|
-
const generator = new OpenApiTsGenerator();
|
|
15431
|
-
const clients = await Promise.all(surfaces.map(async (surface) => ({
|
|
15432
|
-
file: `${kebabCase(surface)}.ts`,
|
|
15433
|
-
content: await generator.generate(endpoints.filter((endpoint) => endpoint.surface === surface).map(({ construct }) => construct), {
|
|
15434
|
-
title: surface,
|
|
15435
|
-
version: "1.0.0"
|
|
15436
|
-
})
|
|
15437
|
-
})));
|
|
15438
|
-
const auths = await authClients(declared, sources, options.env);
|
|
15439
|
-
const drivers = driversFor({
|
|
15440
|
-
appRoot: options.root,
|
|
15441
|
-
cache: cacheBackendsIn(declared, options.cacheBackend)
|
|
15442
|
-
});
|
|
15443
|
-
const database = await databaseSource(declared, sources, endpoints.map(({ construct }) => construct));
|
|
15444
|
-
const files = [.../* @__PURE__ */ new Set([...Object.values(manifest.constructs).map(({ source }) => source.file), ...manifest.endpoints.map(({ source }) => source.file)])];
|
|
15445
|
-
const harnessFor = (dir) => harnessModule({
|
|
15446
|
-
dir,
|
|
15447
|
-
files,
|
|
15448
|
-
database,
|
|
15449
|
-
surfaces: surfaces.map((id, index) => ({
|
|
15450
|
-
id,
|
|
15451
|
-
secured: clients[index].content.includes("authStrategies:")
|
|
15452
|
-
})),
|
|
15453
|
-
auths,
|
|
15454
|
-
drivers
|
|
15455
|
-
});
|
|
15456
|
-
const json = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
15457
|
-
return (await Promise.all([...new Set(options.targets)].map(async (target) => {
|
|
15458
|
-
const dir = join(target, TEST_HARNESS_DIR);
|
|
15459
|
-
await rm(dir, {
|
|
15460
|
-
recursive: true,
|
|
15461
|
-
force: true
|
|
15462
|
-
});
|
|
15463
|
-
await mkdir(join(dir, "clients"), { recursive: true });
|
|
15464
|
-
await writeFile(join(dir, "manifest.json"), json);
|
|
15465
|
-
await writeFile(join(dir, "index.ts"), harnessFor(dir));
|
|
15466
|
-
for (const client of clients) await writeFile(join(dir, "clients", client.file), client.content);
|
|
15467
|
-
return join(dir, "manifest.json");
|
|
15468
|
-
})))[0];
|
|
15469
|
-
}
|
|
15470
|
-
/** Each auth server, with the client plugins its server plugins pair with. */
|
|
15471
|
-
async function authClients(declared, sources, env) {
|
|
15472
|
-
const clients = [];
|
|
15473
|
-
const envParser = new EnvironmentParser({ ...env });
|
|
15474
|
-
for (const [id, declaration] of Object.entries(declared)) {
|
|
15475
|
-
const source = sources[id];
|
|
15476
|
-
if (declaration.kind !== "rest-api" || !source) continue;
|
|
15477
|
-
const exported = (await import(source.file))[source.exportName];
|
|
15478
|
-
if (!isBetterAuth(exported) || exported.id !== id) continue;
|
|
15479
|
-
const ids = await exported.pluginIds({
|
|
15480
|
-
envParser,
|
|
15481
|
-
context: serviceContext
|
|
15482
|
-
});
|
|
15483
|
-
clients.push({
|
|
15484
|
-
id,
|
|
15485
|
-
basePath: exported.basePath,
|
|
15486
|
-
plugins: ids.flatMap((plugin) => CLIENT_PLUGINS[plugin] ?? [])
|
|
15487
|
-
});
|
|
15488
|
-
}
|
|
15489
|
-
return clients;
|
|
15490
|
-
}
|
|
15491
|
-
function isBetterAuth(value) {
|
|
15492
|
-
return typeof value === "object" && value !== null && "pluginIds" in value && "basePath" in value;
|
|
15493
|
-
}
|
|
15494
|
-
/** `Api` → `api`, `AdminApi` → `adminApi`: a construct id as a property name. */
|
|
15495
|
-
function propertyOf(id) {
|
|
15496
|
-
return id.charAt(0).toLowerCase() + id.slice(1);
|
|
15497
|
-
}
|
|
15498
|
-
/**
|
|
15499
|
-
* The database a test's `db` is — the one the endpoints were given with
|
|
15500
|
-
* `.database()`, found by the service they hold, which is the construct's own.
|
|
15501
|
-
* The same rule `featureTest` applies at runtime, applied here so `db` can be
|
|
15502
|
-
* typed by the construct's schema.
|
|
15503
|
-
*/
|
|
15504
|
-
async function databaseSource(declared, sources, endpoints) {
|
|
15505
|
-
const services = new Set(endpoints.map((endpoint) => endpoint.databaseService).filter(Boolean));
|
|
15506
|
-
if (services.size === 0) return void 0;
|
|
15507
|
-
for (const [id, declaration] of Object.entries(declared)) {
|
|
15508
|
-
const source = sources[id];
|
|
15509
|
-
if (!source || !declaration.kind.startsWith("database")) continue;
|
|
15510
|
-
const exported = (await import(source.file))[source.exportName];
|
|
15511
|
-
if (services.has(exported?.service)) return source;
|
|
15512
|
-
}
|
|
15513
|
-
}
|
|
15514
|
-
/** An import specifier for `file`, from the harness written into `dir`. */
|
|
15515
|
-
function specifierFrom(dir, file) {
|
|
15516
|
-
const path = relative(dir, file).replace(/\.tsx?$/, ".js");
|
|
15517
|
-
return path.startsWith(".") ? path : `./${path}`;
|
|
15518
|
-
}
|
|
15519
|
-
function harnessModule(options) {
|
|
15520
|
-
const { surfaces, auths, drivers, database, dir, files } = options;
|
|
15521
|
-
const plugins = [...new Set(auths.flatMap(({ plugins }) => plugins))].sort();
|
|
15522
|
-
const imports = [
|
|
15523
|
-
`import {${database ? " type DatabaseOf," : ""} featureTest, loadTestManifest } from '@geekmidas/constructs/testing';`,
|
|
15524
|
-
...database ? [`import type { ${database.exportName} as __database } from '${specifierFrom(dir, database.file)}';`] : [],
|
|
15525
|
-
`import { Browser as TestBrowser } from '@geekmidas/testkit/browser';`,
|
|
15526
|
-
...surfaces.map(({ id }) => `import { createApi as create${id} } from './clients/${kebabCase(id)}.js';`),
|
|
15527
|
-
...auths.length ? [`import { createAuthClient } from 'better-auth/client';`] : [],
|
|
15528
|
-
...plugins.length ? [`import { ${plugins.join(", ")} } from 'better-auth/client/plugins';`] : [],
|
|
15529
|
-
...drivers.imports ? [drivers.imports] : [],
|
|
15530
|
-
...files.map((file, index) => `import * as __module${index} from '${specifierFrom(dir, file)}';`)
|
|
15531
|
-
];
|
|
15532
|
-
const url = (id) => `manifest.env[${JSON.stringify(provideKey(id, "url"))}]!`;
|
|
15533
|
-
const members = [...surfaces.map(({ id, secured }) => ` /** \`${id}\`, at ${provideKey(id, "url")}. */
|
|
15534
|
-
readonly ${propertyOf(id)} = create${id}({
|
|
15535
|
-
baseURL: ${url(id)},
|
|
15536
|
-
fetch: this.fetch,${secured ? `
|
|
15537
|
-
// Served in-process: no authorizer runs, so nothing is signed. A scheme
|
|
15538
|
-
// with no strategy sends its request without credentials.
|
|
15539
|
-
authStrategies: {} as never,` : ""}
|
|
15540
|
-
});`), ...auths.map((auth) => ` /** \`${auth.id}\`, at ${provideKey(auth.id, "url")}. */
|
|
15541
|
-
readonly ${propertyOf(auth.id)} = createAuthClient({
|
|
15542
|
-
baseURL: \`\${${url(auth.id)}}${auth.basePath}\`,
|
|
15543
|
-
plugins: [${auth.plugins.map((plugin) => `${plugin}()`).join(", ")}],
|
|
15544
|
-
fetchOptions: { customFetchImpl: this.fetch },
|
|
15545
|
-
});`)];
|
|
15546
|
-
return `// Generated by \`gkm test\` from the app's constructs — do not edit.
|
|
15547
|
-
${imports.join("\n")}
|
|
15548
|
-
|
|
15549
|
-
${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));
|
|
15550
|
-
|
|
15551
|
-
/** A browser with a client for every surface the app declares. */
|
|
15552
|
-
export class Browser extends TestBrowser {
|
|
15553
|
-
${members.join("\n\n")}
|
|
15554
|
-
}
|
|
15555
|
-
|
|
15556
|
-
/** The app's modules, by the path the manifest records for each. */
|
|
15557
|
-
const modules = {
|
|
15558
|
-
${files.map((file, index) => `\t${JSON.stringify(file)}: __module${index},`).join("\n")}
|
|
15559
|
-
};
|
|
15560
|
-
|
|
15561
|
-
/** \`it\`, for a test that drives the app the way it runs deployed. */
|
|
15562
|
-
export const it = featureTest${database ? "<Browser, DatabaseOf<typeof __database>>" : ""}({ manifest, modules, browser: Browser });
|
|
15563
|
-
`;
|
|
15564
|
-
}
|
|
15565
|
-
//#endregion
|
|
15566
15239
|
//#region src/test/index.ts
|
|
15567
15240
|
/**
|
|
15568
15241
|
* Run tests with secrets, dependency URLs, and .env files loaded.
|
|
@@ -15614,6 +15287,16 @@ async function testCommand(options = {}) {
|
|
|
15614
15287
|
console.log(manifestPath ? ` 🧪 Test harness written beside ${manifestPath}` : " 🧪 Nothing to prepare: this project declares no constructs");
|
|
15615
15288
|
return;
|
|
15616
15289
|
}
|
|
15290
|
+
if (workspace) await migrateTestStage(workspace, result.credentials);
|
|
15291
|
+
const readyPath = join(cwd, TEST_READY_FILE);
|
|
15292
|
+
await writeFile(readyPath, JSON.stringify({
|
|
15293
|
+
env: result.secretsJsonPath,
|
|
15294
|
+
...manifestPath ? { manifest: manifestPath } : {}
|
|
15295
|
+
}, null, 2));
|
|
15296
|
+
if (options.setup) {
|
|
15297
|
+
console.log(` 🧪 Test stage ready: ${TEST_READY_FILE}`);
|
|
15298
|
+
return;
|
|
15299
|
+
}
|
|
15617
15300
|
const nodeOptions = [
|
|
15618
15301
|
process.env.NODE_OPTIONS ?? "",
|
|
15619
15302
|
"--import=tsx",
|
|
@@ -15632,6 +15315,7 @@ async function testCommand(options = {}) {
|
|
|
15632
15315
|
...process.env,
|
|
15633
15316
|
...finalCredentials,
|
|
15634
15317
|
...manifestPath ? { [TEST_MANIFEST_ENV]: manifestPath } : {},
|
|
15318
|
+
[TEST_READY_ENV]: readyPath,
|
|
15635
15319
|
NODE_ENV: "test",
|
|
15636
15320
|
NODE_OPTIONS: nodeOptions
|
|
15637
15321
|
}
|
|
@@ -15646,6 +15330,24 @@ async function testCommand(options = {}) {
|
|
|
15646
15330
|
});
|
|
15647
15331
|
});
|
|
15648
15332
|
}
|
|
15333
|
+
/** Apply every construct's migrations to the test stage just reconciled. */
|
|
15334
|
+
async function migrateTestStage(workspace, env) {
|
|
15335
|
+
const { migrateDatabases } = await import("./migrate-CBICVMAi.mjs");
|
|
15336
|
+
const { discover } = await import("./discover-CMxesskG.mjs").then((n) => n.n);
|
|
15337
|
+
const sources = {};
|
|
15338
|
+
const manifest = await discover({
|
|
15339
|
+
patterns: constructGlobs(workspace),
|
|
15340
|
+
cwd: workspace.root,
|
|
15341
|
+
sources
|
|
15342
|
+
});
|
|
15343
|
+
const runs = await migrateDatabases({
|
|
15344
|
+
root: workspace.root,
|
|
15345
|
+
manifest,
|
|
15346
|
+
sources,
|
|
15347
|
+
env
|
|
15348
|
+
});
|
|
15349
|
+
for (const { target, applied } of runs) if (applied.length > 0) console.log(` 🗄️ ${target.folder}: applied ${applied.join(", ")}`);
|
|
15350
|
+
}
|
|
15649
15351
|
/** `gkm test` outside a project that declares stages, with none named. */
|
|
15650
15352
|
var NoStageToTest = class extends Error {
|
|
15651
15353
|
constructor() {
|
|
@@ -16054,7 +15756,7 @@ program.command("exec").description("Run a command with secrets injected into Cr
|
|
|
16054
15756
|
process.exit(1);
|
|
16055
15757
|
}
|
|
16056
15758
|
});
|
|
16057
|
-
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) => {
|
|
15759
|
+
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) => {
|
|
16058
15760
|
try {
|
|
16059
15761
|
const globalOptions = program.opts();
|
|
16060
15762
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
@@ -16067,6 +15769,34 @@ program.command("test").description("Run tests with secrets loaded from environm
|
|
|
16067
15769
|
process.exit(1);
|
|
16068
15770
|
}
|
|
16069
15771
|
});
|
|
15772
|
+
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) => {
|
|
15773
|
+
try {
|
|
15774
|
+
const globalOptions = program.opts();
|
|
15775
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
15776
|
+
const { migrateCommand } = await import("./migrate-CBICVMAi.mjs");
|
|
15777
|
+
await migrateCommand({
|
|
15778
|
+
...options,
|
|
15779
|
+
...construct ? { construct } : {}
|
|
15780
|
+
});
|
|
15781
|
+
} catch (error) {
|
|
15782
|
+
console.error(formatError(error));
|
|
15783
|
+
process.exit(1);
|
|
15784
|
+
}
|
|
15785
|
+
});
|
|
15786
|
+
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) => {
|
|
15787
|
+
try {
|
|
15788
|
+
const globalOptions = program.opts();
|
|
15789
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
15790
|
+
const { migrationCommand } = await import("./migrate-CBICVMAi.mjs");
|
|
15791
|
+
await migrationCommand({
|
|
15792
|
+
construct,
|
|
15793
|
+
...name ? { name } : {}
|
|
15794
|
+
});
|
|
15795
|
+
} catch (error) {
|
|
15796
|
+
console.error(formatError(error));
|
|
15797
|
+
process.exit(1);
|
|
15798
|
+
}
|
|
15799
|
+
});
|
|
16070
15800
|
program.command("cron").description("Manage cron jobs").action(() => {
|
|
16071
15801
|
const globalOptions = program.opts();
|
|
16072
15802
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
@@ -16225,7 +15955,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
16225
15955
|
console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
16226
15956
|
process.exit(1);
|
|
16227
15957
|
}
|
|
16228
|
-
const { derivedContainers } = await import("./workspace-
|
|
15958
|
+
const { derivedContainers } = await import("./workspace-C97QUCIf.mjs").then((n) => n.a);
|
|
16229
15959
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
16230
15960
|
if (!result) {
|
|
16231
15961
|
console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
|
|
@@ -16284,7 +16014,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
|
|
|
16284
16014
|
try {
|
|
16285
16015
|
const globalOptions = program.opts();
|
|
16286
16016
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16287
|
-
const { deployGithubCommand } = await import("./github-
|
|
16017
|
+
const { deployGithubCommand } = await import("./github-BrDmlm1x.mjs");
|
|
16288
16018
|
await deployGithubCommand(options);
|
|
16289
16019
|
} catch (error) {
|
|
16290
16020
|
console.error(formatError(error));
|
|
@@ -16409,6 +16139,6 @@ program.command("upgrade").description("Upgrade @geekmidas/cli (with --all, ever
|
|
|
16409
16139
|
});
|
|
16410
16140
|
program.parse();
|
|
16411
16141
|
//#endregion
|
|
16412
|
-
export {
|
|
16142
|
+
export { encryption_exports as a, stamp as i, isRemoteStore as n, getHostingerToken as o, slug as r, pushStageSecrets as t };
|
|
16413
16143
|
|
|
16414
16144
|
//# sourceMappingURL=index.mjs.map
|