@warlock.js/core 5.36.0 → 5.37.0

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 (76) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/esm/cli/commands/migrate.command.mjs +10 -0
  3. package/esm/cli/commands/migrate.command.mjs.map +1 -1
  4. package/esm/cli/commands/release/release-command.action.mjs +245 -0
  5. package/esm/cli/commands/release/release-command.action.mjs.map +1 -0
  6. package/esm/cli/commands/release.command.mjs +66 -0
  7. package/esm/cli/commands/release.command.mjs.map +1 -0
  8. package/esm/cli/framework-cli-commands.mjs +2 -0
  9. package/esm/cli/framework-cli-commands.mjs.map +1 -1
  10. package/esm/database/migrate-action.mjs +24 -2
  11. package/esm/database/migrate-action.mjs.map +1 -1
  12. package/esm/dev-server/loader/load-hook.d.mts.map +1 -1
  13. package/esm/dev-server/loader/load-hook.mjs +6 -0
  14. package/esm/dev-server/loader/load-hook.mjs.map +1 -1
  15. package/esm/index.d.mts +3 -2
  16. package/esm/index.mjs +2 -2
  17. package/esm/production/route-registration-snapshot.mjs +10 -3
  18. package/esm/production/route-registration-snapshot.mjs.map +1 -1
  19. package/esm/release/activate.mjs +261 -0
  20. package/esm/release/activate.mjs.map +1 -0
  21. package/esm/release/candidate.mjs +107 -0
  22. package/esm/release/candidate.mjs.map +1 -0
  23. package/esm/release/cascade-migration-provider.mjs +32 -0
  24. package/esm/release/cascade-migration-provider.mjs.map +1 -0
  25. package/esm/release/config.mjs +60 -0
  26. package/esm/release/config.mjs.map +1 -0
  27. package/esm/release/gate.mjs +67 -0
  28. package/esm/release/gate.mjs.map +1 -0
  29. package/esm/release/migrations-proof.mjs +100 -0
  30. package/esm/release/migrations-proof.mjs.map +1 -0
  31. package/esm/release/prune.mjs +42 -0
  32. package/esm/release/prune.mjs.map +1 -0
  33. package/esm/release/report.mjs +25 -0
  34. package/esm/release/report.mjs.map +1 -0
  35. package/esm/release/runner.mjs +55 -0
  36. package/esm/release/runner.mjs.map +1 -0
  37. package/esm/release/semver.mjs +53 -0
  38. package/esm/release/semver.mjs.map +1 -0
  39. package/esm/release/stage.mjs +122 -0
  40. package/esm/release/stage.mjs.map +1 -0
  41. package/esm/release/store.mjs +131 -0
  42. package/esm/release/store.mjs.map +1 -0
  43. package/esm/release/types.d.mts +102 -0
  44. package/esm/release/types.d.mts.map +1 -0
  45. package/esm/release/verify.mjs +94 -0
  46. package/esm/release/verify.mjs.map +1 -0
  47. package/esm/router/index.d.mts +2 -2
  48. package/esm/router/index.mjs +2 -2
  49. package/esm/router/router.d.mts +4 -2
  50. package/esm/router/router.d.mts.map +1 -1
  51. package/esm/router/router.mjs +13 -5
  52. package/esm/router/router.mjs.map +1 -1
  53. package/esm/router/types.d.mts +26 -0
  54. package/esm/router/types.d.mts.map +1 -1
  55. package/esm/router/unguarded-route-diagnostics.d.mts +6 -0
  56. package/esm/router/unguarded-route-diagnostics.d.mts.map +1 -1
  57. package/esm/router/unguarded-route-diagnostics.mjs +10 -2
  58. package/esm/router/unguarded-route-diagnostics.mjs.map +1 -1
  59. package/esm/tooling/check/security-conventions.mjs +19 -1
  60. package/esm/tooling/check/security-conventions.mjs.map +1 -1
  61. package/esm/tooling/inspect/collect-routes.mjs +3 -2
  62. package/esm/tooling/inspect/collect-routes.mjs.map +1 -1
  63. package/esm/validation/init.mjs +11 -4
  64. package/esm/validation/init.mjs.map +1 -1
  65. package/esm/validation/translate-rule.mjs +63 -0
  66. package/esm/validation/translate-rule.mjs.map +1 -0
  67. package/esm/warlock-config/index.d.mts +2 -1
  68. package/esm/warlock-config/types.d.mts +8 -0
  69. package/esm/warlock-config/types.d.mts.map +1 -1
  70. package/llms-full.txt +94 -2
  71. package/llms.txt +1 -0
  72. package/package.json +13 -13
  73. package/skills/deploy-release/SKILL.md +76 -0
  74. package/skills/index.md +1 -1
  75. package/skills/register-route/SKILL.md +12 -0
  76. package/skills/warlock-conventions/SKILL.md +1 -1
package/CHANGELOG.md CHANGED
@@ -6,6 +6,34 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  > ⚠ **Versioning: `@warlock.js/*` does not follow SemVer strictly — breaking changes may ship in a minor.** This is a deliberate decision, not an oversight: the framework is pre-adoption and the cost of a major per behaviour fix currently outweighs the benefit. **Pin an exact version or a tilde range (`~4.13.0`) if you need to opt into changes rather than receive them.** Every breaking change is marked **BREAKING** in its entry and summarised in an _Upgrading_ section at the top of the release. **This policy will change once the framework has consumers beyond its author.**
8
8
 
9
+ ## 5.37.0 - 2026-10-24
10
+
11
+ ### Added
12
+
13
+ - `warlock release <action> [id]`: a deploy pipeline for applications, configured by the new `release` section of `warlock.config.ts` (`ReleaseConfig`, `ReleaseCheck`). `candidate` freezes a clean commit (`--commit`, `--allow-dirty`) into `.warlock/releases/<version>-<shortsha>/` with a manifest (version, commit, creation time, Node version, lockfile hash). `gate` runs an ordered list of named checks (default: typecheck, check, test, build) and stops at the first failure. `migrations --prove [--target]` snapshots the migration ledger, tables and indexes around a real run of the pending migrations and compares them with the expected set. `stage` copies the build and what is needed to run it. `activate [--preflight] [--dry-run] [--allow-same]` checks the staged files, the gate and that the version is strictly greater than the active one, then switches `current` with an atomic symlink, keeps `previous` and runs the restart command you configure in `release.restart.command`; Warlock never calls a process manager itself. `verify` retries a GET on `release.verify.url` and compares the reported version and commit with the active release. `rollback [--dry-run]` switches back and reruns the restart command; it does not roll migrations back. `list`, `status` and `prune --keep <n>` (the only command that deletes releases) complete the family. Every action accepts `--json`, and a manifest per release plus a `release.log` record what happened.
14
+
15
+ - `sites` route option and `router.group({ sites })` option: the `web.sites` keys a multi-site app offers a named API route to in the browser. Absent means shared by every site; a route's own `sites` replaces its group's. The route snapshot carries `sites` only on routes that declared it.
16
+ - The named route snapshot carries `guarded: true` on a route with an auth guard in its middleware chain (group middleware included), so browser route tables can leave it out for guests.
17
+
18
+ - In development, the first time a validation translation cannot be filled, a warning names its `validation.*` key.
19
+
20
+ ### Changed
21
+
22
+ - **BREAKING** `warlock migrate --rollback` and `warlock migrate --fresh` print a destructive-operation summary (collections/tables dropped, fields/columns unset, document/row counts) before running. Outside development they ask for confirmation, and exit with code 1 when there is no terminal. New `--yes, -y` and `--force` flags confirm without a prompt.
23
+
24
+ ### Fixed
25
+
26
+ - `warlock inspect` and the generated `AGENTS.md` list of "Mutating routes without an auth guard" counted only routes whose guard attached a `warlock.auth` descriptor. A middleware that declares itself a guard with `markAuthGuard()`, including one registered once on a `router.group`, now marks every route inside the group as guarded.
27
+ - `warlock check` (`WL9006`) treats `markAuthGuard(fn, false)` written inline in a middleware list as not a guard, and recognises a local factory such as `const requireAdmin = () => markAuthGuard(fn)` used as `middleware: [requireAdmin()]` in a route or group.
28
+
29
+ - The default validation message translation no longer returns a message with an unfilled `:placeholder`. When the browser form library is imported on the server, its English `validation.*` defaults share the translation registry and name the length value `:length`, where seal uses `:minLength` and `:maxLength`, so a `v.string().maxLength(5)` failure read "This input can not be greater than :length characters". The aliases `:length` (for `minLength` and `maxLength`) and `:matchingInput` (for `match`) are now mapped, and a translation that still cannot be filled falls back to seal's own message. App overrides of `validation.*` keep working.
30
+
31
+ - The dev loader hook answers stylesheet imports (`.css`, `.scss`, `.sass`, `.less`, `.styl`) with an empty module, so sitemap discovery works for a page whose component imports a stylesheet instead of failing with `ERR_UNKNOWN_FILE_EXTENSION`.
32
+
33
+ ### Upgrading
34
+
35
+ - Deploy scripts and CI jobs that run `warlock migrate --rollback` or `--fresh` with `NODE_ENV` set to anything other than `development` must add `--yes` (or `--force`). A rollback that removes no data does not ask. On MongoDB, a plain `dropColumn()` in a migration's `down()` is now skipped (see the Cascade 5.37.0 notes).
36
+
9
37
  ## 5.36.0 - 2026-10-22
10
38
 
11
39
  ### Added
