@styx-api/core 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/dist/index.cjs +7947 -0
- package/dist/index.d.cts +1143 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +1143 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +7877 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +55 -0
- package/src/backend/backend.ts +95 -0
- package/src/backend/boutiques/boutiques.ts +1049 -0
- package/src/backend/boutiques/index.ts +1 -0
- package/src/backend/code-builder.ts +49 -0
- package/src/backend/collect-field-info.ts +50 -0
- package/src/backend/collect-named-types.ts +103 -0
- package/src/backend/collect-output-fields.ts +222 -0
- package/src/backend/find-doc.ts +38 -0
- package/src/backend/find-struct-node.ts +66 -0
- package/src/backend/index.ts +39 -0
- package/src/backend/python/arg-builder.ts +454 -0
- package/src/backend/python/emit.ts +638 -0
- package/src/backend/python/index.ts +9 -0
- package/src/backend/python/outputs-emit.ts +430 -0
- package/src/backend/python/packaging.ts +173 -0
- package/src/backend/python/python.ts +558 -0
- package/src/backend/python/snippet.ts +84 -0
- package/src/backend/python/typemap.ts +131 -0
- package/src/backend/python/types.ts +8 -0
- package/src/backend/python/validate-emit.ts +356 -0
- package/src/backend/resolve-field-binding.ts +41 -0
- package/src/backend/resolve-output-tokens.ts +80 -0
- package/src/backend/schema/index.ts +2 -0
- package/src/backend/schema/jsonschema.ts +303 -0
- package/src/backend/scope.ts +50 -0
- package/src/backend/sig-entries.ts +97 -0
- package/src/backend/snippet-core.ts +185 -0
- package/src/backend/string-case.ts +30 -0
- package/src/backend/styxdefs-compat.ts +21 -0
- package/src/backend/type-keys.ts +52 -0
- package/src/backend/typescript/arg-builder.ts +420 -0
- package/src/backend/typescript/emit.ts +450 -0
- package/src/backend/typescript/index.ts +10 -0
- package/src/backend/typescript/outputs-emit.ts +389 -0
- package/src/backend/typescript/packaging.ts +130 -0
- package/src/backend/typescript/snippet.ts +60 -0
- package/src/backend/typescript/typemap.ts +47 -0
- package/src/backend/typescript/types.ts +8 -0
- package/src/backend/typescript/typescript.ts +507 -0
- package/src/backend/typescript/validate-emit.ts +341 -0
- package/src/backend/union-variants.ts +42 -0
- package/src/backend/validate-walk.ts +111 -0
- package/src/bindings/binding.ts +77 -0
- package/src/bindings/format.ts +176 -0
- package/src/bindings/index.ts +16 -0
- package/src/bindings/output-gate.ts +50 -0
- package/src/bindings/resolved-output.ts +56 -0
- package/src/bindings/types.ts +16 -0
- package/src/frontend/argdump/index.ts +1 -0
- package/src/frontend/argdump/parser.ts +914 -0
- package/src/frontend/boutiques/destruct-template.ts +50 -0
- package/src/frontend/boutiques/index.ts +1 -0
- package/src/frontend/boutiques/parser.ts +676 -0
- package/src/frontend/boutiques/split-command.ts +69 -0
- package/src/frontend/detect-format.ts +42 -0
- package/src/frontend/frontend.ts +31 -0
- package/src/frontend/index.ts +9 -0
- package/src/frontend/workbench/index.ts +1 -0
- package/src/frontend/workbench/parser.ts +351 -0
- package/src/index.ts +41 -0
- package/src/ir/builders.ts +69 -0
- package/src/ir/format.ts +157 -0
- package/src/ir/index.ts +32 -0
- package/src/ir/meta.ts +91 -0
- package/src/ir/node.ts +95 -0
- package/src/ir/passes/canonicalize.ts +108 -0
- package/src/ir/passes/flatten.ts +73 -0
- package/src/ir/passes/index.ts +7 -0
- package/src/ir/passes/pass.ts +86 -0
- package/src/ir/passes/pipeline.ts +21 -0
- package/src/ir/passes/remove-empty.ts +76 -0
- package/src/ir/passes/simplify.ts +179 -0
- package/src/ir/types.ts +15 -0
- package/src/manifest/context.ts +36 -0
- package/src/manifest/index.ts +3 -0
- package/src/manifest/types.ts +15 -0
- package/src/solver/assign-access.ts +218 -0
- package/src/solver/index.ts +4 -0
- package/src/solver/resolve-outputs.ts +233 -0
- package/src/solver/solver.ts +319 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import type { AccessPath, BindingId, BoundType } from "../../bindings/index.js";
|
|
2
|
+
import type { CodegenContext } from "../../manifest/index.js";
|
|
3
|
+
import { CodeBuilder } from "../code-builder.js";
|
|
4
|
+
import type { SigEntry, SigOptions } from "../sig-entries.js";
|
|
5
|
+
import type { ArgResult } from "./arg-builder.js";
|
|
6
|
+
import { buildArgs, resultToStmt } from "./arg-builder.js";
|
|
7
|
+
import { mapType, renderTsLiteral } from "./typemap.js";
|
|
8
|
+
import type { NamedType } from "./types.js";
|
|
9
|
+
import { collectFieldInfo, resolveTypeName } from "./types.js";
|
|
10
|
+
|
|
11
|
+
export function emitJsDoc(cb: CodeBuilder, description?: string): void {
|
|
12
|
+
if (!description) return;
|
|
13
|
+
const lines = description.split("\n");
|
|
14
|
+
if (lines.length === 1) {
|
|
15
|
+
cb.line(`/** ${lines[0]} */`);
|
|
16
|
+
} else {
|
|
17
|
+
cb.line("/**");
|
|
18
|
+
for (const line of lines) {
|
|
19
|
+
cb.line(` * ${line}`);
|
|
20
|
+
}
|
|
21
|
+
cb.line(" */");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function emitImports(cb: CodeBuilder, emitOutputs: boolean): void {
|
|
26
|
+
const inputs = ["Runner", "Execution", "Metadata", "InputPathType"];
|
|
27
|
+
if (emitOutputs) inputs.push("OutputPathType");
|
|
28
|
+
cb.line(`import type { ${inputs.join(", ")} } from "styxdefs";`);
|
|
29
|
+
cb.line('import { getGlobalRunner, StyxValidationError } from "styxdefs";');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function emitMetadata(ctx: CodegenContext, metaConst: string, cb: CodeBuilder): void {
|
|
33
|
+
const id = ctx.app?.id ?? "unknown";
|
|
34
|
+
const name = ctx.app?.doc?.title ?? ctx.app?.id ?? "unknown";
|
|
35
|
+
const pkg = ctx.package?.name ?? "unknown";
|
|
36
|
+
|
|
37
|
+
cb.line(`export const ${metaConst}: Metadata = {`);
|
|
38
|
+
cb.indent(() => {
|
|
39
|
+
cb.line(`id: ${JSON.stringify(id)},`);
|
|
40
|
+
cb.line(`name: ${JSON.stringify(name)},`);
|
|
41
|
+
cb.line(`package: ${JSON.stringify(pkg)},`);
|
|
42
|
+
if (ctx.app?.doc?.literature?.length) {
|
|
43
|
+
cb.line(`citations: ${JSON.stringify(ctx.app.doc.literature)},`);
|
|
44
|
+
}
|
|
45
|
+
if (ctx.app?.container?.image) {
|
|
46
|
+
cb.line(`container_image_tag: ${JSON.stringify(ctx.app.container.image)},`);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
cb.line("};");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function emitTypeDeclarations(
|
|
53
|
+
typeDecls: NamedType[],
|
|
54
|
+
namedTypes: Map<string, string>,
|
|
55
|
+
ctx: CodegenContext,
|
|
56
|
+
rootName: string,
|
|
57
|
+
appId: string | undefined,
|
|
58
|
+
pkg: string,
|
|
59
|
+
cb: CodeBuilder,
|
|
60
|
+
): void {
|
|
61
|
+
const resolve = resolveTypeName(namedTypes);
|
|
62
|
+
|
|
63
|
+
for (const { name, type } of typeDecls) {
|
|
64
|
+
const isRoot = name === rootName;
|
|
65
|
+
|
|
66
|
+
if (type.kind === "struct") {
|
|
67
|
+
const fieldInfo = collectFieldInfo(ctx, type);
|
|
68
|
+
|
|
69
|
+
cb.line(`export interface ${name} {`);
|
|
70
|
+
cb.indent(() => {
|
|
71
|
+
// @type discriminator on root params interface. Optional: the params
|
|
72
|
+
// factory always sets it and runtime dispatch tables read it, but the
|
|
73
|
+
// type system doesn't need it required (there's only one shape). Union
|
|
74
|
+
// variants below keep their @type required - that one IS load-bearing
|
|
75
|
+
// for discriminated-union narrowing.
|
|
76
|
+
if (isRoot && appId) {
|
|
77
|
+
cb.line(`"@type"?: "${pkg}/${appId}";`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (const [fieldName, fieldType] of Object.entries(type.fields)) {
|
|
81
|
+
// Emit literal @type discriminators on union variant structs
|
|
82
|
+
if (fieldType.kind === "literal") {
|
|
83
|
+
if (fieldName === "@type") {
|
|
84
|
+
cb.line(`"@type": ${JSON.stringify(fieldType.value)};`);
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const fi = fieldInfo.get(fieldName);
|
|
89
|
+
emitJsDoc(cb, fi?.doc);
|
|
90
|
+
|
|
91
|
+
const isOptional = fieldType.kind === "optional";
|
|
92
|
+
// A field is omittable (key may be absent) iff it is `optional` or it
|
|
93
|
+
// carries a default (which includes flags - their `defaultValue` is
|
|
94
|
+
// false). The default/absent case is handled by the params factory and
|
|
95
|
+
// the absence-safe runtime reads; the type only needs to permit the key
|
|
96
|
+
// to be missing. Never `| null`: the solver has no nullable, so a
|
|
97
|
+
// present value is never null - omittability is the only "unset".
|
|
98
|
+
const omittable = isOptional || fi?.defaultValue !== undefined;
|
|
99
|
+
const optional = omittable ? "?" : "";
|
|
100
|
+
|
|
101
|
+
const mapped = isOptional
|
|
102
|
+
? mapType(fieldType.inner, resolve)
|
|
103
|
+
: mapType(fieldType, resolve);
|
|
104
|
+
// Quote keys that aren't valid identifiers (e.g. `4d_input`). TS
|
|
105
|
+
// allows reserved-word identifiers as interface keys without quotes.
|
|
106
|
+
cb.line(`${tsObjKey(fieldName)}${optional}: ${mapped};`);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
cb.line("}");
|
|
110
|
+
cb.blank();
|
|
111
|
+
} else if (type.kind === "union") {
|
|
112
|
+
const parts = type.variants.map((v) => mapType(v.type, resolve));
|
|
113
|
+
cb.line(`export type ${name} = ${parts.join(" | ")};`);
|
|
114
|
+
cb.blank();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function emitBuildCargs(
|
|
120
|
+
ctx: CodegenContext,
|
|
121
|
+
rootType: BoundType,
|
|
122
|
+
paramsType: string,
|
|
123
|
+
funcName: string,
|
|
124
|
+
cb: CodeBuilder,
|
|
125
|
+
): void {
|
|
126
|
+
const paramsVar = "params";
|
|
127
|
+
|
|
128
|
+
let result: ArgResult;
|
|
129
|
+
try {
|
|
130
|
+
result = buildArgs(ctx.expr, ctx, rootType);
|
|
131
|
+
} catch {
|
|
132
|
+
emitJsDoc(cb, "Build command-line arguments from parameters.");
|
|
133
|
+
cb.line(
|
|
134
|
+
`export function ${funcName}(_${paramsVar}: ${paramsType}, _execution: Execution): string[] {`,
|
|
135
|
+
);
|
|
136
|
+
cb.indent(() => cb.line("return [];"));
|
|
137
|
+
cb.line("}");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const argsCode = resultToStmt(result);
|
|
142
|
+
|
|
143
|
+
emitJsDoc(cb, "Build command-line arguments from parameters.");
|
|
144
|
+
cb.line(
|
|
145
|
+
`export function ${funcName}(${paramsVar}: ${paramsType}, execution: Execution): string[] {`,
|
|
146
|
+
);
|
|
147
|
+
cb.indent(() => {
|
|
148
|
+
cb.line("const cargs: string[] = [];");
|
|
149
|
+
for (const line of argsCode.split("\n")) {
|
|
150
|
+
if (line.trim()) cb.line(line);
|
|
151
|
+
}
|
|
152
|
+
cb.line("return cargs;");
|
|
153
|
+
});
|
|
154
|
+
cb.line("}");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* SigOptions hooks for TypeScript. The kwarg-signature sentinel for an optional
|
|
159
|
+
* param is `T | null = null` - here `| null` is the *parameter* type (the
|
|
160
|
+
* "not provided" sentinel a caller passes), not the dict field type, which is
|
|
161
|
+
* just `T?`. Keeping the sentinel keeps the ergonomic `foo(x)` call where
|
|
162
|
+
* omitted optionals default to `null` and the factory then drops them.
|
|
163
|
+
*/
|
|
164
|
+
export function tsSigOptions(resolve: (t: BoundType) => string | undefined): SigOptions {
|
|
165
|
+
return {
|
|
166
|
+
renderType: (t) => mapType(t, resolve),
|
|
167
|
+
nullableSuffix: " | null",
|
|
168
|
+
nullableDefault: "null",
|
|
169
|
+
renderDefault: renderTsLiteral,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Scrub a Boutiques wire name into a valid TypeScript host identifier.
|
|
175
|
+
* Mirrors Python's scrub: non-`[A-Za-z0-9_$]` replaced with `_`, digit
|
|
176
|
+
* prefixes get `v_`, reserved-word matches get a trailing `_`. Dedupe through
|
|
177
|
+
* a `Scope` for collisions with already-registered locals.
|
|
178
|
+
*/
|
|
179
|
+
export function tsScrubIdent(name: string, reserved: ReadonlySet<string>): string {
|
|
180
|
+
let scrubbed = name.replace(/[^A-Za-z0-9_$]/g, "_");
|
|
181
|
+
if (/^[0-9]/.test(scrubbed)) scrubbed = "v_" + scrubbed;
|
|
182
|
+
if (scrubbed === "") scrubbed = "_";
|
|
183
|
+
if (reserved.has(scrubbed)) scrubbed = scrubbed + "_";
|
|
184
|
+
return scrubbed;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const TS_IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Render a TypeScript property access path. Uses dot notation when the key is
|
|
191
|
+
* a valid identifier, bracket notation otherwise. (Wire keys like `4d_input`
|
|
192
|
+
* or `@type` can't be dot-accessed.)
|
|
193
|
+
*/
|
|
194
|
+
export function tsPropAccess(base: string, key: string): string {
|
|
195
|
+
return TS_IDENT_RE.test(key) ? `${base}.${key}` : `${base}[${JSON.stringify(key)}]`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Render a solver-assigned `AccessPath` to a TypeScript expression. Starts from
|
|
200
|
+
* `params`; each `field` segment descends a property, and each `iter` segment
|
|
201
|
+
* resets the base to the loop variable bound to that repeat binding (resolved
|
|
202
|
+
* via `lookupLoopVar` - the `iter` gate atom's loop in outputs codegen, or the
|
|
203
|
+
* arg-builder's local loop). Both the arg-builder and the outputs emitter feed
|
|
204
|
+
* this one function instead of re-deriving paths.
|
|
205
|
+
*/
|
|
206
|
+
export function renderAccess(
|
|
207
|
+
path: AccessPath,
|
|
208
|
+
lookupLoopVar: (binding: BindingId) => string,
|
|
209
|
+
): string {
|
|
210
|
+
let cur = "params";
|
|
211
|
+
for (const seg of path) {
|
|
212
|
+
cur = seg.kind === "field" ? tsPropAccess(cur, seg.name) : lookupLoopVar(seg.binding);
|
|
213
|
+
}
|
|
214
|
+
return cur;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Render a TypeScript object-literal key. Bare ident when valid, quoted
|
|
219
|
+
* otherwise. (`name: ...` for `name`, `"4d_input": ...` for `4d_input`.)
|
|
220
|
+
*/
|
|
221
|
+
export function tsObjKey(key: string): string {
|
|
222
|
+
return TS_IDENT_RE.test(key) ? key : JSON.stringify(key);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Emit `name: type [= default],` lines (one per entry) into `cb`. */
|
|
226
|
+
function emitSigParams(entries: readonly SigEntry[], cb: CodeBuilder): void {
|
|
227
|
+
for (const e of entries) {
|
|
228
|
+
if (e.sigDefault !== undefined) {
|
|
229
|
+
cb.line(`${e.name}: ${e.sigType} = ${e.sigDefault},`);
|
|
230
|
+
} else {
|
|
231
|
+
cb.line(`${e.name}: ${e.sigType},`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Emit `@param <name> <doc>` JSDoc lines (trimmed empty docs) for each entry. */
|
|
237
|
+
function emitJsDocParams(
|
|
238
|
+
entries: readonly { name: string; doc?: string }[],
|
|
239
|
+
cb: CodeBuilder,
|
|
240
|
+
): void {
|
|
241
|
+
for (const e of entries) {
|
|
242
|
+
cb.line(` * @param ${e.name} ${e.doc ?? ""}`.trimEnd());
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Emit the `<tool>Params(...)` factory: a kwarg-style builder for the params
|
|
248
|
+
* object. Non-optional fields (required, and defaulted scalars whose default
|
|
249
|
+
* lives on the signature) are always set in the literal. Every optional field is
|
|
250
|
+
* set conditionally when not null - including optional-with-default ones: their
|
|
251
|
+
* value type is `T | null` (the omit sentinel) but the dict field is the
|
|
252
|
+
* non-null `T?`, so a bare literal assignment of a possibly-null value would not
|
|
253
|
+
* type-check. When the caller omits the arg, the signature default (a concrete
|
|
254
|
+
* non-null value) flows through the guard and is written.
|
|
255
|
+
*/
|
|
256
|
+
export function emitParamsFactory(
|
|
257
|
+
entries: readonly SigEntry[],
|
|
258
|
+
funcName: string,
|
|
259
|
+
paramsType: string,
|
|
260
|
+
typeTag: string | undefined,
|
|
261
|
+
cb: CodeBuilder,
|
|
262
|
+
): void {
|
|
263
|
+
// JSDoc
|
|
264
|
+
cb.line("/**");
|
|
265
|
+
cb.line(" * Build parameters.");
|
|
266
|
+
if (entries.length > 0) cb.line(" *");
|
|
267
|
+
emitJsDocParams(entries, cb);
|
|
268
|
+
cb.line(" *");
|
|
269
|
+
cb.line(" * @returns Parameter object.");
|
|
270
|
+
cb.line(" */");
|
|
271
|
+
|
|
272
|
+
if (entries.length === 0) {
|
|
273
|
+
cb.line(`export function ${funcName}(): ${paramsType} {`);
|
|
274
|
+
} else {
|
|
275
|
+
cb.line(`export function ${funcName}(`);
|
|
276
|
+
cb.indent(() => emitSigParams(entries, cb));
|
|
277
|
+
cb.line(`): ${paramsType} {`);
|
|
278
|
+
}
|
|
279
|
+
cb.indent(() => {
|
|
280
|
+
cb.line(`const params: ${paramsType} = {`);
|
|
281
|
+
cb.indent(() => {
|
|
282
|
+
if (typeTag !== undefined) cb.line(`"@type": ${JSON.stringify(typeTag)},`);
|
|
283
|
+
for (const e of entries) {
|
|
284
|
+
if (!e.isOptional) {
|
|
285
|
+
cb.line(`${tsObjKey(e.wireKey)}: ${e.name},`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
cb.line("};");
|
|
290
|
+
for (const e of entries) {
|
|
291
|
+
if (e.isOptional) {
|
|
292
|
+
cb.line(`if (${e.name} !== null) {`);
|
|
293
|
+
cb.indent(() => cb.line(`${tsPropAccess("params", e.wireKey)} = ${e.name};`));
|
|
294
|
+
cb.line("}");
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
cb.line("return params;");
|
|
298
|
+
});
|
|
299
|
+
cb.line("}");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Emit the user-facing kwarg wrapper: takes the same kwargs as the factory
|
|
304
|
+
* plus `runner`, builds the params object, and delegates to the dict-style
|
|
305
|
+
* execute function.
|
|
306
|
+
*/
|
|
307
|
+
export function emitKwargWrapper(
|
|
308
|
+
ctx: CodegenContext,
|
|
309
|
+
entries: readonly SigEntry[],
|
|
310
|
+
funcName: string,
|
|
311
|
+
paramsFnName: string,
|
|
312
|
+
executeFnName: string,
|
|
313
|
+
outputsType: string | undefined,
|
|
314
|
+
cb: CodeBuilder,
|
|
315
|
+
): void {
|
|
316
|
+
const appDoc = ctx.app?.doc;
|
|
317
|
+
cb.line("/**");
|
|
318
|
+
if (appDoc?.title) cb.line(` * ${appDoc.title}`);
|
|
319
|
+
if (appDoc?.description) {
|
|
320
|
+
if (appDoc?.title) cb.line(" *");
|
|
321
|
+
cb.line(` * ${appDoc.description}`);
|
|
322
|
+
}
|
|
323
|
+
if (appDoc?.authors?.length) {
|
|
324
|
+
cb.line(" *");
|
|
325
|
+
cb.line(` * Author: ${appDoc.authors.join(", ")}`);
|
|
326
|
+
}
|
|
327
|
+
if (appDoc?.urls?.length) {
|
|
328
|
+
cb.line(" *");
|
|
329
|
+
cb.line(` * URL: ${appDoc.urls[0]}`);
|
|
330
|
+
}
|
|
331
|
+
cb.line(" *");
|
|
332
|
+
emitJsDocParams(
|
|
333
|
+
[...entries, { name: "runner", doc: "Command runner (defaults to global runner)." }],
|
|
334
|
+
cb,
|
|
335
|
+
);
|
|
336
|
+
cb.line(" *");
|
|
337
|
+
cb.line(
|
|
338
|
+
outputsType
|
|
339
|
+
? " * @returns Tool outputs (paths to files produced by the tool)."
|
|
340
|
+
: " * @returns void",
|
|
341
|
+
);
|
|
342
|
+
cb.line(" */");
|
|
343
|
+
|
|
344
|
+
const returnType = outputsType ?? "void";
|
|
345
|
+
cb.line(`export function ${funcName}(`);
|
|
346
|
+
cb.indent(() => {
|
|
347
|
+
emitSigParams(entries, cb);
|
|
348
|
+
cb.line("runner: Runner | null = null,");
|
|
349
|
+
});
|
|
350
|
+
cb.line(`): ${returnType} {`);
|
|
351
|
+
|
|
352
|
+
cb.indent(() => {
|
|
353
|
+
if (entries.length === 0) {
|
|
354
|
+
cb.line(`const params = ${paramsFnName}();`);
|
|
355
|
+
} else {
|
|
356
|
+
cb.line(`const params = ${paramsFnName}(`);
|
|
357
|
+
cb.indent(() => {
|
|
358
|
+
for (const e of entries) cb.line(`${e.name},`);
|
|
359
|
+
});
|
|
360
|
+
cb.line(");");
|
|
361
|
+
}
|
|
362
|
+
if (outputsType) {
|
|
363
|
+
cb.line(`return ${executeFnName}(params, runner);`);
|
|
364
|
+
} else {
|
|
365
|
+
cb.line(`${executeFnName}(params, runner);`);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
cb.line("}");
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function emitWrapperFunction(
|
|
372
|
+
ctx: CodegenContext,
|
|
373
|
+
paramsType: string,
|
|
374
|
+
funcName: string,
|
|
375
|
+
metaConst: string,
|
|
376
|
+
cargsFunc: string,
|
|
377
|
+
outputsFunc: string | undefined,
|
|
378
|
+
outputsType: string | undefined,
|
|
379
|
+
validateFunc: string | undefined,
|
|
380
|
+
streams: { stdout?: string; stderr?: string },
|
|
381
|
+
cb: CodeBuilder,
|
|
382
|
+
): void {
|
|
383
|
+
const appDoc = ctx.app?.doc;
|
|
384
|
+
const docLines: string[] = [];
|
|
385
|
+
if (appDoc?.title) docLines.push(appDoc.title);
|
|
386
|
+
if (appDoc?.description) {
|
|
387
|
+
if (docLines.length > 0) docLines.push("");
|
|
388
|
+
docLines.push(appDoc.description);
|
|
389
|
+
}
|
|
390
|
+
if (appDoc?.authors?.length) {
|
|
391
|
+
docLines.push("");
|
|
392
|
+
docLines.push(`Author: ${appDoc.authors.join(", ")}`);
|
|
393
|
+
}
|
|
394
|
+
if (appDoc?.urls?.length) {
|
|
395
|
+
docLines.push("");
|
|
396
|
+
docLines.push(`URL: ${appDoc.urls[0]}`);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const emitOutputs = outputsFunc !== undefined;
|
|
400
|
+
|
|
401
|
+
if (docLines.length > 0) {
|
|
402
|
+
cb.line("/**");
|
|
403
|
+
for (const line of docLines) {
|
|
404
|
+
cb.line(` * ${line}`);
|
|
405
|
+
}
|
|
406
|
+
cb.line(" *");
|
|
407
|
+
cb.line(" * @param params - The parameters.");
|
|
408
|
+
cb.line(" * @param runner - Command runner (defaults to global runner).");
|
|
409
|
+
if (emitOutputs) cb.line(" * @returns Tool outputs (paths to files produced by the tool).");
|
|
410
|
+
cb.line(" */");
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const returnType = emitOutputs && outputsType ? outputsType : "void";
|
|
414
|
+
cb.line(
|
|
415
|
+
`export function ${funcName}(params: ${paramsType}, runner: Runner | null = null): ${returnType} {`,
|
|
416
|
+
);
|
|
417
|
+
cb.indent(() => {
|
|
418
|
+
// Validate the params object first (the kwarg wrapper delegates here, so it
|
|
419
|
+
// gets validation transitively; the statically-typed kwargs don't need it).
|
|
420
|
+
if (validateFunc) cb.line(`${validateFunc}(params);`);
|
|
421
|
+
cb.line("runner = runner ?? getGlobalRunner();");
|
|
422
|
+
cb.line(`const execution = runner.startExecution(${metaConst});`);
|
|
423
|
+
cb.line("execution.params(params);");
|
|
424
|
+
// Local names `args`/`out` avoid colliding with the module-level `cargs` /
|
|
425
|
+
// `outputs` functions when they share generic names.
|
|
426
|
+
cb.line(`const args = ${cargsFunc}(params, execution);`);
|
|
427
|
+
if (emitOutputs) {
|
|
428
|
+
cb.line(`const out = ${outputsFunc}(params, execution);`);
|
|
429
|
+
const { stdout, stderr } = streams;
|
|
430
|
+
if (stdout || stderr) {
|
|
431
|
+
// run(cargs, handleStdout?, handleStderr?): pass `undefined` for an
|
|
432
|
+
// absent stdout handler so a stderr-only handler lands in position 3.
|
|
433
|
+
const runArgs = ["args"];
|
|
434
|
+
runArgs.push(
|
|
435
|
+
stdout ? `(s: string): void => { ${tsPropAccess("out", stdout)}.push(s); }` : "undefined",
|
|
436
|
+
);
|
|
437
|
+
if (stderr) {
|
|
438
|
+
runArgs.push(`(s: string): void => { ${tsPropAccess("out", stderr)}.push(s); }`);
|
|
439
|
+
}
|
|
440
|
+
cb.line(`execution.run(${runArgs.join(", ")});`);
|
|
441
|
+
} else {
|
|
442
|
+
cb.line("execution.run(args);");
|
|
443
|
+
}
|
|
444
|
+
cb.line("return out;");
|
|
445
|
+
} else {
|
|
446
|
+
cb.line("execution.run(args);");
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
cb.line("}");
|
|
450
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type { PublicNames } from "./typescript.js";
|
|
2
|
+
export { mapType } from "./typemap.js";
|
|
3
|
+
export {
|
|
4
|
+
appModuleName,
|
|
5
|
+
computePublicNames,
|
|
6
|
+
generatePackageIndex,
|
|
7
|
+
generateTypeScript,
|
|
8
|
+
TypeScriptBackend,
|
|
9
|
+
} from "./typescript.js";
|
|
10
|
+
export { renderTypeScriptCall } from "./snippet.js";
|