@chainlink/cre-sdk 1.6.0-alpha.2 โ 1.6.0-alpha.4
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 +7 -1
- package/bin/cre-compile.ts +41 -12
- package/dist/sdk/report.js +0 -15
- package/package.json +3 -3
- package/scripts/run.ts +6 -1
- package/scripts/src/check-determinism.test.ts +64 -0
- package/scripts/src/check-determinism.ts +32 -0
- package/scripts/src/compile-cli-args.test.ts +32 -0
- package/scripts/src/compile-cli-args.ts +35 -0
- package/scripts/src/compile-to-js.test.ts +90 -0
- package/scripts/src/compile-to-js.ts +53 -7
- package/scripts/src/compile-to-wasm.ts +11 -5
- package/scripts/src/compile-workflow.ts +60 -13
- package/scripts/src/generate-chain-selectors.ts +9 -27
- package/scripts/src/typecheck-workflow.test.ts +77 -0
- package/scripts/src/typecheck-workflow.ts +96 -0
- package/scripts/src/validate-shared.ts +400 -0
- package/scripts/src/validate-workflow-determinism.test.ts +409 -0
- package/scripts/src/validate-workflow-determinism.ts +545 -0
- package/scripts/src/validate-workflow-runtime-compat.ts +25 -377
package/README.md
CHANGED
|
@@ -58,10 +58,16 @@ CRE workflows are compiled to WASM and executed through Javy (QuickJS). This is
|
|
|
58
58
|
|
|
59
59
|
- Node built-ins like `node:fs`, `node:crypto`, `node:http`, `node:net`, etc. are not supported in workflows.
|
|
60
60
|
- Browser globals like `fetch`, `window`, and `setTimeout` are also not available in workflow runtime.
|
|
61
|
-
- `cre compile:workflow` / `cre-compile` now validates workflow source and fails fast when unsupported APIs are used.
|
|
61
|
+
- `cre compile:workflow` / `cre-compile` now typechecks your workflow project first (using your nearest `tsconfig.json`), then validates workflow source and fails fast when unsupported APIs are used.
|
|
62
62
|
|
|
63
63
|
Use CRE capabilities (for example, `cre.capabilities.HTTPClient`) instead of direct Node/browser APIs.
|
|
64
64
|
|
|
65
|
+
If you need to compile despite TypeScript errors, pass `--skip-type-checks`:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
bun x cre-compile src/workflow.ts dist/workflow.wasm --skip-type-checks
|
|
69
|
+
```
|
|
70
|
+
|
|
65
71
|
## Getting Started
|
|
66
72
|
|
|
67
73
|
We recommend you consult the [getting started docs](https://docs.chain.link/cre/getting-started/cli-installation) and install the CRE CLI.
|
package/bin/cre-compile.ts
CHANGED
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
import { main as compileWorkflow } from "../scripts/src/compile-workflow";
|
|
4
|
-
import { parseCompileFlags } from "
|
|
4
|
+
import { parseCompileFlags } from "@chainlink/cre-sdk-javy-plugin/scripts/parse-compile-flags";
|
|
5
|
+
import {
|
|
6
|
+
parseCompileCliArgs,
|
|
7
|
+
skipTypeChecksFlag,
|
|
8
|
+
} from "../scripts/src/compile-cli-args";
|
|
9
|
+
import { WorkflowTypecheckError } from "../scripts/src/typecheck-workflow";
|
|
5
10
|
import { WorkflowRuntimeCompatibilityError } from "../scripts/src/validate-workflow-runtime-compat";
|
|
6
11
|
|
|
7
12
|
const main = async () => {
|
|
8
13
|
const cliArgs = process.argv.slice(2);
|
|
9
14
|
const { creExports, plugin, rest } = parseCompileFlags(cliArgs);
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
let inputPath: string | undefined;
|
|
17
|
+
let outputPathArg: string | undefined;
|
|
18
|
+
let skipTypeChecks = false;
|
|
13
19
|
|
|
14
|
-
|
|
20
|
+
try {
|
|
21
|
+
const parsed = parseCompileCliArgs(rest);
|
|
22
|
+
inputPath = parsed.inputPath;
|
|
23
|
+
outputPathArg = parsed.outputPath;
|
|
24
|
+
skipTypeChecks = parsed.skipTypeChecks;
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error(error instanceof Error ? error.message : error);
|
|
15
27
|
console.error(
|
|
16
|
-
|
|
28
|
+
`Usage: cre-compile [--plugin <path>] [--cre-exports <crate-dir>]... <path/to/workflow.ts> [path/to/output.wasm] [${skipTypeChecksFlag}]`,
|
|
17
29
|
);
|
|
18
30
|
process.exit(1);
|
|
19
31
|
}
|
|
@@ -23,16 +35,33 @@ const main = async () => {
|
|
|
23
35
|
process.exit(1);
|
|
24
36
|
}
|
|
25
37
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
);
|
|
38
|
+
if (!inputPath) {
|
|
39
|
+
console.error(
|
|
40
|
+
`Usage: cre-compile [--plugin <path>] [--cre-exports <crate-dir>]... <path/to/workflow.ts> [path/to/output.wasm] [${skipTypeChecksFlag}]`,
|
|
41
|
+
);
|
|
42
|
+
console.error("Examples:");
|
|
43
|
+
console.error(" cre-compile src/standard_tests/secrets/test.ts");
|
|
44
|
+
console.error(
|
|
45
|
+
" cre-compile src/standard_tests/secrets/test.ts .temp/standard_tests/secrets/test.wasm",
|
|
46
|
+
);
|
|
47
|
+
console.error(
|
|
48
|
+
` cre-compile src/standard_tests/secrets/test.ts .temp/standard_tests/secrets/test.wasm ${skipTypeChecksFlag}`,
|
|
49
|
+
);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await compileWorkflow(inputPath, outputPathArg, {
|
|
54
|
+
skipTypeChecks,
|
|
55
|
+
creExports: creExports.length > 0 ? creExports : undefined,
|
|
56
|
+
plugin,
|
|
57
|
+
});
|
|
32
58
|
};
|
|
33
59
|
|
|
34
60
|
main().catch((e) => {
|
|
35
|
-
if (
|
|
61
|
+
if (
|
|
62
|
+
e instanceof WorkflowRuntimeCompatibilityError ||
|
|
63
|
+
e instanceof WorkflowTypecheckError
|
|
64
|
+
) {
|
|
36
65
|
console.error(`\nโ ${e.message}`);
|
|
37
66
|
} else {
|
|
38
67
|
console.error(e);
|
package/dist/sdk/report.js
CHANGED
|
@@ -227,21 +227,6 @@ function fetchDONInfo(runtime, env, donID) {
|
|
|
227
227
|
signers.set(addr, nodeOperatorId);
|
|
228
228
|
}
|
|
229
229
|
const info = { f, signers };
|
|
230
|
-
if (donID === 1 && isProductionEnvironmentForReport(env)) {
|
|
231
|
-
const patch = (hexAddr, id) => {
|
|
232
|
-
info.signers.set(getAddress(hexAddr), id);
|
|
233
|
-
};
|
|
234
|
-
patch('0xcdf20f8ffd41b02c680988b20e68735cc8c1ca17', 5 + 1);
|
|
235
|
-
patch('0xff9b062fccb2f042311343048b9518068370f837', 4 + 1);
|
|
236
|
-
patch('0xde5cd1dd4300a0b4854f8223add60d20e1dfe21b', 1 + 1);
|
|
237
|
-
patch('0x4d6cfd44f94408a39fb1af94a53c107a730ba161', 9 + 1);
|
|
238
|
-
patch('0xf3baa9a99b5ad64f50779f449bac83baac8bfdb6', 0 + 1);
|
|
239
|
-
patch('0xd7f22fb5382ff477d2ff5c702cab0ef8abf18233', 7 + 1);
|
|
240
|
-
patch('0x4d7d71c7e584cfa1f5c06275e5d283b9d3176924', 8 + 1);
|
|
241
|
-
patch('0x1a89c98e75983ec384ad8e83eaf7d0176eeaf155', 3 + 1);
|
|
242
|
-
patch('0x4f99b550623e77b807df7cbed9c79d55e1163b48', 2 + 1);
|
|
243
|
-
patch('0xe55fcaf921e76c6bbcf9415bba12b1236f07b0c3', 6 + 1);
|
|
244
|
-
}
|
|
245
230
|
donInfoCache.set(key, info);
|
|
246
231
|
return info;
|
|
247
232
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chainlink/cre-sdk",
|
|
3
|
-
"version": "1.6.0-alpha.
|
|
3
|
+
"version": "1.6.0-alpha.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"format": "biome format --write ${BIOME_PATHS:-.}",
|
|
50
50
|
"full-checks": "bun generate:sdk && bun run build && bun typecheck && bun check && bun test && bun test:standard",
|
|
51
51
|
"generate:chain-selectors": "bun scripts/run.ts generate-chain-selectors && BIOME_PATHS=\"src/generated\" bun check",
|
|
52
|
-
"generate:proto": "
|
|
52
|
+
"generate:proto": "bun x @bufbuild/buf generate && BIOME_PATHS=\"src/generated\" bun check",
|
|
53
53
|
"generate:sdk": "bun generate:proto && bun generate:chain-selectors && bun scripts/run generate-sdks && BIOME_PATHS=\"src/generated src/generated-sdk src/sdk/test/generated\" bun check",
|
|
54
54
|
"lint": "biome lint --write",
|
|
55
55
|
"prepublishOnly": "bun typecheck && bun check && bun test && bun test:standard",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@bufbuild/protobuf": "2.6.3",
|
|
62
62
|
"@bufbuild/protoc-gen-es": "2.6.3",
|
|
63
|
-
"@chainlink/cre-sdk-javy-plugin": "1.6.0-alpha.
|
|
63
|
+
"@chainlink/cre-sdk-javy-plugin": "1.6.0-alpha.4",
|
|
64
64
|
"@standard-schema/spec": "1.0.0",
|
|
65
65
|
"viem": "2.34.0",
|
|
66
66
|
"zod": "3.25.76"
|
package/scripts/run.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
+
import { WorkflowTypecheckError } from './src/typecheck-workflow'
|
|
3
4
|
import { WorkflowRuntimeCompatibilityError } from './src/validate-workflow-runtime-compat'
|
|
4
5
|
|
|
5
6
|
const availableScripts = [
|
|
6
7
|
'build-types',
|
|
8
|
+
'check-determinism', // Check for non-deterministic patterns in workflow source
|
|
7
9
|
'compile-to-js',
|
|
8
10
|
'compile-to-wasm',
|
|
9
11
|
'compile-workflow', // TS -> JS -> WASM compilation in single script
|
|
@@ -39,7 +41,10 @@ const main = async () => {
|
|
|
39
41
|
process.exit(1)
|
|
40
42
|
}
|
|
41
43
|
} catch (error) {
|
|
42
|
-
if (
|
|
44
|
+
if (
|
|
45
|
+
error instanceof WorkflowRuntimeCompatibilityError ||
|
|
46
|
+
error instanceof WorkflowTypecheckError
|
|
47
|
+
) {
|
|
43
48
|
console.error(`\nโ ${error.message}`)
|
|
44
49
|
} else {
|
|
45
50
|
console.error(`Failed to run script ${scriptName}:`, error)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
|
+
import { spawnSync } from 'node:child_process'
|
|
3
|
+
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
|
|
7
|
+
let tempDir: string
|
|
8
|
+
|
|
9
|
+
const scriptsDir = path.resolve(import.meta.dir, '..')
|
|
10
|
+
const runScript = path.join(scriptsDir, 'run.ts')
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
tempDir = mkdtempSync(path.join(tmpdir(), 'cre-check-determinism-test-'))
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
rmSync(tempDir, { recursive: true, force: true })
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const runCheckDeterminism = (filePath: string) =>
|
|
21
|
+
spawnSync(process.execPath, [runScript, 'check-determinism', filePath], {
|
|
22
|
+
cwd: scriptsDir,
|
|
23
|
+
encoding: 'utf-8',
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
describe('check-determinism CLI', () => {
|
|
27
|
+
test('fails when the input file does not exist', () => {
|
|
28
|
+
const missingFile = path.join(tempDir, 'does-not-exist.ts')
|
|
29
|
+
const result = runCheckDeterminism(missingFile)
|
|
30
|
+
|
|
31
|
+
expect(result.status).toBe(1)
|
|
32
|
+
expect(result.stdout).not.toContain('No non-determinism warnings found.')
|
|
33
|
+
expect(result.stderr).toContain(`โ File not found: ${missingFile}`)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('prints warnings for non-deterministic patterns and exits 0', () => {
|
|
37
|
+
const filePath = path.join(tempDir, 'workflow.ts')
|
|
38
|
+
writeFileSync(filePath, `const result = await Promise.race([]);\n`, 'utf-8')
|
|
39
|
+
const result = runCheckDeterminism(filePath)
|
|
40
|
+
|
|
41
|
+
expect(result.status).toBe(0)
|
|
42
|
+
expect(result.stderr).toContain('Non-determinism warnings')
|
|
43
|
+
expect(result.stderr).toContain('Promise.race()')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('prints success message for clean workflow and exits 0', () => {
|
|
47
|
+
const filePath = path.join(tempDir, 'workflow.ts')
|
|
48
|
+
writeFileSync(filePath, `const x = 1;\n`, 'utf-8')
|
|
49
|
+
const result = runCheckDeterminism(filePath)
|
|
50
|
+
|
|
51
|
+
expect(result.status).toBe(0)
|
|
52
|
+
expect(result.stdout).toContain('No non-determinism warnings found.')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('fails when no input file is provided', () => {
|
|
56
|
+
const result = spawnSync(process.execPath, [runScript, 'check-determinism'], {
|
|
57
|
+
cwd: scriptsDir,
|
|
58
|
+
encoding: 'utf-8',
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
expect(result.status).toBe(1)
|
|
62
|
+
expect(result.stderr).toContain('Usage:')
|
|
63
|
+
})
|
|
64
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { checkWorkflowDeterminism, printDeterminismWarnings } from './validate-workflow-determinism'
|
|
4
|
+
|
|
5
|
+
const printUsage = () => {
|
|
6
|
+
console.error('Usage: bun scripts/run.ts check-determinism <path/to/workflow.ts>')
|
|
7
|
+
console.error('Example:')
|
|
8
|
+
console.error(' bun scripts/run.ts check-determinism src/workflows/my-workflow/index.ts')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const main = () => {
|
|
12
|
+
const inputPath = process.argv[3]
|
|
13
|
+
|
|
14
|
+
if (!inputPath) {
|
|
15
|
+
printUsage()
|
|
16
|
+
process.exit(1)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const resolvedInput = path.resolve(inputPath)
|
|
20
|
+
if (!existsSync(resolvedInput)) {
|
|
21
|
+
console.error(`โ File not found: ${resolvedInput}`)
|
|
22
|
+
process.exit(1)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const warnings = checkWorkflowDeterminism(resolvedInput)
|
|
26
|
+
|
|
27
|
+
if (warnings.length > 0) {
|
|
28
|
+
printDeterminismWarnings(warnings)
|
|
29
|
+
} else {
|
|
30
|
+
console.info('No non-determinism warnings found.')
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { parseCompileCliArgs } from './compile-cli-args'
|
|
3
|
+
|
|
4
|
+
describe('parseCompileCliArgs', () => {
|
|
5
|
+
test('parses positional input and output', () => {
|
|
6
|
+
const parsed = parseCompileCliArgs(['src/workflow.ts', 'dist/workflow.wasm'])
|
|
7
|
+
expect(parsed).toEqual({
|
|
8
|
+
inputPath: 'src/workflow.ts',
|
|
9
|
+
outputPath: 'dist/workflow.wasm',
|
|
10
|
+
skipTypeChecks: false,
|
|
11
|
+
})
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('parses --skip-type-checks flag', () => {
|
|
15
|
+
const parsed = parseCompileCliArgs(['src/workflow.ts', '--skip-type-checks'])
|
|
16
|
+
expect(parsed).toEqual({
|
|
17
|
+
inputPath: 'src/workflow.ts',
|
|
18
|
+
outputPath: undefined,
|
|
19
|
+
skipTypeChecks: true,
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('throws on unknown flags', () => {
|
|
24
|
+
expect(() => parseCompileCliArgs(['src/workflow.ts', '--foo'])).toThrow('Unknown option: --foo')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('throws on too many positional args', () => {
|
|
28
|
+
expect(() => parseCompileCliArgs(['src/workflow.ts', 'dist/workflow.wasm', 'extra'])).toThrow(
|
|
29
|
+
'Too many positional arguments.',
|
|
30
|
+
)
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const skipTypeChecksFlag = '--skip-type-checks'
|
|
2
|
+
|
|
3
|
+
export type ParsedCompileArgs = {
|
|
4
|
+
inputPath?: string
|
|
5
|
+
outputPath?: string
|
|
6
|
+
skipTypeChecks: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const parseCompileCliArgs = (args: string[]): ParsedCompileArgs => {
|
|
10
|
+
const positionalArgs: string[] = []
|
|
11
|
+
let skipTypeChecks = false
|
|
12
|
+
|
|
13
|
+
for (const arg of args) {
|
|
14
|
+
if (arg === skipTypeChecksFlag) {
|
|
15
|
+
skipTypeChecks = true
|
|
16
|
+
continue
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (arg.startsWith('-')) {
|
|
20
|
+
throw new Error(`Unknown option: ${arg}`)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
positionalArgs.push(arg)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (positionalArgs.length > 2) {
|
|
27
|
+
throw new Error('Too many positional arguments.')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
inputPath: positionalArgs[0],
|
|
32
|
+
outputPath: positionalArgs[1],
|
|
33
|
+
skipTypeChecks,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
|
+
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { main as compileToJs } from './compile-to-js'
|
|
5
|
+
import { WorkflowTypecheckError } from './typecheck-workflow'
|
|
6
|
+
|
|
7
|
+
let tempDir: string
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
tempDir = mkdtempSync(path.join(process.cwd(), '.tmp-cre-compile-to-js-test-'))
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
rmSync(tempDir, { recursive: true, force: true })
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const writeTemp = (filename: string, content: string): string => {
|
|
18
|
+
const filePath = path.join(tempDir, filename)
|
|
19
|
+
mkdirSync(path.dirname(filePath), { recursive: true })
|
|
20
|
+
writeFileSync(filePath, content, 'utf-8')
|
|
21
|
+
return filePath
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('compile-to-js typecheck behavior', () => {
|
|
25
|
+
test('fails on type errors by default', async () => {
|
|
26
|
+
writeTemp(
|
|
27
|
+
'tsconfig.json',
|
|
28
|
+
JSON.stringify(
|
|
29
|
+
{
|
|
30
|
+
compilerOptions: {
|
|
31
|
+
target: 'ES2022',
|
|
32
|
+
module: 'ESNext',
|
|
33
|
+
moduleResolution: 'Bundler',
|
|
34
|
+
skipLibCheck: true,
|
|
35
|
+
strict: true,
|
|
36
|
+
types: [],
|
|
37
|
+
lib: ['ESNext'],
|
|
38
|
+
},
|
|
39
|
+
include: ['src/**/*.ts'],
|
|
40
|
+
},
|
|
41
|
+
null,
|
|
42
|
+
2,
|
|
43
|
+
),
|
|
44
|
+
)
|
|
45
|
+
const entry = writeTemp('src/workflow.ts', "export const shouldBeNumber: number = 'oops'\n")
|
|
46
|
+
const output = path.join(tempDir, 'dist/workflow.js')
|
|
47
|
+
|
|
48
|
+
await expect(compileToJs(entry, output)).rejects.toBeInstanceOf(WorkflowTypecheckError)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('continues when --skip-type-checks is enabled', async () => {
|
|
52
|
+
writeTemp(
|
|
53
|
+
'tsconfig.json',
|
|
54
|
+
JSON.stringify(
|
|
55
|
+
{
|
|
56
|
+
compilerOptions: {
|
|
57
|
+
target: 'ES2022',
|
|
58
|
+
module: 'ESNext',
|
|
59
|
+
moduleResolution: 'Bundler',
|
|
60
|
+
skipLibCheck: true,
|
|
61
|
+
strict: true,
|
|
62
|
+
types: [],
|
|
63
|
+
lib: ['ESNext'],
|
|
64
|
+
},
|
|
65
|
+
include: ['src/**/*.ts'],
|
|
66
|
+
},
|
|
67
|
+
null,
|
|
68
|
+
2,
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
const entry = writeTemp('src/workflow.ts', "export const shouldBeNumber: number = 'oops'\n")
|
|
72
|
+
const output = path.join(tempDir, 'dist/workflow.js')
|
|
73
|
+
|
|
74
|
+
let thrownError: unknown
|
|
75
|
+
let result: string | undefined
|
|
76
|
+
try {
|
|
77
|
+
result = await compileToJs(entry, output, {
|
|
78
|
+
skipTypeChecks: true,
|
|
79
|
+
})
|
|
80
|
+
} catch (error) {
|
|
81
|
+
thrownError = error
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
expect(thrownError).not.toBeInstanceOf(WorkflowTypecheckError)
|
|
85
|
+
if (typeof result === 'string') {
|
|
86
|
+
expect(result).toEqual(output)
|
|
87
|
+
expect(existsSync(output)).toBeTrue()
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
})
|
|
@@ -2,25 +2,71 @@ import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
|
2
2
|
import { mkdir } from 'node:fs/promises'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import { $ } from 'bun'
|
|
5
|
+
import { parseCompileCliArgs, skipTypeChecksFlag } from './compile-cli-args'
|
|
6
|
+
import { assertWorkflowTypecheck } from './typecheck-workflow'
|
|
7
|
+
import { checkWorkflowDeterminism, printDeterminismWarnings } from './validate-workflow-determinism'
|
|
5
8
|
import { assertWorkflowRuntimeCompatibility } from './validate-workflow-runtime-compat'
|
|
6
9
|
import { wrapWorkflowCode } from './workflow-wrapper'
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
type CompileToJsOptions = {
|
|
12
|
+
skipTypeChecks?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const printUsage = () => {
|
|
16
|
+
console.error(`Usage: bun compile:ts-to-js <path-to-file> [output-file] [${skipTypeChecksFlag}]`)
|
|
17
|
+
console.error('Example:')
|
|
18
|
+
console.error(' bun compile:ts-to-js src/tests/foo.ts dist/tests/foo.bundle.js')
|
|
19
|
+
console.error(
|
|
20
|
+
` bun compile:ts-to-js src/tests/foo.ts dist/tests/foo.bundle.js ${skipTypeChecksFlag}`,
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const main = async (
|
|
25
|
+
tsFilePath?: string,
|
|
26
|
+
outputFilePath?: string,
|
|
27
|
+
options?: CompileToJsOptions,
|
|
28
|
+
) => {
|
|
29
|
+
let parsedInputPath: string | undefined
|
|
30
|
+
let parsedOutputPath: string | undefined
|
|
31
|
+
let parsedSkipTypeChecks = false
|
|
32
|
+
|
|
33
|
+
if (tsFilePath != null || outputFilePath != null || options?.skipTypeChecks != null) {
|
|
34
|
+
parsedInputPath = tsFilePath
|
|
35
|
+
parsedOutputPath = outputFilePath
|
|
36
|
+
parsedSkipTypeChecks = options?.skipTypeChecks ?? false
|
|
37
|
+
} else {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = parseCompileCliArgs(process.argv.slice(3))
|
|
40
|
+
parsedInputPath = parsed.inputPath
|
|
41
|
+
parsedOutputPath = parsed.outputPath
|
|
42
|
+
parsedSkipTypeChecks = parsed.skipTypeChecks
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error(error instanceof Error ? error.message : error)
|
|
45
|
+
printUsage()
|
|
46
|
+
process.exit(1)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
10
49
|
|
|
11
50
|
// Prefer function params, fallback to CLI args
|
|
12
|
-
const inputPath =
|
|
13
|
-
const outputPathArg =
|
|
51
|
+
const inputPath = parsedInputPath
|
|
52
|
+
const outputPathArg = parsedOutputPath
|
|
14
53
|
|
|
15
54
|
if (!inputPath) {
|
|
16
|
-
|
|
17
|
-
console.error('Example:')
|
|
18
|
-
console.error(' bun test:standard:compile:js src/tests/foo.ts dist/tests/foo.bundle.js')
|
|
55
|
+
printUsage()
|
|
19
56
|
process.exit(1)
|
|
20
57
|
}
|
|
21
58
|
|
|
22
59
|
const resolvedInput = path.resolve(inputPath)
|
|
60
|
+
if (!parsedSkipTypeChecks) {
|
|
61
|
+
assertWorkflowTypecheck(resolvedInput)
|
|
62
|
+
}
|
|
23
63
|
assertWorkflowRuntimeCompatibility(resolvedInput)
|
|
64
|
+
if (!parsedSkipTypeChecks) {
|
|
65
|
+
const warnings = checkWorkflowDeterminism(resolvedInput)
|
|
66
|
+
if (warnings.length > 0) {
|
|
67
|
+
printDeterminismWarnings(warnings)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
24
70
|
console.info(`๐ Using input file: ${resolvedInput}`)
|
|
25
71
|
|
|
26
72
|
// If no explicit output path โ same dir, swap extension to .js
|
|
@@ -2,7 +2,7 @@ import { spawn } from 'node:child_process'
|
|
|
2
2
|
import { existsSync } from 'node:fs'
|
|
3
3
|
import { mkdir } from 'node:fs/promises'
|
|
4
4
|
import path from 'node:path'
|
|
5
|
-
import { parseCompileFlags } from '
|
|
5
|
+
import { parseCompileFlags } from '@chainlink/cre-sdk-javy-plugin/scripts/parse-compile-flags'
|
|
6
6
|
|
|
7
7
|
function runBun(args: string[]): Promise<void> {
|
|
8
8
|
return new Promise((resolve, reject) => {
|
|
@@ -77,10 +77,16 @@ export const main = async (
|
|
|
77
77
|
}
|
|
78
78
|
compileArgs.push(resolvedInput, resolvedOutput)
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
let javyPluginRoot: string
|
|
81
|
+
if (process.env.CRE_SDK_JAVY_PLUGIN_HOME) {
|
|
82
|
+
javyPluginRoot = path.resolve(process.env.CRE_SDK_JAVY_PLUGIN_HOME)
|
|
83
|
+
} else {
|
|
84
|
+
try {
|
|
85
|
+
javyPluginRoot = path.dirname(require.resolve('@chainlink/cre-sdk-javy-plugin/package.json'))
|
|
86
|
+
} catch {
|
|
87
|
+
javyPluginRoot = path.resolve(import.meta.dir, '../../../cre-sdk-javy-plugin')
|
|
88
|
+
}
|
|
89
|
+
}
|
|
84
90
|
const compilerPath = path.join(javyPluginRoot, 'bin/compile-workflow.ts')
|
|
85
91
|
if (existsSync(compilerPath)) {
|
|
86
92
|
await runBun(['--bun', compilerPath, ...compileArgs])
|
|
@@ -1,31 +1,75 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { mkdir } from 'node:fs/promises'
|
|
3
3
|
import path from 'node:path'
|
|
4
|
-
import { parseCompileFlags } from '
|
|
4
|
+
import { parseCompileFlags } from '@chainlink/cre-sdk-javy-plugin/scripts/parse-compile-flags'
|
|
5
|
+
import { parseCompileCliArgs, skipTypeChecksFlag } from './compile-cli-args'
|
|
5
6
|
import { main as compileToJs } from './compile-to-js'
|
|
6
7
|
import { main as compileToWasm } from './compile-to-wasm'
|
|
7
8
|
|
|
9
|
+
type CompileWorkflowOptions = {
|
|
10
|
+
skipTypeChecks?: boolean
|
|
11
|
+
creExports?: string[]
|
|
12
|
+
plugin?: string | null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const printUsage = () => {
|
|
16
|
+
console.error(
|
|
17
|
+
`Usage: bun compile:workflow [--plugin <path>] [--cre-exports <crate-dir>]... <path/to/workflow.ts> [path/to/output.wasm] [${skipTypeChecksFlag}]`,
|
|
18
|
+
)
|
|
19
|
+
console.error('Examples:')
|
|
20
|
+
console.error(' bun compile:workflow src/standard_tests/secrets/test.ts')
|
|
21
|
+
console.error(
|
|
22
|
+
' bun compile:workflow src/standard_tests/secrets/test.ts .temp/standard_tests/secrets/test.wasm',
|
|
23
|
+
)
|
|
24
|
+
console.error(
|
|
25
|
+
` bun compile:workflow src/standard_tests/secrets/test.ts .temp/standard_tests/secrets/test.wasm ${skipTypeChecksFlag}`,
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
8
29
|
export const main = async (
|
|
9
30
|
inputFile?: string,
|
|
10
31
|
outputWasmFile?: string,
|
|
11
|
-
|
|
12
|
-
pluginPath?: string | null,
|
|
32
|
+
options?: CompileWorkflowOptions,
|
|
13
33
|
) => {
|
|
14
|
-
|
|
15
|
-
|
|
34
|
+
let parsedInputPath: string | undefined
|
|
35
|
+
let parsedOutputPath: string | undefined
|
|
36
|
+
let parsedSkipTypeChecks = false
|
|
37
|
+
let parsedCreExports: string[] = []
|
|
38
|
+
let parsedPlugin: string | null = null
|
|
16
39
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
40
|
+
if (inputFile != null || outputWasmFile != null || options != null) {
|
|
41
|
+
parsedInputPath = inputFile
|
|
42
|
+
parsedOutputPath = outputWasmFile
|
|
43
|
+
parsedSkipTypeChecks = options?.skipTypeChecks ?? false
|
|
44
|
+
parsedCreExports = options?.creExports ?? []
|
|
45
|
+
parsedPlugin = options?.plugin !== undefined ? options.plugin : null
|
|
46
|
+
} else {
|
|
47
|
+
try {
|
|
48
|
+
const cliArgs = process.argv.slice(3)
|
|
49
|
+
const { creExports, plugin, rest } = parseCompileFlags(cliArgs)
|
|
50
|
+
const parsed = parseCompileCliArgs(rest)
|
|
51
|
+
parsedInputPath = parsed.inputPath
|
|
52
|
+
parsedOutputPath = parsed.outputPath
|
|
53
|
+
parsedSkipTypeChecks = parsed.skipTypeChecks
|
|
54
|
+
parsedCreExports = creExports
|
|
55
|
+
parsedPlugin = plugin
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(error instanceof Error ? error.message : error)
|
|
58
|
+
printUsage()
|
|
59
|
+
process.exit(1)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
21
62
|
|
|
22
|
-
|
|
63
|
+
const inputPath = parsedInputPath
|
|
64
|
+
const outputPathArg = parsedOutputPath
|
|
65
|
+
|
|
66
|
+
if (parsedPlugin != null && parsedPlugin !== '' && parsedCreExports.length > 0) {
|
|
23
67
|
console.error('โ Error: --plugin and --cre-exports are mutually exclusive.')
|
|
24
68
|
process.exit(1)
|
|
25
69
|
}
|
|
26
70
|
|
|
27
71
|
if (!inputPath) {
|
|
28
|
-
|
|
72
|
+
printUsage()
|
|
29
73
|
process.exit(1)
|
|
30
74
|
}
|
|
31
75
|
|
|
@@ -48,12 +92,15 @@ export const main = async (
|
|
|
48
92
|
console.info(`๐ Input: ${resolvedInput}`)
|
|
49
93
|
console.info(`๐งช JS out: ${resolvedJsOutput}`)
|
|
50
94
|
console.info(`๐ฏ WASM out:${resolvedWasmOutput}\n`)
|
|
95
|
+
if (parsedSkipTypeChecks) {
|
|
96
|
+
console.info(`โ ๏ธ Skipping TypeScript checks (${skipTypeChecksFlag})`)
|
|
97
|
+
}
|
|
51
98
|
|
|
52
99
|
console.info('๐ฆ Step 1: Compiling JS...')
|
|
53
|
-
await compileToJs(resolvedInput, resolvedJsOutput)
|
|
100
|
+
await compileToJs(resolvedInput, resolvedJsOutput, { skipTypeChecks: parsedSkipTypeChecks })
|
|
54
101
|
|
|
55
102
|
console.info('\n๐จ Step 2: Compiling to WASM...')
|
|
56
|
-
await compileToWasm(resolvedJsOutput, resolvedWasmOutput,
|
|
103
|
+
await compileToWasm(resolvedJsOutput, resolvedWasmOutput, parsedCreExports, parsedPlugin)
|
|
57
104
|
|
|
58
105
|
console.info(`\nโ
Workflow built: ${resolvedWasmOutput}`)
|
|
59
106
|
return resolvedWasmOutput
|