@chronsyn/eas-on-infra 0.0.1

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/src/ingress.ts ADDED
@@ -0,0 +1,350 @@
1
+ import path from "node:path";
2
+ import { execSync, spawn } from 'node:child_process';
3
+ import crypto from 'node:crypto';
4
+
5
+ import express from 'express';
6
+ import multer from 'multer';
7
+
8
+ import z from "zod";
9
+ import unzipper from 'unzipper';
10
+
11
+
12
+ const ZBodySchema = z.object({
13
+ eas_access_token: z.string(),
14
+ eas_profile: z.string(),
15
+ eas_platform: z.enum(['ios', 'android']),
16
+ installer: z.enum([
17
+ 'npm', // npm i
18
+ 'yarn', // yarn
19
+ 'bun', // bun install
20
+ 'pnpm', // pnpm install
21
+ ]).optional()
22
+ }).strict()
23
+
24
+ export const configureRoute = (app: express.Express) => {
25
+
26
+ const multerDestination = !!process.env.UPLOAD_DIRECTORY
27
+ ? path.resolve(process.env.UPLOAD_DIRECTORY)
28
+ : path.resolve(process.cwd(),'uploads')
29
+
30
+ app.post(
31
+ '/ingress',
32
+
33
+ multer({
34
+ limits: {
35
+ fileSize: 300000000, // 300MB
36
+ fieldNameSize: 8 * 50,
37
+ files: 1,
38
+ fields: 20,
39
+ },
40
+
41
+ preservePath: false,
42
+
43
+ storage: multer.diskStorage({
44
+ destination: multerDestination,
45
+ filename: (req, file, cb) => {
46
+ cb(null, [new Date().getTime(), crypto.randomBytes(16).toString('hex'), '.zip'].join('_'));
47
+ }
48
+ }),
49
+ fileFilter: (req, file, cb) => {
50
+ if (file.mimetype.toLowerCase() !== 'application/zip') {
51
+ return cb(new Error('File rejected'))
52
+ }
53
+ return cb(null, true)
54
+ },
55
+ }).single('bundle'),
56
+
57
+ async (req, res, next) => {
58
+
59
+ console.log(`Received ingress request...`)
60
+
61
+ const actualInputFilePath = req.file?.path!;
62
+
63
+ const projectFileExtractName = [
64
+ new Date().getTime(),
65
+ crypto.randomBytes(4).toString("hex")
66
+ ].join('_')
67
+
68
+ const extractedPath = path.resolve(
69
+ process.env.BINARY_OUTPUT_DIRECTORY
70
+ ? (
71
+ process.env.BINARY_OUTPUT_DIRECTORY,
72
+ projectFileExtractName
73
+ )
74
+ : (
75
+ process.cwd(),
76
+ 'extracted',
77
+ projectFileExtractName
78
+ ),
79
+ )
80
+ const directory = await unzipper.Open.file(actualInputFilePath)
81
+ await directory.extract({ path: extractedPath });
82
+
83
+ const parse = await ZBodySchema.safeParseAsync(req.body);
84
+ if (parse.error) {
85
+ res.status(500).json({ status: "error", error: parse.error })
86
+ return
87
+ }
88
+ const {
89
+ eas_access_token,
90
+ // eas_project_id,
91
+ eas_platform,
92
+ eas_profile,
93
+ installer = 'npm'
94
+ } = parse.data;
95
+
96
+ const build_id = crypto.randomBytes(16).toString('hex')
97
+ const fn = [
98
+ new Date().getTime(),
99
+ eas_platform,
100
+ eas_profile,
101
+ build_id
102
+ ].join('__')
103
+
104
+ // If user has provided BINARY_OUTPUT_DIRECTORY in env, use that
105
+ // Else, fallback to output directory
106
+ const easBuildOutputPath = path.resolve(
107
+ process?.env?.BINARY_OUTPUT_DIRECTORY
108
+ ? (process?.env?.BINARY_OUTPUT_DIRECTORY, fn)
109
+ : (process.cwd(), 'builds', fn)
110
+ )
111
+
112
+ console.log(`EXTRACTED PATH => ${extractedPath}`);
113
+ console.log(`BUILD OUTPUT PATH => ${easBuildOutputPath}`)
114
+
115
+
116
+ // const eas_auto_submit = parse.data.eas_auto_submit === "true";
117
+
118
+
119
+
120
+ let platformParsed;
121
+ switch (true){
122
+ case eas_platform === "ios":
123
+ platformParsed = 'ios';
124
+ break;
125
+ case eas_platform === 'android':
126
+ platformParsed = 'android';
127
+ break;
128
+ }
129
+
130
+ // TODO: chdir to directory, install dependencies
131
+ // TODO: check for `yarn.lock` to run `yarn install`, `package-lock.json` to run `npm i`, etc.
132
+
133
+ // const easBuildCommandWithArgs = [
134
+ // `git init &&`,
135
+ // `git add . &&`,
136
+ // `git commit -m "ready to build" &&`,
137
+ // `${builderToolToUse} &&`,
138
+ // `EXPO_TOKEN=${eas_access_token}`,
139
+ // 'eas build',
140
+ // '--profile', eas_profile.toString(),
141
+ // '--platform', eas_platform.toString(),
142
+ // '--local',
143
+ // '--non-interactive',
144
+ // '--output', `"${easBuildOutputPath}"`,
145
+ // ]
146
+
147
+ const easBinaryLocation = execSync(`which eas`, { encoding: 'utf-8' }).trim()
148
+ console.log("EAS BINARY => ", easBinaryLocation.toString());
149
+
150
+ // // All these are defined values - i.e. no user input
151
+ // // We're safe to exec them, as this also ensures steps are performed in the order expected
152
+ // try {
153
+ // execSync(`git init`)
154
+ // } catch (err) {
155
+ // console.error("Error with git init: ", err)
156
+ // }
157
+ // try {
158
+ // execSync(`git add .`);
159
+ // } catch (err) {
160
+ // console.error("Error with git add: ", err)
161
+ // }
162
+ // try {
163
+ // execSync(`git commit -m "ready to build"`);
164
+ // } catch (err) {
165
+ // console.error("Error with git commit: ", err)
166
+ // }
167
+
168
+ try {
169
+ switch (installer) {
170
+ case 'yarn':
171
+ execSync(
172
+ 'yarn',
173
+ {
174
+ stdio: 'inherit',
175
+ env: {
176
+ ...process.env,
177
+ PATH: process.env.PATH,
178
+ EXPO_TOKEN: eas_access_token,
179
+ },
180
+ }
181
+ );
182
+ break;
183
+ case 'bun':
184
+ execSync(
185
+ 'bun install',
186
+ {
187
+ stdio: 'inherit',
188
+ env: {
189
+ ...process.env,
190
+ PATH: process.env.PATH,
191
+ EXPO_TOKEN: eas_access_token,
192
+ },
193
+ }
194
+ );
195
+ break;
196
+ case "pnpm":
197
+ execSync(
198
+ 'pnpm install',
199
+ {
200
+ stdio: 'inherit',
201
+ env: {
202
+ ...process.env,
203
+ PATH: process.env.PATH,
204
+ EXPO_TOKEN: eas_access_token,
205
+ },
206
+ }
207
+ );
208
+ break;
209
+ case 'npm':
210
+ default:
211
+ execSync(
212
+ 'npm i --force',
213
+ {
214
+ stdio: 'inherit',
215
+ env: {
216
+ ...process.env,
217
+ PATH: process.env.PATH,
218
+ EXPO_TOKEN: eas_access_token,
219
+ },
220
+ }
221
+ );
222
+ break;
223
+ }
224
+ } catch (err) {
225
+ console.error("Error with installer step: ", err)
226
+ }
227
+
228
+ process.chdir(extractedPath);
229
+ console.log(`Process cwd is now => `, process.cwd());
230
+
231
+ const easBuildArgs = [
232
+ `build`,
233
+ `--profile`, eas_profile.toString(),
234
+ `--platform`, eas_platform.toString(),
235
+ `--local`,
236
+ `--non-interactive`,
237
+ `--output`, easBuildOutputPath
238
+ ]
239
+
240
+ // We run this with spawn as there's som user input
241
+ // We've sanitised it as much as possible, but we still want to minimise risk
242
+ const easBuildResult = spawn(
243
+ 'eas',
244
+ easBuildArgs,
245
+ {
246
+ stdio: 'inherit',
247
+ shell: false,
248
+ env: {
249
+ ...process.env,
250
+ PATH: process.env.PATH,
251
+ EXPO_TOKEN: eas_access_token,
252
+ },
253
+ },
254
+ )
255
+
256
+ easBuildResult.on('error', (error) => {
257
+ console.error('Error spawning process:', error);
258
+ });
259
+
260
+ easBuildResult.on('close', (code, signal) => {
261
+ console.log(`Process exited with code ${code}, signal: ${signal?.toString()}`);
262
+ });
263
+
264
+ easBuildResult.on('message', (msg) => {
265
+ console.log(`Process msg: ${msg.toString()}`)
266
+ })
267
+
268
+ // easBuildResult.addListener('spawn', () => {
269
+ // console.log('[EAS BUILD] [SPAWN]')
270
+ // })
271
+ // easBuildResult.addListener('disconnect', () => {
272
+ // console.log('[EAS BUILD] [DISCONNECT]')
273
+ // })
274
+
275
+ // easBuildResult.addListener('error', (msg) => {
276
+ // console.log('[EAS BUILD] [ERROR]', msg.toString())
277
+ // })
278
+ // easBuildResult.addListener('exit', () => {
279
+ // console.log('[EAS BUILD] COMPLETED!')
280
+ // })
281
+
282
+ // easBuildResult.stdout.on('close', () => {
283
+ // console.log('[STDOUT] [EAS BUILD] CLOSE!')
284
+ // })
285
+ // easBuildResult.stdout.on('data', (msg) => {
286
+ // console.log('[STDOUT] [EAS BUILD]', msg)
287
+ // })
288
+ // easBuildResult.stdout.on('end', () => {
289
+ // console.log('[STDOUT] [EAS BUILD] END!')
290
+ // })
291
+ // easBuildResult.stdout.on('error', (err) => {
292
+ // console.log('[STDOUT] [ERR] [EAS BUILD] ', err.message)
293
+ // })
294
+ // easBuildResult.stdout.on('pause', () => {
295
+ // console.log('[STDOUT] [EAS BUILD] PAUSE!')
296
+ // })
297
+ // easBuildResult.stdout.on('readable', () => {
298
+ // console.log('[STDOUT] [EAS BUILD] READABLE!')
299
+ // })
300
+ // easBuildResult.stdout.on('resume', () => {
301
+ // console.log('[STDOUT] [EAS BUILD] RESUME!')
302
+ // })
303
+
304
+ // easBuildResult.stderr.on('close', () => {
305
+ // console.log('[STDERR] [EAS BUILD] CLOSE!')
306
+ // })
307
+ // easBuildResult.stderr.on('data', (msg) => {
308
+ // console.log('[STDERR] [EAS BUILD]', msg)
309
+ // })
310
+ // easBuildResult.stderr.on('end', () => {
311
+ // console.log('[STDERR] [EAS BUILD] END!')
312
+ // })
313
+ // easBuildResult.stderr.on('error', (err) => {
314
+ // console.log('[STDERR] [ERR] [EAS BUILD] ', err.message)
315
+ // })
316
+ // easBuildResult.stderr.on('pause', () => {
317
+ // console.log('[STDERR] [EAS BUILD] PAUSE!')
318
+ // })
319
+ // easBuildResult.stderr.on('readable', () => {
320
+ // console.log('[STDERR] [EAS BUILD] READABLE!')
321
+ // })
322
+ // easBuildResult.stderr.on('resume', () => {
323
+ // console.log('[STDERR] [EAS BUILD] RESUME!')
324
+ // })
325
+
326
+ // exec(
327
+ // easBuildCommandWithArgs,
328
+ // {
329
+ // cwd: extractedPath
330
+ // },
331
+ // (err, stdout, stderr) => {
332
+ // if (err) console.log('[ERR] => ', err);
333
+ // if (stdout) console.log('[STDOUT] => ', stdout);
334
+ // if (stderr) console.log('[STDERR] => ', stderr);
335
+ // }
336
+ // )
337
+
338
+ const responseData = {
339
+ ok: true,
340
+ status: 'in_progress',
341
+ build_id,
342
+ command: easBuildArgs,
343
+ status_url: `${(process.env.SITE_BASE_URL ?? "")}/status/${build_id}`,
344
+ }
345
+
346
+ console.log("Processing... Response => ", JSON.stringify(responseData, null, 2))
347
+
348
+ res.status(200).json(responseData)
349
+ })
350
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "exclude": [
3
+ "extracted",
4
+ "builds",
5
+ "uploads",
6
+ "dist",
7
+ ],
8
+ "compilerOptions": {
9
+ "rootDir": "./src",
10
+ "outDir": "./dist",
11
+ "module": "commonjs",
12
+ "target": "es2017",
13
+
14
+ "lib": ["esnext"],
15
+ "types": ["node"],
16
+
17
+
18
+ // Other Outputs
19
+ "sourceMap": false,
20
+ "declaration": true,
21
+ "declarationMap": true,
22
+
23
+
24
+ // Recommended Options
25
+ "strict": true,
26
+ // "verbatimModuleSyntax": true,
27
+ "esModuleInterop": true,
28
+ "isolatedModules": true,
29
+ "noUncheckedSideEffectImports": true,
30
+ "moduleDetection": "force",
31
+ "skipLibCheck": true,
32
+ }
33
+ }