devlanes 0.1.0 → 0.3.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/README.md CHANGED
@@ -36,11 +36,13 @@ The package ships the JSON Schemas of the config files, exported as `devlanes/sc
36
36
  # yaml-language-server: $schema=./node_modules/devlanes/schema.json
37
37
  ```
38
38
 
39
- yaml-language-server resolves the path from the YAML file's directory, so it fits a `devlanes.yaml` next to `node_modules`.
39
+ yaml-language-server resolves the path from the YAML file's directory, so it fits a `devlanes.yaml` next to `node_modules`. This copy matches the installed version. Without the package in the project, use `https://devlanes.run/v1/schema.json` and `https://devlanes.run/v1/user-schema.json`, which follow the latest stable release.
40
40
 
41
41
  ## Behaviour
42
42
 
43
- This package holds a small launcher. npm installs the native binary from one of the `@devlanes/<platform>-<arch>` packages, picked by `os`, `cpu` and `libc`. Builds exist for macOS arm64 and x64 and for Linux arm64 and x64 with glibc.
43
+ npm installs the native binary from one of the `@devlanes/<platform>-<arch>` packages, picked by `os`, `cpu` and `libc`. Builds exist for macOS arm64 and x64 and for Linux arm64 and x64 with glibc.
44
+
45
+ The postinstall script replaces this package's `bin/devlanes` with that binary, so `devlanes` runs without starting Node. When the script does not run (`--ignore-scripts`, Yarn) or fails, `bin/devlanes` stays a small Node launcher that starts the binary, and everything works the same. npm 12, pnpm and Bun skip install scripts of dependencies unless allowed: `npm install -g --allow-scripts=devlanes devlanes`, `pnpm approve-builds` or `bun pm trust devlanes`.
44
46
 
45
47
  ## License
46
48
 
@@ -2,7 +2,9 @@
2
2
  "use strict";
3
3
 
4
4
  // Runs the native binary from the @devlanes/<platform>-<arch> package that npm
5
- // installed as an optional dependency.
5
+ // installed as an optional dependency. install.js replaces this file with that
6
+ // binary, so it only runs when the postinstall script did not (--ignore-scripts,
7
+ // Yarn) or could not swap it.
6
8
  const { spawn } = require("node:child_process");
7
9
  const path = require("node:path");
8
10
 
package/install.js ADDED
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+
3
+ // Postinstall: replaces the Node launcher at bin/devlanes with the native binary
4
+ // from @devlanes/<platform>-<arch>, so the package manager's bin link runs it
5
+ // without starting Node. Any failure leaves the launcher in place and never fails
6
+ // the install.
7
+ const { execFileSync } = require("node:child_process");
8
+ const fs = require("node:fs");
9
+ const path = require("node:path");
10
+
11
+ const { version } = require("./package.json");
12
+ const binDir = path.join(__dirname, "bin");
13
+
14
+ // Yarn PnP keeps packages in zip archives. Yarn may also rerun install scripts in
15
+ // a node_modules shared between platforms (for example a mounted Docker volume),
16
+ // and a swapped binary would then be the wrong one, so Yarn keeps the launcher.
17
+ function skipped() {
18
+ return (
19
+ process.platform === "win32" ||
20
+ Boolean(process.versions.pnp) ||
21
+ /\byarn\//.test(process.env.npm_config_user_agent || "")
22
+ );
23
+ }
24
+
25
+ /** Fails unless `file args` prints `<name> <version>` as its first two words. */
26
+ function checkVersion(file, args, name) {
27
+ const output = execFileSync(file, args, { stdio: "pipe", timeout: 30_000 }).toString();
28
+ const [reportedName, reportedVersion] = output.trim().split(/\s+/);
29
+ if (reportedName !== name || reportedVersion !== version) {
30
+ throw new Error(`${name} reported ${JSON.stringify(output.trim())}, expected ${version}`);
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Puts `source` at bin/<name>: a hard link (else a copy) to a temporary name in
36
+ * the same directory, checked by running it, then renamed over the target. The
37
+ * rename never writes into an existing file, which pnpm may share with its store.
38
+ */
39
+ function place(source, name, args) {
40
+ const target = path.join(binDir, name);
41
+ const temp = path.join(binDir, `.${name}.${process.pid}.tmp`);
42
+ fs.rmSync(temp, { force: true });
43
+ try {
44
+ try {
45
+ fs.linkSync(source, temp);
46
+ } catch {
47
+ fs.copyFileSync(source, temp);
48
+ fs.chmodSync(temp, 0o755);
49
+ }
50
+ checkVersion(temp, args, name);
51
+ fs.renameSync(temp, target);
52
+ } finally {
53
+ // A rerun links the file that is already the target; rename then succeeds
54
+ // without removing the temporary name.
55
+ fs.rmSync(temp, { force: true });
56
+ }
57
+ }
58
+
59
+ function main() {
60
+ if (skipped()) return;
61
+ const platform = `${process.platform}-${process.arch}`;
62
+ const manifest = require.resolve(`@devlanes/${platform}/package.json`);
63
+ const sourceDir = path.join(path.dirname(manifest), "bin");
64
+ // `devlanes certs trust --hosts` looks for devlanes-hosts next to the resolved
65
+ // devlanes executable, so the helper moves first and the binary only with it.
66
+ place(path.join(sourceDir, "devlanes-hosts"), "devlanes-hosts", ["version"]);
67
+ place(path.join(sourceDir, "devlanes"), "devlanes", ["--version"]);
68
+ }
69
+
70
+ try {
71
+ main();
72
+ } catch (error) {
73
+ const reason = String(error && error.message ? error.message : error).split("\n")[0];
74
+ console.error(`devlanes: keeping the Node launcher: ${reason}`);
75
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devlanes",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Development environments in Git worktree lanes",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://devlanes.run",
@@ -10,10 +10,14 @@
10
10
  "directory": "packaging/npm/devlanes"
11
11
  },
12
12
  "bin": {
13
- "devlanes": "bin/devlanes.js"
13
+ "devlanes": "bin/devlanes"
14
+ },
15
+ "scripts": {
16
+ "postinstall": "node install.js"
14
17
  },
15
18
  "files": [
16
19
  "bin",
20
+ "install.js",
17
21
  "schema.json",
18
22
  "user-schema.json"
19
23
  ],
@@ -26,9 +30,9 @@
26
30
  "node": ">=18"
27
31
  },
28
32
  "optionalDependencies": {
29
- "@devlanes/darwin-arm64": "0.1.0",
30
- "@devlanes/darwin-x64": "0.1.0",
31
- "@devlanes/linux-arm64": "0.1.0",
32
- "@devlanes/linux-x64": "0.1.0"
33
+ "@devlanes/darwin-arm64": "0.3.0",
34
+ "@devlanes/darwin-x64": "0.3.0",
35
+ "@devlanes/linux-arm64": "0.3.0",
36
+ "@devlanes/linux-x64": "0.3.0"
33
37
  }
34
38
  }