zudojs-cli 1.2.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -1
- package/dist/src/adapters/frontend/flutter.adapter.d.ts +9 -0
- package/dist/src/adapters/frontend/flutter.adapter.js +16 -1
- package/dist/src/bin/zudojs.js +16 -5
- package/dist/src/cliApplication/cliApplication.builtins.d.ts +4 -0
- package/dist/src/cliApplication/cliApplication.builtins.js +28 -9
- package/dist/src/cliApplication/cliApplication.core.d.ts +8 -0
- package/dist/src/cliApplication/cliApplication.core.js +45 -5
- package/dist/src/cliApplication/cliApplication.help.d.ts +17 -0
- package/dist/src/cliApplication/cliApplication.help.js +94 -0
- package/dist/src/cliApplication/cliApplication.logger.js +4 -4
- package/dist/src/cliCommand/cliCommand.validator.js +22 -0
- package/dist/src/cliConstant/cliConstant.value.d.ts +18 -12
- package/dist/src/cliConstant/cliConstant.value.js +25 -19
- package/dist/src/cliError/cliError.argument.js +2 -2
- package/dist/src/cliError/cliError.base.js +28 -5
- package/dist/src/cliError/cliError.command.js +5 -2
- package/dist/src/cliError/cliError.execution.js +3 -3
- package/dist/src/cliParser/cliParser.core.js +11 -5
- package/dist/src/cliParser/cliParser.helper.d.ts +11 -0
- package/dist/src/cliParser/cliParser.helper.js +19 -0
- package/dist/src/cliParser/cliParser.longOption.js +4 -4
- package/dist/src/cliParser/cliParser.shortOption.js +7 -4
- package/dist/src/cliVersion/cliVersion.update.js +5 -3
- package/dist/src/commands/add.command.js +17 -2
- package/dist/src/commands/commandError.helper.d.ts +15 -0
- package/dist/src/commands/commandError.helper.js +28 -0
- package/dist/src/commands/create.command.js +284 -46
- package/dist/src/commands/doctor.command.d.ts +8 -0
- package/dist/src/commands/doctor.command.js +38 -5
- package/dist/src/commands/generate.command.js +71 -32
- package/dist/src/constants/index.d.ts +40 -68
- package/dist/src/constants/index.js +73 -23
- package/dist/src/generators/frontend/frontendPipeline.js +16 -4
- package/dist/src/generators/integration/integrationGenerator.core.js +10 -6
- package/dist/src/generators/project/project.generator.js +5 -1
- package/dist/src/index.d.ts +4 -4
- package/dist/src/index.js +3 -3
- package/dist/src/manifest/index.d.ts +1 -0
- package/dist/src/manifest/index.js +1 -0
- package/dist/src/manifest/manifestFile.helper.d.ts +42 -0
- package/dist/src/manifest/manifestFile.helper.js +141 -0
- package/dist/src/manifest/manifestManager.core.d.ts +16 -0
- package/dist/src/manifest/manifestManager.core.js +65 -35
- package/dist/src/prompts/backend/api-style.prompt.js +2 -5
- package/dist/src/prompts/backend/backend-architecture.prompt.js +5 -22
- package/dist/src/prompts/backend/database.prompt.js +5 -23
- package/dist/src/prompts/backend/services.prompt.js +12 -10
- package/dist/src/prompts/cancel.prompt.d.ts +18 -0
- package/dist/src/prompts/cancel.prompt.js +26 -0
- package/dist/src/prompts/capabilities/capabilities.prompt.js +2 -5
- package/dist/src/prompts/frontend/framework.prompt.js +2 -5
- package/dist/src/prompts/frontend/frontend-architecture.prompt.js +2 -5
- package/dist/src/prompts/index.d.ts +1 -0
- package/dist/src/prompts/index.js +1 -0
- package/dist/src/prompts/project/confirmation.prompt.js +2 -5
- package/dist/src/prompts/project/project-name.prompt.js +6 -7
- package/dist/src/prompts/project/project-type.prompt.js +2 -5
- package/dist/src/prompts/workspace/package-manager.prompt.js +5 -12
- package/dist/src/registries/adapter/frontendAdapterRegistry.core.d.ts +6 -0
- package/dist/src/registries/adapter/frontendAdapterRegistry.core.js +12 -0
- package/dist/src/registries/adapter/packageManagerRegistry.core.d.ts +5 -0
- package/dist/src/registries/adapter/packageManagerRegistry.core.js +11 -0
- package/dist/src/registries/dependency/dependencyRegistry.core.d.ts +9 -0
- package/dist/src/registries/dependency/dependencyRegistry.core.js +16 -0
- package/dist/src/registries/generator/generatorRegistry.core.d.ts +8 -0
- package/dist/src/registries/generator/generatorRegistry.core.js +14 -0
- package/dist/src/resolvers/capability/capabilityResolver.core.d.ts +1 -1
- package/dist/src/resolvers/capability/capabilityResolver.core.js +0 -2
- package/dist/src/resolvers/layout/projectLayout.core.js +1 -6
- package/dist/src/resolvers/project.resolver.d.ts +9 -3
- package/dist/src/resolvers/project.resolver.js +11 -7
- package/dist/src/rollback/index.d.ts +1 -1
- package/dist/src/rollback/index.js +1 -1
- package/dist/src/rollback/rollbackManager.core.d.ts +19 -1
- package/dist/src/rollback/rollbackManager.core.js +21 -2
- package/dist/src/runners/package-manager/index.d.ts +1 -1
- package/dist/src/runners/package-manager/index.js +1 -1
- package/dist/src/runners/package-manager/packageManagerRunner.core.d.ts +13 -1
- package/dist/src/runners/package-manager/packageManagerRunner.core.js +19 -1
- package/dist/src/runners/process/processRunner.core.d.ts +11 -0
- package/dist/src/runners/process/processRunner.core.js +41 -6
- package/dist/src/runners/task/index.d.ts +1 -1
- package/dist/src/runners/task/taskRunner.core.d.ts +22 -1
- package/dist/src/runners/task/taskRunner.core.js +35 -6
- package/dist/src/scaffolders/framework.scaffolder.js +3 -0
- package/dist/src/scaffolders/scaffolder.helper.d.ts +10 -0
- package/dist/src/scaffolders/scaffolder.helper.js +25 -2
- package/dist/src/templates/microservice/index.d.ts +1 -1
- package/dist/src/templates/microservice/index.js +1 -1
- package/dist/src/templates/microservice/microservice.template.d.ts +11 -4
- package/dist/src/templates/microservice/microservice.template.js +40 -19
- package/dist/src/templates/modular-monolith/modularMonolith.template.js +27 -8
- package/dist/src/templates/monolith/monolith.template.js +11 -17
- package/dist/src/templates/shared/capability.template.d.ts +25 -0
- package/dist/src/templates/shared/capability.template.js +46 -0
- package/dist/src/templates/shared/index.d.ts +1 -0
- package/dist/src/templates/shared/index.js +1 -0
- package/dist/src/utils/utils.exec.d.ts +22 -0
- package/dist/src/utils/utils.exec.js +51 -4
- package/dist/src/utils/utils.fileSystem.js +46 -2
- package/dist/src/utils/utils.name.d.ts +9 -0
- package/dist/src/utils/utils.name.js +29 -3
- package/dist/src/validators/environment/environmentValidator.core.d.ts +11 -1
- package/dist/src/validators/environment/environmentValidator.core.js +40 -73
- package/dist/src/validators/project/index.d.ts +1 -1
- package/dist/src/validators/project/projectValidator.core.d.ts +11 -1
- package/dist/src/validators/project/projectValidator.core.js +20 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -93,6 +93,16 @@ allows esbuild's build script, which pnpm 10+ would otherwise refuse to run.
|
|
|
93
93
|
- **Fullstack + microservice:** the gateway and services are written at
|
|
94
94
|
`apps/gateway` and `apps/services/<name>`, next to `apps/web`, and are part
|
|
95
95
|
of the root workspace.
|
|
96
|
+
- **Capabilities:** `--capabilities cqrs,events,messaging,queue,observability,openapi,database,security`
|
|
97
|
+
picks what the project is wired with. The interactive prompt starts from
|
|
98
|
+
the same list, so both ways of running `create` can produce the same
|
|
99
|
+
project. Without the flag, a non-interactive run enables `cqrs`,
|
|
100
|
+
`messaging`, `observability`, `openapi` and `database`.
|
|
101
|
+
- **Failed installs fail the command.** If dependency installation fails,
|
|
102
|
+
`create` keeps the generated project, prints the command to retry with and
|
|
103
|
+
exits non-zero, so CI cannot go green with no `node_modules`.
|
|
104
|
+
- **Ctrl-C** during `create` removes the half-written project and exits with
|
|
105
|
+
status 130.
|
|
96
106
|
|
|
97
107
|
### Generating code
|
|
98
108
|
|
|
@@ -102,12 +112,44 @@ subclass), exports it from the modules barrel and registers it in `app.ts`.
|
|
|
102
112
|
change, and lists those files; pass `--force` to overwrite them. Barrels are
|
|
103
113
|
only appended to. `--dry-run` lists the files without writing anything.
|
|
104
114
|
|
|
115
|
+
It can be run from any directory inside the project: the project root is
|
|
116
|
+
found by walking up, and everything is generated relative to that root.
|
|
117
|
+
Outside a Zudojs project the command fails instead of writing files into the
|
|
118
|
+
current directory.
|
|
119
|
+
|
|
120
|
+
**Schematics:** `service`, `module`, `command`, `query`, `controller`,
|
|
121
|
+
`repository`, `middleware`, `event`, `job`, `route`, `model`, `dto`,
|
|
122
|
+
`validator`.
|
|
123
|
+
|
|
124
|
+
**Options:**
|
|
125
|
+
|
|
126
|
+
| Option | Applies to | Description |
|
|
127
|
+
| ----------------- | ----------------------------- | ------------------------------------------------------------------ |
|
|
128
|
+
| `--module <name>` | every schematic but `module` | Generates inside `<root>/modules/<name>` instead of the schematic's default directory |
|
|
129
|
+
| `--service <name>`| microservice projects, CQRS | Selects the app a schematic belongs to; also names the CQRS service for `command`/`query` |
|
|
130
|
+
| `--dry-run` | all | Lists the files without writing anything |
|
|
131
|
+
| `--force` | all | Overwrites files that already exist |
|
|
132
|
+
|
|
133
|
+
Where a schematic lands depends on the architecture: a `monolith` puts
|
|
134
|
+
services in `src/services` and modules in `src/modules`; a
|
|
135
|
+
`modular-monolith` has modules only, so `generate service` there generates a
|
|
136
|
+
module and registers it in `app.ts`; in a `microservice` project a schematic
|
|
137
|
+
goes to the gateway app, or to `apps/services/<name>` with `--service <name>`.
|
|
138
|
+
|
|
139
|
+
`generate service` is **refused** in a microservice project. A service there is
|
|
140
|
+
a whole workspace app — its own `package.json`, `tsconfig.json`, `Dockerfile`
|
|
141
|
+
and port — which the schematic does not produce; it used to write a bare class
|
|
142
|
+
into a directory pnpm skipped and no build compiled. Create services with
|
|
143
|
+
`zudojs create <project> --architecture microservice --services <names>`, and
|
|
144
|
+
add domain logic to an existing app with
|
|
145
|
+
`zudojs generate module <name> --service <existing-service>`.
|
|
146
|
+
|
|
105
147
|
## Commands
|
|
106
148
|
|
|
107
149
|
| Command | Description |
|
|
108
150
|
| ---------- | ------------------------------------------------------------------------ |
|
|
109
151
|
| `create` | Scaffold a new project (backend, frontend, or fullstack) |
|
|
110
|
-
| `generate` | Generate files
|
|
152
|
+
| `generate` | Generate files — see the schematics below |
|
|
111
153
|
| `add` | Add feature packages (database, queue, messaging, etc.); `--service` targets one microservice app |
|
|
112
154
|
| `dev` | Start development servers through the project's package manager (`pnpm run dev`, …) |
|
|
113
155
|
| `build` | Build the project with its detected package manager |
|
|
@@ -12,6 +12,15 @@ export declare class FlutterAdapter implements FrontendAdapter {
|
|
|
12
12
|
readonly framework = "flutter";
|
|
13
13
|
isAvailable(): Promise<boolean>;
|
|
14
14
|
getLatestVersion(): Promise<string>;
|
|
15
|
+
/**
|
|
16
|
+
* Converts a project name to a Flutter package name.
|
|
17
|
+
*
|
|
18
|
+
* `flutter create --project-name` only accepts lower_snake_case (a valid
|
|
19
|
+
* Dart package identifier), while `zudojs create` accepts uppercase and
|
|
20
|
+
* hyphens. Passing the raw name made `flutter create` fail outright, and
|
|
21
|
+
* this adapter has no fallback template, so the whole run was rolled back.
|
|
22
|
+
*/
|
|
23
|
+
static toFlutterProjectName(name: string): string;
|
|
15
24
|
scaffold(context: FrontendGenerationContext): Promise<void>;
|
|
16
25
|
getDependencies(context: FrontendGenerationContext): readonly DependencyRequirement[];
|
|
17
26
|
applyZudojsStructure(context: FrontendGenerationContext): Promise<void>;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { execCommand } from "../../utils/utils.exec.js";
|
|
7
7
|
import { writeFileTree } from "../../utils/utils.fileSystem.js";
|
|
8
|
+
import { normalizeName } from "../../utils/utils.name.js";
|
|
8
9
|
/**
|
|
9
10
|
* Flutter adapter for mobile applications.
|
|
10
11
|
*/
|
|
@@ -23,6 +24,20 @@ export class FlutterAdapter {
|
|
|
23
24
|
async getLatestVersion() {
|
|
24
25
|
return "3";
|
|
25
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Converts a project name to a Flutter package name.
|
|
29
|
+
*
|
|
30
|
+
* `flutter create --project-name` only accepts lower_snake_case (a valid
|
|
31
|
+
* Dart package identifier), while `zudojs create` accepts uppercase and
|
|
32
|
+
* hyphens. Passing the raw name made `flutter create` fail outright, and
|
|
33
|
+
* this adapter has no fallback template, so the whole run was rolled back.
|
|
34
|
+
*/
|
|
35
|
+
static toFlutterProjectName(name) {
|
|
36
|
+
const snake = normalizeName(name).replace(/-/g, "_");
|
|
37
|
+
if (snake === "")
|
|
38
|
+
return "zudojs_app";
|
|
39
|
+
return /^[0-9]/.test(snake) ? `app_${snake}` : snake;
|
|
40
|
+
}
|
|
26
41
|
async scaffold(context) {
|
|
27
42
|
const { projectPath } = context;
|
|
28
43
|
const org = "com.zudojs";
|
|
@@ -32,7 +47,7 @@ export class FlutterAdapter {
|
|
|
32
47
|
"--org",
|
|
33
48
|
org,
|
|
34
49
|
"--project-name",
|
|
35
|
-
context.project.name,
|
|
50
|
+
FlutterAdapter.toFlutterProjectName(context.project.name),
|
|
36
51
|
"--platforms",
|
|
37
52
|
"android,ios,web",
|
|
38
53
|
], projectPath);
|
package/dist/src/bin/zudojs.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { createCLI } from "../cliApplication/index.js";
|
|
8
8
|
import { createCommand } from "../cliCommand/index.js";
|
|
9
|
-
import { CLI_VERSION,
|
|
9
|
+
import { CLI_VERSION, FEATURE_NAMES, SCHEMATIC_NAMES, } from "../constants/index.js";
|
|
10
|
+
import { CLI_NAME } from "../cliConstant/cliConstant.value.js";
|
|
10
11
|
import { runCreateCommand } from "../commands/create.command.js";
|
|
11
12
|
import { runDevCommand } from "../commands/dev.command.js";
|
|
12
13
|
import { runGenerateCommand } from "../commands/generate.command.js";
|
|
@@ -15,7 +16,8 @@ import { runBuildCommand } from "../commands/build.command.js";
|
|
|
15
16
|
import { runDoctorCommand } from "../commands/doctor.command.js";
|
|
16
17
|
import { runInfoCommand } from "../commands/info.command.js";
|
|
17
18
|
const app = createCLI({
|
|
18
|
-
name
|
|
19
|
+
// The name the help output tells people to type, so it must be the binary.
|
|
20
|
+
name: CLI_NAME,
|
|
19
21
|
// Read from the CLI's own package.json, so `zudojs --version` cannot
|
|
20
22
|
// drift from the published version.
|
|
21
23
|
version: CLI_VERSION,
|
|
@@ -108,6 +110,15 @@ app.register(createCommand({
|
|
|
108
110
|
description: "Comma-separated service names (microservice architecture only)",
|
|
109
111
|
type: "string",
|
|
110
112
|
},
|
|
113
|
+
{
|
|
114
|
+
// No default. An absent flag keeps the non-interactive branch's
|
|
115
|
+
// historical set (cqrs, messaging, observability, openapi, database)
|
|
116
|
+
// and seeds the interactive prompt with nothing, so adding the flag
|
|
117
|
+
// does not change what an existing command line produces.
|
|
118
|
+
name: "capabilities",
|
|
119
|
+
description: "Comma-separated capabilities (cqrs, events, messaging, queue, observability, openapi, database, security)",
|
|
120
|
+
type: "string",
|
|
121
|
+
},
|
|
111
122
|
],
|
|
112
123
|
execute: async (context) => {
|
|
113
124
|
await runCreateCommand(context);
|
|
@@ -148,7 +159,7 @@ app.register(createCommand({
|
|
|
148
159
|
arguments: [
|
|
149
160
|
{
|
|
150
161
|
name: "schematic",
|
|
151
|
-
description:
|
|
162
|
+
description: `The schematic to generate (${SCHEMATIC_NAMES.join(", ")})`,
|
|
152
163
|
required: true,
|
|
153
164
|
},
|
|
154
165
|
{
|
|
@@ -167,7 +178,7 @@ app.register(createCommand({
|
|
|
167
178
|
{
|
|
168
179
|
name: "module",
|
|
169
180
|
short: "m",
|
|
170
|
-
description: "The module
|
|
181
|
+
description: "The module a generated file belongs to (modular monolith), e.g. --module billing",
|
|
171
182
|
type: "string",
|
|
172
183
|
},
|
|
173
184
|
{
|
|
@@ -193,7 +204,7 @@ app.register(createCommand({
|
|
|
193
204
|
arguments: [
|
|
194
205
|
{
|
|
195
206
|
name: "feature",
|
|
196
|
-
description: `The feature to add (${
|
|
207
|
+
description: `The feature to add (${FEATURE_NAMES.join(", ")})`,
|
|
197
208
|
required: true,
|
|
198
209
|
},
|
|
199
210
|
],
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
* Help and version request detection and output.
|
|
5
5
|
*/
|
|
6
6
|
import type { CLIArguments, CLIWriter } from "../cliType/cliType.type.js";
|
|
7
|
+
/** Returns whether a single token is a help flag (`-h` or `--help`). */
|
|
8
|
+
export declare function isHelpFlag(token: string): boolean;
|
|
9
|
+
/** Returns whether a single token is a version flag (`-v` or `--version`). */
|
|
10
|
+
export declare function isVersionFlag(token: string): boolean;
|
|
7
11
|
/**
|
|
8
12
|
* Returns whether the args are a help request.
|
|
9
13
|
*
|
|
@@ -3,7 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Help and version request detection and output.
|
|
5
5
|
*/
|
|
6
|
-
import { CLI_COMMANDS, CLI_DEFAULTS, } from "../cliConstant/cliConstant.value.js";
|
|
6
|
+
import { CLI_ALIASES, CLI_COMMANDS, CLI_DEFAULTS, CLI_FORMAT, CLI_HELP, CLI_SYMBOLS, } from "../cliConstant/cliConstant.value.js";
|
|
7
|
+
/** Returns whether a single token is a help flag (`-h` or `--help`). */
|
|
8
|
+
export function isHelpFlag(token) {
|
|
9
|
+
return CLI_ALIASES.HELP.includes(token);
|
|
10
|
+
}
|
|
11
|
+
/** Returns whether a single token is a version flag (`-v` or `--version`). */
|
|
12
|
+
export function isVersionFlag(token) {
|
|
13
|
+
return CLI_ALIASES.VERSION.includes(token);
|
|
14
|
+
}
|
|
7
15
|
/**
|
|
8
16
|
* Returns whether the args are a help request.
|
|
9
17
|
*
|
|
@@ -12,7 +20,9 @@ import { CLI_COMMANDS, CLI_DEFAULTS, } from "../cliConstant/cliConstant.value.js
|
|
|
12
20
|
*/
|
|
13
21
|
export function isHelpRequest(args) {
|
|
14
22
|
const first = args[0];
|
|
15
|
-
|
|
23
|
+
if (first === undefined)
|
|
24
|
+
return false;
|
|
25
|
+
return first === CLI_COMMANDS.HELP || isHelpFlag(first);
|
|
16
26
|
}
|
|
17
27
|
/**
|
|
18
28
|
* Returns whether the args are a version request.
|
|
@@ -21,7 +31,9 @@ export function isHelpRequest(args) {
|
|
|
21
31
|
*/
|
|
22
32
|
export function isVersionRequest(args) {
|
|
23
33
|
const first = args[0];
|
|
24
|
-
|
|
34
|
+
if (first === undefined)
|
|
35
|
+
return false;
|
|
36
|
+
return first === CLI_COMMANDS.VERSION || isVersionFlag(first);
|
|
25
37
|
}
|
|
26
38
|
/** Prints the version string. */
|
|
27
39
|
export function printVersion(writer, version) {
|
|
@@ -33,18 +45,25 @@ export function printHelp(writer, name, version, description, commands = []) {
|
|
|
33
45
|
lines.push(`${name} ${version ? `v${version}` : ""}`.trim());
|
|
34
46
|
if (description)
|
|
35
47
|
lines.push(description);
|
|
36
|
-
lines.push(
|
|
48
|
+
lines.push(CLI_FORMAT.EMPTY, CLI_HELP.USAGE, `${CLI_FORMAT.INDENT}${name} <${CLI_SYMBOLS.COMMAND}> [${CLI_SYMBOLS.OPTION}s]`, CLI_FORMAT.EMPTY, CLI_HELP.COMMANDS);
|
|
37
49
|
const sorted = commands.slice().sort((a, b) => a.name.localeCompare(b.name));
|
|
38
50
|
if (sorted.length === 0) {
|
|
39
|
-
lines.push(
|
|
51
|
+
lines.push(`${CLI_FORMAT.INDENT}No commands registered.`);
|
|
40
52
|
}
|
|
41
53
|
else {
|
|
54
|
+
const width = sorted.reduce((max, cmd) => Math.max(max, formatCommandTerm(cmd).length), 0);
|
|
42
55
|
for (const cmd of sorted) {
|
|
43
|
-
const
|
|
44
|
-
lines.push(
|
|
56
|
+
const term = formatCommandTerm(cmd).padEnd(width);
|
|
57
|
+
lines.push(`${CLI_FORMAT.INDENT}${term}${cmd.description ? `${CLI_FORMAT.INDENT}${cmd.description}` : ""}`.trimEnd());
|
|
45
58
|
}
|
|
46
59
|
}
|
|
47
|
-
lines.push(
|
|
48
|
-
writer.writeLine(lines.join(
|
|
60
|
+
lines.push(CLI_FORMAT.EMPTY, CLI_HELP.OPTIONS, `${CLI_FORMAT.INDENT}-h, --help Show help.`, `${CLI_FORMAT.INDENT}-v, --version Show version.`, CLI_FORMAT.EMPTY, `Run "${name} ${CLI_COMMANDS.HELP} <${CLI_SYMBOLS.COMMAND}>" for help on one ${CLI_SYMBOLS.COMMAND}.`);
|
|
61
|
+
writer.writeLine(lines.join(CLI_FORMAT.NEWLINE));
|
|
62
|
+
}
|
|
63
|
+
/** Renders a command's name and aliases for the command list. */
|
|
64
|
+
function formatCommandTerm(cmd) {
|
|
65
|
+
return cmd.aliases?.length
|
|
66
|
+
? `${cmd.name} (${cmd.aliases.join(", ")})`
|
|
67
|
+
: cmd.name;
|
|
49
68
|
}
|
|
50
69
|
//# sourceMappingURL=cliApplication.builtins.js.map
|
|
@@ -32,6 +32,14 @@ export declare class ZudojsCLI implements CLIApplication {
|
|
|
32
32
|
use(hooks: CLIHooks): this;
|
|
33
33
|
/** Runs the CLI application. */
|
|
34
34
|
run(args?: CLIArguments): Promise<number>;
|
|
35
|
+
private printApplicationHelp;
|
|
36
|
+
/**
|
|
37
|
+
* Resolves the command named after a help request, if any.
|
|
38
|
+
*
|
|
39
|
+
* Throws when a name is given that is not registered, so `zudojs help
|
|
40
|
+
* bogus` says so instead of silently printing the global help.
|
|
41
|
+
*/
|
|
42
|
+
private resolveHelpTarget;
|
|
35
43
|
private findCommand;
|
|
36
44
|
private getCommandArguments;
|
|
37
45
|
private createContext;
|
|
@@ -4,15 +4,17 @@
|
|
|
4
4
|
* Main CLI application class that orchestrates command registration,
|
|
5
5
|
* parsing, execution, and built-in help/version handling.
|
|
6
6
|
*/
|
|
7
|
-
import { CLI_DEFAULTS, CLI_EXIT_CODES, } from "../cliConstant/cliConstant.value.js";
|
|
7
|
+
import { CLI_DEFAULTS, CLI_EXIT_CODES, CLI_MESSAGES, CLI_OPTION_PREFIXES, } from "../cliConstant/cliConstant.value.js";
|
|
8
8
|
import { CLIExecutionError, normalizeCLIError } from "../cliError/index.js";
|
|
9
9
|
import { CommandNotFoundError } from "../cliError/cliError.command.js";
|
|
10
|
+
import { InvalidArgumentsError } from "../cliError/cliError.argument.js";
|
|
10
11
|
import { CLICommandRegistry } from "../cliCommand/cliCommand.registry.js";
|
|
11
12
|
import { executeCommand } from "../cliCommand/cliCommand.factory.js";
|
|
12
13
|
import { CLIParser, resolveCommand } from "../cliParser/index.js";
|
|
13
14
|
import { createCLIWriter } from "./cliApplication.writer.js";
|
|
14
15
|
import { createCLILogger } from "./cliApplication.logger.js";
|
|
15
|
-
import { isHelpRequest, isVersionRequest, printVersion, printHelp, } from "./cliApplication.builtins.js";
|
|
16
|
+
import { isHelpFlag, isHelpRequest, isVersionRequest, printVersion, printHelp, } from "./cliApplication.builtins.js";
|
|
17
|
+
import { printCommandHelp } from "./cliApplication.help.js";
|
|
16
18
|
/* -------------------------------------------------------------------------- */
|
|
17
19
|
/* Application */
|
|
18
20
|
/* -------------------------------------------------------------------------- */
|
|
@@ -72,7 +74,13 @@ export class ZudojsCLI {
|
|
|
72
74
|
this.running = true;
|
|
73
75
|
try {
|
|
74
76
|
if (isHelpRequest(args)) {
|
|
75
|
-
|
|
77
|
+
// `zudojs help create` and `zudojs --help create` name a command.
|
|
78
|
+
const target = this.resolveHelpTarget(args);
|
|
79
|
+
if (target) {
|
|
80
|
+
printCommandHelp(this.writer, this.name, target);
|
|
81
|
+
return CLI_EXIT_CODES.SUCCESS;
|
|
82
|
+
}
|
|
83
|
+
this.printApplicationHelp();
|
|
76
84
|
return CLI_EXIT_CODES.SUCCESS;
|
|
77
85
|
}
|
|
78
86
|
if (isVersionRequest(args)) {
|
|
@@ -82,12 +90,24 @@ export class ZudojsCLI {
|
|
|
82
90
|
const command = this.findCommand(args);
|
|
83
91
|
if (!command) {
|
|
84
92
|
if (args.length === 0) {
|
|
85
|
-
|
|
93
|
+
this.printApplicationHelp();
|
|
86
94
|
return CLI_EXIT_CODES.SUCCESS;
|
|
87
95
|
}
|
|
88
|
-
|
|
96
|
+
const first = String(args[0]);
|
|
97
|
+
// `findCommand` skips flag-shaped tokens, so a lone `-vh` would
|
|
98
|
+
// otherwise be reported as a command named "-vh".
|
|
99
|
+
if (first.startsWith(CLI_OPTION_PREFIXES.SHORT)) {
|
|
100
|
+
throw new InvalidArgumentsError(CLI_MESSAGES.MISSING_COMMAND);
|
|
101
|
+
}
|
|
102
|
+
throw new CommandNotFoundError(first);
|
|
89
103
|
}
|
|
90
104
|
const commandArgs = this.getCommandArguments(args, command);
|
|
105
|
+
// Checked before parsing: `--help` is not a declared option, so the
|
|
106
|
+
// parser would reject it as invalid and exit 2.
|
|
107
|
+
if (commandArgs.some(isHelpFlag)) {
|
|
108
|
+
printCommandHelp(this.writer, this.name, command);
|
|
109
|
+
return CLI_EXIT_CODES.SUCCESS;
|
|
110
|
+
}
|
|
91
111
|
const commandContext = this.createContext(commandArgs, command);
|
|
92
112
|
if (this.hooks.beforeRun) {
|
|
93
113
|
await this.hooks.beforeRun(commandContext);
|
|
@@ -131,6 +151,26 @@ export class ZudojsCLI {
|
|
|
131
151
|
}
|
|
132
152
|
}
|
|
133
153
|
/* ---- Internal ---- */
|
|
154
|
+
printApplicationHelp() {
|
|
155
|
+
printHelp(this.writer, this.name, this.version, this.description, this.commands.list());
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Resolves the command named after a help request, if any.
|
|
159
|
+
*
|
|
160
|
+
* Throws when a name is given that is not registered, so `zudojs help
|
|
161
|
+
* bogus` says so instead of silently printing the global help.
|
|
162
|
+
*/
|
|
163
|
+
resolveHelpTarget(args) {
|
|
164
|
+
const target = args
|
|
165
|
+
.slice(1)
|
|
166
|
+
.find((arg) => !arg.startsWith(CLI_OPTION_PREFIXES.SHORT));
|
|
167
|
+
if (target === undefined)
|
|
168
|
+
return undefined;
|
|
169
|
+
const command = resolveCommand(this.commands.list(), target);
|
|
170
|
+
if (!command)
|
|
171
|
+
throw new CommandNotFoundError(target);
|
|
172
|
+
return command;
|
|
173
|
+
}
|
|
134
174
|
findCommand(args) {
|
|
135
175
|
for (const arg of args) {
|
|
136
176
|
if (arg.startsWith("-"))
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zudojs-cli — Command Help
|
|
3
|
+
*
|
|
4
|
+
* Renders the help block for one command: its usage line, aliases,
|
|
5
|
+
* positional arguments and options, with shorts and defaults.
|
|
6
|
+
*/
|
|
7
|
+
import type { CLICommand, CLIWriter } from "../cliType/cliType.type.js";
|
|
8
|
+
/**
|
|
9
|
+
* Prints the help block for a single command.
|
|
10
|
+
*
|
|
11
|
+
* Reached by `zudojs <command> --help`, `zudojs <command> -h` and
|
|
12
|
+
* `zudojs help <command>`. The first two used to be handed to the parser,
|
|
13
|
+
* which rejected `--help` as an undeclared option and exited 2, so no
|
|
14
|
+
* command's flags could be discovered from the CLI.
|
|
15
|
+
*/
|
|
16
|
+
export declare function printCommandHelp(writer: CLIWriter, applicationName: string, command: CLICommand): void;
|
|
17
|
+
//# sourceMappingURL=cliApplication.help.d.ts.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zudojs-cli — Command Help
|
|
3
|
+
*
|
|
4
|
+
* Renders the help block for one command: its usage line, aliases,
|
|
5
|
+
* positional arguments and options, with shorts and defaults.
|
|
6
|
+
*/
|
|
7
|
+
import { CLI_FORMAT, CLI_HELP, CLI_OPTION_PREFIXES, CLI_SYMBOLS, } from "../cliConstant/cliConstant.value.js";
|
|
8
|
+
/* -------------------------------------------------------------------------- */
|
|
9
|
+
/* Formatting */
|
|
10
|
+
/* -------------------------------------------------------------------------- */
|
|
11
|
+
/** Renders one positional as it appears in the usage line. */
|
|
12
|
+
function formatArgumentToken(argument) {
|
|
13
|
+
const name = argument.variadic ? `${argument.name}...` : argument.name;
|
|
14
|
+
return argument.required ? `<${name}>` : `[<${name}>]`;
|
|
15
|
+
}
|
|
16
|
+
/** Renders the flag column of one option, e.g. `-t, --type <string>`. */
|
|
17
|
+
function formatOptionFlags(option) {
|
|
18
|
+
const short = option.short
|
|
19
|
+
? `${CLI_OPTION_PREFIXES.SHORT}${option.short}, `
|
|
20
|
+
: "";
|
|
21
|
+
const long = `${CLI_OPTION_PREFIXES.LONG}${option.name}`;
|
|
22
|
+
const placeholder = option.type === "boolean" ? "" : ` <${option.type ?? "string"}>`;
|
|
23
|
+
return `${short}${long}${placeholder}`;
|
|
24
|
+
}
|
|
25
|
+
/** Renders the description column of one option. */
|
|
26
|
+
function formatOptionDescription(option) {
|
|
27
|
+
const parts = [];
|
|
28
|
+
if (option.description)
|
|
29
|
+
parts.push(option.description);
|
|
30
|
+
if (option.defaultValue !== undefined) {
|
|
31
|
+
parts.push(`(default: ${String(option.defaultValue)})`);
|
|
32
|
+
}
|
|
33
|
+
return parts.join(" ");
|
|
34
|
+
}
|
|
35
|
+
/** Renders `<term> <description>` rows on a common column. */
|
|
36
|
+
function formatRows(rows) {
|
|
37
|
+
const width = rows.reduce((max, [term]) => Math.max(max, term.length), 0);
|
|
38
|
+
return rows.map(([term, description]) => description
|
|
39
|
+
? `${CLI_FORMAT.INDENT}${term.padEnd(width)}${CLI_FORMAT.INDENT}${description}`
|
|
40
|
+
: `${CLI_FORMAT.INDENT}${term}`);
|
|
41
|
+
}
|
|
42
|
+
/* -------------------------------------------------------------------------- */
|
|
43
|
+
/* Command Help */
|
|
44
|
+
/* -------------------------------------------------------------------------- */
|
|
45
|
+
/**
|
|
46
|
+
* Prints the help block for a single command.
|
|
47
|
+
*
|
|
48
|
+
* Reached by `zudojs <command> --help`, `zudojs <command> -h` and
|
|
49
|
+
* `zudojs help <command>`. The first two used to be handed to the parser,
|
|
50
|
+
* which rejected `--help` as an undeclared option and exited 2, so no
|
|
51
|
+
* command's flags could be discovered from the CLI.
|
|
52
|
+
*/
|
|
53
|
+
export function printCommandHelp(writer, applicationName, command) {
|
|
54
|
+
const args = command.arguments ?? [];
|
|
55
|
+
const options = command.options ?? [];
|
|
56
|
+
const lines = [];
|
|
57
|
+
const usage = [
|
|
58
|
+
applicationName,
|
|
59
|
+
command.name,
|
|
60
|
+
...args.map(formatArgumentToken),
|
|
61
|
+
`[${CLI_SYMBOLS.OPTION}s]`,
|
|
62
|
+
].join(" ");
|
|
63
|
+
lines.push(CLI_HELP.USAGE, `${CLI_FORMAT.INDENT}${usage}`);
|
|
64
|
+
if (command.description) {
|
|
65
|
+
lines.push(CLI_FORMAT.EMPTY, CLI_HELP.DESCRIPTION, `${CLI_FORMAT.INDENT}${command.description}`);
|
|
66
|
+
}
|
|
67
|
+
if (command.aliases?.length) {
|
|
68
|
+
lines.push(CLI_FORMAT.EMPTY, CLI_HELP.ALIASES, `${CLI_FORMAT.INDENT}${command.aliases.join(", ")}`);
|
|
69
|
+
}
|
|
70
|
+
lines.push(CLI_FORMAT.EMPTY, CLI_HELP.ARGUMENTS);
|
|
71
|
+
if (args.length === 0) {
|
|
72
|
+
lines.push(`${CLI_FORMAT.INDENT}None — this ${CLI_SYMBOLS.COMMAND} takes no positional ${CLI_SYMBOLS.ARGUMENT}s.`);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
lines.push(...formatRows(args.map((argument) => [
|
|
76
|
+
formatArgumentToken(argument),
|
|
77
|
+
[
|
|
78
|
+
argument.description ?? "",
|
|
79
|
+
argument.defaultValue !== undefined
|
|
80
|
+
? `(default: ${String(argument.defaultValue)})`
|
|
81
|
+
: "",
|
|
82
|
+
]
|
|
83
|
+
.filter(Boolean)
|
|
84
|
+
.join(" "),
|
|
85
|
+
])));
|
|
86
|
+
}
|
|
87
|
+
lines.push(CLI_FORMAT.EMPTY, CLI_HELP.OPTIONS);
|
|
88
|
+
lines.push(...formatRows([
|
|
89
|
+
...options.map((option) => [formatOptionFlags(option), formatOptionDescription(option)]),
|
|
90
|
+
["-h, --help", `Show help for this ${CLI_SYMBOLS.COMMAND}.`],
|
|
91
|
+
]));
|
|
92
|
+
writer.writeLine(lines.join(CLI_FORMAT.NEWLINE));
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=cliApplication.help.js.map
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* @module cliApplication/logger
|
|
15
15
|
*/
|
|
16
16
|
import { createLogger, LoggerLevel, } from "@zudojs/logger";
|
|
17
|
+
import { CLI_ENVIRONMENT, CLI_NAME } from "../cliConstant/cliConstant.value.js";
|
|
17
18
|
/** Renders one entry as the single line the user sees. */
|
|
18
19
|
export function formatCLILogLine(entry) {
|
|
19
20
|
switch (entry.levelName) {
|
|
@@ -31,14 +32,13 @@ export function formatCLILogLine(entry) {
|
|
|
31
32
|
}
|
|
32
33
|
/** Creates the logger handed to every command as `context.logger`. */
|
|
33
34
|
export function createCLILogger(options = {}) {
|
|
35
|
+
const debug = process.env[CLI_ENVIRONMENT.DEBUG];
|
|
34
36
|
const verbose = options.verbose ??
|
|
35
|
-
(
|
|
36
|
-
process.env["ZUDOJS_DEBUG"] !== "" &&
|
|
37
|
-
process.env["ZUDOJS_DEBUG"] !== "0");
|
|
37
|
+
(debug !== undefined && debug !== "" && debug !== "0");
|
|
38
38
|
const stdout = options.stdout ?? ((line) => process.stdout.write(`${line}\n`));
|
|
39
39
|
const stderr = options.stderr ?? ((line) => process.stderr.write(`${line}\n`));
|
|
40
40
|
return createLogger({
|
|
41
|
-
name:
|
|
41
|
+
name: CLI_NAME,
|
|
42
42
|
level: verbose ? LoggerLevel.TRACE : LoggerLevel.INFO,
|
|
43
43
|
// Writes must complete before a command returns; `process.exit` follows
|
|
44
44
|
// an error immediately.
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Validation logic and utility functions for CLI commands.
|
|
5
5
|
*/
|
|
6
|
+
import { CLI_LIMITS } from "../cliConstant/cliConstant.value.js";
|
|
6
7
|
import { InvalidCommandNameError, DuplicateCommandError, } from "../cliError/cliError.command.js";
|
|
8
|
+
import { InvalidOptionNameError } from "../cliError/cliError.option.js";
|
|
7
9
|
/* -------------------------------------------------------------------------- */
|
|
8
10
|
/* Validation */
|
|
9
11
|
/* -------------------------------------------------------------------------- */
|
|
@@ -18,10 +20,27 @@ export function validateCommand(command) {
|
|
|
18
20
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9:_-]*$/.test(command.name.trim())) {
|
|
19
21
|
throw new InvalidCommandNameError(command.name);
|
|
20
22
|
}
|
|
23
|
+
if (command.name.trim().length > CLI_LIMITS.MAX_COMMAND_NAME_LENGTH) {
|
|
24
|
+
throw new InvalidCommandNameError(command.name);
|
|
25
|
+
}
|
|
21
26
|
if (command.description !== undefined &&
|
|
22
27
|
typeof command.description !== "string") {
|
|
23
28
|
throw new TypeError(`Description for command "${command.name}" must be a string.`);
|
|
24
29
|
}
|
|
30
|
+
if (command.description !== undefined &&
|
|
31
|
+
command.description.length > CLI_LIMITS.MAX_DESCRIPTION_LENGTH) {
|
|
32
|
+
throw new TypeError(`Description for command "${command.name}" exceeds ${CLI_LIMITS.MAX_DESCRIPTION_LENGTH} characters.`);
|
|
33
|
+
}
|
|
34
|
+
for (const option of command.options ?? []) {
|
|
35
|
+
if (option.name.length > CLI_LIMITS.MAX_OPTION_NAME_LENGTH) {
|
|
36
|
+
throw new InvalidOptionNameError(option.name);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
for (const argument of command.arguments ?? []) {
|
|
40
|
+
if (argument.name.length > CLI_LIMITS.MAX_ARGUMENT_NAME_LENGTH) {
|
|
41
|
+
throw new InvalidCommandNameError(argument.name);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
25
44
|
if (command.aliases) {
|
|
26
45
|
const seen = new Set();
|
|
27
46
|
for (const alias of command.aliases) {
|
|
@@ -29,6 +48,9 @@ export function validateCommand(command) {
|
|
|
29
48
|
throw new InvalidCommandNameError(String(alias));
|
|
30
49
|
}
|
|
31
50
|
const normalized = alias.trim();
|
|
51
|
+
if (normalized.length > CLI_LIMITS.MAX_ALIAS_LENGTH) {
|
|
52
|
+
throw new InvalidCommandNameError(normalized);
|
|
53
|
+
}
|
|
32
54
|
if (normalized === command.name.trim()) {
|
|
33
55
|
throw new DuplicateCommandError(normalized);
|
|
34
56
|
}
|
|
@@ -14,6 +14,14 @@ export declare const CLI_DEFAULTS: {
|
|
|
14
14
|
readonly DEFAULT_CWD: string;
|
|
15
15
|
readonly EXIT_CODE: 0;
|
|
16
16
|
};
|
|
17
|
+
/** Internal symbolic names for CLI elements. */
|
|
18
|
+
export declare const CLI_SYMBOLS: {
|
|
19
|
+
readonly COMMAND: "command";
|
|
20
|
+
readonly OPTION: "option";
|
|
21
|
+
readonly ARGUMENT: "argument";
|
|
22
|
+
readonly HELP: "help";
|
|
23
|
+
readonly VERSION: "version";
|
|
24
|
+
};
|
|
17
25
|
/** Built-in command names. */
|
|
18
26
|
export declare const CLI_COMMANDS: {
|
|
19
27
|
readonly HELP: "help";
|
|
@@ -30,14 +38,6 @@ export declare const CLI_OPTION_PREFIXES: {
|
|
|
30
38
|
readonly LONG: "--";
|
|
31
39
|
readonly VALUE_SEPARATOR: "=";
|
|
32
40
|
};
|
|
33
|
-
/** Internal symbolic names for CLI elements. */
|
|
34
|
-
export declare const CLI_SYMBOLS: {
|
|
35
|
-
readonly COMMAND: "command";
|
|
36
|
-
readonly OPTION: "option";
|
|
37
|
-
readonly ARGUMENT: "argument";
|
|
38
|
-
readonly HELP: "help";
|
|
39
|
-
readonly VERSION: "version";
|
|
40
|
-
};
|
|
41
41
|
/** Standard CLI messages. */
|
|
42
42
|
export declare const CLI_MESSAGES: {
|
|
43
43
|
readonly COMMAND_NOT_FOUND: "Command not found.";
|
|
@@ -45,7 +45,7 @@ export declare const CLI_MESSAGES: {
|
|
|
45
45
|
readonly PERMISSION_DENIED: "Permission denied.";
|
|
46
46
|
readonly INTERRUPTED: "Process interrupted.";
|
|
47
47
|
readonly UNKNOWN_ERROR: "An unexpected error occurred.";
|
|
48
|
-
readonly MISSING_COMMAND: "
|
|
48
|
+
readonly MISSING_COMMAND: "No command given.";
|
|
49
49
|
};
|
|
50
50
|
/** Labels used in help output. */
|
|
51
51
|
export declare const CLI_HELP: {
|
|
@@ -57,11 +57,17 @@ export declare const CLI_HELP: {
|
|
|
57
57
|
readonly DESCRIPTION: "Description:";
|
|
58
58
|
readonly EXAMPLES: "Examples:";
|
|
59
59
|
};
|
|
60
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Environment variable names the CLI reads.
|
|
62
|
+
*
|
|
63
|
+
* Every entry has a single reader: `DEBUG` turns on verbose logging,
|
|
64
|
+
* `CI` and `NO_UPDATE_CHECK`/`NPM_OFFLINE` suppress the update check.
|
|
65
|
+
*/
|
|
61
66
|
export declare const CLI_ENVIRONMENT: {
|
|
62
|
-
readonly
|
|
63
|
-
readonly DEBUG: "DEBUG";
|
|
67
|
+
readonly DEBUG: "ZUDOJS_DEBUG";
|
|
64
68
|
readonly CI: "CI";
|
|
69
|
+
readonly NO_UPDATE_CHECK: "ZUDOJS_NO_UPDATE_CHECK";
|
|
70
|
+
readonly NPM_OFFLINE: "npm_config_offline";
|
|
65
71
|
};
|
|
66
72
|
/** Formatting constants. */
|
|
67
73
|
export declare const CLI_FORMAT: {
|