@geekmidas/cli 10.0.0-alpha.75 → 10.0.0-alpha.77

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.
Files changed (157) hide show
  1. package/dist/{LegacyStateStore-BFnXUFlx.cjs → LegacyStateStore-DwngfJxr.cjs} +2 -2
  2. package/dist/{LegacyStateStore-BFnXUFlx.cjs.map → LegacyStateStore-DwngfJxr.cjs.map} +1 -1
  3. package/dist/{LegacyStateStore-cpt3K1vx.mjs → LegacyStateStore-dyq07_Ir.mjs} +2 -2
  4. package/dist/{LegacyStateStore-cpt3K1vx.mjs.map → LegacyStateStore-dyq07_Ir.mjs.map} +1 -1
  5. package/dist/{LocalStateStore-zQGZ9b4J.cjs → LocalStateStore-B0NCde8Z.cjs} +2 -2
  6. package/dist/{LocalStateStore-zQGZ9b4J.cjs.map → LocalStateStore-B0NCde8Z.cjs.map} +1 -1
  7. package/dist/{LocalStateStore-RH9F2ZEy.mjs → LocalStateStore-DLxCCatI.mjs} +2 -2
  8. package/dist/{LocalStateStore-RH9F2ZEy.mjs.map → LocalStateStore-DLxCCatI.mjs.map} +1 -1
  9. package/dist/{S3StateStore-CT-ZMlQY.cjs → S3StateStore-BOenb-3o.cjs} +2 -2
  10. package/dist/{S3StateStore-CT-ZMlQY.cjs.map → S3StateStore-BOenb-3o.cjs.map} +1 -1
  11. package/dist/{S3StateStore-DBeBc0c0.mjs → S3StateStore-CGK_4hlo.mjs} +2 -2
  12. package/dist/{S3StateStore-DBeBc0c0.mjs.map → S3StateStore-CGK_4hlo.mjs.map} +1 -1
  13. package/dist/{SSMStateStore-rnzNfOaV.mjs → SSMStateStore-B_5_RqY-.mjs} +2 -2
  14. package/dist/{SSMStateStore-rnzNfOaV.mjs.map → SSMStateStore-B_5_RqY-.mjs.map} +1 -1
  15. package/dist/{SSMStateStore-HmsH3c4v.cjs → SSMStateStore-DYeD__7p.cjs} +2 -2
  16. package/dist/{SSMStateStore-HmsH3c4v.cjs.map → SSMStateStore-DYeD__7p.cjs.map} +1 -1
  17. package/dist/{add-CHBin8TF.cjs → add-DCsgtvDk.cjs} +8 -8
  18. package/dist/{add-CHBin8TF.cjs.map → add-DCsgtvDk.cjs.map} +1 -1
  19. package/dist/{add-Cvzdw8cf.mjs → add-EPQO-_dt.mjs} +3 -3
  20. package/dist/{add-Cvzdw8cf.mjs.map → add-EPQO-_dt.mjs.map} +1 -1
  21. package/dist/{aws-DojzQnLd.mjs → aws-Bja4zGSs.mjs} +13 -9
  22. package/dist/aws-Bja4zGSs.mjs.map +1 -0
  23. package/dist/{aws-DkHdTV-p.cjs → aws-CQAuluiu.cjs} +13 -9
  24. package/dist/aws-CQAuluiu.cjs.map +1 -0
  25. package/dist/awsStore-7VmS_cZR.cjs +54 -0
  26. package/dist/awsStore-7VmS_cZR.cjs.map +1 -0
  27. package/dist/awsStore-B529csle.mjs +43 -0
  28. package/dist/awsStore-B529csle.mjs.map +1 -0
  29. package/dist/{bundler-_8AEZhrs.cjs → bundler-5spPyduN.cjs} +5 -5
  30. package/dist/bundler-5spPyduN.cjs.map +1 -0
  31. package/dist/{bundler-xjp5RwRS.mjs → bundler-DgO_EMVs.mjs} +5 -5
  32. package/dist/bundler-DgO_EMVs.mjs.map +1 -0
  33. package/dist/{cli-BTGfA0B7.mjs → cli-B74Ru-V1.mjs} +3 -3
  34. package/dist/{cli-BTGfA0B7.mjs.map → cli-B74Ru-V1.mjs.map} +1 -1
  35. package/dist/{cli-B4ktxAsM.cjs → cli-ZpFTMbGw.cjs} +4 -4
  36. package/dist/{cli-B4ktxAsM.cjs.map → cli-ZpFTMbGw.cjs.map} +1 -1
  37. package/dist/{compose-UKyv_1yU.cjs → compose-CePlrbUw.cjs} +68 -60
  38. package/dist/compose-CePlrbUw.cjs.map +1 -0
  39. package/dist/{compose-DdH2JQJD.mjs → compose-IUuxRW0J.mjs} +18 -10
  40. package/dist/compose-IUuxRW0J.mjs.map +1 -0
  41. package/dist/{config-DA0kRTQe.d.mts → config-Ckmoj56i.d.mts} +3 -3
  42. package/dist/{config-B38YWicj.d.cts.map → config-Ckmoj56i.d.mts.map} +1 -1
  43. package/dist/{config-B38YWicj.d.cts → config-aAeLXH86.d.cts} +3 -3
  44. package/dist/{config-DA0kRTQe.d.mts.map → config-aAeLXH86.d.cts.map} +1 -1
  45. package/dist/config.cjs +1 -1
  46. package/dist/config.d.cts +3 -3
  47. package/dist/config.d.mts +3 -3
  48. package/dist/config.mjs +1 -1
  49. package/dist/deploy/public.cjs +12 -12
  50. package/dist/deploy/public.d.cts +3 -3
  51. package/dist/deploy/public.d.cts.map +1 -1
  52. package/dist/deploy/public.d.mts +3 -3
  53. package/dist/deploy/public.d.mts.map +1 -1
  54. package/dist/deploy/public.mjs +6 -6
  55. package/dist/{deploy-CuSsiFIc.mjs → deploy-Q6X6RXRG.mjs} +5 -5
  56. package/dist/{deploy-CuSsiFIc.mjs.map → deploy-Q6X6RXRG.mjs.map} +1 -1
  57. package/dist/{deploy-XUZoA1us.cjs → deploy-XHXi-_tL.cjs} +11 -11
  58. package/dist/{deploy-XUZoA1us.cjs.map → deploy-XHXi-_tL.cjs.map} +1 -1
  59. package/dist/{dokploy-DZXTQX10.mjs → dokploy-BtP5XSrS.mjs} +2 -2
  60. package/dist/{dokploy-DZXTQX10.mjs.map → dokploy-BtP5XSrS.mjs.map} +1 -1
  61. package/dist/{dokploy-Cvgg_pID.cjs → dokploy-MSsPBO-t.cjs} +2 -2
  62. package/dist/{dokploy-Cvgg_pID.cjs.map → dokploy-MSsPBO-t.cjs.map} +1 -1
  63. package/dist/{encryption-JkeMPugf.mjs → encryption-D4Ykywfr.mjs} +3 -3
  64. package/dist/{encryption-JkeMPugf.mjs.map → encryption-D4Ykywfr.mjs.map} +1 -1
  65. package/dist/{encryption-w-Ehh9DW.cjs → encryption-DY0na43R.cjs} +6 -6
  66. package/dist/{encryption-w-Ehh9DW.cjs.map → encryption-DY0na43R.cjs.map} +1 -1
  67. package/dist/{engine-CmozkHxq.mjs → engine-BI2xjCKt.mjs} +9 -4
  68. package/dist/{engine-CmozkHxq.mjs.map → engine-BI2xjCKt.mjs.map} +1 -1
  69. package/dist/{engine-zpWBMsQq.cjs → engine-y53kE_3H.cjs} +73 -68
  70. package/dist/{engine-zpWBMsQq.cjs.map → engine-y53kE_3H.cjs.map} +1 -1
  71. package/dist/{github-BNngHoId.mjs → github-BBIqk1Jv.mjs} +6 -6
  72. package/dist/github-BBIqk1Jv.mjs.map +1 -0
  73. package/dist/{github-BGdJ-Lnm.cjs → github-Bmb45xUQ.cjs} +10 -10
  74. package/dist/github-Bmb45xUQ.cjs.map +1 -0
  75. package/dist/{index-vMidfqhU.d.cts → index-C7Tma2m1.d.cts} +2 -2
  76. package/dist/{index-B-1lLGzy.d.mts.map → index-C7Tma2m1.d.cts.map} +1 -1
  77. package/dist/{index-B-1lLGzy.d.mts → index-CNdSRwar.d.mts} +2 -2
  78. package/dist/{index-vMidfqhU.d.cts.map → index-CNdSRwar.d.mts.map} +1 -1
  79. package/dist/index.cjs +210 -221
  80. package/dist/index.cjs.map +1 -1
  81. package/dist/index.mjs +141 -152
  82. package/dist/index.mjs.map +1 -1
  83. package/dist/migrate-31JXUOaG.cjs +93 -0
  84. package/dist/migrate-31JXUOaG.cjs.map +1 -0
  85. package/dist/{migrate-BLL1RTAj.mjs → migrate-BUBsUiM3.mjs} +3 -3
  86. package/dist/{migrate-BLL1RTAj.mjs.map → migrate-BUBsUiM3.mjs.map} +1 -1
  87. package/dist/migrate-DWVl5PQK.mjs +93 -0
  88. package/dist/migrate-DWVl5PQK.mjs.map +1 -0
  89. package/dist/{migrate-CgjlYeun.cjs → migrate-sny0NjKe.cjs} +8 -8
  90. package/dist/{migrate-CgjlYeun.cjs.map → migrate-sny0NjKe.cjs.map} +1 -1
  91. package/dist/openapi.cjs +16 -6
  92. package/dist/openapi.cjs.map +1 -1
  93. package/dist/openapi.d.cts +1 -1
  94. package/dist/openapi.d.cts.map +1 -1
  95. package/dist/openapi.d.mts +1 -1
  96. package/dist/openapi.d.mts.map +1 -1
  97. package/dist/openapi.mjs +16 -6
  98. package/dist/openapi.mjs.map +1 -1
  99. package/dist/reconcile/public.d.cts +1 -1
  100. package/dist/reconcile/public.d.mts +1 -1
  101. package/dist/{resolve-DZNx7rLy.mjs → resolve-BF1ICosS.mjs} +3 -3
  102. package/dist/{resolve-DZNx7rLy.mjs.map → resolve-BF1ICosS.mjs.map} +1 -1
  103. package/dist/{resolve-fevXn-wZ.cjs → resolve-DokLtTUb.cjs} +3 -3
  104. package/dist/{resolve-fevXn-wZ.cjs.map → resolve-DokLtTUb.cjs.map} +1 -1
  105. package/dist/{rollback-Dw-FW3X2.cjs → rollback-CpOWRR9H.cjs} +7 -7
  106. package/dist/{rollback-Dw-FW3X2.cjs.map → rollback-CpOWRR9H.cjs.map} +1 -1
  107. package/dist/{rollback-CLi5Pln_.mjs → rollback-sg0ZhKGG.mjs} +5 -5
  108. package/dist/{rollback-CLi5Pln_.mjs.map → rollback-sg0ZhKGG.mjs.map} +1 -1
  109. package/dist/{runtime-BzZD5HHi.d.cts → runtime-B5bYom9w.d.mts} +2 -2
  110. package/dist/{runtime-BzZD5HHi.d.cts.map → runtime-B5bYom9w.d.mts.map} +1 -1
  111. package/dist/{runtime-Cm6J6L7n.d.mts → runtime-BozOkPtG.d.cts} +2 -2
  112. package/dist/{runtime-Cm6J6L7n.d.mts.map → runtime-BozOkPtG.d.cts.map} +1 -1
  113. package/dist/secretsManager-DUXW3fQt.cjs +70 -0
  114. package/dist/secretsManager-DUXW3fQt.cjs.map +1 -0
  115. package/dist/secretsManager-Ti0oZwsS.mjs +70 -0
  116. package/dist/secretsManager-Ti0oZwsS.mjs.map +1 -0
  117. package/dist/{sniffer-BmCvVyhq.mjs → sniffer-DR8AlRCk.mjs} +17 -17
  118. package/dist/sniffer-DR8AlRCk.mjs.map +1 -0
  119. package/dist/{sniffer-bMzrX4cO.cjs → sniffer-Y6GQOCXB.cjs} +17 -17
  120. package/dist/sniffer-Y6GQOCXB.cjs.map +1 -0
  121. package/dist/{state-BrWIOce_.mjs → stale-DGzpiN5S.mjs} +270 -45
  122. package/dist/stale-DGzpiN5S.mjs.map +1 -0
  123. package/dist/{state-kA4Fkufx.cjs → stale-yu2FkH3G.cjs} +293 -44
  124. package/dist/stale-yu2FkH3G.cjs.map +1 -0
  125. package/dist/target/public.cjs +1 -1
  126. package/dist/target/public.d.cts +2 -2
  127. package/dist/target/public.d.mts +2 -2
  128. package/dist/target/public.mjs +1 -1
  129. package/dist/{types-TzCChNrA.d.cts → types-B9yWJAis.d.mts} +59 -9
  130. package/dist/types-B9yWJAis.d.mts.map +1 -0
  131. package/dist/{types-Dp9lgx_S.d.mts → types-ct7pdQRf.d.cts} +60 -8
  132. package/dist/types-ct7pdQRf.d.cts.map +1 -0
  133. package/dist/workspace/index.cjs +1 -1
  134. package/dist/workspace/index.d.cts +2 -2
  135. package/dist/workspace/index.d.mts +2 -2
  136. package/dist/workspace/index.mjs +1 -1
  137. package/dist/{workspace-vdrcRI-m.cjs → workspace-DOm81oUo.cjs} +54 -1
  138. package/dist/workspace-DOm81oUo.cjs.map +1 -0
  139. package/dist/{workspace-C7NqCsQF.mjs → workspace-QUTqYmVy.mjs} +43 -2
  140. package/dist/workspace-QUTqYmVy.mjs.map +1 -0
  141. package/package.json +16 -15
  142. package/dist/aws-DkHdTV-p.cjs.map +0 -1
  143. package/dist/aws-DojzQnLd.mjs.map +0 -1
  144. package/dist/bundler-_8AEZhrs.cjs.map +0 -1
  145. package/dist/bundler-xjp5RwRS.mjs.map +0 -1
  146. package/dist/compose-DdH2JQJD.mjs.map +0 -1
  147. package/dist/compose-UKyv_1yU.cjs.map +0 -1
  148. package/dist/github-BGdJ-Lnm.cjs.map +0 -1
  149. package/dist/github-BNngHoId.mjs.map +0 -1
  150. package/dist/sniffer-BmCvVyhq.mjs.map +0 -1
  151. package/dist/sniffer-bMzrX4cO.cjs.map +0 -1
  152. package/dist/state-BrWIOce_.mjs.map +0 -1
  153. package/dist/state-kA4Fkufx.cjs.map +0 -1
  154. package/dist/types-Dp9lgx_S.d.mts.map +0 -1
  155. package/dist/types-TzCChNrA.d.cts.map +0 -1
  156. package/dist/workspace-C7NqCsQF.mjs.map +0 -1
  157. package/dist/workspace-vdrcRI-m.cjs.map +0 -1
