dprint 0.53.2 → 0.55.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.
Files changed (3) hide show
  1. package/hashes.json +17 -0
  2. package/install_api.cjs +255 -27
  3. package/package.json +16 -12
package/hashes.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "win32-x64": "31f22db986f62258be1f42a9f3c3e334d9277f938e8948af1055f0308d7d6f41",
3
+ "win32-arm64": "f2ca5597b61fd91fccc0807014dbdbbb4913298d390c85746e4086b9e6573b20",
4
+ "darwin-x64": "f95a06d61508250fd1de20638143b3de13fadc313d45a2ce716925b1d1f96ed2",
5
+ "darwin-arm64": "67442fee500dbfc53d4e019ab18de929868e796ffe9c24df9c905001efe1d822",
6
+ "linux-x64-glibc": "8155ae8516e6dba1cb528efb9e636ab666b1dec7f67ba3548bed77634eeedbbc",
7
+ "linux-x64-musl": "eac4737c1a7b2cab9e600ff66b80aa657a8185a3dc5b6bc7b11d82c4268932a0",
8
+ "linux-arm64-glibc": "8f8edab6a515a05af423b324c4dd2b862ad81146f4e9c932190a6c07245d16ca",
9
+ "linux-arm64-musl": "655e4d16a6bdfb19249c1ae2442b1ff7f654c6711280220b0ec2b98e351e15e0",
10
+ "linux-riscv64-glibc": "45fcb3902b3cb0b49a64130f478c67d613350edaba17cd6afa47e51b6fa86ef4",
11
+ "linux-loong64-glibc": "84b7a8e0a666a8dc429a57a3716c50648c8c807495dec129619e959a91c9e10e",
12
+ "linux-loong64-musl": "12f2b2c44787174fee2c23a9f92970a40217e4d11f275349b67ad00814425c3f",
13
+ "linux-ppc64-glibc": "b56ad03b133c2a8859116fcaeba60363403d31bc3017dc2ec0b829a162f8a65b",
14
+ "linux-ppc64-musl": "f114533153aa3d4ff4c731d13cddd19f0ef5384305f17c0b560ff895206500b0",
15
+ "android-arm64": "6b43057475766b02af46c94a5a3279175a4f2927ace088dfdccc7d148073ccb5",
16
+ "android-x64": "7829ce89f50afb15e720e939aed58d3c8f2a0dbdb3c6e84054afda1bf0127280"
17
+ }
package/install_api.cjs CHANGED
@@ -4,7 +4,7 @@
4
4
  const fs = require("fs");
5
5
  const os = require("os");
6
6
  const path = require("path");
7
- /** @type {string | undefined} */
7
+ /** @type {boolean | undefined} */
8
8
  let cachedIsMusl = undefined;
9
9
 
