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.
- package/README.md +35 -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 +15 -6
- 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 +8 -4
- package/dist/src/templates/microservice/microservice.template.js +30 -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 +6 -6
|
@@ -12,8 +12,14 @@ export declare class FrontendAdapterRegistry {
|
|
|
12
12
|
constructor();
|
|
13
13
|
/**
|
|
14
14
|
* Registers a new frontend adapter.
|
|
15
|
+
*
|
|
16
|
+
* A duplicate name used to overwrite the adapter already registered,
|
|
17
|
+
* silently, so the winner depended on registration order. Use
|
|
18
|
+
* {@link replace} to do that on purpose.
|
|
15
19
|
*/
|
|
16
20
|
register(adapter: FrontendAdapter): void;
|
|
21
|
+
/** Registers an adapter, replacing any adapter of the same name. */
|
|
22
|
+
replace(adapter: FrontendAdapter): void;
|
|
17
23
|
/**
|
|
18
24
|
* Gets an adapter by name.
|
|
19
25
|
*/
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module registries/adapter
|
|
5
5
|
*/
|
|
6
|
+
import { CLIValidationError } from "../../errors/index.js";
|
|
6
7
|
import { ReactAdapter } from "../../adapters/frontend/react.adapter.js";
|
|
7
8
|
import { NextAdapter } from "../../adapters/frontend/next.adapter.js";
|
|
8
9
|
import { VueAdapter } from "../../adapters/frontend/vue.adapter.js";
|
|
@@ -34,8 +35,19 @@ export class FrontendAdapterRegistry {
|
|
|
34
35
|
}
|
|
35
36
|
/**
|
|
36
37
|
* Registers a new frontend adapter.
|
|
38
|
+
*
|
|
39
|
+
* A duplicate name used to overwrite the adapter already registered,
|
|
40
|
+
* silently, so the winner depended on registration order. Use
|
|
41
|
+
* {@link replace} to do that on purpose.
|
|
37
42
|
*/
|
|
38
43
|
register(adapter) {
|
|
44
|
+
if (this.adapters.has(adapter.name)) {
|
|
45
|
+
throw new CLIValidationError(`A frontend adapter named "${adapter.name}" is already registered. Use replace() to override it.`);
|
|
46
|
+
}
|
|
47
|
+
this.adapters.set(adapter.name, adapter);
|
|
48
|
+
}
|
|
49
|
+
/** Registers an adapter, replacing any adapter of the same name. */
|
|
50
|
+
replace(adapter) {
|
|
39
51
|
this.adapters.set(adapter.name, adapter);
|
|
40
52
|
}
|
|
41
53
|
/**
|
|
@@ -12,8 +12,13 @@ export declare class PackageManagerRegistry {
|
|
|
12
12
|
constructor();
|
|
13
13
|
/**
|
|
14
14
|
* Registers a new package manager adapter.
|
|
15
|
+
*
|
|
16
|
+
* Registering the same name twice used to overwrite the first adapter
|
|
17
|
+
* without a word; {@link replace} is the explicit way to do that.
|
|
15
18
|
*/
|
|
16
19
|
register(adapter: PackageManager): void;
|
|
20
|
+
/** Registers an adapter, replacing any adapter of the same name. */
|
|
21
|
+
replace(adapter: PackageManager): void;
|
|
17
22
|
/**
|
|
18
23
|
* Gets an adapter by name.
|
|
19
24
|
*/
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module registries/adapter/packageManager
|
|
5
5
|
*/
|
|
6
|
+
import { CLIValidationError } from "../../errors/index.js";
|
|
6
7
|
import { PnpmAdapter } from "../../adapters/package-managers/pnpm.adapter.js";
|
|
7
8
|
import { NpmAdapter } from "../../adapters/package-managers/npm.adapter.js";
|
|
8
9
|
import { YarnAdapter } from "../../adapters/package-managers/yarn.adapter.js";
|
|
@@ -20,8 +21,18 @@ export class PackageManagerRegistry {
|
|
|
20
21
|
}
|
|
21
22
|
/**
|
|
22
23
|
* Registers a new package manager adapter.
|
|
24
|
+
*
|
|
25
|
+
* Registering the same name twice used to overwrite the first adapter
|
|
26
|
+
* without a word; {@link replace} is the explicit way to do that.
|
|
23
27
|
*/
|
|
24
28
|
register(adapter) {
|
|
29
|
+
if (this.adapters.has(adapter.name)) {
|
|
30
|
+
throw new CLIValidationError(`A package manager adapter named "${adapter.name}" is already registered. Use replace() to override it.`);
|
|
31
|
+
}
|
|
32
|
+
this.adapters.set(adapter.name, adapter);
|
|
33
|
+
}
|
|
34
|
+
/** Registers an adapter, replacing any adapter of the same name. */
|
|
35
|
+
replace(adapter) {
|
|
25
36
|
this.adapters.set(adapter.name, adapter);
|
|
26
37
|
}
|
|
27
38
|
/**
|
|
@@ -11,7 +11,16 @@ export interface DependencyRecord {
|
|
|
11
11
|
}
|
|
12
12
|
export declare class DependencyRegistry {
|
|
13
13
|
private readonly dependencies;
|
|
14
|
+
/**
|
|
15
|
+
* Records a dependency.
|
|
16
|
+
*
|
|
17
|
+
* A second record for the same package used to overwrite the first one
|
|
18
|
+
* silently, which hid version disagreements between two callers. Use
|
|
19
|
+
* {@link replace} when overwriting is the intent.
|
|
20
|
+
*/
|
|
14
21
|
add(record: DependencyRecord): void;
|
|
22
|
+
/** Records a dependency, replacing any record of the same name. */
|
|
23
|
+
replace(record: DependencyRecord): void;
|
|
15
24
|
addMany(records: readonly DependencyRecord[]): void;
|
|
16
25
|
get(name: string): DependencyRecord | undefined;
|
|
17
26
|
getAll(): readonly DependencyRecord[];
|
|
@@ -3,9 +3,25 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Registry for tracking and managing project dependencies.
|
|
5
5
|
*/
|
|
6
|
+
import { CLIValidationError } from "../../errors/index.js";
|
|
6
7
|
export class DependencyRegistry {
|
|
7
8
|
dependencies = new Map();
|
|
9
|
+
/**
|
|
10
|
+
* Records a dependency.
|
|
11
|
+
*
|
|
12
|
+
* A second record for the same package used to overwrite the first one
|
|
13
|
+
* silently, which hid version disagreements between two callers. Use
|
|
14
|
+
* {@link replace} when overwriting is the intent.
|
|
15
|
+
*/
|
|
8
16
|
add(record) {
|
|
17
|
+
const existing = this.dependencies.get(record.name);
|
|
18
|
+
if (existing !== undefined) {
|
|
19
|
+
throw new CLIValidationError(`Dependency "${record.name}" is already registered as ${existing.version} (${existing.source}). Use replace() to override it.`);
|
|
20
|
+
}
|
|
21
|
+
this.dependencies.set(record.name, record);
|
|
22
|
+
}
|
|
23
|
+
/** Records a dependency, replacing any record of the same name. */
|
|
24
|
+
replace(record) {
|
|
9
25
|
this.dependencies.set(record.name, record);
|
|
10
26
|
}
|
|
11
27
|
addMany(records) {
|
|
@@ -11,7 +11,15 @@ export interface GeneratorRegistryEntry {
|
|
|
11
11
|
export declare class GeneratorRegistry {
|
|
12
12
|
private readonly generators;
|
|
13
13
|
constructor();
|
|
14
|
+
/**
|
|
15
|
+
* Registers a generator.
|
|
16
|
+
*
|
|
17
|
+
* A duplicate name used to replace the previous entry silently, so which
|
|
18
|
+
* generator ran depended on registration order.
|
|
19
|
+
*/
|
|
14
20
|
register(entry: GeneratorRegistryEntry): void;
|
|
21
|
+
/** Registers a generator, replacing any entry of the same name. */
|
|
22
|
+
replace(entry: GeneratorRegistryEntry): void;
|
|
15
23
|
get(name: string): GeneratorRegistryEntry | undefined;
|
|
16
24
|
getByCapability(capability: string): GeneratorRegistryEntry[];
|
|
17
25
|
getNames(): readonly string[];
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Registry for project generators with capability-based lookup.
|
|
5
5
|
*/
|
|
6
|
+
import { CLIValidationError } from "../../errors/index.js";
|
|
6
7
|
import { BackendGenerator } from "../../generators/backend/backend.generator.js";
|
|
7
8
|
import { FrontendGenerator } from "../../generators/frontend/frontendGenerator.core.js";
|
|
8
9
|
import { FullstackComposer } from "../../generators/fullstack/fullstackComposer.core.js";
|
|
@@ -49,7 +50,20 @@ export class GeneratorRegistry {
|
|
|
49
50
|
capabilities: ["docker", "database", "microservice"],
|
|
50
51
|
});
|
|
51
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Registers a generator.
|
|
55
|
+
*
|
|
56
|
+
* A duplicate name used to replace the previous entry silently, so which
|
|
57
|
+
* generator ran depended on registration order.
|
|
58
|
+
*/
|
|
52
59
|
register(entry) {
|
|
60
|
+
if (this.generators.has(entry.name)) {
|
|
61
|
+
throw new CLIValidationError(`A generator named "${entry.name}" is already registered. Use replace() to override it.`);
|
|
62
|
+
}
|
|
63
|
+
this.generators.set(entry.name, entry);
|
|
64
|
+
}
|
|
65
|
+
/** Registers a generator, replacing any entry of the same name. */
|
|
66
|
+
replace(entry) {
|
|
53
67
|
this.generators.set(entry.name, entry);
|
|
54
68
|
}
|
|
55
69
|
get(name) {
|
|
@@ -9,8 +9,8 @@ export interface CapabilityDependency {
|
|
|
9
9
|
}
|
|
10
10
|
export interface CapabilityResolutionResult {
|
|
11
11
|
readonly capabilities: readonly string[];
|
|
12
|
+
/** Framework packages the selected capabilities build on. */
|
|
12
13
|
readonly dependencies: readonly string[];
|
|
13
|
-
readonly conflicts: readonly string[];
|
|
14
14
|
}
|
|
15
15
|
export declare class CapabilityResolver {
|
|
16
16
|
private readonly dependencyGraph;
|
|
@@ -37,7 +37,6 @@ export class CapabilityResolver {
|
|
|
37
37
|
resolve(capabilities) {
|
|
38
38
|
const resolved = new Set();
|
|
39
39
|
const dependencies = new Set();
|
|
40
|
-
const conflicts = [];
|
|
41
40
|
for (const capability of capabilities) {
|
|
42
41
|
if (resolved.has(capability))
|
|
43
42
|
continue;
|
|
@@ -52,7 +51,6 @@ export class CapabilityResolver {
|
|
|
52
51
|
return {
|
|
53
52
|
capabilities: Array.from(resolved),
|
|
54
53
|
dependencies: Array.from(dependencies),
|
|
55
|
-
conflicts,
|
|
56
54
|
};
|
|
57
55
|
}
|
|
58
56
|
getCapabilities() {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*
|
|
20
20
|
* @module resolvers/layout
|
|
21
21
|
*/
|
|
22
|
+
import { PACKAGE_MANAGERS } from "../../constants/index.js";
|
|
22
23
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
23
24
|
import { join } from "node:path";
|
|
24
25
|
import { SAFE_PATH_SEGMENT } from "../../utils/utils.name.js";
|
|
@@ -27,12 +28,6 @@ const PROJECT_TYPES = [
|
|
|
27
28
|
"frontend",
|
|
28
29
|
"fullstack",
|
|
29
30
|
];
|
|
30
|
-
const PACKAGE_MANAGERS = [
|
|
31
|
-
"pnpm",
|
|
32
|
-
"npm",
|
|
33
|
-
"yarn",
|
|
34
|
-
"bun",
|
|
35
|
-
];
|
|
36
31
|
function readJson(path) {
|
|
37
32
|
try {
|
|
38
33
|
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Walks upward from `startDir` to the nearest directory that
|
|
3
|
-
* project
|
|
4
|
-
* `zudojs.config.ts`, or a `
|
|
2
|
+
* Walks upward from `startDir` to the nearest directory that really is a
|
|
3
|
+
* Zudojs project — one `resolveProjectLayout` recognizes: a
|
|
4
|
+
* `.zudojs/manifest.json`, a legacy `zudojs.config.ts`/`.js`, or a `zudojs`
|
|
5
|
+
* block in `package.json`.
|
|
6
|
+
*
|
|
7
|
+
* A bare `package.json` used to be accepted too, which made this a
|
|
8
|
+
* "any JavaScript project" detector rather than a Zudojs one: run from a
|
|
9
|
+
* directory with no Zudojs project anywhere, `zudojs build` climbed to an
|
|
10
|
+
* unrelated ancestor `package.json` and ran its `scripts.build`.
|
|
5
11
|
*/
|
|
6
12
|
export declare function findProjectRoot(startDir?: string): string | null;
|
|
7
13
|
export declare function resolveProjectPath(cwd: string, name: string): string;
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
1
|
import { dirname, join } from "node:path";
|
|
2
|
+
import { resolveProjectLayout } from "./layout/projectLayout.core.js";
|
|
3
3
|
/**
|
|
4
|
-
* Walks upward from `startDir` to the nearest directory that
|
|
5
|
-
* project
|
|
6
|
-
* `zudojs.config.ts`, or a `
|
|
4
|
+
* Walks upward from `startDir` to the nearest directory that really is a
|
|
5
|
+
* Zudojs project — one `resolveProjectLayout` recognizes: a
|
|
6
|
+
* `.zudojs/manifest.json`, a legacy `zudojs.config.ts`/`.js`, or a `zudojs`
|
|
7
|
+
* block in `package.json`.
|
|
8
|
+
*
|
|
9
|
+
* A bare `package.json` used to be accepted too, which made this a
|
|
10
|
+
* "any JavaScript project" detector rather than a Zudojs one: run from a
|
|
11
|
+
* directory with no Zudojs project anywhere, `zudojs build` climbed to an
|
|
12
|
+
* unrelated ancestor `package.json` and ran its `scripts.build`.
|
|
7
13
|
*/
|
|
8
14
|
export function findProjectRoot(startDir = process.cwd()) {
|
|
9
15
|
let dir = startDir;
|
|
10
16
|
while (true) {
|
|
11
|
-
if (
|
|
12
|
-
existsSync(join(dir, "zudojs.config.ts")) ||
|
|
13
|
-
existsSync(join(dir, "package.json"))) {
|
|
17
|
+
if (resolveProjectLayout(dir) !== null) {
|
|
14
18
|
return dir;
|
|
15
19
|
}
|
|
16
20
|
const parent = dirname(dir);
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Rollback system barrel exports.
|
|
5
5
|
*/
|
|
6
|
-
export { RollbackManager, type RollbackEntry } from "./rollbackManager.core.js";
|
|
6
|
+
export { RollbackManager, type RollbackEntry, type RollbackFailure, type RollbackResult, } from "./rollbackManager.core.js";
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -8,11 +8,29 @@ export interface RollbackEntry {
|
|
|
8
8
|
readonly path: string;
|
|
9
9
|
readonly timestamp: number;
|
|
10
10
|
}
|
|
11
|
+
/** One entry that could not be removed, with the reason. */
|
|
12
|
+
export interface RollbackFailure {
|
|
13
|
+
readonly path: string;
|
|
14
|
+
readonly reason: string;
|
|
15
|
+
}
|
|
16
|
+
/** What a rollback actually managed to undo. */
|
|
17
|
+
export interface RollbackResult {
|
|
18
|
+
readonly removed: readonly string[];
|
|
19
|
+
readonly failures: readonly RollbackFailure[];
|
|
20
|
+
}
|
|
11
21
|
export declare class RollbackManager {
|
|
12
22
|
private readonly entries;
|
|
13
23
|
trackFile(path: string): void;
|
|
14
24
|
trackDirectory(path: string): void;
|
|
15
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Removes every tracked path, newest first.
|
|
27
|
+
*
|
|
28
|
+
* Failures used to be swallowed and the entry list cleared regardless, so
|
|
29
|
+
* a half-created project stayed on disk with nothing left to retry and
|
|
30
|
+
* nothing said about it. Entries that could not be removed are kept, and
|
|
31
|
+
* the caller is handed the paths so it can name them.
|
|
32
|
+
*/
|
|
33
|
+
rollback(): Promise<RollbackResult>;
|
|
16
34
|
get entriesCount(): number;
|
|
17
35
|
clear(): void;
|
|
18
36
|
}
|
|
@@ -21,21 +21,40 @@ export class RollbackManager {
|
|
|
21
21
|
timestamp: Date.now(),
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Removes every tracked path, newest first.
|
|
26
|
+
*
|
|
27
|
+
* Failures used to be swallowed and the entry list cleared regardless, so
|
|
28
|
+
* a half-created project stayed on disk with nothing left to retry and
|
|
29
|
+
* nothing said about it. Entries that could not be removed are kept, and
|
|
30
|
+
* the caller is handed the paths so it can name them.
|
|
31
|
+
*/
|
|
24
32
|
async rollback() {
|
|
33
|
+
const removed = [];
|
|
34
|
+
const failures = [];
|
|
35
|
+
const remaining = [];
|
|
25
36
|
for (const entry of [...this.entries].reverse()) {
|
|
26
37
|
try {
|
|
27
38
|
if (entry.type === "file" && existsSync(entry.path)) {
|
|
28
39
|
await rm(entry.path);
|
|
40
|
+
removed.push(entry.path);
|
|
29
41
|
}
|
|
30
42
|
else if (entry.type === "directory" && existsSync(entry.path)) {
|
|
31
43
|
await rm(entry.path, { recursive: true, force: true });
|
|
44
|
+
removed.push(entry.path);
|
|
32
45
|
}
|
|
33
46
|
}
|
|
34
|
-
catch {
|
|
35
|
-
|
|
47
|
+
catch (error) {
|
|
48
|
+
failures.push({
|
|
49
|
+
path: entry.path,
|
|
50
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
51
|
+
});
|
|
52
|
+
remaining.push(entry);
|
|
36
53
|
}
|
|
37
54
|
}
|
|
38
55
|
this.entries.length = 0;
|
|
56
|
+
this.entries.push(...remaining);
|
|
57
|
+
return { removed, failures };
|
|
39
58
|
}
|
|
40
59
|
get entriesCount() {
|
|
41
60
|
return this.entries.length;
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Runners for package manager operations.
|
|
5
5
|
*/
|
|
6
|
-
export { PackageManagerRunner, type PackageManagerRunOptions, } from "./packageManagerRunner.core.js";
|
|
6
|
+
export { PackageManagerRunner, assertPackageManager, type PackageManagerRunOptions, } from "./packageManagerRunner.core.js";
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -3,13 +3,25 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Runner for package manager operations (install, add, remove, run).
|
|
5
5
|
*/
|
|
6
|
+
import type { PackageManager } from "../../types/index.js";
|
|
6
7
|
export interface PackageManagerRunOptions {
|
|
7
8
|
readonly cwd: string;
|
|
8
9
|
readonly args: readonly string[];
|
|
9
10
|
}
|
|
11
|
+
/** The package managers this runner will put in the executable position. */
|
|
12
|
+
/**
|
|
13
|
+
* Narrows an untrusted value to a supported package manager.
|
|
14
|
+
*
|
|
15
|
+
* The constructor argument ends up as the executable name of a spawned
|
|
16
|
+
* process, so it is checked at runtime as well as at compile time: this is
|
|
17
|
+
* public API and a JavaScript caller gets no type checking at all.
|
|
18
|
+
*
|
|
19
|
+
* @throws {CLIValidationError} If the value is not a known package manager.
|
|
20
|
+
*/
|
|
21
|
+
export declare function assertPackageManager(value: string): PackageManager;
|
|
10
22
|
export declare class PackageManagerRunner {
|
|
11
23
|
private readonly manager;
|
|
12
|
-
constructor(manager:
|
|
24
|
+
constructor(manager: PackageManager);
|
|
13
25
|
install(cwd: string): Promise<void>;
|
|
14
26
|
add(packages: readonly string[], options: PackageManagerRunOptions): Promise<void>;
|
|
15
27
|
addDev(packages: readonly string[], options: PackageManagerRunOptions): Promise<void>;
|
|
@@ -3,11 +3,29 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Runner for package manager operations (install, add, remove, run).
|
|
5
5
|
*/
|
|
6
|
+
import { PACKAGE_MANAGERS } from "../../constants/index.js";
|
|
7
|
+
import { CLIValidationError } from "../../errors/index.js";
|
|
6
8
|
import { execCommand } from "../../utils/utils.exec.js";
|
|
9
|
+
/** The package managers this runner will put in the executable position. */
|
|
10
|
+
/**
|
|
11
|
+
* Narrows an untrusted value to a supported package manager.
|
|
12
|
+
*
|
|
13
|
+
* The constructor argument ends up as the executable name of a spawned
|
|
14
|
+
* process, so it is checked at runtime as well as at compile time: this is
|
|
15
|
+
* public API and a JavaScript caller gets no type checking at all.
|
|
16
|
+
*
|
|
17
|
+
* @throws {CLIValidationError} If the value is not a known package manager.
|
|
18
|
+
*/
|
|
19
|
+
export function assertPackageManager(value) {
|
|
20
|
+
if (!PACKAGE_MANAGERS.includes(value)) {
|
|
21
|
+
throw new CLIValidationError(`Unsupported package manager: "${value}". Expected one of ${PACKAGE_MANAGERS.join(", ")}.`);
|
|
22
|
+
}
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
7
25
|
export class PackageManagerRunner {
|
|
8
26
|
manager;
|
|
9
27
|
constructor(manager) {
|
|
10
|
-
this.manager = manager;
|
|
28
|
+
this.manager = assertPackageManager(manager);
|
|
11
29
|
}
|
|
12
30
|
async install(cwd) {
|
|
13
31
|
const args = this.manager === "pnpm"
|
|
@@ -18,6 +18,17 @@ export declare class ProcessRunner {
|
|
|
18
18
|
stderr: string;
|
|
19
19
|
exitCode: number;
|
|
20
20
|
}>;
|
|
21
|
+
/**
|
|
22
|
+
* Starts a process and resolves once it has actually spawned.
|
|
23
|
+
*
|
|
24
|
+
* A raw `spawn` with no `'error'` listener turns an ENOENT into an
|
|
25
|
+
* unhandled `'error'` event, which terminates the CLI itself — and the
|
|
26
|
+
* old implementation had already returned `{ pid: 0 }`, so no caller
|
|
27
|
+
* could catch it. The failure is now a rejection, and the command goes
|
|
28
|
+
* through {@link resolveSpawnTarget} so Windows `.cmd` shims work.
|
|
29
|
+
*
|
|
30
|
+
* @throws {Error} If the process cannot be started.
|
|
31
|
+
*/
|
|
21
32
|
runBackground(command: string, args: readonly string[], options: ProcessOptions): Promise<{
|
|
22
33
|
pid: number;
|
|
23
34
|
}>;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Runner for executing system processes.
|
|
5
5
|
*/
|
|
6
|
-
import { execCommand } from "../../utils/utils.exec.js";
|
|
6
|
+
import { execCommand, resolveChildEnv, resolveSpawnTarget, } from "../../utils/utils.exec.js";
|
|
7
7
|
export class ProcessRunner {
|
|
8
8
|
async run(command, args, options) {
|
|
9
9
|
try {
|
|
@@ -29,14 +29,49 @@ export class ProcessRunner {
|
|
|
29
29
|
return { stdout, stderr, exitCode };
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Starts a process and resolves once it has actually spawned.
|
|
34
|
+
*
|
|
35
|
+
* A raw `spawn` with no `'error'` listener turns an ENOENT into an
|
|
36
|
+
* unhandled `'error'` event, which terminates the CLI itself — and the
|
|
37
|
+
* old implementation had already returned `{ pid: 0 }`, so no caller
|
|
38
|
+
* could catch it. The failure is now a rejection, and the command goes
|
|
39
|
+
* through {@link resolveSpawnTarget} so Windows `.cmd` shims work.
|
|
40
|
+
*
|
|
41
|
+
* @throws {Error} If the process cannot be started.
|
|
42
|
+
*/
|
|
32
43
|
async runBackground(command, args, options) {
|
|
33
44
|
const { spawn } = await import("node:child_process");
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
45
|
+
const target = resolveSpawnTarget(command, Array.from(args));
|
|
46
|
+
const env = resolveChildEnv(target.shell, {
|
|
47
|
+
...process.env,
|
|
48
|
+
...options.env,
|
|
49
|
+
});
|
|
50
|
+
return await new Promise((resolve, reject) => {
|
|
51
|
+
const child = spawn(target.file, target.args, {
|
|
52
|
+
cwd: options.cwd,
|
|
53
|
+
env,
|
|
54
|
+
stdio: options.stdio ?? "ignore",
|
|
55
|
+
shell: target.shell,
|
|
56
|
+
});
|
|
57
|
+
let settled = false;
|
|
58
|
+
child.on("error", (error) => {
|
|
59
|
+
if (settled)
|
|
60
|
+
return;
|
|
61
|
+
settled = true;
|
|
62
|
+
if (error.code === "ENOENT") {
|
|
63
|
+
reject(new Error(`Command "${command}" was not found. Install it and make sure it is on your PATH.`));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
reject(error);
|
|
67
|
+
});
|
|
68
|
+
child.on("spawn", () => {
|
|
69
|
+
if (settled)
|
|
70
|
+
return;
|
|
71
|
+
settled = true;
|
|
72
|
+
resolve({ pid: child.pid ?? 0 });
|
|
73
|
+
});
|
|
38
74
|
});
|
|
39
|
-
return { pid: child.pid ?? 0 };
|
|
40
75
|
}
|
|
41
76
|
}
|
|
42
77
|
//# sourceMappingURL=processRunner.core.js.map
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Runners for executing development tasks.
|
|
5
5
|
*/
|
|
6
|
-
export { TaskRunner, type TaskDefinition, type TaskResult, } from "./taskRunner.core.js";
|
|
6
|
+
export { TaskRunner, type TaskDefinition, type TaskResult, type TaskRunOptions, } from "./taskRunner.core.js";
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -8,16 +8,37 @@ export interface TaskDefinition {
|
|
|
8
8
|
readonly command: string;
|
|
9
9
|
readonly args: readonly string[];
|
|
10
10
|
readonly cwd: string;
|
|
11
|
+
/**
|
|
12
|
+
* When true, a failure of this task cancels the other tasks of the same
|
|
13
|
+
* {@link TaskRunner.runParallel} batch. Defaults to false.
|
|
14
|
+
*/
|
|
11
15
|
readonly required?: boolean;
|
|
12
16
|
}
|
|
13
17
|
export interface TaskResult {
|
|
14
18
|
readonly task: string;
|
|
15
19
|
readonly success: boolean;
|
|
20
|
+
/** The child's exit code, or -1 when it never exited normally. */
|
|
16
21
|
readonly exitCode: number;
|
|
22
|
+
/** The child's stdout. */
|
|
17
23
|
readonly output: string;
|
|
24
|
+
/** The child's stderr, or the reason it could not be run. */
|
|
25
|
+
readonly stderr: string;
|
|
26
|
+
/** True when the task was cancelled because a required task failed. */
|
|
27
|
+
readonly cancelled: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface TaskRunOptions {
|
|
30
|
+
/** Aborting the signal terminates the task's child process. */
|
|
31
|
+
readonly signal?: AbortSignal;
|
|
18
32
|
}
|
|
19
33
|
export declare class TaskRunner {
|
|
20
|
-
run(task: TaskDefinition): Promise<TaskResult>;
|
|
34
|
+
run(task: TaskDefinition, options?: TaskRunOptions): Promise<TaskResult>;
|
|
35
|
+
/**
|
|
36
|
+
* Runs every task concurrently.
|
|
37
|
+
*
|
|
38
|
+
* A task marked `required` that fails aborts the tasks still running:
|
|
39
|
+
* there is no point building the rest of a project once a prerequisite
|
|
40
|
+
* step has failed. Those tasks come back with `cancelled: true`.
|
|
41
|
+
*/
|
|
21
42
|
runParallel(tasks: readonly TaskDefinition[]): Promise<readonly TaskResult[]>;
|
|
22
43
|
}
|
|
23
44
|
//# sourceMappingURL=taskRunner.core.d.ts.map
|
|
@@ -4,29 +4,58 @@
|
|
|
4
4
|
* Runner for executing development tasks (dev servers, builds, tests).
|
|
5
5
|
*/
|
|
6
6
|
import { execCommand } from "../../utils/utils.exec.js";
|
|
7
|
+
/** Exit code reported for a child that never exited with a status. */
|
|
8
|
+
const NO_EXIT_CODE = -1;
|
|
9
|
+
function readString(source, key) {
|
|
10
|
+
const value = source?.[key];
|
|
11
|
+
return typeof value === "string" ? value : "";
|
|
12
|
+
}
|
|
7
13
|
export class TaskRunner {
|
|
8
|
-
async run(task) {
|
|
14
|
+
async run(task, options = {}) {
|
|
9
15
|
try {
|
|
10
|
-
await execCommand(task.command, Array.from(task.args), task.cwd);
|
|
16
|
+
const result = await execCommand(task.command, Array.from(task.args), task.cwd, { ...(options.signal ? { signal: options.signal } : {}) });
|
|
11
17
|
return {
|
|
12
18
|
task: task.name,
|
|
13
19
|
success: true,
|
|
14
20
|
exitCode: 0,
|
|
15
|
-
output:
|
|
21
|
+
output: result.stdout,
|
|
22
|
+
stderr: result.stderr,
|
|
23
|
+
cancelled: false,
|
|
16
24
|
};
|
|
17
25
|
}
|
|
18
26
|
catch (error) {
|
|
19
27
|
const message = error instanceof Error ? error.message : String(error);
|
|
28
|
+
const cancelled = error instanceof Error &&
|
|
29
|
+
error.name === "AbortError" &&
|
|
30
|
+
options.signal?.aborted === true;
|
|
31
|
+
const code = error.code;
|
|
32
|
+
const stderr = readString(error, "stderr");
|
|
20
33
|
return {
|
|
21
34
|
task: task.name,
|
|
22
35
|
success: false,
|
|
23
|
-
exitCode:
|
|
24
|
-
output:
|
|
36
|
+
exitCode: typeof code === "number" ? code : NO_EXIT_CODE,
|
|
37
|
+
output: readString(error, "stdout"),
|
|
38
|
+
stderr: stderr !== "" ? stderr : message,
|
|
39
|
+
cancelled,
|
|
25
40
|
};
|
|
26
41
|
}
|
|
27
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Runs every task concurrently.
|
|
45
|
+
*
|
|
46
|
+
* A task marked `required` that fails aborts the tasks still running:
|
|
47
|
+
* there is no point building the rest of a project once a prerequisite
|
|
48
|
+
* step has failed. Those tasks come back with `cancelled: true`.
|
|
49
|
+
*/
|
|
28
50
|
async runParallel(tasks) {
|
|
29
|
-
const
|
|
51
|
+
const controller = new AbortController();
|
|
52
|
+
const results = await Promise.all(tasks.map(async (task) => {
|
|
53
|
+
const result = await this.run(task, { signal: controller.signal });
|
|
54
|
+
if (!result.success && task.required === true) {
|
|
55
|
+
controller.abort();
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}));
|
|
30
59
|
return results;
|
|
31
60
|
}
|
|
32
61
|
}
|
|
@@ -4,12 +4,15 @@
|
|
|
4
4
|
* Invokes official framework scaffolders to create projects.
|
|
5
5
|
*/
|
|
6
6
|
import { execCommand } from "../utils/utils.exec.js";
|
|
7
|
+
import { SCAFFOLD_TIMEOUT_MS } from "./scaffolder.helper.js";
|
|
7
8
|
export class FrameworkScaffolder {
|
|
8
9
|
async scaffold(targetPath) {
|
|
9
10
|
try {
|
|
10
11
|
await execCommand(this.command, this.args, targetPath, {
|
|
11
12
|
// Suppress interactive prompts from create-* tools.
|
|
12
13
|
env: { ...process.env, CI: "1" },
|
|
14
|
+
// Registry downloads outrun the default 2-minute probe timeout.
|
|
15
|
+
timeout: SCAFFOLD_TIMEOUT_MS,
|
|
13
16
|
});
|
|
14
17
|
return { success: true, path: targetPath };
|
|
15
18
|
}
|