@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,743 @@
|
|
|
1
|
+
const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
|
|
2
|
+
const require_discover = require("./discover-B0f_kuNN.cjs");
|
|
3
|
+
const require_config = require("./config-CJ-PAb7M.cjs");
|
|
4
|
+
const require_workspace = require("./workspace-RYSYtHP7.cjs");
|
|
5
|
+
const require_store = require("./store-DQgxT_WR.cjs");
|
|
6
|
+
const require_workspace$1 = require("./workspace-BIqeeqcR.cjs");
|
|
7
|
+
const require_index = require("./index.cjs");
|
|
8
|
+
let node_fs_promises = require("node:fs/promises");
|
|
9
|
+
let node_path = require("node:path");
|
|
10
|
+
let _geekmidas_manifest = require("@geekmidas/manifest");
|
|
11
|
+
let node_module = require("node:module");
|
|
12
|
+
let node_url = require("node:url");
|
|
13
|
+
let pg = require("pg");
|
|
14
|
+
pg = require_rolldown_runtime.__toESM(pg, 1);
|
|
15
|
+
let _geekmidas_envkit = require("@geekmidas/envkit");
|
|
16
|
+
let _geekmidas_services = require("@geekmidas/services");
|
|
17
|
+
//#region src/migrate/provider.ts
|
|
18
|
+
/**
|
|
19
|
+
* One construct's migrations and seeds, read from its folders.
|
|
20
|
+
*
|
|
21
|
+
* Two kinds of file, told apart by extension rather than by configuration: a
|
|
22
|
+
* script exports `up` (and may export `down`) — or `seed(db, { stage })`, in
|
|
23
|
+
* `seeds/` — and a
|
|
24
|
+
* `.sql` file is run whole. The second is what makes a folder independent of
|
|
25
|
+
* the client the app uses — Better Auth's schema is SQL whatever the
|
|
26
|
+
* application queries with.
|
|
27
|
+
*/
|
|
28
|
+
const SCRIPTS = /* @__PURE__ */ new Set([
|
|
29
|
+
".ts",
|
|
30
|
+
".mts",
|
|
31
|
+
".js",
|
|
32
|
+
".mjs"
|
|
33
|
+
]);
|
|
34
|
+
/** Two files in one folder that would be one migration. */
|
|
35
|
+
var DuplicateMigration = class extends Error {
|
|
36
|
+
folder;
|
|
37
|
+
migration;
|
|
38
|
+
constructor(folder, migration) {
|
|
39
|
+
super(`${folder} has two migrations named '${migration}' — a script and a .sql file with the same name. A migration is recorded by name, so only one of them could ever run; rename or remove the other.`);
|
|
40
|
+
this.folder = folder;
|
|
41
|
+
this.migration = migration;
|
|
42
|
+
this.name = "DuplicateMigration";
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
/** A script in a migrations folder that exports no `up`. */
|
|
46
|
+
var MigrationHasNoUp = class extends Error {
|
|
47
|
+
file;
|
|
48
|
+
constructor(file) {
|
|
49
|
+
super(`${file} is in a migrations folder but exports no \`up\` function. Export \`async function up(db: Kysely<unknown>)\`, or move the file out of the folder if it is not a migration.`);
|
|
50
|
+
this.file = file;
|
|
51
|
+
this.name = "MigrationHasNoUp";
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
/** A script in a seeds folder that exports no `seed`. */
|
|
55
|
+
var SeedHasNoSeed = class extends Error {
|
|
56
|
+
file;
|
|
57
|
+
constructor(file) {
|
|
58
|
+
super(`${file} is in a seeds folder but exports no \`seed\` function. Export \`async function seed(db, { stage })\`, or move the file out of the folder if it is not a seed.`);
|
|
59
|
+
this.file = file;
|
|
60
|
+
this.name = "SeedHasNoSeed";
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* The migration files in a folder, by name, without reading them.
|
|
65
|
+
*
|
|
66
|
+
* Also what `gkm dev` compares against a construct's history to report what
|
|
67
|
+
* is pending, which is why it is separate from loading them.
|
|
68
|
+
*/
|
|
69
|
+
async function migrationFiles(folder) {
|
|
70
|
+
const entries = await (0, node_fs_promises.readdir)(folder, { withFileTypes: true }).catch((error) => {
|
|
71
|
+
if (error.code === "ENOENT") return [];
|
|
72
|
+
throw error;
|
|
73
|
+
});
|
|
74
|
+
const files = /* @__PURE__ */ new Map();
|
|
75
|
+
for (const entry of entries) {
|
|
76
|
+
if (!entry.isFile() || !isMigration(entry.name)) continue;
|
|
77
|
+
const name = entry.name.slice(0, -(0, node_path.extname)(entry.name).length);
|
|
78
|
+
if (files.has(name)) throw new DuplicateMigration(folder, name);
|
|
79
|
+
files.set(name, (0, node_path.join)(folder, entry.name));
|
|
80
|
+
}
|
|
81
|
+
return files;
|
|
82
|
+
}
|
|
83
|
+
/** A Kysely provider over one construct's folder. */
|
|
84
|
+
function folderProvider(folder, raw) {
|
|
85
|
+
return { async getMigrations() {
|
|
86
|
+
const migrations = {};
|
|
87
|
+
for (const [name, file] of await migrationFiles(folder)) migrations[name] = (0, node_path.extname)(file) === ".sql" ? await sqlMigration(file, raw) : await scriptMigration(file);
|
|
88
|
+
return migrations;
|
|
89
|
+
} };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The seeds in a folder, loaded, in name order.
|
|
93
|
+
*
|
|
94
|
+
* Every one runs on every pass — there is no history to consult — so the order
|
|
95
|
+
* is the only thing between two seeds where one reads what another writes.
|
|
96
|
+
* Name them to sort that way.
|
|
97
|
+
*/
|
|
98
|
+
async function loadSeeds(folder, raw) {
|
|
99
|
+
const files = [...await migrationFiles(folder)].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
100
|
+
const seeds = [];
|
|
101
|
+
for (const [name, file] of files) seeds.push({
|
|
102
|
+
name,
|
|
103
|
+
run: (0, node_path.extname)(file) === ".sql" ? (await sqlMigration(file, raw)).up : await scriptSeed(file)
|
|
104
|
+
});
|
|
105
|
+
return seeds;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* A seed script, imported as it is on disk now.
|
|
109
|
+
*
|
|
110
|
+
* Keyed by its modification time: a seed is run again after it is edited, and
|
|
111
|
+
* a process that has imported it once would otherwise run the old one.
|
|
112
|
+
*/
|
|
113
|
+
async function scriptSeed(file) {
|
|
114
|
+
const url = (0, node_url.pathToFileURL)(file);
|
|
115
|
+
url.searchParams.set("mtime", String((await (0, node_fs_promises.stat)(file)).mtimeMs));
|
|
116
|
+
const module = await import(url.href);
|
|
117
|
+
if (typeof module.seed !== "function") throw new SeedHasNoSeed(file);
|
|
118
|
+
return module.seed;
|
|
119
|
+
}
|
|
120
|
+
function isMigration(file) {
|
|
121
|
+
if (file.endsWith(".d.ts")) return false;
|
|
122
|
+
if (/\.(spec|test)\.[cm]?[jt]s$/.test(file)) return false;
|
|
123
|
+
const extension = (0, node_path.extname)(file);
|
|
124
|
+
return extension === ".sql" || SCRIPTS.has(extension);
|
|
125
|
+
}
|
|
126
|
+
async function scriptMigration(file) {
|
|
127
|
+
const module = await import((0, node_url.pathToFileURL)(file).href);
|
|
128
|
+
if (typeof module.up !== "function") throw new MigrationHasNoUp(file);
|
|
129
|
+
return {
|
|
130
|
+
up: module.up,
|
|
131
|
+
...typeof module.down === "function" ? { down: module.down } : {}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* A `.sql` file, run whole.
|
|
136
|
+
*
|
|
137
|
+
* One query rather than split on `;`: with no parameters, Postgres takes
|
|
138
|
+
* several statements in one message, and splitting by hand breaks on the first
|
|
139
|
+
* `;` inside a string or a function body. There is no `down` — a generated
|
|
140
|
+
* schema is rolled back by a migration that says how, not by guessing.
|
|
141
|
+
*/
|
|
142
|
+
async function sqlMigration(file, raw) {
|
|
143
|
+
const statements = await (0, node_fs_promises.readFile)(file, "utf-8");
|
|
144
|
+
return { up: async (db) => {
|
|
145
|
+
await raw.raw(statements).execute(db);
|
|
146
|
+
} };
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region src/migrate/databases.ts
|
|
150
|
+
/**
|
|
151
|
+
* Bring each construct's schema up to its migrations folder.
|
|
152
|
+
*
|
|
153
|
+
* Which constructs, in what order, from which folder, is `migrationTargets`'
|
|
154
|
+
* answer and nobody else's. What is decided here is only how one target is
|
|
155
|
+
* run: as its owner, in its own schema, with a history of its own.
|
|
156
|
+
*/
|
|
157
|
+
/** A folder under `db/` that names no construct. */
|
|
158
|
+
var UnknownMigrationFolder = class extends Error {
|
|
159
|
+
folder;
|
|
160
|
+
known;
|
|
161
|
+
constructor(folder, known) {
|
|
162
|
+
super(`${folder} names no database construct, so its migrations would never run. ${known.length > 0 ? `The folders that do: ${known.join(", ")}.` : "This project declares no database."} Rename the folder after the construct it belongs to.`);
|
|
163
|
+
this.folder = folder;
|
|
164
|
+
this.known = known;
|
|
165
|
+
this.name = "UnknownMigrationFolder";
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
/** A construct was asked for by a name that takes no migrations. */
|
|
169
|
+
var NoSuchMigrationTarget = class extends Error {
|
|
170
|
+
construct;
|
|
171
|
+
known;
|
|
172
|
+
constructor(construct, known) {
|
|
173
|
+
super(`'${construct}' is not a database construct. ` + (known.length > 0 ? `The ones with migrations: ${known.join(", ")}.` : "This project declares no database."));
|
|
174
|
+
this.construct = construct;
|
|
175
|
+
this.known = known;
|
|
176
|
+
this.name = "NoSuchMigrationTarget";
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
/**
|
|
180
|
+
* A migration left directly in a construct's folder, where nothing reads it.
|
|
181
|
+
*
|
|
182
|
+
* Migrations moved into `migrations/` so seeds could sit beside them. A file
|
|
183
|
+
* still at the old level would otherwise never run, and nothing would say so.
|
|
184
|
+
*/
|
|
185
|
+
var MigrationsOutsideFolder = class extends Error {
|
|
186
|
+
folder;
|
|
187
|
+
files;
|
|
188
|
+
constructor(folder, files) {
|
|
189
|
+
super(`${folder} has ${files.join(", ")} directly inside it, where nothing runs them. Migrations live in ${folder}/migrations/ and seeds in ${folder}/seeds/ — move ${files.length === 1 ? "it" : "them"} there.`);
|
|
190
|
+
this.folder = folder;
|
|
191
|
+
this.files = files;
|
|
192
|
+
this.name = "MigrationsOutsideFolder";
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
/** A folder inside a construct's folder that is neither `migrations` nor `seeds`. */
|
|
196
|
+
var UnknownDatabaseFolder = class extends Error {
|
|
197
|
+
folder;
|
|
198
|
+
constructor(folder) {
|
|
199
|
+
super(`${folder} is neither migrations/ nor seeds/, so nothing in it would ever run. A construct's folder holds those two; rename or remove it.`);
|
|
200
|
+
this.folder = folder;
|
|
201
|
+
this.name = "UnknownDatabaseFolder";
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
/** A seed failed; its own writes are rolled back, the ones before it stay. */
|
|
205
|
+
var SeedFailed = class extends Error {
|
|
206
|
+
construct;
|
|
207
|
+
seed;
|
|
208
|
+
cause;
|
|
209
|
+
constructor(construct, seed, cause) {
|
|
210
|
+
super(`${construct}: seed '${seed}' failed — ${cause instanceof Error ? cause.message : String(cause)}. Its writes were rolled back; fix it and run it again.`);
|
|
211
|
+
this.construct = construct;
|
|
212
|
+
this.seed = seed;
|
|
213
|
+
this.cause = cause;
|
|
214
|
+
this.name = "SeedFailed";
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
/** A construct with roles has no owner URL to migrate with. */
|
|
218
|
+
var NoOwnerCredential = class extends Error {
|
|
219
|
+
construct;
|
|
220
|
+
key;
|
|
221
|
+
constructor(construct, key) {
|
|
222
|
+
super(`${construct} has no ${key} to migrate with. Migrations run as the owner role — the runtime one cannot create a table — and nothing resolved its URL. Is the stage reconciled?`);
|
|
223
|
+
this.construct = construct;
|
|
224
|
+
this.key = key;
|
|
225
|
+
this.name = "NoOwnerCredential";
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
/** A migration failed; the ones before it in the run stay applied. */
|
|
229
|
+
var MigrationFailed = class extends Error {
|
|
230
|
+
construct;
|
|
231
|
+
migration;
|
|
232
|
+
cause;
|
|
233
|
+
constructor(construct, migration, cause) {
|
|
234
|
+
super(`${construct}: migration ${migration ? `'${migration}' ` : ""}failed — ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
235
|
+
this.construct = construct;
|
|
236
|
+
this.migration = migration;
|
|
237
|
+
this.cause = cause;
|
|
238
|
+
this.name = "MigrationFailed";
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
/** The project's Kysely could not be found from where a database is declared. */
|
|
242
|
+
var KyselyNotFound = class extends Error {
|
|
243
|
+
from;
|
|
244
|
+
constructor(from) {
|
|
245
|
+
super(`Could not resolve 'kysely' from ${from}. Migrations run with the project's own Kysely — install it where the database is declared.`);
|
|
246
|
+
this.from = from;
|
|
247
|
+
this.name = "KyselyNotFound";
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
/** Apply every pending migration, construct by construct, parents first. */
|
|
251
|
+
async function migrateDatabases(options) {
|
|
252
|
+
const runs = [];
|
|
253
|
+
for (const target of await targetsFor(options)) {
|
|
254
|
+
if ((await migrationFiles((0, node_path.join)(options.root, target.migrations))).size === 0) {
|
|
255
|
+
runs.push({
|
|
256
|
+
target,
|
|
257
|
+
applied: []
|
|
258
|
+
});
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const applied = await withMigrator(options, target, async (migrator) => {
|
|
262
|
+
const { error, results = [] } = await migrator.migrateToLatest();
|
|
263
|
+
if (error) {
|
|
264
|
+
const failed = results.find((result) => result.status === "Error");
|
|
265
|
+
throw new MigrationFailed(target.id, failed?.migrationName, error);
|
|
266
|
+
}
|
|
267
|
+
return results.filter((result) => result.status === "Success").map((result) => result.migrationName);
|
|
268
|
+
});
|
|
269
|
+
runs.push({
|
|
270
|
+
target,
|
|
271
|
+
applied
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
return runs;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Run every construct's seeds, parents first — each one, every time, on every
|
|
278
|
+
* stage, production included. Each is handed the stage, so a seed decides
|
|
279
|
+
* what belongs where.
|
|
280
|
+
*
|
|
281
|
+
* Seeds are reference data kept up to date by being run again, so there is no
|
|
282
|
+
* history: a seed is an upsert, and a changed one is applied by the next run.
|
|
283
|
+
* Each runs in its own transaction, as the construct's owner, so a failure
|
|
284
|
+
* leaves nothing half-written. Assumes the schema is migrated —
|
|
285
|
+
* {@link migrateAndSeed} is what makes that true.
|
|
286
|
+
*/
|
|
287
|
+
async function seedDatabases(options) {
|
|
288
|
+
const runs = [];
|
|
289
|
+
for (const target of await targetsFor(options)) {
|
|
290
|
+
const folder = (0, node_path.join)(options.root, target.seeds);
|
|
291
|
+
if ((await migrationFiles(folder)).size === 0) {
|
|
292
|
+
runs.push({
|
|
293
|
+
target,
|
|
294
|
+
seeded: []
|
|
295
|
+
});
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const seeded = await withOwner(options, target, async (db, kysely) => {
|
|
299
|
+
const names = [];
|
|
300
|
+
for (const seed of await loadSeeds(folder, kysely.sql)) {
|
|
301
|
+
try {
|
|
302
|
+
await db.transaction().execute((trx) => seed.run(trx, { stage: options.stage }));
|
|
303
|
+
} catch (error) {
|
|
304
|
+
throw new SeedFailed(target.id, seed.name, error);
|
|
305
|
+
}
|
|
306
|
+
names.push(seed.name);
|
|
307
|
+
}
|
|
308
|
+
return names;
|
|
309
|
+
});
|
|
310
|
+
runs.push({
|
|
311
|
+
target,
|
|
312
|
+
seeded
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
return runs;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Migrate, then seed: a seed only ever runs against the schema it was written
|
|
319
|
+
* for.
|
|
320
|
+
*
|
|
321
|
+
* With `only`, the constructs it lives in are migrated too — a tenant's seed
|
|
322
|
+
* cannot run before the database that owns its schema exists — and only it is
|
|
323
|
+
* seeded.
|
|
324
|
+
*/
|
|
325
|
+
async function migrateAndSeed(options) {
|
|
326
|
+
const migrations = [];
|
|
327
|
+
for (const target of await withParents(options)) migrations.push(...await migrateDatabases({
|
|
328
|
+
...options,
|
|
329
|
+
only: target.id
|
|
330
|
+
}));
|
|
331
|
+
return {
|
|
332
|
+
migrations,
|
|
333
|
+
seeds: await seedDatabases(options)
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
/** The targets `only` names, after the ones it lives in; all without it. */
|
|
337
|
+
async function withParents(options) {
|
|
338
|
+
const chosen = await targetsFor(options);
|
|
339
|
+
if (!options.only) return chosen;
|
|
340
|
+
const all = (0, _geekmidas_manifest.migrationTargets)(options.manifest);
|
|
341
|
+
const ids = /* @__PURE__ */ new Set();
|
|
342
|
+
for (const target of chosen) {
|
|
343
|
+
let current = target;
|
|
344
|
+
while (current) {
|
|
345
|
+
ids.add(current.id);
|
|
346
|
+
const parent = current.of;
|
|
347
|
+
current = parent ? all.find((t) => t.id === parent) : void 0;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return all.filter((target) => ids.has(target.id));
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* What each construct's folder has that its history does not — without
|
|
354
|
+
* applying any of it. What `gkm dev` reports.
|
|
355
|
+
*/
|
|
356
|
+
async function pendingMigrations(options) {
|
|
357
|
+
const found = [];
|
|
358
|
+
for (const target of await targetsFor(options)) {
|
|
359
|
+
if ((await migrationFiles((0, node_path.join)(options.root, target.migrations))).size === 0) continue;
|
|
360
|
+
const pending = await withMigrator(options, target, async (migrator) => (await migrator.getMigrations()).filter((migration) => migration.executedAt === void 0).map((migration) => migration.name));
|
|
361
|
+
if (pending.length > 0) found.push({
|
|
362
|
+
target,
|
|
363
|
+
pending
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
return found;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* The targets this run covers, with every folder under `db/` accounted for.
|
|
370
|
+
*
|
|
371
|
+
* A folder that names no construct is an error rather than something to skip:
|
|
372
|
+
* a typo in its name is otherwise a migration that silently never runs.
|
|
373
|
+
*/
|
|
374
|
+
async function targetsFor(options) {
|
|
375
|
+
const targets = (0, _geekmidas_manifest.migrationTargets)(options.manifest);
|
|
376
|
+
const folders = targets.map((target) => target.folder);
|
|
377
|
+
for (const folder of await foldersIn((0, node_path.join)(options.root, _geekmidas_manifest.MIGRATIONS_ROOT))) {
|
|
378
|
+
const relative = `${_geekmidas_manifest.MIGRATIONS_ROOT}/${folder}`;
|
|
379
|
+
if (!folders.includes(relative)) throw new UnknownMigrationFolder(relative, folders);
|
|
380
|
+
await checkLayout(options.root, relative);
|
|
381
|
+
}
|
|
382
|
+
if (!options.only) return targets;
|
|
383
|
+
const id = (0, _geekmidas_manifest.canonicalId)(options.only);
|
|
384
|
+
const chosen = targets.filter((target) => target.id === id);
|
|
385
|
+
if (chosen.length === 0) throw new NoSuchMigrationTarget(options.only, targets.map((target) => target.id));
|
|
386
|
+
return chosen;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* A construct's folder holds `migrations/` and `seeds/`, and nothing else —
|
|
390
|
+
* anything beside them is a file or folder nothing would ever run.
|
|
391
|
+
*/
|
|
392
|
+
async function checkLayout(root, folder) {
|
|
393
|
+
const entries = await (0, node_fs_promises.readdir)((0, node_path.join)(root, folder), { withFileTypes: true });
|
|
394
|
+
const loose = entries.filter((entry) => entry.isFile() && !entry.name.startsWith(".")).map((entry) => entry.name);
|
|
395
|
+
if (loose.length > 0) throw new MigrationsOutsideFolder(folder, loose);
|
|
396
|
+
for (const entry of entries) if (entry.isDirectory() && !_geekmidas_manifest.DATABASE_FOLDERS.includes(entry.name)) throw new UnknownDatabaseFolder(`${folder}/${entry.name}`);
|
|
397
|
+
}
|
|
398
|
+
async function foldersIn(dir) {
|
|
399
|
+
return (await (0, node_fs_promises.readdir)(dir, { withFileTypes: true }).catch((error) => {
|
|
400
|
+
if (error.code === "ENOENT") return [];
|
|
401
|
+
throw error;
|
|
402
|
+
})).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* A migrator for one target, connected as its owner.
|
|
406
|
+
*
|
|
407
|
+
* No `migrationTableSchema`: the owner role's `search_path` is pinned to the
|
|
408
|
+
* construct's schema, so the history lands there — `app.kysely_migration` for
|
|
409
|
+
* the database, `auth_database.kysely_migration` for its tenant — and two
|
|
410
|
+
* constructs in one Postgres never share one.
|
|
411
|
+
*
|
|
412
|
+
* Unordered migrations are allowed. Files are timestamped, so a branch merged
|
|
413
|
+
* after another's migration ran carries an older one; refusing it would make
|
|
414
|
+
* every such merge a manual repair.
|
|
415
|
+
*/
|
|
416
|
+
async function withMigrator(options, target, run) {
|
|
417
|
+
return withOwner(options, target, (db, { Migrator, sql }) => run(new Migrator({
|
|
418
|
+
db,
|
|
419
|
+
provider: folderProvider((0, node_path.join)(options.root, target.migrations), sql),
|
|
420
|
+
allowUnorderedMigrations: true
|
|
421
|
+
})));
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* One target's database, connected as its owner and closed afterwards — what
|
|
425
|
+
* migrations and seeds both run as.
|
|
426
|
+
*/
|
|
427
|
+
async function withOwner(options, target, run) {
|
|
428
|
+
const kysely = await kyselyFrom(options.sources?.[target.id]?.file ?? (target.of ? options.sources?.[target.of]?.file : void 0) ?? (0, node_path.join)(options.root, "package.json"));
|
|
429
|
+
const db = new kysely.Kysely({ dialect: new kysely.PostgresDialect({ pool: new pg.default.Pool(poolConfig(ownerUrl(options, target))) }) });
|
|
430
|
+
try {
|
|
431
|
+
return await run(db, kysely);
|
|
432
|
+
} finally {
|
|
433
|
+
await db.destroy();
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* The owner's URL, with no fallback to the runtime one.
|
|
438
|
+
*
|
|
439
|
+
* The runtime role can create nothing, so falling back would only move the
|
|
440
|
+
* failure to the first `CREATE TABLE` and make it read like a permissions
|
|
441
|
+
* bug. The one exception is a database declared `roles: false`, where there is
|
|
442
|
+
* no owner — both URLs are the master credential by design.
|
|
443
|
+
*/
|
|
444
|
+
function ownerUrl(options, target) {
|
|
445
|
+
const key = (0, _geekmidas_manifest.provideKey)(target.id, "ownerUrl");
|
|
446
|
+
const owner = options.env[key];
|
|
447
|
+
if (owner) return owner;
|
|
448
|
+
const database = options.manifest[target.of ?? target.id];
|
|
449
|
+
if (database?.kind === "database" && database.roles === false) {
|
|
450
|
+
const url = options.env[(0, _geekmidas_manifest.provideKey)(target.id, "url")];
|
|
451
|
+
if (url) return url;
|
|
452
|
+
}
|
|
453
|
+
throw new NoOwnerCredential(target.id, key);
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Pool settings for a URL, with `?search_path=` as the startup option Postgres
|
|
457
|
+
* reads. Only a `roles: false` URL carries it — a role pins its own.
|
|
458
|
+
*/
|
|
459
|
+
function poolConfig(url) {
|
|
460
|
+
const parsed = new URL(url);
|
|
461
|
+
const searchPath = parsed.searchParams.get("search_path");
|
|
462
|
+
if (!searchPath) return { connectionString: url };
|
|
463
|
+
parsed.searchParams.delete("search_path");
|
|
464
|
+
return {
|
|
465
|
+
connectionString: parsed.toString(),
|
|
466
|
+
options: `-c search_path=${searchPath}`
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
/** Kysely as the project resolves it from `from`. */
|
|
470
|
+
async function kyselyFrom(from) {
|
|
471
|
+
const require$1 = (0, node_module.createRequire)(from);
|
|
472
|
+
try {
|
|
473
|
+
const [main, migration] = await Promise.all([import((0, node_url.pathToFileURL)(require$1.resolve("kysely")).href), import((0, node_url.pathToFileURL)(require$1.resolve("kysely/migration")).href)]);
|
|
474
|
+
return {
|
|
475
|
+
Kysely: main.Kysely,
|
|
476
|
+
PostgresDialect: main.PostgresDialect,
|
|
477
|
+
sql: main.sql,
|
|
478
|
+
Migrator: migration.Migrator
|
|
479
|
+
};
|
|
480
|
+
} catch {
|
|
481
|
+
throw new KyselyNotFound(from);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
//#endregion
|
|
485
|
+
//#region src/migrate/index.ts
|
|
486
|
+
/**
|
|
487
|
+
* `gkm migrate`, `gkm seed` and `gkm migration` — applying a construct's
|
|
488
|
+
* migrations, running its seeds, and writing its next migration.
|
|
489
|
+
*
|
|
490
|
+
* Both work on a stage this machine reconciles: its containers up, its roles
|
|
491
|
+
* provisioned, its owner URLs resolved. A deployed stage is migrated by the
|
|
492
|
+
* deploy that ships it, which is the one thing that can reach its database.
|
|
493
|
+
*/
|
|
494
|
+
const logger = console;
|
|
495
|
+
/** `gkm migrate --stage` named a stage only a deploy can reach. */
|
|
496
|
+
var MigrateDeployedStage = class extends Error {
|
|
497
|
+
stage;
|
|
498
|
+
local;
|
|
499
|
+
constructor(stage, local) {
|
|
500
|
+
super(`'${stage}' is a deployed stage, and its database is migrated by \`gkm deploy --stage ${stage}\`, which is what can reach it. \`gkm migrate\` runs against '${local}' or '${require_workspace.TEST_STAGE}'.`);
|
|
501
|
+
this.stage = stage;
|
|
502
|
+
this.local = local;
|
|
503
|
+
this.name = "MigrateDeployedStage";
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
/** `gkm migration` for a Kysely database, with no name for the file. */
|
|
507
|
+
var MigrationNeedsName = class extends Error {
|
|
508
|
+
construct;
|
|
509
|
+
constructor(construct) {
|
|
510
|
+
super(`Name the migration: \`gkm migration ${construct} add_workouts\`. The name is what the file — and the history — calls it.`);
|
|
511
|
+
this.construct = construct;
|
|
512
|
+
this.name = "MigrationNeedsName";
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
/**
|
|
516
|
+
* Reconcile a stage so it can be migrated: containers up, databases, schemas,
|
|
517
|
+
* roles and grants in place, owner URLs resolved.
|
|
518
|
+
*
|
|
519
|
+
* Migrating assumes the roles exist, and this is what makes that true rather
|
|
520
|
+
* than something a developer has to have run first.
|
|
521
|
+
*/
|
|
522
|
+
async function readyStage(cwd, stage) {
|
|
523
|
+
const { workspace } = await require_config.loadWorkspaceConfig(cwd);
|
|
524
|
+
const local = workspace.stages.local;
|
|
525
|
+
const target = stage ?? local;
|
|
526
|
+
if (target !== local && target !== "test") throw new MigrateDeployedStage(target, local);
|
|
527
|
+
const sources = {};
|
|
528
|
+
const manifest = await require_discover.discover({
|
|
529
|
+
patterns: require_workspace$1.constructGlobs(workspace),
|
|
530
|
+
cwd: workspace.root,
|
|
531
|
+
sources
|
|
532
|
+
});
|
|
533
|
+
return {
|
|
534
|
+
workspace,
|
|
535
|
+
manifest,
|
|
536
|
+
sources,
|
|
537
|
+
env: { ...(await require_workspace$1.reconcileWorkspace(workspace, {
|
|
538
|
+
stage: target,
|
|
539
|
+
manifest,
|
|
540
|
+
start: true
|
|
541
|
+
})).env },
|
|
542
|
+
stage: target
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
/** `gkm migrate [construct]`. */
|
|
546
|
+
async function migrateCommand(options = {}) {
|
|
547
|
+
const ready = await readyStage(process.cwd(), options.stage);
|
|
548
|
+
const runs = await migrateDatabases({
|
|
549
|
+
root: ready.workspace.root,
|
|
550
|
+
manifest: ready.manifest,
|
|
551
|
+
sources: ready.sources,
|
|
552
|
+
env: ready.env,
|
|
553
|
+
...options.construct ? { only: options.construct } : {}
|
|
554
|
+
});
|
|
555
|
+
if (runs.length === 0) {
|
|
556
|
+
logger.log("🗄️ No database constructs to migrate.");
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
logMigrations(runs);
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* `gkm seed [construct]` — migrate, then run the seeds.
|
|
563
|
+
*
|
|
564
|
+
* Always migrates first: a seed is written against the schema its migrations
|
|
565
|
+
* build, and against an older one it fails on a table that is not there yet.
|
|
566
|
+
*/
|
|
567
|
+
async function seedCommand(options = {}) {
|
|
568
|
+
const ready = await readyStage(process.cwd(), options.stage);
|
|
569
|
+
const { migrations, seeds } = await migrateAndSeed({
|
|
570
|
+
root: ready.workspace.root,
|
|
571
|
+
manifest: ready.manifest,
|
|
572
|
+
sources: ready.sources,
|
|
573
|
+
env: ready.env,
|
|
574
|
+
stage: ready.stage,
|
|
575
|
+
...options.construct ? { only: options.construct } : {}
|
|
576
|
+
});
|
|
577
|
+
if (migrations.length === 0) {
|
|
578
|
+
logger.log("🗄️ No database constructs to seed.");
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
logMigrations(migrations);
|
|
582
|
+
logSeeds(seeds);
|
|
583
|
+
}
|
|
584
|
+
/** One line per construct: up to date, or what was applied. */
|
|
585
|
+
function logMigrations(runs, log = logger.log) {
|
|
586
|
+
for (const { target, applied } of runs) {
|
|
587
|
+
if (applied.length === 0) {
|
|
588
|
+
log(`🗄️ ${target.migrations}: up to date`);
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
log(`🗄️ ${target.migrations}: applied ${applied.length}`);
|
|
592
|
+
for (const name of applied) log(` ✓ ${name}`);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
/** One line per construct that has seeds, and each seed it ran. */
|
|
596
|
+
function logSeeds(runs, log = logger.log) {
|
|
597
|
+
for (const { target, seeded } of runs) {
|
|
598
|
+
if (seeded.length === 0) continue;
|
|
599
|
+
log(`🌱 ${target.seeds}: ran ${seeded.length}`);
|
|
600
|
+
for (const name of seeded) log(` ✓ ${name}`);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* What `gkm dev` prints: one line per construct whose folder is ahead of its
|
|
605
|
+
* history, and nothing when every folder is applied.
|
|
606
|
+
*
|
|
607
|
+
* Reported, never applied. A migration being written would otherwise run the
|
|
608
|
+
* moment its file is saved, half-finished, against the database the developer
|
|
609
|
+
* is working in.
|
|
610
|
+
*/
|
|
611
|
+
async function reportPendingMigrations(workspace, env) {
|
|
612
|
+
const sources = {};
|
|
613
|
+
const manifest = await require_discover.discover({
|
|
614
|
+
patterns: require_workspace$1.constructGlobs(workspace),
|
|
615
|
+
cwd: workspace.root,
|
|
616
|
+
sources
|
|
617
|
+
});
|
|
618
|
+
const pending = await pendingMigrations({
|
|
619
|
+
root: workspace.root,
|
|
620
|
+
manifest,
|
|
621
|
+
sources,
|
|
622
|
+
env
|
|
623
|
+
});
|
|
624
|
+
for (const { target, pending: names } of pending) logger.log(`⚠️ ${target.migrations}: ${names.length} pending (${names.join(", ")}) — run gkm migrate`);
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* What `gkm dev --migrate` and `gkm dev --seed` do before the apps start:
|
|
628
|
+
* migrate, and with `seed`, run the seeds after — once, at startup, never on a
|
|
629
|
+
* save, for the reason {@link reportPendingMigrations} gives.
|
|
630
|
+
*
|
|
631
|
+
* A failure here is thrown, unlike the report's: the developer asked for the
|
|
632
|
+
* database to be brought up to date, and apps started against one in an
|
|
633
|
+
* unknown state would fail somewhere less obvious.
|
|
634
|
+
*/
|
|
635
|
+
async function prepareDevDatabases(workspace, env, { seed = false } = {}) {
|
|
636
|
+
const sources = {};
|
|
637
|
+
const manifest = await require_discover.discover({
|
|
638
|
+
patterns: require_workspace$1.constructGlobs(workspace),
|
|
639
|
+
cwd: workspace.root,
|
|
640
|
+
sources
|
|
641
|
+
});
|
|
642
|
+
const options = {
|
|
643
|
+
root: workspace.root,
|
|
644
|
+
manifest,
|
|
645
|
+
sources,
|
|
646
|
+
env
|
|
647
|
+
};
|
|
648
|
+
if (!seed) {
|
|
649
|
+
logMigrations(await migrateDatabases(options));
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
const { migrations, seeds } = await migrateAndSeed({
|
|
653
|
+
...options,
|
|
654
|
+
stage: workspace.stages.local
|
|
655
|
+
});
|
|
656
|
+
logMigrations(migrations);
|
|
657
|
+
logSeeds(seeds);
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* `gkm migration <construct> [name]` — write the construct's next migration.
|
|
661
|
+
*
|
|
662
|
+
* For a database, an empty `up`/`down` to fill in. For an auth server, what
|
|
663
|
+
* its schema is missing, computed against its tenant once every committed
|
|
664
|
+
* migration has been applied — so the file is only the change, and is
|
|
665
|
+
* reviewed and committed like any other.
|
|
666
|
+
*/
|
|
667
|
+
async function migrationCommand(options) {
|
|
668
|
+
const ready = await readyStage(process.cwd());
|
|
669
|
+
const targets = (0, _geekmidas_manifest.migrationTargets)(ready.manifest);
|
|
670
|
+
const id = (0, _geekmidas_manifest.canonicalId)(options.construct);
|
|
671
|
+
const auth = authConstruct(ready.sources, id);
|
|
672
|
+
if (auth) {
|
|
673
|
+
const target = targets.find((t) => t.id === auth.databaseId);
|
|
674
|
+
if (!target) throw new NoSuchMigrationTarget(auth.databaseId, ids(targets));
|
|
675
|
+
return writeAuthMigration(ready, auth, target, options);
|
|
676
|
+
}
|
|
677
|
+
const target = targets.find((t) => t.id === id);
|
|
678
|
+
if (!target) throw new NoSuchMigrationTarget(options.construct, [...ids(targets), ...authIds(ready.sources)]);
|
|
679
|
+
if (!options.name) throw new MigrationNeedsName(options.construct);
|
|
680
|
+
const file = await write(ready.workspace.root, target, `${require_index.stamp(options.now)}_${require_index.slug(options.name)}.ts`, STUB);
|
|
681
|
+
logger.log(`📝 ${file}`);
|
|
682
|
+
return file;
|
|
683
|
+
}
|
|
684
|
+
function authConstruct(sources, id) {
|
|
685
|
+
const construct = sources[id]?.construct;
|
|
686
|
+
return construct && typeof construct.pendingMigration === "function" && typeof construct.databaseId === "string" ? construct : void 0;
|
|
687
|
+
}
|
|
688
|
+
function authIds(sources) {
|
|
689
|
+
return Object.keys(sources).filter((id) => authConstruct(sources, id));
|
|
690
|
+
}
|
|
691
|
+
async function writeAuthMigration(ready, auth, target, options) {
|
|
692
|
+
await migrateDatabases({
|
|
693
|
+
root: ready.workspace.root,
|
|
694
|
+
manifest: ready.manifest,
|
|
695
|
+
sources: ready.sources,
|
|
696
|
+
env: ready.env,
|
|
697
|
+
only: target.id
|
|
698
|
+
});
|
|
699
|
+
const stage = ready.workspace.stages.local;
|
|
700
|
+
const secrets = await require_workspace$1.loadSecretsForApp(await require_store.secretsStoreFor(ready.workspace, stage), stage);
|
|
701
|
+
const pending = await auth.pendingMigration({
|
|
702
|
+
envParser: new _geekmidas_envkit.EnvironmentParser({
|
|
703
|
+
...secrets,
|
|
704
|
+
...ready.env
|
|
705
|
+
}),
|
|
706
|
+
context: _geekmidas_services.serviceContext
|
|
707
|
+
});
|
|
708
|
+
if (!pending) {
|
|
709
|
+
logger.log(`✓ ${target.migrations}: ${auth.id} is up to date`);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
const file = await write(ready.workspace.root, target, `${require_index.stamp(options.now)}_${require_index.slug(options.name ?? "better_auth")}.sql`, `${pending.trim()}\n`);
|
|
713
|
+
logger.log(`📝 ${file}`);
|
|
714
|
+
return file;
|
|
715
|
+
}
|
|
716
|
+
const STUB = `import type { Kysely } from 'kysely';
|
|
717
|
+
|
|
718
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
719
|
+
// await db.schema.createTable('…').addColumn(…).execute();
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
723
|
+
// await db.schema.dropTable('…').execute();
|
|
724
|
+
}
|
|
725
|
+
`;
|
|
726
|
+
async function write(root, target, file, content) {
|
|
727
|
+
const folder = (0, node_path.join)(root, target.migrations);
|
|
728
|
+
await (0, node_fs_promises.mkdir)(folder, { recursive: true });
|
|
729
|
+
await (0, node_fs_promises.writeFile)((0, node_path.join)(folder, file), content, { flag: "wx" });
|
|
730
|
+
return `${target.migrations}/${file}`;
|
|
731
|
+
}
|
|
732
|
+
function ids(targets) {
|
|
733
|
+
return targets.map((target) => target.id);
|
|
734
|
+
}
|
|
735
|
+
//#endregion
|
|
736
|
+
exports.migrateAndSeed = migrateAndSeed;
|
|
737
|
+
exports.migrateCommand = migrateCommand;
|
|
738
|
+
exports.migrationCommand = migrationCommand;
|
|
739
|
+
exports.prepareDevDatabases = prepareDevDatabases;
|
|
740
|
+
exports.reportPendingMigrations = reportPendingMigrations;
|
|
741
|
+
exports.seedCommand = seedCommand;
|
|
742
|
+
|
|
743
|
+
//# sourceMappingURL=migrate-CFOUApUt.cjs.map
|