@forge-ts/cli 0.14.0 → 0.16.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.js CHANGED
@@ -1,8 +1,20 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ initProjectCommand,
4
+ runInitProject
5
+ } from "./chunk-DV33Y4E2.js";
6
+ import {
7
+ configureLogger,
8
+ forgeLogger
9
+ } from "./chunk-CNINN7IQ.js";
10
+ import {
11
+ emitResult,
12
+ resolveExitCode
13
+ } from "./chunk-ZFFY4AQX.js";
2
14
 
3
15
  // src/index.ts
4
16
  import { createRequire } from "module";
5
- import { defineCommand as defineCommand12, runMain } from "citty";
17
+ import { defineCommand as defineCommand13, runMain } from "citty";
6
18
 
7
19
  // src/commands/audit.ts
8
20
  import {
@@ -10,74 +22,6 @@ import {
10
22
  readAuditLog
11
23
  } from "@forge-ts/core";
12
24
  import { defineCommand } from "citty";
13
-
14
- // src/output.ts
15
- import { randomUUID } from "crypto";
16
- import {
17
- createEnvelope,
18
- resolveFlags
19
- } from "@cleocode/lafs-protocol";
20
- function emitResult(output, flags, humanFormatter) {
21
- const flagInput = {
22
- json: flags.json,
23
- human: flags.human,
24
- quiet: flags.quiet,
25
- mvi: flags.mvi,
26
- // LAFS 1.8.0: TTY detection drives the default format.
27
- // Terminals get human output, pipes/agents get JSON.
28
- tty: process.stdout.isTTY ?? false
29
- };
30
- const resolved = resolveFlags(flagInput);
31
- const format = resolved.format.format;
32
- const quiet = resolved.format.quiet;
33
- if (quiet) {
34
- return;
35
- }
36
- const meta = {
37
- operation: `forge-ts.${output.operation}`,
38
- requestId: randomUUID(),
39
- transport: "cli",
40
- mvi: flags.mvi ?? "full"
41
- };
42
- const resultData = output.data;
43
- if (output.warnings && output.warnings.length > 0) {
44
- resultData._warnings = output.warnings.map((w) => ({
45
- code: w.code,
46
- message: w.message
47
- }));
48
- }
49
- const envelope = output.success ? createEnvelope({
50
- success: true,
51
- result: resultData,
52
- meta
53
- }) : createEnvelope({
54
- success: false,
55
- result: resultData,
56
- error: {
57
- code: output.errors?.[0]?.code ?? "FORGE_CHECK_FAILED",
58
- message: output.errors?.[0]?.message ?? "Check failed \u2014 see result for actionable fixes",
59
- category: "VALIDATION",
60
- retryable: true,
61
- retryAfterMs: null
62
- },
63
- meta
64
- });
65
- if (format === "json") {
66
- process.stdout.write(`${JSON.stringify(envelope, null, 2)}
67
- `);
68
- } else {
69
- const formatted = humanFormatter(output.data, output);
70
- if (formatted) {
71
- console.log(formatted);
72
- }
73
- }
74
- }
75
- function resolveExitCode(output) {
76
- if (output.success) return 0;
77
- return 1;
78
- }
79
-
80
- // src/commands/audit.ts
81
25
  function runAudit(args) {
82
26
  const rootDir = args.cwd ?? process.cwd();
83
27
  const limit = args.limit ?? 20;
@@ -153,8 +97,8 @@ var auditCommand = defineCommand({
153
97
  run({ args }) {
154
98
  const eventType = args.type;
155
99
  if (eventType && !VALID_EVENT_TYPES.includes(eventType)) {
156
- console.error(
157
- `Error: invalid event type "${eventType}". Valid types: ${VALID_EVENT_TYPES.join(", ")}`
100
+ forgeLogger.error(
101
+ `Invalid event type "${eventType}". Valid types: ${VALID_EVENT_TYPES.join(", ")}`
158
102
  );
159
103
  process.exit(1);
160
104
  }
@@ -178,46 +122,6 @@ import { generateApi } from "@forge-ts/api";
178
122
  import { loadConfig } from "@forge-ts/core";
179
123
  import { generate } from "@forge-ts/gen";
180
124
  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
-
220
- // src/commands/build.ts
221
125
  async function runBuild(args) {
222
126
  const config = await loadConfig(args.cwd);
223
127
  const buildStart = Date.now();
@@ -380,15 +284,15 @@ var buildCommand = defineCommand2({
380
284
  mvi: args.mvi
381
285
  };
382
286
  emitResult(output, flags, (data) => {
383
- const logger = createLogger();
384
287
  for (const step of data.steps) {
385
288
  if (step.status === "failed") {
386
289
  for (const err of step.errors ?? []) {
387
- logger.error(`[${step.name}] ${err.message}`);
290
+ forgeLogger.error(`[${step.name}] ${err.message}`);
388
291
  }
389
292
  } else if (step.status === "success") {
390
293
  const detail = step.name === "api" && step.outputPath != null ? `Generated OpenAPI spec \u2192 ${step.outputPath}` : `Step complete`;
391
- logger.step(step.name.toUpperCase(), detail, step.duration);
294
+ const durationStr = step.duration !== void 0 ? ` (${step.duration}ms)` : "";
295
+ forgeLogger.success(`${step.name.toUpperCase()}: ${detail}${durationStr}`);
392
296
  }
393
297
  }
394
298
  if (output.success) {
@@ -555,9 +459,7 @@ var bypassCommand = defineCommand3({
555
459
  return;
556
460
  }
557
461
  if (!args.reason) {
558
- console.error(
559
- "[forge-ts] error: --reason is required. Provide a justification for the bypass."
560
- );
462
+ forgeLogger.error("--reason is required. Provide a justification for the bypass.");
561
463
  process.exit(1);
562
464
  }
563
465
  const output = await runBypassCreate({
@@ -924,17 +826,15 @@ import { loadConfig as loadConfig4 } from "@forge-ts/core";
924
826
  import { DEFAULT_TARGET, getAdapter } from "@forge-ts/gen";
925
827
  import { defineCommand as defineCommand5 } from "citty";
926
828
  async function runDocsDev(args) {
927
- const logger = createLogger();
928
829
  const config = await loadConfig4(args.cwd);
929
830
  const target = args.target ?? config.gen.ssgTarget ?? DEFAULT_TARGET;
930
831
  const adapter = getAdapter(target);
931
832
  const outDir = resolve(config.outDir);
932
833
  const devCmd = adapter.getDevCommand(outDir);
933
- logger.info(`Starting ${devCmd.label}...`);
934
- logger.info(` Target: ${target}`);
935
- logger.info(` Directory: ${outDir}`);
936
- logger.info(` URL: ${devCmd.url}`);
937
- logger.info("");
834
+ forgeLogger.info(`Starting ${devCmd.label}...`);
835
+ forgeLogger.info(` Target: ${target}`);
836
+ forgeLogger.info(` Directory: ${outDir}`);
837
+ forgeLogger.info(` URL: ${devCmd.url}`);
938
838
  const spawnArgs = [...devCmd.args];
939
839
  if (args.port) {
940
840
  spawnArgs.push("--port", args.port);
@@ -979,17 +879,474 @@ var docsDevCommand = defineCommand5({
979
879
  }
980
880
  });
981
881
 
982
- // src/commands/init-docs.ts
983
- import { existsSync } from "fs";
882
+ // src/commands/doctor.ts
883
+ import { existsSync, readFileSync } from "fs";
984
884
  import { mkdir, writeFile } from "fs/promises";
985
- import { join, resolve as resolve2 } from "path";
885
+ import { join } from "path";
886
+ import { defineCommand as defineCommand6 } from "citty";
887
+ function readJsonSafe(filePath) {
888
+ if (!existsSync(filePath)) {
889
+ return null;
890
+ }
891
+ try {
892
+ const raw = readFileSync(filePath, "utf8");
893
+ return JSON.parse(raw);
894
+ } catch {
895
+ return null;
896
+ }
897
+ }
898
+ var DEFAULT_CONFIG_CONTENT = `import { defineConfig } from "@forge-ts/core";
899
+
900
+ export default defineConfig({
901
+ rootDir: ".",
902
+ tsconfig: "tsconfig.json",
903
+ outDir: "docs/generated",
904
+ enforce: {
905
+ enabled: true,
906
+ minVisibility: "public",
907
+ strict: false,
908
+ },
909
+ gen: {
910
+ enabled: true,
911
+ formats: ["mdx"],
912
+ llmsTxt: true,
913
+ readmeSync: false,
914
+ ssgTarget: "mintlify",
915
+ },
916
+ });
917
+ `;
918
+ var DEFAULT_TSDOC_CONTENT = JSON.stringify(
919
+ {
920
+ $schema: "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json",
921
+ extends: ["@forge-ts/core/tsdoc-preset/tsdoc.json"]
922
+ },
923
+ null,
924
+ " "
925
+ );
926
+ async function runDoctor(args) {
927
+ const start = Date.now();
928
+ const rootDir = args.cwd ?? process.cwd();
929
+ const fix = args.fix ?? false;
930
+ const checks = [];
931
+ const fixed = [];
932
+ const configPath = join(rootDir, "forge-ts.config.ts");
933
+ const configJsPath = join(rootDir, "forge-ts.config.js");
934
+ if (existsSync(configPath) || existsSync(configJsPath)) {
935
+ const which = existsSync(configPath) ? "forge-ts.config.ts" : "forge-ts.config.js";
936
+ checks.push({
937
+ name: "forge-ts.config",
938
+ status: "pass",
939
+ message: `${which} \u2014 found`,
940
+ fixable: false
941
+ });
942
+ } else if (fix) {
943
+ await mkdir(rootDir, { recursive: true });
944
+ await writeFile(configPath, DEFAULT_CONFIG_CONTENT, "utf8");
945
+ fixed.push("forge-ts.config.ts");
946
+ checks.push({
947
+ name: "forge-ts.config",
948
+ status: "pass",
949
+ message: "forge-ts.config.ts \u2014 created by --fix",
950
+ fixable: true
951
+ });
952
+ } else {
953
+ checks.push({
954
+ name: "forge-ts.config",
955
+ status: "error",
956
+ message: "forge-ts.config.ts \u2014 MISSING (run forge-ts init or forge-ts doctor --fix)",
957
+ fixable: true
958
+ });
959
+ }
960
+ const tsdocPath = join(rootDir, "tsdoc.json");
961
+ if (existsSync(tsdocPath)) {
962
+ const tsdoc = readJsonSafe(tsdocPath);
963
+ if (tsdoc?.extends?.includes("@forge-ts/core/tsdoc-preset/tsdoc.json")) {
964
+ checks.push({
965
+ name: "tsdoc.json",
966
+ status: "pass",
967
+ message: "tsdoc.json \u2014 extends @forge-ts/core/tsdoc-preset",
968
+ fixable: false
969
+ });
970
+ } else {
971
+ checks.push({
972
+ name: "tsdoc.json",
973
+ status: "warn",
974
+ message: "tsdoc.json \u2014 does not extend @forge-ts/core/tsdoc-preset",
975
+ fixable: false
976
+ });
977
+ }
978
+ } else if (fix) {
979
+ await writeFile(tsdocPath, `${DEFAULT_TSDOC_CONTENT}
980
+ `, "utf8");
981
+ fixed.push("tsdoc.json");
982
+ checks.push({
983
+ name: "tsdoc.json",
984
+ status: "pass",
985
+ message: "tsdoc.json \u2014 created by --fix",
986
+ fixable: true
987
+ });
988
+ } else {
989
+ checks.push({
990
+ name: "tsdoc.json",
991
+ status: "error",
992
+ message: "tsdoc.json \u2014 MISSING (run forge-ts init or forge-ts doctor --fix)",
993
+ fixable: true
994
+ });
995
+ }
996
+ const coreModulePath = join(rootDir, "node_modules", "@forge-ts", "core", "package.json");
997
+ if (existsSync(coreModulePath)) {
998
+ const corePkg = readJsonSafe(coreModulePath);
999
+ const version = corePkg?.version ?? "unknown";
1000
+ checks.push({
1001
+ name: "@forge-ts/core",
1002
+ status: "pass",
1003
+ message: `@forge-ts/core \u2014 installed (${version})`,
1004
+ fixable: false
1005
+ });
1006
+ } else {
1007
+ checks.push({
1008
+ name: "@forge-ts/core",
1009
+ status: "warn",
1010
+ message: "@forge-ts/core \u2014 MISSING (run npm install @forge-ts/core)",
1011
+ fixable: false
1012
+ });
1013
+ }
1014
+ const tsPkgPath = join(rootDir, "node_modules", "typescript", "package.json");
1015
+ if (existsSync(tsPkgPath)) {
1016
+ const tsPkg = readJsonSafe(tsPkgPath);
1017
+ const version = tsPkg?.version ?? "unknown";
1018
+ checks.push({
1019
+ name: "TypeScript",
1020
+ status: "pass",
1021
+ message: `TypeScript \u2014 ${version}`,
1022
+ fixable: false
1023
+ });
1024
+ } else {
1025
+ const pkgPath = join(rootDir, "package.json");
1026
+ const pkg2 = readJsonSafe(pkgPath);
1027
+ const allDeps = {
1028
+ ...pkg2?.dependencies,
1029
+ ...pkg2?.devDependencies
1030
+ };
1031
+ if ("typescript" in allDeps) {
1032
+ checks.push({
1033
+ name: "TypeScript",
1034
+ status: "warn",
1035
+ message: `TypeScript \u2014 in package.json (${allDeps.typescript}) but not in node_modules`,
1036
+ fixable: false
1037
+ });
1038
+ } else {
1039
+ checks.push({
1040
+ name: "TypeScript",
1041
+ status: "error",
1042
+ message: "TypeScript \u2014 MISSING (run npm install -D typescript)",
1043
+ fixable: false
1044
+ });
1045
+ }
1046
+ }
1047
+ const tsconfigPath = join(rootDir, "tsconfig.json");
1048
+ if (existsSync(tsconfigPath)) {
1049
+ const tsconfig = readJsonSafe(tsconfigPath);
1050
+ if (tsconfig?.compilerOptions?.strict === true) {
1051
+ checks.push({
1052
+ name: "tsconfig.json",
1053
+ status: "pass",
1054
+ message: "tsconfig.json \u2014 strict mode enabled",
1055
+ fixable: false
1056
+ });
1057
+ } else {
1058
+ const missingFlags = ["strict"];
1059
+ if (!tsconfig?.compilerOptions?.strictNullChecks) {
1060
+ missingFlags.push("strictNullChecks");
1061
+ }
1062
+ if (!tsconfig?.compilerOptions?.noImplicitAny) {
1063
+ missingFlags.push("noImplicitAny");
1064
+ }
1065
+ checks.push({
1066
+ name: "tsconfig.json",
1067
+ status: "warn",
1068
+ message: `tsconfig.json \u2014 strict mode not fully enabled (missing ${missingFlags.join(", ")})`,
1069
+ fixable: false
1070
+ });
1071
+ }
1072
+ } else {
1073
+ checks.push({
1074
+ name: "tsconfig.json",
1075
+ status: "warn",
1076
+ message: "tsconfig.json \u2014 not found",
1077
+ fixable: false
1078
+ });
1079
+ }
1080
+ const biomePath = join(rootDir, "biome.json");
1081
+ const biomecPath = join(rootDir, "biome.jsonc");
1082
+ if (existsSync(biomePath) || existsSync(biomecPath)) {
1083
+ checks.push({
1084
+ name: "biome.json",
1085
+ status: "pass",
1086
+ message: "biome.json \u2014 found",
1087
+ fixable: false
1088
+ });
1089
+ } else {
1090
+ checks.push({
1091
+ name: "biome.json",
1092
+ status: "info",
1093
+ message: "biome.json \u2014 not found (optional)",
1094
+ fixable: false
1095
+ });
1096
+ }
1097
+ const lockPath = join(rootDir, ".forge-lock.json");
1098
+ if (existsSync(lockPath)) {
1099
+ const lock = readJsonSafe(lockPath);
1100
+ if (lock?.lockedAt) {
1101
+ checks.push({
1102
+ name: ".forge-lock.json",
1103
+ status: "pass",
1104
+ message: `.forge-lock.json \u2014 locked at ${lock.lockedAt}`,
1105
+ fixable: false
1106
+ });
1107
+ } else {
1108
+ checks.push({
1109
+ name: ".forge-lock.json",
1110
+ status: "warn",
1111
+ message: ".forge-lock.json \u2014 invalid format (run forge-ts lock to regenerate)",
1112
+ fixable: false
1113
+ });
1114
+ }
1115
+ } else {
1116
+ checks.push({
1117
+ name: ".forge-lock.json",
1118
+ status: "warn",
1119
+ message: ".forge-lock.json \u2014 not locked (run forge-ts lock)",
1120
+ fixable: false
1121
+ });
1122
+ }
1123
+ const auditPath = join(rootDir, ".forge-audit.jsonl");
1124
+ if (existsSync(auditPath)) {
1125
+ try {
1126
+ const raw = readFileSync(auditPath, "utf8");
1127
+ const lines = raw.split("\n").filter((line) => line.trim().length > 0);
1128
+ checks.push({
1129
+ name: ".forge-audit.jsonl",
1130
+ status: "info",
1131
+ message: `.forge-audit.jsonl \u2014 ${lines.length} event${lines.length !== 1 ? "s" : ""}`,
1132
+ fixable: false
1133
+ });
1134
+ } catch {
1135
+ checks.push({
1136
+ name: ".forge-audit.jsonl",
1137
+ status: "warn",
1138
+ message: ".forge-audit.jsonl \u2014 exists but unreadable",
1139
+ fixable: false
1140
+ });
1141
+ }
1142
+ } else {
1143
+ checks.push({
1144
+ name: ".forge-audit.jsonl",
1145
+ status: "info",
1146
+ message: ".forge-audit.jsonl \u2014 no audit trail",
1147
+ fixable: false
1148
+ });
1149
+ }
1150
+ const bypassPath = join(rootDir, ".forge-bypass.json");
1151
+ if (existsSync(bypassPath)) {
1152
+ const records = readJsonSafe(bypassPath);
1153
+ if (records && Array.isArray(records)) {
1154
+ const now = /* @__PURE__ */ new Date();
1155
+ const active = records.filter((r) => r.expiresAt && new Date(r.expiresAt) > now);
1156
+ const expired = records.length - active.length;
1157
+ if (active.length > 0) {
1158
+ checks.push({
1159
+ name: ".forge-bypass.json",
1160
+ status: "info",
1161
+ message: `.forge-bypass.json \u2014 ${active.length} active bypass${active.length !== 1 ? "es" : ""}`,
1162
+ fixable: false
1163
+ });
1164
+ } else if (expired > 0) {
1165
+ checks.push({
1166
+ name: ".forge-bypass.json",
1167
+ status: "info",
1168
+ message: `.forge-bypass.json \u2014 ${expired} expired bypass${expired !== 1 ? "es" : ""} (run cleanup)`,
1169
+ fixable: false
1170
+ });
1171
+ } else {
1172
+ checks.push({
1173
+ name: ".forge-bypass.json",
1174
+ status: "pass",
1175
+ message: ".forge-bypass.json \u2014 no active bypasses",
1176
+ fixable: false
1177
+ });
1178
+ }
1179
+ } else {
1180
+ checks.push({
1181
+ name: ".forge-bypass.json",
1182
+ status: "warn",
1183
+ message: ".forge-bypass.json \u2014 invalid format",
1184
+ fixable: false
1185
+ });
1186
+ }
1187
+ } else {
1188
+ checks.push({
1189
+ name: ".forge-bypass.json",
1190
+ status: "pass",
1191
+ message: ".forge-bypass.json \u2014 no active bypasses",
1192
+ fixable: false
1193
+ });
1194
+ }
1195
+ const huskyPreCommit = join(rootDir, ".husky", "pre-commit");
1196
+ const lefthookYml = join(rootDir, "lefthook.yml");
1197
+ let hookConfigured = false;
1198
+ let hookLocation = "";
1199
+ if (existsSync(huskyPreCommit)) {
1200
+ try {
1201
+ const content = readFileSync(huskyPreCommit, "utf8");
1202
+ if (content.includes("forge-ts check")) {
1203
+ hookConfigured = true;
1204
+ hookLocation = "husky pre-commit";
1205
+ }
1206
+ } catch {
1207
+ }
1208
+ }
1209
+ if (!hookConfigured && existsSync(lefthookYml)) {
1210
+ try {
1211
+ const content = readFileSync(lefthookYml, "utf8");
1212
+ if (content.includes("forge-ts check")) {
1213
+ hookConfigured = true;
1214
+ hookLocation = "lefthook";
1215
+ }
1216
+ } catch {
1217
+ }
1218
+ }
1219
+ if (hookConfigured) {
1220
+ checks.push({
1221
+ name: "Git hooks",
1222
+ status: "pass",
1223
+ message: `Git hooks \u2014 forge-ts check in ${hookLocation}`,
1224
+ fixable: false
1225
+ });
1226
+ } else {
1227
+ checks.push({
1228
+ name: "Git hooks",
1229
+ status: "warn",
1230
+ message: "Git hooks \u2014 forge-ts check not in pre-commit (run forge-ts init hooks)",
1231
+ fixable: false
1232
+ });
1233
+ }
1234
+ const summary = {
1235
+ passed: checks.filter((c) => c.status === "pass").length,
1236
+ warnings: checks.filter((c) => c.status === "warn").length,
1237
+ errors: checks.filter((c) => c.status === "error").length,
1238
+ info: checks.filter((c) => c.status === "info").length
1239
+ };
1240
+ const data = {
1241
+ success: summary.errors === 0,
1242
+ checks,
1243
+ summary,
1244
+ fixed
1245
+ };
1246
+ return {
1247
+ operation: "doctor",
1248
+ success: summary.errors === 0,
1249
+ data,
1250
+ duration: Date.now() - start
1251
+ };
1252
+ }
1253
+ var STATUS_LABELS = {
1254
+ pass: "[PASS]",
1255
+ warn: "[WARN]",
1256
+ error: "[FAIL]",
1257
+ info: "[INFO]"
1258
+ };
1259
+ function formatDoctorHuman(result) {
1260
+ const lines = [];
1261
+ lines.push("\nforge-ts doctor: project health check\n");
1262
+ for (const check of result.checks) {
1263
+ const label = STATUS_LABELS[check.status];
1264
+ lines.push(` ${label} ${check.message}`);
1265
+ }
1266
+ if (result.fixed.length > 0) {
1267
+ lines.push("");
1268
+ lines.push(" Fixed:");
1269
+ for (const file of result.fixed) {
1270
+ lines.push(` ${file}`);
1271
+ }
1272
+ }
1273
+ lines.push("");
1274
+ lines.push(
1275
+ ` Summary: ${result.summary.passed} passed, ${result.summary.warnings} warning${result.summary.warnings !== 1 ? "s" : ""}, ${result.summary.errors} error${result.summary.errors !== 1 ? "s" : ""}`
1276
+ );
1277
+ if (result.summary.errors > 0 || result.summary.warnings > 0) {
1278
+ lines.push(" Run: forge-ts doctor --fix to auto-fix resolvable issues");
1279
+ }
1280
+ return lines.join("\n");
1281
+ }
1282
+ var doctorCommand = defineCommand6({
1283
+ meta: {
1284
+ name: "doctor",
1285
+ description: "Project integrity check and repair"
1286
+ },
1287
+ args: {
1288
+ cwd: {
1289
+ type: "string",
1290
+ description: "Project root directory"
1291
+ },
1292
+ fix: {
1293
+ type: "boolean",
1294
+ description: "Auto-fix resolvable issues",
1295
+ default: false
1296
+ },
1297
+ json: {
1298
+ type: "boolean",
1299
+ description: "Output as LAFS JSON envelope",
1300
+ default: false
1301
+ },
1302
+ human: {
1303
+ type: "boolean",
1304
+ description: "Output as formatted text",
1305
+ default: false
1306
+ },
1307
+ quiet: {
1308
+ type: "boolean",
1309
+ description: "Suppress non-essential output",
1310
+ default: false
1311
+ },
1312
+ mvi: {
1313
+ type: "string",
1314
+ description: "MVI verbosity level: minimal, standard, full"
1315
+ }
1316
+ },
1317
+ async run({ args }) {
1318
+ const output = await runDoctor({
1319
+ cwd: args.cwd,
1320
+ fix: args.fix,
1321
+ mvi: args.mvi
1322
+ });
1323
+ const flags = {
1324
+ json: args.json,
1325
+ human: args.human,
1326
+ quiet: args.quiet,
1327
+ mvi: args.mvi
1328
+ };
1329
+ emitResult(output, flags, (data, cmd) => {
1330
+ if (!cmd.success) {
1331
+ return formatDoctorHuman(data);
1332
+ }
1333
+ return formatDoctorHuman(data);
1334
+ });
1335
+ process.exit(resolveExitCode(output));
1336
+ }
1337
+ });
1338
+
1339
+ // src/commands/init-docs.ts
1340
+ import { existsSync as existsSync2 } from "fs";
1341
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
1342
+ import { join as join2, resolve as resolve2 } from "path";
986
1343
  import { loadConfig as loadConfig5 } from "@forge-ts/core";
987
1344
  import {
988
1345
  DEFAULT_TARGET as DEFAULT_TARGET2,
989
1346
  getAdapter as getAdapter2,
990
1347
  getAvailableTargets
991
1348
  } from "@forge-ts/gen";
992
- import { defineCommand as defineCommand6 } from "citty";
1349
+ import { defineCommand as defineCommand7 } from "citty";
993
1350
  async function runInitDocs(args) {
994
1351
  const start = Date.now();
995
1352
  const rawTarget = args.target ?? DEFAULT_TARGET2;
@@ -1060,15 +1417,15 @@ async function runInitDocs(args) {
1060
1417
  const manifest = adapter.scaffold(context);
1061
1418
  const writtenFiles = [];
1062
1419
  for (const file of manifest.files) {
1063
- const filePath = join(outDir, file.path);
1420
+ const filePath = join2(outDir, file.path);
1064
1421
  const fileDir = filePath.substring(0, filePath.lastIndexOf("/"));
1065
- await mkdir(fileDir, { recursive: true });
1066
- await writeFile(filePath, file.content, "utf8");
1422
+ await mkdir2(fileDir, { recursive: true });
1423
+ await writeFile2(filePath, file.content, "utf8");
1067
1424
  writtenFiles.push(file.path);
1068
1425
  }
1069
1426
  if (config.tsdoc.writeConfig) {
1070
- const tsdocPath = join(config.rootDir, "tsdoc.json");
1071
- if (existsSync(tsdocPath)) {
1427
+ const tsdocPath = join2(config.rootDir, "tsdoc.json");
1428
+ if (existsSync2(tsdocPath)) {
1072
1429
  warnings.push({
1073
1430
  code: "INIT_TSDOC_EXISTS",
1074
1431
  message: "tsdoc.json already exists \u2014 skipping. Remove it and re-run to regenerate."
@@ -1077,13 +1434,13 @@ async function runInitDocs(args) {
1077
1434
  const tsdocContent = JSON.stringify(
1078
1435
  {
1079
1436
  $schema: "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json",
1080
- extends: ["@forge-ts/tsdoc-config/tsdoc.json"]
1437
+ extends: ["@forge-ts/core/tsdoc-preset/tsdoc.json"]
1081
1438
  },
1082
1439
  null,
1083
1440
  " "
1084
1441
  );
1085
- await mkdir(config.rootDir, { recursive: true });
1086
- await writeFile(tsdocPath, `${tsdocContent}
1442
+ await mkdir2(config.rootDir, { recursive: true });
1443
+ await writeFile2(tsdocPath, `${tsdocContent}
1087
1444
  `, "utf8");
1088
1445
  writtenFiles.push("tsdoc.json");
1089
1446
  }
@@ -1145,7 +1502,7 @@ function formatInitDocsHuman(result) {
1145
1502
  );
1146
1503
  return lines.join("\n");
1147
1504
  }
1148
- var initDocsCommand = defineCommand6({
1505
+ var initDocsCommand = defineCommand7({
1149
1506
  meta: {
1150
1507
  name: "init",
1151
1508
  description: "Scaffold a documentation site"
@@ -1204,9 +1561,8 @@ var initDocsCommand = defineCommand6({
1204
1561
  };
1205
1562
  emitResult(output, flags, (data, cmd) => {
1206
1563
  if (!cmd.success) {
1207
- const logger = createLogger();
1208
1564
  const msg = cmd.errors?.[0]?.message ?? "Scaffold failed";
1209
- logger.error(msg);
1565
+ forgeLogger.error(msg);
1210
1566
  return "";
1211
1567
  }
1212
1568
  return formatInitDocsHuman(data);
@@ -1214,7 +1570,7 @@ var initDocsCommand = defineCommand6({
1214
1570
  process.exit(resolveExitCode(output));
1215
1571
  }
1216
1572
  });
1217
- var initCommand = defineCommand6({
1573
+ var initCommand = defineCommand7({
1218
1574
  meta: {
1219
1575
  name: "init",
1220
1576
  description: "Scaffold project artefacts"
@@ -1225,23 +1581,23 @@ var initCommand = defineCommand6({
1225
1581
  });
1226
1582
 
1227
1583
  // 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";
1584
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
1585
+ import { mkdir as mkdir3, readFile, writeFile as writeFile3 } from "fs/promises";
1586
+ import { join as join3 } from "path";
1587
+ import { defineCommand as defineCommand8 } from "citty";
1232
1588
  function detectHookManager(rootDir) {
1233
- const huskyDir = join2(rootDir, ".husky");
1234
- if (existsSync2(huskyDir)) {
1589
+ const huskyDir = join3(rootDir, ".husky");
1590
+ if (existsSync3(huskyDir)) {
1235
1591
  return "husky";
1236
1592
  }
1237
- const lefthookYml = join2(rootDir, "lefthook.yml");
1238
- if (existsSync2(lefthookYml)) {
1593
+ const lefthookYml = join3(rootDir, "lefthook.yml");
1594
+ if (existsSync3(lefthookYml)) {
1239
1595
  return "lefthook";
1240
1596
  }
1241
- const pkgJsonPath = join2(rootDir, "package.json");
1242
- if (existsSync2(pkgJsonPath)) {
1597
+ const pkgJsonPath = join3(rootDir, "package.json");
1598
+ if (existsSync3(pkgJsonPath)) {
1243
1599
  try {
1244
- const raw = readFileSync(pkgJsonPath, "utf8");
1600
+ const raw = readFileSync2(pkgJsonPath, "utf8");
1245
1601
  const pkg2 = JSON.parse(raw);
1246
1602
  const allDeps = { ...pkg2.dependencies, ...pkg2.devDependencies };
1247
1603
  if ("husky" in allDeps) return "husky";
@@ -1270,10 +1626,10 @@ async function runInitHooks(args) {
1270
1626
  const warnings = [];
1271
1627
  const instructions = [];
1272
1628
  if (hookManager === "husky" || hookManager === "none") {
1273
- const huskyDir = join2(rootDir, ".husky");
1274
- const hookPath = join2(huskyDir, "pre-commit");
1629
+ const huskyDir = join3(rootDir, ".husky");
1630
+ const hookPath = join3(huskyDir, "pre-commit");
1275
1631
  const relativePath = ".husky/pre-commit";
1276
- if (existsSync2(hookPath) && !args.force) {
1632
+ if (existsSync3(hookPath) && !args.force) {
1277
1633
  const existing = await readFile(hookPath, "utf8");
1278
1634
  if (existing.includes("forge-ts check")) {
1279
1635
  skippedFiles.push(relativePath);
@@ -1286,12 +1642,12 @@ async function runInitHooks(args) {
1286
1642
 
1287
1643
  npx forge-ts check
1288
1644
  `;
1289
- await writeFile2(hookPath, appended, { mode: 493 });
1645
+ await writeFile3(hookPath, appended, { mode: 493 });
1290
1646
  writtenFiles.push(relativePath);
1291
1647
  }
1292
1648
  } else {
1293
- await mkdir2(huskyDir, { recursive: true });
1294
- await writeFile2(hookPath, HUSKY_PRE_COMMIT, { mode: 493 });
1649
+ await mkdir3(huskyDir, { recursive: true });
1650
+ await writeFile3(hookPath, HUSKY_PRE_COMMIT, { mode: 493 });
1295
1651
  writtenFiles.push(relativePath);
1296
1652
  }
1297
1653
  if (hookManager === "none") {
@@ -1303,9 +1659,9 @@ npx forge-ts check
1303
1659
  instructions.push("Husky pre-commit hook configured to run forge-ts check.");
1304
1660
  }
1305
1661
  } else if (hookManager === "lefthook") {
1306
- const lefthookPath = join2(rootDir, "lefthook.yml");
1662
+ const lefthookPath = join3(rootDir, "lefthook.yml");
1307
1663
  const relativePath = "lefthook.yml";
1308
- if (existsSync2(lefthookPath)) {
1664
+ if (existsSync3(lefthookPath)) {
1309
1665
  const existing = await readFile(lefthookPath, "utf8");
1310
1666
  if (existing.includes("forge-ts check") && !args.force) {
1311
1667
  skippedFiles.push(relativePath);
@@ -1318,17 +1674,17 @@ npx forge-ts check
1318
1674
  forge-ts-check:
1319
1675
  run: npx forge-ts check
1320
1676
  `;
1321
- await writeFile2(lefthookPath, appended, "utf8");
1677
+ await writeFile3(lefthookPath, appended, "utf8");
1322
1678
  writtenFiles.push(relativePath);
1323
1679
  } else {
1324
1680
  const appended = `${existing.trimEnd()}
1325
1681
 
1326
1682
  ${LEFTHOOK_BLOCK}`;
1327
- await writeFile2(lefthookPath, appended, "utf8");
1683
+ await writeFile3(lefthookPath, appended, "utf8");
1328
1684
  writtenFiles.push(relativePath);
1329
1685
  }
1330
1686
  } else {
1331
- await writeFile2(lefthookPath, LEFTHOOK_BLOCK, "utf8");
1687
+ await writeFile3(lefthookPath, LEFTHOOK_BLOCK, "utf8");
1332
1688
  writtenFiles.push(relativePath);
1333
1689
  }
1334
1690
  instructions.push("Lefthook pre-commit hook configured to run forge-ts check.");
@@ -1373,7 +1729,7 @@ function formatInitHooksHuman(result) {
1373
1729
  ${result.summary.filesWritten} file(s) written.`);
1374
1730
  return lines.join("\n");
1375
1731
  }
1376
- var initHooksCommand = defineCommand7({
1732
+ var initHooksCommand = defineCommand8({
1377
1733
  meta: {
1378
1734
  name: "hooks",
1379
1735
  description: "Scaffold git hook integration (husky/lefthook)"
@@ -1422,9 +1778,8 @@ var initHooksCommand = defineCommand7({
1422
1778
  };
1423
1779
  emitResult(output, flags, (data, cmd) => {
1424
1780
  if (!cmd.success) {
1425
- const logger = createLogger();
1426
1781
  const msg = cmd.errors?.[0]?.message ?? "Hook scaffolding failed";
1427
- logger.error(msg);
1782
+ forgeLogger.error(msg);
1428
1783
  return "";
1429
1784
  }
1430
1785
  return formatInitHooksHuman(data);
@@ -1441,7 +1796,7 @@ import {
1441
1796
  readLockFile,
1442
1797
  writeLockFile
1443
1798
  } from "@forge-ts/core";
1444
- import { defineCommand as defineCommand8 } from "citty";
1799
+ import { defineCommand as defineCommand9 } from "citty";
1445
1800
  async function runLock(args) {
1446
1801
  const config = await loadConfig6(args.cwd);
1447
1802
  const rootDir = config.rootDir;
@@ -1500,7 +1855,7 @@ function formatLockHuman(result) {
1500
1855
  To modify locked settings, run: forge-ts unlock --reason="..."`);
1501
1856
  return lines.join("\n");
1502
1857
  }
1503
- var lockCommand = defineCommand8({
1858
+ var lockCommand = defineCommand9({
1504
1859
  meta: {
1505
1860
  name: "lock",
1506
1861
  description: "Lock current config to prevent silent weakening"
@@ -1539,7 +1894,7 @@ var lockCommand = defineCommand8({
1539
1894
  });
1540
1895
 
1541
1896
  // src/commands/prepublish.ts
1542
- import { defineCommand as defineCommand9 } from "citty";
1897
+ import { defineCommand as defineCommand10 } from "citty";
1543
1898
  async function runPrepublish(args) {
1544
1899
  const start = Date.now();
1545
1900
  const allErrors = [];
@@ -1643,7 +1998,7 @@ function formatPrepublishHuman(result) {
1643
1998
  }
1644
1999
  return lines.join("\n");
1645
2000
  }
1646
- var prepublishCommand = defineCommand9({
2001
+ var prepublishCommand = defineCommand10({
1647
2002
  meta: {
1648
2003
  name: "prepublish",
1649
2004
  description: "Safety gate: check + build before npm publish"
@@ -1692,8 +2047,7 @@ var prepublishCommand = defineCommand9({
1692
2047
  };
1693
2048
  emitResult(output, flags, (data, cmd) => {
1694
2049
  if (!cmd.success) {
1695
- const logger = createLogger();
1696
- logger.error("Prepublish gate failed");
2050
+ forgeLogger.error("Prepublish gate failed");
1697
2051
  }
1698
2052
  return formatPrepublishHuman(data);
1699
2053
  });
@@ -1704,7 +2058,7 @@ var prepublishCommand = defineCommand9({
1704
2058
  // src/commands/test.ts
1705
2059
  import { loadConfig as loadConfig7 } from "@forge-ts/core";
1706
2060
  import { doctest } from "@forge-ts/doctest";
1707
- import { defineCommand as defineCommand10 } from "citty";
2061
+ import { defineCommand as defineCommand11 } from "citty";
1708
2062
  async function runTest(args) {
1709
2063
  const config = await loadConfig7(args.cwd);
1710
2064
  const result = await doctest(config);
@@ -1747,7 +2101,7 @@ async function runTest(args) {
1747
2101
  duration: result.duration
1748
2102
  };
1749
2103
  }
1750
- var testCommand = defineCommand10({
2104
+ var testCommand = defineCommand11({
1751
2105
  meta: {
1752
2106
  name: "test",
1753
2107
  description: "Run @example blocks as doctests"
@@ -1808,7 +2162,7 @@ import {
1808
2162
  readLockFile as readLockFile2,
1809
2163
  removeLockFile
1810
2164
  } from "@forge-ts/core";
1811
- import { defineCommand as defineCommand11 } from "citty";
2165
+ import { defineCommand as defineCommand12 } from "citty";
1812
2166
  async function runUnlock(args) {
1813
2167
  const config = await loadConfig8(args.cwd);
1814
2168
  const rootDir = config.rootDir;
@@ -1893,7 +2247,7 @@ function formatUnlockHuman(result) {
1893
2247
  lines.push(" Run `forge-ts lock` to re-lock after changes.");
1894
2248
  return lines.join("\n");
1895
2249
  }
1896
- var unlockCommand = defineCommand11({
2250
+ var unlockCommand = defineCommand12({
1897
2251
  meta: {
1898
2252
  name: "unlock",
1899
2253
  description: "Remove config lock (requires --reason)"
@@ -1926,9 +2280,7 @@ var unlockCommand = defineCommand11({
1926
2280
  },
1927
2281
  async run({ args }) {
1928
2282
  if (!args.reason) {
1929
- console.error(
1930
- "[forge-ts] error: --reason is required. Provide a reason for unlocking the config."
1931
- );
2283
+ forgeLogger.error("--reason is required. Provide a reason for unlocking the config.");
1932
2284
  process.exit(1);
1933
2285
  }
1934
2286
  const output = await runUnlock({
@@ -1948,7 +2300,7 @@ var unlockCommand = defineCommand11({
1948
2300
  // src/index.ts
1949
2301
  var require2 = createRequire(import.meta.url);
1950
2302
  var pkg = require2("../package.json");
1951
- var docsCommand = defineCommand12({
2303
+ var docsCommand = defineCommand13({
1952
2304
  meta: {
1953
2305
  name: "docs",
1954
2306
  description: "Documentation site management"
@@ -1958,17 +2310,119 @@ var docsCommand = defineCommand12({
1958
2310
  dev: docsDevCommand
1959
2311
  }
1960
2312
  });
1961
- var initCommand2 = defineCommand12({
2313
+ var initCommand2 = defineCommand13({
1962
2314
  meta: {
1963
2315
  name: "init",
1964
- description: "Scaffold project artefacts"
2316
+ description: "Full project setup (bare) or scaffold artefacts (with subcommand)"
2317
+ },
2318
+ args: {
2319
+ cwd: {
2320
+ type: "string",
2321
+ description: "Project root directory"
2322
+ },
2323
+ json: {
2324
+ type: "boolean",
2325
+ description: "Output as LAFS JSON envelope",
2326
+ default: false
2327
+ },
2328
+ human: {
2329
+ type: "boolean",
2330
+ description: "Output as formatted text",
2331
+ default: false
2332
+ },
2333
+ quiet: {
2334
+ type: "boolean",
2335
+ description: "Suppress non-essential output",
2336
+ default: false
2337
+ },
2338
+ mvi: {
2339
+ type: "string",
2340
+ description: "MVI verbosity level: minimal, standard, full"
2341
+ }
1965
2342
  },
1966
2343
  subCommands: {
1967
2344
  docs: initDocsCommand,
1968
- hooks: initHooksCommand
2345
+ hooks: initHooksCommand,
2346
+ setup: initProjectCommand
2347
+ },
2348
+ async run({ rawArgs, args }) {
2349
+ const subCommandNames = /* @__PURE__ */ new Set(["docs", "hooks", "setup"]);
2350
+ const hasSubCommand = rawArgs.some((arg) => subCommandNames.has(arg));
2351
+ if (hasSubCommand) {
2352
+ return;
2353
+ }
2354
+ const { runInitProject: runInitProject2 } = await import("./init-project-5AWZ2LAI.js");
2355
+ const { emitResult: emitResult2, resolveExitCode: resolveExitCode2 } = await import("./output-OSCHMPOX.js");
2356
+ const { forgeLogger: forgeLogger2 } = await import("./forge-logger-RTOBEKWH.js");
2357
+ const output = await runInitProject2({
2358
+ cwd: args.cwd,
2359
+ mvi: args.mvi
2360
+ });
2361
+ const flags = {
2362
+ json: args.json,
2363
+ human: args.human,
2364
+ quiet: args.quiet,
2365
+ mvi: args.mvi
2366
+ };
2367
+ emitResult2(output, flags, (data, cmd) => {
2368
+ if (!cmd.success) {
2369
+ const msg = cmd.errors?.[0]?.message ?? "Init failed";
2370
+ forgeLogger2.error(msg);
2371
+ return "";
2372
+ }
2373
+ const lines = [];
2374
+ lines.push("\nforge-ts init: project setup complete\n");
2375
+ if (data.created.length > 0) {
2376
+ lines.push(" Created:");
2377
+ for (const file of data.created) {
2378
+ lines.push(` ${file}`);
2379
+ }
2380
+ }
2381
+ if (data.skipped.length > 0) {
2382
+ lines.push("");
2383
+ lines.push(" Already exists (skipped):");
2384
+ for (const file of data.skipped) {
2385
+ lines.push(` ${file}`);
2386
+ }
2387
+ } else if (data.created.length > 0) {
2388
+ lines.push("");
2389
+ lines.push(" Already exists (skipped):");
2390
+ lines.push(" (none)");
2391
+ }
2392
+ if (data.warnings.length > 0) {
2393
+ lines.push("");
2394
+ lines.push(" Warnings:");
2395
+ for (const warning of data.warnings) {
2396
+ lines.push(` ${warning}`);
2397
+ }
2398
+ }
2399
+ const env = data.environment;
2400
+ lines.push("");
2401
+ lines.push(" Environment:");
2402
+ lines.push(` TypeScript: ${env.typescriptVersion ?? "not detected"}`);
2403
+ lines.push(` Biome: ${env.biomeDetected ? "detected" : "not detected"}`);
2404
+ const hookLabel = env.hookManager === "none" ? "not detected" : `${env.hookManager} detected`;
2405
+ lines.push(` Git hooks: ${hookLabel}`);
2406
+ if (env.monorepo) {
2407
+ lines.push(
2408
+ ` Monorepo: ${env.monorepoType === "pnpm" ? "pnpm workspaces" : "npm/yarn workspaces"}`
2409
+ );
2410
+ } else {
2411
+ lines.push(" Monorepo: no");
2412
+ }
2413
+ if (data.nextSteps.length > 0) {
2414
+ lines.push("");
2415
+ lines.push(" Next steps:");
2416
+ for (const [idx, step] of data.nextSteps.entries()) {
2417
+ lines.push(` ${idx + 1}. ${step}`);
2418
+ }
2419
+ }
2420
+ return lines.join("\n");
2421
+ });
2422
+ process.exit(resolveExitCode2(output));
1969
2423
  }
1970
2424
  });
1971
- var main = defineCommand12({
2425
+ var main = defineCommand13({
1972
2426
  meta: {
1973
2427
  name: "forge-ts",
1974
2428
  version: pkg.version,
@@ -1984,7 +2438,8 @@ var main = defineCommand12({
1984
2438
  unlock: unlockCommand,
1985
2439
  bypass: bypassCommand,
1986
2440
  audit: auditCommand,
1987
- prepublish: prepublishCommand
2441
+ prepublish: prepublishCommand,
2442
+ doctor: doctorCommand
1988
2443
  }
1989
2444
  });
1990
2445
  runMain(main);
@@ -1993,18 +2448,23 @@ export {
1993
2448
  buildCommand,
1994
2449
  bypassCommand,
1995
2450
  checkCommand,
1996
- createLogger,
2451
+ configureLogger,
1997
2452
  docsDevCommand,
2453
+ doctorCommand,
1998
2454
  emitResult,
2455
+ forgeLogger,
1999
2456
  initDocsCommand,
2000
2457
  initHooksCommand,
2458
+ initProjectCommand,
2001
2459
  lockCommand,
2002
2460
  prepublishCommand,
2003
2461
  resolveExitCode,
2004
2462
  runBypassCreate,
2005
2463
  runBypassStatus,
2006
2464
  runDocsDev,
2465
+ runDoctor,
2007
2466
  runInitHooks,
2467
+ runInitProject,
2008
2468
  runLock,
2009
2469
  runPrepublish,
2010
2470
  runUnlock,