@warlock.js/core 4.2.10 → 4.3.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.
package/CHANGELOG.md CHANGED
@@ -6,12 +6,36 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
- - `lowerStage3Decorators()` — shared Vite/Vitest plugin that lowers TC39 Stage-3 decorators with esbuild before oxc / the SSR rewrite mangles them. Drop it first in a config's `plugins` so model-decorated files load under Vitest 4 / Vite 8.
9
+ ## 4.3.0 - 2026-06-21
10
+
11
+ ### Added
12
+
13
+ - `warlock update` — update every `@warlock.js/*` package in package.json to its latest version (operator preserved), then run the detected package manager's install
14
+ - dev-server update notice — `warlock dev` checks npm on start and prints a one-line notice when a newer `@warlock.js/core` is published
15
+ - `devServer.checkForUpdates` config flag (default `true`) to toggle the dev-server update notice
16
+ - `fetchLatestVersion()` and `isNewerVersion()` registry/version utilities
17
+
18
+ ### Fixed
19
+
20
+ - `warlock dev --skip-typings` and `--skip-health` long-form flags now work (were silently ignored)
21
+
22
+ ## 4.2.11
23
+
24
+ ### Added
25
+
26
+ - `lowerStage3Decorators()` — Vite/Vitest plugin that lowers TC39 Stage-3 decorators with esbuild before oxc / the SSR rewrite mangles them; drop it first in a config's `plugins` so model-decorated files load under Vitest 4 / Vite 8.
27
+ - `warlock add notifications` — installs `@warlock.js/notifications` (+ the `mail` feature), ejects `config/notifications.ts`, and scaffolds the app-owned `Notification` model + migration into `src/app/notifications/` (idempotent; async queue opt-in).
28
+ - Notifications connector — built-in connector (priority `8`, early phase) reads `config/notifications.ts` at boot and passes it to `setNotificationConfig`; lazy-imports `@warlock.js/notifications` (config-gated), so core keeps no hard dependency on it.
29
+
30
+ ### Changed
31
+
10
32
  - `warlock add test` now scaffolds a `vite.config.ts` that includes `lowerStage3Decorators()`, so a fresh project can test decorated models out of the box.
