@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
package/dist/cli/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import * as path from 'node:path';
|
|
|
11
11
|
// Load built-in extensions (CI/CD, etc.) before any commands run
|
|
12
12
|
import '../extensions/index.js';
|
|
13
13
|
import { Command, Option } from 'commander';
|
|
14
|
+
import { parseIntStrict } from './utils/parse-int-strict.js';
|
|
14
15
|
import { logger } from './utils/logger.js';
|
|
15
16
|
import { getErrorMessage } from '../utils/error-utils.js';
|
|
16
17
|
const version = typeof __CLI_VERSION__ !== 'undefined' ? __CLI_VERSION__ : '0.0.0-dev';
|
|
@@ -71,14 +72,13 @@ program
|
|
|
71
72
|
.option('-w, --workflow <name>', 'Specific workflow name to compile')
|
|
72
73
|
.addOption(new Option('-f, --format <format>', 'Module format').choices(['esm', 'cjs', 'auto']).default('auto'))
|
|
73
74
|
.option('--strict', 'Treat type coercion warnings as errors', false)
|
|
74
|
-
.option('--inline-runtime', 'Force inline runtime even when @synergenius/flow-weaver package is installed', false)
|
|
75
75
|
.option('--clean', 'Omit redundant @param/@returns annotations from compiled output', false)
|
|
76
76
|
.option('--target <target>', 'Compilation target: typescript (default) or a registered extension target')
|
|
77
77
|
.option('--cron <schedule>', 'Set cron trigger schedule')
|
|
78
78
|
.option('--serve', 'Generate serve() handler for HTTP event reception')
|
|
79
79
|
.option('--framework <name>', 'Framework adapter for serve handler (next, express, hono, fastify, remix)')
|
|
80
80
|
.option('--typed-events', 'Generate Zod event schemas from workflow @param annotations')
|
|
81
|
-
.option('--retries <n>', 'Number of retries per function',
|
|
81
|
+
.option('--retries <n>', 'Number of retries per function', parseIntStrict)
|
|
82
82
|
.option('--timeout <duration>', 'Function timeout (e.g. "30m", "1h")')
|
|
83
83
|
.action(wrapAction(async (input, options) => {
|
|
84
84
|
const { compileCommand } = await import('./commands/compile.js');
|
|
@@ -115,7 +115,7 @@ program
|
|
|
115
115
|
program
|
|
116
116
|
.command('diagram <input>')
|
|
117
117
|
.description('Generate SVG or interactive HTML diagram of a workflow')
|
|
118
|
-
.
|
|
118
|
+
.addOption(new Option('-t, --theme <theme>', 'Color theme').choices(['dark', 'light']).default('dark'))
|
|
119
119
|
.option('--width <pixels>', 'SVG width in pixels')
|
|
120
120
|
.option('-p, --padding <pixels>', 'Canvas padding in pixels')
|
|
121
121
|
.option('--no-port-labels', 'Hide data type labels on ports')
|
|
@@ -190,7 +190,6 @@ program
|
|
|
190
190
|
.option('--with-weaver', 'Install Weaver AI assistant')
|
|
191
191
|
.option('--no-weaver', 'Skip Weaver installation')
|
|
192
192
|
.option('--force', 'Overwrite existing files', false)
|
|
193
|
-
.option('--json', 'Output results as JSON', false)
|
|
194
193
|
.action(wrapAction(async (directory, options) => {
|
|
195
194
|
const { initCommand } = await import('./commands/init.js');
|
|
196
195
|
await initCommand(directory, options);
|
|
@@ -224,8 +223,6 @@ program
|
|
|
224
223
|
.option('--once', 'Run once then exit', false)
|
|
225
224
|
.option('--json', 'Output result as JSON', false)
|
|
226
225
|
.option('--target <target>', 'Compilation target (default: typescript)')
|
|
227
|
-
.option('--framework <framework>', 'Framework for serve handler', 'express')
|
|
228
|
-
.option('--port <port>', 'Port for dev server', (v) => parseInt(v, 10), 3000)
|
|
229
226
|
.action(wrapAction(async (input, options) => {
|
|
230
227
|
const { devCommand } = await import('./commands/dev.js');
|
|
231
228
|
await devCommand(input, options);
|
|
@@ -255,7 +252,7 @@ const createCmd = program.command('create').description('Create workflows or nod
|
|
|
255
252
|
createCmd
|
|
256
253
|
.command('workflow <template> <file>')
|
|
257
254
|
.description('Create a workflow from a template')
|
|
258
|
-
.option('-l, --line <number>', 'Insert at specific line number',
|
|
255
|
+
.option('-l, --line <number>', 'Insert at specific line number', parseIntStrict)
|
|
259
256
|
.option('-a, --async', 'Generate an async workflow', false)
|
|
260
257
|
.option('-p, --preview', 'Preview generated code without writing', false)
|
|
261
258
|
.option('--provider <provider>', 'LLM provider (openai, anthropic, ollama, mock)')
|
|
@@ -272,7 +269,7 @@ createCmd
|
|
|
272
269
|
createCmd
|
|
273
270
|
.command('node <name> <file>')
|
|
274
271
|
.description('Create a node type from a template')
|
|
275
|
-
.option('-l, --line <number>', 'Insert at specific line number',
|
|
272
|
+
.option('-l, --line <number>', 'Insert at specific line number', parseIntStrict)
|
|
276
273
|
.option('-t, --template <template>', 'Node template to use', 'transformer')
|
|
277
274
|
.option('-p, --preview', 'Preview generated code without writing', false)
|
|
278
275
|
.option('--strategy <strategy>', 'Template strategy (e.g. mock, callback, webhook)')
|
|
@@ -414,7 +411,7 @@ program
|
|
|
414
411
|
.option('-t, --trace', 'Include execution trace events')
|
|
415
412
|
.option('-s, --stream', 'Stream trace events in real-time')
|
|
416
413
|
.option('--json', 'Output result as JSON', false)
|
|
417
|
-
.option('--timeout <ms>', 'Execution timeout in milliseconds',
|
|
414
|
+
.option('--timeout <ms>', 'Execution timeout in milliseconds', parseIntStrict)
|
|
418
415
|
.option('--mocks <json>', 'Mock config for built-in nodes (events, invocations, agents, fast) as JSON')
|
|
419
416
|
.option('--mocks-file <path>', 'Path to JSON file with mock config for built-in nodes')
|
|
420
417
|
.option('-d, --debug', 'Start in step-through debug mode')
|
|
@@ -439,7 +436,7 @@ program
|
|
|
439
436
|
.action(wrapAction(async (directory, options) => {
|
|
440
437
|
const { serveCommand } = await import('./commands/serve.js');
|
|
441
438
|
await serveCommand(directory, {
|
|
442
|
-
port:
|
|
439
|
+
port: parseIntStrict(options.port),
|
|
443
440
|
host: options.host,
|
|
444
441
|
watch: options.watch,
|
|
445
442
|
production: options.production,
|
|
@@ -455,7 +452,8 @@ program
|
|
|
455
452
|
.requiredOption('-t, --target <target>', 'Target platform (install target packs via marketplace)')
|
|
456
453
|
.requiredOption('-o, --output <path>', 'Output directory')
|
|
457
454
|
.option('-w, --workflow <name>', 'Specific workflow name to export')
|
|
458
|
-
.option('-p, --production', 'Production mode',
|
|
455
|
+
.option('-p, --production', 'Production mode (no debug events)', false)
|
|
456
|
+
.option('--bundle', 'Bundle node types into the output', false)
|
|
459
457
|
.option('--dry-run', 'Preview without writing files', false)
|
|
460
458
|
.option('--multi', 'Export all workflows in file as a single multi-workflow service', false)
|
|
461
459
|
.option('--workflows <names>', 'Comma-separated list of workflows to export (used with --multi)')
|
|
@@ -625,7 +623,7 @@ marketCmd
|
|
|
625
623
|
.option('--json', 'Output as JSON', false)
|
|
626
624
|
.action(wrapAction(async (query, options) => {
|
|
627
625
|
const { marketSearchCommand } = await import('./commands/market.js');
|
|
628
|
-
await marketSearchCommand(query, { ...options, limit:
|
|
626
|
+
await marketSearchCommand(query, { ...options, limit: parseIntStrict(options.limit) });
|
|
629
627
|
}));
|
|
630
628
|
marketCmd
|
|
631
629
|
.command('list')
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Postinstall welcome message.
|
|
3
|
+
*
|
|
4
|
+
* Context-aware, shown once after npm install.
|
|
5
|
+
* Silent in CI. No telemetry, no file modifications, no dark patterns.
|
|
6
|
+
*/
|
|
7
|
+
export type InstallContext = 'ci' | 'global' | 'typescript' | 'existing' | 'project';
|
|
8
|
+
/**
|
|
9
|
+
* Detect the installation context based on cwd and environment variables.
|
|
10
|
+
*/
|
|
11
|
+
export declare function detectContext(cwd: string, env: Record<string, string | undefined>): InstallContext;
|
|
12
|
+
/**
|
|
13
|
+
* Format the welcome message for the detected context.
|
|
14
|
+
*/
|
|
15
|
+
export declare function formatMessage(context: InstallContext): string;
|
|
16
|
+
//# sourceMappingURL=postinstall.d.ts.map
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Postinstall welcome message.
|
|
3
|
+
*
|
|
4
|
+
* Context-aware, shown once after npm install.
|
|
5
|
+
* Silent in CI. No telemetry, no file modifications, no dark patterns.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
const CI_ENV_VARS = ['CI', 'CONTINUOUS_INTEGRATION', 'BUILD_NUMBER', 'GITHUB_ACTIONS', 'GITLAB_CI', 'CIRCLECI', 'JENKINS_URL', 'CODEBUILD_BUILD_ID'];
|
|
10
|
+
/**
|
|
11
|
+
* Scan .ts files in a directory (non-recursive) for @flowWeaver annotations.
|
|
12
|
+
*/
|
|
13
|
+
function dirHasFlowWeaver(dir) {
|
|
14
|
+
try {
|
|
15
|
+
const entries = fs.readdirSync(dir);
|
|
16
|
+
for (const entry of entries) {
|
|
17
|
+
if (!entry.endsWith('.ts') || entry.endsWith('.d.ts'))
|
|
18
|
+
continue;
|
|
19
|
+
try {
|
|
20
|
+
const content = fs.readFileSync(path.join(dir, entry), 'utf8');
|
|
21
|
+
if (content.includes('@flowWeaver'))
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Permission error or similar — skip
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Directory doesn't exist or can't be read
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Scan .ts files in a directory (non-recursive) for existence.
|
|
36
|
+
*/
|
|
37
|
+
function dirHasTsFiles(dir) {
|
|
38
|
+
try {
|
|
39
|
+
const entries = fs.readdirSync(dir);
|
|
40
|
+
return entries.some((e) => e.endsWith('.ts') && !e.endsWith('.d.ts'));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Detect the installation context based on cwd and environment variables.
|
|
48
|
+
*/
|
|
49
|
+
export function detectContext(cwd, env) {
|
|
50
|
+
// CI — always silent
|
|
51
|
+
if (CI_ENV_VARS.some((v) => env[v])) {
|
|
52
|
+
return 'ci';
|
|
53
|
+
}
|
|
54
|
+
// No package.json — likely global install or outside a project
|
|
55
|
+
if (!fs.existsSync(path.join(cwd, 'package.json'))) {
|
|
56
|
+
return 'global';
|
|
57
|
+
}
|
|
58
|
+
// Check for TypeScript project (tsconfig.json as proxy)
|
|
59
|
+
const hasTsConfig = fs.existsSync(path.join(cwd, 'tsconfig.json'));
|
|
60
|
+
if (!hasTsConfig) {
|
|
61
|
+
return 'project';
|
|
62
|
+
}
|
|
63
|
+
// Check for existing @flowWeaver usage (top-level and src/ only)
|
|
64
|
+
if (dirHasFlowWeaver(cwd) || dirHasFlowWeaver(path.join(cwd, 'src'))) {
|
|
65
|
+
return 'existing';
|
|
66
|
+
}
|
|
67
|
+
// TypeScript project without @flowWeaver
|
|
68
|
+
if (dirHasTsFiles(cwd) || dirHasTsFiles(path.join(cwd, 'src'))) {
|
|
69
|
+
return 'typescript';
|
|
70
|
+
}
|
|
71
|
+
// Has tsconfig but no .ts files yet
|
|
72
|
+
return 'typescript';
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Format the welcome message for the detected context.
|
|
76
|
+
*/
|
|
77
|
+
export function formatMessage(context) {
|
|
78
|
+
switch (context) {
|
|
79
|
+
case 'ci':
|
|
80
|
+
return '';
|
|
81
|
+
case 'global':
|
|
82
|
+
return [
|
|
83
|
+
'',
|
|
84
|
+
' flow-weaver installed \u2713',
|
|
85
|
+
'',
|
|
86
|
+
' Create a project: fw init my-project',
|
|
87
|
+
' Or try it now: fw create workflow hello-world',
|
|
88
|
+
'',
|
|
89
|
+
].join('\n');
|
|
90
|
+
case 'typescript':
|
|
91
|
+
return [
|
|
92
|
+
'',
|
|
93
|
+
' flow-weaver installed \u2713',
|
|
94
|
+
'',
|
|
95
|
+
' Add above any function: /** @flowWeaver nodeType */',
|
|
96
|
+
' Then compile: fw compile src/',
|
|
97
|
+
'',
|
|
98
|
+
].join('\n');
|
|
99
|
+
case 'existing':
|
|
100
|
+
return [
|
|
101
|
+
'',
|
|
102
|
+
' flow-weaver updated \u2713',
|
|
103
|
+
'',
|
|
104
|
+
' Check health: fw doctor',
|
|
105
|
+
'',
|
|
106
|
+
].join('\n');
|
|
107
|
+
case 'project':
|
|
108
|
+
return [
|
|
109
|
+
'',
|
|
110
|
+
' flow-weaver installed \u2713',
|
|
111
|
+
'',
|
|
112
|
+
' Get started: fw init',
|
|
113
|
+
'',
|
|
114
|
+
].join('\n');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Entry point logic lives in scripts/postinstall.cjs (standalone CJS, no build step).
|
|
118
|
+
// This module is the testable source of truth for the detection/formatting logic.
|
|
119
|
+
//# sourceMappingURL=postinstall.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict integer parser for CLI options.
|
|
3
|
+
* Unlike parseInt, rejects partial matches ("12abc") and non-numeric values.
|
|
4
|
+
* Throws a clear error instead of silently returning NaN.
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseIntStrict(value: string): number;
|
|
7
|
+
//# sourceMappingURL=parse-int-strict.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict integer parser for CLI options.
|
|
3
|
+
* Unlike parseInt, rejects partial matches ("12abc") and non-numeric values.
|
|
4
|
+
* Throws a clear error instead of silently returning NaN.
|
|
5
|
+
*/
|
|
6
|
+
export function parseIntStrict(value) {
|
|
7
|
+
const trimmed = value.trim();
|
|
8
|
+
if (trimmed === '') {
|
|
9
|
+
throw new Error(`"${value}" is not a valid number`);
|
|
10
|
+
}
|
|
11
|
+
const n = Number(trimmed);
|
|
12
|
+
if (!Number.isFinite(n) || !Number.isInteger(n)) {
|
|
13
|
+
throw new Error(`"${value}" is not a valid number`);
|
|
14
|
+
}
|
|
15
|
+
return n;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=parse-int-strict.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform safe file writing utilities.
|
|
3
|
+
*
|
|
4
|
+
* - Creates parent directories automatically
|
|
5
|
+
* - Provides clear error messages for permission issues
|
|
6
|
+
* - Works on Windows, macOS, and Linux
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Write content to a file, creating parent directories as needed.
|
|
10
|
+
* Throws a clear error on permission issues.
|
|
11
|
+
*/
|
|
12
|
+
export declare function safeWriteFile(filePath: string, content: string, encoding?: BufferEncoding): void;
|
|
13
|
+
/**
|
|
14
|
+
* Append content to a file, creating the file and parent directories as needed.
|
|
15
|
+
* Throws a clear error on permission issues.
|
|
16
|
+
*/
|
|
17
|
+
export declare function safeAppendFile(filePath: string, content: string, encoding?: BufferEncoding): void;
|
|
18
|
+
//# sourceMappingURL=safe-write.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform safe file writing utilities.
|
|
3
|
+
*
|
|
4
|
+
* - Creates parent directories automatically
|
|
5
|
+
* - Provides clear error messages for permission issues
|
|
6
|
+
* - Works on Windows, macOS, and Linux
|
|
7
|
+
*/
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
function ensureDir(dirPath) {
|
|
11
|
+
if (!fs.existsSync(dirPath)) {
|
|
12
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function wrapIOError(filePath, error) {
|
|
16
|
+
if (error instanceof Error) {
|
|
17
|
+
const code = error.code;
|
|
18
|
+
if (code === 'EACCES' || code === 'EPERM') {
|
|
19
|
+
throw new Error(`Permission denied: cannot write to ${filePath}. Check file/directory permissions.`);
|
|
20
|
+
}
|
|
21
|
+
if (code === 'EROFS') {
|
|
22
|
+
throw new Error(`Read-only file system: cannot write to ${filePath}.`);
|
|
23
|
+
}
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Write content to a file, creating parent directories as needed.
|
|
30
|
+
* Throws a clear error on permission issues.
|
|
31
|
+
*/
|
|
32
|
+
export function safeWriteFile(filePath, content, encoding = 'utf8') {
|
|
33
|
+
try {
|
|
34
|
+
ensureDir(path.dirname(filePath));
|
|
35
|
+
fs.writeFileSync(filePath, content, encoding);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
wrapIOError(filePath, error);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Append content to a file, creating the file and parent directories as needed.
|
|
43
|
+
* Throws a clear error on permission issues.
|
|
44
|
+
*/
|
|
45
|
+
export function safeAppendFile(filePath, content, encoding = 'utf8') {
|
|
46
|
+
try {
|
|
47
|
+
ensureDir(path.dirname(filePath));
|
|
48
|
+
fs.appendFileSync(filePath, content, encoding);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
wrapIOError(filePath, error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=safe-write.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.24.0";
|
|
2
2
|
//# sourceMappingURL=generated-version.d.ts.map
|
|
@@ -456,7 +456,7 @@ export function generateControlFlowWithExecutionContext(workflow, nodeTypes, isA
|
|
|
456
456
|
branchNeedsClose = true;
|
|
457
457
|
}
|
|
458
458
|
}
|
|
459
|
-
generateBranchingNodeCode(instance, nodeType, workflow, nodeTypes, nodeRegion, availableVars, generatedNodes, lines, branchIndent, false, branchingNodes, branchRegions, isAsync, 'ctx', bundleMode, promotedPreDeclared, branchingNodesNeedingSuccessFlag.has(instanceId), production);
|
|
459
|
+
generateBranchingNodeCode(instance, nodeType, workflow, nodeTypes, nodeRegion, availableVars, generatedNodes, lines, branchIndent, false, branchingNodes, branchRegions, isAsync, 'ctx', bundleMode, promotedPreDeclared, branchingNodesNeedingSuccessFlag.has(instanceId) || topLevelSuccessFlags.has(toValidIdentifier(instanceId)), production);
|
|
460
460
|
if (branchNeedsClose) {
|
|
461
461
|
lines.push(` }`);
|
|
462
462
|
}
|
|
@@ -885,8 +885,8 @@ function generateBranchingChainCode(chain, workflow, nodeTypes, branchingNodes,
|
|
|
885
885
|
const preDeclaredFlags = new Set(alreadyDeclaredFlags);
|
|
886
886
|
for (let i = 0; i < chain.length; i++) {
|
|
887
887
|
const isLast = i === chain.length - 1;
|
|
888
|
-
|
|
889
|
-
|
|
888
|
+
const safeId = toValidIdentifier(chain[i]);
|
|
889
|
+
if (!isLast || forceTrackSuccessNodes.has(chain[i]) || alreadyDeclaredFlags.has(safeId)) {
|
|
890
890
|
if (!alreadyDeclaredFlags.has(safeId)) {
|
|
891
891
|
lines.push(`${indent}let ${safeId}_success = false;`);
|
|
892
892
|
}
|
|
@@ -924,7 +924,7 @@ function generateBranchingChainCode(chain, workflow, nodeTypes, branchingNodes,
|
|
|
924
924
|
lines.push(`${indent}if (${guardCondition}) {`);
|
|
925
925
|
}
|
|
926
926
|
const nodeIndent = hasGuard ? indent + ' ' : indent;
|
|
927
|
-
generateBranchingNodeCode(instance, nodeType, workflow, nodeTypes, effectiveRegion, availableVars, generatedNodes, lines, nodeIndent, false, branchingNodes, branchRegions, isAsync, ctxVar, bundleMode, preDeclaredFlags, !isLast || forceTrackSuccessNodes.has(chain[i]), // forceTrackSuccess for non-last chain nodes or nodes with
|
|
927
|
+
generateBranchingNodeCode(instance, nodeType, workflow, nodeTypes, effectiveRegion, availableVars, generatedNodes, lines, nodeIndent, false, branchingNodes, branchRegions, isAsync, ctxVar, bundleMode, preDeclaredFlags, !isLast || forceTrackSuccessNodes.has(chain[i]) || alreadyDeclaredFlags.has(safeId), // forceTrackSuccess for non-last chain nodes, nodes with promoted dependents, or nodes with pre-declared flags
|
|
928
928
|
production);
|
|
929
929
|
// Generate scoped children for this chain node
|
|
930
930
|
generateScopedChildrenExecution(instance, nodeType, workflow, nodeTypes, generatedNodes, availableVars, lines, nodeIndent, branchingNodes, branchRegions, isAsync, bundleMode, production);
|
|
@@ -1225,7 +1225,8 @@ bundleMode = false, preDeclaredSuccessFlags = new Set(), forceTrackSuccess = fal
|
|
|
1225
1225
|
lines.push(`${indent} let ${nestedSafeId}_success = false;`);
|
|
1226
1226
|
nestedPreDeclared.add(nestedSafeId);
|
|
1227
1227
|
}
|
|
1228
|
-
generateBranchingNodeCode(inst, nodeType, workflow, allNodeTypes, nestedRegion, successVars, generatedNodes, lines, `${indent} `, false, branchingNodes, branchRegions, isAsync, ctxVar, bundleMode, nestedPreDeclared,
|
|
1228
|
+
generateBranchingNodeCode(inst, nodeType, workflow, allNodeTypes, nestedRegion, successVars, generatedNodes, lines, `${indent} `, false, branchingNodes, branchRegions, isAsync, ctxVar, bundleMode, nestedPreDeclared, nestedPreDeclared.has(nestedSafeId), // force tracking if flag was pre-declared at higher scope
|
|
1229
|
+
production);
|
|
1229
1230
|
successExecutedNodes.push(instanceId);
|
|
1230
1231
|
nestedRegion.successNodes.forEach((n) => successExecutedNodes.push(n));
|
|
1231
1232
|
nestedRegion.failureNodes.forEach((n) => successExecutedNodes.push(n));
|
|
@@ -1275,7 +1276,8 @@ bundleMode = false, preDeclaredSuccessFlags = new Set(), forceTrackSuccess = fal
|
|
|
1275
1276
|
lines.push(`${indent} let ${nestedSafeId}_success = false;`);
|
|
1276
1277
|
nestedPreDeclared.add(nestedSafeId);
|
|
1277
1278
|
}
|
|
1278
|
-
generateBranchingNodeCode(inst, nodeType, workflow, allNodeTypes, nestedRegion, failureVars, generatedNodes, lines, `${indent} `, false, branchingNodes, branchRegions, isAsync, ctxVar, bundleMode, nestedPreDeclared,
|
|
1279
|
+
generateBranchingNodeCode(inst, nodeType, workflow, allNodeTypes, nestedRegion, failureVars, generatedNodes, lines, `${indent} `, false, branchingNodes, branchRegions, isAsync, ctxVar, bundleMode, nestedPreDeclared, nestedPreDeclared.has(nestedSafeId), // force tracking if flag was pre-declared at higher scope
|
|
1280
|
+
production);
|
|
1279
1281
|
failureExecutedNodes.push(instanceId);
|
|
1280
1282
|
nestedRegion.successNodes.forEach((n) => failureExecutedNodes.push(n));
|
|
1281
1283
|
nestedRegion.failureNodes.forEach((n) => failureExecutedNodes.push(n));
|
|
@@ -67,7 +67,6 @@ fw compile <input> [options]
|
|
|
67
67
|
| `-w, --workflow-name <name>` | Specific workflow name | all |
|
|
68
68
|
| `-f, --format <format>` | Module format: `esm`, `cjs`, `auto` | `auto` |
|
|
69
69
|
| `--strict` | Type coercion warnings become errors | `false` |
|
|
70
|
-
| `--inline-runtime` | Force inline runtime | `false` |
|
|
71
70
|
| `--clean` | Omit redundant @param/@returns | `false` |
|
|
72
71
|
| `--target <target>` | `typescript` or `inngest` | `typescript` |
|
|
73
72
|
| `--cron <schedule>` | Cron schedule (Inngest only) | — |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: Compilation
|
|
3
3
|
description: How compilation works, TypeScript and Inngest targets, compile options, and serve handler generation
|
|
4
|
-
keywords: [compile, compilation, target, typescript, inngest, production, source-map, format, strict,
|
|
4
|
+
keywords: [compile, compilation, target, typescript, inngest, production, source-map, format, strict, clean, serve, framework, step.run, durable, trigger, cancelOn, retries, timeout, throttle, cron, markers]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Compilation
|
|
@@ -59,7 +59,7 @@ fw compile workflow.ts
|
|
|
59
59
|
Generates code like:
|
|
60
60
|
```typescript
|
|
61
61
|
// @flow-weaver-runtime — start
|
|
62
|
-
|
|
62
|
+
// (inline runtime: GeneratedExecutionContext, CancellationError, types)
|
|
63
63
|
// @flow-weaver-runtime — end
|
|
64
64
|
|
|
65
65
|
export function myWorkflow(params: { data: string }) {
|
|
@@ -272,14 +272,6 @@ fw compile workflow.ts --strict
|
|
|
272
272
|
|
|
273
273
|
Equivalent to adding `@strictTypes` to the workflow annotation.
|
|
274
274
|
|
|
275
|
-
### Inline Runtime (`--inline-runtime`)
|
|
276
|
-
|
|
277
|
-
Force inline runtime code even when `@synergenius/flow-weaver` is installed as a dependency. Normally the compiler generates an import; this flag embeds the runtime directly.
|
|
278
|
-
|
|
279
|
-
```bash
|
|
280
|
-
fw compile workflow.ts --inline-runtime
|
|
281
|
-
```
|
|
282
|
-
|
|
283
275
|
### Clean Output (`--clean`)
|
|
284
276
|
|
|
285
277
|
Omit redundant `@param`/`@returns` annotations from the compiled output. Produces cleaner generated code.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synergenius/flow-weaver",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Deterministic workflow compiler for AI agents. Compiles to standalone TypeScript, no runtime dependencies.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -108,6 +108,7 @@
|
|
|
108
108
|
"dist",
|
|
109
109
|
"!dist/**/*.map",
|
|
110
110
|
"docs/reference",
|
|
111
|
+
"scripts/postinstall.cjs",
|
|
111
112
|
"README.md",
|
|
112
113
|
"LICENSE"
|
|
113
114
|
],
|
|
@@ -131,7 +132,8 @@
|
|
|
131
132
|
"docs:serve": "npm run docs && npx http-server docs/api -c-1 -o",
|
|
132
133
|
"prepare": "npm run build && husky || true",
|
|
133
134
|
"cli": "tsx src/cli/index.ts",
|
|
134
|
-
"diagram": "tsx scripts/generate-diagram.ts"
|
|
135
|
+
"diagram": "tsx scripts/generate-diagram.ts",
|
|
136
|
+
"postinstall": "node scripts/postinstall.cjs"
|
|
135
137
|
},
|
|
136
138
|
"keywords": [
|
|
137
139
|
"flow-weaver",
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Postinstall welcome message — context-aware, shown after npm install.
|
|
4
|
+
*
|
|
5
|
+
* This is a standalone CJS script (no build step, no ESM, no dependencies).
|
|
6
|
+
* The logic is duplicated from src/cli/postinstall.ts which has full test coverage.
|
|
7
|
+
*
|
|
8
|
+
* Silent in CI. No telemetry. No file modifications. No dark patterns.
|
|
9
|
+
* Never fails the install.
|
|
10
|
+
*/
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const CI_VARS = ['CI', 'CONTINUOUS_INTEGRATION', 'BUILD_NUMBER', 'GITHUB_ACTIONS', 'GITLAB_CI', 'CIRCLECI', 'JENKINS_URL', 'CODEBUILD_BUILD_ID'];
|
|
18
|
+
if (CI_VARS.some(v => process.env[v])) process.exit(0);
|
|
19
|
+
|
|
20
|
+
const cwd = process.env.INIT_CWD || process.cwd();
|
|
21
|
+
|
|
22
|
+
function hasPkg() { try { return fs.existsSync(path.join(cwd, 'package.json')); } catch { return false; } }
|
|
23
|
+
function hasTsConfig() { try { return fs.existsSync(path.join(cwd, 'tsconfig.json')); } catch { return false; } }
|
|
24
|
+
|
|
25
|
+
function dirHasTs(dir) {
|
|
26
|
+
try { return fs.readdirSync(dir).some(f => f.endsWith('.ts') && !f.endsWith('.d.ts')); }
|
|
27
|
+
catch { return false; }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function dirHasFlowWeaver(dir) {
|
|
31
|
+
try {
|
|
32
|
+
for (const f of fs.readdirSync(dir)) {
|
|
33
|
+
if (!f.endsWith('.ts') || f.endsWith('.d.ts')) continue;
|
|
34
|
+
try { if (fs.readFileSync(path.join(dir, f), 'utf8').includes('@flowWeaver')) return true; }
|
|
35
|
+
catch { /* skip */ }
|
|
36
|
+
}
|
|
37
|
+
} catch { /* skip */ }
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let msg;
|
|
42
|
+
|
|
43
|
+
if (!hasPkg()) {
|
|
44
|
+
// Global install or outside a project
|
|
45
|
+
msg = [
|
|
46
|
+
'',
|
|
47
|
+
' flow-weaver installed \u2713',
|
|
48
|
+
'',
|
|
49
|
+
' Create a project: fw init my-project',
|
|
50
|
+
' Or try it now: fw create workflow hello-world',
|
|
51
|
+
'',
|
|
52
|
+
].join('\n');
|
|
53
|
+
} else if (!hasTsConfig()) {
|
|
54
|
+
// Project without TypeScript
|
|
55
|
+
msg = [
|
|
56
|
+
'',
|
|
57
|
+
' flow-weaver installed \u2713',
|
|
58
|
+
'',
|
|
59
|
+
' Get started: fw init',
|
|
60
|
+
'',
|
|
61
|
+
].join('\n');
|
|
62
|
+
} else if (dirHasFlowWeaver(cwd) || dirHasFlowWeaver(path.join(cwd, 'src'))) {
|
|
63
|
+
// Existing flow-weaver project
|
|
64
|
+
msg = [
|
|
65
|
+
'',
|
|
66
|
+
' flow-weaver updated \u2713',
|
|
67
|
+
'',
|
|
68
|
+
' Check health: fw doctor',
|
|
69
|
+
'',
|
|
70
|
+
].join('\n');
|
|
71
|
+
} else {
|
|
72
|
+
// TypeScript project, no @flowWeaver yet
|
|
73
|
+
msg = [
|
|
74
|
+
'',
|
|
75
|
+
' flow-weaver installed \u2713',
|
|
76
|
+
'',
|
|
77
|
+
' Add above any function: /** @flowWeaver nodeType */',
|
|
78
|
+
' Then compile: fw compile src/',
|
|
79
|
+
'',
|
|
80
|
+
].join('\n');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
process.stderr.write(msg);
|
|
84
|
+
} catch {
|
|
85
|
+
// Never fail the install
|
|
86
|
+
}
|