@nubjs/nub 0.0.42 → 0.0.45

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 (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -0
  3. package/bin/launch.js +68 -7
  4. package/package.json +29 -11
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nub 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,60 @@
1
+ # Nub
2
+
3
+ TypeScript-first developer supertool for Node.js. Run `.ts`/`.tsx` files directly on your installed Node, a faster `npm run`, a pnpm-compatible package manager, and a built-in Node version manager — no config, no lock-in, no nub-specific public APIs.
4
+
5
+ **Documentation:** https://nubjs.com/docs
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ # macOS / Linux
11
+ curl -fsSL https://nubjs.com/install.sh | bash
12
+
13
+ # Windows (PowerShell)
14
+ irm https://nubjs.com/install.ps1 | iex
15
+
16
+ # Or via npm
17
+ npm install -g @nubjs/nub
18
+ ```
19
+
20
+ ## Quickstart
21
+
22
+ ```sh
23
+ # Run TypeScript directly — no tsconfig, no build step
24
+ nub server.ts
25
+
26
+ # Run package.json scripts (faster than npm/pnpm run)
27
+ nub run dev
28
+ nub run build
29
+
30
+ # Execute a local or remote binary
31
+ nubx vitest --run
32
+
33
+ # Watch mode — restart on change
34
+ nub watch server.ts
35
+ ```
36
+
37
+ ## What Nub does
38
+
39
+ - **TypeScript just works.** Files ending in `.ts`, `.tsx`, `.mts`, `.cts`, `.jsx` execute directly. Enums, decorators, parameter properties, and namespaces are handled. Source maps work in error traces.
40
+ - **The `.env` loading is built in.** Workspace-aware, with `${VAR}` expansion and `.env.local` / `.env.production` layering.
41
+ - **The script runner is faster.** Running `nub run` resolves scripts, adds `node_modules/.bin` to PATH, and runs lifecycle hooks — without the per-invocation Node bootstrap that `npm run` / `pnpm run` pay.
42
+ - **Auto-flag injection.** Experimental Node features are unflagged based on your Node version; opt out with `--no-experimental-*`.
43
+ - **The tsconfig paths resolve.** A path like `@lib/utils` resolves via `compilerOptions.paths` with no build step. Extensionless and data-format imports (`.jsonc`, `.json5`, `.toml`, `.yaml`) work too.
44
+ - **Polyfills.** `Temporal`, `URLPattern`, `RegExp.escape`, `Error.isError`, `Promise.try`, `navigator` — feature-detected, native wins.
45
+ - **Package management.** Commands such as `nub install` / `add` / `remove` are pnpm-compatible on the CLI and lockfile-compatible with whatever the project already uses (npm / pnpm / bun round-trip, yarn read-only) — Nub does not impose its own lockfile format.
46
+ - **Node version management.** Nub provisions and pins the project's Node version, so `nvm` / `corepack` are not needed.
47
+
48
+ ## How it works
49
+
50
+ Nub is not a Node fork. It is a Rust CLI that orchestrates your installed Node via Node's own extension surfaces — `module.registerHooks()`, `--import` preloads, V8 flag injection, an N-API addon for fast transpilation, and a per-invocation PATH shim. Code targeting Node runs on Nub byte-for-byte. For orchestration without augmentation, use `nub run --node`.
51
+
52
+ There are no nub-specific public APIs to import, no `nub:*` module namespace, and no config field you author named after the tool. Adopting Nub is reversible — the project keeps running under plain Node and your existing package manager.
53
+
54
+ ## Requirements
55
+
56
+ Nub runs on your installed Node, version **18.19.0 or newer**. The fast tier (sync `module.registerHooks()`) engages on Node **22.15+**; versions 18.19–22.14 run in a compatibility tier via async `module.register()`. Supported platforms: macOS (arm64, x64), Linux (x64, arm64), Windows (x64).
57
+
58
+ ## License
59
+
60
+ MIT — see [LICENSE](./LICENSE). Full docs at https://nubjs.com/docs.
package/bin/launch.js CHANGED
@@ -57,6 +57,64 @@ function resolveBinary(verb) {
57
57
  // POSIX single-quote a string for safe embedding in the sh trampoline.
58
58
  function shq(s) { return `'${String(s).replace(/'/g, "'\\''")}'`; }
59
59
 
