@nm-lang/cli 0.0.0-stage → 0.2.1
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.txt +57 -0
- package/NOTICE.txt +11 -0
- package/README.md +75 -2
- package/bin/nm.mjs +315 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1527 -0
- package/dist/version.d.ts +109 -0
- package/dist/version.js +156 -0
- package/package.json +56 -4
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
QuantumSoftware N/M npm Packages — Free Use License
|
|
2
|
+
Version 1.0, 5 October 2026
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2026 QuantumSoftware.tech contributors. All rights reserved.
|
|
5
|
+
|
|
6
|
+
1. Scope
|
|
7
|
+
This license covers QuantumSoftware-owned code and assets in the official
|
|
8
|
+
@nm-lang/core, @nm-lang/cli, @nm-lang/language-server and @nm-lang/mcp npm
|
|
9
|
+
packages. It does not license the rest of the repository or the separately
|
|
10
|
+
distributed VS Code extension. Third-party components remain subject to their
|
|
11
|
+
respective licenses; their notices and permissions take precedence for those
|
|
12
|
+
components.
|
|
13
|
+
|
|
14
|
+
2. Free use
|
|
15
|
+
You may download, install and use these official packages without charge for
|
|
16
|
+
personal, educational and commercial purposes, import their APIs into your own
|
|
17
|
+
programs, and make backup copies for your own use. QuantumSoftware authorizes
|
|
18
|
+
distribution of its official packages through the nm-lang organization on the
|
|
19
|
+
npm registry, including the registry's normal delivery and package-manager
|
|
20
|
+
installation and caching mechanisms.
|
|
21
|
+
|
|
22
|
+
3. Reserved rights
|
|
23
|
+
This is not an open-source license. Except for the permissions above, rights
|
|
24
|
+
to QuantumSoftware-owned code and assets are reserved. Modification,
|
|
25
|
+
redistribution, sublicensing, sale or publication of that code or those assets
|
|
26
|
+
requires separate permission from the rights holders, unless applicable law
|
|
27
|
+
provides otherwise. Do not remove copyright or license notices. These
|
|
28
|
+
restrictions do not override rights granted by any third-party license.
|
|
29
|
+
|
|
30
|
+
4. Your programs
|
|
31
|
+
This license imposes no restrictions on N/M programs you write, their source
|
|
32
|
+
code, or simulation results produced using these packages. It does not
|
|
33
|
+
transfer ownership of your work to QuantumSoftware. This clause does not grant
|
|
34
|
+
additional redistribution rights to QuantumSoftware-owned runtime code bundled
|
|
35
|
+
with your application; those rights are governed by sections 2 and 3.
|
|
36
|
+
|
|
37
|
+
5. No warranty
|
|
38
|
+
THE PACKAGES ARE PROVIDED "AS IS", WITHOUT WARRANTIES OF ANY KIND, TO THE
|
|
39
|
+
EXTENT PERMITTED BY APPLICABLE LAW. TO THAT SAME EXTENT, THE RIGHTS HOLDERS
|
|
40
|
+
ARE NOT LIABLE FOR DAMAGES ARISING FROM USE OF THE PACKAGES. RIGHTS THAT
|
|
41
|
+
CANNOT BE EXCLUDED UNDER APPLICABLE LAW ARE NOT AFFECTED.
|
|
42
|
+
|
|
43
|
+
6. Permission requests
|
|
44
|
+
For additional permissions, contact the publisher through:
|
|
45
|
+
https://quantumsoftware-tech.vercel.app/en/#contact
|
|
46
|
+
|
|
47
|
+
Türkçe açıklama
|
|
48
|
+
Resmî dört npm paketi kişisel, eğitim ve ticari amaçlarla ücretsiz kurulup
|
|
49
|
+
kullanılabilir; API'leri kendi programlarınızdan çağırabilir ve kendi kullanımınız
|
|
50
|
+
için yedek kopya alabilirsiniz. Bu bir açık kaynak lisansı değildir.
|
|
51
|
+
QuantumSoftware'a ait kod ve varlıkları değiştirmek veya yeniden dağıtmak için
|
|
52
|
+
ayrıca izin gerekir. Paketleri indirilebilir kılmak, kaynak kodu üzerinde açık
|
|
53
|
+
kaynak hakları vermez. Üçüncü taraf lisanslarının ve yürürlükteki hukukun verdiği
|
|
54
|
+
haklar korunur. Yazdığınız N/M programları ve ürettiğiniz sonuçların mülkiyeti
|
|
55
|
+
size aittir. Uygulamanızla birlikte dağıtacağınız QuantumSoftware çalışma zamanı
|
|
56
|
+
dosyaları için yukarıdaki izin ve kısıtlamalar geçerlidir. Yukarıdaki İngilizce
|
|
57
|
+
metin lisansın koşullarını belirler.
|
package/NOTICE.txt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
QuantumSoftware N/M package notices
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 QuantumSoftware.tech contributors. All rights reserved.
|
|
4
|
+
QuantumSoftware-owned code is licensed under LICENSE.txt.
|
|
5
|
+
|
|
6
|
+
Runtime dependencies listed in package.json are installed separately by npm.
|
|
7
|
+
Their own licenses and notices remain applicable. The QuantumSoftware license
|
|
8
|
+
does not restrict the rights granted for those third-party components.
|
|
9
|
+
|
|
10
|
+
jiti 2.x is a separately installed runtime dependency licensed under MIT.
|
|
11
|
+
Its original LICENSE is included in the jiti package.
|
package/README.md
CHANGED
|
@@ -1,3 +1,76 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @nm-lang/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
N/M 0.2.0 Preview command-line tools for running, formatting, linting, testing,
|
|
4
|
+
exporting and managing local N/M workspaces.
|
|
5
|
+
|
|
6
|
+
Package release **0.2.1** targets the N/M **0.2.0** language contract.
|
|
7
|
+
|
|
8
|
+
## Install / Kurulum
|
|
9
|
+
|
|
10
|
+
Requires Node.js 20 or newer. Release checks use Node.js 22.23.1.
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install --global @nm-lang/cli@next
|
|
14
|
+
nm --version
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or run without a global install:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npx --package=@nm-lang/cli@next nm --version
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The `next` tag selects the preview channel. To pin this version after publication,
|
|
24
|
+
replace `@next` with `@0.2.1`. npm installs the matching Core dependency.
|
|
25
|
+
No repository checkout or TypeScript build is needed to use a published package.
|
|
26
|
+
|
|
27
|
+
Türkçe: `next` önizleme kanalını seçer. Yayından sonra `@0.2.1` ile sürümü
|
|
28
|
+
sabitleyebilirsiniz. Core bağımlılığı otomatik kurulur; depoyu indirmeniz gerekmez.
|
|
29
|
+
|
|
30
|
+
## First program / İlk program
|
|
31
|
+
|
|
32
|
+
Save the following as `bell.nm`:
|
|
33
|
+
|
|
34
|
+
```nm
|
|
35
|
+
module npm_bell;
|
|
36
|
+
fn main() {
|
|
37
|
+
let q = qreg[2];
|
|
38
|
+
H(q[0]);
|
|
39
|
+
CNOT(q[0], q[1]);
|
|
40
|
+
let a = measure(q[0]);
|
|
41
|
+
let b = measure(q[1]);
|
|
42
|
+
return (a, b);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
nm run bell.nm --json
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The program creates and measures a Bell pair using a local simulator.
|
|
51
|
+
Türkçe: Kodu `bell.nm` olarak kaydedip komutla çalıştırın. Sonuç yerel
|
|
52
|
+
simülasyondan gelir; gerçek kuantum donanımı kanıtı değildir.
|
|
53
|
+
|
|
54
|
+
## Language options / Dil seçenekleri
|
|
55
|
+
|
|
56
|
+
New CLI invocations enable the default language profile: classical functions,
|
|
57
|
+
typed values/Result, module visibility, parameter groups and compute/apply.
|
|
58
|
+
Use `--legacy-features` for the previous opt-in behavior. Other experimental
|
|
59
|
+
features still require their own flags; export/backend limits remain enforced.
|
|
60
|
+
|
|
61
|
+
Türkçe: Yeni CLI çağrılarında varsayılan dil profili açıktır. Eski davranış için
|
|
62
|
+
`--legacy-features` kullanın. Diğer deneysel özellikler ayrıca açılmalıdır.
|
|
63
|
+
|
|
64
|
+
- [CLI documentation](https://quantumsoftware-tech.vercel.app/en/docs/nm-language/cli)
|
|
65
|
+
- [Türkçe CLI rehberi](https://quantumsoftware-tech.vercel.app/tr/docs/nm-language/cli)
|
|
66
|
+
- [Support / Destek](https://quantumsoftware-tech.vercel.app/en/#contact)
|
|
67
|
+
|
|
68
|
+
## License / Lisans
|
|
69
|
+
|
|
70
|
+
Free personal, educational and commercial use; source-code rights are reserved.
|
|
71
|
+
Modification or redistribution of QuantumSoftware-owned package code requires
|
|
72
|
+
separate permission. Your N/M programs and results remain yours. See
|
|
73
|
+
[LICENSE.txt](./LICENSE.txt) and [NOTICE.txt](./NOTICE.txt).
|
|
74
|
+
|
|
75
|
+
Türkçe: Kullanım ücretsizdir; açık kaynak lisansı verilmez. Paket kodunu
|
|
76
|
+
değiştirme ve yeniden dağıtma için ayrıca izin gerekir.
|
package/bin/nm.mjs
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
let runNMCLI;
|
|
8
|
+
let validateNMWorkspaceManifestJsonText;
|
|
9
|
+
let NM_WORKSPACE_MAX_DOCUMENTS;
|
|
10
|
+
let NM_WORKSPACE_MAX_SOURCE_BYTES;
|
|
11
|
+
let nmWorkspaceDiagnosticCodes;
|
|
12
|
+
let getNMWorkspaceModuleName;
|
|
13
|
+
const workspaceRoot = path.resolve(
|
|
14
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
15
|
+
"../../..",
|
|
16
|
+
);
|
|
17
|
+
const workspaceCLIPath = path.join(workspaceRoot, "lib/nm/cli.ts");
|
|
18
|
+
const workspaceCache = new Map();
|
|
19
|
+
const cliArgs = process.argv.slice(2);
|
|
20
|
+
const isPackedVersionCommand =
|
|
21
|
+
process.env.NM_CLI_FORCE_PACKED === "1" &&
|
|
22
|
+
(cliArgs[0] === "--version" || cliArgs[0] === "version");
|
|
23
|
+
|
|
24
|
+
if (isPackedVersionCommand) {
|
|
25
|
+
const { nmVersionManifest } = await import("../dist/version.js");
|
|
26
|
+
process.stdout.write(
|
|
27
|
+
cliArgs.includes("--json")
|
|
28
|
+
? `${JSON.stringify(nmVersionManifest, null, 2)}\n`
|
|
29
|
+
: `N/M ${nmVersionManifest.languageVersion} (${nmVersionManifest.stability})\n`,
|
|
30
|
+
);
|
|
31
|
+
} else {
|
|
32
|
+
if (existsSync(workspaceCLIPath) && process.env.NM_CLI_FORCE_PACKED !== "1") {
|
|
33
|
+
const [{ default: Module }, { createJiti }] = await Promise.all([
|
|
34
|
+
import("node:module"),
|
|
35
|
+
import("jiti"),
|
|
36
|
+
]);
|
|
37
|
+
installWorkspaceAliasResolver(Module);
|
|
38
|
+
const jiti = createJiti(import.meta.url);
|
|
39
|
+
({
|
|
40
|
+
runNMCLI,
|
|
41
|
+
validateNMWorkspaceManifestJsonText,
|
|
42
|
+
NM_WORKSPACE_MAX_DOCUMENTS,
|
|
43
|
+
NM_WORKSPACE_MAX_SOURCE_BYTES,
|
|
44
|
+
nmWorkspaceDiagnosticCodes,
|
|
45
|
+
getNMWorkspaceModuleName,
|
|
46
|
+
} = jiti(workspaceCLIPath));
|
|
47
|
+
} else {
|
|
48
|
+
try {
|
|
49
|
+
({
|
|
50
|
+
runNMCLI,
|
|
51
|
+
validateNMWorkspaceManifestJsonText,
|
|
52
|
+
NM_WORKSPACE_MAX_DOCUMENTS,
|
|
53
|
+
NM_WORKSPACE_MAX_SOURCE_BYTES,
|
|
54
|
+
nmWorkspaceDiagnosticCodes,
|
|
55
|
+
getNMWorkspaceModuleName,
|
|
56
|
+
} = await import("../dist/index.js"));
|
|
57
|
+
} catch {
|
|
58
|
+
({
|
|
59
|
+
runNMCLI,
|
|
60
|
+
validateNMWorkspaceManifestJsonText,
|
|
61
|
+
NM_WORKSPACE_MAX_DOCUMENTS,
|
|
62
|
+
NM_WORKSPACE_MAX_SOURCE_BYTES,
|
|
63
|
+
nmWorkspaceDiagnosticCodes,
|
|
64
|
+
getNMWorkspaceModuleName,
|
|
65
|
+
} = await import("../../nm-core/dist/index.js"));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const packageAction = cliArgs[0] === "package" ? cliArgs[1] : undefined;
|
|
69
|
+
const crypto =
|
|
70
|
+
packageAction === "sign" || packageAction === "verify"
|
|
71
|
+
? await import("node:crypto")
|
|
72
|
+
: undefined;
|
|
73
|
+
const packageSigner =
|
|
74
|
+
packageAction === "sign"
|
|
75
|
+
? createConfiguredPackageSigner(crypto)
|
|
76
|
+
: undefined;
|
|
77
|
+
const packageVerifier =
|
|
78
|
+
packageAction === "verify"
|
|
79
|
+
? createConfiguredPackageVerifier(crypto)
|
|
80
|
+
: undefined;
|
|
81
|
+
const result = runNMCLI(cliArgs, {
|
|
82
|
+
readFile: (filePath) => readFileSync(filePath, "utf8"),
|
|
83
|
+
readWorkspaceModules: (entryPath) => getWorkspaceIndex(entryPath).modules,
|
|
84
|
+
readWorkspaceDocuments: (entryPath) =>
|
|
85
|
+
getWorkspaceIndex(entryPath).documents,
|
|
86
|
+
fileExists: (filePath) => existsSync(filePath),
|
|
87
|
+
writeFile: (filePath, content, { overwrite }) =>
|
|
88
|
+
writeFileSync(filePath, content, {
|
|
89
|
+
encoding: "utf8",
|
|
90
|
+
flag: overwrite ? "w" : "wx",
|
|
91
|
+
}),
|
|
92
|
+
packageSigner,
|
|
93
|
+
packageVerifier,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (result.stdout.length > 0) {
|
|
97
|
+
process.stdout.write(`${result.stdout.join("\n")}\n`);
|
|
98
|
+
}
|
|
99
|
+
if (result.stderr.length > 0) {
|
|
100
|
+
process.stderr.write(`${result.stderr.join("\n")}\n`);
|
|
101
|
+
}
|
|
102
|
+
process.exitCode = result.exitCode;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getWorkspaceIndex(entryPath) {
|
|
106
|
+
const cacheKey = path.resolve(entryPath);
|
|
107
|
+
const cached = workspaceCache.get(cacheKey);
|
|
108
|
+
if (cached) {
|
|
109
|
+
return cached;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const entryDirectory = path.dirname(cacheKey);
|
|
113
|
+
const manifestPath = findWorkspaceManifest(entryDirectory);
|
|
114
|
+
const manifest = manifestPath
|
|
115
|
+
? validateWorkspaceManifest(manifestPath)
|
|
116
|
+
: undefined;
|
|
117
|
+
const root = manifestPath ? path.dirname(manifestPath) : entryDirectory;
|
|
118
|
+
const sourceRoots = manifest
|
|
119
|
+
? manifest.sourceRoots.map((sourceRoot) =>
|
|
120
|
+
resolveInsideWorkspace(root, sourceRoot),
|
|
121
|
+
)
|
|
122
|
+
: [root];
|
|
123
|
+
const modules = Object.create(null);
|
|
124
|
+
const documents = [];
|
|
125
|
+
const seenFiles = new Set();
|
|
126
|
+
let sourceBytes = 0;
|
|
127
|
+
for (const sourceRoot of sourceRoots) {
|
|
128
|
+
for (const filePath of collectNMFiles(sourceRoot)) {
|
|
129
|
+
if (seenFiles.has(filePath)) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
seenFiles.add(filePath);
|
|
133
|
+
const source = readFileSync(filePath, "utf8");
|
|
134
|
+
sourceBytes += new TextEncoder().encode(source).byteLength;
|
|
135
|
+
if (sourceBytes > NM_WORKSPACE_MAX_SOURCE_BYTES) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`${nmWorkspaceDiagnosticCodes.limit}: N/M workspace source exceeded ${NM_WORKSPACE_MAX_SOURCE_BYTES} UTF-8 bytes.`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
const moduleName = getNMWorkspaceModuleName(source);
|
|
141
|
+
if (!moduleName) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (Object.prototype.hasOwnProperty.call(modules, moduleName)) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`${nmWorkspaceDiagnosticCodes.duplicateModule}: Duplicate N/M workspace module: ${moduleName}.`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
modules[moduleName] = source;
|
|
150
|
+
documents.push({
|
|
151
|
+
path: path.relative(root, filePath).split(path.sep).join("/"),
|
|
152
|
+
source,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const index = { modules, documents, manifest, root };
|
|
157
|
+
workspaceCache.set(cacheKey, index);
|
|
158
|
+
return index;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function findWorkspaceManifest(startDirectory) {
|
|
162
|
+
let directory = startDirectory;
|
|
163
|
+
while (true) {
|
|
164
|
+
const candidate = path.join(directory, "nm-workspace.json");
|
|
165
|
+
if (existsSync(candidate)) {
|
|
166
|
+
return candidate;
|
|
167
|
+
}
|
|
168
|
+
const parent = path.dirname(directory);
|
|
169
|
+
if (parent === directory) {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
directory = parent;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function validateWorkspaceManifest(manifestPath) {
|
|
177
|
+
const validation = validateNMWorkspaceManifestJsonText(
|
|
178
|
+
readFileSync(manifestPath, "utf8"),
|
|
179
|
+
);
|
|
180
|
+
if (!validation.valid) {
|
|
181
|
+
throw new Error(
|
|
182
|
+
`Invalid ${manifestPath}: ${validation.issues.map((issue) => `${issue.code} ${issue.path}: ${issue.message}`).join(" ")}`,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
return validation.manifest;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function resolveInsideWorkspace(root, relativePath) {
|
|
189
|
+
const resolved = path.resolve(root, relativePath);
|
|
190
|
+
const relative = path.relative(root, resolved);
|
|
191
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
`Workspace source root escapes its manifest directory: ${relativePath}.`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
return resolved;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function collectNMFiles(root) {
|
|
200
|
+
const ignoredDirectories = new Set(["node_modules", ".git", ".next", "dist"]);
|
|
201
|
+
const files = [];
|
|
202
|
+
const pending = [root];
|
|
203
|
+
while (pending.length > 0) {
|
|
204
|
+
const directory = pending.pop();
|
|
205
|
+
if (!directory) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
209
|
+
const entryPath = path.join(directory, entry.name);
|
|
210
|
+
if (entry.isDirectory() && !ignoredDirectories.has(entry.name)) {
|
|
211
|
+
pending.push(entryPath);
|
|
212
|
+
} else if (entry.isFile() && entry.name.endsWith(".nm")) {
|
|
213
|
+
files.push(entryPath);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (files.length > NM_WORKSPACE_MAX_DOCUMENTS) {
|
|
217
|
+
throw new Error(
|
|
218
|
+
`${nmWorkspaceDiagnosticCodes.limit}: N/M workspace index exceeded ${NM_WORKSPACE_MAX_DOCUMENTS} files under ${root}.`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return files.sort();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function installWorkspaceAliasResolver(Module) {
|
|
226
|
+
const originalResolveFilename = Module._resolveFilename;
|
|
227
|
+
Module._resolveFilename = function resolveWorkspaceAlias(
|
|
228
|
+
request,
|
|
229
|
+
parent,
|
|
230
|
+
isMain,
|
|
231
|
+
options,
|
|
232
|
+
) {
|
|
233
|
+
if (typeof request === "string" && request.startsWith("@/")) {
|
|
234
|
+
const absoluteRequest = path.join(workspaceRoot, request.slice(2));
|
|
235
|
+
const tsRequest = `${absoluteRequest}.ts`;
|
|
236
|
+
return originalResolveFilename.call(
|
|
237
|
+
this,
|
|
238
|
+
existsSync(tsRequest) ? tsRequest : absoluteRequest,
|
|
239
|
+
parent,
|
|
240
|
+
isMain,
|
|
241
|
+
options,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
return originalResolveFilename.call(this, request, parent, isMain, options);
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function createConfiguredPackageSigner(crypto) {
|
|
249
|
+
const privateKeyText = process.env.NM_PACKAGE_SIGNING_PRIVATE_KEY?.replaceAll(
|
|
250
|
+
"\\n",
|
|
251
|
+
"\n",
|
|
252
|
+
).trim();
|
|
253
|
+
const keyId = process.env.NM_PACKAGE_SIGNING_KEY_ID?.trim();
|
|
254
|
+
if (!privateKeyText && !keyId) return undefined;
|
|
255
|
+
if (!privateKeyText || !keyId) {
|
|
256
|
+
throw new Error(
|
|
257
|
+
"NM_PACKAGE_SIGNING_PRIVATE_KEY and NM_PACKAGE_SIGNING_KEY_ID must be configured together.",
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
const privateKey = crypto.createPrivateKey(privateKeyText);
|
|
261
|
+
if (privateKey.asymmetricKeyType !== "ed25519")
|
|
262
|
+
throw new Error("NM package signing key must be Ed25519.");
|
|
263
|
+
return {
|
|
264
|
+
algorithm: "ed25519",
|
|
265
|
+
keyId,
|
|
266
|
+
sign: (payload) =>
|
|
267
|
+
crypto.sign(null, payload, privateKey).toString("base64url"),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function createConfiguredPackageVerifier(crypto) {
|
|
272
|
+
const keyringText = process.env.NM_PACKAGE_TRUSTED_PUBLIC_KEYS_JSON?.trim();
|
|
273
|
+
if (!keyringText) return undefined;
|
|
274
|
+
let keyringValue;
|
|
275
|
+
try {
|
|
276
|
+
keyringValue = JSON.parse(keyringText);
|
|
277
|
+
} catch {
|
|
278
|
+
throw new Error("NM_PACKAGE_TRUSTED_PUBLIC_KEYS_JSON must be valid JSON.");
|
|
279
|
+
}
|
|
280
|
+
if (
|
|
281
|
+
!keyringValue ||
|
|
282
|
+
typeof keyringValue !== "object" ||
|
|
283
|
+
Array.isArray(keyringValue)
|
|
284
|
+
) {
|
|
285
|
+
throw new Error(
|
|
286
|
+
"NM_PACKAGE_TRUSTED_PUBLIC_KEYS_JSON must be an object keyed by key id.",
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
const keyring = new Map();
|
|
290
|
+
for (const [keyId, publicKeyText] of Object.entries(keyringValue)) {
|
|
291
|
+
if (typeof publicKeyText !== "string" || !publicKeyText.trim())
|
|
292
|
+
throw new Error(`Trusted public key is invalid: ${keyId}.`);
|
|
293
|
+
const publicKey = crypto.createPublicKey(
|
|
294
|
+
publicKeyText.replaceAll("\\n", "\n"),
|
|
295
|
+
);
|
|
296
|
+
if (publicKey.asymmetricKeyType !== "ed25519")
|
|
297
|
+
throw new Error(`Trusted public key must be Ed25519: ${keyId}.`);
|
|
298
|
+
keyring.set(keyId, publicKey);
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
verify: ({ algorithm, keyId, payload, signature }) => {
|
|
302
|
+
const publicKey = keyring.get(keyId);
|
|
303
|
+
return (
|
|
304
|
+
algorithm === "ed25519" &&
|
|
305
|
+
Boolean(publicKey) &&
|
|
306
|
+
crypto.verify(
|
|
307
|
+
null,
|
|
308
|
+
payload,
|
|
309
|
+
publicKey,
|
|
310
|
+
Buffer.from(signature, "base64url"),
|
|
311
|
+
)
|
|
312
|
+
);
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { getNMWorkspaceModuleName, evaluateNMLanguagePreviewReadiness, NM_CLI_CONTRACT_VERSION, NM_LANGUAGE_PREVIEW_READINESS_FORMAT, NM_LANGUAGE_PREVIEW_READINESS_VERSION, NM_WORKSPACE_MAX_DOCUMENTS, NM_WORKSPACE_MAX_SOURCE_BYTES, nmCliCommandContracts, nmCliCommands, nmCliExitCodeContract, nmLanguagePreviewReadiness, nmLanguageReleaseChangelog, nmVersionManifest, nmWorkspaceDiagnosticCodes, runNMCLI, validateNMWorkspaceManifestJsonText } from "@nm-lang/core/cli";
|
|
2
|
+
export type { NMCapabilityStatus, NMCLICommand, NMCLICommandContract, NMCLIDeterminism, NMCLIExportTarget, NMCLIOptions, NMCLIOutputKind, NMCLIResult, NMLanguagePreviewReadiness, NMLanguageReleaseChange, NMVersionManifest } from "@nm-lang/core/cli";
|