zudojs-cli 1.2.0 → 2.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 (109) hide show
  1. package/README.md +35 -1
  2. package/dist/src/adapters/frontend/flutter.adapter.d.ts +9 -0
  3. package/dist/src/adapters/frontend/flutter.adapter.js +16 -1
  4. package/dist/src/bin/zudojs.js +16 -5
  5. package/dist/src/cliApplication/cliApplication.builtins.d.ts +4 -0
  6. package/dist/src/cliApplication/cliApplication.builtins.js +28 -9
  7. package/dist/src/cliApplication/cliApplication.core.d.ts +8 -0
  8. package/dist/src/cliApplication/cliApplication.core.js +45 -5
  9. package/dist/src/cliApplication/cliApplication.help.d.ts +17 -0
  10. package/dist/src/cliApplication/cliApplication.help.js +94 -0
  11. package/dist/src/cliApplication/cliApplication.logger.js +4 -4
  12. package/dist/src/cliCommand/cliCommand.validator.js +22 -0
  13. package/dist/src/cliConstant/cliConstant.value.d.ts +18 -12
  14. package/dist/src/cliConstant/cliConstant.value.js +25 -19
  15. package/dist/src/cliError/cliError.argument.js +2 -2
  16. package/dist/src/cliError/cliError.base.js +28 -5
  17. package/dist/src/cliError/cliError.command.js +5 -2
  18. package/dist/src/cliError/cliError.execution.js +3 -3
  19. package/dist/src/cliParser/cliParser.core.js +15 -6
  20. package/dist/src/cliParser/cliParser.helper.d.ts +11 -0
  21. package/dist/src/cliParser/cliParser.helper.js +19 -0
  22. package/dist/src/cliParser/cliParser.longOption.js +4 -4
  23. package/dist/src/cliParser/cliParser.shortOption.js +7 -4
  24. package/dist/src/cliVersion/cliVersion.update.js +5 -3
  25. package/dist/src/commands/add.command.js +17 -2
  26. package/dist/src/commands/commandError.helper.d.ts +15 -0
  27. package/dist/src/commands/commandError.helper.js +28 -0
  28. package/dist/src/commands/create.command.js +284 -46
  29. package/dist/src/commands/doctor.command.d.ts +8 -0
  30. package/dist/src/commands/doctor.command.js +38 -5
  31. package/dist/src/commands/generate.command.js +71 -32
  32. package/dist/src/constants/index.d.ts +40 -68
  33. package/dist/src/constants/index.js +73 -23
  34. package/dist/src/generators/frontend/frontendPipeline.js +16 -4
  35. package/dist/src/generators/integration/integrationGenerator.core.js +10 -6
  36. package/dist/src/generators/project/project.generator.js +5 -1
  37. package/dist/src/index.d.ts +4 -4
  38. package/dist/src/index.js +3 -3
  39. package/dist/src/manifest/index.d.ts +1 -0
  40. package/dist/src/manifest/index.js +1 -0
  41. package/dist/src/manifest/manifestFile.helper.d.ts +42 -0
  42. package/dist/src/manifest/manifestFile.helper.js +141 -0
  43. package/dist/src/manifest/manifestManager.core.d.ts +16 -0
  44. package/dist/src/manifest/manifestManager.core.js +65 -35
  45. package/dist/src/prompts/backend/api-style.prompt.js +2 -5
  46. package/dist/src/prompts/backend/backend-architecture.prompt.js +5 -22
  47. package/dist/src/prompts/backend/database.prompt.js +5 -23
  48. package/dist/src/prompts/backend/services.prompt.js +12 -10
  49. package/dist/src/prompts/cancel.prompt.d.ts +18 -0
  50. package/dist/src/prompts/cancel.prompt.js +26 -0
  51. package/dist/src/prompts/capabilities/capabilities.prompt.js +2 -5
  52. package/dist/src/prompts/frontend/framework.prompt.js +2 -5
  53. package/dist/src/prompts/frontend/frontend-architecture.prompt.js +2 -5
  54. package/dist/src/prompts/index.d.ts +1 -0
  55. package/dist/src/prompts/index.js +1 -0
  56. package/dist/src/prompts/project/confirmation.prompt.js +2 -5
  57. package/dist/src/prompts/project/project-name.prompt.js +6 -7
  58. package/dist/src/prompts/project/project-type.prompt.js +2 -5
  59. package/dist/src/prompts/workspace/package-manager.prompt.js +5 -12
  60. package/dist/src/registries/adapter/frontendAdapterRegistry.core.d.ts +6 -0
  61. package/dist/src/registries/adapter/frontendAdapterRegistry.core.js +12 -0
  62. package/dist/src/registries/adapter/packageManagerRegistry.core.d.ts +5 -0
  63. package/dist/src/registries/adapter/packageManagerRegistry.core.js +11 -0
  64. package/dist/src/registries/dependency/dependencyRegistry.core.d.ts +9 -0
  65. package/dist/src/registries/dependency/dependencyRegistry.core.js +16 -0
  66. package/dist/src/registries/generator/generatorRegistry.core.d.ts +8 -0
  67. package/dist/src/registries/generator/generatorRegistry.core.js +14 -0
  68. package/dist/src/resolvers/capability/capabilityResolver.core.d.ts +1 -1
  69. package/dist/src/resolvers/capability/capabilityResolver.core.js +0 -2
  70. package/dist/src/resolvers/layout/projectLayout.core.js +1 -6
  71. package/dist/src/resolvers/project.resolver.d.ts +9 -3
  72. package/dist/src/resolvers/project.resolver.js +11 -7
  73. package/dist/src/rollback/index.d.ts +1 -1
  74. package/dist/src/rollback/index.js +1 -1
  75. package/dist/src/rollback/rollbackManager.core.d.ts +19 -1
  76. package/dist/src/rollback/rollbackManager.core.js +21 -2
  77. package/dist/src/runners/package-manager/index.d.ts +1 -1
  78. package/dist/src/runners/package-manager/index.js +1 -1
  79. package/dist/src/runners/package-manager/packageManagerRunner.core.d.ts +13 -1
  80. package/dist/src/runners/package-manager/packageManagerRunner.core.js +19 -1
  81. package/dist/src/runners/process/processRunner.core.d.ts +11 -0
  82. package/dist/src/runners/process/processRunner.core.js +41 -6
  83. package/dist/src/runners/task/index.d.ts +1 -1
  84. package/dist/src/runners/task/taskRunner.core.d.ts +22 -1
  85. package/dist/src/runners/task/taskRunner.core.js +35 -6
  86. package/dist/src/scaffolders/framework.scaffolder.js +3 -0
  87. package/dist/src/scaffolders/scaffolder.helper.d.ts +10 -0
  88. package/dist/src/scaffolders/scaffolder.helper.js +25 -2
  89. package/dist/src/templates/microservice/index.d.ts +1 -1
  90. package/dist/src/templates/microservice/index.js +1 -1
  91. package/dist/src/templates/microservice/microservice.template.d.ts +8 -4
  92. package/dist/src/templates/microservice/microservice.template.js +30 -19
  93. package/dist/src/templates/modular-monolith/modularMonolith.template.js +27 -8
  94. package/dist/src/templates/monolith/monolith.template.js +11 -17
  95. package/dist/src/templates/shared/capability.template.d.ts +25 -0
  96. package/dist/src/templates/shared/capability.template.js +46 -0
  97. package/dist/src/templates/shared/index.d.ts +1 -0
  98. package/dist/src/templates/shared/index.js +1 -0
  99. package/dist/src/utils/utils.exec.d.ts +22 -0
  100. package/dist/src/utils/utils.exec.js +51 -4
  101. package/dist/src/utils/utils.fileSystem.js +46 -2
  102. package/dist/src/utils/utils.name.d.ts +9 -0
  103. package/dist/src/utils/utils.name.js +29 -3
  104. package/dist/src/validators/environment/environmentValidator.core.d.ts +11 -1
  105. package/dist/src/validators/environment/environmentValidator.core.js +40 -73
  106. package/dist/src/validators/project/index.d.ts +1 -1
  107. package/dist/src/validators/project/projectValidator.core.d.ts +11 -1
  108. package/dist/src/validators/project/projectValidator.core.js +20 -4
  109. package/package.json +6 -6
