@nage-api/cli 1.0.0-beta.2
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 +202 -0
- package/README.md +196 -0
- package/dist/cli.d.ts +25 -0
- package/dist/cli.js +276 -0
- package/dist/commands/create.d.ts +56 -0
- package/dist/commands/create.js +219 -0
- package/dist/commands/doctor.d.ts +47 -0
- package/dist/commands/doctor.js +208 -0
- package/dist/commands/features.d.ts +56 -0
- package/dist/commands/features.js +229 -0
- package/dist/commands/generate.d.ts +37 -0
- package/dist/commands/generate.js +151 -0
- package/dist/fs/file-tree.d.ts +57 -0
- package/dist/fs/file-tree.js +136 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +80 -0
- package/dist/main.d.ts +11 -0
- package/dist/main.js +43 -0
- package/dist/naming.d.ts +36 -0
- package/dist/naming.js +72 -0
- package/dist/templates/app.template.d.ts +19 -0
- package/dist/templates/app.template.js +601 -0
- package/dist/templates/resource.template.d.ts +39 -0
- package/dist/templates/resource.template.js +600 -0
- package/dist/templates/workspace.template.d.ts +22 -0
- package/dist/templates/workspace.template.js +457 -0
- package/dist/workspace/manifest.d.ts +70 -0
- package/dist/workspace/manifest.js +162 -0
- package/dist/workspace/wiring.d.ts +33 -0
- package/dist/workspace/wiring.js +112 -0
- package/package.json +51 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The set of files a command intends to write, and the transaction that writes
|
|
3
|
+
* them (PLAN.md §10.5 — "atomically").
|
|
4
|
+
*
|
|
5
|
+
* Commands build a `FileTree` and hand it to `commitFileTree`, which writes
|
|
6
|
+
* everything or nothing. A half-created app — registered in the manifest but
|
|
7
|
+
* missing its module, or vice versa — is worse than a failed command, because
|
|
8
|
+
* the next command starts from an inconsistent workspace.
|
|
9
|
+
*/
|
|
10
|
+
export interface PlannedFile {
|
|
11
|
+
/** Path relative to the workspace root. */
|
|
12
|
+
readonly path: string;
|
|
13
|
+
readonly contents: string;
|
|
14
|
+
/**
|
|
15
|
+
* What to do when the file already exists.
|
|
16
|
+
* - `error` (default): the command fails; nothing is written.
|
|
17
|
+
* - `skip`: leave the existing file — how a generator stays idempotent.
|
|
18
|
+
* - `overwrite`: replace it, for files the CLI owns outright.
|
|
19
|
+
*/
|
|
20
|
+
readonly onConflict?: 'error' | 'skip' | 'overwrite';
|
|
21
|
+
}
|
|
22
|
+
/** An ordered, de-duplicated set of files to write. */
|
|
23
|
+
export declare class FileTree {
|
|
24
|
+
#private;
|
|
25
|
+
add(file: PlannedFile): this;
|
|
26
|
+
addAll(files: readonly PlannedFile[]): this;
|
|
27
|
+
get files(): readonly PlannedFile[];
|
|
28
|
+
get paths(): readonly string[];
|
|
29
|
+
get size(): number;
|
|
30
|
+
}
|
|
31
|
+
export interface CommitOptions {
|
|
32
|
+
readonly root: string;
|
|
33
|
+
/** Report what would be written without touching the disk. */
|
|
34
|
+
readonly dryRun?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface CommitResult {
|
|
37
|
+
readonly written: readonly string[];
|
|
38
|
+
readonly skipped: readonly string[];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Write a tree, all or nothing.
|
|
42
|
+
*
|
|
43
|
+
* Files that already exist are backed up before being replaced, and everything
|
|
44
|
+
* is rolled back if any write fails — so an interrupted `nage new app` leaves
|
|
45
|
+
* the workspace exactly as it was.
|
|
46
|
+
*/
|
|
47
|
+
export declare function commitFileTree(tree: FileTree, options: CommitOptions): Promise<CommitResult>;
|
|
48
|
+
/**
|
|
49
|
+
* Join and verify the result stays under the root.
|
|
50
|
+
*
|
|
51
|
+
* A generated name reaches this function; `../../etc/passwd` must not be a way
|
|
52
|
+
* for one to escape the workspace.
|
|
53
|
+
*/
|
|
54
|
+
export declare function safeJoin(root: string, path: string): string;
|
|
55
|
+
/** Move a directory aside instead of deleting it — used by `remove app`. */
|
|
56
|
+
export declare function archiveDirectory(root: string, path: string): Promise<string>;
|
|
57
|
+
//# sourceMappingURL=file-tree.d.ts.map
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The set of files a command intends to write, and the transaction that writes
|
|
4
|
+
* them (PLAN.md §10.5 — "atomically").
|
|
5
|
+
*
|
|
6
|
+
* Commands build a `FileTree` and hand it to `commitFileTree`, which writes
|
|
7
|
+
* everything or nothing. A half-created app — registered in the manifest but
|
|
8
|
+
* missing its module, or vice versa — is worse than a failed command, because
|
|
9
|
+
* the next command starts from an inconsistent workspace.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.FileTree = void 0;
|
|
13
|
+
exports.commitFileTree = commitFileTree;
|
|
14
|
+
exports.safeJoin = safeJoin;
|
|
15
|
+
exports.archiveDirectory = archiveDirectory;
|
|
16
|
+
const promises_1 = require("node:fs/promises");
|
|
17
|
+
const node_fs_1 = require("node:fs");
|
|
18
|
+
const node_path_1 = require("node:path");
|
|
19
|
+
const core_1 = require("@nage-api/core");
|
|
20
|
+
/** An ordered, de-duplicated set of files to write. */
|
|
21
|
+
class FileTree {
|
|
22
|
+
#files = new Map();
|
|
23
|
+
add(file) {
|
|
24
|
+
const normalised = file.path.split('\\').join('/');
|
|
25
|
+
const existing = this.#files.get(normalised);
|
|
26
|
+
if (existing !== undefined && existing.contents !== file.contents) {
|
|
27
|
+
throw new core_1.ConfigurationError({
|
|
28
|
+
detail: `Two different contents were planned for "${normalised}"`,
|
|
29
|
+
meta: { path: normalised },
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
this.#files.set(normalised, { ...file, path: normalised });
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
addAll(files) {
|
|
36
|
+
for (const file of files)
|
|
37
|
+
this.add(file);
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
get files() {
|
|
41
|
+
return [...this.#files.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
42
|
+
}
|
|
43
|
+
get paths() {
|
|
44
|
+
return this.files.map((file) => file.path);
|
|
45
|
+
}
|
|
46
|
+
get size() {
|
|
47
|
+
return this.#files.size;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.FileTree = FileTree;
|
|
51
|
+
/**
|
|
52
|
+
* Write a tree, all or nothing.
|
|
53
|
+
*
|
|
54
|
+
* Files that already exist are backed up before being replaced, and everything
|
|
55
|
+
* is rolled back if any write fails — so an interrupted `nage new app` leaves
|
|
56
|
+
* the workspace exactly as it was.
|
|
57
|
+
*/
|
|
58
|
+
async function commitFileTree(tree, options) {
|
|
59
|
+
const root = (0, node_path_1.resolve)(options.root);
|
|
60
|
+
const written = [];
|
|
61
|
+
const skipped = [];
|
|
62
|
+
const created = [];
|
|
63
|
+
const backups = new Map();
|
|
64
|
+
// Refuse the whole batch before writing anything, rather than discovering a
|
|
65
|
+
// conflict half way through.
|
|
66
|
+
for (const file of tree.files) {
|
|
67
|
+
const absolute = safeJoin(root, file.path);
|
|
68
|
+
if (!(0, node_fs_1.existsSync)(absolute))
|
|
69
|
+
continue;
|
|
70
|
+
const policy = file.onConflict ?? 'error';
|
|
71
|
+
if (policy === 'error') {
|
|
72
|
+
throw new core_1.ConfigurationError({
|
|
73
|
+
detail: `"${file.path}" already exists; refusing to overwrite it`,
|
|
74
|
+
meta: { path: file.path, hint: 'Remove the file, or re-run with --force.' },
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (policy === 'skip')
|
|
78
|
+
skipped.push(file.path);
|
|
79
|
+
}
|
|
80
|
+
if (options.dryRun === true) {
|
|
81
|
+
return { written: tree.paths.filter((path) => !skipped.includes(path)), skipped };
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
for (const file of tree.files) {
|
|
85
|
+
if (skipped.includes(file.path))
|
|
86
|
+
continue;
|
|
87
|
+
const absolute = safeJoin(root, file.path);
|
|
88
|
+
await (0, promises_1.mkdir)((0, node_path_1.dirname)(absolute), { recursive: true });
|
|
89
|
+
if ((0, node_fs_1.existsSync)(absolute)) {
|
|
90
|
+
backups.set(absolute, await (0, promises_1.readFile)(absolute, 'utf8'));
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
created.push(absolute);
|
|
94
|
+
}
|
|
95
|
+
await (0, promises_1.writeFile)(absolute, file.contents, 'utf8');
|
|
96
|
+
written.push(file.path);
|
|
97
|
+
}
|
|
98
|
+
return { written, skipped };
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
await rollback(created, backups);
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function rollback(created, backups) {
|
|
106
|
+
for (const path of created)
|
|
107
|
+
await (0, promises_1.rm)(path, { force: true });
|
|
108
|
+
for (const [path, contents] of backups)
|
|
109
|
+
await (0, promises_1.writeFile)(path, contents, 'utf8');
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Join and verify the result stays under the root.
|
|
113
|
+
*
|
|
114
|
+
* A generated name reaches this function; `../../etc/passwd` must not be a way
|
|
115
|
+
* for one to escape the workspace.
|
|
116
|
+
*/
|
|
117
|
+
function safeJoin(root, path) {
|
|
118
|
+
const absolute = (0, node_path_1.resolve)((0, node_path_1.join)(root, path));
|
|
119
|
+
const relation = (0, node_path_1.relative)((0, node_path_1.resolve)(root), absolute);
|
|
120
|
+
if (relation.startsWith('..')) {
|
|
121
|
+
throw new core_1.ConfigurationError({
|
|
122
|
+
detail: `"${path}" resolves outside the workspace`,
|
|
123
|
+
meta: { path },
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return absolute;
|
|
127
|
+
}
|
|
128
|
+
/** Move a directory aside instead of deleting it — used by `remove app`. */
|
|
129
|
+
async function archiveDirectory(root, path) {
|
|
130
|
+
const source = safeJoin(root, path);
|
|
131
|
+
const destination = `${source}.removed`;
|
|
132
|
+
await (0, promises_1.rm)(destination, { recursive: true, force: true });
|
|
133
|
+
await (0, promises_1.rename)(source, destination);
|
|
134
|
+
return (0, node_path_1.relative)(root, destination);
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=file-tree.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nage-api/cli` — the scaffolding and workspace-management tool (PLAN.md §10).
|
|
3
|
+
*
|
|
4
|
+
* The package is a library first and an executable second: every command is a
|
|
5
|
+
* pure planning function returning the files it would write, and `bin/nage`
|
|
6
|
+
* (`main.ts`) is a thin process wrapper. That split is what makes the CLI
|
|
7
|
+
* testable without a subprocess, and reusable from an editor extension or a
|
|
8
|
+
* higher-level generator.
|
|
9
|
+
*/
|
|
10
|
+
export { run, usage, CLI_VERSION, type CliIo, type RunOptions } from './cli.js';
|
|
11
|
+
export { deriveNames, pluralise, type Names } from './naming.js';
|
|
12
|
+
export { FileTree, archiveDirectory, commitFileTree, safeJoin, type CommitOptions, type CommitResult, type PlannedFile, } from './fs/file-tree.js';
|
|
13
|
+
export { MANIFEST_FILE, DEFAULT_PORT, createManifest, findWorkspaceRoot, loadWorkspace, nextAvailablePort, parseManifest, resolveTargetApp, serialiseManifest, type AppEntry, type FeatureName, type PackageEntry, type Preset, type WorkspaceManifest, } from './workspace/manifest.js';
|
|
14
|
+
export { addPathAlias, addProjectReference, addTurboTaskOutput, addWorkspaceGlob, removePathAlias, removeProjectReference, } from './workspace/wiring.js';
|
|
15
|
+
export { DEFAULT_FRAMEWORK_VERSION, planCreate, planNewApp, planNewPackage, type CommandPlan, type CreateOptions, type NewAppOptions, type NewPackageOptions, } from './commands/create.js';
|
|
16
|
+
export { SCHEMATICS, planGenerate, type GenerateOptions, type GenerateResult, type Schematic, } from './commands/generate.js';
|
|
17
|
+
export { FEATURES, formatInfo, formatList, planAddFeature, planRemoveApp, planRemoveFeature, planRemovePackage, selectApps, type FeatureChange, type FeatureOptions, type RemoveAppOptions, } from './commands/features.js';
|
|
18
|
+
export { checkWorkspaceIntegrity, readDotEnv, runDoctor, type DoctorOptions, type DoctorReport, } from './commands/doctor.js';
|
|
19
|
+
export { appFiles, featurePackage, isHttpPreset, type AppTemplateInput, } from './templates/app.template.js';
|
|
20
|
+
export { compactTimestamp, parseFields, resourceFiles, type FieldSpec, type ResourceTemplateInput, } from './templates/resource.template.js';
|
|
21
|
+
export { driverPackageFor, json, workspaceFiles, type WorkspaceTemplateInput, } from './templates/workspace.template.js';
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `@nage-api/cli` — the scaffolding and workspace-management tool (PLAN.md §10).
|
|
4
|
+
*
|
|
5
|
+
* The package is a library first and an executable second: every command is a
|
|
6
|
+
* pure planning function returning the files it would write, and `bin/nage`
|
|
7
|
+
* (`main.ts`) is a thin process wrapper. That split is what makes the CLI
|
|
8
|
+
* testable without a subprocess, and reusable from an editor extension or a
|
|
9
|
+
* higher-level generator.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.workspaceFiles = exports.json = exports.driverPackageFor = exports.resourceFiles = exports.parseFields = exports.compactTimestamp = exports.isHttpPreset = exports.featurePackage = exports.appFiles = exports.runDoctor = exports.readDotEnv = exports.checkWorkspaceIntegrity = exports.selectApps = exports.planRemovePackage = exports.planRemoveFeature = exports.planRemoveApp = exports.planAddFeature = exports.formatList = exports.formatInfo = exports.FEATURES = exports.planGenerate = exports.SCHEMATICS = exports.planNewPackage = exports.planNewApp = exports.planCreate = exports.DEFAULT_FRAMEWORK_VERSION = exports.removeProjectReference = exports.removePathAlias = exports.addWorkspaceGlob = exports.addTurboTaskOutput = exports.addProjectReference = exports.addPathAlias = exports.serialiseManifest = exports.resolveTargetApp = exports.parseManifest = exports.nextAvailablePort = exports.loadWorkspace = exports.findWorkspaceRoot = exports.createManifest = exports.DEFAULT_PORT = exports.MANIFEST_FILE = exports.safeJoin = exports.commitFileTree = exports.archiveDirectory = exports.FileTree = exports.pluralise = exports.deriveNames = exports.CLI_VERSION = exports.usage = exports.run = void 0;
|
|
13
|
+
var cli_js_1 = require("./cli.js");
|
|
14
|
+
Object.defineProperty(exports, "run", { enumerable: true, get: function () { return cli_js_1.run; } });
|
|
15
|
+
Object.defineProperty(exports, "usage", { enumerable: true, get: function () { return cli_js_1.usage; } });
|
|
16
|
+
Object.defineProperty(exports, "CLI_VERSION", { enumerable: true, get: function () { return cli_js_1.CLI_VERSION; } });
|
|
17
|
+
// Naming — one word in, every casing out.
|
|
18
|
+
var naming_js_1 = require("./naming.js");
|
|
19
|
+
Object.defineProperty(exports, "deriveNames", { enumerable: true, get: function () { return naming_js_1.deriveNames; } });
|
|
20
|
+
Object.defineProperty(exports, "pluralise", { enumerable: true, get: function () { return naming_js_1.pluralise; } });
|
|
21
|
+
// The write transaction.
|
|
22
|
+
var file_tree_js_1 = require("./fs/file-tree.js");
|
|
23
|
+
Object.defineProperty(exports, "FileTree", { enumerable: true, get: function () { return file_tree_js_1.FileTree; } });
|
|
24
|
+
Object.defineProperty(exports, "archiveDirectory", { enumerable: true, get: function () { return file_tree_js_1.archiveDirectory; } });
|
|
25
|
+
Object.defineProperty(exports, "commitFileTree", { enumerable: true, get: function () { return file_tree_js_1.commitFileTree; } });
|
|
26
|
+
Object.defineProperty(exports, "safeJoin", { enumerable: true, get: function () { return file_tree_js_1.safeJoin; } });
|
|
27
|
+
// The workspace manifest and the files that must stay in step with it.
|
|
28
|
+
var manifest_js_1 = require("./workspace/manifest.js");
|
|
29
|
+
Object.defineProperty(exports, "MANIFEST_FILE", { enumerable: true, get: function () { return manifest_js_1.MANIFEST_FILE; } });
|
|
30
|
+
Object.defineProperty(exports, "DEFAULT_PORT", { enumerable: true, get: function () { return manifest_js_1.DEFAULT_PORT; } });
|
|
31
|
+
Object.defineProperty(exports, "createManifest", { enumerable: true, get: function () { return manifest_js_1.createManifest; } });
|
|
32
|
+
Object.defineProperty(exports, "findWorkspaceRoot", { enumerable: true, get: function () { return manifest_js_1.findWorkspaceRoot; } });
|
|
33
|
+
Object.defineProperty(exports, "loadWorkspace", { enumerable: true, get: function () { return manifest_js_1.loadWorkspace; } });
|
|
34
|
+
Object.defineProperty(exports, "nextAvailablePort", { enumerable: true, get: function () { return manifest_js_1.nextAvailablePort; } });
|
|
35
|
+
Object.defineProperty(exports, "parseManifest", { enumerable: true, get: function () { return manifest_js_1.parseManifest; } });
|
|
36
|
+
Object.defineProperty(exports, "resolveTargetApp", { enumerable: true, get: function () { return manifest_js_1.resolveTargetApp; } });
|
|
37
|
+
Object.defineProperty(exports, "serialiseManifest", { enumerable: true, get: function () { return manifest_js_1.serialiseManifest; } });
|
|
38
|
+
var wiring_js_1 = require("./workspace/wiring.js");
|
|
39
|
+
Object.defineProperty(exports, "addPathAlias", { enumerable: true, get: function () { return wiring_js_1.addPathAlias; } });
|
|
40
|
+
Object.defineProperty(exports, "addProjectReference", { enumerable: true, get: function () { return wiring_js_1.addProjectReference; } });
|
|
41
|
+
Object.defineProperty(exports, "addTurboTaskOutput", { enumerable: true, get: function () { return wiring_js_1.addTurboTaskOutput; } });
|
|
42
|
+
Object.defineProperty(exports, "addWorkspaceGlob", { enumerable: true, get: function () { return wiring_js_1.addWorkspaceGlob; } });
|
|
43
|
+
Object.defineProperty(exports, "removePathAlias", { enumerable: true, get: function () { return wiring_js_1.removePathAlias; } });
|
|
44
|
+
Object.defineProperty(exports, "removeProjectReference", { enumerable: true, get: function () { return wiring_js_1.removeProjectReference; } });
|
|
45
|
+
// Commands, as plans.
|
|
46
|
+
var create_js_1 = require("./commands/create.js");
|
|
47
|
+
Object.defineProperty(exports, "DEFAULT_FRAMEWORK_VERSION", { enumerable: true, get: function () { return create_js_1.DEFAULT_FRAMEWORK_VERSION; } });
|
|
48
|
+
Object.defineProperty(exports, "planCreate", { enumerable: true, get: function () { return create_js_1.planCreate; } });
|
|
49
|
+
Object.defineProperty(exports, "planNewApp", { enumerable: true, get: function () { return create_js_1.planNewApp; } });
|
|
50
|
+
Object.defineProperty(exports, "planNewPackage", { enumerable: true, get: function () { return create_js_1.planNewPackage; } });
|
|
51
|
+
var generate_js_1 = require("./commands/generate.js");
|
|
52
|
+
Object.defineProperty(exports, "SCHEMATICS", { enumerable: true, get: function () { return generate_js_1.SCHEMATICS; } });
|
|
53
|
+
Object.defineProperty(exports, "planGenerate", { enumerable: true, get: function () { return generate_js_1.planGenerate; } });
|
|
54
|
+
var features_js_1 = require("./commands/features.js");
|
|
55
|
+
Object.defineProperty(exports, "FEATURES", { enumerable: true, get: function () { return features_js_1.FEATURES; } });
|
|
56
|
+
Object.defineProperty(exports, "formatInfo", { enumerable: true, get: function () { return features_js_1.formatInfo; } });
|
|
57
|
+
Object.defineProperty(exports, "formatList", { enumerable: true, get: function () { return features_js_1.formatList; } });
|
|
58
|
+
Object.defineProperty(exports, "planAddFeature", { enumerable: true, get: function () { return features_js_1.planAddFeature; } });
|
|
59
|
+
Object.defineProperty(exports, "planRemoveApp", { enumerable: true, get: function () { return features_js_1.planRemoveApp; } });
|
|
60
|
+
Object.defineProperty(exports, "planRemoveFeature", { enumerable: true, get: function () { return features_js_1.planRemoveFeature; } });
|
|
61
|
+
Object.defineProperty(exports, "planRemovePackage", { enumerable: true, get: function () { return features_js_1.planRemovePackage; } });
|
|
62
|
+
Object.defineProperty(exports, "selectApps", { enumerable: true, get: function () { return features_js_1.selectApps; } });
|
|
63
|
+
var doctor_js_1 = require("./commands/doctor.js");
|
|
64
|
+
Object.defineProperty(exports, "checkWorkspaceIntegrity", { enumerable: true, get: function () { return doctor_js_1.checkWorkspaceIntegrity; } });
|
|
65
|
+
Object.defineProperty(exports, "readDotEnv", { enumerable: true, get: function () { return doctor_js_1.readDotEnv; } });
|
|
66
|
+
Object.defineProperty(exports, "runDoctor", { enumerable: true, get: function () { return doctor_js_1.runDoctor; } });
|
|
67
|
+
// Templates, so a project can generate the same files from its own tooling.
|
|
68
|
+
var app_template_js_1 = require("./templates/app.template.js");
|
|
69
|
+
Object.defineProperty(exports, "appFiles", { enumerable: true, get: function () { return app_template_js_1.appFiles; } });
|
|
70
|
+
Object.defineProperty(exports, "featurePackage", { enumerable: true, get: function () { return app_template_js_1.featurePackage; } });
|
|
71
|
+
Object.defineProperty(exports, "isHttpPreset", { enumerable: true, get: function () { return app_template_js_1.isHttpPreset; } });
|
|
72
|
+
var resource_template_js_1 = require("./templates/resource.template.js");
|
|
73
|
+
Object.defineProperty(exports, "compactTimestamp", { enumerable: true, get: function () { return resource_template_js_1.compactTimestamp; } });
|
|
74
|
+
Object.defineProperty(exports, "parseFields", { enumerable: true, get: function () { return resource_template_js_1.parseFields; } });
|
|
75
|
+
Object.defineProperty(exports, "resourceFiles", { enumerable: true, get: function () { return resource_template_js_1.resourceFiles; } });
|
|
76
|
+
var workspace_template_js_1 = require("./templates/workspace.template.js");
|
|
77
|
+
Object.defineProperty(exports, "driverPackageFor", { enumerable: true, get: function () { return workspace_template_js_1.driverPackageFor; } });
|
|
78
|
+
Object.defineProperty(exports, "json", { enumerable: true, get: function () { return workspace_template_js_1.json; } });
|
|
79
|
+
Object.defineProperty(exports, "workspaceFiles", { enumerable: true, get: function () { return workspace_template_js_1.workspaceFiles; } });
|
|
80
|
+
//# sourceMappingURL=index.js.map
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The `nage` executable.
|
|
4
|
+
*
|
|
5
|
+
* This file is the only place in the package that reads `process` or sets an
|
|
6
|
+
* exit code: it turns the process environment into `RunOptions` and the
|
|
7
|
+
* returned code into `process.exitCode`. Everything else is a function a test
|
|
8
|
+
* can call directly.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=main.d.ts.map
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* The `nage` executable.
|
|
5
|
+
*
|
|
6
|
+
* This file is the only place in the package that reads `process` or sets an
|
|
7
|
+
* exit code: it turns the process environment into `RunOptions` and the
|
|
8
|
+
* returned code into `process.exitCode`. Everything else is a function a test
|
|
9
|
+
* can call directly.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const node_fs_1 = require("node:fs");
|
|
13
|
+
const node_path_1 = require("node:path");
|
|
14
|
+
const cli_js_1 = require("./cli.js");
|
|
15
|
+
(0, cli_js_1.run)({
|
|
16
|
+
argv: process.argv.slice(2),
|
|
17
|
+
cwd: process.cwd(),
|
|
18
|
+
version: readVersion(),
|
|
19
|
+
io: {
|
|
20
|
+
out: (message) => void process.stdout.write(message),
|
|
21
|
+
err: (message) => void process.stderr.write(message),
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
.then((code) => {
|
|
25
|
+
// Setting `exitCode` rather than calling `exit()` lets buffered stdout
|
|
26
|
+
// flush; `process.exit()` truncates piped output.
|
|
27
|
+
process.exitCode = code;
|
|
28
|
+
})
|
|
29
|
+
.catch((error) => {
|
|
30
|
+
process.stderr.write(`✖ ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`);
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
});
|
|
33
|
+
/** The installed version, so `nage info` and bug reports agree with npm. */
|
|
34
|
+
function readVersion() {
|
|
35
|
+
try {
|
|
36
|
+
const manifest = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, '..', 'package.json'), 'utf8'));
|
|
37
|
+
return manifest.version ?? '0.0.0';
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return '0.0.0';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=main.js.map
|
package/dist/naming.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Name derivation for generators (PLAN.md §10.3).
|
|
3
|
+
*
|
|
4
|
+
* A developer types `nage g resource product` once; from that single word the
|
|
5
|
+
* generator needs a class name, a file stem, a route, a table and a variable.
|
|
6
|
+
* Deriving them in one place is what keeps `ProductModule`, `product.module.ts`
|
|
7
|
+
* and `/products` consistent across every emitted file.
|
|
8
|
+
*/
|
|
9
|
+
/** Every casing of one name. */
|
|
10
|
+
export interface Names {
|
|
11
|
+
/** As typed, normalised: `product-category`. */
|
|
12
|
+
readonly kebab: string;
|
|
13
|
+
/** `ProductCategory` — class names. */
|
|
14
|
+
readonly pascal: string;
|
|
15
|
+
/** `productCategory` — variables and properties. */
|
|
16
|
+
readonly camel: string;
|
|
17
|
+
/** `product_category` — columns and tables. */
|
|
18
|
+
readonly snake: string;
|
|
19
|
+
/** `product-categories` — route segments. */
|
|
20
|
+
readonly pluralKebab: string;
|
|
21
|
+
/** `product_categories` — table names. */
|
|
22
|
+
readonly pluralSnake: string;
|
|
23
|
+
/** `Product category` — human-readable, for docs and messages. */
|
|
24
|
+
readonly title: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Naive English pluralisation.
|
|
28
|
+
*
|
|
29
|
+
* Deliberately naive: it covers the cases a resource name actually hits, and a
|
|
30
|
+
* wrong guess is fixed by `--route`. Pulling in an inflection library to get
|
|
31
|
+
* `octopus` right is not worth the dependency.
|
|
32
|
+
*/
|
|
33
|
+
export declare function pluralise(word: string): string;
|
|
34
|
+
/** Derive every casing of `name`, or reject it with a readable reason. */
|
|
35
|
+
export declare function deriveNames(name: string): Names;
|
|
36
|
+
//# sourceMappingURL=naming.d.ts.map
|
package/dist/naming.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Name derivation for generators (PLAN.md §10.3).
|
|
4
|
+
*
|
|
5
|
+
* A developer types `nage g resource product` once; from that single word the
|
|
6
|
+
* generator needs a class name, a file stem, a route, a table and a variable.
|
|
7
|
+
* Deriving them in one place is what keeps `ProductModule`, `product.module.ts`
|
|
8
|
+
* and `/products` consistent across every emitted file.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.pluralise = pluralise;
|
|
12
|
+
exports.deriveNames = deriveNames;
|
|
13
|
+
const core_1 = require("@nage-api/core");
|
|
14
|
+
/** Names must be usable as identifiers, file names and URL segments. */
|
|
15
|
+
const VALID_NAME = /^[a-z][a-z0-9]*(?:[-_ ]?[a-zA-Z0-9]+)*$/;
|
|
16
|
+
/** Reserved because they collide with framework or workspace directories. */
|
|
17
|
+
const RESERVED = new Set(['node_modules', 'dist', 'src', 'test', 'apps', 'packages', 'database']);
|
|
18
|
+
function words(name) {
|
|
19
|
+
return name
|
|
20
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
21
|
+
.split(/[-_\s]+/)
|
|
22
|
+
.filter((word) => word.length > 0)
|
|
23
|
+
.map((word) => word.toLowerCase());
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Naive English pluralisation.
|
|
27
|
+
*
|
|
28
|
+
* Deliberately naive: it covers the cases a resource name actually hits, and a
|
|
29
|
+
* wrong guess is fixed by `--route`. Pulling in an inflection library to get
|
|
30
|
+
* `octopus` right is not worth the dependency.
|
|
31
|
+
*/
|
|
32
|
+
function pluralise(word) {
|
|
33
|
+
if (/(?:s|x|z|ch|sh)$/.test(word))
|
|
34
|
+
return `${word}es`;
|
|
35
|
+
if (/[^aeiou]y$/.test(word))
|
|
36
|
+
return `${word.slice(0, -1)}ies`;
|
|
37
|
+
return `${word}s`;
|
|
38
|
+
}
|
|
39
|
+
/** Derive every casing of `name`, or reject it with a readable reason. */
|
|
40
|
+
function deriveNames(name) {
|
|
41
|
+
const trimmed = name.trim();
|
|
42
|
+
if (!VALID_NAME.test(trimmed)) {
|
|
43
|
+
throw new core_1.ConfigurationError({
|
|
44
|
+
detail: `"${name}" is not a usable name. Start with a lowercase letter and use ` +
|
|
45
|
+
`letters, digits, hyphens or underscores — it becomes a class name, a ` +
|
|
46
|
+
`file name and a URL segment.`,
|
|
47
|
+
meta: { name },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (RESERVED.has(trimmed)) {
|
|
51
|
+
throw new core_1.ConfigurationError({
|
|
52
|
+
detail: `"${trimmed}" is reserved; it would collide with a workspace directory.`,
|
|
53
|
+
meta: { name: trimmed },
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const parts = words(trimmed);
|
|
57
|
+
const last = parts[parts.length - 1] ?? trimmed;
|
|
58
|
+
const plural = [...parts.slice(0, -1), pluralise(last)];
|
|
59
|
+
return {
|
|
60
|
+
kebab: parts.join('-'),
|
|
61
|
+
pascal: parts.map(capitalise).join(''),
|
|
62
|
+
camel: parts.map((word, index) => (index === 0 ? word : capitalise(word))).join(''),
|
|
63
|
+
snake: parts.join('_'),
|
|
64
|
+
pluralKebab: plural.join('-'),
|
|
65
|
+
pluralSnake: plural.join('_'),
|
|
66
|
+
title: capitalise(parts.join(' ')),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function capitalise(word) {
|
|
70
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=naming.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Files for one application (PLAN.md §9.1, §10.2).
|
|
3
|
+
*
|
|
4
|
+
* Four presets, one shape: a thin `main.ts` that calls `bootstrap`, a typed
|
|
5
|
+
* `nage.config.ts`, a zod env schema, health endpoints and a Dockerfile. What
|
|
6
|
+
* differs between presets is which modules are imported and whether an HTTP
|
|
7
|
+
* server is started at all.
|
|
8
|
+
*/
|
|
9
|
+
import type { PlannedFile } from '../fs/file-tree.js';
|
|
10
|
+
import type { AppEntry, FeatureName, Preset, WorkspaceManifest } from '../workspace/manifest.js';
|
|
11
|
+
export interface AppTemplateInput {
|
|
12
|
+
readonly app: AppEntry;
|
|
13
|
+
readonly manifest: WorkspaceManifest;
|
|
14
|
+
}
|
|
15
|
+
export declare function featurePackage(feature: FeatureName): string;
|
|
16
|
+
/** A worker has no HTTP surface, so it neither binds a port nor gets CORS. */
|
|
17
|
+
export declare function isHttpPreset(preset: Preset): boolean;
|
|
18
|
+
export declare function appFiles(input: AppTemplateInput): PlannedFile[];
|
|
19
|
+
//# sourceMappingURL=app.template.d.ts.map
|