1
1
#!/usr/bin/env node
2
2
3
+ import { esbuildDecorators } from "@anatine/esbuild-decorators" ;
3
4
import { Command , Option } from "commander" ;
5
+ import { build } from "esbuild" ;
6
+ import { readFileSync } from "node:fs" ;
7
+ import { mkdir , writeFile } from "node:fs/promises" ;
8
+ import { join , posix } from "node:path" ;
9
+ import invariant from "tiny-invariant" ;
4
10
import { z } from "zod" ;
11
+ import { fromZodError } from "zod-validation-error" ;
5
12
6
- import { compileProject , DeployCommandOptions } from "../src/commands/deploy.js" ;
13
+ import {
14
+ bundleDependenciesPlugin ,
15
+ mockServerOnlyPlugin ,
16
+ workerSetupImportConfigPlugin ,
17
+ } from "../src/utilities/build.js" ;
7
18
import { readConfig } from "../src/utilities/configFiles.js" ;
19
+ import { writeJSONFile } from "../src/utilities/fileSystem.js" ;
8
20
import { logger } from "../src/utilities/logger.js" ;
9
- import { fromZodError } from "zod-validation-error" ;
21
+ import { cliRootPath } from "../src/utilities/resolveInternalFilePath.js" ;
22
+ import { createTaskFileImports , gatherTaskFiles } from "../src/utilities/taskFiles.js" ;
23
+ import { escapeImportPath , spinner } from "../src/utilities/windows.js" ;
10
24
11
25
const CompileCommandOptionsSchema = z . object ( {
12
26
logLevel : z . enum ( [ "debug" , "info" , "log" , "warn" , "error" , "none" ] ) . default ( "log" ) ,
13
27
skipTypecheck : z . boolean ( ) . default ( false ) ,
14
- config : z . string ( ) . optional ( ) ,
15
- projectRef : z . string ( ) . optional ( ) ,
16
28
outputMetafile : z . string ( ) . optional ( ) ,
17
29
} ) ;
18
30
@@ -31,11 +43,6 @@ export function configureCompileCommand(program: Command) {
31
43
"log"
32
44
)
33
45
. option ( "--skip-typecheck" , "Whether to skip the pre-build typecheck" )
34
- . option ( "-c, --config <config file>" , "The name of the config file, found at [path]" )
35
- . option (
36
- "-p, --project-ref <project ref>" ,
37
- "The project ref. Required if there is no config file. This will override the project specified in the config file."
38
- )
39
46
. addOption (
40
47
new Option (
41
48
"--output-metafile <path>" ,
@@ -52,20 +59,194 @@ async function compile(dir: string, options: CompileCommandOptions) {
52
59
}
53
60
logger . loggerLevel = parsedOptions . data . logLevel ;
54
61
55
- const resolvedConfig = await readConfig ( dir , {
56
- configFile : options . config ,
57
- projectRef : options . projectRef ,
58
- } ) ;
62
+ const resolvedConfig = await readConfig ( dir ) ;
59
63
60
64
if ( resolvedConfig . status === "error" ) {
61
65
throw new Error ( `cannot resolve config in directory ${ dir } ` ) ;
62
66
}
63
67
64
- const { path } = await compileProject (
65
- resolvedConfig . config ,
66
- options as DeployCommandOptions ,
67
- resolvedConfig . status === "file" ? resolvedConfig . path : undefined
68
+ const { config } = resolvedConfig ;
69
+ const configPath = resolvedConfig . status === "file" ? resolvedConfig . path : undefined ;
70
+
71
+ // COPIED FROM compileProject()
72
+ // const compileSpinner = spinner();
73
+ // compileSpinner.start(`Building project in ${config.projectDir}`);
74
+
75
+ const taskFiles = await gatherTaskFiles ( config ) ;
76
+ const workerFacade = readFileSync (
77
+ join ( cliRootPath ( ) , "workers" , "prod" , "worker-facade.js" ) ,
78
+ "utf-8"
79
+ ) ;
80
+
81
+ const workerSetupPath = join ( cliRootPath ( ) , "workers" , "prod" , "worker-setup.js" ) ;
82
+
83
+ let workerContents = workerFacade
84
+ . replace ( "__TASKS__" , createTaskFileImports ( taskFiles ) )
85
+ . replace (
86
+ "__WORKER_SETUP__" ,
87
+ `import { tracingSDK, otelTracer, otelLogger } from "${ escapeImportPath ( workerSetupPath ) } ";`
88
+ ) ;
89
+
90
+ if ( configPath ) {
91
+ logger . debug ( "Importing project config from" , { configPath } ) ;
92
+
93
+ workerContents = workerContents . replace (
94
+ "__IMPORTED_PROJECT_CONFIG__" ,
95
+ `import * as importedConfigExports from "${ escapeImportPath (
96
+ configPath
97
+ ) } "; const importedConfig = importedConfigExports.config; const handleError = importedConfigExports.handleError;`
98
+ ) ;
99
+ } else {
100
+ workerContents = workerContents . replace (
101
+ "__IMPORTED_PROJECT_CONFIG__" ,
102
+ `const importedConfig = undefined; const handleError = undefined;`
103
+ ) ;
104
+ }
105
+
106
+ const result = await build ( {
107
+ stdin : {
108
+ contents : workerContents ,
109
+ resolveDir : process . cwd ( ) ,
110
+ sourcefile : "__entryPoint.ts" ,
111
+ } ,
112
+ bundle : true ,
113
+ metafile : true ,
114
+ write : false ,
115
+ minify : false ,
116
+ sourcemap : "external" , // does not set the //# sourceMappingURL= comment in the file, we handle it ourselves
117
+ logLevel : "error" ,
118
+ platform : "node" ,
119
+ format : "cjs" , // This is needed to support opentelemetry instrumentation that uses module patching
120
+ target : [ "node18" , "es2020" ] ,
121
+ outdir : "out" ,
122
+ banner : {
123
+ js : `process.on("uncaughtException", function(error, origin) { if (error instanceof Error) { process.send && process.send({ type: "EVENT", message: { type: "UNCAUGHT_EXCEPTION", payload: { error: { name: error.name, message: error.message, stack: error.stack }, origin }, version: "v1" } }); } else { process.send && process.send({ type: "EVENT", message: { type: "UNCAUGHT_EXCEPTION", payload: { error: { name: "Error", message: typeof error === "string" ? error : JSON.stringify(error) }, origin }, version: "v1" } }); } });` ,
124
+ } ,
125
+ define : {
126
+ TRIGGER_API_URL : `"${ config . triggerUrl } "` ,
127
+ __PROJECT_CONFIG__ : JSON . stringify ( config ) ,
128
+ } ,
129
+ plugins : [
130
+ mockServerOnlyPlugin ( ) ,
131
+ bundleDependenciesPlugin ( "workerFacade" , config . dependenciesToBundle , config . tsconfigPath ) ,
132
+ workerSetupImportConfigPlugin ( configPath ) ,
133
+ esbuildDecorators ( {
134
+ tsconfig : config . tsconfigPath ,
135
+ tsx : true ,
136
+ force : false ,
137
+ } ) ,
138
+ ] ,
139
+ } ) ;
140
+
141
+ if ( result . errors . length > 0 ) {
142
+ // compileSpinner.stop("Build failed, aborting deployment");
143
+
144
+ // span.setAttributes({
145
+ // "build.workerErrors": result.errors.map(
146
+ // (error) => `Error: ${error.text} at ${error.location?.file}`
147
+ // ),
148
+ // });
149
+
150
+ throw new Error ( "Build failed, aborting deployment" ) ;
151
+ }
152
+
153
+ if ( options . outputMetafile ) {
154
+ await writeJSONFile ( join ( options . outputMetafile , "worker.json" ) , result . metafile ) ;
155
+ }
156
+
157
+ const entryPointContents = readFileSync (
158
+ join ( cliRootPath ( ) , "workers" , "prod" , "entry-point.js" ) ,
159
+ "utf-8"
68
160
) ;
69
161
70
- console . log ( path ) ;
162
+ const entryPointResult = await build ( {
163
+ stdin : {
164
+ contents : entryPointContents ,
165
+ resolveDir : process . cwd ( ) ,
166
+ sourcefile : "index.ts" ,
167
+ } ,
168
+ bundle : true ,
169
+ metafile : true ,
170
+ write : false ,
171
+ minify : false ,
172
+ sourcemap : false ,
173
+ logLevel : "error" ,
174
+ platform : "node" ,
175
+ packages : "external" ,
176
+ format : "cjs" , // This is needed to support opentelemetry instrumentation that uses module patching
177
+ target : [ "node18" , "es2020" ] ,
178
+ outdir : "out" ,
179
+ define : {
180
+ __PROJECT_CONFIG__ : JSON . stringify ( config ) ,
181
+ } ,
182
+ plugins : [
183
+ bundleDependenciesPlugin ( "entryPoint.ts" , config . dependenciesToBundle , config . tsconfigPath ) ,
184
+ ] ,
185
+ } ) ;
186
+
187
+ if ( entryPointResult . errors . length > 0 ) {
188
+ // compileSpinner.stop("Build failed, aborting deployment");
189
+
190
+ // span.setAttributes({
191
+ // "build.entryPointErrors": entryPointResult.errors.map(
192
+ // (error) => `Error: ${error.text} at ${error.location?.file}`
193
+ // ),
194
+ // });
195
+
196
+ throw new Error ( "Build failed, aborting deployment" ) ;
197
+ }
198
+
199
+ if ( options . outputMetafile ) {
200
+ await writeJSONFile (
201
+ join ( options . outputMetafile , "entry-point.json" ) ,
202
+ entryPointResult . metafile
203
+ ) ;
204
+ }
205
+
206
+ // Create a tmp directory to store the build
207
+ // const tempDir = await createTempDir();
208
+ const tempDir = await mkdir ( join ( config . projectDir , ".trigger" ) , { recursive : true } ) ;
209
+
210
+ logger . debug ( `Writing compiled files to ${ tempDir } ` ) ;
211
+
212
+ // Get the metaOutput for the result build
213
+ const metaOutput = result . metafile ! . outputs [ posix . join ( "out" , "stdin.js" ) ] ;
214
+
215
+ invariant ( metaOutput , "Meta output for the result build is missing" ) ;
216
+
217
+ // Get the metaOutput for the entryPoint build
218
+ const entryPointMetaOutput = entryPointResult . metafile ! . outputs [ posix . join ( "out" , "stdin.js" ) ] ;
219
+
220
+ invariant ( entryPointMetaOutput , "Meta output for the entryPoint build is missing" ) ;
221
+
222
+ // Get the outputFile and the sourceMapFile for the result build
223
+ const workerOutputFile = result . outputFiles . find (
224
+ ( file ) => file . path === join ( config . projectDir , "out" , "stdin.js" )
225
+ ) ;
226
+
227
+ invariant ( workerOutputFile , "Output file for the result build is missing" ) ;
228
+
229
+ const workerSourcemapFile = result . outputFiles . find (
230
+ ( file ) => file . path === join ( config . projectDir , "out" , "stdin.js.map" )
231
+ ) ;
232
+
233
+ invariant ( workerSourcemapFile , "Sourcemap file for the result build is missing" ) ;
234
+
235
+ // Get the outputFile for the entryPoint build
236
+
237
+ const entryPointOutputFile = entryPointResult . outputFiles . find (
238
+ ( file ) => file . path === join ( config . projectDir , "out" , "stdin.js" )
239
+ ) ;
240
+
241
+ invariant ( entryPointOutputFile , "Output file for the entryPoint build is missing" ) ;
242
+
243
+ // Save the result outputFile to /tmp/dir/worker.js (and make sure to map the sourceMap to the correct location in the file)
244
+ await writeFile (
245
+ join ( tempDir ! , "worker.js" ) ,
246
+ `${ workerOutputFile . text } \n//# sourceMappingURL=worker.js.map`
247
+ ) ;
248
+ // Save the sourceMapFile to /tmp/dir/worker.js.map
249
+ await writeFile ( join ( tempDir ! , "worker.js.map" ) , workerSourcemapFile . text ) ;
250
+ // Save the entryPoint outputFile to /tmp/dir/index.js
251
+ await writeFile ( join ( tempDir ! , "index.js" ) , entryPointOutputFile . text ) ;
71
252
}
0 commit comments