devlanes 0.2.0 → 0.4.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 +3 -1
- package/bin/devlanes +57 -0
- package/install.js +75 -0
- package/package.json +12 -6
- package/schema.json +993 -942
- package/user-schema.json +528 -389
- package/bin/devlanes.js +0 -44
package/README.md
CHANGED
|
@@ -40,7 +40,9 @@ yaml-language-server resolves the path from the YAML file's directory, so it fit
|
|
|
40
40
|
|
|
41
41
|
## Behaviour
|
|
42
42
|
|
|
43
|
-
|
|
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
|
|
package/bin/devlanes
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// Runs the native binary from the @devlanes/<platform>-<arch> package that npm
|
|
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, Windows) or could not swap it.
|
|
8
|
+
const { spawn } = require("node:child_process");
|
|
9
|
+
const path = require("node:path");
|
|
10
|
+
|
|
11
|
+
const windows = process.platform === "win32";
|
|
12
|
+
const platform = `${process.platform}-${process.arch}`;
|
|
13
|
+
let binary;
|
|
14
|
+
try {
|
|
15
|
+
const manifest = require.resolve(`@devlanes/${platform}/package.json`);
|
|
16
|
+
binary = path.join(path.dirname(manifest), "bin", windows ? "devlanes.exe" : "devlanes");
|
|
17
|
+
} catch {
|
|
18
|
+
console.error(
|
|
19
|
+
`devlanes: no binary installed for ${platform}. Release builds exist for ` +
|
|
20
|
+
"darwin-arm64, darwin-x64, linux-arm64 and linux-x64 (glibc), win32-arm64 and win32-x64.\n" +
|
|
21
|
+
"If npm ran with --omit=optional or --no-optional, reinstall without it.",
|
|
22
|
+
);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const child = spawn(binary, process.argv.slice(2), { stdio: "inherit" });
|
|
27
|
+
|
|
28
|
+
if (windows) {
|
|
29
|
+
// Ctrl-C, Ctrl-Break and closing the console reach every process attached to
|
|
30
|
+
// the console, the binary included, which handles them itself. The listeners
|
|
31
|
+
// only keep Node from exiting before it; child.kill() would end the binary
|
|
32
|
+
// without notice.
|
|
33
|
+
for (const signal of ["SIGINT", "SIGBREAK", "SIGHUP"]) process.on(signal, () => {});
|
|
34
|
+
} else {
|
|
35
|
+
// The terminal already delivers SIGINT and SIGQUIT to the whole process group,
|
|
36
|
+
// so forwarding them would reach the binary twice. Signals sent only to this
|
|
37
|
+
// wrapper are passed on.
|
|
38
|
+
for (const signal of ["SIGINT", "SIGQUIT"]) process.on(signal, () => {});
|
|
39
|
+
for (const signal of ["SIGTERM", "SIGHUP"]) {
|
|
40
|
+
process.on(signal, () => child.kill(signal));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
child.on("error", (error) => {
|
|
45
|
+
console.error(`devlanes: cannot run ${binary}: ${error.message}`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
});
|
|
48
|
+
child.on("exit", (code, signal) => {
|
|
49
|
+
// Windows processes end with a code only, such as 0xC000013A after Ctrl-C,
|
|
50
|
+
// which process.exit passes on unchanged.
|
|
51
|
+
if (signal && !windows) {
|
|
52
|
+
process.removeAllListeners(signal);
|
|
53
|
+
process.kill(process.pid, signal);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
process.exit(code ?? 1);
|
|
57
|
+
});
|
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.
|
|
3
|
+
"version": "0.4.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
|
|
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,11 @@
|
|
|
26
30
|
"node": ">=18"
|
|
27
31
|
},
|
|
28
32
|
"optionalDependencies": {
|
|
29
|
-
"@devlanes/darwin-arm64": "0.
|
|
30
|
-
"@devlanes/darwin-x64": "0.
|
|
31
|
-
"@devlanes/linux-arm64": "0.
|
|
32
|
-
"@devlanes/linux-x64": "0.
|
|
33
|
+
"@devlanes/darwin-arm64": "0.4.0",
|
|
34
|
+
"@devlanes/darwin-x64": "0.4.0",
|
|
35
|
+
"@devlanes/linux-arm64": "0.4.0",
|
|
36
|
+
"@devlanes/linux-x64": "0.4.0",
|
|
37
|
+
"@devlanes/win32-arm64": "0.4.0",
|
|
38
|
+
"@devlanes/win32-x64": "0.4.0"
|
|
33
39
|
}
|
|
34
40
|
}
|