@journeyapps/cloudcode-build-agent 0.0.0-dev.55699c3 → 0.0.0-dev.6530336
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/dist/builder.d.ts +17 -1
- package/dist/builder.js +95 -4
- package/dist/builder.js.map +1 -1
- package/dist/cli.js +17 -22
- package/dist/cli.js.map +1 -1
- package/dist/defs.d.ts +7 -2
- package/dist/defs.js +1 -1
- package/dist/defs.js.map +1 -1
- package/dist/detect_tasks.d.ts +6 -5
- package/dist/detect_tasks.js +1 -0
- package/dist/detect_tasks.js.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/installer.d.ts +16 -1
- package/dist/installer.js +107 -61
- package/dist/installer.js.map +1 -1
- package/dist/run.d.ts +4 -0
- package/dist/run.js +19 -0
- package/dist/run.js.map +1 -0
- package/dist/spawn-stream.d.ts +15 -0
- package/dist/spawn-stream.js +59 -0
- package/dist/spawn-stream.js.map +1 -0
- package/package.json +4 -2
- package/src/builder.ts +103 -6
- package/src/cli.ts +22 -31
- package/src/defs.ts +2 -2
- package/src/detect_tasks.ts +11 -1
- package/src/index.ts +0 -2
- package/src/installer.ts +113 -78
- package/src/run.ts +23 -0
- package/src/spawn-stream.ts +73 -0
- package/tsconfig.json +1 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/errors.d.ts +0 -8
- package/dist/errors.js +0 -21
- package/dist/errors.js.map +0 -1
- package/dist/utils.d.ts +0 -11
- package/dist/utils.js +0 -99
- package/dist/utils.js.map +0 -1
- package/src/errors.ts +0 -22
- package/src/utils.ts +0 -82
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.spawnStream = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const stream_1 = require("stream");
|
|
6
|
+
function spawnStream(command, args, options) {
|
|
7
|
+
const process = (0, child_process_1.spawn)(command, args, {
|
|
8
|
+
...options,
|
|
9
|
+
stdio: 'pipe'
|
|
10
|
+
});
|
|
11
|
+
return {
|
|
12
|
+
childProcess: process,
|
|
13
|
+
stream: processToStream(process, options)
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
exports.spawnStream = spawnStream;
|
|
17
|
+
function processToStream(process, options) {
|
|
18
|
+
const combinedStream = new stream_1.PassThrough({ objectMode: true });
|
|
19
|
+
async function* transform(stream, key) {
|
|
20
|
+
stream.setEncoding('utf-8');
|
|
21
|
+
let iterable = stream;
|
|
22
|
+
if (options.splitLines) {
|
|
23
|
+
iterable = splitLines(iterable);
|
|
24
|
+
}
|
|
25
|
+
for await (const chunk of iterable) {
|
|
26
|
+
yield {
|
|
27
|
+
event: key,
|
|
28
|
+
[key]: chunk
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (process.stdout) {
|
|
33
|
+
stream_1.Readable.from(transform(process.stdout, 'stdout')).pipe(combinedStream, { end: false });
|
|
34
|
+
}
|
|
35
|
+
if (process.stderr) {
|
|
36
|
+
stream_1.Readable.from(transform(process.stderr, 'stderr')).pipe(combinedStream, { end: false });
|
|
37
|
+
}
|
|
38
|
+
process.on('exit', (code) => {
|
|
39
|
+
combinedStream.write({ exitCode: code }, () => {
|
|
40
|
+
combinedStream.end();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
return combinedStream;
|
|
44
|
+
}
|
|
45
|
+
async function* splitLines(stream) {
|
|
46
|
+
let buffer = '';
|
|
47
|
+
for await (const chunk of stream) {
|
|
48
|
+
buffer += chunk;
|
|
49
|
+
const lines = buffer.split('\n');
|
|
50
|
+
buffer = lines.pop();
|
|
51
|
+
for (let line of lines) {
|
|
52
|
+
yield `${line}\n`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (buffer) {
|
|
56
|
+
yield buffer;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=spawn-stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn-stream.js","sourceRoot":"","sources":["../src/spawn-stream.ts"],"names":[],"mappings":";;;AAAA,iDAAkE;AAClE,mCAA+C;AAM/C,SAAgB,WAAW,CAAC,OAAe,EAAE,IAAc,EAAE,OAAqC;IAChG,MAAM,OAAO,GAAG,IAAA,qBAAK,EAAC,OAAO,EAAE,IAAI,EAAE;QACnC,GAAG,OAAO;QACV,KAAK,EAAE,MAAM;KACd,CAAC,CAAC;IAEH,OAAO;QACL,YAAY,EAAE,OAAO;QACrB,MAAM,EAAE,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC;KAC1C,CAAC;AACJ,CAAC;AAVD,kCAUC;AASD,SAAS,eAAe,CAAC,OAAqB,EAAE,OAAsB;IACpE,MAAM,cAAc,GAAG,IAAI,oBAAW,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;IAE7D,KAAK,SAAS,CAAC,CAAC,SAAS,CAAC,MAAgB,EAAE,GAAwB;QAClE,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QAC5B,IAAI,QAAQ,GAA0B,MAAM,CAAC;QAC7C,IAAI,OAAO,CAAC,UAAU,EAAE;YACtB,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;SACjC;QACD,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,QAAQ,EAAE;YAClC,MAAM;gBACJ,KAAK,EAAE,GAAG;gBACV,CAAC,GAAG,CAAC,EAAE,KAAK;aACb,CAAC;SACH;IACH,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,EAAE;QAClB,iBAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;KACzF;IACD,IAAI,OAAO,CAAC,MAAM,EAAE;QAClB,iBAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;KACzF;IAED,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAmB,EAAE,EAAE;QACzC,cAAc,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE;YAC5C,cAAc,CAAC,GAAG,EAAE,CAAC;QACvB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,KAAK,SAAS,CAAC,CAAC,UAAU,CAAC,MAA6B;IACtD,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE;QAChC,MAAM,IAAI,KAAK,CAAC;QAChB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACjC,MAAM,GAAG,KAAK,CAAC,GAAG,EAAG,CAAC;QACtB,KAAK,IAAI,IAAI,IAAI,KAAK,EAAE;YACtB,MAAM,GAAG,IAAI,IAAI,CAAC;SACnB;KACF;IACD,IAAI,MAAM,EAAE;QACV,MAAM,MAAM,CAAC;KACd;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@journeyapps/cloudcode-build-agent",
|
|
3
|
-
"version": "0.0.0-dev.
|
|
3
|
+
"version": "0.0.0-dev.6530336",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./dist/index",
|
|
6
6
|
"types": "./dist/index",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"cloudcode-build-agent": "./bin.js"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
+
"child-process-promise": "^2.2.1",
|
|
12
13
|
"fs-jetpack": "5.1.0",
|
|
13
14
|
"lodash": "4.17.21",
|
|
14
15
|
"node-fetch": "<3",
|
|
@@ -17,8 +18,9 @@
|
|
|
17
18
|
"yargs": "17.6.2"
|
|
18
19
|
},
|
|
19
20
|
"devDependencies": {
|
|
21
|
+
"@types/child-process-promise": "2.2.2",
|
|
20
22
|
"@types/lodash": "4.14.191",
|
|
21
|
-
"@types/node": "
|
|
23
|
+
"@types/node": "14.18.36",
|
|
22
24
|
"@types/node-fetch": "2.6.2",
|
|
23
25
|
"@types/semver": "7.3.13",
|
|
24
26
|
"@types/tar": "6.1.4",
|
package/src/builder.ts
CHANGED
|
@@ -1,11 +1,108 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import * as jetpack from 'fs-jetpack';
|
|
3
|
+
import * as semver from 'semver';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
import { ensureCustomNodeVersion } from './installer';
|
|
6
|
+
import { detectTasks, DetectedTask } from './detect_tasks';
|
|
7
|
+
import { runCommand } from './run';
|
|
8
|
+
|
|
9
|
+
import * as _ from 'lodash';
|
|
10
|
+
|
|
11
|
+
export type GeneralTaskConfig = {
|
|
12
|
+
app_id: string;
|
|
13
|
+
env: string;
|
|
14
|
+
backend_id: string;
|
|
15
|
+
backend_url: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type ToolPaths = { bin_path: string; node_bin: string; npm_bin: string };
|
|
19
|
+
|
|
20
|
+
export async function buildTasks(project_path: string, dest: string, config: GeneralTaskConfig, only?: string) {
|
|
6
21
|
const tasks = await detectTasks(project_path, only);
|
|
7
22
|
|
|
8
|
-
|
|
9
|
-
|
|
23
|
+
// Use the version of nodejs that's running this script as the default.
|
|
24
|
+
const default_tool_paths = {
|
|
25
|
+
bin_path: path.dirname(process.execPath),
|
|
26
|
+
node_bin: process.execPath,
|
|
27
|
+
npm_bin: path.join(path.dirname(process.execPath), 'npm')
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
for (const task of tasks) {
|
|
31
|
+
let custom_tool_paths;
|
|
32
|
+
if (task.required_node_version) {
|
|
33
|
+
// FIXME: put this in another directory to avoid leaking into the builder
|
|
34
|
+
const custom_node_path = path.resolve(`node-${semver.major(task.required_node_version)}`);
|
|
35
|
+
custom_tool_paths = await ensureCustomNodeVersion(task.required_node_version, custom_node_path);
|
|
36
|
+
}
|
|
37
|
+
const node_version = task.required_node_version ?? process.version;
|
|
38
|
+
|
|
39
|
+
await buildTask(task, project_path, dest, config, {
|
|
40
|
+
node_version,
|
|
41
|
+
...(custom_tool_paths ?? default_tool_paths)
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function buildTask(
|
|
47
|
+
task: DetectedTask,
|
|
48
|
+
project_path: string,
|
|
49
|
+
dest: string,
|
|
50
|
+
config: GeneralTaskConfig,
|
|
51
|
+
node_context: { node_version: string } & ToolPaths
|
|
52
|
+
) {
|
|
53
|
+
const builder_package = task.builder_package;
|
|
54
|
+
const builder_script = task.builder_script;
|
|
55
|
+
const { bin_path, node_bin, npm_bin } = node_context;
|
|
56
|
+
const builder_bin = path.resolve(bin_path, builder_script);
|
|
57
|
+
|
|
58
|
+
if (!jetpack.exists(node_bin)) {
|
|
59
|
+
throw new Error(`Node binary not found: ${node_bin}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.debug(
|
|
63
|
+
`[${task.task_name}] Installing builder script "${builder_package}" for node ${node_context.node_version}`
|
|
64
|
+
);
|
|
65
|
+
// debug(`Installing builder script "${builder_package}" for node ${node_version}`);
|
|
66
|
+
|
|
67
|
+
const stream1 = runCommand(node_bin, [npm_bin, '--global', 'install', builder_package], {
|
|
68
|
+
cwd: project_path,
|
|
69
|
+
env: process.env
|
|
70
|
+
});
|
|
71
|
+
for await (let event of stream1) {
|
|
72
|
+
const log = event.stdout ?? event.stderr;
|
|
73
|
+
if (log) {
|
|
74
|
+
console.log(`[${task.task_name} - install]`, log.trimRight());
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!jetpack.exists(builder_bin)) {
|
|
79
|
+
console.error(`[${task.task_name}] ${builder_bin} not found`);
|
|
80
|
+
throw new Error(`Builder script not found for task ${task.task_name}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const builder_args: string[] = [];
|
|
84
|
+
// --src ./ --out ./dist/echo --task echo --appId 'appid' --env 'testing' --backendId 'backendid' --backendUrl 'http://run.journeyapps.test'
|
|
85
|
+
builder_args.push(builder_bin);
|
|
86
|
+
builder_args.push('--src', project_path);
|
|
87
|
+
builder_args.push(`--out`, path.join(dest, task.task_name));
|
|
88
|
+
builder_args.push(`--zip`, path.join(dest, `${task.task_name}.zip`));
|
|
89
|
+
builder_args.push(`--task`, `${task.task_name}`);
|
|
90
|
+
builder_args.push(`--appId`, config.app_id);
|
|
91
|
+
builder_args.push(`--env`, config.env);
|
|
92
|
+
builder_args.push(`--backendId`, config.backend_id);
|
|
93
|
+
builder_args.push(`--backendUrl`, config.backend_url);
|
|
94
|
+
// builder_args.push(`--runInstallScripts`, 'true'); // TODO: handle this from feature flags somehow
|
|
95
|
+
|
|
96
|
+
console.debug(`[${task.task_name}] Trying: ${node_bin} ${builder_args.join(' ')}`);
|
|
97
|
+
// debug(`Trying: ${node_bin} ${builder_args.join(' ')}`);
|
|
98
|
+
|
|
99
|
+
const stream2 = runCommand(node_bin, builder_args, { cwd: project_path, env: process.env });
|
|
100
|
+
|
|
101
|
+
for await (let event of stream2) {
|
|
102
|
+
const log = event.stdout ?? event.stderr;
|
|
103
|
+
if (log) {
|
|
104
|
+
console.log(`[${task.task_name} - build]`, log.trimRight());
|
|
105
|
+
}
|
|
10
106
|
}
|
|
107
|
+
// console.debug('----');
|
|
11
108
|
}
|
package/src/cli.ts
CHANGED
|
@@ -2,40 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
import * as yargs from 'yargs';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { buildTasks } from './';
|
|
6
6
|
|
|
7
7
|
const DEFAULT_SRC_PATH = './';
|
|
8
|
-
const DEFAULT_OUT_PATH = '';
|
|
8
|
+
const DEFAULT_OUT_PATH = './dist';
|
|
9
9
|
|
|
10
10
|
yargs
|
|
11
11
|
.command(
|
|
12
12
|
['build', '$0'], // $0 means this is the default command
|
|
13
13
|
'build tasks',
|
|
14
14
|
(yargs) => {
|
|
15
|
-
return
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
return (
|
|
16
|
+
yargs
|
|
17
|
+
.option('only', { string: true })
|
|
18
|
+
.option('path', { default: DEFAULT_SRC_PATH })
|
|
19
|
+
.option('out', { default: DEFAULT_OUT_PATH })
|
|
20
|
+
// TODO: investigate yargs.config() for reading task config blob from a file. But how to ensure required args?
|
|
21
|
+
.option('appId', { string: true, demandOption: true, describe: "CloudCode's reference id" })
|
|
22
|
+
.option('env', { string: true, demandOption: true })
|
|
23
|
+
.option('backendId', { string: true, demandOption: true })
|
|
24
|
+
.option('backendUrl', { string: true, demandOption: true })
|
|
25
|
+
);
|
|
19
26
|
},
|
|
20
27
|
handled(async (argv) => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
'prepare-env',
|
|
29
|
-
'Install node environments and packages for each task',
|
|
30
|
-
(yargs) => {
|
|
31
|
-
return yargs.option('only', { string: true }).option('path', { default: DEFAULT_SRC_PATH });
|
|
32
|
-
},
|
|
33
|
-
handled(async (argv) => {
|
|
34
|
-
// iterate over task directories:
|
|
35
|
-
// ensure required node version for task CC version is installed - What if too old - error?
|
|
36
|
-
// run yarn install
|
|
37
|
-
|
|
38
|
-
await prepareBuildEnvForTasks(argv.path, argv.only);
|
|
28
|
+
const config = {
|
|
29
|
+
app_id: argv.appId,
|
|
30
|
+
env: argv.env,
|
|
31
|
+
backend_id: argv.backendId,
|
|
32
|
+
backend_url: argv.backendUrl
|
|
33
|
+
};
|
|
34
|
+
await buildTasks(argv.path, argv.out, config, argv.only);
|
|
39
35
|
})
|
|
40
36
|
)
|
|
41
37
|
.option('verbose', {
|
|
@@ -48,13 +44,8 @@ function handled<T>(fn: (argv: T) => Promise<void>): (argv: T) => Promise<void>
|
|
|
48
44
|
try {
|
|
49
45
|
await fn(argv);
|
|
50
46
|
} catch (err) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
process.exit(err.status);
|
|
54
|
-
} else {
|
|
55
|
-
console.error(err.stack);
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
47
|
+
console.error(err.stack);
|
|
48
|
+
process.exit(1);
|
|
58
49
|
}
|
|
59
50
|
};
|
|
60
51
|
}
|
package/src/defs.ts
CHANGED
|
@@ -3,13 +3,13 @@ export const SUPPORTED_VERSIONS = [
|
|
|
3
3
|
{
|
|
4
4
|
// proposed
|
|
5
5
|
version: '1.13.0',
|
|
6
|
-
node: '16.19.1',
|
|
6
|
+
node: '16.19.1', // FIXME: maybe the very specific node version here is too brittle? Should we just specify the major version?
|
|
7
7
|
builder_package: '@journeyapps/cloudcode-build@1.13.0',
|
|
8
8
|
builder_script: 'cloudcode-build'
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
version: '1.12.0',
|
|
12
|
-
node: '16.19.1',
|
|
12
|
+
// node: '16.19.1',
|
|
13
13
|
// builder_package: '@journeyapps/cloudcode-build-legacy@1.12.0'
|
|
14
14
|
// DEV HACK:
|
|
15
15
|
builder_package: '~/src/journey-cloudcode/packages/cloudcode-build-legacy',
|
package/src/detect_tasks.ts
CHANGED
|
@@ -17,7 +17,16 @@ async function readPackage(path: string) {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export
|
|
20
|
+
export type DetectedTask = {
|
|
21
|
+
pkg_path: string;
|
|
22
|
+
task_name: string;
|
|
23
|
+
task_version: string;
|
|
24
|
+
required_node_version?: string;
|
|
25
|
+
builder_package: string;
|
|
26
|
+
builder_script: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export async function detectTasks(project_path: string, only?: string): Promise<DetectedTask[]> {
|
|
21
30
|
const package_files = await jetpack.findAsync(path.join(project_path, 'cloudcode'), { matching: '**/package.json' });
|
|
22
31
|
const filtered_package_files = package_files.filter((pkg_path) => {
|
|
23
32
|
// FIXME: this is kinda clunky.
|
|
@@ -40,6 +49,7 @@ export async function detectTasks(project_path: string, only?: string) {
|
|
|
40
49
|
|
|
41
50
|
const task_name = task_package.name; // CAVEAT: the pkg name _must_ match the dir.
|
|
42
51
|
const task_version = task_package?.cloudcode?.runtime;
|
|
52
|
+
// FIXME: Do we want to filter out disabled tasks from the build process?
|
|
43
53
|
|
|
44
54
|
console.debug(`Detected task version ${task_version}`);
|
|
45
55
|
|
package/src/index.ts
CHANGED
package/src/installer.ts
CHANGED
|
@@ -1,82 +1,117 @@
|
|
|
1
|
-
import * as fs from 'node:fs';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
1
|
import * as jetpack from 'fs-jetpack';
|
|
4
|
-
import * as
|
|
5
|
-
import * as
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
import * as
|
|
10
|
-
|
|
11
|
-
export async function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
for (const required_node_version of install_node_versions) {
|
|
19
|
-
// FIXME: maybe a path prefix.
|
|
20
|
-
const custom_node_path = path.resolve(`node-${semver.major(required_node_version)}`);
|
|
21
|
-
|
|
22
|
-
if (!jetpack.exists(custom_node_path)) {
|
|
23
|
-
console.log(`installing to ${custom_node_path}`);
|
|
24
|
-
await jetpack.dirAsync(custom_node_path);
|
|
25
|
-
await utils.downloadAndInstallNode(required_node_version, custom_node_path);
|
|
26
|
-
} else {
|
|
27
|
-
console.log(`already installed in ${custom_node_path}`);
|
|
28
|
-
}
|
|
29
|
-
tool_paths[required_node_version] = utils.nodePaths(custom_node_path);
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as URL from 'node:url';
|
|
4
|
+
import fetch from 'node-fetch';
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as os from 'node:os';
|
|
7
|
+
import * as tar from 'tar';
|
|
8
|
+
|
|
9
|
+
export async function ensureCustomNodeVersion(node_version: string, install_path: string) {
|
|
10
|
+
if (!jetpack.exists(path.join(install_path, 'bin/node'))) {
|
|
11
|
+
console.debug(`[node ${node_version}] Installing to ${install_path}`);
|
|
12
|
+
await jetpack.dirAsync(install_path);
|
|
13
|
+
await downloadAndInstallNode(node_version, install_path);
|
|
14
|
+
} else {
|
|
15
|
+
console.debug(`[node ${node_version}] Already installed in ${install_path}`);
|
|
30
16
|
}
|
|
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
|
-
if (builderSpawnResult.status !== 0) {
|
|
69
|
-
console.log(`[${task.task_name}] FAILED TO RUN BUILDER SCRIPT`);
|
|
70
|
-
console.log(`[${task.task_name}] OUTPUT: ${builderSpawnResult.stdout}`);
|
|
71
|
-
console.log(`[${task.task_name}] ERROR: ${builderSpawnResult.stderr}`);
|
|
72
|
-
} else {
|
|
73
|
-
console.log(`[${task.task_name}] SCRIPT SUCCESS?`);
|
|
74
|
-
console.log(`[${task.task_name}] OUTPUT: ${builderSpawnResult.stdout}`);
|
|
75
|
-
console.log(`[${task.task_name}] ERROR: ${builderSpawnResult.stderr}`);
|
|
76
|
-
}
|
|
77
|
-
console.log('----');
|
|
78
|
-
} else {
|
|
79
|
-
console.log(`[${task.task_name}] failed to install ${builder_package}`);
|
|
80
|
-
}
|
|
17
|
+
return nodePaths(install_path);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Basically this, but for different dirs.
|
|
21
|
+
curl https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${PLATFORM}64.tar.gz -L | tar -xzC /node-dedicated && \
|
|
22
|
+
mv /node-dedicated/node-v${NODE_VERSION}-linux-${PLATFORM}64/bin/node /node-dedicated/node
|
|
23
|
+
*/
|
|
24
|
+
// TODO: feature to find the latest minor and patch for a given major version?
|
|
25
|
+
export async function downloadAndInstallNode(node_version: string, destination: string) {
|
|
26
|
+
// eg. https://nodejs.org/dist/v18.14.2/node-v18.14.2-linux-x64.tar.xz
|
|
27
|
+
const ARCH = os.arch();
|
|
28
|
+
const PLATFORM = os.platform();
|
|
29
|
+
const node_download_url = `https://nodejs.org/dist/v${node_version}/node-v${node_version}-${PLATFORM}-${ARCH}.tar.gz`;
|
|
30
|
+
|
|
31
|
+
await downloadAndExtractTarball(node_download_url, destination);
|
|
32
|
+
return nodePaths(destination);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function nodePaths(destination: string) {
|
|
36
|
+
return {
|
|
37
|
+
bin_path: path.resolve(destination, 'bin'),
|
|
38
|
+
node_bin: path.resolve(destination, 'bin', 'node'),
|
|
39
|
+
npm_bin: path.resolve(destination, 'bin', 'npm')
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function downloadAndExtractTarball(url: string, dest: string) {
|
|
44
|
+
const tmpdir = os.tmpdir();
|
|
45
|
+
|
|
46
|
+
const filename = path.basename(URL.parse(url).pathname as string);
|
|
47
|
+
const base = path.basename(filename, '.tar.gz');
|
|
48
|
+
const download = path.join(tmpdir, filename);
|
|
49
|
+
|
|
50
|
+
if (fs.existsSync(download)) {
|
|
51
|
+
console.debug(`deleting old ${download}`);
|
|
52
|
+
fs.unlinkSync(download);
|
|
81
53
|
}
|
|
54
|
+
|
|
55
|
+
await new Promise(async (resolve, reject) => {
|
|
56
|
+
console.log(`fetching ${url} into ${download}`);
|
|
57
|
+
|
|
58
|
+
const response = await fetch(url);
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
const errorBody = await response.statusText;
|
|
61
|
+
throw new Error(`Failed to download: ${errorBody}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const file = fs.createWriteStream(download); // Sink the download stream into a file, so it can be extracted.
|
|
65
|
+
response.body.pipe(file);
|
|
66
|
+
|
|
67
|
+
file.on('finish', async () => {
|
|
68
|
+
try {
|
|
69
|
+
console.debug('Extracting...', download);
|
|
70
|
+
|
|
71
|
+
tar.extract({
|
|
72
|
+
cwd: tmpdir,
|
|
73
|
+
file: download,
|
|
74
|
+
sync: true
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const uncomp = path.join(tmpdir, base);
|
|
78
|
+
|
|
79
|
+
const dist = path.resolve(dest);
|
|
80
|
+
// FIXME: dangerous. Add protection or replace.
|
|
81
|
+
// if (fs.existsSync(dist)) {
|
|
82
|
+
// fs.rmSync(dist, { recursive: true });
|
|
83
|
+
// }
|
|
84
|
+
if (fs.existsSync(uncomp)) {
|
|
85
|
+
console.debug(`Moving extracted files from ${uncomp} to ${dist}`);
|
|
86
|
+
fs.renameSync(uncomp, dist);
|
|
87
|
+
} else {
|
|
88
|
+
console.debug(`Can't find extract dir ${uncomp}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/*
|
|
92
|
+
FIXME: this seems to sometimes cause errors: eg.
|
|
93
|
+
```
|
|
94
|
+
node:events:505
|
|
95
|
+
throw er; // Unhandled 'error' event
|
|
96
|
+
^
|
|
97
|
+
|
|
98
|
+
Error: ENOENT: no such file or directory, open '/var/folders/kc/h6m4zpmd23v13s63mygvkslm0000gn/T/node-v16.19.1-darwin-arm64.tar.gz'
|
|
99
|
+
Emitted 'error' event on ReadStream instance at:
|
|
100
|
+
at emitErrorNT (node:internal/streams/destroy:157:8)
|
|
101
|
+
at emitErrorCloseNT (node:internal/streams/destroy:122:3)
|
|
102
|
+
at processTicksAndRejections (node:internal/process/task_queues:83:21) {
|
|
103
|
+
errno: -2,
|
|
104
|
+
code: 'ENOENT',
|
|
105
|
+
syscall: 'open',
|
|
106
|
+
path: '/var/folders/kc/h6m4zpmd23v13s63mygvkslm0000gn/T/node-v16.19.1-darwin-arm64.tar.gz'
|
|
107
|
+
```
|
|
108
|
+
*/
|
|
109
|
+
// fs.unlinkSync(file.path);
|
|
110
|
+
|
|
111
|
+
resolve(null);
|
|
112
|
+
} catch (err) {
|
|
113
|
+
reject(err);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
});
|
|
82
117
|
}
|
package/src/run.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { spawnStream, SpawnStreamEvent } from './spawn-stream';
|
|
2
|
+
import { SpawnOptions } from 'child_process';
|
|
3
|
+
|
|
4
|
+
export async function* runCommand(
|
|
5
|
+
command: string,
|
|
6
|
+
args: string[],
|
|
7
|
+
options?: Partial<SpawnOptions>
|
|
8
|
+
): AsyncIterable<SpawnStreamEvent> {
|
|
9
|
+
const { childProcess, stream } = spawnStream(command, args, { cwd: options?.cwd, splitLines: true, ...options });
|
|
10
|
+
|
|
11
|
+
let exitCode: number | null = null;
|
|
12
|
+
|
|
13
|
+
for await (let event of stream) {
|
|
14
|
+
yield event;
|
|
15
|
+
if (event.exitCode != null) {
|
|
16
|
+
exitCode = event.exitCode;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (exitCode != 0) {
|
|
21
|
+
throw new Error(`Command failed with code ${exitCode}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { ChildProcess, spawn, SpawnOptions } from 'child_process';
|
|
2
|
+
import { PassThrough, Readable } from 'stream';
|
|
3
|
+
|
|
4
|
+
export interface StreamOptions {
|
|
5
|
+
splitLines?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function spawnStream(command: string, args: string[], options: SpawnOptions & StreamOptions) {
|
|
9
|
+
const process = spawn(command, args, {
|
|
10
|
+
...options,
|
|
11
|
+
stdio: 'pipe'
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
childProcess: process,
|
|
16
|
+
stream: processToStream(process, options)
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SpawnStreamEvent {
|
|
21
|
+
event: 'stdout' | 'stderr' | 'exit';
|
|
22
|
+
stdout?: string;
|
|
23
|
+
stderr?: string;
|
|
24
|
+
exitCode?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function processToStream(process: ChildProcess, options: StreamOptions): AsyncIterable<SpawnStreamEvent> {
|
|
28
|
+
const combinedStream = new PassThrough({ objectMode: true });
|
|
29
|
+
|
|
30
|
+
async function* transform(stream: Readable, key: 'stdout' | 'stderr'): AsyncIterable<SpawnStreamEvent> {
|
|
31
|
+
stream.setEncoding('utf-8');
|
|
32
|
+
let iterable: AsyncIterable<string> = stream;
|
|
33
|
+
if (options.splitLines) {
|
|
34
|
+
iterable = splitLines(iterable);
|
|
35
|
+
}
|
|
36
|
+
for await (const chunk of iterable) {
|
|
37
|
+
yield {
|
|
38
|
+
event: key,
|
|
39
|
+
[key]: chunk
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (process.stdout) {
|
|
45
|
+
Readable.from(transform(process.stdout, 'stdout')).pipe(combinedStream, { end: false });
|
|
46
|
+
}
|
|
47
|
+
if (process.stderr) {
|
|
48
|
+
Readable.from(transform(process.stderr, 'stderr')).pipe(combinedStream, { end: false });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
process.on('exit', (code: number | null) => {
|
|
52
|
+
combinedStream.write({ exitCode: code }, () => {
|
|
53
|
+
combinedStream.end();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return combinedStream;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function* splitLines(stream: AsyncIterable<string>): AsyncIterable<string> {
|
|
61
|
+
let buffer = '';
|
|
62
|
+
for await (const chunk of stream) {
|
|
63
|
+
buffer += chunk;
|
|
64
|
+
const lines = buffer.split('\n');
|
|
65
|
+
buffer = lines.pop()!;
|
|
66
|
+
for (let line of lines) {
|
|
67
|
+
yield `${line}\n`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (buffer) {
|
|
71
|
+
yield buffer;
|
|
72
|
+
}
|
|
73
|
+
}
|