@forge-ts/cli 0.13.0 → 0.15.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,19 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ initProjectCommand,
4
+ runInitProject
5
+ } from "./chunk-CINQWGH7.js";
6
+ import {
7
+ emitResult,
8
+ resolveExitCode
9
+ } from "./chunk-ZFFY4AQX.js";
10
+ import {
11
+ createLogger
12
+ } from "./chunk-7UPSAG3L.js";
2
13
 
3
14
  // src/index.ts
4
15
  import { createRequire } from "module";
5
- import { defineCommand as defineCommand12, runMain } from "citty";
16
+ import { defineCommand as defineCommand13, runMain } from "citty";
6
17
 
7
18
  // src/commands/audit.ts
8
19
  import {
@@ -10,74 +21,6 @@ import {
10
21
  readAuditLog
11
22
  } from "@forge-ts/core";
12
23
  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
24
  function runAudit(args) {
82
25
  const rootDir = args.cwd ?? process.cwd();
83
26
  const limit = args.limit ?? 20;
@@ -178,46 +121,6 @@ import { generateApi } from "@forge-ts/api";
178
121
  import { loadConfig } from "@forge-ts/core";
179
122
  import { generate } from "@forge-ts/gen";
180
123
  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
124
  async function runBuild(args) {
222
125
  const config = await loadConfig(args.cwd);
223
126
  const buildStart = Date.now();
@@ -917,12 +820,486 @@ var checkCommand = defineCommand4({
917
820
  }
918
821
  });
919
822
 
823
+ // src/commands/doctor.ts
824
+ import { existsSync, readFileSync } from "fs";
825
+ import { mkdir, writeFile } from "fs/promises";
826
+ import { join } from "path";
827
+ import { defineCommand as defineCommand5 } from "citty";
828
+ function readJsonSafe(filePath) {
829
+ if (!existsSync(filePath)) {
830
+ return null;
831
+ }
832
+ try {
833
+ const raw = readFileSync(filePath, "utf8");
834
+ return JSON.parse(raw);
835
+ } catch {
836
+ return null;
837
+ }
838
+ }
839
+ var DEFAULT_CONFIG_CONTENT = `import { defineConfig } from "@forge-ts/core";
840
+
841
+ export default defineConfig({
842
+ rootDir: ".",
843
+ tsconfig: "tsconfig.json",
844
+ outDir: "docs/generated",
845
+ enforce: {
846
+ enabled: true,
847
+ minVisibility: "public",
848
+ strict: false,
849
+ },
850
+ gen: {
851
+ enabled: true,
852
+ formats: ["mdx"],
853
+ llmsTxt: true,
854
+ readmeSync: false,
855
+ ssgTarget: "mintlify",
856
+ },
857
+ });
858
+ `;
859
+ var DEFAULT_TSDOC_CONTENT = JSON.stringify(
860
+ {
861
+ $schema: "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json",
862
+ extends: ["@forge-ts/tsdoc-config/tsdoc.json"]
863
+ },
864
+ null,
865
+ " "
866
+ );
867
+ async function runDoctor(args) {
868
+ const start = Date.now();
869
+ const rootDir = args.cwd ?? process.cwd();
870
+ const fix = args.fix ?? false;
871
+ const checks = [];
872
+ const fixed = [];
873
+ const configPath = join(rootDir, "forge-ts.config.ts");
874
+ const configJsPath = join(rootDir, "forge-ts.config.js");
875
+ if (existsSync(configPath) || existsSync(configJsPath)) {
876
+ const which = existsSync(configPath) ? "forge-ts.config.ts" : "forge-ts.config.js";
877
+ checks.push({
878
+ name: "forge-ts.config",
879
+ status: "pass",
880
+ message: `${which} \u2014 found`,
881
+ fixable: false
882
+ });
883
+ } else if (fix) {
884
+ await mkdir(rootDir, { recursive: true });
885
+ await writeFile(configPath, DEFAULT_CONFIG_CONTENT, "utf8");
886
+ fixed.push("forge-ts.config.ts");
887
+ checks.push({
888
+ name: "forge-ts.config",
889
+ status: "pass",
890
+ message: "forge-ts.config.ts \u2014 created by --fix",
891
+ fixable: true
892
+ });
893
+ } else {
894
+ checks.push({
895
+ name: "forge-ts.config",
896
+ status: "error",
897
+ message: "forge-ts.config.ts \u2014 MISSING (run forge-ts init or forge-ts doctor --fix)",
898
+ fixable: true
899
+ });
900
+ }
901
+ const tsdocPath = join(rootDir, "tsdoc.json");
902
+ if (existsSync(tsdocPath)) {
903
+ const tsdoc = readJsonSafe(tsdocPath);
904
+ if (tsdoc?.extends && tsdoc.extends.includes("@forge-ts/tsdoc-config/tsdoc.json")) {
905
+ checks.push({
906
+ name: "tsdoc.json",
907
+ status: "pass",
908
+ message: "tsdoc.json \u2014 extends @forge-ts/tsdoc-config",
909
+ fixable: false
910
+ });
911
+ } else {
912
+ checks.push({
913
+ name: "tsdoc.json",
914
+ status: "warn",
915
+ message: "tsdoc.json \u2014 does not extend @forge-ts/tsdoc-config",
916
+ fixable: false
917
+ });
918
+ }
919
+ } else if (fix) {
920
+ await writeFile(tsdocPath, `${DEFAULT_TSDOC_CONTENT}
921
+ `, "utf8");
922
+ fixed.push("tsdoc.json");
923
+ checks.push({
924
+ name: "tsdoc.json",
925
+ status: "pass",
926
+ message: "tsdoc.json \u2014 created by --fix",
927
+ fixable: true
928
+ });
929
+ } else {
930
+ checks.push({
931
+ name: "tsdoc.json",
932
+ status: "error",
933
+ message: "tsdoc.json \u2014 MISSING (run forge-ts init or forge-ts doctor --fix)",
934
+ fixable: true
935
+ });
936
+ }
937
+ const tsdocConfigModulePath = join(
938
+ rootDir,
939
+ "node_modules",
940
+ "@forge-ts",
941
+ "tsdoc-config",
942
+ "package.json"
943
+ );
944
+ if (existsSync(tsdocConfigModulePath)) {
945
+ const tsdocPkg = readJsonSafe(
946
+ tsdocConfigModulePath
947
+ );
948
+ const version = tsdocPkg?.version ?? "unknown";
949
+ checks.push({
950
+ name: "@forge-ts/tsdoc-config",
951
+ status: "pass",
952
+ message: `@forge-ts/tsdoc-config \u2014 installed (${version})`,
953
+ fixable: false
954
+ });
955
+ } else {
956
+ checks.push({
957
+ name: "@forge-ts/tsdoc-config",
958
+ status: "warn",
959
+ message: "@forge-ts/tsdoc-config \u2014 MISSING (run npm install @forge-ts/tsdoc-config)",
960
+ fixable: false
961
+ });
962
+ }
963
+ const tsPkgPath = join(
964
+ rootDir,
965
+ "node_modules",
966
+ "typescript",
967
+ "package.json"
968
+ );
969
+ if (existsSync(tsPkgPath)) {
970
+ const tsPkg = readJsonSafe(tsPkgPath);
971
+ const version = tsPkg?.version ?? "unknown";
972
+ checks.push({
973
+ name: "TypeScript",
974
+ status: "pass",
975
+ message: `TypeScript \u2014 ${version}`,
976
+ fixable: false
977
+ });
978
+ } else {
979
+ const pkgPath = join(rootDir, "package.json");
980
+ const pkg2 = readJsonSafe(pkgPath);
981
+ const allDeps = {
982
+ ...pkg2?.dependencies,
983
+ ...pkg2?.devDependencies
984
+ };
985
+ if ("typescript" in allDeps) {
986
+ checks.push({
987
+ name: "TypeScript",
988
+ status: "warn",
989
+ message: `TypeScript \u2014 in package.json (${allDeps.typescript}) but not in node_modules`,
990
+ fixable: false
991
+ });
992
+ } else {
993
+ checks.push({
994
+ name: "TypeScript",
995
+ status: "error",
996
+ message: "TypeScript \u2014 MISSING (run npm install -D typescript)",
997
+ fixable: false
998
+ });
999
+ }
1000
+ }
1001
+ const tsconfigPath = join(rootDir, "tsconfig.json");
1002
+ if (existsSync(tsconfigPath)) {
1003
+ const tsconfig = readJsonSafe(tsconfigPath);
1004
+ if (tsconfig?.compilerOptions?.strict === true) {
1005
+ checks.push({
1006
+ name: "tsconfig.json",
1007
+ status: "pass",
1008
+ message: "tsconfig.json \u2014 strict mode enabled",
1009
+ fixable: false
1010
+ });
1011
+ } else {
1012
+ const missingFlags = ["strict"];
1013
+ if (!tsconfig?.compilerOptions?.strictNullChecks) {
1014
+ missingFlags.push("strictNullChecks");
1015
+ }
1016
+ if (!tsconfig?.compilerOptions?.noImplicitAny) {
1017
+ missingFlags.push("noImplicitAny");
1018
+ }
1019
+ checks.push({
1020
+ name: "tsconfig.json",
1021
+ status: "warn",
1022
+ message: `tsconfig.json \u2014 strict mode not fully enabled (missing ${missingFlags.join(", ")})`,
1023
+ fixable: false
1024
+ });
1025
+ }
1026
+ } else {
1027
+ checks.push({
1028
+ name: "tsconfig.json",
1029
+ status: "warn",
1030
+ message: "tsconfig.json \u2014 not found",
1031
+ fixable: false
1032
+ });
1033
+ }
1034
+ const biomePath = join(rootDir, "biome.json");
1035
+ const biomecPath = join(rootDir, "biome.jsonc");
1036
+ if (existsSync(biomePath) || existsSync(biomecPath)) {
1037
+ checks.push({
1038
+ name: "biome.json",
1039
+ status: "pass",
1040
+ message: "biome.json \u2014 found",
1041
+ fixable: false
1042
+ });
1043
+ } else {
1044
+ checks.push({
1045
+ name: "biome.json",
1046
+ status: "info",
1047
+ message: "biome.json \u2014 not found (optional)",
1048
+ fixable: false
1049
+ });
1050
+ }
1051
+ const lockPath = join(rootDir, ".forge-lock.json");
1052
+ if (existsSync(lockPath)) {
1053
+ const lock = readJsonSafe(lockPath);
1054
+ if (lock?.lockedAt) {
1055
+ checks.push({
1056
+ name: ".forge-lock.json",
1057
+ status: "pass",
1058
+ message: `.forge-lock.json \u2014 locked at ${lock.lockedAt}`,
1059
+ fixable: false
1060
+ });
1061
+ } else {
1062
+ checks.push({
1063
+ name: ".forge-lock.json",
1064
+ status: "warn",
1065
+ message: ".forge-lock.json \u2014 invalid format (run forge-ts lock to regenerate)",
1066
+ fixable: false
1067
+ });
1068
+ }
1069
+ } else {
1070
+ checks.push({
1071
+ name: ".forge-lock.json",
1072
+ status: "warn",
1073
+ message: ".forge-lock.json \u2014 not locked (run forge-ts lock)",
1074
+ fixable: false
1075
+ });
1076
+ }
1077
+ const auditPath = join(rootDir, ".forge-audit.jsonl");
1078
+ if (existsSync(auditPath)) {
1079
+ try {
1080
+ const raw = readFileSync(auditPath, "utf8");
1081
+ const lines = raw.split("\n").filter((line) => line.trim().length > 0);
1082
+ checks.push({
1083
+ name: ".forge-audit.jsonl",
1084
+ status: "info",
1085
+ message: `.forge-audit.jsonl \u2014 ${lines.length} event${lines.length !== 1 ? "s" : ""}`,
1086
+ fixable: false
1087
+ });
1088
+ } catch {
1089
+ checks.push({
1090
+ name: ".forge-audit.jsonl",
1091
+ status: "warn",
1092
+ message: ".forge-audit.jsonl \u2014 exists but unreadable",
1093
+ fixable: false
1094
+ });
1095
+ }
1096
+ } else {
1097
+ checks.push({
1098
+ name: ".forge-audit.jsonl",
1099
+ status: "info",
1100
+ message: ".forge-audit.jsonl \u2014 no audit trail",
1101
+ fixable: false
1102
+ });
1103
+ }
1104
+ const bypassPath = join(rootDir, ".forge-bypass.json");
1105
+ if (existsSync(bypassPath)) {
1106
+ const records = readJsonSafe(bypassPath);
1107
+ if (records && Array.isArray(records)) {
1108
+ const now = /* @__PURE__ */ new Date();
1109
+ const active = records.filter(
1110
+ (r) => r.expiresAt && new Date(r.expiresAt) > now
1111
+ );
1112
+ const expired = records.length - active.length;
1113
+ if (active.length > 0) {
1114
+ checks.push({
1115
+ name: ".forge-bypass.json",
1116
+ status: "info",
1117
+ message: `.forge-bypass.json \u2014 ${active.length} active bypass${active.length !== 1 ? "es" : ""}`,
1118
+ fixable: false
1119
+ });
1120
+ } else if (expired > 0) {
1121
+ checks.push({
1122
+ name: ".forge-bypass.json",
1123
+ status: "info",
1124
+ message: `.forge-bypass.json \u2014 ${expired} expired bypass${expired !== 1 ? "es" : ""} (run cleanup)`,
1125
+ fixable: false
1126
+ });
1127
+ } else {
1128
+ checks.push({
1129
+ name: ".forge-bypass.json",
1130
+ status: "pass",
1131
+ message: ".forge-bypass.json \u2014 no active bypasses",
1132
+ fixable: false
1133
+ });
1134
+ }
1135
+ } else {
1136
+ checks.push({
1137
+ name: ".forge-bypass.json",
1138
+ status: "warn",
1139
+ message: ".forge-bypass.json \u2014 invalid format",
1140
+ fixable: false
1141
+ });
1142
+ }
1143
+ } else {
1144
+ checks.push({
1145
+ name: ".forge-bypass.json",
1146
+ status: "pass",
1147
+ message: ".forge-bypass.json \u2014 no active bypasses",
1148
+ fixable: false
1149
+ });
1150
+ }
1151
+ const huskyPreCommit = join(rootDir, ".husky", "pre-commit");
1152
+ const lefthookYml = join(rootDir, "lefthook.yml");
1153
+ let hookConfigured = false;
1154
+ let hookLocation = "";
1155
+ if (existsSync(huskyPreCommit)) {
1156
+ try {
1157
+ const content = readFileSync(huskyPreCommit, "utf8");
1158
+ if (content.includes("forge-ts check")) {
1159
+ hookConfigured = true;
1160
+ hookLocation = "husky pre-commit";
1161
+ }
1162
+ } catch {
1163
+ }
1164
+ }
1165
+ if (!hookConfigured && existsSync(lefthookYml)) {
1166
+ try {
1167
+ const content = readFileSync(lefthookYml, "utf8");
1168
+ if (content.includes("forge-ts check")) {
1169
+ hookConfigured = true;
1170
+ hookLocation = "lefthook";
1171
+ }
1172
+ } catch {
1173
+ }
1174
+ }
1175
+ if (hookConfigured) {
1176
+ checks.push({
1177
+ name: "Git hooks",
1178
+ status: "pass",
1179
+ message: `Git hooks \u2014 forge-ts check in ${hookLocation}`,
1180
+ fixable: false
1181
+ });
1182
+ } else {
1183
+ checks.push({
1184
+ name: "Git hooks",
1185
+ status: "warn",
1186
+ message: "Git hooks \u2014 forge-ts check not in pre-commit (run forge-ts init hooks)",
1187
+ fixable: false
1188
+ });
1189
+ }
1190
+ const summary = {
1191
+ passed: checks.filter((c) => c.status === "pass").length,
1192
+ warnings: checks.filter((c) => c.status === "warn").length,
1193
+ errors: checks.filter((c) => c.status === "error").length,
1194
+ info: checks.filter((c) => c.status === "info").length
1195
+ };
1196
+ const data = {
1197
+ success: summary.errors === 0,
1198
+ checks,
1199
+ summary,
1200
+ fixed
1201
+ };
1202
+ return {
1203
+ operation: "doctor",
1204
+ success: summary.errors === 0,
1205
+ data,
1206
+ duration: Date.now() - start
1207
+ };
1208
+ }
1209
+ var STATUS_LABELS = {
1210
+ pass: "[PASS]",
1211
+ warn: "[WARN]",
1212
+ error: "[FAIL]",
1213
+ info: "[INFO]"
1214
+ };
1215
+ function formatDoctorHuman(result) {
1216
+ const lines = [];
1217
+ lines.push("\nforge-ts doctor: project health check\n");
1218
+ for (const check of result.checks) {
1219
+ const label = STATUS_LABELS[check.status];
1220
+ lines.push(` ${label} ${check.message}`);
1221
+ }
1222
+ if (result.fixed.length > 0) {
1223
+ lines.push("");
1224
+ lines.push(" Fixed:");
1225
+ for (const file of result.fixed) {
1226
+ lines.push(` ${file}`);
1227
+ }
1228
+ }
1229
+ lines.push("");
1230
+ lines.push(
1231
+ ` Summary: ${result.summary.passed} passed, ${result.summary.warnings} warning${result.summary.warnings !== 1 ? "s" : ""}, ${result.summary.errors} error${result.summary.errors !== 1 ? "s" : ""}`
1232
+ );
1233
+ if (result.summary.errors > 0 || result.summary.warnings > 0) {
1234
+ lines.push(
1235
+ " Run: forge-ts doctor --fix to auto-fix resolvable issues"
1236
+ );
1237
+ }
1238
+ return lines.join("\n");
1239
+ }
1240
+ var doctorCommand = defineCommand5({
1241
+ meta: {
1242
+ name: "doctor",
1243
+ description: "Project integrity check and repair"
1244
+ },
1245
+ args: {
1246
+ cwd: {
1247
+ type: "string",
1248
+ description: "Project root directory"
1249
+ },
1250
+ fix: {
1251
+ type: "boolean",
1252
+ description: "Auto-fix resolvable issues",
1253
+ default: false
1254
+ },
1255
+ json: {
1256
+ type: "boolean",
1257
+ description: "Output as LAFS JSON envelope",
1258
+ default: false
1259
+ },
1260
+ human: {
1261
+ type: "boolean",
1262
+ description: "Output as formatted text",
1263
+ default: false
1264
+ },
1265
+ quiet: {
1266
+ type: "boolean",
1267
+ description: "Suppress non-essential output",
1268
+ default: false
1269
+ },
1270
+ mvi: {
1271
+ type: "string",
1272
+ description: "MVI verbosity level: minimal, standard, full"
1273
+ }
1274
+ },
1275
+ async run({ args }) {
1276
+ const output = await runDoctor({
1277
+ cwd: args.cwd,
1278
+ fix: args.fix,
1279
+ mvi: args.mvi
1280
+ });
1281
+ const flags = {
1282
+ json: args.json,
1283
+ human: args.human,
1284
+ quiet: args.quiet,
1285
+ mvi: args.mvi
1286
+ };
1287
+ emitResult(output, flags, (data, cmd) => {
1288
+ if (!cmd.success) {
1289
+ return formatDoctorHuman(data);
1290
+ }
1291
+ return formatDoctorHuman(data);
1292
+ });
1293
+ process.exit(resolveExitCode(output));
1294
+ }
1295
+ });
1296
+
920
1297
  // src/commands/docs-dev.ts
921
1298
  import { spawn } from "child_process";
922
1299
  import { resolve } from "path";
923
1300
  import { loadConfig as loadConfig4 } from "@forge-ts/core";
924
1301
  import { DEFAULT_TARGET, getAdapter } from "@forge-ts/gen";
925
- import { defineCommand as defineCommand5 } from "citty";
1302
+ import { defineCommand as defineCommand6 } from "citty";
926
1303
  async function runDocsDev(args) {
927
1304
  const logger = createLogger();
928
1305
  const config = await loadConfig4(args.cwd);
@@ -955,7 +1332,7 @@ async function runDocsDev(args) {
955
1332
  proc.on("error", reject);
956
1333
  });
957
1334
  }
958
- var docsDevCommand = defineCommand5({
1335
+ var docsDevCommand = defineCommand6({
959
1336
  meta: {
960
1337
  name: "dev",
961
1338
  description: "Start a local doc preview server"
@@ -980,16 +1357,16 @@ var docsDevCommand = defineCommand5({
980
1357
  });
981
1358
 
982
1359
  // src/commands/init-docs.ts
983
- import { existsSync } from "fs";
984
- import { mkdir, writeFile } from "fs/promises";
985
- import { join, resolve as resolve2 } from "path";
1360
+ import { existsSync as existsSync2 } from "fs";
1361
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
1362
+ import { join as join2, resolve as resolve2 } from "path";
986
1363
  import { loadConfig as loadConfig5 } from "@forge-ts/core";
987
1364
  import {
988
1365
  DEFAULT_TARGET as DEFAULT_TARGET2,
989
1366
  getAdapter as getAdapter2,
990
1367
  getAvailableTargets
991
1368
  } from "@forge-ts/gen";
992
- import { defineCommand as defineCommand6 } from "citty";
1369
+ import { defineCommand as defineCommand7 } from "citty";
993
1370
  async function runInitDocs(args) {
994
1371
  const start = Date.now();
995
1372
  const rawTarget = args.target ?? DEFAULT_TARGET2;
@@ -1060,15 +1437,15 @@ async function runInitDocs(args) {
1060
1437
  const manifest = adapter.scaffold(context);
1061
1438
  const writtenFiles = [];
1062
1439
  for (const file of manifest.files) {
1063
- const filePath = join(outDir, file.path);
1440
+ const filePath = join2(outDir, file.path);
1064
1441
  const fileDir = filePath.substring(0, filePath.lastIndexOf("/"));
1065
- await mkdir(fileDir, { recursive: true });
1066
- await writeFile(filePath, file.content, "utf8");
1442
+ await mkdir2(fileDir, { recursive: true });
1443
+ await writeFile2(filePath, file.content, "utf8");
1067
1444
  writtenFiles.push(file.path);
1068
1445
  }
1069
1446
  if (config.tsdoc.writeConfig) {
1070
- const tsdocPath = join(config.rootDir, "tsdoc.json");
1071
- if (existsSync(tsdocPath)) {
1447
+ const tsdocPath = join2(config.rootDir, "tsdoc.json");
1448
+ if (existsSync2(tsdocPath)) {
1072
1449
  warnings.push({
1073
1450
  code: "INIT_TSDOC_EXISTS",
1074
1451
  message: "tsdoc.json already exists \u2014 skipping. Remove it and re-run to regenerate."
@@ -1082,8 +1459,8 @@ async function runInitDocs(args) {
1082
1459
  null,
1083
1460
  " "
1084
1461
  );
1085
- await mkdir(config.rootDir, { recursive: true });
1086
- await writeFile(tsdocPath, `${tsdocContent}
1462
+ await mkdir2(config.rootDir, { recursive: true });
1463
+ await writeFile2(tsdocPath, `${tsdocContent}
1087
1464
  `, "utf8");
1088
1465
  writtenFiles.push("tsdoc.json");
1089
1466
  }
@@ -1145,7 +1522,7 @@ function formatInitDocsHuman(result) {
1145
1522
  );
1146
1523
  return lines.join("\n");
1147
1524
  }
1148
- var initDocsCommand = defineCommand6({
1525
+ var initDocsCommand = defineCommand7({
1149
1526
  meta: {
1150
1527
  name: "init",
1151
1528
  description: "Scaffold a documentation site"
@@ -1214,7 +1591,7 @@ var initDocsCommand = defineCommand6({
1214
1591
  process.exit(resolveExitCode(output));
1215
1592
  }
1216
1593
  });
1217
- var initCommand = defineCommand6({
1594
+ var initCommand = defineCommand7({
1218
1595
  meta: {
1219
1596
  name: "init",
1220
1597
  description: "Scaffold project artefacts"
@@ -1225,23 +1602,23 @@ var initCommand = defineCommand6({
1225
1602
  });
1226
1603
 
1227
1604
  // 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";
1605
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
1606
+ import { mkdir as mkdir3, readFile, writeFile as writeFile3 } from "fs/promises";
1607
+ import { join as join3 } from "path";
1608
+ import { defineCommand as defineCommand8 } from "citty";
1232
1609
  function detectHookManager(rootDir) {
1233
- const huskyDir = join2(rootDir, ".husky");
1234
- if (existsSync2(huskyDir)) {
1610
+ const huskyDir = join3(rootDir, ".husky");
1611
+ if (existsSync3(huskyDir)) {
1235
1612
  return "husky";
1236
1613
  }
1237
- const lefthookYml = join2(rootDir, "lefthook.yml");
1238
- if (existsSync2(lefthookYml)) {
1614
+ const lefthookYml = join3(rootDir, "lefthook.yml");
1615
+ if (existsSync3(lefthookYml)) {
1239
1616
  return "lefthook";
1240
1617
  }
1241
- const pkgJsonPath = join2(rootDir, "package.json");
1242
- if (existsSync2(pkgJsonPath)) {
1618
+ const pkgJsonPath = join3(rootDir, "package.json");
1619
+ if (existsSync3(pkgJsonPath)) {
1243
1620
  try {
1244
- const raw = readFileSync(pkgJsonPath, "utf8");
1621
+ const raw = readFileSync2(pkgJsonPath, "utf8");
1245
1622
  const pkg2 = JSON.parse(raw);
1246
1623
  const allDeps = { ...pkg2.dependencies, ...pkg2.devDependencies };
1247
1624
  if ("husky" in allDeps) return "husky";
@@ -1270,10 +1647,10 @@ async function runInitHooks(args) {
1270
1647
  const warnings = [];
1271
1648
  const instructions = [];
1272
1649
  if (hookManager === "husky" || hookManager === "none") {
1273
- const huskyDir = join2(rootDir, ".husky");
1274
- const hookPath = join2(huskyDir, "pre-commit");
1650
+ const huskyDir = join3(rootDir, ".husky");
1651
+ const hookPath = join3(huskyDir, "pre-commit");
1275
1652
  const relativePath = ".husky/pre-commit";
1276
- if (existsSync2(hookPath) && !args.force) {
1653
+ if (existsSync3(hookPath) && !args.force) {
1277
1654
  const existing = await readFile(hookPath, "utf8");
1278
1655
  if (existing.includes("forge-ts check")) {
1279
1656
  skippedFiles.push(relativePath);
@@ -1286,12 +1663,12 @@ async function runInitHooks(args) {
1286
1663
 
1287
1664
  npx forge-ts check
1288
1665
  `;
1289
- await writeFile2(hookPath, appended, { mode: 493 });
1666
+ await writeFile3(hookPath, appended, { mode: 493 });
1290
1667
  writtenFiles.push(relativePath);
1291
1668
  }
1292
1669
  } else {
1293
- await mkdir2(huskyDir, { recursive: true });
1294
- await writeFile2(hookPath, HUSKY_PRE_COMMIT, { mode: 493 });
1670
+ await mkdir3(huskyDir, { recursive: true });
1671
+ await writeFile3(hookPath, HUSKY_PRE_COMMIT, { mode: 493 });
1295
1672
  writtenFiles.push(relativePath);
1296
1673
  }
1297
1674
  if (hookManager === "none") {
@@ -1303,9 +1680,9 @@ npx forge-ts check
1303
1680
  instructions.push("Husky pre-commit hook configured to run forge-ts check.");
1304
1681
  }
1305
1682
  } else if (hookManager === "lefthook") {
1306
- const lefthookPath = join2(rootDir, "lefthook.yml");
1683
+ const lefthookPath = join3(rootDir, "lefthook.yml");
1307
1684
  const relativePath = "lefthook.yml";
1308
- if (existsSync2(lefthookPath)) {
1685
+ if (existsSync3(lefthookPath)) {
1309
1686
  const existing = await readFile(lefthookPath, "utf8");
1310
1687
  if (existing.includes("forge-ts check") && !args.force) {
1311
1688
  skippedFiles.push(relativePath);
@@ -1318,17 +1695,17 @@ npx forge-ts check
1318
1695
  forge-ts-check:
1319
1696
  run: npx forge-ts check
1320
1697
  `;
1321
- await writeFile2(lefthookPath, appended, "utf8");
1698
+ await writeFile3(lefthookPath, appended, "utf8");
1322
1699
  writtenFiles.push(relativePath);
1323
1700
  } else {
1324
1701
  const appended = `${existing.trimEnd()}
1325
1702
 
1326
1703
  ${LEFTHOOK_BLOCK}`;
1327
- await writeFile2(lefthookPath, appended, "utf8");
1704
+ await writeFile3(lefthookPath, appended, "utf8");
1328
1705
  writtenFiles.push(relativePath);
1329
1706
  }
1330
1707
  } else {
1331
- await writeFile2(lefthookPath, LEFTHOOK_BLOCK, "utf8");
1708
+ await writeFile3(lefthookPath, LEFTHOOK_BLOCK, "utf8");
1332
1709
  writtenFiles.push(relativePath);
1333
1710
  }
1334
1711
  instructions.push("Lefthook pre-commit hook configured to run forge-ts check.");
@@ -1373,7 +1750,7 @@ function formatInitHooksHuman(result) {
1373
1750
  ${result.summary.filesWritten} file(s) written.`);
1374
1751
  return lines.join("\n");
1375
1752
  }
1376
- var initHooksCommand = defineCommand7({
1753
+ var initHooksCommand = defineCommand8({
1377
1754
  meta: {
1378
1755
  name: "hooks",
1379
1756
  description: "Scaffold git hook integration (husky/lefthook)"
@@ -1441,7 +1818,7 @@ import {
1441
1818
  readLockFile,
1442
1819
  writeLockFile
1443
1820
  } from "@forge-ts/core";
1444
- import { defineCommand as defineCommand8 } from "citty";
1821
+ import { defineCommand as defineCommand9 } from "citty";
1445
1822
  async function runLock(args) {
1446
1823
  const config = await loadConfig6(args.cwd);
1447
1824
  const rootDir = config.rootDir;
@@ -1500,7 +1877,7 @@ function formatLockHuman(result) {
1500
1877
  To modify locked settings, run: forge-ts unlock --reason="..."`);
1501
1878
  return lines.join("\n");
1502
1879
  }
1503
- var lockCommand = defineCommand8({
1880
+ var lockCommand = defineCommand9({
1504
1881
  meta: {
1505
1882
  name: "lock",
1506
1883
  description: "Lock current config to prevent silent weakening"
@@ -1539,7 +1916,7 @@ var lockCommand = defineCommand8({
1539
1916
  });
1540
1917
 
1541
1918
  // src/commands/prepublish.ts
1542
- import { defineCommand as defineCommand9 } from "citty";
1919
+ import { defineCommand as defineCommand10 } from "citty";
1543
1920
  async function runPrepublish(args) {
1544
1921
  const start = Date.now();
1545
1922
  const allErrors = [];
@@ -1643,7 +2020,7 @@ function formatPrepublishHuman(result) {
1643
2020
  }
1644
2021
  return lines.join("\n");
1645
2022
  }
1646
- var prepublishCommand = defineCommand9({
2023
+ var prepublishCommand = defineCommand10({
1647
2024
  meta: {
1648
2025
  name: "prepublish",
1649
2026
  description: "Safety gate: check + build before npm publish"
@@ -1704,7 +2081,7 @@ var prepublishCommand = defineCommand9({
1704
2081
  // src/commands/test.ts
1705
2082
  import { loadConfig as loadConfig7 } from "@forge-ts/core";
1706
2083
  import { doctest } from "@forge-ts/doctest";
1707
- import { defineCommand as defineCommand10 } from "citty";
2084
+ import { defineCommand as defineCommand11 } from "citty";
1708
2085
  async function runTest(args) {
1709
2086
  const config = await loadConfig7(args.cwd);
1710
2087
  const result = await doctest(config);
@@ -1747,7 +2124,7 @@ async function runTest(args) {
1747
2124
  duration: result.duration
1748
2125
  };
1749
2126
  }
1750
- var testCommand = defineCommand10({
2127
+ var testCommand = defineCommand11({
1751
2128
  meta: {
1752
2129
  name: "test",
1753
2130
  description: "Run @example blocks as doctests"
@@ -1808,7 +2185,7 @@ import {
1808
2185
  readLockFile as readLockFile2,
1809
2186
  removeLockFile
1810
2187
  } from "@forge-ts/core";
1811
- import { defineCommand as defineCommand11 } from "citty";
2188
+ import { defineCommand as defineCommand12 } from "citty";
1812
2189
  async function runUnlock(args) {
1813
2190
  const config = await loadConfig8(args.cwd);
1814
2191
  const rootDir = config.rootDir;
@@ -1893,7 +2270,7 @@ function formatUnlockHuman(result) {
1893
2270
  lines.push(" Run `forge-ts lock` to re-lock after changes.");
1894
2271
  return lines.join("\n");
1895
2272
  }
1896
- var unlockCommand = defineCommand11({
2273
+ var unlockCommand = defineCommand12({
1897
2274
  meta: {
1898
2275
  name: "unlock",
1899
2276
  description: "Remove config lock (requires --reason)"
@@ -1948,7 +2325,7 @@ var unlockCommand = defineCommand11({
1948
2325
  // src/index.ts
1949
2326
  var require2 = createRequire(import.meta.url);
1950
2327
  var pkg = require2("../package.json");
1951
- var docsCommand = defineCommand12({
2328
+ var docsCommand = defineCommand13({
1952
2329
  meta: {
1953
2330
  name: "docs",
1954
2331
  description: "Documentation site management"
@@ -1958,17 +2335,120 @@ var docsCommand = defineCommand12({
1958
2335
  dev: docsDevCommand
1959
2336
  }
1960
2337
  });
1961
- var initCommand2 = defineCommand12({
2338
+ var initCommand2 = defineCommand13({
1962
2339
  meta: {
1963
2340
  name: "init",
1964
- description: "Scaffold project artefacts"
2341
+ description: "Full project setup (bare) or scaffold artefacts (with subcommand)"
2342
+ },
2343
+ args: {
2344
+ cwd: {
2345
+ type: "string",
2346
+ description: "Project root directory"
2347
+ },
2348
+ json: {
2349
+ type: "boolean",
2350
+ description: "Output as LAFS JSON envelope",
2351
+ default: false
2352
+ },
2353
+ human: {
2354
+ type: "boolean",
2355
+ description: "Output as formatted text",
2356
+ default: false
2357
+ },
2358
+ quiet: {
2359
+ type: "boolean",
2360
+ description: "Suppress non-essential output",
2361
+ default: false
2362
+ },
2363
+ mvi: {
2364
+ type: "string",
2365
+ description: "MVI verbosity level: minimal, standard, full"
2366
+ }
1965
2367
  },
1966
2368
  subCommands: {
1967
2369
  docs: initDocsCommand,
1968
- hooks: initHooksCommand
2370
+ hooks: initHooksCommand,
2371
+ setup: initProjectCommand
2372
+ },
2373
+ async run({ rawArgs, args }) {
2374
+ const subCommandNames = /* @__PURE__ */ new Set(["docs", "hooks", "setup"]);
2375
+ const hasSubCommand = rawArgs.some((arg) => subCommandNames.has(arg));
2376
+ if (hasSubCommand) {
2377
+ return;
2378
+ }
2379
+ const { runInitProject: runInitProject2 } = await import("./init-project-5DESIZ73.js");
2380
+ const { emitResult: emitResult2, resolveExitCode: resolveExitCode2 } = await import("./output-OSCHMPOX.js");
2381
+ const { createLogger: createLogger3 } = await import("./logger-MMBBZG6U.js");
2382
+ const output = await runInitProject2({
2383
+ cwd: args.cwd,
2384
+ mvi: args.mvi
2385
+ });
2386
+ const flags = {
2387
+ json: args.json,
2388
+ human: args.human,
2389
+ quiet: args.quiet,
2390
+ mvi: args.mvi
2391
+ };
2392
+ emitResult2(output, flags, (data, cmd) => {
2393
+ if (!cmd.success) {
2394
+ const logger = createLogger3();
2395
+ const msg = cmd.errors?.[0]?.message ?? "Init failed";
2396
+ logger.error(msg);
2397
+ return "";
2398
+ }
2399
+ const lines = [];
2400
+ lines.push("\nforge-ts init: project setup complete\n");
2401
+ if (data.created.length > 0) {
2402
+ lines.push(" Created:");
2403
+ for (const file of data.created) {
2404
+ lines.push(` ${file}`);
2405
+ }
2406
+ }
2407
+ if (data.skipped.length > 0) {
2408
+ lines.push("");
2409
+ lines.push(" Already exists (skipped):");
2410
+ for (const file of data.skipped) {
2411
+ lines.push(` ${file}`);
2412
+ }
2413
+ } else if (data.created.length > 0) {
2414
+ lines.push("");
2415
+ lines.push(" Already exists (skipped):");
2416
+ lines.push(" (none)");
2417
+ }
2418
+ if (data.warnings.length > 0) {
2419
+ lines.push("");
2420
+ lines.push(" Warnings:");
2421
+ for (const warning of data.warnings) {
2422
+ lines.push(` ${warning}`);
2423
+ }
2424
+ }
2425
+ const env = data.environment;
2426
+ lines.push("");
2427
+ lines.push(" Environment:");
2428
+ lines.push(` TypeScript: ${env.typescriptVersion ?? "not detected"}`);
2429
+ lines.push(` Biome: ${env.biomeDetected ? "detected" : "not detected"}`);
2430
+ const hookLabel = env.hookManager === "none" ? "not detected" : `${env.hookManager} detected`;
2431
+ lines.push(` Git hooks: ${hookLabel}`);
2432
+ if (env.monorepo) {
2433
+ lines.push(
2434
+ ` Monorepo: ${env.monorepoType === "pnpm" ? "pnpm workspaces" : "npm/yarn workspaces"}`
2435
+ );
2436
+ } else {
2437
+ lines.push(" Monorepo: no");
2438
+ }
2439
+ if (data.nextSteps.length > 0) {
2440
+ lines.push("");
2441
+ lines.push(" Next steps:");
2442
+ for (const [idx, step] of data.nextSteps.entries()) {
2443
+ lines.push(` ${idx + 1}. ${step}`);
2444
+ }
2445
+ }
2446
+ return lines.join("\n");
2447
+ });
2448
+ process.exit(resolveExitCode2(output));
1969
2449
  }
1970
2450
  });
1971
- var main = defineCommand12({
2451
+ var main = defineCommand13({
1972
2452
  meta: {
1973
2453
  name: "forge-ts",
1974
2454
  version: pkg.version,
@@ -1984,7 +2464,8 @@ var main = defineCommand12({
1984
2464
  unlock: unlockCommand,
1985
2465
  bypass: bypassCommand,
1986
2466
  audit: auditCommand,
1987
- prepublish: prepublishCommand
2467
+ prepublish: prepublishCommand,
2468
+ doctor: doctorCommand
1988
2469
  }
1989
2470
  });
1990
2471
  runMain(main);
@@ -1995,16 +2476,20 @@ export {
1995
2476
  checkCommand,
1996
2477
  createLogger,
1997
2478
  docsDevCommand,
2479
+ doctorCommand,
1998
2480
  emitResult,
1999
2481
  initDocsCommand,
2000
2482
  initHooksCommand,
2483
+ initProjectCommand,
2001
2484
  lockCommand,
2002
2485
  prepublishCommand,
2003
2486
  resolveExitCode,
2004
2487
  runBypassCreate,
2005
2488
  runBypassStatus,
2006
2489
  runDocsDev,
2490
+ runDoctor,
2007
2491
  runInitHooks,
2492
+ runInitProject,
2008
2493
  runLock,
2009
2494
  runPrepublish,
2010
2495
  runUnlock,