60
+ // True iff `p` is executable by THIS process (the +x bits that matter to us).
61
+ function isExecutable(p) {
62
+ try { fs.accessSync(p, fs.constants.X_OK); return true; } catch { return false; }
63
+ }
64
+
65
+ // Make `binPath` runnable by us, returning a path that IS executable — `binPath`
66
+ // itself when we can fix it in place, otherwise a user-owned executable copy.
67
+ //
68
+ // npm strips +x on extract from any file that isn't a `bin`-field entry, and the
69
+ // platform packages declare no `bin` field, so the native binary lands 0o644. The
70
+ // install-time `postinstall.js` chmods it back — but when npm SKIPS lifecycle scripts
71
+ // (npm v12's default, or `ignore-scripts=true`) that never runs, leaving a 0o644
72
+ // binary at runtime. The launcher's in-place chmod recovers the common case (we own
73
+ // the file). It CANNOT recover the canonical container case: `root` does `npm i -g`,
74
+ // the image drops to a non-root `USER`, and that user's first `nub` can neither run
75
+ // the 0o644 binary nor chmod a file it doesn't own — `spawn` dies EACCES (verified:
76
+ // scripts-off install + non-root run + root-owned 0o644 binary). For THAT case we
77
+ // copy the binary into a user-owned cache dir at 0o755 and run the copy. This is the
78
+ // scripts-off complement to postinstall's install-time chmod: self-heal that holds
79
+ // even when we're not the binary's owner. Best-effort; returns the original path if
80
+ // every recovery fails (the caller surfaces the spawn error).
81
+ function ensureExecutable(binPath, verb) {
82
+ if (process.platform === "win32") return binPath; // .exe needs no +x bit
83
+ if (isExecutable(binPath)) return binPath;
84
+ // Common case: we own the file (e.g. `sudo`-free user-prefix install) — chmod in place.
85
+ try { fs.chmodSync(binPath, 0o755); } catch {}
86
+ if (isExecutable(binPath)) return binPath;
87
+ // Non-owner / read-only store: stage a user-owned executable copy in the cache.
88
+ // Key the copy on the source path + size + mtime so a binary upgrade re-stages and
89
+ // we never exec stale bytes; an existing fresh+executable copy is reused as-is.
90
+ try {
91
+ const st = fs.statSync(binPath);
92
+ const base = process.env.XDG_CACHE_HOME ||
93
+ (os.homedir() && os.homedir() !== "/" ? path.join(os.homedir(), ".cache") : null);
94
+ if (!base) return binPath;
95
+ // The copy's FILENAME must stay the bare verb (`nub`/`nubx`): the native binary
96
+ // selects its verb from argv[0]'s basename, and the healed sh trampoline `exec`s
97
+ // this path with no argv0 override — a tagged filename like `nubx-<tag>` would make
98
+ // `nubx` misclassify as plain `nub`. So the staleness tag goes in the DIRECTORY,
99
+ // and the executable keeps its real name: `<cache>/nub/bin/<tag>/<verb>`.
100
+ const tag = `${st.size}-${Math.trunc(st.mtimeMs)}`;
101
+ const dir = path.join(base, "nub", "bin", tag);
102
+ fs.mkdirSync(dir, { recursive: true });
103
+ const dest = path.join(dir, verb);
104
+ if (isExecutable(dest)) {
105
+ const dst = fs.statSync(dest);
106
+ if (dst.size === st.size) return dest; // already staged, current, runnable
107
+ }
108
+ // Atomic stage: copy to a unique temp in the same dir, chmod, rename into place.
109
+ const tmp = path.join(dir, `.${verb}.${process.pid}.${Date.now()}.tmp`);
110
+ fs.copyFileSync(binPath, tmp);
111
+ fs.chmodSync(tmp, 0o755);
112
+ fs.renameSync(tmp, dest);
113
+ if (isExecutable(dest)) return dest;
114
+ } catch {}
115
+ return binPath; // give up; caller's spawn surfaces the real error
116
+ }
117
+
60
118
  // Verify a PATH entry demonstrably resolves to OUR launcher before replacing it —
61
119
  // never clobber an unrelated `nub` (there is an unrelated nub@1.0.0 on npm). For a
62
120
  // symlink, realpath(entry) must equal our launcher's realpath. For a pnpm cmd-shim
