@forge-ts/cli 0.14.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/chunk-7UPSAG3L.js +44 -0
- package/dist/chunk-7UPSAG3L.js.map +1 -0
- package/dist/chunk-CINQWGH7.js +344 -0
- package/dist/chunk-CINQWGH7.js.map +1 -0
- package/dist/chunk-ZFFY4AQX.js +73 -0
- package/dist/chunk-ZFFY4AQX.js.map +1 -0
- package/dist/index.d.ts +246 -1
- package/dist/index.js +646 -161
- package/dist/index.js.map +1 -1
- package/dist/init-project-5DESIZ73.js +14 -0
- package/dist/init-project-5DESIZ73.js.map +1 -0
- package/dist/logger-MMBBZG6U.js +8 -0
- package/dist/logger-MMBBZG6U.js.map +1 -0
- package/dist/output-OSCHMPOX.js +10 -0
- package/dist/output-OSCHMPOX.js.map +1 -0
- package/package.json +7 -7
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/logger.ts
|
|
4
|
+
var GREEN = "\x1B[32m";
|
|
5
|
+
var YELLOW = "\x1B[33m";
|
|
6
|
+
var RED = "\x1B[31m";
|
|
7
|
+
var BOLD = "\x1B[1m";
|
|
8
|
+
var RESET = "\x1B[0m";
|
|
9
|
+
function createLogger(options) {
|
|
10
|
+
const useColors = options?.colors ?? process.stdout.isTTY ?? false;
|
|
11
|
+
function colorize(text, code) {
|
|
12
|
+
return useColors ? `${code}${text}${RESET}` : text;
|
|
13
|
+
}
|
|
14
|
+
function bold(text) {
|
|
15
|
+
return useColors ? `${BOLD}${text}${RESET}` : text;
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
info(msg) {
|
|
19
|
+
console.log(msg);
|
|
20
|
+
},
|
|
21
|
+
success(msg) {
|
|
22
|
+
const prefix = colorize("\u2713", GREEN);
|
|
23
|
+
console.log(`${prefix} ${msg}`);
|
|
24
|
+
},
|
|
25
|
+
warn(msg) {
|
|
26
|
+
const prefix = colorize("warn", YELLOW);
|
|
27
|
+
console.warn(`${bold(prefix)} ${msg}`);
|
|
28
|
+
},
|
|
29
|
+
error(msg) {
|
|
30
|
+
const prefix = colorize("error", RED);
|
|
31
|
+
console.error(`${bold(prefix)} ${msg}`);
|
|
32
|
+
},
|
|
33
|
+
step(label, detail, duration) {
|
|
34
|
+
const check = colorize("\u2713", GREEN);
|
|
35
|
+
const durationStr = duration !== void 0 ? ` (${duration}ms)` : "";
|
|
36
|
+
console.log(` ${check} ${bold(label)}: ${detail}${durationStr}`);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
createLogger
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=chunk-7UPSAG3L.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/logger.ts"],"sourcesContent":["/**\n * Simple TTY-aware logger for forge-ts CLI output.\n *\n * Uses ANSI escape codes directly — no external colour library.\n *\n * @packageDocumentation\n * @internal\n */\n\n// ---------------------------------------------------------------------------\n// ANSI constants\n// ---------------------------------------------------------------------------\n\nconst GREEN = \"\\x1b[32m\";\nconst YELLOW = \"\\x1b[33m\";\nconst RED = \"\\x1b[31m\";\nconst BOLD = \"\\x1b[1m\";\nconst RESET = \"\\x1b[0m\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * A minimal structured logger used throughout the CLI commands.\n * @internal\n */\nexport interface Logger {\n\t/** Print an informational message. */\n\tinfo(msg: string): void;\n\t/** Print a success message (green ✓ prefix when colours are on). */\n\tsuccess(msg: string): void;\n\t/** Print a warning message (yellow prefix when colours are on). */\n\twarn(msg: string): void;\n\t/** Print an error message (red ✗ prefix when colours are on). */\n\terror(msg: string): void;\n\t/**\n\t * Print a build-step line.\n\t *\n\t * @param label - Short category label (e.g. \"API\", \"Gen\").\n\t * @param detail - Description of what was produced.\n\t * @param duration - Optional wall-clock time in milliseconds.\n\t */\n\tstep(label: string, detail: string, duration?: number): void;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a {@link Logger} instance.\n *\n * @param options - Optional configuration.\n * @param options.colors - Emit ANSI colour codes. Defaults to `process.stdout.isTTY`.\n * @returns A configured logger.\n * @internal\n */\nexport function createLogger(options?: { colors?: boolean }): Logger {\n\tconst useColors = options?.colors ?? process.stdout.isTTY ?? false;\n\n\tfunction colorize(text: string, code: string): string {\n\t\treturn useColors ? `${code}${text}${RESET}` : text;\n\t}\n\n\tfunction bold(text: string): string {\n\t\treturn useColors ? `${BOLD}${text}${RESET}` : text;\n\t}\n\n\treturn {\n\t\tinfo(msg: string): void {\n\t\t\tconsole.log(msg);\n\t\t},\n\n\t\tsuccess(msg: string): void {\n\t\t\tconst prefix = colorize(\"✓\", GREEN);\n\t\t\tconsole.log(`${prefix} ${msg}`);\n\t\t},\n\n\t\twarn(msg: string): void {\n\t\t\tconst prefix = colorize(\"warn\", YELLOW);\n\t\t\tconsole.warn(`${bold(prefix)} ${msg}`);\n\t\t},\n\n\t\terror(msg: string): void {\n\t\t\tconst prefix = colorize(\"error\", RED);\n\t\t\tconsole.error(`${bold(prefix)} ${msg}`);\n\t\t},\n\n\t\tstep(label: string, detail: string, duration?: number): void {\n\t\t\tconst check = colorize(\"✓\", GREEN);\n\t\t\tconst durationStr = duration !== undefined ? ` (${duration}ms)` : \"\";\n\t\t\tconsole.log(` ${check} ${bold(label)}: ${detail}${durationStr}`);\n\t\t},\n\t};\n}\n"],"mappings":";;;AAaA,IAAM,QAAQ;AACd,IAAM,SAAS;AACf,IAAM,MAAM;AACZ,IAAM,OAAO;AACb,IAAM,QAAQ;AAyCP,SAAS,aAAa,SAAwC;AACpE,QAAM,YAAY,SAAS,UAAU,QAAQ,OAAO,SAAS;AAE7D,WAAS,SAAS,MAAc,MAAsB;AACrD,WAAO,YAAY,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,KAAK;AAAA,EAC/C;AAEA,WAAS,KAAK,MAAsB;AACnC,WAAO,YAAY,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,KAAK;AAAA,EAC/C;AAEA,SAAO;AAAA,IACN,KAAK,KAAmB;AACvB,cAAQ,IAAI,GAAG;AAAA,IAChB;AAAA,IAEA,QAAQ,KAAmB;AAC1B,YAAM,SAAS,SAAS,UAAK,KAAK;AAClC,cAAQ,IAAI,GAAG,MAAM,IAAI,GAAG,EAAE;AAAA,IAC/B;AAAA,IAEA,KAAK,KAAmB;AACvB,YAAM,SAAS,SAAS,QAAQ,MAAM;AACtC,cAAQ,KAAK,GAAG,KAAK,MAAM,CAAC,IAAI,GAAG,EAAE;AAAA,IACtC;AAAA,IAEA,MAAM,KAAmB;AACxB,YAAM,SAAS,SAAS,SAAS,GAAG;AACpC,cAAQ,MAAM,GAAG,KAAK,MAAM,CAAC,IAAI,GAAG,EAAE;AAAA,IACvC;AAAA,IAEA,KAAK,OAAe,QAAgB,UAAyB;AAC5D,YAAM,QAAQ,SAAS,UAAK,KAAK;AACjC,YAAM,cAAc,aAAa,SAAY,KAAK,QAAQ,QAAQ;AAClE,cAAQ,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC,KAAK,MAAM,GAAG,WAAW,EAAE;AAAA,IACjE;AAAA,EACD;AACD;","names":[]}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
emitResult,
|
|
4
|
+
resolveExitCode
|
|
5
|
+
} from "./chunk-ZFFY4AQX.js";
|
|
6
|
+
import {
|
|
7
|
+
createLogger
|
|
8
|
+
} from "./chunk-7UPSAG3L.js";
|
|
9
|
+
|
|
10
|
+
// src/commands/init-project.ts
|
|
11
|
+
import { existsSync, readFileSync } from "fs";
|
|
12
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
13
|
+
import { join } from "path";
|
|
14
|
+
import { defineCommand } from "citty";
|
|
15
|
+
function readJsonSafe(filePath) {
|
|
16
|
+
if (!existsSync(filePath)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const raw = readFileSync(filePath, "utf8");
|
|
21
|
+
return JSON.parse(raw);
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function detectEnvironment(rootDir) {
|
|
27
|
+
const packageJsonPath = join(rootDir, "package.json");
|
|
28
|
+
const tsconfigPath = join(rootDir, "tsconfig.json");
|
|
29
|
+
const biomePath = join(rootDir, "biome.json");
|
|
30
|
+
const biomecPath = join(rootDir, "biome.jsonc");
|
|
31
|
+
const pnpmWorkspacePath = join(rootDir, "pnpm-workspace.yaml");
|
|
32
|
+
const huskyDir = join(rootDir, ".husky");
|
|
33
|
+
const lefthookYml = join(rootDir, "lefthook.yml");
|
|
34
|
+
const packageJsonExists = existsSync(packageJsonPath);
|
|
35
|
+
const tsconfigExists = existsSync(tsconfigPath);
|
|
36
|
+
const biomeDetected = existsSync(biomePath) || existsSync(biomecPath);
|
|
37
|
+
let typescriptVersion = null;
|
|
38
|
+
if (packageJsonExists) {
|
|
39
|
+
const pkg = readJsonSafe(packageJsonPath);
|
|
40
|
+
if (pkg) {
|
|
41
|
+
const tsVersion = pkg.devDependencies?.typescript ?? pkg.dependencies?.typescript ?? null;
|
|
42
|
+
typescriptVersion = tsVersion;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
let hookManager = "none";
|
|
46
|
+
if (existsSync(huskyDir)) {
|
|
47
|
+
hookManager = "husky";
|
|
48
|
+
} else if (existsSync(lefthookYml)) {
|
|
49
|
+
hookManager = "lefthook";
|
|
50
|
+
} else if (packageJsonExists) {
|
|
51
|
+
const pkg = readJsonSafe(packageJsonPath);
|
|
52
|
+
if (pkg) {
|
|
53
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
54
|
+
if ("husky" in allDeps) {
|
|
55
|
+
hookManager = "husky";
|
|
56
|
+
} else if ("lefthook" in allDeps) {
|
|
57
|
+
hookManager = "lefthook";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
let monorepo = false;
|
|
62
|
+
let monorepoType = null;
|
|
63
|
+
if (existsSync(pnpmWorkspacePath)) {
|
|
64
|
+
monorepo = true;
|
|
65
|
+
monorepoType = "pnpm";
|
|
66
|
+
} else if (packageJsonExists) {
|
|
67
|
+
const pkg = readJsonSafe(packageJsonPath);
|
|
68
|
+
if (pkg?.workspaces) {
|
|
69
|
+
monorepo = true;
|
|
70
|
+
monorepoType = "npm/yarn";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
packageJsonExists,
|
|
75
|
+
tsconfigExists,
|
|
76
|
+
biomeDetected,
|
|
77
|
+
typescriptVersion,
|
|
78
|
+
hookManager,
|
|
79
|
+
monorepo,
|
|
80
|
+
monorepoType
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
var DEFAULT_CONFIG_CONTENT = `import { defineConfig } from "@forge-ts/core";
|
|
84
|
+
|
|
85
|
+
export default defineConfig({
|
|
86
|
+
rootDir: ".",
|
|
87
|
+
tsconfig: "tsconfig.json",
|
|
88
|
+
outDir: "docs/generated",
|
|
89
|
+
enforce: {
|
|
90
|
+
enabled: true,
|
|
91
|
+
minVisibility: "public",
|
|
92
|
+
strict: false,
|
|
93
|
+
},
|
|
94
|
+
gen: {
|
|
95
|
+
enabled: true,
|
|
96
|
+
formats: ["mdx"],
|
|
97
|
+
llmsTxt: true,
|
|
98
|
+
readmeSync: false,
|
|
99
|
+
ssgTarget: "mintlify",
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
`;
|
|
103
|
+
var DEFAULT_TSDOC_CONTENT = JSON.stringify(
|
|
104
|
+
{
|
|
105
|
+
$schema: "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json",
|
|
106
|
+
extends: ["@forge-ts/tsdoc-config/tsdoc.json"]
|
|
107
|
+
},
|
|
108
|
+
null,
|
|
109
|
+
" "
|
|
110
|
+
);
|
|
111
|
+
async function runInitProject(args) {
|
|
112
|
+
const start = Date.now();
|
|
113
|
+
const rootDir = args.cwd ?? process.cwd();
|
|
114
|
+
const created = [];
|
|
115
|
+
const skipped = [];
|
|
116
|
+
const warnings = [];
|
|
117
|
+
const cliWarnings = [];
|
|
118
|
+
const environment = detectEnvironment(rootDir);
|
|
119
|
+
if (!environment.packageJsonExists) {
|
|
120
|
+
const err = {
|
|
121
|
+
code: "INIT_NO_PACKAGE_JSON",
|
|
122
|
+
message: "package.json not found. Run `npm init` or `pnpm init` first."
|
|
123
|
+
};
|
|
124
|
+
return {
|
|
125
|
+
operation: "init",
|
|
126
|
+
success: false,
|
|
127
|
+
data: {
|
|
128
|
+
success: false,
|
|
129
|
+
created: [],
|
|
130
|
+
skipped: [],
|
|
131
|
+
warnings: [],
|
|
132
|
+
environment,
|
|
133
|
+
nextSteps: []
|
|
134
|
+
},
|
|
135
|
+
errors: [err],
|
|
136
|
+
duration: Date.now() - start
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (!environment.tsconfigExists) {
|
|
140
|
+
warnings.push("tsconfig.json not found. forge-ts requires TypeScript.");
|
|
141
|
+
cliWarnings.push({
|
|
142
|
+
code: "INIT_NO_TSCONFIG",
|
|
143
|
+
message: "tsconfig.json not found. forge-ts requires TypeScript."
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (!environment.biomeDetected) {
|
|
147
|
+
}
|
|
148
|
+
const configPath = join(rootDir, "forge-ts.config.ts");
|
|
149
|
+
if (existsSync(configPath)) {
|
|
150
|
+
skipped.push("forge-ts.config.ts");
|
|
151
|
+
} else {
|
|
152
|
+
await mkdir(rootDir, { recursive: true });
|
|
153
|
+
await writeFile(configPath, DEFAULT_CONFIG_CONTENT, "utf8");
|
|
154
|
+
created.push("forge-ts.config.ts");
|
|
155
|
+
}
|
|
156
|
+
const tsdocPath = join(rootDir, "tsdoc.json");
|
|
157
|
+
if (existsSync(tsdocPath)) {
|
|
158
|
+
skipped.push("tsdoc.json");
|
|
159
|
+
} else {
|
|
160
|
+
await writeFile(tsdocPath, `${DEFAULT_TSDOC_CONTENT}
|
|
161
|
+
`, "utf8");
|
|
162
|
+
created.push("tsdoc.json");
|
|
163
|
+
}
|
|
164
|
+
if (environment.tsconfigExists) {
|
|
165
|
+
const tsconfigPath = join(rootDir, "tsconfig.json");
|
|
166
|
+
const tsconfig = readJsonSafe(tsconfigPath);
|
|
167
|
+
if (tsconfig) {
|
|
168
|
+
if (!tsconfig.compilerOptions || tsconfig.compilerOptions.strict !== true) {
|
|
169
|
+
warnings.push(
|
|
170
|
+
"tsconfig.json: 'strict' is not enabled. forge-ts recommends strict mode."
|
|
171
|
+
);
|
|
172
|
+
cliWarnings.push({
|
|
173
|
+
code: "INIT_TSCONFIG_NOT_STRICT",
|
|
174
|
+
message: "tsconfig.json: 'strict' is not enabled. forge-ts recommends strict mode."
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const pkgPath = join(rootDir, "package.json");
|
|
180
|
+
const pkg = readJsonSafe(pkgPath);
|
|
181
|
+
if (pkg) {
|
|
182
|
+
if (pkg.type !== "module") {
|
|
183
|
+
warnings.push(
|
|
184
|
+
'package.json: missing "type": "module". forge-ts uses ESM.'
|
|
185
|
+
);
|
|
186
|
+
cliWarnings.push({
|
|
187
|
+
code: "INIT_NO_ESM",
|
|
188
|
+
message: 'package.json: missing "type": "module". forge-ts uses ESM.'
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (!pkg.engines?.node) {
|
|
192
|
+
warnings.push("package.json: missing engines.node field.");
|
|
193
|
+
cliWarnings.push({
|
|
194
|
+
code: "INIT_NO_ENGINES",
|
|
195
|
+
message: "package.json: missing engines.node field."
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
199
|
+
if (!("@forge-ts/cli" in allDeps)) {
|
|
200
|
+
warnings.push(
|
|
201
|
+
"package.json: @forge-ts/cli not in devDependencies (running via npx?)."
|
|
202
|
+
);
|
|
203
|
+
cliWarnings.push({
|
|
204
|
+
code: "INIT_CLI_NOT_INSTALLED",
|
|
205
|
+
message: "package.json: @forge-ts/cli not in devDependencies (running via npx?)."
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const nextSteps = [
|
|
210
|
+
"Run: forge-ts check (lint TSDoc coverage)",
|
|
211
|
+
"Run: forge-ts init docs (scaffold documentation site)",
|
|
212
|
+
"Run: forge-ts init hooks (scaffold pre-commit hooks)",
|
|
213
|
+
"Run: forge-ts lock (lock config to prevent drift)"
|
|
214
|
+
];
|
|
215
|
+
const data = {
|
|
216
|
+
success: true,
|
|
217
|
+
created,
|
|
218
|
+
skipped,
|
|
219
|
+
warnings,
|
|
220
|
+
environment,
|
|
221
|
+
nextSteps
|
|
222
|
+
};
|
|
223
|
+
return {
|
|
224
|
+
operation: "init",
|
|
225
|
+
success: true,
|
|
226
|
+
data,
|
|
227
|
+
warnings: cliWarnings.length > 0 ? cliWarnings : void 0,
|
|
228
|
+
duration: Date.now() - start
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function formatInitProjectHuman(result) {
|
|
232
|
+
const lines = [];
|
|
233
|
+
lines.push("\nforge-ts init: project setup complete\n");
|
|
234
|
+
if (result.created.length > 0) {
|
|
235
|
+
lines.push(" Created:");
|
|
236
|
+
for (const file of result.created) {
|
|
237
|
+
lines.push(` ${file}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (result.skipped.length > 0) {
|
|
241
|
+
lines.push("");
|
|
242
|
+
lines.push(" Already exists (skipped):");
|
|
243
|
+
for (const file of result.skipped) {
|
|
244
|
+
lines.push(` ${file}`);
|
|
245
|
+
}
|
|
246
|
+
} else if (result.created.length > 0) {
|
|
247
|
+
lines.push("");
|
|
248
|
+
lines.push(" Already exists (skipped):");
|
|
249
|
+
lines.push(" (none)");
|
|
250
|
+
}
|
|
251
|
+
if (result.warnings.length > 0) {
|
|
252
|
+
lines.push("");
|
|
253
|
+
lines.push(" Warnings:");
|
|
254
|
+
for (const warning of result.warnings) {
|
|
255
|
+
lines.push(` ${warning}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const env = result.environment;
|
|
259
|
+
lines.push("");
|
|
260
|
+
lines.push(" Environment:");
|
|
261
|
+
lines.push(
|
|
262
|
+
` TypeScript: ${env.typescriptVersion ?? "not detected"}`
|
|
263
|
+
);
|
|
264
|
+
lines.push(
|
|
265
|
+
` Biome: ${env.biomeDetected ? "detected" : "not detected"}`
|
|
266
|
+
);
|
|
267
|
+
const hookLabel = env.hookManager === "none" ? "not detected" : `${env.hookManager} detected`;
|
|
268
|
+
lines.push(` Git hooks: ${hookLabel}`);
|
|
269
|
+
if (env.monorepo) {
|
|
270
|
+
lines.push(
|
|
271
|
+
` Monorepo: ${env.monorepoType === "pnpm" ? "pnpm workspaces" : "npm/yarn workspaces"}`
|
|
272
|
+
);
|
|
273
|
+
} else {
|
|
274
|
+
lines.push(" Monorepo: no");
|
|
275
|
+
}
|
|
276
|
+
if (result.nextSteps.length > 0) {
|
|
277
|
+
lines.push("");
|
|
278
|
+
lines.push(" Next steps:");
|
|
279
|
+
for (const [idx, step] of result.nextSteps.entries()) {
|
|
280
|
+
lines.push(` ${idx + 1}. ${step}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return lines.join("\n");
|
|
284
|
+
}
|
|
285
|
+
var initProjectCommand = defineCommand({
|
|
286
|
+
meta: {
|
|
287
|
+
name: "init",
|
|
288
|
+
description: "Full project setup for forge-ts"
|
|
289
|
+
},
|
|
290
|
+
args: {
|
|
291
|
+
cwd: {
|
|
292
|
+
type: "string",
|
|
293
|
+
description: "Project root directory"
|
|
294
|
+
},
|
|
295
|
+
json: {
|
|
296
|
+
type: "boolean",
|
|
297
|
+
description: "Output as LAFS JSON envelope",
|
|
298
|
+
default: false
|
|
299
|
+
},
|
|
300
|
+
human: {
|
|
301
|
+
type: "boolean",
|
|
302
|
+
description: "Output as formatted text",
|
|
303
|
+
default: false
|
|
304
|
+
},
|
|
305
|
+
quiet: {
|
|
306
|
+
type: "boolean",
|
|
307
|
+
description: "Suppress non-essential output",
|
|
308
|
+
default: false
|
|
309
|
+
},
|
|
310
|
+
mvi: {
|
|
311
|
+
type: "string",
|
|
312
|
+
description: "MVI verbosity level: minimal, standard, full"
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
async run({ args }) {
|
|
316
|
+
const output = await runInitProject({
|
|
317
|
+
cwd: args.cwd,
|
|
318
|
+
mvi: args.mvi
|
|
319
|
+
});
|
|
320
|
+
const flags = {
|
|
321
|
+
json: args.json,
|
|
322
|
+
human: args.human,
|
|
323
|
+
quiet: args.quiet,
|
|
324
|
+
mvi: args.mvi
|
|
325
|
+
};
|
|
326
|
+
emitResult(output, flags, (data, cmd) => {
|
|
327
|
+
if (!cmd.success) {
|
|
328
|
+
const logger = createLogger();
|
|
329
|
+
const msg = cmd.errors?.[0]?.message ?? "Init failed";
|
|
330
|
+
logger.error(msg);
|
|
331
|
+
return "";
|
|
332
|
+
}
|
|
333
|
+
return formatInitProjectHuman(data);
|
|
334
|
+
});
|
|
335
|
+
process.exit(resolveExitCode(output));
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
export {
|
|
340
|
+
detectEnvironment,
|
|
341
|
+
runInitProject,
|
|
342
|
+
initProjectCommand
|
|
343
|
+
};
|
|
344
|
+
//# sourceMappingURL=chunk-CINQWGH7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/init-project.ts"],"sourcesContent":["/**\n * `forge-ts init` command — full project setup.\n *\n * Detects the project environment, writes default config files, validates\n * tsconfig/package.json, and prints a summary with next steps.\n *\n * @packageDocumentation\n * @internal\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { defineCommand } from \"citty\";\nimport { createLogger } from \"../logger.js\";\nimport {\n\ttype CommandOutput,\n\temitResult,\n\ttype ForgeCliError,\n\ttype ForgeCliWarning,\n\ttype OutputFlags,\n\tresolveExitCode,\n} from \"../output.js\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Detected project environment from Step 1 of the init flow.\n *\n * @public\n */\nexport interface InitProjectEnvironment {\n\t/** Whether package.json exists. */\n\tpackageJsonExists: boolean;\n\t/** Whether tsconfig.json exists. */\n\ttsconfigExists: boolean;\n\t/** Whether biome.json or biome.jsonc exists. */\n\tbiomeDetected: boolean;\n\t/** TypeScript version from dependencies, or null if not found. */\n\ttypescriptVersion: string | null;\n\t/** Detected hook manager. */\n\thookManager: \"husky\" | \"lefthook\" | \"none\";\n\t/** Whether the project is a monorepo. */\n\tmonorepo: boolean;\n\t/** Monorepo type if detected. */\n\tmonorepoType: \"pnpm\" | \"npm/yarn\" | null;\n}\n\n/**\n * Result of the `init` (project setup) command.\n *\n * @example\n * ```typescript\n * import { runInitProject } from \"@forge-ts/cli/commands/init-project\";\n * const output = await runInitProject({ cwd: process.cwd() });\n * console.log(output.data.created); // list of created files\n * ```\n * @public\n */\nexport interface InitProjectResult {\n\t/** Whether the init succeeded. */\n\tsuccess: boolean;\n\t/** Files that were created. */\n\tcreated: string[];\n\t/** Files that already existed and were skipped. */\n\tskipped: string[];\n\t/** Warning messages collected during init. */\n\twarnings: string[];\n\t/** Detected project environment. */\n\tenvironment: InitProjectEnvironment;\n\t/** Next steps for the user. */\n\tnextSteps: string[];\n}\n\n/**\n * Arguments for the `init` (project setup) command.\n * @internal\n */\nexport interface InitProjectArgs {\n\t/** Project root directory (default: cwd). */\n\tcwd?: string;\n\t/** MVI verbosity level for structured output. */\n\tmvi?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Reads and parses a package.json file.\n * @internal\n */\ninterface PackageJsonShape {\n\ttype?: string;\n\tengines?: { node?: string };\n\tdependencies?: Record<string, string>;\n\tdevDependencies?: Record<string, string>;\n\tworkspaces?: string[] | { packages: string[] };\n}\n\n/**\n * Safely reads and parses a JSON file.\n * @internal\n */\nfunction readJsonSafe<T>(filePath: string): T | null {\n\tif (!existsSync(filePath)) {\n\t\treturn null;\n\t}\n\ttry {\n\t\tconst raw = readFileSync(filePath, \"utf8\");\n\t\treturn JSON.parse(raw) as T;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Environment detection\n// ---------------------------------------------------------------------------\n\n/**\n * Detects the project environment by checking for files and dependencies.\n *\n * @param rootDir - Absolute path to the project root.\n * @returns The detected environment.\n * @internal\n */\nexport function detectEnvironment(rootDir: string): InitProjectEnvironment {\n\tconst packageJsonPath = join(rootDir, \"package.json\");\n\tconst tsconfigPath = join(rootDir, \"tsconfig.json\");\n\tconst biomePath = join(rootDir, \"biome.json\");\n\tconst biomecPath = join(rootDir, \"biome.jsonc\");\n\tconst pnpmWorkspacePath = join(rootDir, \"pnpm-workspace.yaml\");\n\tconst huskyDir = join(rootDir, \".husky\");\n\tconst lefthookYml = join(rootDir, \"lefthook.yml\");\n\n\tconst packageJsonExists = existsSync(packageJsonPath);\n\tconst tsconfigExists = existsSync(tsconfigPath);\n\tconst biomeDetected = existsSync(biomePath) || existsSync(biomecPath);\n\n\t// TypeScript version detection\n\tlet typescriptVersion: string | null = null;\n\tif (packageJsonExists) {\n\t\tconst pkg = readJsonSafe<PackageJsonShape>(packageJsonPath);\n\t\tif (pkg) {\n\t\t\tconst tsVersion =\n\t\t\t\tpkg.devDependencies?.typescript ?? pkg.dependencies?.typescript ?? null;\n\t\t\ttypescriptVersion = tsVersion;\n\t\t}\n\t}\n\n\t// Hook manager detection\n\tlet hookManager: \"husky\" | \"lefthook\" | \"none\" = \"none\";\n\tif (existsSync(huskyDir)) {\n\t\thookManager = \"husky\";\n\t} else if (existsSync(lefthookYml)) {\n\t\thookManager = \"lefthook\";\n\t} else if (packageJsonExists) {\n\t\tconst pkg = readJsonSafe<PackageJsonShape>(packageJsonPath);\n\t\tif (pkg) {\n\t\t\tconst allDeps = { ...pkg.dependencies, ...pkg.devDependencies };\n\t\t\tif (\"husky\" in allDeps) {\n\t\t\t\thookManager = \"husky\";\n\t\t\t} else if (\"lefthook\" in allDeps) {\n\t\t\t\thookManager = \"lefthook\";\n\t\t\t}\n\t\t}\n\t}\n\n\t// Monorepo detection\n\tlet monorepo = false;\n\tlet monorepoType: \"pnpm\" | \"npm/yarn\" | null = null;\n\tif (existsSync(pnpmWorkspacePath)) {\n\t\tmonorepo = true;\n\t\tmonorepoType = \"pnpm\";\n\t} else if (packageJsonExists) {\n\t\tconst pkg = readJsonSafe<PackageJsonShape>(packageJsonPath);\n\t\tif (pkg?.workspaces) {\n\t\t\tmonorepo = true;\n\t\t\tmonorepoType = \"npm/yarn\";\n\t\t}\n\t}\n\n\treturn {\n\t\tpackageJsonExists,\n\t\ttsconfigExists,\n\t\tbiomeDetected,\n\t\ttypescriptVersion,\n\t\thookManager,\n\t\tmonorepo,\n\t\tmonorepoType,\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Config file content\n// ---------------------------------------------------------------------------\n\n/**\n * Default forge-ts.config.ts content for new projects.\n * @internal\n */\nconst DEFAULT_CONFIG_CONTENT = `import { defineConfig } from \"@forge-ts/core\";\n\nexport default defineConfig({\n rootDir: \".\",\n tsconfig: \"tsconfig.json\",\n outDir: \"docs/generated\",\n enforce: {\n enabled: true,\n minVisibility: \"public\",\n strict: false,\n },\n gen: {\n enabled: true,\n formats: [\"mdx\"],\n llmsTxt: true,\n readmeSync: false,\n ssgTarget: \"mintlify\",\n },\n});\n`;\n\n/**\n * Default tsdoc.json content.\n * @internal\n */\nconst DEFAULT_TSDOC_CONTENT = JSON.stringify(\n\t{\n\t\t$schema:\n\t\t\t\"https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json\",\n\t\textends: [\"@forge-ts/tsdoc-config/tsdoc.json\"],\n\t},\n\tnull,\n\t\"\\t\",\n);\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Runs the full project init flow.\n *\n * Steps:\n * 1. Detect project environment\n * 2. Write forge-ts.config.ts (if not exists)\n * 3. Write tsdoc.json (if not exists)\n * 4. Validate tsconfig.json strictness\n * 5. Validate package.json\n * 6. Report summary\n *\n * @param args - CLI arguments for the init command.\n * @returns A typed `CommandOutput<InitProjectResult>`.\n * @example\n * ```typescript\n * import { runInitProject } from \"@forge-ts/cli/commands/init-project\";\n * const output = await runInitProject({ cwd: process.cwd() });\n * console.log(output.data.created); // [\"forge-ts.config.ts\", \"tsdoc.json\"]\n * ```\n * @public\n */\nexport async function runInitProject(\n\targs: InitProjectArgs,\n): Promise<CommandOutput<InitProjectResult>> {\n\tconst start = Date.now();\n\tconst rootDir = args.cwd ?? process.cwd();\n\n\tconst created: string[] = [];\n\tconst skipped: string[] = [];\n\tconst warnings: string[] = [];\n\tconst cliWarnings: ForgeCliWarning[] = [];\n\n\t// -----------------------------------------------------------------------\n\t// Step 1: Detect project environment\n\t// -----------------------------------------------------------------------\n\n\tconst environment = detectEnvironment(rootDir);\n\n\tif (!environment.packageJsonExists) {\n\t\tconst err: ForgeCliError = {\n\t\t\tcode: \"INIT_NO_PACKAGE_JSON\",\n\t\t\tmessage:\n\t\t\t\t\"package.json not found. Run `npm init` or `pnpm init` first.\",\n\t\t};\n\t\treturn {\n\t\t\toperation: \"init\",\n\t\t\tsuccess: false,\n\t\t\tdata: {\n\t\t\t\tsuccess: false,\n\t\t\t\tcreated: [],\n\t\t\t\tskipped: [],\n\t\t\t\twarnings: [],\n\t\t\t\tenvironment,\n\t\t\t\tnextSteps: [],\n\t\t\t},\n\t\t\terrors: [err],\n\t\t\tduration: Date.now() - start,\n\t\t};\n\t}\n\n\tif (!environment.tsconfigExists) {\n\t\twarnings.push(\"tsconfig.json not found. forge-ts requires TypeScript.\");\n\t\tcliWarnings.push({\n\t\t\tcode: \"INIT_NO_TSCONFIG\",\n\t\t\tmessage:\n\t\t\t\t\"tsconfig.json not found. forge-ts requires TypeScript.\",\n\t\t});\n\t}\n\n\tif (!environment.biomeDetected) {\n\t\t// Informational — not a warning\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Step 2: Write forge-ts.config.ts\n\t// -----------------------------------------------------------------------\n\n\tconst configPath = join(rootDir, \"forge-ts.config.ts\");\n\tif (existsSync(configPath)) {\n\t\tskipped.push(\"forge-ts.config.ts\");\n\t} else {\n\t\tawait mkdir(rootDir, { recursive: true });\n\t\tawait writeFile(configPath, DEFAULT_CONFIG_CONTENT, \"utf8\");\n\t\tcreated.push(\"forge-ts.config.ts\");\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Step 3: Write tsdoc.json\n\t// -----------------------------------------------------------------------\n\n\tconst tsdocPath = join(rootDir, \"tsdoc.json\");\n\tif (existsSync(tsdocPath)) {\n\t\tskipped.push(\"tsdoc.json\");\n\t} else {\n\t\tawait writeFile(tsdocPath, `${DEFAULT_TSDOC_CONTENT}\\n`, \"utf8\");\n\t\tcreated.push(\"tsdoc.json\");\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Step 4: Validate tsconfig.json strictness\n\t// -----------------------------------------------------------------------\n\n\tif (environment.tsconfigExists) {\n\t\tconst tsconfigPath = join(rootDir, \"tsconfig.json\");\n\t\tconst tsconfig = readJsonSafe<{\n\t\t\tcompilerOptions?: { strict?: boolean };\n\t\t}>(tsconfigPath);\n\n\t\tif (tsconfig) {\n\t\t\tif (\n\t\t\t\t!tsconfig.compilerOptions ||\n\t\t\t\ttsconfig.compilerOptions.strict !== true\n\t\t\t) {\n\t\t\t\twarnings.push(\n\t\t\t\t\t\"tsconfig.json: 'strict' is not enabled. forge-ts recommends strict mode.\",\n\t\t\t\t);\n\t\t\t\tcliWarnings.push({\n\t\t\t\t\tcode: \"INIT_TSCONFIG_NOT_STRICT\",\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"tsconfig.json: 'strict' is not enabled. forge-ts recommends strict mode.\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Step 5: Validate package.json\n\t// -----------------------------------------------------------------------\n\n\tconst pkgPath = join(rootDir, \"package.json\");\n\tconst pkg = readJsonSafe<PackageJsonShape>(pkgPath);\n\n\tif (pkg) {\n\t\tif (pkg.type !== \"module\") {\n\t\t\twarnings.push(\n\t\t\t\t'package.json: missing \"type\": \"module\". forge-ts uses ESM.',\n\t\t\t);\n\t\t\tcliWarnings.push({\n\t\t\t\tcode: \"INIT_NO_ESM\",\n\t\t\t\tmessage:\n\t\t\t\t\t'package.json: missing \"type\": \"module\". forge-ts uses ESM.',\n\t\t\t});\n\t\t}\n\n\t\tif (!pkg.engines?.node) {\n\t\t\twarnings.push(\"package.json: missing engines.node field.\");\n\t\t\tcliWarnings.push({\n\t\t\t\tcode: \"INIT_NO_ENGINES\",\n\t\t\t\tmessage: \"package.json: missing engines.node field.\",\n\t\t\t});\n\t\t}\n\n\t\tconst allDeps = { ...pkg.dependencies, ...pkg.devDependencies };\n\t\tif (!(\"@forge-ts/cli\" in allDeps)) {\n\t\t\twarnings.push(\n\t\t\t\t\"package.json: @forge-ts/cli not in devDependencies (running via npx?).\",\n\t\t\t);\n\t\t\tcliWarnings.push({\n\t\t\t\tcode: \"INIT_CLI_NOT_INSTALLED\",\n\t\t\t\tmessage:\n\t\t\t\t\t\"package.json: @forge-ts/cli not in devDependencies (running via npx?).\",\n\t\t\t});\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Step 6: Build result\n\t// -----------------------------------------------------------------------\n\n\tconst nextSteps: string[] = [\n\t\t\"Run: forge-ts check (lint TSDoc coverage)\",\n\t\t\"Run: forge-ts init docs (scaffold documentation site)\",\n\t\t\"Run: forge-ts init hooks (scaffold pre-commit hooks)\",\n\t\t\"Run: forge-ts lock (lock config to prevent drift)\",\n\t];\n\n\tconst data: InitProjectResult = {\n\t\tsuccess: true,\n\t\tcreated,\n\t\tskipped,\n\t\twarnings,\n\t\tenvironment,\n\t\tnextSteps,\n\t};\n\n\treturn {\n\t\toperation: \"init\",\n\t\tsuccess: true,\n\t\tdata,\n\t\twarnings: cliWarnings.length > 0 ? cliWarnings : undefined,\n\t\tduration: Date.now() - start,\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Human formatter\n// ---------------------------------------------------------------------------\n\n/**\n * Formats an InitProjectResult as human-readable text.\n * @internal\n */\nfunction formatInitProjectHuman(result: InitProjectResult): string {\n\tconst lines: string[] = [];\n\n\tlines.push(\"\\nforge-ts init: project setup complete\\n\");\n\n\t// Created files\n\tif (result.created.length > 0) {\n\t\tlines.push(\" Created:\");\n\t\tfor (const file of result.created) {\n\t\t\tlines.push(` ${file}`);\n\t\t}\n\t}\n\n\t// Skipped files\n\tif (result.skipped.length > 0) {\n\t\tlines.push(\"\");\n\t\tlines.push(\" Already exists (skipped):\");\n\t\tfor (const file of result.skipped) {\n\t\t\tlines.push(` ${file}`);\n\t\t}\n\t} else if (result.created.length > 0) {\n\t\tlines.push(\"\");\n\t\tlines.push(\" Already exists (skipped):\");\n\t\tlines.push(\" (none)\");\n\t}\n\n\t// Warnings\n\tif (result.warnings.length > 0) {\n\t\tlines.push(\"\");\n\t\tlines.push(\" Warnings:\");\n\t\tfor (const warning of result.warnings) {\n\t\t\tlines.push(` ${warning}`);\n\t\t}\n\t}\n\n\t// Environment\n\tconst env = result.environment;\n\tlines.push(\"\");\n\tlines.push(\" Environment:\");\n\tlines.push(\n\t\t` TypeScript: ${env.typescriptVersion ?? \"not detected\"}`,\n\t);\n\tlines.push(\n\t\t` Biome: ${env.biomeDetected ? \"detected\" : \"not detected\"}`,\n\t);\n\n\tconst hookLabel =\n\t\tenv.hookManager === \"none\" ? \"not detected\" : `${env.hookManager} detected`;\n\tlines.push(` Git hooks: ${hookLabel}`);\n\n\tif (env.monorepo) {\n\t\tlines.push(\n\t\t\t` Monorepo: ${env.monorepoType === \"pnpm\" ? \"pnpm workspaces\" : \"npm/yarn workspaces\"}`,\n\t\t);\n\t} else {\n\t\tlines.push(\" Monorepo: no\");\n\t}\n\n\t// Next steps\n\tif (result.nextSteps.length > 0) {\n\t\tlines.push(\"\");\n\t\tlines.push(\" Next steps:\");\n\t\tfor (const [idx, step] of result.nextSteps.entries()) {\n\t\t\tlines.push(` ${idx + 1}. ${step}`);\n\t\t}\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Citty command definition\n// ---------------------------------------------------------------------------\n\n/**\n * Citty command definition for `forge-ts init` (bare — full project setup).\n *\n * Detects the project environment, writes default configuration files,\n * validates tsconfig/package.json, and reports a summary.\n *\n * @example\n * ```typescript\n * import { initProjectCommand } from \"@forge-ts/cli/commands/init-project\";\n * // Registered as the default handler for `forge-ts init`\n * ```\n * @public\n */\nexport const initProjectCommand = defineCommand({\n\tmeta: {\n\t\tname: \"init\",\n\t\tdescription: \"Full project setup for forge-ts\",\n\t},\n\targs: {\n\t\tcwd: {\n\t\t\ttype: \"string\",\n\t\t\tdescription: \"Project root directory\",\n\t\t},\n\t\tjson: {\n\t\t\ttype: \"boolean\",\n\t\t\tdescription: \"Output as LAFS JSON envelope\",\n\t\t\tdefault: false,\n\t\t},\n\t\thuman: {\n\t\t\ttype: \"boolean\",\n\t\t\tdescription: \"Output as formatted text\",\n\t\t\tdefault: false,\n\t\t},\n\t\tquiet: {\n\t\t\ttype: \"boolean\",\n\t\t\tdescription: \"Suppress non-essential output\",\n\t\t\tdefault: false,\n\t\t},\n\t\tmvi: {\n\t\t\ttype: \"string\",\n\t\t\tdescription: \"MVI verbosity level: minimal, standard, full\",\n\t\t},\n\t},\n\tasync run({ args }) {\n\t\tconst output = await runInitProject({\n\t\t\tcwd: args.cwd,\n\t\t\tmvi: args.mvi,\n\t\t});\n\n\t\tconst flags: OutputFlags = {\n\t\t\tjson: args.json,\n\t\t\thuman: args.human,\n\t\t\tquiet: args.quiet,\n\t\t\tmvi: args.mvi,\n\t\t};\n\n\t\temitResult(output, flags, (data, cmd) => {\n\t\t\tif (!cmd.success) {\n\t\t\t\tconst logger = createLogger();\n\t\t\t\tconst msg = cmd.errors?.[0]?.message ?? \"Init failed\";\n\t\t\t\tlogger.error(msg);\n\t\t\t\treturn \"\";\n\t\t\t}\n\t\t\treturn formatInitProjectHuman(data);\n\t\t});\n\n\t\tprocess.exit(resolveExitCode(output));\n\t},\n});\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/output.ts
|
|
4
|
+
import { randomUUID } from "crypto";
|
|
5
|
+
import {
|
|
6
|
+
createEnvelope,
|
|
7
|
+
resolveFlags
|
|
8
|
+
} from "@cleocode/lafs-protocol";
|
|
9
|
+
function emitResult(output, flags, humanFormatter) {
|
|
10
|
+
const flagInput = {
|
|
11
|
+
json: flags.json,
|
|
12
|
+
human: flags.human,
|
|
13
|
+
quiet: flags.quiet,
|
|
14
|
+
mvi: flags.mvi,
|
|
15
|
+
// LAFS 1.8.0: TTY detection drives the default format.
|
|
16
|
+
// Terminals get human output, pipes/agents get JSON.
|
|
17
|
+
tty: process.stdout.isTTY ?? false
|
|
18
|
+
};
|
|
19
|
+
const resolved = resolveFlags(flagInput);
|
|
20
|
+
const format = resolved.format.format;
|
|
21
|
+
const quiet = resolved.format.quiet;
|
|
22
|
+
if (quiet) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const meta = {
|
|
26
|
+
operation: `forge-ts.${output.operation}`,
|
|
27
|
+
requestId: randomUUID(),
|
|
28
|
+
transport: "cli",
|
|
29
|
+
mvi: flags.mvi ?? "full"
|
|
30
|
+
};
|
|
31
|
+
const resultData = output.data;
|
|
32
|
+
if (output.warnings && output.warnings.length > 0) {
|
|
33
|
+
resultData._warnings = output.warnings.map((w) => ({
|
|
34
|
+
code: w.code,
|
|
35
|
+
message: w.message
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
const envelope = output.success ? createEnvelope({
|
|
39
|
+
success: true,
|
|
40
|
+
result: resultData,
|
|
41
|
+
meta
|
|
42
|
+
}) : createEnvelope({
|
|
43
|
+
success: false,
|
|
44
|
+
result: resultData,
|
|
45
|
+
error: {
|
|
46
|
+
code: output.errors?.[0]?.code ?? "FORGE_CHECK_FAILED",
|
|
47
|
+
message: output.errors?.[0]?.message ?? "Check failed \u2014 see result for actionable fixes",
|
|
48
|
+
category: "VALIDATION",
|
|
49
|
+
retryable: true,
|
|
50
|
+
retryAfterMs: null
|
|
51
|
+
},
|
|
52
|
+
meta
|
|
53
|
+
});
|
|
54
|
+
if (format === "json") {
|
|
55
|
+
process.stdout.write(`${JSON.stringify(envelope, null, 2)}
|
|
56
|
+
`);
|
|
57
|
+
} else {
|
|
58
|
+
const formatted = humanFormatter(output.data, output);
|
|
59
|
+
if (formatted) {
|
|
60
|
+
console.log(formatted);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function resolveExitCode(output) {
|
|
65
|
+
if (output.success) return 0;
|
|
66
|
+
return 1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export {
|
|
70
|
+
emitResult,
|
|
71
|
+
resolveExitCode
|
|
72
|
+
};
|
|
73
|
+
//# sourceMappingURL=chunk-ZFFY4AQX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/output.ts"],"sourcesContent":["/**\n * Central output layer for forge-ts CLI.\n *\n * Wraps all command results in LAFS envelopes for agent-first output, while\n * preserving human-readable formatting for TTY consumers.\n *\n * @packageDocumentation\n * @internal\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport {\n\tcreateEnvelope,\n\ttype MVILevel,\n\tresolveFlags,\n\ttype UnifiedFlagInput,\n} from \"@cleocode/lafs-protocol\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** Typed result from a forge-ts command. */\nexport interface CommandOutput<T> {\n\t/** Name of the command that produced this output (e.g., \"check\", \"build\"). */\n\toperation: string;\n\t/** Whether the command completed successfully. */\n\tsuccess: boolean;\n\t/** Strongly-typed command-specific result payload. */\n\tdata: T;\n\t/** Structured errors produced by the command, if any. */\n\terrors?: ForgeCliError[];\n\t/** Structured warnings produced by the command, if any. */\n\twarnings?: ForgeCliWarning[];\n\t/** Wall-clock duration of the command in milliseconds. */\n\tduration?: number;\n}\n\n/** Structured error for CLI commands. */\nexport interface ForgeCliError {\n\t/** Machine-readable error code (e.g., \"E004\"). */\n\tcode: string;\n\t/** Human-readable error description. */\n\tmessage: string;\n\t/** Absolute path to the source file containing the error, if applicable. */\n\tfilePath?: string;\n\t/** 1-based line number of the error, if applicable. */\n\tline?: number;\n\t/** 0-based column number of the error, if applicable. */\n\tcolumn?: number;\n}\n\n/** Structured warning for CLI commands. */\nexport interface ForgeCliWarning {\n\t/** Machine-readable warning code. */\n\tcode: string;\n\t/** Human-readable warning description. */\n\tmessage: string;\n\t/** Absolute path to the source file containing the warning, if applicable. */\n\tfilePath?: string;\n\t/** 1-based line number of the warning, if applicable. */\n\tline?: number;\n\t/** 0-based column number of the warning, if applicable. */\n\tcolumn?: number;\n}\n\n/** Output format flags passed through from citty args. */\nexport interface OutputFlags {\n\t/** Emit output as a LAFS JSON envelope instead of human-readable text. */\n\tjson?: boolean;\n\t/** Emit output as formatted human-readable text. */\n\thuman?: boolean;\n\t/** Suppress all output regardless of format. */\n\tquiet?: boolean;\n\t/** MVI verbosity level: \"minimal\", \"standard\", or \"full\". */\n\tmvi?: string;\n}\n\n// ---------------------------------------------------------------------------\n// emitResult\n// ---------------------------------------------------------------------------\n\n/**\n * Wraps a command result in a LAFS envelope and emits it.\n *\n * Output format is determined by LAFS flag resolution:\n * - TTY terminals default to human-readable output.\n * - Non-TTY (piped, CI, agents) defaults to JSON.\n * - Explicit `--json` or `--human` flags always take precedence.\n *\n * On failure, the full result is included alongside the error so agents\n * get actionable data (e.g., suggestedFix) in a single response.\n *\n * @param output - Typed result from the command.\n * @param flags - Output format flags from citty args.\n * @param humanFormatter - Produces a human-readable string for TTY consumers.\n * @example\n * ```typescript\n * import { emitResult } from \"@forge-ts/cli/output\";\n * emitResult(output, { human: true }, (data) => `Done: ${data.summary.duration}ms`);\n * ```\n * @internal\n */\nexport function emitResult<T>(\n\toutput: CommandOutput<T>,\n\tflags: OutputFlags,\n\thumanFormatter: (data: T, output: CommandOutput<T>) => string,\n): void {\n\tconst flagInput: UnifiedFlagInput = {\n\t\tjson: flags.json,\n\t\thuman: flags.human,\n\t\tquiet: flags.quiet,\n\t\tmvi: flags.mvi,\n\t\t// LAFS 1.8.0: TTY detection drives the default format.\n\t\t// Terminals get human output, pipes/agents get JSON.\n\t\ttty: process.stdout.isTTY ?? false,\n\t};\n\n\tconst resolved = resolveFlags(flagInput);\n\tconst format = resolved.format.format;\n\tconst quiet = resolved.format.quiet;\n\n\tif (quiet) {\n\t\treturn;\n\t}\n\n\tconst meta = {\n\t\toperation: `forge-ts.${output.operation}`,\n\t\trequestId: randomUUID(),\n\t\ttransport: \"cli\" as const,\n\t\tmvi: (flags.mvi as MVILevel) ?? \"full\",\n\t};\n\n\t// Include warnings in the result so agents see them in the JSON envelope.\n\t// Config warnings (unknown keys, invalid rules) go to stderr for TTY\n\t// consumers but agents in non-TTY contexts only see the JSON envelope.\n\tconst resultData = output.data as Record<string, unknown>;\n\tif (output.warnings && output.warnings.length > 0) {\n\t\tresultData._warnings = output.warnings.map((w) => ({\n\t\t\tcode: w.code,\n\t\t\tmessage: w.message,\n\t\t}));\n\t}\n\n\t// LAFS 1.8.0: result is included on error envelopes so agents get\n\t// actionable data (byFile, suggestedFix) alongside error metadata.\n\tconst envelope = output.success\n\t\t? createEnvelope({\n\t\t\t\tsuccess: true,\n\t\t\t\tresult: resultData,\n\t\t\t\tmeta,\n\t\t\t})\n\t\t: createEnvelope({\n\t\t\t\tsuccess: false,\n\t\t\t\tresult: resultData,\n\t\t\t\terror: {\n\t\t\t\t\tcode: output.errors?.[0]?.code ?? \"FORGE_CHECK_FAILED\",\n\t\t\t\t\tmessage: output.errors?.[0]?.message ?? \"Check failed — see result for actionable fixes\",\n\t\t\t\t\tcategory: \"VALIDATION\",\n\t\t\t\t\tretryable: true,\n\t\t\t\t\tretryAfterMs: null,\n\t\t\t\t},\n\t\t\t\tmeta,\n\t\t\t});\n\n\tif (format === \"json\") {\n\t\tprocess.stdout.write(`${JSON.stringify(envelope, null, 2)}\\n`);\n\t} else {\n\t\tconst formatted = humanFormatter(output.data, output);\n\t\tif (formatted) {\n\t\t\tconsole.log(formatted);\n\t\t}\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// resolveExitCode\n// ---------------------------------------------------------------------------\n\n/**\n * Returns the LAFS-compliant exit code for a command output.\n *\n * @param output - Typed result from the command.\n * @returns `0` on success, `1` on validation/check failure.\n * @internal\n */\nexport function resolveExitCode(output: CommandOutput<unknown>): number {\n\tif (output.success) return 0;\n\treturn 1;\n}\n"],"mappings":";;;AAUA,SAAS,kBAAkB;AAC3B;AAAA,EACC;AAAA,EAEA;AAAA,OAEM;AAuFA,SAAS,WACf,QACA,OACA,gBACO;AACP,QAAM,YAA8B;AAAA,IACnC,MAAM,MAAM;AAAA,IACZ,OAAO,MAAM;AAAA,IACb,OAAO,MAAM;AAAA,IACb,KAAK,MAAM;AAAA;AAAA;AAAA,IAGX,KAAK,QAAQ,OAAO,SAAS;AAAA,EAC9B;AAEA,QAAM,WAAW,aAAa,SAAS;AACvC,QAAM,SAAS,SAAS,OAAO;AAC/B,QAAM,QAAQ,SAAS,OAAO;AAE9B,MAAI,OAAO;AACV;AAAA,EACD;AAEA,QAAM,OAAO;AAAA,IACZ,WAAW,YAAY,OAAO,SAAS;AAAA,IACvC,WAAW,WAAW;AAAA,IACtB,WAAW;AAAA,IACX,KAAM,MAAM,OAAoB;AAAA,EACjC;AAKA,QAAM,aAAa,OAAO;AAC1B,MAAI,OAAO,YAAY,OAAO,SAAS,SAAS,GAAG;AAClD,eAAW,YAAY,OAAO,SAAS,IAAI,CAAC,OAAO;AAAA,MAClD,MAAM,EAAE;AAAA,MACR,SAAS,EAAE;AAAA,IACZ,EAAE;AAAA,EACH;AAIA,QAAM,WAAW,OAAO,UACrB,eAAe;AAAA,IACf,SAAS;AAAA,IACT,QAAQ;AAAA,IACR;AAAA,EACD,CAAC,IACA,eAAe;AAAA,IACf,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,OAAO;AAAA,MACN,MAAM,OAAO,SAAS,CAAC,GAAG,QAAQ;AAAA,MAClC,SAAS,OAAO,SAAS,CAAC,GAAG,WAAW;AAAA,MACxC,UAAU;AAAA,MACV,WAAW;AAAA,MACX,cAAc;AAAA,IACf;AAAA,IACA;AAAA,EACD,CAAC;AAEH,MAAI,WAAW,QAAQ;AACtB,YAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAAA,CAAI;AAAA,EAC9D,OAAO;AACN,UAAM,YAAY,eAAe,OAAO,MAAM,MAAM;AACpD,QAAI,WAAW;AACd,cAAQ,IAAI,SAAS;AAAA,IACtB;AAAA,EACD;AACD;AAaO,SAAS,gBAAgB,QAAwC;AACvE,MAAI,OAAO,QAAS,QAAO;AAC3B,SAAO;AACR;","names":[]}
|