10
10
  module.exports = {
@@ -21,11 +21,18 @@ module.exports = {
21
21
  }
22
22
 
23
23
  const target = getTarget();
24
- const sourcePackagePath = path.dirname(require.resolve("@dprint/" + target + "/package.json"));
25
- const sourceExecutablePath = path.join(sourcePackagePath, dprintFileName);
24
+ const sourceExecutablePath = resolveSourceExecutablePath(target, dprintFileName);
26
25
 
27
- if (!fs.existsSync(sourceExecutablePath)) {
28
- throw new Error("Could not find executable for @dprint/" + target + " at " + sourceExecutablePath);
26
+ if (sourceExecutablePath == null) {
27
+ // the @dprint/<target> optional dependency isn't installed (for example
28
+ // the user ran `npm install --omit=optional`), so download the binary
29
+ // directly from the registry like esbuild does
30
+ downloadExecutable(target, dprintFileName, targetExecutablePath);
31
+ if (os.platform() !== "win32") {
32
+ // chmod +x
33
+ chmodX(targetExecutablePath);
34
+ }
35
+ return targetExecutablePath;
29
36
  }
30
37
 
31
38
  try {
@@ -59,6 +66,200 @@ module.exports = {
59
66
  },
60
67
  };
61
68
 
69
+ /**
70
+ * Resolves the path to the executable provided by the @dprint/<target>
71
+ * optional dependency, or undefined when that package isn't installed.
72
+ * @param target {string}
73
+ * @param dprintFileName {string}
74
+ * @returns {string | undefined}
75
+ */
76
+ function resolveSourceExecutablePath(target, dprintFileName) {
77
+ let sourcePackagePath;
78
+ try {
79
+ sourcePackagePath = path.dirname(require.resolve("@dprint/" + target + "/package.json"));
80
+ } catch {
81
+ // the optional dependency wasn't installed
82
+ return undefined;
83
+ }
84
+ const sourceExecutablePath = path.join(sourcePackagePath, dprintFileName);
85
+ return fs.existsSync(sourceExecutablePath) ? sourceExecutablePath : undefined;
86
+ }
87
+
88
+ /**
89
+ * Downloads the binary tarball for the target from the registry and extracts
90
+ * the executable to the destination path.
91
+ * @param target {string}
92
+ * @param dprintFileName {string}
93
+ * @param destinationPath {string}
94
+ */
95
+ function downloadExecutable(target, dprintFileName, destinationPath) {
96
+ const version = require("./package.json").version;
97
+ const registry = (process.env.npm_config_registry || "https://registry.npmjs.org")
98
+ .replace(/\/+$/, "");
99
+ // npm tarball urls drop the scope from the file name (e.g.
100
+ // https://registry.npmjs.org/@dprint/win32-x64/-/win32-x64-1.0.0.tgz)
101
+ const tarballUrl = registry + "/@dprint/" + target + "/-/" + target + "-" + version + ".tgz";
102
+ console.error("[dprint] Optional dependency @dprint/" + target + " was not installed. Downloading from " + tarballUrl);
103
+ const tarballBuffer = downloadBufferSync(tarballUrl);
104
+ // files inside an npm tarball live under the "package/" directory
105
+ const executableBuffer = extractFileFromTarGzip(tarballBuffer, "package/" + dprintFileName);
106
+ verifyExecutableHash(target, executableBuffer);
107
+ atomicWriteFile(destinationPath, executableBuffer);
108
+ }
109
+
110
+ /**
111
+ * Verifies the downloaded executable against the hash recorded at build time
112
+ * so that we only ever run the exact binary that was published.
113
+ * @param target {string}
114
+ * @param buffer {Buffer}
115
+ */
116
+ function verifyExecutableHash(target, buffer) {
117
+ let hashes;
118
+ try {
119
+ hashes = require("./hashes.json");
120
+ } catch (err) {
121
+ throw new Error("Could not load hashes.json to verify the downloaded binary: " + (err && err.message || err));
122
+ }
123
+ const expected = hashes[target];
124
+ if (typeof expected !== "string") {
125
+ throw new Error("No known hash for @dprint/" + target + " to verify the download against.");
126
+ }
127
+ const actual = require("crypto").createHash("sha256").update(buffer).digest("hex");
128
+ if (actual !== expected) {
129
+ throw new Error(
130
+ "Integrity check failed for the downloaded @dprint/" + target + " binary.\n"
131
+ + " Expected sha256: " + expected + "\n"
132
+ + " Actual sha256: " + actual,
133
+ );
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Downloads a url to a buffer synchronously. Prefer curl since it transparently
139
+ * supports proxies (http_proxy/https_proxy/no_proxy and npm's proxy config),
140
+ * redirects and TLS, then fall back to node for environments without curl.
141
+ * @param url {string}
142
+ * @returns {Buffer}
143
+ */
144
+ function downloadBufferSync(url) {
145
+ const fromCurl = downloadBufferWithCurl(url);
146
+ return fromCurl != null ? fromCurl : downloadBufferWithNode(url);
147
+ }
148
+
149
+ /**
150
+ * @param url {string}
151
+ * @returns {Buffer | undefined} undefined when curl isn't available
152
+ */
153
+ function downloadBufferWithCurl(url) {
154
+ // -f: fail on http errors, -s: silent, -S: still show errors, -L: follow redirects
155
+ const args = ["-fsSL"];
156
+ const proxy = process.env.npm_config_https_proxy
157
+ || process.env.npm_config_proxy
158
+ || process.env.HTTPS_PROXY
159
+ || process.env.https_proxy;
160
+ if (proxy) {
161
+ args.push("--proxy", proxy);
162
+ }
163
+ const noProxy = process.env.npm_config_noproxy || process.env.NO_PROXY || process.env.no_proxy;
164
+ if (noProxy) {
165
+ args.push("--noproxy", noProxy);
166
+ }
167
+ args.push("-o", "-", url);
168
+ try {
169
+ return require("child_process").execFileSync("curl", args, { maxBuffer: 512 * 1024 * 1024 });
170
+ } catch (err) {
171
+ if (err && err.code === "ENOENT") {
172
+ // curl isn't installed, fall back to node
173
+ return undefined;
174
+ }
175
+ throw err;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Node has no synchronous https client, so spawn a child node process that
181
+ * streams the response to stdout. Does not support proxies.
182
+ * @param url {string}
183
+ * @returns {Buffer}
184
+ */
185
+ function downloadBufferWithNode(url) {
186
+ const script = "const https=require('https');"
187
+ + "function f(u){https.get(u,r=>{"
188
+ + "const s=r.statusCode;"
189
+ + "if((s===301||s===302||s===303||s===307||s===308)&&r.headers.location){f(new URL(r.headers.location,u).toString());return;}"
190
+ + "if(s!==200){console.error('[dprint] Unexpected status code '+s+' downloading '+u);process.exit(1);}"
191
+ + "r.pipe(process.stdout);"
192
+ + "}).on('error',e=>{console.error(String(e&&e.message||e));process.exit(1);});}"
193
+ + "f(process.argv[1]);";
194
+ return require("child_process").execFileSync(
195
+ process.execPath,
196
+ ["-e", script, url],
197
+ { maxBuffer: 512 * 1024 * 1024 },
198
+ );
199
+ }
200
+
201
+ /**
202
+ * Extracts a single file from a gzipped tarball buffer.
203
+ * @param buffer {Buffer}
204
+ * @param subpath {string}
205
+ * @returns {Buffer}
206
+ */
207
+ function extractFileFromTarGzip(buffer, subpath) {
208
+ const zlib = require("zlib");
209
+ let tar;
210
+ try {
211
+ tar = zlib.gunzipSync(buffer);
212
+ } catch (err) {
213
+ throw new Error("Invalid gzip data in downloaded tarball: " + (err && err.message || err));
214
+ }
215
+ let offset = 0;
216
+ while (offset < tar.length) {
217
+ const name = readTarString(tar, offset, 100);
218
+ const size = parseInt(readTarString(tar, offset + 124, 12).trim(), 8);
219
+ offset += 512;
220
+ if (!isNaN(size)) {
221
+ if (name === subpath) {
222
+ return tar.subarray(offset, offset + size);
223
+ }
224
+ // entries are padded to 512 byte boundaries
225
+ offset += (size + 511) & ~511;
226
+ }
227
+ }
228
+ throw new Error("Could not find " + JSON.stringify(subpath) + " in downloaded tarball");
229
+ }
230
+
231
+ /**
232
+ * @param buffer {Buffer}
233
+ * @param offset {number}
234
+ * @param length {number}
235
+ */
236
+ function readTarString(buffer, offset, length) {
237
+ return buffer.toString("utf8", offset, offset + length).replace(/\0.*$/, "");
238
+ }
239
+
240
+ /**
241
+ * @param destinationPath {string}
242
+ * @param buffer {Buffer}
243
+ */
244
+ function atomicWriteFile(destinationPath, buffer) {
245
+ const crypto = require("crypto");
246
+ const rand = crypto.randomBytes(4).toString("hex");
247
+ const tempFilePath = destinationPath + "." + rand;
248
+ fs.writeFileSync(tempFilePath, buffer);
249
+ try {
250
+ fs.renameSync(tempFilePath, destinationPath);
251
+ } catch (err) {
252
+ // will maybe throw when another process had already done this
253
+ // so just ignore and delete the created temporary file
254
+ try {
255
+ fs.unlinkSync(tempFilePath);
256
+ } catch (_err2) {
257
+ // ignore
258
+ }
259
+ throw err;
260
+ }
261
+ }
262
+
62
263
  /** @filePath {string} */
63
264
  function chmodX(filePath) {
64
265
  const fd = fs.openSync(filePath, "r");
@@ -81,8 +282,8 @@ function getTarget() {
81
282
 
82
283
  function getArch() {
83
284
  const arch = os.arch();
84
- if (arch !== "arm64" && arch !== "x64" && arch !== "riscv64" && arch !== "loong64") {
85
- throw new Error("Unsupported architecture " + os.arch() + ". Only x64, aarch64, riscv64 and loong64 binaries are available.");
285
+ if (arch !== "arm64" && arch !== "x64" && arch !== "riscv64" && arch !== "loong64" && arch !== "ppc64") {
286
+ throw new Error("Unsupported architecture " + os.arch() + ". Only x64, arm64, riscv64, loong64 and ppc64 binaries are available.");
86
287
  }
87
288
  return arch;
88
289
  }
@@ -91,8 +292,8 @@ function getLinuxFamily() {
91
292
  return getIsMusl() ? "musl" : "glibc";
92
293
 
93
294
  function getIsMusl() {
94
- // code adapted from https://github.com/lovell/detect-libc
95
- // Copyright Apache 2.0 license, the detect-libc maintainers
295
+ // code adapted from https://github.com/napi-rs/package-template/blob/main/index.js
296
+ // which is in turn based on https://github.com/lovell/detect-libc (Apache 2.0 license)
96
297
  if (cachedIsMusl == null) {
97
298
  cachedIsMusl = innerGet();
98
299
  }
@@ -103,7 +304,14 @@ function getLinuxFamily() {
103
304
  if (os.platform() !== "linux") {
104
305
  return false;
105
306
  }
106
- return isProcessReportMusl() || isConfMusl();
307
+ let musl = isMuslFromFilesystem();
308
+ if (musl == null) {
309
+ musl = isMuslFromReport();
310
+ }
311
+ if (musl == null) {
312
+ musl = isMuslFromChildProcess();
313
+ }
314
+ return musl;
107
315
  } catch (err) {
108
316
  // just in case
109
317
  console.warn("Error checking if musl.", err);
@@ -111,32 +319,52 @@ function getLinuxFamily() {
111
319
  }
112
320
  }
113
321
 
114
- function isProcessReportMusl() {
115
- if (!process.report) {
116
- return false;
322
+ function isMuslFromFilesystem() {
323
+ try {
324
+ return fs.readFileSync("/usr/bin/ldd", "utf-8").includes("musl");
325
+ } catch {
326
+ return null;
327
+ }
328
+ }
329
+
330
+ function isMuslFromReport() {
331
+ if (typeof process.report?.getReport !== "function") {
332
+ return null;
333
+ }
334
+ // excludeNetwork avoids a slow reverse DNS lookup while generating the
335
+ // report, but it's a global flag so restore it once we're done
336
+ const originalExcludeNetwork = process.report.excludeNetwork;
337
+ let rawReport;
338
+ try {
339
+ process.report.excludeNetwork = true;
340
+ rawReport = process.report.getReport();
341
+ } finally {
342
+ process.report.excludeNetwork = originalExcludeNetwork;
117
343
  }
118
- const rawReport = process.report.getReport();
119
344
  const report = typeof rawReport === "string" ? JSON.parse(rawReport) : rawReport;
120
- if (!report || !(report.sharedObjects instanceof Array)) {
345
+ if (!report) {
346
+ return null;
347
+ }
348
+ if (report.header && report.header.glibcVersionRuntime) {
121
349
  return false;
122
350
  }
123
- return report.sharedObjects.some(o => o.includes("libc.musl-") || o.includes("ld-musl-"));
124
- }
125
-
126
- function isConfMusl() {
127
- const output = getCommandOutput();
128
- const [_, ldd1] = output.split(/[\r\n]+/);
129
- return ldd1 && ldd1.includes("musl");
351
+ if (Array.isArray(report.sharedObjects)) {
352
+ return report.sharedObjects.some(isFileMusl);
353
+ }
354
+ return false;
130
355
  }
131
356
 
132
- function getCommandOutput() {
357
+ function isMuslFromChildProcess() {
133
358
  try {
134
- const command = "getconf GNU_LIBC_VERSION 2>&1 || true; ldd --version 2>&1 || true";
135
- return require("child_process").execSync(command, { encoding: "utf8" });
136
- } catch (_err) {
137
- return "";
359
+ return require("child_process").execSync("ldd --version", { encoding: "utf8" }).includes("musl");
360
+ } catch {
361
+ return false;
138
362
  }
139
363
  }
364
+
365
+ function isFileMusl(f) {
366
+ return f.includes("libc.musl-") || f.includes("ld-musl-");
367
+ }
140
368
  }
141
369
  }
142
370
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dprint",
3
- "version": "0.53.2",
3
+ "version": "0.55.0",
4
4
  "description": "Pluggable and configurable code formatting platform written in Rust.",
5
5
  "bin": "bin.cjs",
6
6
  "repository": {
@@ -22,16 +22,20 @@
22
22
  "postinstall": "node ./install.cjs"
23
23
  },
24
24
  "optionalDependencies": {
25
- "@dprint/win32-x64": "0.53.2",
26
- "@dprint/win32-arm64": "0.53.2",
27
- "@dprint/darwin-x64": "0.53.2",
28
- "@dprint/darwin-arm64": "0.53.2",
29
- "@dprint/linux-x64-glibc": "0.53.2",
30
- "@dprint/linux-x64-musl": "0.53.2",
31
- "@dprint/linux-arm64-glibc": "0.53.2",
32
- "@dprint/linux-arm64-musl": "0.53.2",
33
- "@dprint/linux-riscv64-glibc": "0.53.2",
34
- "@dprint/linux-loong64-glibc": "0.53.2",
35
- "@dprint/linux-loong64-musl": "0.53.2"
25
+ "@dprint/win32-x64": "0.55.0",
26
+ "@dprint/win32-arm64": "0.55.0",
27
+ "@dprint/darwin-x64": "0.55.0",
28
+ "@dprint/darwin-arm64": "0.55.0",
29
+ "@dprint/linux-x64-glibc": "0.55.0",
30
+ "@dprint/linux-x64-musl": "0.55.0",
31
+ "@dprint/linux-arm64-glibc": "0.55.0",
32
+ "@dprint/linux-arm64-musl": "0.55.0",
33
+ "@dprint/linux-riscv64-glibc": "0.55.0",
34
+ "@dprint/linux-loong64-glibc": "0.55.0",
35
+ "@dprint/linux-loong64-musl": "0.55.0",
36
+ "@dprint/linux-ppc64-glibc": "0.55.0",
37
+ "@dprint/linux-ppc64-musl": "0.55.0",
38
+ "@dprint/android-arm64": "0.55.0",
39
+ "@dprint/android-x64": "0.55.0"
36
40
  }
37
41
  }