no-mistakes 0.6.0 → 0.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "no-mistakes",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Static codebase analysis tools for TS/JS dependencies, dependents, and symbols",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -23,8 +23,5 @@
23
23
  },
24
24
  "scripts": {
25
25
  "postinstall": "node scripts/install.js"
26
- },
27
- "dependencies": {
28
- "no-mistakes-core": "0.6.0"
29
26
  }
30
27
  }
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ const { basename } = require("node:path");
4
+
5
+ function assetName(binName, version, target) {
6
+ const ext = target.endsWith("windows-msvc") ? ".exe" : "";
7
+ return `${binName}-v${version}-${target}${ext}`;
8
+ }
9
+
10
+ function releaseBaseUrl(repository, version, envVar) {
11
+ return (
12
+ (envVar && process.env[envVar]) ||
13
+ `https://github.com/${repository}/releases/download/v${version}`
14
+ );
15
+ }
16
+
17
+ function parseChecksum(text, expectedAsset) {
18
+ for (const line of text.split(/\r?\n/)) {
19
+ const trimmed = line.trim();
20
+ if (!trimmed) {
21
+ continue;
22
+ }
23
+ const [hash, file] = trimmed.split(/\s+/, 2);
24
+ if (!/^[a-fA-F0-9]{64}$/.test(hash)) {
25
+ continue;
26
+ }
27
+ const normalizedFile = file?.replace(/^\*/, "");
28
+ if (!file || normalizedFile === expectedAsset || basename(normalizedFile) === expectedAsset) {
29
+ return hash.toLowerCase();
30
+ }
31
+ }
32
+ throw new Error(`No SHA-256 checksum found for ${expectedAsset}`);
33
+ }
34
+
35
+ module.exports = {
36
+ assetName,
37
+ parseChecksum,
38
+ releaseBaseUrl,
39
+ };
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+
3
+ const { copyFile, readFile } = require("node:fs/promises");
4
+ const { createWriteStream } = require("node:fs");
5
+ const http = require("node:http");
6
+ const https = require("node:https");
7
+ const { pipeline } = require("node:stream/promises");
8
+ const { fileURLToPath } = require("node:url");
9
+
10
+ const DOWNLOAD_TIMEOUT_MS = 30_000;
11
+
12
+ function download(url, destination, redirects = 0) {
13
+ if (url.startsWith("file://")) {
14
+ return copyFile(fileURLToPath(url), destination);
15
+ }
16
+
17
+ return request(
18
+ url,
19
+ async (response) => {
20
+ await pipeline(response, createWriteStream(destination));
21
+ },
22
+ redirects,
23
+ );
24
+ }
25
+
26
+ function request(
27
+ url,
28
+ handleResponse,
29
+ redirects = 0,
30
+ clients = { http, https },
31
+ timeoutMs = DOWNLOAD_TIMEOUT_MS,
32
+ ) {
33
+ return new Promise((resolve, reject) => {
34
+ const client = url.startsWith("http://") ? clients.http : clients.https;
35
+ const req = client.get(url, (response) => {
36
+ if (isRedirectStatus(response.statusCode)) {
37
+ response.resume();
38
+ if (redirects >= 5) {
39
+ reject(new Error(`Too many redirects while downloading ${url}`));
40
+ return;
41
+ }
42
+ if (!response.headers.location) {
43
+ reject(new Error(`Redirect missing Location header while downloading ${url}`));
44
+ return;
45
+ }
46
+ request(
47
+ new URL(response.headers.location, url).toString(),
48
+ handleResponse,
49
+ redirects + 1,
50
+ clients,
51
+ timeoutMs,
52
+ ).then(resolve, reject);
53
+ return;
54
+ }
55
+
56
+ if (response.statusCode !== 200) {
57
+ response.resume();
58
+ reject(new Error(`Download failed for ${url}: HTTP ${response.statusCode}`));
59
+ return;
60
+ }
61
+
62
+ Promise.resolve(handleResponse(response)).then(resolve, reject);
63
+ });
64
+
65
+ req.setTimeout(timeoutMs, () => {
66
+ req.destroy(new Error(`Download timed out after ${timeoutMs}ms: ${url}`));
67
+ });
68
+ req.on("error", reject);
69
+ });
70
+ }
71
+
72
+ function isRedirectStatus(statusCode) {
73
+ return [301, 302, 303, 307, 308].includes(statusCode);
74
+ }
75
+
76
+ async function fetchText(url) {
77
+ if (url.startsWith("file://")) {
78
+ return readFile(fileURLToPath(url), "utf8");
79
+ }
80
+ const chunks = [];
81
+ let totalLength = 0;
82
+ const MAX_LENGTH = 1024 * 1024;
83
+ await request(url, async (response) => {
84
+ for await (const chunk of response) {
85
+ totalLength += chunk.length;
86
+ if (totalLength > MAX_LENGTH) {
87
+ throw new Error(`Response exceeded maximum size of ${MAX_LENGTH} bytes`);
88
+ }
89
+ chunks.push(chunk);
90
+ }
91
+ });
92
+ return Buffer.concat(chunks).toString("utf8");
93
+ }
94
+
95
+ module.exports = {
96
+ download,
97
+ fetchText,
98
+ isRedirectStatus,
99
+ request,
100
+ };
@@ -1,3 +1,13 @@
1
1
  "use strict";
