-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Expand file tree
/
Copy pathfetch-native.mjs
More file actions
114 lines (97 loc) · 5.12 KB
/
Copy pathfetch-native.mjs
File metadata and controls
114 lines (97 loc) · 5.12 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
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/
/**
* Downloads the `runtime.node` native binary for a single platform classifier
* and stages it for packaging into a classifier JAR.
*
* Steps:
* 1. Read the pinned version and the SHA-512 `integrity` value for
* `@github/copilot-<classifier>` from `nodejs/package-lock.json`.
* 2. `npm pack` that exact version into the staging directory.
* 3. Verify the downloaded tarball against the `integrity` value.
* 4. Extract `package/prebuilds/<classifier>/runtime.node` to
* `<staging>/<classifier>/native/<classifier>/runtime.node`.
* 5. Extract `package/copilot` (or `package/copilot.exe` on Windows) to
* `<staging>/<classifier>/native/<classifier>/copilot`.
* 6. Write `<staging>/<classifier>/native/<classifier>/platform.properties`.
*
* Usage: node fetch-native.mjs <repoRoot> <stagingDir> <classifier>
*/
import { createHash } from 'node:crypto';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
const [repoRoot, stagingDir, classifier] = process.argv.slice(2);
if (!repoRoot || !stagingDir || !classifier) {
console.error('Usage: node fetch-native.mjs <repoRoot> <stagingDir> <classifier>');
process.exit(1);
}
const lockPath = path.join(repoRoot, 'nodejs', 'package-lock.json');
const packageName = `@github/copilot-${classifier}`;
const lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
const entry = lock.packages?.[`node_modules/${packageName}`];
if (!entry?.version || !entry?.integrity) {
console.error(`Could not find version/integrity for ${packageName} in ${lockPath}`);
process.exit(1);
}
const { version, integrity } = entry;
if (!integrity.startsWith('sha512-')) {
console.error(`Unsupported integrity algorithm for ${packageName}: ${integrity}`);
process.exit(1);
}
const outDir = path.join(stagingDir, classifier);
const resourceDir = path.join(outDir, 'native', classifier);
const runtimePath = path.join(resourceDir, 'runtime.node');
const stampPath = path.join(outDir, '.version');
// Idempotence: skip the download when the staged binary already matches.
// The stamp stores version + integrity + binary digest to ensure a corrupted
// binary or lockfile integrity change is detected.
if (fs.existsSync(runtimePath) && fs.existsSync(stampPath)) {
const stampLines = fs.readFileSync(stampPath, 'utf8').trim().split('\n');
const stampVersion = stampLines[0] || '';
const stampIntegrity = stampLines[1] || '';
const stampBinaryDigest = stampLines[2] || '';
const currentBinaryDigest = `sha512-${createHash('sha512').update(fs.readFileSync(runtimePath)).digest('base64')}`;
if (stampVersion === version && stampIntegrity === integrity && stampBinaryDigest === currentBinaryDigest) {
console.log(`${packageName}@${version} already staged at ${runtimePath}`);
process.exit(0);
}
}
fs.rmSync(outDir, { recursive: true, force: true });
fs.mkdirSync(resourceDir, { recursive: true });
console.log(`Downloading ${packageName}@${version} ...`);
const packOutput = execFileSync('npm', ['pack', `${packageName}@${version}`, '--pack-destination', outDir], {
encoding: 'utf8',
shell: process.platform === 'win32',
});
const tarballName = packOutput.trim().split('\n').pop().trim();
const tarballPath = path.join(outDir, tarballName);
const actual = `sha512-${createHash('sha512').update(fs.readFileSync(tarballPath)).digest('base64')}`;
if (actual !== integrity) {
console.error(`Integrity verification failed for ${tarballPath}`);
console.error(` expected: ${integrity}`);
console.error(` actual: ${actual}`);
process.exit(1);
}
console.log(`Integrity verified (${integrity.slice(0, 20)}...).`);
const memberPath = `package/prebuilds/${classifier}/runtime.node`;
execFileSync('tar', ['-xzf', tarballPath, '-C', outDir, memberPath], { stdio: 'inherit' });
fs.renameSync(path.join(outDir, memberPath), runtimePath);
// Extract the copilot CLI executable (necessary-and-sufficient runtime artifact invariant:
// host_start needs both runtime.node and the copilot CLI from the same package version).
const isWindows = classifier.startsWith('win32');
const cliTarballMember = isWindows ? 'package/copilot.exe' : 'package/copilot';
const cliFilename = isWindows ? 'copilot.exe' : 'copilot';
const cliPath = path.join(resourceDir, cliFilename);
execFileSync('tar', ['-xzf', tarballPath, '-C', outDir, cliTarballMember], { stdio: 'inherit' });
fs.renameSync(path.join(outDir, cliTarballMember), cliPath);
if (!isWindows) {
fs.chmodSync(cliPath, 0o755);
}
fs.rmSync(path.join(outDir, 'package'), { recursive: true, force: true });
fs.rmSync(tarballPath, { force: true });
fs.writeFileSync(path.join(resourceDir, 'platform.properties'), `classifier=${classifier}\nversion=${version}\n`);
const binaryDigest = `sha512-${createHash('sha512').update(fs.readFileSync(runtimePath)).digest('base64')}`;
fs.writeFileSync(stampPath, `${version}\n${integrity}\n${binaryDigest}\n`);
console.log(`Staged ${runtimePath}`);