@synergenius/flow-weaver 0.15.1 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/compile.js +158 -149
- package/dist/cli/commands/dev.js +14 -11
- package/dist/cli/commands/diff.js +6 -1
- package/dist/cli/commands/doctor.js +16 -25
- package/dist/cli/commands/export.js +2 -0
- package/dist/cli/commands/grammar.js +3 -1
- package/dist/cli/commands/init.js +18 -28
- package/dist/cli/commands/market.js +14 -6
- package/dist/cli/commands/migrate.js +3 -2
- package/dist/cli/commands/openapi.js +4 -1
- package/dist/cli/commands/run.js +44 -16
- package/dist/cli/commands/status.js +5 -3
- package/dist/cli/commands/strip.js +3 -1
- package/dist/cli/commands/templates.js +1 -1
- package/dist/cli/commands/validate.js +27 -24
- package/dist/cli/env-setup.d.ts +5 -0
- package/dist/cli/env-setup.js +12 -0
- package/dist/cli/flow-weaver.mjs +763 -794
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +254 -508
- package/dist/cli/utils/logger.d.ts +14 -0
- package/dist/cli/utils/logger.js +96 -16
- package/dist/generated-version.d.ts +1 -1
- package/dist/generated-version.js +1 -1
- package/dist/mcp/workflow-executor.js +7 -1
- package/dist/parser.js +1 -0
- package/package.json +2 -1
|
@@ -14,6 +14,15 @@ import { getFriendlyError } from '../../friendly-errors.js';
|
|
|
14
14
|
import { detectProjectModuleFormat } from './doctor.js';
|
|
15
15
|
import { generateInngestFunction } from '../../generator/inngest.js';
|
|
16
16
|
import { AnnotationParser } from '../../parser.js';
|
|
17
|
+
/** Show path relative to cwd for cleaner output */
|
|
18
|
+
function displayPath(filePath) {
|
|
19
|
+
const rel = path.relative(process.cwd(), filePath);
|
|
20
|
+
// Use relative if it's shorter and doesn't escape cwd
|
|
21
|
+
if (rel && !rel.startsWith('..') && rel.length < filePath.length) {
|
|
22
|
+
return rel;
|
|
23
|
+
}
|
|
24
|
+
return filePath;
|
|
25
|
+
}
|
|
17
26
|
/**
|
|
18
27
|
* Resolve the module format to use for compilation.
|
|
19
28
|
* If 'auto' or not specified, detect from the project's package.json.
|
|
@@ -35,181 +44,181 @@ export async function compileCommand(input, options = {}) {
|
|
|
35
44
|
// Resolve module format (auto-detect if not specified)
|
|
36
45
|
const cwd = process.cwd();
|
|
37
46
|
const moduleFormat = resolveModuleFormat(format, cwd);
|
|
47
|
+
// If input is a directory, expand to all .ts files recursively
|
|
48
|
+
let pattern = input;
|
|
38
49
|
try {
|
|
39
|
-
|
|
40
|
-
|
|
50
|
+
if (fs.existsSync(input) && fs.statSync(input).isDirectory()) {
|
|
51
|
+
pattern = path.join(input, '**/*.ts');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Not a valid path, use as glob pattern
|
|
56
|
+
}
|
|
57
|
+
// Find files matching the pattern, filter to actual files only
|
|
58
|
+
const allFiles = await glob(pattern, { absolute: true });
|
|
59
|
+
const files = allFiles.filter((f) => {
|
|
41
60
|
try {
|
|
42
|
-
|
|
43
|
-
pattern = path.join(input, '**/*.ts');
|
|
44
|
-
}
|
|
61
|
+
return fs.statSync(f).isFile();
|
|
45
62
|
}
|
|
46
63
|
catch {
|
|
47
|
-
|
|
64
|
+
return false;
|
|
48
65
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
if (files.length === 0) {
|
|
60
|
-
throw new Error(`No files found matching pattern: ${input}`);
|
|
61
|
-
}
|
|
62
|
-
logger.section('Compiling Workflows');
|
|
66
|
+
});
|
|
67
|
+
if (files.length === 0) {
|
|
68
|
+
throw new Error(`No files found matching pattern: ${input}`);
|
|
69
|
+
}
|
|
70
|
+
const totalTimer = logger.timer();
|
|
71
|
+
logger.section('Compiling Workflows');
|
|
72
|
+
if (verbose) {
|
|
63
73
|
logger.info(`Found ${files.length} file(s)`);
|
|
64
74
|
logger.info(`Module format: ${moduleFormat.toUpperCase()}`);
|
|
65
75
|
if (sourceMap) {
|
|
66
76
|
logger.info('Source maps: enabled');
|
|
67
77
|
}
|
|
68
|
-
|
|
69
|
-
files.forEach((file) => logger.info(` - ${file}`));
|
|
70
|
-
}
|
|
78
|
+
files.forEach((file) => logger.info(` ${displayPath(file)}`));
|
|
71
79
|
logger.newline();
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
// Parse the workflow
|
|
88
|
-
const parseResult = await parseWorkflow(file, { workflowName });
|
|
89
|
-
if (parseResult.warnings.length > 0) {
|
|
90
|
-
parseResult.warnings.forEach((w) => logger.warn(` ${w}`));
|
|
80
|
+
}
|
|
81
|
+
let successCount = 0;
|
|
82
|
+
let errorCount = 0;
|
|
83
|
+
for (let i = 0; i < files.length; i++) {
|
|
84
|
+
const file = files[i];
|
|
85
|
+
const fileName = path.basename(file);
|
|
86
|
+
const fileTimer = logger.timer();
|
|
87
|
+
try {
|
|
88
|
+
// Skip files without @flowWeaver annotations before parsing
|
|
89
|
+
const rawSource = fs.readFileSync(file, 'utf8');
|
|
90
|
+
if (!rawSource.includes('@flowWeaver')) {
|
|
91
|
+
if (verbose) {
|
|
92
|
+
logger.info(` ${logger.dim('skip')} ${fileName} ${logger.dim('(no @flowWeaver annotations)')}`);
|
|
91
93
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
// Parse the workflow
|
|
97
|
+
const parseResult = await parseWorkflow(file, { workflowName });
|
|
98
|
+
if (parseResult.warnings.length > 0 && verbose) {
|
|
99
|
+
logger.warn(`Parse warnings in ${fileName}:`);
|
|
100
|
+
parseResult.warnings.forEach((w) => logger.warn(` ${w}`));
|
|
101
|
+
}
|
|
102
|
+
if (parseResult.errors.length > 0) {
|
|
103
|
+
// Skip non-workflow files silently (only error is "No workflows found")
|
|
104
|
+
const isNonWorkflowFile = parseResult.errors.length === 1 &&
|
|
105
|
+
typeof parseResult.errors[0] === 'string' &&
|
|
106
|
+
parseResult.errors[0].startsWith('No workflows found');
|
|
107
|
+
if (isNonWorkflowFile) {
|
|
108
|
+
if (verbose) {
|
|
109
|
+
logger.info(` ${logger.dim('skip')} ${fileName} ${logger.dim('(no workflow)')}`);
|
|
102
110
|
}
|
|
103
|
-
logger.error(`Failed to parse ${fileName}:`);
|
|
104
|
-
parseResult.errors.forEach((err) => logger.error(` ${err}`));
|
|
105
|
-
errorCount++;
|
|
106
111
|
continue;
|
|
107
112
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
msg += ` (node: ${err.node})`;
|
|
127
|
-
}
|
|
128
|
-
logger.error(msg);
|
|
129
|
-
if (err.docUrl) {
|
|
130
|
-
logger.info(` See: ${err.docUrl}`);
|
|
131
|
-
}
|
|
113
|
+
logger.error(` ${fileName}`);
|
|
114
|
+
parseResult.errors.forEach((err) => logger.error(` ${err}`));
|
|
115
|
+
errorCount++;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
// Validate the AST (especially for --strict mode)
|
|
119
|
+
if (strict) {
|
|
120
|
+
const validation = validator.validate(parseResult.ast, { strictMode: true });
|
|
121
|
+
if (validation.errors.length > 0) {
|
|
122
|
+
logger.error(` ${fileName}`);
|
|
123
|
+
validation.errors.forEach((err) => {
|
|
124
|
+
const friendly = getFriendlyError(err);
|
|
125
|
+
if (friendly) {
|
|
126
|
+
const loc = err.location ? `[line ${err.location.line}] ` : '';
|
|
127
|
+
logger.error(` ${loc}${friendly.title}: ${friendly.explanation}`);
|
|
128
|
+
logger.warn(` How to fix: ${friendly.fix}`);
|
|
129
|
+
if (err.docUrl) {
|
|
130
|
+
logger.warn(` See: ${err.docUrl}`);
|
|
132
131
|
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
validation.warnings.forEach((warn) => {
|
|
139
|
-
const friendly = getFriendlyError(warn);
|
|
140
|
-
if (friendly) {
|
|
141
|
-
const loc = warn.location ? `[line ${warn.location.line}] ` : '';
|
|
142
|
-
logger.warn(` ${loc}${friendly.title}: ${friendly.explanation}`);
|
|
143
|
-
logger.info(` How to fix: ${friendly.fix}`);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
let msg = ` ${err.message}`;
|
|
135
|
+
if (err.node) {
|
|
136
|
+
msg += ` (node: ${err.node})`;
|
|
144
137
|
}
|
|
145
|
-
|
|
146
|
-
|
|
138
|
+
logger.error(msg);
|
|
139
|
+
if (err.docUrl) {
|
|
140
|
+
logger.warn(` See: ${err.docUrl}`);
|
|
147
141
|
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
errorCount++;
|
|
145
|
+
continue;
|
|
150
146
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
// Generate source map if requested
|
|
159
|
-
if (sourceMap) {
|
|
160
|
-
const mapResult = generateCode(parseResult.ast, {
|
|
161
|
-
production,
|
|
162
|
-
sourceMap: true,
|
|
163
|
-
moduleFormat,
|
|
164
|
-
});
|
|
165
|
-
if (mapResult.sourceMap) {
|
|
166
|
-
const mapPath = file + '.map';
|
|
167
|
-
fs.writeFileSync(mapPath, mapResult.sourceMap, 'utf8');
|
|
168
|
-
// Append sourceMappingURL comment to the compiled file
|
|
169
|
-
const sourceMappingComment = `\n//# sourceMappingURL=${path.basename(mapPath)}\n`;
|
|
170
|
-
if (!result.code.includes('//# sourceMappingURL=')) {
|
|
171
|
-
fs.appendFileSync(file, sourceMappingComment, 'utf8');
|
|
172
|
-
}
|
|
173
|
-
if (verbose) {
|
|
174
|
-
logger.info(` Source map: ${mapPath}`);
|
|
175
|
-
}
|
|
147
|
+
if (validation.warnings.length > 0 && verbose) {
|
|
148
|
+
validation.warnings.forEach((warn) => {
|
|
149
|
+
const friendly = getFriendlyError(warn);
|
|
150
|
+
if (friendly) {
|
|
151
|
+
const loc = warn.location ? `[line ${warn.location.line}] ` : '';
|
|
152
|
+
logger.warn(` ${loc}${friendly.title}: ${friendly.explanation}`);
|
|
153
|
+
logger.warn(` How to fix: ${friendly.fix}`);
|
|
176
154
|
}
|
|
177
|
-
|
|
155
|
+
else {
|
|
156
|
+
logger.warn(` ${warn.message}`);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
178
159
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
160
|
+
}
|
|
161
|
+
// Read original source
|
|
162
|
+
const sourceCode = fs.readFileSync(file, 'utf8');
|
|
163
|
+
// Generate code in-place (preserves types, interfaces, etc.)
|
|
164
|
+
const result = generateInPlace(sourceCode, parseResult.ast, { production, moduleFormat, inlineRuntime, sourceFile: file, skipParamReturns: clean });
|
|
165
|
+
// Write back to original file (skip in dry-run mode)
|
|
166
|
+
if (!dryRun) {
|
|
167
|
+
fs.writeFileSync(file, result.code, 'utf8');
|
|
168
|
+
// Generate source map if requested
|
|
169
|
+
if (sourceMap) {
|
|
170
|
+
const mapResult = generateCode(parseResult.ast, {
|
|
171
|
+
production,
|
|
172
|
+
sourceMap: true,
|
|
173
|
+
moduleFormat,
|
|
174
|
+
});
|
|
175
|
+
if (mapResult.sourceMap) {
|
|
176
|
+
const mapPath = file + '.map';
|
|
177
|
+
fs.writeFileSync(mapPath, mapResult.sourceMap, 'utf8');
|
|
178
|
+
const sourceMappingComment = `\n//# sourceMappingURL=${path.basename(mapPath)}\n`;
|
|
179
|
+
if (!result.code.includes('//# sourceMappingURL=')) {
|
|
180
|
+
fs.appendFileSync(file, sourceMappingComment, 'utf8');
|
|
181
|
+
}
|
|
182
|
+
if (verbose) {
|
|
183
|
+
logger.info(` source map: ${displayPath(mapPath)}`);
|
|
184
|
+
}
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
|
-
|
|
188
|
-
|
|
187
|
+
}
|
|
188
|
+
const timing = logger.dim(fileTimer.elapsed());
|
|
189
|
+
const filePrint = displayPath(file);
|
|
190
|
+
if (dryRun) {
|
|
191
|
+
if (result.hasChanges) {
|
|
192
|
+
logger.success(`${filePrint} ${timing} ${logger.dim('(dry run)')}`);
|
|
189
193
|
}
|
|
190
|
-
else
|
|
191
|
-
logger.
|
|
194
|
+
else {
|
|
195
|
+
logger.log(` ${filePrint} ${logger.dim('(no changes, dry run)')}`);
|
|
192
196
|
}
|
|
193
|
-
successCount++;
|
|
194
197
|
}
|
|
195
|
-
|
|
196
|
-
logger.
|
|
197
|
-
errorCount++;
|
|
198
|
+
else if (result.hasChanges) {
|
|
199
|
+
logger.success(`${filePrint} ${timing}`);
|
|
198
200
|
}
|
|
201
|
+
else if (verbose) {
|
|
202
|
+
logger.info(` ${filePrint} ${logger.dim('no changes')}`);
|
|
203
|
+
}
|
|
204
|
+
successCount++;
|
|
199
205
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
logger.success(`${successCount} file(s) compiled successfully`);
|
|
204
|
-
if (errorCount > 0) {
|
|
205
|
-
throw new Error(`${errorCount} file(s) failed to compile`);
|
|
206
|
+
catch (error) {
|
|
207
|
+
logger.error(` ${fileName}: ${getErrorMessage(error)}`);
|
|
208
|
+
errorCount++;
|
|
206
209
|
}
|
|
207
210
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
211
|
+
// Summary
|
|
212
|
+
const elapsed = totalTimer.elapsed();
|
|
213
|
+
const formatNote = verbose ? '' : ` (${moduleFormat.toUpperCase()})`;
|
|
214
|
+
const dryRunNote = dryRun ? ' (dry run)' : '';
|
|
215
|
+
logger.newline();
|
|
216
|
+
if (errorCount > 0) {
|
|
217
|
+
logger.log(` ${successCount} compiled, ${errorCount} failed${formatNote} in ${elapsed}${dryRunNote}`);
|
|
218
|
+
throw new Error(`${errorCount} file(s) failed to compile`);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
logger.log(` ${successCount} file${successCount !== 1 ? 's' : ''} compiled${formatNote} in ${elapsed}${dryRunNote}`);
|
|
213
222
|
}
|
|
214
223
|
}
|
|
215
224
|
/**
|
|
@@ -218,10 +227,10 @@ export async function compileCommand(input, options = {}) {
|
|
|
218
227
|
export async function compileInngestTarget(input, options) {
|
|
219
228
|
const filePath = path.resolve(input);
|
|
220
229
|
if (!fs.existsSync(filePath)) {
|
|
221
|
-
throw new Error(`File not found: ${filePath}`);
|
|
230
|
+
throw new Error(`File not found: ${displayPath(filePath)}`);
|
|
222
231
|
}
|
|
223
232
|
logger.section('Compiling to Inngest');
|
|
224
|
-
logger.info(`Input: ${filePath}`);
|
|
233
|
+
logger.info(`Input: ${displayPath(filePath)}`);
|
|
225
234
|
logger.info(`Target: Inngest (per-node step.run)`);
|
|
226
235
|
logger.newline();
|
|
227
236
|
const parser = new AnnotationParser();
|
|
@@ -262,7 +271,7 @@ export async function compileInngestTarget(input, options) {
|
|
|
262
271
|
});
|
|
263
272
|
const outputPath = filePath.replace(/\.ts$/, '.inngest.ts');
|
|
264
273
|
if (options.dryRun) {
|
|
265
|
-
logger.success(`Would generate: ${outputPath}`);
|
|
274
|
+
logger.success(`Would generate: ${displayPath(outputPath)}`);
|
|
266
275
|
logger.newline();
|
|
267
276
|
logger.section('Preview');
|
|
268
277
|
const lines = code.split('\n');
|
|
@@ -274,7 +283,7 @@ export async function compileInngestTarget(input, options) {
|
|
|
274
283
|
}
|
|
275
284
|
else {
|
|
276
285
|
fs.writeFileSync(outputPath, code, 'utf8');
|
|
277
|
-
logger.success(`Compiled: ${outputPath}`);
|
|
286
|
+
logger.success(`Compiled: ${displayPath(outputPath)}`);
|
|
278
287
|
}
|
|
279
288
|
logger.newline();
|
|
280
289
|
logger.section('Summary');
|
package/dist/cli/commands/dev.js
CHANGED
|
@@ -16,7 +16,15 @@ function timestamp() {
|
|
|
16
16
|
const h = String(now.getHours()).padStart(2, '0');
|
|
17
17
|
const m = String(now.getMinutes()).padStart(2, '0');
|
|
18
18
|
const s = String(now.getSeconds()).padStart(2, '0');
|
|
19
|
-
return
|
|
19
|
+
return `${h}:${m}:${s}`;
|
|
20
|
+
}
|
|
21
|
+
function cycleSeparator(file) {
|
|
22
|
+
const ts = timestamp();
|
|
23
|
+
const pad = '─'.repeat(40);
|
|
24
|
+
logger.log(`\n ${logger.dim(`─── ${ts} ${pad}`)}`);
|
|
25
|
+
if (file) {
|
|
26
|
+
logger.log(` ${logger.dim('File changed:')} ${path.basename(file)}`);
|
|
27
|
+
}
|
|
20
28
|
}
|
|
21
29
|
/**
|
|
22
30
|
* Parse params from --params or --params-file.
|
|
@@ -56,9 +64,10 @@ async function compileAndRun(filePath, params, options) {
|
|
|
56
64
|
clean: options.clean,
|
|
57
65
|
};
|
|
58
66
|
try {
|
|
67
|
+
const ct = logger.timer();
|
|
59
68
|
await compileCommand(filePath, compileOpts);
|
|
60
69
|
if (!options.json) {
|
|
61
|
-
logger.success(
|
|
70
|
+
logger.success(`Compiled in ${ct.elapsed()}`);
|
|
62
71
|
}
|
|
63
72
|
}
|
|
64
73
|
catch (error) {
|
|
@@ -70,7 +79,7 @@ async function compileAndRun(filePath, params, options) {
|
|
|
70
79
|
const friendly = getFriendlyError(err);
|
|
71
80
|
if (friendly) {
|
|
72
81
|
logger.error(` ${friendly.title}: ${friendly.explanation}`);
|
|
73
|
-
logger.
|
|
82
|
+
logger.warn(` How to fix: ${friendly.fix}`);
|
|
74
83
|
}
|
|
75
84
|
else {
|
|
76
85
|
logger.error(` - ${err.message}`);
|
|
@@ -99,8 +108,6 @@ async function compileAndRun(filePath, params, options) {
|
|
|
99
108
|
}
|
|
100
109
|
else {
|
|
101
110
|
logger.success(`Workflow "${result.functionName}" completed in ${result.executionTime}ms`);
|
|
102
|
-
logger.newline();
|
|
103
|
-
logger.section('Result');
|
|
104
111
|
logger.log(JSON.stringify(result.result, null, 2));
|
|
105
112
|
}
|
|
106
113
|
return true;
|
|
@@ -286,10 +293,8 @@ async function runInngestDevMode(filePath, options) {
|
|
|
286
293
|
ignoreInitial: true,
|
|
287
294
|
});
|
|
288
295
|
watcher.on('change', async (file) => {
|
|
289
|
-
|
|
290
|
-
logger.info(`${timestamp()} File changed: ${path.basename(file)}`);
|
|
296
|
+
cycleSeparator(file);
|
|
291
297
|
logger.info('Recompiling and restarting server...');
|
|
292
|
-
logger.newline();
|
|
293
298
|
await restartServer();
|
|
294
299
|
});
|
|
295
300
|
// Cleanup
|
|
@@ -360,9 +365,7 @@ export async function devCommand(input, options = {}) {
|
|
|
360
365
|
});
|
|
361
366
|
watcher.on('change', async (file) => {
|
|
362
367
|
if (!options.json) {
|
|
363
|
-
|
|
364
|
-
logger.info(`${timestamp()} File changed: ${path.basename(file)}`);
|
|
365
|
-
logger.newline();
|
|
368
|
+
cycleSeparator(file);
|
|
366
369
|
}
|
|
367
370
|
await compileAndRun(filePath, params, options);
|
|
368
371
|
});
|
|
@@ -40,7 +40,12 @@ export async function diffCommand(file1, file2, options = {}) {
|
|
|
40
40
|
const diff = WorkflowDiffer.compare(result1.ast, result2.ast);
|
|
41
41
|
// Output based on format
|
|
42
42
|
if (diff.identical) {
|
|
43
|
-
|
|
43
|
+
if (format === 'json') {
|
|
44
|
+
console.log(JSON.stringify({ identical: true }));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
logger.success('Workflows are identical');
|
|
48
|
+
}
|
|
44
49
|
}
|
|
45
50
|
else {
|
|
46
51
|
// eslint-disable-next-line no-console
|
|
@@ -669,37 +669,28 @@ export async function doctorCommand(options = {}) {
|
|
|
669
669
|
}
|
|
670
670
|
else {
|
|
671
671
|
logger.section('Flow Weaver Doctor');
|
|
672
|
-
logger.
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
case 'fail':
|
|
686
|
-
logger.error(check.message);
|
|
687
|
-
if (check.fix) {
|
|
688
|
-
logger.log(` Fix: ${check.fix}`);
|
|
689
|
-
}
|
|
690
|
-
break;
|
|
672
|
+
const statusIcon = (s) => s === 'pass' ? logger.highlight('pass') : s === 'warn' ? '⚠ warn' : '✗ fail';
|
|
673
|
+
const rows = report.checks.map((check) => [
|
|
674
|
+
check.name,
|
|
675
|
+
check.message,
|
|
676
|
+
statusIcon(check.status),
|
|
677
|
+
]);
|
|
678
|
+
logger.table(rows);
|
|
679
|
+
// Show fixes for non-passing checks
|
|
680
|
+
const fixable = report.checks.filter((c) => c.status !== 'pass' && c.fix);
|
|
681
|
+
if (fixable.length > 0) {
|
|
682
|
+
logger.newline();
|
|
683
|
+
for (const check of fixable) {
|
|
684
|
+
logger.log(` ${logger.dim(check.name + ':')} ${check.fix}`);
|
|
691
685
|
}
|
|
692
686
|
}
|
|
693
687
|
logger.newline();
|
|
694
|
-
logger.
|
|
695
|
-
logger.info(` Detected: ${report.moduleFormat.format.toUpperCase()} (${report.moduleFormat.details})`);
|
|
696
|
-
logger.newline();
|
|
697
|
-
logger.section('Summary');
|
|
688
|
+
logger.log(` Module format: ${report.moduleFormat.format.toUpperCase()} ${logger.dim(`(${report.moduleFormat.details})`)}`);
|
|
698
689
|
const parts = [];
|
|
699
690
|
if (report.summary.pass > 0)
|
|
700
691
|
parts.push(`${report.summary.pass} passed`);
|
|
701
692
|
if (report.summary.warn > 0)
|
|
702
|
-
parts.push(`${report.summary.warn}
|
|
693
|
+
parts.push(`${report.summary.warn} warnings`);
|
|
703
694
|
if (report.summary.fail > 0)
|
|
704
695
|
parts.push(`${report.summary.fail} failed`);
|
|
705
696
|
logger.log(` ${parts.join(', ')}`);
|
|
@@ -709,7 +700,7 @@ export async function doctorCommand(options = {}) {
|
|
|
709
700
|
}
|
|
710
701
|
else {
|
|
711
702
|
logger.newline();
|
|
712
|
-
logger.error('
|
|
703
|
+
logger.error('Fix the issues above to continue.');
|
|
713
704
|
}
|
|
714
705
|
}
|
|
715
706
|
if (!report.ok) {
|
|
@@ -39,6 +39,7 @@ export async function exportCommand(input, options) {
|
|
|
39
39
|
throw new Error('--target is required. Install a target pack (e.g. npm install flowweaver-pack-lambda)');
|
|
40
40
|
}
|
|
41
41
|
const isDryRun = options.dryRun ?? false;
|
|
42
|
+
const t = logger.timer();
|
|
42
43
|
const isMulti = options.multi ?? false;
|
|
43
44
|
const workflowsList = options.workflows
|
|
44
45
|
? options.workflows.split(',').map((w) => w.trim())
|
|
@@ -98,6 +99,7 @@ export async function exportCommand(input, options) {
|
|
|
98
99
|
else {
|
|
99
100
|
logger.success(`Exported workflow "${result.workflow}" for ${result.target}`);
|
|
100
101
|
}
|
|
102
|
+
logger.info(`done in ${t.elapsed()}`);
|
|
101
103
|
}
|
|
102
104
|
logger.newline();
|
|
103
105
|
logger.section(isDryRun ? 'Files That Would Be Generated' : 'Generated Files');
|
|
@@ -6,7 +6,9 @@ import { generateGrammarDiagrams, getAllGrammars, serializedToEBNF, } from '../.
|
|
|
6
6
|
import { logger } from '../utils/logger.js';
|
|
7
7
|
import { getErrorMessage } from '../../utils/error-utils.js';
|
|
8
8
|
export async function grammarCommand(options = {}) {
|
|
9
|
-
|
|
9
|
+
// Default to ebnf on TTY (readable in terminal), html when writing to file
|
|
10
|
+
const defaultFormat = options.output ? 'html' : (process.stdout.isTTY ? 'ebnf' : 'html');
|
|
11
|
+
const { format = defaultFormat, output } = options;
|
|
10
12
|
try {
|
|
11
13
|
let content;
|
|
12
14
|
if (format === 'ebnf') {
|
|
@@ -338,27 +338,26 @@ export async function initCommand(dirArg, options) {
|
|
|
338
338
|
// Post-scaffold actions
|
|
339
339
|
let installResult;
|
|
340
340
|
if (config.install) {
|
|
341
|
-
|
|
342
|
-
logger.info('Installing dependencies...');
|
|
343
|
-
}
|
|
341
|
+
const spinner = !options.json ? logger.spinner('Installing dependencies...') : null;
|
|
344
342
|
installResult = runNpmInstall(config.targetDir);
|
|
343
|
+
if (spinner) {
|
|
344
|
+
if (installResult.success)
|
|
345
|
+
spinner.stop('Dependencies installed');
|
|
346
|
+
else
|
|
347
|
+
spinner.fail(`npm install failed: ${installResult.error}`);
|
|
348
|
+
}
|
|
345
349
|
}
|
|
346
350
|
let gitResult;
|
|
347
351
|
if (config.git) {
|
|
348
|
-
if (!options.json) {
|
|
349
|
-
logger.info('Initializing git repository...');
|
|
350
|
-
}
|
|
351
352
|
gitResult = runGitInit(config.targetDir);
|
|
352
353
|
}
|
|
353
|
-
// Auto-compile the workflow so `npm start` works immediately
|
|
354
|
-
const workflowFile = `${config.projectName}-workflow.ts`;
|
|
355
|
-
const workflowPath = path.join(config.targetDir, 'src', workflowFile);
|
|
356
354
|
// Auto-compile the workflow so `npm start` works immediately.
|
|
357
355
|
// Skip in JSON mode since compileCommand writes to logger.
|
|
356
|
+
const workflowFile = `${config.projectName}-workflow.ts`;
|
|
357
|
+
const workflowPath = path.join(config.targetDir, 'src', workflowFile);
|
|
358
358
|
let compileResult;
|
|
359
359
|
if (!options.json && fs.existsSync(workflowPath)) {
|
|
360
360
|
try {
|
|
361
|
-
logger.info('Compiling workflow...');
|
|
362
361
|
await compileCommand(workflowPath, { format: config.format });
|
|
363
362
|
compileResult = { success: true };
|
|
364
363
|
}
|
|
@@ -382,24 +381,15 @@ export async function initCommand(dirArg, options) {
|
|
|
382
381
|
return;
|
|
383
382
|
}
|
|
384
383
|
// Human output
|
|
385
|
-
logger.
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
logger.warn(`Skipped ${file} (already exists)`);
|
|
391
|
-
}
|
|
392
|
-
if (installResult) {
|
|
393
|
-
if (installResult.success) {
|
|
394
|
-
logger.success('Dependencies installed');
|
|
395
|
-
}
|
|
396
|
-
else {
|
|
397
|
-
logger.warn(`npm install failed: ${installResult.error}`);
|
|
398
|
-
}
|
|
384
|
+
logger.newline();
|
|
385
|
+
logger.success(`Created ${logger.highlight(config.projectName)} ${logger.dim(`(${config.template}, ${config.format.toUpperCase()})`)}`);
|
|
386
|
+
logger.log(` ${filesCreated.join(', ')}`);
|
|
387
|
+
if (filesSkipped.length > 0) {
|
|
388
|
+
logger.warn(`Skipped ${filesSkipped.length} existing file(s)`);
|
|
399
389
|
}
|
|
400
390
|
if (gitResult) {
|
|
401
391
|
if (gitResult.success) {
|
|
402
|
-
logger.success('Git
|
|
392
|
+
logger.success('Git initialized');
|
|
403
393
|
}
|
|
404
394
|
else {
|
|
405
395
|
logger.warn(`git init failed: ${gitResult.error}`);
|
|
@@ -407,14 +397,14 @@ export async function initCommand(dirArg, options) {
|
|
|
407
397
|
}
|
|
408
398
|
if (compileResult) {
|
|
409
399
|
if (compileResult.success) {
|
|
410
|
-
logger.success('Workflow compiled
|
|
400
|
+
logger.success('Workflow compiled');
|
|
411
401
|
}
|
|
412
402
|
else {
|
|
413
|
-
logger.warn(`
|
|
403
|
+
logger.warn(`Compile failed: ${compileResult.error}`);
|
|
414
404
|
}
|
|
415
405
|
}
|
|
416
406
|
logger.newline();
|
|
417
|
-
logger.
|
|
407
|
+
logger.log(` ${logger.bold('Next steps')}`);
|
|
418
408
|
const relDir = path.relative(process.cwd(), config.targetDir);
|
|
419
409
|
const displayDir = !relDir || relDir === '.' ? null : relDir.startsWith('../../') ? config.targetDir : relDir;
|
|
420
410
|
if (displayDir) {
|