@@ -0,0 +1,93 @@
1
+ const require_workspace = require("./workspace-DOm81oUo.cjs");
2
+ const require_config = require("./config.cjs");
3
+ const require_stale = require("./stale-yu2FkH3G.cjs");
4
+ //#region src/secrets/migrate.ts
5
+ /**
6
+ * `gkm secrets:migrate` — copy a deployed stage's secrets from the store
7
+ * `secrets.store` names to another one, whole: service passwords, URLs and
8
+ * custom keys alike, so nothing is regenerated and running services keep
9
+ * their credentials.
10
+ *
11
+ * It copies and nothing more. The source is left as it was, and gkm.config.ts
12
+ * is not edited: pointing `secrets.store` at the new store is the switch, and
13
+ * the old copy is deleted by hand once deploys read from the new one.
14
+ */
15
+ const logger = console;
16
+ /** An AWS store to copy to, with no region given and none configured. */
17
+ var MigrateTargetNeedsRegion = class extends Error {
18
+ to;
19
+ constructor(to) {
20
+ super(`Copying to ${to} needs a region, and secrets.store has none to reuse. Pass it: gkm secrets:migrate --to ${to} --region <region> --stage <stage>.`);
21
+ this.to = to;
22
+ this.name = "MigrateTargetNeedsRegion";
23
+ }
24
+ };
25
+ /** The target is the store the stage is already read from. */
26
+ var MigrateTargetIsSource = class extends Error {
27
+ stage;
28
+ to;
29
+ constructor(stage, to) {
30
+ super(`Stage "${stage}" is already kept in ${to}, so there is nothing to copy. Name a different store with --to, or a different region with --region.`);
31
+ this.stage = stage;
32
+ this.to = to;
33
+ this.name = "MigrateTargetIsSource";
34
+ }
35
+ };
36
+ /** The configured store holds nothing for the stage. */
37
+ var NoSecretsToMigrate = class extends Error {
38
+ stage;
39
+ store;
40
+ constructor(stage, store) {
41
+ super(`The ${store} store holds no secrets for stage "${stage}", so there is nothing to copy. Check secrets.store in gkm.config.ts still names the store the stage is in, or create the stage there with gkm secrets:init --stage ${stage}.`);
42
+ this.stage = stage;
43
+ this.store = store;
44
+ this.name = "NoSecretsToMigrate";
45
+ }
46
+ };
47
+ /** The target already holds the stage, and `--force` was not given. */
48
+ var MigrateTargetHoldsStage = class extends Error {
49
+ stage;
50
+ to;
51
+ constructor(stage, to) {
52
+ super(`The ${to} store already holds secrets for stage "${stage}". Pass --force to replace them with the ones being copied.`);
53
+ this.stage = stage;
54
+ this.to = to;
55
+ this.name = "MigrateTargetHoldsStage";
56
+ }
57
+ };
58
+ /** The store a `--to` name and region describe. */
59
+ function targetConfig(to, region, configured) {
60
+ if (!require_workspace.SECRETS_STORE_PROVIDERS.includes(to)) throw new require_workspace.UnknownSecretsStoreProvider(to);
61
+ const provider = to;
62
+ if (provider === "file") return "file";
63
+ const fallback = typeof configured === "object" && "region" in configured ? configured.region : void 0;
64
+ const resolved = region ?? fallback;
65
+ if (!resolved) throw new MigrateTargetNeedsRegion(provider);
66
+ return {
67
+ provider,
68
+ region: resolved
69
+ };
70
+ }
71
+ async function secretsMigrateCommand(options) {
72
+ const { stage, to } = options;
73
+ const workspace = await require_config.loadWorkspaceSettings();
74
+ require_workspace.assertDeployedStage(workspace.stages, stage);
75
+ const configured = workspace.secrets.store ?? "file";
76
+ const target = targetConfig(to, options.region, configured);
77
+ if (typeof configured === "string" || typeof target === "string" ? configured === target : configured.provider === target.provider && "region" in configured && "region" in target && configured.region === target.region) throw new MigrateTargetIsSource(stage, to);
78
+ const storeOptions = options.profile ? { profile: options.profile } : {};
79
+ const source = await require_stale.secretsStoreFor(workspace, stage, storeOptions);
80
+ const destination = await require_stale.storeFromConfig(workspace, target, storeOptions);
81
+ const secrets = await source.read(stage);
82
+ if (!secrets) throw new NoSecretsToMigrate(stage, source.name);
83
+ if (!options.force && await destination.read(stage)) throw new MigrateTargetHoldsStage(stage, destination.name);
84
+ await destination.write(stage, secrets);
85
+ const shape = typeof target === "object" && "region" in target ? `{ provider: '${target.provider}', region: '${target.region}' }` : `'file'`;
86
+ logger.log(`\n✓ Copied the secrets for stage "${stage}" from ${source.name} to ${destination.name}`);
87
+ logger.log(`\n Next: set secrets.store to ${shape} in gkm.config.ts, so commands and deploys read from ${destination.name}.`);
88
+ logger.log(` The copy in ${source.name} is left as it was; delete it once deploys read from ${destination.name}.`);
89
+ }
90
+ //#endregion
91
+ exports.secretsMigrateCommand = secretsMigrateCommand;
92
+
93
+ //# sourceMappingURL=migrate-31JXUOaG.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-31JXUOaG.cjs","names":["SECRETS_STORE_PROVIDERS","UnknownSecretsStoreProvider","loadWorkspaceSettings","secretsStoreFor","storeFromConfig"],"sources":["../src/secrets/migrate.ts"],"sourcesContent":["/**\n * `gkm secrets:migrate` — copy a deployed stage's secrets from the store\n * `secrets.store` names to another one, whole: service passwords, URLs and\n * custom keys alike, so nothing is regenerated and running services keep\n * their credentials.\n *\n * It copies and nothing more. The source is left as it was, and gkm.config.ts\n * is not edited: pointing `secrets.store` at the new store is the switch, and\n * the old copy is deleted by hand once deploys read from the new one.\n */\n\nimport { loadWorkspaceSettings } from '../config.js';\nimport { assertDeployedStage } from '../workspace/stages.js';\nimport {\n\tSECRETS_STORE_PROVIDERS,\n\ttype SecretsStoreConfig,\n\ttype SecretsStoreOptions,\n\ttype SecretsStoreProvider,\n\tsecretsStoreFor,\n\tstoreFromConfig,\n\tUnknownSecretsStoreProvider,\n} from './store.js';\n\nconst logger = console;\n\nexport interface SecretsMigrateOptions {\n\tstage: string;\n\t/** The store to copy to: `'file'`, `'ssm'` or `'secrets-manager'`. */\n\tto: string;\n\t/** The target's AWS region; defaults to the configured store's. */\n\tregion?: string;\n\t/** Overwrite a stage the target already holds. */\n\tforce?: boolean;\n\t/** The AWS profile for the stage's account, for both stores. */\n\tprofile?: string;\n}\n\n/** An AWS store to copy to, with no region given and none configured. */\nexport class MigrateTargetNeedsRegion extends Error {\n\tconstructor(readonly to: string) {\n\t\tsuper(\n\t\t\t`Copying to ${to} needs a region, and secrets.store has none to reuse. ` +\n\t\t\t\t`Pass it: gkm secrets:migrate --to ${to} --region <region> --stage <stage>.`,\n\t\t);\n\t\tthis.name = 'MigrateTargetNeedsRegion';\n\t}\n}\n\n/** The target is the store the stage is already read from. */\nexport class MigrateTargetIsSource extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly to: string,\n\t) {\n\t\tsuper(\n\t\t\t`Stage \"${stage}\" is already kept in ${to}, so there is nothing to copy. ` +\n\t\t\t\t'Name a different store with --to, or a different region with --region.',\n\t\t);\n\t\tthis.name = 'MigrateTargetIsSource';\n\t}\n}\n\n/** The configured store holds nothing for the stage. */\nexport class NoSecretsToMigrate extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly store: string,\n\t) {\n\t\tsuper(\n\t\t\t`The ${store} store holds no secrets for stage \"${stage}\", so there is nothing to copy. ` +\n\t\t\t\t`Check secrets.store in gkm.config.ts still names the store the stage is in, ` +\n\t\t\t\t`or create the stage there with gkm secrets:init --stage ${stage}.`,\n\t\t);\n\t\tthis.name = 'NoSecretsToMigrate';\n\t}\n}\n\n/** The target already holds the stage, and `--force` was not given. */\nexport class MigrateTargetHoldsStage extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly to: string,\n\t) {\n\t\tsuper(\n\t\t\t`The ${to} store already holds secrets for stage \"${stage}\". ` +\n\t\t\t\t'Pass --force to replace them with the ones being copied.',\n\t\t);\n\t\tthis.name = 'MigrateTargetHoldsStage';\n\t}\n}\n\n/** The store a `--to` name and region describe. */\nfunction targetConfig(\n\tto: string,\n\tregion: string | undefined,\n\tconfigured: SecretsStoreConfig,\n): SecretsStoreConfig {\n\tif (!(SECRETS_STORE_PROVIDERS as readonly string[]).includes(to)) {\n\t\tthrow new UnknownSecretsStoreProvider(to);\n\t}\n\tconst provider = to as SecretsStoreProvider;\n\tif (provider === 'file') return 'file';\n\n\tconst fallback =\n\t\ttypeof configured === 'object' && 'region' in configured\n\t\t\t? configured.region\n\t\t\t: undefined;\n\tconst resolved = region ?? fallback;\n\tif (!resolved) throw new MigrateTargetNeedsRegion(provider);\n\treturn { provider, region: resolved };\n}\n\nexport async function secretsMigrateCommand(\n\toptions: SecretsMigrateOptions,\n): Promise<void> {\n\tconst { stage, to } = options;\n\tconst workspace = await loadWorkspaceSettings();\n\tassertDeployedStage(workspace.stages, stage);\n\n\tconst configured = workspace.secrets.store ?? 'file';\n\tconst target = targetConfig(to, options.region, configured);\n\tconst sameStore =\n\t\ttypeof configured === 'string' || typeof target === 'string'\n\t\t\t? configured === target\n\t\t\t: configured.provider === target.provider &&\n\t\t\t\t'region' in configured &&\n\t\t\t\t'region' in target &&\n\t\t\t\tconfigured.region === target.region;\n\tif (sameStore) throw new MigrateTargetIsSource(stage, to);\n\n\tconst storeOptions: SecretsStoreOptions = options.profile\n\t\t? { profile: options.profile }\n\t\t: {};\n\tconst source = await secretsStoreFor(workspace, stage, storeOptions);\n\tconst destination = await storeFromConfig(workspace, target, storeOptions);\n\n\tconst secrets = await source.read(stage);\n\tif (!secrets) throw new NoSecretsToMigrate(stage, source.name);\n\n\tif (!options.force && (await destination.read(stage))) {\n\t\tthrow new MigrateTargetHoldsStage(stage, destination.name);\n\t}\n\n\tawait destination.write(stage, secrets);\n\n\tconst shape =\n\t\ttypeof target === 'object' && 'region' in target\n\t\t\t? `{ provider: '${target.provider}', region: '${target.region}' }`\n\t\t\t: `'file'`;\n\tlogger.log(\n\t\t`\\n✓ Copied the secrets for stage \"${stage}\" from ${source.name} to ${destination.name}`,\n\t);\n\tlogger.log(\n\t\t`\\n Next: set secrets.store to ${shape} in gkm.config.ts, so commands and deploys read from ${destination.name}.`,\n\t);\n\tlogger.log(\n\t\t` The copy in ${source.name} is left as it was; delete it once deploys read from ${destination.name}.`,\n\t);\n}\n"],"mappings":";;;;;;;;;;;;;;AAuBA,MAAM,SAAS;;AAef,IAAa,2BAAb,cAA8C,MAAM;CAC9B;CAArB,YAAY,IAAqB;EAChC,MACC,cAAc,GAAG,0FACqB,GAAG,oCAC1C;EAJoB,KAAA,KAAA;EAKpB,KAAK,OAAO;CACb;AACD;;AAGA,IAAa,wBAAb,cAA2C,MAAM;CAEtC;CACA;CAFV,YACC,OACA,IACC;EACD,MACC,UAAU,MAAM,uBAAuB,GAAG,sGAE3C;EANS,KAAA,QAAA;EACA,KAAA,KAAA;EAMT,KAAK,OAAO;CACb;AACD;;AAGA,IAAa,qBAAb,cAAwC,MAAM;CAEnC;CACA;CAFV,YACC,OACA,OACC;EACD,MACC,OAAO,MAAM,qCAAqC,MAAM,sKAEI,MAAM,EACnE;EAPS,KAAA,QAAA;EACA,KAAA,QAAA;EAOT,KAAK,OAAO;CACb;AACD;;AAGA,IAAa,0BAAb,cAA6C,MAAM;CAExC;CACA;CAFV,YACC,OACA,IACC;EACD,MACC,OAAO,GAAG,0CAA0C,MAAM,4DAE3D;EANS,KAAA,QAAA;EACA,KAAA,KAAA;EAMT,KAAK,OAAO;CACb;AACD;;AAGA,SAAS,aACR,IACA,QACA,YACqB;CACrB,IAAI,CAAEA,kBAAAA,wBAA8C,SAAS,EAAE,GAC9D,MAAM,IAAIC,kBAAAA,4BAA4B,EAAE;CAEzC,MAAM,WAAW;CACjB,IAAI,aAAa,QAAQ,OAAO;CAEhC,MAAM,WACL,OAAO,eAAe,YAAY,YAAY,aAC3C,WAAW,SACX,KAAA;CACJ,MAAM,WAAW,UAAU;CAC3B,IAAI,CAAC,UAAU,MAAM,IAAI,yBAAyB,QAAQ;CAC1D,OAAO;EAAE;EAAU,QAAQ;CAAS;AACrC;AAEA,eAAsB,sBACrB,SACgB;CAChB,MAAM,EAAE,OAAO,OAAO;CACtB,MAAM,YAAY,MAAMC,eAAAA,sBAAsB;CAC9C,kBAAA,oBAAoB,UAAU,QAAQ,KAAK;CAE3C,MAAM,aAAa,UAAU,QAAQ,SAAS;CAC9C,MAAM,SAAS,aAAa,IAAI,QAAQ,QAAQ,UAAU;CAQ1D,IANC,OAAO,eAAe,YAAY,OAAO,WAAW,WACjD,eAAe,SACf,WAAW,aAAa,OAAO,YAChC,YAAY,cACZ,YAAY,UACZ,WAAW,WAAW,OAAO,QACjB,MAAM,IAAI,sBAAsB,OAAO,EAAE;CAExD,MAAM,eAAoC,QAAQ,UAC/C,EAAE,SAAS,QAAQ,QAAQ,IAC3B,CAAC;CACJ,MAAM,SAAS,MAAMC,cAAAA,gBAAgB,WAAW,OAAO,YAAY;CACnE,MAAM,cAAc,MAAMC,cAAAA,gBAAgB,WAAW,QAAQ,YAAY;CAEzE,MAAM,UAAU,MAAM,OAAO,KAAK,KAAK;CACvC,IAAI,CAAC,SAAS,MAAM,IAAI,mBAAmB,OAAO,OAAO,IAAI;CAE7D,IAAI,CAAC,QAAQ,SAAU,MAAM,YAAY,KAAK,KAAK,GAClD,MAAM,IAAI,wBAAwB,OAAO,YAAY,IAAI;CAG1D,MAAM,YAAY,MAAM,OAAO,OAAO;CAEtC,MAAM,QACL,OAAO,WAAW,YAAY,YAAY,SACvC,gBAAgB,OAAO,SAAS,cAAc,OAAO,OAAO,OAC5D;CACJ,OAAO,IACN,qCAAqC,MAAM,SAAS,OAAO,KAAK,MAAM,YAAY,MACnF;CACA,OAAO,IACN,kCAAkC,MAAM,uDAAuD,YAAY,KAAK,EACjH;CACA,OAAO,IACN,iBAAiB,OAAO,KAAK,uDAAuD,YAAY,KAAK,EACtG;AACD"}
@@ -1,7 +1,7 @@
1
1
  import { r as discover } from "./discover-BPelzeGW.mjs";
