@timeax/scaffold 0.0.4 → 0.0.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/.idea/dictionaries/project.xml +7 -0
- package/.idea/modules.xml +8 -0
- package/.idea/php.xml +34 -0
- package/.idea/scaffold.iml +8 -0
- package/.idea/vcs.xml +6 -0
- package/dist/ast.cjs.map +1 -1
- package/dist/ast.d.cts +4 -2
- package/dist/ast.d.ts +4 -2
- package/dist/ast.mjs.map +1 -1
- package/dist/cli.cjs +1673 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.mjs +1662 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/config-C0067l3c.d.cts +383 -0
- package/dist/config-C0067l3c.d.ts +383 -0
- package/dist/index.cjs +439 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -347
- package/dist/index.d.ts +8 -347
- package/dist/index.mjs +439 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/ast/format.ts +2 -1
- package/src/cli/main.ts +3 -1
- package/src/core/format.ts +87 -0
- package/src/core/init-scaffold.ts +126 -88
- package/src/core/runner.ts +74 -66
- package/src/core/watcher.ts +87 -79
- package/src/schema/config.ts +198 -154
- package/tsup.config.ts +24 -60
package/dist/index.cjs
CHANGED
|
@@ -213,6 +213,199 @@ async function importTsConfig(configPath) {
|
|
|
213
213
|
}
|
|
214
214
|
|
|
215
215
|
// src/ast/parser.ts
|
|
216
|
+
function parseStructureAst(text, opts = {}) {
|
|
217
|
+
const indentStep = opts.indentStep ?? 2;
|
|
218
|
+
const mode = opts.mode ?? "loose";
|
|
219
|
+
const diagnostics = [];
|
|
220
|
+
const lines = [];
|
|
221
|
+
const rawLines = text.split(/\r?\n/);
|
|
222
|
+
for (let i = 0; i < rawLines.length; i++) {
|
|
223
|
+
const raw = rawLines[i];
|
|
224
|
+
const lineNo = i + 1;
|
|
225
|
+
const m = raw.match(/^(\s*)(.*)$/);
|
|
226
|
+
const indentRaw = m ? m[1] : "";
|
|
227
|
+
const content = m ? m[2] : "";
|
|
228
|
+
const { indentSpaces, hasTabs } = measureIndent(indentRaw, indentStep);
|
|
229
|
+
if (hasTabs) {
|
|
230
|
+
diagnostics.push({
|
|
231
|
+
line: lineNo,
|
|
232
|
+
message: "Tabs detected in indentation. Consider using spaces only for consistent levels.",
|
|
233
|
+
severity: mode === "strict" ? "warning" : "info",
|
|
234
|
+
code: "indent-tabs"
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
const trimmed = content.trim();
|
|
238
|
+
let kind;
|
|
239
|
+
if (!trimmed) {
|
|
240
|
+
kind = "blank";
|
|
241
|
+
} else if (trimmed.startsWith("#") || trimmed.startsWith("//")) {
|
|
242
|
+
kind = "comment";
|
|
243
|
+
} else {
|
|
244
|
+
kind = "entry";
|
|
245
|
+
}
|
|
246
|
+
lines.push({
|
|
247
|
+
index: i,
|
|
248
|
+
lineNo,
|
|
249
|
+
raw,
|
|
250
|
+
kind,
|
|
251
|
+
indentSpaces,
|
|
252
|
+
content
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
const rootNodes = [];
|
|
256
|
+
const stack = [];
|
|
257
|
+
const depthCtx = {
|
|
258
|
+
lastIndentSpaces: null,
|
|
259
|
+
lastDepth: null,
|
|
260
|
+
lastWasFile: false
|
|
261
|
+
};
|
|
262
|
+
for (const line of lines) {
|
|
263
|
+
if (line.kind !== "entry") continue;
|
|
264
|
+
const { entry, depth, diags } = parseEntryLine(
|
|
265
|
+
line,
|
|
266
|
+
indentStep,
|
|
267
|
+
mode,
|
|
268
|
+
depthCtx
|
|
269
|
+
);
|
|
270
|
+
diagnostics.push(...diags);
|
|
271
|
+
if (!entry) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
attachNode(entry, depth, line, rootNodes, stack, diagnostics, mode);
|
|
275
|
+
depthCtx.lastWasFile = !entry.isDir;
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
rootNodes,
|
|
279
|
+
lines,
|
|
280
|
+
diagnostics,
|
|
281
|
+
options: {
|
|
282
|
+
indentStep,
|
|
283
|
+
mode
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function measureIndent(rawIndent, indentStep) {
|
|
288
|
+
let spaces = 0;
|
|
289
|
+
let hasTabs = false;
|
|
290
|
+
for (const ch of rawIndent) {
|
|
291
|
+
if (ch === " ") {
|
|
292
|
+
spaces += 1;
|
|
293
|
+
} else if (ch === " ") {
|
|
294
|
+
hasTabs = true;
|
|
295
|
+
spaces += indentStep;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return { indentSpaces: spaces, hasTabs };
|
|
299
|
+
}
|
|
300
|
+
function computeDepth(line, indentStep, mode, ctx, diagnostics) {
|
|
301
|
+
let spaces = line.indentSpaces;
|
|
302
|
+
if (spaces < 0) spaces = 0;
|
|
303
|
+
let depth;
|
|
304
|
+
if (ctx.lastIndentSpaces == null || ctx.lastDepth == null) {
|
|
305
|
+
depth = 0;
|
|
306
|
+
} else {
|
|
307
|
+
const prevSpaces = ctx.lastIndentSpaces;
|
|
308
|
+
const prevDepth = ctx.lastDepth;
|
|
309
|
+
if (spaces > prevSpaces) {
|
|
310
|
+
const diff = spaces - prevSpaces;
|
|
311
|
+
if (ctx.lastWasFile) {
|
|
312
|
+
diagnostics.push({
|
|
313
|
+
line: line.lineNo,
|
|
314
|
+
message: "Entry appears indented under a file; treating it as a sibling of the file instead of a child.",
|
|
315
|
+
severity: mode === "strict" ? "error" : "warning",
|
|
316
|
+
code: "child-of-file-loose"
|
|
317
|
+
});
|
|
318
|
+
depth = prevDepth;
|
|
319
|
+
} else {
|
|
320
|
+
if (diff > indentStep) {
|
|
321
|
+
diagnostics.push({
|
|
322
|
+
line: line.lineNo,
|
|
323
|
+
message: `Indentation jumps from ${prevSpaces} to ${spaces} spaces; treating as one level deeper.`,
|
|
324
|
+
severity: mode === "strict" ? "error" : "warning",
|
|
325
|
+
code: "indent-skip-level"
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
depth = prevDepth + 1;
|
|
329
|
+
}
|
|
330
|
+
} else if (spaces === prevSpaces) {
|
|
331
|
+
depth = prevDepth;
|
|
332
|
+
} else {
|
|
333
|
+
const diff = prevSpaces - spaces;
|
|
334
|
+
const steps = Math.round(diff / indentStep);
|
|
335
|
+
if (diff % indentStep !== 0) {
|
|
336
|
+
diagnostics.push({
|
|
337
|
+
line: line.lineNo,
|
|
338
|
+
message: `Indentation decreases from ${prevSpaces} to ${spaces} spaces, which is not a multiple of indent step (${indentStep}).`,
|
|
339
|
+
severity: mode === "strict" ? "error" : "warning",
|
|
340
|
+
code: "indent-misaligned"
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
depth = Math.max(prevDepth - steps, 0);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
ctx.lastIndentSpaces = spaces;
|
|
347
|
+
ctx.lastDepth = depth;
|
|
348
|
+
return depth;
|
|
349
|
+
}
|
|
350
|
+
function parseEntryLine(line, indentStep, mode, ctx) {
|
|
351
|
+
const diags = [];
|
|
352
|
+
const depth = computeDepth(line, indentStep, mode, ctx, diags);
|
|
353
|
+
const { contentWithoutComment } = extractInlineCommentParts(line.content);
|
|
354
|
+
const trimmed = contentWithoutComment.trim();
|
|
355
|
+
if (!trimmed) {
|
|
356
|
+
return { entry: null, depth, diags };
|
|
357
|
+
}
|
|
358
|
+
const parts = trimmed.split(/\s+/);
|
|
359
|
+
const pathToken = parts[0];
|
|
360
|
+
const annotationTokens = parts.slice(1);
|
|
361
|
+
if (pathToken.includes(":")) {
|
|
362
|
+
diags.push({
|
|
363
|
+
line: line.lineNo,
|
|
364
|
+
message: 'Path token contains ":" which is reserved for annotations. This is likely a mistake.',
|
|
365
|
+
severity: mode === "strict" ? "error" : "warning",
|
|
366
|
+
code: "path-colon"
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
const isDir = pathToken.endsWith("/");
|
|
370
|
+
const segmentName = pathToken;
|
|
371
|
+
let stub;
|
|
372
|
+
const include = [];
|
|
373
|
+
const exclude = [];
|
|
374
|
+
for (const token of annotationTokens) {
|
|
375
|
+
if (token.startsWith("@stub:")) {
|
|
376
|
+
stub = token.slice("@stub:".length);
|
|
377
|
+
} else if (token.startsWith("@include:")) {
|
|
378
|
+
const val = token.slice("@include:".length);
|
|
379
|
+
if (val) {
|
|
380
|
+
include.push(
|
|
381
|
+
...val.split(",").map((s) => s.trim()).filter(Boolean)
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
} else if (token.startsWith("@exclude:")) {
|
|
385
|
+
const val = token.slice("@exclude:".length);
|
|
386
|
+
if (val) {
|
|
387
|
+
exclude.push(
|
|
388
|
+
...val.split(",").map((s) => s.trim()).filter(Boolean)
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
} else if (token.startsWith("@")) {
|
|
392
|
+
diags.push({
|
|
393
|
+
line: line.lineNo,
|
|
394
|
+
message: `Unknown annotation token "${token}".`,
|
|
395
|
+
severity: "info",
|
|
396
|
+
code: "unknown-annotation"
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
const entry = {
|
|
401
|
+
segmentName,
|
|
402
|
+
isDir,
|
|
403
|
+
stub,
|
|
404
|
+
include: include.length ? include : void 0,
|
|
405
|
+
exclude: exclude.length ? exclude : void 0
|
|
406
|
+
};
|
|
407
|
+
return { entry, depth, diags };
|
|
408
|
+
}
|
|
216
409
|
function mapThrough(content) {
|
|
217
410
|
let cutIndex = -1;
|
|
218
411
|
const len = content.length;
|
|
@@ -235,6 +428,214 @@ function mapThrough(content) {
|
|
|
235
428
|
}
|
|
236
429
|
return cutIndex;
|
|
237
430
|
}
|
|
431
|
+
function extractInlineCommentParts(content) {
|
|
432
|
+
const cutIndex = mapThrough(content);
|
|
433
|
+
if (cutIndex === -1) {
|
|
434
|
+
return {
|
|
435
|
+
contentWithoutComment: content,
|
|
436
|
+
inlineComment: null
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
return {
|
|
440
|
+
contentWithoutComment: content.slice(0, cutIndex),
|
|
441
|
+
inlineComment: content.slice(cutIndex)
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function attachNode(entry, depth, line, rootNodes, stack, diagnostics, mode) {
|
|
445
|
+
const lineNo = line.lineNo;
|
|
446
|
+
while (stack.length > depth) {
|
|
447
|
+
stack.pop();
|
|
448
|
+
}
|
|
449
|
+
let parent = null;
|
|
450
|
+
if (depth > 0) {
|
|
451
|
+
const candidate = stack[depth - 1];
|
|
452
|
+
if (!candidate) {
|
|
453
|
+
diagnostics.push({
|
|
454
|
+
line: lineNo,
|
|
455
|
+
message: `Entry has indent depth ${depth} but no parent at depth ${depth - 1}. Treating as root.`,
|
|
456
|
+
severity: mode === "strict" ? "error" : "warning",
|
|
457
|
+
code: "missing-parent"
|
|
458
|
+
});
|
|
459
|
+
} else if (candidate.type === "file") {
|
|
460
|
+
if (mode === "strict") {
|
|
461
|
+
diagnostics.push({
|
|
462
|
+
line: lineNo,
|
|
463
|
+
message: `Cannot attach child under file "${candidate.path}".`,
|
|
464
|
+
severity: "error",
|
|
465
|
+
code: "child-of-file"
|
|
466
|
+
});
|
|
467
|
+
} else {
|
|
468
|
+
diagnostics.push({
|
|
469
|
+
line: lineNo,
|
|
470
|
+
message: `Entry appears under file "${candidate.path}". Attaching as sibling at depth ${candidate.depth}.`,
|
|
471
|
+
severity: "warning",
|
|
472
|
+
code: "child-of-file-loose"
|
|
473
|
+
});
|
|
474
|
+
while (stack.length > candidate.depth) {
|
|
475
|
+
stack.pop();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
} else {
|
|
479
|
+
parent = candidate;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const parentPath = parent ? parent.path.replace(/\/$/, "") : "";
|
|
483
|
+
const normalizedSegment = toPosixPath(entry.segmentName.replace(/\/+$/, ""));
|
|
484
|
+
const fullPath = parentPath ? `${parentPath}/${normalizedSegment}${entry.isDir ? "/" : ""}` : `${normalizedSegment}${entry.isDir ? "/" : ""}`;
|
|
485
|
+
const baseNode = {
|
|
486
|
+
type: entry.isDir ? "dir" : "file",
|
|
487
|
+
name: entry.segmentName,
|
|
488
|
+
depth,
|
|
489
|
+
line: lineNo,
|
|
490
|
+
path: fullPath,
|
|
491
|
+
parent,
|
|
492
|
+
...entry.stub ? { stub: entry.stub } : {},
|
|
493
|
+
...entry.include ? { include: entry.include } : {},
|
|
494
|
+
...entry.exclude ? { exclude: entry.exclude } : {}
|
|
495
|
+
};
|
|
496
|
+
if (entry.isDir) {
|
|
497
|
+
const dirNode = {
|
|
498
|
+
...baseNode,
|
|
499
|
+
type: "dir",
|
|
500
|
+
children: []
|
|
501
|
+
};
|
|
502
|
+
if (parent) {
|
|
503
|
+
parent.children.push(dirNode);
|
|
504
|
+
} else {
|
|
505
|
+
rootNodes.push(dirNode);
|
|
506
|
+
}
|
|
507
|
+
while (stack.length > depth) {
|
|
508
|
+
stack.pop();
|
|
509
|
+
}
|
|
510
|
+
stack[depth] = dirNode;
|
|
511
|
+
} else {
|
|
512
|
+
const fileNode = {
|
|
513
|
+
...baseNode,
|
|
514
|
+
type: "file"
|
|
515
|
+
};
|
|
516
|
+
if (parent) {
|
|
517
|
+
parent.children.push(fileNode);
|
|
518
|
+
} else {
|
|
519
|
+
rootNodes.push(fileNode);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// src/ast/format.ts
|
|
525
|
+
function formatStructureText(text, options = {}) {
|
|
526
|
+
const indentStep = options.indentStep ?? 2;
|
|
527
|
+
const mode = options.mode ?? "loose";
|
|
528
|
+
const normalizeNewlines = options.normalizeNewlines === void 0 ? true : options.normalizeNewlines;
|
|
529
|
+
const trimTrailingWhitespace = options.trimTrailingWhitespace === void 0 ? true : options.trimTrailingWhitespace;
|
|
530
|
+
const normalizeAnnotations = options.normalizeAnnotations === void 0 ? true : options.normalizeAnnotations;
|
|
531
|
+
const ast = parseStructureAst(text, {
|
|
532
|
+
indentStep,
|
|
533
|
+
mode
|
|
534
|
+
});
|
|
535
|
+
const rawLines = text.split(/\r?\n/);
|
|
536
|
+
const lineCount = rawLines.length;
|
|
537
|
+
if (ast.lines.length !== lineCount) {
|
|
538
|
+
return {
|
|
539
|
+
text: basicNormalize(text, { normalizeNewlines, trimTrailingWhitespace }),
|
|
540
|
+
ast
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
const entryLineIndexes = [];
|
|
544
|
+
const inlineComments = [];
|
|
545
|
+
for (let i = 0; i < lineCount; i++) {
|
|
546
|
+
const lineMeta = ast.lines[i];
|
|
547
|
+
if (lineMeta.kind === "entry") {
|
|
548
|
+
entryLineIndexes.push(i);
|
|
549
|
+
const { inlineComment } = extractInlineCommentParts(lineMeta.content);
|
|
550
|
+
inlineComments.push(inlineComment);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
const flattened = [];
|
|
554
|
+
flattenAstNodes(ast.rootNodes, 0, flattened);
|
|
555
|
+
if (flattened.length !== entryLineIndexes.length) {
|
|
556
|
+
return {
|
|
557
|
+
text: basicNormalize(text, { normalizeNewlines, trimTrailingWhitespace }),
|
|
558
|
+
ast
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
const canonicalEntryLines = flattened.map(
|
|
562
|
+
({ node, level }) => formatAstNodeLine(node, level, indentStep, normalizeAnnotations)
|
|
563
|
+
);
|
|
564
|
+
const resultLines = [];
|
|
565
|
+
let entryIdx = 0;
|
|
566
|
+
for (let i = 0; i < lineCount; i++) {
|
|
567
|
+
const lineMeta = ast.lines[i];
|
|
568
|
+
const originalLine = rawLines[i];
|
|
569
|
+
if (lineMeta.kind === "entry") {
|
|
570
|
+
const base = canonicalEntryLines[entryIdx].replace(/[ \t]+$/g, "");
|
|
571
|
+
const inline = inlineComments[entryIdx];
|
|
572
|
+
entryIdx++;
|
|
573
|
+
if (inline) {
|
|
574
|
+
resultLines.push(base + " " + inline);
|
|
575
|
+
} else {
|
|
576
|
+
resultLines.push(base);
|
|
577
|
+
}
|
|
578
|
+
} else {
|
|
579
|
+
let out = originalLine;
|
|
580
|
+
if (trimTrailingWhitespace) {
|
|
581
|
+
out = out.replace(/[ \t]+$/g, "");
|
|
582
|
+
}
|
|
583
|
+
resultLines.push(out);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
const eol = normalizeNewlines ? detectPreferredEol(text) : getRawEol(text);
|
|
587
|
+
return {
|
|
588
|
+
text: resultLines.join(eol),
|
|
589
|
+
ast
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
function basicNormalize(text, opts) {
|
|
593
|
+
const lines = text.split(/\r?\n/);
|
|
594
|
+
const normalizedLines = opts.trimTrailingWhitespace ? lines.map((line) => line.replace(/[ \t]+$/g, "")) : lines;
|
|
595
|
+
const eol = opts.normalizeNewlines ? detectPreferredEol(text) : getRawEol(text);
|
|
596
|
+
return normalizedLines.join(eol);
|
|
597
|
+
}
|
|
598
|
+
function detectPreferredEol(text) {
|
|
599
|
+
const crlfCount = (text.match(/\r\n/g) || []).length;
|
|
600
|
+
const lfCount = (text.match(/(?<!\r)\n/g) || []).length;
|
|
601
|
+
if (crlfCount === 0 && lfCount === 0) {
|
|
602
|
+
return "\n";
|
|
603
|
+
}
|
|
604
|
+
if (crlfCount > lfCount) {
|
|
605
|
+
return "\r\n";
|
|
606
|
+
}
|
|
607
|
+
return "\n";
|
|
608
|
+
}
|
|
609
|
+
function getRawEol(text) {
|
|
610
|
+
return text.includes("\r\n") ? "\r\n" : "\n";
|
|
611
|
+
}
|
|
612
|
+
function flattenAstNodes(nodes, level, out) {
|
|
613
|
+
for (const node of nodes) {
|
|
614
|
+
out.push({ node, level });
|
|
615
|
+
if (node.type === "dir" && node.children && node.children.length) {
|
|
616
|
+
flattenAstNodes(node.children, level + 1, out);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
function formatAstNodeLine(node, level, indentStep, normalizeAnnotations) {
|
|
621
|
+
const indent = " ".repeat(indentStep * level);
|
|
622
|
+
const baseName = node.name;
|
|
623
|
+
if (!normalizeAnnotations) {
|
|
624
|
+
return indent + baseName;
|
|
625
|
+
}
|
|
626
|
+
const tokens = [];
|
|
627
|
+
if (node.stub) {
|
|
628
|
+
tokens.push(`@stub:${node.stub}`);
|
|
629
|
+
}
|
|
630
|
+
if (node.include && node.include.length > 0) {
|
|
631
|
+
tokens.push(`@include:${node.include.join(",")}`);
|
|
632
|
+
}
|
|
633
|
+
if (node.exclude && node.exclude.length > 0) {
|
|
634
|
+
tokens.push(`@exclude:${node.exclude.join(",")}`);
|
|
635
|
+
}
|
|
636
|
+
const annotations = tokens.length ? " " + tokens.join(" ") : "";
|
|
637
|
+
return indent + baseName + annotations;
|
|
638
|
+
}
|
|
238
639
|
|
|
239
640
|
// src/core/structure-txt.ts
|
|
240
641
|
function stripInlineComment(content) {
|
|
@@ -655,6 +1056,43 @@ async function applyStructure(opts) {
|
|
|
655
1056
|
}
|
|
656
1057
|
}
|
|
657
1058
|
}
|
|
1059
|
+
function getStructureFilesFromConfig(projectRoot, scaffoldDir, config) {
|
|
1060
|
+
const baseDir = path2__default.default.resolve(projectRoot, scaffoldDir || SCAFFOLD_ROOT_DIR);
|
|
1061
|
+
const files = [];
|
|
1062
|
+
if (config.groups && config.groups.length > 0) {
|
|
1063
|
+
for (const group of config.groups) {
|
|
1064
|
+
const structureFile = group.structureFile && group.structureFile.trim().length ? group.structureFile : `${group.name}.txt`;
|
|
1065
|
+
files.push(path2__default.default.join(baseDir, structureFile));
|
|
1066
|
+
}
|
|
1067
|
+
} else {
|
|
1068
|
+
const structureFile = config.structureFile || "structure.txt";
|
|
1069
|
+
files.push(path2__default.default.join(baseDir, structureFile));
|
|
1070
|
+
}
|
|
1071
|
+
return files;
|
|
1072
|
+
}
|
|
1073
|
+
async function formatStructureFilesFromConfig(projectRoot, scaffoldDir, config, opts = {}) {
|
|
1074
|
+
const formatCfg = config.format;
|
|
1075
|
+
const enabled = !!(formatCfg?.enabled || opts.force);
|
|
1076
|
+
if (!enabled) return;
|
|
1077
|
+
const files = getStructureFilesFromConfig(projectRoot, scaffoldDir, config);
|
|
1078
|
+
const indentStep = formatCfg?.indentStep ?? config.indentStep ?? 2;
|
|
1079
|
+
const mode = formatCfg?.mode ?? "loose";
|
|
1080
|
+
!!formatCfg?.sortEntries;
|
|
1081
|
+
for (const filePath of files) {
|
|
1082
|
+
let text;
|
|
1083
|
+
try {
|
|
1084
|
+
text = fs2__default.default.readFileSync(filePath, "utf8");
|
|
1085
|
+
} catch {
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
const { text: formatted } = formatStructureText(text, {
|
|
1089
|
+
indentStep,
|
|
1090
|
+
mode});
|
|
1091
|
+
if (formatted !== text) {
|
|
1092
|
+
fs2__default.default.writeFileSync(filePath, formatted, "utf8");
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
658
1096
|
|
|
659
1097
|
// src/core/runner.ts
|
|
660
1098
|
async function runOnce(cwd, options = {}) {
|
|
@@ -663,6 +1101,7 @@ async function runOnce(cwd, options = {}) {
|
|
|
663
1101
|
scaffoldDir: options.scaffoldDir,
|
|
664
1102
|
configPath: options.configPath
|
|
665
1103
|
});
|
|
1104
|
+
await formatStructureFilesFromConfig(projectRoot, scaffoldDir, config, { force: options.format });
|
|
666
1105
|
const cachePath = config.cacheFile ?? ".scaffold-cache.json";
|
|
667
1106
|
const cache = new CacheManager(projectRoot, cachePath);
|
|
668
1107
|
cache.load();
|