2
2
 
3
- module.exports = require("no-mistakes-core");
3
+ const assets = require("./assets");
4
+ const download = require("./download");
5
+ const installer = require("./installer");
6
+ const platform = require("./platform");
7
+
8
+ module.exports = {
9
+ ...assets,
10
+ ...download,
11
+ ...installer,
12
+ ...platform,
13
+ };
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+
3
+ const { createHash } = require("node:crypto");
4
+ const { closeSync, createReadStream, existsSync, openSync, readSync } = require("node:fs");
5
+ const { chmod, mkdir, rename, rm } = require("node:fs/promises");
6
+ const { join } = require("node:path");
7
+
8
+ const { assetName, parseChecksum, releaseBaseUrl } = require("./assets");
9
+ const { download, fetchText } = require("./download");
10
+ const { platformTarget, supportedGlibc } = require("./platform");
11
+
12
+ const PLACEHOLDER_TEXT = "Native binary placeholder.";
13
+ const PLACEHOLDER_READ_BYTES = 256;
14
+
15
+ async function sha256(path) {
16
+ const hash = createHash("sha256");
17
+ for await (const chunk of createReadStream(path)) {
18
+ hash.update(chunk);
19
+ }
20
+ return hash.digest("hex");
21
+ }
22
+
23
+ async function install(binName, repository, options = {}) {
24
+ const version = options.version;
25
+ if (!version) {
26
+ throw new Error("version is required for install()");
27
+ }
28
+ const target = Object.hasOwn(options, "target") ? options.target : platformTarget();
29
+ if (!target) {
30
+ throw new Error(unsupportedPlatformMessage(binName));
31
+ }
32
+
33
+ const vendorDir = options.vendorDir;
34
+ if (!vendorDir) {
35
+ throw new Error("vendorDir is required for install()");
36
+ }
37
+ const executable = options.destinationName || binName;
38
+ const destination = join(vendorDir, executable);
39
+ const destinationExists = existsSync(destination);
40
+ const destinationIsPlaceholder = destinationExists && isPlaceholder(destination);
41
+
42
+ if (process.env.SKIP_BINARY_DOWNLOAD) {
43
+ if (destinationExists && !destinationIsPlaceholder) {
44
+ return destination;
45
+ }
46
+ throw new Error(
47
+ `SKIP_BINARY_DOWNLOAD is set but ${destination} is missing or still a placeholder; unset SKIP_BINARY_DOWNLOAD to install ${binName}.`,
48
+ );
49
+ }
50
+
51
+ if (options.checkExisting && destinationExists && !destinationIsPlaceholder) {
52
+ return destination;
53
+ }
54
+
55
+ const asset = assetName(binName, version, target);
56
+ const baseUrl = options.baseUrl || releaseBaseUrl(repository, version, options.envVar);
57
+ const temp = `${destination}.tmp-${process.pid}`;
58
+
59
+ await mkdir(vendorDir, { recursive: true });
60
+
61
+ try {
62
+ console.log(`Downloading ${binName} v${version} for ${target}...`);
63
+ await download(`${baseUrl}/${asset}`, temp);
64
+
65
+ let checksumText;
66
+ try {
67
+ checksumText = await fetchText(`${baseUrl}/${asset}.sha256`);
68
+ } catch (e) {
69
+ throw new Error(`Failed to fetch checksum for ${asset}: ${e.message}`);
70
+ }
71
+
72
+ const expected = parseChecksum(checksumText, asset);
73
+ const actual = await sha256(temp);
74
+ if (actual !== expected) {
75
+ throw new Error(`Checksum mismatch for ${asset}: expected ${expected}, got ${actual}`);
76
+ }
77
+ if (!target.endsWith("windows-msvc")) {
78
+ await chmod(temp, 0o755);
79
+ }
80
+ await rename(temp, destination);
81
+ return destination;
82
+ } catch (error) {
83
+ await rm(temp, { force: true });
84
+ throw new Error(`Failed to install ${binName}: ${error.message}`);
85
+ }
86
+ }
87
+
88
+ function isPlaceholder(path) {
89
+ let fd;
90
+ try {
91
+ fd = openSync(path, "r");
92
+ const buffer = Buffer.alloc(PLACEHOLDER_READ_BYTES);
93
+ const bytesRead = readSync(fd, buffer, 0, buffer.length, 0);
94
+ return buffer.subarray(0, bytesRead).toString("utf8").includes(PLACEHOLDER_TEXT);
95
+ } catch (error) {
96
+ if (error.code === "ENOENT") {
97
+ return false;
98
+ }
99
+ throw new Error(`Failed to inspect native binary placeholder ${path}: ${error.message}`);
100
+ } finally {
101
+ if (fd !== undefined) {
102
+ closeSync(fd);
103
+ }
104
+ }
105
+ }
106
+
107
+ function unsupportedPlatformMessage(
108
+ binName,
109
+ platform = process.platform,
110
+ arch = process.arch,
111
+ report = process.report,
112
+ ) {
113
+ if (platform === "linux" && (arch === "x64" || arch === "arm64") && !supportedGlibc(report)) {
114
+ return `Linux npm installs require glibc 2.35 or newer. Install with \`cargo install ${binName}\` instead.`;
115
+ }
116
+ return `Unsupported platform ${platform}/${arch}. Install with \`cargo install ${binName}\` instead.`;
117
+ }
118
+
119
+ module.exports = {
120
+ install,
121
+ isPlaceholder,
122
+ sha256,
123
+ unsupportedPlatformMessage,
124
+ };
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+
3
+ const MIN_GLIBC = [2, 35];
4
+
5
+ function platformTarget(platform = process.platform, arch = process.arch, report = process.report) {
6
+ if (platform === "darwin" && arch === "x64") {
7
+ return "x86_64-apple-darwin";
8
+ }
9
+ if (platform === "darwin" && arch === "arm64") {
10
+ return "aarch64-apple-darwin";
11
+ }
12
+ if (platform === "win32" && arch === "x64") {
13
+ return "x86_64-pc-windows-msvc";
14
+ }
15
+ if (platform === "linux" && (arch === "x64" || arch === "arm64")) {
16
+ if (!supportedGlibc(report)) {
17
+ return null;
18
+ }
19
+ return arch === "x64" ? "x86_64-unknown-linux-gnu" : "aarch64-unknown-linux-gnu";
20
+ }
21
+ return null;
22
+ }
23
+
24
+ function isGlibc(report = process.report) {
25
+ return glibcVersion(report) !== null;
26
+ }
27
+
28
+ function glibcVersion(report = process.report) {
29
+ const header = typeof report?.getReport === "function" ? report.getReport().header : {};
30
+ return header?.glibcVersionRuntime || header?.glibcVersionCompiler || null;
31
+ }
32
+
33
+ function supportedGlibc(report = process.report) {
34
+ const version = glibcVersion(report);
35
+ if (!version) {
36
+ return false;
37
+ }
38
+ const [major, minor] = version.split(".").map((part) => Number.parseInt(part, 10));
39
+ if (!Number.isInteger(major) || !Number.isInteger(minor)) {
40
+ return false;
41
+ }
42
+ return major > MIN_GLIBC[0] || (major === MIN_GLIBC[0] && minor >= MIN_GLIBC[1]);
43
+ }
44
+
45
+ module.exports = {
46
+ glibcVersion,
47
+ isGlibc,
48
+ platformTarget,
49
+ supportedGlibc,
50
+ };