@@ -11,20 +11,31 @@ import { CLI_VERSION } from "./cliVersion.js";
11
11
  export const CLI_NAME = "zudojs";
12
12
  /** Default application configuration. */
13
13
  export const CLI_DEFAULTS = {
14
- NAME: "zudojs",
14
+ NAME: CLI_NAME,
15
15
  VERSION: CLI_VERSION,
16
16
  DESCRIPTION: "Command-line interface for the Zudojs framework.",
17
- COMMAND_PREFIX: "zudojs",
17
+ COMMAND_PREFIX: CLI_NAME,
18
18
  DEFAULT_CWD: process.cwd(),
19
19
  EXIT_CODE: 0,
20
20
  };
21
21
  /* -------------------------------------------------------------------------- */
22
+ /* Symbolic Names */
23
+ /* -------------------------------------------------------------------------- */
24
+ /** Internal symbolic names for CLI elements. */
25
+ export const CLI_SYMBOLS = {
26
+ COMMAND: "command",
27
+ OPTION: "option",
28
+ ARGUMENT: "argument",
29
+ HELP: "help",
30
+ VERSION: "version",
31
+ };
32
+ /* -------------------------------------------------------------------------- */
22
33
  /* Built-in Commands */
23
34
  /* -------------------------------------------------------------------------- */
