@unrdf/project-engine 5.0.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/LICENSE +21 -0
- package/README.md +53 -0
- package/package.json +58 -0
- package/src/api-contract-validator.mjs +711 -0
- package/src/auto-test-generator.mjs +444 -0
- package/src/autonomic-mapek.mjs +511 -0
- package/src/capabilities-manifest.mjs +125 -0
- package/src/code-complexity-js.mjs +368 -0
- package/src/dependency-graph.mjs +276 -0
- package/src/doc-drift-checker.mjs +172 -0
- package/src/doc-generator.mjs +229 -0
- package/src/domain-infer.mjs +966 -0
- package/src/drift-snapshot.mjs +775 -0
- package/src/file-roles.mjs +94 -0
- package/src/fs-scan.mjs +305 -0
- package/src/gap-finder.mjs +376 -0
- package/src/golden-structure.mjs +149 -0
- package/src/hotspot-analyzer.mjs +412 -0
- package/src/index.mjs +151 -0
- package/src/initialize.mjs +957 -0
- package/src/lens/project-structure.mjs +74 -0
- package/src/mapek-orchestration.mjs +665 -0
- package/src/materialize-apply.mjs +505 -0
- package/src/materialize-plan.mjs +422 -0
- package/src/materialize.mjs +137 -0
- package/src/policy-derivation.mjs +869 -0
- package/src/project-config.mjs +142 -0
- package/src/project-diff.mjs +28 -0
- package/src/project-engine/build-utils.mjs +237 -0
- package/src/project-engine/code-analyzer.mjs +248 -0
- package/src/project-engine/doc-generator.mjs +407 -0
- package/src/project-engine/infrastructure.mjs +213 -0
- package/src/project-engine/metrics.mjs +146 -0
- package/src/project-model.mjs +111 -0
- package/src/project-report.mjs +348 -0
- package/src/refactoring-guide.mjs +242 -0
- package/src/stack-detect.mjs +102 -0
- package/src/stack-linter.mjs +213 -0
- package/src/template-infer.mjs +674 -0
- package/src/type-auditor.mjs +609 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Documentation Generator - Auto-generate API docs from JSDoc
|
|
3
|
+
* @module @unrdf/project-engine/doc-generator
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
7
|
+
import { join, basename, relative } from 'node:path';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* JSDoc tag schema
|
|
12
|
+
*/
|
|
13
|
+
const _JSDocTagSchema = z.object({
|
|
14
|
+
name: z.string(),
|
|
15
|
+
description: z.string(),
|
|
16
|
+
params: z.array(
|
|
17
|
+
z.object({
|
|
18
|
+
name: z.string(),
|
|
19
|
+
type: z.string(),
|
|
20
|
+
description: z.string(),
|
|
21
|
+
})
|
|
22
|
+
),
|
|
23
|
+
returns: z
|
|
24
|
+
.object({
|
|
25
|
+
type: z.string(),
|
|
26
|
+
description: z.string(),
|
|
27
|
+
})
|
|
28
|
+
.optional(),
|
|
29
|
+
throws: z.array(z.string()).optional(),
|
|
30
|
+
examples: z.array(z.string()).optional(),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Generate API documentation from JSDoc comments
|
|
35
|
+
* @param {string} packagePath - Path to package directory
|
|
36
|
+
* @returns {Promise<string>} Markdown API documentation
|
|
37
|
+
*
|
|
38
|
+
* @throws {TypeError} If packagePath is not a string
|
|
39
|
+
* @throws {Error} If package directory doesn't exist
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* const docs = await generateApiDocs('./packages/core');
|
|
43
|
+
* console.log(docs);
|
|
44
|
+
*/
|
|
45
|
+
export async function generateApiDocs(packagePath) {
|
|
46
|
+
if (typeof packagePath !== 'string') {
|
|
47
|
+
throw new TypeError('generateApiDocs: packagePath must be a string');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const srcPath = join(packagePath, 'src');
|
|
52
|
+
const files = await findSourceFiles(srcPath);
|
|
53
|
+
|
|
54
|
+
const sections = [];
|
|
55
|
+
sections.push('# API Documentation\n');
|
|
56
|
+
sections.push(`Package: \`${basename(packagePath)}\`\n`);
|
|
57
|
+
|
|
58
|
+
for (const file of files) {
|
|
59
|
+
const content = await readFile(file, 'utf-8');
|
|
60
|
+
const functions = extractFunctions(content);
|
|
61
|
+
|
|
62
|
+
if (functions.length > 0) {
|
|
63
|
+
const relPath = relative(srcPath, file);
|
|
64
|
+
sections.push(`## ${relPath}\n`);
|
|
65
|
+
|
|
66
|
+
for (const fn of functions) {
|
|
67
|
+
sections.push(formatFunctionDoc(fn));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return sections.join('\n');
|
|
73
|
+
} catch (error) {
|
|
74
|
+
throw new Error(`generateApiDocs failed: ${error.message}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Generate package user guide template
|
|
80
|
+
* @param {string} packagePath - Path to package directory
|
|
81
|
+
* @returns {Promise<string>} Markdown user guide template
|
|
82
|
+
*
|
|
83
|
+
* @throws {TypeError} If packagePath is not a string
|
|
84
|
+
* @throws {Error} If package.json cannot be read
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* const guide = await generatePackageGuide('./packages/core');
|
|
88
|
+
* console.log(guide);
|
|
89
|
+
*/
|
|
90
|
+
export async function generatePackageGuide(packagePath) {
|
|
91
|
+
if (typeof packagePath !== 'string') {
|
|
92
|
+
throw new TypeError('generatePackageGuide: packagePath must be a string');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const packageJsonPath = join(packagePath, 'package.json');
|
|
97
|
+
const packageJsonContent = await readFile(packageJsonPath, 'utf-8');
|
|
98
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
99
|
+
|
|
100
|
+
const sections = [];
|
|
101
|
+
sections.push(`# ${packageJson.name}\n`);
|
|
102
|
+
sections.push(`${packageJson.description}\n`);
|
|
103
|
+
sections.push('## Installation\n');
|
|
104
|
+
sections.push('```bash');
|
|
105
|
+
sections.push(`pnpm add ${packageJson.name}`);
|
|
106
|
+
sections.push('```\n');
|
|
107
|
+
sections.push('## Quick Start\n');
|
|
108
|
+
sections.push('```javascript');
|
|
109
|
+
sections.push(`import { /* exports */ } from '${packageJson.name}';`);
|
|
110
|
+
sections.push('```\n');
|
|
111
|
+
sections.push('## Features\n');
|
|
112
|
+
sections.push('- Feature 1');
|
|
113
|
+
sections.push('- Feature 2\n');
|
|
114
|
+
sections.push('## API Reference\n');
|
|
115
|
+
sections.push('See [API.md](./API.md) for detailed API documentation.\n');
|
|
116
|
+
sections.push('## Examples\n');
|
|
117
|
+
sections.push('### Basic Usage\n');
|
|
118
|
+
sections.push('```javascript');
|
|
119
|
+
sections.push('// Add example code here');
|
|
120
|
+
sections.push('```\n');
|
|
121
|
+
sections.push('## License\n');
|
|
122
|
+
sections.push(`${packageJson.license || 'MIT'}\n`);
|
|
123
|
+
|
|
124
|
+
return sections.join('\n');
|
|
125
|
+
} catch (error) {
|
|
126
|
+
throw new Error(`generatePackageGuide failed: ${error.message}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Generate changelog from git commits
|
|
132
|
+
* @param {string} repoPath - Path to git repository
|
|
133
|
+
* @returns {Promise<string>} Markdown changelog
|
|
134
|
+
*
|
|
135
|
+
* @throws {TypeError} If repoPath is not a string
|
|
136
|
+
* @throws {Error} If git operations fail
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* const changelog = await generateChangelog('.');
|
|
140
|
+
* console.log(changelog);
|
|
141
|
+
*/
|
|
142
|
+
export async function generateChangelog(repoPath) {
|
|
143
|
+
if (typeof repoPath !== 'string') {
|
|
144
|
+
throw new TypeError('generateChangelog: repoPath must be a string');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const { execFile } = await import('node:child_process');
|
|
149
|
+
const { promisify } = await import('node:util');
|
|
150
|
+
const execFileAsync = promisify(execFile);
|
|
151
|
+
|
|
152
|
+
// Get git log with commit messages
|
|
153
|
+
const { stdout } = await execFileAsync(
|
|
154
|
+
'git',
|
|
155
|
+
['log', '--pretty=format:%H|%ai|%s|%an', '--no-merges'],
|
|
156
|
+
{ cwd: repoPath }
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const commits = stdout
|
|
160
|
+
.trim()
|
|
161
|
+
.split('\n')
|
|
162
|
+
.map(line => {
|
|
163
|
+
const [hash, date, message, author] = line.split('|');
|
|
164
|
+
return { hash, date, message, author };
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const sections = [];
|
|
168
|
+
sections.push('# Changelog\n');
|
|
169
|
+
|
|
170
|
+
// Group by date (YYYY-MM-DD)
|
|
171
|
+
const byDate = new Map();
|
|
172
|
+
for (const commit of commits) {
|
|
173
|
+
const dateKey = commit.date.split(' ')[0];
|
|
174
|
+
if (!byDate.has(dateKey)) {
|
|
175
|
+
byDate.set(dateKey, []);
|
|
176
|
+
}
|
|
177
|
+
byDate.get(dateKey).push(commit);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Sort dates descending
|
|
181
|
+
const sortedDates = Array.from(byDate.keys()).sort().reverse();
|
|
182
|
+
|
|
183
|
+
for (const date of sortedDates.slice(0, 30)) {
|
|
184
|
+
sections.push(`## ${date}\n`);
|
|
185
|
+
const dateCommits = byDate.get(date);
|
|
186
|
+
for (const commit of dateCommits) {
|
|
187
|
+
sections.push(`- ${commit.message} (${commit.hash.slice(0, 7)})`);
|
|
188
|
+
}
|
|
189
|
+
sections.push('');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return sections.join('\n');
|
|
193
|
+
} catch (error) {
|
|
194
|
+
throw new Error(`generateChangelog failed: ${error.message}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Find all source files in directory
|
|
200
|
+
* @param {string} dirPath - Directory path
|
|
201
|
+
* @returns {Promise<Array<string>>} List of file paths
|
|
202
|
+
*/
|
|
203
|
+
async function findSourceFiles(dirPath) {
|
|
204
|
+
const files = [];
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
208
|
+
|
|
209
|
+
for (const entry of entries) {
|
|
210
|
+
const fullPath = join(dirPath, entry.name);
|
|
211
|
+
|
|
212
|
+
if (entry.isDirectory()) {
|
|
213
|
+
const subFiles = await findSourceFiles(fullPath);
|
|
214
|
+
files.push(...subFiles);
|
|
215
|
+
} else if (entry.isFile() && entry.name.endsWith('.mjs')) {
|
|
216
|
+
files.push(fullPath);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} catch (error) {
|
|
220
|
+
// Directory doesn't exist or not accessible
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return files;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Extract function documentation from source code
|
|
228
|
+
* @param {string} content - Source code content
|
|
229
|
+
* @returns {Array<Object>} Function documentation objects
|
|
230
|
+
*/
|
|
231
|
+
function extractFunctions(content) {
|
|
232
|
+
const functions = [];
|
|
233
|
+
const lines = content.split('\n');
|
|
234
|
+
|
|
235
|
+
let currentDoc = null;
|
|
236
|
+
let inDocComment = false;
|
|
237
|
+
|
|
238
|
+
for (let i = 0; i < lines.length; i++) {
|
|
239
|
+
const line = lines[i].trim();
|
|
240
|
+
|
|
241
|
+
// Start of JSDoc comment
|
|
242
|
+
if (line.startsWith('/**')) {
|
|
243
|
+
inDocComment = true;
|
|
244
|
+
currentDoc = { lines: [] };
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// End of JSDoc comment
|
|
249
|
+
if (line.startsWith('*/') && inDocComment) {
|
|
250
|
+
inDocComment = false;
|
|
251
|
+
|
|
252
|
+
// Check next non-empty line for function declaration
|
|
253
|
+
let j = i + 1;
|
|
254
|
+
while (j < lines.length && lines[j].trim() === '') {
|
|
255
|
+
j++;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (j < lines.length) {
|
|
259
|
+
const nextLine = lines[j].trim();
|
|
260
|
+
const fnMatch = nextLine.match(/export\s+(async\s+)?function\s+(\w+)/);
|
|
261
|
+
|
|
262
|
+
if (fnMatch) {
|
|
263
|
+
const fnName = fnMatch[2];
|
|
264
|
+
const doc = parseJSDoc(currentDoc.lines);
|
|
265
|
+
functions.push({
|
|
266
|
+
name: fnName,
|
|
267
|
+
description: doc.description,
|
|
268
|
+
params: doc.params,
|
|
269
|
+
returns: doc.returns,
|
|
270
|
+
throws: doc.throws,
|
|
271
|
+
examples: doc.examples,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
currentDoc = null;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Inside JSDoc comment
|
|
281
|
+
if (inDocComment && currentDoc) {
|
|
282
|
+
const cleaned = line.replace(/^\*\s?/, '');
|
|
283
|
+
currentDoc.lines.push(cleaned);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return functions;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Parse JSDoc comment lines
|
|
292
|
+
* @param {Array<string>} lines - JSDoc comment lines
|
|
293
|
+
* @returns {Object} Parsed documentation
|
|
294
|
+
*/
|
|
295
|
+
function parseJSDoc(lines) {
|
|
296
|
+
const doc = {
|
|
297
|
+
description: '',
|
|
298
|
+
params: [],
|
|
299
|
+
returns: null,
|
|
300
|
+
throws: [],
|
|
301
|
+
examples: [],
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
let currentTag = null;
|
|
305
|
+
let currentText = [];
|
|
306
|
+
|
|
307
|
+
for (const line of lines) {
|
|
308
|
+
if (line.startsWith('@')) {
|
|
309
|
+
// Save previous tag
|
|
310
|
+
if (currentTag) {
|
|
311
|
+
saveTag(doc, currentTag, currentText.join(' '));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Parse new tag
|
|
315
|
+
const match = line.match(/@(\w+)\s+(.+)/);
|
|
316
|
+
if (match) {
|
|
317
|
+
currentTag = match[1];
|
|
318
|
+
currentText = [match[2]];
|
|
319
|
+
}
|
|
320
|
+
} else if (currentTag) {
|
|
321
|
+
currentText.push(line);
|
|
322
|
+
} else {
|
|
323
|
+
doc.description += line + ' ';
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Save last tag
|
|
328
|
+
if (currentTag) {
|
|
329
|
+
saveTag(doc, currentTag, currentText.join(' '));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
doc.description = doc.description.trim();
|
|
333
|
+
|
|
334
|
+
return doc;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Save parsed JSDoc tag
|
|
339
|
+
* @param {Object} doc - Documentation object
|
|
340
|
+
* @param {string} tag - Tag name
|
|
341
|
+
* @param {string} text - Tag text
|
|
342
|
+
*/
|
|
343
|
+
function saveTag(doc, tag, text) {
|
|
344
|
+
if (tag === 'param') {
|
|
345
|
+
const match = text.match(/\{([^}]+)\}\s+(\w+)\s+-\s+(.+)/);
|
|
346
|
+
if (match) {
|
|
347
|
+
doc.params.push({
|
|
348
|
+
type: match[1],
|
|
349
|
+
name: match[2],
|
|
350
|
+
description: match[3].trim(),
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
} else if (tag === 'returns') {
|
|
354
|
+
const match = text.match(/\{([^}]+)\}\s+(.+)/);
|
|
355
|
+
if (match) {
|
|
356
|
+
doc.returns = {
|
|
357
|
+
type: match[1],
|
|
358
|
+
description: match[2].trim(),
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
} else if (tag === 'throws') {
|
|
362
|
+
doc.throws.push(text.trim());
|
|
363
|
+
} else if (tag === 'example') {
|
|
364
|
+
doc.examples.push(text.trim());
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Format function documentation as Markdown
|
|
370
|
+
* @param {Object} fn - Function documentation
|
|
371
|
+
* @returns {string} Markdown formatted documentation
|
|
372
|
+
*/
|
|
373
|
+
function formatFunctionDoc(fn) {
|
|
374
|
+
const sections = [];
|
|
375
|
+
|
|
376
|
+
sections.push(`### ${fn.name}\n`);
|
|
377
|
+
sections.push(`${fn.description}\n`);
|
|
378
|
+
|
|
379
|
+
if (fn.params.length > 0) {
|
|
380
|
+
sections.push('**Parameters:**\n');
|
|
381
|
+
for (const param of fn.params) {
|
|
382
|
+
sections.push(`- \`${param.name}\` (\`${param.type}\`): ${param.description}`);
|
|
383
|
+
}
|
|
384
|
+
sections.push('');
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (fn.returns) {
|
|
388
|
+
sections.push(`**Returns:** \`${fn.returns.type}\` - ${fn.returns.description}\n`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (fn.throws && fn.throws.length > 0) {
|
|
392
|
+
sections.push('**Throws:**\n');
|
|
393
|
+
for (const throwsDesc of fn.throws) {
|
|
394
|
+
sections.push(`- ${throwsDesc}`);
|
|
395
|
+
}
|
|
396
|
+
sections.push('');
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (fn.examples && fn.examples.length > 0) {
|
|
400
|
+
sections.push('**Example:**\n');
|
|
401
|
+
sections.push('```javascript');
|
|
402
|
+
sections.push(fn.examples.join('\n'));
|
|
403
|
+
sections.push('```\n');
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return sections.join('\n');
|
|
407
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Infrastructure - Project configuration and templates
|
|
3
|
+
* @module @unrdf/project-engine/infrastructure
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Project config schema
|
|
10
|
+
*/
|
|
11
|
+
const ProjectConfigSchema = z.object({
|
|
12
|
+
name: z.string(),
|
|
13
|
+
type: z.enum(['library', 'application', 'monorepo']),
|
|
14
|
+
runtime: z.enum(['node', 'browser', 'both']),
|
|
15
|
+
testing: z.object({
|
|
16
|
+
framework: z.enum(['vitest', 'jest', 'mocha']),
|
|
17
|
+
coverage: z.number().min(0).max(100),
|
|
18
|
+
}),
|
|
19
|
+
linting: z.object({
|
|
20
|
+
enabled: z.boolean(),
|
|
21
|
+
rules: z.string(),
|
|
22
|
+
}),
|
|
23
|
+
formatting: z.object({
|
|
24
|
+
enabled: z.boolean(),
|
|
25
|
+
tool: z.enum(['prettier', 'eslint']),
|
|
26
|
+
}),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Create project configuration
|
|
31
|
+
* @param {string} template - Template name (basic, library, monorepo)
|
|
32
|
+
* @returns {Object} Project configuration object
|
|
33
|
+
*
|
|
34
|
+
* @throws {TypeError} If template is not a string
|
|
35
|
+
* @throws {Error} If template is invalid
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* const config = createProjectConfig('library');
|
|
39
|
+
* console.log('Config:', config);
|
|
40
|
+
*/
|
|
41
|
+
export function createProjectConfig(template) {
|
|
42
|
+
if (typeof template !== 'string') {
|
|
43
|
+
throw new TypeError('createProjectConfig: template must be a string');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const templates = {
|
|
47
|
+
basic: {
|
|
48
|
+
name: 'basic-project',
|
|
49
|
+
type: 'library',
|
|
50
|
+
runtime: 'node',
|
|
51
|
+
testing: {
|
|
52
|
+
framework: 'vitest',
|
|
53
|
+
coverage: 80,
|
|
54
|
+
},
|
|
55
|
+
linting: {
|
|
56
|
+
enabled: true,
|
|
57
|
+
rules: 'recommended',
|
|
58
|
+
},
|
|
59
|
+
formatting: {
|
|
60
|
+
enabled: true,
|
|
61
|
+
tool: 'prettier',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
library: {
|
|
65
|
+
name: 'my-library',
|
|
66
|
+
type: 'library',
|
|
67
|
+
runtime: 'both',
|
|
68
|
+
testing: {
|
|
69
|
+
framework: 'vitest',
|
|
70
|
+
coverage: 90,
|
|
71
|
+
},
|
|
72
|
+
linting: {
|
|
73
|
+
enabled: true,
|
|
74
|
+
rules: 'strict',
|
|
75
|
+
},
|
|
76
|
+
formatting: {
|
|
77
|
+
enabled: true,
|
|
78
|
+
tool: 'prettier',
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
monorepo: {
|
|
82
|
+
name: 'my-monorepo',
|
|
83
|
+
type: 'monorepo',
|
|
84
|
+
runtime: 'both',
|
|
85
|
+
testing: {
|
|
86
|
+
framework: 'vitest',
|
|
87
|
+
coverage: 80,
|
|
88
|
+
},
|
|
89
|
+
linting: {
|
|
90
|
+
enabled: true,
|
|
91
|
+
rules: 'recommended',
|
|
92
|
+
},
|
|
93
|
+
formatting: {
|
|
94
|
+
enabled: true,
|
|
95
|
+
tool: 'prettier',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const config = templates[template];
|
|
101
|
+
|
|
102
|
+
if (!config) {
|
|
103
|
+
throw new Error(`createProjectConfig: invalid template "${template}"`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return ProjectConfigSchema.parse(config);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Setup development environment configuration
|
|
111
|
+
* @returns {Object} Development environment config
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* const devConfig = setupDevEnvironment();
|
|
115
|
+
* console.log('Dev tools:', devConfig.tools);
|
|
116
|
+
*/
|
|
117
|
+
export function setupDevEnvironment() {
|
|
118
|
+
return {
|
|
119
|
+
tools: {
|
|
120
|
+
packageManager: 'pnpm',
|
|
121
|
+
nodeVersion: '>=18.0.0',
|
|
122
|
+
editor: {
|
|
123
|
+
vscode: {
|
|
124
|
+
extensions: ['dbaeumer.vscode-eslint', 'esbenp.prettier-vscode', 'vitest.explorer'],
|
|
125
|
+
settings: {
|
|
126
|
+
'editor.formatOnSave': true,
|
|
127
|
+
'editor.codeActionsOnSave': {
|
|
128
|
+
'source.fixAll.eslint': true,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
git: {
|
|
134
|
+
hooks: {
|
|
135
|
+
'pre-commit': 'pnpm lint && pnpm test',
|
|
136
|
+
'pre-push': 'pnpm build',
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
scripts: {
|
|
141
|
+
dev: 'vitest --watch',
|
|
142
|
+
build: 'node build.config.mjs',
|
|
143
|
+
test: 'vitest run --coverage',
|
|
144
|
+
lint: 'eslint src/ test/ --max-warnings=0',
|
|
145
|
+
format: 'prettier --write src/ test/',
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Create deployment configuration
|
|
152
|
+
* @param {string} environment - Environment name (development, staging, production)
|
|
153
|
+
* @returns {Object} Deployment configuration
|
|
154
|
+
*
|
|
155
|
+
* @throws {TypeError} If environment is not a string
|
|
156
|
+
* @throws {Error} If environment is invalid
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* const deployConfig = createDeploymentConfig('production');
|
|
160
|
+
* console.log('Deploy to:', deployConfig.target);
|
|
161
|
+
*/
|
|
162
|
+
export function createDeploymentConfig(environment) {
|
|
163
|
+
if (typeof environment !== 'string') {
|
|
164
|
+
throw new TypeError('createDeploymentConfig: environment must be a string');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const configs = {
|
|
168
|
+
development: {
|
|
169
|
+
environment: 'development',
|
|
170
|
+
target: 'local',
|
|
171
|
+
build: {
|
|
172
|
+
minify: false,
|
|
173
|
+
sourceMaps: true,
|
|
174
|
+
},
|
|
175
|
+
env: {
|
|
176
|
+
NODE_ENV: 'development',
|
|
177
|
+
DEBUG: 'true',
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
staging: {
|
|
181
|
+
environment: 'staging',
|
|
182
|
+
target: 'staging-server',
|
|
183
|
+
build: {
|
|
184
|
+
minify: true,
|
|
185
|
+
sourceMaps: true,
|
|
186
|
+
},
|
|
187
|
+
env: {
|
|
188
|
+
NODE_ENV: 'staging',
|
|
189
|
+
DEBUG: 'false',
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
production: {
|
|
193
|
+
environment: 'production',
|
|
194
|
+
target: 'production-server',
|
|
195
|
+
build: {
|
|
196
|
+
minify: true,
|
|
197
|
+
sourceMaps: false,
|
|
198
|
+
},
|
|
199
|
+
env: {
|
|
200
|
+
NODE_ENV: 'production',
|
|
201
|
+
DEBUG: 'false',
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const config = configs[environment];
|
|
207
|
+
|
|
208
|
+
if (!config) {
|
|
209
|
+
throw new Error(`createDeploymentConfig: invalid environment "${environment}"`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return config;
|
|
213
|
+
}
|