@synergenius/flow-weaver 0.23.5 → 0.24.1
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/agent/index.d.ts +2 -1
- package/dist/agent/index.js +2 -0
- package/dist/agent/providers/anthropic.js +14 -1
- package/dist/agent/providers/claude-cli.d.ts +1 -1
- package/dist/agent/providers/claude-cli.js +4 -1
- package/dist/agent/providers/openai-compat.d.ts +1 -1
- package/dist/agent/providers/openai-compat.js +2 -1
- package/dist/agent/providers/platform.d.ts +1 -1
- package/dist/agent/providers/platform.js +3 -1
- package/dist/agent/types.d.ts +21 -2
- package/dist/agent/types.js +6 -1
- package/dist/api/generate-in-place.d.ts +0 -9
- package/dist/api/generate-in-place.js +6 -54
- package/dist/api/generate.d.ts +4 -5
- package/dist/api/generate.js +6 -26
- package/dist/cli/commands/compile.d.ts +0 -5
- package/dist/cli/commands/compile.js +36 -8
- package/dist/cli/commands/context.js +4 -6
- package/dist/cli/commands/create.js +6 -14
- package/dist/cli/commands/describe.js +6 -10
- package/dist/cli/commands/diagram.js +18 -25
- package/dist/cli/commands/diff.js +7 -14
- package/dist/cli/commands/docs.js +3 -6
- package/dist/cli/commands/doctor.js +1 -1
- package/dist/cli/commands/export.js +1 -1
- package/dist/cli/commands/grammar.js +3 -4
- package/dist/cli/commands/implement.js +8 -13
- package/dist/cli/commands/market.js +4 -8
- package/dist/cli/commands/migrate.js +2 -1
- package/dist/cli/commands/modify.js +2 -1
- package/dist/cli/commands/openapi.js +2 -1
- package/dist/cli/commands/pattern.js +3 -2
- package/dist/cli/commands/strip.js +3 -6
- package/dist/cli/commands/validate.js +6 -1
- package/dist/cli/flow-weaver.mjs +781 -791
- package/dist/cli/index.js +10 -12
- package/dist/cli/postinstall.d.ts +16 -0
- package/dist/cli/postinstall.js +119 -0
- package/dist/cli/utils/parse-int-strict.d.ts +7 -0
- package/dist/cli/utils/parse-int-strict.js +17 -0
- package/dist/cli/utils/safe-write.d.ts +18 -0
- package/dist/cli/utils/safe-write.js +54 -0
- package/dist/generated-version.d.ts +1 -1
- package/dist/generated-version.js +1 -1
- package/dist/mcp/tools-debug.js +2 -2
- package/docs/reference/cli-reference.md +0 -1
- package/docs/reference/compilation.md +2 -10
- package/package.json +4 -2
- package/scripts/postinstall.cjs +86 -0
|
@@ -6,38 +6,31 @@ import * as fs from 'fs';
|
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import { fileToSVG, fileToHTML, fileToASCII } from '../../diagram/index.js';
|
|
8
8
|
import { logger } from '../utils/logger.js';
|
|
9
|
-
import {
|
|
9
|
+
import { safeWriteFile } from '../utils/safe-write.js';
|
|
10
10
|
const ASCII_FORMATS = new Set(['ascii', 'ascii-compact', 'text']);
|
|
11
11
|
export async function diagramCommand(input, options = {}) {
|
|
12
12
|
const { output, format = 'svg', ...diagramOptions } = options;
|
|
13
13
|
const filePath = path.resolve(input);
|
|
14
14
|
if (!fs.existsSync(filePath)) {
|
|
15
|
-
|
|
16
|
-
process.exit(1);
|
|
15
|
+
throw new Error(`File not found: ${filePath}`);
|
|
17
16
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
result = fileToASCII(filePath, { ...diagramOptions, format });
|
|
22
|
-
}
|
|
23
|
-
else if (format === 'html') {
|
|
24
|
-
result = fileToHTML(filePath, diagramOptions);
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
result = fileToSVG(filePath, diagramOptions);
|
|
28
|
-
}
|
|
29
|
-
if (output) {
|
|
30
|
-
const outputPath = path.resolve(output);
|
|
31
|
-
fs.writeFileSync(outputPath, result, 'utf-8');
|
|
32
|
-
logger.success(`Diagram written to ${outputPath}`);
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
process.stdout.write(result);
|
|
36
|
-
}
|
|
17
|
+
let result;
|
|
18
|
+
if (ASCII_FORMATS.has(format)) {
|
|
19
|
+
result = fileToASCII(filePath, { ...diagramOptions, format });
|
|
37
20
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
21
|
+
else if (format === 'html') {
|
|
22
|
+
result = fileToHTML(filePath, diagramOptions);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
result = fileToSVG(filePath, diagramOptions);
|
|
26
|
+
}
|
|
27
|
+
if (output) {
|
|
28
|
+
const outputPath = path.resolve(output);
|
|
29
|
+
safeWriteFile(outputPath, result);
|
|
30
|
+
logger.success(`Diagram written to ${outputPath}`);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
process.stdout.write(result);
|
|
41
34
|
}
|
|
42
35
|
}
|
|
43
36
|
//# sourceMappingURL=diagram.js.map
|
|
@@ -13,12 +13,10 @@ export async function diffCommand(file1, file2, options = {}) {
|
|
|
13
13
|
const filePath2 = path.resolve(file2);
|
|
14
14
|
// Validate files exist
|
|
15
15
|
if (!fs.existsSync(filePath1)) {
|
|
16
|
-
|
|
17
|
-
process.exit(1);
|
|
16
|
+
throw new Error(`File not found: ${filePath1}`);
|
|
18
17
|
}
|
|
19
18
|
if (!fs.existsSync(filePath2)) {
|
|
20
|
-
|
|
21
|
-
process.exit(1);
|
|
19
|
+
throw new Error(`File not found: ${filePath2}`);
|
|
22
20
|
}
|
|
23
21
|
try {
|
|
24
22
|
// Parse both workflows
|
|
@@ -27,14 +25,10 @@ export async function diffCommand(file1, file2, options = {}) {
|
|
|
27
25
|
parseWorkflow(filePath2, { workflowName }),
|
|
28
26
|
]);
|
|
29
27
|
if (result1.errors.length > 0) {
|
|
30
|
-
|
|
31
|
-
result1.errors.forEach((err) => logger.error(` ${err}`));
|
|
32
|
-
process.exit(1);
|
|
28
|
+
throw new Error(`Parse errors in ${file1}:\n${result1.errors.map((err) => ` ${err}`).join('\n')}`);
|
|
33
29
|
}
|
|
34
30
|
if (result2.errors.length > 0) {
|
|
35
|
-
|
|
36
|
-
result2.errors.forEach((err) => logger.error(` ${err}`));
|
|
37
|
-
process.exit(1);
|
|
31
|
+
throw new Error(`Parse errors in ${file2}:\n${result2.errors.map((err) => ` ${err}`).join('\n')}`);
|
|
38
32
|
}
|
|
39
33
|
// Compare workflows
|
|
40
34
|
const diff = WorkflowDiffer.compare(result1.ast, result2.ast);
|
|
@@ -50,15 +44,14 @@ export async function diffCommand(file1, file2, options = {}) {
|
|
|
50
44
|
else {
|
|
51
45
|
// eslint-disable-next-line no-console
|
|
52
46
|
console.log(formatDiff(diff, format));
|
|
53
|
-
//
|
|
47
|
+
// Throw if there are differences (useful for CI)
|
|
54
48
|
if (!exitZero) {
|
|
55
|
-
|
|
49
|
+
throw new Error('Workflows have differences');
|
|
56
50
|
}
|
|
57
51
|
}
|
|
58
52
|
}
|
|
59
53
|
catch (error) {
|
|
60
|
-
|
|
61
|
-
process.exit(1);
|
|
54
|
+
throw new Error(`Failed to diff workflows: ${getErrorMessage(error)}`);
|
|
62
55
|
}
|
|
63
56
|
}
|
|
64
57
|
//# sourceMappingURL=diff.js.map
|
|
@@ -3,8 +3,7 @@ import { logger } from '../utils/logger.js';
|
|
|
3
3
|
export async function docsListCommand(options) {
|
|
4
4
|
const topics = listTopics();
|
|
5
5
|
if (topics.length === 0) {
|
|
6
|
-
|
|
7
|
-
process.exit(1);
|
|
6
|
+
throw new Error('No documentation topics found.');
|
|
8
7
|
}
|
|
9
8
|
if (options.json) {
|
|
10
9
|
process.stdout.write(JSON.stringify({ topics }, null, 2) + '\n');
|
|
@@ -25,16 +24,14 @@ export async function docsReadCommand(topic, options) {
|
|
|
25
24
|
if (options.json) {
|
|
26
25
|
const structured = readTopicStructured(topic);
|
|
27
26
|
if (!structured) {
|
|
28
|
-
|
|
29
|
-
process.exit(1);
|
|
27
|
+
throw new Error(`Unknown topic: "${topic}". Run "fw docs" to see available topics.`);
|
|
30
28
|
}
|
|
31
29
|
process.stdout.write(JSON.stringify(structured, null, 2) + '\n');
|
|
32
30
|
return;
|
|
33
31
|
}
|
|
34
32
|
const doc = readTopic(topic, options.compact);
|
|
35
33
|
if (!doc) {
|
|
36
|
-
|
|
37
|
-
process.exit(1);
|
|
34
|
+
throw new Error(`Unknown topic: "${topic}". Run "fw docs" to see available topics.`);
|
|
38
35
|
}
|
|
39
36
|
process.stdout.write(doc.content + '\n');
|
|
40
37
|
}
|
|
@@ -66,7 +66,7 @@ export async function exportCommand(input, options) {
|
|
|
66
66
|
input,
|
|
67
67
|
output: options.output,
|
|
68
68
|
workflow: options.workflow,
|
|
69
|
-
production: options.production ??
|
|
69
|
+
production: options.production ?? false,
|
|
70
70
|
bundle: options.bundle,
|
|
71
71
|
dryRun: isDryRun,
|
|
72
72
|
multi: isMulti,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Grammar command - output JSDoc grammar as HTML railroad diagrams or EBNF text
|
|
3
3
|
*/
|
|
4
|
-
import * as fs from 'fs';
|
|
5
4
|
import { generateGrammarDiagrams, getAllGrammars, serializedToEBNF, } from '../../chevrotain-parser/grammar-diagrams.js';
|
|
6
5
|
import { logger } from '../utils/logger.js';
|
|
7
6
|
import { getErrorMessage } from '../../utils/error-utils.js';
|
|
7
|
+
import { safeWriteFile } from '../utils/safe-write.js';
|
|
8
8
|
export async function grammarCommand(options = {}) {
|
|
9
9
|
// Default to ebnf on TTY (readable in terminal), html when writing to file
|
|
10
10
|
const defaultFormat = options.output ? 'html' : (process.stdout.isTTY ? 'ebnf' : 'html');
|
|
@@ -26,7 +26,7 @@ export async function grammarCommand(options = {}) {
|
|
|
26
26
|
content = generateGrammarDiagrams();
|
|
27
27
|
}
|
|
28
28
|
if (output) {
|
|
29
|
-
|
|
29
|
+
safeWriteFile(output, content);
|
|
30
30
|
logger.success(`Grammar written to ${output}`);
|
|
31
31
|
}
|
|
32
32
|
else {
|
|
@@ -34,8 +34,7 @@ export async function grammarCommand(options = {}) {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
catch (error) {
|
|
37
|
-
|
|
38
|
-
process.exit(1);
|
|
37
|
+
throw new Error(`Grammar generation failed: ${getErrorMessage(error)}`);
|
|
39
38
|
}
|
|
40
39
|
}
|
|
41
40
|
//# sourceMappingURL=grammar.js.map
|
|
@@ -7,6 +7,7 @@ import { parseWorkflow } from '../../api/index.js';
|
|
|
7
7
|
import { generateFunctionSignature } from '../../annotation-generator.js';
|
|
8
8
|
import { logger } from '../utils/logger.js';
|
|
9
9
|
import { getErrorMessage } from '../../utils/error-utils.js';
|
|
10
|
+
import { safeWriteFile } from '../utils/safe-write.js';
|
|
10
11
|
/**
|
|
11
12
|
* Find a `declare function <name>(...): ...;` declaration in source text,
|
|
12
13
|
* handling multiline signatures. Returns the full matched text and its
|
|
@@ -37,14 +38,11 @@ export async function implementCommand(input, nodeName, options = {}) {
|
|
|
37
38
|
try {
|
|
38
39
|
const filePath = path.resolve(input);
|
|
39
40
|
if (!fs.existsSync(filePath)) {
|
|
40
|
-
|
|
41
|
-
process.exit(1);
|
|
41
|
+
throw new Error(`File not found: ${input}`);
|
|
42
42
|
}
|
|
43
43
|
const parseResult = await parseWorkflow(filePath, { workflowName });
|
|
44
44
|
if (parseResult.errors.length > 0) {
|
|
45
|
-
|
|
46
|
-
parseResult.errors.forEach((e) => logger.error(` ${e}`));
|
|
47
|
-
process.exit(1);
|
|
45
|
+
throw new Error(`Parse errors:\n${parseResult.errors.map((e) => ` ${e}`).join('\n')}`);
|
|
48
46
|
}
|
|
49
47
|
const ast = parseResult.ast;
|
|
50
48
|
// Find the stub node type
|
|
@@ -59,18 +57,16 @@ export async function implementCommand(input, nodeName, options = {}) {
|
|
|
59
57
|
.filter((nt) => nt.variant === 'STUB')
|
|
60
58
|
.map((nt) => nt.functionName);
|
|
61
59
|
if (available.length === 0) {
|
|
62
|
-
|
|
60
|
+
throw new Error('No stub nodes found in this workflow.');
|
|
63
61
|
}
|
|
64
62
|
else {
|
|
65
|
-
|
|
63
|
+
throw new Error(`Stub node "${nodeName}" not found. Available stubs: ${available.join(', ')}`);
|
|
66
64
|
}
|
|
67
|
-
process.exit(1);
|
|
68
65
|
}
|
|
69
66
|
const source = fs.readFileSync(filePath, 'utf8');
|
|
70
67
|
const found = findDeclareFunction(source, stubNodeType.functionName);
|
|
71
68
|
if (!found) {
|
|
72
|
-
|
|
73
|
-
process.exit(1);
|
|
69
|
+
throw new Error(`Could not find "declare function ${stubNodeType.functionName}" in source file.`);
|
|
74
70
|
}
|
|
75
71
|
// Generate the real function signature
|
|
76
72
|
const implementedType = { ...stubNodeType, variant: 'FUNCTION' };
|
|
@@ -84,13 +80,12 @@ export async function implementCommand(input, nodeName, options = {}) {
|
|
|
84
80
|
}
|
|
85
81
|
else {
|
|
86
82
|
const updated = source.replace(found.match, replacement);
|
|
87
|
-
|
|
83
|
+
safeWriteFile(filePath, updated);
|
|
88
84
|
logger.success(`Implemented ${stubNodeType.functionName} in ${path.basename(filePath)}`);
|
|
89
85
|
}
|
|
90
86
|
}
|
|
91
87
|
catch (error) {
|
|
92
|
-
|
|
93
|
-
process.exit(1);
|
|
88
|
+
throw new Error(`Implement failed: ${getErrorMessage(error)}`);
|
|
94
89
|
}
|
|
95
90
|
}
|
|
96
91
|
//# sourceMappingURL=implement.js.map
|
|
@@ -24,13 +24,11 @@ export async function marketInitCommand(name, options = {}) {
|
|
|
24
24
|
if (stat.isDirectory()) {
|
|
25
25
|
const contents = fs.readdirSync(targetDir);
|
|
26
26
|
if (contents.length > 0) {
|
|
27
|
-
|
|
28
|
-
process.exit(1);
|
|
27
|
+
throw new Error(`Directory "${name}" already exists and is not empty`);
|
|
29
28
|
}
|
|
30
29
|
}
|
|
31
30
|
else {
|
|
32
|
-
|
|
33
|
-
process.exit(1);
|
|
31
|
+
throw new Error(`"${name}" already exists and is not a directory`);
|
|
34
32
|
}
|
|
35
33
|
}
|
|
36
34
|
logger.section('Creating Marketplace Package');
|
|
@@ -211,8 +209,7 @@ export async function marketPackCommand(directory, options = {}) {
|
|
|
211
209
|
}
|
|
212
210
|
if (!validation.valid) {
|
|
213
211
|
logger.newline();
|
|
214
|
-
|
|
215
|
-
process.exit(1);
|
|
212
|
+
throw new Error('Package validation failed. Fix errors above before publishing.');
|
|
216
213
|
}
|
|
217
214
|
// 3. Write manifest
|
|
218
215
|
const outPath = writeManifest(dir, manifest);
|
|
@@ -253,8 +250,7 @@ export async function marketPublishCommand(directory, options = {}) {
|
|
|
253
250
|
}
|
|
254
251
|
}
|
|
255
252
|
catch (err) {
|
|
256
|
-
|
|
257
|
-
process.exit(1);
|
|
253
|
+
throw new Error(`npm publish failed: ${getErrorMessage(err)}`);
|
|
258
254
|
}
|
|
259
255
|
}
|
|
260
256
|
/**
|
|
@@ -13,6 +13,7 @@ import { generateInPlace } from '../../api/generate-in-place.js';
|
|
|
13
13
|
import { WorkflowDiffer, formatDiff } from '../../diff/index.js';
|
|
14
14
|
import { applyMigrations, getRegisteredMigrations } from '../../migration/registry.js';
|
|
15
15
|
import { logger } from '../utils/logger.js';
|
|
16
|
+
import { safeWriteFile } from '../utils/safe-write.js';
|
|
16
17
|
import { getErrorMessage } from '../../utils/error-utils.js';
|
|
17
18
|
export async function migrateCommand(globPattern, options = {}) {
|
|
18
19
|
const { dryRun = false, diff = false } = options;
|
|
@@ -69,7 +70,7 @@ export async function migrateCommand(globPattern, options = {}) {
|
|
|
69
70
|
migratedCount++;
|
|
70
71
|
continue;
|
|
71
72
|
}
|
|
72
|
-
|
|
73
|
+
safeWriteFile(filePath, genResult.code);
|
|
73
74
|
logger.success(` ${file}: migrated`);
|
|
74
75
|
migratedCount++;
|
|
75
76
|
}
|
|
@@ -4,6 +4,7 @@ import { parseWorkflow } from '../../api/index.js';
|
|
|
4
4
|
import { generateInPlace } from '../../api/generate-in-place.js';
|
|
5
5
|
import { applyModifyOperation, validateModifyParams } from '../../api/modify-operation.js';
|
|
6
6
|
import { logger } from '../utils/logger.js';
|
|
7
|
+
import { safeWriteFile } from '../utils/safe-write.js';
|
|
7
8
|
async function readParseModifyWrite(file, operation, params) {
|
|
8
9
|
const validation = validateModifyParams(operation, params);
|
|
9
10
|
if (!validation.success) {
|
|
@@ -17,7 +18,7 @@ async function readParseModifyWrite(file, operation, params) {
|
|
|
17
18
|
}
|
|
18
19
|
const { ast: modifiedAST, warnings } = applyModifyOperation(parseResult.ast, operation, params);
|
|
19
20
|
const result = generateInPlace(source, modifiedAST);
|
|
20
|
-
|
|
21
|
+
safeWriteFile(filePath, result.code);
|
|
21
22
|
for (const w of warnings) {
|
|
22
23
|
logger.warn(w);
|
|
23
24
|
}
|
|
@@ -6,6 +6,7 @@ import * as fs from 'fs';
|
|
|
6
6
|
import { WorkflowRegistry } from '../../server/workflow-registry.js';
|
|
7
7
|
import { generateOpenAPIJson, generateOpenAPIYaml } from '../../deployment/openapi/generator.js';
|
|
8
8
|
import { logger } from '../utils/logger.js';
|
|
9
|
+
import { safeWriteFile } from '../utils/safe-write.js';
|
|
9
10
|
/**
|
|
10
11
|
* Generate OpenAPI specification from workflows in a directory.
|
|
11
12
|
*
|
|
@@ -59,7 +60,7 @@ export async function openapiCommand(dir, options) {
|
|
|
59
60
|
// Output
|
|
60
61
|
if (options.output) {
|
|
61
62
|
const outputPath = path.resolve(options.output);
|
|
62
|
-
|
|
63
|
+
safeWriteFile(outputPath, spec);
|
|
63
64
|
logger.success(`OpenAPI specification written to ${outputPath}`);
|
|
64
65
|
}
|
|
65
66
|
else {
|
|
@@ -10,6 +10,7 @@ import * as path from 'path';
|
|
|
10
10
|
import { glob } from 'glob';
|
|
11
11
|
import { AnnotationParser } from '../../parser.js';
|
|
12
12
|
import { logger } from '../utils/logger.js';
|
|
13
|
+
import { safeWriteFile } from '../utils/safe-write.js';
|
|
13
14
|
import { listPatterns, applyPattern, extractPattern } from '../../api/patterns.js';
|
|
14
15
|
const parser = new AnnotationParser();
|
|
15
16
|
/**
|
|
@@ -132,7 +133,7 @@ export async function patternApplyCommand(patternFile, targetFile, options) {
|
|
|
132
133
|
return;
|
|
133
134
|
}
|
|
134
135
|
// Write modified content
|
|
135
|
-
|
|
136
|
+
safeWriteFile(targetFile, result.modifiedContent);
|
|
136
137
|
logger.success(`Applied pattern "${pattern.name}" to ${targetFile}`);
|
|
137
138
|
if (result.nodeTypesAdded.length > 0) {
|
|
138
139
|
logger.info(`Added node types: ${result.nodeTypesAdded.join(', ')}`);
|
|
@@ -176,7 +177,7 @@ export async function patternExtractCommand(sourceFile, options) {
|
|
|
176
177
|
return;
|
|
177
178
|
}
|
|
178
179
|
// Write to output file
|
|
179
|
-
|
|
180
|
+
safeWriteFile(options.output, result.patternCode);
|
|
180
181
|
logger.success(`Extracted pattern "${result.patternName}" to ${options.output}`);
|
|
181
182
|
logger.info(`Included nodes: ${result.nodes.join(', ')}`);
|
|
182
183
|
logger.info(`Input ports: ${result.inputPorts.join(', ') || 'none'}`);
|
|
@@ -3,6 +3,7 @@ import * as path from 'path';
|
|
|
3
3
|
import { glob } from 'glob';
|
|
4
4
|
import { hasInPlaceMarkers, stripGeneratedSections } from '../../api/generate-in-place.js';
|
|
5
5
|
import { logger } from '../utils/logger.js';
|
|
6
|
+
import { safeWriteFile } from '../utils/safe-write.js';
|
|
6
7
|
export async function stripCommand(input, options = {}) {
|
|
7
8
|
const { output, dryRun = false, verbose = false } = options;
|
|
8
9
|
// If input is a directory, expand to all .ts files recursively
|
|
@@ -25,8 +26,7 @@ export async function stripCommand(input, options = {}) {
|
|
|
25
26
|
}
|
|
26
27
|
});
|
|
27
28
|
if (files.length === 0) {
|
|
28
|
-
|
|
29
|
-
process.exit(1);
|
|
29
|
+
throw new Error(`No files found matching pattern: ${input}`);
|
|
30
30
|
}
|
|
31
31
|
const t = logger.timer();
|
|
32
32
|
let stripped = 0;
|
|
@@ -49,10 +49,7 @@ export async function stripCommand(input, options = {}) {
|
|
|
49
49
|
const outPath = output
|
|
50
50
|
? path.join(path.resolve(output), path.basename(filePath))
|
|
51
51
|
: filePath;
|
|
52
|
-
|
|
53
|
-
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
54
|
-
}
|
|
55
|
-
fs.writeFileSync(outPath, result);
|
|
52
|
+
safeWriteFile(outPath, result);
|
|
56
53
|
stripped++;
|
|
57
54
|
if (verbose) {
|
|
58
55
|
logger.success(`Stripped: ${path.relative(process.cwd(), outPath)}`);
|
|
@@ -247,7 +247,12 @@ export async function validateCommand(input, options = {}) {
|
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
if (totalErrors > 0) {
|
|
250
|
-
|
|
250
|
+
if (json) {
|
|
251
|
+
// JSON output already emitted above — just signal failure via exit code
|
|
252
|
+
process.exitCode = 1;
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
throw new Error(`Validation failed with ${totalErrors} error${totalErrors !== 1 ? 's' : ''}`);
|
|
251
256
|
}
|
|
252
257
|
}
|
|
253
258
|
catch (error) {
|