@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.cjs
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
|
|
4
|
-
const require_discover = require("./discover-
|
|
5
|
-
const require_config = require("./config-
|
|
4
|
+
const require_discover = require("./discover-CroTIm0K.cjs");
|
|
5
|
+
const require_config = require("./config-URVD4bTj.cjs");
|
|
6
6
|
const require_workspace = require("./workspace-Bav3TVrP.cjs");
|
|
7
7
|
const require_storage = require("./storage-C2JC31V9.cjs");
|
|
8
|
-
const require_workspace$1 = require("./workspace-
|
|
9
|
-
const
|
|
8
|
+
const require_workspace$1 = require("./workspace-BhiMkRuA.cjs");
|
|
9
|
+
const require_OpenApiTsGenerator = require("./OpenApiTsGenerator-D1nhN98m.cjs");
|
|
10
|
+
const require_openapi = require("./openapi.cjs");
|
|
11
|
+
const require_ready = require("./ready-DXtNfk7v.cjs");
|
|
10
12
|
const require_emit = require("./emit-BdoPFjZf.cjs");
|
|
11
13
|
const require_reconcile = require("./reconcile-BxZTV2vl.cjs");
|
|
12
14
|
let commander = require("commander");
|
|
@@ -39,10 +41,8 @@ let _geekmidas_constructs_queue = require("@geekmidas/constructs/queue");
|
|
|
39
41
|
let _geekmidas_constructs_subscribers = require("@geekmidas/constructs/subscribers");
|
|
40
42
|
let _geekmidas_constructs_topic = require("@geekmidas/constructs/topic");
|
|
41
43
|
let node_dns_promises = require("node:dns/promises");
|
|
42
|
-
let _geekmidas_envkit = require("@geekmidas/envkit");
|
|
43
|
-
let _geekmidas_services = require("@geekmidas/services");
|
|
44
44
|
//#region package.json
|
|
45
|
-
var version = "10.0.0-alpha.
|
|
45
|
+
var version = "10.0.0-alpha.30";
|
|
46
46
|
//#endregion
|
|
47
47
|
//#region src/auth/credentials.ts
|
|
48
48
|
/**
|
|
@@ -1091,6 +1091,9 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1091
1091
|
...rawSecrets,
|
|
1092
1092
|
...reconciled.env
|
|
1093
1093
|
};
|
|
1094
|
+
await Promise.resolve().then(() => require("./migrate-BuCx7tIv.cjs")).then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
|
|
1095
|
+
logger$10.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
|
|
1096
|
+
});
|
|
1094
1097
|
const appUrls = {};
|
|
1095
1098
|
for (const resource of reconciled.plan.resources) {
|
|
1096
1099
|
if (resource.kind !== "rest-api" && resource.kind !== "site") continue;
|
|
@@ -1784,7 +1787,7 @@ export async function stopCrons(): Promise<void> {
|
|
|
1784
1787
|
const handlerFileName = `${exportName}.ts`;
|
|
1785
1788
|
const handlerPath = (0, node_path.join)(outputDir, handlerFileName);
|
|
1786
1789
|
const importPath = (0, node_path.relative)((0, node_path.dirname)(handlerPath), sourceFile).replace(/\.ts$/, ".js");
|
|
1787
|
-
const runtime =
|
|
1790
|
+
const runtime = require_OpenApiTsGenerator.runtimeFor(context, (0, node_path.dirname)(handlerPath), owner);
|
|
1788
1791
|
const content = `import { AWSScheduledFunction } from '@geekmidas/constructs/aws';
|
|
1789
1792
|
import { ${exportName} } from '${importPath}';
|
|
1790
1793
|
${runtime.imports}
|
|
@@ -1799,111 +1802,6 @@ export const handler = adapter.handler;
|
|
|
1799
1802
|
}
|
|
1800
1803
|
};
|
|
1801
1804
|
//#endregion
|
|
1802
|
-
//#region src/generators/drivers.ts
|
|
1803
|
-
/**
|
|
1804
|
-
* Driver registration for generated entry points.
|
|
1805
|
-
*
|
|
1806
|
-
* A construct declares that it needs object storage and is handed one URL; the
|
|
1807
|
-
* scheme in that URL picks the driver that builds the client. Which drivers
|
|
1808
|
-
* exist is therefore the *entry point's* decision, not the construct's and not
|
|
1809
|
-
* the application's — an app that never talks to S3 should never resolve the AWS
|
|
1810
|
-
* SDK, and application code that registers a driver has named a provider in the
|
|
1811
|
-
* one layer this design keeps provider-free.
|
|
1812
|
-
*
|
|
1813
|
-
* So the generated entries register: `gkm dev`'s server for local development,
|
|
1814
|
-
* and each Lambda handler for its own target. Registration is an explicit call
|
|
1815
|
-
* rather than an import side effect, because a side-effecting module is exactly
|
|
1816
|
-
* what a bundler is entitled to drop.
|
|
1817
|
-
*/
|
|
1818
|
-
/** The `s3://` driver, which serves MinIO locally and S3 deployed. */
|
|
1819
|
-
const S3 = {
|
|
1820
|
-
imports: `import { registerStorageDriver } from '@geekmidas/storage';\nimport { s3Driver } from '@geekmidas/storage/aws';`,
|
|
1821
|
-
setup: "registerStorageDriver(s3Driver);"
|
|
1822
|
-
};
|
|
1823
|
-
const NONE = {
|
|
1824
|
-
imports: "",
|
|
1825
|
-
setup: ""
|
|
1826
|
-
};
|
|
1827
|
-
/**
|
|
1828
|
-
* The cache driver for a backend — exactly one, never all three.
|
|
1829
|
-
*
|
|
1830
|
-
* Keyed off the *backend* rather than off a dependency, because unlike storage
|
|
1831
|
-
* the backend already says which driver is right: a project caching in Postgres
|
|
1832
|
-
* resolves `pg` and never `ioredis`, and one caching in Upstash resolves
|
|
1833
|
-
* neither. That is the whole reason the drivers live behind separate subpaths.
|
|
1834
|
-
*
|
|
1835
|
-
* Both HTTP schemes are registered for Upstash, because the local proxy speaks
|
|
1836
|
-
* `http://` and Upstash speaks `https://`, and an entry that registered one
|
|
1837
|
-
* would work in exactly one of the two places.
|
|
1838
|
-
*/
|
|
1839
|
-
const CACHE_DRIVERS = {
|
|
1840
|
-
upstash: {
|
|
1841
|
-
imports: `import { registerCacheDriver } from '@geekmidas/cache';\nimport { upstashCacheDriver, upstashInsecureCacheDriver } from '@geekmidas/cache/upstash';`,
|
|
1842
|
-
setup: "registerCacheDriver(upstashCacheDriver);\nregisterCacheDriver(upstashInsecureCacheDriver);"
|
|
1843
|
-
},
|
|
1844
|
-
elasticache: {
|
|
1845
|
-
imports: `import { registerCacheDriver } from '@geekmidas/cache';\nimport { redisCacheDriver, redissCacheDriver } from '@geekmidas/cache/redis';`,
|
|
1846
|
-
setup: "registerCacheDriver(redisCacheDriver);\nregisterCacheDriver(redissCacheDriver);"
|
|
1847
|
-
},
|
|
1848
|
-
db: {
|
|
1849
|
-
imports: `import { registerCacheDriver } from '@geekmidas/cache';\nimport { postgresCacheDriver } from '@geekmidas/cache/postgres';`,
|
|
1850
|
-
setup: "registerCacheDriver(postgresCacheDriver);"
|
|
1851
|
-
}
|
|
1852
|
-
};
|
|
1853
|
-
/**
|
|
1854
|
-
* Everything a generated entry should register: object storage, and the cache.
|
|
1855
|
-
*
|
|
1856
|
-
* Merged here rather than threaded separately, because a generated file has one
|
|
1857
|
-
* import block and one setup block whatever fills them.
|
|
1858
|
-
*/
|
|
1859
|
-
function driversFor(options) {
|
|
1860
|
-
const backends = options.cache === false || options.cache === void 0 ? [] : typeof options.cache === "string" ? [options.cache] : options.cache;
|
|
1861
|
-
const parts = [storageDriversFor(options.appRoot), ...[...new Set(backends)].map((backend) => CACHE_DRIVERS[backend] ?? CACHE_DRIVERS[require_discover.DEFAULT_CACHE.aws])].filter((part) => part.imports || part.setup);
|
|
1862
|
-
if (parts.length === 0) return NONE;
|
|
1863
|
-
return {
|
|
1864
|
-
imports: parts.map((part) => part.imports).join("\n"),
|
|
1865
|
-
setup: parts.map((part) => part.setup).join("\n")
|
|
1866
|
-
};
|
|
1867
|
-
}
|
|
1868
|
-
/**
|
|
1869
|
-
* Which cache backends an app's entry actually has to speak.
|
|
1870
|
-
*
|
|
1871
|
-
* The config answers this for a cache that named nowhere, and the *declaration*
|
|
1872
|
-
* answers it for one that named a database — `orders.cache('Sessions')` is a
|
|
1873
|
-
* statement about the application, so a deployment cannot move that cache and
|
|
1874
|
-
* the entry cannot register a driver for somewhere else.
|
|
1875
|
-
*
|
|
1876
|
-
* Reading only the config is how an entry ends up registering the Upstash
|
|
1877
|
-
* driver for a URL the target composed as `postgres://`, which fails at the
|
|
1878
|
-
* first request with `UnregisteredCacheScheme` and a stack that points at the
|
|
1879
|
-
* cache rather than at the config that disagreed.
|
|
1880
|
-
*/
|
|
1881
|
-
function cacheBackendsIn(manifest, configured) {
|
|
1882
|
-
const caches = Object.values(manifest).filter((d) => d.kind === "cache");
|
|
1883
|
-
return [...new Set(caches.map((c) => c.of ? "db" : configured))];
|
|
1884
|
-
}
|
|
1885
|
-
/**
|
|
1886
|
-
* The drivers an app's entry points should register.
|
|
1887
|
-
*
|
|
1888
|
-
* Keyed off the dependency rather than off a declared bucket, because the entry
|
|
1889
|
-
* is generated before discovery has run on the watcher's rebuild path — and an
|
|
1890
|
-
* app that installed `@geekmidas/storage` has already paid for the SDK it would
|
|
1891
|
-
* otherwise resolve lazily.
|
|
1892
|
-
*/
|
|
1893
|
-
function storageDriversFor(appRoot) {
|
|
1894
|
-
return dependsOnStorage(appRoot) ? S3 : NONE;
|
|
1895
|
-
}
|
|
1896
|
-
function dependsOnStorage(appRoot) {
|
|
1897
|
-
const manifest = (0, node_path.join)(appRoot, "package.json");
|
|
1898
|
-
if (!(0, node_fs.existsSync)(manifest)) return false;
|
|
1899
|
-
try {
|
|
1900
|
-
const pkg = JSON.parse((0, node_fs.readFileSync)(manifest, "utf8"));
|
|
1901
|
-
return Boolean(pkg.dependencies?.["@geekmidas/storage"] ?? pkg.devDependencies?.["@geekmidas/storage"]);
|
|
1902
|
-
} catch {
|
|
1903
|
-
return false;
|
|
1904
|
-
}
|
|
1905
|
-
}
|
|
1906
|
-
//#endregion
|
|
1907
1805
|
//#region src/generators/FunctionGenerator.ts
|
|
1908
1806
|
var FunctionGenerator = class extends require_discover.ConstructGenerator {
|
|
1909
1807
|
isConstruct(value) {
|
|
@@ -1934,7 +1832,7 @@ var FunctionGenerator = class extends require_discover.ConstructGenerator {
|
|
|
1934
1832
|
const handlerFileName = `${exportName}.ts`;
|
|
1935
1833
|
const handlerPath = (0, node_path.join)(outputDir, handlerFileName);
|
|
1936
1834
|
const importPath = (0, node_path.relative)((0, node_path.dirname)(handlerPath), sourceFile).replace(/\.ts$/, ".js");
|
|
1937
|
-
const runtime =
|
|
1835
|
+
const runtime = require_OpenApiTsGenerator.runtimeFor(context, (0, node_path.dirname)(handlerPath), owner);
|
|
1938
1836
|
const content = `import { AWSLambdaFunction } from '@geekmidas/constructs/aws';
|
|
1939
1837
|
import { ${exportName} } from '${importPath}';
|
|
1940
1838
|
${runtime.imports}
|
|
@@ -1993,7 +1891,7 @@ var QueueGenerator = class extends require_discover.ConstructGenerator {
|
|
|
1993
1891
|
async generateQueueHandler(outputDir, sourceFile, exportName, context, owner) {
|
|
1994
1892
|
const handlerPath = (0, node_path.join)(outputDir, `${exportName}.ts`);
|
|
1995
1893
|
const importPath = (0, node_path.relative)((0, node_path.dirname)(handlerPath), sourceFile).replace(/\.ts$/, ".js");
|
|
1996
|
-
const runtime =
|
|
1894
|
+
const runtime = require_OpenApiTsGenerator.runtimeFor(context, (0, node_path.dirname)(handlerPath), owner);
|
|
1997
1895
|
const content = `import { AWSLambdaQueue } from '@geekmidas/constructs/aws';
|
|
1998
1896
|
import { ${exportName} } from '${importPath}';
|
|
1999
1897
|
${runtime.imports}
|
|
@@ -2173,7 +2071,7 @@ var SubscriberGenerator = class extends require_discover.ConstructGenerator {
|
|
|
2173
2071
|
const handlerFileName = `${exportName}.ts`;
|
|
2174
2072
|
const handlerPath = (0, node_path.join)(outputDir, handlerFileName);
|
|
2175
2073
|
const importPath = (0, node_path.relative)((0, node_path.dirname)(handlerPath), sourceFile).replace(/\.ts$/, ".js");
|
|
2176
|
-
const runtime =
|
|
2074
|
+
const runtime = require_OpenApiTsGenerator.runtimeFor(context, (0, node_path.dirname)(handlerPath), owner);
|
|
2177
2075
|
const content = `import { AWSLambdaSubscriber } from '@geekmidas/constructs/aws';
|
|
2178
2076
|
import { ${exportName} } from '${importPath}';
|
|
2179
2077
|
${runtime.imports}
|
|
@@ -2827,13 +2725,13 @@ async function buildApp(input) {
|
|
|
2827
2725
|
constructGlobs,
|
|
2828
2726
|
cacheBackend,
|
|
2829
2727
|
emailBackend: "ses",
|
|
2830
|
-
storageDrivers: driversFor({
|
|
2728
|
+
storageDrivers: require_ready.driversFor({
|
|
2831
2729
|
appRoot,
|
|
2832
|
-
cache: cacheBackendsIn(declared, cacheBackend)
|
|
2730
|
+
cache: require_ready.cacheBackendsIn(declared, cacheBackend)
|
|
2833
2731
|
}),
|
|
2834
2732
|
markOptional: input.markOptional ?? false
|
|
2835
2733
|
};
|
|
2836
|
-
const endpointGenerator = new
|
|
2734
|
+
const endpointGenerator = new require_OpenApiTsGenerator.EndpointGenerator();
|
|
2837
2735
|
const functionGenerator = new FunctionGenerator();
|
|
2838
2736
|
const cronGenerator = new CronGenerator();
|
|
2839
2737
|
const subscriberGenerator = new SubscriberGenerator();
|
|
@@ -8060,6 +7958,22 @@ function printStateDetails(state) {
|
|
|
8060
7958
|
}
|
|
8061
7959
|
}
|
|
8062
7960
|
//#endregion
|
|
7961
|
+
//#region src/migrate/names.ts
|
|
7962
|
+
/**
|
|
7963
|
+
* What a migration file is called: a UTC timestamp, then a name.
|
|
7964
|
+
*
|
|
7965
|
+
* Kept apart from the commands so the scaffold can name the migration it
|
|
7966
|
+
* writes the same way `gkm migration` does, without loading a migrator.
|
|
7967
|
+
*/
|
|
7968
|
+
/** UTC `YYYYMMDDHHmmss` — sorts as text, and reads as a date. */
|
|
7969
|
+
function stamp(now = /* @__PURE__ */ new Date()) {
|
|
7970
|
+
return now.toISOString().replace(/\.\d+Z$/, "").replace(/\D/g, "");
|
|
7971
|
+
}
|
|
7972
|
+
/** `Add workouts` → `add_workouts`: the part of a file name after its stamp. */
|
|
7973
|
+
function slug(name) {
|
|
7974
|
+
return name.trim().replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
7975
|
+
}
|
|
7976
|
+
//#endregion
|
|
8063
7977
|
//#region src/init/constructs.ts
|
|
8064
7978
|
/**
|
|
8065
7979
|
* What a scaffolded project declares, and the keys that derive from it.
|
|
@@ -8176,7 +8090,7 @@ export const database = new KyselyDatabase<Database, '${db.id}'>('${db.id}', {
|
|
|
8176
8090
|
*/
|
|
8177
8091
|
function usersMigration() {
|
|
8178
8092
|
return {
|
|
8179
|
-
path:
|
|
8093
|
+
path: `${(0, _geekmidas_manifest.migrationFolder)(databaseFor().id)}/${stamp()}_create_users.ts`,
|
|
8180
8094
|
content: `import type { Kysely } from 'kysely';
|
|
8181
8095
|
|
|
8182
8096
|
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
@@ -8339,7 +8253,7 @@ environment and fail in a way that looks like a config bug. Go through
|
|
|
8339
8253
|
\`gkm exec --\`, which is what injects them.`);
|
|
8340
8254
|
if (!isWorker) sections.push(endpointSection(options));
|
|
8341
8255
|
if (isWorker || isServerless) sections.push(backgroundSection(template));
|
|
8342
|
-
if (database) sections.push(databaseSection(), queriesSection());
|
|
8256
|
+
if (database) sections.push(databaseSection(), migrationsSection(), queriesSection());
|
|
8343
8257
|
sections.push(`## Environment
|
|
8344
8258
|
|
|
8345
8259
|
Environment is parsed once, through \`envkit\`, and never read from
|
|
@@ -8390,7 +8304,7 @@ function layoutBlock(options, template) {
|
|
|
8390
8304
|
const { database } = options.constructs;
|
|
8391
8305
|
const isWorker = template.name === "worker";
|
|
8392
8306
|
if (monorepo) return `constructs/ # every construct: the surface, the database, topics
|
|
8393
|
-
${apiPath}/ # the API app
|
|
8307
|
+
${database ? "db/<construct>/ # migrations, one folder per database construct\n" : ""}${apiPath}/ # the API app
|
|
8394
8308
|
gkm.config.ts # name, constructs glob, secrets`;
|
|
8395
8309
|
const lines = ["src/constructs/ # the surface, and anything it declares", "src/config/ # env parser, logger"];
|
|
8396
8310
|
if (!isWorker) lines.push("src/endpoints/ # HTTP handlers");
|
|
@@ -8398,7 +8312,7 @@ gkm.config.ts # name, constructs glob, secrets`;
|
|
|
8398
8312
|
lines.push("src/crons/ # scheduled work");
|
|
8399
8313
|
lines.push("src/subscribers/ # topic consumers");
|
|
8400
8314
|
}
|
|
8401
|
-
if (database) lines.push("
|
|
8315
|
+
if (database) lines.push("db/<construct>/ # migrations, one folder per database construct");
|
|
8402
8316
|
lines.push("gkm.config.ts # name, constructs glob, secrets");
|
|
8403
8317
|
return lines.join("\n");
|
|
8404
8318
|
}
|
|
@@ -8526,6 +8440,38 @@ export const monthly = router
|
|
|
8526
8440
|
Queries go through Kysely. Never open your own connection pool — the one the
|
|
8527
8441
|
construct provides is the one that gets credentials, pooling and shutdown.`;
|
|
8528
8442
|
}
|
|
8443
|
+
function migrationsSection() {
|
|
8444
|
+
return `## Migrations
|
|
8445
|
+
|
|
8446
|
+
Each database construct has its own folder, named after the construct:
|
|
8447
|
+
\`Database\` is \`db/database/\`, a tenant \`AuthDatabase\` is
|
|
8448
|
+
\`db/auth-database/\`. Each keeps its own history, in its own schema, and is
|
|
8449
|
+
migrated as its own **owner** role — the role a handler runs as cannot create a
|
|
8450
|
+
table.
|
|
8451
|
+
|
|
8452
|
+
\`\`\`bash
|
|
8453
|
+
gkm migration database add_workouts # db/database/20260930143012_add_workouts.ts
|
|
8454
|
+
gkm migration auth # Better Auth's schema change, as SQL
|
|
8455
|
+
gkm migrate # apply every folder, parents first
|
|
8456
|
+
gkm migrate database # apply one construct's
|
|
8457
|
+
\`\`\`
|
|
8458
|
+
|
|
8459
|
+
- Files are \`<UTC timestamp>_<name>.ts\` exporting \`up\` (and \`down\`), or
|
|
8460
|
+
\`.sql\`, which runs whole. Never rename or edit one that has been applied —
|
|
8461
|
+
it is recorded by name. Change the schema with a new migration.
|
|
8462
|
+
- Write migrations with \`Kysely<unknown>\` and snake_case names: they are the
|
|
8463
|
+
SQL, and the camelCase plugin is the app's, not theirs.
|
|
8464
|
+
- \`gkm migration auth\` compares Better Auth's schema to its tenant and writes
|
|
8465
|
+
only the difference. Run it after adding a Better Auth plugin, and commit the
|
|
8466
|
+
file.
|
|
8467
|
+
- Tests migrate themselves: the root vitest config's
|
|
8468
|
+
\`globalSetup: ['@geekmidas/cli/vitest']\` applies every folder to the test
|
|
8469
|
+
stage before any test runs. \`gkm dev\` only reports what is pending.
|
|
8470
|
+
- A deploy migrates before the new version goes live, so the old version runs
|
|
8471
|
+
against the new schema until it is replaced. Keep each migration compatible
|
|
8472
|
+
with the code before it: add a column in one release, stop using the old one,
|
|
8473
|
+
drop it in a later release.`;
|
|
8474
|
+
}
|
|
8529
8475
|
function queriesSection() {
|
|
8530
8476
|
return `## Writing queries and relations
|
|
8531
8477
|
|
|
@@ -8675,7 +8621,6 @@ const DEPENDENCY_VERSIONS = {
|
|
|
8675
8621
|
hono: "~4.13.9",
|
|
8676
8622
|
"@hono/node-server": "~1.19.17",
|
|
8677
8623
|
kysely: "~0.29.6",
|
|
8678
|
-
"kysely-ctl": "~0.21.0",
|
|
8679
8624
|
pg: "~8.23.0",
|
|
8680
8625
|
pino: "~10.3.1",
|
|
8681
8626
|
zod: "~4.6.5",
|
|
@@ -8782,26 +8727,26 @@ function loadPackageJson() {
|
|
|
8782
8727
|
* Run: pnpm --filter @geekmidas/cli sync-versions
|
|
8783
8728
|
*/
|
|
8784
8729
|
const GEEKMIDAS_VERSIONS = {
|
|
8785
|
-
"@geekmidas/audit": "~10.0.0-alpha.
|
|
8786
|
-
"@geekmidas/auth": "~10.0.0-alpha.
|
|
8787
|
-
"@geekmidas/cache": "~10.0.0-alpha.
|
|
8788
|
-
"@geekmidas/client": "~10.0.0-alpha.
|
|
8789
|
-
"@geekmidas/cloud": "~10.0.0-alpha.
|
|
8790
|
-
"@geekmidas/constructs": "~10.0.0-alpha.
|
|
8791
|
-
"@geekmidas/db": "~10.0.0-alpha.
|
|
8792
|
-
"@geekmidas/emailkit": "~10.0.0-alpha.
|
|
8793
|
-
"@geekmidas/envkit": "~10.0.0-alpha.
|
|
8794
|
-
"@geekmidas/errors": "~10.0.0-alpha.
|
|
8795
|
-
"@geekmidas/events": "~10.0.0-alpha.
|
|
8796
|
-
"@geekmidas/logger": "~10.0.0-alpha.
|
|
8797
|
-
"@geekmidas/manifest": "~10.0.0-alpha.
|
|
8798
|
-
"@geekmidas/rate-limit": "~10.0.0-alpha.
|
|
8799
|
-
"@geekmidas/schema": "~10.0.0-alpha.
|
|
8800
|
-
"@geekmidas/services": "~10.0.0-alpha.
|
|
8801
|
-
"@geekmidas/storage": "~10.0.0-alpha.
|
|
8802
|
-
"@geekmidas/studio": "~10.0.0-alpha.
|
|
8803
|
-
"@geekmidas/telescope": "~10.0.0-alpha.
|
|
8804
|
-
"@geekmidas/testkit": "~10.0.0-alpha.
|
|
8730
|
+
"@geekmidas/audit": "~10.0.0-alpha.30",
|
|
8731
|
+
"@geekmidas/auth": "~10.0.0-alpha.30",
|
|
8732
|
+
"@geekmidas/cache": "~10.0.0-alpha.30",
|
|
8733
|
+
"@geekmidas/client": "~10.0.0-alpha.30",
|
|
8734
|
+
"@geekmidas/cloud": "~10.0.0-alpha.30",
|
|
8735
|
+
"@geekmidas/constructs": "~10.0.0-alpha.30",
|
|
8736
|
+
"@geekmidas/db": "~10.0.0-alpha.30",
|
|
8737
|
+
"@geekmidas/emailkit": "~10.0.0-alpha.30",
|
|
8738
|
+
"@geekmidas/envkit": "~10.0.0-alpha.30",
|
|
8739
|
+
"@geekmidas/errors": "~10.0.0-alpha.30",
|
|
8740
|
+
"@geekmidas/events": "~10.0.0-alpha.30",
|
|
8741
|
+
"@geekmidas/logger": "~10.0.0-alpha.30",
|
|
8742
|
+
"@geekmidas/manifest": "~10.0.0-alpha.30",
|
|
8743
|
+
"@geekmidas/rate-limit": "~10.0.0-alpha.30",
|
|
8744
|
+
"@geekmidas/schema": "~10.0.0-alpha.30",
|
|
8745
|
+
"@geekmidas/services": "~10.0.0-alpha.30",
|
|
8746
|
+
"@geekmidas/storage": "~10.0.0-alpha.30",
|
|
8747
|
+
"@geekmidas/studio": "~10.0.0-alpha.30",
|
|
8748
|
+
"@geekmidas/telescope": "~10.0.0-alpha.30",
|
|
8749
|
+
"@geekmidas/testkit": "~10.0.0-alpha.30",
|
|
8805
8750
|
"@geekmidas/cli": `~${loadPackageJson().version}`
|
|
8806
8751
|
};
|
|
8807
8752
|
//#endregion
|
|
@@ -8819,9 +8764,7 @@ function generateAuthAppFiles(options) {
|
|
|
8819
8764
|
type: "module",
|
|
8820
8765
|
scripts: {
|
|
8821
8766
|
dev: "gkm dev",
|
|
8822
|
-
build: "gkm build"
|
|
8823
|
-
"db:migrate": "gkm exec -- npx @better-auth/cli migrate",
|
|
8824
|
-
"db:generate": "gkm exec -- npx @better-auth/cli generate"
|
|
8767
|
+
build: "gkm build"
|
|
8825
8768
|
},
|
|
8826
8769
|
dependencies: {
|
|
8827
8770
|
[`@${options.name}/models`]: "workspace:*",
|
|
@@ -8901,16 +8844,24 @@ function stageEnv(stages) {
|
|
|
8901
8844
|
//#endregion
|
|
8902
8845
|
//#region src/init/generators/config.ts
|
|
8903
8846
|
/**
|
|
8904
|
-
* Vitest config
|
|
8847
|
+
* Vitest config for a database-enabled app.
|
|
8848
|
+
*
|
|
8849
|
+
* On its own, it is the root config and carries gkm's setup: the test stage
|
|
8850
|
+
* reconciled and every construct migrated, however the suite starts. In a
|
|
8851
|
+
* workspace the root config carries it instead — once, so no project filter
|
|
8852
|
+
* can skip it and nothing runs it twice.
|
|
8905
8853
|
*/
|
|
8906
|
-
const
|
|
8854
|
+
const vitestConfigFor = (monorepo) => `import { defineConfig } from 'vitest/config';
|
|
8907
8855
|
|
|
8908
8856
|
export default defineConfig({
|
|
8909
8857
|
// Vite resolves tsconfig \`paths\` itself; no plugin needed.
|
|
8910
8858
|
resolve: { tsconfigPaths: true },
|
|
8911
8859
|
test: {
|
|
8912
|
-
environment: 'node'
|
|
8913
|
-
|
|
8860
|
+
environment: 'node',${monorepo ? "" : `
|
|
8861
|
+
// The test stage, reconciled and every database construct's migrations
|
|
8862
|
+
// applied, before any test runs — whether \`gkm test\` or plain
|
|
8863
|
+
// \`vitest\` started it.
|
|
8864
|
+
globalSetup: ['@geekmidas/cli/vitest'],`}
|
|
8914
8865
|
},
|
|
8915
8866
|
});
|
|
8916
8867
|
`;
|
|
@@ -8988,7 +8939,7 @@ export default defineConfig({${stagesBlock(options.stages)}${options.monorepo ?
|
|
|
8988
8939
|
}];
|
|
8989
8940
|
if (options.constructs.database) files.push({
|
|
8990
8941
|
path: "vitest.config.ts",
|
|
8991
|
-
content:
|
|
8942
|
+
content: vitestConfigFor(Boolean(options.monorepo))
|
|
8992
8943
|
});
|
|
8993
8944
|
return files;
|
|
8994
8945
|
}
|
|
@@ -9078,7 +9029,7 @@ export default defineConfig({${stagesBlock(options.stages)}${options.monorepo ?
|
|
|
9078
9029
|
];
|
|
9079
9030
|
if (options.constructs.database) files.push({
|
|
9080
9031
|
path: "vitest.config.ts",
|
|
9081
|
-
content:
|
|
9032
|
+
content: vitestConfigFor(Boolean(options.monorepo))
|
|
9082
9033
|
});
|
|
9083
9034
|
return files;
|
|
9084
9035
|
}
|
|
@@ -9114,7 +9065,7 @@ function generateSingleAppConfigFiles(options, _template, _helpers) {
|
|
|
9114
9065
|
}];
|
|
9115
9066
|
if (options.constructs.database) files.push({
|
|
9116
9067
|
path: "vitest.config.ts",
|
|
9117
|
-
content:
|
|
9068
|
+
content: vitestConfigFor(Boolean(options.monorepo))
|
|
9118
9069
|
});
|
|
9119
9070
|
return files;
|
|
9120
9071
|
}
|
|
@@ -10561,8 +10512,12 @@ ${isSst ? "\n# SST\n.sst/\n" : ""}`;
|
|
|
10561
10512
|
|
|
10562
10513
|
export default defineConfig({
|
|
10563
10514
|
test: {
|
|
10564
|
-
//
|
|
10565
|
-
//
|
|
10515
|
+
// The test stage, reconciled and every database construct's migrations
|
|
10516
|
+
// applied, before any project's tests start — declared here, once, so no
|
|
10517
|
+
// project filter can skip it and nothing runs it twice.
|
|
10518
|
+
globalSetup: ['@geekmidas/cli/vitest'],
|
|
10519
|
+
// Each app and package is its own project, so its own config — its path
|
|
10520
|
+
// aliases — applies to its own tests.
|
|
10566
10521
|
projects: ['apps/*', 'packages/*'],
|
|
10567
10522
|
// The shared packages ship without tests of their own.
|
|
10568
10523
|
passWithNoTests: true,
|
|
@@ -10780,6 +10735,7 @@ export const database = new KyselyDatabase<Database, '${db.id}'>('${db.id}', {
|
|
|
10780
10735
|
export const authDb = database.schema<Record<string, never>, 'AuthDb'>('AuthDb');
|
|
10781
10736
|
`
|
|
10782
10737
|
});
|
|
10738
|
+
files.push(usersMigration());
|
|
10783
10739
|
files.push({
|
|
10784
10740
|
path: "constructs/auth.ts",
|
|
10785
10741
|
content: `import { BetterAuth } from '@geekmidas/constructs/auth';
|
|
@@ -11168,7 +11124,6 @@ export const router = api${options.constructs.database ? ".database(database)" :
|
|
|
11168
11124
|
`
|
|
11169
11125
|
});
|
|
11170
11126
|
if (options.constructs.database && declares) files.push(...databaseFiles());
|
|
11171
|
-
else if (options.constructs.database) files.push(usersMigration());
|
|
11172
11127
|
if (uploads && declares) files.push({
|
|
11173
11128
|
path: "src/constructs/storage.ts",
|
|
11174
11129
|
content: `import { ObjectStorage } from '@geekmidas/constructs/object-storage';
|
|
@@ -11936,7 +11891,6 @@ function generatePackageJson(options, template) {
|
|
|
11936
11891
|
devDependencies["@types/pg"] = DEPENDENCY_VERSIONS["@types/pg"];
|
|
11937
11892
|
devDependencies["@geekmidas/testkit"] = GEEKMIDAS_VERSIONS["@geekmidas/testkit"];
|
|
11938
11893
|
devDependencies["@faker-js/faker"] = DEPENDENCY_VERSIONS["@faker-js/faker"];
|
|
11939
|
-
devDependencies["kysely-ctl"] = DEPENDENCY_VERSIONS["kysely-ctl"];
|
|
11940
11894
|
}
|
|
11941
11895
|
if (monorepo) {
|
|
11942
11896
|
delete devDependencies["@biomejs/biome"];
|
|
@@ -11987,38 +11941,16 @@ function generateSourceFiles(options, template) {
|
|
|
11987
11941
|
//#region src/init/generators/test.ts
|
|
11988
11942
|
/**
|
|
11989
11943
|
* Generate test infrastructure files when database is enabled.
|
|
11990
|
-
* Includes transaction-isolated test config,
|
|
11944
|
+
* Includes transaction-isolated test config,
|
|
11991
11945
|
* factory system with builders/seeds, and an example spec.
|
|
11992
11946
|
*/
|
|
11993
11947
|
function generateTestFiles(options, _template) {
|
|
11994
11948
|
if (!options.constructs.database) return [];
|
|
11995
11949
|
const db = databaseFor();
|
|
11996
11950
|
const schema = !options.monorepo ? "../src/constructs/database.ts" : `@${options.name}/constructs/database.ts`;
|
|
11997
|
-
|
|
11998
|
-
|
|
11951
|
+
db.urlKey;
|
|
11952
|
+
db.ownerUrlKey;
|
|
11999
11953
|
return [
|
|
12000
|
-
{
|
|
12001
|
-
path: "kysely.config.ts",
|
|
12002
|
-
content: `import { Credentials } from '@geekmidas/envkit/credentials';
|
|
12003
|
-
import { PostgresDialect } from 'kysely';
|
|
12004
|
-
import { defineConfig } from 'kysely-ctl';
|
|
12005
|
-
import pg from 'pg';
|
|
12006
|
-
|
|
12007
|
-
// The owner role's URL — the one that may create, alter, and drop. Both keys
|
|
12008
|
-
// are published by the declared database construct; run this under
|
|
12009
|
-
// \`gkm exec -- pnpm kysely migrate:latest\` so they are injected.
|
|
12010
|
-
const url = Credentials.${ownerUrl} ?? Credentials.${runtimeUrl};
|
|
12011
|
-
|
|
12012
|
-
export default defineConfig({
|
|
12013
|
-
dialect: new PostgresDialect({
|
|
12014
|
-
pool: new pg.Pool({ connectionString: url }),
|
|
12015
|
-
}),
|
|
12016
|
-
migrations: {
|
|
12017
|
-
migrationFolder: './src/db/migrations',
|
|
12018
|
-
},
|
|
12019
|
-
});
|
|
12020
|
-
`
|
|
12021
|
-
},
|
|
12022
11954
|
{
|
|
12023
11955
|
path: "test/config.ts",
|
|
12024
11956
|
content: `import { it as itVitest } from 'vitest';
|
|
@@ -12030,44 +11962,6 @@ export const it = wrapVitestKyselyTransaction<Database>(itVitest, {
|
|
|
12030
11962
|
// the plugins it was given, \`CamelCasePlugin\` included.
|
|
12031
11963
|
connection: database,
|
|
12032
11964
|
});
|
|
12033
|
-
`
|
|
12034
|
-
},
|
|
12035
|
-
{
|
|
12036
|
-
path: "test/globalSetup.ts",
|
|
12037
|
-
content: `import fs from 'node:fs/promises';
|
|
12038
|
-
import path from 'node:path';
|
|
12039
|
-
import { Credentials } from '@geekmidas/envkit/credentials';
|
|
12040
|
-
import { PostgresKyselyMigrator } from '@geekmidas/testkit/kysely';
|
|
12041
|
-
import { Kysely, PostgresDialect } from 'kysely';
|
|
12042
|
-
import { FileMigrationProvider } from 'kysely/migration';
|
|
12043
|
-
import pg from 'pg';
|
|
12044
|
-
|
|
12045
|
-
export default async function globalSetup() {
|
|
12046
|
-
// \`gkm test\` reconciles the test stage before this runs: the container is
|
|
12047
|
-
// up, the database exists, and its roles are created. What is left is the
|
|
12048
|
-
// schema, which is what migrations are for.
|
|
12049
|
-
const databaseUrl = Credentials.${ownerUrl} ?? Credentials.${runtimeUrl}!;
|
|
12050
|
-
const migrationFolder = path.resolve(import.meta.dirname, '../src/db/migrations');
|
|
12051
|
-
|
|
12052
|
-
const db = new Kysely({
|
|
12053
|
-
dialect: new PostgresDialect({
|
|
12054
|
-
pool: new pg.Pool({ connectionString: databaseUrl }),
|
|
12055
|
-
}),
|
|
12056
|
-
});
|
|
12057
|
-
|
|
12058
|
-
const migrator = new PostgresKyselyMigrator({
|
|
12059
|
-
uri: databaseUrl,
|
|
12060
|
-
db,
|
|
12061
|
-
provider: new FileMigrationProvider({
|
|
12062
|
-
fs,
|
|
12063
|
-
path,
|
|
12064
|
-
migrationFolder,
|
|
12065
|
-
}),
|
|
12066
|
-
});
|
|
12067
|
-
|
|
12068
|
-
const teardown = await migrator.start();
|
|
12069
|
-
return teardown;
|
|
12070
|
-
}
|
|
12071
11965
|
`
|
|
12072
11966
|
},
|
|
12073
11967
|
{
|
|
@@ -14695,10 +14589,14 @@ function printNextSteps(projectName, options, pkgManager) {
|
|
|
14695
14589
|
console.log("Next steps:\n");
|
|
14696
14590
|
console.log(` ${cdCommand}`);
|
|
14697
14591
|
console.log(` ${devCommand}`);
|
|
14698
|
-
if (
|
|
14592
|
+
if (options.constructs.database) {
|
|
14699
14593
|
console.log("");
|
|
14700
|
-
console.log(" #
|
|
14701
|
-
console.log(
|
|
14594
|
+
console.log(" # Apply each database construct's db/<construct>/ migrations:");
|
|
14595
|
+
console.log(" gkm migrate");
|
|
14596
|
+
if (options.monorepo) {
|
|
14597
|
+
console.log(" # Better Auth's tables, as its tenant's first migration:");
|
|
14598
|
+
console.log(" gkm migration auth && gkm migrate");
|
|
14599
|
+
}
|
|
14702
14600
|
}
|
|
14703
14601
|
console.log("");
|
|
14704
14602
|
if (options.monorepo) {
|
|
@@ -15050,7 +14948,7 @@ var NoStoredSecrets = class extends Error {
|
|
|
15050
14948
|
}
|
|
15051
14949
|
};
|
|
15052
14950
|
async function reconcile(secrets, workspace, stage) {
|
|
15053
|
-
const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-
|
|
14951
|
+
const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-BhiMkRuA.cjs")).then((n) => n.workspace_exports);
|
|
15054
14952
|
const result = require_reconcile.reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
15055
14953
|
return result ? {
|
|
15056
14954
|
secrets: result.secrets,
|
|
@@ -15349,231 +15247,6 @@ function printSummary(workspace, stage) {
|
|
|
15349
15247
|
logger$1.log("");
|
|
15350
15248
|
}
|
|
15351
15249
|
//#endregion
|
|
15352
|
-
//#region src/test/harness.ts
|
|
15353
|
-
/**
|
|
15354
|
-
* What `gkm test` writes for a feature test to be built from.
|
|
15355
|
-
*
|
|
15356
|
-
* `gkm test` already discovers an app's constructs and resolves the test
|
|
15357
|
-
* stage's environment; this keeps both, under `.gkm/test/`:
|
|
15358
|
-
*
|
|
15359
|
-
* - `manifest.json` — which construct and endpoint is exported where, and the
|
|
15360
|
-
* environment the test stage resolved, keyed as the constructs derive keys.
|
|
15361
|
-
* - `clients/<surface>.ts` — each surface's typed client, from the same
|
|
15362
|
-
* generator `gkm build` uses for the app's own.
|
|
15363
|
-
* - `index.ts` — the configured `it`, and a `Browser` holding one client per
|
|
15364
|
-
* surface and one per auth server, each on the browser's `fetch`.
|
|
15365
|
-
*
|
|
15366
|
-
* A test imports `it` from there and declares nothing — not a construct, not an
|
|
15367
|
-
* environment key, not a client.
|
|
15368
|
-
*/
|
|
15369
|
-
/**
|
|
15370
|
-
* The variable the manifest's path travels in — what
|
|
15371
|
-
* `@geekmidas/constructs/testing` reads. Its own constant rather than an import:
|
|
15372
|
-
* that entry carries the whole harness (MSW, Hono, Vitest), and importing it
|
|
15373
|
-
* here put all of it on the start-up path of every `gkm` command.
|
|
15374
|
-
*/
|
|
15375
|
-
const TEST_MANIFEST_ENV = "GKM_TEST_MANIFEST";
|
|
15376
|
-
/** Where the harness lives, relative to the workspace root. */
|
|
15377
|
-
const TEST_HARNESS_DIR = ".gkm/test";
|
|
15378
|
-
/**
|
|
15379
|
-
* Better-auth's client plugin for each server plugin id.
|
|
15380
|
-
*
|
|
15381
|
-
* Written down rather than derived: the ids and the client names do not follow
|
|
15382
|
-
* one rule (`admin` pairs with `adminClient`, `email-otp` with
|
|
15383
|
-
* `emailOTPClient`), and a guess that missed would hand a test a client
|
|
15384
|
-
* silently lacking every method of that plugin. A server plugin not listed has
|
|
15385
|
-
* no client counterpart — `bearer`, `open-api` — or needs one from another
|
|
15386
|
-
* package, and is left out.
|
|
15387
|
-
*/
|
|
15388
|
-
const CLIENT_PLUGINS = {
|
|
15389
|
-
admin: "adminClient",
|
|
15390
|
-
anonymous: "anonymousClient",
|
|
15391
|
-
"device-authorization": "deviceAuthorizationClient",
|
|
15392
|
-
"email-otp": "emailOTPClient",
|
|
15393
|
-
jwt: "jwtClient",
|
|
15394
|
-
"last-login-method": "lastLoginMethodClient",
|
|
15395
|
-
"magic-link": "magicLinkClient",
|
|
15396
|
-
"multi-session": "multiSessionClient",
|
|
15397
|
-
"oauth-popup": "oauthPopupClient",
|
|
15398
|
-
"one-tap": "oneTapClient",
|
|
15399
|
-
"one-time-token": "oneTimeTokenClient",
|
|
15400
|
-
organization: "organizationClient",
|
|
15401
|
-
"phone-number": "phoneNumberClient",
|
|
15402
|
-
siwe: "siweClient",
|
|
15403
|
-
"two-factor": "twoFactorClient",
|
|
15404
|
-
username: "usernameClient"
|
|
15405
|
-
};
|
|
15406
|
-
/** Write the manifest, the clients and the harness; return the manifest path. */
|
|
15407
|
-
async function writeTestHarness(options) {
|
|
15408
|
-
const sources = {};
|
|
15409
|
-
const declared = await require_discover.discover({
|
|
15410
|
-
patterns: options.patterns,
|
|
15411
|
-
cwd: options.root,
|
|
15412
|
-
sources
|
|
15413
|
-
});
|
|
15414
|
-
const endpoints = (await new require_openapi.EndpointGenerator().load(options.patterns, options.root)).map(({ key, construct, path }) => ({
|
|
15415
|
-
surface: construct.owner ?? construct.surface?.id,
|
|
15416
|
-
construct,
|
|
15417
|
-
source: {
|
|
15418
|
-
file: path.absolute,
|
|
15419
|
-
export: key
|
|
15420
|
-
}
|
|
15421
|
-
})).filter((endpoint) => endpoint.surface !== void 0);
|
|
15422
|
-
const manifest = {
|
|
15423
|
-
stage: options.stage,
|
|
15424
|
-
constructs: Object.fromEntries(Object.entries(declared).flatMap(([id, declaration]) => {
|
|
15425
|
-
const source = sources[id];
|
|
15426
|
-
return source ? [[id, {
|
|
15427
|
-
kind: declaration.kind,
|
|
15428
|
-
source: {
|
|
15429
|
-
file: source.file,
|
|
15430
|
-
export: source.exportName
|
|
15431
|
-
}
|
|
15432
|
-
}]] : [];
|
|
15433
|
-
})),
|
|
15434
|
-
endpoints: endpoints.map(({ surface, source }) => ({
|
|
15435
|
-
surface,
|
|
15436
|
-
source
|
|
15437
|
-
})),
|
|
15438
|
-
env: options.env
|
|
15439
|
-
};
|
|
15440
|
-
const surfaces = [...new Set(endpoints.map(({ surface }) => surface))];
|
|
15441
|
-
const generator = new require_openapi.OpenApiTsGenerator();
|
|
15442
|
-
const clients = await Promise.all(surfaces.map(async (surface) => ({
|
|
15443
|
-
file: `${(0, _geekmidas_manifest.kebabCase)(surface)}.ts`,
|
|
15444
|
-
content: await generator.generate(endpoints.filter((endpoint) => endpoint.surface === surface).map(({ construct }) => construct), {
|
|
15445
|
-
title: surface,
|
|
15446
|
-
version: "1.0.0"
|
|
15447
|
-
})
|
|
15448
|
-
})));
|
|
15449
|
-
const auths = await authClients(declared, sources, options.env);
|
|
15450
|
-
const drivers = driversFor({
|
|
15451
|
-
appRoot: options.root,
|
|
15452
|
-
cache: cacheBackendsIn(declared, options.cacheBackend)
|
|
15453
|
-
});
|
|
15454
|
-
const database = await databaseSource(declared, sources, endpoints.map(({ construct }) => construct));
|
|
15455
|
-
const files = [.../* @__PURE__ */ new Set([...Object.values(manifest.constructs).map(({ source }) => source.file), ...manifest.endpoints.map(({ source }) => source.file)])];
|
|
15456
|
-
const harnessFor = (dir) => harnessModule({
|
|
15457
|
-
dir,
|
|
15458
|
-
files,
|
|
15459
|
-
database,
|
|
15460
|
-
surfaces: surfaces.map((id, index) => ({
|
|
15461
|
-
id,
|
|
15462
|
-
secured: clients[index].content.includes("authStrategies:")
|
|
15463
|
-
})),
|
|
15464
|
-
auths,
|
|
15465
|
-
drivers
|
|
15466
|
-
});
|
|
15467
|
-
const json = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
15468
|
-
return (await Promise.all([...new Set(options.targets)].map(async (target) => {
|
|
15469
|
-
const dir = (0, node_path.join)(target, TEST_HARNESS_DIR);
|
|
15470
|
-
await (0, node_fs_promises.rm)(dir, {
|
|
15471
|
-
recursive: true,
|
|
15472
|
-
force: true
|
|
15473
|
-
});
|
|
15474
|
-
await (0, node_fs_promises.mkdir)((0, node_path.join)(dir, "clients"), { recursive: true });
|
|
15475
|
-
await (0, node_fs_promises.writeFile)((0, node_path.join)(dir, "manifest.json"), json);
|
|
15476
|
-
await (0, node_fs_promises.writeFile)((0, node_path.join)(dir, "index.ts"), harnessFor(dir));
|
|
15477
|
-
for (const client of clients) await (0, node_fs_promises.writeFile)((0, node_path.join)(dir, "clients", client.file), client.content);
|
|
15478
|
-
return (0, node_path.join)(dir, "manifest.json");
|
|
15479
|
-
})))[0];
|
|
15480
|
-
}
|
|
15481
|
-
/** Each auth server, with the client plugins its server plugins pair with. */
|
|
15482
|
-
async function authClients(declared, sources, env) {
|
|
15483
|
-
const clients = [];
|
|
15484
|
-
const envParser = new _geekmidas_envkit.EnvironmentParser({ ...env });
|
|
15485
|
-
for (const [id, declaration] of Object.entries(declared)) {
|
|
15486
|
-
const source = sources[id];
|
|
15487
|
-
if (declaration.kind !== "rest-api" || !source) continue;
|
|
15488
|
-
const exported = (await import(source.file))[source.exportName];
|
|
15489
|
-
if (!isBetterAuth(exported) || exported.id !== id) continue;
|
|
15490
|
-
const ids = await exported.pluginIds({
|
|
15491
|
-
envParser,
|
|
15492
|
-
context: _geekmidas_services.serviceContext
|
|
15493
|
-
});
|
|
15494
|
-
clients.push({
|
|
15495
|
-
id,
|
|
15496
|
-
basePath: exported.basePath,
|
|
15497
|
-
plugins: ids.flatMap((plugin) => CLIENT_PLUGINS[plugin] ?? [])
|
|
15498
|
-
});
|
|
15499
|
-
}
|
|
15500
|
-
return clients;
|
|
15501
|
-
}
|
|
15502
|
-
function isBetterAuth(value) {
|
|
15503
|
-
return typeof value === "object" && value !== null && "pluginIds" in value && "basePath" in value;
|
|
15504
|
-
}
|
|
15505
|
-
/** `Api` → `api`, `AdminApi` → `adminApi`: a construct id as a property name. */
|
|
15506
|
-
function propertyOf(id) {
|
|
15507
|
-
return id.charAt(0).toLowerCase() + id.slice(1);
|
|
15508
|
-
}
|
|
15509
|
-
/**
|
|
15510
|
-
* The database a test's `db` is — the one the endpoints were given with
|
|
15511
|
-
* `.database()`, found by the service they hold, which is the construct's own.
|
|
15512
|
-
* The same rule `featureTest` applies at runtime, applied here so `db` can be
|
|
15513
|
-
* typed by the construct's schema.
|
|
15514
|
-
*/
|
|
15515
|
-
async function databaseSource(declared, sources, endpoints) {
|
|
15516
|
-
const services = new Set(endpoints.map((endpoint) => endpoint.databaseService).filter(Boolean));
|
|
15517
|
-
if (services.size === 0) return void 0;
|
|
15518
|
-
for (const [id, declaration] of Object.entries(declared)) {
|
|
15519
|
-
const source = sources[id];
|
|
15520
|
-
if (!source || !declaration.kind.startsWith("database")) continue;
|
|
15521
|
-
const exported = (await import(source.file))[source.exportName];
|
|
15522
|
-
if (services.has(exported?.service)) return source;
|
|
15523
|
-
}
|
|
15524
|
-
}
|
|
15525
|
-
/** An import specifier for `file`, from the harness written into `dir`. */
|
|
15526
|
-
function specifierFrom(dir, file) {
|
|
15527
|
-
const path = (0, node_path.relative)(dir, file).replace(/\.tsx?$/, ".js");
|
|
15528
|
-
return path.startsWith(".") ? path : `./${path}`;
|
|
15529
|
-
}
|
|
15530
|
-
function harnessModule(options) {
|
|
15531
|
-
const { surfaces, auths, drivers, database, dir, files } = options;
|
|
15532
|
-
const plugins = [...new Set(auths.flatMap(({ plugins }) => plugins))].sort();
|
|
15533
|
-
const imports = [
|
|
15534
|
-
`import {${database ? " type DatabaseOf," : ""} featureTest, loadTestManifest } from '@geekmidas/constructs/testing';`,
|
|
15535
|
-
...database ? [`import type { ${database.exportName} as __database } from '${specifierFrom(dir, database.file)}';`] : [],
|
|
15536
|
-
`import { Browser as TestBrowser } from '@geekmidas/testkit/browser';`,
|
|
15537
|
-
...surfaces.map(({ id }) => `import { createApi as create${id} } from './clients/${(0, _geekmidas_manifest.kebabCase)(id)}.js';`),
|
|
15538
|
-
...auths.length ? [`import { createAuthClient } from 'better-auth/client';`] : [],
|
|
15539
|
-
...plugins.length ? [`import { ${plugins.join(", ")} } from 'better-auth/client/plugins';`] : [],
|
|
15540
|
-
...drivers.imports ? [drivers.imports] : [],
|
|
15541
|
-
...files.map((file, index) => `import * as __module${index} from '${specifierFrom(dir, file)}';`)
|
|
15542
|
-
];
|
|
15543
|
-
const url = (id) => `manifest.env[${JSON.stringify((0, _geekmidas_manifest.provideKey)(id, "url"))}]!`;
|
|
15544
|
-
const members = [...surfaces.map(({ id, secured }) => ` /** \`${id}\`, at ${(0, _geekmidas_manifest.provideKey)(id, "url")}. */
|
|
15545
|
-
readonly ${propertyOf(id)} = create${id}({
|
|
15546
|
-
baseURL: ${url(id)},
|
|
15547
|
-
fetch: this.fetch,${secured ? `
|
|
15548
|
-
// Served in-process: no authorizer runs, so nothing is signed. A scheme
|
|
15549
|
-
// with no strategy sends its request without credentials.
|
|
15550
|
-
authStrategies: {} as never,` : ""}
|
|
15551
|
-
});`), ...auths.map((auth) => ` /** \`${auth.id}\`, at ${(0, _geekmidas_manifest.provideKey)(auth.id, "url")}. */
|
|
15552
|
-
readonly ${propertyOf(auth.id)} = createAuthClient({
|
|
15553
|
-
baseURL: \`\${${url(auth.id)}}${auth.basePath}\`,
|
|
15554
|
-
plugins: [${auth.plugins.map((plugin) => `${plugin}()`).join(", ")}],
|
|
15555
|
-
fetchOptions: { customFetchImpl: this.fetch },
|
|
15556
|
-
});`)];
|
|
15557
|
-
return `// Generated by \`gkm test\` from the app's constructs — do not edit.
|
|
15558
|
-
${imports.join("\n")}
|
|
15559
|
-
|
|
15560
|
-
${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));
|
|
15561
|
-
|
|
15562
|
-
/** A browser with a client for every surface the app declares. */
|
|
15563
|
-
export class Browser extends TestBrowser {
|
|
15564
|
-
${members.join("\n\n")}
|
|
15565
|
-
}
|
|
15566
|
-
|
|
15567
|
-
/** The app's modules, by the path the manifest records for each. */
|
|
15568
|
-
const modules = {
|
|
15569
|
-
${files.map((file, index) => `\t${JSON.stringify(file)}: __module${index},`).join("\n")}
|
|
15570
|
-
};
|
|
15571
|
-
|
|
15572
|
-
/** \`it\`, for a test that drives the app the way it runs deployed. */
|
|
15573
|
-
export const it = featureTest${database ? "<Browser, DatabaseOf<typeof __database>>" : ""}({ manifest, modules, browser: Browser });
|
|
15574
|
-
`;
|
|
15575
|
-
}
|
|
15576
|
-
//#endregion
|
|
15577
15250
|
//#region src/test/index.ts
|
|
15578
15251
|
/**
|
|
15579
15252
|
* Run tests with secrets, dependency URLs, and .env files loaded.
|
|
@@ -15613,7 +15286,7 @@ async function testCommand(options = {}) {
|
|
|
15613
15286
|
const preloadPath = (0, node_path.join)(gkmDir, "test-credentials-preload.ts");
|
|
15614
15287
|
await require_workspace$1.createCredentialsPreload(preloadPath, result.secretsJsonPath);
|
|
15615
15288
|
const workspace = await require_config.loadWorkspaceConfig(cwd).then((loaded) => loaded.workspace).catch(() => void 0);
|
|
15616
|
-
const manifestPath = workspace ? await writeTestHarness({
|
|
15289
|
+
const manifestPath = workspace ? await require_ready.writeTestHarness({
|
|
15617
15290
|
root: workspace.root,
|
|
15618
15291
|
targets: [workspace.root, ...Object.values(workspace.apps).map((app) => (0, node_path.isAbsolute)(app.path) ? app.path : (0, node_path.join)(workspace.root, app.path))],
|
|
15619
15292
|
patterns: require_workspace$1.constructGlobs(workspace),
|
|
@@ -15625,6 +15298,16 @@ async function testCommand(options = {}) {
|
|
|
15625
15298
|
console.log(manifestPath ? ` 🧪 Test harness written beside ${manifestPath}` : " 🧪 Nothing to prepare: this project declares no constructs");
|
|
15626
15299
|
return;
|
|
15627
15300
|
}
|
|
15301
|
+
if (workspace) await migrateTestStage(workspace, result.credentials);
|
|
15302
|
+
const readyPath = (0, node_path.join)(cwd, require_ready.TEST_READY_FILE);
|
|
15303
|
+
await (0, node_fs_promises.writeFile)(readyPath, JSON.stringify({
|
|
15304
|
+
env: result.secretsJsonPath,
|
|
15305
|
+
...manifestPath ? { manifest: manifestPath } : {}
|
|
15306
|
+
}, null, 2));
|
|
15307
|
+
if (options.setup) {
|
|
15308
|
+
console.log(` 🧪 Test stage ready: ${require_ready.TEST_READY_FILE}`);
|
|
15309
|
+
return;
|
|
15310
|
+
}
|
|
15628
15311
|
const nodeOptions = [
|
|
15629
15312
|
process.env.NODE_OPTIONS ?? "",
|
|
15630
15313
|
"--import=tsx",
|
|
@@ -15642,7 +15325,8 @@ async function testCommand(options = {}) {
|
|
|
15642
15325
|
env: {
|
|
15643
15326
|
...process.env,
|
|
15644
15327
|
...finalCredentials,
|
|
15645
|
-
...manifestPath ? { [TEST_MANIFEST_ENV]: manifestPath } : {},
|
|
15328
|
+
...manifestPath ? { [require_ready.TEST_MANIFEST_ENV]: manifestPath } : {},
|
|
15329
|
+
[require_ready.TEST_READY_ENV]: readyPath,
|
|
15646
15330
|
NODE_ENV: "test",
|
|
15647
15331
|
NODE_OPTIONS: nodeOptions
|
|
15648
15332
|
}
|
|
@@ -15657,6 +15341,24 @@ async function testCommand(options = {}) {
|
|
|
15657
15341
|
});
|
|
15658
15342
|
});
|
|
15659
15343
|
}
|
|
15344
|
+
/** Apply every construct's migrations to the test stage just reconciled. */
|
|
15345
|
+
async function migrateTestStage(workspace, env) {
|
|
15346
|
+
const { migrateDatabases } = await Promise.resolve().then(() => require("./migrate-BuCx7tIv.cjs"));
|
|
15347
|
+
const { discover } = await Promise.resolve().then(() => require("./discover-CroTIm0K.cjs")).then((n) => n.discover_exports);
|
|
15348
|
+
const sources = {};
|
|
15349
|
+
const manifest = await discover({
|
|
15350
|
+
patterns: require_workspace$1.constructGlobs(workspace),
|
|
15351
|
+
cwd: workspace.root,
|
|
15352
|
+
sources
|
|
15353
|
+
});
|
|
15354
|
+
const runs = await migrateDatabases({
|
|
15355
|
+
root: workspace.root,
|
|
15356
|
+
manifest,
|
|
15357
|
+
sources,
|
|
15358
|
+
env
|
|
15359
|
+
});
|
|
15360
|
+
for (const { target, applied } of runs) if (applied.length > 0) console.log(` 🗄️ ${target.folder}: applied ${applied.join(", ")}`);
|
|
15361
|
+
}
|
|
15660
15362
|
/** `gkm test` outside a project that declares stages, with none named. */
|
|
15661
15363
|
var NoStageToTest = class extends Error {
|
|
15662
15364
|
constructor() {
|
|
@@ -16065,7 +15767,7 @@ program.command("exec").description("Run a command with secrets injected into Cr
|
|
|
16065
15767
|
process.exit(1);
|
|
16066
15768
|
}
|
|
16067
15769
|
});
|
|
16068
|
-
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) => {
|
|
15770
|
+
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) => {
|
|
16069
15771
|
try {
|
|
16070
15772
|
const globalOptions = program.opts();
|
|
16071
15773
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
@@ -16078,6 +15780,34 @@ program.command("test").description("Run tests with secrets loaded from environm
|
|
|
16078
15780
|
process.exit(1);
|
|
16079
15781
|
}
|
|
16080
15782
|
});
|
|
15783
|
+
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) => {
|
|
15784
|
+
try {
|
|
15785
|
+
const globalOptions = program.opts();
|
|
15786
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
15787
|
+
const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-BuCx7tIv.cjs"));
|
|
15788
|
+
await migrateCommand({
|
|
15789
|
+
...options,
|
|
15790
|
+
...construct ? { construct } : {}
|
|
15791
|
+
});
|
|
15792
|
+
} catch (error) {
|
|
15793
|
+
console.error(formatError(error));
|
|
15794
|
+
process.exit(1);
|
|
15795
|
+
}
|
|
15796
|
+
});
|
|
15797
|
+
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) => {
|
|
15798
|
+
try {
|
|
15799
|
+
const globalOptions = program.opts();
|
|
15800
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
15801
|
+
const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-BuCx7tIv.cjs"));
|
|
15802
|
+
await migrationCommand({
|
|
15803
|
+
construct,
|
|
15804
|
+
...name ? { name } : {}
|
|
15805
|
+
});
|
|
15806
|
+
} catch (error) {
|
|
15807
|
+
console.error(formatError(error));
|
|
15808
|
+
process.exit(1);
|
|
15809
|
+
}
|
|
15810
|
+
});
|
|
16081
15811
|
program.command("cron").description("Manage cron jobs").action(() => {
|
|
16082
15812
|
const globalOptions = program.opts();
|
|
16083
15813
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
@@ -16236,7 +15966,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
16236
15966
|
console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
16237
15967
|
process.exit(1);
|
|
16238
15968
|
}
|
|
16239
|
-
const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-
|
|
15969
|
+
const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-BhiMkRuA.cjs")).then((n) => n.workspace_exports);
|
|
16240
15970
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
16241
15971
|
if (!result) {
|
|
16242
15972
|
console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
|
|
@@ -16295,7 +16025,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
|
|
|
16295
16025
|
try {
|
|
16296
16026
|
const globalOptions = program.opts();
|
|
16297
16027
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16298
|
-
const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-
|
|
16028
|
+
const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-B2fis34P.cjs"));
|
|
16299
16029
|
await deployGithubCommand(options);
|
|
16300
16030
|
} catch (error) {
|
|
16301
16031
|
console.error(formatError(error));
|
|
@@ -16429,5 +16159,7 @@ Object.defineProperty(exports, "encryption_exports", {
|
|
|
16429
16159
|
exports.getHostingerToken = getHostingerToken;
|
|
16430
16160
|
exports.isRemoteStore = isRemoteStore;
|
|
16431
16161
|
exports.pushStageSecrets = pushStageSecrets;
|
|
16162
|
+
exports.slug = slug;
|
|
16163
|
+
exports.stamp = stamp;
|
|
16432
16164
|
|
|
16433
16165
|
//# sourceMappingURL=index.cjs.map
|