fallow-similar-code 3.19.0
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/LICENSE +21 -0
- package/README.md +18 -0
- package/bin/fallow-similar-code +11 -0
- package/package.json +38 -0
- package/scripts/package-matrix.test.js +18 -0
- package/scripts/platform-package.js +26 -0
- package/scripts/platform-package.test.js +27 -0
- package/scripts/run-binary.js +201 -0
- package/scripts/run-binary.test.js +165 -0
- package/scripts/verify-binary.js +180 -0
- package/scripts/verify-binary.test.js +129 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-present Fallow contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# fallow-similar-code
|
|
2
|
+
|
|
3
|
+
Optional first-party native sidecar used by Fallow's local similar-code analysis.
|
|
4
|
+
Install it through the main `fallow` package. Then run:
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
fallow similar-code setup --local
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Setup downloads the exact pinned Apache-2.0 model artifacts into the user cache.
|
|
11
|
+
Analysis after setup is offline. Source is passed to the native sidecar only on
|
|
12
|
+
stdin and is never logged or cached by this package.
|
|
13
|
+
|
|
14
|
+
The wrapper only launches an exact-version `@fallow-cli/fallow-similar-code-*`
|
|
15
|
+
platform package after verifying its detached Ed25519 signature and the SHA-256
|
|
16
|
+
digest embedded in that exact platform package. Missing or mismatched integrity
|
|
17
|
+
metadata fails closed. The wrapper does not contain or execute a JavaScript
|
|
18
|
+
model runtime.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
require("../scripts/run-binary")
|
|
6
|
+
.run(process.argv.slice(2))
|
|
7
|
+
.catch((error) => {
|
|
8
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
9
|
+
process.stderr.write(`fallow-similar-code: ${message}\n`);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fallow-similar-code",
|
|
3
|
+
"version": "3.19.0",
|
|
4
|
+
"description": "First-party local embedding sidecar for Fallow similar-code analysis",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/fallow-rs/fallow.git"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=22"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"fallow-similar-code": "bin/fallow-similar-code"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"bin",
|
|
18
|
+
"scripts",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"test": "node --test scripts/*.test.js"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"detect-libc": "2.1.2"
|
|
27
|
+
},
|
|
28
|
+
"optionalDependencies": {
|
|
29
|
+
"@fallow-cli/fallow-similar-code-darwin-arm64": "3.19.0",
|
|
30
|
+
"@fallow-cli/fallow-similar-code-darwin-x64": "3.19.0",
|
|
31
|
+
"@fallow-cli/fallow-similar-code-linux-x64-gnu": "3.19.0",
|
|
32
|
+
"@fallow-cli/fallow-similar-code-linux-arm64-gnu": "3.19.0",
|
|
33
|
+
"@fallow-cli/fallow-similar-code-linux-x64-musl": "3.19.0",
|
|
34
|
+
"@fallow-cli/fallow-similar-code-linux-arm64-musl": "3.19.0",
|
|
35
|
+
"@fallow-cli/fallow-similar-code-win32-x64-msvc": "3.19.0",
|
|
36
|
+
"@fallow-cli/fallow-similar-code-win32-arm64-msvc": "3.19.0"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const assert = require("node:assert/strict");
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
const test = require("node:test");
|
|
7
|
+
const companion = require("../package.json");
|
|
8
|
+
|
|
9
|
+
test("all optional native packages are exact-version compatible", () => {
|
|
10
|
+
for (const [name, version] of Object.entries(companion.optionalDependencies)) {
|
|
11
|
+
assert.equal(version, companion.version);
|
|
12
|
+
const directory = name.replace("@fallow-cli/fallow-similar-code-", "fallow-similar-code-");
|
|
13
|
+
const manifestPath = path.join(__dirname, "..", "..", directory, "package.json");
|
|
14
|
+
const native = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
15
|
+
assert.equal(native.name, name);
|
|
16
|
+
assert.equal(native.version, companion.version);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const PACKAGES = Object.freeze({
|
|
4
|
+
"darwin-arm64": "@fallow-cli/fallow-similar-code-darwin-arm64",
|
|
5
|
+
"darwin-x64": "@fallow-cli/fallow-similar-code-darwin-x64",
|
|
6
|
+
"linux-arm64-gnu": "@fallow-cli/fallow-similar-code-linux-arm64-gnu",
|
|
7
|
+
"linux-arm64-musl": "@fallow-cli/fallow-similar-code-linux-arm64-musl",
|
|
8
|
+
"linux-x64-gnu": "@fallow-cli/fallow-similar-code-linux-x64-gnu",
|
|
9
|
+
"linux-x64-musl": "@fallow-cli/fallow-similar-code-linux-x64-musl",
|
|
10
|
+
"win32-arm64-msvc": "@fallow-cli/fallow-similar-code-win32-arm64-msvc",
|
|
11
|
+
"win32-x64-msvc": "@fallow-cli/fallow-similar-code-win32-x64-msvc",
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const getPlatformPackage = (platform, arch, libc) => {
|
|
15
|
+
const suffix =
|
|
16
|
+
platform === "linux"
|
|
17
|
+
? `-${libc === "glibc" ? "gnu" : "musl"}`
|
|
18
|
+
: platform === "win32"
|
|
19
|
+
? "-msvc"
|
|
20
|
+
: "";
|
|
21
|
+
return PACKAGES[`${platform}-${arch}${suffix}`];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const isPlatformPackage = (packageName) => Object.values(PACKAGES).includes(packageName);
|
|
25
|
+
|
|
26
|
+
module.exports = { getPlatformPackage, isPlatformPackage };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const assert = require("node:assert/strict");
|
|
4
|
+
const test = require("node:test");
|
|
5
|
+
const { getPlatformPackage, isPlatformPackage } = require("./platform-package");
|
|
6
|
+
|
|
7
|
+
test("maps every supported target to its platform package", () => {
|
|
8
|
+
assert.equal(
|
|
9
|
+
getPlatformPackage("darwin", "arm64"),
|
|
10
|
+
"@fallow-cli/fallow-similar-code-darwin-arm64",
|
|
11
|
+
);
|
|
12
|
+
assert.equal(
|
|
13
|
+
getPlatformPackage("linux", "x64", "glibc"),
|
|
14
|
+
"@fallow-cli/fallow-similar-code-linux-x64-gnu",
|
|
15
|
+
);
|
|
16
|
+
assert.equal(
|
|
17
|
+
getPlatformPackage("linux", "arm64", "musl"),
|
|
18
|
+
"@fallow-cli/fallow-similar-code-linux-arm64-musl",
|
|
19
|
+
);
|
|
20
|
+
assert.equal(
|
|
21
|
+
getPlatformPackage("win32", "x64"),
|
|
22
|
+
"@fallow-cli/fallow-similar-code-win32-x64-msvc",
|
|
23
|
+
);
|
|
24
|
+
assert.equal(getPlatformPackage("freebsd", "x64"), undefined);
|
|
25
|
+
assert.equal(isPlatformPackage("@fallow-cli/fallow-similar-code-darwin-arm64"), true);
|
|
26
|
+
assert.equal(isPlatformPackage("@fallow-cli/darwin-arm64"), false);
|
|
27
|
+
});
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
const { spawn, spawnSync } = require("node:child_process");
|
|
6
|
+
const { once } = require("node:events");
|
|
7
|
+
const { getPlatformPackage, isPlatformPackage } = require("./platform-package");
|
|
8
|
+
const { verifyBinary } = require("./verify-binary");
|
|
9
|
+
const ownManifest = require("../package.json");
|
|
10
|
+
|
|
11
|
+
const SETUP_PROCESS_TIMEOUT_MS = 55 * 60 * 1000;
|
|
12
|
+
const SETUP_TERMINATE_GRACE_MS = 1000;
|
|
13
|
+
const SETUP_HARD_EXIT_GRACE_MS = 1000;
|
|
14
|
+
const SETUP_TIMEOUT_EXIT_CODE = 124;
|
|
15
|
+
const SETUP_TIMEOUT_MESSAGE =
|
|
16
|
+
"local model setup timed out; check DNS, proxy, and network connectivity before retrying";
|
|
17
|
+
const SIGNAL_EXIT_CODES = new Map([
|
|
18
|
+
["SIGINT", 130],
|
|
19
|
+
["SIGTERM", 143],
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
const resolvePlatformPackage = () => {
|
|
23
|
+
if (process.platform !== "linux") {
|
|
24
|
+
return getPlatformPackage(process.platform, process.arch);
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const { familySync } = require("detect-libc");
|
|
28
|
+
return getPlatformPackage(process.platform, process.arch, familySync());
|
|
29
|
+
} catch {
|
|
30
|
+
return getPlatformPackage(process.platform, process.arch, "musl");
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const resolveBinaryArtifact = (
|
|
35
|
+
packageName,
|
|
36
|
+
resolve = require.resolve,
|
|
37
|
+
readFile = fs.readFileSync,
|
|
38
|
+
stat = fs.lstatSync,
|
|
39
|
+
platform = process.platform,
|
|
40
|
+
) => {
|
|
41
|
+
if (!isPlatformPackage(packageName)) {
|
|
42
|
+
throw new Error(`unsupported similar-code platform package: ${String(packageName)}`);
|
|
43
|
+
}
|
|
44
|
+
const manifestPath = resolve(`${packageName}/package.json`);
|
|
45
|
+
const manifestMetadata = stat(manifestPath);
|
|
46
|
+
if (!manifestMetadata.isFile() || manifestMetadata.isSymbolicLink()) {
|
|
47
|
+
throw new Error(`platform manifest is not a regular file: ${manifestPath}`);
|
|
48
|
+
}
|
|
49
|
+
const manifest = JSON.parse(readFile(manifestPath, "utf8"));
|
|
50
|
+
if (manifest.name !== packageName) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`platform package ownership mismatch, expected ${packageName} but manifest declares ${String(manifest.name)}`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (manifest.version !== ownManifest.version) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`version mismatch: fallow-similar-code ${ownManifest.version} requires ${packageName} ${ownManifest.version}, found ${manifest.version}`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
const binaryName = platform === "win32" ? "fallow-similar-code.exe" : "fallow-similar-code";
|
|
61
|
+
const binaryPath = path.join(path.dirname(manifestPath), binaryName);
|
|
62
|
+
const metadata = stat(binaryPath);
|
|
63
|
+
if (!metadata.isFile() || metadata.isSymbolicLink()) {
|
|
64
|
+
throw new Error(`native sidecar is not a regular file: ${binaryPath}`);
|
|
65
|
+
}
|
|
66
|
+
return Object.freeze({
|
|
67
|
+
packageName,
|
|
68
|
+
packageVersion: manifest.version,
|
|
69
|
+
manifestPath,
|
|
70
|
+
binaryName,
|
|
71
|
+
binaryPath,
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const resolveBinary = (...args) => resolveBinaryArtifact(...args).binaryPath;
|
|
76
|
+
|
|
77
|
+
const setupTimeoutFor = (args) => (args[0] === "setup" ? SETUP_PROCESS_TIMEOUT_MS : null);
|
|
78
|
+
|
|
79
|
+
const spawnNative = (binaryPath, args, spawnProcess = spawnSync) => {
|
|
80
|
+
const options = { env: process.env, stdio: "inherit" };
|
|
81
|
+
const result = spawnProcess(binaryPath, args, options);
|
|
82
|
+
if (result.error) {
|
|
83
|
+
throw result.error;
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const superviseSetup = async (
|
|
89
|
+
binaryPath,
|
|
90
|
+
args,
|
|
91
|
+
{
|
|
92
|
+
spawnProcess = spawn,
|
|
93
|
+
timeoutMs = SETUP_PROCESS_TIMEOUT_MS,
|
|
94
|
+
terminateGraceMs = SETUP_TERMINATE_GRACE_MS,
|
|
95
|
+
hardExitGraceMs = SETUP_HARD_EXIT_GRACE_MS,
|
|
96
|
+
} = {},
|
|
97
|
+
) => {
|
|
98
|
+
const child = spawnProcess(binaryPath, args, {
|
|
99
|
+
env: process.env,
|
|
100
|
+
stdio: "inherit",
|
|
101
|
+
});
|
|
102
|
+
let forceTimer;
|
|
103
|
+
let hardExitTimer;
|
|
104
|
+
let forwardedSignal;
|
|
105
|
+
let timedOut = false;
|
|
106
|
+
let resolveHardExit;
|
|
107
|
+
const hardExit = new Promise((resolve) => {
|
|
108
|
+
resolveHardExit = resolve;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const terminate = (signal, status) => {
|
|
112
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
113
|
+
child.kill(signal);
|
|
114
|
+
if (forceTimer) return;
|
|
115
|
+
forceTimer = setTimeout(() => {
|
|
116
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
117
|
+
child.kill("SIGKILL");
|
|
118
|
+
hardExitTimer = setTimeout(() => {
|
|
119
|
+
child.unref();
|
|
120
|
+
resolveHardExit([status, null]);
|
|
121
|
+
}, hardExitGraceMs);
|
|
122
|
+
}, terminateGraceMs);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const signalHandlers = new Map();
|
|
126
|
+
for (const signal of SIGNAL_EXIT_CODES.keys()) {
|
|
127
|
+
const handler = () => {
|
|
128
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
129
|
+
forwardedSignal ??= signal;
|
|
130
|
+
terminate(signal, SIGNAL_EXIT_CODES.get(forwardedSignal) ?? 1);
|
|
131
|
+
};
|
|
132
|
+
signalHandlers.set(signal, handler);
|
|
133
|
+
process.on(signal, handler);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const timeout = setTimeout(() => {
|
|
137
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
138
|
+
timedOut = true;
|
|
139
|
+
terminate("SIGTERM", SETUP_TIMEOUT_EXIT_CODE);
|
|
140
|
+
}, timeoutMs);
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const [status, signal] = await Promise.race([once(child, "exit"), hardExit]);
|
|
144
|
+
if (timedOut) {
|
|
145
|
+
return { status: SETUP_TIMEOUT_EXIT_CODE, signal: null, timedOut: true };
|
|
146
|
+
}
|
|
147
|
+
if (forwardedSignal) {
|
|
148
|
+
return {
|
|
149
|
+
status: SIGNAL_EXIT_CODES.get(forwardedSignal) ?? 1,
|
|
150
|
+
signal: forwardedSignal,
|
|
151
|
+
timedOut: false,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
return { status: status ?? 1, signal, timedOut: false };
|
|
155
|
+
} finally {
|
|
156
|
+
clearTimeout(timeout);
|
|
157
|
+
if (forceTimer) clearTimeout(forceTimer);
|
|
158
|
+
if (hardExitTimer) clearTimeout(hardExitTimer);
|
|
159
|
+
for (const [signal, handler] of signalHandlers) process.removeListener(signal, handler);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const run = async (args) => {
|
|
164
|
+
try {
|
|
165
|
+
const packageName = resolvePlatformPackage();
|
|
166
|
+
const artifact = resolveBinaryArtifact(packageName);
|
|
167
|
+
const verification = verifyBinary(artifact);
|
|
168
|
+
if (!verification.ok) {
|
|
169
|
+
throw new Error(`binary verification failed: ${verification.message}`);
|
|
170
|
+
}
|
|
171
|
+
const result =
|
|
172
|
+
setupTimeoutFor(args) === null
|
|
173
|
+
? spawnNative(artifact.binaryPath, args)
|
|
174
|
+
: await superviseSetup(artifact.binaryPath, args);
|
|
175
|
+
if (result.timedOut) {
|
|
176
|
+
throw new Error(SETUP_TIMEOUT_MESSAGE);
|
|
177
|
+
}
|
|
178
|
+
if (result.signal) {
|
|
179
|
+
process.stderr.write(`fallow-similar-code terminated by signal ${result.signal}\n`);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
process.exit(result.status ?? 1);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
185
|
+
process.stderr.write(`fallow-similar-code: ${message}\n`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
module.exports = {
|
|
191
|
+
SETUP_PROCESS_TIMEOUT_MS,
|
|
192
|
+
SETUP_TIMEOUT_EXIT_CODE,
|
|
193
|
+
SETUP_TIMEOUT_MESSAGE,
|
|
194
|
+
resolveBinary,
|
|
195
|
+
resolveBinaryArtifact,
|
|
196
|
+
resolvePlatformPackage,
|
|
197
|
+
run,
|
|
198
|
+
setupTimeoutFor,
|
|
199
|
+
spawnNative,
|
|
200
|
+
superviseSetup,
|
|
201
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const assert = require("node:assert/strict");
|
|
4
|
+
const test = require("node:test");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
const ownManifest = require("../package.json");
|
|
7
|
+
const {
|
|
8
|
+
SETUP_PROCESS_TIMEOUT_MS,
|
|
9
|
+
SETUP_TIMEOUT_EXIT_CODE,
|
|
10
|
+
SETUP_TIMEOUT_MESSAGE,
|
|
11
|
+
resolveBinary,
|
|
12
|
+
resolveBinaryArtifact,
|
|
13
|
+
setupTimeoutFor,
|
|
14
|
+
spawnNative,
|
|
15
|
+
superviseSetup,
|
|
16
|
+
} = require("./run-binary");
|
|
17
|
+
|
|
18
|
+
const PACKAGE_NAME = "@fallow-cli/fallow-similar-code-darwin-arm64";
|
|
19
|
+
const manifest = (overrides = {}) =>
|
|
20
|
+
JSON.stringify({ name: PACKAGE_NAME, version: ownManifest.version, ...overrides });
|
|
21
|
+
|
|
22
|
+
test("resolves an exact-version regular native binary", () => {
|
|
23
|
+
const manifestPath = path.join("/packages", "native", "package.json");
|
|
24
|
+
const resolved = resolveBinary(
|
|
25
|
+
PACKAGE_NAME,
|
|
26
|
+
() => manifestPath,
|
|
27
|
+
() => manifest(),
|
|
28
|
+
() => ({ isFile: () => true, isSymbolicLink: () => false }),
|
|
29
|
+
);
|
|
30
|
+
assert.equal(resolved, path.join("/packages", "native", "fallow-similar-code"));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("rejects a platform package from another release", () => {
|
|
34
|
+
assert.throws(
|
|
35
|
+
() =>
|
|
36
|
+
resolveBinary(
|
|
37
|
+
PACKAGE_NAME,
|
|
38
|
+
() => "/native/package.json",
|
|
39
|
+
() => manifest({ version: "0.0.0" }),
|
|
40
|
+
() => ({ isFile: () => true, isSymbolicLink: () => false }),
|
|
41
|
+
),
|
|
42
|
+
/version mismatch/,
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("rejects symlinked binaries", () => {
|
|
47
|
+
assert.throws(
|
|
48
|
+
() =>
|
|
49
|
+
resolveBinary(
|
|
50
|
+
PACKAGE_NAME,
|
|
51
|
+
() => "/native/package.json",
|
|
52
|
+
() => manifest(),
|
|
53
|
+
(filePath) => ({
|
|
54
|
+
isFile: () => true,
|
|
55
|
+
isSymbolicLink: () => !filePath.endsWith("package.json"),
|
|
56
|
+
}),
|
|
57
|
+
),
|
|
58
|
+
/not a regular file/,
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("rejects a symlinked platform manifest", () => {
|
|
63
|
+
assert.throws(
|
|
64
|
+
() =>
|
|
65
|
+
resolveBinaryArtifact(
|
|
66
|
+
PACKAGE_NAME,
|
|
67
|
+
() => "/native/package.json",
|
|
68
|
+
() => manifest(),
|
|
69
|
+
() => ({ isFile: () => true, isSymbolicLink: () => true }),
|
|
70
|
+
),
|
|
71
|
+
/platform manifest is not a regular file/,
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("returns the exact owning manifest in the verification artifact", () => {
|
|
76
|
+
const manifestPath = path.join("/packages", "native", "package.json");
|
|
77
|
+
const artifact = resolveBinaryArtifact(
|
|
78
|
+
PACKAGE_NAME,
|
|
79
|
+
() => manifestPath,
|
|
80
|
+
() => manifest(),
|
|
81
|
+
() => ({ isFile: () => true, isSymbolicLink: () => false }),
|
|
82
|
+
);
|
|
83
|
+
assert.deepEqual(artifact, {
|
|
84
|
+
packageName: PACKAGE_NAME,
|
|
85
|
+
packageVersion: ownManifest.version,
|
|
86
|
+
manifestPath,
|
|
87
|
+
binaryName: "fallow-similar-code",
|
|
88
|
+
binaryPath: path.join("/packages", "native", "fallow-similar-code"),
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("rejects a manifest that does not belong to the resolved package", () => {
|
|
93
|
+
assert.throws(
|
|
94
|
+
() =>
|
|
95
|
+
resolveBinaryArtifact(
|
|
96
|
+
PACKAGE_NAME,
|
|
97
|
+
() => "/native/package.json",
|
|
98
|
+
() => manifest({ name: "@fallow-cli/other" }),
|
|
99
|
+
() => ({ isFile: () => true, isSymbolicLink: () => false }),
|
|
100
|
+
),
|
|
101
|
+
/ownership mismatch/,
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("uses the Windows executable basename in the artifact", () => {
|
|
106
|
+
const artifact = resolveBinaryArtifact(
|
|
107
|
+
PACKAGE_NAME,
|
|
108
|
+
() => path.join("C:\\packages", "native", "package.json"),
|
|
109
|
+
() => manifest(),
|
|
110
|
+
() => ({ isFile: () => true, isSymbolicLink: () => false }),
|
|
111
|
+
"win32",
|
|
112
|
+
);
|
|
113
|
+
assert.equal(artifact.binaryName, "fallow-similar-code.exe");
|
|
114
|
+
assert.match(artifact.binaryPath, /fallow-similar-code\.exe$/);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("bounds only the direct setup command", () => {
|
|
118
|
+
assert.equal(setupTimeoutFor(["setup", "--local"]), SETUP_PROCESS_TIMEOUT_MS);
|
|
119
|
+
assert.equal(setupTimeoutFor(["status", "--json"]), null);
|
|
120
|
+
assert.equal(setupTimeoutFor(["serve"]), null);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("setup timeout guidance names the network phases a user can inspect", () => {
|
|
124
|
+
assert.match(SETUP_TIMEOUT_MESSAGE, /timed out/);
|
|
125
|
+
assert.match(SETUP_TIMEOUT_MESSAGE, /DNS/);
|
|
126
|
+
assert.match(SETUP_TIMEOUT_MESSAGE, /proxy/);
|
|
127
|
+
assert.match(SETUP_TIMEOUT_MESSAGE, /network connectivity/);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("force-kills a local setup process that ignores graceful termination", async () => {
|
|
131
|
+
const result = await superviseSetup(
|
|
132
|
+
process.execPath,
|
|
133
|
+
["-e", "process.on('SIGTERM', () => {}); setInterval(() => {}, 1000)"],
|
|
134
|
+
{ timeoutMs: 80, terminateGraceMs: 40, hardExitGraceMs: 100 },
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
assert.deepEqual(result, {
|
|
138
|
+
status: SETUP_TIMEOUT_EXIT_CODE,
|
|
139
|
+
signal: null,
|
|
140
|
+
timedOut: true,
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("returns a successful local setup process result", async () => {
|
|
145
|
+
const result = await superviseSetup(process.execPath, ["-e", "process.exit(0)"], {
|
|
146
|
+
timeoutMs: 1000,
|
|
147
|
+
terminateGraceMs: 40,
|
|
148
|
+
hardExitGraceMs: 100,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
assert.deepEqual(result, { status: 0, signal: null, timedOut: false });
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("spawns non-setup commands without a wrapper timeout", () => {
|
|
155
|
+
let captured = null;
|
|
156
|
+
const result = spawnNative("/fixture/sidecar", ["status", "--json"], (binary, args, options) => {
|
|
157
|
+
captured = { binary, args, options };
|
|
158
|
+
return { status: 0, signal: null };
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
assert.equal(result.status, 0);
|
|
162
|
+
assert.equal(captured.binary, "/fixture/sidecar");
|
|
163
|
+
assert.deepEqual(captured.args, ["status", "--json"]);
|
|
164
|
+
assert.deepEqual(captured.options, { env: process.env, stdio: "inherit" });
|
|
165
|
+
});
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const crypto = require("node:crypto");
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
const { isPlatformPackage } = require("./platform-package");
|
|
7
|
+
const ownManifest = require("../package.json");
|
|
8
|
+
|
|
9
|
+
const PUBLIC_KEY = Buffer.from([
|
|
10
|
+
131, 78, 111, 215, 115, 51, 230, 238, 223, 119, 147, 71, 199, 16, 172, 180, 3, 210, 216, 35, 77,
|
|
11
|
+
85, 159, 94, 215, 200, 126, 85, 42, 222, 11, 209,
|
|
12
|
+
]);
|
|
13
|
+
const SPKI_HEADER = Buffer.from([
|
|
14
|
+
0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00,
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
const failure = (code, message) => ({ ok: false, code, message });
|
|
18
|
+
|
|
19
|
+
const validateArtifact = (artifact) => {
|
|
20
|
+
if (
|
|
21
|
+
!artifact ||
|
|
22
|
+
typeof artifact !== "object" ||
|
|
23
|
+
typeof artifact.packageName !== "string" ||
|
|
24
|
+
typeof artifact.packageVersion !== "string" ||
|
|
25
|
+
typeof artifact.manifestPath !== "string" ||
|
|
26
|
+
typeof artifact.binaryName !== "string" ||
|
|
27
|
+
typeof artifact.binaryPath !== "string"
|
|
28
|
+
) {
|
|
29
|
+
return failure("artifact-invalid", "similar-code binary artifact is incomplete");
|
|
30
|
+
}
|
|
31
|
+
const expectedBinaryName = artifact.packageName.includes("-win32-")
|
|
32
|
+
? "fallow-similar-code.exe"
|
|
33
|
+
: "fallow-similar-code";
|
|
34
|
+
if (!isPlatformPackage(artifact.packageName) || artifact.binaryName !== expectedBinaryName) {
|
|
35
|
+
return failure(
|
|
36
|
+
"package-mismatch",
|
|
37
|
+
"similar-code artifact does not identify a supported platform package and binary",
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
const manifestDirectory = path.resolve(path.dirname(artifact.manifestPath));
|
|
41
|
+
const binaryDirectory = path.resolve(path.dirname(artifact.binaryPath));
|
|
42
|
+
if (
|
|
43
|
+
manifestDirectory !== binaryDirectory ||
|
|
44
|
+
path.basename(artifact.manifestPath) !== "package.json" ||
|
|
45
|
+
path.basename(artifact.binaryPath) !== artifact.binaryName
|
|
46
|
+
) {
|
|
47
|
+
return failure(
|
|
48
|
+
"package-mismatch",
|
|
49
|
+
"similar-code binary and package manifest do not have the same package owner",
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return { ok: true };
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const readManifestDigest = (artifact, readFile) => {
|
|
56
|
+
let manifest;
|
|
57
|
+
try {
|
|
58
|
+
manifest = JSON.parse(readFile(artifact.manifestPath, "utf8"));
|
|
59
|
+
} catch (error) {
|
|
60
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
61
|
+
return failure("manifest-invalid", `cannot read platform package manifest: ${message}`);
|
|
62
|
+
}
|
|
63
|
+
if (!manifest || typeof manifest !== "object") {
|
|
64
|
+
return failure("manifest-invalid", "platform package manifest must be a JSON object");
|
|
65
|
+
}
|
|
66
|
+
if (manifest.name !== artifact.packageName) {
|
|
67
|
+
return failure(
|
|
68
|
+
"package-mismatch",
|
|
69
|
+
`platform package ownership mismatch, expected ${artifact.packageName} but got ${String(manifest.name)}`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
if (
|
|
73
|
+
artifact.packageVersion !== ownManifest.version ||
|
|
74
|
+
manifest.version !== artifact.packageVersion
|
|
75
|
+
) {
|
|
76
|
+
return failure(
|
|
77
|
+
"version-mismatch",
|
|
78
|
+
`similar-code platform package must match companion version ${ownManifest.version}`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (!manifest.fallowDigests || typeof manifest.fallowDigests !== "object") {
|
|
82
|
+
return failure("digest-missing", "platform package manifest has no fallowDigests object");
|
|
83
|
+
}
|
|
84
|
+
const digest = manifest.fallowDigests[artifact.binaryName];
|
|
85
|
+
if (digest === undefined) {
|
|
86
|
+
return failure(
|
|
87
|
+
"digest-missing",
|
|
88
|
+
`platform package manifest has no SHA-256 digest for ${artifact.binaryName}`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
if (typeof digest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(digest)) {
|
|
92
|
+
return failure(
|
|
93
|
+
"digest-invalid",
|
|
94
|
+
`platform package manifest has a malformed SHA-256 digest for ${artifact.binaryName}`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
return { ok: true, digest: digest.slice("sha256:".length) };
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const readBinary = (binaryPath, readFile) => {
|
|
101
|
+
try {
|
|
102
|
+
return { ok: true, bytes: readFile(binaryPath) };
|
|
103
|
+
} catch (error) {
|
|
104
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
105
|
+
return failure("read-error", `cannot read similar-code binary: ${message}`);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const readSignature = (binaryPath, readFile) => {
|
|
110
|
+
try {
|
|
111
|
+
const signature = readFile(`${binaryPath}.sig`);
|
|
112
|
+
return signature.length === 64
|
|
113
|
+
? { ok: true, signature }
|
|
114
|
+
: failure("sig-invalid", "signature has an unexpected length");
|
|
115
|
+
} catch (error) {
|
|
116
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
117
|
+
return failure("sig-missing", `cannot read detached signature: ${message}`);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const verifySignature = (binary, signature, verify, createPublicKey) => {
|
|
122
|
+
try {
|
|
123
|
+
const key = createPublicKey({
|
|
124
|
+
key: Buffer.concat([SPKI_HEADER, PUBLIC_KEY]),
|
|
125
|
+
format: "der",
|
|
126
|
+
type: "spki",
|
|
127
|
+
});
|
|
128
|
+
return verify(null, binary, key, signature)
|
|
129
|
+
? { ok: true }
|
|
130
|
+
: failure("sig-invalid", "Ed25519 signature verification failed");
|
|
131
|
+
} catch (error) {
|
|
132
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
133
|
+
return failure("sig-invalid", `Ed25519 signature verification failed: ${message}`);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const verifyDigest = (binary, expectedDigest) => {
|
|
138
|
+
const actual = crypto.createHash("sha256").update(binary).digest();
|
|
139
|
+
const expected = Buffer.from(expectedDigest, "hex");
|
|
140
|
+
return crypto.timingSafeEqual(actual, expected)
|
|
141
|
+
? { ok: true }
|
|
142
|
+
: failure("digest-mismatch", "embedded SHA-256 digest does not match the binary");
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const verifyBinary = (
|
|
146
|
+
artifact,
|
|
147
|
+
{
|
|
148
|
+
readFile = fs.readFileSync,
|
|
149
|
+
verify = crypto.verify,
|
|
150
|
+
createPublicKey = crypto.createPublicKey,
|
|
151
|
+
} = {},
|
|
152
|
+
) => {
|
|
153
|
+
const artifactResult = validateArtifact(artifact);
|
|
154
|
+
if (!artifactResult.ok) return artifactResult;
|
|
155
|
+
|
|
156
|
+
const manifestResult = readManifestDigest(artifact, readFile);
|
|
157
|
+
if (!manifestResult.ok) return manifestResult;
|
|
158
|
+
|
|
159
|
+
const binaryResult = readBinary(artifact.binaryPath, readFile);
|
|
160
|
+
if (!binaryResult.ok) return binaryResult;
|
|
161
|
+
|
|
162
|
+
const signatureResult = readSignature(artifact.binaryPath, readFile);
|
|
163
|
+
if (!signatureResult.ok) return signatureResult;
|
|
164
|
+
|
|
165
|
+
const signatureVerification = verifySignature(
|
|
166
|
+
binaryResult.bytes,
|
|
167
|
+
signatureResult.signature,
|
|
168
|
+
verify,
|
|
169
|
+
createPublicKey,
|
|
170
|
+
);
|
|
171
|
+
if (!signatureVerification.ok) return signatureVerification;
|
|
172
|
+
|
|
173
|
+
return verifyDigest(binaryResult.bytes, manifestResult.digest);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
module.exports = {
|
|
177
|
+
PUBLIC_KEY,
|
|
178
|
+
SPKI_HEADER,
|
|
179
|
+
verifyBinary,
|
|
180
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const assert = require("node:assert/strict");
|
|
4
|
+
const crypto = require("node:crypto");
|
|
5
|
+
const fs = require("node:fs");
|
|
6
|
+
const os = require("node:os");
|
|
7
|
+
const path = require("node:path");
|
|
8
|
+
const test = require("node:test");
|
|
9
|
+
const ownManifest = require("../package.json");
|
|
10
|
+
const { verifyBinary } = require("./verify-binary");
|
|
11
|
+
|
|
12
|
+
const PACKAGE_NAME = "@fallow-cli/fallow-similar-code-darwin-arm64";
|
|
13
|
+
const BINARY_NAME = "fallow-similar-code";
|
|
14
|
+
|
|
15
|
+
const digest = (bytes) => `sha256:${crypto.createHash("sha256").update(bytes).digest("hex")}`;
|
|
16
|
+
|
|
17
|
+
const makeFixture = (t, options = {}) => {
|
|
18
|
+
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "fallow-similar-code-verify-"));
|
|
19
|
+
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
|
20
|
+
const binaryPath = path.join(directory, BINARY_NAME);
|
|
21
|
+
const manifestPath = path.join(directory, "package.json");
|
|
22
|
+
const binary = options.binary ?? Buffer.from("trusted binary");
|
|
23
|
+
fs.writeFileSync(binaryPath, binary);
|
|
24
|
+
fs.writeFileSync(`${binaryPath}.sig`, options.signature ?? Buffer.alloc(64));
|
|
25
|
+
const manifest = {
|
|
26
|
+
name: PACKAGE_NAME,
|
|
27
|
+
version: ownManifest.version,
|
|
28
|
+
fallowDigests: { [BINARY_NAME]: options.digest ?? digest(binary) },
|
|
29
|
+
...options.manifest,
|
|
30
|
+
};
|
|
31
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest));
|
|
32
|
+
return {
|
|
33
|
+
artifact: {
|
|
34
|
+
packageName: PACKAGE_NAME,
|
|
35
|
+
packageVersion: ownManifest.version,
|
|
36
|
+
manifestPath,
|
|
37
|
+
binaryName: BINARY_NAME,
|
|
38
|
+
binaryPath,
|
|
39
|
+
},
|
|
40
|
+
manifestPath,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const acceptSignature = {
|
|
45
|
+
verify: () => true,
|
|
46
|
+
createPublicKey: () => ({}),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
test("verifies Ed25519 and the exact platform manifest digest before launch", (t) => {
|
|
50
|
+
const binary = Buffer.from("signed trusted binary");
|
|
51
|
+
const { privateKey, publicKey } = crypto.generateKeyPairSync("ed25519");
|
|
52
|
+
const { artifact } = makeFixture(t, {
|
|
53
|
+
binary,
|
|
54
|
+
signature: crypto.sign(null, binary, privateKey),
|
|
55
|
+
});
|
|
56
|
+
assert.deepEqual(verifyBinary(artifact, { createPublicKey: () => publicKey }), { ok: true });
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("rejects the legacy path-only call instead of falling back to signature-only", () => {
|
|
60
|
+
const result = verifyBinary("/native/fallow-similar-code", acceptSignature);
|
|
61
|
+
assert.equal(result.ok, false);
|
|
62
|
+
assert.equal(result.code, "artifact-invalid");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("rejects malformed and invalid detached signatures", (t) => {
|
|
66
|
+
const malformed = makeFixture(t, { signature: Buffer.alloc(63) });
|
|
67
|
+
assert.equal(verifyBinary(malformed.artifact).code, "sig-invalid");
|
|
68
|
+
|
|
69
|
+
const invalid = makeFixture(t);
|
|
70
|
+
assert.equal(
|
|
71
|
+
verifyBinary(invalid.artifact, {
|
|
72
|
+
verify: () => false,
|
|
73
|
+
createPublicKey: () => ({}),
|
|
74
|
+
}).code,
|
|
75
|
+
"sig-invalid",
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("fails closed when the embedded digest is missing or malformed", (t) => {
|
|
80
|
+
const absent = makeFixture(t, { manifest: { fallowDigests: undefined } });
|
|
81
|
+
assert.equal(verifyBinary(absent.artifact, acceptSignature).code, "digest-missing");
|
|
82
|
+
|
|
83
|
+
const missingEntry = makeFixture(t, { manifest: { fallowDigests: {} } });
|
|
84
|
+
assert.equal(verifyBinary(missingEntry.artifact, acceptSignature).code, "digest-missing");
|
|
85
|
+
|
|
86
|
+
const malformed = makeFixture(t, {
|
|
87
|
+
manifest: { fallowDigests: { [BINARY_NAME]: "not-a-digest" } },
|
|
88
|
+
});
|
|
89
|
+
assert.equal(verifyBinary(malformed.artifact, acceptSignature).code, "digest-invalid");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("rejects digest tampering even when signature verification succeeds", (t) => {
|
|
93
|
+
const binary = Buffer.from("authentically signed but stale digest");
|
|
94
|
+
const { privateKey, publicKey } = crypto.generateKeyPairSync("ed25519");
|
|
95
|
+
const fixture = makeFixture(t, {
|
|
96
|
+
binary,
|
|
97
|
+
signature: crypto.sign(null, binary, privateKey),
|
|
98
|
+
digest: `sha256:${"a".repeat(64)}`,
|
|
99
|
+
});
|
|
100
|
+
const result = verifyBinary(fixture.artifact, { createPublicKey: () => publicKey });
|
|
101
|
+
assert.equal(result.ok, false);
|
|
102
|
+
assert.equal(result.code, "digest-mismatch");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("rejects a manifest owned by another package or version", (t) => {
|
|
106
|
+
const wrongOwner = makeFixture(t, { manifest: { name: "@fallow-cli/other" } });
|
|
107
|
+
assert.equal(verifyBinary(wrongOwner.artifact, acceptSignature).code, "package-mismatch");
|
|
108
|
+
|
|
109
|
+
const wrongVersion = makeFixture(t, { manifest: { version: "0.0.0" } });
|
|
110
|
+
assert.equal(verifyBinary(wrongVersion.artifact, acceptSignature).code, "version-mismatch");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("rejects a digest from an adjacent manifest", (t) => {
|
|
114
|
+
const fixture = makeFixture(t);
|
|
115
|
+
const adjacent = fs.mkdtempSync(path.join(os.tmpdir(), "fallow-similar-code-adjacent-"));
|
|
116
|
+
t.after(() => fs.rmSync(adjacent, { recursive: true, force: true }));
|
|
117
|
+
const result = verifyBinary(
|
|
118
|
+
{ ...fixture.artifact, manifestPath: path.join(adjacent, "package.json") },
|
|
119
|
+
acceptSignature,
|
|
120
|
+
);
|
|
121
|
+
assert.equal(result.ok, false);
|
|
122
|
+
assert.equal(result.code, "package-mismatch");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("rejects a missing platform manifest", (t) => {
|
|
126
|
+
const fixture = makeFixture(t);
|
|
127
|
+
fs.rmSync(fixture.manifestPath);
|
|
128
|
+
assert.equal(verifyBinary(fixture.artifact, acceptSignature).code, "manifest-invalid");
|
|
129
|
+
});
|