@warlock.js/core 5.7.0 → 5.9.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 +22 -0
- package/esm/cli/cli-commands.manager.mjs +2 -2
- package/esm/cli/cli-commands.manager.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/index.mjs +2 -0
- package/esm/cli/commands/doctor/checks/index.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/jwt-secret.check.mjs +55 -0
- package/esm/cli/commands/doctor/checks/jwt-secret.check.mjs.map +1 -0
- package/esm/cli/commands/generate/generators/controller.generator.mjs +4 -4
- package/esm/cli/commands/generate/generators/controller.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/migration.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/model.generator.mjs +4 -4
- package/esm/cli/commands/generate/generators/model.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/module.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/repository.generator.mjs +4 -4
- package/esm/cli/commands/generate/generators/repository.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/resource.generator.mjs +4 -4
- package/esm/cli/commands/generate/generators/resource.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/service.generator.mjs +4 -4
- package/esm/cli/commands/generate/generators/service.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/templates/stubs.mjs +6 -3
- package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -1
- package/esm/dev-server/files-orchestrator.mjs +1 -0
- package/esm/dev-server/files-orchestrator.mjs.map +1 -1
- package/esm/dev-server/parse-imports.mjs +1 -1
- package/esm/dev-server/parse-imports.mjs.map +1 -1
- package/esm/generations/features/shadcn.feature.mjs +1 -0
- package/esm/generations/features/shadcn.feature.mjs.map +1 -1
- package/esm/generations/stubs.mjs +6 -6
- package/esm/generations/stubs.mjs.map +1 -1
- package/esm/http/boot-port-preflight.d.mts +25 -1
- package/esm/http/boot-port-preflight.d.mts.map +1 -1
- package/esm/http/boot-port-preflight.mjs +51 -2
- package/esm/http/boot-port-preflight.mjs.map +1 -1
- package/esm/http/index.d.mts +1 -1
- package/esm/http/index.mjs +1 -1
- package/esm/index.d.mts +2 -2
- package/esm/index.mjs +2 -2
- package/esm/utils/load-environment.mjs +4 -1
- package/esm/utils/load-environment.mjs.map +1 -1
- package/esm/utils/recorded-environment-overrides.mjs +41 -0
- package/esm/utils/recorded-environment-overrides.mjs.map +1 -0
- package/llms-full.txt +3 -0
- package/package.json +12 -12
- package/skills/warlock-doctor/SKILL.md +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,28 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
> ⚠ **Versioning: `@warlock.js/*` does not follow SemVer strictly — breaking changes may ship in a minor.** This is a deliberate decision, not an oversight: the framework is pre-adoption and the cost of a major per behaviour fix currently outweighs the benefit. **Pin an exact version or a tilde range (`~4.13.0`) if you need to opt into changes rather than receive them.** Every breaking change is marked **BREAKING** in its entry and summarised in an *Upgrading* section at the top of the release. **This policy will change once the framework has consumers beyond its author.**
|
|
8
8
|
|
|
9
|
+
## 5.9.0 - 2026-09-13
|
|
10
|
+
|
|
11
|
+
_Released in lockstep with the `@warlock.js/*` family; no package-specific changes in 5.9.0._
|
|
12
|
+
|
|
13
|
+
## 5.8.0 - 2026-09-13
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- `doctor` gains a `jwt-secret` check: it fails when `auth.userType` is configured but no JWT signing secret is set — the pre-flight form of the silent first-login 500 the lazy secret resolution would otherwise throw.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- CLI command modules now resolve the `app/*` path alias and `.ts` siblings the way controllers and pages do. The ESM loader hook is registered before a command module is imported, so a `warlock <command>` file that imports `app/*` no longer dies with `ERR_MODULE_NOT_FOUND`.
|
|
22
|
+
- `warlock generate.*` error hints point at `generate`, not the removed `create.*` command.
|
|
23
|
+
- A generated CRUD repository imports every `@warlock.js/core` type it references, and a generated seed stub ships disabled so an unfilled stub cannot abort the whole `warlock seed` run.
|
|
24
|
+
- The HTTP-port preflight runs only for a boot that actually starts the http connector, so a scoped data command (`warlock seed`, `migrate`) no longer probes — and collides with — a port it never binds.
|
|
25
|
+
- A port-in-use error now names the `HTTP_PORT` environment variable and tells you to unset it when an ambient value is the cause, instead of advising a `src/config/http.ts` edit that the environment variable would just override.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- The emitted generator and starter templates no longer carry a redundant `.required()` — `@warlock.js/seal` fields are required by default, so the call was a no-op that taught the opposite of the truth. `.required()` still exists for setting a custom message.
|
|
30
|
+
|
|
9
31
|
## 5.7.0 - 2026-09-11
|
|
10
32
|
|
|
11
33
|
### Security
|
|
@@ -2,7 +2,7 @@ import { appPath } from "../utils/paths.mjs";
|
|
|
2
2
|
import "../utils/index.mjs";
|
|
3
3
|
import { Application } from "../application/application.mjs";
|
|
4
4
|
import "../application/index.mjs";
|
|
5
|
-
import { preflightConfiguredHttpPort } from "../http/boot-port-preflight.mjs";
|
|
5
|
+
import { preflightConfiguredHttpPort, shouldPreflightHttpPort } from "../http/boot-port-preflight.mjs";
|
|
6
6
|
import { loadEnvironmentFiles } from "../utils/load-environment.mjs";
|
|
7
7
|
import { bootstrap } from "../bootstrap.mjs";
|
|
8
8
|
import { ConnectorLifecyclePhase } from "../connectors/types.mjs";
|
|
@@ -391,7 +391,7 @@ var CLICommandsManager = class {
|
|
|
391
391
|
}
|
|
392
392
|
if (preloaders.connectors) {
|
|
393
393
|
registerConfiguredConnectors();
|
|
394
|
-
await preflightConfiguredHttpPort();
|
|
394
|
+
if (shouldPreflightHttpPort(preloaders.connectors)) await preflightConfiguredHttpPort();
|
|
395
395
|
if (preloaders.connectors === true) await connectorsManager.startPhase("early");
|
|
396
396
|
else await connectorsManager.start(preloaders.connectors);
|
|
397
397
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-commands.manager.mjs","names":[],"sources":["../../../../../../../core/src/cli/cli-commands.manager.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { fileExistsAsync } from \"@warlock.js/fs\";\r\nimport { Application } from \"../application\";\r\nimport { bootstrap } from \"../bootstrap\";\r\nimport { loadConfigFiles } from \"../config/load-config-files\";\r\nimport { connectorsManager } from \"../connectors/connectors-manager\";\r\nimport { registerConfiguredConnectors } from \"../connectors/register-configured-connectors\";\r\nimport { ConnectorLifecyclePhase } from \"../connectors/types\";\r\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\r\nimport { isDevWorker } from \"../dev-server/supervisor\";\r\nimport { preflightConfiguredHttpPort } from \"../http/boot-port-preflight\";\r\nimport { manifestManager } from \"../manifest/manifest-manager\";\r\nimport { appPath } from \"../utils\";\r\nimport { loadEnvironmentFiles } from \"../utils/load-environment\";\r\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\r\nimport { CLICommand } from \"../commands/cli-command\";\r\nimport {\r\n displayBootError,\r\n displayCommandError,\r\n displayCommandHelp,\r\n displayCommandNotFound,\r\n displayCommandSuccess,\r\n displayExecutingCommand,\r\n displayHelp,\r\n displayMissingCommand,\r\n displayMissingOptions,\r\n displayWarlockVersionInTerminal,\r\n type HelpCommandInfo,\r\n isMatchingCommandName,\r\n} from \"./cli-commands.utils\";\r\nimport { cliCommandsLoader } from \"./commands-loader\";\r\nimport { frameworkCommands } from \"./framework-cli-commands\";\r\nimport { CliOptionValueError, type ParsedCliArgs, parseCliArgs } from \"./parse-cli-args\";\r\nimport { findSimilar } from \"./string-similarity\";\r\nimport type { CommandActionData, ResolvedCLICommandOption } from \"../commands/types\";\r\n\r\n/**\r\n * Best-effort budget (ms) for draining stdout/stderr before a forced exit.\r\n * Bounded so a stuck stream can never hang the CLI.\r\n */\r\nconst EXIT_FLUSH_BUDGET = 500;\r\n\r\n/**\r\n * Exit with `code` once stdout/stderr have drained.\r\n *\r\n * `process.exit()` truncates writes still queued on a non-TTY stdout — a pipe,\r\n * a CI log — which is how the *reason* for a failure gets lost even when the\r\n * exit code is right. `exitCode` is set up front so that a natural exit (should\r\n * the drain outlive the process's remaining work) is non-zero too.\r\n */\r\nfunction exitAfterFlush(code: number) {\r\n process.exitCode = code;\r\n\r\n const timer = setTimeout(() => process.exit(code), EXIT_FLUSH_BUDGET);\r\n timer.unref?.();\r\n\r\n let pending = 2;\r\n const drained = () => {\r\n if (--pending > 0) return;\r\n\r\n clearTimeout(timer);\r\n process.exit(code);\r\n };\r\n\r\n // A zero-length write's callback fires once everything queued ahead of it has\r\n // been handed to the OS.\r\n process.stdout.write(\"\", drained);\r\n process.stderr.write(\"\", drained);\r\n}\r\n\r\n/**\r\n * How a command run ended, from the process's point of view.\r\n *\r\n * - `succeeded` — finished, nothing rejected afterwards; the CLI may exit 0.\r\n * - `failed` — reported an error; the exit code is owned by whoever reported it.\r\n * - `running` — a persistent command (dev/start) now owns the process.\r\n *\r\n * `execute` returns this instead of exiting on success itself. A method that\r\n * terminates the process out from under its caller cannot be awaited honestly:\r\n * the exit it schedules outlives the call, so the success it represents is\r\n * still unsettled when `execute` resolves — which is exactly how a green check\r\n * ends up printed next to the failure that contradicts it.\r\n */\r\nexport type CommandRunVerdict = \"succeeded\" | \"failed\" | \"running\";\r\n\r\n/**\r\n * Whether this process is the CHILD half of a supervised command.\r\n *\r\n * ## The finding this encodes\r\n *\r\n * `warlock dev` printing `› Running dev...` twice was NOT a double boot, and\r\n * the distinction is the whole point of this function existing rather than the\r\n * line simply being deleted. The sequence, in file order:\r\n *\r\n * 1. `execute()` prints the header (below) — BEFORE `commandPreAction`.\r\n * 2. `cli/commands/dev-server.command.ts` `preAction` calls\r\n * `superviseDevServer()` when `!isDevWorker()`.\r\n * 3. `dev-server/supervisor.ts` `spawnWorker()` re-spawns `process.argv` with\r\n * `WARLOCK_DEV_WORKER=1`, so the child runs `warlock dev` again and prints\r\n * the header too.\r\n * 4. `superviseDevServer()` returns a promise that never settles, so the\r\n * supervisor's `await commandPreAction(...)` never resolves — `loadPreloaders`\r\n * and `command.execute` are unreachable in the supervisor.\r\n *\r\n * Step 4 is why only ONE process ever loads config, connectors, app modules or\r\n * a scheduler. Verified by running it: one `APP-CODE-LOADED`, one\r\n * `Application.markBooted`, one `⚡ Warlock.js` banner, one route registration,\r\n * two processes. The `2 job(s) registered` in the original report is two jobs\r\n * in one scheduler, not two schedulers — a genuine double boot would have\r\n * printed that warning twice.\r\n *\r\n * So the defect is exactly one duplicated line, and this is the guard that\r\n * keeps it one. `tests/unit/cli/dev-command-single-boot.test.ts` fails if the\r\n * supervisor ever starts running preloaders — i.e. if the double boot the\r\n * duplicated line looked like ever becomes real.\r\n */\r\nfunction isRespawnedWorker(): boolean {\r\n return isDevWorker();\r\n}\r\n\r\nexport class CLICommandsManager {\r\n /**\r\n * List of commands\r\n */\r\n public commands: CLICommand[] = [];\r\n\r\n /**\r\n * Commands map (name -> command)\r\n */\r\n public commandsMap: Map<string, CLICommand> = new Map();\r\n\r\n /**\r\n * Alias map (alias -> command name)\r\n */\r\n public aliasMap: Map<string, string> = new Map();\r\n\r\n /**\r\n * Register the given commands\r\n */\r\n public register(...commands: CLICommand[]): this {\r\n this.commands.push(...commands);\r\n\r\n commands.forEach((command) => {\r\n // `split` always yields a first element, so this holds. Falling back to\r\n // the whole name rather than asserting keeps the map keyed by a REAL\r\n // command name — a key of `undefined` would register the command under\r\n // the literal string \"undefined\" and make it unreachable by its own name.\r\n const commandKey = command.name.split(\" \")[0] ?? command.name;\r\n\r\n manifestManager.addCommandToList(command.name, {\r\n relativePath: command.commandRelativePath,\r\n source: command.commandSource || \"project\",\r\n description: command.commandDescription,\r\n alias: command.commandAlias,\r\n options: command.commandOptions.length > 0 ? command.commandOptions : undefined,\r\n });\r\n\r\n this.commandsMap.set(commandKey, command);\r\n\r\n // Register alias if exists\r\n if (command.commandAlias) {\r\n this.aliasMap.set(command.commandAlias, commandKey);\r\n }\r\n });\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get command by name or alias\r\n */\r\n public getCommand(name: string): CLICommand | undefined {\r\n // First try direct lookup\r\n let command = this.commandsMap.get(name);\r\n if (command) return command;\r\n\r\n // Try alias lookup\r\n const realName = this.aliasMap.get(name);\r\n if (realName) {\r\n return this.commandsMap.get(realName);\r\n }\r\n\r\n return;\r\n }\r\n\r\n /**\r\n * Get all command names and aliases\r\n * Used for fuzzy matching suggestions\r\n */\r\n public getAllCommandNames(): string[] {\r\n const names: string[] = [];\r\n\r\n // Add all command names\r\n for (const name of this.commandsMap.keys()) {\r\n names.push(name);\r\n }\r\n\r\n // Add all aliases\r\n for (const alias of this.aliasMap.keys()) {\r\n names.push(alias);\r\n }\r\n\r\n // Also include cached commands from manifest\r\n const manifestCommands = manifestManager.commandsJson?.commands || {};\r\n // `Object.entries` rather than keys + index: the index read is\r\n // `T | undefined` under the strictness contract even for a key that came\r\n // from the object itself, and these names feed the CLI's \"did you mean?\"\r\n // suggestions — a literal \"undefined\" in that list is a suggestion no\r\n // operator can act on.\r\n for (const [name, meta] of Object.entries(manifestCommands)) {\r\n const baseName = name.split(\" \")[0] ?? name;\r\n\r\n if (!names.includes(baseName)) {\r\n names.push(baseName);\r\n }\r\n\r\n const alias = meta?.alias;\r\n\r\n if (alias && !names.includes(alias)) {\r\n names.push(alias);\r\n }\r\n }\r\n\r\n return names;\r\n }\r\n\r\n /**\r\n * Start the cli manager\r\n */\r\n public async start() {\r\n // Schema-less first pass: it exists to discover the command name and the\r\n // command-independent flags (--help, --version, --no-cache). Values here are\r\n // raw strings; the typed pass below is the one whose options reach a command.\r\n const { name, options } = parseCliArgs(process.argv);\r\n\r\n if (options.noCache) {\r\n manifestManager.clearCommandsCache();\r\n // remove the commands.json file as wel\r\n await manifestManager.removeCommandsFile();\r\n }\r\n\r\n if (options.version || options.v) {\r\n await displayWarlockVersionInTerminal();\r\n process.exit(0);\r\n }\r\n\r\n // Try to load from manifest first (fastest path)\r\n await manifestManager.loadCommands();\r\n\r\n // Register framework commands first (needed for help)\r\n this.register(\r\n ...frameworkCommands.map((command) => {\r\n if (!command.commandSource) {\r\n command.commandSource = \"framework\";\r\n }\r\n return command;\r\n }),\r\n );\r\n\r\n const isHelpCommand = options.help || options.h;\r\n\r\n // Handle global help (no command)\r\n if (!name && isHelpCommand) {\r\n await this.showGlobalHelp();\r\n process.exit(0);\r\n }\r\n\r\n // Handle warm cache\r\n if (options.warmCache) {\r\n await this.warmCache();\r\n process.exit(0);\r\n }\r\n\r\n if (!name) {\r\n displayMissingCommand();\r\n process.exit(1);\r\n }\r\n\r\n const command = await this.lazyGetCommand(name);\r\n\r\n if (manifestManager.hasChanges) {\r\n await manifestManager.saveCommands();\r\n }\r\n\r\n if (!command) {\r\n // Find similar commands for suggestions\r\n const allCommandNames = this.getAllCommandNames();\r\n const suggestions = findSimilar(name, allCommandNames).map((s) => s.value);\r\n\r\n displayCommandNotFound(name, suggestions);\r\n process.exit(1);\r\n }\r\n\r\n // Handle command-specific help\r\n if (isHelpCommand) {\r\n displayCommandHelp({\r\n name: command.name,\r\n alias: command.commandAlias,\r\n description: command.commandDescription,\r\n options: command.commandOptions,\r\n });\r\n process.exit(0);\r\n }\r\n\r\n // The first parse above had no command to consult, so every value in it is\r\n // a raw string. Now that the command is resolved, re-read argv against its\r\n // declared options so a `type: \"boolean\"` option arrives as a boolean.\r\n const resolved = this.resolveCommandArgs(command);\r\n\r\n const verdict = await this.execute(command, {\r\n options: resolved.options,\r\n args: resolved.args,\r\n });\r\n\r\n // The process-level exit belongs to the entrypoint, not to `execute`.\r\n // A one-shot command can leave a handle open (a db connection, a watcher a\r\n // plugin forgot to close), so the CLI exits explicitly rather than waiting\r\n // for the loop to drain — but only on a verdict of `succeeded`. `failed`\r\n // already exited non-zero through whoever reported it, and `running` means\r\n // a persistent command owns the process now.\r\n if (verdict === \"succeeded\") {\r\n exitAfterFlush(0);\r\n }\r\n }\r\n\r\n /**\r\n * Re-parse argv against the resolved command's declared options.\r\n *\r\n * parseCliArgs runs twice on purpose. The first run produces the command\r\n * name, and there is no schema to consult yet; the second one, here, can be\r\n * type-aware. Re-reading argv (rather than post-processing the first result)\r\n * is what makes `--rollback users.ts` recoverable: by the time the first pass\r\n * returns, the swallowed positional is indistinguishable from a value.\r\n */\r\n protected resolveCommandArgs(command: CLICommand, argv: string[] = process.argv): ParsedCliArgs {\r\n try {\r\n return parseCliArgs(argv, command.commandOptions);\r\n } catch (error) {\r\n if (error instanceof CliOptionValueError) {\r\n console.log();\r\n console.log(` ${colors.redBright(\"Error:\")} ${error.message}`);\r\n console.log();\r\n process.exit(1);\r\n }\r\n\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Show global help with all commands\r\n * Uses manifest if available for fast display\r\n */\r\n protected async showGlobalHelp() {\r\n if (\r\n manifestManager.isCommandLoaded &&\r\n Object.keys(manifestManager.commandsJson?.commands || {}).length > 0\r\n ) {\r\n // Use manifest directly - no need to load command files\r\n const helpCommands: HelpCommandInfo[] = Object.entries(\r\n manifestManager.commandsJson?.commands || {},\r\n ).map(([name, cmd]) => ({\r\n name,\r\n alias: cmd.alias,\r\n description: cmd.description,\r\n source: cmd.source,\r\n }));\r\n\r\n await displayHelp(helpCommands);\r\n return;\r\n }\r\n\r\n // Fallback: No manifest, build from registered commands\r\n // This happens on first run before warm-cache\r\n await this.loadPluginsCommands();\r\n\r\n const projectCommands = await cliCommandsLoader.scanAll();\r\n\r\n this.register(...projectCommands);\r\n\r\n await manifestManager.saveCommands();\r\n\r\n // Build help info from registered commands\r\n const helpCommands: HelpCommandInfo[] = this.commands.map((cmd) => ({\r\n name: cmd.name,\r\n alias: cmd.commandAlias,\r\n description: cmd.commandDescription,\r\n source: cmd.commandSource || \"project\",\r\n }));\r\n\r\n await displayHelp(helpCommands);\r\n }\r\n\r\n /**\r\n * Warm cache - scan all project commands and save to manifest\r\n */\r\n protected async warmCache() {\r\n console.log();\r\n console.log(` ${colors.cyan(\"›\")} Scanning project commands...`);\r\n\r\n const projectCommands = await cliCommandsLoader.scanAll();\r\n\r\n this.register(...projectCommands);\r\n\r\n await manifestManager.saveCommands();\r\n\r\n console.log(\r\n ` ${colors.green(\"✔\")} Cached ${colors.bold(String(projectCommands.length))} project commands`,\r\n );\r\n console.log();\r\n }\r\n\r\n /**\r\n * Load plugins commands\r\n */\r\n protected async loadPluginsCommands() {\r\n await warlockConfigManager.load();\r\n\r\n if (warlockConfigManager.isLoaded) {\r\n this.register(\r\n ...(warlockConfigManager.get(\"cli\")?.commands || []).map((command) => {\r\n if (!command.commandSource) {\r\n command.commandSource = \"plugin\";\r\n }\r\n return command;\r\n }),\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Try to find the command based on the given command name or alias\r\n */\r\n protected async lazyGetCommand(name: string) {\r\n // first step, try to find it directly through current commands\r\n let command = this.getCommand(name);\r\n if (command) {\r\n return command;\r\n }\r\n\r\n // second step, try to find it through warlock config commands\r\n await this.loadPluginsCommands();\r\n\r\n command = this.getCommand(name);\r\n\r\n if (command) {\r\n return command;\r\n }\r\n\r\n // third step, try to find it through project commands\r\n await this.loadProjectCommands(name);\r\n\r\n command = this.getCommand(name);\r\n if (command) {\r\n return command;\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Load project commands\r\n */\r\n protected async loadProjectCommands(name: string) {\r\n // first get the commands.json contents as an object\r\n const jsonCommandsFile = await manifestManager.loadCommands();\r\n\r\n if (jsonCommandsFile) {\r\n // Check by name or alias\r\n // `Object.entries` hands the metadata over already narrowed; a `for...in`\r\n // index read is `T | undefined` and every field below is read off it.\r\n for (const [fullCommandName, cmdMeta] of Object.entries(jsonCommandsFile.commands)) {\r\n if (cmdMeta === undefined) continue;\r\n\r\n // Match by name or alias\r\n if (isMatchingCommandName(fullCommandName, name) || cmdMeta.alias === name) {\r\n const commandPath = cmdMeta.relativePath;\r\n if (commandPath) {\r\n const executedCommand = await cliCommandsLoader.load(commandPath);\r\n\r\n if (executedCommand) {\r\n executedCommand.$relativePath(commandPath);\r\n this.register(executedCommand);\r\n return;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n const command = await cliCommandsLoader.locate(name);\r\n\r\n if (command) {\r\n this.register(command);\r\n return;\r\n }\r\n }\r\n\r\n /**\r\n * Validate required options\r\n */\r\n protected validateOptions(\r\n command: CLICommand,\r\n options: Record<string, string | boolean | number>,\r\n ): ResolvedCLICommandOption[] {\r\n const missing: ResolvedCLICommandOption[] = [];\r\n\r\n command.commandOptions.forEach((opt) => {\r\n if (opt.required) {\r\n // Check if option is provided by name or alias\r\n const hasOption =\r\n options[opt.name] !== undefined || (opt.alias && options[opt.alias] !== undefined);\r\n if (!hasOption) {\r\n missing.push(opt);\r\n }\r\n }\r\n });\r\n\r\n return missing;\r\n }\r\n\r\n /**\r\n * Apply default values to options\r\n */\r\n protected applyDefaultOptions(\r\n command: CLICommand,\r\n options: Record<string, string | boolean | number>,\r\n ): Record<string, string | boolean | number> {\r\n const result = { ...options };\r\n\r\n command.commandOptions.forEach((opt) => {\r\n if (opt.defaultValue !== undefined) {\r\n const hasOption =\r\n result[opt.name] !== undefined || (opt.alias && result[opt.alias] !== undefined);\r\n if (!hasOption) {\r\n result[opt.name] = opt.defaultValue;\r\n }\r\n }\r\n\r\n // `!== undefined`, not truthiness: `-r=false` now resolves to the boolean\r\n // `false`, and a truthiness test would drop it before it ever reached the\r\n // canonical name — leaving `rollback` undefined instead of explicitly off.\r\n if (\r\n opt.alias !== undefined &&\r\n result[opt.alias] !== undefined &&\r\n result[opt.name] === undefined\r\n ) {\r\n // The `!== undefined` guard on the line above already proves this read\r\n // is defined, but the compiler does not carry a guard on one index\r\n // expression to a second, textually identical one. Reading it into a\r\n // local makes the narrowing survive, WITHOUT weakening the check — an\r\n // `?? ` fallback here would resurrect exactly the bug the comment above\r\n // describes, turning an explicit `-r=false` into a default.\r\n const aliasValue = result[opt.alias];\r\n\r\n if (aliasValue !== undefined) {\r\n result[opt.name] = aliasValue;\r\n }\r\n }\r\n });\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Execute the given command and report how it ended.\r\n *\r\n * Returning the verdict — rather than exiting on success from in here — is\r\n * what makes the awaited result trustworthy: see {@link CommandRunVerdict}.\r\n */\r\n public async execute(command: CLICommand, data: CommandActionData): Promise<CommandRunVerdict> {\r\n const startTime = Date.now();\r\n\r\n // Validate required options\r\n const missingOptions = this.validateOptions(command, data.options);\r\n if (missingOptions.length > 0) {\r\n displayMissingOptions(missingOptions);\r\n process.exit(1);\r\n }\r\n\r\n // Apply default values\r\n data.options = this.applyDefaultOptions(command, data.options);\r\n\r\n // ONE header per `warlock <command>` the user typed — not one per process.\r\n //\r\n // `warlock dev` is TWO processes by design: the invocation the user typed\r\n // becomes a thin supervisor (`dev-server/supervisor.ts`) that re-spawns\r\n // `process.argv` with `WARLOCK_DEV_WORKER=1`, and the child is the actual\r\n // server. Both are `warlock dev`, so both reached this line and the user saw\r\n //\r\n // › Running dev...\r\n // › Running dev...\r\n //\r\n // which reads exactly like the application booting twice. It is not — see\r\n // the block comment on `isRespawnedWorker` — but the line was still wrong,\r\n // and it re-printed on every crash-restart on top of the supervisor's own\r\n // \"restarting\" notice.\r\n //\r\n // The SUPERVISOR keeps the header, not the worker: it prints immediately\r\n // (so a slow boot is not silent), it prints once for the whole session\r\n // (restarts are already announced as restarts), and it is the process the\r\n // user actually started.\r\n if (!isRespawnedWorker()) {\r\n displayExecutingCommand(command.name);\r\n }\r\n\r\n if (command.commandPreAction) {\r\n await command.commandPreAction(data);\r\n }\r\n\r\n // load preloaders\r\n if (command.commandPreload) {\r\n try {\r\n await this.loadPreloaders(command);\r\n } catch (error) {\r\n // A preload failure (a bad import in a config file, a connector that\r\n // throws on boot, a missing module export) happens BEFORE the command's\r\n // action runs — there is no run loop to recover into, so it is ALWAYS\r\n // fatal, persistent command (dev/start) or not. Surface the real cause\r\n // and the offending file, then exit. This MUST stay outside the\r\n // command-execute try/catch below: letting a preload rejection escape\r\n // turned `warlock dev` into a silent hang — the unhandled rejection slid\r\n // past while the already-started loader worker thread kept the process\r\n // alive, freezing just after the banner with no error printed.\r\n displayBootError(command.name, error as Error);\r\n process.exit(1);\r\n }\r\n }\r\n\r\n this.captureFatalAsyncErrors(command);\r\n\r\n try {\r\n await command.execute(data);\r\n\r\n // Persistent commands (dev/start) hand control to their run loop here.\r\n // There is no success banner to print and nothing to exit for.\r\n if (command.isPersistent) return \"running\";\r\n\r\n // Yield one full event-loop turn before declaring success, and AWAIT that\r\n // yield. Node flags an unhandled rejection only after the current turn's\r\n // microtasks drain, so reporting success synchronously here fired FIRST\r\n // and erased the failure — a `build` whose async work rejected exited 0\r\n // having emitted nothing.\r\n //\r\n // The await is the load-bearing part, not the yield. Scheduling this as a\r\n // bare `setImmediate` let `execute()` RESOLVE while the verdict was still\r\n // pending: the callback outlived the call it belonged to, so the green\r\n // check could surface after the caller had already moved on — in a later\r\n // command, or after a rejection reported in the interim, which the guard\r\n // below cannot see because by then it reads the state of a run that has\r\n // already ended. Awaiting binds the banner to THIS invocation: `execute`\r\n // does not resolve until the verdict it is about to report has settled.\r\n //\r\n // `setImmediate` runs in the same loop iteration's check phase, so this\r\n // cannot hang on a lingering handle the way dropping the exit would; it\r\n // only lets `captureFatalAsyncErrors` win the race when there is one.\r\n await new Promise<void>((resolve) => setImmediate(resolve));\r\n\r\n // The yield is what gives a floating rejection the chance to be reported;\r\n // this is what acts on it. A green check printed after that error would\r\n // contradict it, and the exit that follows would reset `process.exitCode`\r\n // to 0 — re-erasing the very failure this whole path exists to surface.\r\n if (this.fatalAsyncErrorReported) return \"failed\";\r\n\r\n displayCommandSuccess(command.name, Date.now() - startTime);\r\n\r\n return \"succeeded\";\r\n } catch (error) {\r\n displayCommandError(command.name, error as Error);\r\n // Persistent commands (dev/start) own their startup-failure exits;\r\n // by the time an error reaches here they've already entered their\r\n // run loop, so it's a runtime error we log but don't crash on —\r\n // HMR or process supervisors can recover. Non-persistent\r\n // one-shot commands always exit on failure.\r\n if (!command.isPersistent) {\r\n process.exit(1);\r\n }\r\n\r\n return \"failed\";\r\n }\r\n }\r\n\r\n /**\r\n * Fail loudly on async errors that escape the command's own promise.\r\n *\r\n * `execute` awaits `command.execute(data)`, but work the command merely\r\n * *started* settles outside that await — `@mongez/events` `trigger` /\r\n * `triggerAll` are synchronous and do not await their listeners, so an async\r\n * listener that throws leaves a floating rejection behind. Node reports it at\r\n * the end of the turn, by which point the CLI had already exited 0: the\r\n * failure was erased and the command looked green.\r\n *\r\n * Persistent commands (dev/start) are exempt from the exit for the same\r\n * reason as the catch block in `execute`: by the time they reach this point\r\n * they own a run loop that is expected to survive runtime errors so HMR or a\r\n * supervisor can recover. They still print — silence is what made this class\r\n * of failure invisible in the first place.\r\n */\r\n protected fatalAsyncErrorReported = false;\r\n\r\n protected captureFatalAsyncErrors(command: CLICommand) {\r\n const onFatalAsyncError = (reason: unknown) => {\r\n // A rejection reason is not necessarily an Error (`Promise.reject(\"x\")`),\r\n // and a crash handler must never itself throw.\r\n const error = reason instanceof Error ? reason : new Error(String(reason));\r\n\r\n // Set before printing, so the deferred success banner is suppressed even\r\n // if displaying the error yields.\r\n this.fatalAsyncErrorReported = true;\r\n\r\n displayCommandError(command.name, error);\r\n\r\n if (!command.isPersistent) {\r\n exitAfterFlush(1);\r\n }\r\n };\r\n\r\n process.on(\"unhandledRejection\", onFatalAsyncError);\r\n process.on(\"uncaughtException\", onFatalAsyncError);\r\n }\r\n\r\n /**\r\n * Load preloaders\r\n */\r\n protected async loadPreloaders(command: CLICommand) {\r\n const preloaders = command.commandPreload || {};\r\n\r\n if (preloaders.runtimeStrategy) {\r\n Application.setRuntimeStrategy(preloaders.runtimeStrategy);\r\n }\r\n\r\n // Override, not a default: a command that declares an environment gets it\r\n // regardless of what the shell exported. Then read it back — `process.env`\r\n // is process-global and anything may have written to it, so a value that\r\n // did not take must fail here, loudly, rather than surface later as a\r\n // production React bundle under `warlock dev`.\r\n if (preloaders.environment) {\r\n Application.setEnvironment(preloaders.environment);\r\n\r\n if (process.env.NODE_ENV !== preloaders.environment) {\r\n throw new Error(\r\n `Command \"${command.name}\" set NODE_ENV to \"${preloaders.environment}\", but it read back as ` +\r\n `\"${process.env.NODE_ENV}\". The environment did not take, and every decision made ` +\r\n `downstream of it — env file selection, bundler dep optimisation — would be wrong.`,\r\n );\r\n }\r\n }\r\n\r\n // Env BEFORE config, unconditionally, for every command.\r\n //\r\n // `warlock.config.ts` is a module whose body runs on import, and projects\r\n // legitimately call `env(\"BUILD_OUT\", \"dist\")` in it. Loading config first\r\n // evaluated that body against an empty env store, so every such call\r\n // silently returned its default — under `build` and `start`, which never\r\n // loaded env at all, and under `dev` too, because config was loaded first\r\n // regardless. The ordering is load-bearing in the other direction as well:\r\n // `loadEnv()` reads `process.env.NODE_ENV` at call time to choose\r\n // `.env.<NODE_ENV>` over `.env`, so it must follow `setEnvironment` above.\r\n await loadEnvironmentFiles();\r\n\r\n await warlockConfigManager.load();\r\n\r\n if (preloaders.config || preloaders.bootstrap || preloaders.prestart) {\r\n await filesOrchestrator.init();\r\n }\r\n\r\n // `preloaders.env` is gone: env is loaded above for every command, so the\r\n // branch that used to serve it could only re-parse the same files and\r\n // re-override `process.env` a second time.\r\n if (preloaders.bootstrap) {\r\n await bootstrap();\r\n\r\n if (await fileExistsAsync(appPath(\"bootstrap.ts\"))) {\r\n await filesOrchestrator.load(\"src/app/bootstrap.ts\");\r\n }\r\n }\r\n\r\n // Load configuration files\r\n if (preloaders.config) {\r\n await loadConfigFiles(preloaders.config);\r\n }\r\n\r\n if (preloaders.prestart) {\r\n if (await fileExistsAsync(appPath(\"prestart.ts\"))) {\r\n await filesOrchestrator.load(\"src/app/prestart.ts\");\r\n }\r\n }\r\n\r\n // Initialize connectors.\r\n // `connectors: true` only starts the early phase here — late-phase\r\n // connectors (http, socket) start after the command's action loads\r\n // app code. Explicit lists bypass phase splitting (caller knows\r\n // exactly which connectors they want).\r\n if (preloaders.connectors) {\r\n // REGISTER before starting — the non-bundled counterpart of section 2.5\r\n // of the generated production entry, and the \"dev preloader\" path\r\n // `register-configured-connectors.ts` documents.\r\n //\r\n // Without this, `warlock.config.ts > connectors` drove the BUILD only:\r\n // `warlock build` read the array to drain each connector's contribution,\r\n // but under `warlock dev` nothing ever registered it, so an app had to\r\n // call `connectorsManager.register(...)` from its own `main.ts` to get a\r\n // connector running. Apps that did BOTH then registered the same\r\n // connector twice in production — once here from the baked config, once\r\n // from their own app code — and it booted twice, which surfaced as\r\n // `Route name \"...\" is already taken` while installing page routes.\r\n //\r\n // The array is omitted deliberately, so the function reads the config\r\n // loaded above; `expectedNames` is omitted too, because drift is a\r\n // build↔runtime relationship and dev has no build to drift from.\r\n //\r\n // The idempotency this function provides is against ITSELF — a second\r\n // call skips names the first registered. It does NOT protect against an\r\n // app calling `connectorsManager.register()` directly, which pushes\r\n // unconditionally, and which runs AFTER this point in both modes. So a\r\n // connector belongs in the config array or in app code, never both:\r\n // listing it in both is the duplicate this comment's route-name error\r\n // describes.\r\n registerConfiguredConnectors();\r\n\r\n // Fail fast on a busy port BEFORE the early-phase connectors (database,\r\n // cache, ...) connect. `http` itself is a LATE-phase connector, so its\r\n // own preflight is not reached until after those early connectors are\r\n // up and every app module has been imported — 7-13s of work discarded\r\n // before anyone learns the port was taken. This is the dev-preload\r\n // counterpart of the check the generated production entry runs at the\r\n // same point in its own boot sequence (`production-builder.ts`, step\r\n // 2.6) — a cheap bind-and-release probe, not a connector reorder.\r\n await preflightConfiguredHttpPort();\r\n\r\n if (preloaders.connectors === true) {\r\n await connectorsManager.startPhase(ConnectorLifecyclePhase.Early);\r\n } else {\r\n await connectorsManager.start(preloaders.connectors);\r\n }\r\n }\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli-commands.manager.mjs","names":[],"sources":["../../../../../../../core/src/cli/cli-commands.manager.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { fileExistsAsync } from \"@warlock.js/fs\";\r\nimport { Application } from \"../application\";\r\nimport { bootstrap } from \"../bootstrap\";\r\nimport { loadConfigFiles } from \"../config/load-config-files\";\r\nimport { connectorsManager } from \"../connectors/connectors-manager\";\r\nimport { registerConfiguredConnectors } from \"../connectors/register-configured-connectors\";\r\nimport { ConnectorLifecyclePhase } from \"../connectors/types\";\r\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\r\nimport { isDevWorker } from \"../dev-server/supervisor\";\r\nimport { preflightConfiguredHttpPort, shouldPreflightHttpPort } from \"../http/boot-port-preflight\";\r\nimport { manifestManager } from \"../manifest/manifest-manager\";\r\nimport { appPath } from \"../utils\";\r\nimport { loadEnvironmentFiles } from \"../utils/load-environment\";\r\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\r\nimport { CLICommand } from \"../commands/cli-command\";\r\nimport {\r\n displayBootError,\r\n displayCommandError,\r\n displayCommandHelp,\r\n displayCommandNotFound,\r\n displayCommandSuccess,\r\n displayExecutingCommand,\r\n displayHelp,\r\n displayMissingCommand,\r\n displayMissingOptions,\r\n displayWarlockVersionInTerminal,\r\n type HelpCommandInfo,\r\n isMatchingCommandName,\r\n} from \"./cli-commands.utils\";\r\nimport { cliCommandsLoader } from \"./commands-loader\";\r\nimport { frameworkCommands } from \"./framework-cli-commands\";\r\nimport { CliOptionValueError, type ParsedCliArgs, parseCliArgs } from \"./parse-cli-args\";\r\nimport { findSimilar } from \"./string-similarity\";\r\nimport type { CommandActionData, ResolvedCLICommandOption } from \"../commands/types\";\r\n\r\n/**\r\n * Best-effort budget (ms) for draining stdout/stderr before a forced exit.\r\n * Bounded so a stuck stream can never hang the CLI.\r\n */\r\nconst EXIT_FLUSH_BUDGET = 500;\r\n\r\n/**\r\n * Exit with `code` once stdout/stderr have drained.\r\n *\r\n * `process.exit()` truncates writes still queued on a non-TTY stdout — a pipe,\r\n * a CI log — which is how the *reason* for a failure gets lost even when the\r\n * exit code is right. `exitCode` is set up front so that a natural exit (should\r\n * the drain outlive the process's remaining work) is non-zero too.\r\n */\r\nfunction exitAfterFlush(code: number) {\r\n process.exitCode = code;\r\n\r\n const timer = setTimeout(() => process.exit(code), EXIT_FLUSH_BUDGET);\r\n timer.unref?.();\r\n\r\n let pending = 2;\r\n const drained = () => {\r\n if (--pending > 0) return;\r\n\r\n clearTimeout(timer);\r\n process.exit(code);\r\n };\r\n\r\n // A zero-length write's callback fires once everything queued ahead of it has\r\n // been handed to the OS.\r\n process.stdout.write(\"\", drained);\r\n process.stderr.write(\"\", drained);\r\n}\r\n\r\n/**\r\n * How a command run ended, from the process's point of view.\r\n *\r\n * - `succeeded` — finished, nothing rejected afterwards; the CLI may exit 0.\r\n * - `failed` — reported an error; the exit code is owned by whoever reported it.\r\n * - `running` — a persistent command (dev/start) now owns the process.\r\n *\r\n * `execute` returns this instead of exiting on success itself. A method that\r\n * terminates the process out from under its caller cannot be awaited honestly:\r\n * the exit it schedules outlives the call, so the success it represents is\r\n * still unsettled when `execute` resolves — which is exactly how a green check\r\n * ends up printed next to the failure that contradicts it.\r\n */\r\nexport type CommandRunVerdict = \"succeeded\" | \"failed\" | \"running\";\r\n\r\n/**\r\n * Whether this process is the CHILD half of a supervised command.\r\n *\r\n * ## The finding this encodes\r\n *\r\n * `warlock dev` printing `› Running dev...` twice was NOT a double boot, and\r\n * the distinction is the whole point of this function existing rather than the\r\n * line simply being deleted. The sequence, in file order:\r\n *\r\n * 1. `execute()` prints the header (below) — BEFORE `commandPreAction`.\r\n * 2. `cli/commands/dev-server.command.ts` `preAction` calls\r\n * `superviseDevServer()` when `!isDevWorker()`.\r\n * 3. `dev-server/supervisor.ts` `spawnWorker()` re-spawns `process.argv` with\r\n * `WARLOCK_DEV_WORKER=1`, so the child runs `warlock dev` again and prints\r\n * the header too.\r\n * 4. `superviseDevServer()` returns a promise that never settles, so the\r\n * supervisor's `await commandPreAction(...)` never resolves — `loadPreloaders`\r\n * and `command.execute` are unreachable in the supervisor.\r\n *\r\n * Step 4 is why only ONE process ever loads config, connectors, app modules or\r\n * a scheduler. Verified by running it: one `APP-CODE-LOADED`, one\r\n * `Application.markBooted`, one `⚡ Warlock.js` banner, one route registration,\r\n * two processes. The `2 job(s) registered` in the original report is two jobs\r\n * in one scheduler, not two schedulers — a genuine double boot would have\r\n * printed that warning twice.\r\n *\r\n * So the defect is exactly one duplicated line, and this is the guard that\r\n * keeps it one. `tests/unit/cli/dev-command-single-boot.test.ts` fails if the\r\n * supervisor ever starts running preloaders — i.e. if the double boot the\r\n * duplicated line looked like ever becomes real.\r\n */\r\nfunction isRespawnedWorker(): boolean {\r\n return isDevWorker();\r\n}\r\n\r\nexport class CLICommandsManager {\r\n /**\r\n * List of commands\r\n */\r\n public commands: CLICommand[] = [];\r\n\r\n /**\r\n * Commands map (name -> command)\r\n */\r\n public commandsMap: Map<string, CLICommand> = new Map();\r\n\r\n /**\r\n * Alias map (alias -> command name)\r\n */\r\n public aliasMap: Map<string, string> = new Map();\r\n\r\n /**\r\n * Register the given commands\r\n */\r\n public register(...commands: CLICommand[]): this {\r\n this.commands.push(...commands);\r\n\r\n commands.forEach((command) => {\r\n // `split` always yields a first element, so this holds. Falling back to\r\n // the whole name rather than asserting keeps the map keyed by a REAL\r\n // command name — a key of `undefined` would register the command under\r\n // the literal string \"undefined\" and make it unreachable by its own name.\r\n const commandKey = command.name.split(\" \")[0] ?? command.name;\r\n\r\n manifestManager.addCommandToList(command.name, {\r\n relativePath: command.commandRelativePath,\r\n source: command.commandSource || \"project\",\r\n description: command.commandDescription,\r\n alias: command.commandAlias,\r\n options: command.commandOptions.length > 0 ? command.commandOptions : undefined,\r\n });\r\n\r\n this.commandsMap.set(commandKey, command);\r\n\r\n // Register alias if exists\r\n if (command.commandAlias) {\r\n this.aliasMap.set(command.commandAlias, commandKey);\r\n }\r\n });\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get command by name or alias\r\n */\r\n public getCommand(name: string): CLICommand | undefined {\r\n // First try direct lookup\r\n let command = this.commandsMap.get(name);\r\n if (command) return command;\r\n\r\n // Try alias lookup\r\n const realName = this.aliasMap.get(name);\r\n if (realName) {\r\n return this.commandsMap.get(realName);\r\n }\r\n\r\n return;\r\n }\r\n\r\n /**\r\n * Get all command names and aliases\r\n * Used for fuzzy matching suggestions\r\n */\r\n public getAllCommandNames(): string[] {\r\n const names: string[] = [];\r\n\r\n // Add all command names\r\n for (const name of this.commandsMap.keys()) {\r\n names.push(name);\r\n }\r\n\r\n // Add all aliases\r\n for (const alias of this.aliasMap.keys()) {\r\n names.push(alias);\r\n }\r\n\r\n // Also include cached commands from manifest\r\n const manifestCommands = manifestManager.commandsJson?.commands || {};\r\n // `Object.entries` rather than keys + index: the index read is\r\n // `T | undefined` under the strictness contract even for a key that came\r\n // from the object itself, and these names feed the CLI's \"did you mean?\"\r\n // suggestions — a literal \"undefined\" in that list is a suggestion no\r\n // operator can act on.\r\n for (const [name, meta] of Object.entries(manifestCommands)) {\r\n const baseName = name.split(\" \")[0] ?? name;\r\n\r\n if (!names.includes(baseName)) {\r\n names.push(baseName);\r\n }\r\n\r\n const alias = meta?.alias;\r\n\r\n if (alias && !names.includes(alias)) {\r\n names.push(alias);\r\n }\r\n }\r\n\r\n return names;\r\n }\r\n\r\n /**\r\n * Start the cli manager\r\n */\r\n public async start() {\r\n // Schema-less first pass: it exists to discover the command name and the\r\n // command-independent flags (--help, --version, --no-cache). Values here are\r\n // raw strings; the typed pass below is the one whose options reach a command.\r\n const { name, options } = parseCliArgs(process.argv);\r\n\r\n if (options.noCache) {\r\n manifestManager.clearCommandsCache();\r\n // remove the commands.json file as wel\r\n await manifestManager.removeCommandsFile();\r\n }\r\n\r\n if (options.version || options.v) {\r\n await displayWarlockVersionInTerminal();\r\n process.exit(0);\r\n }\r\n\r\n // Try to load from manifest first (fastest path)\r\n await manifestManager.loadCommands();\r\n\r\n // Register framework commands first (needed for help)\r\n this.register(\r\n ...frameworkCommands.map((command) => {\r\n if (!command.commandSource) {\r\n command.commandSource = \"framework\";\r\n }\r\n return command;\r\n }),\r\n );\r\n\r\n const isHelpCommand = options.help || options.h;\r\n\r\n // Handle global help (no command)\r\n if (!name && isHelpCommand) {\r\n await this.showGlobalHelp();\r\n process.exit(0);\r\n }\r\n\r\n // Handle warm cache\r\n if (options.warmCache) {\r\n await this.warmCache();\r\n process.exit(0);\r\n }\r\n\r\n if (!name) {\r\n displayMissingCommand();\r\n process.exit(1);\r\n }\r\n\r\n const command = await this.lazyGetCommand(name);\r\n\r\n if (manifestManager.hasChanges) {\r\n await manifestManager.saveCommands();\r\n }\r\n\r\n if (!command) {\r\n // Find similar commands for suggestions\r\n const allCommandNames = this.getAllCommandNames();\r\n const suggestions = findSimilar(name, allCommandNames).map((s) => s.value);\r\n\r\n displayCommandNotFound(name, suggestions);\r\n process.exit(1);\r\n }\r\n\r\n // Handle command-specific help\r\n if (isHelpCommand) {\r\n displayCommandHelp({\r\n name: command.name,\r\n alias: command.commandAlias,\r\n description: command.commandDescription,\r\n options: command.commandOptions,\r\n });\r\n process.exit(0);\r\n }\r\n\r\n // The first parse above had no command to consult, so every value in it is\r\n // a raw string. Now that the command is resolved, re-read argv against its\r\n // declared options so a `type: \"boolean\"` option arrives as a boolean.\r\n const resolved = this.resolveCommandArgs(command);\r\n\r\n const verdict = await this.execute(command, {\r\n options: resolved.options,\r\n args: resolved.args,\r\n });\r\n\r\n // The process-level exit belongs to the entrypoint, not to `execute`.\r\n // A one-shot command can leave a handle open (a db connection, a watcher a\r\n // plugin forgot to close), so the CLI exits explicitly rather than waiting\r\n // for the loop to drain — but only on a verdict of `succeeded`. `failed`\r\n // already exited non-zero through whoever reported it, and `running` means\r\n // a persistent command owns the process now.\r\n if (verdict === \"succeeded\") {\r\n exitAfterFlush(0);\r\n }\r\n }\r\n\r\n /**\r\n * Re-parse argv against the resolved command's declared options.\r\n *\r\n * parseCliArgs runs twice on purpose. The first run produces the command\r\n * name, and there is no schema to consult yet; the second one, here, can be\r\n * type-aware. Re-reading argv (rather than post-processing the first result)\r\n * is what makes `--rollback users.ts` recoverable: by the time the first pass\r\n * returns, the swallowed positional is indistinguishable from a value.\r\n */\r\n protected resolveCommandArgs(command: CLICommand, argv: string[] = process.argv): ParsedCliArgs {\r\n try {\r\n return parseCliArgs(argv, command.commandOptions);\r\n } catch (error) {\r\n if (error instanceof CliOptionValueError) {\r\n console.log();\r\n console.log(` ${colors.redBright(\"Error:\")} ${error.message}`);\r\n console.log();\r\n process.exit(1);\r\n }\r\n\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Show global help with all commands\r\n * Uses manifest if available for fast display\r\n */\r\n protected async showGlobalHelp() {\r\n if (\r\n manifestManager.isCommandLoaded &&\r\n Object.keys(manifestManager.commandsJson?.commands || {}).length > 0\r\n ) {\r\n // Use manifest directly - no need to load command files\r\n const helpCommands: HelpCommandInfo[] = Object.entries(\r\n manifestManager.commandsJson?.commands || {},\r\n ).map(([name, cmd]) => ({\r\n name,\r\n alias: cmd.alias,\r\n description: cmd.description,\r\n source: cmd.source,\r\n }));\r\n\r\n await displayHelp(helpCommands);\r\n return;\r\n }\r\n\r\n // Fallback: No manifest, build from registered commands\r\n // This happens on first run before warm-cache\r\n await this.loadPluginsCommands();\r\n\r\n const projectCommands = await cliCommandsLoader.scanAll();\r\n\r\n this.register(...projectCommands);\r\n\r\n await manifestManager.saveCommands();\r\n\r\n // Build help info from registered commands\r\n const helpCommands: HelpCommandInfo[] = this.commands.map((cmd) => ({\r\n name: cmd.name,\r\n alias: cmd.commandAlias,\r\n description: cmd.commandDescription,\r\n source: cmd.commandSource || \"project\",\r\n }));\r\n\r\n await displayHelp(helpCommands);\r\n }\r\n\r\n /**\r\n * Warm cache - scan all project commands and save to manifest\r\n */\r\n protected async warmCache() {\r\n console.log();\r\n console.log(` ${colors.cyan(\"›\")} Scanning project commands...`);\r\n\r\n const projectCommands = await cliCommandsLoader.scanAll();\r\n\r\n this.register(...projectCommands);\r\n\r\n await manifestManager.saveCommands();\r\n\r\n console.log(\r\n ` ${colors.green(\"✔\")} Cached ${colors.bold(String(projectCommands.length))} project commands`,\r\n );\r\n console.log();\r\n }\r\n\r\n /**\r\n * Load plugins commands\r\n */\r\n protected async loadPluginsCommands() {\r\n await warlockConfigManager.load();\r\n\r\n if (warlockConfigManager.isLoaded) {\r\n this.register(\r\n ...(warlockConfigManager.get(\"cli\")?.commands || []).map((command) => {\r\n if (!command.commandSource) {\r\n command.commandSource = \"plugin\";\r\n }\r\n return command;\r\n }),\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Try to find the command based on the given command name or alias\r\n */\r\n protected async lazyGetCommand(name: string) {\r\n // first step, try to find it directly through current commands\r\n let command = this.getCommand(name);\r\n if (command) {\r\n return command;\r\n }\r\n\r\n // second step, try to find it through warlock config commands\r\n await this.loadPluginsCommands();\r\n\r\n command = this.getCommand(name);\r\n\r\n if (command) {\r\n return command;\r\n }\r\n\r\n // third step, try to find it through project commands\r\n await this.loadProjectCommands(name);\r\n\r\n command = this.getCommand(name);\r\n if (command) {\r\n return command;\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Load project commands\r\n */\r\n protected async loadProjectCommands(name: string) {\r\n // first get the commands.json contents as an object\r\n const jsonCommandsFile = await manifestManager.loadCommands();\r\n\r\n if (jsonCommandsFile) {\r\n // Check by name or alias\r\n // `Object.entries` hands the metadata over already narrowed; a `for...in`\r\n // index read is `T | undefined` and every field below is read off it.\r\n for (const [fullCommandName, cmdMeta] of Object.entries(jsonCommandsFile.commands)) {\r\n if (cmdMeta === undefined) continue;\r\n\r\n // Match by name or alias\r\n if (isMatchingCommandName(fullCommandName, name) || cmdMeta.alias === name) {\r\n const commandPath = cmdMeta.relativePath;\r\n if (commandPath) {\r\n const executedCommand = await cliCommandsLoader.load(commandPath);\r\n\r\n if (executedCommand) {\r\n executedCommand.$relativePath(commandPath);\r\n this.register(executedCommand);\r\n return;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n const command = await cliCommandsLoader.locate(name);\r\n\r\n if (command) {\r\n this.register(command);\r\n return;\r\n }\r\n }\r\n\r\n /**\r\n * Validate required options\r\n */\r\n protected validateOptions(\r\n command: CLICommand,\r\n options: Record<string, string | boolean | number>,\r\n ): ResolvedCLICommandOption[] {\r\n const missing: ResolvedCLICommandOption[] = [];\r\n\r\n command.commandOptions.forEach((opt) => {\r\n if (opt.required) {\r\n // Check if option is provided by name or alias\r\n const hasOption =\r\n options[opt.name] !== undefined || (opt.alias && options[opt.alias] !== undefined);\r\n if (!hasOption) {\r\n missing.push(opt);\r\n }\r\n }\r\n });\r\n\r\n return missing;\r\n }\r\n\r\n /**\r\n * Apply default values to options\r\n */\r\n protected applyDefaultOptions(\r\n command: CLICommand,\r\n options: Record<string, string | boolean | number>,\r\n ): Record<string, string | boolean | number> {\r\n const result = { ...options };\r\n\r\n command.commandOptions.forEach((opt) => {\r\n if (opt.defaultValue !== undefined) {\r\n const hasOption =\r\n result[opt.name] !== undefined || (opt.alias && result[opt.alias] !== undefined);\r\n if (!hasOption) {\r\n result[opt.name] = opt.defaultValue;\r\n }\r\n }\r\n\r\n // `!== undefined`, not truthiness: `-r=false` now resolves to the boolean\r\n // `false`, and a truthiness test would drop it before it ever reached the\r\n // canonical name — leaving `rollback` undefined instead of explicitly off.\r\n if (\r\n opt.alias !== undefined &&\r\n result[opt.alias] !== undefined &&\r\n result[opt.name] === undefined\r\n ) {\r\n // The `!== undefined` guard on the line above already proves this read\r\n // is defined, but the compiler does not carry a guard on one index\r\n // expression to a second, textually identical one. Reading it into a\r\n // local makes the narrowing survive, WITHOUT weakening the check — an\r\n // `?? ` fallback here would resurrect exactly the bug the comment above\r\n // describes, turning an explicit `-r=false` into a default.\r\n const aliasValue = result[opt.alias];\r\n\r\n if (aliasValue !== undefined) {\r\n result[opt.name] = aliasValue;\r\n }\r\n }\r\n });\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Execute the given command and report how it ended.\r\n *\r\n * Returning the verdict — rather than exiting on success from in here — is\r\n * what makes the awaited result trustworthy: see {@link CommandRunVerdict}.\r\n */\r\n public async execute(command: CLICommand, data: CommandActionData): Promise<CommandRunVerdict> {\r\n const startTime = Date.now();\r\n\r\n // Validate required options\r\n const missingOptions = this.validateOptions(command, data.options);\r\n if (missingOptions.length > 0) {\r\n displayMissingOptions(missingOptions);\r\n process.exit(1);\r\n }\r\n\r\n // Apply default values\r\n data.options = this.applyDefaultOptions(command, data.options);\r\n\r\n // ONE header per `warlock <command>` the user typed — not one per process.\r\n //\r\n // `warlock dev` is TWO processes by design: the invocation the user typed\r\n // becomes a thin supervisor (`dev-server/supervisor.ts`) that re-spawns\r\n // `process.argv` with `WARLOCK_DEV_WORKER=1`, and the child is the actual\r\n // server. Both are `warlock dev`, so both reached this line and the user saw\r\n //\r\n // › Running dev...\r\n // › Running dev...\r\n //\r\n // which reads exactly like the application booting twice. It is not — see\r\n // the block comment on `isRespawnedWorker` — but the line was still wrong,\r\n // and it re-printed on every crash-restart on top of the supervisor's own\r\n // \"restarting\" notice.\r\n //\r\n // The SUPERVISOR keeps the header, not the worker: it prints immediately\r\n // (so a slow boot is not silent), it prints once for the whole session\r\n // (restarts are already announced as restarts), and it is the process the\r\n // user actually started.\r\n if (!isRespawnedWorker()) {\r\n displayExecutingCommand(command.name);\r\n }\r\n\r\n if (command.commandPreAction) {\r\n await command.commandPreAction(data);\r\n }\r\n\r\n // load preloaders\r\n if (command.commandPreload) {\r\n try {\r\n await this.loadPreloaders(command);\r\n } catch (error) {\r\n // A preload failure (a bad import in a config file, a connector that\r\n // throws on boot, a missing module export) happens BEFORE the command's\r\n // action runs — there is no run loop to recover into, so it is ALWAYS\r\n // fatal, persistent command (dev/start) or not. Surface the real cause\r\n // and the offending file, then exit. This MUST stay outside the\r\n // command-execute try/catch below: letting a preload rejection escape\r\n // turned `warlock dev` into a silent hang — the unhandled rejection slid\r\n // past while the already-started loader worker thread kept the process\r\n // alive, freezing just after the banner with no error printed.\r\n displayBootError(command.name, error as Error);\r\n process.exit(1);\r\n }\r\n }\r\n\r\n this.captureFatalAsyncErrors(command);\r\n\r\n try {\r\n await command.execute(data);\r\n\r\n // Persistent commands (dev/start) hand control to their run loop here.\r\n // There is no success banner to print and nothing to exit for.\r\n if (command.isPersistent) return \"running\";\r\n\r\n // Yield one full event-loop turn before declaring success, and AWAIT that\r\n // yield. Node flags an unhandled rejection only after the current turn's\r\n // microtasks drain, so reporting success synchronously here fired FIRST\r\n // and erased the failure — a `build` whose async work rejected exited 0\r\n // having emitted nothing.\r\n //\r\n // The await is the load-bearing part, not the yield. Scheduling this as a\r\n // bare `setImmediate` let `execute()` RESOLVE while the verdict was still\r\n // pending: the callback outlived the call it belonged to, so the green\r\n // check could surface after the caller had already moved on — in a later\r\n // command, or after a rejection reported in the interim, which the guard\r\n // below cannot see because by then it reads the state of a run that has\r\n // already ended. Awaiting binds the banner to THIS invocation: `execute`\r\n // does not resolve until the verdict it is about to report has settled.\r\n //\r\n // `setImmediate` runs in the same loop iteration's check phase, so this\r\n // cannot hang on a lingering handle the way dropping the exit would; it\r\n // only lets `captureFatalAsyncErrors` win the race when there is one.\r\n await new Promise<void>((resolve) => setImmediate(resolve));\r\n\r\n // The yield is what gives a floating rejection the chance to be reported;\r\n // this is what acts on it. A green check printed after that error would\r\n // contradict it, and the exit that follows would reset `process.exitCode`\r\n // to 0 — re-erasing the very failure this whole path exists to surface.\r\n if (this.fatalAsyncErrorReported) return \"failed\";\r\n\r\n displayCommandSuccess(command.name, Date.now() - startTime);\r\n\r\n return \"succeeded\";\r\n } catch (error) {\r\n displayCommandError(command.name, error as Error);\r\n // Persistent commands (dev/start) own their startup-failure exits;\r\n // by the time an error reaches here they've already entered their\r\n // run loop, so it's a runtime error we log but don't crash on —\r\n // HMR or process supervisors can recover. Non-persistent\r\n // one-shot commands always exit on failure.\r\n if (!command.isPersistent) {\r\n process.exit(1);\r\n }\r\n\r\n return \"failed\";\r\n }\r\n }\r\n\r\n /**\r\n * Fail loudly on async errors that escape the command's own promise.\r\n *\r\n * `execute` awaits `command.execute(data)`, but work the command merely\r\n * *started* settles outside that await — `@mongez/events` `trigger` /\r\n * `triggerAll` are synchronous and do not await their listeners, so an async\r\n * listener that throws leaves a floating rejection behind. Node reports it at\r\n * the end of the turn, by which point the CLI had already exited 0: the\r\n * failure was erased and the command looked green.\r\n *\r\n * Persistent commands (dev/start) are exempt from the exit for the same\r\n * reason as the catch block in `execute`: by the time they reach this point\r\n * they own a run loop that is expected to survive runtime errors so HMR or a\r\n * supervisor can recover. They still print — silence is what made this class\r\n * of failure invisible in the first place.\r\n */\r\n protected fatalAsyncErrorReported = false;\r\n\r\n protected captureFatalAsyncErrors(command: CLICommand) {\r\n const onFatalAsyncError = (reason: unknown) => {\r\n // A rejection reason is not necessarily an Error (`Promise.reject(\"x\")`),\r\n // and a crash handler must never itself throw.\r\n const error = reason instanceof Error ? reason : new Error(String(reason));\r\n\r\n // Set before printing, so the deferred success banner is suppressed even\r\n // if displaying the error yields.\r\n this.fatalAsyncErrorReported = true;\r\n\r\n displayCommandError(command.name, error);\r\n\r\n if (!command.isPersistent) {\r\n exitAfterFlush(1);\r\n }\r\n };\r\n\r\n process.on(\"unhandledRejection\", onFatalAsyncError);\r\n process.on(\"uncaughtException\", onFatalAsyncError);\r\n }\r\n\r\n /**\r\n * Load preloaders\r\n */\r\n protected async loadPreloaders(command: CLICommand) {\r\n const preloaders = command.commandPreload || {};\r\n\r\n if (preloaders.runtimeStrategy) {\r\n Application.setRuntimeStrategy(preloaders.runtimeStrategy);\r\n }\r\n\r\n // Override, not a default: a command that declares an environment gets it\r\n // regardless of what the shell exported. Then read it back — `process.env`\r\n // is process-global and anything may have written to it, so a value that\r\n // did not take must fail here, loudly, rather than surface later as a\r\n // production React bundle under `warlock dev`.\r\n if (preloaders.environment) {\r\n Application.setEnvironment(preloaders.environment);\r\n\r\n if (process.env.NODE_ENV !== preloaders.environment) {\r\n throw new Error(\r\n `Command \"${command.name}\" set NODE_ENV to \"${preloaders.environment}\", but it read back as ` +\r\n `\"${process.env.NODE_ENV}\". The environment did not take, and every decision made ` +\r\n `downstream of it — env file selection, bundler dep optimisation — would be wrong.`,\r\n );\r\n }\r\n }\r\n\r\n // Env BEFORE config, unconditionally, for every command.\r\n //\r\n // `warlock.config.ts` is a module whose body runs on import, and projects\r\n // legitimately call `env(\"BUILD_OUT\", \"dist\")` in it. Loading config first\r\n // evaluated that body against an empty env store, so every such call\r\n // silently returned its default — under `build` and `start`, which never\r\n // loaded env at all, and under `dev` too, because config was loaded first\r\n // regardless. The ordering is load-bearing in the other direction as well:\r\n // `loadEnv()` reads `process.env.NODE_ENV` at call time to choose\r\n // `.env.<NODE_ENV>` over `.env`, so it must follow `setEnvironment` above.\r\n await loadEnvironmentFiles();\r\n\r\n await warlockConfigManager.load();\r\n\r\n if (preloaders.config || preloaders.bootstrap || preloaders.prestart) {\r\n await filesOrchestrator.init();\r\n }\r\n\r\n // `preloaders.env` is gone: env is loaded above for every command, so the\r\n // branch that used to serve it could only re-parse the same files and\r\n // re-override `process.env` a second time.\r\n if (preloaders.bootstrap) {\r\n await bootstrap();\r\n\r\n if (await fileExistsAsync(appPath(\"bootstrap.ts\"))) {\r\n await filesOrchestrator.load(\"src/app/bootstrap.ts\");\r\n }\r\n }\r\n\r\n // Load configuration files\r\n if (preloaders.config) {\r\n await loadConfigFiles(preloaders.config);\r\n }\r\n\r\n if (preloaders.prestart) {\r\n if (await fileExistsAsync(appPath(\"prestart.ts\"))) {\r\n await filesOrchestrator.load(\"src/app/prestart.ts\");\r\n }\r\n }\r\n\r\n // Initialize connectors.\r\n // `connectors: true` only starts the early phase here — late-phase\r\n // connectors (http, socket) start after the command's action loads\r\n // app code. Explicit lists bypass phase splitting (caller knows\r\n // exactly which connectors they want).\r\n if (preloaders.connectors) {\r\n // REGISTER before starting — the non-bundled counterpart of section 2.5\r\n // of the generated production entry, and the \"dev preloader\" path\r\n // `register-configured-connectors.ts` documents.\r\n //\r\n // Without this, `warlock.config.ts > connectors` drove the BUILD only:\r\n // `warlock build` read the array to drain each connector's contribution,\r\n // but under `warlock dev` nothing ever registered it, so an app had to\r\n // call `connectorsManager.register(...)` from its own `main.ts` to get a\r\n // connector running. Apps that did BOTH then registered the same\r\n // connector twice in production — once here from the baked config, once\r\n // from their own app code — and it booted twice, which surfaced as\r\n // `Route name \"...\" is already taken` while installing page routes.\r\n //\r\n // The array is omitted deliberately, so the function reads the config\r\n // loaded above; `expectedNames` is omitted too, because drift is a\r\n // build↔runtime relationship and dev has no build to drift from.\r\n //\r\n // The idempotency this function provides is against ITSELF — a second\r\n // call skips names the first registered. It does NOT protect against an\r\n // app calling `connectorsManager.register()` directly, which pushes\r\n // unconditionally, and which runs AFTER this point in both modes. So a\r\n // connector belongs in the config array or in app code, never both:\r\n // listing it in both is the duplicate this comment's route-name error\r\n // describes.\r\n registerConfiguredConnectors();\r\n\r\n // Fail fast on a busy port BEFORE the early-phase connectors (database,\r\n // cache, ...) connect. `http` itself is a LATE-phase connector, so its\r\n // own preflight is not reached until after those early connectors are\r\n // up and every app module has been imported — 7-13s of work discarded\r\n // before anyone learns the port was taken. This is the dev-preload\r\n // counterpart of the check the generated production entry runs at the\r\n // same point in its own boot sequence (`production-builder.ts`, step\r\n // 2.6) — a cheap bind-and-release probe, not a connector reorder.\r\n // Only when this boot will actually start http: a scoped list without\r\n // \"http\" (seed, migrate, …) never binds the port, so probing it would\r\n // collide with a running dev server on a port it never uses (f9ace89e).\r\n if (shouldPreflightHttpPort(preloaders.connectors)) {\r\n await preflightConfiguredHttpPort();\r\n }\r\n\r\n if (preloaders.connectors === true) {\r\n await connectorsManager.startPhase(ConnectorLifecyclePhase.Early);\r\n } else {\r\n await connectorsManager.start(preloaders.connectors);\r\n }\r\n }\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,MAAM,oBAAoB;;;;;;;;;AAU1B,SAAS,eAAe,MAAc;CACpC,QAAQ,WAAW;CAEnB,MAAM,QAAQ,iBAAiB,QAAQ,KAAK,IAAI,GAAG,iBAAiB;CACpE,MAAM,QAAQ;CAEd,IAAI,UAAU;CACd,MAAM,gBAAgB;EACpB,IAAI,EAAE,UAAU,GAAG;EAEnB,aAAa,KAAK;EAClB,QAAQ,KAAK,IAAI;CACnB;CAIA,QAAQ,OAAO,MAAM,IAAI,OAAO;CAChC,QAAQ,OAAO,MAAM,IAAI,OAAO;AAClC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,SAAS,oBAA6B;CACpC,OAAO,YAAY;AACrB;AAEA,IAAa,qBAAb,MAAgC;;kBAIE,CAAC;qCAKa,IAAI,IAAI;kCAKf,IAAI,IAAI;iCAojBX;;;;;CA/iBpC,AAAO,SAAS,GAAG,UAA8B;EAC/C,KAAK,SAAS,KAAK,GAAG,QAAQ;EAE9B,SAAS,SAAS,YAAY;GAK5B,MAAM,aAAa,QAAQ,KAAK,MAAM,GAAG,CAAC,CAAC,MAAM,QAAQ;GAEzD,gBAAgB,iBAAiB,QAAQ,MAAM;IAC7C,cAAc,QAAQ;IACtB,QAAQ,QAAQ,iBAAiB;IACjC,aAAa,QAAQ;IACrB,OAAO,QAAQ;IACf,SAAS,QAAQ,eAAe,SAAS,IAAI,QAAQ,iBAAiB;GACxE,CAAC;GAED,KAAK,YAAY,IAAI,YAAY,OAAO;GAGxC,IAAI,QAAQ,cACV,KAAK,SAAS,IAAI,QAAQ,cAAc,UAAU;EAEtD,CAAC;EAED,OAAO;CACT;;;;CAKA,AAAO,WAAW,MAAsC;EAEtD,IAAI,UAAU,KAAK,YAAY,IAAI,IAAI;EACvC,IAAI,SAAS,OAAO;EAGpB,MAAM,WAAW,KAAK,SAAS,IAAI,IAAI;EACvC,IAAI,UACF,OAAO,KAAK,YAAY,IAAI,QAAQ;CAIxC;;;;;CAMA,AAAO,qBAA+B;EACpC,MAAM,QAAkB,CAAC;EAGzB,KAAK,MAAM,QAAQ,KAAK,YAAY,KAAK,GACvC,MAAM,KAAK,IAAI;EAIjB,KAAK,MAAM,SAAS,KAAK,SAAS,KAAK,GACrC,MAAM,KAAK,KAAK;EAIlB,MAAM,mBAAmB,gBAAgB,cAAc,YAAY,CAAC;EAMpE,KAAK,MAAM,CAAC,MAAM,SAAS,OAAO,QAAQ,gBAAgB,GAAG;GAC3D,MAAM,WAAW,KAAK,MAAM,GAAG,CAAC,CAAC,MAAM;GAEvC,IAAI,CAAC,MAAM,SAAS,QAAQ,GAC1B,MAAM,KAAK,QAAQ;GAGrB,MAAM,QAAQ,MAAM;GAEpB,IAAI,SAAS,CAAC,MAAM,SAAS,KAAK,GAChC,MAAM,KAAK,KAAK;EAEpB;EAEA,OAAO;CACT;;;;CAKA,MAAa,QAAQ;EAInB,MAAM,EAAE,MAAM,YAAY,aAAa,QAAQ,IAAI;EAEnD,IAAI,QAAQ,SAAS;GACnB,gBAAgB,mBAAmB;GAEnC,MAAM,gBAAgB,mBAAmB;EAC3C;EAEA,IAAI,QAAQ,WAAW,QAAQ,GAAG;GAChC,MAAM,gCAAgC;GACtC,QAAQ,KAAK,CAAC;EAChB;EAGA,MAAM,gBAAgB,aAAa;EAGnC,KAAK,SACH,GAAG,kBAAkB,KAAK,YAAY;GACpC,IAAI,CAAC,QAAQ,eACX,QAAQ,gBAAgB;GAE1B,OAAO;EACT,CAAC,CACH;EAEA,MAAM,gBAAgB,QAAQ,QAAQ,QAAQ;EAG9C,IAAI,CAAC,QAAQ,eAAe;GAC1B,MAAM,KAAK,eAAe;GAC1B,QAAQ,KAAK,CAAC;EAChB;EAGA,IAAI,QAAQ,WAAW;GACrB,MAAM,KAAK,UAAU;GACrB,QAAQ,KAAK,CAAC;EAChB;EAEA,IAAI,CAAC,MAAM;GACT,sBAAsB;GACtB,QAAQ,KAAK,CAAC;EAChB;EAEA,MAAM,UAAU,MAAM,KAAK,eAAe,IAAI;EAE9C,IAAI,gBAAgB,YAClB,MAAM,gBAAgB,aAAa;EAGrC,IAAI,CAAC,SAAS;GAKZ,uBAAuB,MAFH,YAAY,MADR,KAAK,mBACuB,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,KAE7B,CAAC;GACxC,QAAQ,KAAK,CAAC;EAChB;EAGA,IAAI,eAAe;GACjB,mBAAmB;IACjB,MAAM,QAAQ;IACd,OAAO,QAAQ;IACf,aAAa,QAAQ;IACrB,SAAS,QAAQ;GACnB,CAAC;GACD,QAAQ,KAAK,CAAC;EAChB;EAKA,MAAM,WAAW,KAAK,mBAAmB,OAAO;EAahD,IAAI,MAXkB,KAAK,QAAQ,SAAS;GAC1C,SAAS,SAAS;GAClB,MAAM,SAAS;EACjB,CAAC,MAQe,aACd,eAAe,CAAC;CAEpB;;;;;;;;;;CAWA,AAAU,mBAAmB,SAAqB,OAAiB,QAAQ,MAAqB;EAC9F,IAAI;GACF,OAAO,aAAa,MAAM,QAAQ,cAAc;EAClD,SAAS,OAAO;GACd,IAAI,iBAAiB,qBAAqB;IACxC,QAAQ,IAAI;IACZ,QAAQ,IAAI,KAAK,OAAO,UAAU,QAAQ,EAAE,GAAG,MAAM,SAAS;IAC9D,QAAQ,IAAI;IACZ,QAAQ,KAAK,CAAC;GAChB;GAEA,MAAM;EACR;CACF;;;;;CAMA,MAAgB,iBAAiB;EAC/B,IACE,gBAAgB,mBAChB,OAAO,KAAK,gBAAgB,cAAc,YAAY,CAAC,CAAC,CAAC,CAAC,SAAS,GACnE;GAWA,MAAM,YATkC,OAAO,QAC7C,gBAAgB,cAAc,YAAY,CAAC,CAC7C,CAAC,CAAC,KAAK,CAAC,MAAM,UAAU;IACtB;IACA,OAAO,IAAI;IACX,aAAa,IAAI;IACjB,QAAQ,IAAI;GACd,EAE6B,CAAC;GAC9B;EACF;EAIA,MAAM,KAAK,oBAAoB;EAE/B,MAAM,kBAAkB,MAAM,kBAAkB,QAAQ;EAExD,KAAK,SAAS,GAAG,eAAe;EAEhC,MAAM,gBAAgB,aAAa;EAUnC,MAAM,YAPkC,KAAK,SAAS,KAAK,SAAS;GAClE,MAAM,IAAI;GACV,OAAO,IAAI;GACX,aAAa,IAAI;GACjB,QAAQ,IAAI,iBAAiB;EAC/B,EAE6B,CAAC;CAChC;;;;CAKA,MAAgB,YAAY;EAC1B,QAAQ,IAAI;EACZ,QAAQ,IAAI,KAAK,OAAO,KAAK,KAAK,EAAE,8BAA8B;EAElE,MAAM,kBAAkB,MAAM,kBAAkB,QAAQ;EAExD,KAAK,SAAS,GAAG,eAAe;EAEhC,MAAM,gBAAgB,aAAa;EAEnC,QAAQ,IACN,KAAK,OAAO,MAAM,KAAK,EAAE,UAAU,OAAO,KAAK,OAAO,gBAAgB,MAAM,CAAC,EAAE,kBACjF;EACA,QAAQ,IAAI;CACd;;;;CAKA,MAAgB,sBAAsB;EACpC,MAAM,qBAAqB,KAAK;EAEhC,IAAI,qBAAqB,UACvB,KAAK,SACH,IAAI,qBAAqB,IAAI,KAAK,CAAC,EAAE,YAAY,CAAC,EAAC,CAAE,KAAK,YAAY;GACpE,IAAI,CAAC,QAAQ,eACX,QAAQ,gBAAgB;GAE1B,OAAO;EACT,CAAC,CACH;CAEJ;;;;CAKA,MAAgB,eAAe,MAAc;EAE3C,IAAI,UAAU,KAAK,WAAW,IAAI;EAClC,IAAI,SACF,OAAO;EAIT,MAAM,KAAK,oBAAoB;EAE/B,UAAU,KAAK,WAAW,IAAI;EAE9B,IAAI,SACF,OAAO;EAIT,MAAM,KAAK,oBAAoB,IAAI;EAEnC,UAAU,KAAK,WAAW,IAAI;EAC9B,IAAI,SACF,OAAO;EAGT,OAAO;CACT;;;;CAKA,MAAgB,oBAAoB,MAAc;EAEhD,MAAM,mBAAmB,MAAM,gBAAgB,aAAa;EAE5D,IAAI,kBAIF,KAAK,MAAM,CAAC,iBAAiB,YAAY,OAAO,QAAQ,iBAAiB,QAAQ,GAAG;GAClF,IAAI,YAAY,QAAW;GAG3B,IAAI,sBAAsB,iBAAiB,IAAI,KAAK,QAAQ,UAAU,MAAM;IAC1E,MAAM,cAAc,QAAQ;IAC5B,IAAI,aAAa;KACf,MAAM,kBAAkB,MAAM,kBAAkB,KAAK,WAAW;KAEhE,IAAI,iBAAiB;MACnB,gBAAgB,cAAc,WAAW;MACzC,KAAK,SAAS,eAAe;MAC7B;KACF;IACF;GACF;EACF;EAGF,MAAM,UAAU,MAAM,kBAAkB,OAAO,IAAI;EAEnD,IAAI,SAAS;GACX,KAAK,SAAS,OAAO;GACrB;EACF;CACF;;;;CAKA,AAAU,gBACR,SACA,SAC4B;EAC5B,MAAM,UAAsC,CAAC;EAE7C,QAAQ,eAAe,SAAS,QAAQ;GACtC,IAAI,IAAI,UAIN;QAAI,EADF,QAAQ,IAAI,UAAU,UAAc,IAAI,SAAS,QAAQ,IAAI,WAAW,SAExE,QAAQ,KAAK,GAAG;GAClB;EAEJ,CAAC;EAED,OAAO;CACT;;;;CAKA,AAAU,oBACR,SACA,SAC2C;EAC3C,MAAM,SAAS,EAAE,GAAG,QAAQ;EAE5B,QAAQ,eAAe,SAAS,QAAQ;GACtC,IAAI,IAAI,iBAAiB,QAGvB;QAAI,EADF,OAAO,IAAI,UAAU,UAAc,IAAI,SAAS,OAAO,IAAI,WAAW,SAEtE,OAAO,IAAI,QAAQ,IAAI;GACzB;GAMF,IACE,IAAI,UAAU,UACd,OAAO,IAAI,WAAW,UACtB,OAAO,IAAI,UAAU,QACrB;IAOA,MAAM,aAAa,OAAO,IAAI;IAE9B,IAAI,eAAe,QACjB,OAAO,IAAI,QAAQ;GAEvB;EACF,CAAC;EAED,OAAO;CACT;;;;;;;CAQA,MAAa,QAAQ,SAAqB,MAAqD;EAC7F,MAAM,YAAY,KAAK,IAAI;EAG3B,MAAM,iBAAiB,KAAK,gBAAgB,SAAS,KAAK,OAAO;EACjE,IAAI,eAAe,SAAS,GAAG;GAC7B,sBAAsB,cAAc;GACpC,QAAQ,KAAK,CAAC;EAChB;EAGA,KAAK,UAAU,KAAK,oBAAoB,SAAS,KAAK,OAAO;EAqB7D,IAAI,CAAC,kBAAkB,GACrB,wBAAwB,QAAQ,IAAI;EAGtC,IAAI,QAAQ,kBACV,MAAM,QAAQ,iBAAiB,IAAI;EAIrC,IAAI,QAAQ,gBACV,IAAI;GACF,MAAM,KAAK,eAAe,OAAO;EACnC,SAAS,OAAO;GAUd,iBAAiB,QAAQ,MAAM,KAAc;GAC7C,QAAQ,KAAK,CAAC;EAChB;EAGF,KAAK,wBAAwB,OAAO;EAEpC,IAAI;GACF,MAAM,QAAQ,QAAQ,IAAI;GAI1B,IAAI,QAAQ,cAAc,OAAO;GAoBjC,MAAM,IAAI,SAAe,YAAY,aAAa,OAAO,CAAC;GAM1D,IAAI,KAAK,yBAAyB,OAAO;GAEzC,sBAAsB,QAAQ,MAAM,KAAK,IAAI,IAAI,SAAS;GAE1D,OAAO;EACT,SAAS,OAAO;GACd,oBAAoB,QAAQ,MAAM,KAAc;GAMhD,IAAI,CAAC,QAAQ,cACX,QAAQ,KAAK,CAAC;GAGhB,OAAO;EACT;CACF;CAoBA,AAAU,wBAAwB,SAAqB;EACrD,MAAM,qBAAqB,WAAoB;GAG7C,MAAM,QAAQ,kBAAkB,QAAQ,SAAS,IAAI,MAAM,OAAO,MAAM,CAAC;GAIzE,KAAK,0BAA0B;GAE/B,oBAAoB,QAAQ,MAAM,KAAK;GAEvC,IAAI,CAAC,QAAQ,cACX,eAAe,CAAC;EAEpB;EAEA,QAAQ,GAAG,sBAAsB,iBAAiB;EAClD,QAAQ,GAAG,qBAAqB,iBAAiB;CACnD;;;;CAKA,MAAgB,eAAe,SAAqB;EAClD,MAAM,aAAa,QAAQ,kBAAkB,CAAC;EAE9C,IAAI,WAAW,iBACb,YAAY,mBAAmB,WAAW,eAAe;EAQ3D,IAAI,WAAW,aAAa;GAC1B,YAAY,eAAe,WAAW,WAAW;GAEjD,IAAI,QAAQ,IAAI,aAAa,WAAW,aACtC,MAAM,IAAI,MACR,YAAY,QAAQ,KAAK,qBAAqB,WAAW,YAAY,0BAC/D,QAAQ,IAAI,SAAS,2IAE7B;EAEJ;EAYA,MAAM,qBAAqB;EAE3B,MAAM,qBAAqB,KAAK;EAEhC,IAAI,WAAW,UAAU,WAAW,aAAa,WAAW,UAC1D,MAAM,kBAAkB,KAAK;EAM/B,IAAI,WAAW,WAAW;GACxB,MAAM,UAAU;GAEhB,IAAI,MAAM,gBAAgB,QAAQ,cAAc,CAAC,GAC/C,MAAM,kBAAkB,KAAK,sBAAsB;EAEvD;EAGA,IAAI,WAAW,QACb,MAAM,gBAAgB,WAAW,MAAM;EAGzC,IAAI,WAAW,UACb;OAAI,MAAM,gBAAgB,QAAQ,aAAa,CAAC,GAC9C,MAAM,kBAAkB,KAAK,qBAAqB;EACpD;EAQF,IAAI,WAAW,YAAY;GAyBzB,6BAA6B;GAa7B,IAAI,wBAAwB,WAAW,UAAU,GAC/C,MAAM,4BAA4B;GAGpC,IAAI,WAAW,eAAe,MAC5B,MAAM,kBAAkB,kBAAwC;QAEhE,MAAM,kBAAkB,MAAM,WAAW,UAAU;EAEvD;CACF;AACF"}
|
|
@@ -2,6 +2,7 @@ import { configCheck } from "./config.check.mjs";
|
|
|
2
2
|
import { connectorsCheck } from "./connectors.check.mjs";
|
|
3
3
|
import { handlerSignatureCheck } from "./handler-signature.check.mjs";
|
|
4
4
|
import { healthCheck } from "./health.check.mjs";
|
|
5
|
+
import { jwtSecretCheck } from "./jwt-secret.check.mjs";
|
|
5
6
|
import { optionalPeersCheck } from "./optional-peers.check.mjs";
|
|
6
7
|
import { releaseHygieneCheck } from "./release-hygiene.check.mjs";
|
|
7
8
|
import { routesCheck } from "./routes.check.mjs";
|
|
@@ -23,6 +24,7 @@ const defaultDoctorChecks = [
|
|
|
23
24
|
configCheck,
|
|
24
25
|
connectorsCheck,
|
|
25
26
|
optionalPeersCheck,
|
|
27
|
+
jwtSecretCheck,
|
|
26
28
|
healthCheck,
|
|
27
29
|
releaseHygieneCheck
|
|
28
30
|
];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/doctor/checks/index.ts"],"sourcesContent":["import type { DoctorCheck } from \"../check.types\";\nimport { configCheck } from \"./config.check\";\nimport { connectorsCheck } from \"./connectors.check\";\nimport { handlerSignatureCheck } from \"./handler-signature.check\";\nimport { healthCheck } from \"./health.check\";\nimport { optionalPeersCheck } from \"./optional-peers.check\";\nimport { releaseHygieneCheck } from \"./release-hygiene.check\";\nimport { routesCheck } from \"./routes.check\";\n\n/**\n * The default, ordered set of checks `warlock doctor` runs. Ordering controls\n * the report layout: runtime-surface checks first (routes, handler signatures,\n * config, connectors, peers, health), release hygiene last.\n *\n * Every check is handed the same boot context, and any of them may return\n * `undefined` to opt out of a project it does not apply to — so this list is\n * the set of checks that COULD run, not the set of lines a given project will\n * print.\n */\nexport const defaultDoctorChecks: DoctorCheck[] = [\n routesCheck,\n handlerSignatureCheck,\n configCheck,\n connectorsCheck,\n optionalPeersCheck,\n healthCheck,\n releaseHygieneCheck,\n];\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/doctor/checks/index.ts"],"sourcesContent":["import type { DoctorCheck } from \"../check.types\";\nimport { configCheck } from \"./config.check\";\nimport { connectorsCheck } from \"./connectors.check\";\nimport { handlerSignatureCheck } from \"./handler-signature.check\";\nimport { healthCheck } from \"./health.check\";\nimport { jwtSecretCheck } from \"./jwt-secret.check\";\nimport { optionalPeersCheck } from \"./optional-peers.check\";\nimport { releaseHygieneCheck } from \"./release-hygiene.check\";\nimport { routesCheck } from \"./routes.check\";\n\n/**\n * The default, ordered set of checks `warlock doctor` runs. Ordering controls\n * the report layout: runtime-surface checks first (routes, handler signatures,\n * config, connectors, peers, health), release hygiene last.\n *\n * Every check is handed the same boot context, and any of them may return\n * `undefined` to opt out of a project it does not apply to — so this list is\n * the set of checks that COULD run, not the set of lines a given project will\n * print.\n */\nexport const defaultDoctorChecks: DoctorCheck[] = [\n routesCheck,\n handlerSignatureCheck,\n configCheck,\n connectorsCheck,\n optionalPeersCheck,\n jwtSecretCheck,\n healthCheck,\n releaseHygieneCheck,\n];\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoBA,MAAa,sBAAqC;CAChD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { config } from "../../../../config/config-getter.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../core/src/cli/commands/doctor/checks/jwt-secret.check.ts
|
|
4
|
+
/**
|
|
5
|
+
* A non-empty string, or `undefined`. A secret arrives from `env(...)`; an
|
|
6
|
+
* unset variable yields `undefined` and a `.env` line with nothing after the
|
|
7
|
+
* `=` yields `""` — both are "not configured".
|
|
8
|
+
*/
|
|
9
|
+
function text(value) {
|
|
10
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Fails when the app has configured auth but no JWT signing secret is set.
|
|
14
|
+
*
|
|
15
|
+
* WHY THIS EXISTS. `authConfig.accessToken.secret()` throws "no JWT secret
|
|
16
|
+
* configured" — but LAZILY, the first time a token is signed (a login). A fresh
|
|
17
|
+
* scaffold's `src/config/auth.ts` reads `env("JWT_SECRET")`, and a fresh `.env`
|
|
18
|
+
* has none, so the app BOOTS clean and then 500s on the very first login, with
|
|
19
|
+
* the real message going only to server stderr (finding 7b606e38). `doctor`
|
|
20
|
+
* turns that first-login surprise into a pre-flight failure that names the fix.
|
|
21
|
+
*
|
|
22
|
+
* NEEDS NO BOOTED APP — it reads config only, like `optional-peers`.
|
|
23
|
+
*
|
|
24
|
+
* OPTS OUT when auth is not in use. The signal is `auth.userType`: the config
|
|
25
|
+
* an app fills in to declare its authenticatable models (`{ user: User }`). An
|
|
26
|
+
* app with no userType configured is not doing auth, so a missing secret is not
|
|
27
|
+
* a finding for it — silence keeps the doctor's false-positive budget intact.
|
|
28
|
+
*
|
|
29
|
+
* The secret is resolved from BOTH the current key (`auth.accessToken.secret`)
|
|
30
|
+
* and the legacy one (`auth.jwt.secret`) the scaffold still emits, so this does
|
|
31
|
+
* not fire on a project that is simply using the older, still-supported shape.
|
|
32
|
+
*/
|
|
33
|
+
const jwtSecretCheck = {
|
|
34
|
+
name: "jwt-secret",
|
|
35
|
+
run: () => {
|
|
36
|
+
const userType = config.get("auth.userType");
|
|
37
|
+
if (!(typeof userType === "object" && userType !== null && Object.keys(userType).length > 0)) return void 0;
|
|
38
|
+
const secret = text(config.get("auth.accessToken.secret")) ?? text(config.get("auth.jwt.secret"));
|
|
39
|
+
const status = secret ? "ok" : "fail";
|
|
40
|
+
if (secret) return {
|
|
41
|
+
name: "jwt-secret",
|
|
42
|
+
status,
|
|
43
|
+
detail: "A JWT signing secret is configured."
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
name: "jwt-secret",
|
|
47
|
+
status,
|
|
48
|
+
detail: "auth is configured (auth.userType) but no JWT signing secret is set — every login and token operation will fail with a generic 500. Run `warlock jwt.generate` to create one, or set `auth.accessToken.secret`."
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
export { jwtSecretCheck };
|
|
55
|
+
//# sourceMappingURL=jwt-secret.check.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt-secret.check.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/doctor/checks/jwt-secret.check.ts"],"sourcesContent":["import { config } from \"../../../../config/config-getter\";\nimport type { CheckStatus, DoctorCheck } from \"../check.types\";\n\n/**\n * A non-empty string, or `undefined`. A secret arrives from `env(...)`; an\n * unset variable yields `undefined` and a `.env` line with nothing after the\n * `=` yields `\"\"` — both are \"not configured\".\n */\nfunction text(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim().length > 0 ? value.trim() : undefined;\n}\n\n/**\n * Fails when the app has configured auth but no JWT signing secret is set.\n *\n * WHY THIS EXISTS. `authConfig.accessToken.secret()` throws \"no JWT secret\n * configured\" — but LAZILY, the first time a token is signed (a login). A fresh\n * scaffold's `src/config/auth.ts` reads `env(\"JWT_SECRET\")`, and a fresh `.env`\n * has none, so the app BOOTS clean and then 500s on the very first login, with\n * the real message going only to server stderr (finding 7b606e38). `doctor`\n * turns that first-login surprise into a pre-flight failure that names the fix.\n *\n * NEEDS NO BOOTED APP — it reads config only, like `optional-peers`.\n *\n * OPTS OUT when auth is not in use. The signal is `auth.userType`: the config\n * an app fills in to declare its authenticatable models (`{ user: User }`). An\n * app with no userType configured is not doing auth, so a missing secret is not\n * a finding for it — silence keeps the doctor's false-positive budget intact.\n *\n * The secret is resolved from BOTH the current key (`auth.accessToken.secret`)\n * and the legacy one (`auth.jwt.secret`) the scaffold still emits, so this does\n * not fire on a project that is simply using the older, still-supported shape.\n */\nexport const jwtSecretCheck: DoctorCheck = {\n name: \"jwt-secret\",\n run: () => {\n const userType = config.get(\"auth.userType\");\n\n const authInUse =\n typeof userType === \"object\" && userType !== null && Object.keys(userType).length > 0;\n\n if (!authInUse) return undefined;\n\n const secret =\n text(config.get(\"auth.accessToken.secret\")) ?? text(config.get(\"auth.jwt.secret\"));\n\n const status: CheckStatus = secret ? \"ok\" : \"fail\";\n\n if (secret) {\n return { name: \"jwt-secret\", status, detail: \"A JWT signing secret is configured.\" };\n }\n\n return {\n name: \"jwt-secret\",\n status,\n detail:\n \"auth is configured (auth.userType) but no JWT signing secret is set — \" +\n \"every login and token operation will fail with a generic 500. \" +\n \"Run `warlock jwt.generate` to create one, or set `auth.accessToken.secret`.\",\n };\n },\n};\n"],"mappings":";;;;;;;;AAQA,SAAS,KAAK,OAAoC;CAChD,OAAO,OAAO,UAAU,YAAY,MAAM,KAAK,CAAC,CAAC,SAAS,IAAI,MAAM,KAAK,IAAI;AAC/E;;;;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,iBAA8B;CACzC,MAAM;CACN,WAAW;EACT,MAAM,WAAW,OAAO,IAAI,eAAe;EAK3C,IAAI,EAFF,OAAO,aAAa,YAAY,aAAa,QAAQ,OAAO,KAAK,QAAQ,CAAC,CAAC,SAAS,IAEtE,OAAO;EAEvB,MAAM,SACJ,KAAK,OAAO,IAAI,yBAAyB,CAAC,KAAK,KAAK,OAAO,IAAI,iBAAiB,CAAC;EAEnF,MAAM,SAAsB,SAAS,OAAO;EAE5C,IAAI,QACF,OAAO;GAAE,MAAM;GAAc;GAAQ,QAAQ;EAAsC;EAGrF,OAAO;GACL,MAAM;GACN;GACA,QACE;EAGJ;CACF;AACF"}
|
|
@@ -9,19 +9,19 @@ async function generateController(data) {
|
|
|
9
9
|
const input = data.args[0];
|
|
10
10
|
if (!input) {
|
|
11
11
|
console.log(colors.red("Error: Controller name is required"));
|
|
12
|
-
console.log(colors.yellow("Usage: warlock
|
|
13
|
-
console.log(colors.yellow("Example: warlock
|
|
12
|
+
console.log(colors.yellow("Usage: warlock generate.controller <module>/<name>"));
|
|
13
|
+
console.log(colors.yellow("Example: warlock generate.controller users/create-user"));
|
|
14
14
|
process.exit(1);
|
|
15
15
|
}
|
|
16
16
|
const { module, name: componentName } = parseModulePath(input);
|
|
17
17
|
if (!module) {
|
|
18
18
|
console.log(colors.red("Error: Module name is required"));
|
|
19
|
-
console.log(colors.yellow("Usage: warlock
|
|
19
|
+
console.log(colors.yellow("Usage: warlock generate.controller <module>/<name>"));
|
|
20
20
|
process.exit(1);
|
|
21
21
|
}
|
|
22
22
|
if (!await moduleExists(module)) {
|
|
23
23
|
console.log(colors.red(`Error: Module "${module}" does not exist`));
|
|
24
|
-
console.log(colors.yellow(`Run: warlock
|
|
24
|
+
console.log(colors.yellow(`Run: warlock generate.module ${module}`));
|
|
25
25
|
process.exit(1);
|
|
26
26
|
}
|
|
27
27
|
const name = parseName(componentName);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.generator.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/generators/controller.generator.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\
|
|
1
|
+
{"version":3,"file":"controller.generator.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/generators/controller.generator.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport type { CommandActionData } from \"../../../../commands/types\";\nimport { controllerStub, schemaStub } from \"../templates/stubs\";\nimport { parseModulePath, parseName } from \"../utils/name-parser\";\nimport {\n componentExists,\n ensureComponentDirectory,\n moduleExists,\n resolveComponentPath,\n} from \"../utils/path-resolver\";\nimport { putFileAsync, setDryRun } from \"../utils/writer\";\n\nexport async function generateController(data: CommandActionData): Promise<void> {\n const input = data.args[0];\n\n if (!input) {\n console.log(colors.red(\"Error: Controller name is required\"));\n console.log(colors.yellow(\"Usage: warlock generate.controller <module>/<name>\"));\n console.log(colors.yellow(\"Example: warlock generate.controller users/create-user\"));\n process.exit(1);\n }\n\n const { module, name: componentName } = parseModulePath(input);\n\n if (!module) {\n console.log(colors.red(\"Error: Module name is required\"));\n console.log(colors.yellow(\"Usage: warlock generate.controller <module>/<name>\"));\n process.exit(1);\n }\n\n // Check if module exists\n if (!(await moduleExists(module))) {\n console.log(colors.red(`Error: Module \"${module}\" does not exist`));\n console.log(colors.yellow(`Run: warlock generate.module ${module}`));\n process.exit(1);\n }\n\n const name = parseName(componentName);\n const withValidation = data.options.withValidation || data.options.v;\n const force = data.options.force || data.options.f;\n setDryRun(Boolean(data.options.dryRun));\n\n // Check if controller already exists\n const controllerPath = resolveComponentPath(module, \"controllers\", `${name.kebab}.controller`);\n if ((await componentExists(module, \"controllers\", `${name.kebab}.controller`)) && !force) {\n console.log(colors.red(`Error: Controller \"${name.kebab}.controller.ts\" already exists`));\n console.log(colors.yellow(\"Use --force to overwrite\"));\n process.exit(1);\n }\n\n // Ensure directories exist\n await ensureComponentDirectory(module, \"controllers\");\n\n // Generate controller\n const controllerContent = controllerStub(name, { withValidation: !!withValidation });\n await putFileAsync(controllerPath, controllerContent);\n\n // Generate the validation schema alongside the controller if requested.\n // The controller imports the schema's exported type + value directly —\n // there is no separate `requests/` alias file.\n if (withValidation) {\n await ensureComponentDirectory(module, \"schema\");\n\n const schemaPath = resolveComponentPath(module, \"schema\", `${name.kebab}.schema`);\n const schemaContent = schemaStub(name);\n\n await putFileAsync(schemaPath, schemaContent);\n }\n\n console.log(colors.cyan(`\\n✨ Controller \"${name.camel}\" generated successfully!`));\n}\n"],"mappings":";;;;;;;AAYA,eAAsB,mBAAmB,MAAwC;CAC/E,MAAM,QAAQ,KAAK,KAAK;CAExB,IAAI,CAAC,OAAO;EACV,QAAQ,IAAI,OAAO,IAAI,oCAAoC,CAAC;EAC5D,QAAQ,IAAI,OAAO,OAAO,oDAAoD,CAAC;EAC/E,QAAQ,IAAI,OAAO,OAAO,wDAAwD,CAAC;EACnF,QAAQ,KAAK,CAAC;CAChB;CAEA,MAAM,EAAE,QAAQ,MAAM,kBAAkB,gBAAgB,KAAK;CAE7D,IAAI,CAAC,QAAQ;EACX,QAAQ,IAAI,OAAO,IAAI,gCAAgC,CAAC;EACxD,QAAQ,IAAI,OAAO,OAAO,oDAAoD,CAAC;EAC/E,QAAQ,KAAK,CAAC;CAChB;CAGA,IAAI,CAAE,MAAM,aAAa,MAAM,GAAI;EACjC,QAAQ,IAAI,OAAO,IAAI,kBAAkB,OAAO,iBAAiB,CAAC;EAClE,QAAQ,IAAI,OAAO,OAAO,gCAAgC,QAAQ,CAAC;EACnE,QAAQ,KAAK,CAAC;CAChB;CAEA,MAAM,OAAO,UAAU,aAAa;CACpC,MAAM,iBAAiB,KAAK,QAAQ,kBAAkB,KAAK,QAAQ;CACnE,MAAM,QAAQ,KAAK,QAAQ,SAAS,KAAK,QAAQ;CACjD,UAAU,QAAQ,KAAK,QAAQ,MAAM,CAAC;CAGtC,MAAM,iBAAiB,qBAAqB,QAAQ,eAAe,GAAG,KAAK,MAAM,YAAY;CAC7F,IAAK,MAAM,gBAAgB,QAAQ,eAAe,GAAG,KAAK,MAAM,YAAY,KAAM,CAAC,OAAO;EACxF,QAAQ,IAAI,OAAO,IAAI,sBAAsB,KAAK,MAAM,+BAA+B,CAAC;EACxF,QAAQ,IAAI,OAAO,OAAO,0BAA0B,CAAC;EACrD,QAAQ,KAAK,CAAC;CAChB;CAGA,MAAM,yBAAyB,QAAQ,aAAa;CAIpD,MAAM,aAAa,gBADO,eAAe,MAAM,EAAE,gBAAgB,CAAC,CAAC,eAAe,CAC/B,CAAC;CAKpD,IAAI,gBAAgB;EAClB,MAAM,yBAAyB,QAAQ,QAAQ;EAK/C,MAAM,aAHa,qBAAqB,QAAQ,UAAU,GAAG,KAAK,MAAM,QAG5C,GAFN,WAAW,IAEU,CAAC;CAC9C;CAEA,QAAQ,IAAI,OAAO,KAAK,qBAAqB,KAAK,MAAM,0BAA0B,CAAC;AACrF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migration.generator.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/generators/migration.generator.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\
|
|
1
|
+
{"version":3,"file":"migration.generator.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/generators/migration.generator.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport path from \"node:path\";\nimport { appPath } from \"../../../../utils\";\nimport type { CommandActionData } from \"../../../../commands/types\";\nimport { migrationAlterStub, migrationStub } from \"../templates/stubs\";\nimport { parseColumnDsl } from \"./column-dsl-parser\";\nimport { parseName } from \"../utils/name-parser\";\nimport { ensureDirectoryAsync, putFileAsync, setDryRun } from \"../utils/writer\";\n\n/**\n * Generate a migration file for a model\n */\n/**\n * Create a migration file for a model\n */\nexport async function createMigrationFile(\n moduleName: string,\n entityName: string,\n options: any = {},\n) {\n const entity = parseName(entityName);\n\n // Generate timestamp: MM-DD-YYYY_HH-MM-SS\n const now = new Date();\n const timestamp = `${String(now.getMonth() + 1).padStart(2, \"0\")}-${String(now.getDate()).padStart(2, \"0\")}-${now.getFullYear()}_${String(now.getHours()).padStart(2, \"0\")}-${String(now.getMinutes()).padStart(2, \"0\")}-${String(now.getSeconds()).padStart(2, \"0\")}`;\n\n const migrationFileName = `${timestamp}-${entity.kebab}.migration.ts`;\n const migrationsPath = path.join(appPath(), moduleName, \"models\", entity.kebab, \"migrations\");\n\n // Ensure migrations directory exists\n await ensureDirectoryAsync(migrationsPath);\n\n const addParams = options.add as string;\n const dropParams = options.drop as string;\n const renameParams = options.rename as string;\n const timestamps = options.timestamps !== \"false\" && options.timestamps !== false;\n\n let migrationContent = \"\";\n\n if (addParams || dropParams || renameParams) {\n // Generate alter stub\n const parsedAdd = parseColumnDsl(addParams || \"\");\n const helpersSet = new Set<string>();\n\n const addLines = parsedAdd.map((col) => {\n helpersSet.add(col.helper);\n return ` ${col.name}: ${col.helper}()${col.modifiers.join(\"\")},`;\n });\n\n let formattedDrop: string | undefined = undefined;\n if (dropParams) {\n formattedDrop = JSON.stringify(dropParams.split(\",\").map((s) => s.trim()));\n }\n\n let formattedRename: string | undefined = undefined;\n if (renameParams) {\n const obj: Record<string, string> = {};\n renameParams\n .split(\",\")\n .map((s) => s.trim())\n .forEach((p) => {\n const [oldN, newN] = p.split(\":\").map((s) => s.trim());\n if (oldN && newN) obj[oldN] = newN;\n });\n formattedRename = JSON.stringify(obj, null, 2).replace(/\\n/g, \"\\n \");\n }\n\n migrationContent = migrationAlterStub(entity, {\n add: addLines.length > 0 ? addLines.join(\"\\n\") : undefined,\n drop: formattedDrop,\n rename: formattedRename,\n imports: Array.from(helpersSet),\n });\n } else {\n // Generate create stub\n migrationContent = migrationStub(entity, {\n timestamps,\n });\n }\n\n // Create migration file\n const migrationFilePath = path.join(migrationsPath, migrationFileName);\n await putFileAsync(migrationFilePath, migrationContent);\n\n return migrationFilePath;\n}\n\n/**\n * Generate a migration file for a model\n */\nexport async function generateMigration(data: CommandActionData) {\n const modelPath = data.args[0] as string;\n\n if (!modelPath) {\n console.log(colors.red(\"Error: Model path is required\"));\n console.log(colors.gray(\"Usage: warlock gen.migration <model-path>\"));\n console.log(colors.gray(\"Example: warlock gen.migration products/product\"));\n return;\n }\n\n // Parse model path (e.g., \"products/product\")\n const [moduleName, entityName] = modelPath.split(\"/\");\n\n if (!moduleName || !entityName) {\n console.log(colors.red(\"Error: Invalid model path format. Expected: <module>/<entity>\"));\n console.log(colors.gray(\"Example: warlock gen.migration products/product\"));\n return;\n }\n\n setDryRun(Boolean(data.options.dryRun));\n\n const migrationFilePath = await createMigrationFile(moduleName, entityName, data.options);\n\n console.log(\n colors.cyan(`\\n✨ Migration file created at: ${path.relative(appPath(), migrationFilePath)}`),\n );\n}\n"],"mappings":"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"}
|
|
@@ -10,19 +10,19 @@ async function generateModel(data) {
|
|
|
10
10
|
const input = data.args[0];
|
|
11
11
|
if (!input) {
|
|
12
12
|
console.log(colors.red("Error: Model name is required"));
|
|
13
|
-
console.log(colors.yellow("Usage: warlock
|
|
14
|
-
console.log(colors.yellow("Example: warlock
|
|
13
|
+
console.log(colors.yellow("Usage: warlock generate.model <module>/<name>"));
|
|
14
|
+
console.log(colors.yellow("Example: warlock generate.model users/user"));
|
|
15
15
|
process.exit(1);
|
|
16
16
|
}
|
|
17
17
|
const { module, name: componentName } = parseModulePath(input);
|
|
18
18
|
if (!module) {
|
|
19
19
|
console.log(colors.red("Error: Module name is required"));
|
|
20
|
-
console.log(colors.yellow("Usage: warlock
|
|
20
|
+
console.log(colors.yellow("Usage: warlock generate.model <module>/<name>"));
|
|
21
21
|
process.exit(1);
|
|
22
22
|
}
|
|
23
23
|
if (!await moduleExists(module)) {
|
|
24
24
|
console.log(colors.red(`Error: Module "${module}" does not exist`));
|
|
25
|
-
console.log(colors.yellow(`Run: warlock
|
|
25
|
+
console.log(colors.yellow(`Run: warlock generate.module ${module}`));
|
|
26
26
|
process.exit(1);
|
|
27
27
|
}
|
|
28
28
|
const name = singularName(componentName);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.generator.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/generators/model.generator.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\
|
|
1
|
+
{"version":3,"file":"model.generator.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/generators/model.generator.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport path from \"node:path\";\nimport type { CommandActionData } from \"../../../../commands/types\";\nimport { migrationStub, modelStub } from \"../templates/stubs\";\nimport { parseModulePath, singularName } from \"../utils/name-parser\";\nimport { componentExists, moduleExists, resolveModulePath } from \"../utils/path-resolver\";\nimport { ensureDirectoryAsync, putFileAsync, setDryRun } from \"../utils/writer\";\n\nexport async function generateModel(data: CommandActionData): Promise<void> {\n const input = data.args[0];\n\n if (!input) {\n console.log(colors.red(\"Error: Model name is required\"));\n console.log(colors.yellow(\"Usage: warlock generate.model <module>/<name>\"));\n console.log(colors.yellow(\"Example: warlock generate.model users/user\"));\n process.exit(1);\n }\n\n const { module, name: componentName } = parseModulePath(input);\n\n if (!module) {\n console.log(colors.red(\"Error: Module name is required\"));\n console.log(colors.yellow(\"Usage: warlock generate.model <module>/<name>\"));\n process.exit(1);\n }\n\n // Check if module exists\n if (!(await moduleExists(module))) {\n console.log(colors.red(`Error: Module \"${module}\" does not exist`));\n console.log(colors.yellow(`Run: warlock generate.module ${module}`));\n process.exit(1);\n }\n\n const name = singularName(componentName);\n const force = data.options.force || data.options.f;\n setDryRun(Boolean(data.options.dryRun));\n const withResource = data.options.withResource || data.options.rs;\n const tableName = (data.options.table as string) || name.plural.snake;\n\n // Check if model already exists\n const modelDir = path.join(resolveModulePath(module), \"models\", name.kebab);\n const modelPath = path.join(modelDir, `${name.kebab}.model.ts`);\n\n if ((await componentExists(module, `models/${name.kebab}`, `${name.kebab}.model`)) && !force) {\n console.log(colors.red(`Error: Model \"${name.kebab}\" already exists`));\n console.log(colors.yellow(\"Use --force to overwrite\"));\n process.exit(1);\n }\n\n // Ensure directories exist\n await ensureDirectoryAsync(modelDir);\n await ensureDirectoryAsync(path.join(modelDir, \"migrations\"));\n\n // Generate model\n const modelContent = modelStub(name, { tableName, withResource: !!withResource });\n await putFileAsync(modelPath, modelContent);\n\n // Generate index.ts\n const indexContent = `export * from \"./${name.kebab}.model\";\n`;\n await putFileAsync(path.join(modelDir, \"index.ts\"), indexContent);\n\n // Generate migration\n const timestamp = new Date().toISOString().replace(/[-:T]/g, \"_\").split(\".\")[0];\n const migrationPath = path.join(\n modelDir,\n \"migrations\",\n `${timestamp}_${name.kebab}.migration.ts`,\n );\n\n const migrationContent = migrationStub(name, {\n timestamps: data.options.timestamps !== \"false\" && data.options.timestamps !== false,\n });\n\n await putFileAsync(migrationPath, migrationContent);\n\n console.log(colors.cyan(`\\n✨ Model \"${name.pascal}\" generated successfully!`));\n console.log(colors.gray(`\\nNext steps:`));\n console.log(colors.gray(` 1. Update model schema in ${name.kebab}.model.ts`));\n console.log(\n colors.gray(` 2. Update migration in migrations/${timestamp}_${name.kebab}.migration.ts`),\n );\n console.log(colors.gray(` 3. Run migration: warlock migrate`));\n}\n"],"mappings":";;;;;;;;AAQA,eAAsB,cAAc,MAAwC;CAC1E,MAAM,QAAQ,KAAK,KAAK;CAExB,IAAI,CAAC,OAAO;EACV,QAAQ,IAAI,OAAO,IAAI,+BAA+B,CAAC;EACvD,QAAQ,IAAI,OAAO,OAAO,+CAA+C,CAAC;EAC1E,QAAQ,IAAI,OAAO,OAAO,4CAA4C,CAAC;EACvE,QAAQ,KAAK,CAAC;CAChB;CAEA,MAAM,EAAE,QAAQ,MAAM,kBAAkB,gBAAgB,KAAK;CAE7D,IAAI,CAAC,QAAQ;EACX,QAAQ,IAAI,OAAO,IAAI,gCAAgC,CAAC;EACxD,QAAQ,IAAI,OAAO,OAAO,+CAA+C,CAAC;EAC1E,QAAQ,KAAK,CAAC;CAChB;CAGA,IAAI,CAAE,MAAM,aAAa,MAAM,GAAI;EACjC,QAAQ,IAAI,OAAO,IAAI,kBAAkB,OAAO,iBAAiB,CAAC;EAClE,QAAQ,IAAI,OAAO,OAAO,gCAAgC,QAAQ,CAAC;EACnE,QAAQ,KAAK,CAAC;CAChB;CAEA,MAAM,OAAO,aAAa,aAAa;CACvC,MAAM,QAAQ,KAAK,QAAQ,SAAS,KAAK,QAAQ;CACjD,UAAU,QAAQ,KAAK,QAAQ,MAAM,CAAC;CACtC,MAAM,eAAe,KAAK,QAAQ,gBAAgB,KAAK,QAAQ;CAC/D,MAAM,YAAa,KAAK,QAAQ,SAAoB,KAAK,OAAO;CAGhE,MAAM,WAAW,KAAK,KAAK,kBAAkB,MAAM,GAAG,UAAU,KAAK,KAAK;CAC1E,MAAM,YAAY,KAAK,KAAK,UAAU,GAAG,KAAK,MAAM,UAAU;CAE9D,IAAK,MAAM,gBAAgB,QAAQ,UAAU,KAAK,SAAS,GAAG,KAAK,MAAM,OAAO,KAAM,CAAC,OAAO;EAC5F,QAAQ,IAAI,OAAO,IAAI,iBAAiB,KAAK,MAAM,iBAAiB,CAAC;EACrE,QAAQ,IAAI,OAAO,OAAO,0BAA0B,CAAC;EACrD,QAAQ,KAAK,CAAC;CAChB;CAGA,MAAM,qBAAqB,QAAQ;CACnC,MAAM,qBAAqB,KAAK,KAAK,UAAU,YAAY,CAAC;CAI5D,MAAM,aAAa,WADE,UAAU,MAAM;EAAE;EAAW,cAAc,CAAC,CAAC;CAAa,CACtC,CAAC;CAG1C,MAAM,eAAe,oBAAoB,KAAK,MAAM;;CAEpD,MAAM,aAAa,KAAK,KAAK,UAAU,UAAU,GAAG,YAAY;CAGhE,MAAM,6BAAY,IAAI,KAAK,EAAC,CAAC,YAAY,CAAC,CAAC,QAAQ,UAAU,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;CAW7E,MAAM,aAVgB,KAAK,KACzB,UACA,cACA,GAAG,UAAU,GAAG,KAAK,MAAM,cAOE,GAJN,cAAc,MAAM,EAC3C,YAAY,KAAK,QAAQ,eAAe,WAAW,KAAK,QAAQ,eAAe,MACjF,CAEiD,CAAC;CAElD,QAAQ,IAAI,OAAO,KAAK,gBAAgB,KAAK,OAAO,0BAA0B,CAAC;CAC/E,QAAQ,IAAI,OAAO,KAAK,eAAe,CAAC;CACxC,QAAQ,IAAI,OAAO,KAAK,+BAA+B,KAAK,MAAM,UAAU,CAAC;CAC7E,QAAQ,IACN,OAAO,KAAK,uCAAuC,UAAU,GAAG,KAAK,MAAM,cAAc,CAC3F;CACA,QAAQ,IAAI,OAAO,KAAK,qCAAqC,CAAC;AAChE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.generator.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/generators/module.generator.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport path from \"node:path\";\r\nimport { appPath } from \"../../../../utils\";\r\nimport type { CommandActionData } from \"../../../../commands/types\";\r\nimport {\r\n crudCreateControllerStub,\r\n crudCreateSchemaStub,\r\n crudCreateServiceStub,\r\n crudDeleteControllerStub,\r\n crudDeleteServiceStub,\r\n crudGetServiceStub,\r\n crudListControllerStub,\r\n crudListServiceStub,\r\n crudModelStub,\r\n crudRepositoryStub,\r\n crudResourceStub,\r\n crudRoutesStub,\r\n crudSeedStub,\r\n crudShowControllerStub,\r\n crudUpdateControllerStub,\r\n crudUpdateSchemaStub,\r\n crudUpdateServiceStub,\r\n} from \"../templates/stubs\";\r\nimport { pluralName, singularName } from \"../utils/name-parser\";\r\nimport { moduleExists } from \"../utils/path-resolver\";\r\nimport { ensureDirectoryAsync, putFileAsync, setDryRun } from \"../utils/writer\";\r\nimport { createMigrationFile } from \"./migration.generator\";\r\n\r\nexport async function generateModule(data: CommandActionData): Promise<void> {\r\n const moduleName = data.args[0];\r\n\r\n if (!moduleName) {\r\n console.log(colors.red(\"Error: Module name is required\"));\r\n console.log(colors.yellow(\"Usage: warlock generate.module <name> [options]\"));\r\n console.log(colors.yellow(\"Example: warlock generate.module products\"));\r\n console.log(colors.yellow(\" warlock generate.module products --minimal\"));\r\n process.exit(1);\r\n }\r\n\r\n const name = pluralName(moduleName);\r\n const force = data.options.force || data.options.f;\r\n setDryRun(Boolean(data.options.dryRun));\r\n\r\n // Full CRUD is the default; --minimal (-m) opts down to a bare skeleton.\r\n const minimal = data.options.minimal || data.options.m;\r\n const withCrud = !minimal;\r\n\r\n // Check if module already exists\r\n if ((await moduleExists(name.kebab)) && !force) {\r\n console.log(colors.red(`Error: Module \"${name.kebab}\" already exists`));\r\n console.log(colors.yellow(\"Use --force to overwrite\"));\r\n process.exit(1);\r\n }\r\n\r\n const modulePath = appPath(name.kebab);\r\n\r\n // Create module directory structure\r\n const directories = [\r\n \"controllers\",\r\n \"services\",\r\n \"models\",\r\n \"repositories\",\r\n \"schema\",\r\n \"resources\",\r\n \"events\",\r\n \"types\",\r\n \"utils\",\r\n ];\r\n\r\n for (const dir of directories) {\r\n await ensureDirectoryAsync(path.join(modulePath, dir));\r\n }\r\n\r\n // Create main.ts\r\n const mainContent = `// You may use this file as a custom entry point to be executed when the app starts.\r\n// it runs only once, and you don't need to import the routes.ts file, it will be imported automatically.\r\n// Use it to regsiter or boot up any custom logic for this module.\r\n`;\r\n await putFileAsync(path.join(modulePath, \"main.ts\"), mainContent);\r\n\r\n // Create routes.ts\r\n const routesContent = withCrud\r\n ? crudRoutesStub(name)\r\n : `import { router } from \"@warlock.js/core\";\r\nimport { guarded } from \"app/shared/utils/router\";\r\n\r\n// Define your routes here\r\n// Example:\r\n// router.get(\"/${name.kebab}\", listController);\r\n`;\r\n await putFileAsync(path.join(modulePath, \"routes.ts\"), routesContent);\r\n\r\n // Create utils/locales.ts\r\n const localesContent = `import { groupedTranslations } from \"@warlock.js/core\";\r\n\r\ngroupedTranslations(\"${name.camel}\", {\r\n // Add your translations here\r\n // Example:\r\n // welcome: {\r\n // en: \"Welcome\",\r\n // ar: \"Ù…Ø±ØØ¨Ø§\",\r\n // },\r\n});\r\n`;\r\n await putFileAsync(path.join(modulePath, \"utils\", \"locales.ts\"), localesContent);\r\n\r\n // Full CRUD scaffold (the default) unless --minimal was passed\r\n if (withCrud) {\r\n const entity = singularName(moduleName);\r\n\r\n // Create controllers\r\n await putFileAsync(\r\n path.join(modulePath, \"controllers\", `create-${entity.kebab}.controller.ts`),\r\n crudCreateControllerStub(entity),\r\n );\r\n\r\n await putFileAsync(\r\n path.join(modulePath, \"controllers\", `update-${entity.kebab}.controller.ts`),\r\n crudUpdateControllerStub(entity),\r\n );\r\n\r\n await putFileAsync(\r\n path.join(modulePath, \"controllers\", `list-${name.kebab}.controller.ts`),\r\n crudListControllerStub(name),\r\n );\r\n\r\n await putFileAsync(\r\n path.join(modulePath, \"controllers\", `get-${entity.kebab}.controller.ts`),\r\n crudShowControllerStub(entity),\r\n );\r\n\r\n await putFileAsync(\r\n path.join(modulePath, \"controllers\", `delete-${entity.kebab}.controller.ts`),\r\n crudDeleteControllerStub(entity),\r\n );\r\n\r\n // Create schema files — each exports both the schema value and its\r\n // inferred type; controllers import them directly (no requests/ alias).\r\n await putFileAsync(\r\n path.join(modulePath, \"schema\", `create-${entity.kebab}.schema.ts`),\r\n crudCreateSchemaStub(entity),\r\n );\r\n\r\n await putFileAsync(\r\n path.join(modulePath, \"schema\", `update-${entity.kebab}.schema.ts`),\r\n crudUpdateSchemaStub(entity),\r\n );\r\n\r\n // Create model\r\n await ensureDirectoryAsync(path.join(modulePath, \"models\", entity.kebab));\r\n await putFileAsync(\r\n path.join(modulePath, \"models\", entity.kebab, `${entity.kebab}.model.ts`),\r\n crudModelStub(entity),\r\n );\r\n\r\n // Create model index\r\n await putFileAsync(\r\n path.join(modulePath, \"models\", entity.kebab, \"index.ts\"),\r\n `export * from \"./${entity.kebab}.model\";\\n`,\r\n );\r\n\r\n // Create migrations folder\r\n await ensureDirectoryAsync(path.join(modulePath, \"models\", entity.kebab, \"migrations\"));\r\n\r\n // Create resource\r\n await putFileAsync(\r\n path.join(modulePath, \"resources\", `${entity.kebab}.resource.ts`),\r\n crudResourceStub(name),\r\n );\r\n\r\n // Create repository\r\n await putFileAsync(\r\n path.join(modulePath, \"repositories\", `${name.kebab}.repository.ts`),\r\n crudRepositoryStub(name),\r\n );\r\n\r\n // Create services\r\n await putFileAsync(\r\n path.join(modulePath, \"services\", `create-${entity.kebab}.service.ts`),\r\n crudCreateServiceStub(entity),\r\n );\r\n\r\n await putFileAsync(\r\n path.join(modulePath, \"services\", `update-${entity.kebab}.service.ts`),\r\n crudUpdateServiceStub(entity),\r\n );\r\n\r\n await putFileAsync(\r\n path.join(modulePath, \"services\", `list-${name.kebab}.service.ts`),\r\n crudListServiceStub(name),\r\n );\r\n\r\n await putFileAsync(\r\n path.join(modulePath, \"services\", `get-${entity.kebab}.service.ts`),\r\n crudGetServiceStub(entity),\r\n );\r\n\r\n await putFileAsync(\r\n path.join(modulePath, \"services\", `delete-${entity.kebab}.service.ts`),\r\n crudDeleteServiceStub(entity),\r\n );\r\n\r\n // Create seeds\r\n await ensureDirectoryAsync(path.join(modulePath, \"seeds\"));\r\n await putFileAsync(path.join(modulePath, \"seeds\", `${name.kebab}.seed.ts`), crudSeedStub(name));\r\n\r\n // Create migration\r\n await createMigrationFile(name.kebab, entity.kebab);\r\n }\r\n\r\n console.log(colors.cyan(`\\n✨ Module \"${name.kebab}\" generated successfully!`));\r\n\r\n if (withCrud) {\r\n console.log(colors.gray(`\\n📦 CRUD scaffold created with:`));\r\n console.log(colors.gray(` - List, Get, Create, Update & Delete controllers`));\r\n console.log(colors.gray(` - Repository`));\r\n console.log(colors.gray(` - Validation schemas in schema/`));\r\n console.log(colors.gray(` - Model with resource`));\r\n console.log(colors.gray(` - Routes configured`));\r\n console.log(colors.gray(`\\nNext steps:`));\r\n console.log(\r\n colors.gray(` 1. Update model schema in models/${name.kebab}/${name.kebab}.model.ts`),\r\n );\r\n console.log(colors.gray(` 2. Update schema rules in schema/*.schema.ts`));\r\n console.log(\r\n colors.gray(` 3. Create migration: warlock generate.model ${name.kebab}/${name.kebab}`),\r\n );\r\n } else {\r\n console.log(colors.gray(`\\nNext steps:`));\r\n console.log(colors.gray(` 1. Define routes in ${name.kebab}/routes.ts`));\r\n console.log(\r\n colors.gray(` 2. Create controllers: warlock generate.controller ${name.kebab}/<name>`),\r\n );\r\n console.log(colors.gray(` 3. Create models: warlock generate.model ${name.kebab}/<name>`));\r\n console.log(colors.gray(`\\nTip: omit --minimal to generate a full CRUD scaffold`));\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"module.generator.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/generators/module.generator.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport path from \"node:path\";\nimport { appPath } from \"../../../../utils\";\nimport type { CommandActionData } from \"../../../../commands/types\";\nimport {\n crudCreateControllerStub,\n crudCreateSchemaStub,\n crudCreateServiceStub,\n crudDeleteControllerStub,\n crudDeleteServiceStub,\n crudGetServiceStub,\n crudListControllerStub,\n crudListServiceStub,\n crudModelStub,\n crudRepositoryStub,\n crudResourceStub,\n crudRoutesStub,\n crudSeedStub,\n crudShowControllerStub,\n crudUpdateControllerStub,\n crudUpdateSchemaStub,\n crudUpdateServiceStub,\n} from \"../templates/stubs\";\nimport { pluralName, singularName } from \"../utils/name-parser\";\nimport { moduleExists } from \"../utils/path-resolver\";\nimport { ensureDirectoryAsync, putFileAsync, setDryRun } from \"../utils/writer\";\nimport { createMigrationFile } from \"./migration.generator\";\n\nexport async function generateModule(data: CommandActionData): Promise<void> {\n const moduleName = data.args[0];\n\n if (!moduleName) {\n console.log(colors.red(\"Error: Module name is required\"));\n console.log(colors.yellow(\"Usage: warlock generate.module <name> [options]\"));\n console.log(colors.yellow(\"Example: warlock generate.module products\"));\n console.log(colors.yellow(\" warlock generate.module products --minimal\"));\n process.exit(1);\n }\n\n const name = pluralName(moduleName);\n const force = data.options.force || data.options.f;\n setDryRun(Boolean(data.options.dryRun));\n\n // Full CRUD is the default; --minimal (-m) opts down to a bare skeleton.\n const minimal = data.options.minimal || data.options.m;\n const withCrud = !minimal;\n\n // Check if module already exists\n if ((await moduleExists(name.kebab)) && !force) {\n console.log(colors.red(`Error: Module \"${name.kebab}\" already exists`));\n console.log(colors.yellow(\"Use --force to overwrite\"));\n process.exit(1);\n }\n\n const modulePath = appPath(name.kebab);\n\n // Create module directory structure\n const directories = [\n \"controllers\",\n \"services\",\n \"models\",\n \"repositories\",\n \"schema\",\n \"resources\",\n \"events\",\n \"types\",\n \"utils\",\n ];\n\n for (const dir of directories) {\n await ensureDirectoryAsync(path.join(modulePath, dir));\n }\n\n // Create main.ts\n const mainContent = `// You may use this file as a custom entry point to be executed when the app starts.\n// it runs only once, and you don't need to import the routes.ts file, it will be imported automatically.\n// Use it to regsiter or boot up any custom logic for this module.\n`;\n await putFileAsync(path.join(modulePath, \"main.ts\"), mainContent);\n\n // Create routes.ts\n const routesContent = withCrud\n ? crudRoutesStub(name)\n : `import { router } from \"@warlock.js/core\";\nimport { guarded } from \"app/shared/utils/router\";\n\n// Define your routes here\n// Example:\n// router.get(\"/${name.kebab}\", listController);\n`;\n await putFileAsync(path.join(modulePath, \"routes.ts\"), routesContent);\n\n // Create utils/locales.ts\n const localesContent = `import { groupedTranslations } from \"@warlock.js/core\";\n\ngroupedTranslations(\"${name.camel}\", {\n // Add your translations here\n // Example:\n // welcome: {\n // en: \"Welcome\",\n // ar: \"Ù…Ø±ØØ¨Ø§\",\n // },\n});\n`;\n await putFileAsync(path.join(modulePath, \"utils\", \"locales.ts\"), localesContent);\n\n // Full CRUD scaffold (the default) unless --minimal was passed\n if (withCrud) {\n const entity = singularName(moduleName);\n\n // Create controllers\n await putFileAsync(\n path.join(modulePath, \"controllers\", `create-${entity.kebab}.controller.ts`),\n crudCreateControllerStub(entity),\n );\n\n await putFileAsync(\n path.join(modulePath, \"controllers\", `update-${entity.kebab}.controller.ts`),\n crudUpdateControllerStub(entity),\n );\n\n await putFileAsync(\n path.join(modulePath, \"controllers\", `list-${name.kebab}.controller.ts`),\n crudListControllerStub(name),\n );\n\n await putFileAsync(\n path.join(modulePath, \"controllers\", `get-${entity.kebab}.controller.ts`),\n crudShowControllerStub(entity),\n );\n\n await putFileAsync(\n path.join(modulePath, \"controllers\", `delete-${entity.kebab}.controller.ts`),\n crudDeleteControllerStub(entity),\n );\n\n // Create schema files — each exports both the schema value and its\n // inferred type; controllers import them directly (no requests/ alias).\n await putFileAsync(\n path.join(modulePath, \"schema\", `create-${entity.kebab}.schema.ts`),\n crudCreateSchemaStub(entity),\n );\n\n await putFileAsync(\n path.join(modulePath, \"schema\", `update-${entity.kebab}.schema.ts`),\n crudUpdateSchemaStub(entity),\n );\n\n // Create model\n await ensureDirectoryAsync(path.join(modulePath, \"models\", entity.kebab));\n await putFileAsync(\n path.join(modulePath, \"models\", entity.kebab, `${entity.kebab}.model.ts`),\n crudModelStub(entity),\n );\n\n // Create model index\n await putFileAsync(\n path.join(modulePath, \"models\", entity.kebab, \"index.ts\"),\n `export * from \"./${entity.kebab}.model\";\\n`,\n );\n\n // Create migrations folder\n await ensureDirectoryAsync(path.join(modulePath, \"models\", entity.kebab, \"migrations\"));\n\n // Create resource\n await putFileAsync(\n path.join(modulePath, \"resources\", `${entity.kebab}.resource.ts`),\n crudResourceStub(name),\n );\n\n // Create repository\n await putFileAsync(\n path.join(modulePath, \"repositories\", `${name.kebab}.repository.ts`),\n crudRepositoryStub(name),\n );\n\n // Create services\n await putFileAsync(\n path.join(modulePath, \"services\", `create-${entity.kebab}.service.ts`),\n crudCreateServiceStub(entity),\n );\n\n await putFileAsync(\n path.join(modulePath, \"services\", `update-${entity.kebab}.service.ts`),\n crudUpdateServiceStub(entity),\n );\n\n await putFileAsync(\n path.join(modulePath, \"services\", `list-${name.kebab}.service.ts`),\n crudListServiceStub(name),\n );\n\n await putFileAsync(\n path.join(modulePath, \"services\", `get-${entity.kebab}.service.ts`),\n crudGetServiceStub(entity),\n );\n\n await putFileAsync(\n path.join(modulePath, \"services\", `delete-${entity.kebab}.service.ts`),\n crudDeleteServiceStub(entity),\n );\n\n // Create seeds\n await ensureDirectoryAsync(path.join(modulePath, \"seeds\"));\n await putFileAsync(path.join(modulePath, \"seeds\", `${name.kebab}.seed.ts`), crudSeedStub(name));\n\n // Create migration\n await createMigrationFile(name.kebab, entity.kebab);\n }\n\n console.log(colors.cyan(`\\n✨ Module \"${name.kebab}\" generated successfully!`));\n\n if (withCrud) {\n console.log(colors.gray(`\\n📦 CRUD scaffold created with:`));\n console.log(colors.gray(` - List, Get, Create, Update & Delete controllers`));\n console.log(colors.gray(` - Repository`));\n console.log(colors.gray(` - Validation schemas in schema/`));\n console.log(colors.gray(` - Model with resource`));\n console.log(colors.gray(` - Routes configured`));\n console.log(colors.gray(`\\nNext steps:`));\n console.log(\n colors.gray(` 1. Update model schema in models/${name.kebab}/${name.kebab}.model.ts`),\n );\n console.log(colors.gray(` 2. Update schema rules in schema/*.schema.ts`));\n console.log(\n colors.gray(` 3. Create migration: warlock generate.model ${name.kebab}/${name.kebab}`),\n );\n } else {\n console.log(colors.gray(`\\nNext steps:`));\n console.log(colors.gray(` 1. Define routes in ${name.kebab}/routes.ts`));\n console.log(\n colors.gray(` 2. Create controllers: warlock generate.controller ${name.kebab}/<name>`),\n );\n console.log(colors.gray(` 3. Create models: warlock generate.model ${name.kebab}/<name>`));\n console.log(colors.gray(`\\nTip: omit --minimal to generate a full CRUD scaffold`));\n }\n}\n"],"mappings":"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"}
|
|
@@ -9,19 +9,19 @@ async function generateRepository(data) {
|
|
|
9
9
|
const input = data.args[0];
|
|
10
10
|
if (!input) {
|
|
11
11
|
console.log(colors.red("Error: Repository name is required"));
|
|
12
|
-
console.log(colors.yellow("Usage: warlock
|
|
13
|
-
console.log(colors.yellow("Example: warlock
|
|
12
|
+
console.log(colors.yellow("Usage: warlock generate.repository <module>/<name>"));
|
|
13
|
+
console.log(colors.yellow("Example: warlock generate.repository users/user"));
|
|
14
14
|
process.exit(1);
|
|
15
15
|
}
|
|
16
16
|
const { module, name: componentName } = parseModulePath(input);
|
|
17
17
|
if (!module) {
|
|
18
18
|
console.log(colors.red("Error: Module name is required"));
|
|
19
|
-
console.log(colors.yellow("Usage: warlock
|
|
19
|
+
console.log(colors.yellow("Usage: warlock generate.repository <module>/<name>"));
|
|
20
20
|
process.exit(1);
|
|
21
21
|
}
|
|
22
22
|
if (!await moduleExists(module)) {
|
|
23
23
|
console.log(colors.red(`Error: Module "${module}" does not exist`));
|
|
24
|
-
console.log(colors.yellow(`Run: warlock
|
|
24
|
+
console.log(colors.yellow(`Run: warlock generate.module ${module}`));
|
|
25
25
|
process.exit(1);
|
|
26
26
|
}
|
|
27
27
|
const name = parseName(componentName);
|