@warlock.js/core 4.16.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/bin/warlock.js +154 -1
  3. package/esm/application/application.d.mts +65 -1
  4. package/esm/application/application.d.mts.map +1 -1
  5. package/esm/application/application.mjs +62 -0
  6. package/esm/application/application.mjs.map +1 -1
  7. package/esm/application/index.d.mts +1 -1
  8. package/esm/cli/cli-commands.manager.mjs +60 -10
  9. package/esm/cli/cli-commands.manager.mjs.map +1 -1
  10. package/esm/cli/commands/dev-server.command.mjs +1 -0
  11. package/esm/cli/commands/dev-server.command.mjs.map +1 -1
  12. package/esm/cli/commands/doctor/checks/handler-signature.check.mjs +40 -0
  13. package/esm/cli/commands/doctor/checks/handler-signature.check.mjs.map +1 -0
  14. package/esm/cli/commands/doctor/checks/index.mjs +4 -2
  15. package/esm/cli/commands/doctor/checks/index.mjs.map +1 -1
  16. package/esm/commands/cli-command.d.mts +1 -1
  17. package/esm/commands/cli-command.d.mts.map +1 -1
  18. package/esm/commands/cli-command.mjs.map +1 -1
  19. package/esm/commands/types.d.mts +10 -2
  20. package/esm/commands/types.d.mts.map +1 -1
  21. package/esm/config/config-handlers.d.mts +1 -2
  22. package/esm/config/config-handlers.d.mts.map +1 -1
  23. package/esm/config/config-handlers.mjs +3 -2
  24. package/esm/config/config-handlers.mjs.map +1 -1
  25. package/esm/config/locale-configuration.mjs +29 -0
  26. package/esm/config/locale-configuration.mjs.map +1 -0
  27. package/esm/connectors/assert-no-reserved-connector-names.d.mts +7 -0
  28. package/esm/connectors/assert-no-reserved-connector-names.d.mts.map +1 -0
  29. package/esm/connectors/assert-no-reserved-connector-names.mjs +44 -0
  30. package/esm/connectors/assert-no-reserved-connector-names.mjs.map +1 -0
  31. package/esm/connectors/assert-unique-connector-names.d.mts +7 -0
  32. package/esm/connectors/assert-unique-connector-names.d.mts.map +1 -0
  33. package/esm/connectors/assert-unique-connector-names.mjs +20 -0
  34. package/esm/connectors/assert-unique-connector-names.mjs.map +1 -0
  35. package/esm/connectors/connectors-manager.d.mts +38 -0
  36. package/esm/connectors/connectors-manager.d.mts.map +1 -1
  37. package/esm/connectors/connectors-manager.mjs +45 -12
  38. package/esm/connectors/connectors-manager.mjs.map +1 -1
  39. package/esm/connectors/describe-server-address.mjs +75 -0
  40. package/esm/connectors/describe-server-address.mjs.map +1 -0
  41. package/esm/connectors/http-connector.d.mts.map +1 -1
  42. package/esm/connectors/http-connector.mjs +6 -3
  43. package/esm/connectors/http-connector.mjs.map +1 -1
  44. package/esm/connectors/index.d.mts +4 -1
  45. package/esm/connectors/index.mjs +4 -1
  46. package/esm/connectors/register-configured-connectors.d.mts +14 -0
  47. package/esm/connectors/register-configured-connectors.d.mts.map +1 -0
  48. package/esm/connectors/register-configured-connectors.mjs +99 -0
  49. package/esm/connectors/register-configured-connectors.mjs.map +1 -0
  50. package/esm/connectors/socket-connector.d.mts.map +1 -1
  51. package/esm/connectors/socket-connector.mjs +4 -1
  52. package/esm/connectors/socket-connector.mjs.map +1 -1
  53. package/esm/connectors/types.d.mts +76 -1
  54. package/esm/connectors/types.d.mts.map +1 -1
  55. package/esm/connectors/types.mjs.map +1 -1
  56. package/esm/database/models/database-log/database-log.d.mts +50 -2
  57. package/esm/database/models/database-log/database-log.d.mts.map +1 -1
  58. package/esm/database/utils.d.mts +2 -2
  59. package/esm/database/utils.d.mts.map +1 -1
  60. package/esm/dev-server/development-server.mjs +1 -0
  61. package/esm/dev-server/development-server.mjs.map +1 -1
  62. package/esm/dev-server/layer-executor.mjs +2 -1
  63. package/esm/dev-server/layer-executor.mjs.map +1 -1
  64. package/esm/dev-server/read-config-ast.mjs +48 -0
  65. package/esm/dev-server/read-config-ast.mjs.map +1 -0
  66. package/esm/dev-server/run-typings-generation.mjs +18 -0
  67. package/esm/dev-server/run-typings-generation.mjs.map +1 -0
  68. package/esm/dev-server/type-generator.mjs +30 -39
  69. package/esm/dev-server/type-generator.mjs.map +1 -1
  70. package/esm/generations/add-command.action.mjs +169 -1
  71. package/esm/generations/add-command.action.mjs.map +1 -1
  72. package/esm/generations/stubs.mjs +93 -1
  73. package/esm/generations/stubs.mjs.map +1 -1
  74. package/esm/http/context/request-context.d.mts +14 -3
  75. package/esm/http/context/request-context.d.mts.map +1 -1
  76. package/esm/http/context/request-context.mjs +11 -1
  77. package/esm/http/context/request-context.mjs.map +1 -1
  78. package/esm/http/context/request-memo.d.mts +21 -0
  79. package/esm/http/context/request-memo.d.mts.map +1 -0
  80. package/esm/http/context/request-memo.mjs +48 -0
  81. package/esm/http/context/request-memo.mjs.map +1 -0
  82. package/esm/http/database/RequestLog.d.mts +164 -2
  83. package/esm/http/database/RequestLog.d.mts.map +1 -1
  84. package/esm/http/index.d.mts +6 -5
  85. package/esm/http/index.mjs +2 -1
  86. package/esm/http/middleware/cache-response-middleware.d.mts +2 -2
  87. package/esm/http/middleware/cache-response-middleware.d.mts.map +1 -1
  88. package/esm/http/middleware/cache-response-middleware.mjs +1 -1
  89. package/esm/http/middleware/cache-response-middleware.mjs.map +1 -1
  90. package/esm/http/middleware/concurrency-limit.middleware.mjs +1 -1
  91. package/esm/http/middleware/concurrency-limit.middleware.mjs.map +1 -1
  92. package/esm/http/middleware/idempotency.middleware.mjs +1 -1
  93. package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
  94. package/esm/http/middleware/index.d.mts +1 -1
  95. package/esm/http/middleware/index.mjs +1 -1
  96. package/esm/http/middleware/inject-request-context.d.mts +1 -8
  97. package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
  98. package/esm/http/middleware/inject-request-context.mjs +9 -22
  99. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  100. package/esm/http/middleware/ip-filter.middleware.mjs +1 -1
  101. package/esm/http/middleware/ip-filter.middleware.mjs.map +1 -1
  102. package/esm/http/middleware/maintenance.middleware.mjs +1 -1
  103. package/esm/http/middleware/maintenance.middleware.mjs.map +1 -1
  104. package/esm/http/middleware/max-body-size.middleware.mjs +1 -1
  105. package/esm/http/middleware/max-body-size.middleware.mjs.map +1 -1
  106. package/esm/http/middleware/rate-limit.middleware.mjs +1 -1
  107. package/esm/http/middleware/rate-limit.middleware.mjs.map +1 -1
  108. package/esm/http/middleware/utils/idempotency-key.d.mts +0 -14
  109. package/esm/http/middleware/utils/idempotency-key.d.mts.map +1 -1
  110. package/esm/http/middleware/utils/idempotency-key.mjs +19 -1
  111. package/esm/http/middleware/utils/idempotency-key.mjs.map +1 -1
  112. package/esm/http/request.d.mts +119 -30
  113. package/esm/http/request.d.mts.map +1 -1
  114. package/esm/http/request.mjs +98 -25
  115. package/esm/http/request.mjs.map +1 -1
  116. package/esm/http/response.mjs +1 -1
  117. package/esm/http/response.mjs.map +1 -1
  118. package/esm/http/types.d.mts +36 -1
  119. package/esm/http/types.d.mts.map +1 -1
  120. package/esm/http/uploaded-file.d.mts +51 -19
  121. package/esm/http/uploaded-file.d.mts.map +1 -1
  122. package/esm/http/uploaded-file.mjs +36 -8
  123. package/esm/http/uploaded-file.mjs.map +1 -1
  124. package/esm/index.d.mts +17 -9
  125. package/esm/index.mjs +10 -4
  126. package/esm/production/build-contributions.mjs +115 -0
  127. package/esm/production/build-contributions.mjs.map +1 -0
  128. package/esm/production/production-builder.mjs +97 -2
  129. package/esm/production/production-builder.mjs.map +1 -1
  130. package/esm/production/resolve-build-config.d.mts +25 -0
  131. package/esm/production/resolve-build-config.d.mts.map +1 -0
  132. package/esm/production/resolve-build-config.mjs +43 -1
  133. package/esm/production/resolve-build-config.mjs.map +1 -1
  134. package/esm/repositories/adapters/cascade/cascade-query-builder.d.mts +1 -1
  135. package/esm/repositories/adapters/cascade/filter-applicator.mjs +20 -9
  136. package/esm/repositories/adapters/cascade/filter-applicator.mjs.map +1 -1
  137. package/esm/restful/restful.d.mts +29 -8
  138. package/esm/restful/restful.d.mts.map +1 -1
  139. package/esm/restful/restful.mjs +31 -13
  140. package/esm/restful/restful.mjs.map +1 -1
  141. package/esm/router/describe-route-for-log.mjs +29 -0
  142. package/esm/router/describe-route-for-log.mjs.map +1 -0
  143. package/esm/router/index.d.mts +3 -1
  144. package/esm/router/index.mjs +2 -0
  145. package/esm/router/log-request-lifecycle.mjs +38 -0
  146. package/esm/router/log-request-lifecycle.mjs.map +1 -0
  147. package/esm/router/positional-handler-diagnostics.d.mts +90 -0
  148. package/esm/router/positional-handler-diagnostics.d.mts.map +1 -0
  149. package/esm/router/positional-handler-diagnostics.mjs +247 -0
  150. package/esm/router/positional-handler-diagnostics.mjs.map +1 -0
  151. package/esm/router/route-registry.d.mts +34 -0
  152. package/esm/router/route-registry.d.mts.map +1 -0
  153. package/esm/router/router.d.mts.map +1 -1
  154. package/esm/router/router.mjs +87 -63
  155. package/esm/router/router.mjs.map +1 -1
  156. package/esm/router/types.d.mts +23 -5
  157. package/esm/router/types.d.mts.map +1 -1
  158. package/esm/storage/storage.d.mts.map +1 -1
  159. package/esm/storage/storage.mjs +1 -1
  160. package/esm/storage/storage.mjs.map +1 -1
  161. package/esm/storage/utils/safe-fetch.d.mts +11 -1
  162. package/esm/storage/utils/safe-fetch.d.mts.map +1 -1
  163. package/esm/storage/utils/safe-fetch.mjs +51 -4
  164. package/esm/storage/utils/safe-fetch.mjs.map +1 -1
  165. package/esm/tests/vitest-setup.mjs +1 -1
  166. package/esm/utils/load-environment.mjs +33 -2
  167. package/esm/utils/load-environment.mjs.map +1 -1
  168. package/esm/utils/types.d.mts +4 -0
  169. package/esm/utils/types.d.mts.map +1 -1
  170. package/esm/validation/validateAll.mjs +4 -1
  171. package/esm/validation/validateAll.mjs.map +1 -1
  172. package/esm/validation/validators/file-validator.d.mts +25 -1
  173. package/esm/validation/validators/file-validator.d.mts.map +1 -1
  174. package/esm/warlock-config/index.mjs +1 -1
  175. package/esm/warlock-config/normalize-build-config.mjs.map +1 -1
  176. package/esm/warlock-config/types.d.mts +36 -2
  177. package/esm/warlock-config/types.d.mts.map +1 -1
  178. package/package.json +15 -12
  179. package/skills/request-memo/SKILL.md +142 -0
  180. package/skills/use-request-locals/SKILL.md +144 -0
