nim-toolchain 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alec Larson
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
13
+ all 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
20
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
+ IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,130 @@
1
- # Temporary Holding Version
1
+ # nim-toolchain
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Use a pinned native Nim compiler in an npm project. This package provides
4
+ Nim **2.2.12**, its standard library, Nimble, and nimsuggest, using the
5
+ [official Nim downloads](https://nim-lang.org/install.html).
6
+
7
+ This is an independent npm wrapper maintained with MyNim, not an official
8
+ Nim project package. The npm package version and Nim version are separate.
9
+
10
+ ## Install and compile
11
+
12
+ Requires Node.js 22 or later and `tar` with support for the platform's archive
13
+ format (`.tar.xz` on Unix, `.zip` on Windows). Linux also needs `xz` when
14
+ required by its `tar`. macOS and Windows 10/11 include a suitable `tar`.
15
+
16
+ Native compilation also requires a C compiler and the platform SDK: Xcode
17
+ Command Line Tools on macOS, GCC or Clang on Linux, and MinGW or configured
18
+ Visual C++ Build Tools on Windows. This package does not install those tools
19
+ or run Nim's interactive `finish.exe` installer.
20
+
21
+ ```sh
22
+ npm install --save-dev --save-exact nim-toolchain
23
+ npx nim --version
24
+ npx nimble --version
25
+ ```
26
+
27
+ The first command that uses the toolchain downloads the pinned distribution
28
+ from `nim-lang/nightlies` on GitHub and verifies its SHA-256 checksum before
29
+ extraction. Subsequent commands use the cached distribution. No npm install
30
+ scripts are required; `npm install --ignore-scripts` works too.
31
+
32
+ Create `hello.nim`:
33
+
34
+ ```nim
35
+ echo "Hello from Nim"
36
+ ```
37
+
38
+ Compile and run it:
39
+
40
+ ```sh
41
+ npx nim c -r hello.nim
42
+ ```
43
+
44
+ The executable prints `Hello from Nim`. `npx nim` uses this package after it
45
+ has been installed; the unrelated npm package named `nim` is not required.
46
+ For a one-off command without installing a project dependency, use the
47
+ package's own name:
48
+
49
+ ```sh
50
+ npx nim-toolchain --version
51
+ ```
52
+
53
+ All compiler arguments pass through unchanged. npm scripts can invoke `nim`,
54
+ `nimble`, and `nimsuggest` directly.
55
+
56
+ ## Platforms and cache
57
+
58
+ | OS | Architectures |
59
+ | --- | --- |
60
+ | macOS | arm64, x64 |
61
+ | Linux with glibc | arm64, x64 |
62
+ | Windows | x64 |
63
+
64
+ Other platforms, including musl/Alpine Linux and native Windows arm64, fail
65
+ with an unsupported-platform message. System Nim installations are left
66
+ unchanged. Nimble starts with this distribution first on `PATH`; its own
67
+ project dependency resolution still applies.
68
+
69
+ Downloads require network access on first use. Cache locations are:
70
+
71
+ - macOS: `~/Library/Caches/nim-toolchain`
72
+ - Linux: `$XDG_CACHE_HOME/nim-toolchain`, or `~/.cache/nim-toolchain`
73
+ - Windows: `%LOCALAPPDATA%\nim-toolchain\Cache`
74
+
75
+ Set `NIM_TOOLCHAIN_CACHE` to use a different cache directory, including a
76
+ directory restored by CI. **The cache path must not contain spaces or shell
77
+ metacharacters:** the bundled Nimble 0.24.1 does not quote the compiler path
78
+ when checking its version. If your home directory contains spaces, select a
79
+ writable cache path without them. Project paths and compiler arguments may
80
+ contain spaces.
81
+
82
+ Each distribution has its own directory identified by version, platform, and
83
+ checksum. Concurrent commands install into separate
84
+ temporary directories and share the completed result. Incomplete downloads
85
+ are discarded. A completed cache works without network access.
86
+
87
+ ## Use from another CLI
88
+
89
+ ```js
90
+ import { spawnSync } from 'node:child_process';
91
+ import { getToolchain, nimVersion } from 'nim-toolchain';
92
+
93
+ const toolchain = await getToolchain();
94
+ console.log(`Using Nim ${nimVersion}`);
95
+ const result = spawnSync(toolchain.nim, ['c', 'hello.nim'], {
96
+ stdio: 'inherit',
97
+ env: toolchain.env,
98
+ });
99
+ if (result.error) throw result.error;
100
+ process.exitCode = result.status ?? 1;
101
+ ```
102
+
103
+ `getToolchain({ cacheDir })` can select an explicit cache directory. It returns
104
+ absolute paths for `nim`, `nimble`, and `nimsuggest`, the distribution directory,
105
+ its `binDirectory`, its version, and an environment with that `binDirectory`
106
+ prepended to `PATH`. It rejects on download, verification, extraction, cache-path,
107
+ or platform errors. Public declarations are in [index.d.ts](./index.d.ts).
108
+
109
+ ## Develop and release
110
+
111
+ From this directory:
112
+
113
+ ```sh
114
+ npm test
115
+ npm run test:smoke
116
+ npm pack --dry-run
117
+ ```
118
+
119
+ Unit tests use local archives and HTTP fixtures. The smoke test downloads the
120
+ real pinned toolchain, installs a packed copy in a temporary npm project with
121
+ install scripts disabled, and compiles a Nim program through both Nim and
122
+ Nimble. It requires the native build prerequisites above.
123
+
124
+ To update Nim, choose a stable release linked from the official download page
125
+ and update `release.json` with its exact release tag, filenames, and SHA-256
126
+ digests. Verify every supported platform in CI and bump this npm package's
127
+ version before publishing from this directory with `npm publish --access public`.
128
+
129
+ The wrapper is MIT licensed. Downloaded distributions retain upstream files
130
+ and license notices; see Nim's `copying.txt` in the distribution.
package/bin/nim.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { runTool } from '../lib/run.js';
3
+ await runTool('nim');
package/bin/nimble.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { runTool } from '../lib/run.js';
3
+ await runTool('nimble');
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { runTool } from '../lib/run.js';
3
+ await runTool('nimsuggest');
package/index.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ /** The exact upstream Nim version installed by this package. */
2
+ export const nimVersion: string;
3
+
4
+ /** A verified Nim distribution and environment for running its tools. */
5
+ export interface Toolchain {
6
+ version: string;
7
+ directory: string;
8
+ binDirectory: string;
9
+ nim: string;
10
+ nimble: string;
11
+ nimsuggest: string;
12
+ env: Record<string, string | undefined>;
13
+ }
14
+
15
+ /**
16
+ * Download and verify the pinned Nim distribution if it is not cached.
17
+ * Throws for unsupported platforms or cache paths, failed downloads, or checksum mismatches.
18
+ * The default cache is user-owned; NIM_TOOLCHAIN_CACHE overrides its location.
19
+ * The cache path must not contain spaces or shell metacharacters (a Nimble limitation).
20
+ */
21
+ export function getToolchain(options?: { cacheDir?: string }): Promise<Toolchain>;
package/index.js ADDED
@@ -0,0 +1,64 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { delimiter, join, resolve } from 'node:path';
4
+ import { installRelease } from './lib/install.js';
5
+
6
+ const release = JSON.parse(readFileSync(new URL('./release.json', import.meta.url)));
7
+
8
+ /** The exact upstream Nim version installed by this package. */
9
+ export const nimVersion = release.version;
10
+
11
+ function defaultCacheDir() {
12
+ if (process.env.NIM_TOOLCHAIN_CACHE) return process.env.NIM_TOOLCHAIN_CACHE;
13
+ if (process.platform === 'darwin') return join(homedir(), 'Library', 'Caches', 'nim-toolchain');
14
+ if (process.platform === 'win32') {
15
+ return join(process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local'), 'nim-toolchain', 'Cache');
16
+ }
17
+ return join(process.env.XDG_CACHE_HOME || join(homedir(), '.cache'), 'nim-toolchain');
18
+ }
19
+
20
+ /**
21
+ * Download and verify the pinned Nim distribution if it is not cached.
22
+ * Returns absolute tool paths and an environment that uses this compiler for
23
+ * child processes, including compilers launched by Nimble.
24
+ *
25
+ * @param {{ cacheDir?: string }} [options] User-owned cache directory.
26
+ * @throws For unsupported platforms or cache paths, failed downloads, or checksum mismatches.
27
+ */
28
+ export async function getToolchain({ cacheDir = defaultCacheDir() } = {}) {
29
+ const platform = `${process.platform}-${process.arch}`;
30
+ const asset = release.platforms[platform];
31
+ if (!asset) throw new Error(`Nim ${nimVersion} is not packaged for ${platform}.`);
32
+ if (process.platform === 'linux' && !process.report.getReport().header.glibcVersionRuntime) {
33
+ throw new Error('nim-toolchain requires glibc on Linux; use a system Nim installation on musl/Alpine.');
34
+ }
35
+ cacheDir = resolve(cacheDir);
36
+ // Bundled Nimble 0.24.1 interpolates the compiler path into a shell command.
37
+ // Reject paths it cannot quote safely rather than letting compilation fail later.
38
+ if (/[\s'"`$;&|<>()*?\[\]{}!]/u.test(cacheDir) || (process.platform !== 'win32' && cacheDir.includes('\\'))) {
39
+ throw new Error('The bundled Nimble requires a cache path without spaces or shell metacharacters. Set NIM_TOOLCHAIN_CACHE to such a directory.');
40
+ }
41
+
42
+ const directory = await installRelease({
43
+ cacheDir,
44
+ version: nimVersion,
45
+ platform,
46
+ url: `https://github.com/nim-lang/nightlies/releases/download/${release.tag}/${asset.file}`,
47
+ sha256: asset.sha256,
48
+ });
49
+ const binDirectory = join(directory, 'bin');
50
+ const extension = process.platform === 'win32' ? '.exe' : '';
51
+ const env = { ...process.env };
52
+ const pathKey = Object.keys(env).find(key => key.toUpperCase() === 'PATH') || 'PATH';
53
+ env[pathKey] = binDirectory + delimiter + (env[pathKey] || '');
54
+
55
+ return {
56
+ version: nimVersion,
57
+ directory,
58
+ binDirectory,
59
+ nim: join(binDirectory, 'nim' + extension),
60
+ nimble: join(binDirectory, 'nimble' + extension),
61
+ nimsuggest: join(binDirectory, 'nimsuggest' + extension),
62
+ env,
63
+ };
64
+ }
package/lib/install.js ADDED
@@ -0,0 +1,79 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import { createReadStream, createWriteStream } from 'node:fs';
4
+ import { access, mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';
5
+ import { join } from 'node:path';
6
+ import { Readable } from 'node:stream';
7
+ import { pipeline } from 'node:stream/promises';
8
+
9
+ async function isInstalled(directory, sha256, platform) {
10
+ try {
11
+ if (await readFile(join(directory, '.nim-toolchain'), 'utf8') !== sha256) return false;
12
+ const extension = platform.startsWith('win32-') ? '.exe' : '';
13
+ for (const tool of ['nim', 'nimble', 'nimsuggest']) {
14
+ await access(join(directory, 'bin', tool + extension));
15
+ }
16
+ await access(join(directory, 'lib', 'system.nim'));
17
+ await access(join(directory, 'config', 'nim.cfg'));
18
+ return true;
19
+ } catch (error) {
20
+ if (error.code === 'ENOENT' || error.code === 'ENOTDIR') return false;
21
+ throw error;
22
+ }
23
+ }
24
+
25
+ async function extract(archive, directory) {
26
+ await new Promise((resolve, reject) => {
27
+ const child = spawn('tar', ['-xf', archive, '-C', directory], { stdio: ['ignore', 'ignore', 'pipe'] });
28
+ let stderr = '';
29
+ child.stderr.on('data', chunk => { stderr += chunk; });
30
+ child.on('error', error => reject(new Error(`Cannot extract Nim: tar is required (${error.message}).`)));
31
+ child.on('close', code => {
32
+ if (code === 0) resolve();
33
+ else reject(new Error(`Cannot extract Nim: ${stderr.trim() || `tar exited with ${code}`}`));
34
+ });
35
+ });
36
+ }
37
+
38
+ export async function installRelease({ cacheDir, version, platform, url, sha256 }) {
39
+ // Each process extracts privately; only a complete installation becomes visible.
40
+ // The digest separates different builds of the same upstream version.
41
+ const directory = join(cacheDir, `${version}-${platform}-${sha256}`);
42
+ if (await isInstalled(directory, sha256, platform)) return directory;
43
+ await mkdir(cacheDir, { recursive: true });
44
+ const temporary = await mkdtemp(join(cacheDir, '.download-'));
45
+ try {
46
+ const response = await fetch(url, { signal: AbortSignal.timeout(120_000) });
47
+ if (!response.ok) throw new Error(`Cannot download Nim ${version}: HTTP ${response.status}.`);
48
+ const archive = join(temporary, url.endsWith('.zip') ? 'nim.zip' : 'nim.tar.xz');
49
+ await pipeline(Readable.fromWeb(response.body), createWriteStream(archive, { flags: 'wx' }));
50
+ const hash = createHash('sha256');
51
+ for await (const chunk of createReadStream(archive)) hash.update(chunk);
52
+ const actual = hash.digest('hex');
53
+ if (actual !== sha256) {
54
+ throw new Error(`Nim ${version} checksum mismatch: expected ${sha256}, received ${actual}.`);
55
+ }
56
+
57
+ const extracted = join(temporary, 'extracted');
58
+ await mkdir(extracted);
59
+ await extract(archive, extracted);
60
+ const roots = await readdir(extracted, { withFileTypes: true });
61
+ if (roots.length !== 1 || !roots[0].isDirectory()) {
62
+ throw new Error(`Nim ${version} archive must contain a single toolchain directory.`);
63
+ }
64
+ const source = join(extracted, roots[0].name);
65
+ await writeFile(join(source, '.nim-toolchain'), sha256);
66
+ if (!await isInstalled(source, sha256, platform)) {
67
+ throw new Error(`Nim ${version} archive is missing compiler, tools, standard library, or configuration.`);
68
+ }
69
+ try {
70
+ await rename(source, directory);
71
+ } catch (error) {
72
+ // Another process may have installed the same verified release first.
73
+ if (!await isInstalled(directory, sha256, platform)) throw error;
74
+ }
75
+ return directory;
76
+ } finally {
77
+ await rm(temporary, { recursive: true, force: true });
78
+ }
79
+ }
package/lib/run.js ADDED
@@ -0,0 +1,33 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { constants } from 'node:os';
3
+ import { getToolchain } from '../index.js';
4
+
5
+ export async function runTool(tool) {
6
+ try {
7
+ const toolchain = await getToolchain();
8
+ const child = spawn(toolchain[tool], process.argv.slice(2), {
9
+ stdio: 'inherit',
10
+ env: toolchain.env,
11
+ });
12
+ const handlers = new Map();
13
+ for (const signal of ['SIGINT', 'SIGTERM']) {
14
+ const forward = () => child.kill(signal);
15
+ handlers.set(signal, forward);
16
+ process.on(signal, forward);
17
+ }
18
+ try {
19
+ await new Promise((resolve, reject) => {
20
+ child.on('error', reject);
21
+ child.on('close', (code, signal) => {
22
+ process.exitCode = code ?? (128 + (constants.signals[signal] || 1));
23
+ resolve();
24
+ });
25
+ });
26
+ } finally {
27
+ for (const [signal, handler] of handlers) process.off(signal, handler);
28
+ }
29
+ } catch (error) {
30
+ console.error(`nim-toolchain: ${error.message}`);
31
+ process.exitCode = 1;
32
+ }
33
+ }
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
2
  "name": "nim-toolchain",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "A pinned native Nim compiler, standard library, and Nimble for npm projects",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Alec Larson",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/aleclarson/mynim.git",
11
+ "directory": "packages/nim-toolchain"
12
+ },
13
+ "engines": {
14
+ "node": ">=22"
15
+ },
16
+ "bin": {
17
+ "nim-toolchain": "bin/nim.js",
18
+ "nim": "bin/nim.js",
19
+ "nimble": "bin/nimble.js",
20
+ "nimsuggest": "bin/nimsuggest.js"
21
+ },
22
+ "exports": {
23
+ ".": {
24
+ "types": "./index.d.ts",
25
+ "default": "./index.js"
26
+ }
27
+ },
28
+ "files": [
29
+ "bin/",
30
+ "lib/",
31
+ "index.js",
32
+ "index.d.ts",
33
+ "release.json"
34
+ ],
35
+ "scripts": {
36
+ "test": "node --test test/*.test.js",
37
+ "test:smoke": "node test/smoke.js",
38
+ "prepublishOnly": "npm test"
39
+ },
40
+ "keywords": [
41
+ "nim",
42
+ "nimble",
43
+ "compiler",
44
+ "toolchain"
45
+ ]
46
+ }
package/release.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "version": "2.2.12",
3
+ "source": "https://nim-lang.org/install.html",
4
+ "tag": "2026-09-08-version-2-2-8e8fbf60693418dc95bb0d762fd660231d08a583",
5
+ "platforms": {
6
+ "darwin-arm64": {
7
+ "file": "nim-2.2.12-macosx_arm64.tar.xz",
8
+ "sha256": "130a43eff76c1c3cc78b89329a7efd892c1f65839dbc6d8e53651f3f77516fe5"
9
+ },
10
+ "darwin-x64": {
11
+ "file": "nim-2.2.12-macosx_x64.tar.xz",
12
+ "sha256": "a09fa096aeb5f6ef32f8839eced2591e145e526e42f56c79e04a18519b8b132c"
13
+ },
14
+ "linux-arm64": {
15
+ "file": "nim-2.2.12-linux_arm64.tar.xz",
16
+ "sha256": "3dc04191af72a8310cf1c8ffb6f772cc3716fc5f264bc7c453ab4aecb4085221"
17
+ },
18
+ "linux-x64": {
19
+ "file": "nim-2.2.12-linux_x64.tar.xz",
20
+ "sha256": "7df1611449a6842af69322aa2c1206942982650a5f6bc0d37bc8ec109932f638"
21
+ },
22
+ "win32-x64": {
23
+ "file": "nim-2.2.12-windows_x64.zip",
24
+ "sha256": "dafe9c81f8118fac2e07ee24f3649448a590d8b0f3b55d1a97c66f99035cc8e6"
25
+ }
26
+ }
27
+ }