@@ -128,15 +186,18 @@ function healPathEntry(verb, nativePath) {
128
186
  // argv0Name: the verb this stub represents ("nubx" for bin/nubx; undefined => nub).
129
187
  module.exports = function launch(argv0Name) {
130
188
  const verb = argv0Name || "nub";
131
- const binPath = resolveBinary(verb);
189
+ const resolved = resolveBinary(verb);
132
190
  // Ensure the platform binary is executable. npm strips the +x bit on install from
133
191
  // files that aren't `bin`-field entries, and the platform package declares no bin
134
- // field — so the staged-executable binary lands 0o644 and both spawnSync (below)
135
- // and the healed sh trampoline's `exec` would EACCES / "Permission denied". The old
136
- // postinstall did this chmod; doing it in the launcher covers EVERY package manager
137
- // (npm strips, others may not) and runs before the heal writes a trampoline that
138
- // exec's this binary. Best-effort; harmless if already executable.
139
- try { fs.chmodSync(binPath, 0o755); } catch {}
192
+ // field — so the binary lands 0o644 and both spawn (below) and the healed sh
193
+ // trampoline's `exec` would EACCES / "Permission denied". `postinstall.js` chmods it
194
+ // at INSTALL time, but that's skipped under npm v12's scripts-off default (or
195
+ // `ignore-scripts=true`); ensureExecutable is the runtime net for that. It chmods in
196
+ // place when we own the file, and falls back to a user-owned executable COPY when we
197
+ // don't (the root-installs-then-drops-to-non-root container case the launcher's bare
198
+ // chmod cannot reach). Returns a path that IS executable; the heal + spawn both use
199
+ // it so the fast-path trampoline targets the runnable file too.
200
+ const binPath = ensureExecutable(resolved, verb);
140
201
  // Self-heal the PATH entry on first POSIX call so later calls skip Node entirely.
141
202
  healPathEntry(verb, binPath);
142
203
  // This call still runs through Node; spawn the native binary. argv0 basename of
package/package.json CHANGED
@@ -1,10 +1,26 @@
1
1
  {
2
2
  "name": "@nubjs/nub",
3
- "version": "0.0.42",
3
+ "version": "0.0.45",
4
4
  "description": "TypeScript-first developer supertool — a fast script runner and TS runtime powered by Node.js",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/nubjs/nub",
7
- "homepage": "https://github.com/nubjs/nub",
7
+ "homepage": "https://nubjs.com",
8
+ "bugs": {
9
+ "url": "https://github.com/nubjs/nub/issues"
10
+ },
11
+ "keywords": [
12
+ "typescript",
13
+ "nodejs",
14
+ "runtime",
15
+ "ts-node",
16
+ "tsx",
17
+ "script-runner",
18
+ "package-manager",
19
+ "node-version-manager"
20
+ ],
21
+ "engines": {
22
+ "node": ">=18.19.0"
23
+ },
8
24
  "bin": {
9
25
  "nub": "bin/nub",
10
26
  "nubx": "bin/nubx"
@@ -15,16 +31,18 @@
15
31
  "files": [
16
32
  "bin",
17
33
  "platform.js",
18
- "postinstall.js"
34
+ "postinstall.js",
35
+ "README.md",
36
+ "LICENSE"
19
37
  ],
20
38
  "optionalDependencies": {
21
- "@nubjs/nub-darwin-arm64": "0.0.42",
22
- "@nubjs/nub-darwin-x64": "0.0.42",
23
- "@nubjs/nub-linux-x64": "0.0.42",
24
- "@nubjs/nub-linux-x64-musl": "0.0.42",
25
- "@nubjs/nub-linux-arm64": "0.0.42",
26
- "@nubjs/nub-linux-arm64-musl": "0.0.42",
27
- "@nubjs/nub-win32-x64": "0.0.42",
28
- "@nubjs/nub-win32-arm64": "0.0.42"
39
+ "@nubjs/nub-darwin-arm64": "0.0.45",
40
+ "@nubjs/nub-darwin-x64": "0.0.45",
41
+ "@nubjs/nub-linux-x64": "0.0.45",
42
+ "@nubjs/nub-linux-x64-musl": "0.0.45",
43
+ "@nubjs/nub-linux-arm64": "0.0.45",
44
+ "@nubjs/nub-linux-arm64-musl": "0.0.45",
45
+ "@nubjs/nub-win32-x64": "0.0.45",
46
+ "@nubjs/nub-win32-arm64": "0.0.45"
29
47
  }
30
48
  }