@@ -25,6 +25,16 @@ const migrateCommand = command({
25
25
  description: "Rollback migrations, drop all tables",
26
26
  type: "boolean"
27
27
  },
28
+ {
29
+ text: "--yes, -y",
30
+ description: "With --fresh or --rollback: confirm a rollback that removes data without asking (required outside development when there is no terminal)",
31
+ type: "boolean"
32
+ },
33
+ {
34
+ text: "--force",
35
+ description: "Same as --yes",
36
+ type: "boolean"
37
+ },
28
38
  {
29
39
  text: "--path, -p",
30
40
  description: "Migration file path, if not provided, all migrations will be wroking",
@@ -1 +1 @@
1
- {"version":3,"file":"migrate.command.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/cli/commands/migrate.command.ts"],"sourcesContent":["import { migrateAction } from \"../../database/migrate-action\";\nimport { command } from \"../../commands/cli-command\";\n\nexport const migrateCommand = command({\n action: migrateAction,\n name: \"migrate\",\n description: \"Run database migrations\",\n preload: {\n config: [\"database\", \"log\"],\n connectors: [\"database\", \"logger\", \"storage\"],\n },\n options: [\n {\n text: \"--fresh, -f\",\n description: \"Drop all tables and re-run migrations\",\n type: \"boolean\",\n },\n {\n text: \"--rollback, -r\",\n description: \"Rollback migrations, drop all tables\",\n type: \"boolean\",\n },\n {\n text: \"--path, -p\",\n description: \"Migration file path, if not provided, all migrations will be wroking\",\n type: \"string\",\n },\n {\n text: \"--list, -l\",\n description: \"Show migration state: executed migrations, then what will run next\",\n type: \"boolean\",\n },\n {\n text: \"--pending\",\n description:\n \"List only what will run next, in execution order. Exits 0 if nothing is pending, 1 if something is, 2 if it could not be determined\",\n type: \"boolean\",\n },\n {\n text: \"--all, -a\",\n description: \"List all migrations files in the app\",\n type: \"boolean\",\n },\n {\n text: \"--sql, -s\",\n description: \"Export all migrations as phase-ordered SQL files instead of executing them\",\n type: \"boolean\",\n },\n {\n text: \"--pending-only\",\n description: \"When used with --sql, exports only pending migrations instead of all\",\n type: \"boolean\",\n },\n {\n text: \"--compact, -c\",\n description:\n \"When used with --sql, strips out generated comments and blank lines to reduce file size\",\n type: \"boolean\",\n },\n ],\n});\n"],"mappings":";;;;AAGA,MAAa,iBAAiB,QAAQ;CACpC,QAAQ;CACR,MAAM;CACN,aAAa;CACb,SAAS;EACP,QAAQ,CAAC,YAAY,KAAK;EAC1B,YAAY;GAAC;GAAY;GAAU;EAAS;CAC9C;CACA,SAAS;EACP;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aACE;GACF,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aACE;GACF,MAAM;EACR;CACF;AACF,CAAC"}
1
+ {"version":3,"file":"migrate.command.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/cli/commands/migrate.command.ts"],"sourcesContent":["import { migrateAction } from \"../../database/migrate-action\";\nimport { command } from \"../../commands/cli-command\";\n\nexport const migrateCommand = command({\n action: migrateAction,\n name: \"migrate\",\n description: \"Run database migrations\",\n preload: {\n config: [\"database\", \"log\"],\n connectors: [\"database\", \"logger\", \"storage\"],\n },\n options: [\n {\n text: \"--fresh, -f\",\n description: \"Drop all tables and re-run migrations\",\n type: \"boolean\",\n },\n {\n text: \"--rollback, -r\",\n description: \"Rollback migrations, drop all tables\",\n type: \"boolean\",\n },\n {\n text: \"--yes, -y\",\n description:\n \"With --fresh or --rollback: confirm a rollback that removes data without asking (required outside development when there is no terminal)\",\n type: \"boolean\",\n },\n {\n text: \"--force\",\n description: \"Same as --yes\",\n type: \"boolean\",\n },\n {\n text: \"--path, -p\",\n description: \"Migration file path, if not provided, all migrations will be wroking\",\n type: \"string\",\n },\n {\n text: \"--list, -l\",\n description: \"Show migration state: executed migrations, then what will run next\",\n type: \"boolean\",\n },\n {\n text: \"--pending\",\n description:\n \"List only what will run next, in execution order. Exits 0 if nothing is pending, 1 if something is, 2 if it could not be determined\",\n type: \"boolean\",\n },\n {\n text: \"--all, -a\",\n description: \"List all migrations files in the app\",\n type: \"boolean\",\n },\n {\n text: \"--sql, -s\",\n description: \"Export all migrations as phase-ordered SQL files instead of executing them\",\n type: \"boolean\",\n },\n {\n text: \"--pending-only\",\n description: \"When used with --sql, exports only pending migrations instead of all\",\n type: \"boolean\",\n },\n {\n text: \"--compact, -c\",\n description:\n \"When used with --sql, strips out generated comments and blank lines to reduce file size\",\n type: \"boolean\",\n },\n ],\n});\n"],"mappings":";;;;AAGA,MAAa,iBAAiB,QAAQ;CACpC,QAAQ;CACR,MAAM;CACN,aAAa;CACb,SAAS;EACP,QAAQ,CAAC,YAAY,KAAK;EAC1B,YAAY;GAAC;GAAY;GAAU;EAAS;CAC9C;CACA,SAAS;EACP;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aACE;GACF,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aACE;GACF,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aACE;GACF,MAAM;EACR;CACF;AACF,CAAC"}
@@ -0,0 +1,245 @@
1
+ import { connectorsManager } from "../../../connectors/connectors-manager.mjs";
2
+ import { warlockConfigManager } from "../../../warlock-config/warlock-config.manager.mjs";
3
+ import { loadConfigFiles } from "../../../config/load-config-files.mjs";
4
+ import { flushAndExit } from "../../flush-and-exit.mjs";
5
+ import { resolveRelease } from "../../../release/store.mjs";
6
+ import { stageRelease } from "../../../release/stage.mjs";
7
+ import { activateRelease, rollbackRelease } from "../../../release/activate.mjs";
8
+ import { createCandidate } from "../../../release/candidate.mjs";
9
+ import { resolveReleaseConfig } from "../../../release/config.mjs";
10
+ import { runGate } from "../../../release/gate.mjs";
11
+ import { planMigrationProof, proveMigrations } from "../../../release/migrations-proof.mjs";
12
+ import { pruneReleases } from "../../../release/prune.mjs";
13
+ import { listReleases, releaseStatus } from "../../../release/report.mjs";
14
+ import { shellRunner } from "../../../release/runner.mjs";
15
+ import { verifyRelease } from "../../../release/verify.mjs";
16
+
17
+ //#region ../../packages/core/src/cli/commands/release/release-command.action.ts
18
+ const ACTIONS = [
19
+ "candidate",
20
+ "gate",
21
+ "migrations",
22
+ "stage",
23
+ "activate",
24
+ "verify",
25
+ "rollback",
26
+ "list",
27
+ "status",
28
+ "prune"
29
+ ];
30
+ function stringOption(data, name) {
31
+ const value = data.options[name];
32
+ if (value === void 0) return void 0;
33
+ if (typeof value !== "string" || value.length === 0) throw new Error(`--${name} requires a value.`);
34
+ return value;
35
+ }
36
+ function flag(data, name) {
37
+ return data.options[name] === true;
38
+ }
39
+ function allowOptions(data, action, allowed) {
40
+ for (const key of Object.keys(data.options)) if (key !== "json" && !allowed.includes(key)) throw new Error(`Unsupported option for release ${action}: --${key}`);
41
+ }
42
+ /**
43
+ * Run one `warlock release` action and describe the result. Pure of process
44
+ * state (no printing, no exit), so it can be tested with temp directories and
45
+ * a fake runner.
46
+ */
47
+ async function runReleaseAction(data, dependencies) {
48
+ const [action, id, ...extra] = data.args;
49
+ const { context } = dependencies;
50
+ if (!ACTIONS.includes(action) || extra.length > 0) throw new Error(`Usage: warlock release <${ACTIONS.join(" | ")}> [id]`);
51
+ switch (action) {
52
+ case "candidate": {
53
+ allowOptions(data, "candidate", ["commit", "allowDirty"]);
54
+ const manifest = await createCandidate(context, {
55
+ commit: stringOption(data, "commit"),
56
+ allowDirty: flag(data, "allowDirty")
57
+ });
58
+ return {
59
+ ok: true,
60
+ data: manifest,
61
+ lines: [`Created candidate ${manifest.id} (commit ${manifest.commit.slice(0, 7)}).`]
62
+ };
63
+ }
64
+ case "gate": {
65
+ allowOptions(data, "gate", []);
66
+ const target = await resolveRelease(context, id);
67
+ const manifest = await runGate(context, target.id);
68
+ const results = manifest.gate?.results ?? [];
69
+ const lines = results.map((result) => `${result.status === "passed" ? "PASS" : "FAIL"} ${result.name} (${result.durationMs}ms)`);
70
+ const failed = results.find((result) => result.status === "failed");
71
+ if (failed?.output) lines.push("", failed.output.trim());
72
+ lines.push(manifest.gate?.passed ? `Gate passed for ${manifest.id}.` : "Gate failed.");
73
+ return {
74
+ ok: manifest.gate?.passed === true,
75
+ data: manifest,
76
+ lines
77
+ };
78
+ }
79
+ case "migrations": {
80
+ allowOptions(data, "migrations", ["prove", "target"]);
81
+ const targetName = stringOption(data, "target") ?? null;
82
+ const provider = await dependencies.openMigrations(targetName);
83
+ if (!flag(data, "prove")) {
84
+ const plan = await planMigrationProof(context, provider, targetName);
85
+ return {
86
+ ok: true,
87
+ data: plan,
88
+ lines: [plan.pending.length === 0 ? "Nothing is pending." : `Pending, in order: ${plan.pending.join(", ")}.`, "Pass --prove to run them and record the proof."]
89
+ };
90
+ }
91
+ const release = await resolveRelease(context, id);
92
+ const proof = await proveMigrations(context, provider, {
93
+ target: targetName,
94
+ releaseId: release.id
95
+ });
96
+ const lines = [
97
+ `Applied ${proof.applied.length} migration(s): ${proof.applied.join(", ") || "none"}.`,
98
+ `Tables added: ${proof.tablesAdded.join(", ") || "none"}; removed: ${proof.tablesRemoved.join(", ") || "none"}.`,
99
+ ...proof.problems.map((problem) => `PROBLEM ${problem}`),
100
+ proof.passed ? "Migration proof passed." : "Migration proof failed."
101
+ ];
102
+ return {
103
+ ok: proof.passed,
104
+ data: proof,
105
+ lines
106
+ };
107
+ }
108
+ case "stage": {
109
+ allowOptions(data, "stage", []);
110
+ const target = await resolveRelease(context, id);
111
+ const manifest = await stageRelease(context, target.id);
112
+ return {
113
+ ok: true,
114
+ data: manifest,
115
+ lines: [`Staged ${manifest.id} (${manifest.staged?.files} files).`]
116
+ };
117
+ }
118
+ case "activate": {
119
+ allowOptions(data, "activate", [
120
+ "preflight",
121
+ "allowSame",
122
+ "dryRun"
123
+ ]);
124
+ const target = await resolveRelease(context, id);
125
+ const result = await activateRelease(context, target.id, {
126
+ preflightOnly: flag(data, "preflight"),
127
+ dryRun: flag(data, "dryRun"),
128
+ allowSame: flag(data, "allowSame")
129
+ });
130
+ const lines = [...result.checks.map((check) => `${check.ok ? "PASS" : "FAIL"} ${check.name}: ${check.detail}`), result.message];
131
+ if (result.restartOutput) lines.push("", result.restartOutput.trim());
132
+ return {
133
+ ok: result.ok,
134
+ data: result,
135
+ lines
136
+ };
137
+ }
138
+ case "verify": {
139
+ allowOptions(data, "verify", []);
140
+ const result = await verifyRelease(context);
141
+ return {
142
+ ok: result.ok,
143
+ data: result,
144
+ lines: [result.ok ? `Verified ${result.id} after ${result.attempts} attempt(s).` : `Verification of ${result.id} failed after ${result.attempts} attempt(s): ${result.reason}`]
145
+ };
146
+ }
147
+ case "rollback": {
148
+ allowOptions(data, "rollback", ["dryRun"]);
149
+ const result = await rollbackRelease(context, { dryRun: flag(data, "dryRun") });
150
+ const lines = [result.message];
151
+ if (result.restartOutput) lines.push("", result.restartOutput.trim());
152
+ if (result.ok && !result.dryRun) lines.push("Migrations were not rolled back. Run `warlock migrate --rollback` yourself if needed.");
153
+ return {
154
+ ok: result.ok,
155
+ data: result,
156
+ lines
157
+ };
158
+ }
159
+ case "list": {
160
+ allowOptions(data, "list", []);
161
+ const listing = await listReleases(context);
162
+ return {
163
+ ok: true,
164
+ data: listing,
165
+ lines: listing.releases.length === 0 ? ["No releases yet."] : listing.releases.map((release) => {
166
+ const marks = [release.id === listing.current ? "current" : "", release.id === listing.previous ? "previous" : ""].filter(Boolean);
167
+ return `${release.id} ${release.status} ${release.createdAt}${marks.length > 0 ? ` [${marks.join(", ")}]` : ""}`;
168
+ })
169
+ };
170
+ }
171
+ case "status": {
172
+ allowOptions(data, "status", []);
173
+ const report = await releaseStatus(context);
174
+ const lines = report.active ? [`Active: ${report.active.id} (version ${report.active.version}, commit ${report.active.commit.slice(0, 7)})`, `Previous: ${report.previous ?? "none"}`] : ["No active release."];
175
+ for (const entry of report.log) lines.push(`${entry.at} ${entry.event} ${entry.release ?? "-"} ${entry.ok ? "ok" : "failed"}`);
176
+ return {
177
+ ok: true,
178
+ data: report,
179
+ lines
180
+ };
181
+ }
182
+ case "prune": {
183
+ allowOptions(data, "prune", ["keep", "dryRun"]);
184
+ const keep = stringOption(data, "keep");
185
+ if (keep === void 0) throw new Error("release prune needs --keep <n>.");
186
+ const result = await pruneReleases(context, {
187
+ keep: Number(keep),
188
+ dryRun: flag(data, "dryRun")
189
+ });
190
+ return {
191
+ ok: true,
192
+ data: result,
193
+ lines: [`${result.dryRun ? "Would remove" : "Removed"}: ${result.removed.join(", ") || "nothing"}.`]
194
+ };
195
+ }
196
+ }
197
+ }
198
+ /** Start the database the way `warlock migrate` does, then wrap cascade. */
199
+ async function openCascadeMigrations(target) {
200
+ const { createCascadeMigrationProvider } = await import("../../../release/cascade-migration-provider.mjs");
201
+ if (target !== null) {
202
+ const entry = (warlockConfigManager.get("release")?.migrations?.targets ?? {})[target];
203
+ if (!entry) throw new Error(`release.migrations.targets has no "${target}" entry.`);
204
+ Object.assign(process.env, entry.env ?? {});
205
+ }
206
+ await loadConfigFiles(["database", "log"]);
207
+ await connectorsManager.start(["database", "logger"]);
208
+ return createCascadeMigrationProvider();
209
+ }
210
+ async function releaseCommandAction(data) {
211
+ const context = {
212
+ root: process.cwd(),
213
+ config: resolveReleaseConfig(warlockConfigManager.get("release")),
214
+ runner: shellRunner,
215
+ now: () => /* @__PURE__ */ new Date(),
216
+ nodeVersion: process.version
217
+ };
218
+ let output;
219
+ try {
220
+ output = await runReleaseAction(data, {
221
+ context,
222
+ openMigrations: openCascadeMigrations
223
+ });
224
+ } catch (error) {
225
+ if (data.options.json === true) console.log(JSON.stringify({
226
+ ok: false,
227
+ error: error.message
228
+ }, null, 2));
229
+ else console.error(error.message);
230
+ return flushAndExit(1);
231
+ }
232
+ if (data.options.json === true) console.log(JSON.stringify({
233
+ ok: output.ok,
234
+ ...asObject(output.data)
235
+ }, null, 2));
236
+ else for (const line of output.lines) console.log(line);
237
+ if (!output.ok) return flushAndExit(1);
238
+ }
239
+ function asObject(value) {
240
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : { data: value };
241
+ }
242
+
243
+ //#endregion
244
+ export { releaseCommandAction, runReleaseAction };
245
+ //# sourceMappingURL=release-command.action.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"release-command.action.mjs","names":[],"sources":["../../../../../../../../../../packages/core/src/cli/commands/release/release-command.action.ts"],"sourcesContent":["import type { CommandActionData } from \"../../../commands/types\";\nimport { loadConfigFiles } from \"../../../config/load-config-files\";\nimport { connectorsManager } from \"../../../connectors/connectors-manager\";\nimport { activateRelease, rollbackRelease } from \"../../../release/activate\";\nimport { createCandidate } from \"../../../release/candidate\";\nimport { resolveReleaseConfig } from \"../../../release/config\";\nimport { runGate } from \"../../../release/gate\";\nimport {\n planMigrationProof,\n proveMigrations,\n type MigrationProofProvider,\n} from \"../../../release/migrations-proof\";\nimport { pruneReleases } from \"../../../release/prune\";\nimport { listReleases, releaseStatus } from \"../../../release/report\";\nimport { shellRunner } from \"../../../release/runner\";\nimport { stageRelease } from \"../../../release/stage\";\nimport { resolveRelease, type ReleaseContext } from \"../../../release/store\";\nimport { verifyRelease } from \"../../../release/verify\";\nimport { warlockConfigManager } from \"../../../warlock-config/warlock-config.manager\";\nimport { flushAndExit } from \"../../flush-and-exit\";\n\nconst ACTIONS = [\n \"candidate\",\n \"gate\",\n \"migrations\",\n \"stage\",\n \"activate\",\n \"verify\",\n \"rollback\",\n \"list\",\n \"status\",\n \"prune\",\n] as const;\n\ntype ReleaseAction = (typeof ACTIONS)[number];\n\n/** What an action produced: a JSON-able payload, text lines and the verdict. */\nexport type ReleaseActionOutput = {\n ok: boolean;\n data: unknown;\n lines: string[];\n};\n\nexport type ReleaseActionDependencies = {\n context: ReleaseContext;\n /** Starts the database and returns the provider; only `migrations` calls it. */\n openMigrations: (target: string | null) => Promise<MigrationProofProvider>;\n};\n\nfunction stringOption(data: CommandActionData, name: string): string | undefined {\n const value = data.options[name];\n\n if (value === undefined) return undefined;\n if (typeof value !== \"string\" || value.length === 0) {\n throw new Error(`--${name} requires a value.`);\n }\n\n return value;\n}\n\nfunction flag(data: CommandActionData, name: string): boolean {\n return data.options[name] === true;\n}\n\nfunction allowOptions(data: CommandActionData, action: ReleaseAction, allowed: string[]): void {\n for (const key of Object.keys(data.options)) {\n if (key !== \"json\" && !allowed.includes(key)) {\n throw new Error(`Unsupported option for release ${action}: --${key}`);\n }\n }\n}\n\n/**\n * Run one `warlock release` action and describe the result. Pure of process\n * state (no printing, no exit), so it can be tested with temp directories and\n * a fake runner.\n */\nexport async function runReleaseAction(\n data: CommandActionData,\n dependencies: ReleaseActionDependencies,\n): Promise<ReleaseActionOutput> {\n const [action, id, ...extra] = data.args;\n const { context } = dependencies;\n\n if (!ACTIONS.includes(action as ReleaseAction) || extra.length > 0) {\n throw new Error(`Usage: warlock release <${ACTIONS.join(\" | \")}> [id]`);\n }\n\n switch (action as ReleaseAction) {\n case \"candidate\": {\n allowOptions(data, \"candidate\", [\"commit\", \"allowDirty\"]);\n const manifest = await createCandidate(context, {\n commit: stringOption(data, \"commit\"),\n allowDirty: flag(data, \"allowDirty\"),\n });\n\n return {\n ok: true,\n data: manifest,\n lines: [`Created candidate ${manifest.id} (commit ${manifest.commit.slice(0, 7)}).`],\n };\n }\n\n case \"gate\": {\n allowOptions(data, \"gate\", []);\n const target = await resolveRelease(context, id);\n const manifest = await runGate(context, target.id);\n const results = manifest.gate?.results ?? [];\n const lines = results.map(\n (result) =>\n `${result.status === \"passed\" ? \"PASS\" : \"FAIL\"} ${result.name} (${result.durationMs}ms)`,\n );\n const failed = results.find((result) => result.status === \"failed\");\n\n if (failed?.output) lines.push(\"\", failed.output.trim());\n lines.push(manifest.gate?.passed ? `Gate passed for ${manifest.id}.` : \"Gate failed.\");\n\n return { ok: manifest.gate?.passed === true, data: manifest, lines };\n }\n\n case \"migrations\": {\n allowOptions(data, \"migrations\", [\"prove\", \"target\"]);\n const targetName = stringOption(data, \"target\") ?? null;\n const provider = await dependencies.openMigrations(targetName);\n\n if (!flag(data, \"prove\")) {\n const plan = await planMigrationProof(context, provider, targetName);\n\n return {\n ok: true,\n data: plan,\n lines: [\n plan.pending.length === 0\n ? \"Nothing is pending.\"\n : `Pending, in order: ${plan.pending.join(\", \")}.`,\n \"Pass --prove to run them and record the proof.\",\n ],\n };\n }\n\n const release = await resolveRelease(context, id);\n const proof = await proveMigrations(context, provider, {\n target: targetName,\n releaseId: release.id,\n });\n const lines = [\n `Applied ${proof.applied.length} migration(s): ${proof.applied.join(\", \") || \"none\"}.`,\n `Tables added: ${proof.tablesAdded.join(\", \") || \"none\"}; removed: ${\n proof.tablesRemoved.join(\", \") || \"none\"\n }.`,\n ...proof.problems.map((problem) => `PROBLEM ${problem}`),\n proof.passed ? \"Migration proof passed.\" : \"Migration proof failed.\",\n ];\n\n return { ok: proof.passed, data: proof, lines };\n }\n\n case \"stage\": {\n allowOptions(data, \"stage\", []);\n const target = await resolveRelease(context, id);\n const manifest = await stageRelease(context, target.id);\n\n return {\n ok: true,\n data: manifest,\n lines: [`Staged ${manifest.id} (${manifest.staged?.files} files).`],\n };\n }\n\n case \"activate\": {\n allowOptions(data, \"activate\", [\"preflight\", \"allowSame\", \"dryRun\"]);\n const target = await resolveRelease(context, id);\n const result = await activateRelease(context, target.id, {\n preflightOnly: flag(data, \"preflight\"),\n dryRun: flag(data, \"dryRun\"),\n allowSame: flag(data, \"allowSame\"),\n });\n const lines = [\n ...result.checks.map(\n (check) => `${check.ok ? \"PASS\" : \"FAIL\"} ${check.name}: ${check.detail}`,\n ),\n result.message,\n ];\n\n if (result.restartOutput) lines.push(\"\", result.restartOutput.trim());\n\n return { ok: result.ok, data: result, lines };\n }\n\n case \"verify\": {\n allowOptions(data, \"verify\", []);\n const result = await verifyRelease(context);\n\n return {\n ok: result.ok,\n data: result,\n lines: [\n result.ok\n ? `Verified ${result.id} after ${result.attempts} attempt(s).`\n : `Verification of ${result.id} failed after ${result.attempts} attempt(s): ${result.reason}`,\n ],\n };\n }\n\n case \"rollback\": {\n allowOptions(data, \"rollback\", [\"dryRun\"]);\n const result = await rollbackRelease(context, { dryRun: flag(data, \"dryRun\") });\n const lines = [result.message];\n\n if (result.restartOutput) lines.push(\"\", result.restartOutput.trim());\n if (result.ok && !result.dryRun) {\n lines.push(\n \"Migrations were not rolled back. Run `warlock migrate --rollback` yourself if needed.\",\n );\n }\n\n return { ok: result.ok, data: result, lines };\n }\n\n case \"list\": {\n allowOptions(data, \"list\", []);\n const listing = await listReleases(context);\n\n return {\n ok: true,\n data: listing,\n lines:\n listing.releases.length === 0\n ? [\"No releases yet.\"]\n : listing.releases.map((release) => {\n const marks = [\n release.id === listing.current ? \"current\" : \"\",\n release.id === listing.previous ? \"previous\" : \"\",\n ].filter(Boolean);\n\n return `${release.id} ${release.status} ${release.createdAt}${\n marks.length > 0 ? ` [${marks.join(\", \")}]` : \"\"\n }`;\n }),\n };\n }\n\n case \"status\": {\n allowOptions(data, \"status\", []);\n const report = await releaseStatus(context);\n const lines = report.active\n ? [\n `Active: ${report.active.id} (version ${report.active.version}, commit ${report.active.commit.slice(0, 7)})`,\n `Previous: ${report.previous ?? \"none\"}`,\n ]\n : [\"No active release.\"];\n\n for (const entry of report.log) {\n lines.push(\n `${entry.at} ${entry.event} ${entry.release ?? \"-\"} ${entry.ok ? \"ok\" : \"failed\"}`,\n );\n }\n\n return { ok: true, data: report, lines };\n }\n\n case \"prune\": {\n allowOptions(data, \"prune\", [\"keep\", \"dryRun\"]);\n const keep = stringOption(data, \"keep\");\n\n if (keep === undefined) throw new Error(\"release prune needs --keep <n>.\");\n\n const result = await pruneReleases(context, {\n keep: Number(keep),\n dryRun: flag(data, \"dryRun\"),\n });\n\n return {\n ok: true,\n data: result,\n lines: [\n `${result.dryRun ? \"Would remove\" : \"Removed\"}: ${result.removed.join(\", \") || \"nothing\"}.`,\n ],\n };\n }\n }\n}\n\n/** Start the database the way `warlock migrate` does, then wrap cascade. */\nasync function openCascadeMigrations(target: string | null): Promise<MigrationProofProvider> {\n const { createCascadeMigrationProvider } =\n await import(\"../../../release/cascade-migration-provider\");\n\n if (target !== null) {\n const targets = warlockConfigManager.get(\"release\")?.migrations?.targets ?? {};\n const entry = targets[target];\n\n if (!entry) throw new Error(`release.migrations.targets has no \"${target}\" entry.`);\n\n // Applied before the database config is read, so it reaches `env()` calls.\n Object.assign(process.env, entry.env ?? {});\n }\n\n await loadConfigFiles([\"database\", \"log\"]);\n await connectorsManager.start([\"database\", \"logger\"]);\n\n return createCascadeMigrationProvider();\n}\n\nexport async function releaseCommandAction(data: CommandActionData): Promise<void> {\n const context: ReleaseContext = {\n root: process.cwd(),\n config: resolveReleaseConfig(warlockConfigManager.get(\"release\")),\n runner: shellRunner,\n now: () => new Date(),\n nodeVersion: process.version,\n };\n\n let output: ReleaseActionOutput;\n\n try {\n output = await runReleaseAction(data, { context, openMigrations: openCascadeMigrations });\n } catch (error) {\n if (data.options.json === true) {\n console.log(JSON.stringify({ ok: false, error: (error as Error).message }, null, 2));\n } else {\n console.error((error as Error).message);\n }\n\n return flushAndExit(1);\n }\n\n if (data.options.json === true) {\n console.log(JSON.stringify({ ok: output.ok, ...asObject(output.data) }, null, 2));\n } else {\n for (const line of output.lines) console.log(line);\n }\n\n if (!output.ok) return flushAndExit(1);\n}\n\nfunction asObject(value: unknown): Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : { data: value };\n}\n"],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ import { command } from "../../commands/cli-command.mjs";
2
+ import { releaseCommandAction } from "./release/release-command.action.mjs";
3
+
4
+ //#region ../../packages/core/src/cli/commands/release.command.ts
5
+ /**
6
+ * `warlock release <action> [id]` — the deploy pipeline for an application:
7
+ * candidate, gate, migrations, stage, activate, verify, rollback, list,
8
+ * status and prune. Reads the `release` section of `warlock.config.ts`.
9
+ */
10
+ const releaseCommand = command({
11
+ name: "release <action> [id]",
12
+ description: "Release pipeline: candidate, gate, migrations, stage, activate, verify, rollback, list, status, prune",
13
+ preload: { warlockConfig: true },
14
+ action: releaseCommandAction,
15
+ options: [
16
+ {
17
+ text: "--commit",
18
+ type: "string",
19
+ description: "candidate: commit to freeze (default HEAD)"
20
+ },
21
+ {
22
+ text: "--allow-dirty",
23
+ type: "boolean",
24
+ description: "candidate: freeze even with uncommitted changes in the working tree"
25
+ },
26
+ {
27
+ text: "--prove",
28
+ type: "boolean",
29
+ description: "migrations: really run the pending migrations and record the proof (without it, only the plan is shown)"
30
+ },
31
+ {
32
+ text: "--target",
33
+ type: "string",
34
+ description: "migrations: name of a release.migrations.targets entry to apply to the env"
35
+ },
36
+ {
37
+ text: "--preflight",
38
+ type: "boolean",
39
+ description: "activate: run the preflight checks only"
40
+ },
41
+ {
42
+ text: "--allow-same",
43
+ type: "boolean",
44
+ description: "activate: accept a version equal to the active one"
45
+ },
46
+ {
47
+ text: "--dry-run",
48
+ type: "boolean",
49
+ description: "activate, rollback, prune: show what would change without changing it"
50
+ },
51
+ {
52
+ text: "--keep",
53
+ type: "string",
54
+ description: "prune: number of newest releases to keep"
55
+ },
56
+ {
57
+ text: "--json, -j",
58
+ type: "boolean",
59
+ description: "Print the result as JSON"
60
+ }
61
+ ]
62
+ });
63
+
64
+ //#endregion
65
+ export { releaseCommand };
66
+ //# sourceMappingURL=release.command.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"release.command.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/cli/commands/release.command.ts"],"sourcesContent":["import { command } from \"../../commands/cli-command\";\nimport { releaseCommandAction } from \"./release/release-command.action\";\n\n/**\n * `warlock release <action> [id]` — the deploy pipeline for an application:\n * candidate, gate, migrations, stage, activate, verify, rollback, list,\n * status and prune. Reads the `release` section of `warlock.config.ts`.\n */\nexport const releaseCommand = command({\n name: \"release <action> [id]\",\n description:\n \"Release pipeline: candidate, gate, migrations, stage, activate, verify, rollback, list, status, prune\",\n // Config only. The migrations action starts the database itself, so the\n // other actions never connect to anything.\n preload: { warlockConfig: true },\n action: releaseCommandAction,\n options: [\n {\n text: \"--commit\",\n type: \"string\",\n description: \"candidate: commit to freeze (default HEAD)\",\n },\n {\n text: \"--allow-dirty\",\n type: \"boolean\",\n description: \"candidate: freeze even with uncommitted changes in the working tree\",\n },\n {\n text: \"--prove\",\n type: \"boolean\",\n description:\n \"migrations: really run the pending migrations and record the proof (without it, only the plan is shown)\",\n },\n {\n text: \"--target\",\n type: \"string\",\n description: \"migrations: name of a release.migrations.targets entry to apply to the env\",\n },\n {\n text: \"--preflight\",\n type: \"boolean\",\n description: \"activate: run the preflight checks only\",\n },\n {\n text: \"--allow-same\",\n type: \"boolean\",\n description: \"activate: accept a version equal to the active one\",\n },\n {\n text: \"--dry-run\",\n type: \"boolean\",\n description: \"activate, rollback, prune: show what would change without changing it\",\n },\n {\n text: \"--keep\",\n type: \"string\",\n description: \"prune: number of newest releases to keep\",\n },\n {\n text: \"--json, -j\",\n type: \"boolean\",\n description: \"Print the result as JSON\",\n },\n ],\n});\n"],"mappings":";;;;;;;;;AAQA,MAAa,iBAAiB,QAAQ;CACpC,MAAM;CACN,aACE;CAGF,SAAS,EAAE,eAAe,KAAK;CAC/B,QAAQ;CACR,SAAS;EACP;GACE,MAAM;GACN,MAAM;GACN,aAAa;EACf;EACA;GACE,MAAM;GACN,MAAM;GACN,aAAa;EACf;EACA;GACE,MAAM;GACN,MAAM;GACN,aACE;EACJ;EACA;GACE,MAAM;GACN,MAAM;GACN,aAAa;EACf;EACA;GACE,MAAM;GACN,MAAM;GACN,aAAa;EACf;EACA;GACE,MAAM;GACN,MAAM;GACN,aAAa;EACf;EACA;GACE,MAAM;GACN,MAAM;GACN,aAAa;EACf;EACA;GACE,MAAM;GACN,MAAM;GACN,aAAa;EACf;EACA;GACE,MAAM;GACN,MAAM;GACN,aAAa;EACf;CACF;AACF,CAAC"}
@@ -18,6 +18,7 @@ import { migrateCommand } from "./commands/migrate.command.mjs";
18
18
  import { openApiGeneratorCommand } from "./commands/openapi-generator.command.mjs";
