typeweld 0.0.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 ADDED
@@ -0,0 +1,10 @@
1
+ # typeweld
2
+
3
+ Npm launcher for the Typeweld CLI.
4
+
5
+ ```sh
6
+ npx typeweld new my-api
7
+ ```
8
+
9
+ The package resolves a native `typeweld` binary from a packaged release, a local
10
+ Cargo build, or `TYPEWELD_BINARY`.
Binary file
package/dist/index.js ADDED
@@ -0,0 +1,323 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import { accessSync, constants, existsSync, realpathSync, statSync, } from "node:fs";
4
+ import { delimiter, dirname, join, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ export const binaryName = "typeweld";
7
+ export const binaryOverrideEnvVars = ["TYPEWELD_BINARY", "API_CLI_BINARY"];
8
+ const wrapperFile = fileURLToPath(import.meta.url);
9
+ const platformBinaryName = process.platform === "win32" ? `${binaryName}.exe` : binaryName;
10
+ export class TypeweldWrapperError extends Error {
11
+ checked;
12
+ constructor(message, checked) {
13
+ super(message);
14
+ this.name = "TypeweldWrapperError";
15
+ this.checked = checked;
16
+ }
17
+ }
18
+ export function resolveTypeweldBinary(options = {}) {
19
+ const env = options.env ?? process.env;
20
+ const cwd = resolve(options.cwd ?? process.cwd());
21
+ const currentWrapperFile = resolve(options.wrapperFile ?? wrapperFile);
22
+ const invocationFile = options.invocationFile
23
+ ? resolve(options.invocationFile)
24
+ : process.argv[1];
25
+ const wrapperRealpaths = realpathSet([currentWrapperFile, invocationFile]);
26
+ const checked = [];
27
+ const override = resolveOverride(env, cwd);
28
+ if (override !== undefined) {
29
+ const status = executableStatus(override.path);
30
+ checked.push(formatChecked(override.source, override.path, status.reason));
31
+ if (status.ok) {
32
+ return override.path;
33
+ }
34
+ throw new TypeweldWrapperError([
35
+ `${override.source} points to \`${override.path}\`, but ${status.reason}.`,
36
+ "",
37
+ "Build the CLI with:",
38
+ " cargo build -p typeweld-cli --bin typeweld",
39
+ "",
40
+ "Or set TYPEWELD_BINARY to the absolute path of a working typeweld binary.",
41
+ ].join("\n"), checked);
42
+ }
43
+ for (const candidate of candidateBinaries({
44
+ cwd,
45
+ env,
46
+ wrapperFile: currentWrapperFile,
47
+ })) {
48
+ const status = executableStatus(candidate.path);
49
+ if (status.ok) {
50
+ const realCandidate = realpathSafe(candidate.path);
51
+ if (realCandidate !== undefined && wrapperRealpaths.has(realCandidate)) {
52
+ checked.push(formatChecked(candidate.source, candidate.path, "wrapper shim skipped"));
53
+ continue;
54
+ }
55
+ return candidate.path;
56
+ }
57
+ checked.push(formatChecked(candidate.source, candidate.path, status.reason));
58
+ }
59
+ throw new TypeweldWrapperError(formatMissingDiagnostic(checked), checked);
60
+ }
61
+ export function launchTypeweld(binary, args = process.argv.slice(2), env = process.env) {
62
+ const child = spawn(binary, [...args], {
63
+ env,
64
+ stdio: "inherit",
65
+ windowsHide: true,
66
+ });
67
+ const forwardedSignals = ["SIGINT", "SIGTERM", "SIGHUP"];
68
+ for (const signal of forwardedSignals) {
69
+ process.once(signal, () => {
70
+ child.kill(signal);
71
+ });
72
+ }
73
+ child.once("error", (error) => {
74
+ console.error([
75
+ `failed to start typeweld CLI at \`${binary}\`: ${error.message}`,
76
+ "",
77
+ "Build the CLI with:",
78
+ " cargo build -p typeweld-cli --bin typeweld",
79
+ "",
80
+ "Or set TYPEWELD_BINARY to the absolute path of a working typeweld binary.",
81
+ ].join("\n"));
82
+ process.exit(1);
83
+ });
84
+ child.once("exit", (code, signal) => {
85
+ if (signal !== null) {
86
+ exitFromSignal(signal);
87
+ return;
88
+ }
89
+ process.exit(code ?? 1);
90
+ });
91
+ return child;
92
+ }
93
+ export function main() {
94
+ try {
95
+ launchTypeweld(resolveTypeweldBinary(), process.argv.slice(2));
96
+ }
97
+ catch (error) {
98
+ if (error instanceof Error) {
99
+ console.error(error.message);
100
+ }
101
+ else {
102
+ console.error(String(error));
103
+ }
104
+ process.exit(1);
105
+ }
106
+ }
107
+ if (isMainModule(wrapperFile, process.argv[1])) {
108
+ main();
109
+ }
110
+ function candidateBinaries(options) {
111
+ const packageRoots = findPackageRoots(dirname(options.wrapperFile));
112
+ const candidates = [];
113
+ for (const packageRoot of packageRoots) {
114
+ candidates.push({
115
+ source: "packaged binary",
116
+ path: join(packageRoot, "bin", `${process.platform}-${process.arch}`, platformBinaryName),
117
+ }, {
118
+ source: "packaged binary",
119
+ path: join(packageRoot, "bin", platformBinaryName),
120
+ });
121
+ }
122
+ for (const targetDir of cargoTargetDirs(options)) {
123
+ candidates.push({
124
+ source: "local cargo build",
125
+ path: join(targetDir, "debug", platformBinaryName),
126
+ }, {
127
+ source: "local cargo build",
128
+ path: join(targetDir, "release", platformBinaryName),
129
+ });
130
+ }
131
+ for (const pathCandidate of pathBinaries(options.env)) {
132
+ candidates.push({
133
+ source: "PATH",
134
+ path: pathCandidate,
135
+ });
136
+ }
137
+ return dedupeCandidates(candidates);
138
+ }
139
+ function cargoTargetDirs(options) {
140
+ const roots = [];
141
+ const explicitTargetDir = options.env.CARGO_TARGET_DIR?.trim();
142
+ if (explicitTargetDir !== undefined && explicitTargetDir !== "") {
143
+ roots.push(resolve(options.cwd, explicitTargetDir));
144
+ }
145
+ for (const root of workspaceRoots(options.cwd)) {
146
+ roots.push(join(root, "target"));
147
+ }
148
+ for (const packageRoot of findPackageRoots(dirname(options.wrapperFile))) {
149
+ for (const root of workspaceRoots(packageRoot)) {
150
+ roots.push(join(root, "target"));
151
+ }
152
+ }
153
+ return dedupeStrings(roots);
154
+ }
155
+ function workspaceRoots(start) {
156
+ const roots = [];
157
+ for (const directory of parentDirs(resolve(start))) {
158
+ if (existsSync(join(directory, "Cargo.toml")) ||
159
+ existsSync(join(directory, "target"))) {
160
+ roots.push(directory);
161
+ }
162
+ }
163
+ return roots;
164
+ }
165
+ function resolveOverride(env, cwd) {
166
+ for (const envVar of binaryOverrideEnvVars) {
167
+ const raw = env[envVar]?.trim();
168
+ if (raw !== undefined && raw !== "") {
169
+ return {
170
+ source: envVar,
171
+ path: resolve(cwd, raw),
172
+ };
173
+ }
174
+ }
175
+ return undefined;
176
+ }
177
+ function pathBinaries(env) {
178
+ const pathValue = readPathEnv(env);
179
+ if (pathValue === undefined || pathValue === "") {
180
+ return [];
181
+ }
182
+ return pathValue
183
+ .split(delimiter)
184
+ .filter((entry) => entry !== "")
185
+ .flatMap((entry) => executableNames().map((name) => resolve(entry, name)));
186
+ }
187
+ function executableNames() {
188
+ return process.platform === "win32"
189
+ ? [`${binaryName}.exe`, binaryName]
190
+ : [binaryName];
191
+ }
192
+ function readPathEnv(env) {
193
+ const pathKey = Object.keys(env).find((key) => key.toLowerCase() === "path");
194
+ return pathKey === undefined ? undefined : env[pathKey];
195
+ }
196
+ function findPackageRoots(start) {
197
+ const roots = [];
198
+ for (const directory of parentDirs(resolve(start))) {
199
+ if (existsSync(join(directory, "package.json"))) {
200
+ roots.push(directory);
201
+ }
202
+ }
203
+ return roots;
204
+ }
205
+ function parentDirs(start) {
206
+ const directories = [];
207
+ let current = resolve(start);
208
+ while (true) {
209
+ directories.push(current);
210
+ const parent = dirname(current);
211
+ if (parent === current) {
212
+ return directories;
213
+ }
214
+ current = parent;
215
+ }
216
+ }
217
+ function executableStatus(file) {
218
+ if (!existsSync(file)) {
219
+ return { ok: false, reason: "it does not exist" };
220
+ }
221
+ let stat;
222
+ try {
223
+ stat = statSync(file);
224
+ }
225
+ catch (error) {
226
+ return {
227
+ ok: false,
228
+ reason: `it could not be inspected: ${errorMessage(error)}`,
229
+ };
230
+ }
231
+ if (!stat.isFile()) {
232
+ return { ok: false, reason: "it is not a file" };
233
+ }
234
+ try {
235
+ accessSync(file, process.platform === "win32" ? constants.F_OK : constants.X_OK);
236
+ }
237
+ catch {
238
+ return { ok: false, reason: "it is not executable" };
239
+ }
240
+ return { ok: true, reason: "found" };
241
+ }
242
+ function realpathSet(files) {
243
+ const paths = new Set();
244
+ for (const file of files) {
245
+ const realpath = file === undefined ? undefined : realpathSafe(file);
246
+ if (realpath !== undefined) {
247
+ paths.add(realpath);
248
+ }
249
+ }
250
+ return paths;
251
+ }
252
+ function realpathSafe(file) {
253
+ try {
254
+ return realpathSync(file);
255
+ }
256
+ catch {
257
+ return undefined;
258
+ }
259
+ }
260
+ function dedupeStrings(values) {
261
+ return [...new Set(values)];
262
+ }
263
+ function dedupeCandidates(candidates) {
264
+ const seen = new Set();
265
+ const deduped = [];
266
+ for (const candidate of candidates) {
267
+ const key = `${candidate.source}\0${candidate.path}`;
268
+ if (seen.has(key)) {
269
+ continue;
270
+ }
271
+ seen.add(key);
272
+ deduped.push(candidate);
273
+ }
274
+ return deduped;
275
+ }
276
+ function formatChecked(source, file, reason) {
277
+ return `${source}: ${file} (${reason})`;
278
+ }
279
+ function formatMissingDiagnostic(checked) {
280
+ const maxLines = 24;
281
+ const displayed = checked.slice(0, maxLines);
282
+ const omitted = checked.length - displayed.length;
283
+ const checkedLines = displayed.length === 0
284
+ ? [" - no candidate locations were available"]
285
+ : displayed.map((line) => ` - ${line}`);
286
+ if (omitted > 0) {
287
+ checkedLines.push(` - ... ${omitted} more candidates omitted`);
288
+ }
289
+ return [
290
+ "typeweld npm wrapper could not find the Rust typeweld CLI binary.",
291
+ "",
292
+ "Checked:",
293
+ ...checkedLines,
294
+ "",
295
+ "Build the CLI with:",
296
+ " cargo build -p typeweld-cli --bin typeweld",
297
+ "",
298
+ "Packaged npm releases should include a binary under bin/<platform>-<arch>/typeweld.",
299
+ "Or set TYPEWELD_BINARY to the absolute path of a working typeweld binary.",
300
+ ].join("\n");
301
+ }
302
+ function isMainModule(moduleFile, invocationFile) {
303
+ if (invocationFile === undefined) {
304
+ return false;
305
+ }
306
+ const moduleRealpath = realpathSafe(moduleFile);
307
+ const invocationRealpath = realpathSafe(invocationFile);
308
+ return (moduleRealpath !== undefined &&
309
+ invocationRealpath !== undefined &&
310
+ moduleRealpath === invocationRealpath);
311
+ }
312
+ function exitFromSignal(signal) {
313
+ try {
314
+ process.kill(process.pid, signal);
315
+ }
316
+ catch {
317
+ process.exit(1);
318
+ }
319
+ setTimeout(() => process.exit(1), 250).unref();
320
+ }
321
+ function errorMessage(error) {
322
+ return error instanceof Error ? error.message : String(error);
323
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "typeweld",
3
+ "version": "0.0.0",
4
+ "description": "CLI for Typeweld Rust-to-TypeScript API contract tooling.",
5
+ "license": "MIT OR Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/typeweld/typeweld.git",
9
+ "directory": "npm/cli"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "type": "module",
15
+ "bin": {
16
+ "typeweld": "./dist/index.js"
17
+ },
18
+ "files": [
19
+ "bin",
20
+ "dist"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.build.json",
24
+ "prepare:binary": "tsx scripts/prepare-package.mts",
25
+ "prepack": "npm run build",
26
+ "test": "npm run build && npm run typecheck && npm run test:wrapper",
27
+ "test:wrapper": "vitest run",
28
+ "typecheck": "tsc --noEmit -p tsconfig.json"
29
+ }
30
+ }