@@ -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 { ConnectorLifecyclePhase } from \"../connectors/types\";\r\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\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, ParsedCliArgs, parseCliArgs } from \"./parse-cli-args\";\r\nimport { findSimilar } from \"./string-similarity\";\r\nimport { CommandActionData, ResolvedCLICommandOption } from \"../commands/types\";\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 const commandKey = command.name.split(\" \")[0];\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 for (const name of Object.keys(manifestCommands)) {\r\n const baseName = name.split(\" \")[0];\r\n if (!names.includes(baseName)) {\r\n names.push(baseName);\r\n }\r\n const alias = manifestCommands[name].alias;\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 await this.execute(command, {\r\n options: resolved.options,\r\n args: resolved.args,\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 for (const fullCommandName in jsonCommandsFile.commands) {\r\n const cmdMeta = jsonCommandsFile.commands[fullCommandName];\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 result[opt.name] = result[opt.alias];\r\n }\r\n });\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Execute the given command\r\n */\r\n public async execute(command: CLICommand, data: CommandActionData) {\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 displayExecutingCommand(command.name);\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 try {\r\n await command.execute(data);\r\n\r\n if (!command.isPersistent) {\r\n displayCommandSuccess(command.name, Date.now() - startTime);\r\n\r\n process.exit(0);\r\n }\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 }\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 if (preloaders.environemnt) {\r\n Application.setEnvironment(preloaders.environemnt);\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 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 { 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, ParsedCliArgs, parseCliArgs } from \"./parse-cli-args\";\r\nimport { findSimilar } from \"./string-similarity\";\r\nimport { 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\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 const commandKey = command.name.split(\" \")[0];\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 for (const name of Object.keys(manifestCommands)) {\r\n const baseName = name.split(\" \")[0];\r\n if (!names.includes(baseName)) {\r\n names.push(baseName);\r\n }\r\n const alias = manifestCommands[name].alias;\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 for (const fullCommandName in jsonCommandsFile.commands) {\r\n const cmdMeta = jsonCommandsFile.commands[fullCommandName];\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 result[opt.name] = result[opt.alias];\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 displayExecutingCommand(command.name);\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 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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"}
@@ -11,6 +11,7 @@ const devServerCommand = command({
11
11
  persistent: true,
12
12
  preload: {
13
13
  runtimeStrategy: "development",
14
+ environment: "development",
14
15
  config: true,
15
16
  bootstrap: true,
16
17
  prestart: true,
@@ -1 +1 @@
1
- {"version":3,"file":"dev-server.command.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/dev-server.command.ts"],"sourcesContent":["import { checkForFrameworkUpdate } from \"../../dev-server/check-for-updates\";\r\nimport { startDevelopmentServer } from \"../../dev-server/start-development-server\";\r\nimport { isDevWorker, superviseDevServer } from \"../../dev-server/supervisor\";\r\nimport { command } from \"../../commands/cli-command\";\r\nimport { displayStartupBanner } from \"../cli-commands.utils\";\r\n\r\nexport const devServerCommand = command({\r\n name: \"dev\",\r\n description: \"Start development server (HMR, type-gen, health checks)\",\r\n persistent: true,\r\n preload: {\r\n runtimeStrategy: \"development\",\r\n config: true, // load all config\r\n bootstrap: true,\r\n prestart: true, // load prestart file (if exists)\r\n // Only the Early lifecycle phase starts here; the Late phase\r\n // (http, socket) starts after app modules load — see\r\n // development-server.ts STEP 8.5.\r\n connectors: true,\r\n },\r\n preAction: async () => {\r\n // Two roles share this command. The first `warlock dev` becomes a thin\r\n // supervisor that owns the terminal and (re)spawns the real server; the\r\n // process it spawns carries WARLOCK_DEV_WORKER and falls through to the\r\n // server itself. See supervisor.ts for why the split exists.\r\n //\r\n // This branch has to sit in `preAction`, which runs BEFORE the command's\r\n // preloaders: the supervisor must never load config or start connectors,\r\n // or it would hold a second database connection open for the whole\r\n // session. `superviseDevServer()` never resolves, so nothing below it —\r\n // preload included — runs in the supervisor.\r\n if (!isDevWorker()) {\r\n return superviseDevServer();\r\n }\r\n\r\n await displayStartupBanner({ environment: \"development\" });\r\n },\r\n action: async (data) => {\r\n const devServer = await startDevelopmentServer({\r\n fresh: Boolean(data.options.fresh),\r\n // Pass `false` only when the CLI flag is explicitly set; `undefined`\r\n // lets `warlock.config.ts > devServer.*` defaults apply.\r\n generateTypings: data.options.skipTypings ? false : undefined,\r\n healthCheckers: data.options.skipHealth ? false : undefined,\r\n });\r\n\r\n // Fire-and-forget: once the server is ready, surface a one-line notice if\r\n // a newer @warlock.js/core has been published, and arm the `u` shortcut\r\n // that updates + restarts. Fully self-guarded — it never blocks startup\r\n // nor breaks dev if the registry is unreachable.\r\n void checkForFrameworkUpdate(devServer);\r\n },\r\n options: [\r\n {\r\n text: \"--fresh, -f\",\r\n description: \"Delete .warlock/manifest.json before start (force full re-parse from disk)\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--skip-typings, -st\",\r\n description: \"Skip background type generation for this run\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--skip-health, -sh\",\r\n description: \"Skip file health checkers for this run\",\r\n type: \"boolean\",\r\n },\r\n ],\r\n});\r\n"],"mappings":";;;;;;;AAMA,MAAa,mBAAmB,QAAQ;CACtC,MAAM;CACN,aAAa;CACb,YAAY;CACZ,SAAS;EACP,iBAAiB;EACjB,QAAQ;EACR,WAAW;EACX,UAAU;EAIV,YAAY;CACd;CACA,WAAW,YAAY;EAWrB,IAAI,CAAC,YAAY,GACf,OAAO,mBAAmB;EAG5B,MAAM,qBAAqB,EAAE,aAAa,cAAc,CAAC;CAC3D;CACA,QAAQ,OAAO,SAAS;EAatB,AAAK,wBAAwB,MAZL,uBAAuB;GAC7C,OAAO,QAAQ,KAAK,QAAQ,KAAK;GAGjC,iBAAiB,KAAK,QAAQ,cAAc,QAAQ;GACpD,gBAAgB,KAAK,QAAQ,aAAa,QAAQ;EACpD,CAAC,CAMqC;CACxC;CACA,SAAS;EACP;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;CACF;AACF,CAAC"}
1
+ {"version":3,"file":"dev-server.command.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/dev-server.command.ts"],"sourcesContent":["import { checkForFrameworkUpdate } from \"../../dev-server/check-for-updates\";\r\nimport { startDevelopmentServer } from \"../../dev-server/start-development-server\";\r\nimport { isDevWorker, superviseDevServer } from \"../../dev-server/supervisor\";\r\nimport { command } from \"../../commands/cli-command\";\r\nimport { displayStartupBanner } from \"../cli-commands.utils\";\r\n\r\nexport const devServerCommand = command({\r\n name: \"dev\",\r\n description: \"Start development server (HMR, type-gen, health checks)\",\r\n persistent: true,\r\n preload: {\r\n runtimeStrategy: \"development\",\r\n // Overrides whatever NODE_ENV the shell exported. A shell with\r\n // NODE_ENV=production made Vite's dep optimiser pre-bundle the PRODUCTION\r\n // build of React, which silently suppresses hydration-mismatch warnings —\r\n // the dev server was hiding the exact class of bug it exists to surface.\r\n // Set here, in preload, because preloaders run before the action, and the\r\n // action is what eventually reaches Vite's `createServer`.\r\n environment: \"development\",\r\n config: true, // load all config\r\n bootstrap: true,\r\n prestart: true, // load prestart file (if exists)\r\n // Only the Early lifecycle phase starts here; the Late phase\r\n // (http, socket) starts after app modules load — see\r\n // development-server.ts STEP 8.5.\r\n connectors: true,\r\n },\r\n preAction: async () => {\r\n // Two roles share this command. The first `warlock dev` becomes a thin\r\n // supervisor that owns the terminal and (re)spawns the real server; the\r\n // process it spawns carries WARLOCK_DEV_WORKER and falls through to the\r\n // server itself. See supervisor.ts for why the split exists.\r\n //\r\n // This branch has to sit in `preAction`, which runs BEFORE the command's\r\n // preloaders: the supervisor must never load config or start connectors,\r\n // or it would hold a second database connection open for the whole\r\n // session. `superviseDevServer()` never resolves, so nothing below it —\r\n // preload included — runs in the supervisor.\r\n if (!isDevWorker()) {\r\n return superviseDevServer();\r\n }\r\n\r\n await displayStartupBanner({ environment: \"development\" });\r\n },\r\n action: async (data) => {\r\n const devServer = await startDevelopmentServer({\r\n fresh: Boolean(data.options.fresh),\r\n // Pass `false` only when the CLI flag is explicitly set; `undefined`\r\n // lets `warlock.config.ts > devServer.*` defaults apply.\r\n generateTypings: data.options.skipTypings ? false : undefined,\r\n healthCheckers: data.options.skipHealth ? false : undefined,\r\n });\r\n\r\n // Fire-and-forget: once the server is ready, surface a one-line notice if\r\n // a newer @warlock.js/core has been published, and arm the `u` shortcut\r\n // that updates + restarts. Fully self-guarded — it never blocks startup\r\n // nor breaks dev if the registry is unreachable.\r\n void checkForFrameworkUpdate(devServer);\r\n },\r\n options: [\r\n {\r\n text: \"--fresh, -f\",\r\n description: \"Delete .warlock/manifest.json before start (force full re-parse from disk)\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--skip-typings, -st\",\r\n description: \"Skip background type generation for this run\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--skip-health, -sh\",\r\n description: \"Skip file health checkers for this run\",\r\n type: \"boolean\",\r\n },\r\n ],\r\n});\r\n"],"mappings":";;;;;;;AAMA,MAAa,mBAAmB,QAAQ;CACtC,MAAM;CACN,aAAa;CACb,YAAY;CACZ,SAAS;EACP,iBAAiB;EAOjB,aAAa;EACb,QAAQ;EACR,WAAW;EACX,UAAU;EAIV,YAAY;CACd;CACA,WAAW,YAAY;EAWrB,IAAI,CAAC,YAAY,GACf,OAAO,mBAAmB;EAG5B,MAAM,qBAAqB,EAAE,aAAa,cAAc,CAAC;CAC3D;CACA,QAAQ,OAAO,SAAS;EAatB,AAAK,wBAAwB,MAZL,uBAAuB;GAC7C,OAAO,QAAQ,KAAK,QAAQ,KAAK;GAGjC,iBAAiB,KAAK,QAAQ,cAAc,QAAQ;GACpD,gBAAgB,KAAK,QAAQ,aAAa,QAAQ;EACpD,CAAC,CAMqC;CACxC;CACA,SAAS;EACP;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;CACF;AACF,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { describePositionalHandlerSuspect, listPositionalHandlerSuspects } from "../../../../router/positional-handler-diagnostics.mjs";
2
+
3
+ //#region ../core/src/cli/commands/doctor/checks/handler-signature.check.ts
4
+ /**
5
+ * Surfaces route handlers still written against the v4 positional signature,
6
+ * `(request, response)`. v5 calls a handler with a single context object, so
7
+ * the second parameter is always `undefined` and the route fails on its first
8
+ * request with a `TypeError` that names nothing.
9
+ *
10
+ * Verdicts:
11
+ * - one or more suspects → `warn`;
12
+ * - otherwise → `ok`.
13
+ *
14
+ * `warn`, never `fail`: the detection is a heuristic on the handler's declared
15
+ * parameters, so a false positive is possible and must not break a build.
16
+ *
17
+ * Read-only: reads the list the router collected while routes registered. It
18
+ * therefore reports on the routes this process has loaded — a run before route
19
+ * registration sees nothing.
20
+ */
21
+ const handlerSignatureCheck = {
22
+ name: "handler-signature",
23
+ run: () => {
24
+ const suspects = listPositionalHandlerSuspects();
25
+ if (suspects.length === 0) return {
26
+ name: "handler-signature",
27
+ status: "ok",
28
+ detail: "no handlers look like the v4 positional signature"
29
+ };
30
+ return {
31
+ name: "handler-signature",
32
+ status: "warn",
33
+ detail: `${suspects.length === 1 ? "1 handler looks like the v4 positional signature" : `${suspects.length} handlers look like the v4 positional signature`}:\n` + suspects.map((suspect) => ` - ${describePositionalHandlerSuspect(suspect)}`).join("\n")
34
+ };
35
+ }
36
+ };
37
+
38
+ //#endregion
39
+ export { handlerSignatureCheck };
40
+ //# sourceMappingURL=handler-signature.check.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler-signature.check.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/doctor/checks/handler-signature.check.ts"],"sourcesContent":["import {\n describePositionalHandlerSuspect,\n listPositionalHandlerSuspects,\n} from \"../../../../router/positional-handler-diagnostics\";\nimport type { DoctorCheck } from \"../check.types\";\n\n/**\n * Surfaces route handlers still written against the v4 positional signature,\n * `(request, response)`. v5 calls a handler with a single context object, so\n * the second parameter is always `undefined` and the route fails on its first\n * request with a `TypeError` that names nothing.\n *\n * Verdicts:\n * - one or more suspects → `warn`;\n * - otherwise → `ok`.\n *\n * `warn`, never `fail`: the detection is a heuristic on the handler's declared\n * parameters, so a false positive is possible and must not break a build.\n *\n * Read-only: reads the list the router collected while routes registered. It\n * therefore reports on the routes this process has loaded — a run before route\n * registration sees nothing.\n */\nexport const handlerSignatureCheck: DoctorCheck = {\n name: \"handler-signature\",\n run: () => {\n const suspects = listPositionalHandlerSuspects();\n\n if (suspects.length === 0) {\n return {\n name: \"handler-signature\",\n status: \"ok\",\n detail: \"no handlers look like the v4 positional signature\",\n };\n }\n\n const headline =\n suspects.length === 1\n ? \"1 handler looks like the v4 positional signature\"\n : `${suspects.length} handlers look like the v4 positional signature`;\n\n return {\n name: \"handler-signature\",\n status: \"warn\",\n detail:\n `${headline}:\\n` +\n suspects.map((suspect) => ` - ${describePositionalHandlerSuspect(suspect)}`).join(\"\\n\"),\n };\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,wBAAqC;CAChD,MAAM;CACN,WAAW;EACT,MAAM,WAAW,8BAA8B;EAE/C,IAAI,SAAS,WAAW,GACtB,OAAO;GACL,MAAM;GACN,QAAQ;GACR,QAAQ;EACV;EAQF,OAAO;GACL,MAAM;GACN,QAAQ;GACR,QACE,GARF,SAAS,WAAW,IAChB,qDACA,GAAG,SAAS,OAAO,iDAMT,OACZ,SAAS,KAAK,YAAY,OAAO,iCAAiC,OAAO,GAAG,CAAC,CAAC,KAAK,IAAI;EAC3F;CACF;AACF"}
@@ -1,5 +1,6 @@
1
1
  import { configCheck } from "./config.check.mjs";
2
2
  import { connectorsCheck } from "./connectors.check.mjs";
3
+ import { handlerSignatureCheck } from "./handler-signature.check.mjs";
3
4
  import { healthCheck } from "./health.check.mjs";
4
5
  import { optionalPeersCheck } from "./optional-peers.check.mjs";
5
6
  import { releaseHygieneCheck } from "./release-hygiene.check.mjs";
@@ -8,11 +9,12 @@ import { routesCheck } from "./routes.check.mjs";
8
9
  //#region ../core/src/cli/commands/doctor/checks/index.ts
9
10
  /**
10
11
  * The default, ordered set of checks `warlock doctor` runs. Ordering controls
11
- * the report layout: runtime-surface checks first (routes, config, connectors,
12
- * peers, health), release hygiene last.
12
+ * the report layout: runtime-surface checks first (routes, handler signatures,
13
+ * config, connectors, peers, health), release hygiene last.
13
14
  */
14
15
  const defaultDoctorChecks = [
15
16
  routesCheck,
17
+ handlerSignatureCheck,
16
18
  configCheck,
17
19
  connectorsCheck,
18
20
  optionalPeersCheck,
@@ -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 { 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, config, connectors,\n * peers, health), release hygiene last.\n */\nexport const defaultDoctorChecks: DoctorCheck[] = [\n routesCheck,\n configCheck,\n connectorsCheck,\n optionalPeersCheck,\n healthCheck,\n releaseHygieneCheck,\n];\n"],"mappings":";;;;;;;;;;;;;AAaA,MAAa,sBAAqC;CAChD;CACA;CACA;CACA;CACA;CACA;AACF"}
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 */\nexport const defaultDoctorChecks: DoctorCheck[] = [\n routesCheck,\n handlerSignatureCheck,\n configCheck,\n connectorsCheck,\n optionalPeersCheck,\n healthCheck,\n releaseHygieneCheck,\n];\n"],"mappings":";;;;;;;;;;;;;;AAcA,MAAa,sBAAqC;CAChD;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}
@@ -84,7 +84,7 @@ declare class CLICommand {
84
84
  * Add command option
85
85
  */
86
86
  option(option: CLICommandOption): this;
87
- option(name: string, description?: string, options?: Omit<CLICommandOption, "name">): this;
87
+ option(name: string, description?: string, options?: Omit<CLICommandOption, "name" | "text" | "description">): this;
88
88
  /**
89
89
  * Parse option name and alias if exists
90
90
  *
@@ -1 +1 @@
1
- {"version":3,"file":"cli-command.d.mts","names":[],"sources":["../../../../../../../core/src/commands/cli-command.ts"],"mappings":";;;cAUa,UAAA;EAqDF,IAAA;EArDE;;;EAIJ,aAAA,GAAgB,gBAAA;EAKA;;;EAAhB,aAAA,GAAgB,gBAAA;EAyDD;;;;EAnDf,gBAAA,GAAmB,gBAAA;EAuHuC;;;EAlH1D,cAAA,GAAiB,iBAAA;EAoNA;;;EA/MjB,kBAAA;EAuNqC;;;EAlNrC,cAAA,EAAgB,wBAAA;EArBhB;;;;;EA4BA,mBAAA;EAZA;;;EAiBA,YAAA;EAAA;;;EAKA,YAAA;EAOL;;;cADO,IAAA,UACP,WAAA;EAoBK;;;EARA,MAAA,CAAO,MAAA,EAAQ,gBAAA;EAyBf;;;EAjBA,WAAA,CAAY,WAAA;EAyBL;;;EAjBP,UAAA,CAAW,YAAA;EAkCX;;;;EAzBA,KAAA,CAAM,KAAA;EA0CN;;;EAlCA,MAAA,CAAO,MAAA,EAAQ,gBAAA;EAmCR;;;;EA1BP,SAAA,CAAU,MAAA,EAAQ,gBAAA;EAwDf;;;EAhDH,OAAA,CAAQ,OAAA,EAAS,gBAAA;EA8FhB;;;EArFD,aAAA,CAAc,YAAA;EAmHR;;;EA3GN,MAAA,CAAO,MAAA,EAAQ,gBAAA;EACf,MAAA,CAAO,IAAA,UAAc,WAAA,WAAsB,OAAA,GAAU,IAAA,CAAK,gBAAA;EA0GrB;AAS9C;;;;;;;;EAT8C,UA5ElC,WAAA,CAAY,MAAA,EAAQ,gBAAA,GAAmB,wBAAA;;;;;;;;;UA8CzC,iBAAA;;;;EAsBD,OAAA,CAAQ,OAAA,EAAS,iBAAA;;;;EAQX,OAAA,CAAQ,IAAA,EAAM,iBAAA,GAAiB,OAAA;AAAA;AAAA,iBAS9B,OAAA,CAAQ,OAAA,EAAS,iBAAA,GAAiB,UAAA"}
1
+ {"version":3,"file":"cli-command.d.mts","names":[],"sources":["../../../../../../../core/src/commands/cli-command.ts"],"mappings":";;;cAUa,UAAA;EAqDF,IAAA;EArDE;;;EAIJ,aAAA,GAAgB,gBAAA;EAKA;;;EAAhB,aAAA,GAAgB,gBAAA;EAyDD;;;;EAnDf,gBAAA,GAAmB,gBAAA;EA0HT;;;EArHV,cAAA,GAAiB,iBAAA;EA0NA;;;EArNjB,kBAAA;EA6NqC;;;EAxNrC,cAAA,EAAgB,wBAAA;EArBhB;;;;;EA4BA,mBAAA;EAZA;;;EAiBA,YAAA;EAAA;;;EAKA,YAAA;EAOL;;;cADO,IAAA,UACP,WAAA;EAoBK;;;EARA,MAAA,CAAO,MAAA,EAAQ,gBAAA;EAyBf;;;EAjBA,WAAA,CAAY,WAAA;EAyBL;;;EAjBP,UAAA,CAAW,YAAA;EAkCX;;;;EAzBA,KAAA,CAAM,KAAA;EA0CN;;;EAlCA,MAAA,CAAO,MAAA,EAAQ,gBAAA;EAoCpB;;;;EA3BK,SAAA,CAAU,MAAA,EAAQ,gBAAA;EA8Df;;;EAtDH,OAAA,CAAQ,OAAA,EAAS,gBAAA;EAoGhB;;;EA3FD,aAAA,CAAc,YAAA;EAyHR;;;EAjHN,MAAA,CAAO,MAAA,EAAQ,gBAAA;EACf,MAAA,CACL,IAAA,UACA,WAAA,WACA,OAAA,GAAU,IAAA,CAAK,gBAAA;EA6G2B;AAS9C;;;;;;;;EAT8C,UA5ElC,WAAA,CAAY,MAAA,EAAQ,gBAAA,GAAmB,wBAAA;;;;;;;;;UA8CzC,iBAAA;;;;EAsBD,OAAA,CAAQ,OAAA,EAAS,iBAAA;;;;EAQX,OAAA,CAAQ,IAAA,EAAM,iBAAA,GAAiB,OAAA;AAAA;AAAA,iBAS9B,OAAA,CAAQ,OAAA,EAAS,iBAAA,GAAiB,UAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"cli-command.mjs","names":[],"sources":["../../../../../../../core/src/commands/cli-command.ts"],"sourcesContent":["import type {\n CLICommandAction,\n CLICommandOption,\n CLICommandOptions,\n CLICommandPreload,\n CLICommandSource,\n CommandActionData,\n ResolvedCLICommandOption,\n} from \"./types\";\n\nexport class CLICommand {\n /**\n * Command source\n */\n public commandSource?: CLICommandSource;\n\n /**\n * Command action\n */\n public commandAction?: CLICommandAction;\n\n /**\n * Command pre action\n * This will be executed before loading preloaders\n */\n public commandPreAction?: CLICommandAction;\n\n /**\n * Command preload\n */\n public commandPreload?: CLICommandPreload;\n\n /**\n * Command description\n */\n public commandDescription?: string;\n\n /**\n * Command options\n */\n public commandOptions: ResolvedCLICommandOption[] = [];\n\n /**\n * Command relative path\n * Available only for project commands\n * Auto injected by the framework itself\n */\n public commandRelativePath?: string;\n\n /**\n * Determine if the command is persistent\n */\n public isPersistent: boolean = false;\n\n /**\n * Command alias (short name)\n */\n public commandAlias?: string;\n\n /**\n * Constructor\n */\n public constructor(\n public name: string,\n description?: string,\n ) {\n if (description) {\n this.commandDescription = description;\n }\n\n return this;\n }\n\n /**\n * Add command source\n */\n public source(source: CLICommandSource): this {\n this.commandSource = source;\n return this;\n }\n\n /**\n * Set command description\n */\n public description(description: string): this {\n this.commandDescription = description;\n return this;\n }\n\n /**\n * Determine if the command is persistent\n */\n public persistent(isPersistent = true): this {\n this.isPersistent = isPersistent;\n return this;\n }\n\n /**\n * Set command alias (short name)\n * @example .alias(\"m\") for \"migrate\"\n */\n public alias(alias: string): this {\n this.commandAlias = alias;\n return this;\n }\n\n /**\n * Command action\n */\n public action(action: CLICommandAction): this {\n this.commandAction = action;\n return this;\n }\n\n /**\n * Command pre action\n * This will be executed before loading preloaders\n */\n public preAction(action: CLICommandAction): this {\n this.commandPreAction = action;\n return this;\n }\n\n /**\n * Add command options\n */\n public options(options: CLICommandOption[]): this {\n options.map((option) => this.option(option));\n\n return this;\n }\n\n /**\n * Add command relative path\n */\n public $relativePath(relativePath: string) {\n this.commandRelativePath = relativePath;\n return this;\n }\n\n /**\n * Add command option\n */\n public option(option: CLICommandOption): this;\n public option(name: string, description?: string, options?: Omit<CLICommandOption, \"name\">): this;\n public option(\n ...args: [CLICommandOption] | [string, string?, Omit<CLICommandOption, \"name\">?]\n ): this {\n let option: CLICommandOption;\n if (args.length === 1) {\n option = args[0] as CLICommandOption;\n } else {\n option = {\n text: args[0],\n description: args[1],\n ...args[2],\n name: \"\",\n };\n }\n\n this.commandOptions.push(this.parseOption(option));\n\n return this;\n }\n\n /**\n * Parse option name and alias if exists\n *\n * Supports formats:\n * - \"--port, -p\" → name: \"port\", alias: \"p\"\n * - \"-p, --port\" → name: \"port\", alias: \"p\"\n * - \"--port\" → name: \"port\", alias: undefined\n * - \"-p\" → name: \"p\", alias: undefined\n */\n protected parseOption(option: CLICommandOption): ResolvedCLICommandOption {\n const text = option.text.trim();\n\n // Split by comma to check for alias\n const parts = text.split(\",\").map((part) => part.trim());\n\n let name = \"\";\n let alias = \"\";\n\n if (parts.length === 1) {\n // Single option: \"--port\" or \"-p\"\n name = this.extractOptionName(parts[0]);\n } else if (parts.length === 2) {\n // Two options: \"--port, -p\" or \"-p, --port\"\n const first = parts[0];\n const second = parts[1];\n\n // Determine which is the long form (name) and which is short (alias)\n if (first.startsWith(\"--\")) {\n name = this.extractOptionName(first);\n alias = this.extractOptionName(second);\n } else {\n name = this.extractOptionName(second);\n alias = this.extractOptionName(first);\n }\n }\n\n if (alias === \"h\" || name === \"help\") {\n throw new Error(\"Help option is not allowed, it's reserved for displaying command help\");\n }\n\n return {\n ...option,\n name,\n alias,\n };\n }\n\n /**\n * Extract option name from text (removes -- or -)\n *\n * @example\n * extractOptionName(\"--port\") → \"port\"\n * extractOptionName(\"-p\") → \"p\"\n * extractOptionName(\"--port=3000\") → \"port\"\n */\n private extractOptionName(text: string): string {\n // Remove leading dashes\n let name = text.replace(/^-+/, \"\");\n\n // Remove value assignment if exists (e.g., \"--port=3000\" → \"port\")\n const equalIndex = name.indexOf(\"=\");\n if (equalIndex !== -1) {\n name = name.slice(0, equalIndex);\n }\n\n // Remove angle brackets if exists (e.g., \"--port <number>\" → \"port\")\n const spaceIndex = name.indexOf(\" \");\n if (spaceIndex !== -1) {\n name = name.slice(0, spaceIndex);\n }\n\n return name.trim();\n }\n\n /**\n * Command preload\n */\n public preload(options: CLICommandPreload) {\n this.commandPreload = options;\n return this;\n }\n\n /**\n * Execute the command\n */\n public async execute(data: CommandActionData) {\n if (!this.commandAction) {\n throw new Error(`Command \"${this.name}\" has no action defined`);\n }\n\n await this.commandAction(data);\n }\n}\n\nexport function command(options: CLICommandOptions) {\n const commandInstnace = new CLICommand(options.name, options.description);\n\n if (options.preload) {\n commandInstnace.preload(options.preload);\n }\n\n if (options.persistent) {\n commandInstnace.persistent(options.persistent);\n }\n\n if (options.alias) {\n commandInstnace.alias(options.alias);\n }\n\n commandInstnace.action(options.action);\n\n if (options.options) {\n commandInstnace.options(options.options);\n }\n\n if (options.preAction) {\n commandInstnace.preAction(options.preAction);\n }\n\n return commandInstnace;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli-command.mjs","names":[],"sources":["../../../../../../../core/src/commands/cli-command.ts"],"sourcesContent":["import type {\n CLICommandAction,\n CLICommandOption,\n CLICommandOptions,\n CLICommandPreload,\n CLICommandSource,\n CommandActionData,\n ResolvedCLICommandOption,\n} from \"./types\";\n\nexport class CLICommand {\n /**\n * Command source\n */\n public commandSource?: CLICommandSource;\n\n /**\n * Command action\n */\n public commandAction?: CLICommandAction;\n\n /**\n * Command pre action\n * This will be executed before loading preloaders\n */\n public commandPreAction?: CLICommandAction;\n\n /**\n * Command preload\n */\n public commandPreload?: CLICommandPreload;\n\n /**\n * Command description\n */\n public commandDescription?: string;\n\n /**\n * Command options\n */\n public commandOptions: ResolvedCLICommandOption[] = [];\n\n /**\n * Command relative path\n * Available only for project commands\n * Auto injected by the framework itself\n */\n public commandRelativePath?: string;\n\n /**\n * Determine if the command is persistent\n */\n public isPersistent: boolean = false;\n\n /**\n * Command alias (short name)\n */\n public commandAlias?: string;\n\n /**\n * Constructor\n */\n public constructor(\n public name: string,\n description?: string,\n ) {\n if (description) {\n this.commandDescription = description;\n }\n\n return this;\n }\n\n /**\n * Add command source\n */\n public source(source: CLICommandSource): this {\n this.commandSource = source;\n return this;\n }\n\n /**\n * Set command description\n */\n public description(description: string): this {\n this.commandDescription = description;\n return this;\n }\n\n /**\n * Determine if the command is persistent\n */\n public persistent(isPersistent = true): this {\n this.isPersistent = isPersistent;\n return this;\n }\n\n /**\n * Set command alias (short name)\n * @example .alias(\"m\") for \"migrate\"\n */\n public alias(alias: string): this {\n this.commandAlias = alias;\n return this;\n }\n\n /**\n * Command action\n */\n public action(action: CLICommandAction): this {\n this.commandAction = action;\n return this;\n }\n\n /**\n * Command pre action\n * This will be executed before loading preloaders\n */\n public preAction(action: CLICommandAction): this {\n this.commandPreAction = action;\n return this;\n }\n\n /**\n * Add command options\n */\n public options(options: CLICommandOption[]): this {\n options.map((option) => this.option(option));\n\n return this;\n }\n\n /**\n * Add command relative path\n */\n public $relativePath(relativePath: string) {\n this.commandRelativePath = relativePath;\n return this;\n }\n\n /**\n * Add command option\n */\n public option(option: CLICommandOption): this;\n public option(\n name: string,\n description?: string,\n options?: Omit<CLICommandOption, \"name\" | \"text\" | \"description\">,\n ): this;\n public option(\n ...args:\n | [CLICommandOption]\n | [string, string?, Omit<CLICommandOption, \"name\" | \"text\" | \"description\">?]\n ): this {\n let option: CLICommandOption;\n if (args.length === 1) {\n option = args[0] as CLICommandOption;\n } else {\n option = {\n text: args[0],\n description: args[1],\n ...args[2],\n name: \"\",\n };\n }\n\n this.commandOptions.push(this.parseOption(option));\n\n return this;\n }\n\n /**\n * Parse option name and alias if exists\n *\n * Supports formats:\n * - \"--port, -p\" → name: \"port\", alias: \"p\"\n * - \"-p, --port\" → name: \"port\", alias: \"p\"\n * - \"--port\" → name: \"port\", alias: undefined\n * - \"-p\" → name: \"p\", alias: undefined\n */\n protected parseOption(option: CLICommandOption): ResolvedCLICommandOption {\n const text = option.text.trim();\n\n // Split by comma to check for alias\n const parts = text.split(\",\").map((part) => part.trim());\n\n let name = \"\";\n let alias = \"\";\n\n if (parts.length === 1) {\n // Single option: \"--port\" or \"-p\"\n name = this.extractOptionName(parts[0]);\n } else if (parts.length === 2) {\n // Two options: \"--port, -p\" or \"-p, --port\"\n const first = parts[0];\n const second = parts[1];\n\n // Determine which is the long form (name) and which is short (alias)\n if (first.startsWith(\"--\")) {\n name = this.extractOptionName(first);\n alias = this.extractOptionName(second);\n } else {\n name = this.extractOptionName(second);\n alias = this.extractOptionName(first);\n }\n }\n\n if (alias === \"h\" || name === \"help\") {\n throw new Error(\"Help option is not allowed, it's reserved for displaying command help\");\n }\n\n return {\n ...option,\n name,\n alias,\n };\n }\n\n /**\n * Extract option name from text (removes -- or -)\n *\n * @example\n * extractOptionName(\"--port\") → \"port\"\n * extractOptionName(\"-p\") → \"p\"\n * extractOptionName(\"--port=3000\") → \"port\"\n */\n private extractOptionName(text: string): string {\n // Remove leading dashes\n let name = text.replace(/^-+/, \"\");\n\n // Remove value assignment if exists (e.g., \"--port=3000\" → \"port\")\n const equalIndex = name.indexOf(\"=\");\n if (equalIndex !== -1) {\n name = name.slice(0, equalIndex);\n }\n\n // Remove angle brackets if exists (e.g., \"--port <number>\" → \"port\")\n const spaceIndex = name.indexOf(\" \");\n if (spaceIndex !== -1) {\n name = name.slice(0, spaceIndex);\n }\n\n return name.trim();\n }\n\n /**\n * Command preload\n */\n public preload(options: CLICommandPreload) {\n this.commandPreload = options;\n return this;\n }\n\n /**\n * Execute the command\n */\n public async execute(data: CommandActionData) {\n if (!this.commandAction) {\n throw new Error(`Command \"${this.name}\" has no action defined`);\n }\n\n await this.commandAction(data);\n }\n}\n\nexport function command(options: CLICommandOptions) {\n const commandInstnace = new CLICommand(options.name, options.description);\n\n if (options.preload) {\n commandInstnace.preload(options.preload);\n }\n\n if (options.persistent) {\n commandInstnace.persistent(options.persistent);\n }\n\n if (options.alias) {\n commandInstnace.alias(options.alias);\n }\n\n commandInstnace.action(options.action);\n\n if (options.options) {\n commandInstnace.options(options.options);\n }\n\n if (options.preAction) {\n commandInstnace.preAction(options.preAction);\n }\n\n return commandInstnace;\n}\n"],"mappings":"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"}
@@ -71,9 +71,17 @@ type CLICommandPreload = {
71
71
  */
72
72
  runtimeStrategy?: RuntimeStrategy;
73
73
  /**
74
- * Override current detect environment
74
+ * The environment this command runs under, whatever the shell exported.
75
+ *
76
+ * Set unconditionally — this is an override, not a default. A gate decides
77
+ * its own environment; inheriting `NODE_ENV` from whoever typed the command
78
+ * is how `warlock dev` ended up serving the production build of React.
79
+ *
80
+ * The key was spelled `environemnt` until it was fixed here. It was internally
81
+ * consistent, so it compiled and never failed — it just silently ignored every
82
+ * command that spelled it correctly, which was all of them.
75
83
  */
76
- environemnt?: "production" | "development" | "test";
84
+ environment?: "production" | "development" | "test";
77
85
  /**
78
86
  * Whether to load environment variables from .env files.
79
87
  *
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../core/src/commands/types.ts"],"mappings":";;;;;AAgB4B;AAY5B;;;;KAZY,gBAAA;;;;AAwBK;AAsBjB;;;;;;KAlCY,iBAAA;EAuDV;;;;EAlDA,IAAA;EAiFA;;;AAA0B;AAgB5B;EA1FE,OAAA,EAAS,MAAM;AAAA;;;;;;;;;;AAiIP;AAGV;;;;;;;;;KA9GY,iBAAA;EAuHA;;;;;EAjHV,MAAA;EAiHiE;;AAAO;EA5GxE,QAAA;EAkI2B;;;EA7H3B,aAAA;EAsKQ;;;EAjKR,eAAA,GAAkB,eAAA;EA6HlB;;;EAxHA,WAAA;EAyIU;;;;;;;;;EA9HV,GAAA;;;;;EAMA,SAAA;;;;;;;;EASA,UAAA,GAAa,aAAa;AAAA;;;;;;;;;;;;;;KAgBhB,gBAAA;;;;;EAKV,IAAA;;;;;EAMA,IAAA;;;;;EAMA,KAAA;;;;EAKA,WAAA;;;;;EAMA,IAAA;;;;EAKA,YAAA;;;;;EAMA,QAAA;AAAA;AAAA,KAGU,wBAAA,GAA2B,IAAI,CAAC,gBAAA;EAC1C,IAAA;EACA,KAAA;AAAA;;;;;KAOU,gBAAA,IAAoB,IAAA,EAAM,iBAAA,YAA6B,OAAO;;;;;;;;;;;;;;;;;;;;;KAsB9D,iBAAA;;;;;EAKV,IAAA;;;;;EAMA,KAAA;;;;EAKA,WAAA;;;;;EAMA,OAAA,GAAU,iBAAA;;;;;;EAOV,UAAA;;;;;EAMA,SAAA,GAAY,gBAAA;;;;;EAMZ,MAAA,EAAQ,gBAAA;;;;;EAMR,OAAA,GAAU,gBAAA;AAAA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../core/src/commands/types.ts"],"mappings":";;;;;AAgB4B;AAY5B;;;;KAZY,gBAAA;;;;AAwBK;AAsBjB;;;;;;KAlCY,iBAAA;EAuDV;;;;EAlDA,IAAA;EAyFA;;;AAA0B;AAgB5B;EAlGE,OAAA,EAAS,MAAM;AAAA;;;;;;;;;;AAyIP;AAGV;;;;;;;;;KAtHY,iBAAA;EA+HA;;;;;EAzHV,MAAA;EAyHiE;;AAAO;EApHxE,QAAA;EA0I2B;;;EArI3B,aAAA;EA8KQ;;;EAzKR,eAAA,GAAkB,eAAA;EAqIlB;;;;;;;;;;;EAxHA,WAAA;EAkK0B;AAAA;;;;;;;;EAvJ1B,GAAA;;;;;EAMA,SAAA;;;;;;;;EASA,UAAA,GAAa,aAAa;AAAA;;;;;;;;;;;;;;KAgBhB,gBAAA;;;;;EAKV,IAAA;;;;;EAMA,IAAA;;;;;EAMA,KAAA;;;;EAKA,WAAA;;;;;EAMA,IAAA;;;;EAKA,YAAA;;;;;EAMA,QAAA;AAAA;AAAA,KAGU,wBAAA,GAA2B,IAAI,CAAC,gBAAA;EAC1C,IAAA;EACA,KAAA;AAAA;;;;;KAOU,gBAAA,IAAoB,IAAA,EAAM,iBAAA,YAA6B,OAAO;;;;;;;;;;;;;;;;;;;;;KAsB9D,iBAAA;;;;;EAKV,IAAA;;;;;EAMA,KAAA;;;;EAKA,WAAA;;;;;EAMA,OAAA,GAAU,iBAAA;;;;;;EAOV,UAAA;;;;;EAMA,SAAA,GAAY,gBAAA;;;;;EAMZ,MAAA,EAAQ,gBAAA;;;;;EAMR,OAAA,GAAU,gBAAA;AAAA"}
@@ -2,8 +2,7 @@ import { AppConfigurations } from "../utils/types.mjs";
2
2
 
3
3
  //#region ../core/src/config/config-handlers.d.ts
4
4
  /**
5
- * App Config Handler
6
- * Handles locale loading for dayjs
5
+ * Enforce request-locale configuration when app config loads.
7
6
  */
8
7
  declare const registerAppConfig: (config: AppConfigurations) => Promise<void>;
9
8
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"config-handlers.d.mts","names":[],"sources":["../../../../../../../core/src/config/config-handlers.ts"],"mappings":";;;;;AAOA;;cAAa,iBAAA,GAA2B,MAAA,EAAQ,iBAAA,KAAiB,OAAA"}
1
+ {"version":3,"file":"config-handlers.d.mts","names":[],"sources":["../../../../../../../core/src/config/config-handlers.ts"],"mappings":";;;;;AAOA;cAAa,iBAAA,GAA2B,MAAA,EAAQ,iBAAA,KAAoB,OAAO"}
@@ -1,11 +1,12 @@
1
1
  import { configSpecialHandlers } from "./config-special-handlers.mjs";
2
+ import { assertLocaleConfiguration } from "./locale-configuration.mjs";
2
3
 
3
4
  //#region ../core/src/config/config-handlers.ts
4
5
  /**
5
- * App Config Handler
6
- * Handles locale loading for dayjs
6
+ * Enforce request-locale configuration when app config loads.
7
7
  */
8
8
  const registerAppConfig = async (config) => {
9
+ assertLocaleConfiguration(config);
9
10
  const locales = config.locales || ["en"];
10
11
  for (const locale of locales) {
11
12
  if (locale === "en") continue;
@@ -1 +1 @@
1
- {"version":3,"file":"config-handlers.mjs","names":[],"sources":["../../../../../../../core/src/config/config-handlers.ts"],"sourcesContent":["import { AppConfigurations } from \"../utils/types\";\nimport { configSpecialHandlers } from \"./config-special-handlers\";\n\n/**\n * App Config Handler\n * Handles locale loading for dayjs\n */\nexport const registerAppConfig = async (config: AppConfigurations) => {\n // Load dayjs locales based on app.localeCodes\n const locales = config.locales || [\"en\"];\n\n for (const locale of locales) {\n if (locale === \"en\") continue; // English is default\n\n try {\n await import(`dayjs/locale/${locale}.js`);\n } catch (error) {\n console.warn(` ⚠️ Failed to load dayjs locale: ${locale}`);\n }\n }\n};\n\nconfigSpecialHandlers.register(\"app\", registerAppConfig);\n"],"mappings":";;;;;;;AAOA,MAAa,oBAAoB,OAAO,WAA8B;CAEpE,MAAM,UAAU,OAAO,WAAW,CAAC,IAAI;CAEvC,KAAK,MAAM,UAAU,SAAS;EAC5B,IAAI,WAAW,MAAM;EAErB,IAAI;GACF,MAAM,OAAO,gBAAgB,OAAO;EACtC,SAAS,OAAO;GACd,QAAQ,KAAK,uCAAuC,QAAQ;EAC9D;CACF;AACF;AAEA,sBAAsB,SAAS,OAAO,iBAAiB"}
1
+ {"version":3,"file":"config-handlers.mjs","names":[],"sources":["../../../../../../../core/src/config/config-handlers.ts"],"sourcesContent":["import type { AppConfigurations } from \"../utils/types\";\nimport { configSpecialHandlers } from \"./config-special-handlers\";\nimport { assertLocaleConfiguration } from \"./locale-configuration\";\n\n/**\n * Enforce request-locale configuration when app config loads.\n */\nexport const registerAppConfig = async (config: AppConfigurations): Promise<void> => {\n assertLocaleConfiguration(config);\n\n // Load dayjs locales from the legacy app.locales integration.\n const locales = config.locales || [\"en\"];\n\n for (const locale of locales) {\n if (locale === \"en\") continue; // English is default\n\n try {\n await import(`dayjs/locale/${locale}.js`);\n } catch (error) {\n console.warn(` ⚠️ Failed to load dayjs locale: ${locale}`);\n }\n }\n};\n\nconfigSpecialHandlers.register(\"app\", registerAppConfig);\n"],"mappings":";;;;;;;AAOA,MAAa,oBAAoB,OAAO,WAA6C;CACnF,0BAA0B,MAAM;CAGhC,MAAM,UAAU,OAAO,WAAW,CAAC,IAAI;CAEvC,KAAK,MAAM,UAAU,SAAS;EAC5B,IAAI,WAAW,MAAM;EAErB,IAAI;GACF,MAAM,OAAO,gBAAgB,OAAO;EACtC,SAAS,OAAO;GACd,QAAQ,KAAK,uCAAuC,QAAQ;EAC9D;CACF;AACF;AAEA,sBAAsB,SAAS,OAAO,iBAAiB"}
@@ -0,0 +1,29 @@
1
+ //#region ../core/src/config/locale-configuration.ts
2
+ function isNonEmptyStringArray(value) {
3
+ return Array.isArray(value) && value.length > 0 && value.every((candidate) => typeof candidate === "string");
4
+ }
5
+ /**
6
+ * Resolve the locale configuration used by every request.
7
+ *
8
+ * It fails closed only where there is a declared allow-list to fail against;
9
+ * an application that declares none opts out of validation, not into a
10
+ * single-locale list.
11
+ */
12
+ function resolveLocaleConfiguration(localeCode, localeCodes) {
13
+ return {
14
+ defaultLocaleCode: typeof localeCode === "string" && localeCode.length > 0 ? localeCode : "en",
15
+ localeCodes: isNonEmptyStringArray(localeCodes) ? localeCodes : void 0
16
+ };
17
+ }
18
+ /**
19
+ * A default outside the declared list makes every fallback contradictory, so
20
+ * configuration loading must stop before the application can accept requests.
21
+ */
22
+ function assertLocaleConfiguration(config) {
23
+ const { defaultLocaleCode } = resolveLocaleConfiguration(config.localeCode, config.localeCodes);
24
+ if (isNonEmptyStringArray(config.localeCodes) && !config.localeCodes.includes(defaultLocaleCode)) throw new Error(`app.localeCodes does not include the resolved default locale "${defaultLocaleCode}". Add "${defaultLocaleCode}" to app.localeCodes or change app.localeCode; boot cannot continue with a default that the application does not support.`);
25
+ }
26
+
27
+ //#endregion
28
+ export { assertLocaleConfiguration, resolveLocaleConfiguration };
29
+ //# sourceMappingURL=locale-configuration.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale-configuration.mjs","names":[],"sources":["../../../../../../../core/src/config/locale-configuration.ts"],"sourcesContent":["export type LocaleConfiguration = {\n defaultLocaleCode: string;\n /**\n * The declared allow-list, or `undefined` when the application declares none.\n *\n * The absence of a list is NOT an implicit list of one. With nothing to\n * validate against, a client locale has nothing it can fail — coercing it to\n * the default would discard what the client actually asked for.\n */\n localeCodes: string[] | undefined;\n};\n\ntype AppLocaleConfiguration = {\n localeCode?: unknown;\n localeCodes?: unknown;\n};\n\nfunction isNonEmptyStringArray(value: unknown): value is string[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n value.every((candidate) => typeof candidate === \"string\")\n );\n}\n\n/**\n * Resolve the locale configuration used by every request.\n *\n * It fails closed only where there is a declared allow-list to fail against;\n * an application that declares none opts out of validation, not into a\n * single-locale list.\n */\nexport function resolveLocaleConfiguration(\n localeCode: unknown,\n localeCodes: unknown,\n): LocaleConfiguration {\n const defaultLocaleCode =\n typeof localeCode === \"string\" && localeCode.length > 0 ? localeCode : \"en\";\n\n return {\n defaultLocaleCode,\n localeCodes: isNonEmptyStringArray(localeCodes) ? localeCodes : undefined,\n };\n}\n\n/**\n * A default outside the declared list makes every fallback contradictory, so\n * configuration loading must stop before the application can accept requests.\n */\nexport function assertLocaleConfiguration(config: AppLocaleConfiguration): void {\n const { defaultLocaleCode } = resolveLocaleConfiguration(\n config.localeCode,\n config.localeCodes,\n );\n\n if (\n isNonEmptyStringArray(config.localeCodes) &&\n !config.localeCodes.includes(defaultLocaleCode)\n ) {\n throw new Error(\n `app.localeCodes does not include the resolved default locale \"${defaultLocaleCode}\". ` +\n `Add \"${defaultLocaleCode}\" to app.localeCodes or change app.localeCode; ` +\n `boot cannot continue with a default that the application does not support.`,\n );\n }\n}\n"],"mappings":";AAiBA,SAAS,sBAAsB,OAAmC;CAChE,OACE,MAAM,QAAQ,KAAK,KACnB,MAAM,SAAS,KACf,MAAM,OAAO,cAAc,OAAO,cAAc,QAAQ;AAE5D;;;;;;;;AASA,SAAgB,2BACd,YACA,aACqB;CAIrB,OAAO;EACL,mBAHA,OAAO,eAAe,YAAY,WAAW,SAAS,IAAI,aAAa;EAIvE,aAAa,sBAAsB,WAAW,IAAI,cAAc;CAClE;AACF;;;;;AAMA,SAAgB,0BAA0B,QAAsC;CAC9E,MAAM,EAAE,sBAAsB,2BAC5B,OAAO,YACP,OAAO,WACT;CAEA,IACE,sBAAsB,OAAO,WAAW,KACxC,CAAC,OAAO,YAAY,SAAS,iBAAiB,GAE9C,MAAM,IAAI,MACR,iEAAiE,kBAAkB,UACzE,kBAAkB,0HAE9B;AAEJ"}
@@ -0,0 +1,7 @@
1
+ import { Connector } from "./types.mjs";
2
+
3
+ //#region ../core/src/connectors/assert-no-reserved-connector-names.d.ts
4
+ declare function assertNoReservedConnectorNames(connectors: Connector[]): void;
5
+ //#endregion
6
+ export { assertNoReservedConnectorNames };
7
+ //# sourceMappingURL=assert-no-reserved-connector-names.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assert-no-reserved-connector-names.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/assert-no-reserved-connector-names.ts"],"mappings":";;;iBA+BgB,8BAAA,CAA+B,UAAuB,EAAX,SAAS"}
@@ -0,0 +1,44 @@
1
+ import { connectorsManager } from "./connectors-manager.mjs";
2
+
3
+ //#region ../core/src/connectors/assert-no-reserved-connector-names.ts
4
+ /**
5
+ * Refuses a `connectors` array that claims a name a framework built-in owns.
6
+ *
7
+ * The uniqueness check rejects a name repeated inside the array; this rejects
8
+ * one already held by a BUILT-IN outside it. The manager registers its
9
+ * built-ins in its own constructor, so those names are taken before any config
10
+ * is read — and the two halves disagreed about what that meant. The build
11
+ * drains per ENTRY and knows nothing of the manager, so it prepared the custom
12
+ * connector's artifacts; the runtime registers per NAME and found the built-in
13
+ * already there, so it skipped the entry. The result booted the built-in the
14
+ * app did not ask for, never booted the connector it did, and reported success
15
+ * from both halves.
16
+ *
17
+ * Refused rather than resolved in the app's favour: the built-ins are the
18
+ * framework's own wiring — other framework code reaches for `http`, `cache`,
19
+ * `database` by name — so letting a config replace one silently repoints
20
+ * everything that looks it up. The name is the app's to change; the built-in's
21
+ * is not.
22
+ *
23
+ * Scope, precisely: BUILT-IN names only. A name held by a PREVIOUSLY
24
+ * CONFIGURED connector (an earlier call in the same process) is not this
25
+ * check's business — that case is the registration skip's deliberate
26
+ * idempotency, and stays out of both validators.
27
+ *
28
+ * Reads {@link ConnectorsManager.isBuiltInName}, which is the ONLY inventory of
29
+ * built-in names, so this cannot fall out of step with what the manager
30
+ * actually registered.
31
+ */
32
+ function assertNoReservedConnectorNames(connectors) {
33
+ const reserved = connectors.map((connector) => connector.name).filter((name) => connectorsManager.isBuiltInName(name));
34
+ if (reserved.length === 0) return;
35
+ const unique = [...new Set(reserved)];
36
+ const names = unique.map((name) => JSON.stringify(name)).join(", ");
37
+ const subject = unique.length === 1 ? "name is" : "names are";
38
+ const object = unique.length === 1 ? "it" : "one of them";
39
+ throw new Error(`The following connector ${subject} reserved by a framework built-in connector: [${names}].\nEvery connector name must be unique, and the framework already registers a built-in connector under ${unique.length === 1 ? "that name" : "those names"}, so a custom connector carrying ${object} would be built for and then never booted. Rename the custom connector in \`warlock.config.ts > connectors\` to a name of your own.`);
40
+ }
41
+
42
+ //#endregion
43
+ export { assertNoReservedConnectorNames };
44
+ //# sourceMappingURL=assert-no-reserved-connector-names.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assert-no-reserved-connector-names.mjs","names":[],"sources":["../../../../../../../core/src/connectors/assert-no-reserved-connector-names.ts"],"sourcesContent":["/**\n * Refuses a `connectors` array that claims a name a framework built-in owns.\n *\n * The uniqueness check rejects a name repeated inside the array; this rejects\n * one already held by a BUILT-IN outside it. The manager registers its\n * built-ins in its own constructor, so those names are taken before any config\n * is read — and the two halves disagreed about what that meant. The build\n * drains per ENTRY and knows nothing of the manager, so it prepared the custom\n * connector's artifacts; the runtime registers per NAME and found the built-in\n * already there, so it skipped the entry. The result booted the built-in the\n * app did not ask for, never booted the connector it did, and reported success\n * from both halves.\n *\n * Refused rather than resolved in the app's favour: the built-ins are the\n * framework's own wiring — other framework code reaches for `http`, `cache`,\n * `database` by name — so letting a config replace one silently repoints\n * everything that looks it up. The name is the app's to change; the built-in's\n * is not.\n *\n * Scope, precisely: BUILT-IN names only. A name held by a PREVIOUSLY\n * CONFIGURED connector (an earlier call in the same process) is not this\n * check's business — that case is the registration skip's deliberate\n * idempotency, and stays out of both validators.\n *\n * Reads {@link ConnectorsManager.isBuiltInName}, which is the ONLY inventory of\n * built-in names, so this cannot fall out of step with what the manager\n * actually registered.\n */\nimport { connectorsManager } from \"./connectors-manager\";\nimport type { Connector } from \"./types\";\n\nexport function assertNoReservedConnectorNames(connectors: Connector[]): void {\n const reserved = connectors\n .map((connector) => connector.name)\n .filter((name) => connectorsManager.isBuiltInName(name));\n\n if (reserved.length === 0) return;\n\n const unique = [...new Set(reserved)];\n const names = unique.map((name) => JSON.stringify(name)).join(\", \");\n const subject = unique.length === 1 ? \"name is\" : \"names are\";\n const object = unique.length === 1 ? \"it\" : \"one of them\";\n\n throw new Error(\n `The following connector ${subject} reserved by a framework built-in connector: [${names}].\\n` +\n \"Every connector name must be unique, and the framework already registers a built-in connector under \" +\n `${unique.length === 1 ? \"that name\" : \"those names\"}, so a custom connector carrying ${object} ` +\n \"would be built for and then never booted. \" +\n \"Rename the custom connector in `warlock.config.ts > connectors` to a name of your own.\",\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,+BAA+B,YAA+B;CAC5E,MAAM,WAAW,WACd,KAAK,cAAc,UAAU,IAAI,CAAC,CAClC,QAAQ,SAAS,kBAAkB,cAAc,IAAI,CAAC;CAEzD,IAAI,SAAS,WAAW,GAAG;CAE3B,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC;CACpC,MAAM,QAAQ,OAAO,KAAK,SAAS,KAAK,UAAU,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI;CAClE,MAAM,UAAU,OAAO,WAAW,IAAI,YAAY;CAClD,MAAM,SAAS,OAAO,WAAW,IAAI,OAAO;CAE5C,MAAM,IAAI,MACR,2BAA2B,QAAQ,gDAAgD,MAAM,0GAEpF,OAAO,WAAW,IAAI,cAAc,cAAc,mCAAmC,OAAO,oIAGnG;AACF"}
@@ -0,0 +1,7 @@
1
+ import { Connector } from "./types.mjs";
2
+
3
+ //#region ../core/src/connectors/assert-unique-connector-names.d.ts
4
+ declare function assertUniqueConnectorNames(connectors: Connector[]): void;
5
+ //#endregion
6
+ export { assertUniqueConnectorNames };
7
+ //# sourceMappingURL=assert-unique-connector-names.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assert-unique-connector-names.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/assert-unique-connector-names.ts"],"mappings":";;;iBAyBgB,0BAAA,CAA2B,UAAuB,EAAX,SAAS"}
@@ -0,0 +1,20 @@
1
+ //#region ../core/src/connectors/assert-unique-connector-names.ts
2
+ function assertUniqueConnectorNames(connectors) {
3
+ const seen = /* @__PURE__ */ new Set();
4
+ const duplicated = /* @__PURE__ */ new Set();
5
+ for (const connector of connectors) {
6
+ if (seen.has(connector.name)) {
7
+ duplicated.add(connector.name);
8
+ continue;
9
+ }
10
+ seen.add(connector.name);
11
+ }
12
+ if (duplicated.size === 0) return;
13
+ const names = [...duplicated].map((name) => JSON.stringify(name)).join(", ");
14
+ const subject = duplicated.size === 1 ? "name is" : "names are";
15
+ throw new Error(`The same connector ${subject} listed more than once in \`connectors\`: [${names}].\nEvery connector in \`warlock.config.ts > connectors\` must have a unique name. The build prepares artifacts per ENTRY — it drains the build contribution of each one, so a repeated name contributes twice — while the runtime registers each NAME once and skips the rest. A duplicate is therefore half-applied, and neither half reports it. Remove the extra entry, or give the connectors distinct names.`);
16
+ }
17
+
18
+ //#endregion
19
+ export { assertUniqueConnectorNames };
20
+ //# sourceMappingURL=assert-unique-connector-names.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assert-unique-connector-names.mjs","names":[],"sources":["../../../../../../../core/src/connectors/assert-unique-connector-names.ts"],"sourcesContent":["/**\n * Refuses a `connectors` array that lists the same name more than once.\n *\n * A name is a connector's identity across both halves of its life, and the two\n * halves treat a repeat differently. The build works per ARRAY ENTRY: it drains\n * the build contribution of every element, so a name listed twice contributes\n * twice — files written twice, esbuild patches merged twice. The runtime works\n * per NAME: the first entry registers and every later one with that name is\n * skipped, because registering it again would boot and shut down the same\n * connector twice.\n *\n * So a duplicate is half-applied: built for twice, booted once, with nothing\n * reported either way. The name-set the drift check compares is identical on\n * both sides, so it passes the array straight through — this asymmetry is\n * invisible to it, which is why it is caught here instead.\n *\n * Called by both halves (the build before it drains, the registration before it\n * registers) so neither can be reached with an array the other would refuse.\n * It rejects duplicates WITHIN the array it is handed. A name held OUTSIDE the\n * array is out of scope here: if a BUILT-IN holds it, the reserved-name check\n * refuses it; if an EARLIER CALL configured it, the registration skip treats it\n * as idempotency on purpose — neither validator claims that case.\n */\nimport type { Connector } from \"./types\";\n\nexport function assertUniqueConnectorNames(connectors: Connector[]): void {\n const seen = new Set<string>();\n const duplicated = new Set<string>();\n\n for (const connector of connectors) {\n if (seen.has(connector.name)) {\n duplicated.add(connector.name);\n continue;\n }\n\n seen.add(connector.name);\n }\n\n if (duplicated.size === 0) return;\n\n const names = [...duplicated].map((name) => JSON.stringify(name)).join(\", \");\n const subject = duplicated.size === 1 ? \"name is\" : \"names are\";\n\n throw new Error(\n `The same connector ${subject} listed more than once in \\`connectors\\`: [${names}].\\n` +\n \"Every connector in `warlock.config.ts > connectors` must have a unique name. \" +\n \"The build prepares artifacts per ENTRY — it drains the build contribution of each one, so a repeated name contributes twice — \" +\n \"while the runtime registers each NAME once and skips the rest. \" +\n \"A duplicate is therefore half-applied, and neither half reports it. \" +\n \"Remove the extra entry, or give the connectors distinct names.\",\n );\n}\n"],"mappings":";AAyBA,SAAgB,2BAA2B,YAA+B;CACxE,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,6BAAa,IAAI,IAAY;CAEnC,KAAK,MAAM,aAAa,YAAY;EAClC,IAAI,KAAK,IAAI,UAAU,IAAI,GAAG;GAC5B,WAAW,IAAI,UAAU,IAAI;GAC7B;EACF;EAEA,KAAK,IAAI,UAAU,IAAI;CACzB;CAEA,IAAI,WAAW,SAAS,GAAG;CAE3B,MAAM,QAAQ,CAAC,GAAG,UAAU,CAAC,CAAC,KAAK,SAAS,KAAK,UAAU,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI;CAC3E,MAAM,UAAU,WAAW,SAAS,IAAI,YAAY;CAEpD,MAAM,IAAI,MACR,sBAAsB,QAAQ,6CAA6C,MAAM,mZAMnF;AACF"}