@synergenius/flow-weaver 0.23.4 → 0.24.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/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 +789 -797
- 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/generator/unified.js +8 -6
- 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
|
@@ -29,17 +29,8 @@ export interface InPlaceGenerateOptions {
|
|
|
29
29
|
* @default 'esm'
|
|
30
30
|
*/
|
|
31
31
|
moduleFormat?: TModuleFormat;
|
|
32
|
-
/**
|
|
33
|
-
* Force inline runtime even when @synergenius/flow-weaver package is installed.
|
|
34
|
-
* When false/undefined, the compiler auto-detects the package and uses
|
|
35
|
-
* external imports when available (smaller generated code, shared runtime).
|
|
36
|
-
* @default false
|
|
37
|
-
*/
|
|
38
|
-
inlineRuntime?: boolean;
|
|
39
32
|
/**
|
|
40
33
|
* Absolute path to the source file being compiled.
|
|
41
|
-
* Used to detect if @synergenius/flow-weaver is installed relative to the file.
|
|
42
|
-
* If not provided, falls back to process.cwd().
|
|
43
34
|
*/
|
|
44
35
|
sourceFile?: string;
|
|
45
36
|
/**
|
|
@@ -15,7 +15,6 @@ import { shouldWorkflowBeAsync } from '../generator/async-detection.js';
|
|
|
15
15
|
import { detectSugarPatterns, filterStaleMacros } from '../sugar-optimizer.js';
|
|
16
16
|
import * as ts from 'typescript';
|
|
17
17
|
import * as path from 'path';
|
|
18
|
-
import * as fs from 'fs';
|
|
19
18
|
// Marker constants
|
|
20
19
|
export const MARKERS = {
|
|
21
20
|
RUNTIME_START: '// @flow-weaver-runtime-start',
|
|
@@ -23,30 +22,6 @@ export const MARKERS = {
|
|
|
23
22
|
BODY_START: '// @flow-weaver-body-start',
|
|
24
23
|
BODY_END: '// @flow-weaver-body-end',
|
|
25
24
|
};
|
|
26
|
-
/**
|
|
27
|
-
* Check if `@synergenius/flow-weaver` is available as an npm package
|
|
28
|
-
* by walking up from the given directory looking for node_modules.
|
|
29
|
-
*/
|
|
30
|
-
function isFlowWeaverPackageInstalled(startDir) {
|
|
31
|
-
let dir = startDir;
|
|
32
|
-
const root = path.parse(dir).root;
|
|
33
|
-
while (dir !== root) {
|
|
34
|
-
const candidate = path.join(dir, 'node_modules', '@synergenius', 'flow-weaver');
|
|
35
|
-
try {
|
|
36
|
-
if (fs.existsSync(candidate)) {
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
// Permission error or similar — skip and keep walking
|
|
42
|
-
}
|
|
43
|
-
const parent = path.dirname(dir);
|
|
44
|
-
if (parent === dir)
|
|
45
|
-
break;
|
|
46
|
-
dir = parent;
|
|
47
|
-
}
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
25
|
/**
|
|
51
26
|
* Generate executable code in-place, preserving user code.
|
|
52
27
|
*
|
|
@@ -56,7 +31,7 @@ function isFlowWeaverPackageInstalled(startDir) {
|
|
|
56
31
|
* @returns The updated source code with generated sections
|
|
57
32
|
*/
|
|
58
33
|
export function generateInPlace(sourceCode, ast, options = {}) {
|
|
59
|
-
const { production = false, allWorkflows, moduleFormat = 'esm',
|
|
34
|
+
const { production = false, allWorkflows, moduleFormat = 'esm', sourceFile, skipParamReturns = false } = options;
|
|
60
35
|
let result = sourceCode;
|
|
61
36
|
let hasChanges = false;
|
|
62
37
|
// Step 1: Update JSDoc annotations for node type functions
|
|
@@ -90,15 +65,8 @@ export function generateInPlace(sourceCode, ast, options = {}) {
|
|
|
90
65
|
result = jsdocResult.code;
|
|
91
66
|
hasChanges = true;
|
|
92
67
|
}
|
|
93
|
-
// Step 3: Generate and insert/replace runtime section
|
|
94
|
-
|
|
95
|
-
let useExternalRuntime = false;
|
|
96
|
-
if (!inlineRuntime) {
|
|
97
|
-
const lookupDir = sourceFile ? path.dirname(sourceFile) : (ast.sourceFile ? path.dirname(ast.sourceFile) : process.cwd());
|
|
98
|
-
useExternalRuntime = isFlowWeaverPackageInstalled(lookupDir);
|
|
99
|
-
}
|
|
100
|
-
const externalRuntimePath = useExternalRuntime ? '@synergenius/flow-weaver/runtime' : undefined;
|
|
101
|
-
const runtimeCode = generateRuntimeSection(ast.functionName, production, moduleFormat, externalRuntimePath);
|
|
68
|
+
// Step 3: Generate and insert/replace runtime section (always inlined — zero runtime dependencies)
|
|
69
|
+
const runtimeCode = generateRuntimeSection(ast.functionName, production, moduleFormat);
|
|
102
70
|
const runtimeResult = replaceOrInsertSection(result, MARKERS.RUNTIME_START, MARKERS.RUNTIME_END, runtimeCode, 'top');
|
|
103
71
|
if (runtimeResult.changed) {
|
|
104
72
|
result = runtimeResult.code;
|
|
@@ -145,31 +113,15 @@ export function generateInPlace(sourceCode, ast, options = {}) {
|
|
|
145
113
|
}
|
|
146
114
|
/**
|
|
147
115
|
* Generate the runtime section with proper markers.
|
|
148
|
-
*
|
|
116
|
+
* Runtime is always inlined — zero runtime dependencies.
|
|
149
117
|
*/
|
|
150
|
-
function generateRuntimeSection(functionName, production, moduleFormat = 'esm'
|
|
118
|
+
function generateRuntimeSection(functionName, production, moduleFormat = 'esm') {
|
|
151
119
|
const lines = [];
|
|
152
120
|
lines.push('// ============================================================================');
|
|
153
121
|
lines.push('// DO NOT EDIT - This section is auto-generated by Flow Weaver');
|
|
154
122
|
lines.push('// ============================================================================');
|
|
155
123
|
lines.push('');
|
|
156
|
-
|
|
157
|
-
// External runtime: generate import statements instead of inline code
|
|
158
|
-
lines.push(`import { GeneratedExecutionContext, CancellationError } from '${externalRuntimePath}';`);
|
|
159
|
-
if (!production) {
|
|
160
|
-
lines.push(`import type { TDebugger, TDebugController } from '${externalRuntimePath}';`);
|
|
161
|
-
// Declare __flowWeaverDebugger__ so body code can reference it
|
|
162
|
-
lines.push('declare const __flowWeaverDebugger__: TDebugger | undefined;');
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
// Production mode still needs TDebugController for the __ctrl__ variable
|
|
166
|
-
lines.push(`import type { TDebugController } from '${externalRuntimePath}';`);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
// Inline runtime: embed all types and classes directly
|
|
171
|
-
lines.push(generateInlineRuntime(production));
|
|
172
|
-
}
|
|
124
|
+
lines.push(generateInlineRuntime(production));
|
|
173
125
|
return lines.join('\n');
|
|
174
126
|
}
|
|
175
127
|
/**
|
package/dist/api/generate.d.ts
CHANGED
|
@@ -18,12 +18,11 @@ export interface GenerateOptions extends Partial<ASTGenerateOptions> {
|
|
|
18
18
|
*/
|
|
19
19
|
moduleFormat?: TModuleFormat;
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
22
|
-
* When
|
|
23
|
-
*
|
|
24
|
-
* @example '../runtime/types.js'
|
|
21
|
+
* Enable bundle mode for multi-workflow bundles.
|
|
22
|
+
* When true, imports node types from node-types/ directory and workflows from sibling files.
|
|
23
|
+
* Runtime is always inlined regardless of this setting.
|
|
25
24
|
*/
|
|
26
|
-
|
|
25
|
+
bundleMode?: boolean;
|
|
27
26
|
/**
|
|
28
27
|
* Constants from source file(s) to include at the top of the generated file.
|
|
29
28
|
* Used in bundle mode when local node functions are inlined and need their
|
package/dist/api/generate.js
CHANGED
|
@@ -38,7 +38,7 @@ export function generateModuleExports(functionNames) {
|
|
|
38
38
|
return `module.exports = { ${functionNames.join(', ')} };`;
|
|
39
39
|
}
|
|
40
40
|
export function generateCode(ast, options) {
|
|
41
|
-
const { production = false, sourceMap = false, allWorkflows = [], moduleFormat = 'esm',
|
|
41
|
+
const { production = false, sourceMap = false, allWorkflows = [], moduleFormat = 'esm', bundleMode = false, constants = [], externalNodeTypes = {}, generateStubs = false, outputFormat = 'typescript', } = options || {};
|
|
42
42
|
// Check for stub nodes — refuse to generate unless explicitly allowed
|
|
43
43
|
const stubNodeTypes = ast.nodeTypes.filter((nt) => nt.variant === 'STUB');
|
|
44
44
|
if (stubNodeTypes.length > 0 && !generateStubs) {
|
|
@@ -78,8 +78,6 @@ export function generateCode(ast, options) {
|
|
|
78
78
|
}
|
|
79
79
|
};
|
|
80
80
|
// Generate function body using existing body generator
|
|
81
|
-
// Bundle mode uses params object pattern for node wrapper calls
|
|
82
|
-
const bundleMode = !!externalRuntimePath;
|
|
83
81
|
const functionBody = bodyGenerator.generateWithExecutionContext(ast, ast.nodeTypes, shouldBeAsync, production, bundleMode);
|
|
84
82
|
// Build the complete module
|
|
85
83
|
const lines = [];
|
|
@@ -87,25 +85,8 @@ export function generateCode(ast, options) {
|
|
|
87
85
|
addLine();
|
|
88
86
|
lines.push('');
|
|
89
87
|
addLine();
|
|
90
|
-
// Include runtime (
|
|
91
|
-
|
|
92
|
-
// Import from external runtime module to avoid duplicate declarations in multi-file bundles
|
|
93
|
-
lines.push(`// Runtime imported from shared module`);
|
|
94
|
-
addLine();
|
|
95
|
-
lines.push(generateImportStatement(['GeneratedExecutionContext', 'CancellationError'], externalRuntimePath, moduleFormat));
|
|
96
|
-
addLine();
|
|
97
|
-
if (!production) {
|
|
98
|
-
// Import TDebugger type from external runtime
|
|
99
|
-
lines.push(moduleFormat === 'cjs'
|
|
100
|
-
? `const { TDebugger } = require('${externalRuntimePath}');`
|
|
101
|
-
: `import type { TDebugger } from '${externalRuntimePath}';`);
|
|
102
|
-
addLine();
|
|
103
|
-
}
|
|
104
|
-
lines.push('');
|
|
105
|
-
addLine();
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
// Include inline runtime (types + GeneratedExecutionContext)
|
|
88
|
+
// Include inline runtime (always inlined — zero runtime dependencies)
|
|
89
|
+
{
|
|
109
90
|
const inlineRuntime = generateInlineRuntime(production);
|
|
110
91
|
const runtimeLines = inlineRuntime.split('\n');
|
|
111
92
|
runtimeLines.forEach((line) => {
|
|
@@ -172,11 +153,10 @@ export function generateCode(ast, options) {
|
|
|
172
153
|
lines.push('');
|
|
173
154
|
addLine();
|
|
174
155
|
// Import regular node functions from source files
|
|
175
|
-
// In bundle mode
|
|
156
|
+
// In bundle mode, import from node-types directory
|
|
176
157
|
// Otherwise import from .generated files in the same directory
|
|
177
158
|
functionImportsByFile.forEach((nodes, sourceFile) => {
|
|
178
|
-
if (
|
|
179
|
-
// Bundle mode: import from node-types directory
|
|
159
|
+
if (bundleMode) {
|
|
180
160
|
// Bundle mode: import _impl (positional data args for expression nodes, execute + data args for regular)
|
|
181
161
|
// The wrapper is only for HTTP entry points, not internal workflow calls
|
|
182
162
|
nodes.forEach((node) => {
|
|
@@ -199,7 +179,7 @@ export function generateCode(ast, options) {
|
|
|
199
179
|
// Import workflows from their generated files
|
|
200
180
|
// In bundle mode, import from sibling workflow files
|
|
201
181
|
workflowImportsByFile.forEach((names, sourceFile) => {
|
|
202
|
-
if (
|
|
182
|
+
if (bundleMode) {
|
|
203
183
|
// Bundle mode: import each workflow from the workflows directory
|
|
204
184
|
names.forEach((name) => {
|
|
205
185
|
const relativePath = `./${name}.js`;
|
|
@@ -16,11 +16,6 @@ export interface CompileOptions {
|
|
|
16
16
|
* - 'auto': Auto-detect from project's package.json (default)
|
|
17
17
|
*/
|
|
18
18
|
format?: 'esm' | 'cjs' | 'auto';
|
|
19
|
-
/**
|
|
20
|
-
* Force inline runtime even when @synergenius/flow-weaver package is installed.
|
|
21
|
-
* By default, the compiler uses external runtime imports when the package is available.
|
|
22
|
-
*/
|
|
23
|
-
inlineRuntime?: boolean;
|
|
24
19
|
/**
|
|
25
20
|
* Omit redundant @param/@returns annotations from compiled output.
|
|
26
21
|
* Useful for vibe coders who don't use the visual editor.
|
|
@@ -14,6 +14,7 @@ import { getFriendlyError } from '../../friendly-errors.js';
|
|
|
14
14
|
import { detectProjectModuleFormat } from './doctor.js';
|
|
15
15
|
import { compileTargetRegistry } from '../../generator/compile-target-registry.js';
|
|
16
16
|
import { AnnotationParser } from '../../parser.js';
|
|
17
|
+
import { safeWriteFile, safeAppendFile } from '../utils/safe-write.js';
|
|
17
18
|
/** Show path relative to cwd for cleaner output */
|
|
18
19
|
function displayPath(filePath) {
|
|
19
20
|
const rel = path.relative(process.cwd(), filePath);
|
|
@@ -36,7 +37,7 @@ function resolveModuleFormat(format, cwd) {
|
|
|
36
37
|
return detection.format;
|
|
37
38
|
}
|
|
38
39
|
export async function compileCommand(input, options = {}) {
|
|
39
|
-
const { production = false, sourceMap = false, strict = false, verbose = false, workflowName, dryRun = false, format,
|
|
40
|
+
const { production = false, sourceMap = false, strict = false, verbose = false, workflowName, dryRun = false, format, clean = false, target, output } = options;
|
|
40
41
|
// Handle custom compile target
|
|
41
42
|
if (target && target !== 'typescript') {
|
|
42
43
|
return compileCustomTarget(target, input, { production, verbose, workflowName, dryRun, cron: options.cron, serve: options.serve, framework: options.framework, typedEvents: options.typedEvents, retries: options.retries, timeout: options.timeout });
|
|
@@ -67,6 +68,27 @@ export async function compileCommand(input, options = {}) {
|
|
|
67
68
|
if (files.length === 0) {
|
|
68
69
|
throw new Error(`No files found matching pattern: ${input}`);
|
|
69
70
|
}
|
|
71
|
+
// Resolve --output: determine if it's a file or directory target
|
|
72
|
+
let outputDir;
|
|
73
|
+
let outputFile;
|
|
74
|
+
if (output) {
|
|
75
|
+
const isOutputDir = output.endsWith('/') || output.endsWith(path.sep) ||
|
|
76
|
+
(fs.existsSync(output) && fs.statSync(output).isDirectory());
|
|
77
|
+
if (isOutputDir) {
|
|
78
|
+
outputDir = output.endsWith('/') || output.endsWith(path.sep) ? output.slice(0, -1) : output;
|
|
79
|
+
if (!fs.existsSync(outputDir)) {
|
|
80
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else if (files.length > 1) {
|
|
84
|
+
// Multiple input files but output is a single file — ambiguous
|
|
85
|
+
throw new Error(`Cannot use --output with a file path when compiling multiple files. ` +
|
|
86
|
+
`Use a directory path instead (e.g. --output ${output}/)`);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
outputFile = path.resolve(output);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
70
92
|
const totalTimer = logger.timer();
|
|
71
93
|
logger.section('Compiling Workflows');
|
|
72
94
|
if (verbose) {
|
|
@@ -161,10 +183,16 @@ export async function compileCommand(input, options = {}) {
|
|
|
161
183
|
// Read original source
|
|
162
184
|
const sourceCode = fs.readFileSync(file, 'utf8');
|
|
163
185
|
// Generate code in-place (preserves types, interfaces, etc.)
|
|
164
|
-
const result = generateInPlace(sourceCode, parseResult.ast, { production, moduleFormat,
|
|
165
|
-
//
|
|
186
|
+
const result = generateInPlace(sourceCode, parseResult.ast, { production, moduleFormat, sourceFile: file, skipParamReturns: clean });
|
|
187
|
+
// Determine where to write the compiled output
|
|
188
|
+
const writePath = outputFile
|
|
189
|
+
? outputFile
|
|
190
|
+
: outputDir
|
|
191
|
+
? path.join(outputDir, path.basename(file))
|
|
192
|
+
: file; // in-place
|
|
193
|
+
// Write compiled output (skip in dry-run mode)
|
|
166
194
|
if (!dryRun) {
|
|
167
|
-
|
|
195
|
+
safeWriteFile(writePath, result.code);
|
|
168
196
|
// Generate source map if requested
|
|
169
197
|
if (sourceMap) {
|
|
170
198
|
const mapResult = generateCode(parseResult.ast, {
|
|
@@ -173,11 +201,11 @@ export async function compileCommand(input, options = {}) {
|
|
|
173
201
|
moduleFormat,
|
|
174
202
|
});
|
|
175
203
|
if (mapResult.sourceMap) {
|
|
176
|
-
const mapPath =
|
|
177
|
-
|
|
204
|
+
const mapPath = writePath + '.map';
|
|
205
|
+
safeWriteFile(mapPath, mapResult.sourceMap);
|
|
178
206
|
const sourceMappingComment = `\n//# sourceMappingURL=${path.basename(mapPath)}\n`;
|
|
179
207
|
if (!result.code.includes('//# sourceMappingURL=')) {
|
|
180
|
-
|
|
208
|
+
safeAppendFile(writePath, sourceMappingComment);
|
|
181
209
|
}
|
|
182
210
|
if (verbose) {
|
|
183
211
|
logger.info(` source map: ${displayPath(mapPath)}`);
|
|
@@ -291,7 +319,7 @@ export async function compileCustomTarget(target, input, options) {
|
|
|
291
319
|
}
|
|
292
320
|
}
|
|
293
321
|
else {
|
|
294
|
-
|
|
322
|
+
safeWriteFile(outputPath, code);
|
|
295
323
|
logger.success(`Compiled: ${displayPath(outputPath)}`);
|
|
296
324
|
}
|
|
297
325
|
logger.newline();
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Context command - generate LLM context bundles from documentation and grammar
|
|
3
3
|
*/
|
|
4
|
-
import * as fs from 'fs';
|
|
5
4
|
import { buildContext, PRESETS, PRESET_NAMES } from '../../context/index.js';
|
|
6
5
|
import { logger } from '../utils/logger.js';
|
|
6
|
+
import { safeWriteFile } from '../utils/safe-write.js';
|
|
7
7
|
export async function contextCommand(preset, options) {
|
|
8
8
|
// --list: show presets and exit
|
|
9
9
|
if (options.list) {
|
|
@@ -22,14 +22,12 @@ export async function contextCommand(preset, options) {
|
|
|
22
22
|
// Validate preset
|
|
23
23
|
const presetName = (preset ?? 'core');
|
|
24
24
|
if (!PRESET_NAMES.includes(presetName) && !options.topics) {
|
|
25
|
-
|
|
26
|
-
process.exit(1);
|
|
25
|
+
throw new Error(`Unknown preset "${preset}". Available: ${PRESET_NAMES.join(', ')}. Or use --topics to specify topics directly.`);
|
|
27
26
|
}
|
|
28
27
|
// Validate profile
|
|
29
28
|
const profile = options.profile ?? 'standalone';
|
|
30
29
|
if (profile !== 'standalone' && profile !== 'assistant') {
|
|
31
|
-
|
|
32
|
-
process.exit(1);
|
|
30
|
+
throw new Error(`Unknown profile "${profile}". Use "standalone" or "assistant".`);
|
|
33
31
|
}
|
|
34
32
|
const result = buildContext({
|
|
35
33
|
preset: PRESET_NAMES.includes(presetName) ? presetName : 'core',
|
|
@@ -40,7 +38,7 @@ export async function contextCommand(preset, options) {
|
|
|
40
38
|
});
|
|
41
39
|
// Write output
|
|
42
40
|
if (options.output) {
|
|
43
|
-
|
|
41
|
+
safeWriteFile(options.output, result.content);
|
|
44
42
|
logger.success(`Context written to ${options.output}`);
|
|
45
43
|
}
|
|
46
44
|
else {
|
|
@@ -41,9 +41,7 @@ export async function createWorkflowCommand(template, file, options = {}) {
|
|
|
41
41
|
const { line, async: isAsync = false, preview = false, provider, model, config: configJson, } = options;
|
|
42
42
|
const templateDef = getWorkflowTemplate(template);
|
|
43
43
|
if (!templateDef) {
|
|
44
|
-
|
|
45
|
-
logger.info("Run 'fw templates' to see available templates");
|
|
46
|
-
process.exit(1);
|
|
44
|
+
throw new Error(`Unknown workflow template: ${template}. Run 'fw templates' to see available templates`);
|
|
47
45
|
}
|
|
48
46
|
// Resolve to absolute path
|
|
49
47
|
const filePath = path.resolve(file);
|
|
@@ -67,8 +65,7 @@ export async function createWorkflowCommand(template, file, options = {}) {
|
|
|
67
65
|
Object.assign(config, JSON.parse(configJson));
|
|
68
66
|
}
|
|
69
67
|
catch {
|
|
70
|
-
|
|
71
|
-
process.exit(1);
|
|
68
|
+
throw new Error('Invalid --config JSON');
|
|
72
69
|
}
|
|
73
70
|
}
|
|
74
71
|
// Generate the template code
|
|
@@ -91,8 +88,7 @@ export async function createWorkflowCommand(template, file, options = {}) {
|
|
|
91
88
|
logger.info(` Workflow function: ${workflowName}`);
|
|
92
89
|
}
|
|
93
90
|
catch (error) {
|
|
94
|
-
|
|
95
|
-
process.exit(1);
|
|
91
|
+
throw new Error(`Failed to create workflow: ${getErrorMessage(error)}`);
|
|
96
92
|
}
|
|
97
93
|
}
|
|
98
94
|
/**
|
|
@@ -103,9 +99,7 @@ export async function createNodeCommand(name, file, options = {}) {
|
|
|
103
99
|
const { line, template = 'processor', preview = false, strategy, config: configJson } = options;
|
|
104
100
|
const templateDef = getNodeTemplate(template);
|
|
105
101
|
if (!templateDef) {
|
|
106
|
-
|
|
107
|
-
logger.info("Run 'fw templates' to see available templates");
|
|
108
|
-
process.exit(1);
|
|
102
|
+
throw new Error(`Unknown node template: ${template}. Run 'fw templates' to see available templates`);
|
|
109
103
|
}
|
|
110
104
|
// Resolve to absolute path
|
|
111
105
|
const filePath = path.resolve(file);
|
|
@@ -118,8 +112,7 @@ export async function createNodeCommand(name, file, options = {}) {
|
|
|
118
112
|
Object.assign(config, JSON.parse(configJson));
|
|
119
113
|
}
|
|
120
114
|
catch {
|
|
121
|
-
|
|
122
|
-
process.exit(1);
|
|
115
|
+
throw new Error('Invalid --config JSON');
|
|
123
116
|
}
|
|
124
117
|
}
|
|
125
118
|
// Generate the template code with provided name and optional config
|
|
@@ -140,8 +133,7 @@ export async function createNodeCommand(name, file, options = {}) {
|
|
|
140
133
|
logger.info(` Node function: ${nodeName}`);
|
|
141
134
|
}
|
|
142
135
|
catch (error) {
|
|
143
|
-
|
|
144
|
-
process.exit(1);
|
|
136
|
+
throw new Error(`Failed to create node: ${getErrorMessage(error)}`);
|
|
145
137
|
}
|
|
146
138
|
}
|
|
147
139
|
//# sourceMappingURL=create.js.map
|
|
@@ -9,6 +9,7 @@ import { validator } from '../../validator.js';
|
|
|
9
9
|
import { getNode, getIncomingConnections, getOutgoingConnections } from '../../api/query.js';
|
|
10
10
|
import { logger } from '../utils/logger.js';
|
|
11
11
|
import { getErrorMessage } from '../../utils/error-utils.js';
|
|
12
|
+
import { safeWriteFile } from '../utils/safe-write.js';
|
|
12
13
|
import { buildDiagramGraph } from '../../diagram/geometry.js';
|
|
13
14
|
import { renderASCII, renderASCIICompact } from '../../diagram/ascii-renderer.js';
|
|
14
15
|
export function buildNodeInfo(instance, nodeType) {
|
|
@@ -344,16 +345,13 @@ export async function describeCommand(input, options = {}) {
|
|
|
344
345
|
const { format = 'json', node: focusNodeId, workflowName, compile = false } = options;
|
|
345
346
|
const filePath = path.resolve(input);
|
|
346
347
|
if (!fs.existsSync(filePath)) {
|
|
347
|
-
|
|
348
|
-
process.exit(1);
|
|
348
|
+
throw new Error(`File not found: ${filePath}`);
|
|
349
349
|
}
|
|
350
350
|
try {
|
|
351
351
|
// Parse the workflow
|
|
352
352
|
const parseResult = await parseWorkflow(filePath, { workflowName });
|
|
353
353
|
if (parseResult.errors.length > 0) {
|
|
354
|
-
|
|
355
|
-
parseResult.errors.forEach((err) => logger.error(` ${err}`));
|
|
356
|
-
process.exit(1);
|
|
354
|
+
throw new Error(`Parse errors:\n${parseResult.errors.map((err) => ` ${err}`).join('\n')}`);
|
|
357
355
|
}
|
|
358
356
|
const ast = parseResult.ast;
|
|
359
357
|
// Only update runtime markers when explicitly requested via --compile
|
|
@@ -362,7 +360,7 @@ export async function describeCommand(input, options = {}) {
|
|
|
362
360
|
const sourceCode = fs.readFileSync(filePath, 'utf8');
|
|
363
361
|
const generated = generateInPlace(sourceCode, ast, { production: false });
|
|
364
362
|
if (generated.hasChanges) {
|
|
365
|
-
|
|
363
|
+
safeWriteFile(filePath, generated.code);
|
|
366
364
|
logger.info(`Updated runtime markers in ${path.basename(filePath)}`);
|
|
367
365
|
}
|
|
368
366
|
}
|
|
@@ -373,11 +371,9 @@ export async function describeCommand(input, options = {}) {
|
|
|
373
371
|
}
|
|
374
372
|
catch (error) {
|
|
375
373
|
if (error instanceof Error && error.message.startsWith('Node not found:')) {
|
|
376
|
-
|
|
377
|
-
process.exit(1);
|
|
374
|
+
throw error;
|
|
378
375
|
}
|
|
379
|
-
|
|
380
|
-
process.exit(1);
|
|
376
|
+
throw new Error(`Failed to describe workflow: ${getErrorMessage(error)}`);
|
|
381
377
|
}
|
|
382
378
|
}
|
|
383
379
|
//# sourceMappingURL=describe.js.map
|
|
@@ -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
|