@forge-ts/cli 0.8.0 → 0.14.0
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/dist/index.d.ts +518 -1
- package/dist/index.js +1018 -72
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
package/dist/index.js
CHANGED
|
@@ -2,52 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { createRequire } from "module";
|
|
5
|
-
import { defineCommand as
|
|
5
|
+
import { defineCommand as defineCommand12, runMain } from "citty";
|
|
6
6
|
|
|
7
|
-
// src/commands/
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
// src/commands/audit.ts
|
|
8
|
+
import {
|
|
9
|
+
formatAuditEvent,
|
|
10
|
+
readAuditLog
|
|
11
|
+
} from "@forge-ts/core";
|
|
11
12
|
import { defineCommand } from "citty";
|
|
12
13
|
|
|
13
|
-
// src/logger.ts
|
|
14
|
-
var GREEN = "\x1B[32m";
|
|
15
|
-
var YELLOW = "\x1B[33m";
|
|
16
|
-
var RED = "\x1B[31m";
|
|
17
|
-
var BOLD = "\x1B[1m";
|
|
18
|
-
var RESET = "\x1B[0m";
|
|
19
|
-
function createLogger(options) {
|
|
20
|
-
const useColors = options?.colors ?? process.stdout.isTTY ?? false;
|
|
21
|
-
function colorize(text, code) {
|
|
22
|
-
return useColors ? `${code}${text}${RESET}` : text;
|
|
23
|
-
}
|
|
24
|
-
function bold(text) {
|
|
25
|
-
return useColors ? `${BOLD}${text}${RESET}` : text;
|
|
26
|
-
}
|
|
27
|
-
return {
|
|
28
|
-
info(msg) {
|
|
29
|
-
console.log(msg);
|
|
30
|
-
},
|
|
31
|
-
success(msg) {
|
|
32
|
-
const prefix = colorize("\u2713", GREEN);
|
|
33
|
-
console.log(`${prefix} ${msg}`);
|
|
34
|
-
},
|
|
35
|
-
warn(msg) {
|
|
36
|
-
const prefix = colorize("warn", YELLOW);
|
|
37
|
-
console.warn(`${bold(prefix)} ${msg}`);
|
|
38
|
-
},
|
|
39
|
-
error(msg) {
|
|
40
|
-
const prefix = colorize("error", RED);
|
|
41
|
-
console.error(`${bold(prefix)} ${msg}`);
|
|
42
|
-
},
|
|
43
|
-
step(label, detail, duration) {
|
|
44
|
-
const check = colorize("\u2713", GREEN);
|
|
45
|
-
const durationStr = duration !== void 0 ? ` (${duration}ms)` : "";
|
|
46
|
-
console.log(` ${check} ${bold(label)}: ${detail}${durationStr}`);
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
14
|
// src/output.ts
|
|
52
15
|
import { randomUUID } from "crypto";
|
|
53
16
|
import {
|
|
@@ -114,6 +77,146 @@ function resolveExitCode(output) {
|
|
|
114
77
|
return 1;
|
|
115
78
|
}
|
|
116
79
|
|
|
80
|
+
// src/commands/audit.ts
|
|
81
|
+
function runAudit(args) {
|
|
82
|
+
const rootDir = args.cwd ?? process.cwd();
|
|
83
|
+
const limit = args.limit ?? 20;
|
|
84
|
+
const events = readAuditLog(rootDir, {
|
|
85
|
+
limit,
|
|
86
|
+
eventType: args.type
|
|
87
|
+
});
|
|
88
|
+
const data = {
|
|
89
|
+
success: true,
|
|
90
|
+
count: events.length,
|
|
91
|
+
events
|
|
92
|
+
};
|
|
93
|
+
return {
|
|
94
|
+
operation: "audit",
|
|
95
|
+
success: true,
|
|
96
|
+
data
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function formatAuditHuman(data) {
|
|
100
|
+
if (data.count === 0) {
|
|
101
|
+
return "forge-ts audit: no events found.";
|
|
102
|
+
}
|
|
103
|
+
const lines = [];
|
|
104
|
+
lines.push(`forge-ts audit: ${data.count} event(s)
|
|
105
|
+
`);
|
|
106
|
+
for (const event of data.events) {
|
|
107
|
+
lines.push(` ${formatAuditEvent(event)}`);
|
|
108
|
+
}
|
|
109
|
+
return lines.join("\n");
|
|
110
|
+
}
|
|
111
|
+
var VALID_EVENT_TYPES = [
|
|
112
|
+
"config.lock",
|
|
113
|
+
"config.unlock",
|
|
114
|
+
"config.drift",
|
|
115
|
+
"bypass.create",
|
|
116
|
+
"bypass.expire",
|
|
117
|
+
"rule.change"
|
|
118
|
+
];
|
|
119
|
+
var auditCommand = defineCommand({
|
|
120
|
+
meta: {
|
|
121
|
+
name: "audit",
|
|
122
|
+
description: "Display the forge-ts audit trail"
|
|
123
|
+
},
|
|
124
|
+
args: {
|
|
125
|
+
cwd: {
|
|
126
|
+
type: "string",
|
|
127
|
+
description: "Project root directory"
|
|
128
|
+
},
|
|
129
|
+
limit: {
|
|
130
|
+
type: "string",
|
|
131
|
+
description: "Maximum events to display (default: 20)"
|
|
132
|
+
},
|
|
133
|
+
type: {
|
|
134
|
+
type: "string",
|
|
135
|
+
description: "Filter by event type (config.lock, config.unlock, config.drift, bypass.create, bypass.expire, rule.change)"
|
|
136
|
+
},
|
|
137
|
+
json: {
|
|
138
|
+
type: "boolean",
|
|
139
|
+
description: "Output as LAFS JSON envelope (agent-friendly)",
|
|
140
|
+
default: false
|
|
141
|
+
},
|
|
142
|
+
human: {
|
|
143
|
+
type: "boolean",
|
|
144
|
+
description: "Output as formatted text (default for TTY)",
|
|
145
|
+
default: false
|
|
146
|
+
},
|
|
147
|
+
quiet: {
|
|
148
|
+
type: "boolean",
|
|
149
|
+
description: "Suppress non-essential output",
|
|
150
|
+
default: false
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
run({ args }) {
|
|
154
|
+
const eventType = args.type;
|
|
155
|
+
if (eventType && !VALID_EVENT_TYPES.includes(eventType)) {
|
|
156
|
+
console.error(
|
|
157
|
+
`Error: invalid event type "${eventType}". Valid types: ${VALID_EVENT_TYPES.join(", ")}`
|
|
158
|
+
);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
const output = runAudit({
|
|
162
|
+
cwd: args.cwd,
|
|
163
|
+
limit: args.limit ? Number.parseInt(args.limit, 10) : void 0,
|
|
164
|
+
type: eventType
|
|
165
|
+
});
|
|
166
|
+
const flags = {
|
|
167
|
+
json: args.json,
|
|
168
|
+
human: args.human,
|
|
169
|
+
quiet: args.quiet
|
|
170
|
+
};
|
|
171
|
+
emitResult(output, flags, (data) => formatAuditHuman(data));
|
|
172
|
+
process.exit(resolveExitCode(output));
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// src/commands/build.ts
|
|
177
|
+
import { generateApi } from "@forge-ts/api";
|
|
178
|
+
import { loadConfig } from "@forge-ts/core";
|
|
179
|
+
import { generate } from "@forge-ts/gen";
|
|
180
|
+
import { defineCommand as defineCommand2 } from "citty";
|
|
181
|
+
|
|
182
|
+
// src/logger.ts
|
|
183
|
+
var GREEN = "\x1B[32m";
|
|
184
|
+
var YELLOW = "\x1B[33m";
|
|
185
|
+
var RED = "\x1B[31m";
|
|
186
|
+
var BOLD = "\x1B[1m";
|
|
187
|
+
var RESET = "\x1B[0m";
|
|
188
|
+
function createLogger(options) {
|
|
189
|
+
const useColors = options?.colors ?? process.stdout.isTTY ?? false;
|
|
190
|
+
function colorize(text, code) {
|
|
191
|
+
return useColors ? `${code}${text}${RESET}` : text;
|
|
192
|
+
}
|
|
193
|
+
function bold(text) {
|
|
194
|
+
return useColors ? `${BOLD}${text}${RESET}` : text;
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
info(msg) {
|
|
198
|
+
console.log(msg);
|
|
199
|
+
},
|
|
200
|
+
success(msg) {
|
|
201
|
+
const prefix = colorize("\u2713", GREEN);
|
|
202
|
+
console.log(`${prefix} ${msg}`);
|
|
203
|
+
},
|
|
204
|
+
warn(msg) {
|
|
205
|
+
const prefix = colorize("warn", YELLOW);
|
|
206
|
+
console.warn(`${bold(prefix)} ${msg}`);
|
|
207
|
+
},
|
|
208
|
+
error(msg) {
|
|
209
|
+
const prefix = colorize("error", RED);
|
|
210
|
+
console.error(`${bold(prefix)} ${msg}`);
|
|
211
|
+
},
|
|
212
|
+
step(label, detail, duration) {
|
|
213
|
+
const check = colorize("\u2713", GREEN);
|
|
214
|
+
const durationStr = duration !== void 0 ? ` (${duration}ms)` : "";
|
|
215
|
+
console.log(` ${check} ${bold(label)}: ${detail}${durationStr}`);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
117
220
|
// src/commands/build.ts
|
|
118
221
|
async function runBuild(args) {
|
|
119
222
|
const config = await loadConfig(args.cwd);
|
|
@@ -217,7 +320,7 @@ async function runBuild(args) {
|
|
|
217
320
|
duration: totalMs
|
|
218
321
|
};
|
|
219
322
|
}
|
|
220
|
-
var buildCommand =
|
|
323
|
+
var buildCommand = defineCommand2({
|
|
221
324
|
meta: {
|
|
222
325
|
name: "build",
|
|
223
326
|
description: "Generate API reference and documentation"
|
|
@@ -297,10 +400,180 @@ var buildCommand = defineCommand({
|
|
|
297
400
|
}
|
|
298
401
|
});
|
|
299
402
|
|
|
403
|
+
// src/commands/bypass.ts
|
|
404
|
+
import {
|
|
405
|
+
createBypass,
|
|
406
|
+
expireOldBypasses,
|
|
407
|
+
getActiveBypasses,
|
|
408
|
+
getRemainingBudget,
|
|
409
|
+
loadConfig as loadConfig2
|
|
410
|
+
} from "@forge-ts/core";
|
|
411
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
412
|
+
async function runBypassCreate(args) {
|
|
413
|
+
const config = await loadConfig2(args.cwd);
|
|
414
|
+
const rootDir = config.rootDir;
|
|
415
|
+
expireOldBypasses(rootDir);
|
|
416
|
+
try {
|
|
417
|
+
const bypass = createBypass(rootDir, args.reason, args.rule, config.bypass);
|
|
418
|
+
const remainingBudget = getRemainingBudget(rootDir, config.bypass);
|
|
419
|
+
return {
|
|
420
|
+
operation: "bypass",
|
|
421
|
+
success: true,
|
|
422
|
+
data: {
|
|
423
|
+
success: true,
|
|
424
|
+
bypass,
|
|
425
|
+
remainingBudget,
|
|
426
|
+
dailyBudget: config.bypass.dailyBudget
|
|
427
|
+
},
|
|
428
|
+
duration: 0
|
|
429
|
+
};
|
|
430
|
+
} catch (err) {
|
|
431
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
432
|
+
return {
|
|
433
|
+
operation: "bypass",
|
|
434
|
+
success: false,
|
|
435
|
+
data: {
|
|
436
|
+
success: false,
|
|
437
|
+
bypass: {},
|
|
438
|
+
remainingBudget: 0,
|
|
439
|
+
dailyBudget: config.bypass.dailyBudget
|
|
440
|
+
},
|
|
441
|
+
errors: [
|
|
442
|
+
{
|
|
443
|
+
code: "FORGE_BYPASS_BUDGET_EXHAUSTED",
|
|
444
|
+
message
|
|
445
|
+
}
|
|
446
|
+
],
|
|
447
|
+
duration: 0
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
async function runBypassStatus(args) {
|
|
452
|
+
const config = await loadConfig2(args.cwd);
|
|
453
|
+
const rootDir = config.rootDir;
|
|
454
|
+
const expiredRemoved = expireOldBypasses(rootDir);
|
|
455
|
+
const activeBypasses = getActiveBypasses(rootDir);
|
|
456
|
+
const remainingBudget = getRemainingBudget(rootDir, config.bypass);
|
|
457
|
+
return {
|
|
458
|
+
operation: "bypass",
|
|
459
|
+
success: true,
|
|
460
|
+
data: {
|
|
461
|
+
success: true,
|
|
462
|
+
activeBypasses,
|
|
463
|
+
remainingBudget,
|
|
464
|
+
dailyBudget: config.bypass.dailyBudget,
|
|
465
|
+
expiredRemoved
|
|
466
|
+
},
|
|
467
|
+
duration: 0
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
function formatBypassCreateHuman(result) {
|
|
471
|
+
const lines = [];
|
|
472
|
+
if (!result.success) {
|
|
473
|
+
lines.push("forge-ts bypass: FAILED\n");
|
|
474
|
+
lines.push(" Daily bypass budget exhausted.");
|
|
475
|
+
lines.push(` Budget: 0/${result.dailyBudget} remaining`);
|
|
476
|
+
return lines.join("\n");
|
|
477
|
+
}
|
|
478
|
+
lines.push("forge-ts bypass: created\n");
|
|
479
|
+
lines.push(` ID: ${result.bypass.id}`);
|
|
480
|
+
lines.push(` Rule: ${result.bypass.rule}`);
|
|
481
|
+
lines.push(` Reason: ${result.bypass.reason}`);
|
|
482
|
+
lines.push(` Expires: ${result.bypass.expiresAt}`);
|
|
483
|
+
lines.push(` User: ${result.bypass.user}`);
|
|
484
|
+
lines.push(`
|
|
485
|
+
Budget: ${result.remainingBudget}/${result.dailyBudget} remaining today`);
|
|
486
|
+
return lines.join("\n");
|
|
487
|
+
}
|
|
488
|
+
function formatBypassStatusHuman(result) {
|
|
489
|
+
const lines = [];
|
|
490
|
+
lines.push("forge-ts bypass: status\n");
|
|
491
|
+
lines.push(` Budget: ${result.remainingBudget}/${result.dailyBudget} remaining today`);
|
|
492
|
+
if (result.expiredRemoved > 0) {
|
|
493
|
+
lines.push(` Cleaned up ${result.expiredRemoved} expired bypass(es)`);
|
|
494
|
+
}
|
|
495
|
+
if (result.activeBypasses.length === 0) {
|
|
496
|
+
lines.push("\n No active bypasses.");
|
|
497
|
+
} else {
|
|
498
|
+
lines.push(`
|
|
499
|
+
Active bypasses (${result.activeBypasses.length}):`);
|
|
500
|
+
for (const b of result.activeBypasses) {
|
|
501
|
+
lines.push(` - [${b.rule}] ${b.reason} (expires ${b.expiresAt})`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return lines.join("\n");
|
|
505
|
+
}
|
|
506
|
+
var bypassCommand = defineCommand3({
|
|
507
|
+
meta: {
|
|
508
|
+
name: "bypass",
|
|
509
|
+
description: "Create or inspect temporary rule bypasses"
|
|
510
|
+
},
|
|
511
|
+
args: {
|
|
512
|
+
cwd: {
|
|
513
|
+
type: "string",
|
|
514
|
+
description: "Project root directory"
|
|
515
|
+
},
|
|
516
|
+
reason: {
|
|
517
|
+
type: "string",
|
|
518
|
+
description: "Mandatory justification for bypassing rules"
|
|
519
|
+
},
|
|
520
|
+
rule: {
|
|
521
|
+
type: "string",
|
|
522
|
+
description: 'Specific rule code to bypass (e.g., "E009"). Defaults to "all"'
|
|
523
|
+
},
|
|
524
|
+
status: {
|
|
525
|
+
type: "boolean",
|
|
526
|
+
description: "Show active bypasses and remaining budget",
|
|
527
|
+
default: false
|
|
528
|
+
},
|
|
529
|
+
json: {
|
|
530
|
+
type: "boolean",
|
|
531
|
+
description: "Output as LAFS JSON envelope (agent-friendly)",
|
|
532
|
+
default: false
|
|
533
|
+
},
|
|
534
|
+
human: {
|
|
535
|
+
type: "boolean",
|
|
536
|
+
description: "Output as formatted text (default for TTY)",
|
|
537
|
+
default: false
|
|
538
|
+
},
|
|
539
|
+
quiet: {
|
|
540
|
+
type: "boolean",
|
|
541
|
+
description: "Suppress non-essential output",
|
|
542
|
+
default: false
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
async run({ args }) {
|
|
546
|
+
const flags = {
|
|
547
|
+
json: args.json,
|
|
548
|
+
human: args.human,
|
|
549
|
+
quiet: args.quiet
|
|
550
|
+
};
|
|
551
|
+
if (args.status) {
|
|
552
|
+
const output2 = await runBypassStatus({ cwd: args.cwd });
|
|
553
|
+
emitResult(output2, flags, (data) => formatBypassStatusHuman(data));
|
|
554
|
+
process.exit(resolveExitCode(output2));
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (!args.reason) {
|
|
558
|
+
console.error(
|
|
559
|
+
"[forge-ts] error: --reason is required. Provide a justification for the bypass."
|
|
560
|
+
);
|
|
561
|
+
process.exit(1);
|
|
562
|
+
}
|
|
563
|
+
const output = await runBypassCreate({
|
|
564
|
+
cwd: args.cwd,
|
|
565
|
+
reason: args.reason,
|
|
566
|
+
rule: args.rule
|
|
567
|
+
});
|
|
568
|
+
emitResult(output, flags, (data) => formatBypassCreateHuman(data));
|
|
569
|
+
process.exit(resolveExitCode(output));
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
|
|
300
573
|
// src/commands/check.ts
|
|
301
|
-
import { loadConfig as
|
|
574
|
+
import { loadConfig as loadConfig3 } from "@forge-ts/core";
|
|
302
575
|
import { enforce } from "@forge-ts/enforcer";
|
|
303
|
-
import { defineCommand as
|
|
576
|
+
import { defineCommand as defineCommand4 } from "citty";
|
|
304
577
|
var RULE_NAMES = {
|
|
305
578
|
E001: "require-summary",
|
|
306
579
|
E002: "require-param",
|
|
@@ -455,7 +728,7 @@ function buildCheckResult(rawErrors, rawWarnings, exportedSymbolCount, duration,
|
|
|
455
728
|
return result;
|
|
456
729
|
}
|
|
457
730
|
async function runCheck(args) {
|
|
458
|
-
const config = await
|
|
731
|
+
const config = await loadConfig3(args.cwd);
|
|
459
732
|
if (args.strict !== void 0) {
|
|
460
733
|
config.enforce.strict = args.strict;
|
|
461
734
|
}
|
|
@@ -554,7 +827,7 @@ function formatCheckHuman(result) {
|
|
|
554
827
|
}
|
|
555
828
|
}
|
|
556
829
|
}
|
|
557
|
-
if (result.page
|
|
830
|
+
if (result.page?.hasMore) {
|
|
558
831
|
lines.push(
|
|
559
832
|
`
|
|
560
833
|
Showing ${result.page.offset + 1}-${result.page.offset + (result.byFile?.length ?? 0)} of ${result.page.total} file(s). Use --offset ${result.page.offset + result.page.limit} to see more.`
|
|
@@ -566,7 +839,7 @@ function formatCheckHuman(result) {
|
|
|
566
839
|
}
|
|
567
840
|
return lines.join("\n");
|
|
568
841
|
}
|
|
569
|
-
var checkCommand =
|
|
842
|
+
var checkCommand = defineCommand4({
|
|
570
843
|
meta: {
|
|
571
844
|
name: "check",
|
|
572
845
|
description: "Lint TSDoc coverage on exported symbols"
|
|
@@ -647,12 +920,12 @@ var checkCommand = defineCommand2({
|
|
|
647
920
|
// src/commands/docs-dev.ts
|
|
648
921
|
import { spawn } from "child_process";
|
|
649
922
|
import { resolve } from "path";
|
|
650
|
-
import { loadConfig as
|
|
923
|
+
import { loadConfig as loadConfig4 } from "@forge-ts/core";
|
|
651
924
|
import { DEFAULT_TARGET, getAdapter } from "@forge-ts/gen";
|
|
652
|
-
import { defineCommand as
|
|
925
|
+
import { defineCommand as defineCommand5 } from "citty";
|
|
653
926
|
async function runDocsDev(args) {
|
|
654
927
|
const logger = createLogger();
|
|
655
|
-
const config = await
|
|
928
|
+
const config = await loadConfig4(args.cwd);
|
|
656
929
|
const target = args.target ?? config.gen.ssgTarget ?? DEFAULT_TARGET;
|
|
657
930
|
const adapter = getAdapter(target);
|
|
658
931
|
const outDir = resolve(config.outDir);
|
|
@@ -682,7 +955,7 @@ async function runDocsDev(args) {
|
|
|
682
955
|
proc.on("error", reject);
|
|
683
956
|
});
|
|
684
957
|
}
|
|
685
|
-
var docsDevCommand =
|
|
958
|
+
var docsDevCommand = defineCommand5({
|
|
686
959
|
meta: {
|
|
687
960
|
name: "dev",
|
|
688
961
|
description: "Start a local doc preview server"
|
|
@@ -707,15 +980,16 @@ var docsDevCommand = defineCommand3({
|
|
|
707
980
|
});
|
|
708
981
|
|
|
709
982
|
// src/commands/init-docs.ts
|
|
983
|
+
import { existsSync } from "fs";
|
|
710
984
|
import { mkdir, writeFile } from "fs/promises";
|
|
711
985
|
import { join, resolve as resolve2 } from "path";
|
|
712
|
-
import { loadConfig as
|
|
986
|
+
import { loadConfig as loadConfig5 } from "@forge-ts/core";
|
|
713
987
|
import {
|
|
714
988
|
DEFAULT_TARGET as DEFAULT_TARGET2,
|
|
715
989
|
getAdapter as getAdapter2,
|
|
716
990
|
getAvailableTargets
|
|
717
991
|
} from "@forge-ts/gen";
|
|
718
|
-
import { defineCommand as
|
|
992
|
+
import { defineCommand as defineCommand6 } from "citty";
|
|
719
993
|
async function runInitDocs(args) {
|
|
720
994
|
const start = Date.now();
|
|
721
995
|
const rawTarget = args.target ?? DEFAULT_TARGET2;
|
|
@@ -741,7 +1015,7 @@ async function runInitDocs(args) {
|
|
|
741
1015
|
}
|
|
742
1016
|
const target = rawTarget;
|
|
743
1017
|
const adapter = getAdapter2(target);
|
|
744
|
-
const config = await
|
|
1018
|
+
const config = await loadConfig5(args.cwd);
|
|
745
1019
|
const outDir = args.outDir ? resolve2(args.outDir) : config.outDir;
|
|
746
1020
|
const alreadyExists = await adapter.detectExisting(outDir);
|
|
747
1021
|
if (alreadyExists && !args.force) {
|
|
@@ -792,6 +1066,28 @@ async function runInitDocs(args) {
|
|
|
792
1066
|
await writeFile(filePath, file.content, "utf8");
|
|
793
1067
|
writtenFiles.push(file.path);
|
|
794
1068
|
}
|
|
1069
|
+
if (config.tsdoc.writeConfig) {
|
|
1070
|
+
const tsdocPath = join(config.rootDir, "tsdoc.json");
|
|
1071
|
+
if (existsSync(tsdocPath)) {
|
|
1072
|
+
warnings.push({
|
|
1073
|
+
code: "INIT_TSDOC_EXISTS",
|
|
1074
|
+
message: "tsdoc.json already exists \u2014 skipping. Remove it and re-run to regenerate."
|
|
1075
|
+
});
|
|
1076
|
+
} else {
|
|
1077
|
+
const tsdocContent = JSON.stringify(
|
|
1078
|
+
{
|
|
1079
|
+
$schema: "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json",
|
|
1080
|
+
extends: ["@forge-ts/tsdoc-config/tsdoc.json"]
|
|
1081
|
+
},
|
|
1082
|
+
null,
|
|
1083
|
+
" "
|
|
1084
|
+
);
|
|
1085
|
+
await mkdir(config.rootDir, { recursive: true });
|
|
1086
|
+
await writeFile(tsdocPath, `${tsdocContent}
|
|
1087
|
+
`, "utf8");
|
|
1088
|
+
writtenFiles.push("tsdoc.json");
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
795
1091
|
const depCount = Object.keys(manifest.dependencies).length + Object.keys(manifest.devDependencies).length;
|
|
796
1092
|
const scriptCount = Object.keys(manifest.scripts).length;
|
|
797
1093
|
const data = {
|
|
@@ -849,7 +1145,7 @@ function formatInitDocsHuman(result) {
|
|
|
849
1145
|
);
|
|
850
1146
|
return lines.join("\n");
|
|
851
1147
|
}
|
|
852
|
-
var initDocsCommand =
|
|
1148
|
+
var initDocsCommand = defineCommand6({
|
|
853
1149
|
meta: {
|
|
854
1150
|
name: "init",
|
|
855
1151
|
description: "Scaffold a documentation site"
|
|
@@ -918,7 +1214,7 @@ var initDocsCommand = defineCommand4({
|
|
|
918
1214
|
process.exit(resolveExitCode(output));
|
|
919
1215
|
}
|
|
920
1216
|
});
|
|
921
|
-
var initCommand =
|
|
1217
|
+
var initCommand = defineCommand6({
|
|
922
1218
|
meta: {
|
|
923
1219
|
name: "init",
|
|
924
1220
|
description: "Scaffold project artefacts"
|
|
@@ -928,15 +1224,492 @@ var initCommand = defineCommand4({
|
|
|
928
1224
|
}
|
|
929
1225
|
});
|
|
930
1226
|
|
|
931
|
-
// src/commands/
|
|
932
|
-
import {
|
|
933
|
-
import {
|
|
934
|
-
import {
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
const
|
|
938
|
-
|
|
939
|
-
|
|
1227
|
+
// src/commands/init-hooks.ts
|
|
1228
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
1229
|
+
import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "fs/promises";
|
|
1230
|
+
import { join as join2 } from "path";
|
|
1231
|
+
import { defineCommand as defineCommand7 } from "citty";
|
|
1232
|
+
function detectHookManager(rootDir) {
|
|
1233
|
+
const huskyDir = join2(rootDir, ".husky");
|
|
1234
|
+
if (existsSync2(huskyDir)) {
|
|
1235
|
+
return "husky";
|
|
1236
|
+
}
|
|
1237
|
+
const lefthookYml = join2(rootDir, "lefthook.yml");
|
|
1238
|
+
if (existsSync2(lefthookYml)) {
|
|
1239
|
+
return "lefthook";
|
|
1240
|
+
}
|
|
1241
|
+
const pkgJsonPath = join2(rootDir, "package.json");
|
|
1242
|
+
if (existsSync2(pkgJsonPath)) {
|
|
1243
|
+
try {
|
|
1244
|
+
const raw = readFileSync(pkgJsonPath, "utf8");
|
|
1245
|
+
const pkg2 = JSON.parse(raw);
|
|
1246
|
+
const allDeps = { ...pkg2.dependencies, ...pkg2.devDependencies };
|
|
1247
|
+
if ("husky" in allDeps) return "husky";
|
|
1248
|
+
if ("lefthook" in allDeps) return "lefthook";
|
|
1249
|
+
} catch {
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
return "none";
|
|
1253
|
+
}
|
|
1254
|
+
var HUSKY_PRE_COMMIT = `#!/usr/bin/env sh
|
|
1255
|
+
. "$(dirname -- "$0")/_/husky.sh"
|
|
1256
|
+
|
|
1257
|
+
npx forge-ts check
|
|
1258
|
+
`;
|
|
1259
|
+
var LEFTHOOK_BLOCK = `pre-commit:
|
|
1260
|
+
commands:
|
|
1261
|
+
forge-ts-check:
|
|
1262
|
+
run: npx forge-ts check
|
|
1263
|
+
`;
|
|
1264
|
+
async function runInitHooks(args) {
|
|
1265
|
+
const start = Date.now();
|
|
1266
|
+
const rootDir = args.cwd ?? process.cwd();
|
|
1267
|
+
const hookManager = detectHookManager(rootDir);
|
|
1268
|
+
const writtenFiles = [];
|
|
1269
|
+
const skippedFiles = [];
|
|
1270
|
+
const warnings = [];
|
|
1271
|
+
const instructions = [];
|
|
1272
|
+
if (hookManager === "husky" || hookManager === "none") {
|
|
1273
|
+
const huskyDir = join2(rootDir, ".husky");
|
|
1274
|
+
const hookPath = join2(huskyDir, "pre-commit");
|
|
1275
|
+
const relativePath = ".husky/pre-commit";
|
|
1276
|
+
if (existsSync2(hookPath) && !args.force) {
|
|
1277
|
+
const existing = await readFile(hookPath, "utf8");
|
|
1278
|
+
if (existing.includes("forge-ts check")) {
|
|
1279
|
+
skippedFiles.push(relativePath);
|
|
1280
|
+
warnings.push({
|
|
1281
|
+
code: "HOOKS_ALREADY_EXISTS",
|
|
1282
|
+
message: `${relativePath} already contains forge-ts check \u2014 skipping. Use --force to overwrite.`
|
|
1283
|
+
});
|
|
1284
|
+
} else {
|
|
1285
|
+
const appended = `${existing.trimEnd()}
|
|
1286
|
+
|
|
1287
|
+
npx forge-ts check
|
|
1288
|
+
`;
|
|
1289
|
+
await writeFile2(hookPath, appended, { mode: 493 });
|
|
1290
|
+
writtenFiles.push(relativePath);
|
|
1291
|
+
}
|
|
1292
|
+
} else {
|
|
1293
|
+
await mkdir2(huskyDir, { recursive: true });
|
|
1294
|
+
await writeFile2(hookPath, HUSKY_PRE_COMMIT, { mode: 493 });
|
|
1295
|
+
writtenFiles.push(relativePath);
|
|
1296
|
+
}
|
|
1297
|
+
if (hookManager === "none") {
|
|
1298
|
+
instructions.push(
|
|
1299
|
+
"No hook manager detected. Wrote .husky/pre-commit as a starting point.",
|
|
1300
|
+
"Install husky to activate: npx husky-init && npm install (or pnpm dlx husky-init && pnpm install)"
|
|
1301
|
+
);
|
|
1302
|
+
} else {
|
|
1303
|
+
instructions.push("Husky pre-commit hook configured to run forge-ts check.");
|
|
1304
|
+
}
|
|
1305
|
+
} else if (hookManager === "lefthook") {
|
|
1306
|
+
const lefthookPath = join2(rootDir, "lefthook.yml");
|
|
1307
|
+
const relativePath = "lefthook.yml";
|
|
1308
|
+
if (existsSync2(lefthookPath)) {
|
|
1309
|
+
const existing = await readFile(lefthookPath, "utf8");
|
|
1310
|
+
if (existing.includes("forge-ts check") && !args.force) {
|
|
1311
|
+
skippedFiles.push(relativePath);
|
|
1312
|
+
warnings.push({
|
|
1313
|
+
code: "HOOKS_ALREADY_EXISTS",
|
|
1314
|
+
message: `${relativePath} already contains forge-ts check \u2014 skipping. Use --force to overwrite.`
|
|
1315
|
+
});
|
|
1316
|
+
} else if (existing.includes("pre-commit:") && !args.force) {
|
|
1317
|
+
const appended = `${existing.trimEnd()}
|
|
1318
|
+
forge-ts-check:
|
|
1319
|
+
run: npx forge-ts check
|
|
1320
|
+
`;
|
|
1321
|
+
await writeFile2(lefthookPath, appended, "utf8");
|
|
1322
|
+
writtenFiles.push(relativePath);
|
|
1323
|
+
} else {
|
|
1324
|
+
const appended = `${existing.trimEnd()}
|
|
1325
|
+
|
|
1326
|
+
${LEFTHOOK_BLOCK}`;
|
|
1327
|
+
await writeFile2(lefthookPath, appended, "utf8");
|
|
1328
|
+
writtenFiles.push(relativePath);
|
|
1329
|
+
}
|
|
1330
|
+
} else {
|
|
1331
|
+
await writeFile2(lefthookPath, LEFTHOOK_BLOCK, "utf8");
|
|
1332
|
+
writtenFiles.push(relativePath);
|
|
1333
|
+
}
|
|
1334
|
+
instructions.push("Lefthook pre-commit hook configured to run forge-ts check.");
|
|
1335
|
+
}
|
|
1336
|
+
const data = {
|
|
1337
|
+
success: true,
|
|
1338
|
+
hookManager,
|
|
1339
|
+
summary: {
|
|
1340
|
+
filesWritten: writtenFiles.length,
|
|
1341
|
+
filesSkipped: skippedFiles.length
|
|
1342
|
+
},
|
|
1343
|
+
files: writtenFiles,
|
|
1344
|
+
instructions
|
|
1345
|
+
};
|
|
1346
|
+
return {
|
|
1347
|
+
operation: "init.hooks",
|
|
1348
|
+
success: true,
|
|
1349
|
+
data,
|
|
1350
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
1351
|
+
duration: Date.now() - start
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
function formatInitHooksHuman(result) {
|
|
1355
|
+
const lines = [];
|
|
1356
|
+
const managerName = result.hookManager === "none" ? "husky (default)" : result.hookManager;
|
|
1357
|
+
lines.push(`
|
|
1358
|
+
Configuring git hooks (${managerName})...
|
|
1359
|
+
`);
|
|
1360
|
+
for (const file of result.files) {
|
|
1361
|
+
lines.push(` \u2713 ${file}`);
|
|
1362
|
+
}
|
|
1363
|
+
if (result.summary.filesSkipped > 0) {
|
|
1364
|
+
lines.push(` (${result.summary.filesSkipped} file(s) skipped \u2014 already configured)`);
|
|
1365
|
+
}
|
|
1366
|
+
if (result.instructions.length > 0) {
|
|
1367
|
+
lines.push("\n Next steps:");
|
|
1368
|
+
for (const [idx, inst] of result.instructions.entries()) {
|
|
1369
|
+
lines.push(` ${idx + 1}. ${inst}`);
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
lines.push(`
|
|
1373
|
+
${result.summary.filesWritten} file(s) written.`);
|
|
1374
|
+
return lines.join("\n");
|
|
1375
|
+
}
|
|
1376
|
+
var initHooksCommand = defineCommand7({
|
|
1377
|
+
meta: {
|
|
1378
|
+
name: "hooks",
|
|
1379
|
+
description: "Scaffold git hook integration (husky/lefthook)"
|
|
1380
|
+
},
|
|
1381
|
+
args: {
|
|
1382
|
+
cwd: {
|
|
1383
|
+
type: "string",
|
|
1384
|
+
description: "Project root directory"
|
|
1385
|
+
},
|
|
1386
|
+
force: {
|
|
1387
|
+
type: "boolean",
|
|
1388
|
+
description: "Overwrite existing hook files",
|
|
1389
|
+
default: false
|
|
1390
|
+
},
|
|
1391
|
+
json: {
|
|
1392
|
+
type: "boolean",
|
|
1393
|
+
description: "Output as LAFS JSON envelope",
|
|
1394
|
+
default: false
|
|
1395
|
+
},
|
|
1396
|
+
human: {
|
|
1397
|
+
type: "boolean",
|
|
1398
|
+
description: "Output as formatted text",
|
|
1399
|
+
default: false
|
|
1400
|
+
},
|
|
1401
|
+
quiet: {
|
|
1402
|
+
type: "boolean",
|
|
1403
|
+
description: "Suppress non-essential output",
|
|
1404
|
+
default: false
|
|
1405
|
+
},
|
|
1406
|
+
mvi: {
|
|
1407
|
+
type: "string",
|
|
1408
|
+
description: "MVI verbosity level: minimal, standard, full"
|
|
1409
|
+
}
|
|
1410
|
+
},
|
|
1411
|
+
async run({ args }) {
|
|
1412
|
+
const output = await runInitHooks({
|
|
1413
|
+
cwd: args.cwd,
|
|
1414
|
+
force: args.force,
|
|
1415
|
+
mvi: args.mvi
|
|
1416
|
+
});
|
|
1417
|
+
const flags = {
|
|
1418
|
+
json: args.json,
|
|
1419
|
+
human: args.human,
|
|
1420
|
+
quiet: args.quiet,
|
|
1421
|
+
mvi: args.mvi
|
|
1422
|
+
};
|
|
1423
|
+
emitResult(output, flags, (data, cmd) => {
|
|
1424
|
+
if (!cmd.success) {
|
|
1425
|
+
const logger = createLogger();
|
|
1426
|
+
const msg = cmd.errors?.[0]?.message ?? "Hook scaffolding failed";
|
|
1427
|
+
logger.error(msg);
|
|
1428
|
+
return "";
|
|
1429
|
+
}
|
|
1430
|
+
return formatInitHooksHuman(data);
|
|
1431
|
+
});
|
|
1432
|
+
process.exit(resolveExitCode(output));
|
|
1433
|
+
}
|
|
1434
|
+
});
|
|
1435
|
+
|
|
1436
|
+
// src/commands/lock.ts
|
|
1437
|
+
import {
|
|
1438
|
+
appendAuditEvent,
|
|
1439
|
+
createLockManifest,
|
|
1440
|
+
loadConfig as loadConfig6,
|
|
1441
|
+
readLockFile,
|
|
1442
|
+
writeLockFile
|
|
1443
|
+
} from "@forge-ts/core";
|
|
1444
|
+
import { defineCommand as defineCommand8 } from "citty";
|
|
1445
|
+
async function runLock(args) {
|
|
1446
|
+
const config = await loadConfig6(args.cwd);
|
|
1447
|
+
const rootDir = config.rootDir;
|
|
1448
|
+
const existingLock = readLockFile(rootDir);
|
|
1449
|
+
const manifest = createLockManifest(config);
|
|
1450
|
+
writeLockFile(rootDir, manifest);
|
|
1451
|
+
appendAuditEvent(rootDir, {
|
|
1452
|
+
timestamp: manifest.lockedAt,
|
|
1453
|
+
event: "config.lock",
|
|
1454
|
+
user: manifest.lockedBy,
|
|
1455
|
+
details: {
|
|
1456
|
+
rules: Object.keys(manifest.config.rules).length,
|
|
1457
|
+
tsconfig: manifest.config.tsconfig !== void 0,
|
|
1458
|
+
biome: manifest.config.biome !== void 0,
|
|
1459
|
+
overwrote: existingLock !== null
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
const lockFile = `${rootDir}/.forge-lock.json`;
|
|
1463
|
+
const data = {
|
|
1464
|
+
success: true,
|
|
1465
|
+
lockFile,
|
|
1466
|
+
lockedAt: manifest.lockedAt,
|
|
1467
|
+
lockedBy: manifest.lockedBy,
|
|
1468
|
+
locked: {
|
|
1469
|
+
rules: Object.keys(manifest.config.rules).length,
|
|
1470
|
+
tsconfig: manifest.config.tsconfig !== void 0,
|
|
1471
|
+
biome: manifest.config.biome !== void 0
|
|
1472
|
+
},
|
|
1473
|
+
overwrote: existingLock !== null
|
|
1474
|
+
};
|
|
1475
|
+
return {
|
|
1476
|
+
operation: "lock",
|
|
1477
|
+
success: true,
|
|
1478
|
+
data,
|
|
1479
|
+
duration: 0
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
function formatLockHuman(result) {
|
|
1483
|
+
const lines = [];
|
|
1484
|
+
if (result.overwrote) {
|
|
1485
|
+
lines.push("forge-ts lock: updated existing lock\n");
|
|
1486
|
+
} else {
|
|
1487
|
+
lines.push("forge-ts lock: created .forge-lock.json\n");
|
|
1488
|
+
}
|
|
1489
|
+
lines.push(` Locked ${result.locked.rules} enforce rule(s)`);
|
|
1490
|
+
if (result.locked.tsconfig) {
|
|
1491
|
+
lines.push(" Locked tsconfig guard settings");
|
|
1492
|
+
}
|
|
1493
|
+
if (result.locked.biome) {
|
|
1494
|
+
lines.push(" Locked biome guard settings");
|
|
1495
|
+
}
|
|
1496
|
+
lines.push(`
|
|
1497
|
+
Locked by: ${result.lockedBy}`);
|
|
1498
|
+
lines.push(` Locked at: ${result.lockedAt}`);
|
|
1499
|
+
lines.push(`
|
|
1500
|
+
To modify locked settings, run: forge-ts unlock --reason="..."`);
|
|
1501
|
+
return lines.join("\n");
|
|
1502
|
+
}
|
|
1503
|
+
var lockCommand = defineCommand8({
|
|
1504
|
+
meta: {
|
|
1505
|
+
name: "lock",
|
|
1506
|
+
description: "Lock current config to prevent silent weakening"
|
|
1507
|
+
},
|
|
1508
|
+
args: {
|
|
1509
|
+
cwd: {
|
|
1510
|
+
type: "string",
|
|
1511
|
+
description: "Project root directory"
|
|
1512
|
+
},
|
|
1513
|
+
json: {
|
|
1514
|
+
type: "boolean",
|
|
1515
|
+
description: "Output as LAFS JSON envelope (agent-friendly)",
|
|
1516
|
+
default: false
|
|
1517
|
+
},
|
|
1518
|
+
human: {
|
|
1519
|
+
type: "boolean",
|
|
1520
|
+
description: "Output as formatted text (default for TTY)",
|
|
1521
|
+
default: false
|
|
1522
|
+
},
|
|
1523
|
+
quiet: {
|
|
1524
|
+
type: "boolean",
|
|
1525
|
+
description: "Suppress non-essential output",
|
|
1526
|
+
default: false
|
|
1527
|
+
}
|
|
1528
|
+
},
|
|
1529
|
+
async run({ args }) {
|
|
1530
|
+
const output = await runLock({ cwd: args.cwd });
|
|
1531
|
+
const flags = {
|
|
1532
|
+
json: args.json,
|
|
1533
|
+
human: args.human,
|
|
1534
|
+
quiet: args.quiet
|
|
1535
|
+
};
|
|
1536
|
+
emitResult(output, flags, (data) => formatLockHuman(data));
|
|
1537
|
+
process.exit(resolveExitCode(output));
|
|
1538
|
+
}
|
|
1539
|
+
});
|
|
1540
|
+
|
|
1541
|
+
// src/commands/prepublish.ts
|
|
1542
|
+
import { defineCommand as defineCommand9 } from "citty";
|
|
1543
|
+
async function runPrepublish(args) {
|
|
1544
|
+
const start = Date.now();
|
|
1545
|
+
const allErrors = [];
|
|
1546
|
+
const checkOutput = await runCheck({
|
|
1547
|
+
cwd: args.cwd,
|
|
1548
|
+
strict: args.strict,
|
|
1549
|
+
mvi: args.mvi
|
|
1550
|
+
});
|
|
1551
|
+
const checkDuration = checkOutput.duration ?? 0;
|
|
1552
|
+
if (!checkOutput.success) {
|
|
1553
|
+
const data2 = {
|
|
1554
|
+
success: false,
|
|
1555
|
+
summary: {
|
|
1556
|
+
steps: 1,
|
|
1557
|
+
passed: 0,
|
|
1558
|
+
failed: 1,
|
|
1559
|
+
duration: Date.now() - start
|
|
1560
|
+
},
|
|
1561
|
+
check: {
|
|
1562
|
+
success: false,
|
|
1563
|
+
errors: checkOutput.data.summary.errors,
|
|
1564
|
+
warnings: checkOutput.data.summary.warnings,
|
|
1565
|
+
duration: checkDuration
|
|
1566
|
+
},
|
|
1567
|
+
skippedReason: "Check failed \u2014 build step skipped."
|
|
1568
|
+
};
|
|
1569
|
+
if (checkOutput.errors) {
|
|
1570
|
+
allErrors.push(...checkOutput.errors);
|
|
1571
|
+
}
|
|
1572
|
+
return {
|
|
1573
|
+
operation: "prepublish",
|
|
1574
|
+
success: false,
|
|
1575
|
+
data: data2,
|
|
1576
|
+
errors: allErrors.length > 0 ? allErrors : void 0,
|
|
1577
|
+
duration: Date.now() - start
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
const buildOutput = await runBuild({
|
|
1581
|
+
cwd: args.cwd,
|
|
1582
|
+
mvi: args.mvi
|
|
1583
|
+
});
|
|
1584
|
+
const buildDuration = buildOutput.duration ?? 0;
|
|
1585
|
+
const buildSuccess = buildOutput.success;
|
|
1586
|
+
const overallSuccess = buildSuccess;
|
|
1587
|
+
if (buildOutput.errors) {
|
|
1588
|
+
allErrors.push(...buildOutput.errors);
|
|
1589
|
+
}
|
|
1590
|
+
const data = {
|
|
1591
|
+
success: overallSuccess,
|
|
1592
|
+
summary: {
|
|
1593
|
+
steps: 2,
|
|
1594
|
+
passed: (checkOutput.success ? 1 : 0) + (buildSuccess ? 1 : 0),
|
|
1595
|
+
failed: (checkOutput.success ? 0 : 1) + (buildSuccess ? 0 : 1),
|
|
1596
|
+
duration: Date.now() - start
|
|
1597
|
+
},
|
|
1598
|
+
check: {
|
|
1599
|
+
success: checkOutput.success,
|
|
1600
|
+
errors: checkOutput.data.summary.errors,
|
|
1601
|
+
warnings: checkOutput.data.summary.warnings,
|
|
1602
|
+
duration: checkDuration
|
|
1603
|
+
},
|
|
1604
|
+
build: {
|
|
1605
|
+
success: buildSuccess,
|
|
1606
|
+
steps: buildOutput.data.summary.steps,
|
|
1607
|
+
succeeded: buildOutput.data.summary.succeeded,
|
|
1608
|
+
failed: buildOutput.data.summary.failed,
|
|
1609
|
+
duration: buildDuration
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1612
|
+
return {
|
|
1613
|
+
operation: "prepublish",
|
|
1614
|
+
success: overallSuccess,
|
|
1615
|
+
data,
|
|
1616
|
+
errors: allErrors.length > 0 ? allErrors : void 0,
|
|
1617
|
+
duration: Date.now() - start
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
function formatPrepublishHuman(result) {
|
|
1621
|
+
const lines = [];
|
|
1622
|
+
lines.push(`
|
|
1623
|
+
forge-ts prepublish: ${result.success ? "PASSED" : "FAILED"}
|
|
1624
|
+
`);
|
|
1625
|
+
const checkIcon = result.check.success ? "\u2713" : "\u2717";
|
|
1626
|
+
lines.push(
|
|
1627
|
+
` ${checkIcon} check: ${result.check.errors} error(s), ${result.check.warnings} warning(s) (${result.check.duration}ms)`
|
|
1628
|
+
);
|
|
1629
|
+
if (result.build) {
|
|
1630
|
+
const buildIcon = result.build.success ? "\u2713" : "\u2717";
|
|
1631
|
+
lines.push(
|
|
1632
|
+
` ${buildIcon} build: ${result.build.succeeded}/${result.build.steps} steps succeeded (${result.build.duration}ms)`
|
|
1633
|
+
);
|
|
1634
|
+
} else if (result.skippedReason) {
|
|
1635
|
+
lines.push(` - build: skipped (${result.skippedReason})`);
|
|
1636
|
+
}
|
|
1637
|
+
lines.push(
|
|
1638
|
+
`
|
|
1639
|
+
${result.summary.passed}/${result.summary.steps} steps passed in ${result.summary.duration}ms`
|
|
1640
|
+
);
|
|
1641
|
+
if (!result.success) {
|
|
1642
|
+
lines.push("\n Publish blocked. Fix the above issues and re-run forge-ts prepublish.");
|
|
1643
|
+
}
|
|
1644
|
+
return lines.join("\n");
|
|
1645
|
+
}
|
|
1646
|
+
var prepublishCommand = defineCommand9({
|
|
1647
|
+
meta: {
|
|
1648
|
+
name: "prepublish",
|
|
1649
|
+
description: "Safety gate: check + build before npm publish"
|
|
1650
|
+
},
|
|
1651
|
+
args: {
|
|
1652
|
+
cwd: {
|
|
1653
|
+
type: "string",
|
|
1654
|
+
description: "Project root directory"
|
|
1655
|
+
},
|
|
1656
|
+
strict: {
|
|
1657
|
+
type: "boolean",
|
|
1658
|
+
description: "Treat warnings as errors during check",
|
|
1659
|
+
default: false
|
|
1660
|
+
},
|
|
1661
|
+
json: {
|
|
1662
|
+
type: "boolean",
|
|
1663
|
+
description: "Output as LAFS JSON envelope (agent-friendly)",
|
|
1664
|
+
default: false
|
|
1665
|
+
},
|
|
1666
|
+
human: {
|
|
1667
|
+
type: "boolean",
|
|
1668
|
+
description: "Output as formatted text (default for TTY)",
|
|
1669
|
+
default: false
|
|
1670
|
+
},
|
|
1671
|
+
quiet: {
|
|
1672
|
+
type: "boolean",
|
|
1673
|
+
description: "Suppress non-essential output",
|
|
1674
|
+
default: false
|
|
1675
|
+
},
|
|
1676
|
+
mvi: {
|
|
1677
|
+
type: "string",
|
|
1678
|
+
description: "MVI verbosity level: minimal, standard, full"
|
|
1679
|
+
}
|
|
1680
|
+
},
|
|
1681
|
+
async run({ args }) {
|
|
1682
|
+
const output = await runPrepublish({
|
|
1683
|
+
cwd: args.cwd,
|
|
1684
|
+
strict: args.strict,
|
|
1685
|
+
mvi: args.mvi
|
|
1686
|
+
});
|
|
1687
|
+
const flags = {
|
|
1688
|
+
json: args.json,
|
|
1689
|
+
human: args.human,
|
|
1690
|
+
quiet: args.quiet,
|
|
1691
|
+
mvi: args.mvi
|
|
1692
|
+
};
|
|
1693
|
+
emitResult(output, flags, (data, cmd) => {
|
|
1694
|
+
if (!cmd.success) {
|
|
1695
|
+
const logger = createLogger();
|
|
1696
|
+
logger.error("Prepublish gate failed");
|
|
1697
|
+
}
|
|
1698
|
+
return formatPrepublishHuman(data);
|
|
1699
|
+
});
|
|
1700
|
+
process.exit(resolveExitCode(output));
|
|
1701
|
+
}
|
|
1702
|
+
});
|
|
1703
|
+
|
|
1704
|
+
// src/commands/test.ts
|
|
1705
|
+
import { loadConfig as loadConfig7 } from "@forge-ts/core";
|
|
1706
|
+
import { doctest } from "@forge-ts/doctest";
|
|
1707
|
+
import { defineCommand as defineCommand10 } from "citty";
|
|
1708
|
+
async function runTest(args) {
|
|
1709
|
+
const config = await loadConfig7(args.cwd);
|
|
1710
|
+
const result = await doctest(config);
|
|
1711
|
+
const mviLevel = args.mvi ?? "standard";
|
|
1712
|
+
const failCount = result.errors.length;
|
|
940
1713
|
const totalSymbols = result.symbols.length;
|
|
941
1714
|
const passCount = totalSymbols - failCount > 0 ? totalSymbols - failCount : 0;
|
|
942
1715
|
const summary = {
|
|
@@ -974,7 +1747,7 @@ async function runTest(args) {
|
|
|
974
1747
|
duration: result.duration
|
|
975
1748
|
};
|
|
976
1749
|
}
|
|
977
|
-
var testCommand =
|
|
1750
|
+
var testCommand = defineCommand10({
|
|
978
1751
|
meta: {
|
|
979
1752
|
name: "test",
|
|
980
1753
|
description: "Run @example blocks as doctests"
|
|
@@ -1027,10 +1800,155 @@ var testCommand = defineCommand5({
|
|
|
1027
1800
|
}
|
|
1028
1801
|
});
|
|
1029
1802
|
|
|
1803
|
+
// src/commands/unlock.ts
|
|
1804
|
+
import {
|
|
1805
|
+
appendAuditEvent as appendAuditEvent2,
|
|
1806
|
+
getCurrentUser,
|
|
1807
|
+
loadConfig as loadConfig8,
|
|
1808
|
+
readLockFile as readLockFile2,
|
|
1809
|
+
removeLockFile
|
|
1810
|
+
} from "@forge-ts/core";
|
|
1811
|
+
import { defineCommand as defineCommand11 } from "citty";
|
|
1812
|
+
async function runUnlock(args) {
|
|
1813
|
+
const config = await loadConfig8(args.cwd);
|
|
1814
|
+
const rootDir = config.rootDir;
|
|
1815
|
+
const existingLock = readLockFile2(rootDir);
|
|
1816
|
+
if (!existingLock) {
|
|
1817
|
+
return {
|
|
1818
|
+
operation: "unlock",
|
|
1819
|
+
success: false,
|
|
1820
|
+
data: {
|
|
1821
|
+
success: false,
|
|
1822
|
+
reason: args.reason,
|
|
1823
|
+
previousLockedBy: null,
|
|
1824
|
+
previousLockedAt: null
|
|
1825
|
+
},
|
|
1826
|
+
errors: [
|
|
1827
|
+
{
|
|
1828
|
+
code: "FORGE_NO_LOCK",
|
|
1829
|
+
message: "No .forge-lock.json found. Nothing to unlock."
|
|
1830
|
+
}
|
|
1831
|
+
],
|
|
1832
|
+
duration: 0
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
const removed = removeLockFile(rootDir);
|
|
1836
|
+
if (!removed) {
|
|
1837
|
+
return {
|
|
1838
|
+
operation: "unlock",
|
|
1839
|
+
success: false,
|
|
1840
|
+
data: {
|
|
1841
|
+
success: false,
|
|
1842
|
+
reason: args.reason,
|
|
1843
|
+
previousLockedBy: existingLock.lockedBy,
|
|
1844
|
+
previousLockedAt: existingLock.lockedAt
|
|
1845
|
+
},
|
|
1846
|
+
errors: [
|
|
1847
|
+
{
|
|
1848
|
+
code: "FORGE_UNLOCK_FAILED",
|
|
1849
|
+
message: "Failed to remove .forge-lock.json. Check file permissions."
|
|
1850
|
+
}
|
|
1851
|
+
],
|
|
1852
|
+
duration: 0
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
appendAuditEvent2(rootDir, {
|
|
1856
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1857
|
+
event: "config.unlock",
|
|
1858
|
+
user: getCurrentUser(),
|
|
1859
|
+
reason: args.reason,
|
|
1860
|
+
details: {
|
|
1861
|
+
previousLockedBy: existingLock.lockedBy,
|
|
1862
|
+
previousLockedAt: existingLock.lockedAt
|
|
1863
|
+
}
|
|
1864
|
+
});
|
|
1865
|
+
return {
|
|
1866
|
+
operation: "unlock",
|
|
1867
|
+
success: true,
|
|
1868
|
+
data: {
|
|
1869
|
+
success: true,
|
|
1870
|
+
reason: args.reason,
|
|
1871
|
+
previousLockedBy: existingLock.lockedBy,
|
|
1872
|
+
previousLockedAt: existingLock.lockedAt
|
|
1873
|
+
},
|
|
1874
|
+
duration: 0
|
|
1875
|
+
};
|
|
1876
|
+
}
|
|
1877
|
+
function formatUnlockHuman(result) {
|
|
1878
|
+
const lines = [];
|
|
1879
|
+
if (!result.success) {
|
|
1880
|
+
lines.push("forge-ts unlock: FAILED\n");
|
|
1881
|
+
lines.push(" No .forge-lock.json found. Nothing to unlock.");
|
|
1882
|
+
return lines.join("\n");
|
|
1883
|
+
}
|
|
1884
|
+
lines.push("forge-ts unlock: removed .forge-lock.json\n");
|
|
1885
|
+
lines.push(` Reason: ${result.reason}`);
|
|
1886
|
+
if (result.previousLockedBy) {
|
|
1887
|
+
lines.push(` Previously locked by: ${result.previousLockedBy}`);
|
|
1888
|
+
}
|
|
1889
|
+
if (result.previousLockedAt) {
|
|
1890
|
+
lines.push(` Previously locked at: ${result.previousLockedAt}`);
|
|
1891
|
+
}
|
|
1892
|
+
lines.push("\n Config settings can now be modified freely.");
|
|
1893
|
+
lines.push(" Run `forge-ts lock` to re-lock after changes.");
|
|
1894
|
+
return lines.join("\n");
|
|
1895
|
+
}
|
|
1896
|
+
var unlockCommand = defineCommand11({
|
|
1897
|
+
meta: {
|
|
1898
|
+
name: "unlock",
|
|
1899
|
+
description: "Remove config lock (requires --reason)"
|
|
1900
|
+
},
|
|
1901
|
+
args: {
|
|
1902
|
+
cwd: {
|
|
1903
|
+
type: "string",
|
|
1904
|
+
description: "Project root directory"
|
|
1905
|
+
},
|
|
1906
|
+
reason: {
|
|
1907
|
+
type: "string",
|
|
1908
|
+
description: "Mandatory reason for unlocking (audit trail)",
|
|
1909
|
+
required: true
|
|
1910
|
+
},
|
|
1911
|
+
json: {
|
|
1912
|
+
type: "boolean",
|
|
1913
|
+
description: "Output as LAFS JSON envelope (agent-friendly)",
|
|
1914
|
+
default: false
|
|
1915
|
+
},
|
|
1916
|
+
human: {
|
|
1917
|
+
type: "boolean",
|
|
1918
|
+
description: "Output as formatted text (default for TTY)",
|
|
1919
|
+
default: false
|
|
1920
|
+
},
|
|
1921
|
+
quiet: {
|
|
1922
|
+
type: "boolean",
|
|
1923
|
+
description: "Suppress non-essential output",
|
|
1924
|
+
default: false
|
|
1925
|
+
}
|
|
1926
|
+
},
|
|
1927
|
+
async run({ args }) {
|
|
1928
|
+
if (!args.reason) {
|
|
1929
|
+
console.error(
|
|
1930
|
+
"[forge-ts] error: --reason is required. Provide a reason for unlocking the config."
|
|
1931
|
+
);
|
|
1932
|
+
process.exit(1);
|
|
1933
|
+
}
|
|
1934
|
+
const output = await runUnlock({
|
|
1935
|
+
cwd: args.cwd,
|
|
1936
|
+
reason: args.reason
|
|
1937
|
+
});
|
|
1938
|
+
const flags = {
|
|
1939
|
+
json: args.json,
|
|
1940
|
+
human: args.human,
|
|
1941
|
+
quiet: args.quiet
|
|
1942
|
+
};
|
|
1943
|
+
emitResult(output, flags, (data) => formatUnlockHuman(data));
|
|
1944
|
+
process.exit(resolveExitCode(output));
|
|
1945
|
+
}
|
|
1946
|
+
});
|
|
1947
|
+
|
|
1030
1948
|
// src/index.ts
|
|
1031
1949
|
var require2 = createRequire(import.meta.url);
|
|
1032
1950
|
var pkg = require2("../package.json");
|
|
1033
|
-
var docsCommand =
|
|
1951
|
+
var docsCommand = defineCommand12({
|
|
1034
1952
|
meta: {
|
|
1035
1953
|
name: "docs",
|
|
1036
1954
|
description: "Documentation site management"
|
|
@@ -1040,7 +1958,17 @@ var docsCommand = defineCommand6({
|
|
|
1040
1958
|
dev: docsDevCommand
|
|
1041
1959
|
}
|
|
1042
1960
|
});
|
|
1043
|
-
var
|
|
1961
|
+
var initCommand2 = defineCommand12({
|
|
1962
|
+
meta: {
|
|
1963
|
+
name: "init",
|
|
1964
|
+
description: "Scaffold project artefacts"
|
|
1965
|
+
},
|
|
1966
|
+
subCommands: {
|
|
1967
|
+
docs: initDocsCommand,
|
|
1968
|
+
hooks: initHooksCommand
|
|
1969
|
+
}
|
|
1970
|
+
});
|
|
1971
|
+
var main = defineCommand12({
|
|
1044
1972
|
meta: {
|
|
1045
1973
|
name: "forge-ts",
|
|
1046
1974
|
version: pkg.version,
|
|
@@ -1050,19 +1978,37 @@ var main = defineCommand6({
|
|
|
1050
1978
|
check: checkCommand,
|
|
1051
1979
|
test: testCommand,
|
|
1052
1980
|
build: buildCommand,
|
|
1053
|
-
docs: docsCommand
|
|
1981
|
+
docs: docsCommand,
|
|
1982
|
+
init: initCommand2,
|
|
1983
|
+
lock: lockCommand,
|
|
1984
|
+
unlock: unlockCommand,
|
|
1985
|
+
bypass: bypassCommand,
|
|
1986
|
+
audit: auditCommand,
|
|
1987
|
+
prepublish: prepublishCommand
|
|
1054
1988
|
}
|
|
1055
1989
|
});
|
|
1056
1990
|
runMain(main);
|
|
1057
1991
|
export {
|
|
1992
|
+
auditCommand,
|
|
1058
1993
|
buildCommand,
|
|
1994
|
+
bypassCommand,
|
|
1059
1995
|
checkCommand,
|
|
1060
1996
|
createLogger,
|
|
1061
1997
|
docsDevCommand,
|
|
1062
1998
|
emitResult,
|
|
1063
1999
|
initDocsCommand,
|
|
2000
|
+
initHooksCommand,
|
|
2001
|
+
lockCommand,
|
|
2002
|
+
prepublishCommand,
|
|
1064
2003
|
resolveExitCode,
|
|
2004
|
+
runBypassCreate,
|
|
2005
|
+
runBypassStatus,
|
|
1065
2006
|
runDocsDev,
|
|
1066
|
-
|
|
2007
|
+
runInitHooks,
|
|
2008
|
+
runLock,
|
|
2009
|
+
runPrepublish,
|
|
2010
|
+
runUnlock,
|
|
2011
|
+
testCommand,
|
|
2012
|
+
unlockCommand
|
|
1067
2013
|
};
|
|
1068
2014
|
//# sourceMappingURL=index.js.map
|