@fnet/cli 0.3.12 → 0.3.14
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/fnet/index.js +19 -14
- package/package.json +2 -2
- package/template/fnet/node/src/default/blocks/for.js.njk +33 -13
- package/template/fnet/node/src/default/blocks/parallel.js.njk +21 -14
- package/template/fnet/node/src/default/blocks/retry.js.njk +36 -29
- package/template/fnet/node/src/default/blocks/steps.js.njk +16 -9
- package/template/fnet/node/src/default/blocks/switch.js.njk +8 -1
- package/template/fnet/node/src/default/macros/block-next.js.njk +1 -0
- package/template/fnet/node/src/default/macros/block-run-header.js.njk +13 -1
- package/template/fnet/node/src/default/workflow.js.njk +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fnet/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist",
|
|
6
6
|
"template"
|
|
@@ -36,7 +36,6 @@
|
|
|
36
36
|
"@fnet/auto-conda-env": "^0.2.1",
|
|
37
37
|
"@fnet/config": "^0.2.29",
|
|
38
38
|
"@fnet/dir-zipper": "^0.1.8",
|
|
39
|
-
"@fnet/expression": "^0.1.30",
|
|
40
39
|
"@fnet/files-to-gcs": "^0.3.12",
|
|
41
40
|
"@fnet/key-value-transformer": "^0.1.4",
|
|
42
41
|
"@fnet/npm-list-versions": "^0.1.35",
|
|
@@ -87,6 +86,7 @@
|
|
|
87
86
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
88
87
|
"@rollup/plugin-replace": "^6.0.2",
|
|
89
88
|
"@rollup/plugin-terser": "^0.4.4",
|
|
89
|
+
"peggy": "^5.0.6",
|
|
90
90
|
"rollup": "^4.52.5"
|
|
91
91
|
}
|
|
92
92
|
}
|
|
@@ -35,31 +35,51 @@ export default function Block(context) {
|
|
|
35
35
|
{% endif %}
|
|
36
36
|
{% endif%}
|
|
37
37
|
|
|
38
|
-
for await(const {{context.transform.for.
|
|
39
|
-
|
|
38
|
+
for await(const {{context.transform.for.as | safe}} of {{context.transform.for.in | safe}}){
|
|
39
|
+
// Create Proxy for closure-like variable resolution
|
|
40
|
+
// Local variable shadows parent, but parent scope accessible via Proxy fallback
|
|
41
|
+
const localForVar = { {{context.transform.for.as | safe}}: {{context.transform.for.as | safe}} };
|
|
42
|
+
const forProxy = new Proxy(localForVar, {
|
|
43
|
+
get(target, prop) {
|
|
44
|
+
// Check local scope first (shadowing)
|
|
45
|
+
if (prop in target) return target[prop];
|
|
46
|
+
// Fallback to parent scope
|
|
47
|
+
return c.for?.[prop];
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Create new context with updated for
|
|
52
|
+
const loopContext = { ...c, for: forProxy };
|
|
40
53
|
|
|
41
54
|
{% if childs.length %}
|
|
42
|
-
let current= new {{childs[0].codeKey}}({ parent:_this, engine, flow, caller:
|
|
55
|
+
let current= new {{childs[0].codeKey}}({ parent:_this, engine, flow, caller:loopContext });
|
|
43
56
|
let currentArgs=args;
|
|
44
57
|
|
|
45
58
|
do {
|
|
46
|
-
|
|
59
|
+
|
|
47
60
|
{% if workflow.parent.context.atom.doc.features.print_runners %}
|
|
48
61
|
console.log(new Date().toLocaleString(),' * ',_this.constructor.IndexKey,' -> ',current.constructor.IndexKey);
|
|
49
62
|
{% endif %}
|
|
50
63
|
|
|
51
|
-
|
|
52
|
-
|
|
64
|
+
try {
|
|
65
|
+
const nextBlock= typeof currentArgs==='undefined'? await current.run()
|
|
66
|
+
: Array.isArray(currentArgs)? await current.run.apply(current,currentArgs) : await current.run.call(current, currentArgs) ;
|
|
53
67
|
|
|
54
|
-
|
|
55
|
-
|
|
68
|
+
if(nextBlock?.type==='return') return resolve(nextBlock);
|
|
69
|
+
else if(nextBlock?.type!=='block') break;
|
|
56
70
|
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
if(nextBlock.toType.ParentTypeId!==_this.constructor.TypeId)
|
|
72
|
+
return resolve(nextBlock);
|
|
73
|
+
|
|
74
|
+
current=new nextBlock.toType({ parent:_this, engine, flow, caller:loopContext, onError, error });
|
|
75
|
+
currentArgs=nextBlock.input;
|
|
76
|
+
} catch (err) {
|
|
77
|
+
if (onError) {
|
|
78
|
+
return onError(err);
|
|
79
|
+
}
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
59
82
|
|
|
60
|
-
current=new nextBlock.toType({ parent:_this, engine, flow, caller:c, onError, error });
|
|
61
|
-
currentArgs=nextBlock.input;
|
|
62
|
-
|
|
63
83
|
} while(true);
|
|
64
84
|
|
|
65
85
|
{% endif %}
|
|
@@ -55,20 +55,27 @@ export default function Block(context) {
|
|
|
55
55
|
{% set strategy = context.transform.strategy | default('all') %}
|
|
56
56
|
let results;
|
|
57
57
|
|
|
58
|
-
{
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
58
|
+
try {
|
|
59
|
+
{% if strategy == 'race' %}
|
|
60
|
+
// Promise.race - first to complete wins
|
|
61
|
+
results = [await Promise.race(promises)];
|
|
62
|
+
{% elif strategy == 'any' %}
|
|
63
|
+
// Promise.any - first successful wins
|
|
64
|
+
results = [await Promise.any(promises)];
|
|
65
|
+
{% elif strategy == 'allsettled' %}
|
|
66
|
+
// Promise.allSettled - all complete, collect results
|
|
67
|
+
const settled = await Promise.allSettled(promises);
|
|
68
|
+
results = settled.map(r => r.status === 'fulfilled' ? r.value : null);
|
|
69
|
+
{% else %}
|
|
70
|
+
// Promise.all - all must succeed (default)
|
|
71
|
+
results = await Promise.all(promises);
|
|
72
|
+
{% endif %}
|
|
73
|
+
} catch (err) {
|
|
74
|
+
if (onError) {
|
|
75
|
+
return onError(err);
|
|
76
|
+
}
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
72
79
|
|
|
73
80
|
// Check if any child returned or jumped
|
|
74
81
|
for (const result of results) {
|
|
@@ -42,37 +42,44 @@ export default function Block(context){
|
|
|
42
42
|
let lastError;
|
|
43
43
|
let currentDelay = initialDelay;
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
currentDelay
|
|
45
|
+
try {
|
|
46
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
47
|
+
try {
|
|
48
|
+
// Execute child block
|
|
49
|
+
await retryableBlock.run();
|
|
50
|
+
|
|
51
|
+
// Success! Break out of retry loop
|
|
52
|
+
break;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
lastError = error;
|
|
55
|
+
|
|
56
|
+
// Last attempt failed - throw error
|
|
57
|
+
if (attempt === attempts) {
|
|
58
|
+
throw new Error(`Retry failed after ${attempts} attempts: ${error.message}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Wait before retry
|
|
62
|
+
await new Promise(resolve => setTimeout(resolve, currentDelay));
|
|
63
|
+
|
|
64
|
+
// Calculate next delay based on backoff strategy
|
|
65
|
+
if (backoff === 'exponential') {
|
|
66
|
+
currentDelay = currentDelay * 2;
|
|
67
|
+
} else if (backoff === 'linear') {
|
|
68
|
+
currentDelay = currentDelay + initialDelay;
|
|
69
|
+
}
|
|
70
|
+
// 'fixed' keeps currentDelay unchanged
|
|
71
|
+
|
|
72
|
+
// Apply maxDelay cap if specified
|
|
73
|
+
if (maxDelay && currentDelay > maxDelay) {
|
|
74
|
+
currentDelay = maxDelay;
|
|
75
|
+
}
|
|
74
76
|
}
|
|
75
77
|
}
|
|
78
|
+
} catch (err) {
|
|
79
|
+
if (onError) {
|
|
80
|
+
return onError(err);
|
|
81
|
+
}
|
|
82
|
+
throw err;
|
|
76
83
|
}
|
|
77
84
|
{% endif %}
|
|
78
85
|
|
|
@@ -25,22 +25,29 @@ export default function Block(context){
|
|
|
25
25
|
let currentArgs=args;
|
|
26
26
|
|
|
27
27
|
do {
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
{% if workflow.parent.context.atom.doc.features.print_runners %}
|
|
30
30
|
console.log(new Date().toLocaleString(),' * ',_this.constructor.IndexKey,' -> ',current.constructor.IndexKey);
|
|
31
31
|
{% endif %}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
try {
|
|
34
|
+
const nextBlock= typeof currentArgs==='undefined'? await current.run()
|
|
35
|
+
: Array.isArray(currentArgs)? await current.run.apply(current,currentArgs) : await current.run.call(current, currentArgs) ;
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
if(nextBlock?.type==='return') return resolve(nextBlock);
|
|
38
|
+
else if(nextBlock?.type!=='block') break;
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
if(nextBlock.toType.ParentTypeId!==_this.constructor.TypeId)
|
|
41
|
+
return resolve(nextBlock);
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
current=new nextBlock.toType({ parent:_this, engine, flow, caller:c, onError, error });
|
|
44
|
+
currentArgs=nextBlock.input;
|
|
45
|
+
} catch (err) {
|
|
46
|
+
if (onError) {
|
|
47
|
+
return onError(err);
|
|
48
|
+
}
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
44
51
|
|
|
45
52
|
} while(true);
|
|
46
53
|
|
|
@@ -51,7 +51,14 @@ export default function Block(context) {
|
|
|
51
51
|
console.log(new Date().toLocaleString(),' * ',_this.constructor.IndexKey,' -> ',current.constructor.IndexKey);
|
|
52
52
|
{% endif %}
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
try {
|
|
55
|
+
return resolve(Array.isArray(args)? await current.run.apply(current,args) : await current.run.call(current,args));
|
|
56
|
+
} catch (err) {
|
|
57
|
+
if (onError) {
|
|
58
|
+
return onError(err);
|
|
59
|
+
}
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
55
62
|
}
|
|
56
63
|
{% endfor%}
|
|
57
64
|
|
|
@@ -16,7 +16,19 @@ const form=null;
|
|
|
16
16
|
{% endif %}
|
|
17
17
|
|
|
18
18
|
{# closure #}
|
|
19
|
-
const c={
|
|
19
|
+
const c={
|
|
20
|
+
caller,
|
|
21
|
+
resolve,
|
|
22
|
+
reject,
|
|
23
|
+
args,
|
|
24
|
+
get form(){return form||caller?.form},
|
|
25
|
+
get module(){return m||caller?.module},
|
|
26
|
+
get for(){
|
|
27
|
+
// Proxy pattern for nested loop variable resolution with closure semantics
|
|
28
|
+
// Inner loops shadow outer loops, but parent scope is accessible via fallback
|
|
29
|
+
return caller?.for || {};
|
|
30
|
+
}
|
|
31
|
+
};
|
|
20
32
|
|
|
21
33
|
{% if workflow.parent.context.atom.doc.features.print_steps %}
|
|
22
34
|
console.log(new Date().toLocaleString(),'{{indexKey}}');
|
|
@@ -96,10 +96,10 @@ export default class Workflow extends Object {
|
|
|
96
96
|
if(nextBlock?.type==='return') return nextBlock;
|
|
97
97
|
else if(nextBlock?.type!=='block') break;
|
|
98
98
|
|
|
99
|
-
if(nextBlock.toType.ParentTypeId!==this.constructor.TypeId)
|
|
99
|
+
if(nextBlock.toType.ParentTypeId!==this.constructor.TypeId)
|
|
100
100
|
throw new Error('Unknown situation');
|
|
101
|
-
|
|
102
|
-
current=new nextBlock.toType({ parent:this, engine:this.engine, flow:this, caller:c });
|
|
101
|
+
|
|
102
|
+
current=new nextBlock.toType({ parent:this, engine:this.engine, flow:this, caller:c, onError:nextBlock.onError, error:nextBlock.error });
|
|
103
103
|
currentArgs=nextBlock.input;
|
|
104
104
|
|
|
105
105
|
} while(true);
|