@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,483 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) {
13
+ __defProp(to, key, {
14
+ get: ((k) => from[k]).bind(null, key),
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ });
17
+ }
18
+ }
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
26
+
27
+ //#endregion
28
+ let node_fs = require("node:fs");
29
+ let node_path = require("node:path");
30
+ node_path = __toESM(node_path);
31
+ let _styx_api_core = require("@styx-api/core");
32
+
33
+ //#region src/catalog.ts
34
+ function readJson(file) {
35
+ let raw;
36
+ try {
37
+ raw = (0, node_fs.readFileSync)(file, "utf8");
38
+ } catch (e) {
39
+ throw new Error(`${file}: ${e instanceof Error ? e.message : String(e)}`);
40
+ }
41
+ try {
42
+ return JSON.parse(raw);
43
+ } catch (e) {
44
+ throw new Error(`${file}: invalid JSON: ${e instanceof Error ? e.message : String(e)}`);
45
+ }
46
+ }
47
+ function exists(p) {
48
+ try {
49
+ (0, node_fs.statSync)(p);
50
+ return true;
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+ function isDir(p) {
56
+ try {
57
+ return (0, node_fs.statSync)(p).isDirectory();
58
+ } catch {
59
+ return false;
60
+ }
61
+ }
62
+ function detectLevel(dir) {
63
+ if (exists(node_path.join(dir, "project.json"))) return "project";
64
+ if (exists(node_path.join(dir, "package.json"))) return "package";
65
+ if (exists(node_path.join(dir, "version.json"))) return "version";
66
+ if (exists(node_path.join(dir, "app.json"))) return "app";
67
+ return null;
68
+ }
69
+ /**
70
+ * Load one app descriptor. When `warnings` is supplied (catalog-walk mode), a
71
+ * stub app (`app.json` present but no `source.path`) is skipped with a warning
72
+ * instead of aborting the whole build - real catalogs list not-yet-wrapped tools
73
+ * alongside wrapped ones. Without `warnings` (a direct single-app target) the
74
+ * missing source is a hard error, since the user pointed straight at it.
75
+ */
76
+ function loadApp(appDir, warnings) {
77
+ const appJsonPath = node_path.join(appDir, "app.json");
78
+ if (!exists(appJsonPath)) return null;
79
+ const app = readJson(appJsonPath);
80
+ const name = app.name ?? node_path.basename(appDir);
81
+ const sourceRel = app.source?.path;
82
+ if (!sourceRel) {
83
+ if (warnings) {
84
+ warnings.push(`${appJsonPath}: skipped (no source.path - not yet wrapped)`);
85
+ return null;
86
+ }
87
+ throw new Error(`${appJsonPath}: missing source.path`);
88
+ }
89
+ return {
90
+ name,
91
+ sourcePath: node_path.resolve(appDir, sourceRel),
92
+ sourceFormat: app.source?.type
93
+ };
94
+ }
95
+ function loadVersion(versionDir, warnings) {
96
+ const versionPath = node_path.join(versionDir, "version.json");
97
+ if (!exists(versionPath)) return null;
98
+ const version = readJson(versionPath);
99
+ const appNames = version.apps ?? listSubdirs(versionDir).sort();
100
+ const apps = [];
101
+ for (const appName of appNames) {
102
+ const appDir = node_path.join(versionDir, appName);
103
+ if (!isDir(appDir)) continue;
104
+ const app = loadApp(appDir, warnings);
105
+ if (app) apps.push(app);
106
+ }
107
+ const parentPkg = node_path.join(versionDir, "..", "package.json");
108
+ let pkgName;
109
+ let pkgDoc;
110
+ if (exists(parentPkg)) {
111
+ const pkg = readJson(parentPkg);
112
+ pkgName = pkg.name;
113
+ pkgDoc = pkg.docs;
114
+ }
115
+ pkgName ??= node_path.basename(node_path.dirname(versionDir));
116
+ return {
117
+ meta: {
118
+ name: pkgName,
119
+ version: version.name,
120
+ docker: version.container,
121
+ doc: pkgDoc
122
+ },
123
+ apps
124
+ };
125
+ }
126
+ function loadPackage(pkgDir, warnings) {
127
+ const pkgPath = node_path.join(pkgDir, "package.json");
128
+ if (!exists(pkgPath)) return null;
129
+ const pkg = readJson(pkgPath);
130
+ const versionName = pkg.default ?? pkg.versions?.[0] ?? firstVersionDir(pkgDir);
131
+ if (!versionName) return null;
132
+ const versionDir = node_path.join(pkgDir, versionName);
133
+ if (!isDir(versionDir)) return null;
134
+ const loaded = loadVersion(versionDir, warnings);
135
+ if (!loaded) return null;
136
+ return {
137
+ meta: {
138
+ name: pkg.name ?? node_path.basename(pkgDir),
139
+ version: versionName,
140
+ docker: loaded.meta.docker,
141
+ doc: pkg.docs ?? loaded.meta.doc
142
+ },
143
+ apps: loaded.apps
144
+ };
145
+ }
146
+ function listSubdirs(dir) {
147
+ return (0, node_fs.readdirSync)(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
148
+ }
149
+ function firstVersionDir(pkgDir) {
150
+ for (const name of listSubdirs(pkgDir)) if (exists(node_path.join(pkgDir, name, "version.json"))) return name;
151
+ return null;
152
+ }
153
+ /**
154
+ * Load a catalog from any niwrap layout level. The level is auto-detected from
155
+ * which `*.json` index file is present in `root`. Always returns a normalized
156
+ * `project > package > app` shape, synthesizing missing layers as needed.
157
+ */
158
+ function loadCatalog(root) {
159
+ const level = detectLevel(root);
160
+ if (!level) throw new Error(`${root}: no project.json / package.json / version.json / app.json found - not a catalog root`);
161
+ const warnings = [];
162
+ if (level === "project") {
163
+ const project = readJson(node_path.join(root, "project.json"));
164
+ const packageNames = project.packages ?? listSubdirs(root);
165
+ const packages = [];
166
+ for (const name of packageNames) {
167
+ const pkg = loadPackage(node_path.join(root, name), warnings);
168
+ if (pkg) packages.push(pkg);
169
+ }
170
+ return {
171
+ meta: {
172
+ name: project.name,
173
+ version: project.version,
174
+ doc: project.docs,
175
+ license: project.license ? { description: project.license } : void 0
176
+ },
177
+ packages,
178
+ warnings
179
+ };
180
+ }
181
+ if (level === "package") {
182
+ const pkg = loadPackage(root, warnings);
183
+ if (!pkg) throw new Error(`${root}: package.json present but no resolvable version`);
184
+ return {
185
+ meta: { name: pkg.meta.name },
186
+ packages: [pkg],
187
+ warnings
188
+ };
189
+ }
190
+ if (level === "version") {
191
+ const pkg = loadVersion(root, warnings);
192
+ if (!pkg) throw new Error(`${root}: version.json present but failed to load`);
193
+ return {
194
+ meta: { name: pkg.meta.name },
195
+ packages: [pkg],
196
+ warnings
197
+ };
198
+ }
199
+ const app = loadApp(root);
200
+ if (!app) throw new Error(`${root}: app.json present but missing source.path`);
201
+ return {
202
+ meta: {},
203
+ packages: [{
204
+ meta: { name: node_path.basename(node_path.dirname(root)) },
205
+ apps: [app]
206
+ }],
207
+ warnings
208
+ };
209
+ }
210
+
211
+ //#endregion
212
+ //#region src/build.ts
213
+ /** Frontend formats the compiler can parse; others are skipped with a warning. */
214
+ const SUPPORTED_FORMATS = new Set([
215
+ "boutiques",
216
+ "argdump",
217
+ "workbench"
218
+ ]);
219
+ /** Render an unknown thrown value as a message string. */
220
+ function errMsg(e) {
221
+ return e instanceof Error ? e.message : String(e);
222
+ }
223
+ /**
224
+ * Run the build. Returns the assembled file map and diagnostics; the caller
225
+ * is responsible for actually writing to disk (so tests can introspect the
226
+ * map directly).
227
+ */
228
+ function build(options) {
229
+ if (options.input && options.catalog) return {
230
+ files: [],
231
+ errors: ["--input and --catalog are mutually exclusive"],
232
+ warnings: []
233
+ };
234
+ if (!options.input && !options.catalog) return {
235
+ files: [],
236
+ errors: ["missing input: pass a descriptor path or --catalog"],
237
+ warnings: []
238
+ };
239
+ if (options.input) return buildSingle(options.input, options);
240
+ return buildCatalog(options.catalog, options);
241
+ }
242
+ function buildSingle(inputPath, options) {
243
+ const result = {
244
+ files: [],
245
+ errors: [],
246
+ warnings: []
247
+ };
248
+ const ctx = readAndCompile(inputPath, void 0, void 0, void 0, result);
249
+ if (!ctx) return result;
250
+ for (const backend of options.backends) {
251
+ let emitted;
252
+ try {
253
+ emitted = backend.emitApp(ctx);
254
+ } catch (e) {
255
+ result.errors.push(`[${backend.name}] ${errMsg(e)}`);
256
+ continue;
257
+ }
258
+ appendEmitMessages(result, emitted, backend);
259
+ for (const [name, content] of emitted.files) result.files.push({
260
+ path: node_path.resolve(options.out, backend.target, name),
261
+ content
262
+ });
263
+ }
264
+ return result;
265
+ }
266
+ function buildCatalog(catalogPath, options) {
267
+ const result = {
268
+ files: [],
269
+ errors: [],
270
+ warnings: [],
271
+ stats: {
272
+ appsCompiled: 0,
273
+ appsFailed: 0,
274
+ appsSkipped: 0
275
+ }
276
+ };
277
+ let catalog;
278
+ try {
279
+ catalog = loadCatalog(catalogPath);
280
+ } catch (e) {
281
+ result.errors.push(e instanceof Error ? e.message : String(e));
282
+ return result;
283
+ }
284
+ result.warnings.push(...catalog.warnings);
285
+ options.backends.forEach((backend, i) => {
286
+ runBackendOverCatalog(backend, catalog, options.mode, options.out, result, i === 0);
287
+ });
288
+ return result;
289
+ }
290
+ /**
291
+ * Parse a descriptor, run the default optimization pipeline, solve to
292
+ * bindings, and build a `CodegenContext` wired up with the supplied
293
+ * package/project meta. Returns null on read failure (with the error already
294
+ * appended to `result`). Parse-level errors/warnings are also appended.
295
+ */
296
+ function readAndCompile(sourcePath, format, pkg, proj, result) {
297
+ let source;
298
+ try {
299
+ source = (0, node_fs.readFileSync)(sourcePath, "utf8");
300
+ } catch (e) {
301
+ result.errors.push(`${sourcePath}: ${errMsg(e)}`);
302
+ return null;
303
+ }
304
+ try {
305
+ const parsed = (0, _styx_api_core.compile)(source, format && SUPPORTED_FORMATS.has(format) ? {
306
+ filename: sourcePath,
307
+ format
308
+ } : sourcePath);
309
+ for (const e of parsed.errors) result.errors.push(`${sourcePath}: ${e.message}`);
310
+ for (const w of parsed.warnings) result.warnings.push(`${sourcePath}: ${w.message}`);
311
+ const piped = _styx_api_core.defaultPipeline.apply(parsed.expr);
312
+ if (piped.warnings) for (const w of piped.warnings) result.warnings.push(`${sourcePath}: ${w}`);
313
+ const solveResult = (0, _styx_api_core.solve)(piped.expr);
314
+ const outputs = (0, _styx_api_core.resolveOutputs)(piped.expr, solveResult);
315
+ return (0, _styx_api_core.createContext)(piped.expr, solveResult, outputs, {
316
+ app: parsed.meta,
317
+ package: pkg,
318
+ project: proj
319
+ });
320
+ } catch (e) {
321
+ result.errors.push(`${sourcePath}: ${errMsg(e)}`);
322
+ return null;
323
+ }
324
+ }
325
+ function runBackendOverCatalog(backend, catalog, mode, outRoot, result, recordWarnings) {
326
+ const backendRoot = node_path.resolve(outRoot, backend.target);
327
+ const packagesEmitted = [];
328
+ for (const pkg of catalog.packages) {
329
+ const pkgDir = pkg.meta.name ?? "package";
330
+ const appsEmitted = [];
331
+ let skipped = 0;
332
+ const pkgScope = backend.newPackageScope?.();
333
+ for (const app of pkg.apps) {
334
+ if (app.sourceFormat && !SUPPORTED_FORMATS.has(app.sourceFormat)) {
335
+ skipped++;
336
+ if (recordWarnings) {
337
+ if (result.stats) result.stats.appsSkipped++;
338
+ result.warnings.push(`${app.sourcePath}: skipped (unsupported source format "${app.sourceFormat}")`);
339
+ }
340
+ continue;
341
+ }
342
+ const ctx = readAndCompile(app.sourcePath, app.sourceFormat, pkg.meta, catalog.meta, result);
343
+ if (!ctx) {
344
+ if (recordWarnings && result.stats) result.stats.appsFailed++;
345
+ continue;
346
+ }
347
+ let emitted;
348
+ try {
349
+ emitted = backend.emitApp(ctx, pkgScope);
350
+ } catch (e) {
351
+ result.errors.push(`[${backend.name} ${pkg.meta.name}/${app.name}] ${errMsg(e)}`);
352
+ if (recordWarnings && result.stats) result.stats.appsFailed++;
353
+ continue;
354
+ }
355
+ appendEmitMessages(result, emitted, backend, `${pkg.meta.name}/${app.name}`);
356
+ appsEmitted.push(emitted);
357
+ if (recordWarnings && result.stats) result.stats.appsCompiled++;
358
+ for (const [name, content] of emitted.files) result.files.push({
359
+ path: node_path.join(backendRoot, pkgDir, name),
360
+ content
361
+ });
362
+ }
363
+ if (appsEmitted.length === 0) {
364
+ if (recordWarnings && skipped > 0 && skipped === pkg.apps.length) result.warnings.push(`${pkgDir}: all ${skipped} tool(s) skipped, package omitted`);
365
+ continue;
366
+ }
367
+ if (mode !== "scripts" && backend.emitPackage) try {
368
+ const pkgEmit = backend.emitPackage(pkg.meta, appsEmitted);
369
+ appendEmitMessages(result, pkgEmit, backend, pkg.meta.name);
370
+ packagesEmitted.push(pkgEmit);
371
+ for (const [name, content] of pkgEmit.files) result.files.push({
372
+ path: node_path.join(backendRoot, pkgDir, name),
373
+ content
374
+ });
375
+ } catch (e) {
376
+ result.errors.push(`[${backend.name} ${pkgDir}] package emit failed: ${errMsg(e)}`);
377
+ }
378
+ }
379
+ if (mode === "multi" && backend.emitProject && packagesEmitted.length > 0) try {
380
+ const projEmit = backend.emitProject(catalog.meta, packagesEmitted);
381
+ appendEmitMessages(result, projEmit, backend, catalog.meta.name);
382
+ for (const [name, content] of projEmit.files) result.files.push({
383
+ path: node_path.join(backendRoot, name),
384
+ content
385
+ });
386
+ } catch (e) {
387
+ result.errors.push(`[${backend.name}] project emit failed: ${errMsg(e)}`);
388
+ }
389
+ }
390
+ function appendEmitMessages(result, emit, backend, scope) {
391
+ const tag = scope ? `[${backend.name} ${scope}]` : `[${backend.name}]`;
392
+ for (const e of emit.errors) result.errors.push(`${tag} ${e.message}`);
393
+ for (const w of emit.warnings) result.warnings.push(`${tag} ${w.message}`);
394
+ }
395
+
396
+ //#endregion
397
+ //#region src/backends.ts
398
+ /**
399
+ * Registry of backends keyed by CLI alias. The alias is what users pass to
400
+ * `-b`; the backend's `name`/`target` is what we use for the output subdir.
401
+ */
402
+ const registry = {
403
+ python: () => new _styx_api_core.PythonBackend(),
404
+ typescript: () => new _styx_api_core.TypeScriptBackend(),
405
+ ts: () => new _styx_api_core.TypeScriptBackend(),
406
+ schema: () => new _styx_api_core.JsonSchemaBackend(),
407
+ "json-schema": () => new _styx_api_core.JsonSchemaBackend(),
408
+ boutiques: () => new _styx_api_core.BoutiquesBackend()
409
+ };
410
+ const knownBackends = Object.keys(registry);
411
+ function resolveBackends(names) {
412
+ const backends = [];
413
+ const seen = /* @__PURE__ */ new Set();
414
+ const unknown = [];
415
+ for (const name of names) {
416
+ const factory = registry[name];
417
+ if (!factory) {
418
+ unknown.push(name);
419
+ continue;
420
+ }
421
+ const backend = factory();
422
+ if (seen.has(backend.name)) continue;
423
+ seen.add(backend.name);
424
+ backends.push(backend);
425
+ }
426
+ return {
427
+ backends,
428
+ unknown
429
+ };
430
+ }
431
+
432
+ //#endregion
433
+ //#region src/write.ts
434
+ function writeFiles(files) {
435
+ for (const file of files) {
436
+ (0, node_fs.mkdirSync)(node_path.dirname(file.path), { recursive: true });
437
+ (0, node_fs.writeFileSync)(file.path, file.content, "utf8");
438
+ }
439
+ }
440
+
441
+ //#endregion
442
+ Object.defineProperty(exports, '__toESM', {
443
+ enumerable: true,
444
+ get: function () {
445
+ return __toESM;
446
+ }
447
+ });
448
+ Object.defineProperty(exports, 'build', {
449
+ enumerable: true,
450
+ get: function () {
451
+ return build;
452
+ }
453
+ });
454
+ Object.defineProperty(exports, 'detectLevel', {
455
+ enumerable: true,
456
+ get: function () {
457
+ return detectLevel;
458
+ }
459
+ });
460
+ Object.defineProperty(exports, 'knownBackends', {
461
+ enumerable: true,
462
+ get: function () {
463
+ return knownBackends;
464
+ }
465
+ });
466
+ Object.defineProperty(exports, 'loadCatalog', {
467
+ enumerable: true,
468
+ get: function () {
469
+ return loadCatalog;
470
+ }
471
+ });
472
+ Object.defineProperty(exports, 'resolveBackends', {
473
+ enumerable: true,
474
+ get: function () {
475
+ return resolveBackends;
476
+ }
477
+ });
478
+ Object.defineProperty(exports, 'writeFiles', {
479
+ enumerable: true,
480
+ get: function () {
481
+ return writeFiles;
482
+ }
483
+ });