11
- - `warlock add test` `test`/`test:coverage` scripts now run one-shot (`vitest run`) instead of watch mode — safe for CI by default.
12
- - `startHttpTestServer` now starts early-phase connectors (database, cache, logger, …) **before** loading app modules, then late-phase connectors (http, socket) after — mirroring the dev/prod boot order. Fixes a `MissingDataSourceError` when a module's `main.ts` boot side-effect queries the DB at import time under the Vitest integration harness.
13
- - `warlock add notifications` — new feature in the `add` command: installs `@warlock.js/notifications` (and pulls the `mail` feature for the default mail channel), ejects `config/notifications.ts` (mail + in-app channels wired), and scaffolds the app-owned `Notification` model + a timestamped migration into `src/app/notifications/`. Re-running is idempotent (the model file is the sentinel — no duplicate migration). The async queue stays opt-in (commented in the config; enable with `warlock add herald` + `heraldQueue()`).
14
- - **Notifications connector** — a built-in connector (priority `8`, early phase) reads `config/notifications.ts` at boot and hands its default export to `setNotificationConfig`. `@warlock.js/notifications` is lazy-imported (gated on the config's presence), so core keeps no hard dependency on it — the same pattern as the herald connector. Config files stay declarative (`export default config`); the side-effect moves out of the config file.
33
+ - `warlock add test` `test` / `test:coverage` scripts now run one-shot (`vitest run`) instead of watch mode — CI-safe by default.
34
+ - Bumped `@mongez/reinforcements` to 3.3.0
35
+
36
+ ### Fixed
37
+
38
+ - `startHttpTestServer` now starts early-phase connectors (database, cache, logger, …) before loading app modules, then late-phase (http, socket) after — mirroring dev/prod boot order; fixes a `MissingDataSourceError` when a module's boot side-effect queries the DB at import under the Vitest integration harness.
15
39
 
16
40
  ## 4.2.5
17
41
 
@@ -1,5 +1,6 @@
1
1
  import { command } from "../cli-command.mjs";
2
2
  import { displayStartupBanner } from "../cli-commands.utils.mjs";
3
+ import { checkForFrameworkUpdate } from "../../dev-server/check-for-updates.mjs";
3
4
  import { startDevelopmentServer } from "../../dev-server/start-development-server.mjs";
4
5
 
5
6
  //#region ../@warlock.js/core/src/cli/commands/dev-server.command.ts
@@ -20,9 +21,10 @@ const devServerCommand = command({
20
21
  action: async (data) => {
21
22
  await startDevelopmentServer({
22
23
  fresh: Boolean(data.options.fresh),
23
- generateTypings: data.options["skip-typings"] ? false : void 0,
24
- healthCheckers: data.options["skip-health"] ? false : void 0
24
+ generateTypings: data.options.skipTypings ? false : void 0,
25
+ healthCheckers: data.options.skipHealth ? false : void 0
25
26
  });
27
+ checkForFrameworkUpdate();
26
28
  },
27
29
  options: [
28
30
  {
@@ -1 +1 @@
1
- {"version":3,"file":"dev-server.command.mjs","names":[],"sources":["../../../../../../../../@warlock.js/core/src/cli/commands/dev-server.command.ts"],"sourcesContent":["import { startDevelopmentServer } from \"../../dev-server/start-development-server\";\r\nimport { command } from \"../cli-command\";\r\nimport { displayStartupBanner } from \"../cli-commands.utils\";\r\n\r\nexport const devServerCommand = command({\r\n name: \"dev\",\r\n description: \"Start development server (HMR, type-gen, health checks)\",\r\n persistent: true,\r\n preload: {\r\n runtimeStrategy: \"development\",\r\n config: true, // load all config\r\n bootstrap: true,\r\n prestart: true, // load prestart file (if exists)\r\n // Only the Early lifecycle phase starts here; the Late phase\r\n // (http, socket) starts after app modules load — see\r\n // development-server.ts STEP 8.5.\r\n connectors: true,\r\n },\r\n preAction: async () => {\r\n await displayStartupBanner({ environment: \"development\" });\r\n },\r\n action: async (data) => {\r\n await startDevelopmentServer({\r\n fresh: Boolean(data.options.fresh),\r\n // Pass `false` only when the CLI flag is explicitly set; `undefined`\r\n // lets `warlock.config.ts > devServer.*` defaults apply.\r\n generateTypings: data.options[\"skip-typings\"] ? false : undefined,\r\n healthCheckers: data.options[\"skip-health\"] ? false : undefined,\r\n });\r\n },\r\n options: [\r\n {\r\n text: \"--fresh, -f\",\r\n description: \"Delete .warlock/manifest.json before start (force full re-parse from disk)\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--skip-typings, -st\",\r\n description: \"Skip background type generation for this run\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--skip-health, -sh\",\r\n description: \"Skip file health checkers for this run\",\r\n type: \"boolean\",\r\n },\r\n ],\r\n});\r\n"],"mappings":";;;;;AAIA,MAAa,mBAAmB,QAAQ;CACtC,MAAM;CACN,aAAa;CACb,YAAY;CACZ,SAAS;EACP,iBAAiB;EACjB,QAAQ;EACR,WAAW;EACX,UAAU;EAIV,YAAY;CACd;CACA,WAAW,YAAY;EACrB,MAAM,qBAAqB,EAAE,aAAa,cAAc,CAAC;CAC3D;CACA,QAAQ,OAAO,SAAS;EACtB,MAAM,uBAAuB;GAC3B,OAAO,QAAQ,KAAK,QAAQ,KAAK;GAGjC,iBAAiB,KAAK,QAAQ,kBAAkB,QAAQ;GACxD,gBAAgB,KAAK,QAAQ,iBAAiB,QAAQ;EACxD,CAAC;CACH;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;CACF;AACF,CAAC"}
1
+ {"version":3,"file":"dev-server.command.mjs","names":[],"sources":["../../../../../../../../@warlock.js/core/src/cli/commands/dev-server.command.ts"],"sourcesContent":["import { checkForFrameworkUpdate } from \"../../dev-server/check-for-updates\";\r\nimport { startDevelopmentServer } from \"../../dev-server/start-development-server\";\r\nimport { command } from \"../cli-command\";\r\nimport { displayStartupBanner } from \"../cli-commands.utils\";\r\n\r\nexport const devServerCommand = command({\r\n name: \"dev\",\r\n description: \"Start development server (HMR, type-gen, health checks)\",\r\n persistent: true,\r\n preload: {\r\n runtimeStrategy: \"development\",\r\n config: true, // load all config\r\n bootstrap: true,\r\n prestart: true, // load prestart file (if exists)\r\n // Only the Early lifecycle phase starts here; the Late phase\r\n // (http, socket) starts after app modules load — see\r\n // development-server.ts STEP 8.5.\r\n connectors: true,\r\n },\r\n preAction: async () => {\r\n await displayStartupBanner({ environment: \"development\" });\r\n },\r\n action: async (data) => {\r\n await startDevelopmentServer({\r\n fresh: Boolean(data.options.fresh),\r\n // Pass `false` only when the CLI flag is explicitly set; `undefined`\r\n // lets `warlock.config.ts > devServer.*` defaults apply.\r\n generateTypings: data.options.skipTypings ? false : undefined,\r\n healthCheckers: data.options.skipHealth ? false : undefined,\r\n });\r\n\r\n // Fire-and-forget: once the server is ready, surface a one-line notice if\r\n // a newer @warlock.js/core has been published. Fully self-guarded — it\r\n // never blocks startup nor breaks dev if the registry is unreachable.\r\n void checkForFrameworkUpdate();\r\n },\r\n options: [\r\n {\r\n text: \"--fresh, -f\",\r\n description: \"Delete .warlock/manifest.json before start (force full re-parse from disk)\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--skip-typings, -st\",\r\n description: \"Skip background type generation for this run\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--skip-health, -sh\",\r\n description: \"Skip file health checkers for this run\",\r\n type: \"boolean\",\r\n },\r\n ],\r\n});\r\n"],"mappings":";;;;;;AAKA,MAAa,mBAAmB,QAAQ;CACtC,MAAM;CACN,aAAa;CACb,YAAY;CACZ,SAAS;EACP,iBAAiB;EACjB,QAAQ;EACR,WAAW;EACX,UAAU;EAIV,YAAY;CACd;CACA,WAAW,YAAY;EACrB,MAAM,qBAAqB,EAAE,aAAa,cAAc,CAAC;CAC3D;CACA,QAAQ,OAAO,SAAS;EACtB,MAAM,uBAAuB;GAC3B,OAAO,QAAQ,KAAK,QAAQ,KAAK;GAGjC,iBAAiB,KAAK,QAAQ,cAAc,QAAQ;GACpD,gBAAgB,KAAK,QAAQ,aAAa,QAAQ;EACpD,CAAC;EAKD,AAAK,wBAAwB;CAC/B;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;CACF;AACF,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { command } from "../cli-command.mjs";
2
+ import { updateWarlockPackages } from "../../updater/update-warlock-packages.mjs";
3
+
4
+ //#region ../@warlock.js/core/src/cli/commands/update.command.ts
5
+ const updateCommand = command({
6
+ name: "update",
7
+ description: "Update all @warlock.js packages in this project to their latest version",
8
+ action: async (data) => {
9
+ await updateWarlockPackages({ install: !data.options.noInstall });
10
+ },
11
+ options: [{
12
+ text: "--no-install",
13
+ description: "Rewrite the @warlock.js versions in package.json without running the package manager install",
14
+ type: "boolean"
15
+ }]
16
+ });
17
+
18
+ //#endregion
19
+ export { updateCommand };
20
+ //# sourceMappingURL=update.command.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update.command.mjs","names":[],"sources":["../../../../../../../../@warlock.js/core/src/cli/commands/update.command.ts"],"sourcesContent":["import { updateWarlockPackages } from \"../../updater/update-warlock-packages\";\nimport { command } from \"../cli-command\";\n\nexport const updateCommand = command({\n name: \"update\",\n description: \"Update all @warlock.js packages in this project to their latest version\",\n action: async (data) => {\n // `parseCliArgs` camelCases every flag, so `--no-install` arrives as\n // `options.noInstall` (not `options[\"no-install\"]`).\n await updateWarlockPackages({\n install: !data.options.noInstall,\n });\n },\n options: [\n {\n text: \"--no-install\",\n description:\n \"Rewrite the @warlock.js versions in package.json without running the package manager install\",\n type: \"boolean\",\n },\n ],\n});\n"],"mappings":";;;;AAGA,MAAa,gBAAgB,QAAQ;CACnC,MAAM;CACN,aAAa;CACb,QAAQ,OAAO,SAAS;EAGtB,MAAM,sBAAsB,EAC1B,SAAS,CAAC,KAAK,QAAQ,UACzB,CAAC;CACH;CACA,SAAS,CACP;EACE,MAAM;EACN,aACE;EACF,MAAM;CACR,CACF;AACF,CAAC"}
@@ -9,6 +9,7 @@ import { seedCommand } from "./commands/seed.command.mjs";
9
9
  import { startProductionCommand } from "./commands/start-production.command.mjs";
10
10
  import { storagePutCommand } from "./commands/storage-put.command.mjs";
11
11
  import { typingsGeneratorCommand } from "./commands/typings-generator.command.mjs";
12
+ import { updateCommand } from "./commands/update.command.mjs";
12
13
 
13
14
  //#region ../@warlock.js/core/src/cli/framework-cli-commands.ts
14
15
  const frameworkCommands = [
@@ -21,6 +22,7 @@ const frameworkCommands = [
21
22
  createDatabaseCommand,
22
23
  dropTablesCommand,
23
24
  addCommand,
25
+ updateCommand,
24
26
  generateCommand,
25
27
  generateModuleCommand,
26
28
  generateControllerCommand,
@@ -1 +1 @@
1
- {"version":3,"file":"framework-cli-commands.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/cli/framework-cli-commands.ts"],"sourcesContent":["import { addCommand } from \"./commands/add.command\";\r\nimport { buildCommand } from \"./commands/build.command\";\r\nimport { createDatabaseCommand } from \"./commands/create-database.command\";\r\nimport { devServerCommand } from \"./commands/dev-server.command\";\r\nimport { dropTablesCommand } from \"./commands/drop-tables.command\";\r\nimport {\r\n generateCommand,\r\n generateControllerCommand,\r\n generateMigrationCommand,\r\n generateModelCommand,\r\n generateModuleCommand,\r\n generateRepositoryCommand,\r\n generateResourceCommand,\r\n generateServiceCommand,\r\n} from \"./commands/generate/generate.command\";\r\nimport { migrateCommand } from \"./commands/migrate.command\";\r\nimport { seedCommand } from \"./commands/seed.command\";\r\nimport { startProductionCommand } from \"./commands/start-production.command\";\r\nimport { storagePutCommand } from \"./commands/storage-put.command\";\r\nimport { typingsGeneratorCommand } from \"./commands/typings-generator.command\";\r\n\r\nexport const frameworkCommands = [\r\n // development commands\r\n devServerCommand,\r\n typingsGeneratorCommand,\r\n\r\n // production commands\r\n buildCommand,\r\n startProductionCommand,\r\n\r\n // database commands\r\n migrateCommand,\r\n seedCommand,\r\n createDatabaseCommand,\r\n dropTablesCommand,\r\n\r\n // generation/installation commands\r\n addCommand,\r\n\r\n // scaffolding commands\r\n generateCommand,\r\n generateModuleCommand,\r\n generateControllerCommand,\r\n generateServiceCommand,\r\n generateModelCommand,\r\n generateRepositoryCommand,\r\n generateResourceCommand,\r\n generateMigrationCommand,\r\n\r\n // storage commands\r\n storagePutCommand,\r\n];\r\n"],"mappings":";;;;;;;;;;;;;AAqBA,MAAa,oBAAoB;CAE/B;CACA;CAGA;CACA;CAGA;CACA;CACA;CACA;CAGA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;AACF"}
1
+ {"version":3,"file":"framework-cli-commands.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/cli/framework-cli-commands.ts"],"sourcesContent":["import { addCommand } from \"./commands/add.command\";\r\nimport { buildCommand } from \"./commands/build.command\";\r\nimport { createDatabaseCommand } from \"./commands/create-database.command\";\r\nimport { devServerCommand } from \"./commands/dev-server.command\";\r\nimport { dropTablesCommand } from \"./commands/drop-tables.command\";\r\nimport {\r\n generateCommand,\r\n generateControllerCommand,\r\n generateMigrationCommand,\r\n generateModelCommand,\r\n generateModuleCommand,\r\n generateRepositoryCommand,\r\n generateResourceCommand,\r\n generateServiceCommand,\r\n} from \"./commands/generate/generate.command\";\r\nimport { migrateCommand } from \"./commands/migrate.command\";\r\nimport { seedCommand } from \"./commands/seed.command\";\r\nimport { startProductionCommand } from \"./commands/start-production.command\";\r\nimport { storagePutCommand } from \"./commands/storage-put.command\";\r\nimport { typingsGeneratorCommand } from \"./commands/typings-generator.command\";\r\nimport { updateCommand } from \"./commands/update.command\";\r\n\r\nexport const frameworkCommands = [\r\n // development commands\r\n devServerCommand,\r\n typingsGeneratorCommand,\r\n\r\n // production commands\r\n buildCommand,\r\n startProductionCommand,\r\n\r\n // database commands\r\n migrateCommand,\r\n seedCommand,\r\n createDatabaseCommand,\r\n dropTablesCommand,\r\n\r\n // generation/installation commands\r\n addCommand,\r\n updateCommand,\r\n\r\n // scaffolding commands\r\n generateCommand,\r\n generateModuleCommand,\r\n generateControllerCommand,\r\n generateServiceCommand,\r\n generateModelCommand,\r\n generateRepositoryCommand,\r\n generateResourceCommand,\r\n generateMigrationCommand,\r\n\r\n // storage commands\r\n storagePutCommand,\r\n];\r\n"],"mappings":";;;;;;;;;;;;;;AAsBA,MAAa,oBAAoB;CAE/B;CACA;CAGA;CACA;CAGA;CACA;CACA;CACA;CAGA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;AACF"}
@@ -0,0 +1,59 @@
1
+ import { fetchLatestVersion } from "../utils/npm-registry.mjs";
2
+ import { isNewerVersion } from "../utils/version-compare.mjs";
3
+ import { getWarlockVersion } from "../utils/framework-vesion.mjs";
4
+ import { warlockConfigManager } from "../warlock-config/warlock-config.manager.mjs";
5
+ import "../warlock-config/index.mjs";
6
+ import { colors } from "@mongez/copper";
7
+
8
+ //#region ../@warlock.js/core/src/dev-server/check-for-updates.ts
9
+ /** The package whose version represents the whole (lockstep) family. */
10
+ const CORE_PACKAGE = "@warlock.js/core";
11
+ /** Where developers can read what changed between releases. */
12
+ const CHANGELOG_URL = "https://warlock.js.org/changelog/";
13
+ /**
14
+ * Check npm for a newer `@warlock.js/core` release and, if one exists, print
15
+ * a short non-blocking notice to the terminal. Because the whole family is
16
+ * released in lockstep, core's version stands in for every `@warlock.js/*`
17
+ * package, so a single lookup is enough.
18
+ *
19
+ * Designed to be called fire-and-forget right after the dev server is ready:
20
+ * it never throws, never blocks startup, and stays silent unless there is a
21
+ * genuinely newer version to report. Automatically skipped in CI, in
22
+ * non-interactive (non-TTY) shells, and when `devServer.checkForUpdates` is
23
+ * set to `false`.
24
+ */
25
+ async function checkForFrameworkUpdate() {
26
+ try {
27
+ if (!isUpdateCheckEnabled()) return;
28
+ if ((await warlockConfigManager.get("devServer"))?.checkForUpdates === false) return;
29
+ const currentVersion = await getWarlockVersion();
30
+ const latestVersion = await fetchLatestVersion(CORE_PACKAGE);
31
+ if (!latestVersion || !isNewerVersion(latestVersion, currentVersion)) return;
32
+ printUpdateNotice(currentVersion, latestVersion);
33
+ } catch {}
34
+ }
35
+ /**
36
+ * Whether an update check should run at all in the current environment.
37
+ * Mirrors npm's own update-notifier conventions: stay quiet in CI, in
38
+ * non-interactive shells, and when explicitly opted out via env.
39
+ */
40
+ function isUpdateCheckEnabled() {
41
+ if (process.env.CI) return false;
42
+ if (process.env.NO_UPDATE_NOTIFIER) return false;
43
+ if (!process.stdout.isTTY) return false;
44
+ return true;
45
+ }
46
+ /**
47
+ * Print the "update available" notice in the dev-logger's visual style.
48
+ */
49
+ function printUpdateNotice(currentVersion, latestVersion) {
50
+ console.log();
51
+ console.log(` ${colors.yellow("⚡")} ${colors.bold("A new version of Warlock.js is available")} ${colors.dim(currentVersion)} ${colors.dim("→")} ${colors.greenBright(latestVersion)}`);
52
+ console.log(` ${colors.dim("Run")} ${colors.cyan("npx warlock update")} ${colors.dim("to update all @warlock.js packages")}`);
53
+ console.log(` ${colors.dim("Changelog")} ${colors.cyan(CHANGELOG_URL)}`);
54
+ console.log();
55
+ }
56
+
57
+ //#endregion
58
+ export { checkForFrameworkUpdate };
59
+ //# sourceMappingURL=check-for-updates.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-for-updates.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/dev-server/check-for-updates.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { getWarlockVersion } from \"../utils/framework-vesion\";\nimport { fetchLatestVersion } from \"../utils/npm-registry\";\nimport { isNewerVersion } from \"../utils/version-compare\";\nimport { warlockConfigManager } from \"../warlock-config\";\n\n/** The package whose version represents the whole (lockstep) family. */\nconst CORE_PACKAGE = \"@warlock.js/core\";\n\n/** Where developers can read what changed between releases. */\nconst CHANGELOG_URL = \"https://warlock.js.org/changelog/\";\n\n/**\n * Check npm for a newer `@warlock.js/core` release and, if one exists, print\n * a short non-blocking notice to the terminal. Because the whole family is\n * released in lockstep, core's version stands in for every `@warlock.js/*`\n * package, so a single lookup is enough.\n *\n * Designed to be called fire-and-forget right after the dev server is ready:\n * it never throws, never blocks startup, and stays silent unless there is a\n * genuinely newer version to report. Automatically skipped in CI, in\n * non-interactive (non-TTY) shells, and when `devServer.checkForUpdates` is\n * set to `false`.\n */\nexport async function checkForFrameworkUpdate(): Promise<void> {\n try {\n if (!isUpdateCheckEnabled()) {\n return;\n }\n\n const devServerConfig = await warlockConfigManager.get(\"devServer\");\n\n if (devServerConfig?.checkForUpdates === false) {\n return;\n }\n\n const currentVersion = await getWarlockVersion();\n const latestVersion = await fetchLatestVersion(CORE_PACKAGE);\n\n if (!latestVersion || !isNewerVersion(latestVersion, currentVersion)) {\n return;\n }\n\n printUpdateNotice(currentVersion, latestVersion);\n } catch {\n // An update check is a convenience — never let it disrupt the dev server.\n }\n}\n\n/**\n * Whether an update check should run at all in the current environment.\n * Mirrors npm's own update-notifier conventions: stay quiet in CI, in\n * non-interactive shells, and when explicitly opted out via env.\n */\nfunction isUpdateCheckEnabled(): boolean {\n if (process.env.CI) {\n return false;\n }\n\n if (process.env.NO_UPDATE_NOTIFIER) {\n return false;\n }\n\n if (!process.stdout.isTTY) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Print the \"update available\" notice in the dev-logger's visual style.\n */\nfunction printUpdateNotice(currentVersion: string, latestVersion: string): void {\n console.log();\n console.log(\n ` ${colors.yellow(\"⚡\")} ${colors.bold(\"A new version of Warlock.js is available\")} ` +\n `${colors.dim(currentVersion)} ${colors.dim(\"→\")} ${colors.greenBright(latestVersion)}`,\n );\n console.log(\n ` ${colors.dim(\"Run\")} ${colors.cyan(\"npx warlock update\")} ` +\n `${colors.dim(\"to update all @warlock.js packages\")}`,\n );\n console.log(` ${colors.dim(\"Changelog\")} ${colors.cyan(CHANGELOG_URL)}`);\n console.log();\n}\n"],"mappings":";;;;;;;;;AAOA,MAAM,eAAe;;AAGrB,MAAM,gBAAgB;;;;;;;;;;;;;AActB,eAAsB,0BAAyC;CAC7D,IAAI;EACF,IAAI,CAAC,qBAAqB,GACxB;EAKF,KAAI,MAF0B,qBAAqB,IAAI,WAAW,EAE/C,EAAE,oBAAoB,OACvC;EAGF,MAAM,iBAAiB,MAAM,kBAAkB;EAC/C,MAAM,gBAAgB,MAAM,mBAAmB,YAAY;EAE3D,IAAI,CAAC,iBAAiB,CAAC,eAAe,eAAe,cAAc,GACjE;EAGF,kBAAkB,gBAAgB,aAAa;CACjD,QAAQ,CAER;AACF;;;;;;AAOA,SAAS,uBAAgC;CACvC,IAAI,QAAQ,IAAI,IACd,OAAO;CAGT,IAAI,QAAQ,IAAI,oBACd,OAAO;CAGT,IAAI,CAAC,QAAQ,OAAO,OAClB,OAAO;CAGT,OAAO;AACT;;;;AAKA,SAAS,kBAAkB,gBAAwB,eAA6B;CAC9E,QAAQ,IAAI;CACZ,QAAQ,IACN,KAAK,OAAO,OAAO,GAAG,EAAE,GAAG,OAAO,KAAK,0CAA0C,EAAE,IAC9E,OAAO,IAAI,cAAc,EAAE,GAAG,OAAO,IAAI,GAAG,EAAE,GAAG,OAAO,YAAY,aAAa,GACxF;CACA,QAAQ,IACN,QAAQ,OAAO,IAAI,KAAK,EAAE,GAAG,OAAO,KAAK,oBAAoB,EAAE,GAC1D,OAAO,IAAI,oCAAoC,GACtD;CACA,QAAQ,IAAI,QAAQ,OAAO,IAAI,WAAW,EAAE,GAAG,OAAO,KAAK,aAAa,GAAG;CAC3E,QAAQ,IAAI;AACd"}
package/esm/index.d.mts CHANGED
@@ -123,6 +123,7 @@ import { BadSchemaUseCaseError } from "./use-cases/use-case.errors.mjs";
123
123
  import { $registerUseCase, $unregisterUseCase, addUseCaseHistory, getUseCase, getUseCaseHistory, getUseCases, increaseUseCaseFailedCalls, increaseUseCaseSuccessCalls } from "./use-cases/use-cases-registry.mjs";
124
124
  import { appLog } from "./utils/app-log.mjs";
125
125
  import { DatabaseLog, DatabaseLogOptions } from "./utils/database-log.mjs";
126
+ import { fetchLatestVersion } from "./utils/npm-registry.mjs";
126
127
  import { appPath, cachePath, configPath, logsPath, paths, publicPath, rootPath, sanitizePath, srcPath, storagePath, tempPath, uploadsPath, warlockPath } from "./utils/paths.mjs";
127
128
  import { promiseAllObject } from "./utils/promise-all-object.mjs";
128
129
  import { Queue } from "./utils/queue.mjs";
@@ -130,6 +131,7 @@ import { sleep } from "./utils/sleep.mjs";
130
131
  import { sluggable } from "./utils/sluggable.mjs";
131
132
  import { toJson } from "./utils/to-json.mjs";
132
133
  import { assetsUrl, publicUrl, setBaseUrl, uploadsUrl, url } from "./utils/urls.mjs";
134
+ import { isNewerVersion } from "./utils/version-compare.mjs";
133
135
  import { existsExceptCurrentIdRule } from "./validation/database/exists-except-current-id.mjs";
134
136
  import { existsExceptCurrentUserRule } from "./validation/database/exists-except-current-user.mjs";
135
137
  import { ExistsExceptCurrentIdRuleOptions, ExistsExceptCurrentUserRuleOptions, UniqueExceptCurrentIdRuleOptions, UniqueExceptCurrentUserRuleOptions } from "./validation/database/types.mjs";
@@ -145,7 +147,7 @@ import { WarlockConfigManager, isUnknownTsExtensionError, warlockConfigManager }
145
147
  import { env } from "@mongez/dotenv";
146
148
  import { colors } from "@mongez/copper";
147
149
  export * from "@mongez/localization";
148
- export { $registerUseCase, $unregisterUseCase, AccessConnector, AllRepositoryOptions, AppConfigurations, Application, BadRequestError, BadSchemaUseCaseError, BaseConnector, BaseHealthChecker, type BenchmarkChannel, type BenchmarkConfigurations, type BenchmarkErrorResult, type BenchmarkOptions, BenchmarkProfiler, type BenchmarkProfilerOptions, type BenchmarkResult, BenchmarkSnapshots, type BenchmarkSnapshotsOptions, type BenchmarkStats, type BenchmarkSuccessResult, CLICommand, CLICommandAction, CLICommandOption, CLICommandOptions, CLICommandPreload, CLICommandSource, CacheConnector, type CacheMiddlewareOptions, CachedRepositoryOptions, type CapturedMail, CascadeAdapter, CascadeQueryBuilder, ChunkCallback, CloudDriver, CloudStorageDriverContract, CloudStorageDriverOptions, CloudStorageFileData, CommandActionData, type ConcurrencyLimitOptions, ConfigKey, ConfigKeyRegistry, ConfigName, ConfigRegistry, ConfigSpecialHandlers, ConflictError, Connector, ConnectorLifecyclePhase, ConnectorName, ConnectorPriority, ConnectorsManager, ConsoleChannel, ContainerTypes, CookieOptions, CursorPaginationOptions, CursorPaginationResult, DOSpacesDriver, DatabaseCacheDriver, type DatabaseCacheOptions, DatabaseConnector, DatabaseLog, DatabaseLogModel, DatabaseLogOptions, DecoratorLoweringPlugin, DefineResourceOptions, DeleteManyResult, EncryptionConfigurations, EncryptionPasswordConfigurations, Environment, EslintHealthChecker, EventSubscription, ExistsExceptCurrentIdRuleOptions, ExistsExceptCurrentUserRuleOptions, FastifyInstance, FileNamingStrategy, FileValidationOptions, FileValidator, FileVisibility, FilesOrchestrator, FilterFunction, FilterOperator, FilterOptions, FilterRule, FilterRules, ForbiddenError, GroupedRoutesOptions, HeraldConnector, HttpConfigurations, HttpConnector, HttpError, HttpErrorCodes, type IdempotencyOptions, Image, ImageFormat, ImageInput, ImageTransformCallback, ImageTransformConfig, ImageTransformOptions, type IpFilterOptions, ListOptions, LocalDriver, LocalStorageDriverOptions, LocalizedObject, LogConfigurations, LoggerConnector, MAIL_EVENTS, Mail, type MailAddress, type MailAttachment, type MailConfigurations, MailError, type MailErrorCode, type MailEvents, type MailMode, type MailOptions, type MailPriority, type MailResult, MailerConnector, type MailersConfig, type MaintenanceOptions, Middleware, MiddlewareResponse, MimeTypes, NoopChannel, type NormalizedMail, NotAcceptableError, NotAllowedError, NotificationsConnector, PaginationMode, PaginationResult, PartialMiddleware, PartialPick, PipelineOptions, PrefixConfig, PrefixOptions, PresignedOptions, PresignedUploadOptions, PutDirectoryOptions, PutDirectoryResult, PutOptions, QueryBuilderContract, Queue, R2Driver, R2StorageDriverOptions, type RateLimitOptions, RegisterResource, RegisteredUseCase, RepositoryAdapterContract, RepositoryConfigurations, RepositoryEvent, RepositoryManager, RepositoryOptions, RepositoryOptionsWithCursor, RepositoryOptionsWithPages, Request, RequestContextStore, RequestController, RequestControllerContract, RequestEvent, RequestHandler, RequestHandlerType, RequestHandlerValidation, RequestLog, RequestMethod, ResolvedCLICommandOption, Resource, ResourceArraySchema, ResourceCastType, ResourceConstructor, ResourceContract, ResourceFieldBuilder, ResourceFieldBuilderDateOutputOptions, ResourceFieldConfig, ResourceMethod, ResourceNotFoundError, ResourceOutputValueCastType, ResourceSchema, ResourceSelfReference, Response, ResponseBodyValue, ResponseEvent, ResponseSSEController, ResponseSchema, ResponseStatus, ResponseStreamController, Restful, RestfulMiddleware, ReturnedResponse, Route, RouteOptions, RouteResource, Router, RouterGroupCallback, RouterStacks, S3Driver, type SESConfigurations, type SMTPConfigurations, SaveAsOptions, SaveMode, SaveOptions, ScopedStorage, ScopedStorageContract, SeedResult, Seeder, SeederMetadata, SendBufferOptions, SendFileOptions, ServerError, SocketConnector, SocketOptions, Storage, StorageConfigurations, StorageConnector, StorageCopyEventPayload, StorageDriverConfig, StorageDriverContextStore, StorageDriverContract, StorageDriverName, StorageDriverRegistry, StorageDriverType, StorageEventHandler, StorageEventPayload, StorageEventType, StorageFile, StorageFileData, StorageFileInfo, StorageManagerContract, StoragePutEventPayload, TemporaryTokenError, TemporaryTokenPayload, TemporaryTokenValidation, TypedAllRepositoryOptions, TypedRepositoryOptions, TypedRepositoryOptionsWithCursor, TypedRepositoryOptionsWithPages, TypescriptHealthChecker, UPLOADS_DEFAULTS, UnAuthorizedError, UniqueExceptCurrentIdRuleOptions, UniqueExceptCurrentUserRuleOptions, UploadedFile, UploadedFileImageOptions, UploadsConfigurations, UseCase, UseCaseAfterMiddleware, UseCaseBeforeMiddleware, UseCaseBroadcastChannel, UseCaseBroadcastEvent, UseCaseBroadcastOption, UseCaseConfigurations, UseCaseContext, UseCaseErrorResult, UseCaseEventsCallbacksMap, UseCaseGuard, UseCaseHandler, UseCaseOnExecutingContext, UseCaseResult, UseCaseRuntimeOptions, UseCaseWithSchema, ValidationConfiguration, WarlockConfig, WarlockConfigManager, WatermarkConfig, WhereOperator, addUseCaseHistory, anyMatch, app, appLog, appPath, assertMailCount, assertMailSent, assetsUrl, bootstrap, broadcastUseCaseResult, buildIdempotencyCacheKey, cachePath, captureMail, clearTestMailbox, closeAllMailers, closeMailer, colors, command, config, configPath, configSpecialHandlers, connectorsManager, container, createHttpApplication, createRequestStore, decrypt, defaultHttpConfigurations, defineConfig, defineResource, displayEnvironmentMode, encrypt, env, environment, existsExceptCurrentIdRule, existsExceptCurrentUserRule, expectJson, fileExtensionRule, fileRule, fileTypeRule, filesOrchestrator, findMailsBySubject, findMailsTo, fireLifecycleEvent, fromRequest, generateMailId, getDefaultMailConfig, getHttpServer, getLastMail, getLocalized, getMailEventName, getMailMode, getMailboxSize, getMailer, getMailerConfig, getMimeType, getPoolStats, getSocketServer, getTestMailbox, getTestServerUrl, getUseCase, getUseCaseHistory, getUseCases, globalEventsCallbacksMap, globalUseCasesEvents, hashBody, hashPassword, hmacHash, httpConfig, imageRule, increaseUseCaseFailedCalls, increaseUseCaseSuccessCalls, ipMatches, isDevelopmentMode, isProductionMode, isTestMode, isTestServerRunning, isUnknownTsExtensionError, isValidIdempotencyKey, loadS3, logResponse, logsPath, lowerStage3Decorators, mailEvents, measure, middleware, onCleanup, parseJsonResponse, parseSize, paths, promiseAllObject, publicPath, publicUrl, registerAppConfig, registerHttpPlugins, renderReact, renderReactMail, requestContext, resetMailConfig, resolveMailConfig, rootPath, router, runPipeline, sanitizePath, seeder, sendMail, setBaseUrl, setEnvironment, setLogConfigurations, setMailConfigurations, setMailMode, setupTest, sleep, sluggable, srcPath, startHttpServer, startHttpTestServer, stopHttpApplication, stopHttpTestServer, storage, storageConfig, storageConfigurations, storageDriverContext, storagePath, t, tempPath, testDelete, testGet, testPatch, testPost, testPut, testRequest, toJson, uniqueExceptCurrentIdRule, uniqueExceptCurrentUserRule, uploadedFileMetadataSchema, uploadsConfig, uploadsPath, uploadsUrl, url, useCase, useComputedModel, useComputedSlug, useCurrentUser, useHashedPassword, useRequest, useRequestStore, verifyMailer, verifyPassword, warlockConfigManager, warlockPath, wasMailSentTo, wasMailSentWithSubject, wrapResponseInDataKey };
150
+ export { $registerUseCase, $unregisterUseCase, AccessConnector, AllRepositoryOptions, AppConfigurations, Application, BadRequestError, BadSchemaUseCaseError, BaseConnector, BaseHealthChecker, type BenchmarkChannel, type BenchmarkConfigurations, type BenchmarkErrorResult, type BenchmarkOptions, BenchmarkProfiler, type BenchmarkProfilerOptions, type BenchmarkResult, BenchmarkSnapshots, type BenchmarkSnapshotsOptions, type BenchmarkStats, type BenchmarkSuccessResult, CLICommand, CLICommandAction, CLICommandOption, CLICommandOptions, CLICommandPreload, CLICommandSource, CacheConnector, type CacheMiddlewareOptions, CachedRepositoryOptions, type CapturedMail, CascadeAdapter, CascadeQueryBuilder, ChunkCallback, CloudDriver, CloudStorageDriverContract, CloudStorageDriverOptions, CloudStorageFileData, CommandActionData, type ConcurrencyLimitOptions, ConfigKey, ConfigKeyRegistry, ConfigName, ConfigRegistry, ConfigSpecialHandlers, ConflictError, Connector, ConnectorLifecyclePhase, ConnectorName, ConnectorPriority, ConnectorsManager, ConsoleChannel, ContainerTypes, CookieOptions, CursorPaginationOptions, CursorPaginationResult, DOSpacesDriver, DatabaseCacheDriver, type DatabaseCacheOptions, DatabaseConnector, DatabaseLog, DatabaseLogModel, DatabaseLogOptions, DecoratorLoweringPlugin, DefineResourceOptions, DeleteManyResult, EncryptionConfigurations, EncryptionPasswordConfigurations, Environment, EslintHealthChecker, EventSubscription, ExistsExceptCurrentIdRuleOptions, ExistsExceptCurrentUserRuleOptions, FastifyInstance, FileNamingStrategy, FileValidationOptions, FileValidator, FileVisibility, FilesOrchestrator, FilterFunction, FilterOperator, FilterOptions, FilterRule, FilterRules, ForbiddenError, GroupedRoutesOptions, HeraldConnector, HttpConfigurations, HttpConnector, HttpError, HttpErrorCodes, type IdempotencyOptions, Image, ImageFormat, ImageInput, ImageTransformCallback, ImageTransformConfig, ImageTransformOptions, type IpFilterOptions, ListOptions, LocalDriver, LocalStorageDriverOptions, LocalizedObject, LogConfigurations, LoggerConnector, MAIL_EVENTS, Mail, type MailAddress, type MailAttachment, type MailConfigurations, MailError, type MailErrorCode, type MailEvents, type MailMode, type MailOptions, type MailPriority, type MailResult, MailerConnector, type MailersConfig, type MaintenanceOptions, Middleware, MiddlewareResponse, MimeTypes, NoopChannel, type NormalizedMail, NotAcceptableError, NotAllowedError, NotificationsConnector, PaginationMode, PaginationResult, PartialMiddleware, PartialPick, PipelineOptions, PrefixConfig, PrefixOptions, PresignedOptions, PresignedUploadOptions, PutDirectoryOptions, PutDirectoryResult, PutOptions, QueryBuilderContract, Queue, R2Driver, R2StorageDriverOptions, type RateLimitOptions, RegisterResource, RegisteredUseCase, RepositoryAdapterContract, RepositoryConfigurations, RepositoryEvent, RepositoryManager, RepositoryOptions, RepositoryOptionsWithCursor, RepositoryOptionsWithPages, Request, RequestContextStore, RequestController, RequestControllerContract, RequestEvent, RequestHandler, RequestHandlerType, RequestHandlerValidation, RequestLog, RequestMethod, ResolvedCLICommandOption, Resource, ResourceArraySchema, ResourceCastType, ResourceConstructor, ResourceContract, ResourceFieldBuilder, ResourceFieldBuilderDateOutputOptions, ResourceFieldConfig, ResourceMethod, ResourceNotFoundError, ResourceOutputValueCastType, ResourceSchema, ResourceSelfReference, Response, ResponseBodyValue, ResponseEvent, ResponseSSEController, ResponseSchema, ResponseStatus, ResponseStreamController, Restful, RestfulMiddleware, ReturnedResponse, Route, RouteOptions, RouteResource, Router, RouterGroupCallback, RouterStacks, S3Driver, type SESConfigurations, type SMTPConfigurations, SaveAsOptions, SaveMode, SaveOptions, ScopedStorage, ScopedStorageContract, SeedResult, Seeder, SeederMetadata, SendBufferOptions, SendFileOptions, ServerError, SocketConnector, SocketOptions, Storage, StorageConfigurations, StorageConnector, StorageCopyEventPayload, StorageDriverConfig, StorageDriverContextStore, StorageDriverContract, StorageDriverName, StorageDriverRegistry, StorageDriverType, StorageEventHandler, StorageEventPayload, StorageEventType, StorageFile, StorageFileData, StorageFileInfo, StorageManagerContract, StoragePutEventPayload, TemporaryTokenError, TemporaryTokenPayload, TemporaryTokenValidation, TypedAllRepositoryOptions, TypedRepositoryOptions, TypedRepositoryOptionsWithCursor, TypedRepositoryOptionsWithPages, TypescriptHealthChecker, UPLOADS_DEFAULTS, UnAuthorizedError, UniqueExceptCurrentIdRuleOptions, UniqueExceptCurrentUserRuleOptions, UploadedFile, UploadedFileImageOptions, UploadsConfigurations, UseCase, UseCaseAfterMiddleware, UseCaseBeforeMiddleware, UseCaseBroadcastChannel, UseCaseBroadcastEvent, UseCaseBroadcastOption, UseCaseConfigurations, UseCaseContext, UseCaseErrorResult, UseCaseEventsCallbacksMap, UseCaseGuard, UseCaseHandler, UseCaseOnExecutingContext, UseCaseResult, UseCaseRuntimeOptions, UseCaseWithSchema, ValidationConfiguration, WarlockConfig, WarlockConfigManager, WatermarkConfig, WhereOperator, addUseCaseHistory, anyMatch, app, appLog, appPath, assertMailCount, assertMailSent, assetsUrl, bootstrap, broadcastUseCaseResult, buildIdempotencyCacheKey, cachePath, captureMail, clearTestMailbox, closeAllMailers, closeMailer, colors, command, config, configPath, configSpecialHandlers, connectorsManager, container, createHttpApplication, createRequestStore, decrypt, defaultHttpConfigurations, defineConfig, defineResource, displayEnvironmentMode, encrypt, env, environment, existsExceptCurrentIdRule, existsExceptCurrentUserRule, expectJson, fetchLatestVersion, fileExtensionRule, fileRule, fileTypeRule, filesOrchestrator, findMailsBySubject, findMailsTo, fireLifecycleEvent, fromRequest, generateMailId, getDefaultMailConfig, getHttpServer, getLastMail, getLocalized, getMailEventName, getMailMode, getMailboxSize, getMailer, getMailerConfig, getMimeType, getPoolStats, getSocketServer, getTestMailbox, getTestServerUrl, getUseCase, getUseCaseHistory, getUseCases, globalEventsCallbacksMap, globalUseCasesEvents, hashBody, hashPassword, hmacHash, httpConfig, imageRule, increaseUseCaseFailedCalls, increaseUseCaseSuccessCalls, ipMatches, isDevelopmentMode, isNewerVersion, isProductionMode, isTestMode, isTestServerRunning, isUnknownTsExtensionError, isValidIdempotencyKey, loadS3, logResponse, logsPath, lowerStage3Decorators, mailEvents, measure, middleware, onCleanup, parseJsonResponse, parseSize, paths, promiseAllObject, publicPath, publicUrl, registerAppConfig, registerHttpPlugins, renderReact, renderReactMail, requestContext, resetMailConfig, resolveMailConfig, rootPath, router, runPipeline, sanitizePath, seeder, sendMail, setBaseUrl, setEnvironment, setLogConfigurations, setMailConfigurations, setMailMode, setupTest, sleep, sluggable, srcPath, startHttpServer, startHttpTestServer, stopHttpApplication, stopHttpTestServer, storage, storageConfig, storageConfigurations, storageDriverContext, storagePath, t, tempPath, testDelete, testGet, testPatch, testPost, testPut, testRequest, toJson, uniqueExceptCurrentIdRule, uniqueExceptCurrentUserRule, uploadedFileMetadataSchema, uploadsConfig, uploadsPath, uploadsUrl, url, useCase, useComputedModel, useComputedSlug, useCurrentUser, useHashedPassword, useRequest, useRequestStore, verifyMailer, verifyPassword, warlockConfigManager, warlockPath, wasMailSentTo, wasMailSentWithSubject, wrapResponseInDataKey };
149
151
  import "./config/types.mjs";
150
152
  import "./storage/types.mjs";
151
153
  import "./validation/types.mjs";
package/esm/index.mjs CHANGED
@@ -8,6 +8,7 @@ import { DatabaseLog } from "./utils/database-log.mjs";
8
8
  import { environment, setEnvironment } from "./utils/environment.mjs";
9
9
  import { requestContext, useCurrentUser, useRequest, useRequestStore } from "./http/context/request-context.mjs";
10
10
  import { getLocalized } from "./utils/get-localized.mjs";
11
+ import { fetchLatestVersion } from "./utils/npm-registry.mjs";
11
12
  import { appPath, cachePath, configPath, logsPath, paths, publicPath, rootPath, sanitizePath, srcPath, storagePath, tempPath, uploadsPath, warlockPath } from "./utils/paths.mjs";
12
13
  import { promiseAllObject } from "./utils/promise-all-object.mjs";
13
14
  import { Queue } from "./utils/queue.mjs";
@@ -15,6 +16,7 @@ import { sleep } from "./utils/sleep.mjs";
15
16
  import { sluggable } from "./utils/sluggable.mjs";
16
17
  import { toJson } from "./utils/to-json.mjs";
17
18
  import { assetsUrl, publicUrl, setBaseUrl, uploadsUrl, url } from "./utils/urls.mjs";
19
+ import { isNewerVersion } from "./utils/version-compare.mjs";
18
20
  import "./utils/index.mjs";
19
21
  import { BadRequestError, ConflictError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError, ResourceNotFoundError, ServerError, UnAuthorizedError } from "./http/errors/errors.mjs";
20
22
  import { createRequestStore, fromRequest, t } from "./http/middleware/inject-request-context.mjs";
@@ -146,4 +148,4 @@ import { colors } from "@mongez/copper";
146
148
 
147
149
  export * from "@mongez/localization"
148
150
 
149
- export { $registerUseCase, $unregisterUseCase, AccessConnector, Application, BadRequestError, BadSchemaUseCaseError, BaseConnector, BaseHealthChecker, BenchmarkProfiler, BenchmarkSnapshots, CLICommand, CacheConnector, CascadeAdapter, CascadeQueryBuilder, CloudDriver, ConfigSpecialHandlers, ConflictError, ConnectorLifecyclePhase, ConnectorPriority, ConnectorsManager, ConsoleChannel, DOSpacesDriver, DatabaseCacheDriver, DatabaseConnector, DatabaseLog, DatabaseLogModel, EslintHealthChecker, FileValidator, FilesOrchestrator, ForbiddenError, HeraldConnector, HttpConnector, HttpError, HttpErrorCodes, Image, LocalDriver, LoggerConnector, MAIL_EVENTS, Mail, MailError, MailerConnector, MimeTypes, NoopChannel, NotAcceptableError, NotAllowedError, NotificationsConnector, Queue, R2Driver, RegisterResource, RepositoryManager, Request, RequestController, RequestLog, Resource, ResourceFieldBuilder, ResourceNotFoundError, Response, ResponseStatus, Restful, Router, S3Driver, ScopedStorage, ServerError, SocketConnector, Storage, StorageConnector, StorageFile, TypescriptHealthChecker, UPLOADS_DEFAULTS, UnAuthorizedError, UploadedFile, WarlockConfigManager, addUseCaseHistory, anyMatch, app, appLog, appPath, assertMailCount, assertMailSent, assetsUrl, bootstrap, broadcastUseCaseResult, buildIdempotencyCacheKey, cachePath, captureMail, clearTestMailbox, closeAllMailers, closeMailer, colors, command, config, configPath, configSpecialHandlers, connectorsManager, container, createHttpApplication, createRequestStore, decrypt, defaultHttpConfigurations, defineConfig, defineResource, displayEnvironmentMode, encrypt, env, environment, existsExceptCurrentIdRule, existsExceptCurrentUserRule, expectJson, fileExtensionRule, fileRule, fileTypeRule, filesOrchestrator, findMailsBySubject, findMailsTo, fireLifecycleEvent, fromRequest, generateMailId, getDefaultMailConfig, getHttpServer, getLastMail, getLocalized, getMailEventName, getMailMode, getMailboxSize, getMailer, getMailerConfig, getMimeType, getPoolStats, getSocketServer, getTestMailbox, getTestServerUrl, getUseCase, getUseCaseHistory, getUseCases, globalEventsCallbacksMap, globalUseCasesEvents, hashBody, hashPassword, hmacHash, httpConfig, imageRule, increaseUseCaseFailedCalls, increaseUseCaseSuccessCalls, ipMatches, isDevelopmentMode, isProductionMode, isTestMode, isTestServerRunning, isUnknownTsExtensionError, isValidIdempotencyKey, loadS3, logResponse, logsPath, lowerStage3Decorators, mailEvents, measure, middleware, onCleanup, parseJsonResponse, parseSize, paths, promiseAllObject, publicPath, publicUrl, registerAppConfig, registerHttpPlugins, renderReact, renderReactMail, requestContext, resetMailConfig, resolveMailConfig, rootPath, router, runPipeline, sanitizePath, seeder, sendMail, setBaseUrl, setEnvironment, setLogConfigurations, setMailConfigurations, setMailMode, setupTest, sleep, sluggable, srcPath, startHttpServer, startHttpTestServer, stopHttpApplication, stopHttpTestServer, storage, storageConfig, storageConfigurations, storageDriverContext, storagePath, t, tempPath, testDelete, testGet, testPatch, testPost, testPut, testRequest, toJson, uniqueExceptCurrentIdRule, uniqueExceptCurrentUserRule, uploadedFileMetadataSchema, uploadsConfig, uploadsPath, uploadsUrl, url, useCase, useComputedModel, useComputedSlug, useCurrentUser, useHashedPassword, useRequest, useRequestStore, verifyMailer, verifyPassword, warlockConfigManager, warlockPath, wasMailSentTo, wasMailSentWithSubject, wrapResponseInDataKey };
151
+ export { $registerUseCase, $unregisterUseCase, AccessConnector, Application, BadRequestError, BadSchemaUseCaseError, BaseConnector, BaseHealthChecker, BenchmarkProfiler, BenchmarkSnapshots, CLICommand, CacheConnector, CascadeAdapter, CascadeQueryBuilder, CloudDriver, ConfigSpecialHandlers, ConflictError, ConnectorLifecyclePhase, ConnectorPriority, ConnectorsManager, ConsoleChannel, DOSpacesDriver, DatabaseCacheDriver, DatabaseConnector, DatabaseLog, DatabaseLogModel, EslintHealthChecker, FileValidator, FilesOrchestrator, ForbiddenError, HeraldConnector, HttpConnector, HttpError, HttpErrorCodes, Image, LocalDriver, LoggerConnector, MAIL_EVENTS, Mail, MailError, MailerConnector, MimeTypes, NoopChannel, NotAcceptableError, NotAllowedError, NotificationsConnector, Queue, R2Driver, RegisterResource, RepositoryManager, Request, RequestController, RequestLog, Resource, ResourceFieldBuilder, ResourceNotFoundError, Response, ResponseStatus, Restful, Router, S3Driver, ScopedStorage, ServerError, SocketConnector, Storage, StorageConnector, StorageFile, TypescriptHealthChecker, UPLOADS_DEFAULTS, UnAuthorizedError, UploadedFile, WarlockConfigManager, addUseCaseHistory, anyMatch, app, appLog, appPath, assertMailCount, assertMailSent, assetsUrl, bootstrap, broadcastUseCaseResult, buildIdempotencyCacheKey, cachePath, captureMail, clearTestMailbox, closeAllMailers, closeMailer, colors, command, config, configPath, configSpecialHandlers, connectorsManager, container, createHttpApplication, createRequestStore, decrypt, defaultHttpConfigurations, defineConfig, defineResource, displayEnvironmentMode, encrypt, env, environment, existsExceptCurrentIdRule, existsExceptCurrentUserRule, expectJson, fetchLatestVersion, fileExtensionRule, fileRule, fileTypeRule, filesOrchestrator, findMailsBySubject, findMailsTo, fireLifecycleEvent, fromRequest, generateMailId, getDefaultMailConfig, getHttpServer, getLastMail, getLocalized, getMailEventName, getMailMode, getMailboxSize, getMailer, getMailerConfig, getMimeType, getPoolStats, getSocketServer, getTestMailbox, getTestServerUrl, getUseCase, getUseCaseHistory, getUseCases, globalEventsCallbacksMap, globalUseCasesEvents, hashBody, hashPassword, hmacHash, httpConfig, imageRule, increaseUseCaseFailedCalls, increaseUseCaseSuccessCalls, ipMatches, isDevelopmentMode, isNewerVersion, isProductionMode, isTestMode, isTestServerRunning, isUnknownTsExtensionError, isValidIdempotencyKey, loadS3, logResponse, logsPath, lowerStage3Decorators, mailEvents, measure, middleware, onCleanup, parseJsonResponse, parseSize, paths, promiseAllObject, publicPath, publicUrl, registerAppConfig, registerHttpPlugins, renderReact, renderReactMail, requestContext, resetMailConfig, resolveMailConfig, rootPath, router, runPipeline, sanitizePath, seeder, sendMail, setBaseUrl, setEnvironment, setLogConfigurations, setMailConfigurations, setMailMode, setupTest, sleep, sluggable, srcPath, startHttpServer, startHttpTestServer, stopHttpApplication, stopHttpTestServer, storage, storageConfig, storageConfigurations, storageDriverContext, storagePath, t, tempPath, testDelete, testGet, testPatch, testPost, testPut, testRequest, toJson, uniqueExceptCurrentIdRule, uniqueExceptCurrentUserRule, uploadedFileMetadataSchema, uploadsConfig, uploadsPath, uploadsUrl, url, useCase, useComputedModel, useComputedSlug, useCurrentUser, useHashedPassword, useRequest, useRequestStore, verifyMailer, verifyPassword, warlockConfigManager, warlockPath, wasMailSentTo, wasMailSentWithSubject, wrapResponseInDataKey };
@@ -0,0 +1,32 @@
1
+ import { rootPath } from "../utils/paths.mjs";
2
+ import "../utils/index.mjs";
3
+ import { fileExistsAsync } from "@warlock.js/fs";
4
+
5
+ //#region ../@warlock.js/core/src/updater/package-manager.ts
6
+ /**
7
+ * Detect the project's package manager from its lockfile, falling back to
8
+ * npm when none is present. The lookup order matches `warlock add`, so both
9
+ * commands agree on a project that happens to carry more than one lockfile.
10
+ */
11
+ async function detectPackageManager() {
12
+ if (await fileExistsAsync(rootPath("package-lock.json"))) return "npm";
13
+ if (await fileExistsAsync(rootPath("yarn.lock"))) return "yarn";
14
+ if (await fileExistsAsync(rootPath("pnpm-lock.yaml"))) return "pnpm";
15
+ return "npm";
16
+ }
17
+ /**
18
+ * The lockfile-syncing install command for the given manager. No package
19
+ * arguments — `warlock update` rewrites the versions in package.json first,
20
+ * then a plain install reconciles `node_modules` to match.
21
+ */
22
+ function getInstallCommand(packageManager) {
23
+ switch (packageManager) {
24
+ case "yarn": return "yarn install";
25
+ case "pnpm": return "pnpm install";
26
+ default: return "npm install";
27
+ }
28
+ }
29
+
30
+ //#endregion
31
+ export { detectPackageManager, getInstallCommand };
32
+ //# sourceMappingURL=package-manager.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-manager.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/updater/package-manager.ts"],"sourcesContent":["import { fileExistsAsync } from \"@warlock.js/fs\";\nimport { rootPath } from \"../utils\";\n\n/** Package managers the framework knows how to drive. */\nexport type PackageManager = \"npm\" | \"yarn\" | \"pnpm\";\n\n/**\n * Detect the project's package manager from its lockfile, falling back to\n * npm when none is present. The lookup order matches `warlock add`, so both\n * commands agree on a project that happens to carry more than one lockfile.\n */\nexport async function detectPackageManager(): Promise<PackageManager> {\n if (await fileExistsAsync(rootPath(\"package-lock.json\"))) {\n return \"npm\";\n }\n\n if (await fileExistsAsync(rootPath(\"yarn.lock\"))) {\n return \"yarn\";\n }\n\n if (await fileExistsAsync(rootPath(\"pnpm-lock.yaml\"))) {\n return \"pnpm\";\n }\n\n return \"npm\";\n}\n\n/**\n * The lockfile-syncing install command for the given manager. No package\n * arguments — `warlock update` rewrites the versions in package.json first,\n * then a plain install reconciles `node_modules` to match.\n */\nexport function getInstallCommand(packageManager: PackageManager): string {\n switch (packageManager) {\n case \"yarn\":\n return \"yarn install\";\n\n case \"pnpm\":\n return \"pnpm install\";\n\n default:\n return \"npm install\";\n }\n}\n"],"mappings":";;;;;;;;;;AAWA,eAAsB,uBAAgD;CACpE,IAAI,MAAM,gBAAgB,SAAS,mBAAmB,CAAC,GACrD,OAAO;CAGT,IAAI,MAAM,gBAAgB,SAAS,WAAW,CAAC,GAC7C,OAAO;CAGT,IAAI,MAAM,gBAAgB,SAAS,gBAAgB,CAAC,GAClD,OAAO;CAGT,OAAO;AACT;;;;;;AAOA,SAAgB,kBAAkB,gBAAwC;CACxE,QAAQ,gBAAR;EACE,KAAK,QACH,OAAO;EAET,KAAK,QACH,OAAO;EAET,SACE,OAAO;CACX;AACF"}
@@ -0,0 +1,134 @@
1
+ import { fetchLatestVersion } from "../utils/npm-registry.mjs";
2
+ import { rootPath } from "../utils/paths.mjs";
3
+ import { isNewerVersion } from "../utils/version-compare.mjs";
4
+ import "../utils/index.mjs";
5
+ import { detectPackageManager, getInstallCommand } from "./package-manager.mjs";
6
+ import { colors } from "@mongez/copper";
7
+ import { fileExistsAsync, getJsonFileAsync, putJsonFileAsync } from "@warlock.js/fs";
8
+ import { execSync } from "node:child_process";
9
+
10
+ //#region ../@warlock.js/core/src/updater/update-warlock-packages.ts
11
+ /** Scope prefix that identifies a Warlock framework package. */
12
+ const WARLOCK_SCOPE = "@warlock.js/";
13
+ /** The dependency maps in package.json we update, in display order. */
14
+ const DEPENDENCY_SECTIONS = ["dependencies", "devDependencies"];
15
+ /**
16
+ * Update every `@warlock.js/*` package listed in the project's root
17
+ * package.json to its latest published version, then reconcile `node_modules`
18
+ * via the detected package manager.
19
+ *
20
+ * The original range operator on each dependency (`^`, `~`, or an exact pin)
21
+ * is preserved; specs that aren't a plain semver — `workspace:*`, `*`,
22
+ * `latest`, git/file URLs — are left untouched. Only genuine upgrades are
23
+ * written, so re-running on an already-current project is a no-op.
24
+ */
25
+ async function updateWarlockPackages(options = {}) {
26
+ const runInstall = options.install ?? true;
27
+ const packageJsonPath = rootPath("package.json");
28
+ if (!await fileExistsAsync(packageJsonPath)) {
29
+ console.log(`${colors.red("✖")} No package.json found at the project root.`);
30
+ return;
31
+ }
32
+ const packageJson = await getJsonFileAsync(packageJsonPath);
33
+ const dependencies = collectWarlockDependencies(packageJson);
34
+ if (dependencies.length === 0) {
35
+ console.log(`${colors.yellow("⚠")} No @warlock.js packages found in package.json.`);
36
+ return;
37
+ }
38
+ console.log(`${colors.cyan("›")} Checking ${colors.bold(String(dependencies.length))} @warlock.js package(s) for updates…`);
39
+ const updates = resolvePackageUpdates(await resolveLatestVersions(dependencies));
40
+ if (updates.length === 0) {
41
+ console.log(`${colors.green("✓")} All @warlock.js packages are already up to date.`);
42
+ return;
43
+ }
44
+ applyUpdates(packageJson, updates);
45
+ await putJsonFileAsync(packageJsonPath, packageJson);
46
+ printUpdates(updates);
47
+ if (!runInstall) {
48
+ console.log(colors.dim("Skipped install (--no-install). Run your package manager to apply the changes."));
49
+ return;
50
+ }
51
+ await installDependencies();
52
+ }
53
+ /** Collect every `@warlock.js/*` dependency across the relevant sections. */
54
+ function collectWarlockDependencies(packageJson) {
55
+ const dependencies = [];
56
+ for (const section of DEPENDENCY_SECTIONS) {
57
+ const map = packageJson[section];
58
+ if (!map) continue;
59
+ for (const [name, current] of Object.entries(map)) if (name.startsWith(WARLOCK_SCOPE)) dependencies.push({
60
+ name,
61
+ section,
62
+ current
63
+ });
64
+ }
65
+ return dependencies;
66
+ }
67
+ /** Resolve each dependency's latest version from the registry in parallel. */
68
+ async function resolveLatestVersions(dependencies) {
69
+ return Promise.all(dependencies.map(async (dependency) => ({
70
+ ...dependency,
71
+ latest: await fetchLatestVersion(dependency.name)
72
+ })));
73
+ }
74
+ /**
75
+ * Turn resolved dependencies into the concrete set of version bumps to write.
76
+ * Pure (no I/O) so it can be unit-tested directly: skips lookups that failed,
77
+ * non-semver specs, and anything already at (or ahead of) the latest version,
78
+ * and preserves the original range operator on everything it does rewrite.
79
+ */
80
+ function resolvePackageUpdates(resolved) {
81
+ const updates = [];
82
+ for (const dependency of resolved) {
83
+ if (!dependency.latest) continue;
84
+ const parsed = parseSpec(dependency.current);
85
+ if (!parsed) continue;
86
+ if (!isNewerVersion(dependency.latest, parsed.version)) continue;
87
+ updates.push({
88
+ name: dependency.name,
89
+ section: dependency.section,
90
+ from: dependency.current,
91
+ to: `${parsed.operator}${dependency.latest}`
92
+ });
93
+ }
94
+ return updates;
95
+ }
96
+ /**
97
+ * Split a dependency spec into its range operator and concrete version.
98
+ * Returns `undefined` for specs we won't touch — `workspace:*`, `*`,
99
+ * `latest`, multi-part ranges, or git/file URLs — leaving them exactly as
100
+ * the author wrote them.
101
+ */
102
+ function parseSpec(spec) {
103
+ const match = spec.trim().match(/^(\^|~)?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/);
104
+ if (!match) return;
105
+ return {
106
+ operator: match[1] ?? "",
107
+ version: match[2]
108
+ };
109
+ }
110
+ /** Write the resolved bumps back into the package.json object in place. */
111
+ function applyUpdates(packageJson, updates) {
112
+ for (const update of updates) {
113
+ const map = packageJson[update.section];
114
+ if (map) map[update.name] = update.to;
115
+ }
116
+ }
117
+ /** Print the list of applied version bumps. */
118
+ function printUpdates(updates) {
119
+ console.log(`${colors.green("✓")} Updated ${colors.bold(String(updates.length))} package(s):`);
120
+ for (const update of updates) console.log(` ${colors.cyan(update.name)} ${colors.dim(update.from)} ${colors.dim("→")} ${colors.greenBright(update.to)}`);
121
+ }
122
+ /** Reconcile node_modules to the rewritten package.json via the project PM. */
123
+ async function installDependencies() {
124
+ const installCommand = getInstallCommand(await detectPackageManager());
125
+ console.log(`${colors.cyan("›")} Running ${colors.bold(installCommand)}…`);
126
+ execSync(installCommand, {
127
+ cwd: process.cwd(),
128
+ stdio: "inherit"
129
+ });
130
+ }
131
+
132
+ //#endregion
133
+ export { updateWarlockPackages };
134
+ //# sourceMappingURL=update-warlock-packages.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-warlock-packages.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/updater/update-warlock-packages.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { fileExistsAsync, getJsonFileAsync, putJsonFileAsync } from \"@warlock.js/fs\";\nimport { execSync } from \"node:child_process\";\nimport { rootPath } from \"../utils\";\nimport { fetchLatestVersion } from \"../utils/npm-registry\";\nimport { isNewerVersion } from \"../utils/version-compare\";\nimport { detectPackageManager, getInstallCommand } from \"./package-manager\";\n\n/** Scope prefix that identifies a Warlock framework package. */\nconst WARLOCK_SCOPE = \"@warlock.js/\";\n\n/** The dependency maps in package.json we update, in display order. */\nconst DEPENDENCY_SECTIONS = [\"dependencies\", \"devDependencies\"] as const;\n\ntype DependencySection = (typeof DEPENDENCY_SECTIONS)[number];\n\ntype DependencyMap = Record<string, string>;\n\ntype PackageJson = Record<string, unknown> & {\n dependencies?: DependencyMap;\n devDependencies?: DependencyMap;\n};\n\n/** A `@warlock.js/*` dependency found in package.json. */\ntype WarlockDependency = {\n name: string;\n section: DependencySection;\n /** The current spec exactly as written, e.g. `^4.2.0`. */\n current: string;\n};\n\n/** A dependency whose latest version has been resolved from the registry. */\nexport type ResolvedDependency = WarlockDependency & {\n /** Latest version from npm, or `undefined` when the lookup failed. */\n latest: string | undefined;\n};\n\n/** A concrete version bump to write back into package.json. */\nexport type PackageUpdate = {\n name: string;\n section: DependencySection;\n /** Existing spec, e.g. `^4.2.0`. */\n from: string;\n /** New spec with the original range operator preserved, e.g. `^4.3.0`. */\n to: string;\n};\n\n/** Options accepted by {@link updateWarlockPackages}. */\nexport type UpdateWarlockPackagesOptions = {\n /** Run the package manager install after rewriting versions (default true). */\n install?: boolean;\n};\n\n/**\n * Update every `@warlock.js/*` package listed in the project's root\n * package.json to its latest published version, then reconcile `node_modules`\n * via the detected package manager.\n *\n * The original range operator on each dependency (`^`, `~`, or an exact pin)\n * is preserved; specs that aren't a plain semver — `workspace:*`, `*`,\n * `latest`, git/file URLs — are left untouched. Only genuine upgrades are\n * written, so re-running on an already-current project is a no-op.\n */\nexport async function updateWarlockPackages(\n options: UpdateWarlockPackagesOptions = {},\n): Promise<void> {\n const runInstall = options.install ?? true;\n const packageJsonPath = rootPath(\"package.json\");\n\n if (!(await fileExistsAsync(packageJsonPath))) {\n console.log(`${colors.red(\"✖\")} No package.json found at the project root.`);\n return;\n }\n\n const packageJson = (await getJsonFileAsync(packageJsonPath)) as PackageJson;\n const dependencies = collectWarlockDependencies(packageJson);\n\n if (dependencies.length === 0) {\n console.log(`${colors.yellow(\"⚠\")} No @warlock.js packages found in package.json.`);\n return;\n }\n\n console.log(\n `${colors.cyan(\"›\")} Checking ${colors.bold(String(dependencies.length))} ` +\n `@warlock.js package(s) for updates…`,\n );\n\n const resolved = await resolveLatestVersions(dependencies);\n const updates = resolvePackageUpdates(resolved);\n\n if (updates.length === 0) {\n console.log(`${colors.green(\"✓\")} All @warlock.js packages are already up to date.`);\n return;\n }\n\n applyUpdates(packageJson, updates);\n await putJsonFileAsync(packageJsonPath, packageJson);\n\n printUpdates(updates);\n\n if (!runInstall) {\n console.log(\n colors.dim(\"Skipped install (--no-install). Run your package manager to apply the changes.\"),\n );\n return;\n }\n\n await installDependencies();\n}\n\n/** Collect every `@warlock.js/*` dependency across the relevant sections. */\nfunction collectWarlockDependencies(packageJson: PackageJson): WarlockDependency[] {\n const dependencies: WarlockDependency[] = [];\n\n for (const section of DEPENDENCY_SECTIONS) {\n const map = packageJson[section];\n\n if (!map) {\n continue;\n }\n\n for (const [name, current] of Object.entries(map)) {\n if (name.startsWith(WARLOCK_SCOPE)) {\n dependencies.push({ name, section, current });\n }\n }\n }\n\n return dependencies;\n}\n\n/** Resolve each dependency's latest version from the registry in parallel. */\nasync function resolveLatestVersions(\n dependencies: WarlockDependency[],\n): Promise<ResolvedDependency[]> {\n return Promise.all(\n dependencies.map(async (dependency) => ({\n ...dependency,\n latest: await fetchLatestVersion(dependency.name),\n })),\n );\n}\n\n/**\n * Turn resolved dependencies into the concrete set of version bumps to write.\n * Pure (no I/O) so it can be unit-tested directly: skips lookups that failed,\n * non-semver specs, and anything already at (or ahead of) the latest version,\n * and preserves the original range operator on everything it does rewrite.\n */\nexport function resolvePackageUpdates(resolved: ResolvedDependency[]): PackageUpdate[] {\n const updates: PackageUpdate[] = [];\n\n for (const dependency of resolved) {\n if (!dependency.latest) {\n continue;\n }\n\n const parsed = parseSpec(dependency.current);\n\n if (!parsed) {\n continue;\n }\n\n if (!isNewerVersion(dependency.latest, parsed.version)) {\n continue;\n }\n\n updates.push({\n name: dependency.name,\n section: dependency.section,\n from: dependency.current,\n to: `${parsed.operator}${dependency.latest}`,\n });\n }\n\n return updates;\n}\n\n/**\n * Split a dependency spec into its range operator and concrete version.\n * Returns `undefined` for specs we won't touch — `workspace:*`, `*`,\n * `latest`, multi-part ranges, or git/file URLs — leaving them exactly as\n * the author wrote them.\n */\nfunction parseSpec(spec: string): { operator: string; version: string } | undefined {\n const match = spec.trim().match(/^(\\^|~)?(\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?)$/);\n\n if (!match) {\n return undefined;\n }\n\n return { operator: match[1] ?? \"\", version: match[2] };\n}\n\n/** Write the resolved bumps back into the package.json object in place. */\nfunction applyUpdates(packageJson: PackageJson, updates: PackageUpdate[]): void {\n for (const update of updates) {\n const map = packageJson[update.section];\n\n if (map) {\n map[update.name] = update.to;\n }\n }\n}\n\n/** Print the list of applied version bumps. */\nfunction printUpdates(updates: PackageUpdate[]): void {\n console.log(`${colors.green(\"✓\")} Updated ${colors.bold(String(updates.length))} package(s):`);\n\n for (const update of updates) {\n console.log(\n ` ${colors.cyan(update.name)} ` +\n `${colors.dim(update.from)} ${colors.dim(\"→\")} ${colors.greenBright(update.to)}`,\n );\n }\n}\n\n/** Reconcile node_modules to the rewritten package.json via the project PM. */\nasync function installDependencies(): Promise<void> {\n const packageManager = await detectPackageManager();\n const installCommand = getInstallCommand(packageManager);\n\n console.log(`${colors.cyan(\"›\")} Running ${colors.bold(installCommand)}…`);\n\n execSync(installCommand, { cwd: process.cwd(), stdio: \"inherit\" });\n}\n"],"mappings":";;;;;;;;;;;AASA,MAAM,gBAAgB;;AAGtB,MAAM,sBAAsB,CAAC,gBAAgB,iBAAiB;;;;;;;;;;;AAmD9D,eAAsB,sBACpB,UAAwC,CAAC,GAC1B;CACf,MAAM,aAAa,QAAQ,WAAW;CACtC,MAAM,kBAAkB,SAAS,cAAc;CAE/C,IAAI,CAAE,MAAM,gBAAgB,eAAe,GAAI;EAC7C,QAAQ,IAAI,GAAG,OAAO,IAAI,GAAG,EAAE,4CAA4C;EAC3E;CACF;CAEA,MAAM,cAAe,MAAM,iBAAiB,eAAe;CAC3D,MAAM,eAAe,2BAA2B,WAAW;CAE3D,IAAI,aAAa,WAAW,GAAG;EAC7B,QAAQ,IAAI,GAAG,OAAO,OAAO,GAAG,EAAE,gDAAgD;EAClF;CACF;CAEA,QAAQ,IACN,GAAG,OAAO,KAAK,GAAG,EAAE,YAAY,OAAO,KAAK,OAAO,aAAa,MAAM,CAAC,EAAE,qCAE3E;CAGA,MAAM,UAAU,sBAAsB,MADf,sBAAsB,YAAY,CACX;CAE9C,IAAI,QAAQ,WAAW,GAAG;EACxB,QAAQ,IAAI,GAAG,OAAO,MAAM,GAAG,EAAE,kDAAkD;EACnF;CACF;CAEA,aAAa,aAAa,OAAO;CACjC,MAAM,iBAAiB,iBAAiB,WAAW;CAEnD,aAAa,OAAO;CAEpB,IAAI,CAAC,YAAY;EACf,QAAQ,IACN,OAAO,IAAI,gFAAgF,CAC7F;EACA;CACF;CAEA,MAAM,oBAAoB;AAC5B;;AAGA,SAAS,2BAA2B,aAA+C;CACjF,MAAM,eAAoC,CAAC;CAE3C,KAAK,MAAM,WAAW,qBAAqB;EACzC,MAAM,MAAM,YAAY;EAExB,IAAI,CAAC,KACH;EAGF,KAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,GAAG,GAC9C,IAAI,KAAK,WAAW,aAAa,GAC/B,aAAa,KAAK;GAAE;GAAM;GAAS;EAAQ,CAAC;CAGlD;CAEA,OAAO;AACT;;AAGA,eAAe,sBACb,cAC+B;CAC/B,OAAO,QAAQ,IACb,aAAa,IAAI,OAAO,gBAAgB;EACtC,GAAG;EACH,QAAQ,MAAM,mBAAmB,WAAW,IAAI;CAClD,EAAE,CACJ;AACF;;;;;;;AAQA,SAAgB,sBAAsB,UAAiD;CACrF,MAAM,UAA2B,CAAC;CAElC,KAAK,MAAM,cAAc,UAAU;EACjC,IAAI,CAAC,WAAW,QACd;EAGF,MAAM,SAAS,UAAU,WAAW,OAAO;EAE3C,IAAI,CAAC,QACH;EAGF,IAAI,CAAC,eAAe,WAAW,QAAQ,OAAO,OAAO,GACnD;EAGF,QAAQ,KAAK;GACX,MAAM,WAAW;GACjB,SAAS,WAAW;GACpB,MAAM,WAAW;GACjB,IAAI,GAAG,OAAO,WAAW,WAAW;EACtC,CAAC;CACH;CAEA,OAAO;AACT;;;;;;;AAQA,SAAS,UAAU,MAAiE;CAClF,MAAM,QAAQ,KAAK,KAAK,CAAC,CAAC,MAAM,8CAA8C;CAE9E,IAAI,CAAC,OACH;CAGF,OAAO;EAAE,UAAU,MAAM,MAAM;EAAI,SAAS,MAAM;CAAG;AACvD;;AAGA,SAAS,aAAa,aAA0B,SAAgC;CAC9E,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,MAAM,YAAY,OAAO;EAE/B,IAAI,KACF,IAAI,OAAO,QAAQ,OAAO;CAE9B;AACF;;AAGA,SAAS,aAAa,SAAgC;CACpD,QAAQ,IAAI,GAAG,OAAO,MAAM,GAAG,EAAE,WAAW,OAAO,KAAK,OAAO,QAAQ,MAAM,CAAC,EAAE,aAAa;CAE7F,KAAK,MAAM,UAAU,SACnB,QAAQ,IACN,MAAM,OAAO,KAAK,OAAO,IAAI,EAAE,IAC1B,OAAO,IAAI,OAAO,IAAI,EAAE,GAAG,OAAO,IAAI,GAAG,EAAE,GAAG,OAAO,YAAY,OAAO,EAAE,GACjF;AAEJ;;AAGA,eAAe,sBAAqC;CAElD,MAAM,iBAAiB,kBAAkB,MADZ,qBAAqB,CACK;CAEvD,QAAQ,IAAI,GAAG,OAAO,KAAK,GAAG,EAAE,WAAW,OAAO,KAAK,cAAc,EAAE,EAAE;CAEzE,SAAS,gBAAgB;EAAE,KAAK,QAAQ,IAAI;EAAG,OAAO;CAAU,CAAC;AACnE"}
@@ -2,10 +2,12 @@ import { LocalizedObject, getLocalized } from "./get-localized.mjs";
2
2
  import { Environment, environment, setEnvironment } from "./environment.mjs";
3
3
  import { appLog } from "./app-log.mjs";
4
4
  import { DatabaseLog, DatabaseLogOptions } from "./database-log.mjs";
5
+ import { fetchLatestVersion } from "./npm-registry.mjs";
5
6
  import { appPath, cachePath, configPath, logsPath, paths, publicPath, rootPath, sanitizePath, srcPath, storagePath, tempPath, uploadsPath, warlockPath } from "./paths.mjs";
6
7
  import { promiseAllObject } from "./promise-all-object.mjs";
7
8
  import { Queue } from "./queue.mjs";
8
9
  import { sleep } from "./sleep.mjs";
9
10
  import { sluggable } from "./sluggable.mjs";
10
11
  import { toJson } from "./to-json.mjs";
11
- import { assetsUrl, publicUrl, setBaseUrl, uploadsUrl, url } from "./urls.mjs";
12
+ import { assetsUrl, publicUrl, setBaseUrl, uploadsUrl, url } from "./urls.mjs";
13
+ import { isNewerVersion } from "./version-compare.mjs";
@@ -2,6 +2,7 @@ import { appLog } from "./app-log.mjs";
2
2
  import { DatabaseLog } from "./database-log.mjs";
3
3
  import { environment, setEnvironment } from "./environment.mjs";
4
4
  import { getLocalized } from "./get-localized.mjs";
5
+ import { fetchLatestVersion } from "./npm-registry.mjs";
5
6
  import { appPath, cachePath, configPath, logsPath, paths, publicPath, rootPath, sanitizePath, srcPath, storagePath, tempPath, uploadsPath, warlockPath } from "./paths.mjs";
6
7
  import { promiseAllObject } from "./promise-all-object.mjs";
7
8
  import { Queue } from "./queue.mjs";
@@ -9,5 +10,6 @@ import { sleep } from "./sleep.mjs";
9
10
  import { sluggable } from "./sluggable.mjs";
10
11
  import { toJson } from "./to-json.mjs";
11
12
  import { assetsUrl, publicUrl, setBaseUrl, uploadsUrl, url } from "./urls.mjs";
13
+ import { isNewerVersion } from "./version-compare.mjs";
12
14
 
13
15
  export { };
@@ -0,0 +1,24 @@
1
+ //#region ../@warlock.js/core/src/utils/npm-registry.d.ts
2
+ /**
3
+ * Minimal npm-registry access for the framework's self-update tooling
4
+ * (the dev-server update notice + the `warlock update` command).
5
+ *
6
+ * No SDK and no caching layer — just the public registry over `fetch`.
7
+ * Every lookup is best-effort: any failure resolves to `undefined` so a
8
+ * registry hiccup can never break the dev server or the CLI.
9
+ */
10
+ /**
11
+ * Fetch the latest published version of a package from the npm registry.
12
+ *
13
+ * Hits the abbreviated `/<package>/latest` endpoint, which returns only the
14
+ * latest dist-tag manifest (not the full packument). Resolves to `undefined`
15
+ * on any failure — offline, timeout, non-200 response, or malformed payload —
16
+ * and never throws, so callers can stay fail-silent.
17
+ *
18
+ * @param packageName Fully-qualified npm name, e.g. `@warlock.js/core`.
19
+ * @param timeoutMs Abort budget in milliseconds (defaults to 2500).
20
+ */
21
+ declare function fetchLatestVersion(packageName: string, timeoutMs?: number): Promise<string | undefined>;
22
+ //#endregion
23
+ export { fetchLatestVersion };
24
+ //# sourceMappingURL=npm-registry.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npm-registry.d.mts","names":[],"sources":["../../../../../../../@warlock.js/core/src/utils/npm-registry.ts"],"mappings":";;AAwBA;;;;;;;;AAGU;;;;;;;;;;iBAHY,kBAAA,CACpB,WAAA,UACA,SAAA,YACC,OAAO"}
@@ -0,0 +1,43 @@
1
+ //#region ../@warlock.js/core/src/utils/npm-registry.ts
2
+ /**
3
+ * Minimal npm-registry access for the framework's self-update tooling
4
+ * (the dev-server update notice + the `warlock update` command).
5
+ *
6
+ * No SDK and no caching layer — just the public registry over `fetch`.
7
+ * Every lookup is best-effort: any failure resolves to `undefined` so a
8
+ * registry hiccup can never break the dev server or the CLI.
9
+ */
10
+ const REGISTRY_BASE_URL = "https://registry.npmjs.org";
11
+ const DEFAULT_TIMEOUT_MS = 2500;
12
+ /**
13
+ * Fetch the latest published version of a package from the npm registry.
14
+ *
15
+ * Hits the abbreviated `/<package>/latest` endpoint, which returns only the
16
+ * latest dist-tag manifest (not the full packument). Resolves to `undefined`
17
+ * on any failure — offline, timeout, non-200 response, or malformed payload —
18
+ * and never throws, so callers can stay fail-silent.
19
+ *
20
+ * @param packageName Fully-qualified npm name, e.g. `@warlock.js/core`.
21
+ * @param timeoutMs Abort budget in milliseconds (defaults to 2500).
22
+ */
23
+ async function fetchLatestVersion(packageName, timeoutMs = DEFAULT_TIMEOUT_MS) {
24
+ const controller = new AbortController();
25
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
26
+ try {
27
+ const response = await fetch(`${REGISTRY_BASE_URL}/${packageName}/latest`, {
28
+ signal: controller.signal,
29
+ headers: { accept: "application/json" }
30
+ });
31
+ if (!response.ok) return;
32
+ const data = await response.json();
33
+ return typeof data.version === "string" ? data.version : void 0;
34
+ } catch {
35
+ return;
36
+ } finally {
37
+ clearTimeout(timer);
38
+ }
39
+ }
40
+
41
+ //#endregion
42
+ export { fetchLatestVersion };
43
+ //# sourceMappingURL=npm-registry.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npm-registry.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/utils/npm-registry.ts"],"sourcesContent":["/**\n * Minimal npm-registry access for the framework's self-update tooling\n * (the dev-server update notice + the `warlock update` command).\n *\n * No SDK and no caching layer — just the public registry over `fetch`.\n * Every lookup is best-effort: any failure resolves to `undefined` so a\n * registry hiccup can never break the dev server or the CLI.\n */\n\nconst REGISTRY_BASE_URL = \"https://registry.npmjs.org\";\n\nconst DEFAULT_TIMEOUT_MS = 2500;\n\n/**\n * Fetch the latest published version of a package from the npm registry.\n *\n * Hits the abbreviated `/<package>/latest` endpoint, which returns only the\n * latest dist-tag manifest (not the full packument). Resolves to `undefined`\n * on any failure — offline, timeout, non-200 response, or malformed payload —\n * and never throws, so callers can stay fail-silent.\n *\n * @param packageName Fully-qualified npm name, e.g. `@warlock.js/core`.\n * @param timeoutMs Abort budget in milliseconds (defaults to 2500).\n */\nexport async function fetchLatestVersion(\n packageName: string,\n timeoutMs: number = DEFAULT_TIMEOUT_MS,\n): Promise<string | undefined> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(`${REGISTRY_BASE_URL}/${packageName}/latest`, {\n signal: controller.signal,\n headers: { accept: \"application/json\" },\n });\n\n if (!response.ok) {\n return undefined;\n }\n\n const data = (await response.json()) as { version?: unknown };\n\n return typeof data.version === \"string\" ? data.version : undefined;\n } catch {\n // Best-effort: offline, DNS failure, abort/timeout, or bad JSON — all \"unknown\".\n return undefined;\n } finally {\n clearTimeout(timer);\n }\n}\n"],"mappings":";;;;;;;;;AASA,MAAM,oBAAoB;AAE1B,MAAM,qBAAqB;;;;;;;;;;;;AAa3B,eAAsB,mBACpB,aACA,YAAoB,oBACS;CAC7B,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,QAAQ,iBAAiB,WAAW,MAAM,GAAG,SAAS;CAE5D,IAAI;EACF,MAAM,WAAW,MAAM,MAAM,GAAG,kBAAkB,GAAG,YAAY,UAAU;GACzE,QAAQ,WAAW;GACnB,SAAS,EAAE,QAAQ,mBAAmB;EACxC,CAAC;EAED,IAAI,CAAC,SAAS,IACZ;EAGF,MAAM,OAAQ,MAAM,SAAS,KAAK;EAElC,OAAO,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU;CAC3D,QAAQ;EAEN;CACF,UAAU;EACR,aAAa,KAAK;CACpB;AACF"}
@@ -0,0 +1,20 @@
1
+ //#region ../@warlock.js/core/src/utils/version-compare.d.ts
2
+ /**
3
+ * Zero-dependency semver comparison — just enough for the framework's
4
+ * self-update tooling (the dev-server update notice + the `warlock update`
5
+ * command).
6
+ *
7
+ * We deliberately avoid pulling in a semver library: the only question these
8
+ * callers ask is "is the published version newer than what's installed?".
9
+ */
10
+ /**
11
+ * Return `true` when `latest` is a strictly newer semantic version than
12
+ * `current`. Compares the numeric `major.minor.patch` first, then treats a
13
+ * stable release as newer than a prerelease of the same core
14
+ * (`1.2.0` > `1.2.0-beta`). Unparseable input yields `false` — we never nag
15
+ * on a version string we can't understand.
16
+ */
17
+ declare function isNewerVersion(latest: string, current: string): boolean;
18
+ //#endregion
19
+ export { isNewerVersion };
20
+ //# sourceMappingURL=version-compare.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-compare.d.mts","names":[],"sources":["../../../../../../../@warlock.js/core/src/utils/version-compare.ts"],"mappings":";;AAkGA;;;;AAA8D;;;;;;;;;;iBAA9C,cAAA,CAAe,MAAA,UAAgB,OAAe"}
@@ -0,0 +1,70 @@
1
+ //#region ../@warlock.js/core/src/utils/version-compare.ts
2
+ /**
3
+ * Parse a semver string into its numeric core + prerelease identifiers.
4
+ * Tolerates a leading `v` and ignores build metadata (`+…`). Returns
5
+ * `undefined` when the core isn't exactly three non-negative integers.
6
+ */
7
+ function parseVersion(version) {
8
+ const [coreAndPrerelease] = version.trim().replace(/^v/i, "").split("+");
9
+ const [core, ...prereleaseParts] = coreAndPrerelease.split("-");
10
+ const segments = core.split(".");
11
+ if (segments.length !== 3) return;
12
+ const numbers = segments.map((segment) => Number(segment));
13
+ if (numbers.some((value) => !Number.isInteger(value) || value < 0)) return;
14
+ const prerelease = prereleaseParts.length > 0 ? prereleaseParts.join("-").split(".") : [];
15
+ return {
16
+ core: [
17
+ numbers[0],
18
+ numbers[1],
19
+ numbers[2]
20
+ ],
21
+ prerelease
22
+ };
23
+ }
24
+ /**
25
+ * Compare two prerelease identifier lists per semver rules: numeric
26
+ * identifiers compare numerically, alphanumeric ones lexically, a numeric
27
+ * identifier ranks below an alphanumeric one, and a longer list wins when
28
+ * otherwise equal. Returns a negative / zero / positive number.
29
+ */
30
+ function comparePrerelease(left, right) {
31
+ const length = Math.max(left.length, right.length);
32
+ for (let index = 0; index < length; index++) {
33
+ const a = left[index];
34
+ const b = right[index];
35
+ if (a === void 0) return -1;
36
+ if (b === void 0) return 1;
37
+ const aIsNumeric = /^\d+$/.test(a);
38
+ const bIsNumeric = /^\d+$/.test(b);
39
+ if (aIsNumeric && bIsNumeric) {
40
+ const diff = Number(a) - Number(b);
41
+ if (diff !== 0) return diff;
42
+ continue;
43
+ }
44
+ if (aIsNumeric !== bIsNumeric) return aIsNumeric ? -1 : 1;
45
+ if (a !== b) return a < b ? -1 : 1;
46
+ }
47
+ return 0;
48
+ }
49
+ /**
50
+ * Return `true` when `latest` is a strictly newer semantic version than
51
+ * `current`. Compares the numeric `major.minor.patch` first, then treats a
52
+ * stable release as newer than a prerelease of the same core
53
+ * (`1.2.0` > `1.2.0-beta`). Unparseable input yields `false` — we never nag
54
+ * on a version string we can't understand.
55
+ */
56
+ function isNewerVersion(latest, current) {
57
+ const latestParsed = parseVersion(latest);
58
+ const currentParsed = parseVersion(current);
59
+ if (!latestParsed || !currentParsed) return false;
60
+ for (let index = 0; index < 3; index++) if (latestParsed.core[index] !== currentParsed.core[index]) return latestParsed.core[index] > currentParsed.core[index];
61
+ const latestIsStable = latestParsed.prerelease.length === 0;
62
+ const currentIsStable = currentParsed.prerelease.length === 0;
63
+ if (latestIsStable && currentIsStable) return false;
64
+ if (latestIsStable !== currentIsStable) return latestIsStable;
65
+ return comparePrerelease(latestParsed.prerelease, currentParsed.prerelease) > 0;
66
+ }
67
+
68
+ //#endregion
69
+ export { isNewerVersion };
70
+ //# sourceMappingURL=version-compare.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-compare.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/utils/version-compare.ts"],"sourcesContent":["/**\n * Zero-dependency semver comparison — just enough for the framework's\n * self-update tooling (the dev-server update notice + the `warlock update`\n * command).\n *\n * We deliberately avoid pulling in a semver library: the only question these\n * callers ask is \"is the published version newer than what's installed?\".\n */\n\ntype ParsedVersion = {\n /** Numeric core as `[major, minor, patch]`. */\n core: [number, number, number];\n /** Dot-separated prerelease identifiers; empty for a stable release. */\n prerelease: string[];\n};\n\n/**\n * Parse a semver string into its numeric core + prerelease identifiers.\n * Tolerates a leading `v` and ignores build metadata (`+…`). Returns\n * `undefined` when the core isn't exactly three non-negative integers.\n */\nfunction parseVersion(version: string): ParsedVersion | undefined {\n const cleaned = version.trim().replace(/^v/i, \"\");\n const [coreAndPrerelease] = cleaned.split(\"+\"); // drop build metadata\n const [core, ...prereleaseParts] = coreAndPrerelease.split(\"-\");\n const segments = core.split(\".\");\n\n if (segments.length !== 3) {\n return undefined;\n }\n\n const numbers = segments.map((segment) => Number(segment));\n\n if (numbers.some((value) => !Number.isInteger(value) || value < 0)) {\n return undefined;\n }\n\n const prerelease = prereleaseParts.length > 0 ? prereleaseParts.join(\"-\").split(\".\") : [];\n\n return {\n core: [numbers[0], numbers[1], numbers[2]],\n prerelease,\n };\n}\n\n/**\n * Compare two prerelease identifier lists per semver rules: numeric\n * identifiers compare numerically, alphanumeric ones lexically, a numeric\n * identifier ranks below an alphanumeric one, and a longer list wins when\n * otherwise equal. Returns a negative / zero / positive number.\n */\nfunction comparePrerelease(left: string[], right: string[]): number {\n const length = Math.max(left.length, right.length);\n\n for (let index = 0; index < length; index++) {\n const a = left[index];\n const b = right[index];\n\n if (a === undefined) {\n return -1;\n }\n\n if (b === undefined) {\n return 1;\n }\n\n const aIsNumeric = /^\\d+$/.test(a);\n const bIsNumeric = /^\\d+$/.test(b);\n\n if (aIsNumeric && bIsNumeric) {\n const diff = Number(a) - Number(b);\n\n if (diff !== 0) {\n return diff;\n }\n\n continue;\n }\n\n if (aIsNumeric !== bIsNumeric) {\n return aIsNumeric ? -1 : 1;\n }\n\n if (a !== b) {\n return a < b ? -1 : 1;\n }\n }\n\n return 0;\n}\n\n/**\n * Return `true` when `latest` is a strictly newer semantic version than\n * `current`. Compares the numeric `major.minor.patch` first, then treats a\n * stable release as newer than a prerelease of the same core\n * (`1.2.0` > `1.2.0-beta`). Unparseable input yields `false` — we never nag\n * on a version string we can't understand.\n */\nexport function isNewerVersion(latest: string, current: string): boolean {\n const latestParsed = parseVersion(latest);\n const currentParsed = parseVersion(current);\n\n if (!latestParsed || !currentParsed) {\n return false;\n }\n\n for (let index = 0; index < 3; index++) {\n if (latestParsed.core[index] !== currentParsed.core[index]) {\n return latestParsed.core[index] > currentParsed.core[index];\n }\n }\n\n // Equal numeric core — order by prerelease (a stable release beats a prerelease).\n const latestIsStable = latestParsed.prerelease.length === 0;\n const currentIsStable = currentParsed.prerelease.length === 0;\n\n if (latestIsStable && currentIsStable) {\n return false;\n }\n\n if (latestIsStable !== currentIsStable) {\n return latestIsStable;\n }\n\n return comparePrerelease(latestParsed.prerelease, currentParsed.prerelease) > 0;\n}\n"],"mappings":";;;;;;AAqBA,SAAS,aAAa,SAA4C;CAEhE,MAAM,CAAC,qBADS,QAAQ,KAAK,CAAC,CAAC,QAAQ,OAAO,EACZ,CAAC,CAAC,MAAM,GAAG;CAC7C,MAAM,CAAC,MAAM,GAAG,mBAAmB,kBAAkB,MAAM,GAAG;CAC9D,MAAM,WAAW,KAAK,MAAM,GAAG;CAE/B,IAAI,SAAS,WAAW,GACtB;CAGF,MAAM,UAAU,SAAS,KAAK,YAAY,OAAO,OAAO,CAAC;CAEzD,IAAI,QAAQ,MAAM,UAAU,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,CAAC,GAC/D;CAGF,MAAM,aAAa,gBAAgB,SAAS,IAAI,gBAAgB,KAAK,GAAG,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC;CAExF,OAAO;EACL,MAAM;GAAC,QAAQ;GAAI,QAAQ;GAAI,QAAQ;EAAE;EACzC;CACF;AACF;;;;;;;AAQA,SAAS,kBAAkB,MAAgB,OAAyB;CAClE,MAAM,SAAS,KAAK,IAAI,KAAK,QAAQ,MAAM,MAAM;CAEjD,KAAK,IAAI,QAAQ,GAAG,QAAQ,QAAQ,SAAS;EAC3C,MAAM,IAAI,KAAK;EACf,MAAM,IAAI,MAAM;EAEhB,IAAI,MAAM,QACR,OAAO;EAGT,IAAI,MAAM,QACR,OAAO;EAGT,MAAM,aAAa,QAAQ,KAAK,CAAC;EACjC,MAAM,aAAa,QAAQ,KAAK,CAAC;EAEjC,IAAI,cAAc,YAAY;GAC5B,MAAM,OAAO,OAAO,CAAC,IAAI,OAAO,CAAC;GAEjC,IAAI,SAAS,GACX,OAAO;GAGT;EACF;EAEA,IAAI,eAAe,YACjB,OAAO,aAAa,KAAK;EAG3B,IAAI,MAAM,GACR,OAAO,IAAI,IAAI,KAAK;CAExB;CAEA,OAAO;AACT;;;;;;;;AASA,SAAgB,eAAe,QAAgB,SAA0B;CACvE,MAAM,eAAe,aAAa,MAAM;CACxC,MAAM,gBAAgB,aAAa,OAAO;CAE1C,IAAI,CAAC,gBAAgB,CAAC,eACpB,OAAO;CAGT,KAAK,IAAI,QAAQ,GAAG,QAAQ,GAAG,SAC7B,IAAI,aAAa,KAAK,WAAW,cAAc,KAAK,QAClD,OAAO,aAAa,KAAK,SAAS,cAAc,KAAK;CAKzD,MAAM,iBAAiB,aAAa,WAAW,WAAW;CAC1D,MAAM,kBAAkB,cAAc,WAAW,WAAW;CAE5D,IAAI,kBAAkB,iBACpB,OAAO;CAGT,IAAI,mBAAmB,iBACrB,OAAO;CAGT,OAAO,kBAAkB,aAAa,YAAY,cAAc,UAAU,IAAI;AAChF"}
@@ -82,6 +82,15 @@ type WarlockConfig = {
82
82
  * @default true
83
83
  */
84
84
  generateTypings?: boolean;
85
+ /**
86
+ * Check npm for a newer `@warlock.js/core` release when the dev server
87
+ * starts and print a one-line notice if one is available. Because the
88
+ * family is versioned in lockstep, core stands in for every package.
89
+ * Best-effort and non-blocking; automatically skipped in CI and in
90
+ * non-interactive (non-TTY) shells.
91
+ * @default true
92
+ */
93
+ checkForUpdates?: boolean;
85
94
  /**
86
95
  * Debug aid for the (always-on) persisted transpile cache. Names cache
87
96
  * files `<slug>.<hash>.js` (last 3 source path segments) and appends a
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../@warlock.js/core/src/warlock-config/types.ts"],"mappings":";;;;;;;AASA;;;KAAY,aAAA;EAuES;;;EAnEnB,MAAA;IACE,IAAA;IACA,IAAA;IACA,cAAA;EAAA;EAAA;;;EAMF,KAAA;IAkBE;;;;;IAZA,YAAA;IAmCA;;;;;IA7BA,OAAA;IA6DA;;;;;IAvDA,MAAA;IA+FA;;;AAOO;;IAhGP,SAAA;EAAA;;;;EAMF,GAAA;IACE,QAAA,GAAW,UAAA;EAAA;;;;EAMb,SAAA;;;;IAIE,KAAA;;;;;;MAME,OAAA;;;;;;MAMA,OAAA;IAAA;;;;IAKF,cAAA,GAAiB,yBAAA;;;;;IAKjB,eAAA;;;;;;;;;;IAUA,mBAAA;EAAA;;;;EAMF,QAAA;;;;;;;;;;;;;;;;;;IAkBE,UAAA,GAAa,KAAA,CAAM,oBAAA;EAAA;;;;;;;EASrB,OAAA;;;;;;;IAOE,OAAA;;;;;;IAOA,OAAA;EAAA;AAAA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../@warlock.js/core/src/warlock-config/types.ts"],"mappings":";;;;;;;AASA;;;KAAY,aAAA;EAuES;;;EAnEnB,MAAA;IACE,IAAA;IACA,IAAA;IACA,cAAA;EAAA;EAAA;;;EAMF,KAAA;IAkBE;;;;;IAZA,YAAA;IAmCA;;;;;IA7BA,OAAA;IA4DA;;;;;IAtDA,MAAA;IAiGF;;;;AAcS;IAzGP,SAAA;EAAA;;;;EAMF,GAAA;IACE,QAAA,GAAW,UAAA;EAAA;;;;EAMb,SAAA;;;;IAIE,KAAA;;;;;;MAME,OAAA;;;;;;MAMA,OAAA;IAAA;;;;IAKF,cAAA,GAAiB,yBAAA;;;;;IAKjB,eAAA;;;;;;;;;IASA,eAAA;;;;;;;;;;IAUA,mBAAA;EAAA;;;;EAMF,QAAA;;;;;;;;;;;;;;;;;;IAkBE,UAAA,GAAa,KAAA,CAAM,oBAAA;EAAA;;;;;;;EASrB,OAAA;;;;;;;IAOE,OAAA;;;;;;IAOA,OAAA;EAAA;AAAA"}
package/llms-full.txt CHANGED
@@ -3043,6 +3043,7 @@ export default defineConfig({
3043
3043
  exclude: ["**/node_modules/**", "**/dist/**", "**/.warlock/**", "**/.git/**"],
3044
3044
  },
3045
3045
  generateTypings: true, // background type generation
3046
+ checkForUpdates: true, // notify on a newer @warlock.js/core at dev start
3046
3047
  healthCheckers: [...] /* or false */,
3047
3048
  transpileCacheDebug: false, // name cache files <slug>.<hash>.js w/ // @source markers
3048
3049
  },
@@ -3053,6 +3054,7 @@ export default defineConfig({
3053
3054
  - **`generateTypings`** — turn off if you're committing generated typings and don't want them rewritten on every boot. The `--skip-typings` flag is the per-run version.
3054
3055
  - **`healthCheckers`** — custom file health checker contracts (or `false` to disable). The `--skip-health` flag is the per-run version.
3055
3056
  - **`transpileCacheDebug`** — diagnostic only. Names `.warlock/transpile/*.js` files `<slug>.<hash>.js` and appends `// @source <path>` markers so you can eyeball which cache entry came from which source. Leave off in normal use.
3057
+ - **`checkForUpdates`** — on `warlock dev` start, check npm for a newer `@warlock.js/core` and print a one-line notice if one exists. Best-effort and non-blocking; auto-skipped in CI and non-TTY shells. Run `warlock update` to upgrade. See [`update-packages/SKILL.md`](../update-packages/SKILL.md).
3056
3058
 
3057
3059
  ## `warlock build` — production bundle
3058
3060
 
@@ -3235,6 +3237,7 @@ NODE_OPTIONS=--max-old-space-size=4096 yarn warlock start
3235
3237
  - [`configure-app/SKILL.md`](../configure-app/SKILL.md) — `warlock.config.ts` shape and `defineConfig`.
3236
3238
  - [`use-app-context/SKILL.md`](../use-app-context/SKILL.md) — `Application.environment` vs `Application.runtimeStrategy`.
3237
3239
  - [`add-connector/SKILL.md`](../add-connector/SKILL.md) — Early vs Late connector phases (why HTTP/socket boot late in dev).
3240
+ - [`update-packages/SKILL.md`](../update-packages/SKILL.md) — `warlock update` + the dev-server update notice (`devServer.checkForUpdates`).
3238
3241
 
3239
3242
 
3240
3243
  ## send-mail `@warlock.js/core/send-mail/SKILL.md`
@@ -4789,6 +4792,88 @@ describe("slugify", () => {
4789
4792
  - [`write-cli-command/SKILL.md`](../write-cli-command/SKILL.md) — `warlock add test` for the initial scaffold.
4790
4793
 
4791
4794
 
4795
+ ## update-packages `@warlock.js/core/update-packages/SKILL.md`
4796
+
4797
+ ---
4798
+ name: update-packages
4799
+ description: 'Keep a project current with `warlock update` — bump every `@warlock.js/*` dependency in package.json to its latest published version (range operator preserved), then run the lockfile-detected package manager install. Also covers the `warlock dev` update notice and the `devServer.checkForUpdates` toggle. Triggers: `warlock update`, `--no-install`, `checkForUpdates`, `fetchLatestVersion`, `isNewerVersion`; "update warlock packages", "upgrade the framework", "is there a new warlock version", "update notice in the dev server", "bump @warlock.js/* to latest"; typical CLI `warlock update`. Skip: dev/build/start runtime — `@warlock.js/core/run-app/SKILL.md`; writing a custom command — `@warlock.js/core/write-cli-command/SKILL.md`; installing a NEW feature package (auth, mail, storage) — that is `warlock add`; releasing/publishing the framework — workspace release tooling, not this command.'
4800
+ ---
4801
+
4802
+ # Warlock — update the framework
4803
+
4804
+ `warlock update` upgrades a project's Warlock packages in one step, and `warlock dev` tells you when an upgrade is available. Because the whole `@warlock.js/*` family ships in **lockstep** — every package shares one version — keeping them in sync is the normal case, and this command does exactly that.
4805
+
4806
+ ## `warlock update`
4807
+
4808
+ ```bash
4809
+ warlock update # bump every @warlock.js/* dep to latest, then install
4810
+ warlock update --no-install # rewrite package.json only; install yourself later
4811
+ ```
4812
+
4813
+ | Flag | Type | Purpose |
4814
+ | -------------- | ------- | ----------------------------------------------------------------------------------- |
4815
+ | `--no-install` | boolean | Rewrite the versions in `package.json` without running the package manager install. |
4816
+
4817
+ What it does, in order:
4818
+
4819
+ 1. Reads the project's root `package.json`.
4820
+ 2. Collects every `@warlock.js/*` package across `dependencies` and `devDependencies`. Only the `@warlock.js/` scope is considered — `create-warlock` and unrelated dependencies are never touched.
4821
+ 3. Looks up each package's latest version on the npm registry, in parallel.
4822
+ 4. Rewrites each matching spec, **preserving the range operator**: `^4.2.0` → `^4.3.0`, `~4.2.0` → `~4.3.0`, an exact `4.2.0` → `4.3.0`. Specs that are not a plain semver — `workspace:*`, `*`, `latest`, git/file URLs — are left exactly as written, and any package already at or ahead of latest is skipped.
4823
+ 5. Runs the project's install to reconcile `node_modules` — `npm install` / `yarn install` / `pnpm install`, chosen by the lockfile present (`package-lock.json` / `yarn.lock` / `pnpm-lock.yaml`, npm as the fallback). Skipped with `--no-install`.
4824
+
4825
+ Re-running on an already-current project is a no-op: nothing resolves as newer, so it prints "All @warlock.js packages are already up to date" and exits without writing or installing.
4826
+
4827
+ ## The dev-server update notice
4828
+
4829
+ On start, `warlock dev` checks npm for a newer `@warlock.js/core` and prints a one-line notice when one exists:
4830
+
4831
+ ```
4832
+ ⚡ A new version of Warlock.js is available 4.2.11 → 4.3.0
4833
+ Run npx warlock update to update all @warlock.js packages
4834
+ Changelog https://warlock.js.org/changelog/
4835
+ ```
4836
+
4837
+ Core's version stands in for the whole family (lockstep), so a single lookup is enough. The check is **best-effort and non-blocking** — it runs fire-and-forget after the server is ready, never delays or breaks startup, and stays silent on any failure (offline, registry down, timeout).
4838
+
4839
+ It is automatically skipped when:
4840
+
4841
+ - `process.env.CI` is set (CI runs),
4842
+ - stdout is not a TTY (piped / non-interactive shells),
4843
+ - `process.env.NO_UPDATE_NOTIFIER` is set, or
4844
+ - `devServer.checkForUpdates` is `false`.
4845
+
4846
+ ```ts title="warlock.config.ts"
4847
+ import { defineConfig } from "@warlock.js/core";
4848
+
4849
+ export default defineConfig({
4850
+ devServer: {
4851
+ checkForUpdates: false, // silence the "update available" notice
4852
+ },
4853
+ });
4854
+ ```
4855
+
4856
+ ## Building blocks
4857
+
4858
+ Two small zero-dependency utilities back the tooling and are exported from `@warlock.js/core`:
4859
+
4860
+ - `fetchLatestVersion(name, timeoutMs?)` — the latest published version of an npm package, or `undefined` on any failure. Never throws.
4861
+ - `isNewerVersion(latest, current)` — `true` when `latest` is a strictly newer semver than `current`. Compares `major.minor.patch` and orders a stable release above its prereleases.
4862
+
4863
+ ## Gotchas
4864
+
4865
+ - **Only the `@warlock.js/` scope is updated.** Mongez packages (`@mongez/*`), `create-warlock`, and everything else are left alone — update those with your package manager directly.
4866
+ - **Non-semver specs are intentionally skipped.** A `workspace:*` or `*` dependency stays as written; `update` will not pin it to a concrete version.
4867
+ - **The notice never blocks dev.** If npm is unreachable, `warlock dev` behaves exactly as before — no delay, no error.
4868
+ - **`warlock update` is not `warlock add`.** `add` installs a *new* feature package and runs its setup hooks; `update` only bumps the versions of packages you already depend on.
4869
+
4870
+ ## See also
4871
+
4872
+ - [`run-app/SKILL.md`](../run-app/SKILL.md) — `warlock dev` / `build` / `start` and the `devServer.*` config knobs.
4873
+ - [`write-cli-command/SKILL.md`](../write-cli-command/SKILL.md) — author your own `warlock <cmd>`.
4874
+ - [`configure-app/SKILL.md`](../configure-app/SKILL.md) — `warlock.config.ts` shape and `defineConfig`.
4875
+
4876
+
4792
4877
  ## upload-file `@warlock.js/core/upload-file/SKILL.md`
4793
4878
 
4794
4879
  ---
package/llms.txt CHANGED
@@ -27,6 +27,7 @@
27
27
  - [store-file](@warlock.js/core/store-file/SKILL.md): Read/write/delete files via the `storage` singleton — disks, drivers (local/S3/R2/DO Spaces), `storage.use(name)`, `StorageFile` handles, presigned URLs. Triggers: `storage.put`, `storage.get`, `storage.use`, `StorageFile`, `storageConfigurations`, `getPresignedUrl`, `getPresignedUploadUrl`; "save an uploaded file", "switch between local and S3", "generate a presigned URL", "read file metadata"; typical import `import { storage } from "@warlock.js/core"`. Skip: multipart parsing + image chain — `@warlock.js/core/upload-file/SKILL.md`; image transforms — `@warlock.js/core/process-image/SKILL.md`; storage config shape — `@warlock.js/core/configure-app/SKILL.md`; competing libs `@aws-sdk/client-s3`, `multer`, `formidable`.
28
28
  - [test-http](@warlock.js/core/test-http/SKILL.md): Integration tests against a real HTTP server — `startHttpTestServer()` boots one shared server in globalSetup, then `testGet` / `testPost` / `expectJson` make typed requests against it. Triggers: `startHttpTestServer`, `stopHttpTestServer`, `testGet`, `testPost`, `testPut`, `testPatch`, `testDelete`, `expectJson`, `getTestServerUrl`, `testRequest`; "integration-test a controller", "end-to-end HTTP test", "globalSetup HTTP server", "assert status and body shape"; typical import `import { testGet, testPost, expectJson } from "@warlock.js/core"`. Skip: pure unit tests — `@warlock.js/core/test-service/SKILL.md`; controller shape — `@warlock.js/core/create-controller/SKILL.md`; competing libs `supertest`, `light-my-request`, `nock`.
29
29
  - [test-service](@warlock.js/core/test-service/SKILL.md): Pure unit tests against services, repositories, models, and use-cases — `setupTest({ connectors })` bootstraps each Vitest worker with its own DB/cache connections so you can call your code directly. Triggers: `setupTest`, `src/test-setup.ts`, `tests.connectors`, `Application.setEnvironment`; "unit-test a service", "test a repository query", "vitest setupFiles", "skip connectors for pure-logic tests"; typical import `import { setupTest } from "@warlock.js/core"`. Skip: HTTP integration — `@warlock.js/core/test-http/SKILL.md`; warlock add test scaffold — `@warlock.js/core/write-cli-command/SKILL.md`; competing tooling: jest direct, `supertest`, `nock`.
30
+ - [update-packages](@warlock.js/core/update-packages/SKILL.md): Keep a project current with `warlock update` — bump every `@warlock.js/*` dependency in package.json to its latest published version (range operator preserved), then run the lockfile-detected package manager install. Also covers the `warlock dev` update notice and the `devServer.checkForUpdates` toggle. Triggers: `warlock update`, `--no-install`, `checkForUpdates`, `fetchLatestVersion`, `isNewerVersion`; "update warlock packages", "upgrade the framework", "is there a new warlock version", "update notice in the dev server", "bump @warlock.js/* to latest"; typical CLI `warlock update`. Skip: dev/build/start runtime — `@warlock.js/core/run-app/SKILL.md`; writing a custom command — `@warlock.js/core/write-cli-command/SKILL.md`; installing a NEW feature package (auth, mail, storage) — that is `warlock add`; releasing/publishing the framework — workspace release tooling, not this command.
30
31
  - [upload-file](@warlock.js/core/upload-file/SKILL.md): Handle multipart file uploads — read via `request.file()` or `request.validated()`, validate with `v.file()`, save via `UploadedFile.save()` or the storage layer, transform images inline. Triggers: `UploadedFile`, `request.file`, `v.file`, `.save`, `.saveAs`, `.resize`, `.format`, `.quality`, `.image`, `.mimeType`, `.maxSize`; "accept a file upload", "validate file size and mime", "save to S3 or local disk", "resize an uploaded image on save"; typical import `import type { UploadedFile, RequestHandler } from "@warlock.js/core"`. Skip: storage drivers + presigned URLs — `@warlock.js/core/store-file/SKILL.md`; image-only transforms — `@warlock.js/core/process-image/SKILL.md`; schema rules — `@warlock.js/core/validate-input/SKILL.md`; competing libs `multer`, `formidable`, `busboy`.
31
32
  - [use-app-context](@warlock.js/core/use-app-context/SKILL.md): Read app-wide context — the `Application` static class (env, version, uptime, runtime strategy) plus the `app` runtime accessor (live Fastify, socket.io, router, database via the DI container). Triggers: `Application.isProduction`, `Application.environment`, `Application.runtimeStrategy`, `Application.uptime`, `Application.version`, `app.http`, `app.socket`, `app.database`, `app.router`; "branch on environment", "reach the live Fastify instance", "framework version in health endpoint", "dev vs production runtime check"; typical import `import { Application, app } from "@warlock.js/core"`. Skip: path helpers — `@warlock.js/core/resolve-path/SKILL.md`; connector start order — `@warlock.js/core/add-connector/SKILL.md`; competing patterns: bare `process.env.NODE_ENV`, ad-hoc Fastify imports.
32
33
  - [use-localization](@warlock.js/core/use-localization/SKILL.md): Multi-locale translations via `groupedTranslations` (declare keys), `t()` / `request.t()` / `request.trans()` (look up), `request.getLocaleCode()` (detect locale from headers/query), `getLocalized` (pick the right value from a localized-array column). Triggers: `groupedTranslations`, `t`, `request.t`, `request.trans`, `request.transFrom`, `request.getLocaleCode`, `request.setLocaleCode`, `getLocalized`; "add a translation key", "resolve a localized error message", "detect request locale", "pick the right per-locale column value"; typical import `import { t, getLocalized } from "@warlock.js/core"`. Skip: resource output — `@warlock.js/core/define-resource/SKILL.md`; module scaffold — `@warlock.js/core/create-module/SKILL.md`; competing libs `i18next`, `react-intl`, raw `@mongez/localization`.
package/package.json CHANGED
@@ -32,17 +32,17 @@
32
32
  "@mongez/events": "^2.2.6",
33
33
  "@mongez/http": "^3.3.8",
34
34
  "@mongez/localization": "^3.4.6",
35
- "@mongez/reinforcements": "^3.2.0",
35
+ "@mongez/reinforcements": "^3.3.0",
36
36
  "@mongez/slug": "^1.0.7",
37
37
  "@mongez/supportive-is": "^2.1.3",
38
38
  "@mongez/time-wizard": "^1.0.6",
39
- "@warlock.js/auth": "4.2.10",
40
- "@warlock.js/cache": "4.2.10",
41
- "@warlock.js/cascade": "4.2.10",
42
- "@warlock.js/context": "4.2.10",
43
- "@warlock.js/logger": "4.2.10",
44
- "@warlock.js/seal": "4.2.10",
45
- "@warlock.js/fs": "4.2.10",
39
+ "@warlock.js/auth": "4.3.0",
40
+ "@warlock.js/cache": "4.3.0",
41
+ "@warlock.js/cascade": "4.3.0",
42
+ "@warlock.js/context": "4.3.0",
43
+ "@warlock.js/logger": "4.3.0",
44
+ "@warlock.js/seal": "4.3.0",
45
+ "@warlock.js/fs": "4.3.0",
46
46
  "chokidar": "^5.0.0",
47
47
  "dayjs": "^1.11.19",
48
48
  "es-module-lexer": "^2.0.0",
@@ -68,12 +68,12 @@
68
68
  "react": "^19.2.3",
69
69
  "react-dom": "^19.2.3",
70
70
  "@react-email/render": "^2.0.5",
71
- "@warlock.js/herald": "4.2.10"
71
+ "@warlock.js/herald": "4.3.0"
72
72
  },
73
73
  "bin": {
74
74
  "warlock": "bin/warlock.js"
75
75
  },
76
- "version": "4.2.10",
76
+ "version": "4.3.0",
77
77
  "type": "module",
78
78
  "main": "./esm/index.mjs",
79
79
  "module": "./esm/index.mjs",
@@ -66,6 +66,7 @@ export default defineConfig({
66
66
  exclude: ["**/node_modules/**", "**/dist/**", "**/.warlock/**", "**/.git/**"],
67
67
  },
68
68
  generateTypings: true, // background type generation
69
+ checkForUpdates: true, // notify on a newer @warlock.js/core at dev start
69
70
  healthCheckers: [...] /* or false */,
70
71
  transpileCacheDebug: false, // name cache files <slug>.<hash>.js w/ // @source markers
71
72
  },
@@ -76,6 +77,7 @@ export default defineConfig({
76
77
  - **`generateTypings`** — turn off if you're committing generated typings and don't want them rewritten on every boot. The `--skip-typings` flag is the per-run version.
77
78
  - **`healthCheckers`** — custom file health checker contracts (or `false` to disable). The `--skip-health` flag is the per-run version.
78
79
  - **`transpileCacheDebug`** — diagnostic only. Names `.warlock/transpile/*.js` files `<slug>.<hash>.js` and appends `// @source <path>` markers so you can eyeball which cache entry came from which source. Leave off in normal use.
80
+ - **`checkForUpdates`** — on `warlock dev` start, check npm for a newer `@warlock.js/core` and print a one-line notice if one exists. Best-effort and non-blocking; auto-skipped in CI and non-TTY shells. Run `warlock update` to upgrade. See [`update-packages/SKILL.md`](../update-packages/SKILL.md).
79
81
 
80
82
  ## `warlock build` — production bundle
81
83
 
@@ -258,3 +260,4 @@ NODE_OPTIONS=--max-old-space-size=4096 yarn warlock start
258
260
  - [`configure-app/SKILL.md`](../configure-app/SKILL.md) — `warlock.config.ts` shape and `defineConfig`.
259
261
  - [`use-app-context/SKILL.md`](../use-app-context/SKILL.md) — `Application.environment` vs `Application.runtimeStrategy`.
260
262
  - [`add-connector/SKILL.md`](../add-connector/SKILL.md) — Early vs Late connector phases (why HTTP/socket boot late in dev).
263
+ - [`update-packages/SKILL.md`](../update-packages/SKILL.md) — `warlock update` + the dev-server update notice (`devServer.checkForUpdates`).
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: update-packages
3
+ description: 'Keep a project current with `warlock update` — bump every `@warlock.js/*` dependency in package.json to its latest published version (range operator preserved), then run the lockfile-detected package manager install. Also covers the `warlock dev` update notice and the `devServer.checkForUpdates` toggle. Triggers: `warlock update`, `--no-install`, `checkForUpdates`, `fetchLatestVersion`, `isNewerVersion`; "update warlock packages", "upgrade the framework", "is there a new warlock version", "update notice in the dev server", "bump @warlock.js/* to latest"; typical CLI `warlock update`. Skip: dev/build/start runtime — `@warlock.js/core/run-app/SKILL.md`; writing a custom command — `@warlock.js/core/write-cli-command/SKILL.md`; installing a NEW feature package (auth, mail, storage) — that is `warlock add`; releasing/publishing the framework — workspace release tooling, not this command.'
4
+ ---
5
+
6
+ # Warlock — update the framework
7
+
8
+ `warlock update` upgrades a project's Warlock packages in one step, and `warlock dev` tells you when an upgrade is available. Because the whole `@warlock.js/*` family ships in **lockstep** — every package shares one version — keeping them in sync is the normal case, and this command does exactly that.
9
+
10
+ ## `warlock update`
11
+
12
+ ```bash
13
+ warlock update # bump every @warlock.js/* dep to latest, then install
14
+ warlock update --no-install # rewrite package.json only; install yourself later
15
+ ```
16
+
17
+ | Flag | Type | Purpose |
18
+ | -------------- | ------- | ----------------------------------------------------------------------------------- |
19
+ | `--no-install` | boolean | Rewrite the versions in `package.json` without running the package manager install. |
20
+
21
+ What it does, in order:
22
+
23
+ 1. Reads the project's root `package.json`.
24
+ 2. Collects every `@warlock.js/*` package across `dependencies` and `devDependencies`. Only the `@warlock.js/` scope is considered — `create-warlock` and unrelated dependencies are never touched.
25
+ 3. Looks up each package's latest version on the npm registry, in parallel.
26
+ 4. Rewrites each matching spec, **preserving the range operator**: `^4.2.0` → `^4.3.0`, `~4.2.0` → `~4.3.0`, an exact `4.2.0` → `4.3.0`. Specs that are not a plain semver — `workspace:*`, `*`, `latest`, git/file URLs — are left exactly as written, and any package already at or ahead of latest is skipped.
27
+ 5. Runs the project's install to reconcile `node_modules` — `npm install` / `yarn install` / `pnpm install`, chosen by the lockfile present (`package-lock.json` / `yarn.lock` / `pnpm-lock.yaml`, npm as the fallback). Skipped with `--no-install`.
28
+
29
+ Re-running on an already-current project is a no-op: nothing resolves as newer, so it prints "All @warlock.js packages are already up to date" and exits without writing or installing.
30
+
31
+ ## The dev-server update notice
32
+
33
+ On start, `warlock dev` checks npm for a newer `@warlock.js/core` and prints a one-line notice when one exists:
34
+
35
+ ```
36
+ ⚡ A new version of Warlock.js is available 4.2.11 → 4.3.0
37
+ Run npx warlock update to update all @warlock.js packages
38
+ Changelog https://warlock.js.org/changelog/
39
+ ```
40
+
41
+ Core's version stands in for the whole family (lockstep), so a single lookup is enough. The check is **best-effort and non-blocking** — it runs fire-and-forget after the server is ready, never delays or breaks startup, and stays silent on any failure (offline, registry down, timeout).
42
+
43
+ It is automatically skipped when:
44
+
45
+ - `process.env.CI` is set (CI runs),
46
+ - stdout is not a TTY (piped / non-interactive shells),
47
+ - `process.env.NO_UPDATE_NOTIFIER` is set, or
48
+ - `devServer.checkForUpdates` is `false`.
49
+
50
+ ```ts title="warlock.config.ts"
51
+ import { defineConfig } from "@warlock.js/core";
52
+
53
+ export default defineConfig({
54
+ devServer: {
55
+ checkForUpdates: false, // silence the "update available" notice
56
+ },
57
+ });
58
+ ```
59
+
60
+ ## Building blocks
61
+
62
+ Two small zero-dependency utilities back the tooling and are exported from `@warlock.js/core`:
63
+
64
+ - `fetchLatestVersion(name, timeoutMs?)` — the latest published version of an npm package, or `undefined` on any failure. Never throws.
65
+ - `isNewerVersion(latest, current)` — `true` when `latest` is a strictly newer semver than `current`. Compares `major.minor.patch` and orders a stable release above its prereleases.
66
+
67
+ ## Gotchas
68
+
69
+ - **Only the `@warlock.js/` scope is updated.** Mongez packages (`@mongez/*`), `create-warlock`, and everything else are left alone — update those with your package manager directly.
70
+ - **Non-semver specs are intentionally skipped.** A `workspace:*` or `*` dependency stays as written; `update` will not pin it to a concrete version.
71
+ - **The notice never blocks dev.** If npm is unreachable, `warlock dev` behaves exactly as before — no delay, no error.
72
+ - **`warlock update` is not `warlock add`.** `add` installs a *new* feature package and runs its setup hooks; `update` only bumps the versions of packages you already depend on.
73
+
74
+ ## See also
75
+
76
+ - [`run-app/SKILL.md`](../run-app/SKILL.md) — `warlock dev` / `build` / `start` and the `devServer.*` config knobs.
77
+ - [`write-cli-command/SKILL.md`](../write-cli-command/SKILL.md) — author your own `warlock <cmd>`.
78
+ - [`configure-app/SKILL.md`](../configure-app/SKILL.md) — `warlock.config.ts` shape and `defineConfig`.