24
35
  /** Built-in command names. */
25
36
  export const CLI_COMMANDS = {
26
- HELP: "help",
27
- VERSION: "version",
37
+ HELP: CLI_SYMBOLS.HELP,
38
+ VERSION: CLI_SYMBOLS.VERSION,
28
39
  };
29
40
  /** Built-in command aliases. */
30
41
  export const CLI_ALIASES = {
@@ -41,17 +52,6 @@ export const CLI_OPTION_PREFIXES = {
41
52
  VALUE_SEPARATOR: "=",
42
53
  };
43
54
  /* -------------------------------------------------------------------------- */
44
- /* Symbolic Names */
45
- /* -------------------------------------------------------------------------- */
46
- /** Internal symbolic names for CLI elements. */
47
- export const CLI_SYMBOLS = {
48
- COMMAND: "command",
49
- OPTION: "option",
50
- ARGUMENT: "argument",
51
- HELP: "help",
52
- VERSION: "version",
53
- };
54
- /* -------------------------------------------------------------------------- */
55
55
  /* User-Facing Messages */
56
56
  /* -------------------------------------------------------------------------- */
57
57
  /** Standard CLI messages. */
@@ -61,7 +61,7 @@ export const CLI_MESSAGES = {
61
61
  PERMISSION_DENIED: "Permission denied.",
62
62
  INTERRUPTED: "Process interrupted.",
63
63
  UNKNOWN_ERROR: "An unexpected error occurred.",
64
- MISSING_COMMAND: "A command is required.",
64
+ MISSING_COMMAND: "No command given.",
65
65
  };
66
66
  /* -------------------------------------------------------------------------- */
67
67
  /* Help Labels */
@@ -79,11 +79,17 @@ export const CLI_HELP = {
79
79
  /* -------------------------------------------------------------------------- */
80
80
  /* Environment */
81
81
  /* -------------------------------------------------------------------------- */
82
- /** Environment variable names. */
82
+ /**
83
+ * Environment variable names the CLI reads.
84
+ *
85
+ * Every entry has a single reader: `DEBUG` turns on verbose logging,
86
+ * `CI` and `NO_UPDATE_CHECK`/`NPM_OFFLINE` suppress the update check.
87
+ */
83
88
  export const CLI_ENVIRONMENT = {
84
- NODE_ENV: "NODE_ENV",
85
- DEBUG: "DEBUG",
89
+ DEBUG: "ZUDOJS_DEBUG",
86
90
  CI: "CI",
91
+ NO_UPDATE_CHECK: "ZUDOJS_NO_UPDATE_CHECK",
92
+ NPM_OFFLINE: "npm_config_offline",
87
93
  };
88
94
  /* -------------------------------------------------------------------------- */
89
95
  /* Formatting */
@@ -4,7 +4,7 @@
4
4
  * Error classes for argument validation failures.
5
5
  */
6
6
  import { CLIError } from "./cliError.base.js";
7
- import { CLI_ERROR_CODES, CLI_EXIT_CODES, } from "../cliConstant/cliConstant.value.js";
7
+ import { CLI_ERROR_CODES, CLI_EXIT_CODES, CLI_MESSAGES, } from "../cliConstant/cliConstant.value.js";
8
8
  /* -------------------------------------------------------------------------- */
9
9
  /* Invalid Arguments */
10
10
  /* -------------------------------------------------------------------------- */
@@ -12,7 +12,7 @@ import { CLI_ERROR_CODES, CLI_EXIT_CODES, } from "../cliConstant/cliConstant.val
12
12
  * Thrown when command arguments fail validation.
13
13
  */
14
14
  export class InvalidArgumentsError extends CLIError {
15
- constructor(message = "Invalid command arguments.", options = {}) {
15
+ constructor(message = CLI_MESSAGES.INVALID_ARGUMENTS, options = {}) {
16
16
  super(message, {
17
17
  ...options,
18
18
  code: CLI_ERROR_CODES.INVALID_ARGUMENTS,
@@ -6,7 +6,7 @@
6
6
  * command, option, argument).
7
7
  */
8
8
  import { ApplicationError } from "@zudojs/errors";
9
- import { CLI_ERROR_CODES, CLI_EXIT_CODES, } from "../cliConstant/cliConstant.value.js";
9
+ import { CLI_ERROR_CODES, CLI_EXIT_CODES, CLI_MESSAGES, } from "../cliConstant/cliConstant.value.js";
10
10
  /**
11
11
  * Base error for all CLI subsystem failures.
12
12
  *
@@ -54,21 +54,44 @@ export class CLIError extends ApplicationError {
54
54
  export function isCLIError(error) {
55
55
  return error instanceof CLIError;
56
56
  }
57
+ /**
58
+ * Reads a process exit code carried by an arbitrary error.
59
+ *
60
+ * Only `CLIError` extends this base class; `CommandNotFoundError`,
61
+ * `CLIPermissionError` and the other errors built on `@zudojs/errors`
62
+ * declare their own `exitCode` field. Without this, every one of them was
63
+ * re-wrapped as `GENERAL_ERROR` and the declared `COMMAND_NOT_FOUND` (3),
64
+ * `PERMISSION_DENIED` (4) and `INTERRUPTED` (130) codes never reached the
65
+ * shell.
66
+ */
67
+ function readExitCode(error) {
68
+ if (typeof error !== "object" || error === null)
69
+ return undefined;
70
+ const candidate = error.exitCode;
71
+ if (typeof candidate !== "number" ||
72
+ !Number.isInteger(candidate) ||
73
+ candidate < 0 ||
74
+ candidate > 255) {
75
+ return undefined;
76
+ }
77
+ return candidate;
78
+ }
57
79
  /** Normalizes any error into a CLIError. */
58
- export function normalizeCLIError(error, fallbackMessage = "An unexpected CLI error occurred.") {
80
+ export function normalizeCLIError(error, fallbackMessage = CLI_MESSAGES.UNKNOWN_ERROR) {
59
81
  if (error instanceof CLIError) {
60
82
  return error;
61
83
  }
84
+ const exitCode = readExitCode(error) ?? CLI_EXIT_CODES.GENERAL_ERROR;
62
85
  if (error instanceof Error) {
63
86
  return new CLIError(error.message || fallbackMessage, {
64
87
  code: CLI_ERROR_CODES.UNKNOWN_ERROR,
65
- exitCode: CLI_EXIT_CODES.GENERAL_ERROR,
88
+ exitCode,
66
89
  cause: error,
67
90
  });
68
91
  }
69
92
  return new CLIError(fallbackMessage, {
70
93
  code: CLI_ERROR_CODES.UNKNOWN_ERROR,
71
- exitCode: CLI_EXIT_CODES.GENERAL_ERROR,
94
+ exitCode,
72
95
  cause: error,
73
96
  });
74
97
  }
@@ -77,7 +100,7 @@ export function getCLIExitCode(error) {
77
100
  if (isCLIError(error)) {
78
101
  return error.exitCode;
79
102
  }
80
- return CLI_EXIT_CODES.GENERAL_ERROR;
103
+ return readExitCode(error) ?? CLI_EXIT_CODES.GENERAL_ERROR;
81
104
  }
82
105
  /** Extracts the error code from an error. */
83
106
  export function getCLIErrorCode(error) {
@@ -5,7 +5,7 @@
5
5
  * Extends base errors from `@zudojs/errors` where possible.
6
6
  */
7
7
  import { NotFoundError, ConflictError } from "@zudojs/errors";
8
- import { CLI_ERROR_CODES, CLI_EXIT_CODES, } from "../cliConstant/cliConstant.value.js";
8
+ import { CLI_ERROR_CODES, CLI_EXIT_CODES, CLI_MESSAGES, } from "../cliConstant/cliConstant.value.js";
9
9
  /* -------------------------------------------------------------------------- */
10
10
  /* Command Not Found */
11
11
  /* -------------------------------------------------------------------------- */
@@ -16,7 +16,10 @@ import { CLI_ERROR_CODES, CLI_EXIT_CODES, } from "../cliConstant/cliConstant.val
16
16
  export class CommandNotFoundError extends NotFoundError {
17
17
  exitCode;
18
18
  constructor(command) {
19
- super(`Command "${command}" was not found.`, {
19
+ const message = command
20
+ ? `Command "${command}" was not found.`
21
+ : CLI_MESSAGES.COMMAND_NOT_FOUND;
22
+ super(message, {
20
23
  code: CLI_ERROR_CODES.COMMAND_NOT_FOUND,
21
24
  statusCode: 404,
22
25
  expose: true,
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { AuthorizationError, ConfigurationError } from "@zudojs/errors";
7
7
  import { CLIError } from "./cliError.base.js";
8
- import { CLI_ERROR_CODES, CLI_EXIT_CODES, } from "../cliConstant/cliConstant.value.js";
8
+ import { CLI_ERROR_CODES, CLI_EXIT_CODES, CLI_MESSAGES, } from "../cliConstant/cliConstant.value.js";
9
9
  /* -------------------------------------------------------------------------- */
10
10
  /* Execution Error */
11
11
  /* -------------------------------------------------------------------------- */
@@ -32,7 +32,7 @@ export class CLIExecutionError extends CLIError {
32
32
  */
33
33
  export class CLIPermissionError extends AuthorizationError {
34
34
  exitCode;
35
- constructor(message = "Permission denied.", options = {}) {
35
+ constructor(message = CLI_MESSAGES.PERMISSION_DENIED, options = {}) {
36
36
  super(message, {
37
37
  ...options,
38
38
  code: CLI_ERROR_CODES.PERMISSION_DENIED,
@@ -56,7 +56,7 @@ export class CLIPermissionError extends AuthorizationError {
56
56
  * Thrown when the CLI process is interrupted (e.g. SIGINT).
57
57
  */
58
58
  export class CLIInterruptedError extends CLIError {
59
- constructor(message = "Process interrupted.", options = {}) {
59
+ constructor(message = CLI_MESSAGES.INTERRUPTED, options = {}) {
60
60
  super(message, {
61
61
  ...options,
62
62
  code: CLI_ERROR_CODES.INTERRUPTED,
@@ -32,9 +32,13 @@ export class CLIParser {
32
32
  const definitions = command?.options ?? [];
33
33
  const argumentsDefinitions = command?.arguments ?? [];
34
34
  let index = 0;
35
+ // Where the `--` pass-through began, if it did: everything after it is
36
+ // deliberately opaque and is never measured against the declarations.
37
+ let escapedAt;
35
38
  while (index < tokens.length) {
36
39
  const token = tokens[index];
37
40
  if (token === "--") {
41
+ escapedAt = positional.length;
38
42
  positional.push(...tokens.slice(index + 1));
39
43
  break;
40
44
  }
@@ -64,8 +68,13 @@ export class CLIParser {
64
68
  options[definition.name] = definition.defaultValue;
65
69
  }
66
70
  }
67
- const parsedArguments = this.parseArguments(positional, argumentsDefinitions);
68
- if (!this.allowUnknownCommands &&
71
+ // Surplus positionals are only an error once a command is known: with no
72
+ // command there is no declaration to measure them against.
73
+ const parsedArguments = this.parseArguments(positional, argumentsDefinitions, command === undefined ? null : (escapedAt ?? positional.length));
74
+ // `stopAtFirstArgument` is checked here too: without it the fallback undid
75
+ // the flag, putting the first positional in both `commands` and `args`.
76
+ if (!this.stopAtFirstArgument &&
77
+ !this.allowUnknownCommands &&
69
78
  !command &&
70
79
  commands.length === 0 &&
71
80
  tokens.length > 0) {
@@ -82,10 +91,8 @@ export class CLIParser {
82
91
  };
83
92
  }
84
93
  /* ---- Arguments ---- */
85
- parseArguments(values, definitions) {
94
+ parseArguments(values, definitions, strictUpTo) {
86
95
  const result = {};
87
- if (definitions.length === 0)
88
- return result;
89
96
  let valueIndex = 0;
90
97
  for (const definition of definitions) {
91
98
  if (definition.variadic) {
@@ -110,7 +117,9 @@ export class CLIParser {
110
117
  result[definition.name] = value;
111
118
  valueIndex++;
112
119
  }
113
- if (valueIndex < values.length) {
120
+ // Commands that declare no arguments used to return before this check,
121
+ // so `zudojs doctor my-proj` discarded `my-proj` without a word.
122
+ if (strictUpTo !== null && valueIndex < strictUpTo) {
114
123
  throw new InvalidArgumentsError(`Unexpected argument "${values[valueIndex]}".`);
115
124
  }
116
125
  return result;
@@ -15,6 +15,17 @@ export declare function parseBoolean(value: string, option?: string): boolean;
15
15
  export declare function isOption(token: string): boolean;
16
16
  /** Returns whether a token is a long option (e.g. `--verbose`). */
17
17
  export declare function isLongOption(token: string): boolean;
18
+ /** Returns whether a token looks like a negative number (e.g. `-1`, `-2.5`). */
19
+ export declare function isNegativeNumber(token: string): boolean;
20
+ /**
21
+ * Returns whether the token following an option can be taken as its value.
22
+ *
23
+ * A flag-looking token is refused so that `--type --frontend react` reports
24
+ * the missing value for `--type` instead of setting it to `"--frontend"`.
25
+ * Negative numbers stay usable for numeric options: `--port -1` and
26
+ * `--port=-1` both keep working.
27
+ */
28
+ export declare function isOptionValueToken(definition: CLIOption, token: string | undefined): token is string;
18
29
  /** Returns whether a token is a short option (e.g. `-v`). */
19
30
  export declare function isShortOption(token: string): boolean;
20
31
  /** Resolves a command name to a CLICommand. */
@@ -54,6 +54,25 @@ export function isLongOption(token) {
54
54
  return (token.startsWith(CLI_OPTION_PREFIXES.LONG) &&
55
55
  token.length > CLI_OPTION_PREFIXES.LONG.length);
56
56
  }
57
+ /** Returns whether a token looks like a negative number (e.g. `-1`, `-2.5`). */
58
+ export function isNegativeNumber(token) {
59
+ return /^-(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(token);
60
+ }
61
+ /**
62
+ * Returns whether the token following an option can be taken as its value.
63
+ *
64
+ * A flag-looking token is refused so that `--type --frontend react` reports
65
+ * the missing value for `--type` instead of setting it to `"--frontend"`.
66
+ * Negative numbers stay usable for numeric options: `--port -1` and
67
+ * `--port=-1` both keep working.
68
+ */
69
+ export function isOptionValueToken(definition, token) {
70
+ if (token === undefined || token === "--")
71
+ return false;
72
+ if (!isOption(token))
73
+ return true;
74
+ return definition.type === "number" && isNegativeNumber(token);
75
+ }
57
76
  /** Returns whether a token is a short option (e.g. `-v`). */
58
77
  export function isShortOption(token) {
59
78
  return (token.startsWith(CLI_OPTION_PREFIXES.SHORT) &&
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { CLI_OPTION_PREFIXES } from "../cliConstant/cliConstant.value.js";
7
7
  import { InvalidOptionError, MissingOptionValueError, } from "../cliError/cliError.option.js";
8
- import { isOption, parseBoolean, parseOptionValue, } from "./cliParser.helper.js";
8
+ import { isOption, isOptionValueToken, parseBoolean, parseOptionValue, } from "./cliParser.helper.js";
9
9
  /** Parses a long option token from the token stream. */
10
10
  export function parseLongOption(tokens, index, definitions, values, allowUnknownOptions) {
11
11
  const token = tokens[index];
@@ -50,11 +50,11 @@ export function parseLongOption(tokens, index, definitions, values, allowUnknown
50
50
  return index + 1;
51
51
  }
52
52
  if (value === undefined) {
53
- if (tokens[index + 1] === undefined || tokens[index + 1] === "--") {
53
+ const next = tokens[index + 1];
54
+ if (!isOptionValueToken(definition, next)) {
54
55
  throw new MissingOptionValueError(token);
55
56
  }
56
- value = tokens[index + 1];
57
- assignOptionValue(definition, value, values);
57
+ assignOptionValue(definition, next, values);
58
58
  return index + 2;
59
59
  }
60
60
  assignOptionValue(definition, value, values);
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { CLI_OPTION_PREFIXES } from "../cliConstant/cliConstant.value.js";
7
7
  import { InvalidOptionError, MissingOptionValueError, } from "../cliError/cliError.option.js";
8
+ import { isOptionValueToken } from "./cliParser.helper.js";
8
9
  import { assignOptionValue } from "./cliParser.longOption.js";
9
10
  /** Parses a short option token from the token stream. */
10
11
  export function parseShortOption(tokens, index, definitions, values, allowUnknownOptions) {
@@ -44,10 +45,11 @@ export function parseShortOption(tokens, index, definitions, values, allowUnknow
44
45
  assignOptionValue(definition, attachedValue, values);
45
46
  return index + 1;
46
47
  }
47
- if (tokens[index + 1] === undefined) {
48
+ const next = tokens[index + 1];
49
+ if (!isOptionValueToken(definition, next)) {
48
50
  throw new MissingOptionValueError(`-${short}`);
49
51
  }
50
- assignOptionValue(definition, tokens[index + 1], values);
52
+ assignOptionValue(definition, next, values);
51
53
  return index + 2;
52
54
  }
53
55
  values[definition.name] = true;
@@ -66,10 +68,11 @@ export function parseShortOption(tokens, index, definitions, values, allowUnknow
66
68
  values[definition.name] = true;
67
69
  return index + 1;
68
70
  }
69
- if (tokens[index + 1] === undefined) {
71
+ const next = tokens[index + 1];
72
+ if (!isOptionValueToken(definition, next)) {
70
73
  throw new MissingOptionValueError(token);
71
74
  }
72
- assignOptionValue(definition, tokens[index + 1], values);
75
+ assignOptionValue(definition, next, values);
73
76
  return index + 2;
74
77
  }
75
78
  /** Finds an option by its short flag. */
@@ -11,6 +11,7 @@
11
11
  *
12
12
  * @module cliVersion/update
13
13
  */
14
+ import { CLI_ENVIRONMENT } from "../cliConstant/cliConstant.value.js";
14
15
  import { compareVersions, isValidVersion } from "./cliVersion.core.js";
15
16
  /**
16
17
  * Whether the update check is turned off for this environment.
@@ -19,12 +20,13 @@ import { compareVersions, isValidVersion } from "./cliVersion.core.js";
19
20
  * mode disable it implicitly.
20
21
  */
21
22
  export function isUpdateCheckDisabled(env = process.env) {
22
- const flag = env["ZUDOJS_NO_UPDATE_CHECK"];
23
+ const flag = env[CLI_ENVIRONMENT.NO_UPDATE_CHECK];
23
24
  if (flag !== undefined && flag !== "" && flag !== "0")
24
25
  return true;
25
- if (env["CI"] !== undefined && env["CI"] !== "" && env["CI"] !== "false")
26
+ const ci = env[CLI_ENVIRONMENT.CI];
27
+ if (ci !== undefined && ci !== "" && ci !== "false")
26
28
  return true;
27
- if (env["npm_config_offline"] === "true")
29
+ if (env[CLI_ENVIRONMENT.NPM_OFFLINE] === "true")
28
30
  return true;
29
31
  return false;
30
32
  }
@@ -88,6 +88,14 @@ export async function runAddCommand(context) {
88
88
  }
89
89
  context.logger.info(`Adding feature: ${feature}`);
90
90
  context.logger.info(`Packages: ${packages.join(", ")}`);
91
+ // The manifest is read and validated before anything is written: an
92
+ // unusable manifest used to be discovered after every package.json had
93
+ // already been rewritten, leaving the project half-updated.
94
+ const manifest = new ManifestManager(layout.root);
95
+ const manifestState = await manifest.readResult();
96
+ if (manifestState.status === "invalid") {
97
+ throw new CLIGenerationError(`The project manifest at ${manifest.path} is unusable: ${manifestState.reason ?? "unknown reason"}. Fix or delete it, then re-run; nothing has been changed.`);
98
+ }
91
99
  try {
92
100
  for (const pkgPath of targets) {
93
101
  if (!existsSync(pkgPath)) {
@@ -111,8 +119,15 @@ export async function runAddCommand(context) {
111
119
  writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
112
120
  context.logger.info(`Updated ${relative(context.cwd, pkgPath) || "package.json"}`);
113
121
  }
114
- // The manifest records capabilities for the whole project.
115
- await new ManifestManager(layout.root).addCapability(feature);
122
+ // The manifest records capabilities for the whole project. A project
123
+ // described by a legacy zudojs.config.ts or a package.json block has
124
+ // none; that is said out loud rather than passed over in silence.
125
+ if (manifestState.status === "ok") {
126
+ await manifest.addCapability(feature);
127
+ }
128
+ else {
129
+ context.logger.warn(`No .zudojs/manifest.json in ${layout.root}; "${feature}" was recorded in package.json only.`);
130
+ }
116
131
  if (context.values["skip-install"] !== true) {
117
132
  const [file, ...args] = getInstallCommand(layout.packageManager);
118
133
  context.logger.info(`Installing dependencies with ${file}...`);
@@ -0,0 +1,15 @@
1
+ /**
2
+ * zudojs-cli — Command Error Helpers
3
+ *
4
+ * Rendering for errors a command reports to the user.
5
+ */
6
+ /**
7
+ * Renders an error together with everything it was caused by.
8
+ *
9
+ * Generators wrap failures in a CLIGenerationError whose own message says
10
+ * only what was being attempted — "Failed to write project files:",
11
+ * "Failed to generate service: billing" — and the cause carried the reason
12
+ * (ENOTDIR, EACCES) without anything ever printing it.
13
+ */
14
+ export declare function describeError(error: unknown): string;
15
+ //# sourceMappingURL=commandError.helper.d.ts.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * zudojs-cli — Command Error Helpers
3
+ *
4
+ * Rendering for errors a command reports to the user.
5
+ */
6
+ /** How many `cause` links are followed before the chain is cut off. */
7
+ const MAX_CAUSE_DEPTH = 5;
8
+ /**
9
+ * Renders an error together with everything it was caused by.
10
+ *
11
+ * Generators wrap failures in a CLIGenerationError whose own message says
12
+ * only what was being attempted — "Failed to write project files:",
13
+ * "Failed to generate service: billing" — and the cause carried the reason
14
+ * (ENOTDIR, EACCES) without anything ever printing it.
15
+ */
16
+ export function describeError(error) {
17
+ const parts = [];
18
+ let current = error;
19
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH && current instanceof Error; depth += 1) {
20
+ const message = current.message.replace(/[:\s]+$/, "");
21
+ if (message.length > 0 && !parts.includes(message)) {
22
+ parts.push(message);
23
+ }
24
+ current = current.cause;
25
+ }
26
+ return parts.length > 0 ? parts.join(": ") : String(error);
27
+ }
28
+ //# sourceMappingURL=commandError.helper.js.map