ts2workflows 0.7.0 → 0.8.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/README.md +8 -2
- package/dist/cli.js +85 -28
- package/dist/transpiler/expressions.d.ts.map +1 -1
- package/dist/transpiler/expressions.js +2 -0
- package/dist/transpiler/index.d.ts +1 -1
- package/dist/transpiler/index.d.ts.map +1 -1
- package/dist/transpiler/index.js +9 -3
- package/dist/transpiler/statements.js +10 -7
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -16,6 +16,12 @@ Converting Typescript code in a file samples/sample1.ts into GCP Workflows YAML
|
|
|
16
16
|
npx ts2workflows samples/sample1.ts
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
Compile multiple files and write result to an output directory `workflowsfiles`. This will write one output file corresponding to each input file in a directory given by the `--outdir` argument. The output files are named similarly to the input files but using `.yaml` as the file extension. The output directory will be created if it doesn't exist. Supplying the TSConfig with the `--project` argument makes compiling multiple files faster.
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npx ts2workflows --project samples/tsconfig.json --outdir workflowsfiles samples/*.ts
|
|
23
|
+
```
|
|
24
|
+
|
|
19
25
|
When developing ts2workflows, you can run the transpiler directly from the source directory:
|
|
20
26
|
|
|
21
27
|
```sh
|
|
@@ -29,10 +35,10 @@ One benefit of writing the workflow programs in Typescript is that the sources c
|
|
|
29
35
|
This example command shows how to type check source files in the [samples](samples) directory using the standard Typescript compiler `tsc`. The command prints typing errors or finishes silently, if there are no errors.
|
|
30
36
|
|
|
31
37
|
```sh
|
|
32
|
-
npx tsc --project tsconfig.
|
|
38
|
+
npx tsc --project samples/tsconfig.json
|
|
33
39
|
```
|
|
34
40
|
|
|
35
|
-
The file [tsconfig.
|
|
41
|
+
The file [samples/tsconfig.json](samples/tsconfig.json) contains a sample configuration for type checking workflow sources.
|
|
36
42
|
|
|
37
43
|
Type annotations for [Workflows standard library functions and expression helpers](https://cloud.google.com/workflows/docs/reference/stdlib/overview) and for some [connectors](https://cloud.google.com/workflows/docs/reference/googleapis) are provided in [types/workflowslib.d.ts](types/workflowslib.d.ts). They are also included in the published npm module. To import type annotations in a project that has ts2workflows module as a dependency, use the following import command:
|
|
38
44
|
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
3
5
|
import { program } from 'commander';
|
|
4
6
|
import { transpile } from './transpiler/index.js';
|
|
5
7
|
import { WorkflowSyntaxError } from './errors.js';
|
|
@@ -7,11 +9,16 @@ import { TSError } from '@typescript-eslint/typescript-estree';
|
|
|
7
9
|
function parseArgs() {
|
|
8
10
|
program
|
|
9
11
|
.name('ts2workflow')
|
|
10
|
-
.version(
|
|
12
|
+
.version(versionFromPackageJson())
|
|
11
13
|
.description('Transpile a Typescript program into GCP Workflows YAML syntax.')
|
|
12
|
-
.
|
|
13
|
-
|
|
14
|
+
.option('--project <path>', 'Path to TSConfig for the Typescript sources files.')
|
|
15
|
+
.option('--outdir <path>', 'Specify an output directory for where transpilation result are written.')
|
|
16
|
+
.option('--generated-file-comment', 'Include a comment stating that the result is a generated file', true)
|
|
17
|
+
.option('--no-generated-file-comment', "Don't include a comment about file being generated")
|
|
18
|
+
.argument('[FILES...]', 'Path to source file(s) to compile. If not given, reads from stdin.')
|
|
19
|
+
.parse();
|
|
14
20
|
return {
|
|
21
|
+
...program.opts(),
|
|
15
22
|
sourceFiles: program.args,
|
|
16
23
|
};
|
|
17
24
|
}
|
|
@@ -25,32 +32,27 @@ function cliMain() {
|
|
|
25
32
|
files = args.sourceFiles;
|
|
26
33
|
}
|
|
27
34
|
files.forEach((inputFile) => {
|
|
28
|
-
const inp = inputFile === '-' ? process.stdin.fd : inputFile;
|
|
29
|
-
let sourceCode = '';
|
|
30
35
|
try {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
catch (err) {
|
|
35
|
-
if (isIoError(err, 'ENOENT')) {
|
|
36
|
-
console.error(`Error: "${inp}" not found`);
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
else if (isIoError(err, 'EISDIR')) {
|
|
40
|
-
console.error(`Error: "${inp}" is a directory`);
|
|
36
|
+
const transpiled = generateTranspiledText(inputFile, args.generatedFileComment, args.project);
|
|
37
|
+
if (transpiled === undefined) {
|
|
41
38
|
process.exit(1);
|
|
42
39
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
40
|
+
writeOutput(transpiled, inputFile, args.outdir);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
if (isIoError(err)) {
|
|
44
|
+
let message;
|
|
45
|
+
if ('code' in err && err.code === 'EAGAIN' && inputFile === '-') {
|
|
46
|
+
// Reading from stdin if there's no input causes error. This is a bug in node
|
|
47
|
+
message = 'Error: Failed to read from stdin';
|
|
48
|
+
}
|
|
49
|
+
else if ('code' in err && err.code === 'EISDIR') {
|
|
50
|
+
message = `Error: "${inputFile}" is a directory`;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
message = err.message;
|
|
54
|
+
}
|
|
55
|
+
console.error(message);
|
|
54
56
|
process.exit(1);
|
|
55
57
|
}
|
|
56
58
|
else {
|
|
@@ -59,8 +61,56 @@ function cliMain() {
|
|
|
59
61
|
}
|
|
60
62
|
});
|
|
61
63
|
}
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
+
function generateTranspiledText(inputFile, addGeneratedFileComment, project) {
|
|
65
|
+
const inputIsStdIn = inputFile === '-';
|
|
66
|
+
const inp = inputIsStdIn ? process.stdin.fd : inputFile;
|
|
67
|
+
const sourceCode = fs.readFileSync(inp, 'utf8');
|
|
68
|
+
try {
|
|
69
|
+
const needsHeader = addGeneratedFileComment && !inputIsStdIn;
|
|
70
|
+
const header = needsHeader ? generatedFileComment(inputFile) : '';
|
|
71
|
+
const transpiled = transpile(sourceCode, inputFile, project);
|
|
72
|
+
return `${header}${transpiled}`;
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
if (err instanceof WorkflowSyntaxError) {
|
|
76
|
+
prettyPrintSyntaxError(err, inputFile, sourceCode);
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
else if (err instanceof TSError) {
|
|
80
|
+
prettyPrintSyntaxError(err, inputFile, sourceCode);
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function writeOutput(transpiled, inputFile, outdir) {
|
|
89
|
+
if (outdir !== undefined) {
|
|
90
|
+
if (!fs.existsSync(outdir)) {
|
|
91
|
+
fs.mkdirSync(outdir, { recursive: true });
|
|
92
|
+
}
|
|
93
|
+
const outputFile = createOutputFilename(inputFile, outdir);
|
|
94
|
+
fs.writeFileSync(outputFile, transpiled);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
process.stdout.write(transpiled);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function createOutputFilename(inputFile, outdir) {
|
|
101
|
+
const parsedInput = path.parse(inputFile);
|
|
102
|
+
return path.format({
|
|
103
|
+
dir: outdir,
|
|
104
|
+
name: parsedInput.name,
|
|
105
|
+
ext: '.yaml',
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function generatedFileComment(inputFile) {
|
|
109
|
+
return (`# This file has been generated by "ts2workflows ${inputFile}"\n` +
|
|
110
|
+
'# Do not edit!\n\n');
|
|
111
|
+
}
|
|
112
|
+
function isIoError(err) {
|
|
113
|
+
return err instanceof Error && 'code' in err;
|
|
64
114
|
}
|
|
65
115
|
function prettyPrintSyntaxError(exception, inputFile, sourceCode) {
|
|
66
116
|
console.error(errorDisplay(inputFile, sourceCode, exception.location));
|
|
@@ -108,6 +158,13 @@ function highlightedSourceCodeLine(sourceCode, lineNumber, start, end) {
|
|
|
108
158
|
const markerLine = `${' '.repeat(start)}${'^'.repeat(markerLength)}`;
|
|
109
159
|
return `${sourceLine}\n${markerLine}`;
|
|
110
160
|
}
|
|
161
|
+
function versionFromPackageJson() {
|
|
162
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
163
|
+
const currentDir = path.dirname(currentFile);
|
|
164
|
+
const packagePath = path.join(currentDir, '..', 'package.json');
|
|
165
|
+
const pjson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
|
|
166
|
+
return pjson.version ?? '???';
|
|
167
|
+
}
|
|
111
168
|
if (import.meta.url.endsWith(process.argv[1]) ||
|
|
112
169
|
process.argv[1].endsWith('/ts2workflows')) {
|
|
113
170
|
cliMain();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expressions.d.ts","sourceRoot":"","sources":["../../src/transpiler/expressions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAkB,MAAM,sCAAsC,CAAA;AAC/E,OAAO,EAGL,UAAU,EAGV,SAAS,EAMV,MAAM,uBAAuB,CAAA;AAI9B,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,GAAG,UAAU,CAO3E;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,QAAQ,CAAC,gBAAgB,GAC9B,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,UAAU,CAAC,CAgCxC;AAED,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,QAAQ,CAAC,gBAAgB,GAC9B,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAG5B;
|
|
1
|
+
{"version":3,"file":"expressions.d.ts","sourceRoot":"","sources":["../../src/transpiler/expressions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAkB,MAAM,sCAAsC,CAAA;AAC/E,OAAO,EAGL,UAAU,EAGV,SAAS,EAMV,MAAM,uBAAuB,CAAA;AAI9B,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,GAAG,UAAU,CAO3E;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,QAAQ,CAAC,gBAAgB,GAC9B,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,UAAU,CAAC,CAgCxC;AAED,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,QAAQ,CAAC,gBAAgB,GAC9B,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAG5B;AAmOD,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,QAAQ,CAAC,gBAAgB,GAC9B,UAAU,CAcZ;AA0JD,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAG3D;AAED,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAGvE;AAoDD,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,YAAY,CAAC,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,CAElE"}
|
|
@@ -87,6 +87,8 @@ function convertExpressionOrPrimitive(instance) {
|
|
|
87
87
|
return convertExpressionOrPrimitive(instance.expression);
|
|
88
88
|
case AST_NODE_TYPES.AwaitExpression:
|
|
89
89
|
return convertExpressionOrPrimitive(instance.argument);
|
|
90
|
+
case AST_NODE_TYPES.TSInstantiationExpression:
|
|
91
|
+
return convertExpressionOrPrimitive(instance.expression);
|
|
90
92
|
default:
|
|
91
93
|
throw new WorkflowSyntaxError(`Not implemented expression type: ${instance.type}`, instance.loc);
|
|
92
94
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function transpile(code: string): string;
|
|
1
|
+
export declare function transpile(code: string, inputFile?: string, tsconfigPath?: string): string;
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transpiler/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transpiler/index.ts"],"names":[],"mappings":"AAaA,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAkBR"}
|
package/dist/transpiler/index.js
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AST_NODE_TYPES, parseAndGenerateServices, } from '@typescript-eslint/typescript-estree';
|
|
2
2
|
import * as YAML from 'yaml';
|
|
3
3
|
import { SubworkflowAST } from '../ast/steps.js';
|
|
4
4
|
import { WorkflowSyntaxError } from '../errors.js';
|
|
5
5
|
import { generateStepNames } from '../ast/stepnames.js';
|
|
6
6
|
import { parseStatement } from './statements.js';
|
|
7
|
-
export function transpile(code) {
|
|
7
|
+
export function transpile(code, inputFile, tsconfigPath) {
|
|
8
8
|
const parserOptions = {
|
|
9
9
|
jsDocParsingMode: 'none',
|
|
10
10
|
loc: true,
|
|
11
11
|
range: false,
|
|
12
12
|
};
|
|
13
|
-
|
|
13
|
+
if (tsconfigPath && inputFile && inputFile != '-') {
|
|
14
|
+
parserOptions.filePath = inputFile;
|
|
15
|
+
parserOptions.projectService = {
|
|
16
|
+
defaultProject: tsconfigPath,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const { ast } = parseAndGenerateServices(code, parserOptions);
|
|
14
20
|
const workflowAst = { subworkflows: ast.body.flatMap(parseTopLevelStatement) };
|
|
15
21
|
const workflow = generateStepNames(workflowAst);
|
|
16
22
|
return YAML.stringify(workflow.render(), { lineWidth: 100 });
|
|
@@ -199,18 +199,21 @@ function createCallStep(node, argumentsNode, resultVariable) {
|
|
|
199
199
|
throw new WorkflowSyntaxError('The first argument must be a Function', node.loc);
|
|
200
200
|
}
|
|
201
201
|
let functionName;
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
202
|
+
const argNode = argumentsNode[0].type === AST_NODE_TYPES.TSInstantiationExpression
|
|
203
|
+
? argumentsNode[0].expression
|
|
204
|
+
: argumentsNode[0];
|
|
205
|
+
if (argNode.type === AST_NODE_TYPES.Identifier) {
|
|
206
|
+
functionName = argNode.name;
|
|
207
|
+
}
|
|
208
|
+
else if (argNode.type === AST_NODE_TYPES.MemberExpression) {
|
|
209
|
+
const memberExp = convertMemberExpression(argNode);
|
|
207
210
|
if (!isFullyQualifiedName(memberExp)) {
|
|
208
|
-
throw new WorkflowSyntaxError('Function name must be a fully-qualified name',
|
|
211
|
+
throw new WorkflowSyntaxError('Function name must be a fully-qualified name', argNode.loc);
|
|
209
212
|
}
|
|
210
213
|
functionName = memberExp.toString();
|
|
211
214
|
}
|
|
212
215
|
else {
|
|
213
|
-
throw new WorkflowSyntaxError('Expected an identifier or a member expression',
|
|
216
|
+
throw new WorkflowSyntaxError('Expected an identifier or a member expression', argNode.loc);
|
|
214
217
|
}
|
|
215
218
|
let args = {};
|
|
216
219
|
if (argumentsNode.length >= 2) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts2workflows",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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
|
-
"prepare": "husky"
|
|
23
|
+
"prepare": "husky && npm run build"
|
|
24
24
|
},
|
|
25
25
|
"lint-staged": {
|
|
26
26
|
"src/**/*.ts": [
|
|
@@ -74,8 +74,8 @@
|
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
76
|
"@typescript-eslint/typescript-estree": "^8.0.0",
|
|
77
|
-
"commander": "^
|
|
77
|
+
"commander": "^13.1.0",
|
|
78
78
|
"typescript": "^5.0.0",
|
|
79
79
|
"yaml": "^2.4.2"
|
|
80
80
|
}
|
|
81
|
-
}
|
|
81
|
+
}
|