@fluojs/cli 1.0.0-beta.4 → 1.0.0-beta.6
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.ko.md +97 -3
- package/README.md +97 -3
- package/dist/cli.d.ts +8 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +201 -4
- package/dist/commands/diagnostics.d.ts +15 -0
- package/dist/commands/diagnostics.d.ts.map +1 -0
- package/dist/commands/diagnostics.js +163 -0
- package/dist/commands/new.js +2 -2
- package/dist/commands/package-manager.d.ts +9 -0
- package/dist/commands/package-manager.d.ts.map +1 -0
- package/dist/commands/package-manager.js +63 -0
- package/dist/commands/package-workflow.d.ts +20 -0
- package/dist/commands/package-workflow.d.ts.map +1 -0
- package/dist/commands/package-workflow.js +137 -0
- package/dist/commands/scripts.d.ts +38 -0
- package/dist/commands/scripts.d.ts.map +1 -0
- package/dist/commands/scripts.js +570 -0
- package/dist/dev-runner/node-restart-runner.d.ts +55 -0
- package/dist/dev-runner/node-restart-runner.d.ts.map +1 -0
- package/dist/dev-runner/node-restart-runner.js +317 -0
- package/dist/dev-runner/preserve-color-tty.d.ts +2 -0
- package/dist/dev-runner/preserve-color-tty.d.ts.map +1 -0
- package/dist/dev-runner/preserve-color-tty.js +12 -0
- package/dist/generators/manifest.d.ts +24 -0
- package/dist/generators/manifest.d.ts.map +1 -1
- package/dist/generators/manifest.js +9 -0
- package/dist/generators/resource.d.ts +10 -0
- package/dist/generators/resource.d.ts.map +1 -0
- package/dist/generators/resource.js +23 -0
- package/dist/generators/templates/controller.ts.ejs +5 -1
- package/dist/generators/templates/request-dto.ts.ejs +3 -0
- package/dist/new/scaffold.d.ts.map +1 -1
- package/dist/new/scaffold.js +193 -148
- package/dist/new/starter-profiles.d.ts.map +1 -1
- package/dist/new/starter-profiles.js +13 -13
- package/dist/update-check.d.ts +1 -0
- package/dist/update-check.d.ts.map +1 -1
- package/dist/update-check.js +7 -5
- package/package.json +2 -2
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { existsSync, readdirSync, readFileSync, statSync, watch } from 'node:fs';
|
|
4
|
+
import { basename, dirname, join, relative, sep } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
/** Runtime target handled by the fluo-owned development restart runner. */
|
|
8
|
+
|
|
9
|
+
/** Options used to configure the fluo-owned Node restart-on-watch process boundary. */
|
|
10
|
+
|
|
11
|
+
const DEFAULT_DEBOUNCE_MS = 100;
|
|
12
|
+
const PRETTY_TTY_COLOR_ENV = 'FLUO_DEV_PRETTY_TTY_COLOR';
|
|
13
|
+
const DEFAULT_IGNORES = ['.cache', '.fluo', '.git', '.turbo', 'coverage', 'dist', 'node_modules', '*.swp', '*.swo', '*~', '.#*'];
|
|
14
|
+
const WATCH_FILES = ['.env', 'package.json', 'tsconfig.json', 'tsconfig.build.json'];
|
|
15
|
+
const SHOW_NODE_RESTART_NOTICE_ENV = 'FLUO_DEV_SHOW_RESTART_NOTICE';
|
|
16
|
+
const CLEAR_SCREEN = '\u001B[2J\u001B[3J\u001B[H';
|
|
17
|
+
function normalizeIgnorePatterns(patterns) {
|
|
18
|
+
return patterns.map(pattern => pattern.trim()).filter(pattern => pattern.length > 0);
|
|
19
|
+
}
|
|
20
|
+
function parseIgnorePatterns(env) {
|
|
21
|
+
const configured = env.FLUO_DEV_WATCH_IGNORE?.split(',') ?? [];
|
|
22
|
+
return normalizeIgnorePatterns([...DEFAULT_IGNORES, ...configured]);
|
|
23
|
+
}
|
|
24
|
+
function matchesSegmentPattern(segment, pattern) {
|
|
25
|
+
if (pattern.startsWith('*.')) {
|
|
26
|
+
return segment.endsWith(pattern.slice(1));
|
|
27
|
+
}
|
|
28
|
+
if (pattern.endsWith('*')) {
|
|
29
|
+
return segment.startsWith(pattern.slice(0, -1));
|
|
30
|
+
}
|
|
31
|
+
return segment === pattern;
|
|
32
|
+
}
|
|
33
|
+
function shouldIgnorePath(filePath, projectDirectory, ignorePatterns) {
|
|
34
|
+
const relativePath = relative(projectDirectory, filePath);
|
|
35
|
+
if (relativePath.startsWith('..')) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
const segments = relativePath.split(sep).filter(Boolean);
|
|
39
|
+
return segments.some(segment => ignorePatterns.some(pattern => matchesSegmentPattern(segment, pattern)));
|
|
40
|
+
}
|
|
41
|
+
function hashFileContent(filePath) {
|
|
42
|
+
try {
|
|
43
|
+
const stats = statSync(filePath);
|
|
44
|
+
if (!stats.isFile()) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
return createHash('sha256').update(readFileSync(filePath)).digest('hex');
|
|
48
|
+
} catch (_error) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function collectContentPaths(filePath, projectDirectory, ignorePatterns, paths) {
|
|
53
|
+
if (shouldIgnorePath(filePath, projectDirectory, ignorePatterns)) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const stats = statSync(filePath);
|
|
58
|
+
if (stats.isDirectory()) {
|
|
59
|
+
for (const entry of readdirSync(filePath)) {
|
|
60
|
+
collectContentPaths(join(filePath, entry), projectDirectory, ignorePatterns, paths);
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (stats.isFile()) {
|
|
65
|
+
paths.add(filePath);
|
|
66
|
+
}
|
|
67
|
+
} catch (_error) {
|
|
68
|
+
paths.add(filePath);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function collectWatchedContentPaths(paths, projectDirectory, ignorePatterns) {
|
|
72
|
+
const collected = new Set();
|
|
73
|
+
for (const filePath of paths) {
|
|
74
|
+
collectContentPaths(filePath, projectDirectory, ignorePatterns, collected);
|
|
75
|
+
}
|
|
76
|
+
return collected;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Creates a content-diff gate for fluo-owned dev restarts.
|
|
81
|
+
*
|
|
82
|
+
* @param projectDirectory Project root used for ignore matching.
|
|
83
|
+
* @param ignorePatterns Additional or default ignore patterns to apply before hashing.
|
|
84
|
+
* @returns A gate that reports whether watched paths changed by content rather than by filesystem event alone.
|
|
85
|
+
*/
|
|
86
|
+
export function createContentChangeGate(projectDirectory, ignorePatterns = DEFAULT_IGNORES) {
|
|
87
|
+
const normalizedIgnores = normalizeIgnorePatterns(ignorePatterns);
|
|
88
|
+
const hashes = new Map();
|
|
89
|
+
return {
|
|
90
|
+
commitBaseline(paths) {
|
|
91
|
+
for (const filePath of collectWatchedContentPaths(paths, projectDirectory, normalizedIgnores)) {
|
|
92
|
+
hashes.set(filePath, hashFileContent(filePath));
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
hasMeaningfulChange(paths) {
|
|
96
|
+
for (const filePath of collectWatchedContentPaths(paths, projectDirectory, normalizedIgnores)) {
|
|
97
|
+
const nextHash = hashFileContent(filePath);
|
|
98
|
+
const previousHash = hashes.get(filePath);
|
|
99
|
+
if (previousHash !== nextHash) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function getWatchTargets(projectDirectory) {
|
|
108
|
+
return [join(projectDirectory, 'src'), ...WATCH_FILES.map(fileName => join(projectDirectory, fileName))].filter(target => existsSync(target));
|
|
109
|
+
}
|
|
110
|
+
function stopChild(child) {
|
|
111
|
+
if (child && child.exitCode === null && !child.killed) {
|
|
112
|
+
child.kill('SIGTERM');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function getPreserveColorTtyImport() {
|
|
116
|
+
return join(dirname(dirname(fileURLToPath(import.meta.url))), 'dev-runner', 'preserve-color-tty.js');
|
|
117
|
+
}
|
|
118
|
+
function buildNodeAppArgs(env, appArgs) {
|
|
119
|
+
const colorTtyImport = env[PRETTY_TTY_COLOR_ENV] === '1' ? ['--import', getPreserveColorTtyImport()] : [];
|
|
120
|
+
return ['--env-file=.env', ...colorTtyImport, '--import', 'tsx', 'src/main.ts', ...appArgs];
|
|
121
|
+
}
|
|
122
|
+
function buildBunAppArgs(env, appArgs) {
|
|
123
|
+
const colorTtyPreload = env[PRETTY_TTY_COLOR_ENV] === '1' ? ['--preload', getPreserveColorTtyImport()] : [];
|
|
124
|
+
return [...colorTtyPreload, 'src/main.ts', ...appArgs];
|
|
125
|
+
}
|
|
126
|
+
function buildAppCommand(runtime, env, appArgs) {
|
|
127
|
+
switch (runtime) {
|
|
128
|
+
case 'bun':
|
|
129
|
+
return {
|
|
130
|
+
command: 'bun',
|
|
131
|
+
args: buildBunAppArgs(env, appArgs)
|
|
132
|
+
};
|
|
133
|
+
case 'cloudflare-workers':
|
|
134
|
+
return {
|
|
135
|
+
command: 'wrangler',
|
|
136
|
+
args: ['dev', '--show-interactive-dev-session=false', ...appArgs]
|
|
137
|
+
};
|
|
138
|
+
case 'deno':
|
|
139
|
+
return {
|
|
140
|
+
command: 'deno',
|
|
141
|
+
args: ['run', '--allow-env', '--allow-net', 'src/main.ts', ...appArgs]
|
|
142
|
+
};
|
|
143
|
+
default:
|
|
144
|
+
return {
|
|
145
|
+
command: process.execPath,
|
|
146
|
+
args: buildNodeAppArgs(env, appArgs)
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function readDevScriptHeader(projectDirectory) {
|
|
151
|
+
const fallbackName = basename(projectDirectory);
|
|
152
|
+
try {
|
|
153
|
+
const manifest = JSON.parse(readFileSync(join(projectDirectory, 'package.json'), 'utf8'));
|
|
154
|
+
const name = typeof manifest.name === 'string' && manifest.name.length > 0 ? manifest.name : fallbackName;
|
|
155
|
+
const version = typeof manifest.version === 'string' && manifest.version.length > 0 ? `@${manifest.version}` : '';
|
|
156
|
+
const devScript = typeof manifest.scripts?.dev === 'string' && manifest.scripts.dev.length > 0 ? manifest.scripts.dev : 'fluo dev';
|
|
157
|
+
return `> ${name}${version} dev ${projectDirectory}\n> ${devScript}\n\n`;
|
|
158
|
+
} catch (_error) {
|
|
159
|
+
return `> ${fallbackName} dev ${projectDirectory}\n> fluo dev\n\n`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function redrawDevScriptHeader(stdout, projectDirectory, env) {
|
|
163
|
+
if (env[SHOW_NODE_RESTART_NOTICE_ENV] !== '1') {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
stdout.write(CLEAR_SCREEN);
|
|
167
|
+
stdout.write(readDevScriptHeader(projectDirectory));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Runs the Node.js development lifecycle through fluo-owned restart supervision.
|
|
172
|
+
*
|
|
173
|
+
* @param options Runner dependencies and project settings.
|
|
174
|
+
* @returns A promise that resolves with the final child exit code when the runner stops.
|
|
175
|
+
*/
|
|
176
|
+
export async function runNodeRestartRunner(options) {
|
|
177
|
+
const projectDirectory = options.projectDirectory ?? process.cwd();
|
|
178
|
+
const env = options.env;
|
|
179
|
+
const runnerRuntime = options.runtime ?? 'node';
|
|
180
|
+
const signalTarget = options.signalTarget ?? process;
|
|
181
|
+
const spawnChild = options.spawnChild ?? spawn;
|
|
182
|
+
const stdout = options.stdout ?? process.stdout;
|
|
183
|
+
const stderr = options.stderr ?? process.stderr;
|
|
184
|
+
const watchTarget = options.watchTarget ?? watch;
|
|
185
|
+
const appArgs = options.appArgs ?? [];
|
|
186
|
+
const debounceMs = options.debounceMs ?? Number(env.FLUO_DEV_RELOAD_DEBOUNCE_MS ?? DEFAULT_DEBOUNCE_MS);
|
|
187
|
+
const gate = createContentChangeGate(projectDirectory, parseIgnorePatterns(env));
|
|
188
|
+
const watchTargets = getWatchTargets(projectDirectory);
|
|
189
|
+
let child;
|
|
190
|
+
const pendingRestartPaths = new Set();
|
|
191
|
+
const restartAfterClosePaths = new Set();
|
|
192
|
+
let restartTimer;
|
|
193
|
+
let restarting = false;
|
|
194
|
+
let stopping = false;
|
|
195
|
+
const startChild = (resolveExitCode, cleanup) => {
|
|
196
|
+
const appCommand = buildAppCommand(runnerRuntime, env, appArgs);
|
|
197
|
+
child = spawnChild(appCommand.command, appCommand.args, {
|
|
198
|
+
cwd: projectDirectory,
|
|
199
|
+
env,
|
|
200
|
+
stdio: 'inherit'
|
|
201
|
+
});
|
|
202
|
+
child.once('close', code => {
|
|
203
|
+
if (restarting) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (stopping) {
|
|
207
|
+
cleanup();
|
|
208
|
+
resolveExitCode(code ?? 0);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
cleanup();
|
|
212
|
+
resolveExitCode(code ?? 1);
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
const scheduleRestart = (filePath, resolveExitCode, cleanup) => {
|
|
216
|
+
pendingRestartPaths.add(filePath);
|
|
217
|
+
if (restartTimer) {
|
|
218
|
+
clearTimeout(restartTimer);
|
|
219
|
+
}
|
|
220
|
+
restartTimer = setTimeout(() => {
|
|
221
|
+
const restartPaths = [...pendingRestartPaths];
|
|
222
|
+
pendingRestartPaths.clear();
|
|
223
|
+
restartTimer = undefined;
|
|
224
|
+
if (!gate.hasMeaningfulChange(restartPaths)) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (env[SHOW_NODE_RESTART_NOTICE_ENV] === '1') {
|
|
228
|
+
stdout.write(`[fluo] restarting after content change: ${relative(projectDirectory, restartPaths[restartPaths.length - 1] ?? projectDirectory)}\n`);
|
|
229
|
+
}
|
|
230
|
+
const previousChild = child;
|
|
231
|
+
const startReplacementChild = () => {
|
|
232
|
+
redrawDevScriptHeader(stdout, projectDirectory, env);
|
|
233
|
+
startChild(resolveExitCode, cleanup);
|
|
234
|
+
gate.commitBaseline(restartPaths);
|
|
235
|
+
};
|
|
236
|
+
if (previousChild) {
|
|
237
|
+
for (const restartPath of restartPaths) {
|
|
238
|
+
restartAfterClosePaths.add(restartPath);
|
|
239
|
+
}
|
|
240
|
+
if (restarting) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
restarting = true;
|
|
244
|
+
previousChild.once('close', () => {
|
|
245
|
+
const committedRestartPaths = [...restartAfterClosePaths];
|
|
246
|
+
restartAfterClosePaths.clear();
|
|
247
|
+
restarting = false;
|
|
248
|
+
if (stopping) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
redrawDevScriptHeader(stdout, projectDirectory, env);
|
|
252
|
+
startChild(resolveExitCode, cleanup);
|
|
253
|
+
gate.commitBaseline(committedRestartPaths);
|
|
254
|
+
});
|
|
255
|
+
stopChild(previousChild);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
startReplacementChild();
|
|
260
|
+
} finally {
|
|
261
|
+
restarting = false;
|
|
262
|
+
}
|
|
263
|
+
}, debounceMs);
|
|
264
|
+
};
|
|
265
|
+
gate.commitBaseline(watchTargets);
|
|
266
|
+
return new Promise(resolveExitCode => {
|
|
267
|
+
const watchers = [];
|
|
268
|
+
let cleanedUp = false;
|
|
269
|
+
const stop = () => {
|
|
270
|
+
stopping = true;
|
|
271
|
+
cleanup();
|
|
272
|
+
stopChild(child);
|
|
273
|
+
};
|
|
274
|
+
const cleanup = () => {
|
|
275
|
+
if (cleanedUp) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
cleanedUp = true;
|
|
279
|
+
if (restartTimer) {
|
|
280
|
+
clearTimeout(restartTimer);
|
|
281
|
+
restartTimer = undefined;
|
|
282
|
+
}
|
|
283
|
+
pendingRestartPaths.clear();
|
|
284
|
+
restartAfterClosePaths.clear();
|
|
285
|
+
for (const watcher of watchers.splice(0)) {
|
|
286
|
+
watcher.close();
|
|
287
|
+
}
|
|
288
|
+
signalTarget.off('SIGINT', stop);
|
|
289
|
+
signalTarget.off('SIGTERM', stop);
|
|
290
|
+
};
|
|
291
|
+
startChild(resolveExitCode, cleanup);
|
|
292
|
+
for (const target of watchTargets) {
|
|
293
|
+
try {
|
|
294
|
+
const stats = statSync(target);
|
|
295
|
+
const watchOptions = {
|
|
296
|
+
recursive: stats.isDirectory()
|
|
297
|
+
};
|
|
298
|
+
const listener = (_event, filename) => {
|
|
299
|
+
const fileName = filename ? String(filename) : basename(target);
|
|
300
|
+
scheduleRestart(stats.isDirectory() ? join(target, fileName) : target, resolveExitCode, cleanup);
|
|
301
|
+
};
|
|
302
|
+
try {
|
|
303
|
+
watchers.push(watchTarget(target, watchOptions, listener));
|
|
304
|
+
} catch (error) {
|
|
305
|
+
if (!stats.isDirectory()) {
|
|
306
|
+
throw error;
|
|
307
|
+
}
|
|
308
|
+
watchers.push(watchTarget(target, listener));
|
|
309
|
+
}
|
|
310
|
+
} catch (error) {
|
|
311
|
+
stderr.write(`[fluo] unable to watch ${target}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
signalTarget.once('SIGINT', stop);
|
|
315
|
+
signalTarget.once('SIGTERM', stop);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preserve-color-tty.d.ts","sourceRoot":"","sources":["../../src/dev-runner/preserve-color-tty.ts"],"names":[],"mappings":"AAAA,iBAAS,yBAAyB,CAAC,MAAM,EAAE,MAAM,CAAC,WAAW,GAAG,IAAI,CASnE"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
function preserveTtyColorDetection(stream) {
|
|
2
|
+
try {
|
|
3
|
+
Object.defineProperty(stream, 'isTTY', {
|
|
4
|
+
configurable: true,
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
} catch (_error) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
preserveTtyColorDetection(process.stdout);
|
|
12
|
+
preserveTtyColorDetection(process.stderr);
|
|
@@ -126,6 +126,14 @@ export declare const builtInGeneratorCollection: {
|
|
|
126
126
|
readonly nextStepHint: "Import the DTO in a controller and add it as a parameter with @FromBody or @FromQuery.";
|
|
127
127
|
readonly schematic: "request-dto";
|
|
128
128
|
readonly wiringBehavior: "files-only";
|
|
129
|
+
}, {
|
|
130
|
+
readonly aliases: readonly ["resrc"];
|
|
131
|
+
readonly description: "Generate a full resource slice with module, controller, service, repository, and DTO stubs.";
|
|
132
|
+
readonly factory: (name: string, options: import("../generator-types.js").GenerateOptions | undefined) => import("../generator-types.js").GeneratedFile[];
|
|
133
|
+
readonly kind: "resource";
|
|
134
|
+
readonly nextStepHint: "Run 'pnpm typecheck' and wire the resource module into a parent module when ready.";
|
|
135
|
+
readonly schematic: "resource";
|
|
136
|
+
readonly wiringBehavior: "files-only";
|
|
129
137
|
}, {
|
|
130
138
|
readonly aliases: readonly ["res"];
|
|
131
139
|
readonly description: "Generate a response DTO for typed response payloads (files only — use it as a controller return type).";
|
|
@@ -230,6 +238,14 @@ export declare const generatorCollections: readonly [{
|
|
|
230
238
|
readonly nextStepHint: "Import the DTO in a controller and add it as a parameter with @FromBody or @FromQuery.";
|
|
231
239
|
readonly schematic: "request-dto";
|
|
232
240
|
readonly wiringBehavior: "files-only";
|
|
241
|
+
}, {
|
|
242
|
+
readonly aliases: readonly ["resrc"];
|
|
243
|
+
readonly description: "Generate a full resource slice with module, controller, service, repository, and DTO stubs.";
|
|
244
|
+
readonly factory: (name: string, options: import("../generator-types.js").GenerateOptions | undefined) => import("../generator-types.js").GeneratedFile[];
|
|
245
|
+
readonly kind: "resource";
|
|
246
|
+
readonly nextStepHint: "Run 'pnpm typecheck' and wire the resource module into a parent module when ready.";
|
|
247
|
+
readonly schematic: "resource";
|
|
248
|
+
readonly wiringBehavior: "files-only";
|
|
233
249
|
}, {
|
|
234
250
|
readonly aliases: readonly ["res"];
|
|
235
251
|
readonly description: "Generate a response DTO for typed response payloads (files only — use it as a controller return type).";
|
|
@@ -332,6 +348,14 @@ export declare const generatorManifest: readonly [{
|
|
|
332
348
|
readonly nextStepHint: "Import the DTO in a controller and add it as a parameter with @FromBody or @FromQuery.";
|
|
333
349
|
readonly schematic: "request-dto";
|
|
334
350
|
readonly wiringBehavior: "files-only";
|
|
351
|
+
}, {
|
|
352
|
+
readonly aliases: readonly ["resrc"];
|
|
353
|
+
readonly description: "Generate a full resource slice with module, controller, service, repository, and DTO stubs.";
|
|
354
|
+
readonly factory: (name: string, options: import("../generator-types.js").GenerateOptions | undefined) => import("../generator-types.js").GeneratedFile[];
|
|
355
|
+
readonly kind: "resource";
|
|
356
|
+
readonly nextStepHint: "Run 'pnpm typecheck' and wire the resource module into a parent module when ready.";
|
|
357
|
+
readonly schematic: "resource";
|
|
358
|
+
readonly wiringBehavior: "files-only";
|
|
335
359
|
}, {
|
|
336
360
|
readonly aliases: readonly ["res"];
|
|
337
361
|
readonly description: "Generate a response DTO for typed response payloads (files only — use it as a controller return type).";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/generators/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAyB,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/generators/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAyB,MAAM,uBAAuB,CAAC;AAarF,yFAAyF;AACzF,MAAM,MAAM,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,YAAY,CAAC;AAExE,KAAK,4BAA4B,GAAG;IAClC,QAAQ,EAAE,cAAc,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,8EAA8E;AAC9E,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB,CAAC,EAAE,4BAA4B,CAAC;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,iBAAiB,GAAG,YAAY,CAAC;CAClD,CAAC;AAEF,4FAA4F;AAC5F,MAAM,MAAM,mBAAmB,GAAG;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,SAAS,sBAAsB,EAAE,CAAC;IAC9C,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,UAAU,CAAC;CACpB,CAAC;AAEF,wFAAwF;AACxF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;EAKkB,CAAC;AAsGtD,6EAA6E;AAC7E,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAKC,CAAC;AAEzC,2FAA2F;AAC3F,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAiF,CAAC;AAEnH,2FAA2F;AAC3F,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAwC,CAAC;AAEvE,kFAAkF;AAClF,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvE,uEAAuE;AACvE,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAcrE;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,aAAa,GAAG,mBAAmB,CAOhF;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,SAAS,mBAAmB,EAAE,CAEzE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,GAAE,MAAsC,GAAG,SAAS,mBAAmB,EAAE,CAO7H;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,CAMzF"}
|
|
@@ -5,6 +5,7 @@ import { generateMiddlewareFiles } from './middleware.js';
|
|
|
5
5
|
import { generateModuleFiles } from './module.js';
|
|
6
6
|
import { generateRepoFiles } from './repository.js';
|
|
7
7
|
import { generateRequestDtoFiles } from './request-dto.js';
|
|
8
|
+
import { generateResourceFiles } from './resource.js';
|
|
8
9
|
import { generateResponseDtoFiles } from './response-dto.js';
|
|
9
10
|
import { generateServiceFiles } from './service.js';
|
|
10
11
|
|
|
@@ -113,6 +114,14 @@ const builtInGeneratorDefinitions = [{
|
|
|
113
114
|
nextStepHint: 'Import the DTO in a controller and add it as a parameter with @FromBody or @FromQuery.',
|
|
114
115
|
schematic: 'request-dto',
|
|
115
116
|
wiringBehavior: 'files-only'
|
|
117
|
+
}, {
|
|
118
|
+
aliases: ['resrc'],
|
|
119
|
+
description: 'Generate a full resource slice with module, controller, service, repository, and DTO stubs.',
|
|
120
|
+
factory: (name, options) => generateResourceFiles(name, options),
|
|
121
|
+
kind: 'resource',
|
|
122
|
+
nextStepHint: "Run 'pnpm typecheck' and wire the resource module into a parent module when ready.",
|
|
123
|
+
schematic: 'resource',
|
|
124
|
+
wiringBehavior: 'files-only'
|
|
116
125
|
}, {
|
|
117
126
|
aliases: ['res'],
|
|
118
127
|
description: 'Generate a response DTO for typed response payloads (files only — use it as a controller return type).',
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { GenerateOptions, GeneratedFile } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generate a complete feature resource slice.
|
|
4
|
+
*
|
|
5
|
+
* @param name The resource name.
|
|
6
|
+
* @param options The generation options.
|
|
7
|
+
* @returns The generated resource files.
|
|
8
|
+
*/
|
|
9
|
+
export declare function generateResourceFiles(name: string, options?: GenerateOptions): GeneratedFile[];
|
|
10
|
+
//# sourceMappingURL=resource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../src/generators/resource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AASlE;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,aAAa,EAAE,CASlG"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { generateControllerFiles } from './controller.js';
|
|
2
|
+
import { generateModuleFiles } from './module.js';
|
|
3
|
+
import { generateRepoFiles } from './repository.js';
|
|
4
|
+
import { generateRequestDtoFiles } from './request-dto.js';
|
|
5
|
+
import { generateResponseDtoFiles } from './response-dto.js';
|
|
6
|
+
import { generateServiceFiles } from './service.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Generate a complete feature resource slice.
|
|
10
|
+
*
|
|
11
|
+
* @param name The resource name.
|
|
12
|
+
* @param options The generation options.
|
|
13
|
+
* @returns The generated resource files.
|
|
14
|
+
*/
|
|
15
|
+
export function generateResourceFiles(name, options = {}) {
|
|
16
|
+
return [...generateModuleFiles(name), ...generateRepoFiles(name, options), ...generateServiceFiles(name, {
|
|
17
|
+
...options,
|
|
18
|
+
hasRepo: true
|
|
19
|
+
}), ...generateControllerFiles(name, {
|
|
20
|
+
...options,
|
|
21
|
+
hasService: true
|
|
22
|
+
}), ...generateRequestDtoFiles(`Create ${name}`), ...generateResponseDtoFiles(name)];
|
|
23
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<% if (hasService) { %>
|
|
2
|
-
import { Inject } from '@fluojs/core';
|
|
2
|
+
import { ensureMetadataSymbol, Inject } from '@fluojs/core';
|
|
3
|
+
<% } else { %>
|
|
4
|
+
import { ensureMetadataSymbol } from '@fluojs/core';
|
|
3
5
|
<% } %>
|
|
4
6
|
import { Controller, Get } from '@fluojs/http';
|
|
5
7
|
|
|
@@ -7,6 +9,8 @@ import { Controller, Get } from '@fluojs/http';
|
|
|
7
9
|
import { <%- service %> } from './<%- kebab %>.service';
|
|
8
10
|
<% } %>
|
|
9
11
|
|
|
12
|
+
ensureMetadataSymbol();
|
|
13
|
+
|
|
10
14
|
@Controller('/<%- kebab %>')
|
|
11
15
|
<% if (hasService) { %>
|
|
12
16
|
@Inject(<%- service %>)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/new/scaffold.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAkB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/new/scaffold.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAkB,MAAM,YAAY,CAAC;AAkoEnE;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,gBAAgB,EACzB,aAAa,SAAkB,GAC9B,OAAO,CAAC,IAAI,CAAC,CA6Bf;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,6BAAuB,CAAC"}
|