freestyle-sync 0.1.4 → 0.1.7

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.
@@ -1,388 +0,0 @@
1
- import { execFile } from "node:child_process";
2
- import { promisify } from "node:util";
3
- import { definePlugin } from "../../../src/plugin-api.js";
4
- const execFileAsync = promisify(execFile);
5
- export function nodeNpmPlugin() {
6
- return definePlugin({
7
- name: "@freestyle-sync/node-npm",
8
- async afterProjectSync({ vm, options }) {
9
- const nodeVersion = process.versions.node;
10
- const npmVersion = await localNpmVersion();
11
- const script = `
12
- set -e
13
- export HOME="\${HOME:-/root}"
14
- set -u
15
- cd ${shellQuote(options.remoteProjectDir)}
16
- if [ ! -f package.json ]; then
17
- exit 0
18
- fi
19
- desired_node=${shellQuote(nodeVersion)}
20
- desired_npm=${shellQuote(npmVersion ?? "")}
21
- nvm_version=v0.40.3
22
- export NVM_DIR=/root/.nvm
23
- runtime_cache=.freestyle-sync/node-runtime-cache.json
24
-
25
- install_nvm() {
26
- if [ -s "$NVM_DIR/nvm.sh" ]; then
27
- return 0
28
- fi
29
- mkdir -p "$NVM_DIR"
30
- if command -v git >/dev/null 2>&1; then
31
- rm -rf "$NVM_DIR"
32
- git clone --depth 1 --branch "$nvm_version" https://github.com/nvm-sh/nvm.git "$NVM_DIR"
33
- elif command -v curl >/dev/null 2>&1; then
34
- curl -fsSL "https://raw.githubusercontent.com/nvm-sh/nvm/$nvm_version/install.sh" | PROFILE=/dev/null bash
35
- elif command -v wget >/dev/null 2>&1; then
36
- wget -qO- "https://raw.githubusercontent.com/nvm-sh/nvm/$nvm_version/install.sh" | PROFILE=/dev/null bash
37
- else
38
- echo "git, curl, or wget is required to install nvm" >&2
39
- return 1
40
- fi
41
- }
42
-
43
- install_nvm
44
- . "$NVM_DIR/nvm.sh"
45
- installed_node=$(nvm version "$desired_node" || true)
46
- if [ "$installed_node" = "N/A" ]; then
47
- echo "Installing Node $desired_node with nvm"
48
- nvm install "$desired_node"
49
- fi
50
- nvm alias default "$desired_node" >/dev/null
51
- nvm use --silent "$desired_node"
52
- actual_node=$(node -p 'process.versions.node')
53
- if [ "$actual_node" != "$desired_node" ]; then
54
- echo "Expected Node $desired_node but activated $actual_node" >&2
55
- exit 1
56
- fi
57
- actual_npm=$(npm --version 2>/dev/null || true)
58
- if [ -n "$desired_npm" ] && [ "$actual_npm" != "$desired_npm" ]; then
59
- echo "Installing npm $desired_npm for Node $desired_node"
60
- npm install -g "npm@$desired_npm"
61
- actual_npm=$(npm --version)
62
- fi
63
- mkdir -p .freestyle-sync
64
- printf '{"node":"%s","npm":"%s","nvm":"%s"}\n' "$actual_node" "$actual_npm" "$nvm_version" > "$runtime_cache"
65
- grep -qxF 'export NVM_DIR=/root/.nvm' /root/.profile || printf '\n%s\n' 'export NVM_DIR=/root/.nvm' >> /root/.profile
66
- grep -qxF '[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" && nvm use --silent default >/dev/null 2>&1' /root/.profile || printf '%s\n' '[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" && nvm use --silent default >/dev/null 2>&1' >> /root/.profile
67
-
68
- node <<'NODE'
69
- const fs = require('node:fs');
70
- const path = require('node:path');
71
-
72
- const root = process.cwd();
73
- const rootPackage = readJson(path.join(root, 'package.json'));
74
- const workspaces = Array.isArray(rootPackage.workspaces)
75
- ? rootPackage.workspaces
76
- : Array.isArray(rootPackage.workspaces && rootPackage.workspaces.packages)
77
- ? rootPackage.workspaces.packages
78
- : [];
79
-
80
- let repaired = 0;
81
- for (const packageDir of unique(workspaces.flatMap(expandWorkspacePattern))) {
82
- const packageJson = readJson(path.join(packageDir, 'package.json'));
83
- if (typeof packageJson.name !== 'string' || packageJson.name.length === 0) continue;
84
-
85
- const linkPath = path.join(root, 'node_modules', ...packageJson.name.split('/'));
86
- const target = path.relative(path.dirname(linkPath), packageDir) || '.';
87
- fs.mkdirSync(path.dirname(linkPath), { recursive: true });
88
-
89
- try {
90
- const stats = fs.lstatSync(linkPath);
91
- if (stats.isSymbolicLink()) {
92
- const currentTarget = fs.readlinkSync(linkPath);
93
- if (currentTarget === target || path.resolve(path.dirname(linkPath), currentTarget) === packageDir) continue;
94
- fs.unlinkSync(linkPath);
95
- } else {
96
- continue;
97
- }
98
- } catch (error) {
99
- if (error.code !== 'ENOENT') continue;
100
- }
101
-
102
- fs.symlinkSync(target, linkPath, 'dir');
103
- repaired += 1;
104
- }
105
-
106
- if (repaired > 0) console.log('Repaired npm workspace links: ' + repaired);
107
-
108
- function readJson(filePath) {
109
- try {
110
- return JSON.parse(fs.readFileSync(filePath, 'utf8'));
111
- } catch {
112
- return {};
113
- }
114
- }
115
-
116
- function expandWorkspacePattern(pattern) {
117
- if (typeof pattern !== 'string' || pattern.length === 0 || pattern.includes('**')) return [];
118
- const segments = pattern.split(/[\\/]+/).filter(Boolean);
119
- const starIndex = segments.indexOf('*');
120
- if (starIndex === -1) return [path.resolve(root, ...segments)];
121
- if (segments.indexOf('*', starIndex + 1) !== -1) return [];
122
-
123
- const baseDir = path.resolve(root, ...segments.slice(0, starIndex));
124
- const suffix = segments.slice(starIndex + 1);
125
- let entries;
126
- try {
127
- entries = fs.readdirSync(baseDir, { withFileTypes: true });
128
- } catch {
129
- return [];
130
- }
131
- return entries.filter((entry) => entry.isDirectory()).map((entry) => path.join(baseDir, entry.name, ...suffix));
132
- }
133
-
134
- function unique(values) {
135
- return Array.from(new Set(values));
136
- }
137
- NODE
138
-
139
- if [ ! -d node_modules ]; then
140
- exit 0
141
- fi
142
- package_list=$(mktemp /tmp/vmpush-native-packages.XXXXXX)
143
- fingerprint_file=$(mktemp /tmp/vmpush-native-fingerprints.XXXXXX)
144
- scanner_file=$(mktemp /tmp/vmpush-native-scanner.XXXXXX.js)
145
- cache_file=.freestyle-sync/npm-native-deps-cache.json
146
- trap 'rm -f "$package_list" "$fingerprint_file" "$scanner_file"' EXIT
147
- cat > "$scanner_file" <<'NODE'
148
- const fs = require('node:fs');
149
- const path = require('node:path');
150
- const crypto = require('node:crypto');
151
-
152
- const cachePath = path.resolve(process.argv[2]);
153
- const fingerprintPath = process.argv[3];
154
- const mode = process.argv[4];
155
- const nodeModules = path.resolve('node_modules');
156
- const packages = new Set();
157
- const packageInfos = [];
158
- const incompatiblePlatformPackages = new Set();
159
- const nativeFilesByPackage = new Map();
160
- const platformPackagesByWrapper = new Map();
161
-
162
- function readPackageJson(packageDir) {
163
- try {
164
- return JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8'));
165
- } catch {
166
- return undefined;
167
- }
168
- }
169
-
170
- function addPackage(packageDir) {
171
- const packageJson = readPackageJson(packageDir);
172
- if (typeof packageJson?.name !== 'string' || packageJson.name.length === 0) return;
173
- packageInfos.push({ dir: packageDir, json: packageJson, name: packageJson.name });
174
- }
175
-
176
- function listAllowsCurrent(value, current) {
177
- if (value === undefined) return true;
178
- const list = Array.isArray(value) ? value : [value];
179
- const positives = list.filter((item) => typeof item === 'string' && !item.startsWith('!'));
180
- const negatives = list.filter((item) => typeof item === 'string' && item.startsWith('!')).map((item) => item.slice(1));
181
- if (negatives.includes(current)) return false;
182
- return positives.length === 0 || positives.includes(current);
183
- }
184
-
185
- function packageAllowsCurrentPlatform(packageJson) {
186
- return listAllowsCurrent(packageJson.os, process.platform) && listAllowsCurrent(packageJson.cpu, process.arch);
187
- }
188
-
189
- function dependencyNames(packageJson) {
190
- return Object.keys(Object.assign({}, packageJson.dependencies, packageJson.optionalDependencies));
191
- }
192
-
193
- function addMapSet(map, key, value) {
194
- const existing = map.get(key);
195
- if (existing) existing.add(value);
196
- else map.set(key, new Set([value]));
197
- }
198
-
199
- function relativeToProject(filePath) {
200
- return path.relative(process.cwd(), filePath).split(path.sep).join('/');
201
- }
202
-
203
- function collectPackageFiles(packageDir, files) {
204
- let entries;
205
- try {
206
- entries = fs.readdirSync(packageDir, { withFileTypes: true });
207
- } catch {
208
- return;
209
- }
210
-
211
- for (const entry of entries) {
212
- const child = path.join(packageDir, entry.name);
213
- if (entry.isDirectory()) collectPackageFiles(child, files);
214
- else if (entry.isFile()) files.push(child);
215
- }
216
- }
217
-
218
- function packageFingerprint(packageName) {
219
- const info = packageInfos.find((candidate) => candidate.name === packageName);
220
- if (!info) return undefined;
221
- const files = [path.join(info.dir, 'package.json')];
222
- for (const nativeFile of nativeFilesByPackage.get(packageName) || []) files.push(nativeFile);
223
- for (const platformPackageName of platformPackagesByWrapper.get(packageName) || []) {
224
- const platformInfo = packageInfos.find((candidate) => candidate.name === platformPackageName);
225
- if (!platformInfo) continue;
226
- files.push(path.join(platformInfo.dir, 'package.json'));
227
- collectPackageFiles(platformInfo.dir, files);
228
- }
229
-
230
- const hash = crypto.createHash('sha256');
231
- hash.update(process.platform);
232
- hash.update(String.fromCharCode(0));
233
- hash.update(process.arch);
234
- hash.update(String.fromCharCode(0));
235
- hash.update(process.versions.modules || '');
236
- hash.update(String.fromCharCode(0));
237
- for (const filePath of Array.from(new Set(files)).sort()) {
238
- try {
239
- const stats = fs.statSync(filePath);
240
- if (!stats.isFile()) continue;
241
- hash.update(relativeToProject(filePath));
242
- hash.update(String.fromCharCode(0));
243
- hash.update(String(stats.size));
244
- hash.update(String.fromCharCode(0));
245
- hash.update(String(stats.mtimeMs));
246
- hash.update(String.fromCharCode(0));
247
- hash.update(String(stats.mode));
248
- hash.update(String.fromCharCode(0));
249
- } catch {
250
- // Ignore files that disappeared while scanning.
251
- }
252
- }
253
- return hash.digest('hex');
254
- }
255
-
256
- function nearestPackageDir(start) {
257
- let current = start;
258
- while (current.startsWith(nodeModules)) {
259
- if (fs.existsSync(path.join(current, 'package.json'))) return current;
260
- const parent = path.dirname(current);
261
- if (parent === current) break;
262
- current = parent;
263
- }
264
- return undefined;
265
- }
266
-
267
- function addNativePackage(nativeFile) {
268
- const packageDir = nearestPackageDir(path.dirname(nativeFile));
269
- if (!packageDir) return;
270
- const packageJson = readPackageJson(packageDir);
271
- if (typeof packageJson?.name !== 'string' || packageJson.name.length === 0) return;
272
- packages.add(packageJson.name);
273
- addMapSet(nativeFilesByPackage, packageJson.name, nativeFile);
274
- }
275
-
276
- function walk(directory) {
277
- let entries;
278
- try {
279
- entries = fs.readdirSync(directory, { withFileTypes: true });
280
- } catch {
281
- return;
282
- }
283
-
284
- for (const entry of entries) {
285
- const child = path.join(directory, entry.name);
286
- if (entry.isDirectory()) {
287
- if (entry.name !== 'node_modules' && fs.existsSync(path.join(child, 'package.json'))) addPackage(child);
288
- walk(child);
289
- } else if (entry.isFile() && entry.name.endsWith('.node')) {
290
- addNativePackage(child);
291
- }
292
- }
293
- }
294
-
295
- function cacheEntryMatches(entry, fingerprint) {
296
- if (entry === fingerprint) return true;
297
- if (entry && typeof entry === 'object' && (entry.input === fingerprint || entry.fixed === fingerprint)) return true;
298
- return false;
299
- }
300
-
301
- walk(nodeModules);
302
- for (const info of packageInfos) {
303
- if ((info.json.os !== undefined || info.json.cpu !== undefined) && !packageAllowsCurrentPlatform(info.json)) {
304
- incompatiblePlatformPackages.add(info.name);
305
- }
306
- }
307
- for (const info of packageInfos) {
308
- const platformDependencies = dependencyNames(info.json).filter((dependency) => incompatiblePlatformPackages.has(dependency));
309
- if (platformDependencies.length === 0) continue;
310
- packages.add(info.name);
311
- for (const dependency of platformDependencies) addMapSet(platformPackagesByWrapper, info.name, dependency);
312
- }
313
-
314
- let previous = {};
315
- try {
316
- previous = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
317
- } catch {
318
- previous = {};
319
- }
320
- const next = Object.assign({}, previous);
321
- const changedPackages = [];
322
- for (const packageName of Array.from(packages).sort()) {
323
- const fingerprint = packageFingerprint(packageName);
324
- if (!fingerprint) continue;
325
- const previousEntry = previous[packageName];
326
- if (mode !== 'record' && !cacheEntryMatches(previousEntry, fingerprint)) changedPackages.push(packageName);
327
- const previousObject = previousEntry && typeof previousEntry === 'object' ? previousEntry : {};
328
- next[packageName] = mode === 'record'
329
- ? Object.assign({}, previousObject, { fixed: fingerprint })
330
- : Object.assign({}, previousObject, { input: fingerprint });
331
- }
332
- fs.mkdirSync(path.dirname(fingerprintPath), { recursive: true });
333
- fs.writeFileSync(fingerprintPath, JSON.stringify(next, null, 2) + String.fromCharCode(10));
334
- if (mode !== 'record') {
335
- for (const packageName of changedPackages) console.log(packageName);
336
- }
337
- NODE
338
- node "$scanner_file" "$cache_file" "$fingerprint_file" detect > "$package_list"
339
- if [ ! -s "$package_list" ]; then
340
- mkdir -p .freestyle-sync
341
- cp "$fingerprint_file" "$cache_file"
342
- exit 0
343
- fi
344
- echo "Rebuilding native Node dependencies: $(tr '\n' ' ' < "$package_list")"
345
- if [ -f pnpm-lock.yaml ] && command -v corepack >/dev/null 2>&1; then
346
- corepack enable >/dev/null 2>&1 || true
347
- xargs pnpm rebuild < "$package_list"
348
- elif [ -f yarn.lock ] && command -v corepack >/dev/null 2>&1; then
349
- corepack enable >/dev/null 2>&1 || true
350
- if yarn rebuild --help >/dev/null 2>&1; then
351
- xargs yarn rebuild < "$package_list"
352
- else
353
- echo "Yarn does not support targeted rebuild here; skipping broad reinstall"
354
- fi
355
- elif command -v npm >/dev/null 2>&1; then
356
- xargs npm rebuild < "$package_list"
357
- fi
358
- node "$scanner_file" "$cache_file" "$fingerprint_file" record > /dev/null
359
- mkdir -p .freestyle-sync
360
- cp "$fingerprint_file" "$cache_file"
361
- `;
362
- const scriptPath = `/tmp/vmpush-node-npm-${Date.now()}.sh`;
363
- await vm.fs.writeTextFile(scriptPath, script);
364
- const result = await vm.exec({
365
- command: `bash ${shellQuote(scriptPath)}\nexit_code=$?\nrm -f ${shellQuote(scriptPath)}\nexit "$exit_code"`,
366
- timeoutMs: 20 * 60 * 1000,
367
- });
368
- if (result.statusCode && result.statusCode !== 0) {
369
- throw new Error(result.stderr ?? result.stdout ?? `Node/npm fixup failed with status ${result.statusCode}`);
370
- }
371
- if (result.stdout?.trim())
372
- console.log(result.stdout.trim());
373
- },
374
- });
375
- }
376
- async function localNpmVersion() {
377
- try {
378
- const { stdout } = await execFileAsync("npm", ["--version"]);
379
- const version = stdout.trim();
380
- return version.length > 0 ? version : undefined;
381
- }
382
- catch {
383
- return undefined;
384
- }
385
- }
386
- function shellQuote(value) {
387
- return `'${value.replace(/'/g, `'"'"'`)}'`;
388
- }
@@ -1,307 +0,0 @@
1
- import { execFile } from "node:child_process";
2
- import { promisify } from "node:util";
3
- const execFileAsync = promisify(execFile);
4
- export const npmNativeDepsPlugin = {
5
- name: "npm-native-deps",
6
- async afterProjectSync({ vm, options }) {
7
- const nodeVersion = process.versions.node;
8
- const npmVersion = await localNpmVersion();
9
- const script = `
10
- set -eu
11
- cd ${shellQuote(options.remoteProjectDir)}
12
- if [ ! -f package.json ]; then
13
- exit 0
14
- fi
15
- desired_node=${shellQuote(nodeVersion)}
16
- desired_npm=${shellQuote(npmVersion ?? "")}
17
- nvm_version=v0.40.3
18
- export NVM_DIR=/root/.nvm
19
- runtime_cache=.vmpush/node-runtime-cache.json
20
-
21
- install_nvm() {
22
- if [ -s "$NVM_DIR/nvm.sh" ]; then
23
- return 0
24
- fi
25
- mkdir -p "$NVM_DIR"
26
- if command -v git >/dev/null 2>&1; then
27
- rm -rf "$NVM_DIR"
28
- git clone --depth 1 --branch "$nvm_version" https://github.com/nvm-sh/nvm.git "$NVM_DIR"
29
- elif command -v curl >/dev/null 2>&1; then
30
- curl -fsSL "https://raw.githubusercontent.com/nvm-sh/nvm/$nvm_version/install.sh" | PROFILE=/dev/null bash
31
- elif command -v wget >/dev/null 2>&1; then
32
- wget -qO- "https://raw.githubusercontent.com/nvm-sh/nvm/$nvm_version/install.sh" | PROFILE=/dev/null bash
33
- else
34
- echo "git, curl, or wget is required to install nvm" >&2
35
- return 1
36
- fi
37
- }
38
-
39
- install_nvm
40
- . "$NVM_DIR/nvm.sh"
41
- installed_node=$(nvm version "$desired_node" || true)
42
- if [ "$installed_node" = "N/A" ]; then
43
- echo "Installing Node $desired_node with nvm"
44
- nvm install "$desired_node"
45
- fi
46
- nvm alias default "$desired_node" >/dev/null
47
- nvm use --silent "$desired_node"
48
- actual_node=$(node -p 'process.versions.node')
49
- if [ "$actual_node" != "$desired_node" ]; then
50
- echo "Expected Node $desired_node but activated $actual_node" >&2
51
- exit 1
52
- fi
53
- actual_npm=$(npm --version 2>/dev/null || true)
54
- if [ -n "$desired_npm" ] && [ "$actual_npm" != "$desired_npm" ]; then
55
- echo "Installing npm $desired_npm for Node $desired_node"
56
- npm install -g "npm@$desired_npm"
57
- actual_npm=$(npm --version)
58
- fi
59
- mkdir -p .vmpush
60
- printf '{"node":"%s","npm":"%s","nvm":"%s"}\n' "$actual_node" "$actual_npm" "$nvm_version" > "$runtime_cache"
61
- grep -qxF 'export NVM_DIR=/root/.nvm' /root/.profile || printf '\n%s\n' 'export NVM_DIR=/root/.nvm' >> /root/.profile
62
- grep -qxF '[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" && nvm use --silent default >/dev/null 2>&1' /root/.profile || printf '%s\n' '[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" && nvm use --silent default >/dev/null 2>&1' >> /root/.profile
63
-
64
- if [ ! -d node_modules ]; then
65
- exit 0
66
- fi
67
- package_list=$(mktemp /tmp/vmpush-native-packages.XXXXXX)
68
- fingerprint_file=$(mktemp /tmp/vmpush-native-fingerprints.XXXXXX)
69
- scanner_file=$(mktemp /tmp/vmpush-native-scanner.XXXXXX.js)
70
- cache_file=.vmpush/npm-native-deps-cache.json
71
- trap 'rm -f "$package_list" "$fingerprint_file" "$scanner_file"' EXIT
72
- cat > "$scanner_file" <<'NODE'
73
- const fs = require('node:fs');
74
- const path = require('node:path');
75
- const crypto = require('node:crypto');
76
-
77
- const cachePath = path.resolve(process.argv[2]);
78
- const fingerprintPath = process.argv[3];
79
- const mode = process.argv[4];
80
- const nodeModules = path.resolve('node_modules');
81
- const packages = new Set();
82
- const packageInfos = [];
83
- const incompatiblePlatformPackages = new Set();
84
- const nativeFilesByPackage = new Map();
85
- const platformPackagesByWrapper = new Map();
86
-
87
- function readPackageJson(packageDir) {
88
- try {
89
- return JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8'));
90
- } catch {
91
- return undefined;
92
- }
93
- }
94
-
95
- function addPackage(packageDir) {
96
- const packageJson = readPackageJson(packageDir);
97
- if (typeof packageJson?.name !== 'string' || packageJson.name.length === 0) return;
98
- packageInfos.push({ dir: packageDir, json: packageJson, name: packageJson.name });
99
- }
100
-
101
- function listAllowsCurrent(value, current) {
102
- if (value === undefined) return true;
103
- const list = Array.isArray(value) ? value : [value];
104
- const positives = list.filter((item) => typeof item === 'string' && !item.startsWith('!'));
105
- const negatives = list.filter((item) => typeof item === 'string' && item.startsWith('!')).map((item) => item.slice(1));
106
- if (negatives.includes(current)) return false;
107
- return positives.length === 0 || positives.includes(current);
108
- }
109
-
110
- function packageAllowsCurrentPlatform(packageJson) {
111
- return listAllowsCurrent(packageJson.os, process.platform) && listAllowsCurrent(packageJson.cpu, process.arch);
112
- }
113
-
114
- function dependencyNames(packageJson) {
115
- return Object.keys(Object.assign({}, packageJson.dependencies, packageJson.optionalDependencies));
116
- }
117
-
118
- function addMapSet(map, key, value) {
119
- const existing = map.get(key);
120
- if (existing) existing.add(value);
121
- else map.set(key, new Set([value]));
122
- }
123
-
124
- function relativeToProject(filePath) {
125
- return path.relative(process.cwd(), filePath).split(path.sep).join('/');
126
- }
127
-
128
- function collectPackageFiles(packageDir, files) {
129
- let entries;
130
- try {
131
- entries = fs.readdirSync(packageDir, { withFileTypes: true });
132
- } catch {
133
- return;
134
- }
135
-
136
- for (const entry of entries) {
137
- const child = path.join(packageDir, entry.name);
138
- if (entry.isDirectory()) collectPackageFiles(child, files);
139
- else if (entry.isFile()) files.push(child);
140
- }
141
- }
142
-
143
- function packageFingerprint(packageName) {
144
- const info = packageInfos.find((candidate) => candidate.name === packageName);
145
- if (!info) return undefined;
146
- const files = [path.join(info.dir, 'package.json')];
147
- for (const nativeFile of nativeFilesByPackage.get(packageName) || []) files.push(nativeFile);
148
- for (const platformPackageName of platformPackagesByWrapper.get(packageName) || []) {
149
- const platformInfo = packageInfos.find((candidate) => candidate.name === platformPackageName);
150
- if (!platformInfo) continue;
151
- files.push(path.join(platformInfo.dir, 'package.json'));
152
- collectPackageFiles(platformInfo.dir, files);
153
- }
154
-
155
- const hash = crypto.createHash('sha256');
156
- hash.update(process.platform);
157
- hash.update('\0');
158
- hash.update(process.arch);
159
- hash.update('\0');
160
- hash.update(process.versions.modules || '');
161
- hash.update('\0');
162
- for (const filePath of Array.from(new Set(files)).sort()) {
163
- try {
164
- const stats = fs.statSync(filePath);
165
- if (!stats.isFile()) continue;
166
- hash.update(relativeToProject(filePath));
167
- hash.update('\0');
168
- hash.update(String(stats.size));
169
- hash.update('\0');
170
- hash.update(String(stats.mtimeMs));
171
- hash.update('\0');
172
- hash.update(String(stats.mode));
173
- hash.update('\0');
174
- } catch {
175
- // Ignore files that disappeared while scanning.
176
- }
177
- }
178
- return hash.digest('hex');
179
- }
180
-
181
- function nearestPackageDir(start) {
182
- let current = start;
183
- while (current.startsWith(nodeModules)) {
184
- if (fs.existsSync(path.join(current, 'package.json'))) return current;
185
- const parent = path.dirname(current);
186
- if (parent === current) break;
187
- current = parent;
188
- }
189
- return undefined;
190
- }
191
-
192
- function addNativePackage(nativeFile) {
193
- const packageDir = nearestPackageDir(path.dirname(nativeFile));
194
- if (!packageDir) return;
195
- const packageJson = readPackageJson(packageDir);
196
- if (typeof packageJson?.name !== 'string' || packageJson.name.length === 0) return;
197
- packages.add(packageJson.name);
198
- addMapSet(nativeFilesByPackage, packageJson.name, nativeFile);
199
- }
200
-
201
- function walk(directory) {
202
- let entries;
203
- try {
204
- entries = fs.readdirSync(directory, { withFileTypes: true });
205
- } catch {
206
- return;
207
- }
208
-
209
- for (const entry of entries) {
210
- const child = path.join(directory, entry.name);
211
- if (entry.isDirectory()) {
212
- if (entry.name !== 'node_modules' && fs.existsSync(path.join(child, 'package.json'))) addPackage(child);
213
- walk(child);
214
- } else if (entry.isFile() && entry.name.endsWith('.node')) {
215
- addNativePackage(child);
216
- }
217
- }
218
- }
219
-
220
- function cacheEntryMatches(entry, fingerprint) {
221
- if (entry === fingerprint) return true;
222
- if (entry && typeof entry === 'object' && (entry.input === fingerprint || entry.fixed === fingerprint)) return true;
223
- return false;
224
- }
225
-
226
- walk(nodeModules);
227
- for (const info of packageInfos) {
228
- if ((info.json.os !== undefined || info.json.cpu !== undefined) && !packageAllowsCurrentPlatform(info.json)) {
229
- incompatiblePlatformPackages.add(info.name);
230
- }
231
- }
232
- for (const info of packageInfos) {
233
- const platformDependencies = dependencyNames(info.json).filter((dependency) => incompatiblePlatformPackages.has(dependency));
234
- if (platformDependencies.length === 0) continue;
235
- packages.add(info.name);
236
- for (const dependency of platformDependencies) addMapSet(platformPackagesByWrapper, info.name, dependency);
237
- }
238
-
239
- let previous = {};
240
- try {
241
- previous = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
242
- } catch {
243
- previous = {};
244
- }
245
- const next = Object.assign({}, previous);
246
- const changedPackages = [];
247
- for (const packageName of Array.from(packages).sort()) {
248
- const fingerprint = packageFingerprint(packageName);
249
- if (!fingerprint) continue;
250
- const previousEntry = previous[packageName];
251
- if (mode !== 'record' && !cacheEntryMatches(previousEntry, fingerprint)) changedPackages.push(packageName);
252
- const previousObject = previousEntry && typeof previousEntry === 'object' ? previousEntry : {};
253
- next[packageName] = mode === 'record'
254
- ? Object.assign({}, previousObject, { fixed: fingerprint })
255
- : Object.assign({}, previousObject, { input: fingerprint });
256
- }
257
- fs.mkdirSync(path.dirname(fingerprintPath), { recursive: true });
258
- fs.writeFileSync(fingerprintPath, JSON.stringify(next, null, 2) + '\n');
259
- if (mode !== 'record') {
260
- for (const packageName of changedPackages) console.log(packageName);
261
- }
262
- NODE
263
- node "$scanner_file" "$cache_file" "$fingerprint_file" detect > "$package_list"
264
- if [ ! -s "$package_list" ]; then
265
- mkdir -p .vmpush
266
- cp "$fingerprint_file" "$cache_file"
267
- exit 0
268
- fi
269
- echo "Rebuilding native Node dependencies: $(tr '\n' ' ' < "$package_list")"
270
- if [ -f pnpm-lock.yaml ] && command -v corepack >/dev/null 2>&1; then
271
- corepack enable >/dev/null 2>&1 || true
272
- xargs pnpm rebuild < "$package_list"
273
- elif [ -f yarn.lock ] && command -v corepack >/dev/null 2>&1; then
274
- corepack enable >/dev/null 2>&1 || true
275
- if yarn rebuild --help >/dev/null 2>&1; then
276
- xargs yarn rebuild < "$package_list"
277
- else
278
- echo "Yarn does not support targeted rebuild here; skipping broad reinstall"
279
- fi
280
- elif command -v npm >/dev/null 2>&1; then
281
- xargs npm rebuild < "$package_list"
282
- fi
283
- node "$scanner_file" "$cache_file" "$fingerprint_file" record > /dev/null
284
- mkdir -p .vmpush
285
- cp "$fingerprint_file" "$cache_file"
286
- `;
287
- const result = await vm.exec({ command: script, timeoutMs: 20 * 60 * 1000 });
288
- if (result.statusCode && result.statusCode !== 0) {
289
- throw new Error(result.stderr ?? result.stdout ?? `npm native dependency fixup failed with status ${result.statusCode}`);
290
- }
291
- if (result.stdout?.trim())
292
- console.log(result.stdout.trim());
293
- },
294
- };
295
- async function localNpmVersion() {
296
- try {
297
- const { stdout } = await execFileAsync("npm", ["--version"]);
298
- const version = stdout.trim();
299
- return version.length > 0 ? version : undefined;
300
- }
301
- catch {
302
- return undefined;
303
- }
304
- }
305
- function shellQuote(value) {
306
- return `'${value.replace(/'/g, `'"'"'`)}'`;
307
- }