zudojs-cli 1.1.1 → 1.2.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 +27 -0
- package/dist/src/adapters/databases/databaseAdapter.resolver.d.ts +21 -0
- package/dist/src/adapters/databases/databaseAdapter.resolver.js +40 -0
- package/dist/src/adapters/databases/index.d.ts +1 -0
- package/dist/src/adapters/databases/index.js +1 -0
- package/dist/src/adapters/databases/mysql.adapter.d.ts +1 -1
- package/dist/src/adapters/databases/mysql.adapter.js +2 -2
- package/dist/src/adapters/databases/postgres.adapter.d.ts +3 -2
- package/dist/src/adapters/databases/postgres.adapter.js +2 -2
- package/dist/src/adapters/databases/sqlite.adapter.d.ts +1 -1
- package/dist/src/adapters/databases/sqlite.adapter.js +2 -2
- package/dist/src/bin/zudojs.js +7 -1
- package/dist/src/commands/add.command.js +5 -1
- package/dist/src/commands/create.command.js +21 -7
- package/dist/src/commands/generate.command.js +37 -6
- package/dist/src/errors/index.d.ts +3 -14
- package/dist/src/errors/index.js +3 -24
- package/dist/src/generators/frontend/frontendPipeline.js +7 -3
- package/dist/src/generators/fullstack/fullstackBackend.layout.d.ts +26 -0
- package/dist/src/generators/fullstack/fullstackBackend.layout.js +34 -0
- package/dist/src/generators/fullstack/fullstackComposer.core.js +5 -1
- package/dist/src/generators/fullstack/index.d.ts +1 -0
- package/dist/src/generators/fullstack/index.js +1 -0
- package/dist/src/generators/infrastructure/infrastructure.generator.d.ts +5 -10
- package/dist/src/generators/infrastructure/infrastructure.generator.js +41 -75
- package/dist/src/generators/integration/integrationGenerator.core.js +8 -2
- package/dist/src/generators/module/index.d.ts +6 -0
- package/dist/src/generators/module/index.js +6 -0
- package/dist/src/generators/module/module.generator.d.ts +6 -1
- package/dist/src/generators/module/module.generator.js +15 -16
- package/dist/src/generators/module/module.registration.d.ts +23 -0
- package/dist/src/generators/module/module.registration.js +50 -0
- package/dist/src/resolvers/dependency/dependencyResolver.core.js +6 -1
- package/dist/src/resolvers/dependency/dependencyVersions.constant.d.ts +12 -0
- package/dist/src/resolvers/dependency/dependencyVersions.constant.js +40 -0
- package/dist/src/resolvers/dependency/index.d.ts +1 -0
- package/dist/src/resolvers/dependency/index.js +1 -0
- package/dist/src/templates/microservice/microservice.template.js +14 -42
- package/dist/src/templates/modular-monolith/modularMonolith.template.js +2 -1
- package/dist/src/templates/monolith/monolith.template.js +5 -2
- package/dist/src/templates/shared/appRuntime.template.d.ts +0 -5
- package/dist/src/templates/shared/appRuntime.template.js +3 -40
- package/dist/src/templates/shared/dockerfile.template.d.ts +28 -0
- package/dist/src/templates/shared/dockerfile.template.js +69 -0
- package/dist/src/templates/shared/index.d.ts +2 -0
- package/dist/src/templates/shared/index.js +2 -0
- package/dist/src/templates/shared/server.template.d.ts +31 -0
- package/dist/src/templates/shared/server.template.js +96 -0
- package/dist/src/utils/index.d.ts +1 -0
- package/dist/src/utils/index.js +1 -0
- package/dist/src/utils/utils.fileSystem.js +6 -0
- package/dist/src/utils/utils.writeGuard.d.ts +25 -0
- package/dist/src/utils/utils.writeGuard.js +46 -0
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Command-line interface for scaffolding, generating, and managing Zudojs framework projects.
|
|
4
4
|
|
|
5
|
+
<!-- zudo-docs:start -->
|
|
6
|
+
|
|
7
|
+
**Documentation:** [zudojs.oyinlola.site/docs/packages-cli](https://zudojs.oyinlola.site/docs/packages-cli) · **For AI agents:** [Markdown version](https://zudojs.oyinlola.site/docs/packages-cli.md), [llms.txt](https://zudojs.oyinlola.site/llms.txt)
|
|
8
|
+
|
|
9
|
+
<!-- zudo-docs:end -->
|
|
10
|
+
|
|
5
11
|
## Installation
|
|
6
12
|
|
|
7
13
|
### First time
|
|
@@ -75,6 +81,27 @@ block of `package.json`. Every follow-up command reads those; no
|
|
|
75
81
|
ranges, and projects created for pnpm carry a `pnpm-workspace.yaml` that
|
|
76
82
|
allows esbuild's build script, which pnpm 10+ would otherwise refuse to run.
|
|
77
83
|
|
|
84
|
+
- **A running server.** `src/server.ts` starts the runtime and serves HTTP
|
|
85
|
+
with `@zudojs/http` on `PORT` (default 3000; each microservice app on its
|
|
86
|
+
own port), answering `GET /health`. SIGINT/SIGTERM stop the HTTP server,
|
|
87
|
+
then the runtime.
|
|
88
|
+
- **Databases:** `--database postgresql|mysql|sqlite` sets `DATABASE_URL` in
|
|
89
|
+
`.env.example` and the database container. `mongodb` is not supported.
|
|
90
|
+
- **Language:** backend code is TypeScript. `--language javascript` is
|
|
91
|
+
rejected for `--type backend`; in a fullstack project it applies to the
|
|
92
|
+
frontend only.
|
|
93
|
+
- **Fullstack + microservice:** the gateway and services are written at
|
|
94
|
+
`apps/gateway` and `apps/services/<name>`, next to `apps/web`, and are part
|
|
95
|
+
of the root workspace.
|
|
96
|
+
|
|
97
|
+
### Generating code
|
|
98
|
+
|
|
99
|
+
`zudojs generate module <name>` writes a runtime module (a `BaseModule`
|
|
100
|
+
subclass), exports it from the modules barrel and registers it in `app.ts`.
|
|
101
|
+
`zudojs generate` refuses to overwrite a file that already exists and would
|
|
102
|
+
change, and lists those files; pass `--force` to overwrite them. Barrels are
|
|
103
|
+
only appended to. `--dry-run` lists the files without writing anything.
|
|
104
|
+
|
|
78
105
|
## Commands
|
|
79
106
|
|
|
80
107
|
| Command | Description |
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zudojs-cli — Database adapter lookup.
|
|
3
|
+
*
|
|
4
|
+
* `--database mysql|sqlite` used to be accepted and then ignored: every
|
|
5
|
+
* template hard-coded a PostgreSQL `DATABASE_URL`, and the MySQL/SQLite
|
|
6
|
+
* adapters were exported but never called. Templates and the
|
|
7
|
+
* InfrastructureGenerator resolve the adapter here instead. `mongodb` has
|
|
8
|
+
* no adapter, so it is rejected rather than silently turned into Postgres.
|
|
9
|
+
*/
|
|
10
|
+
import type { DatabaseAdapter } from "./postgres.adapter.js";
|
|
11
|
+
/** Database engines the CLI can scaffold. */
|
|
12
|
+
export declare const SUPPORTED_DATABASES: readonly ["postgresql", "mysql", "sqlite"];
|
|
13
|
+
/**
|
|
14
|
+
* Returns the adapter for a database engine.
|
|
15
|
+
*
|
|
16
|
+
* @throws {CLIValidationError} For an engine with no adapter (e.g. `mongodb`).
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveDatabaseAdapter(database?: string): DatabaseAdapter;
|
|
19
|
+
/** Renders the adapter's environment variables as `.env` lines. */
|
|
20
|
+
export declare function renderDatabaseEnv(database: string | undefined, dbName: string): string;
|
|
21
|
+
//# sourceMappingURL=databaseAdapter.resolver.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zudojs-cli — Database adapter lookup.
|
|
3
|
+
*
|
|
4
|
+
* `--database mysql|sqlite` used to be accepted and then ignored: every
|
|
5
|
+
* template hard-coded a PostgreSQL `DATABASE_URL`, and the MySQL/SQLite
|
|
6
|
+
* adapters were exported but never called. Templates and the
|
|
7
|
+
* InfrastructureGenerator resolve the adapter here instead. `mongodb` has
|
|
8
|
+
* no adapter, so it is rejected rather than silently turned into Postgres.
|
|
9
|
+
*/
|
|
10
|
+
import { CLIValidationError } from "../../errors/index.js";
|
|
11
|
+
import { MySqlAdapter } from "./mysql.adapter.js";
|
|
12
|
+
import { PostgresAdapter } from "./postgres.adapter.js";
|
|
13
|
+
import { SqliteAdapter } from "./sqlite.adapter.js";
|
|
14
|
+
/** Database engines the CLI can scaffold. */
|
|
15
|
+
export const SUPPORTED_DATABASES = ["postgresql", "mysql", "sqlite"];
|
|
16
|
+
const FACTORIES = new Map([
|
|
17
|
+
["postgresql", () => new PostgresAdapter()],
|
|
18
|
+
["mysql", () => new MySqlAdapter()],
|
|
19
|
+
["sqlite", () => new SqliteAdapter()],
|
|
20
|
+
]);
|
|
21
|
+
/**
|
|
22
|
+
* Returns the adapter for a database engine.
|
|
23
|
+
*
|
|
24
|
+
* @throws {CLIValidationError} For an engine with no adapter (e.g. `mongodb`).
|
|
25
|
+
*/
|
|
26
|
+
export function resolveDatabaseAdapter(database = "postgresql") {
|
|
27
|
+
const factory = FACTORIES.get(database);
|
|
28
|
+
if (!factory) {
|
|
29
|
+
throw new CLIValidationError(`Unsupported database "${database}". Supported: ${SUPPORTED_DATABASES.join(", ")}.`);
|
|
30
|
+
}
|
|
31
|
+
return factory();
|
|
32
|
+
}
|
|
33
|
+
/** Renders the adapter's environment variables as `.env` lines. */
|
|
34
|
+
export function renderDatabaseEnv(database, dbName) {
|
|
35
|
+
const variables = resolveDatabaseAdapter(database).getEnvironmentVariables(dbName);
|
|
36
|
+
return Object.entries(variables)
|
|
37
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
38
|
+
.join("\n");
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=databaseAdapter.resolver.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { PostgresAdapter } from "./postgres.adapter.js";
|
|
2
2
|
export { MySqlAdapter } from "./mysql.adapter.js";
|
|
3
3
|
export { SqliteAdapter } from "./sqlite.adapter.js";
|
|
4
|
+
export { SUPPORTED_DATABASES, renderDatabaseEnv, resolveDatabaseAdapter, } from "./databaseAdapter.resolver.js";
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { PostgresAdapter } from "./postgres.adapter.js";
|
|
2
2
|
export { MySqlAdapter } from "./mysql.adapter.js";
|
|
3
3
|
export { SqliteAdapter } from "./sqlite.adapter.js";
|
|
4
|
+
export { SUPPORTED_DATABASES, renderDatabaseEnv, resolveDatabaseAdapter, } from "./databaseAdapter.resolver.js";
|
|
4
5
|
//# sourceMappingURL=index.js.map
|
|
@@ -7,6 +7,6 @@ export declare class MySqlAdapter implements DatabaseAdapter {
|
|
|
7
7
|
readonly driver = "mysql";
|
|
8
8
|
getConnectionString(dbName: string): string;
|
|
9
9
|
getDependencies(): readonly string[];
|
|
10
|
-
getEnvironmentVariables(): Record<string, string>;
|
|
10
|
+
getEnvironmentVariables(dbName?: string): Record<string, string>;
|
|
11
11
|
}
|
|
12
12
|
//# sourceMappingURL=mysql.adapter.d.ts.map
|
|
@@ -10,9 +10,9 @@ export class MySqlAdapter {
|
|
|
10
10
|
getDependencies() {
|
|
11
11
|
return ["@zudojs/database"];
|
|
12
12
|
}
|
|
13
|
-
getEnvironmentVariables() {
|
|
13
|
+
getEnvironmentVariables(dbName = "mydb") {
|
|
14
14
|
return {
|
|
15
|
-
DATABASE_URL:
|
|
15
|
+
DATABASE_URL: this.getConnectionString(dbName),
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -8,13 +8,14 @@ export interface DatabaseAdapter {
|
|
|
8
8
|
readonly driver: string;
|
|
9
9
|
getConnectionString(dbName: string): string;
|
|
10
10
|
getDependencies(): readonly string[];
|
|
11
|
-
|
|
11
|
+
/** Environment variables for a database named `dbName` (default "mydb"). */
|
|
12
|
+
getEnvironmentVariables(dbName?: string): Record<string, string>;
|
|
12
13
|
}
|
|
13
14
|
export declare class PostgresAdapter implements DatabaseAdapter {
|
|
14
15
|
readonly name = "postgresql";
|
|
15
16
|
readonly driver = "postgres";
|
|
16
17
|
getConnectionString(dbName: string): string;
|
|
17
18
|
getDependencies(): readonly string[];
|
|
18
|
-
getEnvironmentVariables(): Record<string, string>;
|
|
19
|
+
getEnvironmentVariables(dbName?: string): Record<string, string>;
|
|
19
20
|
}
|
|
20
21
|
//# sourceMappingURL=postgres.adapter.d.ts.map
|
|
@@ -12,9 +12,9 @@ export class PostgresAdapter {
|
|
|
12
12
|
getDependencies() {
|
|
13
13
|
return ["@zudojs/database"];
|
|
14
14
|
}
|
|
15
|
-
getEnvironmentVariables() {
|
|
15
|
+
getEnvironmentVariables(dbName = "mydb") {
|
|
16
16
|
return {
|
|
17
|
-
DATABASE_URL:
|
|
17
|
+
DATABASE_URL: this.getConnectionString(dbName),
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
}
|
|
@@ -7,6 +7,6 @@ export declare class SqliteAdapter implements DatabaseAdapter {
|
|
|
7
7
|
readonly driver = "sqlite";
|
|
8
8
|
getConnectionString(dbName: string): string;
|
|
9
9
|
getDependencies(): readonly string[];
|
|
10
|
-
getEnvironmentVariables(): Record<string, string>;
|
|
10
|
+
getEnvironmentVariables(dbName?: string): Record<string, string>;
|
|
11
11
|
}
|
|
12
12
|
//# sourceMappingURL=sqlite.adapter.d.ts.map
|
|
@@ -10,9 +10,9 @@ export class SqliteAdapter {
|
|
|
10
10
|
getDependencies() {
|
|
11
11
|
return ["@zudojs/database"];
|
|
12
12
|
}
|
|
13
|
-
getEnvironmentVariables() {
|
|
13
|
+
getEnvironmentVariables(dbName = "mydb") {
|
|
14
14
|
return {
|
|
15
|
-
DATABASE_URL:
|
|
15
|
+
DATABASE_URL: this.getConnectionString(dbName),
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
}
|
package/dist/src/bin/zudojs.js
CHANGED
|
@@ -56,7 +56,7 @@ app.register(createCommand({
|
|
|
56
56
|
{
|
|
57
57
|
name: "database",
|
|
58
58
|
short: "d",
|
|
59
|
-
description: "Database engine (postgresql, mysql, sqlite
|
|
59
|
+
description: "Database engine (postgresql, mysql, sqlite)",
|
|
60
60
|
type: "string",
|
|
61
61
|
defaultValue: "postgresql",
|
|
62
62
|
},
|
|
@@ -176,6 +176,12 @@ app.register(createCommand({
|
|
|
176
176
|
type: "boolean",
|
|
177
177
|
defaultValue: false,
|
|
178
178
|
},
|
|
179
|
+
{
|
|
180
|
+
name: "force",
|
|
181
|
+
description: "Overwrite existing files the schematic would change",
|
|
182
|
+
type: "boolean",
|
|
183
|
+
defaultValue: false,
|
|
184
|
+
},
|
|
179
185
|
],
|
|
180
186
|
execute: async (context) => {
|
|
181
187
|
await runGenerateCommand(context);
|
|
@@ -64,7 +64,11 @@ export async function runAddCommand(context) {
|
|
|
64
64
|
if (!feature) {
|
|
65
65
|
throw new CLIValidationError(`Feature name is required. Available: ${available}`);
|
|
66
66
|
}
|
|
67
|
-
|
|
67
|
+
// Own keys only: `constructor` or `__proto__` resolved to Object.prototype
|
|
68
|
+
// members and crashed with "packages.join is not a function".
|
|
69
|
+
const packages = Object.hasOwn(FEATURE_PACKAGES, feature)
|
|
70
|
+
? FEATURE_PACKAGES[feature]
|
|
71
|
+
: undefined;
|
|
68
72
|
if (!packages) {
|
|
69
73
|
throw new CLIValidationError(`Unknown feature: "${feature}". Available: ${available}`);
|
|
70
74
|
}
|
|
@@ -12,6 +12,7 @@ import { FrontendGenerator } from "../generators/frontend/frontendGenerator.core
|
|
|
12
12
|
import { FullstackComposer } from "../generators/fullstack/fullstackComposer.core.js";
|
|
13
13
|
import { IntegrationGenerator } from "../generators/integration/integrationGenerator.core.js";
|
|
14
14
|
import { InfrastructureGenerator } from "../generators/infrastructure/infrastructure.generator.js";
|
|
15
|
+
import { layoutFullstackBackend } from "../generators/fullstack/fullstackBackend.layout.js";
|
|
15
16
|
import { BackendGenerator } from "../generators/backend/backend.generator.js";
|
|
16
17
|
import { RollbackManager } from "../rollback/rollbackManager.core.js";
|
|
17
18
|
import { promptProjectName, promptProjectType, promptConfirmation, } from "../prompts/project/index.js";
|
|
@@ -31,7 +32,9 @@ const VALID_ARCHITECTURES = [
|
|
|
31
32
|
"modular-monolith",
|
|
32
33
|
"microservice",
|
|
33
34
|
];
|
|
34
|
-
|
|
35
|
+
// No mongodb: there is no adapter for it, and it used to be accepted and
|
|
36
|
+
// scaffolded as PostgreSQL (tooling/CLI-04).
|
|
37
|
+
const VALID_DATABASES = ["postgresql", "mysql", "sqlite"];
|
|
35
38
|
const VALID_PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
|
|
36
39
|
const SERVICE_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
37
40
|
const VALID_FRONTENDS = [
|
|
@@ -285,6 +288,17 @@ export async function runCreateCommand(context) {
|
|
|
285
288
|
if (!answers.projectName) {
|
|
286
289
|
throw new CLIValidationError("Project name is required.");
|
|
287
290
|
}
|
|
291
|
+
// Backend templates only emit TypeScript. `--language javascript` used to
|
|
292
|
+
// be accepted and ignored (tooling/CLI-09); in a fullstack project it
|
|
293
|
+
// applies to the frontend only, which is said out loud.
|
|
294
|
+
if (answers.language === "javascript") {
|
|
295
|
+
if (answers.projectType === "backend") {
|
|
296
|
+
throw new CLIValidationError("Backend projects are generated in TypeScript only; --language javascript is not supported for --type backend.");
|
|
297
|
+
}
|
|
298
|
+
if (answers.projectType === "fullstack") {
|
|
299
|
+
context.logger.warn("--language javascript applies to the frontend (apps/web) only; the backend is generated in TypeScript.");
|
|
300
|
+
}
|
|
301
|
+
}
|
|
288
302
|
await createProject(answers, context);
|
|
289
303
|
}
|
|
290
304
|
async function createProject(options, context) {
|
|
@@ -453,12 +467,11 @@ async function generateFullstackProject(options, projectPath, rollback) {
|
|
|
453
467
|
}
|
|
454
468
|
// Uses the same BackendGenerator the generator registry exposes rather
|
|
455
469
|
// than a third private copy of the architecture switch.
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
rollback.trackDirectory(join(projectPath, "apps/api"));
|
|
470
|
+
const backend = layoutFullstackBackend(options.architecture, await new BackendGenerator().generate(options, join(projectPath, "apps/api")));
|
|
471
|
+
await writeFileTree(join(projectPath, backend.directory), backend.files);
|
|
472
|
+
for (const directory of backend.createdDirectories) {
|
|
473
|
+
rollback.trackDirectory(join(projectPath, directory));
|
|
474
|
+
}
|
|
462
475
|
const integrationGenerator = new IntegrationGenerator();
|
|
463
476
|
await integrationGenerator.generate({
|
|
464
477
|
project: {
|
|
@@ -492,6 +505,7 @@ async function generateFullstackProject(options, projectPath, rollback) {
|
|
|
492
505
|
database: options.database ?? "postgresql",
|
|
493
506
|
packageManager: options.packageManager,
|
|
494
507
|
services: options.services,
|
|
508
|
+
appDirectory: "apps/api",
|
|
495
509
|
}, projectPath);
|
|
496
510
|
}
|
|
497
511
|
async function generateFrontendProject(options, projectPath) {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Reads the project manifest to determine the architecture and where the
|
|
6
6
|
* backend lives, then places the schematic accordingly.
|
|
7
7
|
*/
|
|
8
|
+
import { join } from "node:path";
|
|
8
9
|
import { generateService } from "../generators/service/service.generator.js";
|
|
9
10
|
import { generateModule } from "../generators/module/module.generator.js";
|
|
10
11
|
import { generateCommand } from "../generators/command/command.generator.js";
|
|
@@ -21,6 +22,7 @@ import { generateValidator } from "../generators/validator/validator.generator.j
|
|
|
21
22
|
import { CLIGenerationError, CLIValidationError } from "../errors/index.js";
|
|
22
23
|
import { assertGeneratableName, assertSafePathSegment, } from "../utils/utils.name.js";
|
|
23
24
|
import { resolveProjectLayout } from "../resolvers/layout/projectLayout.core.js";
|
|
25
|
+
import { captureWrites, findWriteConflicts } from "../utils/utils.writeGuard.js";
|
|
24
26
|
const VALID_SCHEMATICS = [
|
|
25
27
|
"service",
|
|
26
28
|
"module",
|
|
@@ -71,9 +73,14 @@ function getArchitectureRoot(architecture, schematic, serviceName) {
|
|
|
71
73
|
}
|
|
72
74
|
// `--service <name>` selects which app the schematic belongs to.
|
|
73
75
|
// Without it the gateway app — the one app always generated — is used.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
// Modules go to that app's src/modules, next to the scaffolded ones,
|
|
77
|
+
// so they can be registered in its app.ts.
|
|
78
|
+
{
|
|
79
|
+
const appRoot = serviceName !== undefined
|
|
80
|
+
? `apps/services/${serviceName}/src`
|
|
81
|
+
: "apps/gateway/src";
|
|
82
|
+
return schematic === "module" ? `${appRoot}/modules` : appRoot;
|
|
83
|
+
}
|
|
77
84
|
case "monolith":
|
|
78
85
|
default:
|
|
79
86
|
if (schematic === "module") {
|
|
@@ -92,7 +99,11 @@ function getArchitectureRoot(architecture, schematic, serviceName) {
|
|
|
92
99
|
function backendPrefix(cwd) {
|
|
93
100
|
const layout = resolveProjectLayout(cwd);
|
|
94
101
|
const [first] = layout?.backendDirs ?? [];
|
|
95
|
-
|
|
102
|
+
// Only a monolith fullstack backend lives in apps/api; a microservice
|
|
103
|
+
// fullstack project keeps apps/gateway and apps/services at the root.
|
|
104
|
+
if (layout?.projectType === "fullstack" &&
|
|
105
|
+
first !== undefined &&
|
|
106
|
+
first === join(cwd, "apps", "api")) {
|
|
96
107
|
return "apps/api/";
|
|
97
108
|
}
|
|
98
109
|
return "";
|
|
@@ -103,6 +114,7 @@ export async function runGenerateCommand(context) {
|
|
|
103
114
|
const service = context.values.service;
|
|
104
115
|
const moduleName = context.values.module;
|
|
105
116
|
const dryRun = context.values["dry-run"] === true;
|
|
117
|
+
const force = context.values.force === true;
|
|
106
118
|
if (!schematic ||
|
|
107
119
|
!VALID_SCHEMATICS.includes(schematic)) {
|
|
108
120
|
throw new CLIValidationError(`Schematic name is required. Available: ${VALID_SCHEMATICS.join(", ")}`);
|
|
@@ -137,11 +149,30 @@ export async function runGenerateCommand(context) {
|
|
|
137
149
|
if (architecture === "modular-monolith" && schematic === "service") {
|
|
138
150
|
context.logger.info('Mapping "service" → "module" for modular-monolith architecture.');
|
|
139
151
|
}
|
|
140
|
-
const
|
|
152
|
+
const schematicOptions = {
|
|
141
153
|
service,
|
|
142
154
|
module: moduleName,
|
|
143
155
|
dryRun,
|
|
144
156
|
architecture: architecture ?? undefined,
|
|
157
|
+
};
|
|
158
|
+
if (!dryRun && !force) {
|
|
159
|
+
const planned = await captureWrites(() => runSchematic(schematic, name, schematicOptions, cwd));
|
|
160
|
+
const conflicts = findWriteConflicts(cwd, planned);
|
|
161
|
+
if (conflicts.length > 0) {
|
|
162
|
+
throw new CLIValidationError(`Refusing to overwrite existing files:\n${conflicts
|
|
163
|
+
.map((file) => ` - ${file}`)
|
|
164
|
+
.join("\n")}\nRe-run with --force to overwrite them.`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const result = await runSchematic(schematic, name, {
|
|
168
|
+
...schematicOptions,
|
|
169
|
+
onModuleRegistered: (registration) => {
|
|
170
|
+
if (registration.registered)
|
|
171
|
+
return;
|
|
172
|
+
context.logger.warn(`Could not register the module in app.ts automatically. Add:\n${registration.manualSteps
|
|
173
|
+
.map((step) => ` ${step}`)
|
|
174
|
+
.join("\n")}`);
|
|
175
|
+
},
|
|
145
176
|
}, cwd);
|
|
146
177
|
if (dryRun) {
|
|
147
178
|
context.logger.info(`Dry run: ${result.length} files would be generated (nothing written):`);
|
|
@@ -167,7 +198,7 @@ async function runSchematic(schematic, name, options, cwd) {
|
|
|
167
198
|
case "service":
|
|
168
199
|
return await generateService({ name, basePath, dryRun }, cwd);
|
|
169
200
|
case "module":
|
|
170
|
-
return await generateModule({ name, feature: true, basePath, dryRun }, cwd);
|
|
201
|
+
return await generateModule({ name, feature: true, basePath, dryRun, onRegistered: options.onModuleRegistered }, cwd);
|
|
171
202
|
case "command":
|
|
172
203
|
return await generateCommand({ name, ...(cqrsService ? { service: cqrsService } : {}), basePath, dryRun }, cwd);
|
|
173
204
|
case "query":
|
|
@@ -1,19 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* zudojs-cli — Errors
|
|
3
3
|
*
|
|
4
|
-
* Error classes for the CLI scaffolding system.
|
|
4
|
+
* Error classes for the CLI scaffolding system. They live in
|
|
5
|
+
* `@zudojs/errors` and are re-exported here for existing imports.
|
|
5
6
|
*/
|
|
6
|
-
|
|
7
|
-
export declare class CLIValidationError extends ApplicationError {
|
|
8
|
-
constructor(message: string);
|
|
9
|
-
}
|
|
10
|
-
export declare class CLIGenerationError extends ApplicationError {
|
|
11
|
-
constructor(message: string, cause?: unknown);
|
|
12
|
-
}
|
|
13
|
-
export declare class CLINotInProjectError extends ApplicationError {
|
|
14
|
-
constructor();
|
|
15
|
-
}
|
|
16
|
-
export declare class CLITemplateError extends ApplicationError {
|
|
17
|
-
constructor(message: string);
|
|
18
|
-
}
|
|
7
|
+
export { CLIValidationError, CLIGenerationError, CLINotInProjectError, CLITemplateError, } from "@zudojs/errors";
|
|
19
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/errors/index.js
CHANGED
|
@@ -1,29 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* zudojs-cli — Errors
|
|
3
3
|
*
|
|
4
|
-
* Error classes for the CLI scaffolding system.
|
|
4
|
+
* Error classes for the CLI scaffolding system. They live in
|
|
5
|
+
* `@zudojs/errors` and are re-exported here for existing imports.
|
|
5
6
|
*/
|
|
6
|
-
|
|
7
|
-
export class CLIValidationError extends ApplicationError {
|
|
8
|
-
constructor(message) {
|
|
9
|
-
super(message, { isOperational: true });
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
export class CLIGenerationError extends ApplicationError {
|
|
13
|
-
constructor(message, cause) {
|
|
14
|
-
super(message, { cause });
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
export class CLINotInProjectError extends ApplicationError {
|
|
18
|
-
constructor() {
|
|
19
|
-
super("This command must be run inside a Zudojs project directory.", {
|
|
20
|
-
isOperational: true,
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
export class CLITemplateError extends ApplicationError {
|
|
25
|
-
constructor(message) {
|
|
26
|
-
super(message);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
7
|
+
export { CLIValidationError, CLIGenerationError, CLINotInProjectError, CLITemplateError, } from "@zudojs/errors";
|
|
29
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { existsSync, readFileSync } from "node:fs";
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { writeFileTree } from "../../utils/utils.fileSystem.js";
|
|
16
|
+
import { CLIGenerationError } from "../../errors/index.js";
|
|
16
17
|
/**
|
|
17
18
|
* Rejects package names a package manager would read as a flag.
|
|
18
19
|
*
|
|
@@ -107,18 +108,21 @@ async function recordDependencies(projectPath, dependencies, devDependencies) {
|
|
|
107
108
|
const pkgPath = join(projectPath, "package.json");
|
|
108
109
|
let pkg = {};
|
|
109
110
|
if (existsSync(pkgPath)) {
|
|
111
|
+
// Fail the scaffold rather than replace an unparsable package.json with
|
|
112
|
+
// one holding only dependencies: that silently dropped name, scripts and
|
|
113
|
+
// type (tooling/CLI-07).
|
|
110
114
|
try {
|
|
111
115
|
pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
112
116
|
}
|
|
113
|
-
catch {
|
|
114
|
-
|
|
117
|
+
catch (error) {
|
|
118
|
+
throw new CLIGenerationError(`Could not parse ${pkgPath}; refusing to overwrite it with only the resolved dependencies.`, error);
|
|
115
119
|
}
|
|
116
120
|
}
|
|
117
121
|
const merge = (target, entries) => {
|
|
118
122
|
const merged = { ...(target ?? {}) };
|
|
119
123
|
for (const entry of entries) {
|
|
120
124
|
if (!(entry.name in merged)) {
|
|
121
|
-
merged[entry.name] = entry.version
|
|
125
|
+
merged[entry.name] = entry.version;
|
|
122
126
|
}
|
|
123
127
|
}
|
|
124
128
|
return merged;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zudojs-cli — Where a fullstack project's backend files go.
|
|
3
|
+
*
|
|
4
|
+
* A monolith backend is a single app written to `apps/api`. A microservice
|
|
5
|
+
* backend is a set of apps, and nesting it under `apps/api` put the gateway
|
|
6
|
+
* and services at `apps/api/apps/*`, outside the root workspace globs, so
|
|
7
|
+
* their dependencies were never installed and the root docker-compose built
|
|
8
|
+
* directories holding only a Dockerfile. Its apps are written at the root
|
|
9
|
+
* instead (`apps/gateway`, `apps/services/<name>`), next to `apps/web`.
|
|
10
|
+
*/
|
|
11
|
+
/** Backend files and the directory they are written under. */
|
|
12
|
+
export interface FullstackBackendLayout {
|
|
13
|
+
/** Directory relative to the project root ("" for the root). */
|
|
14
|
+
readonly directory: string;
|
|
15
|
+
/** Files relative to `directory`. */
|
|
16
|
+
readonly files: Record<string, string>;
|
|
17
|
+
/** Directories created, relative to the project root, for rollback. */
|
|
18
|
+
readonly createdDirectories: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Places generated backend files inside a fullstack project. Root-level
|
|
22
|
+
* files of a microservice backend (its own package.json, workspace file,
|
|
23
|
+
* compose file, README) are dropped: the fullstack root owns those.
|
|
24
|
+
*/
|
|
25
|
+
export declare function layoutFullstackBackend(architecture: string, backendFiles: Readonly<Record<string, string>>): FullstackBackendLayout;
|
|
26
|
+
//# sourceMappingURL=fullstackBackend.layout.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zudojs-cli — Where a fullstack project's backend files go.
|
|
3
|
+
*
|
|
4
|
+
* A monolith backend is a single app written to `apps/api`. A microservice
|
|
5
|
+
* backend is a set of apps, and nesting it under `apps/api` put the gateway
|
|
6
|
+
* and services at `apps/api/apps/*`, outside the root workspace globs, so
|
|
7
|
+
* their dependencies were never installed and the root docker-compose built
|
|
8
|
+
* directories holding only a Dockerfile. Its apps are written at the root
|
|
9
|
+
* instead (`apps/gateway`, `apps/services/<name>`), next to `apps/web`.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Places generated backend files inside a fullstack project. Root-level
|
|
13
|
+
* files of a microservice backend (its own package.json, workspace file,
|
|
14
|
+
* compose file, README) are dropped: the fullstack root owns those.
|
|
15
|
+
*/
|
|
16
|
+
export function layoutFullstackBackend(architecture, backendFiles) {
|
|
17
|
+
if (architecture !== "microservice") {
|
|
18
|
+
const files = { ...backendFiles };
|
|
19
|
+
delete files["pnpm-workspace.yaml"];
|
|
20
|
+
return { directory: "apps/api", files, createdDirectories: ["apps/api"] };
|
|
21
|
+
}
|
|
22
|
+
const files = {};
|
|
23
|
+
for (const [path, content] of Object.entries(backendFiles)) {
|
|
24
|
+
if (path.startsWith("apps/gateway/") || path.startsWith("apps/services/")) {
|
|
25
|
+
files[path] = content;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
directory: "",
|
|
30
|
+
files,
|
|
31
|
+
createdDirectories: ["apps/gateway", "apps/services"],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=fullstackBackend.layout.js.map
|
|
@@ -63,7 +63,11 @@ export class FullstackComposer {
|
|
|
63
63
|
}
|
|
64
64
|
async createWorkspace(context) {
|
|
65
65
|
const packageManager = this.getPackageManager(context);
|
|
66
|
-
|
|
66
|
+
// A microservice backend lives at apps/gateway and apps/services/<name>
|
|
67
|
+
// (tooling/CLI-02); `apps/*` alone does not reach the services.
|
|
68
|
+
const workspaceGlobs = context.project.backend?.architecture === "microservice"
|
|
69
|
+
? ["apps/*", "apps/services/*", "packages/*"]
|
|
70
|
+
: ["apps/*", "packages/*"];
|
|
67
71
|
const scriptFor = (script) => {
|
|
68
72
|
switch (packageManager) {
|
|
69
73
|
case "npm":
|
|
@@ -4,4 +4,5 @@
|
|
|
4
4
|
* Composes fullstack projects by orchestrating backend, frontend, and integration generators.
|
|
5
5
|
*/
|
|
6
6
|
export { FullstackComposer } from "./fullstackComposer.core.js";
|
|
7
|
+
export { layoutFullstackBackend, type FullstackBackendLayout, } from "./fullstackBackend.layout.js";
|
|
7
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -4,4 +4,5 @@
|
|
|
4
4
|
* Composes fullstack projects by orchestrating backend, frontend, and integration generators.
|
|
5
5
|
*/
|
|
6
6
|
export { FullstackComposer } from "./fullstackComposer.core.js";
|
|
7
|
+
export { layoutFullstackBackend, } from "./fullstackBackend.layout.js";
|
|
7
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -9,23 +9,18 @@ export interface InfrastructureOptions {
|
|
|
9
9
|
readonly database: string;
|
|
10
10
|
readonly packageManager: string;
|
|
11
11
|
readonly services?: readonly string[];
|
|
12
|
+
/**
|
|
13
|
+
* Directory holding the non-microservice server, relative to the project
|
|
14
|
+
* root. The fullstack scaffold passes `"apps/api"`; defaults to `"."`.
|
|
15
|
+
*/
|
|
16
|
+
readonly appDirectory?: string;
|
|
12
17
|
}
|
|
13
18
|
export declare class InfrastructureGenerator {
|
|
14
19
|
generate(options: InfrastructureOptions, basePath: string): Promise<void>;
|
|
15
20
|
private getFiles;
|
|
16
|
-
/**
|
|
17
|
-
* Install and build commands for Dockerfiles.
|
|
18
|
-
*
|
|
19
|
-
* Generated projects have no lockfile yet, so never use `npm ci` or
|
|
20
|
-
* `--frozen-lockfile`. pnpm and yarn need `corepack enable` on the bare
|
|
21
|
-
* node:24-alpine image; bun is not available there so it falls back to npm.
|
|
22
|
-
*/
|
|
23
|
-
private getDockerCommands;
|
|
24
21
|
private getDatabaseCompose;
|
|
25
22
|
private getDatabaseUrl;
|
|
26
23
|
private getDbServiceBlock;
|
|
27
|
-
private getAppDockerfile;
|
|
28
|
-
private getServiceDockerfile;
|
|
29
24
|
private getSimpleDockerCompose;
|
|
30
25
|
private getDockerCompose;
|
|
31
26
|
}
|