pixelkiln 0.19.0 → 0.20.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/README.md CHANGED
@@ -183,9 +183,10 @@ packaging layer.
183
183
  }
184
184
  ```
185
185
 
186
- Styles are namespaces. Adding a second style re-derives the same asset ids into
187
- a separate output directory and separate lock keys without clobbering the first
188
- set. Generator choice, reference-image bytes, dimensions, palette, seed, and
186
+ Styles are namespaces. A variant can `extend` a base style while keeping its own
187
+ output directory, so later parent fixes reach every child without copied config.
188
+ Each style re-derives the same asset ids into separate lock keys. Generator
189
+ choice, reference-image bytes, dimensions, palette, seed, and
189
190
  prompt settings participate in deterministic spec identity. A manifest may
190
191
  select another provider and pass namespaced `providerOptions`; see
191
192
  [Set up PixelLab](./docs/PIXELLAB.md),
package/dist/cli.js CHANGED
@@ -1836,7 +1836,7 @@ var StyleImageSchema = z2.object({
1836
1836
  path: z2.string()
1837
1837
  });
1838
1838
  var HexColorSchema = z2.string().regex(/^#?[0-9a-f]{6}$/i, "expected a six-digit hex colour");
1839
- var QualityProfileSchema = z2.object({
1839
+ var QualityProfileObjectSchema = z2.object({
1840
1840
  /** Output root for approved refined art, relative to the manifest. */
1841
1841
  outDir: z2.string().min(1),
1842
1842
  /** Closed final palette applied after native-grid recovery, without dithering. */
@@ -1851,7 +1851,8 @@ var QualityProfileSchema = z2.object({
1851
1851
  fixerPython: z2.string().min(1).optional(),
1852
1852
  /** Playback rate recorded for an ordered frame set. */
1853
1853
  fps: z2.number().int().min(1).max(60).optional()
1854
- }).strict().refine(
1854
+ }).strict();
1855
+ var QualityProfileSchema = QualityProfileObjectSchema.refine(
1855
1856
  (profile) => new Set(profile.palette.map((color) => color.replace(/^#/, "").toLowerCase())).size === profile.palette.length,
1856
1857
  { message: "quality palette colors must be unique", path: ["palette"] }
1857
1858
  );
@@ -1880,7 +1881,7 @@ var RevisionSchema = z2.object({
1880
1881
  });
1881
1882
  }
1882
1883
  });
1883
- var StyleSchema = z2.object({
1884
+ var StyleObjectSchema = z2.object({
1884
1885
  /** Generation backend for this style. Omit to inherit the manifest default. */
1885
1886
  provider: z2.string().min(1).optional(),
1886
1887
  // `map` is the default because it is 20-40x cheaper and correct for any
@@ -1995,7 +1996,8 @@ var StyleSchema = z2.object({
1995
1996
  tags: z2.array(z2.string()).default([]),
1996
1997
  /** Adapter-owned settings, keyed by provider id. */
1997
1998
  providerOptions: z2.record(z2.record(z2.unknown())).default({})
1998
- }).strict().refine((s) => !(s.tileFeature && s.styleImages.length), {
1999
+ }).strict();
2000
+ var StyleSchema = StyleObjectSchema.refine((s) => !(s.tileFeature && s.styleImages.length), {
1999
2001
  message: "tileFeature and styleImages cannot be combined \u2014 a connectable set derives its own tile geometry, so remove one or the other",
2000
2002
  path: ["tileFeature"]
2001
2003
  }).superRefine((style, ctx) => {
@@ -2007,6 +2009,14 @@ var StyleSchema = z2.object({
2007
2009
  });
2008
2010
  }
2009
2011
  });
2012
+ var InheritedStyleSchema = StyleObjectSchema.partial().extend({
2013
+ extends: z2.string().min(1),
2014
+ /** Children own their destination; sharing a parent's output is never implicit. */
2015
+ outDir: z2.string(),
2016
+ /** A child may override only part of the inherited final-art policy. */
2017
+ quality: QualityProfileObjectSchema.partial().optional()
2018
+ }).strict();
2019
+ var StyleInputSchema = z2.union([StyleSchema, InheritedStyleSchema]);
2010
2020
  var AssetSchema = z2.object({
2011
2021
  /** The subject. Style wrapping comes from the style's prefix/suffix. */
2012
2022
  prompt: z2.string(),
@@ -2095,6 +2105,13 @@ var ManifestSchema = z2.object({
2095
2105
  styles: z2.record(StyleSchema),
2096
2106
  assets: z2.record(AssetSchema)
2097
2107
  }).strict();
2108
+ var ManifestInputSchema = z2.object({
2109
+ $schema: z2.string().optional(),
2110
+ name: z2.string(),
2111
+ provider: z2.string().min(1).default("pixellab"),
2112
+ styles: z2.record(StyleInputSchema),
2113
+ assets: z2.record(AssetSchema)
2114
+ }).strict();
2098
2115
  var LockEntrySchema = z2.object({
2099
2116
  styleId: z2.string(),
2100
2117
  assetId: z2.string(),
@@ -3670,9 +3687,24 @@ function resolveSpecOutputs(spec, lock, manifestDir) {
3670
3687
  async function loadManifest(manifestPath) {
3671
3688
  const abs = path6.resolve(manifestPath);
3672
3689
  if (!existsSync4(abs)) throw new Error(`No manifest at ${abs}`);
3673
- const parsed = ManifestSchema.safeParse(JSON.parse(await readFile3(abs, "utf8")));
3690
+ const input = ManifestInputSchema.safeParse(JSON.parse(await readFile3(abs, "utf8")));
3691
+ if (!input.success) {
3692
+ const issues = formatManifestIssues(input.error.issues);
3693
+ throw new Error(`Manifest at ${abs} is invalid:
3694
+ ${issues}`);
3695
+ }
3696
+ let styles;
3697
+ try {
3698
+ styles = resolveStyleInheritance(input.data.styles);
3699
+ } catch (error) {
3700
+ throw new Error(
3701
+ `Manifest at ${abs} is invalid:
3702
+ ${error instanceof Error ? error.message : String(error)}`
3703
+ );
3704
+ }
3705
+ const parsed = ManifestSchema.safeParse({ ...input.data, styles });
3674
3706
  if (!parsed.success) {
3675
- const issues = parsed.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
3707
+ const issues = formatManifestIssues(parsed.error.issues);
3676
3708
  throw new Error(`Manifest at ${abs} is invalid:
