@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.
- package/dist/bin.cjs +109 -0
- package/dist/bin.d.cts +1 -0
- package/dist/bin.d.mts +1 -0
- package/dist/bin.mjs +111 -0
- package/dist/bin.mjs.map +1 -0
- package/dist/index.cjs +8 -0
- package/dist/index.d.cts +91 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +91 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/write-BtRGJfHV.cjs +483 -0
- package/dist/write-DSW1zlom.mjs +415 -0
- package/dist/write-DSW1zlom.mjs.map +1 -0
- package/package.json +58 -0
- package/src/__snapshots__/greet.py +131 -0
- package/src/__snapshots__/greet.ts +123 -0
- package/src/backends.ts +40 -0
- package/src/bin.ts +35 -0
- package/src/build.ts +297 -0
- package/src/catalog.ts +268 -0
- package/src/command.ts +89 -0
- package/src/index.ts +6 -0
- package/src/write.ts +11 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// This file was auto generated by Styx.
|
|
2
|
+
// Do not edit this file directly.
|
|
3
|
+
|
|
4
|
+
import type { Runner, Execution, Metadata, InputPathType, OutputPathType } from "styxdefs";
|
|
5
|
+
import { getGlobalRunner, StyxValidationError } from "styxdefs";
|
|
6
|
+
|
|
7
|
+
export const GREET_METADATA: Metadata = {
|
|
8
|
+
id: "greet",
|
|
9
|
+
name: "greet",
|
|
10
|
+
package: "unknown",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export interface Greet {
|
|
14
|
+
"@type"?: "unknown/greet";
|
|
15
|
+
/** Person to greet. */
|
|
16
|
+
name: string;
|
|
17
|
+
/** Shout the greeting. */
|
|
18
|
+
loud?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface GreetOutputs {
|
|
22
|
+
root: OutputPathType;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Build parameters.
|
|
27
|
+
*
|
|
28
|
+
* @param name Person to greet.
|
|
29
|
+
* @param loud Shout the greeting.
|
|
30
|
+
*
|
|
31
|
+
* @returns Parameter object.
|
|
32
|
+
*/
|
|
33
|
+
export function greetParams(
|
|
34
|
+
name: string,
|
|
35
|
+
loud: boolean = false,
|
|
36
|
+
): Greet {
|
|
37
|
+
const params: Greet = {
|
|
38
|
+
"@type": "unknown/greet",
|
|
39
|
+
name: name,
|
|
40
|
+
loud: loud,
|
|
41
|
+
};
|
|
42
|
+
return params;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Validate untrusted parameters. Throws StyxValidationError if `params` is not a valid Greet; narrows it to Greet on success. */
|
|
46
|
+
export function greetValidate(params: any): asserts params is Greet {
|
|
47
|
+
if (typeof params !== "object" || params === null) {
|
|
48
|
+
throw new StyxValidationError("Params object has the wrong type");
|
|
49
|
+
}
|
|
50
|
+
if (params.name == null) {
|
|
51
|
+
throw new StyxValidationError("`name` must not be null");
|
|
52
|
+
}
|
|
53
|
+
if (typeof params.name !== "string") {
|
|
54
|
+
throw new StyxValidationError("`name` has the wrong type (expected string)");
|
|
55
|
+
}
|
|
56
|
+
if (params.loud != null) {
|
|
57
|
+
if (typeof params.loud !== "boolean") {
|
|
58
|
+
throw new StyxValidationError("`loud` has the wrong type (expected boolean)");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Build command-line arguments from parameters. */
|
|
64
|
+
export function greet_cargs(params: Greet, execution: Execution): string[] {
|
|
65
|
+
const cargs: string[] = [];
|
|
66
|
+
cargs.push("greet");
|
|
67
|
+
cargs.push(params.name);
|
|
68
|
+
if (params.loud) {
|
|
69
|
+
cargs.push("--loud");
|
|
70
|
+
}
|
|
71
|
+
return cargs;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function greet_outputs(params: Greet, execution: Execution): GreetOutputs {
|
|
75
|
+
const outputs: GreetOutputs = {
|
|
76
|
+
root: null!,
|
|
77
|
+
};
|
|
78
|
+
outputs.root = execution.outputFile(".");
|
|
79
|
+
return outputs;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* greet
|
|
84
|
+
*
|
|
85
|
+
* Print a greeting.
|
|
86
|
+
*
|
|
87
|
+
* @param params - The parameters.
|
|
88
|
+
* @param runner - Command runner (defaults to global runner).
|
|
89
|
+
* @returns Tool outputs (paths to files produced by the tool).
|
|
90
|
+
*/
|
|
91
|
+
export function greetExecute(params: Greet, runner: Runner | null = null): GreetOutputs {
|
|
92
|
+
greetValidate(params);
|
|
93
|
+
runner = runner ?? getGlobalRunner();
|
|
94
|
+
const execution = runner.startExecution(GREET_METADATA);
|
|
95
|
+
execution.params(params);
|
|
96
|
+
const args = greet_cargs(params, execution);
|
|
97
|
+
const out = greet_outputs(params, execution);
|
|
98
|
+
execution.run(args);
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* greet
|
|
104
|
+
*
|
|
105
|
+
* Print a greeting.
|
|
106
|
+
*
|
|
107
|
+
* @param name Person to greet.
|
|
108
|
+
* @param loud Shout the greeting.
|
|
109
|
+
* @param runner Command runner (defaults to global runner).
|
|
110
|
+
*
|
|
111
|
+
* @returns Tool outputs (paths to files produced by the tool).
|
|
112
|
+
*/
|
|
113
|
+
export function greet(
|
|
114
|
+
name: string,
|
|
115
|
+
loud: boolean = false,
|
|
116
|
+
runner: Runner | null = null,
|
|
117
|
+
): GreetOutputs {
|
|
118
|
+
const params = greetParams(
|
|
119
|
+
name,
|
|
120
|
+
loud,
|
|
121
|
+
);
|
|
122
|
+
return greetExecute(params, runner);
|
|
123
|
+
}
|
package/src/backends.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BoutiquesBackend,
|
|
3
|
+
JsonSchemaBackend,
|
|
4
|
+
PythonBackend,
|
|
5
|
+
TypeScriptBackend,
|
|
6
|
+
type Backend,
|
|
7
|
+
} from "@styx-api/core";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Registry of backends keyed by CLI alias. The alias is what users pass to
|
|
11
|
+
* `-b`; the backend's `name`/`target` is what we use for the output subdir.
|
|
12
|
+
*/
|
|
13
|
+
const registry: Record<string, () => Backend> = {
|
|
14
|
+
python: () => new PythonBackend(),
|
|
15
|
+
typescript: () => new TypeScriptBackend(),
|
|
16
|
+
ts: () => new TypeScriptBackend(),
|
|
17
|
+
schema: () => new JsonSchemaBackend(),
|
|
18
|
+
"json-schema": () => new JsonSchemaBackend(),
|
|
19
|
+
boutiques: () => new BoutiquesBackend(),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const knownBackends = Object.keys(registry);
|
|
23
|
+
|
|
24
|
+
export function resolveBackends(names: string[]): { backends: Backend[]; unknown: string[] } {
|
|
25
|
+
const backends: Backend[] = [];
|
|
26
|
+
const seen = new Set<string>();
|
|
27
|
+
const unknown: string[] = [];
|
|
28
|
+
for (const name of names) {
|
|
29
|
+
const factory = registry[name];
|
|
30
|
+
if (!factory) {
|
|
31
|
+
unknown.push(name);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const backend = factory();
|
|
35
|
+
if (seen.has(backend.name)) continue;
|
|
36
|
+
seen.add(backend.name);
|
|
37
|
+
backends.push(backend);
|
|
38
|
+
}
|
|
39
|
+
return { backends, unknown };
|
|
40
|
+
}
|
package/src/bin.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { cac } from "cac";
|
|
3
|
+
|
|
4
|
+
import { knownBackends } from "./backends.js";
|
|
5
|
+
import { runBuildCommand, type BuildFlags } from "./command.js";
|
|
6
|
+
import { writeFiles } from "./write.js";
|
|
7
|
+
|
|
8
|
+
const cli = cac("styx");
|
|
9
|
+
|
|
10
|
+
cli
|
|
11
|
+
.command("build [input]", "Compile a descriptor or catalog into target-language wrappers")
|
|
12
|
+
.option("-o, --out <dir>", "Output directory")
|
|
13
|
+
.option("--catalog <dir>", "Walk a niwrap-style catalog (project/package/version/app layers)")
|
|
14
|
+
.option(
|
|
15
|
+
"-b, --backend <name>",
|
|
16
|
+
`Backend to emit (repeatable, or comma-separated). Known: ${knownBackends.join(", ")}`,
|
|
17
|
+
{ default: "python" },
|
|
18
|
+
)
|
|
19
|
+
.option(
|
|
20
|
+
"-m, --mode <mode>",
|
|
21
|
+
"Which emit tiers run: scripts (app only) | single (+ package) | multi (+ project)",
|
|
22
|
+
{ default: "scripts" },
|
|
23
|
+
)
|
|
24
|
+
.action((input: string | undefined, flags: BuildFlags) => {
|
|
25
|
+
const result = runBuildCommand(input, flags);
|
|
26
|
+
for (const line of result.stderr) console.error(line);
|
|
27
|
+
if (result.exitCode === 0) writeFiles(result.files);
|
|
28
|
+
for (const line of result.stdout) console.log(line);
|
|
29
|
+
if (result.exitCode !== 0) process.exit(result.exitCode);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
cli.help();
|
|
33
|
+
cli.version("0.0.1");
|
|
34
|
+
|
|
35
|
+
cli.parse();
|
package/src/build.ts
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
compile,
|
|
6
|
+
createContext,
|
|
7
|
+
defaultPipeline,
|
|
8
|
+
resolveOutputs,
|
|
9
|
+
solve,
|
|
10
|
+
type Backend,
|
|
11
|
+
type EmitResult,
|
|
12
|
+
type EmittedApp,
|
|
13
|
+
type EmittedPackage,
|
|
14
|
+
type FormatName,
|
|
15
|
+
type PackageMeta,
|
|
16
|
+
type ProjectMeta,
|
|
17
|
+
} from "@styx-api/core";
|
|
18
|
+
|
|
19
|
+
import { loadCatalog, type CatalogProject } from "./catalog.js";
|
|
20
|
+
|
|
21
|
+
export type BuildMode = "scripts" | "single" | "multi";
|
|
22
|
+
|
|
23
|
+
/** Frontend formats the compiler can parse; others are skipped with a warning. */
|
|
24
|
+
const SUPPORTED_FORMATS: ReadonlySet<string> = new Set<FormatName>([
|
|
25
|
+
"boutiques",
|
|
26
|
+
"argdump",
|
|
27
|
+
"workbench",
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
export interface BuildOptions {
|
|
31
|
+
/** Single-descriptor input file (mutually exclusive with `catalog`). */
|
|
32
|
+
input?: string;
|
|
33
|
+
/** Catalog directory (mutually exclusive with `input`). */
|
|
34
|
+
catalog?: string;
|
|
35
|
+
/** Output directory; per-backend files end up under `<out>/<backend.target>/...`. */
|
|
36
|
+
out: string;
|
|
37
|
+
/** Backends to run (instances pre-resolved by the caller). */
|
|
38
|
+
backends: Backend[];
|
|
39
|
+
/** Which emit tiers to run. */
|
|
40
|
+
mode: BuildMode;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** A single file from a backend run, keyed by destination path. */
|
|
44
|
+
export interface BuiltFile {
|
|
45
|
+
/** Absolute destination path. */
|
|
46
|
+
path: string;
|
|
47
|
+
content: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Per-tool tallies for a catalog build (undefined for single-descriptor builds). */
|
|
51
|
+
export interface BuildStats {
|
|
52
|
+
appsCompiled: number;
|
|
53
|
+
appsFailed: number;
|
|
54
|
+
appsSkipped: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface BuildResult {
|
|
58
|
+
files: BuiltFile[];
|
|
59
|
+
errors: string[];
|
|
60
|
+
warnings: string[];
|
|
61
|
+
/** Catalog-build tallies; set only by `--catalog` builds. */
|
|
62
|
+
stats?: BuildStats;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Render an unknown thrown value as a message string. */
|
|
66
|
+
function errMsg(e: unknown): string {
|
|
67
|
+
return e instanceof Error ? e.message : String(e);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Run the build. Returns the assembled file map and diagnostics; the caller
|
|
72
|
+
* is responsible for actually writing to disk (so tests can introspect the
|
|
73
|
+
* map directly).
|
|
74
|
+
*/
|
|
75
|
+
export function build(options: BuildOptions): BuildResult {
|
|
76
|
+
if (options.input && options.catalog) {
|
|
77
|
+
return { files: [], errors: ["--input and --catalog are mutually exclusive"], warnings: [] };
|
|
78
|
+
}
|
|
79
|
+
if (!options.input && !options.catalog) {
|
|
80
|
+
return {
|
|
81
|
+
files: [],
|
|
82
|
+
errors: ["missing input: pass a descriptor path or --catalog"],
|
|
83
|
+
warnings: [],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (options.input) return buildSingle(options.input, options);
|
|
88
|
+
return buildCatalog(options.catalog!, options);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function buildSingle(inputPath: string, options: BuildOptions): BuildResult {
|
|
92
|
+
const result: BuildResult = { files: [], errors: [], warnings: [] };
|
|
93
|
+
|
|
94
|
+
const ctx = readAndCompile(inputPath, undefined, undefined, undefined, result);
|
|
95
|
+
if (!ctx) return result;
|
|
96
|
+
|
|
97
|
+
for (const backend of options.backends) {
|
|
98
|
+
let emitted;
|
|
99
|
+
try {
|
|
100
|
+
emitted = backend.emitApp(ctx);
|
|
101
|
+
} catch (e) {
|
|
102
|
+
result.errors.push(`[${backend.name}] ${errMsg(e)}`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
appendEmitMessages(result, emitted, backend);
|
|
106
|
+
for (const [name, content] of emitted.files) {
|
|
107
|
+
result.files.push({
|
|
108
|
+
path: path.resolve(options.out, backend.target, name),
|
|
109
|
+
content,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function buildCatalog(catalogPath: string, options: BuildOptions): BuildResult {
|
|
117
|
+
const result: BuildResult = {
|
|
118
|
+
files: [],
|
|
119
|
+
errors: [],
|
|
120
|
+
warnings: [],
|
|
121
|
+
stats: { appsCompiled: 0, appsFailed: 0, appsSkipped: 0 },
|
|
122
|
+
};
|
|
123
|
+
let catalog: CatalogProject;
|
|
124
|
+
try {
|
|
125
|
+
catalog = loadCatalog(catalogPath);
|
|
126
|
+
} catch (e) {
|
|
127
|
+
result.errors.push(e instanceof Error ? e.message : String(e));
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
result.warnings.push(...catalog.warnings);
|
|
131
|
+
|
|
132
|
+
// Skip/empty warnings are backend-independent, so only the first backend pass
|
|
133
|
+
// records them - otherwise they'd be duplicated once per backend.
|
|
134
|
+
options.backends.forEach((backend, i) => {
|
|
135
|
+
runBackendOverCatalog(backend, catalog, options.mode, options.out, result, i === 0);
|
|
136
|
+
});
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Parse a descriptor, run the default optimization pipeline, solve to
|
|
142
|
+
* bindings, and build a `CodegenContext` wired up with the supplied
|
|
143
|
+
* package/project meta. Returns null on read failure (with the error already
|
|
144
|
+
* appended to `result`). Parse-level errors/warnings are also appended.
|
|
145
|
+
*/
|
|
146
|
+
function readAndCompile(
|
|
147
|
+
sourcePath: string,
|
|
148
|
+
format: string | undefined,
|
|
149
|
+
pkg: PackageMeta | undefined,
|
|
150
|
+
proj: ProjectMeta | undefined,
|
|
151
|
+
result: BuildResult,
|
|
152
|
+
): ReturnType<typeof createContext> | null {
|
|
153
|
+
let source: string;
|
|
154
|
+
try {
|
|
155
|
+
source = readFileSync(sourcePath, "utf8");
|
|
156
|
+
} catch (e) {
|
|
157
|
+
result.errors.push(`${sourcePath}: ${errMsg(e)}`);
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Isolate the compile pipeline: a single descriptor that makes the solver or
|
|
162
|
+
// a downstream pass throw is recorded as an error and skipped, so one bad tool
|
|
163
|
+
// can't crash a whole-catalog build.
|
|
164
|
+
try {
|
|
165
|
+
// Honor the catalog's declared format when it's one we support, so we don't
|
|
166
|
+
// fall back to content sniffing (and get a clearer error if it mis-parses).
|
|
167
|
+
const parsed = compile(
|
|
168
|
+
source,
|
|
169
|
+
format && SUPPORTED_FORMATS.has(format)
|
|
170
|
+
? { filename: sourcePath, format: format as FormatName }
|
|
171
|
+
: sourcePath,
|
|
172
|
+
);
|
|
173
|
+
for (const e of parsed.errors) result.errors.push(`${sourcePath}: ${e.message}`);
|
|
174
|
+
for (const w of parsed.warnings) result.warnings.push(`${sourcePath}: ${w.message}`);
|
|
175
|
+
|
|
176
|
+
const piped = defaultPipeline.apply(parsed.expr);
|
|
177
|
+
if (piped.warnings) {
|
|
178
|
+
for (const w of piped.warnings) result.warnings.push(`${sourcePath}: ${w}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const solveResult = solve(piped.expr);
|
|
182
|
+
const outputs = resolveOutputs(piped.expr, solveResult);
|
|
183
|
+
return createContext(piped.expr, solveResult, outputs, {
|
|
184
|
+
app: parsed.meta,
|
|
185
|
+
package: pkg,
|
|
186
|
+
project: proj,
|
|
187
|
+
});
|
|
188
|
+
} catch (e) {
|
|
189
|
+
result.errors.push(`${sourcePath}: ${errMsg(e)}`);
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function runBackendOverCatalog(
|
|
195
|
+
backend: Backend,
|
|
196
|
+
catalog: CatalogProject,
|
|
197
|
+
mode: BuildMode,
|
|
198
|
+
outRoot: string,
|
|
199
|
+
result: BuildResult,
|
|
200
|
+
recordWarnings: boolean,
|
|
201
|
+
): void {
|
|
202
|
+
const backendRoot = path.resolve(outRoot, backend.target);
|
|
203
|
+
const packagesEmitted: EmittedPackage[] = [];
|
|
204
|
+
|
|
205
|
+
for (const pkg of catalog.packages) {
|
|
206
|
+
const pkgDir = pkg.meta.name ?? "package";
|
|
207
|
+
const appsEmitted: EmittedApp[] = [];
|
|
208
|
+
let skipped = 0;
|
|
209
|
+
// One scope shared across every tool in the suite so top-level names stay
|
|
210
|
+
// unique across the package's flat barrel re-exports.
|
|
211
|
+
const pkgScope = backend.newPackageScope?.();
|
|
212
|
+
|
|
213
|
+
for (const app of pkg.apps) {
|
|
214
|
+
// A tool can declare a format we have no frontend for yet (e.g. Workbench).
|
|
215
|
+
// Skip it with a warning rather than failing the whole catalog build.
|
|
216
|
+
if (app.sourceFormat && !SUPPORTED_FORMATS.has(app.sourceFormat)) {
|
|
217
|
+
skipped++;
|
|
218
|
+
if (recordWarnings) {
|
|
219
|
+
if (result.stats) result.stats.appsSkipped++;
|
|
220
|
+
result.warnings.push(
|
|
221
|
+
`${app.sourcePath}: skipped (unsupported source format "${app.sourceFormat}")`,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const ctx = readAndCompile(app.sourcePath, app.sourceFormat, pkg.meta, catalog.meta, result);
|
|
228
|
+
if (!ctx) {
|
|
229
|
+
if (recordWarnings && result.stats) result.stats.appsFailed++;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Isolate emit so one tool that makes a backend throw doesn't crash the run.
|
|
234
|
+
let emitted: EmittedApp;
|
|
235
|
+
try {
|
|
236
|
+
emitted = backend.emitApp(ctx, pkgScope);
|
|
237
|
+
} catch (e) {
|
|
238
|
+
result.errors.push(`[${backend.name} ${pkg.meta.name}/${app.name}] ${errMsg(e)}`);
|
|
239
|
+
if (recordWarnings && result.stats) result.stats.appsFailed++;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
appendEmitMessages(result, emitted, backend, `${pkg.meta.name}/${app.name}`);
|
|
243
|
+
appsEmitted.push(emitted);
|
|
244
|
+
if (recordWarnings && result.stats) result.stats.appsCompiled++;
|
|
245
|
+
|
|
246
|
+
for (const [name, content] of emitted.files) {
|
|
247
|
+
result.files.push({ path: path.join(backendRoot, pkgDir, name), content });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// A suite whose every tool was skipped (e.g. all-Workbench) emits nothing;
|
|
252
|
+
// don't synthesize an empty package or wire it into the project metadata.
|
|
253
|
+
// Only warn when emptiness is due to skips - genuine failures already errored.
|
|
254
|
+
if (appsEmitted.length === 0) {
|
|
255
|
+
if (recordWarnings && skipped > 0 && skipped === pkg.apps.length) {
|
|
256
|
+
result.warnings.push(`${pkgDir}: all ${skipped} tool(s) skipped, package omitted`);
|
|
257
|
+
}
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (mode !== "scripts" && backend.emitPackage) {
|
|
262
|
+
try {
|
|
263
|
+
const pkgEmit = backend.emitPackage(pkg.meta, appsEmitted);
|
|
264
|
+
appendEmitMessages(result, pkgEmit, backend, pkg.meta.name);
|
|
265
|
+
packagesEmitted.push(pkgEmit);
|
|
266
|
+
for (const [name, content] of pkgEmit.files) {
|
|
267
|
+
result.files.push({ path: path.join(backendRoot, pkgDir, name), content });
|
|
268
|
+
}
|
|
269
|
+
} catch (e) {
|
|
270
|
+
result.errors.push(`[${backend.name} ${pkgDir}] package emit failed: ${errMsg(e)}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (mode === "multi" && backend.emitProject && packagesEmitted.length > 0) {
|
|
276
|
+
try {
|
|
277
|
+
const projEmit = backend.emitProject(catalog.meta, packagesEmitted);
|
|
278
|
+
appendEmitMessages(result, projEmit, backend, catalog.meta.name);
|
|
279
|
+
for (const [name, content] of projEmit.files) {
|
|
280
|
+
result.files.push({ path: path.join(backendRoot, name), content });
|
|
281
|
+
}
|
|
282
|
+
} catch (e) {
|
|
283
|
+
result.errors.push(`[${backend.name}] project emit failed: ${errMsg(e)}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function appendEmitMessages(
|
|
289
|
+
result: BuildResult,
|
|
290
|
+
emit: EmitResult,
|
|
291
|
+
backend: Backend,
|
|
292
|
+
scope?: string,
|
|
293
|
+
): void {
|
|
294
|
+
const tag = scope ? `[${backend.name} ${scope}]` : `[${backend.name}]`;
|
|
295
|
+
for (const e of emit.errors) result.errors.push(`${tag} ${e.message}`);
|
|
296
|
+
for (const w of emit.warnings) result.warnings.push(`${tag} ${w.message}`);
|
|
297
|
+
}
|