@geekmidas/cli 10.0.0-alpha.4 → 10.0.0-alpha.41
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/README.md +52 -74
- package/bin/adjacent-tsconfig.mjs +97 -0
- package/bin/gkm.mjs +7 -1
- package/dist/{HostingerProvider-h3EbEp-Z.cjs → HostingerProvider-BZFGYYVA.cjs} +21 -22
- package/dist/HostingerProvider-BZFGYYVA.cjs.map +1 -0
- package/dist/{HostingerProvider-Cft7lEYr.mjs → HostingerProvider-Bh6K7QYz.mjs} +20 -21
- package/dist/HostingerProvider-Bh6K7QYz.mjs.map +1 -0
- package/dist/{LocalStateProvider-Db9SEjyH.cjs → LocalStateProvider-7HR6SS2t.cjs} +11 -6
- package/dist/LocalStateProvider-7HR6SS2t.cjs.map +1 -0
- package/dist/{LocalStateProvider-Bn6tS-oL.mjs → LocalStateProvider-D7QyJoLM.mjs} +10 -4
- package/dist/LocalStateProvider-D7QyJoLM.mjs.map +1 -0
- package/dist/{openapi-CA0yJUTN.cjs → OpenApiTsGenerator-B4Kj_qNU.cjs} +142 -303
- package/dist/OpenApiTsGenerator-B4Kj_qNU.cjs.map +1 -0
- package/dist/{openapi-kkQbsRVj.mjs → OpenApiTsGenerator-CEh3EKN3.mjs} +113 -255
- package/dist/OpenApiTsGenerator-CEh3EKN3.mjs.map +1 -0
- package/dist/{Route53Provider-RdpUDJQk.mjs → Route53Provider-Cvj40A8I.mjs} +12 -12
- package/dist/Route53Provider-Cvj40A8I.mjs.map +1 -0
- package/dist/{Route53Provider-di2fMM5N.cjs → Route53Provider-DVILBsvV.cjs} +20 -21
- package/dist/Route53Provider-DVILBsvV.cjs.map +1 -0
- package/dist/{SSMStateProvider-BRt2py-A.mjs → SSMStateProvider-B0Hu1MKZ.mjs} +13 -4
- package/dist/SSMStateProvider-B0Hu1MKZ.mjs.map +1 -0
- package/dist/{SSMStateProvider-DyX8s9HI.cjs → SSMStateProvider-VrQ-yeqd.cjs} +17 -9
- package/dist/SSMStateProvider-VrQ-yeqd.cjs.map +1 -0
- package/dist/aws-DkHdTV-p.cjs +61 -0
- package/dist/aws-DkHdTV-p.cjs.map +1 -0
- package/dist/aws-DojzQnLd.mjs +61 -0
- package/dist/aws-DojzQnLd.mjs.map +1 -0
- package/dist/{backup-provisioner-BrsHwYG0.cjs → backup-provisioner-BmhIO_Yr.cjs} +21 -19
- package/dist/backup-provisioner-BmhIO_Yr.cjs.map +1 -0
- package/dist/{backup-provisioner-Bgj5Vi_L.mjs → backup-provisioner-DIVCbfee.mjs} +10 -7
- package/dist/backup-provisioner-DIVCbfee.mjs.map +1 -0
- package/dist/{bundler-Be8Zg4Wd.mjs → bundler--fyybf1N.mjs} +19 -30
- package/dist/bundler--fyybf1N.mjs.map +1 -0
- package/dist/{bundler-BY1OmV1e.cjs → bundler-By6_sXiL.cjs} +20 -32
- package/dist/bundler-By6_sXiL.cjs.map +1 -0
- package/dist/{config-WRdsQYZK.mjs → config-5watB1D0.mjs} +142 -154
- package/dist/config-5watB1D0.mjs.map +1 -0
- package/dist/{config-C21ocYNd.cjs → config-CJ-PAb7M.cjs} +209 -210
- package/dist/config-CJ-PAb7M.cjs.map +1 -0
- package/dist/config.cjs +9 -6
- package/dist/config.d.cts +62 -33
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts +62 -33
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +3 -5
- package/dist/deploy/sniffer-envkit-patch.cjs +18 -10
- package/dist/deploy/sniffer-envkit-patch.cjs.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.d.cts +0 -2
- package/dist/deploy/sniffer-envkit-patch.d.cts.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.d.mts +0 -2
- package/dist/deploy/sniffer-envkit-patch.d.mts.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.mjs +10 -2
- package/dist/deploy/sniffer-envkit-patch.mjs.map +1 -1
- package/dist/deploy/sniffer-hooks.cjs +13 -7
- package/dist/deploy/sniffer-hooks.cjs.map +1 -1
- package/dist/deploy/sniffer-hooks.d.cts +1 -2
- package/dist/deploy/sniffer-hooks.d.cts.map +1 -1
- package/dist/deploy/sniffer-hooks.d.mts +1 -2
- package/dist/deploy/sniffer-hooks.d.mts.map +1 -1
- package/dist/deploy/sniffer-hooks.mjs +9 -2
- package/dist/deploy/sniffer-hooks.mjs.map +1 -1
- package/dist/deploy/sniffer-loader.cjs +16 -7
- package/dist/deploy/sniffer-loader.cjs.map +1 -1
- package/dist/deploy/sniffer-loader.d.cts +1 -1
- package/dist/deploy/sniffer-loader.d.mts +1 -1
- package/dist/deploy/sniffer-loader.mjs +13 -2
- package/dist/deploy/sniffer-loader.mjs.map +1 -1
- package/dist/deploy/sniffer-routes-worker.cjs +11 -11
- package/dist/deploy/sniffer-routes-worker.cjs.map +1 -1
- package/dist/deploy/sniffer-routes-worker.d.cts +1 -1
- package/dist/deploy/sniffer-routes-worker.d.mts +1 -1
- package/dist/deploy/sniffer-routes-worker.mjs +3 -3
- package/dist/deploy/sniffer-routes-worker.mjs.map +1 -1
- package/dist/deploy/sniffer-worker.cjs +15 -6
- package/dist/deploy/sniffer-worker.cjs.map +1 -1
- package/dist/deploy/sniffer-worker.d.cts +1 -3
- package/dist/deploy/sniffer-worker.d.cts.map +1 -1
- package/dist/deploy/sniffer-worker.d.mts +1 -3
- package/dist/deploy/sniffer-worker.d.mts.map +1 -1
- package/dist/deploy/sniffer-worker.mjs +15 -4
- package/dist/deploy/sniffer-worker.mjs.map +1 -1
- package/dist/{discover-DTmFeloL.cjs → discover-B0f_kuNN.cjs} +118 -66
- package/dist/discover-B0f_kuNN.cjs.map +1 -0
- package/dist/{discover-CAlFgYec.mjs → discover-BLhf7V_J.mjs} +67 -22
- package/dist/discover-BLhf7V_J.mjs.map +1 -0
- package/dist/{emit-CbcRolsa.cjs → emit-BdoPFjZf.cjs} +53 -36
- package/dist/emit-BdoPFjZf.cjs.map +1 -0
- package/dist/{emit-CiZS_5Ub.mjs → emit-Bzq7JsMP.mjs} +24 -6
- package/dist/emit-Bzq7JsMP.mjs.map +1 -0
- package/dist/github-BAr8WGkR.cjs +213 -0
- package/dist/github-BAr8WGkR.cjs.map +1 -0
- package/dist/github-D5MthUlR.mjs +213 -0
- package/dist/github-D5MthUlR.mjs.map +1 -0
- package/dist/{index-DCtipn2h.d.cts → index-B3PeGOQp.d.cts} +4 -23
- package/dist/index-B3PeGOQp.d.cts.map +1 -0
- package/dist/{index-BThUzEIp.d.mts → index-BOyaGcCm.d.mts} +4 -23
- package/dist/index-BOyaGcCm.d.mts.map +1 -0
- package/dist/index.cjs +7071 -4519
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +7117 -4586
- package/dist/index.mjs.map +1 -1
- package/dist/migrate-BVI3yqcE.mjs +736 -0
- package/dist/migrate-BVI3yqcE.mjs.map +1 -0
- package/dist/migrate-CFOUApUt.cjs +743 -0
- package/dist/migrate-CFOUApUt.cjs.map +1 -0
- package/dist/openapi.cjs +197 -8
- package/dist/openapi.cjs.map +1 -0
- package/dist/openapi.d.cts +47 -14
- package/dist/openapi.d.cts.map +1 -1
- package/dist/openapi.d.mts +47 -14
- package/dist/openapi.d.mts.map +1 -1
- package/dist/openapi.mjs +191 -5
- package/dist/openapi.mjs.map +1 -0
- package/dist/ready-D8UXsP6q.mjs +516 -0
- package/dist/ready-D8UXsP6q.mjs.map +1 -0
- package/dist/ready-mcxamf73.cjs +563 -0
- package/dist/ready-mcxamf73.cjs.map +1 -0
- package/dist/reconcile/public.cjs +4 -4
- package/dist/reconcile/public.d.cts +27 -26
- package/dist/reconcile/public.d.cts.map +1 -1
- package/dist/reconcile/public.d.mts +27 -26
- package/dist/reconcile/public.d.mts.map +1 -1
- package/dist/reconcile/public.mjs +3 -4
- package/dist/{reconcile-CFs8C_1a.mjs → reconcile--I7V4Klj.mjs} +4 -7
- package/dist/reconcile--I7V4Klj.mjs.map +1 -0
- package/dist/{reconcile-D0pimNiy.cjs → reconcile-CuCQBa5-.cjs} +5 -8
- package/dist/reconcile-CuCQBa5-.cjs.map +1 -0
- package/dist/rolldown-runtime-BhDjJH2R.mjs +15 -0
- package/dist/{chunk-CUT6urMc.cjs → rolldown-runtime-C0BPl7ul.cjs} +23 -10
- package/dist/storage-BzaUxmqe.mjs +124 -0
- package/dist/storage-BzaUxmqe.mjs.map +1 -0
- package/dist/storage-CSGoC3bK.cjs +152 -0
- package/dist/storage-CSGoC3bK.cjs.map +1 -0
- package/dist/store-DQgxT_WR.cjs +211 -0
- package/dist/store-DQgxT_WR.cjs.map +1 -0
- package/dist/store-DeGATCS-.mjs +182 -0
- package/dist/store-DeGATCS-.mjs.map +1 -0
- package/dist/types-0R124hrY.d.mts +6324 -0
- package/dist/types-0R124hrY.d.mts.map +1 -0
- package/dist/types-DfdYe31u.d.cts +6325 -0
- package/dist/types-DfdYe31u.d.cts.map +1 -0
- package/dist/vitest.cjs +65 -0
- package/dist/vitest.cjs.map +1 -0
- package/dist/vitest.d.cts +29 -0
- package/dist/vitest.d.cts.map +1 -0
- package/dist/vitest.d.mts +29 -0
- package/dist/vitest.d.mts.map +1 -0
- package/dist/vitest.mjs +65 -0
- package/dist/vitest.mjs.map +1 -0
- package/dist/workspace/index.cjs +3 -4
- package/dist/workspace/index.d.cts +3 -3
- package/dist/workspace/index.d.mts +3 -3
- package/dist/workspace/index.mjs +2 -3
- package/dist/{credentials-G1JwBoXH.cjs → workspace-BIqeeqcR.cjs} +2248 -1809
- package/dist/workspace-BIqeeqcR.cjs.map +1 -0
- package/dist/{workspace-BFMrAKqI.mjs → workspace-DOmIhiau.mjs} +863 -952
- package/dist/workspace-DOmIhiau.mjs.map +1 -0
- package/dist/{credentials-BBIRJVp4.mjs → workspace-DwS1JK5u.mjs} +2114 -1706
- package/dist/workspace-DwS1JK5u.mjs.map +1 -0
- package/dist/{workspace-BcucjwvA.cjs → workspace-RYSYtHP7.cjs} +1032 -1098
- package/dist/workspace-RYSYtHP7.cjs.map +1 -0
- package/package.json +29 -20
- package/dist/CachedStateProvider-BLo7zv7S.cjs +0 -3
- package/dist/CachedStateProvider-BxoAm-B2.mjs +0 -3
- package/dist/CachedStateProvider-CqPdW7CB.cjs +0 -60
- package/dist/CachedStateProvider-CqPdW7CB.cjs.map +0 -1
- package/dist/CachedStateProvider-DTniehMP.mjs +0 -54
- package/dist/CachedStateProvider-DTniehMP.mjs.map +0 -1
- package/dist/HostingerProvider-Cft7lEYr.mjs.map +0 -1
- package/dist/HostingerProvider-h3EbEp-Z.cjs.map +0 -1
- package/dist/LocalStateProvider-Bn6tS-oL.mjs.map +0 -1
- package/dist/LocalStateProvider-Db9SEjyH.cjs.map +0 -1
- package/dist/Route53Provider-RdpUDJQk.mjs.map +0 -1
- package/dist/Route53Provider-di2fMM5N.cjs.map +0 -1
- package/dist/SSMStateProvider-BRt2py-A.mjs.map +0 -1
- package/dist/SSMStateProvider-DyX8s9HI.cjs.map +0 -1
- package/dist/backup-provisioner-Bgj5Vi_L.mjs.map +0 -1
- package/dist/backup-provisioner-BrsHwYG0.cjs.map +0 -1
- package/dist/bundler-BY1OmV1e.cjs.map +0 -1
- package/dist/bundler-Be8Zg4Wd.mjs.map +0 -1
- package/dist/chunk-Duj1WY3L.mjs +0 -7
- package/dist/config-C21ocYNd.cjs.map +0 -1
- package/dist/config-WRdsQYZK.mjs.map +0 -1
- package/dist/credentials-B5zRHdAl.mjs +0 -141
- package/dist/credentials-B5zRHdAl.mjs.map +0 -1
- package/dist/credentials-BBIRJVp4.mjs.map +0 -1
- package/dist/credentials-BapzjyV5.cjs +0 -195
- package/dist/credentials-BapzjyV5.cjs.map +0 -1
- package/dist/credentials-G1JwBoXH.cjs.map +0 -1
- package/dist/discover-CAlFgYec.mjs.map +0 -1
- package/dist/discover-DTmFeloL.cjs.map +0 -1
- package/dist/dokploy-api-BS2xnJLZ.mjs +0 -3
- package/dist/dokploy-api-BsVH0y_s.cjs +0 -718
- package/dist/dokploy-api-BsVH0y_s.cjs.map +0 -1
- package/dist/dokploy-api-BuxDRo3l.cjs +0 -3
- package/dist/dokploy-api-aTFPz7_4.mjs +0 -706
- package/dist/dokploy-api-aTFPz7_4.mjs.map +0 -1
- package/dist/emit-CbcRolsa.cjs.map +0 -1
- package/dist/emit-CiZS_5Ub.mjs.map +0 -1
- package/dist/encryption-C6jaU0hE.cjs +0 -54
- package/dist/encryption-C6jaU0hE.cjs.map +0 -1
- package/dist/encryption-CHzzeR8a.cjs +0 -4
- package/dist/encryption-CQUncu7v.mjs +0 -3
- package/dist/encryption-D-abyhZx.mjs +0 -42
- package/dist/encryption-D-abyhZx.mjs.map +0 -1
- package/dist/fullstack-secrets-C-GGohke.cjs +0 -111
- package/dist/fullstack-secrets-C-GGohke.cjs.map +0 -1
- package/dist/fullstack-secrets-D1o9-G5l.mjs +0 -87
- package/dist/fullstack-secrets-D1o9-G5l.mjs.map +0 -1
- package/dist/generator-CNzBw7g5.cjs +0 -289
- package/dist/generator-CNzBw7g5.cjs.map +0 -1
- package/dist/generator-VE2JrFlY.mjs +0 -253
- package/dist/generator-VE2JrFlY.mjs.map +0 -1
- package/dist/index-BThUzEIp.d.mts.map +0 -1
- package/dist/index-DCtipn2h.d.cts.map +0 -1
- package/dist/openapi-CA0yJUTN.cjs.map +0 -1
- package/dist/openapi-kkQbsRVj.mjs.map +0 -1
- package/dist/openapi-react-query-Byltvm_7.mjs +0 -167
- package/dist/openapi-react-query-Byltvm_7.mjs.map +0 -1
- package/dist/openapi-react-query-CmJ1xDbn.cjs +0 -173
- package/dist/openapi-react-query-CmJ1xDbn.cjs.map +0 -1
- package/dist/openapi-react-query.cjs +0 -4
- package/dist/openapi-react-query.d.cts +0 -11
- package/dist/openapi-react-query.d.cts.map +0 -1
- package/dist/openapi-react-query.d.mts +0 -11
- package/dist/openapi-react-query.d.mts.map +0 -1
- package/dist/openapi-react-query.mjs +0 -4
- package/dist/reconcile-CFs8C_1a.mjs.map +0 -1
- package/dist/reconcile-D0pimNiy.cjs.map +0 -1
- package/dist/setup-B5_oHHUj.cjs +0 -11
- package/dist/setup-Cm2UnAdh.cjs +0 -473
- package/dist/setup-Cm2UnAdh.cjs.map +0 -1
- package/dist/setup-DXvRLTZN.mjs +0 -443
- package/dist/setup-DXvRLTZN.mjs.map +0 -1
- package/dist/setup-ZijS4qeC.mjs +0 -11
- package/dist/storage-BJmzYxt8.cjs +0 -363
- package/dist/storage-BJmzYxt8.cjs.map +0 -1
- package/dist/storage-BM9hGpgv.cjs +0 -7
- package/dist/storage-Bi24WfoW.mjs +0 -297
- package/dist/storage-Bi24WfoW.mjs.map +0 -1
- package/dist/storage-CYlGwWTj.mjs +0 -3
- package/dist/sync-CmEuNZsB.mjs +0 -76
- package/dist/sync-CmEuNZsB.mjs.map +0 -1
- package/dist/sync-D306yOWu.cjs +0 -4
- package/dist/sync-SizaChmC.cjs +0 -93
- package/dist/sync-SizaChmC.cjs.map +0 -1
- package/dist/sync-hOqAWHR6.mjs +0 -3
- package/dist/types-BhIl-COG.d.cts +0 -2184
- package/dist/types-BhIl-COG.d.cts.map +0 -1
- package/dist/types-C-arnQ1r.d.mts +0 -2184
- package/dist/types-C-arnQ1r.d.mts.map +0 -1
- package/dist/workspace-BFMrAKqI.mjs.map +0 -1
- package/dist/workspace-BcucjwvA.cjs.map +0 -1
- package/dist/workspace-DdJss8I6.cjs +0 -9
- package/dist/workspace-k4l3rsFh.mjs +0 -8
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate-CFOUApUt.cjs","names":["readdir","extname","join","pathToFileURL","stat","readFile","join","migrationTargets","MIGRATIONS_ROOT","canonicalId","readdir","DATABASE_FOLDERS","provideKey","require","createRequire","pathToFileURL","TEST_STAGE","loadWorkspaceConfig","discover","constructGlobs","reconcileWorkspace","migrationTargets","canonicalId","stamp","slug","loadSecretsForApp","secretsStoreFor","EnvironmentParser","serviceContext","join","mkdir","writeFile"],"sources":["../src/migrate/provider.ts","../src/migrate/databases.ts","../src/migrate/index.ts"],"sourcesContent":["/**\n * One construct's migrations and seeds, read from its folders.\n *\n * Two kinds of file, told apart by extension rather than by configuration: a\n * script exports `up` (and may export `down`) — or `seed(db, { stage })`, in\n * `seeds/` — and a\n * `.sql` file is run whole. The second is what makes a folder independent of\n * the client the app uses — Better Auth's schema is SQL whatever the\n * application queries with.\n */\n\nimport { readdir, readFile, stat } from 'node:fs/promises';\nimport { extname, join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { Kysely, sql } from 'kysely';\nimport type { Migration, MigrationProvider } from 'kysely/migration';\n\nconst SCRIPTS = new Set(['.ts', '.mts', '.js', '.mjs']);\n\n/** Two files in one folder that would be one migration. */\nexport class DuplicateMigration extends Error {\n\tconstructor(\n\t\treadonly folder: string,\n\t\treadonly migration: string,\n\t) {\n\t\tsuper(\n\t\t\t`${folder} has two migrations named '${migration}' — a script and a .sql file ` +\n\t\t\t\t`with the same name. A migration is recorded by name, so only one of ` +\n\t\t\t\t`them could ever run; rename or remove the other.`,\n\t\t);\n\t\tthis.name = 'DuplicateMigration';\n\t}\n}\n\n/** A script in a migrations folder that exports no `up`. */\nexport class MigrationHasNoUp extends Error {\n\tconstructor(readonly file: string) {\n\t\tsuper(\n\t\t\t`${file} is in a migrations folder but exports no \\`up\\` function. ` +\n\t\t\t\t`Export \\`async function up(db: Kysely<unknown>)\\`, or move the file ` +\n\t\t\t\t`out of the folder if it is not a migration.`,\n\t\t);\n\t\tthis.name = 'MigrationHasNoUp';\n\t}\n}\n\n/** A script in a seeds folder that exports no `seed`. */\nexport class SeedHasNoSeed extends Error {\n\tconstructor(readonly file: string) {\n\t\tsuper(\n\t\t\t`${file} is in a seeds folder but exports no \\`seed\\` function. ` +\n\t\t\t\t`Export \\`async function seed(db, { stage })\\`, or move the ` +\n\t\t\t\t`file out of the folder if it is not a seed.`,\n\t\t);\n\t\tthis.name = 'SeedHasNoSeed';\n\t}\n}\n\n/**\n * What a seed is handed beside its database.\n *\n * Seeds run on every stage, production included, so a seed that should write\n * something only somewhere — a demo tenant on `development`, never on\n * `production` — decides by the stage it is given.\n */\nexport interface SeedContext {\n\t/** The stage being seeded: the local one, `test`, or a deployed one. */\n\tstage: string;\n}\n\n/** One seed: what it is called, and what it does. */\nexport interface Seed {\n\tname: string;\n\trun: (db: Kysely<unknown>, context: SeedContext) => Promise<void>;\n}\n\n/**\n * The migration files in a folder, by name, without reading them.\n *\n * Also what `gkm dev` compares against a construct's history to report what\n * is pending, which is why it is separate from loading them.\n */\nexport async function migrationFiles(\n\tfolder: string,\n): Promise<Map<string, string>> {\n\tconst entries = await readdir(folder, { withFileTypes: true }).catch(\n\t\t(error: NodeJS.ErrnoException) => {\n\t\t\tif (error.code === 'ENOENT') return [];\n\t\t\tthrow error;\n\t\t},\n\t);\n\n\tconst files = new Map<string, string>();\n\tfor (const entry of entries) {\n\t\tif (!entry.isFile() || !isMigration(entry.name)) continue;\n\n\t\tconst name = entry.name.slice(0, -extname(entry.name).length);\n\t\tif (files.has(name)) throw new DuplicateMigration(folder, name);\n\n\t\tfiles.set(name, join(folder, entry.name));\n\t}\n\n\treturn files;\n}\n\n/** A Kysely provider over one construct's folder. */\nexport function folderProvider(\n\tfolder: string,\n\traw: typeof sql,\n): MigrationProvider {\n\treturn {\n\t\tasync getMigrations() {\n\t\t\tconst migrations: Record<string, Migration> = {};\n\n\t\t\tfor (const [name, file] of await migrationFiles(folder)) {\n\t\t\t\tmigrations[name] =\n\t\t\t\t\textname(file) === '.sql'\n\t\t\t\t\t\t? await sqlMigration(file, raw)\n\t\t\t\t\t\t: await scriptMigration(file);\n\t\t\t}\n\n\t\t\treturn migrations;\n\t\t},\n\t};\n}\n\n/**\n * The seeds in a folder, loaded, in name order.\n *\n * Every one runs on every pass — there is no history to consult — so the order\n * is the only thing between two seeds where one reads what another writes.\n * Name them to sort that way.\n */\nexport async function loadSeeds(\n\tfolder: string,\n\traw: typeof sql,\n): Promise<Seed[]> {\n\tconst files = [...(await migrationFiles(folder))].sort(([a], [b]) =>\n\t\ta < b ? -1 : a > b ? 1 : 0,\n\t);\n\n\tconst seeds: Seed[] = [];\n\tfor (const [name, file] of files) {\n\t\tseeds.push({\n\t\t\tname,\n\t\t\trun:\n\t\t\t\textname(file) === '.sql'\n\t\t\t\t\t? (await sqlMigration(file, raw)).up\n\t\t\t\t\t: await scriptSeed(file),\n\t\t});\n\t}\n\n\treturn seeds;\n}\n\n/**\n * A seed script, imported as it is on disk now.\n *\n * Keyed by its modification time: a seed is run again after it is edited, and\n * a process that has imported it once would otherwise run the old one.\n */\nasync function scriptSeed(file: string): Promise<Seed['run']> {\n\tconst url = pathToFileURL(file);\n\turl.searchParams.set('mtime', String((await stat(file)).mtimeMs));\n\tconst module = (await import(url.href)) as { seed?: unknown };\n\tif (typeof module.seed !== 'function') throw new SeedHasNoSeed(file);\n\n\treturn module.seed as Seed['run'];\n}\n\nfunction isMigration(file: string): boolean {\n\tif (file.endsWith('.d.ts')) return false;\n\tif (/\\.(spec|test)\\.[cm]?[jt]s$/.test(file)) return false;\n\n\tconst extension = extname(file);\n\treturn extension === '.sql' || SCRIPTS.has(extension);\n}\n\nasync function scriptMigration(file: string): Promise<Migration> {\n\tconst module = (await import(pathToFileURL(file).href)) as Partial<Migration>;\n\tif (typeof module.up !== 'function') throw new MigrationHasNoUp(file);\n\n\treturn {\n\t\tup: module.up,\n\t\t...(typeof module.down === 'function' ? { down: module.down } : {}),\n\t};\n}\n\n/**\n * A `.sql` file, run whole.\n *\n * One query rather than split on `;`: with no parameters, Postgres takes\n * several statements in one message, and splitting by hand breaks on the first\n * `;` inside a string or a function body. There is no `down` — a generated\n * schema is rolled back by a migration that says how, not by guessing.\n */\nasync function sqlMigration(file: string, raw: typeof sql): Promise<Migration> {\n\tconst statements = await readFile(file, 'utf-8');\n\n\treturn {\n\t\tup: async (db: Kysely<unknown>) => {\n\t\t\tawait raw.raw(statements).execute(db);\n\t\t},\n\t};\n}\n","/**\n * Bring each construct's schema up to its migrations folder.\n *\n * Which constructs, in what order, from which folder, is `migrationTargets`'\n * answer and nobody else's. What is decided here is only how one target is\n * run: as its owner, in its own schema, with a history of its own.\n */\n\nimport { readdir } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport {\n\ttype ConstructManifest,\n\tcanonicalId,\n\tDATABASE_FOLDERS,\n\tMIGRATIONS_ROOT,\n\ttype MigrationTarget,\n\tmigrationTargets,\n\tprovideKey,\n} from '@geekmidas/manifest';\nimport type { Kysely } from 'kysely';\nimport type { Migrator } from 'kysely/migration';\nimport pg from 'pg';\nimport type { ConstructSource } from '../reconcile/discover';\nimport { folderProvider, loadSeeds, migrationFiles } from './provider';\n\nexport interface DatabasesOptions {\n\t/** The project root — where `db/` lives. */\n\troot: string;\n\tmanifest: ConstructManifest;\n\t/**\n\t * Where each construct was declared. A construct's migrations run with the\n\t * Kysely its own file resolves, which is the one its migrations import.\n\t */\n\tsources?: Readonly<Record<string, ConstructSource>>;\n\t/** The resolved environment: each construct's owner URL is read from it. */\n\tenv: Readonly<Record<string, string | undefined>>;\n\t/** One construct, by name. All of them when absent. */\n\tonly?: string;\n}\n\nexport interface MigrationRun {\n\ttarget: MigrationTarget;\n\t/** The migrations this run applied, in order. */\n\tapplied: string[];\n}\n\nexport interface SeedOptions extends DatabasesOptions {\n\t/** The stage being seeded, handed to every seed. */\n\tstage: string;\n}\n\nexport interface SeedRun {\n\ttarget: MigrationTarget;\n\t/** The seeds this run ran, in order — every one, every time. */\n\tseeded: string[];\n}\n\nexport interface PendingMigrations {\n\ttarget: MigrationTarget;\n\tpending: string[];\n}\n\n/** A folder under `db/` that names no construct. */\nexport class UnknownMigrationFolder extends Error {\n\tconstructor(\n\t\treadonly folder: string,\n\t\treadonly known: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`${folder} names no database construct, so its migrations would never ` +\n\t\t\t\t`run. ${known.length > 0 ? `The folders that do: ${known.join(', ')}.` : 'This project declares no database.'} ` +\n\t\t\t\t`Rename the folder after the construct it belongs to.`,\n\t\t);\n\t\tthis.name = 'UnknownMigrationFolder';\n\t}\n}\n\n/** A construct was asked for by a name that takes no migrations. */\nexport class NoSuchMigrationTarget extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly known: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`'${construct}' is not a database construct. ` +\n\t\t\t\t(known.length > 0\n\t\t\t\t\t? `The ones with migrations: ${known.join(', ')}.`\n\t\t\t\t\t: 'This project declares no database.'),\n\t\t);\n\t\tthis.name = 'NoSuchMigrationTarget';\n\t}\n}\n\n/**\n * A migration left directly in a construct's folder, where nothing reads it.\n *\n * Migrations moved into `migrations/` so seeds could sit beside them. A file\n * still at the old level would otherwise never run, and nothing would say so.\n */\nexport class MigrationsOutsideFolder extends Error {\n\tconstructor(\n\t\treadonly folder: string,\n\t\treadonly files: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`${folder} has ${files.join(', ')} directly inside it, where nothing ` +\n\t\t\t\t`runs them. Migrations live in ${folder}/migrations/ and seeds in ` +\n\t\t\t\t`${folder}/seeds/ — move ${files.length === 1 ? 'it' : 'them'} there.`,\n\t\t);\n\t\tthis.name = 'MigrationsOutsideFolder';\n\t}\n}\n\n/** A folder inside a construct's folder that is neither `migrations` nor `seeds`. */\nexport class UnknownDatabaseFolder extends Error {\n\tconstructor(readonly folder: string) {\n\t\tsuper(\n\t\t\t`${folder} is neither migrations/ nor seeds/, so nothing in it would ` +\n\t\t\t\t`ever run. A construct's folder holds those two; rename or remove it.`,\n\t\t);\n\t\tthis.name = 'UnknownDatabaseFolder';\n\t}\n}\n\n/** A seed failed; its own writes are rolled back, the ones before it stay. */\nexport class SeedFailed extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly seed: string,\n\t\toverride readonly cause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`${construct}: seed '${seed}' failed — ` +\n\t\t\t\t`${cause instanceof Error ? cause.message : String(cause)}. ` +\n\t\t\t\t`Its writes were rolled back; fix it and run it again.`,\n\t\t);\n\t\tthis.name = 'SeedFailed';\n\t}\n}\n\n/** A construct with roles has no owner URL to migrate with. */\nexport class NoOwnerCredential extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly key: string,\n\t) {\n\t\tsuper(\n\t\t\t`${construct} has no ${key} to migrate with. Migrations run as the owner ` +\n\t\t\t\t`role — the runtime one cannot create a table — and nothing ` +\n\t\t\t\t`resolved its URL. Is the stage reconciled?`,\n\t\t);\n\t\tthis.name = 'NoOwnerCredential';\n\t}\n}\n\n/** A migration failed; the ones before it in the run stay applied. */\nexport class MigrationFailed extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly migration: string | undefined,\n\t\toverride readonly cause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`${construct}: migration ${migration ? `'${migration}' ` : ''}failed — ` +\n\t\t\t\t`${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t);\n\t\tthis.name = 'MigrationFailed';\n\t}\n}\n\n/** The project's Kysely could not be found from where a database is declared. */\nexport class KyselyNotFound extends Error {\n\tconstructor(readonly from: string) {\n\t\tsuper(\n\t\t\t`Could not resolve 'kysely' from ${from}. Migrations run with the ` +\n\t\t\t\t`project's own Kysely — install it where the database is declared.`,\n\t\t);\n\t\tthis.name = 'KyselyNotFound';\n\t}\n}\n\n/** Apply every pending migration, construct by construct, parents first. */\nexport async function migrateDatabases(\n\toptions: DatabasesOptions,\n): Promise<MigrationRun[]> {\n\tconst runs: MigrationRun[] = [];\n\n\tfor (const target of await targetsFor(options)) {\n\t\t// No folder, or an empty one, is nothing to do — and no reason to\n\t\t// connect, which a construct nobody has written a migration for yet\n\t\t// should not need.\n\t\tconst files = await migrationFiles(join(options.root, target.migrations));\n\t\tif (files.size === 0) {\n\t\t\truns.push({ target, applied: [] });\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst applied = await withMigrator(options, target, async (migrator) => {\n\t\t\tconst { error, results = [] } = await migrator.migrateToLatest();\n\t\t\tif (error) {\n\t\t\t\tconst failed = results.find((result) => result.status === 'Error');\n\t\t\t\tthrow new MigrationFailed(target.id, failed?.migrationName, error);\n\t\t\t}\n\n\t\t\treturn results\n\t\t\t\t.filter((result) => result.status === 'Success')\n\t\t\t\t.map((result) => result.migrationName);\n\t\t});\n\n\t\truns.push({ target, applied });\n\t}\n\n\treturn runs;\n}\n\n/**\n * Run every construct's seeds, parents first — each one, every time, on every\n * stage, production included. Each is handed the stage, so a seed decides\n * what belongs where.\n *\n * Seeds are reference data kept up to date by being run again, so there is no\n * history: a seed is an upsert, and a changed one is applied by the next run.\n * Each runs in its own transaction, as the construct's owner, so a failure\n * leaves nothing half-written. Assumes the schema is migrated —\n * {@link migrateAndSeed} is what makes that true.\n */\nexport async function seedDatabases(options: SeedOptions): Promise<SeedRun[]> {\n\tconst runs: SeedRun[] = [];\n\n\tfor (const target of await targetsFor(options)) {\n\t\tconst folder = join(options.root, target.seeds);\n\t\tif ((await migrationFiles(folder)).size === 0) {\n\t\t\truns.push({ target, seeded: [] });\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst seeded = await withOwner(options, target, async (db, kysely) => {\n\t\t\tconst names: string[] = [];\n\t\t\tfor (const seed of await loadSeeds(folder, kysely.sql)) {\n\t\t\t\ttry {\n\t\t\t\t\tawait db\n\t\t\t\t\t\t.transaction()\n\t\t\t\t\t\t.execute((trx) => seed.run(trx, { stage: options.stage }));\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthrow new SeedFailed(target.id, seed.name, error);\n\t\t\t\t}\n\t\t\t\tnames.push(seed.name);\n\t\t\t}\n\t\t\treturn names;\n\t\t});\n\n\t\truns.push({ target, seeded });\n\t}\n\n\treturn runs;\n}\n\n/**\n * Migrate, then seed: a seed only ever runs against the schema it was written\n * for.\n *\n * With `only`, the constructs it lives in are migrated too — a tenant's seed\n * cannot run before the database that owns its schema exists — and only it is\n * seeded.\n */\nexport async function migrateAndSeed(options: SeedOptions): Promise<{\n\tmigrations: MigrationRun[];\n\tseeds: SeedRun[];\n}> {\n\tconst migrations: MigrationRun[] = [];\n\tfor (const target of await withParents(options)) {\n\t\tmigrations.push(\n\t\t\t...(await migrateDatabases({ ...options, only: target.id })),\n\t\t);\n\t}\n\n\treturn { migrations, seeds: await seedDatabases(options) };\n}\n\n/** The targets `only` names, after the ones it lives in; all without it. */\nasync function withParents(\n\toptions: DatabasesOptions,\n): Promise<MigrationTarget[]> {\n\tconst chosen = await targetsFor(options);\n\tif (!options.only) return chosen;\n\n\tconst all = migrationTargets(options.manifest);\n\tconst ids = new Set<string>();\n\tfor (const target of chosen) {\n\t\tlet current: MigrationTarget | undefined = target;\n\t\twhile (current) {\n\t\t\tids.add(current.id);\n\t\t\tconst parent: string | undefined = current.of;\n\t\t\tcurrent = parent ? all.find((t) => t.id === parent) : undefined;\n\t\t}\n\t}\n\n\treturn all.filter((target) => ids.has(target.id));\n}\n\n/**\n * What each construct's folder has that its history does not — without\n * applying any of it. What `gkm dev` reports.\n */\nexport async function pendingMigrations(\n\toptions: DatabasesOptions,\n): Promise<PendingMigrations[]> {\n\tconst found: PendingMigrations[] = [];\n\n\tfor (const target of await targetsFor(options)) {\n\t\tconst files = await migrationFiles(join(options.root, target.migrations));\n\t\tif (files.size === 0) continue;\n\n\t\tconst pending = await withMigrator(options, target, async (migrator) =>\n\t\t\t(await migrator.getMigrations())\n\t\t\t\t.filter((migration) => migration.executedAt === undefined)\n\t\t\t\t.map((migration) => migration.name),\n\t\t);\n\n\t\tif (pending.length > 0) found.push({ target, pending });\n\t}\n\n\treturn found;\n}\n\n/**\n * The targets this run covers, with every folder under `db/` accounted for.\n *\n * A folder that names no construct is an error rather than something to skip:\n * a typo in its name is otherwise a migration that silently never runs.\n */\nasync function targetsFor(\n\toptions: DatabasesOptions,\n): Promise<MigrationTarget[]> {\n\tconst targets = migrationTargets(options.manifest);\n\tconst folders = targets.map((target) => target.folder);\n\n\tfor (const folder of await foldersIn(join(options.root, MIGRATIONS_ROOT))) {\n\t\tconst relative = `${MIGRATIONS_ROOT}/${folder}`;\n\t\tif (!folders.includes(relative)) {\n\t\t\tthrow new UnknownMigrationFolder(relative, folders);\n\t\t}\n\t\tawait checkLayout(options.root, relative);\n\t}\n\n\tif (!options.only) return targets;\n\n\tconst id = canonicalId(options.only);\n\tconst chosen = targets.filter((target) => target.id === id);\n\tif (chosen.length === 0) {\n\t\tthrow new NoSuchMigrationTarget(\n\t\t\toptions.only,\n\t\t\ttargets.map((target) => target.id),\n\t\t);\n\t}\n\n\treturn chosen;\n}\n\n/**\n * A construct's folder holds `migrations/` and `seeds/`, and nothing else —\n * anything beside them is a file or folder nothing would ever run.\n */\nasync function checkLayout(root: string, folder: string): Promise<void> {\n\tconst entries = await readdir(join(root, folder), { withFileTypes: true });\n\n\tconst loose = entries\n\t\t.filter((entry) => entry.isFile() && !entry.name.startsWith('.'))\n\t\t.map((entry) => entry.name);\n\tif (loose.length > 0) throw new MigrationsOutsideFolder(folder, loose);\n\n\tfor (const entry of entries) {\n\t\tif (\n\t\t\tentry.isDirectory() &&\n\t\t\t!(DATABASE_FOLDERS as readonly string[]).includes(entry.name)\n\t\t) {\n\t\t\tthrow new UnknownDatabaseFolder(`${folder}/${entry.name}`);\n\t\t}\n\t}\n}\n\nasync function foldersIn(dir: string): Promise<string[]> {\n\tconst entries = await readdir(dir, { withFileTypes: true }).catch(\n\t\t(error: NodeJS.ErrnoException) => {\n\t\t\tif (error.code === 'ENOENT') return [];\n\t\t\tthrow error;\n\t\t},\n\t);\n\n\treturn entries\n\t\t.filter((entry) => entry.isDirectory())\n\t\t.map((entry) => entry.name);\n}\n\n/**\n * A migrator for one target, connected as its owner.\n *\n * No `migrationTableSchema`: the owner role's `search_path` is pinned to the\n * construct's schema, so the history lands there — `app.kysely_migration` for\n * the database, `auth_database.kysely_migration` for its tenant — and two\n * constructs in one Postgres never share one.\n *\n * Unordered migrations are allowed. Files are timestamped, so a branch merged\n * after another's migration ran carries an older one; refusing it would make\n * every such merge a manual repair.\n */\nasync function withMigrator<T>(\n\toptions: DatabasesOptions,\n\ttarget: MigrationTarget,\n\trun: (migrator: Migrator) => Promise<T>,\n): Promise<T> {\n\treturn withOwner(options, target, (db, { Migrator, sql }) =>\n\t\trun(\n\t\t\tnew Migrator({\n\t\t\t\tdb,\n\t\t\t\tprovider: folderProvider(join(options.root, target.migrations), sql),\n\t\t\t\tallowUnorderedMigrations: true,\n\t\t\t}),\n\t\t),\n\t);\n}\n\n/**\n * One target's database, connected as its owner and closed afterwards — what\n * migrations and seeds both run as.\n */\nasync function withOwner<T>(\n\toptions: DatabasesOptions,\n\ttarget: MigrationTarget,\n\trun: (\n\t\tdb: Kysely<unknown>,\n\t\tkysely: Awaited<ReturnType<typeof kyselyFrom>>,\n\t) => Promise<T>,\n): Promise<T> {\n\tconst from =\n\t\toptions.sources?.[target.id]?.file ??\n\t\t(target.of ? options.sources?.[target.of]?.file : undefined) ??\n\t\tjoin(options.root, 'package.json');\n\tconst kysely = await kyselyFrom(from);\n\n\tconst db: Kysely<unknown> = new kysely.Kysely<unknown>({\n\t\tdialect: new kysely.PostgresDialect({\n\t\t\tpool: new pg.Pool(poolConfig(ownerUrl(options, target))),\n\t\t}),\n\t});\n\n\ttry {\n\t\treturn await run(db, kysely);\n\t} finally {\n\t\tawait db.destroy();\n\t}\n}\n\n/**\n * The owner's URL, with no fallback to the runtime one.\n *\n * The runtime role can create nothing, so falling back would only move the\n * failure to the first `CREATE TABLE` and make it read like a permissions\n * bug. The one exception is a database declared `roles: false`, where there is\n * no owner — both URLs are the master credential by design.\n */\nfunction ownerUrl(options: DatabasesOptions, target: MigrationTarget): string {\n\tconst key = provideKey(target.id, 'ownerUrl');\n\tconst owner = options.env[key];\n\tif (owner) return owner;\n\n\tconst database = options.manifest[target.of ?? target.id];\n\tif (database?.kind === 'database' && database.roles === false) {\n\t\tconst url = options.env[provideKey(target.id, 'url')];\n\t\tif (url) return url;\n\t}\n\n\tthrow new NoOwnerCredential(target.id, key);\n}\n\n/**\n * Pool settings for a URL, with `?search_path=` as the startup option Postgres\n * reads. Only a `roles: false` URL carries it — a role pins its own.\n */\nfunction poolConfig(url: string): pg.PoolConfig {\n\tconst parsed = new URL(url);\n\tconst searchPath = parsed.searchParams.get('search_path');\n\tif (!searchPath) return { connectionString: url };\n\n\tparsed.searchParams.delete('search_path');\n\treturn {\n\t\tconnectionString: parsed.toString(),\n\t\toptions: `-c search_path=${searchPath}`,\n\t};\n}\n\n/** Kysely as the project resolves it from `from`. */\nasync function kyselyFrom(from: string) {\n\tconst require = createRequire(from);\n\n\ttry {\n\t\tconst [main, migration] = await Promise.all([\n\t\t\timport(pathToFileURL(require.resolve('kysely')).href),\n\t\t\timport(pathToFileURL(require.resolve('kysely/migration')).href),\n\t\t]);\n\n\t\treturn {\n\t\t\tKysely: main.Kysely as typeof import('kysely').Kysely,\n\t\t\tPostgresDialect:\n\t\t\t\tmain.PostgresDialect as typeof import('kysely').PostgresDialect,\n\t\t\tsql: main.sql as typeof import('kysely').sql,\n\t\t\tMigrator:\n\t\t\t\tmigration.Migrator as typeof import('kysely/migration').Migrator,\n\t\t};\n\t} catch {\n\t\tthrow new KyselyNotFound(from);\n\t}\n}\n","/**\n * `gkm migrate`, `gkm seed` and `gkm migration` — applying a construct's\n * migrations, running its seeds, and writing its next migration.\n *\n * Both work on a stage this machine reconciles: its containers up, its roles\n * provisioned, its owner URLs resolved. A deployed stage is migrated by the\n * deploy that ships it, which is the one thing that can reach its database.\n */\n\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { EnvironmentParser } from '@geekmidas/envkit';\nimport {\n\ttype ConstructManifest,\n\tcanonicalId,\n\ttype MigrationTarget,\n\tmigrationTargets,\n} from '@geekmidas/manifest';\nimport {\n\ttype ServiceRegisterOptions,\n\tserviceContext,\n} from '@geekmidas/services';\nimport { loadWorkspaceConfig } from '../config';\nimport { loadSecretsForApp } from '../credentials';\nimport { type ConstructSource, discover } from '../reconcile/discover';\nimport { constructGlobs, reconcileWorkspace } from '../reconcile/workspace.js';\nimport { secretsStoreFor } from '../secrets/store.js';\nimport { TEST_STAGE } from '../workspace/stages';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport {\n\ttype MigrationRun,\n\tmigrateAndSeed,\n\tmigrateDatabases,\n\tNoSuchMigrationTarget,\n\tpendingMigrations,\n\ttype SeedRun,\n} from './databases';\nimport { slug, stamp } from './names';\n\nexport {\n\tKyselyNotFound,\n\tMigrationFailed,\n\ttype MigrationRun,\n\tMigrationsOutsideFolder,\n\tmigrateAndSeed,\n\tmigrateDatabases,\n\tNoOwnerCredential,\n\tNoSuchMigrationTarget,\n\ttype PendingMigrations,\n\tpendingMigrations,\n\tSeedFailed,\n\ttype SeedOptions,\n\ttype SeedRun,\n\tseedDatabases,\n\tUnknownDatabaseFolder,\n\tUnknownMigrationFolder,\n} from './databases';\nexport { slug, stamp } from './names';\nexport {\n\tDuplicateMigration,\n\tMigrationHasNoUp,\n\tSeedHasNoSeed,\n} from './provider';\n\nconst logger = console;\n\n/** `gkm migrate --stage` named a stage only a deploy can reach. */\nexport class MigrateDeployedStage extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly local: string,\n\t) {\n\t\tsuper(\n\t\t\t`'${stage}' is a deployed stage, and its database is migrated by ` +\n\t\t\t\t`\\`gkm deploy --stage ${stage}\\`, which is what can reach it. ` +\n\t\t\t\t`\\`gkm migrate\\` runs against '${local}' or '${TEST_STAGE}'.`,\n\t\t);\n\t\tthis.name = 'MigrateDeployedStage';\n\t}\n}\n\n/** `gkm migration` for a Kysely database, with no name for the file. */\nexport class MigrationNeedsName extends Error {\n\tconstructor(readonly construct: string) {\n\t\tsuper(\n\t\t\t`Name the migration: \\`gkm migration ${construct} add_workouts\\`. ` +\n\t\t\t\t`The name is what the file — and the history — calls it.`,\n\t\t);\n\t\tthis.name = 'MigrationNeedsName';\n\t}\n}\n\nexport interface MigrateOptions {\n\t/** The stage to migrate: the project's local one, or `test`. */\n\tstage?: string;\n\t/** One construct, by name. All of them when absent. */\n\tconstruct?: string;\n}\n\n/** A workspace's stage, reconciled and ready to migrate. */\nexport interface ReadyStage {\n\tworkspace: NormalizedWorkspace;\n\tmanifest: ConstructManifest;\n\tsources: Record<string, ConstructSource>;\n\t/** Every URL the stage resolved — the owner URLs among them. */\n\tenv: Record<string, string>;\n\t/** The stage it is: the local one, or `test`. */\n\tstage: string;\n}\n\n/**\n * Reconcile a stage so it can be migrated: containers up, databases, schemas,\n * roles and grants in place, owner URLs resolved.\n *\n * Migrating assumes the roles exist, and this is what makes that true rather\n * than something a developer has to have run first.\n */\nexport async function readyStage(\n\tcwd: string,\n\tstage?: string,\n): Promise<ReadyStage> {\n\tconst { workspace } = await loadWorkspaceConfig(cwd);\n\tconst local = workspace.stages.local;\n\tconst target = stage ?? local;\n\tif (target !== local && target !== TEST_STAGE) {\n\t\tthrow new MigrateDeployedStage(target, local);\n\t}\n\n\tconst sources: Record<string, ConstructSource> = {};\n\tconst manifest = await discover({\n\t\tpatterns: constructGlobs(workspace),\n\t\tcwd: workspace.root,\n\t\tsources,\n\t});\n\n\tconst reconciled = await reconcileWorkspace(workspace, {\n\t\tstage: target,\n\t\tmanifest,\n\t\tstart: true,\n\t});\n\n\treturn {\n\t\tworkspace,\n\t\tmanifest,\n\t\tsources,\n\t\tenv: { ...reconciled.env },\n\t\tstage: target,\n\t};\n}\n\n/** `gkm migrate [construct]`. */\nexport async function migrateCommand(\n\toptions: MigrateOptions = {},\n): Promise<void> {\n\tconst ready = await readyStage(process.cwd(), options.stage);\n\n\tconst runs = await migrateDatabases({\n\t\troot: ready.workspace.root,\n\t\tmanifest: ready.manifest,\n\t\tsources: ready.sources,\n\t\tenv: ready.env,\n\t\t...(options.construct ? { only: options.construct } : {}),\n\t});\n\n\tif (runs.length === 0) {\n\t\tlogger.log('🗄️ No database constructs to migrate.');\n\t\treturn;\n\t}\n\n\tlogMigrations(runs);\n}\n\n/**\n * `gkm seed [construct]` — migrate, then run the seeds.\n *\n * Always migrates first: a seed is written against the schema its migrations\n * build, and against an older one it fails on a table that is not there yet.\n */\nexport async function seedCommand(options: MigrateOptions = {}): Promise<void> {\n\tconst ready = await readyStage(process.cwd(), options.stage);\n\n\tconst { migrations, seeds } = await migrateAndSeed({\n\t\troot: ready.workspace.root,\n\t\tmanifest: ready.manifest,\n\t\tsources: ready.sources,\n\t\tenv: ready.env,\n\t\tstage: ready.stage,\n\t\t...(options.construct ? { only: options.construct } : {}),\n\t});\n\n\tif (migrations.length === 0) {\n\t\tlogger.log('🗄️ No database constructs to seed.');\n\t\treturn;\n\t}\n\n\tlogMigrations(migrations);\n\tlogSeeds(seeds);\n}\n\n/** One line per construct: up to date, or what was applied. */\nexport function logMigrations(\n\truns: readonly MigrationRun[],\n\tlog: (line: string) => void = logger.log,\n): void {\n\tfor (const { target, applied } of runs) {\n\t\tif (applied.length === 0) {\n\t\t\tlog(`🗄️ ${target.migrations}: up to date`);\n\t\t\tcontinue;\n\t\t}\n\t\tlog(`🗄️ ${target.migrations}: applied ${applied.length}`);\n\t\tfor (const name of applied) log(` ✓ ${name}`);\n\t}\n}\n\n/** One line per construct that has seeds, and each seed it ran. */\nexport function logSeeds(\n\truns: readonly SeedRun[],\n\tlog: (line: string) => void = logger.log,\n): void {\n\tfor (const { target, seeded } of runs) {\n\t\tif (seeded.length === 0) continue;\n\t\tlog(`🌱 ${target.seeds}: ran ${seeded.length}`);\n\t\tfor (const name of seeded) log(` ✓ ${name}`);\n\t}\n}\n\n/**\n * What `gkm dev` prints: one line per construct whose folder is ahead of its\n * history, and nothing when every folder is applied.\n *\n * Reported, never applied. A migration being written would otherwise run the\n * moment its file is saved, half-finished, against the database the developer\n * is working in.\n */\nexport async function reportPendingMigrations(\n\tworkspace: NormalizedWorkspace,\n\tenv: Readonly<Record<string, string>>,\n): Promise<void> {\n\tconst sources: Record<string, ConstructSource> = {};\n\tconst manifest = await discover({\n\t\tpatterns: constructGlobs(workspace),\n\t\tcwd: workspace.root,\n\t\tsources,\n\t});\n\n\tconst pending = await pendingMigrations({\n\t\troot: workspace.root,\n\t\tmanifest,\n\t\tsources,\n\t\tenv,\n\t});\n\n\tfor (const { target, pending: names } of pending) {\n\t\tlogger.log(\n\t\t\t`⚠️ ${target.migrations}: ${names.length} pending (${names.join(', ')}) — run gkm migrate`,\n\t\t);\n\t}\n}\n\n/**\n * What `gkm dev --migrate` and `gkm dev --seed` do before the apps start:\n * migrate, and with `seed`, run the seeds after — once, at startup, never on a\n * save, for the reason {@link reportPendingMigrations} gives.\n *\n * A failure here is thrown, unlike the report's: the developer asked for the\n * database to be brought up to date, and apps started against one in an\n * unknown state would fail somewhere less obvious.\n */\nexport async function prepareDevDatabases(\n\tworkspace: NormalizedWorkspace,\n\tenv: Readonly<Record<string, string>>,\n\t{ seed = false }: { seed?: boolean } = {},\n): Promise<void> {\n\tconst sources: Record<string, ConstructSource> = {};\n\tconst manifest = await discover({\n\t\tpatterns: constructGlobs(workspace),\n\t\tcwd: workspace.root,\n\t\tsources,\n\t});\n\tconst options = { root: workspace.root, manifest, sources, env };\n\n\tif (!seed) {\n\t\tlogMigrations(await migrateDatabases(options));\n\t\treturn;\n\t}\n\n\tconst { migrations, seeds } = await migrateAndSeed({\n\t\t...options,\n\t\tstage: workspace.stages.local,\n\t});\n\tlogMigrations(migrations);\n\tlogSeeds(seeds);\n}\n\nexport interface MigrationOptions {\n\t/** The construct the migration is for, by name. */\n\tconstruct: string;\n\t/** What the file is called, after its timestamp. */\n\tname?: string;\n\t/** The file's timestamp; the current time when absent. */\n\tnow?: Date;\n}\n\n/**\n * `gkm migration <construct> [name]` — write the construct's next migration.\n *\n * For a database, an empty `up`/`down` to fill in. For an auth server, what\n * its schema is missing, computed against its tenant once every committed\n * migration has been applied — so the file is only the change, and is\n * reviewed and committed like any other.\n */\nexport async function migrationCommand(\n\toptions: MigrationOptions,\n): Promise<string | undefined> {\n\tconst ready = await readyStage(process.cwd());\n\tconst targets = migrationTargets(ready.manifest);\n\tconst id = canonicalId(options.construct);\n\n\tconst auth = authConstruct(ready.sources, id);\n\tif (auth) {\n\t\tconst target = targets.find((t) => t.id === auth.databaseId);\n\t\tif (!target) {\n\t\t\tthrow new NoSuchMigrationTarget(auth.databaseId, ids(targets));\n\t\t}\n\t\treturn writeAuthMigration(ready, auth, target, options);\n\t}\n\n\tconst target = targets.find((t) => t.id === id);\n\tif (!target) {\n\t\tthrow new NoSuchMigrationTarget(options.construct, [\n\t\t\t...ids(targets),\n\t\t\t...authIds(ready.sources),\n\t\t]);\n\t}\n\tif (!options.name) throw new MigrationNeedsName(options.construct);\n\n\tconst file = await write(\n\t\tready.workspace.root,\n\t\ttarget,\n\t\t`${stamp(options.now)}_${slug(options.name)}.ts`,\n\t\tSTUB,\n\t);\n\tlogger.log(`📝 ${file}`);\n\treturn file;\n}\n\n/** What an auth construct offers `gkm migration`, duck-typed across copies. */\ninterface AuthConstruct {\n\tid: string;\n\tdatabaseId: string;\n\tpendingMigration(\n\t\toptions: ServiceRegisterOptions,\n\t): Promise<string | undefined>;\n}\n\nfunction authConstruct(\n\tsources: Readonly<Record<string, ConstructSource>>,\n\tid: string,\n): AuthConstruct | undefined {\n\tconst construct = sources[id]?.construct as\n\t\t| Partial<AuthConstruct>\n\t\t| undefined;\n\treturn construct &&\n\t\ttypeof construct.pendingMigration === 'function' &&\n\t\ttypeof construct.databaseId === 'string'\n\t\t? (construct as AuthConstruct)\n\t\t: undefined;\n}\n\nfunction authIds(sources: Readonly<Record<string, ConstructSource>>): string[] {\n\treturn Object.keys(sources).filter((id) => authConstruct(sources, id));\n}\n\nasync function writeAuthMigration(\n\tready: ReadyStage,\n\tauth: AuthConstruct,\n\ttarget: MigrationTarget,\n\toptions: MigrationOptions,\n): Promise<string | undefined> {\n\t// Against a tenant already at its committed migrations, so the comparison\n\t// finds only what the files do not yet say.\n\tawait migrateDatabases({\n\t\troot: ready.workspace.root,\n\t\tmanifest: ready.manifest,\n\t\tsources: ready.sources,\n\t\tenv: ready.env,\n\t\tonly: target.id,\n\t});\n\n\t// Its secret and URL come from the stage's secrets; the tenant's owner URL\n\t// from the reconcile above, which wins.\n\tconst stage = ready.workspace.stages.local;\n\tconst secrets = await loadSecretsForApp(\n\t\tawait secretsStoreFor(ready.workspace, stage),\n\t\tstage,\n\t);\n\tconst pending = await auth.pendingMigration({\n\t\tenvParser: new EnvironmentParser({ ...secrets, ...ready.env }),\n\t\tcontext: serviceContext,\n\t});\n\n\tif (!pending) {\n\t\tlogger.log(`✓ ${target.migrations}: ${auth.id} is up to date`);\n\t\treturn undefined;\n\t}\n\n\tconst file = await write(\n\t\tready.workspace.root,\n\t\ttarget,\n\t\t`${stamp(options.now)}_${slug(options.name ?? 'better_auth')}.sql`,\n\t\t`${pending.trim()}\\n`,\n\t);\n\tlogger.log(`📝 ${file}`);\n\treturn file;\n}\n\nconst STUB = `import type { Kysely } from 'kysely';\n\nexport async function up(db: Kysely<unknown>): Promise<void> {\n // await db.schema.createTable('…').addColumn(…).execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n // await db.schema.dropTable('…').execute();\n}\n`;\n\nasync function write(\n\troot: string,\n\ttarget: MigrationTarget,\n\tfile: string,\n\tcontent: string,\n): Promise<string> {\n\tconst folder = join(root, target.migrations);\n\tawait mkdir(folder, { recursive: true });\n\tawait writeFile(join(folder, file), content, { flag: 'wx' });\n\treturn `${target.migrations}/${file}`;\n}\n\nfunction ids(targets: readonly MigrationTarget[]): string[] {\n\treturn targets.map((target) => target.id);\n}\n"],"mappings":"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"}
|
package/dist/openapi.cjs
CHANGED
|
@@ -1,10 +1,199 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
|
-
|
|
3
|
-
require(
|
|
4
|
-
require(
|
|
5
|
-
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const require_config = require("./config-CJ-PAb7M.cjs");
|
|
4
|
+
const require_OpenApiTsGenerator = require("./OpenApiTsGenerator-B4Kj_qNU.cjs");
|
|
5
|
+
let node_fs_promises = require("node:fs/promises");
|
|
6
|
+
let node_path = require("node:path");
|
|
7
|
+
let node_child_process = require("node:child_process");
|
|
8
|
+
let _geekmidas_manifest = require("@geekmidas/manifest");
|
|
9
|
+
let node_url = require("node:url");
|
|
10
|
+
//#region src/openapi.ts
|
|
11
|
+
/**
|
|
12
|
+
* Default output path for generated OpenAPI client (used for single-app configs)
|
|
13
|
+
*/
|
|
14
|
+
const OPENAPI_OUTPUT_PATH = "./.gkm/openapi.ts";
|
|
15
|
+
/**
|
|
16
|
+
* Where a surface's generated client lands.
|
|
17
|
+
*
|
|
18
|
+
* Named from the surface, so `new RestApi('Webhooks')` writes
|
|
19
|
+
* `.gkm/openapi/webhooks.ts` — the same kebab form that gives it its container
|
|
20
|
+
* name and its `WEBHOOKS_URL`.
|
|
21
|
+
*/
|
|
22
|
+
function openApiPathFor(surfaceId) {
|
|
23
|
+
return `./.gkm/openapi/${(0, _geekmidas_manifest.kebabCase)(surfaceId)}.ts`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolve OpenAPI config from GkmConfig
|
|
27
|
+
*/
|
|
28
|
+
function resolveOpenApiConfig(config) {
|
|
29
|
+
if (config.openapi === false) return { enabled: false };
|
|
30
|
+
if (config.openapi === true || config.openapi === void 0) return {
|
|
31
|
+
enabled: true,
|
|
32
|
+
title: "API Documentation",
|
|
33
|
+
version: "1.0.0",
|
|
34
|
+
description: "Auto-generated API documentation from endpoints"
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
enabled: config.openapi.enabled !== false,
|
|
38
|
+
title: config.openapi.title || "API Documentation",
|
|
39
|
+
version: config.openapi.version || "1.0.0",
|
|
40
|
+
description: config.openapi.description || "Auto-generated API documentation from endpoints"
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Load endpoints from a glob, then generate.
|
|
45
|
+
*
|
|
46
|
+
* For the callers that have no endpoints in hand — `gkm openapi`, and `gkm dev`
|
|
47
|
+
* regenerating on reload. The glob is the app's derived one — the conventional
|
|
48
|
+
* directories under it — not a `routes` field anybody wrote.
|
|
49
|
+
*/
|
|
50
|
+
async function generateOpenApiFrom(routes, options = {}) {
|
|
51
|
+
return generateOpenApi((await new require_OpenApiTsGenerator.EndpointGenerator().load(routes, void 0, options.bustCache)).map(({ construct }) => construct), options);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Generate an OpenAPI spec for each surface these endpoints name.
|
|
55
|
+
*
|
|
56
|
+
* Takes the endpoints, not a glob to find them with. The build has already
|
|
57
|
+
* loaded every one of them to generate handlers, so globbing again was a second
|
|
58
|
+
* discovery pass over the same files — and the reason this function needed a
|
|
59
|
+
* `routes` to be told where to look, which is how a retired config field stayed
|
|
60
|
+
* load-bearing.
|
|
61
|
+
*
|
|
62
|
+
* @returns What was written, or null if disabled or there were none
|
|
63
|
+
*/
|
|
64
|
+
async function generateOpenApi(endpoints, options = {}) {
|
|
65
|
+
const logger = options.silent ? { log: () => {} } : console;
|
|
66
|
+
const openApiConfig = resolveOpenApiConfig({ openapi: options.openapi });
|
|
67
|
+
if (!openApiConfig.enabled) return null;
|
|
68
|
+
if (endpoints.length === 0) {
|
|
69
|
+
logger.log("No valid endpoints found for OpenAPI generation");
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const bySurface = /* @__PURE__ */ new Map();
|
|
73
|
+
for (const endpoint of endpoints) {
|
|
74
|
+
const owner = endpoint.owner ?? endpoint.surface?.id;
|
|
75
|
+
if (!owner) continue;
|
|
76
|
+
const existing = bySurface.get(owner);
|
|
77
|
+
if (existing) existing.push(endpoint);
|
|
78
|
+
else bySurface.set(owner, [endpoint]);
|
|
79
|
+
}
|
|
80
|
+
if (bySurface.size === 0) {
|
|
81
|
+
logger.log("No endpoints named a surface, so no OpenAPI was generated");
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const tsGenerator = new require_OpenApiTsGenerator.OpenApiTsGenerator();
|
|
85
|
+
const written = [];
|
|
86
|
+
for (const [surfaceId, surfaceEndpoints] of bySurface) {
|
|
87
|
+
const relative = openApiPathFor(surfaceId);
|
|
88
|
+
const outputPath = (0, node_path.join)(process.cwd(), relative);
|
|
89
|
+
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(outputPath), { recursive: true });
|
|
90
|
+
const tsContent = await tsGenerator.generate(surfaceEndpoints, {
|
|
91
|
+
title: openApiConfig.title ?? surfaceId,
|
|
92
|
+
version: openApiConfig.version,
|
|
93
|
+
description: openApiConfig.description
|
|
94
|
+
});
|
|
95
|
+
await (0, node_fs_promises.writeFile)(outputPath, tsContent);
|
|
96
|
+
written.push(relative);
|
|
97
|
+
logger.log(`📄 OpenAPI client generated: ${relative} (${surfaceEndpoints.length} endpoints)`);
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
outputPath: (0, node_path.join)(process.cwd(), written[0]),
|
|
101
|
+
outputPaths: written.map((r) => (0, node_path.join)(process.cwd(), r)),
|
|
102
|
+
endpointCount: endpoints.length
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async function openapiCommand(options = {}) {
|
|
106
|
+
const logger = console;
|
|
107
|
+
try {
|
|
108
|
+
const loadedConfig = await require_config.loadWorkspaceConfig(options.cwd);
|
|
109
|
+
if (loadedConfig.type === "single") {
|
|
110
|
+
const config = loadedConfig.raw;
|
|
111
|
+
if (!config.openapi) config.openapi = { enabled: true };
|
|
112
|
+
const result = await generateOpenApiFrom(config.constructs, { openapi: config.openapi });
|
|
113
|
+
if (result) logger.log(`Found ${result.endpointCount} endpoints`);
|
|
114
|
+
} else {
|
|
115
|
+
const { workspace } = loadedConfig;
|
|
116
|
+
const workspaceRoot = workspace.root;
|
|
117
|
+
const backendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "backend" && app.openapi !== false && (typeof app.openapi !== "object" || app.openapi.enabled !== false));
|
|
118
|
+
if (backendApps.length === 0) {
|
|
119
|
+
logger.log("No backend apps with OpenAPI enabled found");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (options.app) {
|
|
123
|
+
const entry = backendApps.find(([name]) => name === options.app);
|
|
124
|
+
if (!entry) throw new Error(`App "${options.app}" not found or has OpenAPI disabled`);
|
|
125
|
+
const [appName, app] = entry;
|
|
126
|
+
const result = await generateOpenApiForApp(workspaceRoot, appName, app);
|
|
127
|
+
if (result) logger.log(`📄 [${appName}] Generated OpenAPI (${result.endpointCount} endpoints)`);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
for (const [appName, app] of backendApps) {
|
|
131
|
+
const appPath = (0, node_path.join)(workspaceRoot, app.path);
|
|
132
|
+
if ((0, node_path.resolve)(appPath) === (0, node_path.resolve)(workspaceRoot)) {
|
|
133
|
+
const result = await generateOpenApiForApp(workspaceRoot, appName, app, false);
|
|
134
|
+
if (result) logger.log(`Found ${result.endpointCount} endpoints`);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
await runOpenApiInSubprocess(appPath, appName);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
throw new Error(`OpenAPI generation failed: ${error.message}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Generate OpenAPI for a single named app within a workspace.
|
|
146
|
+
* Runs in-process. The caller is responsible for ensuring `process.cwd()`
|
|
147
|
+
* is the app's directory so tsx loads the app's tsconfig path aliases.
|
|
148
|
+
*/
|
|
149
|
+
async function generateOpenApiForApp(workspaceRoot, _appName, app, silent = true) {
|
|
150
|
+
if (app.type !== "backend") return null;
|
|
151
|
+
const appPath = (0, node_path.join)(workspaceRoot, app.path);
|
|
152
|
+
return generateOpenApiFrom([(0, node_path.join)(appPath, "**/*.ts")], {
|
|
153
|
+
openapi: app.openapi ?? { enabled: true },
|
|
154
|
+
silent
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Resolve the gkm bin path. Tests can override via `GKM_BIN_PATH` env var
|
|
159
|
+
* to avoid depending on the built dist.
|
|
160
|
+
*/
|
|
161
|
+
function resolveGkmBinPath() {
|
|
162
|
+
if (process.env.GKM_BIN_PATH) return process.env.GKM_BIN_PATH;
|
|
163
|
+
return (0, node_url.fileURLToPath)(new URL("../bin/gkm.mjs", require("url").pathToFileURL(__filename).href));
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Spawn a subprocess that runs `gkm openapi --app <name>` with `cwd` set to
|
|
167
|
+
* the app's directory. Inherits `NODE_OPTIONS` (which already contains
|
|
168
|
+
* `--import tsx`), so the child gets a fresh tsx instance whose tsconfig
|
|
169
|
+
* discovery picks up the app's tsconfig path aliases.
|
|
170
|
+
*/
|
|
171
|
+
async function runOpenApiInSubprocess(appCwd, appName) {
|
|
172
|
+
const binPath = resolveGkmBinPath();
|
|
173
|
+
await new Promise((resolve, reject) => {
|
|
174
|
+
const child = (0, node_child_process.spawn)(process.execPath, [
|
|
175
|
+
binPath,
|
|
176
|
+
"openapi",
|
|
177
|
+
"--app",
|
|
178
|
+
appName
|
|
179
|
+
], {
|
|
180
|
+
cwd: appCwd,
|
|
181
|
+
stdio: "inherit",
|
|
182
|
+
env: process.env
|
|
183
|
+
});
|
|
184
|
+
child.on("error", reject);
|
|
185
|
+
child.on("close", (code) => {
|
|
186
|
+
if (code === 0) resolve();
|
|
187
|
+
else reject(/* @__PURE__ */ new Error(`OpenAPI generation for app "${appName}" exited with code ${code}`));
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
//#endregion
|
|
192
|
+
exports.OPENAPI_OUTPUT_PATH = OPENAPI_OUTPUT_PATH;
|
|
193
|
+
exports.generateOpenApi = generateOpenApi;
|
|
194
|
+
exports.generateOpenApiFrom = generateOpenApiFrom;
|
|
195
|
+
exports.openApiPathFor = openApiPathFor;
|
|
196
|
+
exports.openapiCommand = openapiCommand;
|
|
197
|
+
exports.resolveOpenApiConfig = resolveOpenApiConfig;
|
|
6
198
|
|
|
7
|
-
|
|
8
|
-
exports.generateOpenApi = require_openapi.generateOpenApi;
|
|
9
|
-
exports.openapiCommand = require_openapi.openapiCommand;
|
|
10
|
-
exports.resolveOpenApiConfig = require_openapi.resolveOpenApiConfig;
|
|
199
|
+
//# sourceMappingURL=openapi.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.cjs","names":["kebabCase","EndpointGenerator","OpenApiTsGenerator","join","mkdir","dirname","writeFile","loadWorkspaceConfig","resolve","fileURLToPath","spawn"],"sources":["../src/openapi.ts"],"sourcesContent":["#!/usr/bin/env -S npx tsx\n\nimport { spawn } from 'node:child_process';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { Endpoint } from '@geekmidas/constructs/endpoints';\nimport { kebabCase } from '@geekmidas/manifest';\nimport { loadWorkspaceConfig } from './config.js';\nimport { EndpointGenerator } from './generators/EndpointGenerator.js';\nimport { OpenApiTsGenerator } from './generators/OpenApiTsGenerator.js';\nimport type { GkmConfig, OpenApiConfig, Routes } from './types.js';\nimport { normalizeRoutes } from './workspace/client-generator.js';\nimport type { NormalizedAppConfig } from './workspace/types.js';\n\ninterface OpenAPIOptions {\n\tcwd?: string;\n\t/**\n\t * Workspace mode: generate for a single named backend app. When set,\n\t * generation runs in-process against the current CWD. When unset,\n\t * workspace mode spawns one subprocess per backend app so each gets a\n\t * fresh tsx that loads the app's own tsconfig (path aliases included).\n\t */\n\tapp?: string;\n}\n\n/**\n * Default output path for generated OpenAPI client (used for single-app configs)\n */\nexport const OPENAPI_OUTPUT_PATH = './.gkm/openapi.ts';\n\n/**\n * Where a surface's generated client lands.\n *\n * Named from the surface, so `new RestApi('Webhooks')` writes\n * `.gkm/openapi/webhooks.ts` — the same kebab form that gives it its container\n * name and its `WEBHOOKS_URL`.\n */\nexport function openApiPathFor(surfaceId: string): string {\n\treturn `./.gkm/openapi/${kebabCase(surfaceId)}.ts`;\n}\n\n/**\n * Resolve OpenAPI config from GkmConfig\n */\nexport function resolveOpenApiConfig(config: {\n\topenapi?: boolean | OpenApiConfig;\n}): OpenApiConfig & { enabled: boolean } {\n\tif (config.openapi === false) {\n\t\treturn { enabled: false };\n\t}\n\n\tif (config.openapi === true || config.openapi === undefined) {\n\t\t// Enable by default when not explicitly set (undefined) or explicitly true\n\t\treturn {\n\t\t\tenabled: true,\n\t\t\ttitle: 'API Documentation',\n\t\t\tversion: '1.0.0',\n\t\t\tdescription: 'Auto-generated API documentation from endpoints',\n\t\t};\n\t}\n\n\treturn {\n\t\tenabled: config.openapi.enabled !== false,\n\t\ttitle: config.openapi.title || 'API Documentation',\n\t\tversion: config.openapi.version || '1.0.0',\n\t\tdescription:\n\t\t\tconfig.openapi.description ||\n\t\t\t'Auto-generated API documentation from endpoints',\n\t};\n}\n\n/** What a generation run wrote. */\nexport interface OpenApiResult {\n\t/** The first spec written — kept so existing callers still compile. */\n\toutputPath: string;\n\t/** Every spec written, one per surface. */\n\toutputPaths: string[];\n\t/** Endpoints across all of them. */\n\tendpointCount: number;\n}\n\n/**\n * Load endpoints from a glob, then generate.\n *\n * For the callers that have no endpoints in hand — `gkm openapi`, and `gkm dev`\n * regenerating on reload. The glob is the app's derived one — the conventional\n * directories under it — not a `routes` field anybody wrote.\n */\nexport async function generateOpenApiFrom(\n\troutes: Routes,\n\toptions: {\n\t\topenapi?: boolean | OpenApiConfig;\n\t\tsilent?: boolean;\n\t\tbustCache?: boolean;\n\t} = {},\n): Promise<OpenApiResult | null> {\n\tconst loaded = await new EndpointGenerator().load(\n\t\troutes,\n\t\tundefined,\n\t\toptions.bustCache,\n\t);\n\n\treturn generateOpenApi(\n\t\tloaded.map(({ construct }) => construct),\n\t\toptions,\n\t);\n}\n\n/**\n * Generate an OpenAPI spec for each surface these endpoints name.\n *\n * Takes the endpoints, not a glob to find them with. The build has already\n * loaded every one of them to generate handlers, so globbing again was a second\n * discovery pass over the same files — and the reason this function needed a\n * `routes` to be told where to look, which is how a retired config field stayed\n * load-bearing.\n *\n * @returns What was written, or null if disabled or there were none\n */\nexport async function generateOpenApi(\n\tendpoints: readonly Endpoint<any, any, any, any, any>[],\n\toptions: {\n\t\topenapi?: boolean | OpenApiConfig;\n\t\tsilent?: boolean;\n\t} = {},\n): Promise<OpenApiResult | null> {\n\tconst logger = options.silent ? { log: () => {} } : console;\n\tconst openApiConfig = resolveOpenApiConfig({ openapi: options.openapi });\n\n\tif (!openApiConfig.enabled) {\n\t\treturn null;\n\t}\n\n\tif (endpoints.length === 0) {\n\t\tlogger.log('No valid endpoints found for OpenAPI generation');\n\t\treturn null;\n\t}\n\n\t// One spec per surface, not one per glob.\n\t//\n\t// The glob is a directory, and a directory is not a surface: two `RestApi`s\n\t// whose endpoints live side by side produced a single spec describing both,\n\t// under a filename that named neither. Every endpoint carries the surface\n\t// that serves it, so that is what the spec is cut along — `Api` becomes\n\t// `api.ts`, `Webhooks` becomes `webhooks.ts`, by the same kebab rule that\n\t// gives the surface its container name and its environment keys.\n\tconst bySurface = new Map<string, Endpoint<any, any, any, any, any>[]>();\n\tfor (const endpoint of endpoints) {\n\t\tconst owner = endpoint.owner ?? endpoint.surface?.id;\n\t\tif (!owner) continue;\n\t\tconst existing = bySurface.get(owner);\n\t\tif (existing) existing.push(endpoint);\n\t\telse bySurface.set(owner, [endpoint]);\n\t}\n\n\tif (bySurface.size === 0) {\n\t\tlogger.log('No endpoints named a surface, so no OpenAPI was generated');\n\t\treturn null;\n\t}\n\n\tconst tsGenerator = new OpenApiTsGenerator();\n\tconst written: string[] = [];\n\n\tfor (const [surfaceId, surfaceEndpoints] of bySurface) {\n\t\tconst relative = openApiPathFor(surfaceId);\n\t\tconst outputPath = join(process.cwd(), relative);\n\n\t\tawait mkdir(dirname(outputPath), { recursive: true });\n\n\t\tconst tsContent = await tsGenerator.generate(surfaceEndpoints, {\n\t\t\t// The surface's own name, so a spec says which API it describes\n\t\t\t// rather than repeating one title across all of them.\n\t\t\ttitle: openApiConfig.title ?? surfaceId,\n\t\t\tversion: openApiConfig.version!,\n\t\t\tdescription: openApiConfig.description!,\n\t\t});\n\n\t\tawait writeFile(outputPath, tsContent);\n\t\twritten.push(relative);\n\t\tlogger.log(\n\t\t\t`📄 OpenAPI client generated: ${relative} (${surfaceEndpoints.length} endpoints)`,\n\t\t);\n\t}\n\n\treturn {\n\t\t// The first, for a caller that wants one path. `outputPaths` has them all.\n\t\toutputPath: join(process.cwd(), written[0]!),\n\t\toutputPaths: written.map((r) => join(process.cwd(), r)),\n\t\tendpointCount: endpoints.length,\n\t};\n}\n\nexport async function openapiCommand(\n\toptions: OpenAPIOptions = {},\n): Promise<void> {\n\tconst logger = console;\n\n\ttry {\n\t\tconst loadedConfig = await loadWorkspaceConfig(options.cwd);\n\n\t\tif (loadedConfig.type === 'single') {\n\t\t\t// Single-app config - use existing behavior\n\t\t\tconst config = loadedConfig.raw as GkmConfig;\n\n\t\t\t// Enable openapi if not configured\n\t\t\tif (!config.openapi) {\n\t\t\t\tconfig.openapi = { enabled: true };\n\t\t\t}\n\n\t\t\tconst result = await generateOpenApiFrom(config.constructs, {\n\t\t\t\topenapi: config.openapi,\n\t\t\t});\n\n\t\t\tif (result) {\n\t\t\t\tlogger.log(`Found ${result.endpointCount} endpoints`);\n\t\t\t}\n\t\t} else {\n\t\t\t// Workspace config - generate for each backend app\n\t\t\tconst { workspace } = loadedConfig;\n\t\t\t// Always derive the workspace root from the loaded config rather than\n\t\t\t// CWD. Subprocesses run with CWD set to an app directory, and falling\n\t\t\t// back to CWD there causes `join(workspaceRoot, app.path)` to point at\n\t\t\t// a non-existent nested path (e.g. `apps/api/apps/api`).\n\t\t\tconst workspaceRoot = workspace.root;\n\n\t\t\t// Backend apps with openapi enabled — which is all of them that have\n\t\t\t// routes, unless one says otherwise.\n\t\t\t//\n\t\t\t// This used to require `openapi: true` written out, while the app's\n\t\t\t// own build generated a spec regardless. Two paths disagreeing about\n\t\t\t// the same flag: the app emitted `.gkm/openapi.ts` and this pass,\n\t\t\t// finding nothing \"enabled\", skipped copying it to the frontends that\n\t\t\t// consume it. A surface whose routes are declared rather than\n\t\t\t// discovered has no spec to generate, so it is not a candidate.\n\t\t\tconst backendApps = Object.entries(workspace.apps).filter(\n\t\t\t\t([_, app]) =>\n\t\t\t\t\tapp.type === 'backend' &&\n\t\t\t\t\tapp.openapi !== false &&\n\t\t\t\t\t(typeof app.openapi !== 'object' || app.openapi.enabled !== false),\n\t\t\t);\n\n\t\t\tif (backendApps.length === 0) {\n\t\t\t\tlogger.log('No backend apps with OpenAPI enabled found');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Single-app mode: generate in-process. The caller is expected to\n\t\t\t// have CWD set to the app's directory (e.g., when invoked as a\n\t\t\t// subprocess), so tsx picks up the app's tsconfig path aliases.\n\t\t\tif (options.app) {\n\t\t\t\tconst entry = backendApps.find(([name]) => name === options.app);\n\t\t\t\tif (!entry) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`App \"${options.app}\" not found or has OpenAPI disabled`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst [appName, app] = entry;\n\t\t\t\tconst result = await generateOpenApiForApp(workspaceRoot, appName, app);\n\t\t\t\tif (result) {\n\t\t\t\t\tlogger.log(\n\t\t\t\t\t\t`📄 [${appName}] Generated OpenAPI (${result.endpointCount} endpoints)`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Multi-app mode: spawn a subprocess per backend app. Each\n\t\t\t// subprocess starts with CWD at the app's directory so that\n\t\t\t// tsx's tsconfig discovery picks up the app's `paths` aliases\n\t\t\t// (e.g., `~/*`) instead of the workspace root's tsconfig.\n\t\t\tfor (const [appName, app] of backendApps) {\n\t\t\t\t// No `app.routes` check any more: the per-kind globs are gone, and\n\t\t\t\t// an app's code is found by one glob rather than by a field naming\n\t\t\t\t// endpoints. Guarding on `routes` here meant every app was skipped\n\t\t\t\t// the moment that field stopped existing — silently, because a loop\n\t\t\t\t// that runs zero times looks exactly like one with nothing to do.\n\t\t\t\tconst appPath = join(workspaceRoot, app.path);\n\n\t\t\t\t// A subprocess exists to put CWD inside the app, so tsx picks up\n\t\t\t\t// that app's tsconfig aliases instead of the workspace root's. When\n\t\t\t\t// the app *is* the root there is nothing to change: spawning would\n\t\t\t\t// re-enter the same directory, having paid for a process and a\n\t\t\t\t// module graph, and needing `tsx` resolvable from it.\n\t\t\t\tif (resolve(appPath) === resolve(workspaceRoot)) {\n\t\t\t\t\t// Not silent: silence is for a subprocess whose output the\n\t\t\t\t\t// parent relays, and there is no parent here to relay it. The\n\t\t\t\t\t// per-surface lines come from the generator; the count is this\n\t\t\t\t\t// command's own summary, as in the single-app path.\n\t\t\t\t\tconst result = await generateOpenApiForApp(\n\t\t\t\t\t\tworkspaceRoot,\n\t\t\t\t\t\tappName,\n\t\t\t\t\t\tapp,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t);\n\t\t\t\t\tif (result) {\n\t\t\t\t\t\tlogger.log(`Found ${result.endpointCount} endpoints`);\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tawait runOpenApiInSubprocess(appPath, appName);\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(`OpenAPI generation failed: ${(error as Error).message}`);\n\t}\n}\n\n/**\n * Generate OpenAPI for a single named app within a workspace.\n * Runs in-process. The caller is responsible for ensuring `process.cwd()`\n * is the app's directory so tsx loads the app's tsconfig path aliases.\n */\nasync function generateOpenApiForApp(\n\tworkspaceRoot: string,\n\t_appName: string,\n\tapp: NormalizedAppConfig,\n\t/**\n\t * Quiet by default, because the usual caller is a subprocess whose output\n\t * the parent relays. Generating in-process there is no parent to relay to,\n\t * and silence would swallow the only report of what happened.\n\t */\n\tsilent = true,\n): Promise<{ outputPath: string; endpointCount: number } | null> {\n\t// A backend app, and everything under it.\n\t//\n\t// It used to gate on `app.routes` and glob that; derive no longer sets it,\n\t// because an app has one glob and which kind a module exports is decided by\n\t// the value. A surface is an app, so an app's own directory contains exactly\n\t// its own surface's endpoints — and the split inside is by surface anyway.\n\tif (app.type !== 'backend') {\n\t\treturn null;\n\t}\n\n\tconst appPath = join(workspaceRoot, app.path);\n\tconst globs = [join(appPath, '**/*.ts')];\n\n\treturn generateOpenApiFrom(globs, {\n\t\t// Absent means enabled, which is what the caller's filter already decided\n\t\t// when it kept this app: it skips one that says `openapi: false` and keeps\n\t\t// every other. Passing `undefined` through let `resolveOpenApiConfig`\n\t\t// default it to *disabled*, so an app selected for generation generated\n\t\t// nothing and said nothing — two places disagreeing about one flag, which\n\t\t// is the failure the filter above was written to describe.\n\t\topenapi: app.openapi ?? { enabled: true },\n\t\tsilent,\n\t});\n}\n\n/**\n * Resolve the gkm bin path. Tests can override via `GKM_BIN_PATH` env var\n * to avoid depending on the built dist.\n */\nfunction resolveGkmBinPath(): string {\n\tif (process.env.GKM_BIN_PATH) {\n\t\treturn process.env.GKM_BIN_PATH;\n\t}\n\treturn fileURLToPath(new URL('../bin/gkm.mjs', import.meta.url));\n}\n\n/**\n * Spawn a subprocess that runs `gkm openapi --app <name>` with `cwd` set to\n * the app's directory. Inherits `NODE_OPTIONS` (which already contains\n * `--import tsx`), so the child gets a fresh tsx instance whose tsconfig\n * discovery picks up the app's tsconfig path aliases.\n */\nasync function runOpenApiInSubprocess(\n\tappCwd: string,\n\tappName: string,\n): Promise<void> {\n\tconst binPath = resolveGkmBinPath();\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tconst child = spawn(\n\t\t\tprocess.execPath,\n\t\t\t[binPath, 'openapi', '--app', appName],\n\t\t\t{\n\t\t\t\tcwd: appCwd,\n\t\t\t\tstdio: 'inherit',\n\t\t\t\tenv: process.env,\n\t\t\t},\n\t\t);\n\n\t\tchild.on('error', reject);\n\t\tchild.on('close', (code) => {\n\t\t\tif (code === 0) {\n\t\t\t\tresolve();\n\t\t\t} else {\n\t\t\t\treject(\n\t\t\t\t\tnew Error(\n\t\t\t\t\t\t`OpenAPI generation for app \"${appName}\" exited with code ${code}`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t});\n}\n"],"mappings":"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"}
|
package/dist/openapi.d.cts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { a as Routes, i as OpenApiConfig } from "./types-DfdYe31u.cjs";
|
|
2
|
+
import { Endpoint } from "@geekmidas/constructs/endpoints";
|
|
4
3
|
//#region src/openapi.d.ts
|
|
5
4
|
interface OpenAPIOptions {
|
|
6
5
|
cwd?: string;
|
|
@@ -15,25 +14,59 @@ interface OpenAPIOptions {
|
|
|
15
14
|
/**
|
|
16
15
|
* Default output path for generated OpenAPI client (used for single-app configs)
|
|
17
16
|
*/
|
|
18
|
-
declare const OPENAPI_OUTPUT_PATH = "./.gkm/openapi.ts";
|
|
17
|
+
export declare const OPENAPI_OUTPUT_PATH = "./.gkm/openapi.ts";
|
|
18
|
+
/**
|
|
19
|
+
* Where a surface's generated client lands.
|
|
20
|
+
*
|
|
21
|
+
* Named from the surface, so `new RestApi('Webhooks')` writes
|
|
22
|
+
* `.gkm/openapi/webhooks.ts` — the same kebab form that gives it its container
|
|
23
|
+
* name and its `WEBHOOKS_URL`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function openApiPathFor(surfaceId: string): string;
|
|
19
26
|
/**
|
|
20
27
|
* Resolve OpenAPI config from GkmConfig
|
|
21
28
|
*/
|
|
22
|
-
declare function resolveOpenApiConfig(config:
|
|
29
|
+
export declare function resolveOpenApiConfig(config: {
|
|
30
|
+
openapi?: boolean | OpenApiConfig;
|
|
31
|
+
}): OpenApiConfig & {
|
|
23
32
|
enabled: boolean;
|
|
24
33
|
};
|
|
34
|
+
/** What a generation run wrote. */
|
|
35
|
+
export interface OpenApiResult {
|
|
36
|
+
/** The first spec written — kept so existing callers still compile. */
|
|
37
|
+
outputPath: string;
|
|
38
|
+
/** Every spec written, one per surface. */
|
|
39
|
+
outputPaths: string[];
|
|
40
|
+
/** Endpoints across all of them. */
|
|
41
|
+
endpointCount: number;
|
|
42
|
+
}
|
|
25
43
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
44
|
+
* Load endpoints from a glob, then generate.
|
|
45
|
+
*
|
|
46
|
+
* For the callers that have no endpoints in hand — `gkm openapi`, and `gkm dev`
|
|
47
|
+
* regenerating on reload. The glob is the app's derived one — the conventional
|
|
48
|
+
* directories under it — not a `routes` field anybody wrote.
|
|
28
49
|
*/
|
|
29
|
-
declare function
|
|
50
|
+
export declare function generateOpenApiFrom(routes: Routes, options?: {
|
|
51
|
+
openapi?: boolean | OpenApiConfig;
|
|
30
52
|
silent?: boolean;
|
|
31
53
|
bustCache?: boolean;
|
|
32
|
-
}): Promise<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
54
|
+
}): Promise<OpenApiResult | null>;
|
|
55
|
+
/**
|
|
56
|
+
* Generate an OpenAPI spec for each surface these endpoints name.
|
|
57
|
+
*
|
|
58
|
+
* Takes the endpoints, not a glob to find them with. The build has already
|
|
59
|
+
* loaded every one of them to generate handlers, so globbing again was a second
|
|
60
|
+
* discovery pass over the same files — and the reason this function needed a
|
|
61
|
+
* `routes` to be told where to look, which is how a retired config field stayed
|
|
62
|
+
* load-bearing.
|
|
63
|
+
*
|
|
64
|
+
* @returns What was written, or null if disabled or there were none
|
|
65
|
+
*/
|
|
66
|
+
export declare function generateOpenApi(endpoints: readonly Endpoint<any, any, any, any, any>[], options?: {
|
|
67
|
+
openapi?: boolean | OpenApiConfig;
|
|
68
|
+
silent?: boolean;
|
|
69
|
+
}): Promise<OpenApiResult | null>;
|
|
70
|
+
export declare function openapiCommand(options?: OpenAPIOptions): Promise<void>;
|
|
37
71
|
//#endregion
|
|
38
|
-
export { OPENAPI_OUTPUT_PATH, generateOpenApi, openapiCommand, resolveOpenApiConfig };
|
|
39
72
|
//# sourceMappingURL=openapi.d.cts.map
|
package/dist/openapi.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openapi.d.cts","names":[],"sources":["../src/openapi.ts"],"
|
|
1
|
+
{"version":3,"file":"openapi.d.cts","names":[],"sources":["../src/openapi.ts"],"mappings":";;;UAeU;EACT;;;;;;;EAOA;;;;;qBAMY;;;;;;;;wBASG,eAAe;;;;wBAOf,qBAAqB;EACpC,oBAAoB;IACjB;EAAkB;;;iBA0BL;;EAEhB;;EAEA;;EAEA;;;;;;;;;wBAUqB,oBACrB,QAAQ,QACR;EACC,oBAAoB;EACpB;EACA;IAEC,QAAQ;;;;;;;;;;;;wBAwBW,gBACrB,oBAAoB,qCACpB;EACC,oBAAoB;EACpB;IAEC,QAAQ;wBAmEW,eACrB,UAAS,iBACP"}
|
package/dist/openapi.d.mts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { a as Routes, i as OpenApiConfig } from "./types-0R124hrY.mjs";
|
|
2
|
+
import { Endpoint } from "@geekmidas/constructs/endpoints";
|
|
4
3
|
//#region src/openapi.d.ts
|
|
5
4
|
interface OpenAPIOptions {
|
|
6
5
|
cwd?: string;
|
|
@@ -15,25 +14,59 @@ interface OpenAPIOptions {
|
|
|
15
14
|
/**
|
|
16
15
|
* Default output path for generated OpenAPI client (used for single-app configs)
|
|
17
16
|
*/
|
|
18
|
-
declare const OPENAPI_OUTPUT_PATH = "./.gkm/openapi.ts";
|
|
17
|
+
export declare const OPENAPI_OUTPUT_PATH = "./.gkm/openapi.ts";
|
|
18
|
+
/**
|
|
19
|
+
* Where a surface's generated client lands.
|
|
20
|
+
*
|
|
21
|
+
* Named from the surface, so `new RestApi('Webhooks')` writes
|
|
22
|
+
* `.gkm/openapi/webhooks.ts` — the same kebab form that gives it its container
|
|
23
|
+
* name and its `WEBHOOKS_URL`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function openApiPathFor(surfaceId: string): string;
|
|
19
26
|
/**
|
|
20
27
|
* Resolve OpenAPI config from GkmConfig
|
|
21
28
|
*/
|
|
22
|
-
declare function resolveOpenApiConfig(config:
|
|
29
|
+
export declare function resolveOpenApiConfig(config: {
|
|
30
|
+
openapi?: boolean | OpenApiConfig;
|
|
31
|
+
}): OpenApiConfig & {
|
|
23
32
|
enabled: boolean;
|
|
24
33
|
};
|
|
34
|
+
/** What a generation run wrote. */
|
|
35
|
+
export interface OpenApiResult {
|
|
36
|
+
/** The first spec written — kept so existing callers still compile. */
|
|
37
|
+
outputPath: string;
|
|
38
|
+
/** Every spec written, one per surface. */
|
|
39
|
+
outputPaths: string[];
|
|
40
|
+
/** Endpoints across all of them. */
|
|
41
|
+
endpointCount: number;
|
|
42
|
+
}
|
|
25
43
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
44
|
+
* Load endpoints from a glob, then generate.
|
|
45
|
+
*
|
|
46
|
+
* For the callers that have no endpoints in hand — `gkm openapi`, and `gkm dev`
|
|
47
|
+
* regenerating on reload. The glob is the app's derived one — the conventional
|
|
48
|
+
* directories under it — not a `routes` field anybody wrote.
|
|
28
49
|
*/
|
|
29
|
-
declare function
|
|
50
|
+
export declare function generateOpenApiFrom(routes: Routes, options?: {
|
|
51
|
+
openapi?: boolean | OpenApiConfig;
|
|
30
52
|
silent?: boolean;
|
|
31
53
|
bustCache?: boolean;
|
|
32
|
-
}): Promise<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
54
|
+
}): Promise<OpenApiResult | null>;
|
|
55
|
+
/**
|
|
56
|
+
* Generate an OpenAPI spec for each surface these endpoints name.
|
|
57
|
+
*
|
|
58
|
+
* Takes the endpoints, not a glob to find them with. The build has already
|
|
59
|
+
* loaded every one of them to generate handlers, so globbing again was a second
|
|
60
|
+
* discovery pass over the same files — and the reason this function needed a
|
|
61
|
+
* `routes` to be told where to look, which is how a retired config field stayed
|
|
62
|
+
* load-bearing.
|
|
63
|
+
*
|
|
64
|
+
* @returns What was written, or null if disabled or there were none
|
|
65
|
+
*/
|
|
66
|
+
export declare function generateOpenApi(endpoints: readonly Endpoint<any, any, any, any, any>[], options?: {
|
|
67
|
+
openapi?: boolean | OpenApiConfig;
|
|
68
|
+
silent?: boolean;
|
|
69
|
+
}): Promise<OpenApiResult | null>;
|
|
70
|
+
export declare function openapiCommand(options?: OpenAPIOptions): Promise<void>;
|
|
37
71
|
//#endregion
|
|
38
|
-
export { OPENAPI_OUTPUT_PATH, generateOpenApi, openapiCommand, resolveOpenApiConfig };
|
|
39
72
|
//# sourceMappingURL=openapi.d.mts.map
|
package/dist/openapi.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openapi.d.mts","names":[],"sources":["../src/openapi.ts"],"
|
|
1
|
+
{"version":3,"file":"openapi.d.mts","names":[],"sources":["../src/openapi.ts"],"mappings":";;;UAeU;EACT;;;;;;;EAOA;;;;;qBAMY;;;;;;;;wBASG,eAAe;;;;wBAOf,qBAAqB;EACpC,oBAAoB;IACjB;EAAkB;;;iBA0BL;;EAEhB;;EAEA;;EAEA;;;;;;;;;wBAUqB,oBACrB,QAAQ,QACR;EACC,oBAAoB;EACpB;EACA;IAEC,QAAQ;;;;;;;;;;;;wBAwBW,gBACrB,oBAAoB,qCACpB;EACC,oBAAoB;EACpB;IAEC,QAAQ;wBAmEW,eACrB,UAAS,iBACP"}
|