@styx-api/cli 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.
@@ -0,0 +1,415 @@
1
+ import { mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { BoutiquesBackend, JsonSchemaBackend, PythonBackend, TypeScriptBackend, compile, createContext, defaultPipeline, resolveOutputs, solve } from "@styx-api/core";
4
+
5
+ //#region src/catalog.ts
6
+ function readJson(file) {
7
+ let raw;
8
+ try {
9
+ raw = readFileSync(file, "utf8");
10
+ } catch (e) {
11
+ throw new Error(`${file}: ${e instanceof Error ? e.message : String(e)}`);
12
+ }
13
+ try {
14
+ return JSON.parse(raw);
15
+ } catch (e) {
16
+ throw new Error(`${file}: invalid JSON: ${e instanceof Error ? e.message : String(e)}`);
17
+ }
18
+ }
19
+ function exists(p) {
20
+ try {
21
+ statSync(p);
22
+ return true;
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+ function isDir(p) {
28
+ try {
29
+ return statSync(p).isDirectory();
30
+ } catch {
31
+ return false;
32
+ }
33
+ }
34
+ function detectLevel(dir) {
35
+ if (exists(path.join(dir, "project.json"))) return "project";
36
+ if (exists(path.join(dir, "package.json"))) return "package";
37
+ if (exists(path.join(dir, "version.json"))) return "version";
38
+ if (exists(path.join(dir, "app.json"))) return "app";
39
+ return null;
40
+ }
41
+ /**
42
+ * Load one app descriptor. When `warnings` is supplied (catalog-walk mode), a
43
+ * stub app (`app.json` present but no `source.path`) is skipped with a warning
44
+ * instead of aborting the whole build - real catalogs list not-yet-wrapped tools
45
+ * alongside wrapped ones. Without `warnings` (a direct single-app target) the
46
+ * missing source is a hard error, since the user pointed straight at it.
47
+ */
48
+ function loadApp(appDir, warnings) {
49
+ const appJsonPath = path.join(appDir, "app.json");
50
+ if (!exists(appJsonPath)) return null;
51
+ const app = readJson(appJsonPath);
52
+ const name = app.name ?? path.basename(appDir);
53
+ const sourceRel = app.source?.path;
54
+ if (!sourceRel) {
55
+ if (warnings) {
56
+ warnings.push(`${appJsonPath}: skipped (no source.path - not yet wrapped)`);
57
+ return null;
58
+ }
59
+ throw new Error(`${appJsonPath}: missing source.path`);
60
+ }
61
+ return {
62
+ name,
63
+ sourcePath: path.resolve(appDir, sourceRel),
64
+ sourceFormat: app.source?.type
65
+ };
66
+ }
67
+ function loadVersion(versionDir, warnings) {
68
+ const versionPath = path.join(versionDir, "version.json");
69
+ if (!exists(versionPath)) return null;
70
+ const version = readJson(versionPath);
71
+ const appNames = version.apps ?? listSubdirs(versionDir).sort();
72
+ const apps = [];
73
+ for (const appName of appNames) {
74
+ const appDir = path.join(versionDir, appName);
75
+ if (!isDir(appDir)) continue;
76
+ const app = loadApp(appDir, warnings);
77
+ if (app) apps.push(app);
78
+ }
79
+ const parentPkg = path.join(versionDir, "..", "package.json");
80
+ let pkgName;
81
+ let pkgDoc;
82
+ if (exists(parentPkg)) {
83
+ const pkg = readJson(parentPkg);
84
+ pkgName = pkg.name;
85
+ pkgDoc = pkg.docs;
86
+ }
87
+ pkgName ??= path.basename(path.dirname(versionDir));
88
+ return {
89
+ meta: {
90
+ name: pkgName,
91
+ version: version.name,
92
+ docker: version.container,
93
+ doc: pkgDoc
94
+ },
95
+ apps
96
+ };
97
+ }
98
+ function loadPackage(pkgDir, warnings) {
99
+ const pkgPath = path.join(pkgDir, "package.json");
100
+ if (!exists(pkgPath)) return null;
101
+ const pkg = readJson(pkgPath);
102
+ const versionName = pkg.default ?? pkg.versions?.[0] ?? firstVersionDir(pkgDir);
103
+ if (!versionName) return null;
104
+ const versionDir = path.join(pkgDir, versionName);
105
+ if (!isDir(versionDir)) return null;
106
+ const loaded = loadVersion(versionDir, warnings);
107
+ if (!loaded) return null;
108
+ return {
109
+ meta: {
110
+ name: pkg.name ?? path.basename(pkgDir),
111
+ version: versionName,
112
+ docker: loaded.meta.docker,
113
+ doc: pkg.docs ?? loaded.meta.doc
114
+ },
115
+ apps: loaded.apps
116
+ };
117
+ }
118
+ function listSubdirs(dir) {
119
+ return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
120
+ }
121
+ function firstVersionDir(pkgDir) {
122
+ for (const name of listSubdirs(pkgDir)) if (exists(path.join(pkgDir, name, "version.json"))) return name;
123
+ return null;
124
+ }
125
+ /**
126
+ * Load a catalog from any niwrap layout level. The level is auto-detected from
127
+ * which `*.json` index file is present in `root`. Always returns a normalized
128
+ * `project > package > app` shape, synthesizing missing layers as needed.
129
+ */
130
+ function loadCatalog(root) {
131
+ const level = detectLevel(root);
132
+ if (!level) throw new Error(`${root}: no project.json / package.json / version.json / app.json found - not a catalog root`);
133
+ const warnings = [];
134
+ if (level === "project") {
135
+ const project = readJson(path.join(root, "project.json"));
136
+ const packageNames = project.packages ?? listSubdirs(root);
137
+ const packages = [];
138
+ for (const name of packageNames) {
139
+ const pkg = loadPackage(path.join(root, name), warnings);
140
+ if (pkg) packages.push(pkg);
141
+ }
142
+ return {
143
+ meta: {
144
+ name: project.name,
145
+ version: project.version,
146
+ doc: project.docs,
147
+ license: project.license ? { description: project.license } : void 0
148
+ },
149
+ packages,
150
+ warnings
151
+ };
152
+ }
153
+ if (level === "package") {
154
+ const pkg = loadPackage(root, warnings);
155
+ if (!pkg) throw new Error(`${root}: package.json present but no resolvable version`);
156
+ return {
157
+ meta: { name: pkg.meta.name },
158
+ packages: [pkg],
159
+ warnings
160
+ };
161
+ }
162
+ if (level === "version") {
163
+ const pkg = loadVersion(root, warnings);
164
+ if (!pkg) throw new Error(`${root}: version.json present but failed to load`);
165
+ return {
166
+ meta: { name: pkg.meta.name },
167
+ packages: [pkg],
168
+ warnings
169
+ };
170
+ }
171
+ const app = loadApp(root);
172
+ if (!app) throw new Error(`${root}: app.json present but missing source.path`);
173
+ return {
174
+ meta: {},
175
+ packages: [{
176
+ meta: { name: path.basename(path.dirname(root)) },
177
+ apps: [app]
178
+ }],
179
+ warnings
180
+ };
181
+ }
182
+
183
+ //#endregion
184
+ //#region src/build.ts
185
+ /** Frontend formats the compiler can parse; others are skipped with a warning. */
186
+ const SUPPORTED_FORMATS = new Set([
187
+ "boutiques",
188
+ "argdump",
189
+ "workbench"
190
+ ]);
191
+ /** Render an unknown thrown value as a message string. */
192
+ function errMsg(e) {
193
+ return e instanceof Error ? e.message : String(e);
194
+ }
195
+ /**
196
+ * Run the build. Returns the assembled file map and diagnostics; the caller
197
+ * is responsible for actually writing to disk (so tests can introspect the
198
+ * map directly).
199
+ */
200
+ function build(options) {
201
+ if (options.input && options.catalog) return {
202
+ files: [],
203
+ errors: ["--input and --catalog are mutually exclusive"],
204
+ warnings: []
205
+ };
206
+ if (!options.input && !options.catalog) return {
207
+ files: [],
208
+ errors: ["missing input: pass a descriptor path or --catalog"],
209
+ warnings: []
210
+ };
211
+ if (options.input) return buildSingle(options.input, options);
212
+ return buildCatalog(options.catalog, options);
213
+ }
214
+ function buildSingle(inputPath, options) {
215
+ const result = {
216
+ files: [],
217
+ errors: [],
218
+ warnings: []
219
+ };
220
+ const ctx = readAndCompile(inputPath, void 0, void 0, void 0, result);
221
+ if (!ctx) return result;
222
+ for (const backend of options.backends) {
223
+ let emitted;
224
+ try {
225
+ emitted = backend.emitApp(ctx);
226
+ } catch (e) {
227
+ result.errors.push(`[${backend.name}] ${errMsg(e)}`);
228
+ continue;
229
+ }
230
+ appendEmitMessages(result, emitted, backend);
231
+ for (const [name, content] of emitted.files) result.files.push({
232
+ path: path.resolve(options.out, backend.target, name),
233
+ content
234
+ });
235
+ }
236
+ return result;
237
+ }
238
+ function buildCatalog(catalogPath, options) {
239
+ const result = {
240
+ files: [],
241
+ errors: [],
242
+ warnings: [],
243
+ stats: {
244
+ appsCompiled: 0,
245
+ appsFailed: 0,
246
+ appsSkipped: 0
247
+ }
248
+ };
249
+ let catalog;
250
+ try {
251
+ catalog = loadCatalog(catalogPath);
252
+ } catch (e) {
253
+ result.errors.push(e instanceof Error ? e.message : String(e));
254
+ return result;
255
+ }
256
+ result.warnings.push(...catalog.warnings);
257
+ options.backends.forEach((backend, i) => {
258
+ runBackendOverCatalog(backend, catalog, options.mode, options.out, result, i === 0);
259
+ });
260
+ return result;
261
+ }
262
+ /**
263
+ * Parse a descriptor, run the default optimization pipeline, solve to
264
+ * bindings, and build a `CodegenContext` wired up with the supplied
265
+ * package/project meta. Returns null on read failure (with the error already
266
+ * appended to `result`). Parse-level errors/warnings are also appended.
267
+ */
268
+ function readAndCompile(sourcePath, format, pkg, proj, result) {
269
+ let source;
270
+ try {
271
+ source = readFileSync(sourcePath, "utf8");
272
+ } catch (e) {
273
+ result.errors.push(`${sourcePath}: ${errMsg(e)}`);
274
+ return null;
275
+ }
276
+ try {
277
+ const parsed = compile(source, format && SUPPORTED_FORMATS.has(format) ? {
278
+ filename: sourcePath,
279
+ format
280
+ } : sourcePath);
281
+ for (const e of parsed.errors) result.errors.push(`${sourcePath}: ${e.message}`);
282
+ for (const w of parsed.warnings) result.warnings.push(`${sourcePath}: ${w.message}`);
283
+ const piped = defaultPipeline.apply(parsed.expr);
284
+ if (piped.warnings) for (const w of piped.warnings) result.warnings.push(`${sourcePath}: ${w}`);
285
+ const solveResult = solve(piped.expr);
286
+ const outputs = resolveOutputs(piped.expr, solveResult);
287
+ return createContext(piped.expr, solveResult, outputs, {
288
+ app: parsed.meta,
289
+ package: pkg,
290
+ project: proj
291
+ });
292
+ } catch (e) {
293
+ result.errors.push(`${sourcePath}: ${errMsg(e)}`);
294
+ return null;
295
+ }
296
+ }
297
+ function runBackendOverCatalog(backend, catalog, mode, outRoot, result, recordWarnings) {
298
+ const backendRoot = path.resolve(outRoot, backend.target);
299
+ const packagesEmitted = [];
300
+ for (const pkg of catalog.packages) {
301
+ const pkgDir = pkg.meta.name ?? "package";
302
+ const appsEmitted = [];
303
+ let skipped = 0;
304
+ const pkgScope = backend.newPackageScope?.();
305
+ for (const app of pkg.apps) {
306
+ if (app.sourceFormat && !SUPPORTED_FORMATS.has(app.sourceFormat)) {
307
+ skipped++;
308
+ if (recordWarnings) {
309
+ if (result.stats) result.stats.appsSkipped++;
310
+ result.warnings.push(`${app.sourcePath}: skipped (unsupported source format "${app.sourceFormat}")`);
311
+ }
312
+ continue;
313
+ }
314
+ const ctx = readAndCompile(app.sourcePath, app.sourceFormat, pkg.meta, catalog.meta, result);
315
+ if (!ctx) {
316
+ if (recordWarnings && result.stats) result.stats.appsFailed++;
317
+ continue;
318
+ }
319
+ let emitted;
320
+ try {
321
+ emitted = backend.emitApp(ctx, pkgScope);
322
+ } catch (e) {
323
+ result.errors.push(`[${backend.name} ${pkg.meta.name}/${app.name}] ${errMsg(e)}`);
324
+ if (recordWarnings && result.stats) result.stats.appsFailed++;
325
+ continue;
326
+ }
327
+ appendEmitMessages(result, emitted, backend, `${pkg.meta.name}/${app.name}`);
328
+ appsEmitted.push(emitted);
329
+ if (recordWarnings && result.stats) result.stats.appsCompiled++;
330
+ for (const [name, content] of emitted.files) result.files.push({
331
+ path: path.join(backendRoot, pkgDir, name),
332
+ content
333
+ });
334
+ }
335
+ if (appsEmitted.length === 0) {
336
+ if (recordWarnings && skipped > 0 && skipped === pkg.apps.length) result.warnings.push(`${pkgDir}: all ${skipped} tool(s) skipped, package omitted`);
337
+ continue;
338
+ }
339
+ if (mode !== "scripts" && backend.emitPackage) try {
340
+ const pkgEmit = backend.emitPackage(pkg.meta, appsEmitted);
341
+ appendEmitMessages(result, pkgEmit, backend, pkg.meta.name);
342
+ packagesEmitted.push(pkgEmit);
343
+ for (const [name, content] of pkgEmit.files) result.files.push({
344
+ path: path.join(backendRoot, pkgDir, name),
345
+ content
346
+ });
347
+ } catch (e) {
348
+ result.errors.push(`[${backend.name} ${pkgDir}] package emit failed: ${errMsg(e)}`);
349
+ }
350
+ }
351
+ if (mode === "multi" && backend.emitProject && packagesEmitted.length > 0) try {
352
+ const projEmit = backend.emitProject(catalog.meta, packagesEmitted);
353
+ appendEmitMessages(result, projEmit, backend, catalog.meta.name);
354
+ for (const [name, content] of projEmit.files) result.files.push({
355
+ path: path.join(backendRoot, name),
356
+ content
357
+ });
358
+ } catch (e) {
359
+ result.errors.push(`[${backend.name}] project emit failed: ${errMsg(e)}`);
360
+ }
361
+ }
362
+ function appendEmitMessages(result, emit, backend, scope) {
363
+ const tag = scope ? `[${backend.name} ${scope}]` : `[${backend.name}]`;
364
+ for (const e of emit.errors) result.errors.push(`${tag} ${e.message}`);
365
+ for (const w of emit.warnings) result.warnings.push(`${tag} ${w.message}`);
366
+ }
367
+
368
+ //#endregion
369
+ //#region src/backends.ts
370
+ /**
371
+ * Registry of backends keyed by CLI alias. The alias is what users pass to
372
+ * `-b`; the backend's `name`/`target` is what we use for the output subdir.
373
+ */
374
+ const registry = {
375
+ python: () => new PythonBackend(),
376
+ typescript: () => new TypeScriptBackend(),
377
+ ts: () => new TypeScriptBackend(),
378
+ schema: () => new JsonSchemaBackend(),
379
+ "json-schema": () => new JsonSchemaBackend(),
380
+ boutiques: () => new BoutiquesBackend()
381
+ };
382
+ const knownBackends = Object.keys(registry);
383
+ function resolveBackends(names) {
384
+ const backends = [];
385
+ const seen = /* @__PURE__ */ new Set();
386
+ const unknown = [];
387
+ for (const name of names) {
388
+ const factory = registry[name];
389
+ if (!factory) {
390
+ unknown.push(name);
391
+ continue;
392
+ }
393
+ const backend = factory();
394
+ if (seen.has(backend.name)) continue;
395
+ seen.add(backend.name);
396
+ backends.push(backend);
397
+ }
398
+ return {
399
+ backends,
400
+ unknown
401
+ };
402
+ }
403
+
404
+ //#endregion
405
+ //#region src/write.ts
406
+ function writeFiles(files) {
407
+ for (const file of files) {
408
+ mkdirSync(path.dirname(file.path), { recursive: true });
409
+ writeFileSync(file.path, file.content, "utf8");
410
+ }
411
+ }
412
+
413
+ //#endregion
414
+ export { detectLevel as a, build as i, knownBackends as n, loadCatalog as o, resolveBackends as r, writeFiles as t };
415
+ //# sourceMappingURL=write-DSW1zlom.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-DSW1zlom.mjs","names":[],"sources":["../src/catalog.ts","../src/build.ts","../src/backends.ts","../src/write.ts"],"sourcesContent":["import { readFileSync, readdirSync, statSync } from \"node:fs\";\nimport * as path from \"node:path\";\n\nimport type { Documentation, PackageMeta, ProjectMeta } from \"@styx-api/core\";\n\n/**\n * Loaded catalog: a project containing packages, each holding one or more\n * apps. We always normalize to project > package > app even when the catalog\n * root only describes a subset of the hierarchy (e.g. a single version dir).\n */\nexport interface CatalogProject {\n meta: ProjectMeta;\n packages: CatalogPackage[];\n /** Non-fatal issues found while walking the catalog (e.g. skipped stub apps). */\n warnings: string[];\n}\n\nexport interface CatalogPackage {\n meta: PackageMeta;\n apps: CatalogApp[];\n}\n\nexport interface CatalogApp {\n /** Stable identifier from `app.json#name`, used for the emitted module slug. */\n name: string;\n /** Absolute path to the descriptor file referenced by `app.json#source`. */\n sourcePath: string;\n sourceFormat?: string;\n}\n\ninterface ProjectJson {\n name?: string;\n version?: string;\n license?: string;\n packages?: string[];\n docs?: Documentation;\n}\n\ninterface PackageJson {\n name?: string;\n versions?: string[];\n default?: string;\n docs?: Documentation;\n}\n\ninterface VersionJson {\n name?: string;\n container?: string;\n apps?: string[];\n executables?: { required?: string[]; ignored?: string[] };\n}\n\ninterface AppJson {\n name?: string;\n source?: { type?: string; path?: string };\n docs?: Documentation;\n}\n\nfunction readJson<T>(file: string): T {\n let raw: string;\n try {\n raw = readFileSync(file, \"utf8\");\n } catch (e) {\n throw new Error(`${file}: ${e instanceof Error ? e.message : String(e)}`);\n }\n try {\n return JSON.parse(raw) as T;\n } catch (e) {\n throw new Error(`${file}: invalid JSON: ${e instanceof Error ? e.message : String(e)}`);\n }\n}\n\nfunction exists(p: string): boolean {\n try {\n statSync(p);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction isDir(p: string): boolean {\n try {\n return statSync(p).isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Detect what level of the niwrap catalog a directory describes.\n * Project dirs contain `project.json`, package dirs contain `package.json`,\n * version dirs contain `version.json`, app dirs contain `app.json`.\n */\nexport type CatalogLevel = \"project\" | \"package\" | \"version\" | \"app\";\n\nexport function detectLevel(dir: string): CatalogLevel | null {\n if (exists(path.join(dir, \"project.json\"))) return \"project\";\n if (exists(path.join(dir, \"package.json\"))) return \"package\";\n if (exists(path.join(dir, \"version.json\"))) return \"version\";\n if (exists(path.join(dir, \"app.json\"))) return \"app\";\n return null;\n}\n\n/**\n * Load one app descriptor. When `warnings` is supplied (catalog-walk mode), a\n * stub app (`app.json` present but no `source.path`) is skipped with a warning\n * instead of aborting the whole build - real catalogs list not-yet-wrapped tools\n * alongside wrapped ones. Without `warnings` (a direct single-app target) the\n * missing source is a hard error, since the user pointed straight at it.\n */\nfunction loadApp(appDir: string, warnings?: string[]): CatalogApp | null {\n const appJsonPath = path.join(appDir, \"app.json\");\n if (!exists(appJsonPath)) return null;\n const app = readJson<AppJson>(appJsonPath);\n const name = app.name ?? path.basename(appDir);\n const sourceRel = app.source?.path;\n if (!sourceRel) {\n if (warnings) {\n warnings.push(`${appJsonPath}: skipped (no source.path - not yet wrapped)`);\n return null;\n }\n throw new Error(`${appJsonPath}: missing source.path`);\n }\n return {\n name,\n sourcePath: path.resolve(appDir, sourceRel),\n sourceFormat: app.source?.type,\n };\n}\n\nfunction loadVersion(versionDir: string, warnings?: string[]): CatalogPackage | null {\n const versionPath = path.join(versionDir, \"version.json\");\n if (!exists(versionPath)) return null;\n const version = readJson<VersionJson>(versionPath);\n\n // Sort the directory fallback so app order (and thus any shared-scope name\n // dodging) is deterministic regardless of filesystem iteration order.\n const appNames = version.apps ?? listSubdirs(versionDir).sort();\n const apps: CatalogApp[] = [];\n for (const appName of appNames) {\n const appDir = path.join(versionDir, appName);\n if (!isDir(appDir)) continue;\n const app = loadApp(appDir, warnings);\n if (app) apps.push(app);\n }\n\n // Try to enrich with the parent package.json if there is one (package > version layout).\n const parentPkg = path.join(versionDir, \"..\", \"package.json\");\n let pkgName: string | undefined;\n let pkgDoc: Documentation | undefined;\n if (exists(parentPkg)) {\n const pkg = readJson<PackageJson>(parentPkg);\n pkgName = pkg.name;\n pkgDoc = pkg.docs;\n }\n pkgName ??= path.basename(path.dirname(versionDir));\n\n return {\n meta: {\n name: pkgName,\n version: version.name,\n docker: version.container,\n doc: pkgDoc,\n },\n apps,\n };\n}\n\nfunction loadPackage(pkgDir: string, warnings?: string[]): CatalogPackage | null {\n const pkgPath = path.join(pkgDir, \"package.json\");\n if (!exists(pkgPath)) return null;\n const pkg = readJson<PackageJson>(pkgPath);\n\n const versionName = pkg.default ?? pkg.versions?.[0] ?? firstVersionDir(pkgDir);\n if (!versionName) return null;\n const versionDir = path.join(pkgDir, versionName);\n if (!isDir(versionDir)) return null;\n\n const loaded = loadVersion(versionDir, warnings);\n if (!loaded) return null;\n\n return {\n meta: {\n name: pkg.name ?? path.basename(pkgDir),\n version: versionName,\n docker: loaded.meta.docker,\n doc: pkg.docs ?? loaded.meta.doc,\n },\n apps: loaded.apps,\n };\n}\n\nfunction listSubdirs(dir: string): string[] {\n return readdirSync(dir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => e.name);\n}\n\nfunction firstVersionDir(pkgDir: string): string | null {\n for (const name of listSubdirs(pkgDir)) {\n if (exists(path.join(pkgDir, name, \"version.json\"))) return name;\n }\n return null;\n}\n\n/**\n * Load a catalog from any niwrap layout level. The level is auto-detected from\n * which `*.json` index file is present in `root`. Always returns a normalized\n * `project > package > app` shape, synthesizing missing layers as needed.\n */\nexport function loadCatalog(root: string): CatalogProject {\n const level = detectLevel(root);\n if (!level) {\n throw new Error(\n `${root}: no project.json / package.json / version.json / app.json found - not a catalog root`,\n );\n }\n\n const warnings: string[] = [];\n\n if (level === \"project\") {\n const project = readJson<ProjectJson>(path.join(root, \"project.json\"));\n const packageNames = project.packages ?? listSubdirs(root);\n const packages: CatalogPackage[] = [];\n for (const name of packageNames) {\n const pkgDir = path.join(root, name);\n const pkg = loadPackage(pkgDir, warnings);\n if (pkg) packages.push(pkg);\n }\n return {\n meta: {\n name: project.name,\n version: project.version,\n doc: project.docs,\n license: project.license ? { description: project.license } : undefined,\n },\n packages,\n warnings,\n };\n }\n\n if (level === \"package\") {\n const pkg = loadPackage(root, warnings);\n if (!pkg) throw new Error(`${root}: package.json present but no resolvable version`);\n return { meta: { name: pkg.meta.name }, packages: [pkg], warnings };\n }\n\n if (level === \"version\") {\n const pkg = loadVersion(root, warnings);\n if (!pkg) throw new Error(`${root}: version.json present but failed to load`);\n return { meta: { name: pkg.meta.name }, packages: [pkg], warnings };\n }\n\n // level === \"app\": a direct single-app target, so a missing source is fatal.\n const app = loadApp(root);\n if (!app) throw new Error(`${root}: app.json present but missing source.path`);\n return {\n meta: {},\n packages: [\n {\n meta: { name: path.basename(path.dirname(root)) },\n apps: [app],\n },\n ],\n warnings,\n };\n}\n","import { readFileSync } from \"node:fs\";\nimport * as path from \"node:path\";\n\nimport {\n compile,\n createContext,\n defaultPipeline,\n resolveOutputs,\n solve,\n type Backend,\n type EmitResult,\n type EmittedApp,\n type EmittedPackage,\n type FormatName,\n type PackageMeta,\n type ProjectMeta,\n} from \"@styx-api/core\";\n\nimport { loadCatalog, type CatalogProject } from \"./catalog.js\";\n\nexport type BuildMode = \"scripts\" | \"single\" | \"multi\";\n\n/** Frontend formats the compiler can parse; others are skipped with a warning. */\nconst SUPPORTED_FORMATS: ReadonlySet<string> = new Set<FormatName>([\n \"boutiques\",\n \"argdump\",\n \"workbench\",\n]);\n\nexport interface BuildOptions {\n /** Single-descriptor input file (mutually exclusive with `catalog`). */\n input?: string;\n /** Catalog directory (mutually exclusive with `input`). */\n catalog?: string;\n /** Output directory; per-backend files end up under `<out>/<backend.target>/...`. */\n out: string;\n /** Backends to run (instances pre-resolved by the caller). */\n backends: Backend[];\n /** Which emit tiers to run. */\n mode: BuildMode;\n}\n\n/** A single file from a backend run, keyed by destination path. */\nexport interface BuiltFile {\n /** Absolute destination path. */\n path: string;\n content: string;\n}\n\n/** Per-tool tallies for a catalog build (undefined for single-descriptor builds). */\nexport interface BuildStats {\n appsCompiled: number;\n appsFailed: number;\n appsSkipped: number;\n}\n\nexport interface BuildResult {\n files: BuiltFile[];\n errors: string[];\n warnings: string[];\n /** Catalog-build tallies; set only by `--catalog` builds. */\n stats?: BuildStats;\n}\n\n/** Render an unknown thrown value as a message string. */\nfunction errMsg(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n\n/**\n * Run the build. Returns the assembled file map and diagnostics; the caller\n * is responsible for actually writing to disk (so tests can introspect the\n * map directly).\n */\nexport function build(options: BuildOptions): BuildResult {\n if (options.input && options.catalog) {\n return { files: [], errors: [\"--input and --catalog are mutually exclusive\"], warnings: [] };\n }\n if (!options.input && !options.catalog) {\n return {\n files: [],\n errors: [\"missing input: pass a descriptor path or --catalog\"],\n warnings: [],\n };\n }\n\n if (options.input) return buildSingle(options.input, options);\n return buildCatalog(options.catalog!, options);\n}\n\nfunction buildSingle(inputPath: string, options: BuildOptions): BuildResult {\n const result: BuildResult = { files: [], errors: [], warnings: [] };\n\n const ctx = readAndCompile(inputPath, undefined, undefined, undefined, result);\n if (!ctx) return result;\n\n for (const backend of options.backends) {\n let emitted;\n try {\n emitted = backend.emitApp(ctx);\n } catch (e) {\n result.errors.push(`[${backend.name}] ${errMsg(e)}`);\n continue;\n }\n appendEmitMessages(result, emitted, backend);\n for (const [name, content] of emitted.files) {\n result.files.push({\n path: path.resolve(options.out, backend.target, name),\n content,\n });\n }\n }\n return result;\n}\n\nfunction buildCatalog(catalogPath: string, options: BuildOptions): BuildResult {\n const result: BuildResult = {\n files: [],\n errors: [],\n warnings: [],\n stats: { appsCompiled: 0, appsFailed: 0, appsSkipped: 0 },\n };\n let catalog: CatalogProject;\n try {\n catalog = loadCatalog(catalogPath);\n } catch (e) {\n result.errors.push(e instanceof Error ? e.message : String(e));\n return result;\n }\n result.warnings.push(...catalog.warnings);\n\n // Skip/empty warnings are backend-independent, so only the first backend pass\n // records them - otherwise they'd be duplicated once per backend.\n options.backends.forEach((backend, i) => {\n runBackendOverCatalog(backend, catalog, options.mode, options.out, result, i === 0);\n });\n return result;\n}\n\n/**\n * Parse a descriptor, run the default optimization pipeline, solve to\n * bindings, and build a `CodegenContext` wired up with the supplied\n * package/project meta. Returns null on read failure (with the error already\n * appended to `result`). Parse-level errors/warnings are also appended.\n */\nfunction readAndCompile(\n sourcePath: string,\n format: string | undefined,\n pkg: PackageMeta | undefined,\n proj: ProjectMeta | undefined,\n result: BuildResult,\n): ReturnType<typeof createContext> | null {\n let source: string;\n try {\n source = readFileSync(sourcePath, \"utf8\");\n } catch (e) {\n result.errors.push(`${sourcePath}: ${errMsg(e)}`);\n return null;\n }\n\n // Isolate the compile pipeline: a single descriptor that makes the solver or\n // a downstream pass throw is recorded as an error and skipped, so one bad tool\n // can't crash a whole-catalog build.\n try {\n // Honor the catalog's declared format when it's one we support, so we don't\n // fall back to content sniffing (and get a clearer error if it mis-parses).\n const parsed = compile(\n source,\n format && SUPPORTED_FORMATS.has(format)\n ? { filename: sourcePath, format: format as FormatName }\n : sourcePath,\n );\n for (const e of parsed.errors) result.errors.push(`${sourcePath}: ${e.message}`);\n for (const w of parsed.warnings) result.warnings.push(`${sourcePath}: ${w.message}`);\n\n const piped = defaultPipeline.apply(parsed.expr);\n if (piped.warnings) {\n for (const w of piped.warnings) result.warnings.push(`${sourcePath}: ${w}`);\n }\n\n const solveResult = solve(piped.expr);\n const outputs = resolveOutputs(piped.expr, solveResult);\n return createContext(piped.expr, solveResult, outputs, {\n app: parsed.meta,\n package: pkg,\n project: proj,\n });\n } catch (e) {\n result.errors.push(`${sourcePath}: ${errMsg(e)}`);\n return null;\n }\n}\n\nfunction runBackendOverCatalog(\n backend: Backend,\n catalog: CatalogProject,\n mode: BuildMode,\n outRoot: string,\n result: BuildResult,\n recordWarnings: boolean,\n): void {\n const backendRoot = path.resolve(outRoot, backend.target);\n const packagesEmitted: EmittedPackage[] = [];\n\n for (const pkg of catalog.packages) {\n const pkgDir = pkg.meta.name ?? \"package\";\n const appsEmitted: EmittedApp[] = [];\n let skipped = 0;\n // One scope shared across every tool in the suite so top-level names stay\n // unique across the package's flat barrel re-exports.\n const pkgScope = backend.newPackageScope?.();\n\n for (const app of pkg.apps) {\n // A tool can declare a format we have no frontend for yet (e.g. Workbench).\n // Skip it with a warning rather than failing the whole catalog build.\n if (app.sourceFormat && !SUPPORTED_FORMATS.has(app.sourceFormat)) {\n skipped++;\n if (recordWarnings) {\n if (result.stats) result.stats.appsSkipped++;\n result.warnings.push(\n `${app.sourcePath}: skipped (unsupported source format \"${app.sourceFormat}\")`,\n );\n }\n continue;\n }\n\n const ctx = readAndCompile(app.sourcePath, app.sourceFormat, pkg.meta, catalog.meta, result);\n if (!ctx) {\n if (recordWarnings && result.stats) result.stats.appsFailed++;\n continue;\n }\n\n // Isolate emit so one tool that makes a backend throw doesn't crash the run.\n let emitted: EmittedApp;\n try {\n emitted = backend.emitApp(ctx, pkgScope);\n } catch (e) {\n result.errors.push(`[${backend.name} ${pkg.meta.name}/${app.name}] ${errMsg(e)}`);\n if (recordWarnings && result.stats) result.stats.appsFailed++;\n continue;\n }\n appendEmitMessages(result, emitted, backend, `${pkg.meta.name}/${app.name}`);\n appsEmitted.push(emitted);\n if (recordWarnings && result.stats) result.stats.appsCompiled++;\n\n for (const [name, content] of emitted.files) {\n result.files.push({ path: path.join(backendRoot, pkgDir, name), content });\n }\n }\n\n // A suite whose every tool was skipped (e.g. all-Workbench) emits nothing;\n // don't synthesize an empty package or wire it into the project metadata.\n // Only warn when emptiness is due to skips - genuine failures already errored.\n if (appsEmitted.length === 0) {\n if (recordWarnings && skipped > 0 && skipped === pkg.apps.length) {\n result.warnings.push(`${pkgDir}: all ${skipped} tool(s) skipped, package omitted`);\n }\n continue;\n }\n\n if (mode !== \"scripts\" && backend.emitPackage) {\n try {\n const pkgEmit = backend.emitPackage(pkg.meta, appsEmitted);\n appendEmitMessages(result, pkgEmit, backend, pkg.meta.name);\n packagesEmitted.push(pkgEmit);\n for (const [name, content] of pkgEmit.files) {\n result.files.push({ path: path.join(backendRoot, pkgDir, name), content });\n }\n } catch (e) {\n result.errors.push(`[${backend.name} ${pkgDir}] package emit failed: ${errMsg(e)}`);\n }\n }\n }\n\n if (mode === \"multi\" && backend.emitProject && packagesEmitted.length > 0) {\n try {\n const projEmit = backend.emitProject(catalog.meta, packagesEmitted);\n appendEmitMessages(result, projEmit, backend, catalog.meta.name);\n for (const [name, content] of projEmit.files) {\n result.files.push({ path: path.join(backendRoot, name), content });\n }\n } catch (e) {\n result.errors.push(`[${backend.name}] project emit failed: ${errMsg(e)}`);\n }\n }\n}\n\nfunction appendEmitMessages(\n result: BuildResult,\n emit: EmitResult,\n backend: Backend,\n scope?: string,\n): void {\n const tag = scope ? `[${backend.name} ${scope}]` : `[${backend.name}]`;\n for (const e of emit.errors) result.errors.push(`${tag} ${e.message}`);\n for (const w of emit.warnings) result.warnings.push(`${tag} ${w.message}`);\n}\n","import {\n BoutiquesBackend,\n JsonSchemaBackend,\n PythonBackend,\n TypeScriptBackend,\n type Backend,\n} from \"@styx-api/core\";\n\n/**\n * Registry of backends keyed by CLI alias. The alias is what users pass to\n * `-b`; the backend's `name`/`target` is what we use for the output subdir.\n */\nconst registry: Record<string, () => Backend> = {\n python: () => new PythonBackend(),\n typescript: () => new TypeScriptBackend(),\n ts: () => new TypeScriptBackend(),\n schema: () => new JsonSchemaBackend(),\n \"json-schema\": () => new JsonSchemaBackend(),\n boutiques: () => new BoutiquesBackend(),\n};\n\nexport const knownBackends = Object.keys(registry);\n\nexport function resolveBackends(names: string[]): { backends: Backend[]; unknown: string[] } {\n const backends: Backend[] = [];\n const seen = new Set<string>();\n const unknown: string[] = [];\n for (const name of names) {\n const factory = registry[name];\n if (!factory) {\n unknown.push(name);\n continue;\n }\n const backend = factory();\n if (seen.has(backend.name)) continue;\n seen.add(backend.name);\n backends.push(backend);\n }\n return { backends, unknown };\n}\n","import { mkdirSync, writeFileSync } from \"node:fs\";\nimport * as path from \"node:path\";\n\nimport type { BuiltFile } from \"./build.js\";\n\nexport function writeFiles(files: BuiltFile[]): void {\n for (const file of files) {\n mkdirSync(path.dirname(file.path), { recursive: true });\n writeFileSync(file.path, file.content, \"utf8\");\n }\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@styx-api/cli",
3
+ "version": "0.1.0",
4
+ "description": "Command-line interface for the Styx compiler (@styx-api/core): build typed wrappers from CLI tool descriptors and catalogs.",
5
+ "keywords": [
6
+ "styx",
7
+ "niwrap",
8
+ "boutiques",
9
+ "code-generation",
10
+ "cli"
11
+ ],
12
+ "license": "MIT",
13
+ "homepage": "https://niwrap.dev/",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/styx-api/styx-ts.git",
17
+ "directory": "packages/cli"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/styx-api/styx-ts/issues"
21
+ },
22
+ "type": "module",
23
+ "bin": {
24
+ "styx": "./dist/bin.mjs"
25
+ },
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.mts",
29
+ "development": "./src/index.ts",
30
+ "import": "./dist/index.mjs",
31
+ "require": "./dist/index.cjs"
32
+ }
33
+ },
34
+ "main": "./dist/index.cjs",
35
+ "module": "./dist/index.mjs",
36
+ "types": "./dist/index.d.mts",
37
+ "files": [
38
+ "dist",
39
+ "src",
40
+ "!src/**/*.test.ts"
41
+ ],
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "scripts": {
46
+ "build": "tsdown",
47
+ "dev": "tsdown --watch",
48
+ "prepublishOnly": "tsdown"
49
+ },
50
+ "dependencies": {
51
+ "@styx-api/core": "0.1.0",
52
+ "cac": "^6.7.14"
53
+ },
54
+ "devDependencies": {
55
+ "@types/node": "^25.0.3",
56
+ "tsdown": "^0.20.1"
57
+ }
58
+ }
@@ -0,0 +1,131 @@
1
+ # This file was auto generated by Styx.
2
+ # Do not edit this file directly.
3
+
4
+ import dataclasses
5
+ import pathlib
6
+ import typing
7
+
8
+ from styxdefs import Execution, InputPathType, Metadata, Runner, StyxValidationError, OutputPathType, get_global_runner
9
+
10
+ GREET_METADATA = Metadata(
11
+ id="greet",
12
+ name="greet",
13
+ package="unknown",
14
+ )
15
+
16
+ class Greet(typing.TypedDict):
17
+ name: str
18
+ """Person to greet."""
19
+ loud: typing.NotRequired[bool]
20
+ """Shout the greeting."""
21
+
22
+ @dataclasses.dataclass
23
+ class GreetOutputs:
24
+ """Output paths produced by the tool."""
25
+ root: OutputPathType
26
+
27
+ def greet_params(
28
+ name: str,
29
+ loud: bool = False,
30
+ ) -> Greet:
31
+ """
32
+ Build parameters.
33
+
34
+ Args:
35
+ name: Person to greet.
36
+ loud: Shout the greeting.
37
+
38
+ Returns:
39
+ Parameter dictionary.
40
+ """
41
+ params: Greet = {
42
+ "name": name,
43
+ "loud": loud,
44
+ }
45
+ return params
46
+
47
+ def greet_validate(params: typing.Any) -> None:
48
+ """Validate untrusted parameters. Raises StyxValidationError if `params` is not a valid Greet."""
49
+ if params is None or not isinstance(params, dict):
50
+ raise StyxValidationError(f'Params object has the wrong type \'{type(params)}\'')
51
+ if params.get("name", None) is None:
52
+ raise StyxValidationError("`name` must not be None")
53
+ if not isinstance(params["name"], str):
54
+ raise StyxValidationError(f'`name` has the wrong type: Received `{type(params["name"])}` expected `str`')
55
+ if params.get("loud", None) is not None:
56
+ if not isinstance(params["loud"], bool):
57
+ raise StyxValidationError(f'`loud` has the wrong type: Received `{type(params["loud"])}` expected `bool`')
58
+
59
+ def greet_cargs(params: Greet, execution: Execution) -> list[str]:
60
+ """Build command-line arguments from parameters."""
61
+ cargs: list[str] = []
62
+ cargs.append("greet")
63
+ cargs.append(params["name"])
64
+ if params.get("loud"):
65
+ cargs.append("--loud")
66
+ return cargs
67
+
68
+ def greet_outputs(params: Greet, execution: Execution) -> GreetOutputs:
69
+ """Build the GreetOutputs object for this tool."""
70
+ root_v: OutputPathType = execution.output_file(".")
71
+ return GreetOutputs(
72
+ root=root_v,
73
+ )
74
+
75
+ def greet_execute(params: Greet, runner: Runner | None = None) -> GreetOutputs:
76
+ """
77
+ greet
78
+
79
+ Print a greeting.
80
+
81
+ Args:
82
+ params: The parameters.
83
+ runner: Command runner (defaults to global runner).
84
+
85
+ Returns:
86
+ Tool outputs (paths to files produced by the tool).
87
+ """
88
+ greet_validate(params)
89
+ runner = runner if runner is not None else get_global_runner()
90
+ execution = runner.start_execution(GREET_METADATA)
91
+ execution.params(params)
92
+ args = greet_cargs(params, execution)
93
+ out = greet_outputs(params, execution)
94
+ execution.run(args)
95
+ return out
96
+
97
+ def greet(
98
+ name: str,
99
+ loud: bool = False,
100
+ runner: Runner | None = None,
101
+ ) -> GreetOutputs:
102
+ """
103
+ greet
104
+
105
+ Print a greeting.
106
+
107
+ Args:
108
+ name: Person to greet.
109
+ loud: Shout the greeting.
110
+ runner: Command runner (defaults to global runner).
111
+
112
+ Returns:
113
+ Tool outputs (paths to files produced by the tool).
114
+ """
115
+ params = greet_params(
116
+ name=name,
117
+ loud=loud,
118
+ )
119
+ return greet_execute(params, runner)
120
+
121
+ __all__ = [
122
+ "Greet",
123
+ "GreetOutputs",
124
+ "GREET_METADATA",
125
+ "greet_cargs",
126
+ "greet_outputs",
127
+ "greet_params",
128
+ "greet_execute",
129
+ "greet_validate",
130
+ "greet",
131
+ ]