3677
3709
  ${issues}`);
3678
3710
  }
@@ -3723,6 +3755,61 @@ ${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
3723
3755
  }
3724
3756
  return { manifest: parsed.data, root: path6.dirname(abs), path: abs };
3725
3757
  }
3758
+ function formatManifestIssues(issues) {
3759
+ return issues.flatMap((issue) => {
3760
+ if (issue.code === "invalid_union") {
3761
+ return issue.unionErrors.flatMap((error) => formatManifestIssues(error.issues).split("\n"));
3762
+ }
3763
+ return [` ${issue.path.join(".")}: ${issue.message}`];
3764
+ }).join("\n");
3765
+ }
3766
+ function resolveStyleInheritance(styles) {
3767
+ const resolved = /* @__PURE__ */ new Map();
3768
+ const visit = (styleId, chain) => {
3769
+ const cached = resolved.get(styleId);
3770
+ if (cached) return cached;
3771
+ const cycleStart = chain.indexOf(styleId);
3772
+ if (cycleStart >= 0) {
3773
+ throw new Error(
3774
+ `styles.${styleId}.extends: inheritance cycle ${[...chain.slice(cycleStart), styleId].join(" -> ")}`
3775
+ );
3776
+ }
3777
+ const style = styles[styleId];
3778
+ if (!style) throw new Error(`styles.${styleId}: unknown style`);
3779
+ if (!("extends" in style)) {
3780
+ resolved.set(styleId, style);
3781
+ return style;
3782
+ }
3783
+ const parentId = style.extends;
3784
+ if (!Object.hasOwn(styles, parentId)) {
3785
+ throw new Error(`styles.${styleId}.extends: unknown style "${parentId}"`);
3786
+ }
3787
+ const parent = visit(parentId, [...chain, styleId]);
3788
+ const { extends: _parent, ...child } = style;
3789
+ const merged = {
3790
+ ...parent,
3791
+ ...child,
3792
+ quality: child.quality == null ? parent.quality : { ...parent.quality ?? {}, ...child.quality },
3793
+ providerOptions: mergeProviderOptions(parent.providerOptions, child.providerOptions)
3794
+ };
3795
+ const parsed = StyleSchema.safeParse(merged);
3796
+ if (!parsed.success) {
3797
+ const issues = parsed.error.issues.map((issue) => `styles.${styleId}.${issue.path.join(".")}: ${issue.message}`).join("; ");
3798
+ throw new Error(issues);
3799
+ }
3800
+ resolved.set(styleId, parsed.data);
3801
+ return parsed.data;
3802
+ };
3803
+ return Object.fromEntries(Object.keys(styles).map((styleId) => [styleId, visit(styleId, [])]));
3804
+ }
3805
+ function mergeProviderOptions(parent, child) {
3806
+ if (!child) return parent;
3807
+ const merged = { ...parent };
3808
+ for (const [provider, options] of Object.entries(child)) {
3809
+ merged[provider] = { ...parent[provider] ?? {}, ...options };
3810
+ }
3811
+ return merged;
3812
+ }
3726
3813
  async function resolveSpecs(loaded, filter) {
3727
3814
  const { manifest, root } = loaded;
3728
3815
  const providerOverride = filter?.provider;