19
19
  import { postmanGeneratorCommand } from "./commands/postman-generator.command.mjs";
20
20
  import { pm2Command } from "./commands/pm2.command.mjs";
21
+ import { releaseCommand } from "./commands/release.command.mjs";
21
22
  import { routesCommand, routesDiffCommand } from "./commands/routes.command.mjs";
22
23
  import { seedCommand } from "./commands/seed.command.mjs";
23
24
  import { startProductionCommand } from "./commands/start-production.command.mjs";
@@ -53,6 +54,7 @@ const frameworkCommands = [
53
54
  buildCommand,
54
55
  startProductionCommand,
55
56
  pm2Command,
57
+ releaseCommand,
56
58
  migrateCommand,
57
59
  seedCommand,
58
60
  createDatabaseCommand,
@@ -1 +1 @@
1
- {"version":3,"file":"framework-cli-commands.mjs","names":[],"sources":["../../../../../../../../packages/core/src/cli/framework-cli-commands.ts"],"sourcesContent":["import { aiCommand } from \"./commands/ai.command\";\nimport { addCommand } from \"./commands/add.command\";\nimport { agentsMdCommand } from \"./commands/agents-md.command\";\nimport { boardCommand } from \"./commands/board.command\";\nimport { buildCommand } from \"./commands/build.command\";\nimport { checkCommand } from \"./commands/check.command\";\nimport { createDatabaseCommand } from \"./commands/create-database.command\";\nimport { devServerCommand } from \"./commands/dev-server.command\";\nimport { e2eCommand } from \"./commands/e2e.command\";\nimport { doctorCommand } from \"./commands/doctor.command\";\nimport { dropTablesCommand } from \"./commands/drop-tables.command\";\nimport { explainCommand } from \"./commands/explain.command\";\nimport {\n generateCommand,\n generateControllerCommand,\n generateEndpointCommand,\n generateMigrationCommand,\n generateModelCommand,\n generateModuleCommand,\n generateRepositoryCommand,\n generateResourceCommand,\n generateServiceCommand,\n generateUseCaseCommand,\n makeCommand,\n} from \"./commands/generate/generate.command\";\nimport { inspectCommand } from \"./commands/inspect.command\";\nimport { issueCommand } from \"./commands/issue.command\";\nimport { mcpCommand } from \"./commands/mcp.command\";\nimport { migrateCommand } from \"./commands/migrate.command\";\nimport { openApiGeneratorCommand } from \"./commands/openapi-generator.command\";\nimport { postmanGeneratorCommand } from \"./commands/postman-generator.command\";\nimport { pm2Command } from \"./commands/pm2.command\";\nimport { routesCommand, routesDiffCommand } from \"./commands/routes.command\";\nimport { seedCommand } from \"./commands/seed.command\";\nimport { startProductionCommand } from \"./commands/start-production.command\";\nimport { traceCommand } from \"./commands/trace.command\";\nimport { storagePutCommand } from \"./commands/storage-put.command\";\nimport { typecheckCommand } from \"./commands/typecheck.command\";\nimport { testCommand } from \"./commands/test.command\";\nimport { typingsGeneratorCommand } from \"./commands/typings-generator.command\";\nimport { updateCommand } from \"./commands/update.command\";\nimport { upgradeCommand } from \"./commands/upgrade.command\";\n\nexport const frameworkCommands = [\n // development commands\n devServerCommand,\n typingsGeneratorCommand,\n openApiGeneratorCommand,\n postmanGeneratorCommand,\n\n // testing commands\n testCommand,\n e2eCommand,\n\n // diagnostics commands\n aiCommand,\n doctorCommand,\n routesCommand,\n routesDiffCommand,\n inspectCommand,\n explainCommand,\n issueCommand,\n checkCommand,\n typecheckCommand,\n\n // agent tooling commands\n mcpCommand,\n traceCommand,\n agentsMdCommand,\n boardCommand,\n\n // production commands\n buildCommand,\n startProductionCommand,\n pm2Command,\n\n // database commands\n migrateCommand,\n seedCommand,\n createDatabaseCommand,\n dropTablesCommand,\n\n // generation/installation commands\n addCommand,\n updateCommand,\n upgradeCommand,\n\n // scaffolding commands\n generateCommand,\n makeCommand,\n generateEndpointCommand,\n generateModuleCommand,\n generateControllerCommand,\n generateServiceCommand,\n generateUseCaseCommand,\n generateModelCommand,\n generateRepositoryCommand,\n generateResourceCommand,\n generateMigrationCommand,\n\n // storage commands\n storagePutCommand,\n];\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,MAAa,oBAAoB;CAE/B;CACA;CACA;CACA;CAGA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CAGA;CACA;CACA;CAGA;CACA;CACA;CACA;CAGA;CACA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;AACF"}
1
+ {"version":3,"file":"framework-cli-commands.mjs","names":[],"sources":["../../../../../../../../packages/core/src/cli/framework-cli-commands.ts"],"sourcesContent":["import { aiCommand } from \"./commands/ai.command\";\nimport { addCommand } from \"./commands/add.command\";\nimport { agentsMdCommand } from \"./commands/agents-md.command\";\nimport { boardCommand } from \"./commands/board.command\";\nimport { buildCommand } from \"./commands/build.command\";\nimport { checkCommand } from \"./commands/check.command\";\nimport { createDatabaseCommand } from \"./commands/create-database.command\";\nimport { devServerCommand } from \"./commands/dev-server.command\";\nimport { e2eCommand } from \"./commands/e2e.command\";\nimport { doctorCommand } from \"./commands/doctor.command\";\nimport { dropTablesCommand } from \"./commands/drop-tables.command\";\nimport { explainCommand } from \"./commands/explain.command\";\nimport {\n generateCommand,\n generateControllerCommand,\n generateEndpointCommand,\n generateMigrationCommand,\n generateModelCommand,\n generateModuleCommand,\n generateRepositoryCommand,\n generateResourceCommand,\n generateServiceCommand,\n generateUseCaseCommand,\n makeCommand,\n} from \"./commands/generate/generate.command\";\nimport { inspectCommand } from \"./commands/inspect.command\";\nimport { issueCommand } from \"./commands/issue.command\";\nimport { mcpCommand } from \"./commands/mcp.command\";\nimport { migrateCommand } from \"./commands/migrate.command\";\nimport { openApiGeneratorCommand } from \"./commands/openapi-generator.command\";\nimport { postmanGeneratorCommand } from \"./commands/postman-generator.command\";\nimport { pm2Command } from \"./commands/pm2.command\";\nimport { releaseCommand } from \"./commands/release.command\";\nimport { routesCommand, routesDiffCommand } from \"./commands/routes.command\";\nimport { seedCommand } from \"./commands/seed.command\";\nimport { startProductionCommand } from \"./commands/start-production.command\";\nimport { traceCommand } from \"./commands/trace.command\";\nimport { storagePutCommand } from \"./commands/storage-put.command\";\nimport { typecheckCommand } from \"./commands/typecheck.command\";\nimport { testCommand } from \"./commands/test.command\";\nimport { typingsGeneratorCommand } from \"./commands/typings-generator.command\";\nimport { updateCommand } from \"./commands/update.command\";\nimport { upgradeCommand } from \"./commands/upgrade.command\";\n\nexport const frameworkCommands = [\n // development commands\n devServerCommand,\n typingsGeneratorCommand,\n openApiGeneratorCommand,\n postmanGeneratorCommand,\n\n // testing commands\n testCommand,\n e2eCommand,\n\n // diagnostics commands\n aiCommand,\n doctorCommand,\n routesCommand,\n routesDiffCommand,\n inspectCommand,\n explainCommand,\n issueCommand,\n checkCommand,\n typecheckCommand,\n\n // agent tooling commands\n mcpCommand,\n traceCommand,\n agentsMdCommand,\n boardCommand,\n\n // production commands\n buildCommand,\n startProductionCommand,\n pm2Command,\n releaseCommand,\n\n // database commands\n migrateCommand,\n seedCommand,\n createDatabaseCommand,\n dropTablesCommand,\n\n // generation/installation commands\n addCommand,\n updateCommand,\n upgradeCommand,\n\n // scaffolding commands\n generateCommand,\n makeCommand,\n generateEndpointCommand,\n generateModuleCommand,\n generateControllerCommand,\n generateServiceCommand,\n generateUseCaseCommand,\n generateModelCommand,\n generateRepositoryCommand,\n generateResourceCommand,\n generateMigrationCommand,\n\n // storage commands\n storagePutCommand,\n];\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,MAAa,oBAAoB;CAE/B;CACA;CACA;CACA;CAGA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CAGA;CACA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;AACF"}
@@ -1,3 +1,4 @@
1
+ import { environment } from "../utils/environment.mjs";
1
2
  import { Path } from "../utils/normalized-path.mjs";
2
3
  import { srcPath } from "../utils/paths.mjs";
3
4
  import "../utils/index.mjs";
@@ -7,7 +8,7 @@ import { filesOrchestrator } from "../dev-server/files-orchestrator.mjs";
7
8
  import { isTestFile } from "../utils/is-test-file.mjs";
8
9
  import { PENDING_EXIT_CODE, exitCodeFor } from "./pending-exit-code.mjs";
9
10
  import { resolvePendingMigrations } from "./resolve-pending-migrations.mjs";
10
- import { exportMigrationsSQL, freshMigrate, listExecutedMigrations, migrationRunner, rollbackMigrations, runMigrations } from "@warlock.js/cascade";
11
+ import { confirmRollback, exportMigrationsSQL, freshMigrate, listExecutedMigrations, migrationRunner, rollbackMigrations, runMigrations } from "@warlock.js/cascade";
11
12
  import path from "path";
12
13
  import { colors } from "@mongez/copper";
13
14
  import dayjs from "dayjs";
@@ -89,7 +90,7 @@ async function allMigrationsFilesAction() {
89
90
  * If rollback is provided, then run the migration runner against all files in reverse order
90
91
  */
91
92
  async function migrateAction(options) {
92
- const { fresh, path, rollback, all, list, pending, sql, pendingOnly, compact } = options.options;
93
+ const { fresh, path, rollback, all, list, pending, sql, pendingOnly, compact, yes, force } = options.options;
93
94
  if (list) return await listMigrationsAction();
94
95
  if (pending) return await pendingMigrationsAction();
95
96
  if (all) return await allMigrationsFilesAction();
@@ -100,6 +101,7 @@ async function migrateAction(options) {
100
101
  process.exit(1);
101
102
  }
102
103
  if (rollback) {
104
+ if (!await confirmDestructiveRollback(Boolean(yes || force))) return;
103
105
  await rollbackMigrations({ all: true });
104
106
  return;
105
107
  }
@@ -111,11 +113,31 @@ async function migrateAction(options) {
111
113
  return;
112
114
  }
113
115
  if (fresh) {
116
+ if (!await confirmDestructiveRollback(Boolean(yes || force))) return;
114
117
  await freshMigrate();
115
118
  return;
116
119
  }
117
120
  await runMigrations();
118
121
  }
122
+ /**
123
+ * Print what the rollback would remove and confirm it. Development proceeds
124
+ * after the summary; elsewhere it prompts, takes `--yes`/`--force`, and exits 1
125
+ * when there is no terminal to ask.
126
+ */
127
+ async function confirmDestructiveRollback(yes) {
128
+ try {
129
+ const proceed = await confirmRollback({
130
+ all: true,
131
+ development: environment() === "development",
132
+ yes
133
+ });
134
+ if (!proceed) console.log(colors.yellowBright("Rollback cancelled."));
135
+ return proceed;
136
+ } catch (error) {
137
+ console.error(colors.redBright(error instanceof Error ? error.message : String(error)));
138
+ process.exit(1);
139
+ }
140
+ }
119
141
  async function loadMigrationFile(absPath) {
120
142
  const relativePath = Path.toRelative(absPath);
121
143
  const loadedModule = await filesOrchestrator.load(relativePath);
@@ -1 +1 @@
1
- {"version":3,"file":"migrate-action.mjs","names":[],"sources":["../../../../../../../../packages/core/src/database/migrate-action.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport {\n exportMigrationsSQL,\n freshMigrate,\n listExecutedMigrations,\n type MigrationConstructor,\n migrationRunner,\n rollbackMigrations,\n runMigrations,\n} from \"@warlock.js/cascade\";\nimport dayjs from \"dayjs\";\nimport path from \"path\";\nimport type { CommandActionData } from \"../commands/types\";\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\nimport { Path } from \"../utils/normalized-path\";\nimport { getFilesFromDirectory } from \"../dev-server/utils\";\nimport { srcPath } from \"../utils\";\nimport { isTestFile } from \"../utils/is-test-file\";\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\nimport { exitCodeFor, PENDING_EXIT_CODE } from \"./pending-exit-code\";\nimport {\n resolvePendingMigrations,\n type PendingMigrationsResult,\n} from \"./resolve-pending-migrations\";\n\n/**\n * `--list` — print the migration state: what has run, then what will run next.\n *\n * The executed section is printed FIRST and unconditionally, before anything\n * touches disk or config. That ordering is the contract: reading migrations\n * from the table cannot fail because of a broken migration file, and `--list`\n * is the command an operator reaches for *during* an incident. The pending\n * section is best-effort on top of an answer that is already on screen.\n *\n * Always exits 0. It is a report; `--pending` is the gate.\n */\nasync function listMigrationsAction() {\n await printExecutedMigrations();\n\n const pending = await resolvePendingMigrations(loadAllMigrations);\n\n printPendingMigrations(pending);\n}\n\nasync function printExecutedMigrations() {\n const createdMigrations = await listExecutedMigrations();\n\n console.log(`\\nTotal Executed Migrations: ${colors.green(createdMigrations.length)}\\n`);\n\n if (createdMigrations.length === 0) {\n console.log(colors.gray(\" No migrations have been executed yet.\\n\"));\n return;\n }\n\n // Display each migration as a block\n for (const migration of createdMigrations) {\n const executedAt = dayjs(migration.executedAt).format(\"DD-MM-YYYY hh:mm:ss A\");\n const createdAt = migration.createdAt\n ? dayjs(migration.createdAt).format(\"DD-MM-YYYY hh:mm:ss A\")\n : null;\n\n // Migration name with checkmark icon\n console.log(` ${colors.green(\"✔\")} ${colors.cyanBright(migration.name)}`);\n\n // Executed date\n console.log(` ${colors.gray(\"Executed:\")} ${colors.white(executedAt)}`);\n\n // Created date (if available)\n if (createdAt) {\n console.log(` ${colors.gray(\"Created:\")} ${colors.yellow(createdAt)}`);\n }\n\n console.log(\"\"); // Empty line between migrations\n }\n}\n\n/**\n * Print the pending section shared by `--list` and `--pending`.\n *\n * An unavailable result prints an explicit line and NEVER a count. \"0 pending\"\n * over a tree we failed to read is the one output this whole feature exists to\n * prevent.\n */\nfunction printPendingMigrations(result: PendingMigrationsResult) {\n if (result.type === \"unavailable\") {\n console.log(\n `${colors.yellowBright(\"Pending: unavailable\")} — ${result.reason}\\n` +\n colors.gray(\" The executed list above is still accurate.\\n\"),\n );\n return;\n }\n\n const { migrations } = result;\n\n console.log(`Total Pending Migrations: ${colors.green(migrations.length)}\\n`);\n\n if (migrations.length === 0) {\n console.log(colors.gray(\" Nothing pending — the database is up to date.\\n\"));\n return;\n }\n\n // Numbered because the ORDER is the answer: this is what the next\n // `warlock migrate` will do, in the sequence it will do it.\n migrations.forEach((migration, index) => {\n console.log(` ${colors.gray(`${index + 1}.`)} ${colors.cyanBright(migration.name)}`);\n });\n\n console.log(\"\");\n}\n\n/**\n * `--pending` — the scriptable half. Same computation as `--list`'s pending\n * section, different exit contract: see {@link PENDING_EXIT_CODE}.\n */\nasync function pendingMigrationsAction() {\n const result = await resolvePendingMigrations(loadAllMigrations);\n\n printPendingMigrations(result);\n\n const exitCode = exitCodeFor(result);\n\n if (exitCode === PENDING_EXIT_CODE.unavailable) {\n console.error(\n colors.redBright(\"Could not determine the pending set — exiting 2 rather than reporting 0.\"),\n );\n }\n\n process.exit(exitCode);\n}\n\nasync function allMigrationsFilesAction() {\n // get all available migration files in the project\n const files = (await migrationFiles()).map((path) => Path.toRelative(path));\n console.log(`Total Migration Files: ${colors.green(files.length)}`);\n\n for (const file of files) {\n console.log(colors.yellowBright(file));\n }\n}\n\n/**\n * If path is provided, then run the migration runner against that file only\n * If fresh is provided, then rollback all migrations and run all migrations\n * If rollback is provided, then run the migration runner against all files in reverse order\n */\nexport async function migrateAction(options: CommandActionData) {\n const { fresh, path, rollback, all, list, pending, sql, pendingOnly, compact } = options.options;\n\n if (list) {\n return await listMigrationsAction();\n }\n\n if (pending) {\n return await pendingMigrationsAction();\n }\n\n if (all) {\n return await allMigrationsFilesAction();\n }\n\n if (path) {\n await loadMigrationFile(Path.toAbsolute(path as string));\n } else {\n await loadAllMigrations();\n }\n\n if (fresh && rollback) {\n console.log(colors.redBright(\"You can't use --fresh and --rollback together\"));\n process.exit(1);\n }\n\n if (rollback) {\n await rollbackMigrations({ all: true });\n return;\n }\n\n if (sql) {\n await exportMigrationsSQL({\n pendingOnly: pendingOnly as boolean,\n compact: compact as boolean,\n });\n return;\n }\n\n if (fresh) {\n await freshMigrate();\n return;\n }\n\n await runMigrations();\n}\n\nexport async function loadMigrationFile(absPath: string) {\n const relativePath = Path.toRelative(absPath);\n\n // MigrationConstructor, not `typeof Migration`: a migration file default-exports a\n // CONCRETE subclass, while Migration itself is abstract and so cannot satisfy a\n // `new () => ...` parameter. The annotation described the base class rather than\n // what is actually loaded, and that is what migrationRunner.register() rejected.\n const loadedModule = await filesOrchestrator.load<{ default: MigrationConstructor }>(\n relativePath,\n );\n\n if (!loadedModule?.default) {\n throw new Error(`${Path.toRelative(absPath)} must have a default export`);\n }\n\n const MigrationClass = loadedModule.default;\n\n if (!MigrationClass.migrationName) {\n MigrationClass.migrationName = path\n .basename(absPath)\n .split(\".\")[0]!\n .replace(\"-migration\", \"\")\n .replace(\"_migration\", \"\");\n }\n\n // Extract createdAt timestamp from filename if not already set\n // Expected format: MM-DD-YYYY_HH-MM-SS-name.migration.ts\n if (!MigrationClass.createdAt) {\n const filename = path.basename(absPath);\n const timestampMatch = filename.match(/^(\\d{2}-\\d{2}-\\d{4}_\\d{2}-\\d{2}-\\d{2})/);\n if (timestampMatch) {\n MigrationClass.createdAt = timestampMatch[1];\n }\n }\n\n migrationRunner.register(MigrationClass, \"app\");\n}\n\n/**\n *\n * @returns List of absolute paths to migration files\n */\nasync function migrationFiles() {\n const migrationFiles = await getFilesFromDirectory(srcPath(\"app\"), \"*/models/*/migrations/*\");\n const separateMigrationsFolderFIles = await getFilesFromDirectory(\n srcPath(\"app\"),\n \"*/migrations/*\",\n );\n\n const migrations = [...migrationFiles, ...separateMigrationsFolderFIles].filter(\n (file) => !isTestFile(file),\n );\n\n return migrations;\n}\n\nexport async function loadAllMigrations() {\n // Load config-registered migrations (from packages like @warlock.js/auth)\n const configMigrations = warlockConfigManager.get(\"database\")?.migrations || [];\n\n for (const MigrationClass of configMigrations) {\n // Use class name as migration name if not set\n if (!MigrationClass.migrationName) {\n MigrationClass.migrationName = MigrationClass.name;\n }\n migrationRunner.register(MigrationClass, \"package\");\n }\n\n // Always load file-based migrations from src/app, regardless of config\n const migrations = await migrationFiles();\n for (const migrationFile of migrations) {\n await loadMigrationFile(migrationFile);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"migrate-action.mjs","names":[],"sources":["../../../../../../../../packages/core/src/database/migrate-action.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport {\n confirmRollback,\n exportMigrationsSQL,\n freshMigrate,\n listExecutedMigrations,\n type MigrationConstructor,\n migrationRunner,\n rollbackMigrations,\n runMigrations,\n} from \"@warlock.js/cascade\";\nimport dayjs from \"dayjs\";\nimport path from \"path\";\nimport type { CommandActionData } from \"../commands/types\";\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\nimport { Path } from \"../utils/normalized-path\";\nimport { getFilesFromDirectory } from \"../dev-server/utils\";\nimport { srcPath } from \"../utils\";\nimport { isTestFile } from \"../utils/is-test-file\";\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\nimport { environment } from \"../utils/environment\";\nimport { exitCodeFor, PENDING_EXIT_CODE } from \"./pending-exit-code\";\nimport {\n resolvePendingMigrations,\n type PendingMigrationsResult,\n} from \"./resolve-pending-migrations\";\n\n/**\n * `--list` — print the migration state: what has run, then what will run next.\n *\n * The executed section is printed FIRST and unconditionally, before anything\n * touches disk or config. That ordering is the contract: reading migrations\n * from the table cannot fail because of a broken migration file, and `--list`\n * is the command an operator reaches for *during* an incident. The pending\n * section is best-effort on top of an answer that is already on screen.\n *\n * Always exits 0. It is a report; `--pending` is the gate.\n */\nasync function listMigrationsAction() {\n await printExecutedMigrations();\n\n const pending = await resolvePendingMigrations(loadAllMigrations);\n\n printPendingMigrations(pending);\n}\n\nasync function printExecutedMigrations() {\n const createdMigrations = await listExecutedMigrations();\n\n console.log(`\\nTotal Executed Migrations: ${colors.green(createdMigrations.length)}\\n`);\n\n if (createdMigrations.length === 0) {\n console.log(colors.gray(\" No migrations have been executed yet.\\n\"));\n return;\n }\n\n // Display each migration as a block\n for (const migration of createdMigrations) {\n const executedAt = dayjs(migration.executedAt).format(\"DD-MM-YYYY hh:mm:ss A\");\n const createdAt = migration.createdAt\n ? dayjs(migration.createdAt).format(\"DD-MM-YYYY hh:mm:ss A\")\n : null;\n\n // Migration name with checkmark icon\n console.log(` ${colors.green(\"✔\")} ${colors.cyanBright(migration.name)}`);\n\n // Executed date\n console.log(` ${colors.gray(\"Executed:\")} ${colors.white(executedAt)}`);\n\n // Created date (if available)\n if (createdAt) {\n console.log(` ${colors.gray(\"Created:\")} ${colors.yellow(createdAt)}`);\n }\n\n console.log(\"\"); // Empty line between migrations\n }\n}\n\n/**\n * Print the pending section shared by `--list` and `--pending`.\n *\n * An unavailable result prints an explicit line and NEVER a count. \"0 pending\"\n * over a tree we failed to read is the one output this whole feature exists to\n * prevent.\n */\nfunction printPendingMigrations(result: PendingMigrationsResult) {\n if (result.type === \"unavailable\") {\n console.log(\n `${colors.yellowBright(\"Pending: unavailable\")} — ${result.reason}\\n` +\n colors.gray(\" The executed list above is still accurate.\\n\"),\n );\n return;\n }\n\n const { migrations } = result;\n\n console.log(`Total Pending Migrations: ${colors.green(migrations.length)}\\n`);\n\n if (migrations.length === 0) {\n console.log(colors.gray(\" Nothing pending — the database is up to date.\\n\"));\n return;\n }\n\n // Numbered because the ORDER is the answer: this is what the next\n // `warlock migrate` will do, in the sequence it will do it.\n migrations.forEach((migration, index) => {\n console.log(` ${colors.gray(`${index + 1}.`)} ${colors.cyanBright(migration.name)}`);\n });\n\n console.log(\"\");\n}\n\n/**\n * `--pending` — the scriptable half. Same computation as `--list`'s pending\n * section, different exit contract: see {@link PENDING_EXIT_CODE}.\n */\nasync function pendingMigrationsAction() {\n const result = await resolvePendingMigrations(loadAllMigrations);\n\n printPendingMigrations(result);\n\n const exitCode = exitCodeFor(result);\n\n if (exitCode === PENDING_EXIT_CODE.unavailable) {\n console.error(\n colors.redBright(\"Could not determine the pending set — exiting 2 rather than reporting 0.\"),\n );\n }\n\n process.exit(exitCode);\n}\n\nasync function allMigrationsFilesAction() {\n // get all available migration files in the project\n const files = (await migrationFiles()).map((path) => Path.toRelative(path));\n console.log(`Total Migration Files: ${colors.green(files.length)}`);\n\n for (const file of files) {\n console.log(colors.yellowBright(file));\n }\n}\n\n/**\n * If path is provided, then run the migration runner against that file only\n * If fresh is provided, then rollback all migrations and run all migrations\n * If rollback is provided, then run the migration runner against all files in reverse order\n */\nexport async function migrateAction(options: CommandActionData) {\n const { fresh, path, rollback, all, list, pending, sql, pendingOnly, compact, yes, force } =\n options.options;\n\n if (list) {\n return await listMigrationsAction();\n }\n\n if (pending) {\n return await pendingMigrationsAction();\n }\n\n if (all) {\n return await allMigrationsFilesAction();\n }\n\n if (path) {\n await loadMigrationFile(Path.toAbsolute(path as string));\n } else {\n await loadAllMigrations();\n }\n\n if (fresh && rollback) {\n console.log(colors.redBright(\"You can't use --fresh and --rollback together\"));\n process.exit(1);\n }\n\n if (rollback) {\n if (!(await confirmDestructiveRollback(Boolean(yes || force)))) return;\n\n await rollbackMigrations({ all: true });\n return;\n }\n\n if (sql) {\n await exportMigrationsSQL({\n pendingOnly: pendingOnly as boolean,\n compact: compact as boolean,\n });\n return;\n }\n\n if (fresh) {\n if (!(await confirmDestructiveRollback(Boolean(yes || force)))) return;\n\n await freshMigrate();\n return;\n }\n\n await runMigrations();\n}\n\n/**\n * Print what the rollback would remove and confirm it. Development proceeds\n * after the summary; elsewhere it prompts, takes `--yes`/`--force`, and exits 1\n * when there is no terminal to ask.\n */\nasync function confirmDestructiveRollback(yes: boolean): Promise<boolean> {\n try {\n const proceed = await confirmRollback({\n all: true,\n development: environment() === \"development\",\n yes,\n });\n\n if (!proceed) console.log(colors.yellowBright(\"Rollback cancelled.\"));\n\n return proceed;\n } catch (error) {\n console.error(colors.redBright(error instanceof Error ? error.message : String(error)));\n process.exit(1);\n }\n}\n\nexport async function loadMigrationFile(absPath: string) {\n const relativePath = Path.toRelative(absPath);\n\n // MigrationConstructor, not `typeof Migration`: a migration file default-exports a\n // CONCRETE subclass, while Migration itself is abstract and so cannot satisfy a\n // `new () => ...` parameter. The annotation described the base class rather than\n // what is actually loaded, and that is what migrationRunner.register() rejected.\n const loadedModule = await filesOrchestrator.load<{ default: MigrationConstructor }>(\n relativePath,\n );\n\n if (!loadedModule?.default) {\n throw new Error(`${Path.toRelative(absPath)} must have a default export`);\n }\n\n const MigrationClass = loadedModule.default;\n\n if (!MigrationClass.migrationName) {\n MigrationClass.migrationName = path\n .basename(absPath)\n .split(\".\")[0]!\n .replace(\"-migration\", \"\")\n .replace(\"_migration\", \"\");\n }\n\n // Extract createdAt timestamp from filename if not already set\n // Expected format: MM-DD-YYYY_HH-MM-SS-name.migration.ts\n if (!MigrationClass.createdAt) {\n const filename = path.basename(absPath);\n const timestampMatch = filename.match(/^(\\d{2}-\\d{2}-\\d{4}_\\d{2}-\\d{2}-\\d{2})/);\n if (timestampMatch) {\n MigrationClass.createdAt = timestampMatch[1];\n }\n }\n\n migrationRunner.register(MigrationClass, \"app\");\n}\n\n/**\n *\n * @returns List of absolute paths to migration files\n */\nasync function migrationFiles() {\n const migrationFiles = await getFilesFromDirectory(srcPath(\"app\"), \"*/models/*/migrations/*\");\n const separateMigrationsFolderFIles = await getFilesFromDirectory(\n srcPath(\"app\"),\n \"*/migrations/*\",\n );\n\n const migrations = [...migrationFiles, ...separateMigrationsFolderFIles].filter(\n (file) => !isTestFile(file),\n );\n\n return migrations;\n}\n\nexport async function loadAllMigrations() {\n // Load config-registered migrations (from packages like @warlock.js/auth)\n const configMigrations = warlockConfigManager.get(\"database\")?.migrations || [];\n\n for (const MigrationClass of configMigrations) {\n // Use class name as migration name if not set\n if (!MigrationClass.migrationName) {\n MigrationClass.migrationName = MigrationClass.name;\n }\n migrationRunner.register(MigrationClass, \"package\");\n }\n\n // Always load file-based migrations from src/app, regardless of config\n const migrations = await migrationFiles();\n for (const migrationFile of migrations) {\n await loadMigrationFile(migrationFile);\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"load-hook.d.mts","names":[],"sources":["../../../../../../../../../packages/core/src/dev-server/loader/load-hook.ts"],"mappings":";KAMK;EACH;EACA,mBAAmB;;KAGhB;EACH;EACA,iBAAiB;EACjB;;KAGG,YAAY,aAAa,UAAU,gBAAgB,QAAQ;;;;;;YAOpD;;EAEV;;EAEA;;EAEA;;EAEA;;;;;;EAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAqDoB,KACpB,aACA,SAAS,aACT,UAAU,WACT,QAAQ"}
1
+ {"version":3,"file":"load-hook.d.mts","names":[],"sources":["../../../../../../../../../packages/core/src/dev-server/loader/load-hook.ts"],"mappings":";KAMK;EACH;EACA,mBAAmB;;KAGhB;EACH;EACA,iBAAiB;EACjB;;KAGG,YAAY,aAAa,UAAU,gBAAgB,QAAQ;;;;;;YAOpD;;EAEV;;EAEA;;EAEA;;EAEA;;;;;;EAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAsDoB,KACpB,aACA,SAAS,aACT,UAAU,WACT,QAAQ"}
@@ -7,6 +7,7 @@ import { transformSync } from "esbuild";
7
7
  //#region ../../packages/core/src/dev-server/loader/load-hook.ts
8
8
  const VERSION_QUERY = /\?v=\d+$/;
9
9
  const TS_FILE = /\.(ts|tsx|mts|cts)$/;
10
+ const STYLESHEET_FILE = /\.(css|scss|sass|less|styl)$/;
10
11
  let transpile;
11
12
  let cache;
12
13
  /**
@@ -50,6 +51,11 @@ function configureTranspile(init) {
50
51
  */
51
52
  async function load(url, context, nextLoad) {
52
53
  const cleanUrl = url.replace(VERSION_QUERY, "");
54
+ if (STYLESHEET_FILE.test(cleanUrl)) return {
55
+ format: "module",
56
+ source: "export default {};\n",
57
+ shortCircuit: true
58
+ };
53
59
  if (!TS_FILE.test(cleanUrl)) return nextLoad(url, context);
54
60
  const absolutePath = fileURLToPath(cleanUrl);
55
61
  const source = readFileSync(absolutePath, "utf8");