2
- import { O as TEST_STAGE } from "./workspace-C7NqCsQF.mjs";
2
+ import { O as TEST_STAGE } from "./workspace-QUTqYmVy.mjs";
3
3
  import { loadWorkspaceConfig } from "./config.mjs";
4
- import { Bt as loadSecretsForApp, Ut as secretsStoreFor, Y as constructGlobs, Z as reconcileWorkspace } from "./state-BrWIOce_.mjs";
4
+ import { Kt as secretsStoreFor, Q as reconcileWorkspace, Ut as loadSecretsForApp, X as constructGlobs } from "./stale-DGzpiN5S.mjs";
5
5
  import "./provider-D7rQzzzf.mjs";
6
6
  import { a as NoSuchMigrationTarget, d as pendingMigrations, l as migrateAndSeed, u as migrateDatabases } from "./databases-hUHTuwhx.mjs";
7
7
  import { n as slug, r as stamp } from "./index.mjs";
@@ -263,4 +263,4 @@ function ids(targets) {
263
263
  //#endregion
264
264
  export { migrateAndSeed, migrateCommand, migrationCommand, prepareDevDatabases, reportPendingMigrations, seedCommand };
265
265
 
266
- //# sourceMappingURL=migrate-BLL1RTAj.mjs.map
266
+ //# sourceMappingURL=migrate-BUBsUiM3.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"migrate-BLL1RTAj.mjs","names":[],"sources":["../src/migrate/index.ts"],"sourcesContent":["/**\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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"}
1
+ {"version":3,"file":"migrate-BUBsUiM3.mjs","names":[],"sources":["../src/migrate/index.ts"],"sourcesContent":["/**\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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"}
@@ -0,0 +1,93 @@
1
+ import { F as UnknownSecretsStoreProvider, P as SECRETS_STORE_PROVIDERS, k as assertDeployedStage } from "./workspace-QUTqYmVy.mjs";
2
+ import { loadWorkspaceSettings } from "./config.mjs";
3
+ import { Kt as secretsStoreFor, qt as storeFromConfig } from "./stale-DGzpiN5S.mjs";
4
+ //#region src/secrets/migrate.ts
5
+ /**
6
+ * `gkm secrets:migrate` — copy a deployed stage's secrets from the store
7
+ * `secrets.store` names to another one, whole: service passwords, URLs and
8
+ * custom keys alike, so nothing is regenerated and running services keep
9
+ * their credentials.
10
+ *
11
+ * It copies and nothing more. The source is left as it was, and gkm.config.ts
12
+ * is not edited: pointing `secrets.store` at the new store is the switch, and
13
+ * the old copy is deleted by hand once deploys read from the new one.
14
+ */
15
+ const logger = console;
16
+ /** An AWS store to copy to, with no region given and none configured. */
17
+ var MigrateTargetNeedsRegion = class extends Error {
18
+ to;
19
+ constructor(to) {
20
+ super(`Copying to ${to} needs a region, and secrets.store has none to reuse. Pass it: gkm secrets:migrate --to ${to} --region <region> --stage <stage>.`);
21
+ this.to = to;
22
+ this.name = "MigrateTargetNeedsRegion";
23
+ }
24
+ };
25
+ /** The target is the store the stage is already read from. */
26
+ var MigrateTargetIsSource = class extends Error {
27
+ stage;
28
+ to;
29
+ constructor(stage, to) {
30
+ super(`Stage "${stage}" is already kept in ${to}, so there is nothing to copy. Name a different store with --to, or a different region with --region.`);
31
+ this.stage = stage;
32
+ this.to = to;
33
+ this.name = "MigrateTargetIsSource";
34
+ }
35
+ };
36
+ /** The configured store holds nothing for the stage. */
37
+ var NoSecretsToMigrate = class extends Error {
38
+ stage;
39
+ store;
40
+ constructor(stage, store) {
41
+ super(`The ${store} store holds no secrets for stage "${stage}", so there is nothing to copy. Check secrets.store in gkm.config.ts still names the store the stage is in, or create the stage there with gkm secrets:init --stage ${stage}.`);
42
+ this.stage = stage;
43
+ this.store = store;
44
+ this.name = "NoSecretsToMigrate";
45
+ }
46
+ };
47
+ /** The target already holds the stage, and `--force` was not given. */
48
+ var MigrateTargetHoldsStage = class extends Error {
49
+ stage;
50
+ to;
51
+ constructor(stage, to) {
52
+ super(`The ${to} store already holds secrets for stage "${stage}". Pass --force to replace them with the ones being copied.`);
53
+ this.stage = stage;
54
+ this.to = to;
55
+ this.name = "MigrateTargetHoldsStage";
56
+ }
57
+ };
58
+ /** The store a `--to` name and region describe. */
59
+ function targetConfig(to, region, configured) {
60
+ if (!SECRETS_STORE_PROVIDERS.includes(to)) throw new UnknownSecretsStoreProvider(to);
61
+ const provider = to;
62
+ if (provider === "file") return "file";
63
+ const fallback = typeof configured === "object" && "region" in configured ? configured.region : void 0;
64
+ const resolved = region ?? fallback;
65
+ if (!resolved) throw new MigrateTargetNeedsRegion(provider);
66
+ return {
67
+ provider,
68
+ region: resolved
69
+ };
70
+ }
71
+ async function secretsMigrateCommand(options) {
72
+ const { stage, to } = options;
73
+ const workspace = await loadWorkspaceSettings();
74
+ assertDeployedStage(workspace.stages, stage);
75
+ const configured = workspace.secrets.store ?? "file";
76
+ const target = targetConfig(to, options.region, configured);
77
+ if (typeof configured === "string" || typeof target === "string" ? configured === target : configured.provider === target.provider && "region" in configured && "region" in target && configured.region === target.region) throw new MigrateTargetIsSource(stage, to);
78
+ const storeOptions = options.profile ? { profile: options.profile } : {};
79
+ const source = await secretsStoreFor(workspace, stage, storeOptions);
80
+ const destination = await storeFromConfig(workspace, target, storeOptions);
81
+ const secrets = await source.read(stage);
82
+ if (!secrets) throw new NoSecretsToMigrate(stage, source.name);
83
+ if (!options.force && await destination.read(stage)) throw new MigrateTargetHoldsStage(stage, destination.name);
84
+ await destination.write(stage, secrets);
85
+ const shape = typeof target === "object" && "region" in target ? `{ provider: '${target.provider}', region: '${target.region}' }` : `'file'`;
86
+ logger.log(`\n✓ Copied the secrets for stage "${stage}" from ${source.name} to ${destination.name}`);
87
+ logger.log(`\n Next: set secrets.store to ${shape} in gkm.config.ts, so commands and deploys read from ${destination.name}.`);
88
+ logger.log(` The copy in ${source.name} is left as it was; delete it once deploys read from ${destination.name}.`);
89
+ }
90
+ //#endregion
91
+ export { secretsMigrateCommand };
92
+
93
+ //# sourceMappingURL=migrate-DWVl5PQK.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-DWVl5PQK.mjs","names":[],"sources":["../src/secrets/migrate.ts"],"sourcesContent":["/**\n * `gkm secrets:migrate` — copy a deployed stage's secrets from the store\n * `secrets.store` names to another one, whole: service passwords, URLs and\n * custom keys alike, so nothing is regenerated and running services keep\n * their credentials.\n *\n * It copies and nothing more. The source is left as it was, and gkm.config.ts\n * is not edited: pointing `secrets.store` at the new store is the switch, and\n * the old copy is deleted by hand once deploys read from the new one.\n */\n\nimport { loadWorkspaceSettings } from '../config.js';\nimport { assertDeployedStage } from '../workspace/stages.js';\nimport {\n\tSECRETS_STORE_PROVIDERS,\n\ttype SecretsStoreConfig,\n\ttype SecretsStoreOptions,\n\ttype SecretsStoreProvider,\n\tsecretsStoreFor,\n\tstoreFromConfig,\n\tUnknownSecretsStoreProvider,\n} from './store.js';\n\nconst logger = console;\n\nexport interface SecretsMigrateOptions {\n\tstage: string;\n\t/** The store to copy to: `'file'`, `'ssm'` or `'secrets-manager'`. */\n\tto: string;\n\t/** The target's AWS region; defaults to the configured store's. */\n\tregion?: string;\n\t/** Overwrite a stage the target already holds. */\n\tforce?: boolean;\n\t/** The AWS profile for the stage's account, for both stores. */\n\tprofile?: string;\n}\n\n/** An AWS store to copy to, with no region given and none configured. */\nexport class MigrateTargetNeedsRegion extends Error {\n\tconstructor(readonly to: string) {\n\t\tsuper(\n\t\t\t`Copying to ${to} needs a region, and secrets.store has none to reuse. ` +\n\t\t\t\t`Pass it: gkm secrets:migrate --to ${to} --region <region> --stage <stage>.`,\n\t\t);\n\t\tthis.name = 'MigrateTargetNeedsRegion';\n\t}\n}\n\n/** The target is the store the stage is already read from. */\nexport class MigrateTargetIsSource extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly to: string,\n\t) {\n\t\tsuper(\n\t\t\t`Stage \"${stage}\" is already kept in ${to}, so there is nothing to copy. ` +\n\t\t\t\t'Name a different store with --to, or a different region with --region.',\n\t\t);\n\t\tthis.name = 'MigrateTargetIsSource';\n\t}\n}\n\n/** The configured store holds nothing for the stage. */\nexport class NoSecretsToMigrate extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly store: string,\n\t) {\n\t\tsuper(\n\t\t\t`The ${store} store holds no secrets for stage \"${stage}\", so there is nothing to copy. ` +\n\t\t\t\t`Check secrets.store in gkm.config.ts still names the store the stage is in, ` +\n\t\t\t\t`or create the stage there with gkm secrets:init --stage ${stage}.`,\n\t\t);\n\t\tthis.name = 'NoSecretsToMigrate';\n\t}\n}\n\n/** The target already holds the stage, and `--force` was not given. */\nexport class MigrateTargetHoldsStage extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly to: string,\n\t) {\n\t\tsuper(\n\t\t\t`The ${to} store already holds secrets for stage \"${stage}\". ` +\n\t\t\t\t'Pass --force to replace them with the ones being copied.',\n\t\t);\n\t\tthis.name = 'MigrateTargetHoldsStage';\n\t}\n}\n\n/** The store a `--to` name and region describe. */\nfunction targetConfig(\n\tto: string,\n\tregion: string | undefined,\n\tconfigured: SecretsStoreConfig,\n): SecretsStoreConfig {\n\tif (!(SECRETS_STORE_PROVIDERS as readonly string[]).includes(to)) {\n\t\tthrow new UnknownSecretsStoreProvider(to);\n\t}\n\tconst provider = to as SecretsStoreProvider;\n\tif (provider === 'file') return 'file';\n\n\tconst fallback =\n\t\ttypeof configured === 'object' && 'region' in configured\n\t\t\t? configured.region\n\t\t\t: undefined;\n\tconst resolved = region ?? fallback;\n\tif (!resolved) throw new MigrateTargetNeedsRegion(provider);\n\treturn { provider, region: resolved };\n}\n\nexport async function secretsMigrateCommand(\n\toptions: SecretsMigrateOptions,\n): Promise<void> {\n\tconst { stage, to } = options;\n\tconst workspace = await loadWorkspaceSettings();\n\tassertDeployedStage(workspace.stages, stage);\n\n\tconst configured = workspace.secrets.store ?? 'file';\n\tconst target = targetConfig(to, options.region, configured);\n\tconst sameStore =\n\t\ttypeof configured === 'string' || typeof target === 'string'\n\t\t\t? configured === target\n\t\t\t: configured.provider === target.provider &&\n\t\t\t\t'region' in configured &&\n\t\t\t\t'region' in target &&\n\t\t\t\tconfigured.region === target.region;\n\tif (sameStore) throw new MigrateTargetIsSource(stage, to);\n\n\tconst storeOptions: SecretsStoreOptions = options.profile\n\t\t? { profile: options.profile }\n\t\t: {};\n\tconst source = await secretsStoreFor(workspace, stage, storeOptions);\n\tconst destination = await storeFromConfig(workspace, target, storeOptions);\n\n\tconst secrets = await source.read(stage);\n\tif (!secrets) throw new NoSecretsToMigrate(stage, source.name);\n\n\tif (!options.force && (await destination.read(stage))) {\n\t\tthrow new MigrateTargetHoldsStage(stage, destination.name);\n\t}\n\n\tawait destination.write(stage, secrets);\n\n\tconst shape =\n\t\ttypeof target === 'object' && 'region' in target\n\t\t\t? `{ provider: '${target.provider}', region: '${target.region}' }`\n\t\t\t: `'file'`;\n\tlogger.log(\n\t\t`\\n✓ Copied the secrets for stage \"${stage}\" from ${source.name} to ${destination.name}`,\n\t);\n\tlogger.log(\n\t\t`\\n Next: set secrets.store to ${shape} in gkm.config.ts, so commands and deploys read from ${destination.name}.`,\n\t);\n\tlogger.log(\n\t\t` The copy in ${source.name} is left as it was; delete it once deploys read from ${destination.name}.`,\n\t);\n}\n"],"mappings":";;;;;;;;;;;;;;AAuBA,MAAM,SAAS;;AAef,IAAa,2BAAb,cAA8C,MAAM;CAC9B;CAArB,YAAY,IAAqB;EAChC,MACC,cAAc,GAAG,0FACqB,GAAG,oCAC1C;EAJoB,KAAA,KAAA;EAKpB,KAAK,OAAO;CACb;AACD;;AAGA,IAAa,wBAAb,cAA2C,MAAM;CAEtC;CACA;CAFV,YACC,OACA,IACC;EACD,MACC,UAAU,MAAM,uBAAuB,GAAG,sGAE3C;EANS,KAAA,QAAA;EACA,KAAA,KAAA;EAMT,KAAK,OAAO;CACb;AACD;;AAGA,IAAa,qBAAb,cAAwC,MAAM;CAEnC;CACA;CAFV,YACC,OACA,OACC;EACD,MACC,OAAO,MAAM,qCAAqC,MAAM,sKAEI,MAAM,EACnE;EAPS,KAAA,QAAA;EACA,KAAA,QAAA;EAOT,KAAK,OAAO;CACb;AACD;;AAGA,IAAa,0BAAb,cAA6C,MAAM;CAExC;CACA;CAFV,YACC,OACA,IACC;EACD,MACC,OAAO,GAAG,0CAA0C,MAAM,4DAE3D;EANS,KAAA,QAAA;EACA,KAAA,KAAA;EAMT,KAAK,OAAO;CACb;AACD;;AAGA,SAAS,aACR,IACA,QACA,YACqB;CACrB,IAAI,CAAE,wBAA8C,SAAS,EAAE,GAC9D,MAAM,IAAI,4BAA4B,EAAE;CAEzC,MAAM,WAAW;CACjB,IAAI,aAAa,QAAQ,OAAO;CAEhC,MAAM,WACL,OAAO,eAAe,YAAY,YAAY,aAC3C,WAAW,SACX,KAAA;CACJ,MAAM,WAAW,UAAU;CAC3B,IAAI,CAAC,UAAU,MAAM,IAAI,yBAAyB,QAAQ;CAC1D,OAAO;EAAE;EAAU,QAAQ;CAAS;AACrC;AAEA,eAAsB,sBACrB,SACgB;CAChB,MAAM,EAAE,OAAO,OAAO;CACtB,MAAM,YAAY,MAAM,sBAAsB;CAC9C,oBAAoB,UAAU,QAAQ,KAAK;CAE3C,MAAM,aAAa,UAAU,QAAQ,SAAS;CAC9C,MAAM,SAAS,aAAa,IAAI,QAAQ,QAAQ,UAAU;CAQ1D,IANC,OAAO,eAAe,YAAY,OAAO,WAAW,WACjD,eAAe,SACf,WAAW,aAAa,OAAO,YAChC,YAAY,cACZ,YAAY,UACZ,WAAW,WAAW,OAAO,QACjB,MAAM,IAAI,sBAAsB,OAAO,EAAE;CAExD,MAAM,eAAoC,QAAQ,UAC/C,EAAE,SAAS,QAAQ,QAAQ,IAC3B,CAAC;CACJ,MAAM,SAAS,MAAM,gBAAgB,WAAW,OAAO,YAAY;CACnE,MAAM,cAAc,MAAM,gBAAgB,WAAW,QAAQ,YAAY;CAEzE,MAAM,UAAU,MAAM,OAAO,KAAK,KAAK;CACvC,IAAI,CAAC,SAAS,MAAM,IAAI,mBAAmB,OAAO,OAAO,IAAI;CAE7D,IAAI,CAAC,QAAQ,SAAU,MAAM,YAAY,KAAK,KAAK,GAClD,MAAM,IAAI,wBAAwB,OAAO,YAAY,IAAI;CAG1D,MAAM,YAAY,MAAM,OAAO,OAAO;CAEtC,MAAM,QACL,OAAO,WAAW,YAAY,YAAY,SACvC,gBAAgB,OAAO,SAAS,cAAc,OAAO,OAAO,OAC5D;CACJ,OAAO,IACN,qCAAqC,MAAM,SAAS,OAAO,KAAK,MAAM,YAAY,MACnF;CACA,OAAO,IACN,kCAAkC,MAAM,uDAAuD,YAAY,KAAK,EACjH;CACA,OAAO,IACN,iBAAiB,OAAO,KAAK,uDAAuD,YAAY,KAAK,EACtG;AACD"}
@@ -1,7 +1,7 @@
1
1
  const require_discover = require("./discover-D1It5r_h.cjs");
2
- const require_workspace = require("./workspace-vdrcRI-m.cjs");
2
+ const require_workspace = require("./workspace-DOm81oUo.cjs");
3
3
  const require_config = require("./config.cjs");
4
- const require_state = require("./state-kA4Fkufx.cjs");
4
+ const require_stale = require("./stale-yu2FkH3G.cjs");
5
5
  require("./provider-B3Sv_a74.cjs");
6
6
  const require_databases = require("./databases-C2p5LGAp.cjs");
7
7
  const require_index = require("./index.cjs");
@@ -54,7 +54,7 @@ async function readyStage(cwd, stage) {
54
54
  if (target !== local && target !== "test") throw new MigrateDeployedStage(target, local);
55
55
  const sources = {};
56
56
  const manifest = await require_discover.discover({
57
- patterns: require_state.constructGlobs(workspace),
57
+ patterns: require_stale.constructGlobs(workspace),
58
58
  cwd: workspace.root,
59
59
  sources
60
60
  });
@@ -62,7 +62,7 @@ async function readyStage(cwd, stage) {
62
62
  workspace,
63
63
  manifest,
64
64
  sources,
65
- env: { ...(await require_state.reconcileWorkspace(workspace, {
65
+ env: { ...(await require_stale.reconcileWorkspace(workspace, {
66
66
  stage: target,
67
67
  manifest,
68
68
  start: true
@@ -139,7 +139,7 @@ function logSeeds(runs, log = logger.log) {
139
139
  async function reportPendingMigrations(workspace, env) {
140
140
  const sources = {};
141
141
  const manifest = await require_discover.discover({
142
- patterns: require_state.constructGlobs(workspace),
142
+ patterns: require_stale.constructGlobs(workspace),
143
143
  cwd: workspace.root,
144
144
  sources
145
145
  });
@@ -163,7 +163,7 @@ async function reportPendingMigrations(workspace, env) {
163
163
  async function prepareDevDatabases(workspace, env, { seed = false } = {}) {
164
164
  const sources = {};
165
165
  const manifest = await require_discover.discover({
166
- patterns: require_state.constructGlobs(workspace),
166
+ patterns: require_stale.constructGlobs(workspace),
167
167
  cwd: workspace.root,
168
168
  sources
169
169
  });
@@ -225,7 +225,7 @@ async function writeAuthMigration(ready, auth, target, options) {
225
225
  only: target.id
226
226
  });
227
227
  const stage = ready.workspace.stages.local;
228
- const secrets = await require_state.loadSecretsForApp(await require_state.secretsStoreFor(ready.workspace, stage), stage);
228
+ const secrets = await require_stale.loadSecretsForApp(await require_stale.secretsStoreFor(ready.workspace, stage), stage);
229
229
  const pending = await auth.pendingMigration({
230
230
  envParser: new _geekmidas_envkit.EnvironmentParser({
231
231
  ...secrets,
@@ -268,4 +268,4 @@ exports.prepareDevDatabases = prepareDevDatabases;
268
268
  exports.reportPendingMigrations = reportPendingMigrations;
269
269
  exports.seedCommand = seedCommand;
270
270
 
271
- //# sourceMappingURL=migrate-CgjlYeun.cjs.map
271
+ //# sourceMappingURL=migrate-sny0NjKe.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"migrate-CgjlYeun.cjs","names":["TEST_STAGE","loadWorkspaceConfig","discover","constructGlobs","reconcileWorkspace","migrateDatabases","migrateAndSeed","pendingMigrations","migrationTargets","canonicalId","NoSuchMigrationTarget","stamp","slug","loadSecretsForApp","secretsStoreFor","EnvironmentParser","serviceContext","join","mkdir","writeFile"],"sources":["../src/migrate/index.ts"],"sourcesContent":["/**\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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"}
1
+ {"version":3,"file":"migrate-sny0NjKe.cjs","names":["TEST_STAGE","loadWorkspaceConfig","discover","constructGlobs","reconcileWorkspace","migrateDatabases","migrateAndSeed","pendingMigrations","migrationTargets","canonicalId","NoSuchMigrationTarget","stamp","slug","loadSecretsForApp","secretsStoreFor","EnvironmentParser","serviceContext","join","mkdir","writeFile"],"sources":["../src/migrate/index.ts"],"sourcesContent":["/**\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,5 +1,7 @@
1
1
  #!/usr/bin/env -S npx tsx
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const require_derive = require("./derive-DAbo49sm.cjs");
4
+ const require_workspace = require("./workspace-DOm81oUo.cjs");
3
5
  const require_config = require("./config.cjs");
4
6
  const require_EndpointGenerator = require("./EndpointGenerator-CP6sBvQV.cjs");
5
7
  const require_OpenApiTsGenerator = require("./OpenApiTsGenerator-CKBCckKz.cjs");
@@ -125,14 +127,14 @@ async function openapiCommand(options = {}) {
125
127
  const entry = backendApps.find(([name]) => name === options.app);
126
128
  if (!entry) throw new Error(`App "${options.app}" not found or has OpenAPI disabled`);
127
129
  const [appName, app] = entry;
128
- const result = await generateOpenApiForApp(workspaceRoot, appName, app);
130
+ const result = await generateOpenApiForApp(workspace, appName, app);
129
131
  if (result) logger.log(`📄 [${appName}] Generated OpenAPI (${result.endpointCount} endpoints)`);
130
132
  return;
131
133
  }
132
134
  for (const [appName, app] of backendApps) {
133
135
  const appPath = (0, node_path.join)(workspaceRoot, app.path);
134
136
  if ((0, node_path.resolve)(appPath) === (0, node_path.resolve)(workspaceRoot)) {
135
- const result = await generateOpenApiForApp(workspaceRoot, appName, app, false);
137
+ const result = await generateOpenApiForApp(workspace, appName, app, false);
136
138
  if (result) logger.log(`Found ${result.endpointCount} endpoints`);
137
139
  continue;
138
140
  }
@@ -147,14 +149,22 @@ async function openapiCommand(options = {}) {
147
149
  * Generate OpenAPI for a single named app within a workspace.
148
150
  * Runs in-process. The caller is responsible for ensuring `process.cwd()`
149
151
  * is the app's directory so tsx loads the app's tsconfig path aliases.
152
+ *
153
+ * The endpoints are found the way the app's build finds them — through the
154
+ * workspace's constructs globs — never by importing every `.ts` under the app,
155
+ * which loaded its tests and gkm's own generated files with them.
150
156
  */
151
- async function generateOpenApiForApp(workspaceRoot, _appName, app, silent = true) {
157
+ async function generateOpenApiForApp(workspace, appName, app, silent = true) {
152
158
  if (app.type !== "backend") return null;
153
- const appPath = (0, node_path.join)(workspaceRoot, app.path);
154
- return generateOpenApiFrom([(0, node_path.join)(appPath, "**/*.ts")], {
159
+ return generateOpenApi((await new require_EndpointGenerator.EndpointGenerator().load(require_workspace.appConstructGlobs(workspace, appName))).map(({ construct }) => construct).filter((endpoint) => {
160
+ const owner = endpoint.owner ?? endpoint.surface?.id;
161
+ if (!owner) return false;
162
+ const key = require_derive.appKey(owner);
163
+ return key === appName || !workspace.apps[key];
164
+ }), {
155
165
  openapi: app.openapi ?? { enabled: true },
156
166
  silent,
157
- root: workspaceRoot
167
+ root: workspace.root
158
168
  });
159
169
  }
160
170
  /**