@avleon/cli 0.0.47 → 0.4.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/LICENSE +21 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +10 -0
- package/dist/commands/build.d.ts +11 -0
- package/dist/commands/build.js +65 -0
- package/dist/commands/doctor.d.ts +10 -0
- package/dist/commands/doctor.js +96 -0
- package/dist/commands/new.d.ts +19 -0
- package/dist/commands/new.js +306 -0
- package/dist/commands/route-list.d.ts +19 -0
- package/dist/commands/route-list.js +160 -0
- package/dist/commands/serve.d.ts +17 -0
- package/dist/commands/serve.js +238 -0
- package/dist/commands/start.d.ts +10 -0
- package/dist/commands/start.js +43 -0
- package/dist/compiler.d.ts +24 -0
- package/dist/compiler.js +83 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +16 -0
- package/dist/generators/make.d.ts +24 -0
- package/dist/generators/make.js +157 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +40 -15
- package/dist/program.d.ts +22 -0
- package/dist/program.js +178 -0
- package/dist/utils/entry.d.ts +15 -0
- package/dist/utils/entry.js +55 -0
- package/dist/utils/env-types.d.ts +8 -0
- package/dist/utils/env-types.js +19 -0
- package/dist/utils/log.d.ts +25 -0
- package/dist/utils/log.js +39 -0
- package/dist/utils/names.d.ts +27 -0
- package/dist/utils/names.js +58 -0
- package/dist/utils/process.d.ts +18 -0
- package/dist/utils/process.js +84 -0
- package/dist/utils/project.d.ts +17 -0
- package/dist/utils/project.js +91 -0
- package/dist/utils/stubs.d.ts +15 -0
- package/dist/utils/stubs.js +52 -0
- package/dist/utils/tsconfig.d.ts +13 -0
- package/dist/utils/tsconfig.js +102 -0
- package/package.json +28 -27
- package/readme.md +50 -11
- package/stubs/config/config.stub +9 -0
- package/stubs/controllers/basic-controller.stub +3 -3
- package/stubs/controllers/resource-controller.stub +45 -0
- package/stubs/controllers/rest-controller.stub +2 -2
- package/stubs/dtos/dto.stub +11 -0
- package/stubs/middlewares/middleware.stub +9 -0
- package/stubs/models/basic-model.stub +2 -2
- package/stubs/models/entity-model.stub +6 -8
- package/stubs/{app → project}/app-minimal.stub +1 -1
- package/stubs/{app/app-controller.stub → project/app.stub} +2 -7
- package/stubs/project/env.stub +3 -0
- package/stubs/project/gitignore.stub +5 -0
- package/stubs/project/home.controller.spec.stub +19 -0
- package/stubs/project/home.controller.stub +12 -0
- package/stubs/project/home.service.stub +8 -0
- package/stubs/{config.stub → project/openapi.config.stub} +4 -4
- package/stubs/project/readme.stub +26 -0
- package/stubs/{serve.stub → project/serve.stub} +1 -1
- package/stubs/project/tsconfig.build.stub +4 -0
- package/stubs/project/tsconfig.stub +18 -0
- package/stubs/project/vitest-e2e.config.stub +10 -0
- package/stubs/project/vitest.config.stub +17 -0
- package/stubs/services/basic-service.stub +1 -1
- package/stubs/services/orm-rest-service.stub +15 -14
- package/stubs/services/rest-service.stub +9 -9
- package/dist/av-cli.js +0 -60
- package/dist/avcli.js +0 -26
- package/dist/cli.interface.js +0 -2
- package/dist/cli.js +0 -332
- package/dist/commands.js +0 -63
- package/dist/controller.js +0 -96
- package/dist/create.js +0 -512
- package/dist/helpers/file-helper.js +0 -30
- package/dist/helpers/path-helper.js +0 -36
- package/dist/paths.js +0 -32
- package/dist/scripts/serve.js +0 -290
- package/dist/serve.js +0 -311
- package/dist/service.js +0 -69
- package/stubs/controllers/orm-rest-controller.stub +0 -45
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.titleCase = exports.snakeCase = exports.kebabCase = exports.camelCase = exports.pascalCase = void 0;
|
|
7
|
+
exports.words = words;
|
|
8
|
+
exports.resolveName = resolveName;
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const errors_1 = require("../errors");
|
|
11
|
+
/** Split an identifier into words: `JwtAuth`, `jwt-auth`, `jwt_auth` and `jwtAuth` all give `[jwt, auth]`. */
|
|
12
|
+
function words(input) {
|
|
13
|
+
const matches = input.match(/[A-Z]+(?=[A-Z][a-z])|[A-Z]?[a-z]+|[A-Z]+|\d+/g);
|
|
14
|
+
return (matches ?? []).map((w) => w.toLowerCase());
|
|
15
|
+
}
|
|
16
|
+
const upperFirst = (s) => (s ? s[0].toUpperCase() + s.slice(1) : s);
|
|
17
|
+
const pascalCase = (input) => words(input).map(upperFirst).join("");
|
|
18
|
+
exports.pascalCase = pascalCase;
|
|
19
|
+
const camelCase = (input) => {
|
|
20
|
+
const p = (0, exports.pascalCase)(input);
|
|
21
|
+
return p ? p[0].toLowerCase() + p.slice(1) : p;
|
|
22
|
+
};
|
|
23
|
+
exports.camelCase = camelCase;
|
|
24
|
+
const kebabCase = (input) => words(input).join("-");
|
|
25
|
+
exports.kebabCase = kebabCase;
|
|
26
|
+
const snakeCase = (input) => words(input).join("_");
|
|
27
|
+
exports.snakeCase = snakeCase;
|
|
28
|
+
/** Human-readable title, e.g. `my_api` -> `My Api`. */
|
|
29
|
+
const titleCase = (input) => words(input).map(upperFirst).join(" ");
|
|
30
|
+
exports.titleCase = titleCase;
|
|
31
|
+
/**
|
|
32
|
+
* Resolve a user-supplied artifact name (optionally nested, e.g. `admin/User`
|
|
33
|
+
* or `admin\User`) into class and file names. A trailing suffix such as
|
|
34
|
+
* `Controller` is dropped before it is re-added, so `UserController` and `User`
|
|
35
|
+
* give the same result.
|
|
36
|
+
*/
|
|
37
|
+
function resolveName(input, suffix = "") {
|
|
38
|
+
if (!input || !input.trim())
|
|
39
|
+
throw new errors_1.CliError("A name is required.");
|
|
40
|
+
const parts = input.split(/[\\/]+/).filter(Boolean);
|
|
41
|
+
const last = parts.pop() ?? "";
|
|
42
|
+
for (const dir of parts) {
|
|
43
|
+
if (dir === "." || dir === ".." || path_1.default.isAbsolute(dir)) {
|
|
44
|
+
throw new errors_1.CliError(`Invalid name "${input}": use a relative path without "." or "..".`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const stripped = suffix ? last.replace(new RegExp(`[-_.]?${suffix}$`, "i"), "") : last;
|
|
48
|
+
const base = (0, exports.pascalCase)(stripped);
|
|
49
|
+
if (!base)
|
|
50
|
+
throw new errors_1.CliError(`Invalid name "${input}".`);
|
|
51
|
+
return {
|
|
52
|
+
dirs: parts.map((d) => (0, exports.kebabCase)(d) || d),
|
|
53
|
+
base,
|
|
54
|
+
className: base + suffix,
|
|
55
|
+
varName: (0, exports.camelCase)(base),
|
|
56
|
+
fileBase: (0, exports.kebabCase)(base),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ChildProcess } from "child_process";
|
|
2
|
+
/**
|
|
3
|
+
* Stop a child process and everything it spawned. On POSIX the child must
|
|
4
|
+
* have been started with `detached: true` so it leads its own process group;
|
|
5
|
+
* we send SIGTERM to the group and escalate to SIGKILL after `timeoutMs`.
|
|
6
|
+
* Windows has no SIGTERM for console processes, so the tree is ended with
|
|
7
|
+
* `taskkill /T /F`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function killTree(child: ChildProcess | null, timeoutMs?: number): Promise<void>;
|
|
10
|
+
/** Spawn options that make `killTree` work on every platform. */
|
|
11
|
+
export declare const treeSpawnOptions: {
|
|
12
|
+
readonly detached: boolean;
|
|
13
|
+
readonly windowsHide: true;
|
|
14
|
+
};
|
|
15
|
+
/** Locate the project's own TypeScript compiler (`typescript/bin/tsc`). */
|
|
16
|
+
export declare function findTsc(root: string): string | null;
|
|
17
|
+
/** Run a node script to completion with inherited stdio, resolving to its exit code. */
|
|
18
|
+
export declare function runNode(script: string, args: string[], cwd: string, env?: NodeJS.ProcessEnv): Promise<number>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.treeSpawnOptions = void 0;
|
|
7
|
+
exports.killTree = killTree;
|
|
8
|
+
exports.findTsc = findTsc;
|
|
9
|
+
exports.runNode = runNode;
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const isWindows = process.platform === "win32";
|
|
14
|
+
/**
|
|
15
|
+
* Stop a child process and everything it spawned. On POSIX the child must
|
|
16
|
+
* have been started with `detached: true` so it leads its own process group;
|
|
17
|
+
* we send SIGTERM to the group and escalate to SIGKILL after `timeoutMs`.
|
|
18
|
+
* Windows has no SIGTERM for console processes, so the tree is ended with
|
|
19
|
+
* `taskkill /T /F`.
|
|
20
|
+
*/
|
|
21
|
+
function killTree(child, timeoutMs = 3000) {
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
if (!child || child.pid === undefined || child.exitCode !== null || child.signalCode !== null) {
|
|
24
|
+
return resolve();
|
|
25
|
+
}
|
|
26
|
+
const pid = child.pid;
|
|
27
|
+
let done = false;
|
|
28
|
+
const finish = () => {
|
|
29
|
+
if (done)
|
|
30
|
+
return;
|
|
31
|
+
done = true;
|
|
32
|
+
clearTimeout(timer);
|
|
33
|
+
resolve();
|
|
34
|
+
};
|
|
35
|
+
child.once("exit", finish);
|
|
36
|
+
if (isWindows) {
|
|
37
|
+
(0, child_process_1.spawnSync)("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
signalGroup(pid, "SIGTERM");
|
|
41
|
+
}
|
|
42
|
+
const timer = setTimeout(() => {
|
|
43
|
+
if (!isWindows)
|
|
44
|
+
signalGroup(pid, "SIGKILL");
|
|
45
|
+
setTimeout(finish, 200);
|
|
46
|
+
}, timeoutMs);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function signalGroup(pid, signal) {
|
|
50
|
+
try {
|
|
51
|
+
process.kill(-pid, signal);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
try {
|
|
55
|
+
process.kill(pid, signal);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
/* already gone */
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Spawn options that make `killTree` work on every platform. */
|
|
63
|
+
exports.treeSpawnOptions = { detached: !isWindows, windowsHide: true };
|
|
64
|
+
/** Locate the project's own TypeScript compiler (`typescript/bin/tsc`). */
|
|
65
|
+
function findTsc(root) {
|
|
66
|
+
let dir = path_1.default.resolve(root);
|
|
67
|
+
for (;;) {
|
|
68
|
+
const candidate = path_1.default.join(dir, "node_modules", "typescript", "bin", "tsc");
|
|
69
|
+
if (fs_1.default.existsSync(candidate))
|
|
70
|
+
return candidate;
|
|
71
|
+
const parent = path_1.default.dirname(dir);
|
|
72
|
+
if (parent === dir)
|
|
73
|
+
return null;
|
|
74
|
+
dir = parent;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** Run a node script to completion with inherited stdio, resolving to its exit code. */
|
|
78
|
+
function runNode(script, args, cwd, env = process.env) {
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
const child = (0, child_process_1.spawn)(process.execPath, [script, ...args], { cwd, stdio: "inherit", env });
|
|
81
|
+
child.on("error", reject);
|
|
82
|
+
child.on("exit", (code, signal) => resolve(code ?? (signal ? 1 : 0)));
|
|
83
|
+
});
|
|
84
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Walk up from `start` to the nearest directory containing a `package.json`. */
|
|
2
|
+
export declare function findProjectRoot(start: string): string | null;
|
|
3
|
+
export declare function requireProjectRoot(start: string): string;
|
|
4
|
+
export declare function readJson<T = any>(file: string): T;
|
|
5
|
+
export declare function readPackageJson(root: string): Record<string, any>;
|
|
6
|
+
/**
|
|
7
|
+
* Find an installed package's directory by walking up `node_modules` folders.
|
|
8
|
+
* Unlike `require.resolve('<pkg>/package.json')` this works for packages whose
|
|
9
|
+
* `exports` map does not expose `package.json`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function findInstalledPackage(name: string, from: string): string | null;
|
|
12
|
+
export declare function installedVersion(name: string, from: string): string | null;
|
|
13
|
+
/** Write a file, creating parent directories. Refuses to overwrite unless `force`. */
|
|
14
|
+
export declare function writeFileSafe(file: string, content: string, force?: boolean): void;
|
|
15
|
+
/** Copy every non-TypeScript file under `srcDir` to the same relative path under `outDir`. */
|
|
16
|
+
export declare function copyAssets(srcDir: string, outDir: string): number;
|
|
17
|
+
export declare function isAsset(file: string): boolean;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.findProjectRoot = findProjectRoot;
|
|
7
|
+
exports.requireProjectRoot = requireProjectRoot;
|
|
8
|
+
exports.readJson = readJson;
|
|
9
|
+
exports.readPackageJson = readPackageJson;
|
|
10
|
+
exports.findInstalledPackage = findInstalledPackage;
|
|
11
|
+
exports.installedVersion = installedVersion;
|
|
12
|
+
exports.writeFileSafe = writeFileSafe;
|
|
13
|
+
exports.copyAssets = copyAssets;
|
|
14
|
+
exports.isAsset = isAsset;
|
|
15
|
+
const fs_1 = __importDefault(require("fs"));
|
|
16
|
+
const path_1 = __importDefault(require("path"));
|
|
17
|
+
const errors_1 = require("../errors");
|
|
18
|
+
/** Walk up from `start` to the nearest directory containing a `package.json`. */
|
|
19
|
+
function findProjectRoot(start) {
|
|
20
|
+
let dir = path_1.default.resolve(start);
|
|
21
|
+
for (;;) {
|
|
22
|
+
if (fs_1.default.existsSync(path_1.default.join(dir, "package.json")))
|
|
23
|
+
return dir;
|
|
24
|
+
const parent = path_1.default.dirname(dir);
|
|
25
|
+
if (parent === dir)
|
|
26
|
+
return null;
|
|
27
|
+
dir = parent;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function requireProjectRoot(start) {
|
|
31
|
+
const root = findProjectRoot(start);
|
|
32
|
+
if (!root) {
|
|
33
|
+
throw new errors_1.CliError(`No package.json found in ${path_1.default.resolve(start)} or any parent directory. Run this command inside an Avleon project.`);
|
|
34
|
+
}
|
|
35
|
+
return root;
|
|
36
|
+
}
|
|
37
|
+
function readJson(file) {
|
|
38
|
+
return JSON.parse(fs_1.default.readFileSync(file, "utf8"));
|
|
39
|
+
}
|
|
40
|
+
function readPackageJson(root) {
|
|
41
|
+
return readJson(path_1.default.join(root, "package.json"));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Find an installed package's directory by walking up `node_modules` folders.
|
|
45
|
+
* Unlike `require.resolve('<pkg>/package.json')` this works for packages whose
|
|
46
|
+
* `exports` map does not expose `package.json`.
|
|
47
|
+
*/
|
|
48
|
+
function findInstalledPackage(name, from) {
|
|
49
|
+
let dir = path_1.default.resolve(from);
|
|
50
|
+
for (;;) {
|
|
51
|
+
const candidate = path_1.default.join(dir, "node_modules", ...name.split("/"));
|
|
52
|
+
if (fs_1.default.existsSync(path_1.default.join(candidate, "package.json")))
|
|
53
|
+
return candidate;
|
|
54
|
+
const parent = path_1.default.dirname(dir);
|
|
55
|
+
if (parent === dir)
|
|
56
|
+
return null;
|
|
57
|
+
dir = parent;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function installedVersion(name, from) {
|
|
61
|
+
const dir = findInstalledPackage(name, from);
|
|
62
|
+
return dir ? readJson(path_1.default.join(dir, "package.json")).version ?? null : null;
|
|
63
|
+
}
|
|
64
|
+
/** Write a file, creating parent directories. Refuses to overwrite unless `force`. */
|
|
65
|
+
function writeFileSafe(file, content, force = false) {
|
|
66
|
+
if (fs_1.default.existsSync(file) && !force) {
|
|
67
|
+
throw new errors_1.CliError(`${file} already exists. Use -f or --force to overwrite it.`);
|
|
68
|
+
}
|
|
69
|
+
fs_1.default.mkdirSync(path_1.default.dirname(file), { recursive: true });
|
|
70
|
+
fs_1.default.writeFileSync(file, content, "utf8");
|
|
71
|
+
}
|
|
72
|
+
/** Copy every non-TypeScript file under `srcDir` to the same relative path under `outDir`. */
|
|
73
|
+
function copyAssets(srcDir, outDir) {
|
|
74
|
+
if (!fs_1.default.existsSync(srcDir))
|
|
75
|
+
return 0;
|
|
76
|
+
let copied = 0;
|
|
77
|
+
for (const entry of fs_1.default.readdirSync(srcDir, { withFileTypes: true, recursive: true })) {
|
|
78
|
+
if (!entry.isFile() || !isAsset(entry.name))
|
|
79
|
+
continue;
|
|
80
|
+
const parent = entry.parentPath ?? entry.path;
|
|
81
|
+
const from = path_1.default.join(parent, entry.name);
|
|
82
|
+
const to = path_1.default.join(outDir, path_1.default.relative(srcDir, from));
|
|
83
|
+
fs_1.default.mkdirSync(path_1.default.dirname(to), { recursive: true });
|
|
84
|
+
fs_1.default.copyFileSync(from, to);
|
|
85
|
+
copied++;
|
|
86
|
+
}
|
|
87
|
+
return copied;
|
|
88
|
+
}
|
|
89
|
+
function isAsset(file) {
|
|
90
|
+
return !/\.(c|m)?tsx?$/.test(file) && !path_1.default.basename(file).startsWith(".");
|
|
91
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The package root (the directory holding `stubs/` and `package.json`). Source
|
|
3
|
+
* files run from `src/**` under Vitest and from `dist/**` once built, so walk
|
|
4
|
+
* up from this file instead of assuming a fixed depth.
|
|
5
|
+
*/
|
|
6
|
+
export declare const packageRoot: string;
|
|
7
|
+
export declare function stubPath(name: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Read a stub and replace `{Key}` placeholders. Unknown placeholders are left
|
|
10
|
+
* untouched so object literals in stubs are safe.
|
|
11
|
+
*/
|
|
12
|
+
export declare function renderStub(name: string, vars?: Record<string, string>): string;
|
|
13
|
+
export declare function cliVersion(): string;
|
|
14
|
+
/** The `@avleon/core` range new projects should depend on (lockstep with the CLI). */
|
|
15
|
+
export declare function coreRange(): string;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.packageRoot = void 0;
|
|
7
|
+
exports.stubPath = stubPath;
|
|
8
|
+
exports.renderStub = renderStub;
|
|
9
|
+
exports.cliVersion = cliVersion;
|
|
10
|
+
exports.coreRange = coreRange;
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
/**
|
|
14
|
+
* The package root (the directory holding `stubs/` and `package.json`). Source
|
|
15
|
+
* files run from `src/**` under Vitest and from `dist/**` once built, so walk
|
|
16
|
+
* up from this file instead of assuming a fixed depth.
|
|
17
|
+
*/
|
|
18
|
+
exports.packageRoot = (() => {
|
|
19
|
+
let dir = __dirname;
|
|
20
|
+
for (;;) {
|
|
21
|
+
if (fs_1.default.existsSync(path_1.default.join(dir, "stubs")) &&
|
|
22
|
+
fs_1.default.existsSync(path_1.default.join(dir, "package.json"))) {
|
|
23
|
+
return dir;
|
|
24
|
+
}
|
|
25
|
+
const parent = path_1.default.dirname(dir);
|
|
26
|
+
if (parent === dir)
|
|
27
|
+
return path_1.default.resolve(__dirname, "..", "..");
|
|
28
|
+
dir = parent;
|
|
29
|
+
}
|
|
30
|
+
})();
|
|
31
|
+
function stubPath(name) {
|
|
32
|
+
return path_1.default.join(exports.packageRoot, "stubs", ...name.split("/"));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Read a stub and replace `{Key}` placeholders. Unknown placeholders are left
|
|
36
|
+
* untouched so object literals in stubs are safe.
|
|
37
|
+
*/
|
|
38
|
+
function renderStub(name, vars = {}) {
|
|
39
|
+
let content = fs_1.default.readFileSync(stubPath(name), "utf8").replace(/\r\n/g, "\n");
|
|
40
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
41
|
+
content = content.split(`{${key}}`).join(value);
|
|
42
|
+
}
|
|
43
|
+
return content;
|
|
44
|
+
}
|
|
45
|
+
function cliVersion() {
|
|
46
|
+
return JSON.parse(fs_1.default.readFileSync(path_1.default.join(exports.packageRoot, "package.json"), "utf8")).version;
|
|
47
|
+
}
|
|
48
|
+
/** The `@avleon/core` range new projects should depend on (lockstep with the CLI). */
|
|
49
|
+
function coreRange() {
|
|
50
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(path_1.default.join(exports.packageRoot, "package.json"), "utf8"));
|
|
51
|
+
return pkg.peerDependencies?.["@avleon/core"] ?? `^${pkg.version}`;
|
|
52
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type TsConfigInfo = {
|
|
2
|
+
file: string;
|
|
3
|
+
compilerOptions: Record<string, any>;
|
|
4
|
+
};
|
|
5
|
+
/** Parse JSON that may contain comments and trailing commas, as tsconfig files do. */
|
|
6
|
+
export declare function parseJsonc(text: string): any;
|
|
7
|
+
/**
|
|
8
|
+
* Read a tsconfig and merge `extends` chains (relative paths and packages).
|
|
9
|
+
* Path-valued options (`baseUrl`, `outDir`, `rootDir`) are resolved to
|
|
10
|
+
* absolute paths relative to the file that declared them.
|
|
11
|
+
*/
|
|
12
|
+
export declare function readTsConfig(file: string, seen?: Set<string>): TsConfigInfo;
|
|
13
|
+
export declare function findTsConfig(root: string, preferBuild?: boolean): string | null;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.parseJsonc = parseJsonc;
|
|
7
|
+
exports.readTsConfig = readTsConfig;
|
|
8
|
+
exports.findTsConfig = findTsConfig;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
/** Parse JSON that may contain comments and trailing commas, as tsconfig files do. */
|
|
12
|
+
function parseJsonc(text) {
|
|
13
|
+
let out = "";
|
|
14
|
+
let inString = false;
|
|
15
|
+
for (let i = 0; i < text.length; i++) {
|
|
16
|
+
const ch = text[i];
|
|
17
|
+
const next = text[i + 1];
|
|
18
|
+
if (inString) {
|
|
19
|
+
out += ch;
|
|
20
|
+
if (ch === "\\") {
|
|
21
|
+
out += next ?? "";
|
|
22
|
+
i++;
|
|
23
|
+
}
|
|
24
|
+
else if (ch === '"') {
|
|
25
|
+
inString = false;
|
|
26
|
+
}
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (ch === '"') {
|
|
30
|
+
inString = true;
|
|
31
|
+
out += ch;
|
|
32
|
+
}
|
|
33
|
+
else if (ch === "/" && next === "/") {
|
|
34
|
+
while (i < text.length && text[i] !== "\n")
|
|
35
|
+
i++;
|
|
36
|
+
out += "\n";
|
|
37
|
+
}
|
|
38
|
+
else if (ch === "/" && next === "*") {
|
|
39
|
+
i += 2;
|
|
40
|
+
while (i < text.length && !(text[i] === "*" && text[i + 1] === "/"))
|
|
41
|
+
i++;
|
|
42
|
+
i++;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
out += ch;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return JSON.parse(out.replace(/,(\s*[}\]])/g, "$1"));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Read a tsconfig and merge `extends` chains (relative paths and packages).
|
|
52
|
+
* Path-valued options (`baseUrl`, `outDir`, `rootDir`) are resolved to
|
|
53
|
+
* absolute paths relative to the file that declared them.
|
|
54
|
+
*/
|
|
55
|
+
function readTsConfig(file, seen = new Set()) {
|
|
56
|
+
const abs = path_1.default.resolve(file);
|
|
57
|
+
if (seen.has(abs))
|
|
58
|
+
return { file: abs, compilerOptions: {} };
|
|
59
|
+
seen.add(abs);
|
|
60
|
+
const raw = parseJsonc(fs_1.default.readFileSync(abs, "utf8"));
|
|
61
|
+
const dir = path_1.default.dirname(abs);
|
|
62
|
+
let base = {};
|
|
63
|
+
const exts = raw.extends ? [].concat(raw.extends) : [];
|
|
64
|
+
for (const ext of exts) {
|
|
65
|
+
let target = null;
|
|
66
|
+
if (ext.startsWith(".") || path_1.default.isAbsolute(ext)) {
|
|
67
|
+
target = path_1.default.resolve(dir, ext);
|
|
68
|
+
if (!fs_1.default.existsSync(target) && fs_1.default.existsSync(target + ".json"))
|
|
69
|
+
target += ".json";
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
try {
|
|
73
|
+
target = require.resolve(ext, { paths: [dir] });
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
target = null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (target && fs_1.default.existsSync(target)) {
|
|
80
|
+
base = { ...base, ...readTsConfig(target, seen).compilerOptions };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const own = { ...(raw.compilerOptions ?? {}) };
|
|
84
|
+
for (const key of ["baseUrl", "outDir", "rootDir"]) {
|
|
85
|
+
if (typeof own[key] === "string")
|
|
86
|
+
own[key] = path_1.default.resolve(dir, own[key]);
|
|
87
|
+
}
|
|
88
|
+
if (own.paths && own.baseUrl === undefined && base.baseUrl === undefined) {
|
|
89
|
+
// TypeScript resolves `paths` relative to the declaring tsconfig when baseUrl is unset.
|
|
90
|
+
own.pathsBasePath = dir;
|
|
91
|
+
}
|
|
92
|
+
return { file: abs, compilerOptions: { ...base, ...own } };
|
|
93
|
+
}
|
|
94
|
+
function findTsConfig(root, preferBuild = false) {
|
|
95
|
+
const names = preferBuild ? ["tsconfig.build.json", "tsconfig.json"] : ["tsconfig.json"];
|
|
96
|
+
for (const name of names) {
|
|
97
|
+
const file = path_1.default.join(root, name);
|
|
98
|
+
if (fs_1.default.existsSync(file))
|
|
99
|
+
return file;
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
package/package.json
CHANGED
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avleon/cli",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "CLI for scaffolding and running Avleon applications",
|
|
5
5
|
"author": "Tareq Hossain",
|
|
6
|
-
"license": "
|
|
6
|
+
"license": "MIT",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
9
9
|
"bin": {
|
|
10
|
-
"avleon": "
|
|
10
|
+
"avleon": "dist/bin.js"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"dist",
|
|
14
14
|
"stubs",
|
|
15
|
-
"README.md"
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
16
17
|
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=20"
|
|
20
|
+
},
|
|
17
21
|
"scripts": {
|
|
18
|
-
"build": "
|
|
19
|
-
"clean": "
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
+
"build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
|
|
23
|
+
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
24
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"watch": "tsc -p tsconfig.json --watch",
|
|
27
|
+
"prepublishOnly": "npm run build"
|
|
22
28
|
},
|
|
23
29
|
"keywords": [
|
|
24
30
|
"cli",
|
|
@@ -27,36 +33,31 @@
|
|
|
27
33
|
"typescript"
|
|
28
34
|
],
|
|
29
35
|
"dependencies": {
|
|
30
|
-
"@avleon/core": "*",
|
|
31
36
|
"@swc/core": "^1.13.5",
|
|
32
37
|
"chokidar": "^4.0.3",
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"fs-extra": "^11.2.0",
|
|
36
|
-
"inquirer": "^12.6.0",
|
|
37
|
-
"lodash": "^4.17.21",
|
|
38
|
-
"prettier": "^3.3.3",
|
|
39
|
-
"rimraf": "^6.0.1"
|
|
38
|
+
"commander": "^14.0.0",
|
|
39
|
+
"inquirer": "^12.6.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@swc/cli": "^0.7.8",
|
|
43
|
-
"@types/fs-extra": "^11.0.4",
|
|
44
|
-
"@types/lodash": "^4.17.16",
|
|
45
42
|
"@types/node": "^22.9.3",
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"tsc-watch": "^6.2.1",
|
|
49
|
-
"tsup": "^8.4.0",
|
|
50
|
-
"typescript": "^5.7.2"
|
|
43
|
+
"typescript": "^5.7.2",
|
|
44
|
+
"vitest": "^4.1.12"
|
|
51
45
|
},
|
|
52
46
|
"peerDependencies": {
|
|
53
|
-
"@avleon/core": "
|
|
47
|
+
"@avleon/core": "^0.4.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"@avleon/core": {
|
|
51
|
+
"optional": true
|
|
52
|
+
}
|
|
54
53
|
},
|
|
55
54
|
"repository": {
|
|
56
55
|
"type": "git",
|
|
57
|
-
"url": "git+https://github.com/avleonjs/avleon
|
|
56
|
+
"url": "git+https://github.com/avleonjs/avleon.git",
|
|
57
|
+
"directory": "packages/cli"
|
|
58
58
|
},
|
|
59
59
|
"publishConfig": {
|
|
60
60
|
"access": "public"
|
|
61
|
-
}
|
|
61
|
+
},
|
|
62
|
+
"gitHead": "fe89f3bb068deb2794327a5aa8809ad3ed29ce87"
|
|
62
63
|
}
|
package/readme.md
CHANGED
|
@@ -37,17 +37,26 @@ npm run start:dev
|
|
|
37
37
|
|
|
38
38
|
| Command | Alias | Options | Description |
|
|
39
39
|
|---------|-------|---------|-------------|
|
|
40
|
-
| `new
|
|
41
|
-
| `serve` | |
|
|
42
|
-
| `build` | | | Compile with `tsconfig.build.json` |
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
| `make:
|
|
47
|
-
| `make:
|
|
40
|
+
| `new <name>` | | `-m`, `-f`, `-y`, `--pm <npm\|yarn\|pnpm>`, `--features <list>`, `--skip-install` | Create a new application |
|
|
41
|
+
| `serve` | | `--entry <file>`, `--no-type-check`, `--debounce <ms>` | Dev server: SWC compile, run, reload on change |
|
|
42
|
+
| `build` | | `-p <tsconfig>` | Compile with `tsconfig.build.json` and copy assets |
|
|
43
|
+
| `start` | | `--entry <file>` | Run the compiled app with `NODE_ENV=production` |
|
|
44
|
+
| `doctor` | | | Check Node.js, `@avleon/core`, `reflect-metadata` and decorator settings |
|
|
45
|
+
| `route:list` | `routes` | `--json`, `--static` | List the app's routes |
|
|
46
|
+
| `make:model` | `m:m` | `-f`, `--orm`, `--root <dir>` | Create a model / TypeORM entity |
|
|
47
|
+
| `make:controller` | `m:c` | `-f`, `-r`, `-m <model>`, `--orm`, `--root <dir>`, `--base-path` | Create a controller |
|
|
48
|
+
| `make:service` | `m:s` | `-f`, `-r`, `-m <model>`, `--orm`, `--root <dir>` | Create a service |
|
|
49
|
+
| `make:config` | `m:cnf` | `-f`, `--root <dir>` | Create an `@AppConfig` class |
|
|
50
|
+
| `make:middleware` | `m:mlwr` | `-f`, `--root <dir>` | Create a middleware |
|
|
51
|
+
| `make:dto` | `m:dto` | `-f`, `--root <dir>` | Create a class-validator DTO |
|
|
48
52
|
|
|
49
53
|
`-f` / `--force` overwrites an existing file. `-r` / `--resource` generates a
|
|
50
|
-
full REST skeleton.
|
|
54
|
+
full REST skeleton. `--root <dir>` (also `-rt`) writes under `src/<dir>`
|
|
55
|
+
instead of the default folder. Names may be nested, e.g. `admin/User`.
|
|
56
|
+
Run `avleon <command> --help` for details.
|
|
57
|
+
|
|
58
|
+
Generated controllers must be registered in your app with
|
|
59
|
+
`app.useControllers([...])`.
|
|
51
60
|
|
|
52
61
|
### Controller
|
|
53
62
|
|
|
@@ -112,5 +121,35 @@ npx avleon serve
|
|
|
112
121
|
```
|
|
113
122
|
|
|
114
123
|
Compiles `src/` with SWC into `dist/`, starts the entry point, and restarts on
|
|
115
|
-
change. The entry file is taken from
|
|
116
|
-
|
|
124
|
+
change to sources, assets or `.env*` files. The entry file is taken from
|
|
125
|
+
`--entry`, `avleon.entry` in `package.json`, `main`, then `dist/serve.js`,
|
|
126
|
+
`server.js`, `main.js` or `index.js`.
|
|
127
|
+
|
|
128
|
+
Type errors are reported by a parallel `tsc --noEmit --watch` (disable with
|
|
129
|
+
`--no-type-check`) and never block a reload. Syntax errors stop the app until
|
|
130
|
+
they are fixed, so stale code never keeps serving.
|
|
131
|
+
|
|
132
|
+
## Build and Run
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
npx avleon build # tsc with tsconfig.build.json, then copy non-TS assets
|
|
136
|
+
npx avleon start # node dist/serve.js with NODE_ENV=production
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Routes
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
npx avleon route:list # compile and read Avleon's route metadata
|
|
143
|
+
npx avleon route:list --static # scan decorators in the source, no dependencies
|
|
144
|
+
npx avleon routes --json
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Doctor
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
npx avleon doctor
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Checks the Node.js version, that `@avleon/core` matches the CLI's version,
|
|
154
|
+
that `reflect-metadata` is installed, and that `experimentalDecorators` and
|
|
155
|
+
`emitDecoratorMetadata` are enabled. Exits non-zero if a check fails.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ApiController, Get } from '@avleon/core';
|
|
2
2
|
|
|
3
|
-
@ApiController('/{
|
|
4
|
-
export class {
|
|
3
|
+
@ApiController('/{route}')
|
|
4
|
+
export class {ClassName} {
|
|
5
5
|
@Get()
|
|
6
6
|
index() {
|
|
7
|
-
return '{
|
|
7
|
+
return { message: '{ClassName} works' };
|
|
8
8
|
}
|
|
9
9
|
}
|