pixelkiln 0.35.0 → 0.36.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/CONTRIBUTING.md +7 -0
- package/dist/cli.js +165 -81
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +137 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +116 -52
- package/dist/index.d.ts +116 -52
- package/dist/index.js +128 -38
- package/dist/index.js.map +1 -1
- package/docs/CLI.md +49 -0
- package/docs/LIBRARY.md +32 -0
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -474,6 +474,72 @@ function lockKey(styleId, assetId) {
|
|
|
474
474
|
return `${styleId}/${assetId}`;
|
|
475
475
|
}
|
|
476
476
|
|
|
477
|
+
// src/errors.ts
|
|
478
|
+
var EXIT_CODES = {
|
|
479
|
+
usage: 2,
|
|
480
|
+
project: 3,
|
|
481
|
+
provider: 4,
|
|
482
|
+
"refused-overwrite": 5,
|
|
483
|
+
budget: 6,
|
|
484
|
+
capability: 7
|
|
485
|
+
};
|
|
486
|
+
var PixelKilnError = class extends Error {
|
|
487
|
+
code;
|
|
488
|
+
hint;
|
|
489
|
+
constructor(message6, code, opts = {}) {
|
|
490
|
+
super(message6, opts.cause === void 0 ? void 0 : { cause: opts.cause });
|
|
491
|
+
this.name = "PixelKilnError";
|
|
492
|
+
this.code = code;
|
|
493
|
+
if (opts.hint) this.hint = opts.hint;
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
var UsageError = class extends PixelKilnError {
|
|
497
|
+
constructor(message6, opts) {
|
|
498
|
+
super(message6, "usage", opts);
|
|
499
|
+
this.name = "UsageError";
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
var ProjectError = class extends PixelKilnError {
|
|
503
|
+
constructor(message6, opts) {
|
|
504
|
+
super(message6, "project", opts);
|
|
505
|
+
this.name = "ProjectError";
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
var ProviderError = class extends PixelKilnError {
|
|
509
|
+
/** Registry id of the provider that failed. */
|
|
510
|
+
provider;
|
|
511
|
+
/** HTTP status when the failure was a response, otherwise undefined. */
|
|
512
|
+
status;
|
|
513
|
+
constructor(provider, message6, opts = {}) {
|
|
514
|
+
super(message6, "provider", opts);
|
|
515
|
+
this.name = "ProviderError";
|
|
516
|
+
this.provider = provider;
|
|
517
|
+
if (opts.status !== void 0) this.status = opts.status;
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
var OverwriteRefusedError = class extends PixelKilnError {
|
|
521
|
+
/** The file(s) left untouched. */
|
|
522
|
+
files;
|
|
523
|
+
constructor(message6, files, opts) {
|
|
524
|
+
super(message6, "refused-overwrite", opts);
|
|
525
|
+
this.name = "OverwriteRefusedError";
|
|
526
|
+
this.files = files;
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
var BudgetError = class extends PixelKilnError {
|
|
530
|
+
constructor(message6, opts) {
|
|
531
|
+
super(message6, "budget", opts);
|
|
532
|
+
this.name = "BudgetError";
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
function errorCode(err) {
|
|
536
|
+
return err instanceof PixelKilnError ? err.code : void 0;
|
|
537
|
+
}
|
|
538
|
+
function exitCodeFor(err) {
|
|
539
|
+
const code = errorCode(err);
|
|
540
|
+
return code ? EXIT_CODES[code] : 1;
|
|
541
|
+
}
|
|
542
|
+
|
|
477
543
|
// src/client.ts
|
|
478
544
|
import { z as z2 } from "zod";
|
|
479
545
|
|
|
@@ -590,14 +656,12 @@ function validateResponse(schema, raw, operation) {
|
|
|
590
656
|
const issues = parsed.error.issues.slice(0, 4).map((i) => `${i.path.join(".") || "response"}: ${i.message}`).join("; ");
|
|
591
657
|
throw new Error(`Invalid PixelLab response for ${operation}: ${issues}`);
|
|
592
658
|
}
|
|
593
|
-
var PixelLabError = class extends
|
|
659
|
+
var PixelLabError = class extends ProviderError {
|
|
594
660
|
constructor(message6, status, body) {
|
|
595
|
-
super(message6);
|
|
596
|
-
this.status = status;
|
|
661
|
+
super("pixellab", message6, { status });
|
|
597
662
|
this.body = body;
|
|
598
663
|
this.name = "PixelLabError";
|
|
599
664
|
}
|
|
600
|
-
status;
|
|
601
665
|
body;
|
|
602
666
|
};
|
|
603
667
|
var PixelLabClient = class {
|
|
@@ -877,10 +941,11 @@ function measureBalanceChange(before, after) {
|
|
|
877
941
|
};
|
|
878
942
|
}
|
|
879
943
|
var DEFAULT_RATE_LIMIT = { spacingMs: 2500, maxInFlight: 8 };
|
|
880
|
-
var UnsupportedCapabilityError = class extends
|
|
944
|
+
var UnsupportedCapabilityError = class extends PixelKilnError {
|
|
881
945
|
constructor(providerId, capability) {
|
|
882
946
|
super(
|
|
883
|
-
`Provider "${providerId}" does not support ${capability}. That command is unavailable with this backend
|
|
947
|
+
`Provider "${providerId}" does not support ${capability}. That command is unavailable with this backend.`,
|
|
948
|
+
"capability"
|
|
884
949
|
);
|
|
885
950
|
this.name = "UnsupportedCapabilityError";
|
|
886
951
|
}
|
|
@@ -1705,7 +1770,7 @@ var RetroDiffusionClient = class {
|
|
|
1705
1770
|
/** Result URLs are signed; no token header, and the same retry policy as the API. */
|
|
1706
1771
|
async download(url) {
|
|
1707
1772
|
const response = await this.request(url);
|
|
1708
|
-
if (!response.ok) throw new
|
|
1773
|
+
if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
|
|
1709
1774
|
return Buffer.from(await response.arrayBuffer());
|
|
1710
1775
|
}
|
|
1711
1776
|
async call(path32, init = {}) {
|
|
@@ -1728,8 +1793,10 @@ var RetroDiffusionClient = class {
|
|
|
1728
1793
|
if (!response.ok) {
|
|
1729
1794
|
const retry = response.headers.get("retry-after");
|
|
1730
1795
|
const detail = retroError(value);
|
|
1731
|
-
throw new
|
|
1732
|
-
|
|
1796
|
+
throw new ProviderError(
|
|
1797
|
+
"retrodiffusion",
|
|
1798
|
+
`Retro Diffusion request failed (${response.status}): ${detail}` + (retry ? `; retry after ${retry}s` : ""),
|
|
1799
|
+
{ status: response.status }
|
|
1733
1800
|
);
|
|
1734
1801
|
}
|
|
1735
1802
|
return value;
|
|
@@ -2121,7 +2188,7 @@ var ScenarioClient = class {
|
|
|
2121
2188
|
throw new Error("Scenario downloads must use HTTP or HTTPS");
|
|
2122
2189
|
}
|
|
2123
2190
|
const response = await this.request(parsed);
|
|
2124
|
-
if (!response.ok) throw new
|
|
2191
|
+
if (!response.ok) throw new ProviderError("scenario", `Scenario download failed (${response.status})`, { status: response.status });
|
|
2125
2192
|
const declared = Number(response.headers.get("content-length"));
|
|
2126
2193
|
if (Number.isFinite(declared) && declared > MAX_DOWNLOAD_BYTES) {
|
|
2127
2194
|
throw new Error(`Scenario download exceeds the ${MAX_DOWNLOAD_BYTES}-byte safety limit`);
|
|
@@ -2168,8 +2235,10 @@ var ScenarioClient = class {
|
|
|
2168
2235
|
if (!response.ok) {
|
|
2169
2236
|
const retry = response.headers.get("retry-after");
|
|
2170
2237
|
const detail = redactScenarioError(value, [this.apiKey, this.apiSecret, auth]);
|
|
2171
|
-
throw new
|
|
2172
|
-
|
|
2238
|
+
throw new ProviderError(
|
|
2239
|
+
"scenario",
|
|
2240
|
+
`Scenario request failed (${response.status}): ${detail}` + (retry ? `; retry after ${retry}s` : ""),
|
|
2241
|
+
{ status: response.status }
|
|
2173
2242
|
);
|
|
2174
2243
|
}
|
|
2175
2244
|
return value;
|
|
@@ -2619,8 +2688,10 @@ var ComfyUIClient = class {
|
|
|
2619
2688
|
value = null;
|
|
2620
2689
|
}
|
|
2621
2690
|
if (!response.ok) {
|
|
2622
|
-
throw new
|
|
2623
|
-
|
|
2691
|
+
throw new ProviderError(
|
|
2692
|
+
"comfyui",
|
|
2693
|
+
`ComfyUI image upload failed (${response.status})` + (apiError(value) ? `: ${apiError(value)}` : ""),
|
|
2694
|
+
{ status: response.status }
|
|
2624
2695
|
);
|
|
2625
2696
|
}
|
|
2626
2697
|
if (!isObject(value) || typeof value.name !== "string" || !value.name) {
|
|
@@ -2643,7 +2714,7 @@ var ComfyUIClient = class {
|
|
|
2643
2714
|
let target = source;
|
|
2644
2715
|
if (source.startsWith("comfyui://")) target = this.viewUrl(parseSource(source));
|
|
2645
2716
|
const response = await this.request(target);
|
|
2646
|
-
if (!response.ok) throw new
|
|
2717
|
+
if (!response.ok) throw new ProviderError("comfyui", `ComfyUI download failed (${response.status})`, { status: response.status });
|
|
2647
2718
|
return Buffer.from(await response.arrayBuffer());
|
|
2648
2719
|
}
|
|
2649
2720
|
endpoint(route) {
|
|
@@ -2668,8 +2739,10 @@ var ComfyUIClient = class {
|
|
|
2668
2739
|
}
|
|
2669
2740
|
if (!response.ok) {
|
|
2670
2741
|
const detail = apiError(value);
|
|
2671
|
-
throw new
|
|
2672
|
-
|
|
2742
|
+
throw new ProviderError(
|
|
2743
|
+
"comfyui",
|
|
2744
|
+
`ComfyUI request failed (${response.status}) at ${url.pathname}` + (detail ? `: ${detail}` : ""),
|
|
2745
|
+
{ status: response.status }
|
|
2673
2746
|
);
|
|
2674
2747
|
}
|
|
2675
2748
|
return value;
|
|
@@ -3659,9 +3732,10 @@ async function loadLock(lockPath) {
|
|
|
3659
3732
|
try {
|
|
3660
3733
|
return parseLock(JSON.parse(await readFile2(lockPath, "utf8")));
|
|
3661
3734
|
} catch (err) {
|
|
3662
|
-
throw new
|
|
3735
|
+
throw new ProjectError(
|
|
3663
3736
|
`Lockfile at ${lockPath} is malformed:
|
|
3664
|
-
${err instanceof Error ? err.message : String(err)}
|
|
3737
|
+
${err instanceof Error ? err.message : String(err)}`,
|
|
3738
|
+
{ cause: err }
|
|
3665
3739
|
);
|
|
3666
3740
|
}
|
|
3667
3741
|
}
|
|
@@ -3695,8 +3769,9 @@ async function writeLockWhileHeld(file, lock) {
|
|
|
3695
3769
|
try {
|
|
3696
3770
|
disk = parseLock(JSON.parse(await readFile2(file, "utf8")));
|
|
3697
3771
|
} catch (err) {
|
|
3698
|
-
throw new
|
|
3699
|
-
`Refusing to overwrite malformed lockfile at ${file}: ${err instanceof Error ? err.message : String(err)}
|
|
3772
|
+
throw new ProjectError(
|
|
3773
|
+
`Refusing to overwrite malformed lockfile at ${file}: ${err instanceof Error ? err.message : String(err)}`,
|
|
3774
|
+
{ cause: err }
|
|
3700
3775
|
);
|
|
3701
3776
|
}
|
|
3702
3777
|
}
|
|
@@ -3952,18 +4027,18 @@ function resolveSpecOutputs(spec, lock, manifestDir) {
|
|
|
3952
4027
|
// src/manifest.ts
|
|
3953
4028
|
async function loadManifest(manifestPath) {
|
|
3954
4029
|
const abs = path5.resolve(manifestPath);
|
|
3955
|
-
if (!existsSync4(abs)) throw new
|
|
4030
|
+
if (!existsSync4(abs)) throw new ProjectError(`No manifest at ${abs}`);
|
|
3956
4031
|
const input = ManifestInputSchema.safeParse(JSON.parse(await readFile3(abs, "utf8")));
|
|
3957
4032
|
if (!input.success) {
|
|
3958
4033
|
const issues = formatManifestIssues(input.error.issues);
|
|
3959
|
-
throw new
|
|
4034
|
+
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
3960
4035
|
${issues}`);
|
|
3961
4036
|
}
|
|
3962
4037
|
let styles;
|
|
3963
4038
|
try {
|
|
3964
4039
|
styles = resolveStyleInheritance(input.data.styles);
|
|
3965
4040
|
} catch (error) {
|
|
3966
|
-
throw new
|
|
4041
|
+
throw new ProjectError(
|
|
3967
4042
|
`Manifest at ${abs} is invalid:
|
|
3968
4043
|
${error instanceof Error ? error.message : String(error)}`
|
|
3969
4044
|
);
|
|
@@ -3971,7 +4046,7 @@ ${issues}`);
|
|
|
3971
4046
|
const parsed = ManifestSchema.safeParse({ ...input.data, styles });
|
|
3972
4047
|
if (!parsed.success) {
|
|
3973
4048
|
const issues = formatManifestIssues(parsed.error.issues);
|
|
3974
|
-
throw new
|
|
4049
|
+
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
3975
4050
|
${issues}`);
|
|
3976
4051
|
}
|
|
3977
4052
|
const styleIds = new Set(Object.keys(parsed.data.styles));
|
|
@@ -4021,7 +4096,7 @@ ${issues}`);
|
|
|
4021
4096
|
};
|
|
4022
4097
|
for (const assetId of Object.keys(parsed.data.assets)) visitRevision(assetId, []);
|
|
4023
4098
|
if (unknownReferences.length) {
|
|
4024
|
-
throw new
|
|
4099
|
+
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
4025
4100
|
${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
|
|
4026
4101
|
}
|
|
4027
4102
|
return { manifest: parsed.data, root: path5.dirname(abs), path: abs };
|
|
@@ -4100,14 +4175,14 @@ async function resolveSpecs(loaded, filter) {
|
|
|
4100
4175
|
);
|
|
4101
4176
|
for (const unknownStyle of filter?.styles ?? []) {
|
|
4102
4177
|
if (!manifest.styles[unknownStyle]) {
|
|
4103
|
-
throw new
|
|
4178
|
+
throw new UsageError(
|
|
4104
4179
|
`Unknown style "${unknownStyle}". Defined: ${Object.keys(manifest.styles).join(", ") || "(none)"}`
|
|
4105
4180
|
);
|
|
4106
4181
|
}
|
|
4107
4182
|
}
|
|
4108
4183
|
for (const unknownAsset of filter?.assets ?? []) {
|
|
4109
4184
|
if (!manifest.assets[unknownAsset]) {
|
|
4110
|
-
throw new
|
|
4185
|
+
throw new UsageError(`Unknown asset "${unknownAsset}".`);
|
|
4111
4186
|
}
|
|
4112
4187
|
}
|
|
4113
4188
|
const requestedAssetIds = new Set(filter?.assets?.length ? filter.assets : Object.keys(manifest.assets));
|
|
@@ -4718,10 +4793,11 @@ async function writeManagedArtifactBundle(manifestPath, outputs, provenance, opt
|
|
|
4718
4793
|
if (!expected || digest(current) !== expected) conflicts.push(destination);
|
|
4719
4794
|
}
|
|
4720
4795
|
if (conflicts.length) {
|
|
4721
|
-
throw new
|
|
4796
|
+
throw new OverwriteRefusedError(
|
|
4722
4797
|
`Refusing to overwrite modified or unowned artifact file(s):
|
|
4723
4798
|
` + conflicts.map((file) => ` ${file}`).join("\n") + `
|
|
4724
|
-
Pass { force: true } (CLI: --force) to take ownership and replace the complete bundle
|
|
4799
|
+
Pass { force: true } (CLI: --force) to take ownership and replace the complete bundle.`,
|
|
4800
|
+
conflicts
|
|
4725
4801
|
);
|
|
4726
4802
|
}
|
|
4727
4803
|
}
|
|
@@ -6519,8 +6595,9 @@ async function installRecipe(target, options = {}) {
|
|
|
6519
6595
|
if (!existsSync10(file.path)) continue;
|
|
6520
6596
|
const current = await readFile7(file.path);
|
|
6521
6597
|
if (!current.equals(file.data)) {
|
|
6522
|
-
throw new
|
|
6523
|
-
`Recipe destination has local changes: ${file.path}. Choose another --out or pass --force to replace declared recipe files
|
|
6598
|
+
throw new OverwriteRefusedError(
|
|
6599
|
+
`Recipe destination has local changes: ${file.path}. Choose another --out or pass --force to replace declared recipe files.`,
|
|
6600
|
+
[file.path]
|
|
6524
6601
|
);
|
|
6525
6602
|
}
|
|
6526
6603
|
}
|
|
@@ -6787,7 +6864,10 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
6787
6864
|
if (existsSync11(absolute) && !options.force) {
|
|
6788
6865
|
const current = await readFile8(absolute);
|
|
6789
6866
|
if (!current.equals(data)) {
|
|
6790
|
-
throw new
|
|
6867
|
+
throw new OverwriteRefusedError(
|
|
6868
|
+
`Quality baseline already exists with different content: ${absolute}. Pass --force to replace it.`,
|
|
6869
|
+
[absolute]
|
|
6870
|
+
);
|
|
6791
6871
|
}
|
|
6792
6872
|
}
|
|
6793
6873
|
const result = await writeArtifactBundle([{ path: absolute, data }]);
|
|
@@ -7312,8 +7392,9 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
7312
7392
|
const file = currentEntryOutputPath(entry, spec, i);
|
|
7313
7393
|
if (!existsSync14(file)) continue;
|
|
7314
7394
|
if (await sha256File(file) !== entry.outputs[i].sha256 && !opts.force) {
|
|
7315
|
-
throw new
|
|
7316
|
-
`${path17.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it
|
|
7395
|
+
throw new OverwriteRefusedError(
|
|
7396
|
+
`${path17.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`,
|
|
7397
|
+
[file]
|
|
7317
7398
|
);
|
|
7318
7399
|
}
|
|
7319
7400
|
}
|
|
@@ -8063,7 +8144,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
8063
8144
|
if (opts.budget != null) {
|
|
8064
8145
|
const cost = [...estimates.values()].reduce((sum, estimate) => sum + estimate.amount, 0);
|
|
8065
8146
|
if (cost > opts.budget) {
|
|
8066
|
-
throw new
|
|
8147
|
+
throw new BudgetError(
|
|
8067
8148
|
`This run would spend ${formatCost(unit, cost)} but the budget is ${formatCost(unit, opts.budget)}. Narrow it with --only/--style, or raise --budget.`
|
|
8068
8149
|
);
|
|
8069
8150
|
}
|
|
@@ -9390,7 +9471,7 @@ var WorkspaceSchema = z6.object({
|
|
|
9390
9471
|
function parseWorkspace(raw) {
|
|
9391
9472
|
const parsed = WorkspaceSchema.safeParse(raw);
|
|
9392
9473
|
if (parsed.success) return parsed.data;
|
|
9393
|
-
throw new
|
|
9474
|
+
throw new ProjectError(
|
|
9394
9475
|
`Workspace catalog is not valid v1:
|
|
9395
9476
|
${parsed.error.issues.slice(0, 5).map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n")}`
|
|
9396
9477
|
);
|
|
@@ -9401,7 +9482,7 @@ async function loadWorkspace(workspacePath) {
|
|
|
9401
9482
|
try {
|
|
9402
9483
|
raw = JSON.parse(await readFile16(workspacePath, "utf8"));
|
|
9403
9484
|
} catch (err) {
|
|
9404
|
-
throw new
|
|
9485
|
+
throw new ProjectError(
|
|
9405
9486
|
`Workspace catalog at ${workspacePath} is malformed:
|
|
9406
9487
|
${err instanceof Error ? err.message : String(err)}`
|
|
9407
9488
|
);
|
|
@@ -12193,6 +12274,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
12193
12274
|
}
|
|
12194
12275
|
export {
|
|
12195
12276
|
AssetSchema,
|
|
12277
|
+
BudgetError,
|
|
12196
12278
|
ComfyUIProvider,
|
|
12197
12279
|
DEFAULT_HISTORY_LIMIT,
|
|
12198
12280
|
DEFAULT_QUALITY_TOLERANCES,
|
|
@@ -12200,6 +12282,7 @@ export {
|
|
|
12200
12282
|
DEFAULT_TIMEOUT_MS,
|
|
12201
12283
|
EDITOR_PIN,
|
|
12202
12284
|
EDITOR_RELEASE_BASE,
|
|
12285
|
+
EXIT_CODES,
|
|
12203
12286
|
EditorInstallError,
|
|
12204
12287
|
FAKE_PNG,
|
|
12205
12288
|
FakeProvider,
|
|
@@ -12224,11 +12307,15 @@ export {
|
|
|
12224
12307
|
ManifestInputSchema,
|
|
12225
12308
|
ManifestSchema,
|
|
12226
12309
|
MediaType,
|
|
12310
|
+
OverwriteRefusedError,
|
|
12227
12311
|
PIXEL_ART_FIXER_REVISION,
|
|
12228
12312
|
PIXEL_ART_FIXER_URL,
|
|
12313
|
+
PixelKilnError,
|
|
12229
12314
|
PixelLabClient,
|
|
12230
12315
|
PixelLabError,
|
|
12231
12316
|
PixelLabProvider,
|
|
12317
|
+
ProjectError,
|
|
12318
|
+
ProviderError,
|
|
12232
12319
|
QualityBaselineSchema,
|
|
12233
12320
|
QualityProfileSchema,
|
|
12234
12321
|
QualityToleranceSchema,
|
|
@@ -12240,6 +12327,7 @@ export {
|
|
|
12240
12327
|
StyleInputSchema,
|
|
12241
12328
|
StyleSchema,
|
|
12242
12329
|
UnsupportedCapabilityError,
|
|
12330
|
+
UsageError,
|
|
12243
12331
|
WorkspaceProjectSchema,
|
|
12244
12332
|
WorkspaceSchema,
|
|
12245
12333
|
adopt,
|
|
@@ -12277,7 +12365,9 @@ export {
|
|
|
12277
12365
|
editorRoute,
|
|
12278
12366
|
editorStatus,
|
|
12279
12367
|
editorToolsRoot,
|
|
12368
|
+
errorCode,
|
|
12280
12369
|
evaluateAudit,
|
|
12370
|
+
exitCodeFor,
|
|
12281
12371
|
expectedOutputPath,
|
|
12282
12372
|
exportTileset,
|
|
12283
12373
|
fallbackOutputRole,
|