-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathcompile.ts
More file actions
180 lines (160 loc) · 4.65 KB
/
compile.ts
File metadata and controls
180 lines (160 loc) · 4.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import compile, {
preloadAdaptorExports,
Options,
getExports,
} from '@openfn/compiler';
import { getModulePath } from '@openfn/runtime';
import type {
ExecutionPlan,
Job,
SourceMapWithOperations,
} from '@openfn/lexicon';
import createLogger, { COMPILER, Logger } from '../util/logger';
import abort from '../util/abort';
import type { CompileOptions } from './command';
export type CompiledJob = { code: string; map?: SourceMapWithOperations };
export default async function (
job: ExecutionPlan,
opts: CompileOptions,
log: Logger
): Promise<ExecutionPlan>;
export default async function (
plan: string,
opts: CompileOptions,
log: Logger
): Promise<CompiledJob>;
export default async function (
planOrPath: string | ExecutionPlan,
opts: CompileOptions,
log: Logger
): Promise<CompiledJob | ExecutionPlan> {
if (typeof planOrPath === 'string') {
const result = await compileJob(planOrPath as string, opts, log);
log.success(`Compiled expression from ${opts.expressionPath}`);
return result;
}
const compiledPlan = await compileWorkflow(
planOrPath as ExecutionPlan,
opts,
log
);
log.success('Compiled all expressions in workflow');
return compiledPlan;
}
const compileJob = async (
job: string,
opts: CompileOptions,
log: Logger,
jobName?: string
): Promise<CompiledJob> => {
try {
const compilerOptions: Options = await loadTransformOptions(opts, log);
return compile(job, compilerOptions);
} catch (e: any) {
abort(
log,
`Failed to compile job ${jobName ?? ''}`.trim(),
e,
'Check the syntax of the job expression:\n\n' + job
);
// This will never actually execute
return { code: job };
}
};
// Find every expression in the job and run the compiler on it
const compileWorkflow = async (
plan: ExecutionPlan,
opts: CompileOptions,
log: Logger
) => {
let globalsIgnoreList: string[] = getExports(plan.workflow.globals);
for (const step of plan.workflow.steps) {
const job = step as Job;
const jobOpts = {
...opts,
adaptors: job.adaptors ?? opts.adaptors,
ignoreImports: globalsIgnoreList,
};
if (job.expression) {
const { code, map } = await compileJob(
job.expression as string,
jobOpts,
log,
job.id
);
job.expression = code;
job.sourceMap = map;
}
}
return plan;
};
// TODO this is a bit of a temporary solution
// Adaptors need a version specifier right now to load type definitions for auto import
// But that specifier must be excluded in the actual import by the adaptor
export const stripVersionSpecifier = (specifier: string) => {
const idx = specifier.lastIndexOf('@');
if (idx > 0) {
return specifier.substring(0, idx);
}
return specifier;
};
// Take a module path as provided by the CLI and convert it into a path
export const resolveSpecifierPath = async (
pattern: string,
repoDir: string | undefined,
log: Logger
) => {
const [specifier, path] = pattern.split('=');
if (path) {
// given an explicit path, just load it.
log.debug(`Resolved ${specifier} to path: ${path}`);
return path;
}
const repoPath = await getModulePath(specifier, repoDir, log);
if (repoPath) {
return repoPath;
}
return null;
};
// Mutate the opts object to write export information for the add-imports transformer
export const loadTransformOptions = async (
opts: CompileOptions,
log: Logger
) => {
const options: Options = {
logger: log || createLogger(COMPILER, opts as any),
};
// If an adaptor is passed in, we need to look up its declared exports
// and pass them along to the compiler
if (opts.adaptors?.length && opts.ignoreImports != true) {
const adaptorsConfig = [];
for (const adaptorInput of opts.adaptors) {
let exports;
const [specifier] = adaptorInput.split('=');
// Preload exports from a path, optionally logging errors in case of a failure
log.debug(`Trying to preload types for ${specifier}`);
const path = await resolveSpecifierPath(adaptorInput, opts.repoDir, log);
if (path) {
try {
exports = await preloadAdaptorExports(path, log);
} catch (e) {
log.error(`Failed to load adaptor typedefs from path ${path}`);
log.error(e);
}
}
if (!exports || exports.length === 0) {
log.debug(`No module exports found for ${adaptorInput}`);
}
adaptorsConfig.push({
name: stripVersionSpecifier(specifier),
exports,
exportAll: true,
});
}
options['add-imports'] = {
ignore: opts.ignoreImports as string[],
adaptors: adaptorsConfig,
};
}
return options;
};