ts2workflows 0.14.0 → 0.15.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/CHANGELOG.md +11 -0
- package/README.md +1 -1
- package/dist/cli.js +14 -72
- package/dist/diagnostics.d.ts +3 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +46 -0
- package/dist/errors.d.ts +8 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +21 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/transpiler/index.d.ts +1 -4
- package/dist/transpiler/index.d.ts.map +1 -1
- package/dist/transpiler/index.js +34 -15
- package/dist/transpiler/parseexpressions.d.ts.map +1 -1
- package/dist/transpiler/parseexpressions.js +5 -1
- package/dist/transpiler/transformations.d.ts.map +1 -1
- package/dist/transpiler/transformations.js +18 -5
- package/language_reference.md +27 -14
- package/package.json +3 -3
- package/types/global.d.ts +1 -0
- package/types/workflowslib.d.ts +13 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# ts2workflows changelog
|
|
2
2
|
|
|
3
|
+
## Version 0.15.0 - 2026-03-18
|
|
4
|
+
|
|
5
|
+
New features:
|
|
6
|
+
|
|
7
|
+
- Static method `Array.includes(arr, x)` for checking if the array `arr` contain the value `x`
|
|
8
|
+
|
|
9
|
+
Fixes:
|
|
10
|
+
|
|
11
|
+
- Print the correct source code line on error messages
|
|
12
|
+
- Type annotation fixes in workflowslib
|
|
13
|
+
|
|
3
14
|
## Version 0.14.0 - 2025-11-19
|
|
4
15
|
|
|
5
16
|
Fixes:
|
package/README.md
CHANGED
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import { program } from 'commander';
|
|
|
6
6
|
import { transpile, transpileText } from './transpiler/index.js';
|
|
7
7
|
import { WorkflowSyntaxError } from './errors.js';
|
|
8
8
|
import { TSError } from '@typescript-eslint/typescript-estree';
|
|
9
|
+
import { prettifySyntaxError } from './diagnostics.js';
|
|
9
10
|
function parseArgs() {
|
|
10
11
|
program
|
|
11
12
|
.name('ts2workflow')
|
|
@@ -37,19 +38,18 @@ function cliMain() {
|
|
|
37
38
|
files = args.sourceFiles;
|
|
38
39
|
}
|
|
39
40
|
files.forEach((inputFile) => {
|
|
40
|
-
const input = readSourceCode(inputFile);
|
|
41
41
|
try {
|
|
42
|
-
const transpiled = generateTranspiledText(
|
|
42
|
+
const transpiled = generateTranspiledText(inputFile, readSourceCode(inputFile), args.generatedFileComment, args.link, args.project);
|
|
43
43
|
writeOutput(transpiled, inputFile, args.outdir);
|
|
44
44
|
}
|
|
45
45
|
catch (err) {
|
|
46
46
|
if (isIoError(err)) {
|
|
47
47
|
let message;
|
|
48
|
-
if (
|
|
48
|
+
if (err.code === 'EAGAIN' && inputFile === '-') {
|
|
49
49
|
// Reading from stdin if there's no input causes error. This is a bug in node
|
|
50
50
|
message = 'Error: Failed to read from stdin';
|
|
51
51
|
}
|
|
52
|
-
else if (
|
|
52
|
+
else if (err.code === 'EISDIR') {
|
|
53
53
|
message = `Error: "${inputFile}" is a directory`;
|
|
54
54
|
}
|
|
55
55
|
else {
|
|
@@ -59,11 +59,11 @@ function cliMain() {
|
|
|
59
59
|
process.exit(1);
|
|
60
60
|
}
|
|
61
61
|
else if (err instanceof WorkflowSyntaxError) {
|
|
62
|
-
|
|
62
|
+
console.error(prettifySyntaxError(err));
|
|
63
63
|
process.exit(1);
|
|
64
64
|
}
|
|
65
65
|
else if (err instanceof TSError) {
|
|
66
|
-
|
|
66
|
+
console.error(prettifySyntaxError(err));
|
|
67
67
|
process.exit(1);
|
|
68
68
|
}
|
|
69
69
|
else {
|
|
@@ -72,31 +72,19 @@ function cliMain() {
|
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
|
-
function generateTranspiledText(
|
|
76
|
-
if (
|
|
77
|
-
return transpileText(
|
|
75
|
+
function generateTranspiledText(filename, sourceCode, addGeneratedFileComment, linkSubworkflows, project) {
|
|
76
|
+
if (filename === undefined) {
|
|
77
|
+
return transpileText(sourceCode);
|
|
78
78
|
}
|
|
79
79
|
else {
|
|
80
|
-
const header = addGeneratedFileComment
|
|
81
|
-
|
|
82
|
-
: '';
|
|
83
|
-
const transpiled = transpile(input, project, linkSubworkflows);
|
|
80
|
+
const header = addGeneratedFileComment ? generatedFileComment(filename) : '';
|
|
81
|
+
const transpiled = transpile(filename, sourceCode, project, linkSubworkflows);
|
|
84
82
|
return `${header}${transpiled}`;
|
|
85
83
|
}
|
|
86
84
|
}
|
|
87
85
|
function readSourceCode(filename) {
|
|
88
|
-
const
|
|
89
|
-
return
|
|
90
|
-
filename: filename === '-' ? undefined : filename,
|
|
91
|
-
read: () => readCode(),
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
function crateMemorizedReader(filenameOrFd) {
|
|
95
|
-
let cached;
|
|
96
|
-
return () => {
|
|
97
|
-
cached ??= fs.readFileSync(filenameOrFd, 'utf8');
|
|
98
|
-
return cached;
|
|
99
|
-
};
|
|
86
|
+
const filenameOrFd = filename === '-' ? process.stdin.fd : filename;
|
|
87
|
+
return fs.readFileSync(filenameOrFd, 'utf8');
|
|
100
88
|
}
|
|
101
89
|
function writeOutput(transpiled, inputFile, outdir) {
|
|
102
90
|
if (outdir !== undefined) {
|
|
@@ -119,58 +107,12 @@ function createOutputFilename(inputFile, outdir) {
|
|
|
119
107
|
});
|
|
120
108
|
}
|
|
121
109
|
function generatedFileComment(inputFile) {
|
|
122
|
-
return (`# This file has been generated by
|
|
110
|
+
return (`# This file has been generated by ts2workflows from source file ${inputFile}\n` +
|
|
123
111
|
'# Do not edit!\n\n');
|
|
124
112
|
}
|
|
125
113
|
function isIoError(err) {
|
|
126
114
|
return err instanceof Error && 'code' in err;
|
|
127
115
|
}
|
|
128
|
-
function prettyPrintSyntaxError(exception, inp) {
|
|
129
|
-
console.error(errorDisplay(inp, exception.location));
|
|
130
|
-
console.error(`${exception.message}`);
|
|
131
|
-
}
|
|
132
|
-
function errorDisplay(inp, location) {
|
|
133
|
-
const lines = [];
|
|
134
|
-
const prettyFilename = inp.filename ?? '<stdin>';
|
|
135
|
-
if (typeof location?.start === 'undefined' ||
|
|
136
|
-
typeof location?.end === 'undefined' ||
|
|
137
|
-
isNaN(location?.start.line) ||
|
|
138
|
-
isNaN(location?.end.line)) {
|
|
139
|
-
lines.push(`File ${prettyFilename}:`);
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
lines.push(`File ${prettyFilename}, line ${location.start.line}, column ${location.start.column + 1}:`);
|
|
143
|
-
}
|
|
144
|
-
const highlightedLine = highlightedSourceCodeLine(inp.read(), location?.start?.line, location?.start?.column, location?.start?.line === location?.end?.line
|
|
145
|
-
? location?.end?.column
|
|
146
|
-
: undefined);
|
|
147
|
-
if (highlightedLine.length > 0) {
|
|
148
|
-
lines.push(highlightedLine);
|
|
149
|
-
lines.push('');
|
|
150
|
-
}
|
|
151
|
-
return lines.join('\n');
|
|
152
|
-
}
|
|
153
|
-
function highlightedSourceCodeLine(sourceCode, lineNumber, start, end) {
|
|
154
|
-
if (typeof lineNumber === 'undefined' ||
|
|
155
|
-
typeof start === 'undefined' ||
|
|
156
|
-
isNaN(start)) {
|
|
157
|
-
return '';
|
|
158
|
-
}
|
|
159
|
-
const lines = sourceCode.split('\n');
|
|
160
|
-
const sourceLine = lines[lineNumber - 1];
|
|
161
|
-
if (typeof sourceLine === 'undefined') {
|
|
162
|
-
return '';
|
|
163
|
-
}
|
|
164
|
-
let markerLength;
|
|
165
|
-
if (typeof end === 'undefined') {
|
|
166
|
-
markerLength = sourceLine.length - start;
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
markerLength = Math.min(end - start + 1, sourceLine.length - start);
|
|
170
|
-
}
|
|
171
|
-
const markerLine = `${' '.repeat(start)}${'^'.repeat(markerLength)}`;
|
|
172
|
-
return `${sourceLine}\n${markerLine}`;
|
|
173
|
-
}
|
|
174
116
|
function versionFromPackageJson() {
|
|
175
117
|
const currentFile = fileURLToPath(import.meta.url);
|
|
176
118
|
const currentDir = path.dirname(currentFile);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,mBAAmB,EAEpB,MAAM,aAAa,CAAA;AAEpB,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,mBAAmB,GAAG,MAAM,CAc1E"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { WorkflowSyntaxErrorWithText, } from './errors.js';
|
|
2
|
+
export function prettifySyntaxError(exception) {
|
|
3
|
+
const filename = exception instanceof WorkflowSyntaxErrorWithText
|
|
4
|
+
? exception.filename
|
|
5
|
+
: '???';
|
|
6
|
+
const errorLineText = exception instanceof WorkflowSyntaxErrorWithText
|
|
7
|
+
? exception.errorLine
|
|
8
|
+
: undefined;
|
|
9
|
+
return (`${errorLocator(filename, exception.location, errorLineText)}\n` +
|
|
10
|
+
`${exception.message}`);
|
|
11
|
+
}
|
|
12
|
+
function errorLocator(filename, location, errorLineText) {
|
|
13
|
+
const lines = [];
|
|
14
|
+
if (isNaN(location.start.line) || isNaN(location.end.line)) {
|
|
15
|
+
lines.push(`File ${filename}:`);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
lines.push(`File ${filename}, line ${location.start.line}, column ${location.start.column + 1}:`);
|
|
19
|
+
}
|
|
20
|
+
if (errorLineText) {
|
|
21
|
+
const highlightedLine = highlightedSourceCodeLine(errorLineText, location.start.column, location.start.line === location.end.line
|
|
22
|
+
? location.end.column
|
|
23
|
+
: undefined);
|
|
24
|
+
if (highlightedLine.length > 0) {
|
|
25
|
+
lines.push(highlightedLine);
|
|
26
|
+
lines.push('');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return lines.join('\n');
|
|
30
|
+
}
|
|
31
|
+
function highlightedSourceCodeLine(errorLineText, start, end) {
|
|
32
|
+
if (isNaN(start) || start < 0) {
|
|
33
|
+
return '';
|
|
34
|
+
}
|
|
35
|
+
const sourceLine = errorLineText.split('\n')[0] ?? '';
|
|
36
|
+
let markerLength;
|
|
37
|
+
if (end === undefined || isNaN(end) || end < 0) {
|
|
38
|
+
markerLength = sourceLine.length - start;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
markerLength = Math.min(end - start + 1, sourceLine.length - start);
|
|
42
|
+
}
|
|
43
|
+
markerLength = Math.max(markerLength, 0);
|
|
44
|
+
const markerLine = `${' '.repeat(start)}${'^'.repeat(markerLength)}`;
|
|
45
|
+
return `${sourceLine}\n${markerLine}`;
|
|
46
|
+
}
|
package/dist/errors.d.ts
CHANGED
|
@@ -9,9 +9,16 @@ export interface SourceCodeLocation {
|
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
11
|
export declare class WorkflowSyntaxError extends Error {
|
|
12
|
-
location: SourceCodeLocation;
|
|
12
|
+
readonly location: SourceCodeLocation;
|
|
13
13
|
constructor(message: string, location: SourceCodeLocation);
|
|
14
14
|
}
|
|
15
|
+
export declare class WorkflowSyntaxErrorWithText extends WorkflowSyntaxError {
|
|
16
|
+
readonly location: SourceCodeLocation;
|
|
17
|
+
readonly filename: string;
|
|
18
|
+
readonly errorLine: string;
|
|
19
|
+
constructor(message: string, location: SourceCodeLocation, filename: string, errorLine: string);
|
|
20
|
+
}
|
|
21
|
+
export declare function syntaxErrorWithText(error: WorkflowSyntaxError, filename: string, sourceCode: string): WorkflowSyntaxErrorWithText;
|
|
15
22
|
export declare class InternalTranspilingError extends Error {
|
|
16
23
|
constructor(message: string);
|
|
17
24
|
}
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IACvC,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CACtC;AAID,qBAAa,mBAAoB,SAAQ,KAAK;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IACvC,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CACtC;AAID,qBAAa,mBAAoB,SAAQ,KAAK;aAG1B,QAAQ,EAAE,kBAAkB;gBAD5C,OAAO,EAAE,MAAM,EACC,QAAQ,EAAE,kBAAkB;CAI/C;AAID,qBAAa,2BAA4B,SAAQ,mBAAmB;aAGhD,QAAQ,EAAE,kBAAkB;aAC5B,QAAQ,EAAE,MAAM;aAChB,SAAS,EAAE,MAAM;gBAHjC,OAAO,EAAE,MAAM,EACC,QAAQ,EAAE,kBAAkB,EAC5B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM;CAIpC;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,mBAAmB,EAC1B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,2BAA2B,CAW7B;AAID,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,OAAO,EAAE,MAAM;CAG5B;AAGD,qBAAa,OAAQ,SAAQ,KAAK;aAGd,IAAI,EAAE,MAAM;gBAD5B,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,MAAM;CAI/B"}
|
package/dist/errors.js
CHANGED
|
@@ -7,13 +7,33 @@ export class WorkflowSyntaxError extends Error {
|
|
|
7
7
|
this.location = location;
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
|
+
// WorkflowSyntaxError enriched with the source code filename and the text of
|
|
11
|
+
// the line where the error occurred.
|
|
12
|
+
export class WorkflowSyntaxErrorWithText extends WorkflowSyntaxError {
|
|
13
|
+
location;
|
|
14
|
+
filename;
|
|
15
|
+
errorLine;
|
|
16
|
+
constructor(message, location, filename, errorLine) {
|
|
17
|
+
super(message, location);
|
|
18
|
+
this.location = location;
|
|
19
|
+
this.filename = filename;
|
|
20
|
+
this.errorLine = errorLine;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function syntaxErrorWithText(error, filename, sourceCode) {
|
|
24
|
+
const lineNumber = Math.max(error.location.start.line, 0);
|
|
25
|
+
const lines = sourceCode.split('\n');
|
|
26
|
+
const errorLine = lines[lineNumber - 1];
|
|
27
|
+
return new WorkflowSyntaxErrorWithText(error.message, error.location, filename, errorLine);
|
|
28
|
+
}
|
|
10
29
|
// InternalTranspilingError is thrown when ts2workflow ends up in an unexpected state.
|
|
11
|
-
//
|
|
30
|
+
// This is a bug in ts2workflow.
|
|
12
31
|
export class InternalTranspilingError extends Error {
|
|
13
32
|
constructor(message) {
|
|
14
33
|
super(`Internal error: ${message}`);
|
|
15
34
|
}
|
|
16
35
|
}
|
|
36
|
+
// An IO error with an error code string, similar to Node's SystemError
|
|
17
37
|
export class IOError extends Error {
|
|
18
38
|
code;
|
|
19
39
|
constructor(message, code) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { transpile } from './transpiler/index.js';
|
|
2
|
-
export { WorkflowSyntaxError, InternalTranspilingError, SourceCodeLocation, } from './errors.js';
|
|
1
|
+
export { transpile, transpileText } from './transpiler/index.js';
|
|
2
|
+
export { WorkflowSyntaxError, WorkflowSyntaxErrorWithText, InternalTranspilingError, SourceCodeLocation, } from './errors.js';
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAChE,OAAO,EACL,mBAAmB,EACnB,2BAA2B,EAC3B,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,aAAa,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { transpile } from './transpiler/index.js';
|
|
2
|
-
export { WorkflowSyntaxError, InternalTranspilingError, } from './errors.js';
|
|
1
|
+
export { transpile, transpileText } from './transpiler/index.js';
|
|
2
|
+
export { WorkflowSyntaxError, WorkflowSyntaxErrorWithText, InternalTranspilingError, } from './errors.js';
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
import { WorkflowApp } from '../ast/workflows.js';
|
|
3
|
-
export declare function transpile(
|
|
4
|
-
filename?: string;
|
|
5
|
-
read: () => string;
|
|
6
|
-
}, tsconfigPath: string | undefined, linkSubworkflows: boolean): string;
|
|
3
|
+
export declare function transpile(filename: string, sourceCode: string, tsconfigPath: string | undefined, linkSubworkflows: boolean): string;
|
|
7
4
|
export declare function transpileText(sourceCode: string): string;
|
|
8
5
|
/**
|
|
9
6
|
* Returns true if node is an ambient function declaration.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transpiler/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transpiler/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,MAAM,YAAY,CAAA;AAQ3B,OAAO,EAGL,WAAW,EAEZ,MAAM,qBAAqB,CAAA;AAU5B,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,gBAAgB,EAAE,OAAO,GACxB,MAAM,CA2BR;AAED,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,UAS/C;AA2JD;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,EAAE,CAAC,mBAAmB,GAC3B,OAAO,CAkBT;AAgID;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAI1D"}
|
package/dist/transpiler/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import * as path from 'node:path';
|
|
|
3
3
|
import { AST_NODE_TYPES, parseAndGenerateServices, } from '@typescript-eslint/typescript-estree';
|
|
4
4
|
import ts from 'typescript';
|
|
5
5
|
import * as YAML from 'yaml';
|
|
6
|
-
import { InternalTranspilingError, IOError, WorkflowSyntaxError, } from '../errors.js';
|
|
6
|
+
import { InternalTranspilingError, IOError, syntaxErrorWithText, WorkflowSyntaxError, } from '../errors.js';
|
|
7
7
|
import { SubworkflowStatements, WorkflowApp, } from '../ast/workflows.js';
|
|
8
8
|
import { parseStatement } from './parsestatement.js';
|
|
9
9
|
import { transformAST } from './transformations.js';
|
|
@@ -12,11 +12,14 @@ import { isPrimitive, nullEx } from '../ast/expressions.js';
|
|
|
12
12
|
import { convertExpression } from './parseexpressions.js';
|
|
13
13
|
import { generateStepNames } from './stepnames.js';
|
|
14
14
|
const workflowCache = new Map();
|
|
15
|
-
export function transpile(
|
|
16
|
-
const { ast, services } = parseMainFile(
|
|
17
|
-
const inputWorkflow =
|
|
18
|
-
if (linkSubworkflows &&
|
|
19
|
-
|
|
15
|
+
export function transpile(filename, sourceCode, tsconfigPath, linkSubworkflows) {
|
|
16
|
+
const { ast, services } = parseMainFile(filename, sourceCode, tsconfigPath);
|
|
17
|
+
const inputWorkflow = esProgramToWorkflowAppEnrichErrors(ast, filename, sourceCode);
|
|
18
|
+
if (linkSubworkflows &&
|
|
19
|
+
tsconfigPath &&
|
|
20
|
+
services.program !== null &&
|
|
21
|
+
filename) {
|
|
22
|
+
const canonicalInput = path.join(process.cwd(), filename);
|
|
20
23
|
workflowCache.set(canonicalInput, inputWorkflow);
|
|
21
24
|
const subworkflows = generateLinkedOutput(canonicalInput, tsconfigPath, services.program);
|
|
22
25
|
const combinedWorkflow = new WorkflowApp(subworkflows);
|
|
@@ -29,24 +32,40 @@ export function transpile(input, tsconfigPath, linkSubworkflows) {
|
|
|
29
32
|
export function transpileText(sourceCode) {
|
|
30
33
|
const parserOptions = eslintParserOptions();
|
|
31
34
|
const { ast } = parseAndGenerateServices(sourceCode, parserOptions);
|
|
32
|
-
const workflow =
|
|
35
|
+
const workflow = esProgramToWorkflowAppEnrichErrors(ast, '<stdin>', sourceCode);
|
|
33
36
|
return toYAMLString(workflow);
|
|
34
37
|
}
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
function esProgramToWorkflowAppEnrichErrors(program, filename, sourceCode) {
|
|
39
|
+
try {
|
|
40
|
+
return esProgramToWorkflowApp(program);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error instanceof WorkflowSyntaxError) {
|
|
44
|
+
throw syntaxErrorWithText(error, filename, sourceCode);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function esProgramToWorkflowApp(program) {
|
|
52
|
+
return generateStepNames(program.body.flatMap(parseTopLevelStatement));
|
|
53
|
+
}
|
|
54
|
+
function parseMainFile(filename, sourceCode, tsconfigPath) {
|
|
55
|
+
const parserOptions = eslintParserOptions(filename, tsconfigPath);
|
|
56
|
+
if (tsconfigPath) {
|
|
38
57
|
const cwd = process.cwd();
|
|
39
58
|
const configJSON = JSON.parse(fs.readFileSync(path.join(cwd, tsconfigPath), 'utf-8'));
|
|
40
59
|
const { options } = ts.parseJsonConfigFileContent(configJSON, ts.sys, cwd);
|
|
41
|
-
const program = ts.createProgram([
|
|
42
|
-
const mainSourceFile = program.getSourceFile(
|
|
60
|
+
const program = ts.createProgram([filename], options);
|
|
61
|
+
const mainSourceFile = program.getSourceFile(filename);
|
|
43
62
|
if (mainSourceFile === undefined) {
|
|
44
|
-
throw new IOError(`Source file ${
|
|
63
|
+
throw new IOError(`Source file ${filename} not found`, 'ENOENT');
|
|
45
64
|
}
|
|
46
65
|
return parseAndGenerateServices(mainSourceFile, parserOptions);
|
|
47
66
|
}
|
|
48
67
|
else {
|
|
49
|
-
return parseAndGenerateServices(
|
|
68
|
+
return parseAndGenerateServices(sourceCode, parserOptions);
|
|
50
69
|
}
|
|
51
70
|
}
|
|
52
71
|
function eslintParserOptions(inputFile, tsconfigPath) {
|
|
@@ -110,7 +129,7 @@ function getCachedWorkflow(filename, tsconfigPath) {
|
|
|
110
129
|
const parserOptions = eslintParserOptions(filename, tsconfigPath);
|
|
111
130
|
const code = fs.readFileSync(filename, 'utf8');
|
|
112
131
|
const { ast } = parseAndGenerateServices(code, parserOptions);
|
|
113
|
-
const workflow =
|
|
132
|
+
const workflow = esProgramToWorkflowAppEnrichErrors(ast, filename, code);
|
|
114
133
|
workflowCache.set(filename, workflow);
|
|
115
134
|
return workflow;
|
|
116
135
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parseexpressions.d.ts","sourceRoot":"","sources":["../../src/transpiler/parseexpressions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAkB,MAAM,sCAAsC,CAAA;AAC/E,OAAO,EAGL,UAAU,EAGV,aAAa,EACb,gBAAgB,EAChB,2BAA2B,EAc5B,MAAM,uBAAuB,CAAA;AAG9B,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,GAAG,UAAU,CAmE3E;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,QAAQ,CAAC,gBAAgB,GAC9B,aAAa,
|
|
1
|
+
{"version":3,"file":"parseexpressions.d.ts","sourceRoot":"","sources":["../../src/transpiler/parseexpressions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAkB,MAAM,sCAAsC,CAAA;AAC/E,OAAO,EAGL,UAAU,EAGV,aAAa,EACb,gBAAgB,EAChB,2BAA2B,EAc5B,MAAM,uBAAuB,CAAA;AAG9B,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,GAAG,UAAU,CAmE3E;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,QAAQ,CAAC,gBAAgB,GAC9B,aAAa,CAmCf;AAmJD,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,QAAQ,CAAC,gBAAgB,GAC9B,gBAAgB,CAOlB;AA2KD,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAGvD;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAGhE;AA8CD,wBAAgB,aAAa,CAC3B,CAAC,SACG,QAAQ,CAAC,UAAU,GACnB,QAAQ,CAAC,QAAQ,GACjB,QAAQ,CAAC,aAAa,GACtB,IAAI,EACR,KAAK,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,CAgBlD;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,QAAQ,CAAC,UAAU,GAAG,QAAQ,CAAC,iBAAiB,GACrD,QAAQ,CAAC,UAAU,CAMrB;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,QAAQ,CAAC,UAAU,GACxB,2BAA2B,GAAG,gBAAgB,CAWhD"}
|
|
@@ -69,6 +69,10 @@ export function convertObjectExpression(node) {
|
|
|
69
69
|
typeof key.value === 'string') {
|
|
70
70
|
keyPrimitive = key.value;
|
|
71
71
|
}
|
|
72
|
+
else if (key.type === AST_NODE_TYPES.Literal &&
|
|
73
|
+
typeof key.value === 'number') {
|
|
74
|
+
keyPrimitive = key.value.toString();
|
|
75
|
+
}
|
|
72
76
|
else {
|
|
73
77
|
throw new WorkflowSyntaxError(`Map keys must be identifiers or strings, encountered: ${key.type}`, key.loc);
|
|
74
78
|
}
|
|
@@ -319,7 +323,7 @@ function convertConditionalExpression(node) {
|
|
|
319
323
|
function convertTemplateLiteralToExpression(node) {
|
|
320
324
|
const stringTerms = node.quasis
|
|
321
325
|
.map((x) => x.value.cooked)
|
|
322
|
-
.map((x) => stringEx(x));
|
|
326
|
+
.map((x) => stringEx(x ?? ''));
|
|
323
327
|
const templateTerms = node.expressions
|
|
324
328
|
.map(convertExpression)
|
|
325
329
|
.map((ex) => functionInvocationEx('default', [ex, stringEx('null')]));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transformations.d.ts","sourceRoot":"","sources":["../../src/transpiler/transformations.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"transformations.d.ts","sourceRoot":"","sources":["../../src/transpiler/transformations.ts"],"names":[],"mappings":"AAoBA,OAAO,EAcL,iBAAiB,EAClB,MAAM,sBAAsB,CAAA;AAG7B;;GAEG;AACH,wBAAgB,YAAY,CAC1B,UAAU,EAAE,iBAAiB,EAAE,GAC9B,iBAAiB,EAAE,CAUrB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as R from 'ramda';
|
|
2
2
|
import { InternalTranspilingError } from '../errors.js';
|
|
3
|
-
import { binaryEx, functionInvocationEx, listEx, mapEx, memberEx, stringEx, unaryEx, variableReferenceEx, } from '../ast/expressions.js';
|
|
3
|
+
import { binaryEx, functionInvocationEx, listEx, mapEx, memberEx, nullEx, stringEx, unaryEx, variableReferenceEx, } from '../ast/expressions.js';
|
|
4
4
|
import { applyNested, AssignStatement, FunctionInvocationStatement, IfStatement, LabelledStatement, } from '../ast/statements.js';
|
|
5
5
|
import { blockingFunctions } from './generated/functionMetadata.js';
|
|
6
6
|
/**
|
|
@@ -420,13 +420,13 @@ function extractNestedMapUnary(ex, generateName, nestingLevel) {
|
|
|
420
420
|
tempVariables,
|
|
421
421
|
};
|
|
422
422
|
}
|
|
423
|
-
/**
|
|
424
|
-
* Replace `Array.isArray(x)` with `get_type(x) == "list"`
|
|
425
|
-
*/
|
|
426
423
|
const intrinsicFunctionImplementation = expandExpressionToStatements((ex) => [
|
|
427
424
|
[],
|
|
428
|
-
transformExpression(replaceIsArray, ex),
|
|
425
|
+
transformExpression(R.pipe(replaceIsArray, replaceArrayIncludes), ex),
|
|
429
426
|
]);
|
|
427
|
+
/**
|
|
428
|
+
* Replace `Array.isArray(x)` with `get_type(x) == "list"`
|
|
429
|
+
*/
|
|
430
430
|
function replaceIsArray(ex) {
|
|
431
431
|
if (ex.tag === 'functionInvocation' && ex.functionName === 'Array.isArray') {
|
|
432
432
|
return binaryEx(functionInvocationEx('get_type', ex.arguments), '==', stringEx('list'));
|
|
@@ -435,3 +435,16 @@ function replaceIsArray(ex) {
|
|
|
435
435
|
return ex;
|
|
436
436
|
}
|
|
437
437
|
}
|
|
438
|
+
/**
|
|
439
|
+
* Replace `Array.includes(arr, x)` with `x in arr`
|
|
440
|
+
*/
|
|
441
|
+
function replaceArrayIncludes(ex) {
|
|
442
|
+
if (ex.tag === 'functionInvocation' && ex.functionName === 'Array.includes') {
|
|
443
|
+
const arrayEx = ex.arguments[0] ?? nullEx;
|
|
444
|
+
const valueEx = ex.arguments[1] ?? nullEx;
|
|
445
|
+
return binaryEx(valueEx, 'in', arrayEx);
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
return ex;
|
|
449
|
+
}
|
|
450
|
+
}
|
package/language_reference.md
CHANGED
|
@@ -72,6 +72,23 @@ name === 'Bean'
|
|
|
72
72
|
sys.get_env('GOOGLE_CLOUD_PROJECT_ID')
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
+
### Member expression
|
|
76
|
+
|
|
77
|
+
To get a value in an array, use the following code:
|
|
78
|
+
|
|
79
|
+
```javasript
|
|
80
|
+
const val = arr[2];
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
To get a value in a map, use either of following:
|
|
84
|
+
|
|
85
|
+
```javasript
|
|
86
|
+
const val = obj.name;
|
|
87
|
+
const val2 = obj["name"];
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
⚠️ Numeric keys in maps are not cast to strings like in Typescript. It's best not to use numeric keys because GCP Workflows throws errors on numeric keys.
|
|
91
|
+
|
|
75
92
|
## Operators
|
|
76
93
|
|
|
77
94
|
| Operator | Description |
|
|
@@ -880,7 +897,15 @@ This section describes the few standard Javascript runtime functions that are av
|
|
|
880
897
|
Array.isArray(arg: any): arg is any[]
|
|
881
898
|
```
|
|
882
899
|
|
|
883
|
-
|
|
900
|
+
Returns true, if arg is an array, and false otherwise. `Array.isArray` gets converted to the following comparison on Workflows code: `get_type(arg) == "list"`. Unlike a direct call to `get_type()`, `Array.isArray()` acts a type guard in Typescript and allows narrowing the type of `arg` to an array.
|
|
901
|
+
|
|
902
|
+
### Array.includes()
|
|
903
|
+
|
|
904
|
+
```typescript
|
|
905
|
+
Array.includes<T>(arr: Array<T>, x: T): boolean
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
Returns true, if the array `arr` contains the value `x`, and false otherwise. This static Array method should be used instead of `arr.includes()`, which won't work in Workflows, because arrays in Workflows don't have methods. `Array.includes()` is converted to `x in arr` in the outputted Workflows code.
|
|
884
909
|
|
|
885
910
|
## Compiler intrinsics
|
|
886
911
|
|
|
@@ -925,19 +950,7 @@ The `parallel` function executes code blocks in parallel using a [parallel step]
|
|
|
925
950
|
### retry_policy()
|
|
926
951
|
|
|
927
952
|
```typescript
|
|
928
|
-
function retry_policy(
|
|
929
|
-
params:
|
|
930
|
-
| ((errormap: Record<string, any>) => void)
|
|
931
|
-
| {
|
|
932
|
-
predicate: (errormap: Record<string, any>) => boolean
|
|
933
|
-
max_retries: number
|
|
934
|
-
backoff: {
|
|
935
|
-
initial_delay: number
|
|
936
|
-
max_delay: number
|
|
937
|
-
multiplier: number
|
|
938
|
-
}
|
|
939
|
-
},
|
|
940
|
-
): void
|
|
953
|
+
function retry_policy(params: RetryPolicy): void
|
|
941
954
|
```
|
|
942
955
|
|
|
943
956
|
A retry policy can be attached to a `try`-`catch` block by calling `retry_policy` inside the `try` block. ts2workflows ignores `retry_policy` everywhere else.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts2workflows",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Transpile Typescript code to GCP Workflows programs",
|
|
5
5
|
"homepage": "https://github.com/aajanki/ts2workflows",
|
|
6
6
|
"repository": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"lint": "eslint src test scripts",
|
|
21
21
|
"format": "prettier . --write",
|
|
22
22
|
"test": "mocha",
|
|
23
|
-
"test
|
|
23
|
+
"test:coverage": "nyc --reporter=text --reporter=html mocha",
|
|
24
24
|
"prepare": "husky && npm run build"
|
|
25
25
|
},
|
|
26
26
|
"lint-staged": {
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"husky": "^9.1.6",
|
|
74
74
|
"lint-staged": "^16.1.2",
|
|
75
75
|
"mocha": "^11.1.0",
|
|
76
|
-
"nyc": "^
|
|
76
|
+
"nyc": "^18.0.0",
|
|
77
77
|
"prettier": "^3.2.5",
|
|
78
78
|
"rimraf": "^6.0.1",
|
|
79
79
|
"source-map-support": "^0.5.21",
|
package/types/global.d.ts
CHANGED
package/types/workflowslib.d.ts
CHANGED
|
@@ -32,6 +32,16 @@ type HTTPQuery = Record<
|
|
|
32
32
|
string | number | boolean | (string | number | boolean)[]
|
|
33
33
|
>
|
|
34
34
|
|
|
35
|
+
type RetryPolicy = {
|
|
36
|
+
predicate?: (errormap: Record<string, any>) => boolean
|
|
37
|
+
max_retries?: number | string | null
|
|
38
|
+
backoff: {
|
|
39
|
+
initial_delay?: number | string | null
|
|
40
|
+
max_delay?: number | string | null
|
|
41
|
+
multiplier?: number | string | null
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
// GCP Workflows expression helpers
|
|
36
46
|
|
|
37
47
|
export declare function double(x: string | number): number
|
|
@@ -93,10 +103,8 @@ export declare namespace hash {
|
|
|
93
103
|
}
|
|
94
104
|
|
|
95
105
|
export declare namespace http {
|
|
96
|
-
export
|
|
97
|
-
export
|
|
98
|
-
errormap: Record<string, any>,
|
|
99
|
-
): void
|
|
106
|
+
export const default_retry: RetryPolicy
|
|
107
|
+
export const default_retry_non_idempotent: RetryPolicy
|
|
100
108
|
export function default_retry_predicate(
|
|
101
109
|
errormap: Record<string, any>,
|
|
102
110
|
): boolean
|
|
@@ -1076,19 +1084,7 @@ export declare function parallel(
|
|
|
1076
1084
|
},
|
|
1077
1085
|
): void
|
|
1078
1086
|
|
|
1079
|
-
export declare function retry_policy(
|
|
1080
|
-
params:
|
|
1081
|
-
| ((errormap: Record<string, any>) => void)
|
|
1082
|
-
| {
|
|
1083
|
-
predicate?: (errormap: Record<string, any>) => boolean
|
|
1084
|
-
max_retries?: number | string | null
|
|
1085
|
-
backoff: {
|
|
1086
|
-
initial_delay?: number | string | null
|
|
1087
|
-
max_delay?: number | string | null
|
|
1088
|
-
multiplier?: number | string | null
|
|
1089
|
-
}
|
|
1090
|
-
},
|
|
1091
|
-
): void
|
|
1087
|
+
export declare function retry_policy(params: RetryPolicy): void
|
|
1092
1088
|
|
|
1093
1089
|
export declare function call_step<T, A extends any[]>(
|
|
1094
1090
|
func: (...args: A) => T,
|