@factory/cli 0.1.2-dev.5 → 0.55.2
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/README.md +232 -18
- package/bin/droid +112 -0
- package/install.js +314 -0
- package/package.json +38 -94
- package/platform.js +166 -0
- package/bundle/droid.js +0 -1545
- package/bundle/index.js.map +0 -2571
package/install.js
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* postinstall script for @factory/cli
|
|
5
|
+
*
|
|
6
|
+
* This script optimizes the installed package by replacing the JavaScript shim
|
|
7
|
+
* with a hard link to the actual binary executable. This avoids the overhead
|
|
8
|
+
* of launching another Node.js process when using the "droid" command.
|
|
9
|
+
*
|
|
10
|
+
* Based on esbuild's approach: https://github.com/evanw/esbuild/pull/1621
|
|
11
|
+
* and Sentry's guide: https://sentry.engineering/blog/publishing-binaries-on-npm
|
|
12
|
+
*
|
|
13
|
+
* On Unix: replaces bin/droid with hard link to the platform binary
|
|
14
|
+
* On Windows: keeps JS shim (Windows requires .exe extension)
|
|
15
|
+
* With --ignore-scripts: falls back to JS shim (still works, just slower)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const https = require('https');
|
|
21
|
+
const zlib = require('zlib');
|
|
22
|
+
const child_process = require('child_process');
|
|
23
|
+
|
|
24
|
+
const {
|
|
25
|
+
PLATFORM_PACKAGES,
|
|
26
|
+
getPlatformKey,
|
|
27
|
+
getBinaryName,
|
|
28
|
+
getBinaryPath,
|
|
29
|
+
detectAVX2Support,
|
|
30
|
+
} = require('./platform.js');
|
|
31
|
+
|
|
32
|
+
// Read version - handle case where package.json doesn't exist yet (template dir)
|
|
33
|
+
let PACKAGE_VERSION = '0.0.0';
|
|
34
|
+
try {
|
|
35
|
+
PACKAGE_VERSION = require('./package.json').version;
|
|
36
|
+
} catch (e) {
|
|
37
|
+
// Running from template directory before build
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isYarn2OrAbove() {
|
|
41
|
+
const { npm_config_user_agent } = process.env;
|
|
42
|
+
if (npm_config_user_agent) {
|
|
43
|
+
const match = npm_config_user_agent.match(/yarn\/(\d+)/);
|
|
44
|
+
if (match && match[1]) {
|
|
45
|
+
return parseInt(match[1], 10) >= 2;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function validateBinaryVersion(binaryPath) {
|
|
52
|
+
try {
|
|
53
|
+
const result = child_process.execFileSync(binaryPath, ['--version'], {
|
|
54
|
+
encoding: 'utf8',
|
|
55
|
+
timeout: 10000,
|
|
56
|
+
});
|
|
57
|
+
const version = result.trim().split('\n')[0];
|
|
58
|
+
// Version format might be "droid 0.50.0" or just "0.50.0"
|
|
59
|
+
if (PACKAGE_VERSION !== '0.0.0' && !version.includes(PACKAGE_VERSION)) {
|
|
60
|
+
console.warn(
|
|
61
|
+
`Warning: Binary version mismatch. Expected ${PACKAGE_VERSION}, got ${version}`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
} catch (e) {
|
|
65
|
+
// Version check is optional, don't fail install
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Make an HTTPS request with optional npm authentication.
|
|
71
|
+
* Uses NPM_TOKEN env var if available to bypass rate limits.
|
|
72
|
+
*/
|
|
73
|
+
function makeRequest(url) {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const parsedUrl = new URL(url);
|
|
76
|
+
const options = {
|
|
77
|
+
hostname: parsedUrl.hostname,
|
|
78
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
79
|
+
headers: {},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Add auth header if NPM_TOKEN is available (bypasses rate limits in CI)
|
|
83
|
+
if (process.env.NPM_TOKEN && parsedUrl.hostname.includes('npmjs.org')) {
|
|
84
|
+
options.headers['Authorization'] = `Bearer ${process.env.NPM_TOKEN}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
https
|
|
88
|
+
.get(options, (response) => {
|
|
89
|
+
if (response.statusCode >= 200 && response.statusCode < 300) {
|
|
90
|
+
const chunks = [];
|
|
91
|
+
response.on('data', (chunk) => chunks.push(chunk));
|
|
92
|
+
response.on('end', () => resolve(Buffer.concat(chunks)));
|
|
93
|
+
} else if (
|
|
94
|
+
response.statusCode >= 300 &&
|
|
95
|
+
response.statusCode < 400 &&
|
|
96
|
+
response.headers.location
|
|
97
|
+
) {
|
|
98
|
+
makeRequest(response.headers.location).then(resolve, reject);
|
|
99
|
+
} else {
|
|
100
|
+
reject(
|
|
101
|
+
new Error(
|
|
102
|
+
`npm responded with status code ${response.statusCode} when downloading the package`
|
|
103
|
+
)
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
.on('error', reject);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Fetch the tarball URL from npm registry packument.
|
|
113
|
+
* This is more reliable than constructing the URL manually.
|
|
114
|
+
*/
|
|
115
|
+
function fetchTarballUrl(packageName, version) {
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
const encodedName = packageName.replace('/', '%2f');
|
|
118
|
+
const url = `https://registry.npmjs.org/${encodedName}`;
|
|
119
|
+
|
|
120
|
+
makeRequest(url)
|
|
121
|
+
.then((buffer) => {
|
|
122
|
+
const packument = JSON.parse(buffer.toString('utf8'));
|
|
123
|
+
const versionData = packument.versions?.[version];
|
|
124
|
+
if (!versionData) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`Version ${version} not found for package ${packageName}`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
const tarballUrl = versionData.dist?.tarball;
|
|
130
|
+
if (!tarballUrl) {
|
|
131
|
+
throw new Error(`No tarball URL found for ${packageName}@${version}`);
|
|
132
|
+
}
|
|
133
|
+
resolve(tarballUrl);
|
|
134
|
+
})
|
|
135
|
+
.catch(reject);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Download the platform-specific package from npm registry as a fallback.
|
|
141
|
+
* This handles the case where optionalDependencies were disabled.
|
|
142
|
+
*/
|
|
143
|
+
function downloadBinaryFromNpm() {
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
const platformKey = getPlatformKey();
|
|
146
|
+
const packages = PLATFORM_PACKAGES[platformKey];
|
|
147
|
+
|
|
148
|
+
if (!packages) {
|
|
149
|
+
reject(new Error(`Unsupported platform: ${platformKey}`));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Choose package based on AVX2 support
|
|
154
|
+
const hasBaseline = !!packages.baseline;
|
|
155
|
+
let useBaseline = false;
|
|
156
|
+
if (hasBaseline) {
|
|
157
|
+
const avx2Supported = detectAVX2Support();
|
|
158
|
+
if (avx2Supported === false) {
|
|
159
|
+
useBaseline = true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const packageName = useBaseline ? packages.baseline : packages.regular;
|
|
164
|
+
|
|
165
|
+
console.log(`Downloading ${packageName}@${PACKAGE_VERSION} from npm...`);
|
|
166
|
+
|
|
167
|
+
// Fetch the tarball URL from packument instead of constructing it manually
|
|
168
|
+
fetchTarballUrl(packageName, PACKAGE_VERSION)
|
|
169
|
+
.then((tarballUrl) => makeRequest(tarballUrl))
|
|
170
|
+
.then((tarballBuffer) => {
|
|
171
|
+
const unzipped = zlib.gunzipSync(tarballBuffer);
|
|
172
|
+
const binaryName = getBinaryName();
|
|
173
|
+
const binaryData = extractFileFromTarball(
|
|
174
|
+
unzipped,
|
|
175
|
+
`package/bin/${binaryName}`
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
if (!binaryData) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`Binary not found in tarball: package/bin/${binaryName}`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Ensure bin directory exists before writing
|
|
185
|
+
const binDir = path.join(__dirname, 'bin');
|
|
186
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
187
|
+
|
|
188
|
+
// Write binary to local bin directory
|
|
189
|
+
const localBinPath = path.join(binDir, binaryName);
|
|
190
|
+
fs.writeFileSync(localBinPath, binaryData, { mode: 0o755 });
|
|
191
|
+
|
|
192
|
+
console.log(`Downloaded and extracted binary to ${localBinPath}`);
|
|
193
|
+
resolve(localBinPath);
|
|
194
|
+
})
|
|
195
|
+
.catch(reject);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function extractFileFromTarball(tarballBuffer, filepath) {
|
|
200
|
+
// Tar archives are organized in 512 byte blocks
|
|
201
|
+
let offset = 0;
|
|
202
|
+
while (offset < tarballBuffer.length) {
|
|
203
|
+
const header = tarballBuffer.subarray(offset, offset + 512);
|
|
204
|
+
offset += 512;
|
|
205
|
+
|
|
206
|
+
const fileName = header.toString('utf-8', 0, 100).replace(/\0.*/g, '');
|
|
207
|
+
const fileSize = parseInt(
|
|
208
|
+
header.toString('utf-8', 124, 136).replace(/\0.*/g, ''),
|
|
209
|
+
8
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
if (isNaN(fileSize)) break;
|
|
213
|
+
|
|
214
|
+
if (fileName === filepath) {
|
|
215
|
+
return tarballBuffer.subarray(offset, offset + fileSize);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Clamp offset to the upper multiple of 512
|
|
219
|
+
offset = (offset + fileSize + 511) & ~511;
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function main() {
|
|
225
|
+
const shimPath = path.join(__dirname, 'bin', 'droid');
|
|
226
|
+
let binaryPath = getBinaryPath();
|
|
227
|
+
|
|
228
|
+
// If optionalDependency not found, try downloading from npm
|
|
229
|
+
if (!binaryPath) {
|
|
230
|
+
console.log('Platform-specific binary not found in optionalDependencies.');
|
|
231
|
+
try {
|
|
232
|
+
binaryPath = await downloadBinaryFromNpm();
|
|
233
|
+
} catch (e) {
|
|
234
|
+
console.log(
|
|
235
|
+
`Failed to download binary: ${e.message}\n` +
|
|
236
|
+
'The "droid" command will use the JavaScript fallback.\n' +
|
|
237
|
+
'For better performance, ensure optionalDependencies are installed.'
|
|
238
|
+
);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// On Windows, we can't replace the shim with the binary due to .exe requirement
|
|
244
|
+
// On Yarn 2+, PnP mode has issues with binary modules
|
|
245
|
+
if (process.platform === 'win32' || isYarn2OrAbove()) {
|
|
246
|
+
validateBinaryVersion(binaryPath);
|
|
247
|
+
console.log('Using JavaScript shim for droid command.');
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// On Unix, replace the JavaScript shim with a hard link to the binary
|
|
252
|
+
// First, backup the shim in case linking fails
|
|
253
|
+
const shimBackupPath = shimPath + '.backup';
|
|
254
|
+
let shimBackedUp = false;
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
// Backup the existing shim
|
|
258
|
+
if (fs.existsSync(shimPath)) {
|
|
259
|
+
fs.copyFileSync(shimPath, shimBackupPath);
|
|
260
|
+
shimBackedUp = true;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Remove the existing shim
|
|
264
|
+
fs.unlinkSync(shimPath);
|
|
265
|
+
|
|
266
|
+
// Create a hard link to the binary
|
|
267
|
+
fs.linkSync(binaryPath, shimPath);
|
|
268
|
+
|
|
269
|
+
// Clean up backup
|
|
270
|
+
if (shimBackedUp) {
|
|
271
|
+
fs.unlinkSync(shimBackupPath);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
validateBinaryVersion(shimPath);
|
|
275
|
+
console.log(
|
|
276
|
+
'Optimized: droid command now runs the native binary directly.'
|
|
277
|
+
);
|
|
278
|
+
} catch (e) {
|
|
279
|
+
// If hard link fails (e.g., cross-device), try symlink
|
|
280
|
+
try {
|
|
281
|
+
fs.symlinkSync(binaryPath, shimPath);
|
|
282
|
+
|
|
283
|
+
// Clean up backup
|
|
284
|
+
if (shimBackedUp) {
|
|
285
|
+
fs.unlinkSync(shimBackupPath);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
console.log('Optimized: droid command linked to native binary.');
|
|
289
|
+
} catch (e2) {
|
|
290
|
+
// Restore the shim from backup
|
|
291
|
+
if (shimBackedUp) {
|
|
292
|
+
try {
|
|
293
|
+
fs.renameSync(shimBackupPath, shimPath);
|
|
294
|
+
console.log(
|
|
295
|
+
'Using JavaScript shim for droid command (optimization failed).'
|
|
296
|
+
);
|
|
297
|
+
} catch (e3) {
|
|
298
|
+
console.error(
|
|
299
|
+
'Failed to restore shim. Please reinstall the package.'
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
} else {
|
|
303
|
+
console.log(
|
|
304
|
+
'Using JavaScript shim for droid command (optimization failed).'
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
main().catch((e) => {
|
|
312
|
+
console.error('postinstall error:', e.message);
|
|
313
|
+
// Don't fail the install - the JS shim will still work
|
|
314
|
+
});
|
package/package.json
CHANGED
|
@@ -1,103 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@factory/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "dist/index.js",
|
|
3
|
+
"version": "0.55.2",
|
|
4
|
+
"description": "Factory Droid CLI - AI-powered software engineering agent",
|
|
7
5
|
"bin": {
|
|
8
|
-
"droid": "
|
|
9
|
-
},
|
|
10
|
-
"engines": {
|
|
11
|
-
"bun": ">=1.0.0"
|
|
6
|
+
"droid": "bin/droid"
|
|
12
7
|
},
|
|
8
|
+
"main": "platform.js",
|
|
13
9
|
"files": [
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
10
|
+
"bin/",
|
|
11
|
+
"platform.js",
|
|
12
|
+
"install.js",
|
|
13
|
+
"README.md"
|
|
17
14
|
],
|
|
18
15
|
"scripts": {
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
16
|
+
"postinstall": "node install.js"
|
|
17
|
+
},
|
|
18
|
+
"optionalDependencies": {
|
|
19
|
+
"@factory/cli-darwin-arm64": "0.55.2",
|
|
20
|
+
"@factory/cli-darwin-x64": "0.55.2",
|
|
21
|
+
"@factory/cli-darwin-x64-baseline": "0.55.2",
|
|
22
|
+
"@factory/cli-linux-arm64": "0.55.2",
|
|
23
|
+
"@factory/cli-linux-x64": "0.55.2",
|
|
24
|
+
"@factory/cli-linux-x64-baseline": "0.55.2",
|
|
25
|
+
"@factory/cli-win32-x64": "0.55.2",
|
|
26
|
+
"@factory/cli-win32-x64-baseline": "0.55.2"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20.0.0"
|
|
30
|
+
},
|
|
31
|
+
"license": "UNLICENSED",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/Factory-AI/factory.git",
|
|
35
|
+
"directory": "apps/cli"
|
|
37
36
|
},
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"@factory/common": "^0.1.0",
|
|
41
|
-
"@factory/droid-core": "^0.1.0",
|
|
42
|
-
"@factory/errors": "^0.1.0",
|
|
43
|
-
"@factory/logging": "^0.1.0",
|
|
44
|
-
"@factory/mcp": "^0.1.0",
|
|
45
|
-
"@factory/models": "^0.1.0",
|
|
46
|
-
"@factory/services": "^0.1.0",
|
|
47
|
-
"@factory/utils": "^0.1.0",
|
|
48
|
-
"@modelcontextprotocol/sdk": "^1.1.0",
|
|
49
|
-
"@statsig/react-bindings": "^3.8.2",
|
|
50
|
-
"@statsig/statsig-node-core": "^0.6.1",
|
|
51
|
-
"@types/express": "^5.0.3",
|
|
52
|
-
"@types/lodash": "^4.17.20",
|
|
53
|
-
"@types/marked": "^5.0.2",
|
|
54
|
-
"@types/react": "19.0.2",
|
|
55
|
-
"@types/uuid": "^10.0.0",
|
|
56
|
-
"@vscode/ripgrep": "^1.15.14",
|
|
57
|
-
"ansi-escapes": "^7.0.0",
|
|
58
|
-
"chalk": "^5.3.0",
|
|
59
|
-
"commander": "^11.0.0",
|
|
60
|
-
"diff": "^8.0.2",
|
|
61
|
-
"express": "^5.1.0",
|
|
62
|
-
"glob": "^10.4.5",
|
|
63
|
-
"highlight.js": "^11.11.1",
|
|
64
|
-
"ink": "^6.1.0",
|
|
65
|
-
"inquirer": "^12.8.2",
|
|
66
|
-
"jose": "^5.9.4",
|
|
67
|
-
"lodash-es": "^4.17.21",
|
|
68
|
-
"marked": "^16.1.1",
|
|
69
|
-
"mime": "^4.0.7",
|
|
70
|
-
"node-fetch": "^3.3.2",
|
|
71
|
-
"open": "^10.2.0",
|
|
72
|
-
"ora": "^8.1.1",
|
|
73
|
-
"prop-types": "^15.8.1",
|
|
74
|
-
"shell-quote": "^1.8.3",
|
|
75
|
-
"table": "^6.8.2",
|
|
76
|
-
"terminal-link": "^3.0.0",
|
|
77
|
-
"uuid": "^9.0.0",
|
|
78
|
-
"zod": "^3.23.8",
|
|
79
|
-
"zod-to-json-schema": "^3.24.1"
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
80
39
|
},
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
"@types/node": "^20",
|
|
90
|
-
"@types/node-fetch": "^2.6.11",
|
|
91
|
-
"@types/prop-types": "^15.7.15",
|
|
92
|
-
"@types/shell-quote": "^1.7.5",
|
|
93
|
-
"esbuild": "^0.25.0",
|
|
94
|
-
"eslint-plugin-no-barrel-files": "^1.2.2",
|
|
95
|
-
"ink-testing-library": "^4.0.0",
|
|
96
|
-
"jest": "^29.7.0",
|
|
97
|
-
"jsdom": "^26.1.0",
|
|
98
|
-
"ts-jest": "^29.2.6",
|
|
99
|
-
"ts-node": "^10.9.2",
|
|
100
|
-
"tsx": "^4.20.3",
|
|
101
|
-
"typescript": "^5.3.3"
|
|
102
|
-
}
|
|
103
|
-
}
|
|
40
|
+
"keywords": [
|
|
41
|
+
"factory",
|
|
42
|
+
"droid",
|
|
43
|
+
"cli",
|
|
44
|
+
"ai",
|
|
45
|
+
"agent"
|
|
46
|
+
]
|
|
47
|
+
}
|
package/platform.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform detection utilities for @factory/cli
|
|
3
|
+
*
|
|
4
|
+
* Exports the platform package mapping and helper functions for
|
|
5
|
+
* determining the correct binary to use.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const child_process = require('child_process');
|
|
10
|
+
|
|
11
|
+
// Platform-specific package mapping
|
|
12
|
+
// x64 platforms have both regular (AVX2) and baseline (no AVX2) variants
|
|
13
|
+
const PLATFORM_PACKAGES = {
|
|
14
|
+
'darwin-arm64': { regular: '@factory/cli-darwin-arm64' },
|
|
15
|
+
'darwin-x64': {
|
|
16
|
+
regular: '@factory/cli-darwin-x64',
|
|
17
|
+
baseline: '@factory/cli-darwin-x64-baseline',
|
|
18
|
+
},
|
|
19
|
+
'linux-arm64': { regular: '@factory/cli-linux-arm64' },
|
|
20
|
+
'linux-x64': {
|
|
21
|
+
regular: '@factory/cli-linux-x64',
|
|
22
|
+
baseline: '@factory/cli-linux-x64-baseline',
|
|
23
|
+
},
|
|
24
|
+
'win32-x64': {
|
|
25
|
+
regular: '@factory/cli-win32-x64',
|
|
26
|
+
baseline: '@factory/cli-win32-x64-baseline',
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// All packages (used for optionalDependencies)
|
|
31
|
+
const ALL_PACKAGES = Object.values(PLATFORM_PACKAGES).flatMap((p) =>
|
|
32
|
+
[p.regular, p.baseline].filter(Boolean)
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
function getPlatformKey() {
|
|
36
|
+
return `${process.platform}-${process.arch}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getBinaryName() {
|
|
40
|
+
return process.platform === 'win32' ? 'droid.exe' : 'droid';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getPlatformPackages() {
|
|
44
|
+
const platformKey = getPlatformKey();
|
|
45
|
+
return PLATFORM_PACKAGES[platformKey] || null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Detect if CPU supports AVX2 instructions.
|
|
50
|
+
* Returns true if AVX2 is supported, false if not, null if detection failed.
|
|
51
|
+
*/
|
|
52
|
+
function detectAVX2Support() {
|
|
53
|
+
try {
|
|
54
|
+
if (process.platform === 'linux') {
|
|
55
|
+
const cpuinfo = fs.readFileSync('/proc/cpuinfo', 'utf8');
|
|
56
|
+
return cpuinfo.toLowerCase().includes('avx2');
|
|
57
|
+
} else if (process.platform === 'darwin') {
|
|
58
|
+
const result = child_process.execSync(
|
|
59
|
+
'sysctl -n machdep.cpu.leaf7_features',
|
|
60
|
+
{ encoding: 'utf8', timeout: 5000 }
|
|
61
|
+
);
|
|
62
|
+
return result.toLowerCase().includes('avx2');
|
|
63
|
+
} else if (process.platform === 'win32') {
|
|
64
|
+
// Windows: use kernel32.dll IsProcessorFeaturePresent(40) via PowerShell
|
|
65
|
+
// Feature ID 40 = PF_AVX2_INSTRUCTIONS_AVAILABLE
|
|
66
|
+
// This is the same method used by the Factory CLI installer and Bun
|
|
67
|
+
const script = `
|
|
68
|
+
try {
|
|
69
|
+
$hasAvx2 = (Add-Type -MemberDefinition '[DllImport("kernel32.dll")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);' -Name 'Kernel32' -Namespace 'Win32' -PassThru -ErrorAction Stop)::IsProcessorFeaturePresent(40)
|
|
70
|
+
if ($hasAvx2) { Write-Output 'true' } else { Write-Output 'false' }
|
|
71
|
+
} catch {
|
|
72
|
+
try {
|
|
73
|
+
$hasAvx2 = ([Win32.Kernel32]::IsProcessorFeaturePresent(40))
|
|
74
|
+
if ($hasAvx2) { Write-Output 'true' } else { Write-Output 'false' }
|
|
75
|
+
} catch {
|
|
76
|
+
Write-Output 'unknown'
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
`;
|
|
80
|
+
const result = child_process.execSync(
|
|
81
|
+
`powershell -NoProfile -Command "${script.replace(/\n/g, ' ')}"`,
|
|
82
|
+
{ encoding: 'utf8', timeout: 10000 }
|
|
83
|
+
);
|
|
84
|
+
const output = result.trim().toLowerCase();
|
|
85
|
+
if (output === 'true') return true;
|
|
86
|
+
if (output === 'false') return false;
|
|
87
|
+
// 'unknown' or other - detection failed
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
} catch (e) {
|
|
91
|
+
// Detection failed
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function resolveBinaryPath(packageName) {
|
|
97
|
+
if (!packageName) return null;
|
|
98
|
+
try {
|
|
99
|
+
return require.resolve(`${packageName}/bin/${getBinaryName()}`);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get the binary path with AVX2-aware package selection.
|
|
107
|
+
* Returns { path, pkg, hasBaseline } or null if not found.
|
|
108
|
+
*/
|
|
109
|
+
function getBinaryPathWithInfo() {
|
|
110
|
+
const packages = getPlatformPackages();
|
|
111
|
+
if (!packages) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const hasBaseline = !!packages.baseline;
|
|
116
|
+
let useBaseline = false;
|
|
117
|
+
|
|
118
|
+
if (hasBaseline) {
|
|
119
|
+
const avx2Supported = detectAVX2Support();
|
|
120
|
+
if (avx2Supported === false) {
|
|
121
|
+
useBaseline = true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const preferredPkg = useBaseline ? packages.baseline : packages.regular;
|
|
126
|
+
const fallbackPkg = useBaseline ? packages.regular : packages.baseline;
|
|
127
|
+
|
|
128
|
+
let binPath = resolveBinaryPath(preferredPkg);
|
|
129
|
+
let selectedPkg = preferredPkg;
|
|
130
|
+
|
|
131
|
+
if (!binPath && fallbackPkg) {
|
|
132
|
+
binPath = resolveBinaryPath(fallbackPkg);
|
|
133
|
+
selectedPkg = fallbackPkg;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!binPath) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return { path: binPath, pkg: selectedPkg, hasBaseline };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get the binary path (simple version for backward compatibility).
|
|
145
|
+
*/
|
|
146
|
+
function getBinaryPath() {
|
|
147
|
+
const result = getBinaryPathWithInfo();
|
|
148
|
+
return result ? result.path : null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isSupportedPlatform() {
|
|
152
|
+
return getPlatformPackages() !== null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = {
|
|
156
|
+
PLATFORM_PACKAGES,
|
|
157
|
+
ALL_PACKAGES,
|
|
158
|
+
getPlatformKey,
|
|
159
|
+
getBinaryName,
|
|
160
|
+
getPlatformPackages,
|
|
161
|
+
detectAVX2Support,
|
|
162
|
+
resolveBinaryPath,
|
|
163
|
+
getBinaryPathWithInfo,
|
|
164
|
+
getBinaryPath,
|
|
165
|
+
isSupportedPlatform,
|
|
166
|
+
};
|