@avijs/cli 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,11 +1,19 @@
1
1
  # @avijs/cli
2
2
 
3
- The avi command: avi new, avi dev, avi check and avi manifest.
3
+ The avi command for Avi.js projects.
4
4
 
5
5
  ```sh
6
6
  npm install -D @avijs/cli
7
7
  ```
8
8
 
9
+ | Command | What it does |
10
+ | ------------------------- | --------------------------------------------------------------------------------- |
11
+ | `avi new <name>` | Creates a project (`--template modular` or `minimal`). Also `npm create avi` |
12
+ | `avi g <kind> <path>` | Adds a `module`, `controller` or `service` and registers it in the nearest module |
13
+ | `avi dev` | Compiles, then recompiles and restarts on every change |
14
+ | `avi check` | Type-checks every `static inject` list |
15
+ | `avi manifest <entry.js>` | Writes the app's modules, providers and routes as JSON |
16
+
9
17
  Part of [Avi.js](https://github.com/chrischase011/avi-js), a TypeScript framework for Node.js with
10
18
  modules, controllers and dependency injection, without `reflect-metadata`. See the
11
19
  [tutorial](https://github.com/chrischase011/avi-js/blob/main/docs/tutorial/README.md) to get started.
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAwBA,wBAAsB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAcnE"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AA0BA,wBAAsB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAiBnE"}
package/dist/cli.js CHANGED
@@ -5,6 +5,7 @@ import { mkdir, readdir, writeFile } from 'node:fs/promises';
5
5
  import { createRequire } from 'node:module';
6
6
  import path from 'node:path';
7
7
  import { parseArgs } from 'node:util';
8
+ import { GENERATE_USAGE, generate } from './generate.js';
8
9
  import { templateFiles } from './templates.js';
9
10
  const { version } = createRequire(import.meta.url)('../package.json');
10
11
  const USAGE = `Usage:
@@ -13,6 +14,7 @@ const USAGE = `Usage:
13
14
  minimal uses plain routes and services. --from points at a folder of packed
14
15
  @avijs tarballs (pnpm pack) to install instead of the registry. --avi-version
15
16
  defaults to this CLI's version.
17
+ ${GENERATE_USAGE}
16
18
  avi dev [--project tsconfig.json] [--entry dist/main.js]
17
19
  Compile, then recompile and restart on every change.
18
20
  avi check [--project tsconfig.json]
@@ -24,6 +26,9 @@ export async function main(argv) {
24
26
  switch (command) {
25
27
  case 'new':
26
28
  return create(rest);
29
+ case 'generate':
30
+ case 'g':
31
+ return generate(rest);
27
32
  case 'dev':
28
33
  return dev(rest);
29
34
  case 'check':
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare const GENERATE_USAGE = " avi generate <module|controller|service> <path> [--root .] [--dry-run]\n Create a module, controller or service and register it. Short: avi g co admin/posts.\n Modular projects get src/modules/<path>/<name>.<kind>.ts, registered in the nearest\n module; minimal projects get src/routes/<path>.ts or src/services/<path>.ts, registered\n in src/app.ts.";
2
+ export declare function generate(args: readonly string[]): Promise<number>;
3
+ //# sourceMappingURL=generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAmBA,eAAO,MAAM,cAAc,yXAIN,CAAC;AAiDtB,wBAAsB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAmDvE"}
@@ -0,0 +1,308 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { parseArgs } from 'node:util';
5
+ import ts from 'typescript';
6
+ const KINDS = {
7
+ module: 'module',
8
+ mo: 'module',
9
+ controller: 'controller',
10
+ co: 'controller',
11
+ service: 'service',
12
+ s: 'service',
13
+ };
14
+ const SEGMENT = /^[a-z][a-z0-9-]*$/;
15
+ export const GENERATE_USAGE = ` avi generate <module|controller|service> <path> [--root .] [--dry-run]
16
+ Create a module, controller or service and register it. Short: avi g co admin/posts.
17
+ Modular projects get src/modules/<path>/<name>.<kind>.ts, registered in the nearest
18
+ module; minimal projects get src/routes/<path>.ts or src/services/<path>.ts, registered
19
+ in src/app.ts.`;
20
+ /** Files to write, keyed by absolute path. Reads fall back to disk. */
21
+ class Plan {
22
+ #files = new Map();
23
+ notes = [];
24
+ exists(file) {
25
+ return this.#files.has(file) || existsSync(file);
26
+ }
27
+ async read(file) {
28
+ return this.#files.get(file)?.content ?? (await readFile(file, 'utf8'));
29
+ }
30
+ create(file, content) {
31
+ if (this.exists(file))
32
+ throw new GenerateError(`${file} already exists`);
33
+ this.#files.set(file, { content, created: true });
34
+ }
35
+ update(file, content) {
36
+ const existing = this.#files.get(file);
37
+ this.#files.set(file, { content, created: existing?.created ?? false });
38
+ }
39
+ async apply(root, dryRun) {
40
+ for (const [file, { content, created }] of this.#files) {
41
+ console.log(`${created ? 'CREATE' : 'UPDATE'} ${path.relative(root, file).replaceAll('\\', '/')}`);
42
+ if (dryRun)
43
+ continue;
44
+ await mkdir(path.dirname(file), { recursive: true });
45
+ await writeFile(file, content);
46
+ }
47
+ for (const note of this.notes)
48
+ console.log(note);
49
+ }
50
+ }
51
+ class GenerateError extends Error {
52
+ }
53
+ export async function generate(args) {
54
+ const { values, positionals } = parseArgs({
55
+ args: [...args],
56
+ allowPositionals: true,
57
+ options: {
58
+ root: { type: 'string', default: '.' },
59
+ 'dry-run': { type: 'boolean', default: false },
60
+ },
61
+ });
62
+ const [kindArg, target] = positionals;
63
+ const kind = kindArg === undefined ? undefined : KINDS[kindArg];
64
+ if (!kind || !target) {
65
+ console.error(`Usage:\n${GENERATE_USAGE}`);
66
+ return 2;
67
+ }
68
+ const segments = target.split('/').filter(Boolean);
69
+ const bad = segments.find((segment) => !SEGMENT.test(segment));
70
+ if (segments.length === 0 || bad !== undefined) {
71
+ console.error(`Invalid path "${target}": use lowercase names with letters, digits and "-", separated by "/"`);
72
+ return 2;
73
+ }
74
+ const root = path.resolve(values.root);
75
+ const src = path.join(root, 'src');
76
+ const layout = existsSync(path.join(src, 'app.module.ts'))
77
+ ? 'modular'
78
+ : existsSync(path.join(src, 'app.ts'))
79
+ ? 'minimal'
80
+ : undefined;
81
+ if (!layout) {
82
+ console.error(`No src/app.module.ts or src/app.ts in ${root}. Run avi generate in an Avi.js project, or pass --root.`);
83
+ return 1;
84
+ }
85
+ const names = toNames(segments.at(-1));
86
+ const dirs = segments.slice(0, -1);
87
+ const plan = new Plan();
88
+ try {
89
+ if (layout === 'modular')
90
+ await modular(plan, src, kind, dirs, names);
91
+ else
92
+ await minimal(plan, src, kind, dirs, names);
93
+ }
94
+ catch (error) {
95
+ if (!(error instanceof GenerateError))
96
+ throw error;
97
+ console.error(error.message);
98
+ return 1;
99
+ }
100
+ await plan.apply(root, values['dry-run']);
101
+ return 0;
102
+ }
103
+ async function modular(plan, src, kind, dirs, names) {
104
+ const folder = path.join(src, 'modules', ...dirs, names.file);
105
+ const file = path.join(folder, `${names.file}.${kind}.ts`);
106
+ const className = `${names.pascal}${capitalize(kind)}`;
107
+ if (kind === 'module') {
108
+ plan.create(file, `import { Module } from '@avijs/core';\n\n@Module({})\nexport class ${className} {}\n`);
109
+ await register(plan, nearestModule(plan, src, path.dirname(folder)), 'imports', className, file);
110
+ return;
111
+ }
112
+ if (kind === 'service') {
113
+ plan.create(file, `import { Injectable } from '@avijs/core';
114
+
115
+ @Injectable()
116
+ export class ${className} {
117
+ findAll(): unknown[] {
118
+ return [];
119
+ }
120
+ }
121
+ `);
122
+ await register(plan, nearestModule(plan, src, folder), 'providers', className, file);
123
+ return;
124
+ }
125
+ const service = path.join(folder, `${names.file}.service.ts`);
126
+ const serviceClass = `${names.pascal}Service`;
127
+ plan.create(file, plan.exists(service)
128
+ ? `import { Controller, Get } from '@avijs/core';
129
+ import { ${serviceClass} } from './${names.file}.service.js';
130
+
131
+ @Controller('/${names.file}')
132
+ export class ${className} {
133
+ static inject = [${serviceClass}] as const;
134
+
135
+ constructor(private readonly ${names.camel}: ${serviceClass}) {}
136
+
137
+ @Get()
138
+ findAll() {
139
+ return this.${names.camel}.findAll();
140
+ }
141
+ }
142
+ `
143
+ : `import { Controller, Get } from '@avijs/core';
144
+
145
+ @Controller('/${names.file}')
146
+ export class ${className} {
147
+ @Get()
148
+ findAll() {
149
+ return [];
150
+ }
151
+ }
152
+ `);
153
+ await register(plan, nearestModule(plan, src, folder), 'controllers', className, file);
154
+ }
155
+ async function minimal(plan, src, kind, dirs, names) {
156
+ if (kind === 'module') {
157
+ throw new GenerateError('This is a minimal project (src/app.ts, no src/app.module.ts), which does not use modules. Generate a controller or service, or see the project structure chapter to move to modules.');
158
+ }
159
+ const app = path.join(src, 'app.ts');
160
+ const service = path.join(src, 'services', ...dirs, `${names.file}.ts`);
161
+ const serviceClass = `${names.pascal}Service`;
162
+ if (kind === 'service') {
163
+ plan.create(service, `export class ${serviceClass} {
164
+ findAll(): unknown[] {
165
+ return [];
166
+ }
167
+ }
168
+ `);
169
+ await register(plan, app, 'providers', serviceClass, service);
170
+ return;
171
+ }
172
+ const file = path.join(src, 'routes', ...dirs, `${names.file}.ts`);
173
+ const exportName = `${names.camel}Routes`;
174
+ const serviceImport = importPath(file, service);
175
+ plan.create(file, plan.exists(service)
176
+ ? `import { defineController, route } from '@avijs/core';
177
+ import { ${serviceClass} } from '${serviceImport}';
178
+
179
+ export const ${exportName} = defineController({
180
+ prefix: '/${names.file}',
181
+ inject: [${serviceClass}],
182
+ routes: (${names.camel}) => [
183
+ route({ method: 'GET', path: '/', handler: () => ${names.camel}.findAll() }),
184
+ ],
185
+ });
186
+ `
187
+ : `import { defineController, route } from '@avijs/core';
188
+
189
+ export const ${exportName} = defineController({
190
+ prefix: '/${names.file}',
191
+ routes: [route({ method: 'GET', path: '/', handler: () => [] })],
192
+ });
193
+ `);
194
+ await register(plan, app, 'controllers', exportName, file);
195
+ }
196
+ /** The closest `*.module.ts` from `dir` up to `src/modules`, else `src/app.module.ts`. */
197
+ function nearestModule(plan, src, dir) {
198
+ const stop = path.join(src, 'modules');
199
+ for (let current = dir; current.startsWith(stop); current = path.dirname(current)) {
200
+ const own = path.join(current, `${path.basename(current)}.module.ts`);
201
+ if (plan.exists(own))
202
+ return own;
203
+ if (existsSync(current)) {
204
+ const other = readdirSync(current)
205
+ .filter((entry) => entry.endsWith('.module.ts'))
206
+ .sort()[0];
207
+ if (other)
208
+ return path.join(current, other);
209
+ }
210
+ if (current === stop)
211
+ break;
212
+ }
213
+ return path.join(src, 'app.module.ts');
214
+ }
215
+ /**
216
+ * Adds `identifier` to the `property` array of the first `@Module({...})`, `defineModule({...})`
217
+ * or `.register({...})` object in `file`, and imports it. Leaves a note when it can't.
218
+ */
219
+ async function register(plan, file, property, identifier, from) {
220
+ const where = path.basename(file);
221
+ const manual = `NOTE Add ${identifier} to ${property} in ${where} yourself (import it from '${importPath(file, from)}').`;
222
+ if (!plan.exists(file)) {
223
+ plan.notes.push(manual);
224
+ return;
225
+ }
226
+ const text = await plan.read(file);
227
+ const source = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
228
+ const object = findRegistration(source);
229
+ if (!object) {
230
+ plan.notes.push(manual);
231
+ return;
232
+ }
233
+ const edits = [];
234
+ const existing = object.properties.find((prop) => ts.isPropertyAssignment(prop) && prop.name.getText(source) === property);
235
+ if (existing) {
236
+ const array = existing.initializer;
237
+ if (!ts.isArrayLiteralExpression(array)) {
238
+ plan.notes.push(manual);
239
+ return;
240
+ }
241
+ if (array.elements.some((element) => element.getText(source) === identifier))
242
+ return;
243
+ const last = array.elements.at(-1);
244
+ edits.push(last
245
+ ? { at: last.getEnd(), text: `, ${identifier}` }
246
+ : { at: array.getStart(source) + 1, text: identifier });
247
+ }
248
+ else {
249
+ const last = object.properties.at(-1);
250
+ edits.push(last
251
+ ? { at: last.getEnd(), text: `, ${property}: [${identifier}]` }
252
+ : { at: object.getStart(source) + 1, text: ` ${property}: [${identifier}] ` });
253
+ }
254
+ const imported = source.statements.some((statement) => ts.isImportDeclaration(statement) &&
255
+ statement.importClause?.namedBindings !== undefined &&
256
+ ts.isNamedImports(statement.importClause.namedBindings) &&
257
+ statement.importClause.namedBindings.elements.some((e) => e.name.text === identifier));
258
+ if (!imported) {
259
+ const line = `import { ${identifier} } from '${importPath(file, from)}';`;
260
+ const lastImport = source.statements.filter(ts.isImportDeclaration).at(-1);
261
+ edits.push(lastImport ? { at: lastImport.getEnd(), text: `\n${line}` } : { at: 0, text: `${line}\n` });
262
+ }
263
+ let updated = text;
264
+ for (const edit of edits.sort((a, b) => b.at - a.at)) {
265
+ updated = updated.slice(0, edit.at) + edit.text + updated.slice(edit.at);
266
+ }
267
+ plan.update(file, updated);
268
+ }
269
+ function findRegistration(source) {
270
+ let found;
271
+ const visit = (node) => {
272
+ if (found)
273
+ return;
274
+ if (ts.isCallExpression(node)) {
275
+ const callee = node.expression;
276
+ const name = ts.isIdentifier(callee)
277
+ ? callee.text
278
+ : ts.isPropertyAccessExpression(callee)
279
+ ? callee.name.text
280
+ : undefined;
281
+ const [first] = node.arguments;
282
+ if ((name === 'Module' || name === 'defineModule' || name === 'register') &&
283
+ first &&
284
+ ts.isObjectLiteralExpression(first)) {
285
+ found = first;
286
+ return;
287
+ }
288
+ }
289
+ ts.forEachChild(node, visit);
290
+ };
291
+ visit(source);
292
+ return found;
293
+ }
294
+ function importPath(from, to) {
295
+ const relative = path
296
+ .relative(path.dirname(from), to)
297
+ .replaceAll('\\', '/')
298
+ .replace(/\.ts$/, '.js');
299
+ return relative.startsWith('.') ? relative : `./${relative}`;
300
+ }
301
+ function toNames(file) {
302
+ const camel = file.replace(/-([a-z0-9])/g, (_, char) => char.toUpperCase());
303
+ return { file, camel, pascal: capitalize(camel) };
304
+ }
305
+ function capitalize(value) {
306
+ return value.charAt(0).toUpperCase() + value.slice(1);
307
+ }
308
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.js","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,MAAM,YAAY,CAAC;AAK5B,MAAM,KAAK,GAAyB;IAClC,MAAM,EAAE,QAAQ;IAChB,EAAE,EAAE,QAAQ;IACZ,UAAU,EAAE,YAAY;IACxB,EAAE,EAAE,YAAY;IAChB,OAAO,EAAE,SAAS;IAClB,CAAC,EAAE,SAAS;CACb,CAAC;AACF,MAAM,OAAO,GAAG,mBAAmB,CAAC;AAEpC,MAAM,CAAC,MAAM,cAAc,GAAG;;;;qBAIT,CAAC;AAWtB,uEAAuE;AACvE,MAAM,IAAI;IACC,MAAM,GAAG,IAAI,GAAG,EAAiD,CAAC;IAClE,KAAK,GAAa,EAAE,CAAC;IAE9B,MAAM,CAAC,IAAY;QACjB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAY;QACrB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,IAAI,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAC1E,CAAC;IAED,MAAM,CAAC,IAAY,EAAE,OAAe;QAClC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,aAAa,CAAC,GAAG,IAAI,iBAAiB,CAAC,CAAC;QACzE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,CAAC,IAAY,EAAE,OAAe;QAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,IAAY,EAAE,MAAe;QACvC,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACvD,OAAO,CAAC,GAAG,CACT,GAAG,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CACtF,CAAC;YACF,IAAI,MAAM;gBAAE,SAAS;YACrB,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACrD,MAAM,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACjC,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC;CACF;AAED,MAAM,aAAc,SAAQ,KAAK;CAAG;AAEpC,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAuB;IACpD,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,SAAS,CAAC;QACxC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;QACf,gBAAgB,EAAE,IAAI;QACtB,OAAO,EAAE;YACP,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE;YACtC,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;SAC/C;KACF,CAAC,CAAC;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,WAAW,CAAC;IACtC,MAAM,IAAI,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAChE,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACrB,OAAO,CAAC,KAAK,CAAC,WAAW,cAAc,EAAE,CAAC,CAAC;QAC3C,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACnD,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QAC/C,OAAO,CAAC,KAAK,CACX,iBAAiB,MAAM,uEAAuE,CAC/F,CAAC;QACF,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACnC,MAAM,MAAM,GAAuB,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;QAC5E,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;YACpC,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,SAAS,CAAC;IAChB,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CAAC,KAAK,CACX,yCAAyC,IAAI,0DAA0D,CACxG,CAAC;QACF,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAW,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;IACxB,IAAI,CAAC;QACH,IAAI,MAAM,KAAK,SAAS;YAAE,MAAM,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;;YACjE,MAAM,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,CAAC,KAAK,YAAY,aAAa,CAAC;YAAE,MAAM,KAAK,CAAC;QACnD,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC7B,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;IAC1C,OAAO,CAAC,CAAC;AACX,CAAC;AAED,KAAK,UAAU,OAAO,CACpB,IAAU,EACV,GAAW,EACX,IAAU,EACV,IAAuB,EACvB,KAAY;IAEZ,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,IAAI,IAAI,KAAK,CAAC,CAAC;IAC3D,MAAM,SAAS,GAAG,GAAG,KAAK,CAAC,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;IAEvD,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,IAAI,CAAC,MAAM,CACT,IAAI,EACJ,sEAAsE,SAAS,OAAO,CACvF,CAAC;QACF,MAAM,QAAQ,CACZ,IAAI,EACJ,aAAa,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,EAC9C,SAAS,EACT,SAAS,EACT,IAAI,CACL,CAAC;QACF,OAAO;IACT,CAAC;IAED,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,IAAI,CAAC,MAAM,CACT,IAAI,EACJ;;;eAGS,SAAS;;;;;CAKvB,CACI,CAAC;QACF,MAAM,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;QACrF,OAAO;IACT,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,aAAa,CAAC,CAAC;IAC9D,MAAM,YAAY,GAAG,GAAG,KAAK,CAAC,MAAM,SAAS,CAAC;IAC9C,IAAI,CAAC,MAAM,CACT,IAAI,EACJ,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;QAClB,CAAC,CAAC;WACG,YAAY,cAAc,KAAK,CAAC,IAAI;;gBAE/B,KAAK,CAAC,IAAI;eACX,SAAS;qBACH,YAAY;;iCAEA,KAAK,CAAC,KAAK,KAAK,YAAY;;;;kBAI3C,KAAK,CAAC,KAAK;;;CAG5B;QACK,CAAC,CAAC;;gBAEQ,KAAK,CAAC,IAAI;eACX,SAAS;;;;;;CAMvB,CACE,CAAC;IACF,MAAM,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,aAAa,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;AACzF,CAAC;AAED,KAAK,UAAU,OAAO,CACpB,IAAU,EACV,GAAW,EACX,IAAU,EACV,IAAuB,EACvB,KAAY;IAEZ,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,MAAM,IAAI,aAAa,CACrB,sLAAsL,CACvL,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC;IACxE,MAAM,YAAY,GAAG,GAAG,KAAK,CAAC,MAAM,SAAS,CAAC;IAE9C,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,IAAI,CAAC,MAAM,CACT,OAAO,EACP,gBAAgB,YAAY;;;;;CAKjC,CACI,CAAC;QACF,MAAM,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;QAC9D,OAAO;IACT,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC;IACnE,MAAM,UAAU,GAAG,GAAG,KAAK,CAAC,KAAK,QAAQ,CAAC;IAC1C,MAAM,aAAa,GAAG,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAChD,IAAI,CAAC,MAAM,CACT,IAAI,EACJ,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;QAClB,CAAC,CAAC;WACG,YAAY,YAAY,aAAa;;eAEjC,UAAU;cACX,KAAK,CAAC,IAAI;aACX,YAAY;aACZ,KAAK,CAAC,KAAK;uDAC+B,KAAK,CAAC,KAAK;;;CAGjE;QACK,CAAC,CAAC;;eAEO,UAAU;cACX,KAAK,CAAC,IAAI;;;CAGvB,CACE,CAAC;IACF,MAAM,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,aAAa,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;AAC7D,CAAC;AAED,0FAA0F;AAC1F,SAAS,aAAa,CAAC,IAAU,EAAE,GAAW,EAAE,GAAW;IACzD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACvC,KAAK,IAAI,OAAO,GAAG,GAAG,EAAE,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAClF,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QACtE,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC;YAAE,OAAO,GAAG,CAAC;QACjC,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACxB,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC;iBAC/B,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;iBAC/C,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YACb,IAAI,KAAK;gBAAE,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC9C,CAAC;QACD,IAAI,OAAO,KAAK,IAAI;YAAE,MAAM;IAC9B,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,QAAQ,CACrB,IAAU,EACV,IAAY,EACZ,QAAgB,EAChB,UAAkB,EAClB,IAAY;IAEZ,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,cAAc,UAAU,OAAO,QAAQ,OAAO,KAAK,8BAA8B,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC;IAC5H,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACvB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACxB,OAAO;IACT,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,MAAM,GAAG,EAAE,CAAC,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IAC/F,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACxB,OAAO;IACT,CAAC;IAED,MAAM,KAAK,GAAmC,EAAE,CAAC;IACjD,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CACrC,CAAC,IAAI,EAAiC,EAAE,CACtC,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,QAAQ,CAC1E,CAAC;IACF,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,CAAC;QACnC,IAAI,CAAC,EAAE,CAAC,wBAAwB,CAAC,KAAK,CAAC,EAAE,CAAC;YACxC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACxB,OAAO;QACT,CAAC;QACD,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,UAAU,CAAC;YAAE,OAAO;QACrF,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACnC,KAAK,CAAC,IAAI,CACR,IAAI;YACF,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,UAAU,EAAE,EAAE;YAChD,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CACzD,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACtC,KAAK,CAAC,IAAI,CACR,IAAI;YACF,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,QAAQ,MAAM,UAAU,GAAG,EAAE;YAC/D,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,QAAQ,MAAM,UAAU,IAAI,EAAE,CAChF,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CACrC,CAAC,SAAS,EAAE,EAAE,CACZ,EAAE,CAAC,mBAAmB,CAAC,SAAS,CAAC;QACjC,SAAS,CAAC,YAAY,EAAE,aAAa,KAAK,SAAS;QACnD,EAAE,CAAC,cAAc,CAAC,SAAS,CAAC,YAAY,CAAC,aAAa,CAAC;QACvD,SAAS,CAAC,YAAY,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,UAAU,CAAC,CACxF,CAAC;IACF,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,GAAG,YAAY,UAAU,YAAY,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC;QAC1E,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3E,KAAK,CAAC,IAAI,CACR,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,UAAU,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,IAAI,EAAE,CAC3F,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,GAAG,IAAI,CAAC;IACnB,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QACrD,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC7B,CAAC;AAED,SAAS,gBAAgB,CAAC,MAAqB;IAC7C,IAAI,KAA6C,CAAC;IAClD,MAAM,KAAK,GAAG,CAAC,IAAa,EAAQ,EAAE;QACpC,IAAI,KAAK;YAAE,OAAO;QAClB,IAAI,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;YAC/B,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC;gBAClC,CAAC,CAAC,MAAM,CAAC,IAAI;gBACb,CAAC,CAAC,EAAE,CAAC,0BAA0B,CAAC,MAAM,CAAC;oBACrC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI;oBAClB,CAAC,CAAC,SAAS,CAAC;YAChB,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC;YAC/B,IACE,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,cAAc,IAAI,IAAI,KAAK,UAAU,CAAC;gBACrE,KAAK;gBACL,EAAE,CAAC,yBAAyB,CAAC,KAAK,CAAC,EACnC,CAAC;gBACD,KAAK,GAAG,KAAK,CAAC;gBACd,OAAO;YACT,CAAC;QACH,CAAC;QACD,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC/B,CAAC,CAAC;IACF,KAAK,CAAC,MAAM,CAAC,CAAC;IACd,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,UAAU,CAAC,IAAY,EAAE,EAAU;IAC1C,MAAM,QAAQ,GAAG,IAAI;SAClB,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;SAChC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC;SACrB,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC3B,OAAO,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;AAC/D,CAAC;AAED,SAAS,OAAO,CAAC,IAAY;IAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IACpF,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;AACpD,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACxD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@avijs/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "The avi command: project scaffolding, dev mode and build checks.",
5
5
  "keywords": [
6
6
  "avi",
@@ -44,7 +44,7 @@
44
44
  "access": "public"
45
45
  },
46
46
  "dependencies": {
47
- "@avijs/compiler": "0.1.0"
47
+ "@avijs/compiler": "0.1.1"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "typescript": ">=5.8"
package/src/cli.ts CHANGED
@@ -5,6 +5,7 @@ import { mkdir, readdir, writeFile } from 'node:fs/promises';
5
5
  import { createRequire } from 'node:module';
6
6
  import path from 'node:path';
7
7
  import { parseArgs } from 'node:util';
8
+ import { GENERATE_USAGE, generate } from './generate.js';
8
9
  import { templateFiles } from './templates.js';
9
10
 
10
11
  const { version } = createRequire(import.meta.url)('../package.json') as { version: string };
@@ -15,6 +16,7 @@ const USAGE = `Usage:
15
16
  minimal uses plain routes and services. --from points at a folder of packed
16
17
  @avijs tarballs (pnpm pack) to install instead of the registry. --avi-version
17
18
  defaults to this CLI's version.
19
+ ${GENERATE_USAGE}
18
20
  avi dev [--project tsconfig.json] [--entry dist/main.js]
19
21
  Compile, then recompile and restart on every change.
20
22
  avi check [--project tsconfig.json]
@@ -27,6 +29,9 @@ export async function main(argv: readonly string[]): Promise<number> {
27
29
  switch (command) {
28
30
  case 'new':
29
31
  return create(rest);
32
+ case 'generate':
33
+ case 'g':
34
+ return generate(rest);
30
35
  case 'dev':
31
36
  return dev(rest);
32
37
  case 'check':
@@ -0,0 +1,394 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { parseArgs } from 'node:util';
5
+ import ts from 'typescript';
6
+
7
+ type Kind = 'module' | 'controller' | 'service';
8
+ type Layout = 'modular' | 'minimal';
9
+
10
+ const KINDS: Record<string, Kind> = {
11
+ module: 'module',
12
+ mo: 'module',
13
+ controller: 'controller',
14
+ co: 'controller',
15
+ service: 'service',
16
+ s: 'service',
17
+ };
18
+ const SEGMENT = /^[a-z][a-z0-9-]*$/;
19
+
20
+ export const GENERATE_USAGE = ` avi generate <module|controller|service> <path> [--root .] [--dry-run]
21
+ Create a module, controller or service and register it. Short: avi g co admin/posts.
22
+ Modular projects get src/modules/<path>/<name>.<kind>.ts, registered in the nearest
23
+ module; minimal projects get src/routes/<path>.ts or src/services/<path>.ts, registered
24
+ in src/app.ts.`;
25
+
26
+ interface Names {
27
+ /** kebab-case file name, e.g. `user-profile`. */
28
+ readonly file: string;
29
+ /** PascalCase, e.g. `UserProfile`. */
30
+ readonly pascal: string;
31
+ /** camelCase, e.g. `userProfile`. */
32
+ readonly camel: string;
33
+ }
34
+
35
+ /** Files to write, keyed by absolute path. Reads fall back to disk. */
36
+ class Plan {
37
+ readonly #files = new Map<string, { content: string; created: boolean }>();
38
+ readonly notes: string[] = [];
39
+
40
+ exists(file: string): boolean {
41
+ return this.#files.has(file) || existsSync(file);
42
+ }
43
+
44
+ async read(file: string): Promise<string> {
45
+ return this.#files.get(file)?.content ?? (await readFile(file, 'utf8'));
46
+ }
47
+
48
+ create(file: string, content: string): void {
49
+ if (this.exists(file)) throw new GenerateError(`${file} already exists`);
50
+ this.#files.set(file, { content, created: true });
51
+ }
52
+
53
+ update(file: string, content: string): void {
54
+ const existing = this.#files.get(file);
55
+ this.#files.set(file, { content, created: existing?.created ?? false });
56
+ }
57
+
58
+ async apply(root: string, dryRun: boolean): Promise<void> {
59
+ for (const [file, { content, created }] of this.#files) {
60
+ console.log(
61
+ `${created ? 'CREATE' : 'UPDATE'} ${path.relative(root, file).replaceAll('\\', '/')}`,
62
+ );
63
+ if (dryRun) continue;
64
+ await mkdir(path.dirname(file), { recursive: true });
65
+ await writeFile(file, content);
66
+ }
67
+ for (const note of this.notes) console.log(note);
68
+ }
69
+ }
70
+
71
+ class GenerateError extends Error {}
72
+
73
+ export async function generate(args: readonly string[]): Promise<number> {
74
+ const { values, positionals } = parseArgs({
75
+ args: [...args],
76
+ allowPositionals: true,
77
+ options: {
78
+ root: { type: 'string', default: '.' },
79
+ 'dry-run': { type: 'boolean', default: false },
80
+ },
81
+ });
82
+ const [kindArg, target] = positionals;
83
+ const kind = kindArg === undefined ? undefined : KINDS[kindArg];
84
+ if (!kind || !target) {
85
+ console.error(`Usage:\n${GENERATE_USAGE}`);
86
+ return 2;
87
+ }
88
+ const segments = target.split('/').filter(Boolean);
89
+ const bad = segments.find((segment) => !SEGMENT.test(segment));
90
+ if (segments.length === 0 || bad !== undefined) {
91
+ console.error(
92
+ `Invalid path "${target}": use lowercase names with letters, digits and "-", separated by "/"`,
93
+ );
94
+ return 2;
95
+ }
96
+
97
+ const root = path.resolve(values.root);
98
+ const src = path.join(root, 'src');
99
+ const layout: Layout | undefined = existsSync(path.join(src, 'app.module.ts'))
100
+ ? 'modular'
101
+ : existsSync(path.join(src, 'app.ts'))
102
+ ? 'minimal'
103
+ : undefined;
104
+ if (!layout) {
105
+ console.error(
106
+ `No src/app.module.ts or src/app.ts in ${root}. Run avi generate in an Avi.js project, or pass --root.`,
107
+ );
108
+ return 1;
109
+ }
110
+
111
+ const names = toNames(segments.at(-1) as string);
112
+ const dirs = segments.slice(0, -1);
113
+ const plan = new Plan();
114
+ try {
115
+ if (layout === 'modular') await modular(plan, src, kind, dirs, names);
116
+ else await minimal(plan, src, kind, dirs, names);
117
+ } catch (error) {
118
+ if (!(error instanceof GenerateError)) throw error;
119
+ console.error(error.message);
120
+ return 1;
121
+ }
122
+ await plan.apply(root, values['dry-run']);
123
+ return 0;
124
+ }
125
+
126
+ async function modular(
127
+ plan: Plan,
128
+ src: string,
129
+ kind: Kind,
130
+ dirs: readonly string[],
131
+ names: Names,
132
+ ): Promise<void> {
133
+ const folder = path.join(src, 'modules', ...dirs, names.file);
134
+ const file = path.join(folder, `${names.file}.${kind}.ts`);
135
+ const className = `${names.pascal}${capitalize(kind)}`;
136
+
137
+ if (kind === 'module') {
138
+ plan.create(
139
+ file,
140
+ `import { Module } from '@avijs/core';\n\n@Module({})\nexport class ${className} {}\n`,
141
+ );
142
+ await register(
143
+ plan,
144
+ nearestModule(plan, src, path.dirname(folder)),
145
+ 'imports',
146
+ className,
147
+ file,
148
+ );
149
+ return;
150
+ }
151
+
152
+ if (kind === 'service') {
153
+ plan.create(
154
+ file,
155
+ `import { Injectable } from '@avijs/core';
156
+
157
+ @Injectable()
158
+ export class ${className} {
159
+ findAll(): unknown[] {
160
+ return [];
161
+ }
162
+ }
163
+ `,
164
+ );
165
+ await register(plan, nearestModule(plan, src, folder), 'providers', className, file);
166
+ return;
167
+ }
168
+
169
+ const service = path.join(folder, `${names.file}.service.ts`);
170
+ const serviceClass = `${names.pascal}Service`;
171
+ plan.create(
172
+ file,
173
+ plan.exists(service)
174
+ ? `import { Controller, Get } from '@avijs/core';
175
+ import { ${serviceClass} } from './${names.file}.service.js';
176
+
177
+ @Controller('/${names.file}')
178
+ export class ${className} {
179
+ static inject = [${serviceClass}] as const;
180
+
181
+ constructor(private readonly ${names.camel}: ${serviceClass}) {}
182
+
183
+ @Get()
184
+ findAll() {
185
+ return this.${names.camel}.findAll();
186
+ }
187
+ }
188
+ `
189
+ : `import { Controller, Get } from '@avijs/core';
190
+
191
+ @Controller('/${names.file}')
192
+ export class ${className} {
193
+ @Get()
194
+ findAll() {
195
+ return [];
196
+ }
197
+ }
198
+ `,
199
+ );
200
+ await register(plan, nearestModule(plan, src, folder), 'controllers', className, file);
201
+ }
202
+
203
+ async function minimal(
204
+ plan: Plan,
205
+ src: string,
206
+ kind: Kind,
207
+ dirs: readonly string[],
208
+ names: Names,
209
+ ): Promise<void> {
210
+ if (kind === 'module') {
211
+ throw new GenerateError(
212
+ 'This is a minimal project (src/app.ts, no src/app.module.ts), which does not use modules. Generate a controller or service, or see the project structure chapter to move to modules.',
213
+ );
214
+ }
215
+ const app = path.join(src, 'app.ts');
216
+ const service = path.join(src, 'services', ...dirs, `${names.file}.ts`);
217
+ const serviceClass = `${names.pascal}Service`;
218
+
219
+ if (kind === 'service') {
220
+ plan.create(
221
+ service,
222
+ `export class ${serviceClass} {
223
+ findAll(): unknown[] {
224
+ return [];
225
+ }
226
+ }
227
+ `,
228
+ );
229
+ await register(plan, app, 'providers', serviceClass, service);
230
+ return;
231
+ }
232
+
233
+ const file = path.join(src, 'routes', ...dirs, `${names.file}.ts`);
234
+ const exportName = `${names.camel}Routes`;
235
+ const serviceImport = importPath(file, service);
236
+ plan.create(
237
+ file,
238
+ plan.exists(service)
239
+ ? `import { defineController, route } from '@avijs/core';
240
+ import { ${serviceClass} } from '${serviceImport}';
241
+
242
+ export const ${exportName} = defineController({
243
+ prefix: '/${names.file}',
244
+ inject: [${serviceClass}],
245
+ routes: (${names.camel}) => [
246
+ route({ method: 'GET', path: '/', handler: () => ${names.camel}.findAll() }),
247
+ ],
248
+ });
249
+ `
250
+ : `import { defineController, route } from '@avijs/core';
251
+
252
+ export const ${exportName} = defineController({
253
+ prefix: '/${names.file}',
254
+ routes: [route({ method: 'GET', path: '/', handler: () => [] })],
255
+ });
256
+ `,
257
+ );
258
+ await register(plan, app, 'controllers', exportName, file);
259
+ }
260
+
261
+ /** The closest `*.module.ts` from `dir` up to `src/modules`, else `src/app.module.ts`. */
262
+ function nearestModule(plan: Plan, src: string, dir: string): string {
263
+ const stop = path.join(src, 'modules');
264
+ for (let current = dir; current.startsWith(stop); current = path.dirname(current)) {
265
+ const own = path.join(current, `${path.basename(current)}.module.ts`);
266
+ if (plan.exists(own)) return own;
267
+ if (existsSync(current)) {
268
+ const other = readdirSync(current)
269
+ .filter((entry) => entry.endsWith('.module.ts'))
270
+ .sort()[0];
271
+ if (other) return path.join(current, other);
272
+ }
273
+ if (current === stop) break;
274
+ }
275
+ return path.join(src, 'app.module.ts');
276
+ }
277
+
278
+ /**
279
+ * Adds `identifier` to the `property` array of the first `@Module({...})`, `defineModule({...})`
280
+ * or `.register({...})` object in `file`, and imports it. Leaves a note when it can't.
281
+ */
282
+ async function register(
283
+ plan: Plan,
284
+ file: string,
285
+ property: string,
286
+ identifier: string,
287
+ from: string,
288
+ ): Promise<void> {
289
+ const where = path.basename(file);
290
+ const manual = `NOTE Add ${identifier} to ${property} in ${where} yourself (import it from '${importPath(file, from)}').`;
291
+ if (!plan.exists(file)) {
292
+ plan.notes.push(manual);
293
+ return;
294
+ }
295
+ const text = await plan.read(file);
296
+ const source = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
297
+ const object = findRegistration(source);
298
+ if (!object) {
299
+ plan.notes.push(manual);
300
+ return;
301
+ }
302
+
303
+ const edits: { at: number; text: string }[] = [];
304
+ const existing = object.properties.find(
305
+ (prop): prop is ts.PropertyAssignment =>
306
+ ts.isPropertyAssignment(prop) && prop.name.getText(source) === property,
307
+ );
308
+ if (existing) {
309
+ const array = existing.initializer;
310
+ if (!ts.isArrayLiteralExpression(array)) {
311
+ plan.notes.push(manual);
312
+ return;
313
+ }
314
+ if (array.elements.some((element) => element.getText(source) === identifier)) return;
315
+ const last = array.elements.at(-1);
316
+ edits.push(
317
+ last
318
+ ? { at: last.getEnd(), text: `, ${identifier}` }
319
+ : { at: array.getStart(source) + 1, text: identifier },
320
+ );
321
+ } else {
322
+ const last = object.properties.at(-1);
323
+ edits.push(
324
+ last
325
+ ? { at: last.getEnd(), text: `, ${property}: [${identifier}]` }
326
+ : { at: object.getStart(source) + 1, text: ` ${property}: [${identifier}] ` },
327
+ );
328
+ }
329
+
330
+ const imported = source.statements.some(
331
+ (statement) =>
332
+ ts.isImportDeclaration(statement) &&
333
+ statement.importClause?.namedBindings !== undefined &&
334
+ ts.isNamedImports(statement.importClause.namedBindings) &&
335
+ statement.importClause.namedBindings.elements.some((e) => e.name.text === identifier),
336
+ );
337
+ if (!imported) {
338
+ const line = `import { ${identifier} } from '${importPath(file, from)}';`;
339
+ const lastImport = source.statements.filter(ts.isImportDeclaration).at(-1);
340
+ edits.push(
341
+ lastImport ? { at: lastImport.getEnd(), text: `\n${line}` } : { at: 0, text: `${line}\n` },
342
+ );
343
+ }
344
+
345
+ let updated = text;
346
+ for (const edit of edits.sort((a, b) => b.at - a.at)) {
347
+ updated = updated.slice(0, edit.at) + edit.text + updated.slice(edit.at);
348
+ }
349
+ plan.update(file, updated);
350
+ }
351
+
352
+ function findRegistration(source: ts.SourceFile): ts.ObjectLiteralExpression | undefined {
353
+ let found: ts.ObjectLiteralExpression | undefined;
354
+ const visit = (node: ts.Node): void => {
355
+ if (found) return;
356
+ if (ts.isCallExpression(node)) {
357
+ const callee = node.expression;
358
+ const name = ts.isIdentifier(callee)
359
+ ? callee.text
360
+ : ts.isPropertyAccessExpression(callee)
361
+ ? callee.name.text
362
+ : undefined;
363
+ const [first] = node.arguments;
364
+ if (
365
+ (name === 'Module' || name === 'defineModule' || name === 'register') &&
366
+ first &&
367
+ ts.isObjectLiteralExpression(first)
368
+ ) {
369
+ found = first;
370
+ return;
371
+ }
372
+ }
373
+ ts.forEachChild(node, visit);
374
+ };
375
+ visit(source);
376
+ return found;
377
+ }
378
+
379
+ function importPath(from: string, to: string): string {
380
+ const relative = path
381
+ .relative(path.dirname(from), to)
382
+ .replaceAll('\\', '/')
383
+ .replace(/\.ts$/, '.js');
384
+ return relative.startsWith('.') ? relative : `./${relative}`;
385
+ }
386
+
387
+ function toNames(file: string): Names {
388
+ const camel = file.replace(/-([a-z0-9])/g, (_, char: string) => char.toUpperCase());
389
+ return { file, camel, pascal: capitalize(camel) };
390
+ }
391
+
392
+ function capitalize(value: string): string {
393
+ return value.charAt(0).toUpperCase() + value.slice(1);
394
+ }