@ductape/sdk 0.2.25 → 0.3.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/features/feature-control-flow.validator.d.ts +8 -0
- package/dist/features/feature-control-flow.validator.js +108 -0
- package/dist/features/feature-control-flow.validator.js.map +1 -0
- package/dist/features/features.service.js +11 -10
- package/dist/features/features.service.js.map +1 -1
- package/dist/features/types/features.types.d.ts +15 -8
- package/dist/features/types/features.types.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type FeatureControlFlowMode = 'portable' | 'legacy';
|
|
2
|
+
export interface FeatureControlFlowDiagnostic {
|
|
3
|
+
code: string;
|
|
4
|
+
message: string;
|
|
5
|
+
line: number;
|
|
6
|
+
column: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function inspectFeatureHandlerControlFlow(source: string, mode?: FeatureControlFlowMode): FeatureControlFlowDiagnostic[];
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.inspectFeatureHandlerControlFlow = inspectFeatureHandlerControlFlow;
|
|
4
|
+
const parser_1 = require("@babel/parser");
|
|
5
|
+
const NATIVE_BRANCH_NODES = new Map([
|
|
6
|
+
['IfStatement', ['FEATURE_NATIVE_IF', 'Use ctx.branch(ctx.when.*, { then, else }) instead of a native if statement.']],
|
|
7
|
+
['ConditionalExpression', ['FEATURE_NATIVE_TERNARY', 'Use ctx.branch() instead of a native ternary expression.']],
|
|
8
|
+
['SwitchStatement', ['FEATURE_NATIVE_SWITCH', 'Use explicit ctx.branch() paths instead of a native switch statement.']],
|
|
9
|
+
['ForStatement', ['FEATURE_NATIVE_LOOP', 'Move runtime-sized iteration into ctx.functions instead of a native loop.']],
|
|
10
|
+
['ForInStatement', ['FEATURE_NATIVE_LOOP', 'Move runtime-sized iteration into ctx.functions instead of a native loop.']],
|
|
11
|
+
['ForOfStatement', ['FEATURE_NATIVE_LOOP', 'Move runtime-sized iteration into ctx.functions instead of a native loop.']],
|
|
12
|
+
['WhileStatement', ['FEATURE_NATIVE_LOOP', 'Move runtime-sized iteration into ctx.functions instead of a native loop.']],
|
|
13
|
+
['DoWhileStatement', ['FEATURE_NATIVE_LOOP', 'Move runtime-sized iteration into ctx.functions instead of a native loop.']],
|
|
14
|
+
]);
|
|
15
|
+
const COLLECTION_METHODS = new Set([
|
|
16
|
+
'map', 'filter', 'find', 'findIndex', 'some', 'every', 'reduce', 'reduceRight', 'forEach',
|
|
17
|
+
]);
|
|
18
|
+
function memberName(node) {
|
|
19
|
+
var _a, _b;
|
|
20
|
+
if (!node || (node.type !== 'MemberExpression' && node.type !== 'OptionalMemberExpression'))
|
|
21
|
+
return undefined;
|
|
22
|
+
if (!node.computed && ((_a = node.property) === null || _a === void 0 ? void 0 : _a.type) === 'Identifier')
|
|
23
|
+
return node.property.name;
|
|
24
|
+
if (node.computed && ((_b = node.property) === null || _b === void 0 ? void 0 : _b.type) === 'StringLiteral')
|
|
25
|
+
return node.property.value;
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
function isMemberPath(node, objectName, propertyName) {
|
|
29
|
+
var _a;
|
|
30
|
+
return ((node === null || node === void 0 ? void 0 : node.type) === 'MemberExpression' || (node === null || node === void 0 ? void 0 : node.type) === 'OptionalMemberExpression') &&
|
|
31
|
+
((_a = node.object) === null || _a === void 0 ? void 0 : _a.type) === 'Identifier' && node.object.name === objectName &&
|
|
32
|
+
memberName(node) === propertyName;
|
|
33
|
+
}
|
|
34
|
+
function diagnostic(node, code, message) {
|
|
35
|
+
var _a, _b, _c, _d;
|
|
36
|
+
return { code, message, line: (_b = (_a = node.loc) === null || _a === void 0 ? void 0 : _a.start.line) !== null && _b !== void 0 ? _b : 1, column: ((_d = (_c = node.loc) === null || _c === void 0 ? void 0 : _c.start.column) !== null && _d !== void 0 ? _d : 0) + 1 };
|
|
37
|
+
}
|
|
38
|
+
function inspectFeatureHandlerControlFlow(source, mode = 'portable') {
|
|
39
|
+
var _a;
|
|
40
|
+
let root;
|
|
41
|
+
try {
|
|
42
|
+
root = (0, parser_1.parseExpression)(`(${source})`, { sourceType: 'module', plugins: ['typescript'] });
|
|
43
|
+
}
|
|
44
|
+
catch (firstError) {
|
|
45
|
+
try {
|
|
46
|
+
const wrapper = (0, parser_1.parseExpression)(`({${source}})`, { sourceType: 'module', plugins: ['typescript'] });
|
|
47
|
+
root = (_a = wrapper.properties) === null || _a === void 0 ? void 0 : _a[0];
|
|
48
|
+
}
|
|
49
|
+
catch (_b) {
|
|
50
|
+
return [{
|
|
51
|
+
code: 'FEATURE_HANDLER_PARSE_ERROR',
|
|
52
|
+
message: `Feature handler source could not be parsed: ${firstError instanceof Error ? firstError.message : String(firstError)}`,
|
|
53
|
+
line: 1,
|
|
54
|
+
column: 1,
|
|
55
|
+
}];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const diagnostics = [];
|
|
59
|
+
const seen = new Set();
|
|
60
|
+
const add = (node, code, message) => {
|
|
61
|
+
const item = diagnostic(node, code, message);
|
|
62
|
+
const key = `${item.code}:${item.line}:${item.column}`;
|
|
63
|
+
if (!seen.has(key)) {
|
|
64
|
+
seen.add(key);
|
|
65
|
+
diagnostics.push(item);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const visit = (node) => {
|
|
69
|
+
var _a;
|
|
70
|
+
if (!node || typeof node !== 'object')
|
|
71
|
+
return;
|
|
72
|
+
if (mode === 'portable') {
|
|
73
|
+
const native = NATIVE_BRANCH_NODES.get(node.type);
|
|
74
|
+
if (native)
|
|
75
|
+
add(node, native[0], native[1]);
|
|
76
|
+
if (node.type === 'LogicalExpression') {
|
|
77
|
+
add(node, 'FEATURE_NATIVE_LOGICAL_BRANCH', `Native "${node.operator}" short-circuiting is evaluated while recording. Use ctx.branch(), ctx.when, or ctx.default().`);
|
|
78
|
+
}
|
|
79
|
+
if ((node.type === 'CallExpression' || node.type === 'OptionalCallExpression') &&
|
|
80
|
+
COLLECTION_METHODS.has((_a = memberName(node.callee)) !== null && _a !== void 0 ? _a : '')) {
|
|
81
|
+
add(node, 'FEATURE_NATIVE_COLLECTION_CONTROL_FLOW', `Native .${memberName(node.callee)}() is not portable runtime collection logic. Use ctx.functions instead.`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (node.type === 'CallExpression' || node.type === 'OptionalCallExpression') {
|
|
85
|
+
if (isMemberPath(node.callee, 'Date', 'now'))
|
|
86
|
+
add(node, 'FEATURE_HOST_TIME', 'Use ctx.transform.now() instead of Date.now().');
|
|
87
|
+
if (isMemberPath(node.callee, 'Math', 'random'))
|
|
88
|
+
add(node, 'FEATURE_HOST_RANDOM', 'Use a portable function or reusable action instead of Math.random().');
|
|
89
|
+
if (memberName(node.callee) === 'randomUUID')
|
|
90
|
+
add(node, 'FEATURE_HOST_UUID', 'Use ctx.transform.uuid() instead of a host crypto API.');
|
|
91
|
+
}
|
|
92
|
+
if ((node.type === 'MemberExpression' || node.type === 'OptionalMemberExpression') &&
|
|
93
|
+
isMemberPath(node.object, 'process', 'env')) {
|
|
94
|
+
add(node, 'FEATURE_HOST_ENVIRONMENT', 'Do not read process.env while recording. Use Ductape variables, constants, secrets, or runtime function configuration.');
|
|
95
|
+
}
|
|
96
|
+
for (const [key, value] of Object.entries(node)) {
|
|
97
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'extra')
|
|
98
|
+
continue;
|
|
99
|
+
if (Array.isArray(value))
|
|
100
|
+
value.forEach(visit);
|
|
101
|
+
else if (value && typeof value === 'object')
|
|
102
|
+
visit(value);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
visit(root);
|
|
106
|
+
return diagnostics.sort((a, b) => a.line - b.line || a.column - b.column || a.code.localeCompare(b.code));
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=feature-control-flow.validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-control-flow.validator.js","sourceRoot":"","sources":["../../src/features/feature-control-flow.validator.ts"],"names":[],"mappings":"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"}
|
|
@@ -47,6 +47,7 @@ var _a, _b;
|
|
|
47
47
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
48
|
exports.featureService = exports.FeatureCompilationError = exports.FeatureService = exports.FeatureError = void 0;
|
|
49
49
|
const crypto_1 = require("crypto");
|
|
50
|
+
const feature_control_flow_validator_1 = require("./feature-control-flow.validator");
|
|
50
51
|
const functions_1 = require("../functions");
|
|
51
52
|
const products_service_1 = __importDefault(require("../products/services/products.service"));
|
|
52
53
|
const processor_service_1 = __importDefault(require("../processor/services/processor.service"));
|
|
@@ -2519,16 +2520,16 @@ class FeatureCompiler {
|
|
|
2519
2520
|
this.options = options;
|
|
2520
2521
|
}
|
|
2521
2522
|
validatePortableControlFlow() {
|
|
2522
|
-
|
|
2523
|
-
if (
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
if (
|
|
2530
|
-
throw new FeatureCompilationError(this.options.tag, '(handler)', '
|
|
2531
|
-
|
|
2523
|
+
var _a, _b, _c;
|
|
2524
|
+
if (this.options.controlFlowMode !== 'legacy' &&
|
|
2525
|
+
(this.options.branchOverrides || ((_b = (_a = this.options.recordScenarios) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0)) {
|
|
2526
|
+
throw new FeatureCompilationError(this.options.tag, '(handler)', 'branchOverrides and recordScenarios require controlFlowMode: "legacy". ' +
|
|
2527
|
+
'For portable Features, use ctx.branch()/ctx.when and move runtime-sized algorithms into ctx.functions.');
|
|
2528
|
+
}
|
|
2529
|
+
const diagnostics = (0, feature_control_flow_validator_1.inspectFeatureHandlerControlFlow)(this.options.handler.toString(), (_c = this.options.controlFlowMode) !== null && _c !== void 0 ? _c : 'portable');
|
|
2530
|
+
if (diagnostics.length > 0) {
|
|
2531
|
+
throw new FeatureCompilationError(this.options.tag, '(handler)', 'Feature control-flow validation failed. The Feature was not persisted.\n' +
|
|
2532
|
+
diagnostics.map((item) => `${item.code} at ${item.line}:${item.column} - ${item.message}`).join('\n'));
|
|
2532
2533
|
}
|
|
2533
2534
|
}
|
|
2534
2535
|
/**
|