dispersa 0.3.1 → 0.4.1

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.
@@ -325,7 +325,7 @@ type CssRendererOptions = {
325
325
  /**
326
326
  * Error code identifying the type of build error
327
327
  */
328
- type ErrorCode = 'TOKEN_REFERENCE' | 'CIRCULAR_REFERENCE' | 'VALIDATION' | 'COLOR_PARSE' | 'DIMENSION_FORMAT' | 'FILE_OPERATION' | 'CONFIGURATION' | 'BASE_PERMUTATION' | 'MODIFIER' | 'UNKNOWN';
328
+ type ErrorCode = 'TOKEN_REFERENCE' | 'CIRCULAR_REFERENCE' | 'VALIDATION' | 'FILE_OPERATION' | 'CONFIGURATION' | 'BASE_PERMUTATION' | 'MODIFIER' | 'UNKNOWN';
329
329
  /**
330
330
  * Structured error from a build operation
331
331
  *
@@ -473,6 +473,13 @@ type LifecycleHooks = {
473
473
  onBuildEnd?: (result: BuildResult) => void | Promise<void>;
474
474
  };
475
475
 
476
+ /**
477
+ * Function that generates an output file path based on modifier inputs.
478
+ *
479
+ * Used as the `file` property on `OutputConfig` and builder configs when
480
+ * the file name needs to vary per permutation.
481
+ */
482
+ type FileFunction = (modifierInputs: ModifierInputs) => string;
476
483
  /**
477
484
  * Output configuration for a single build target
478
485
  *
@@ -577,7 +584,7 @@ type OutputConfig<TOptions extends FormatOptions = FormatOptions> = Omit<OutputC
577
584
  * }
578
585
  * ```
579
586
  */
580
- file?: string | ((modifierInputs: ModifierInputs) => string);
587
+ file?: string | FileFunction;
581
588
  /**
582
589
  * Renderer-specific options passed to the formatter.
583
590
  */
@@ -701,4 +708,4 @@ type DispersaOptions = Omit<DispersaOptionsBase, 'validation'> & {
701
708
  validation?: ValidationOptions;
702
709
  };
703
710
 
704
- export { type AndroidRendererOptions as A, type BuildConfig as B, type CssRendererOptions as C, type DispersaOptions as D, type ErrorCode as E, type FormatOptions as F, type IosRendererOptions as I, type LifecycleHooks as L, type ModifierInputs as M, type OutputConfig as O, type PermutationData as P, type ResolverDocument as R, type SelectorFunction as S, type TailwindRendererOptions as T, type ValidationOptions as V, type BuildResult as a, type ValidationMode as b, type BuildError as c, type BuildOutput as d, type MediaQueryFunction as e, type OutputTree as f, type Renderer as g, type RenderContext as h, type RenderMeta as i, type RenderOutput as j, defineRenderer as k };
711
+ export { type AndroidRendererOptions as A, type BuildConfig as B, type CssRendererOptions as C, type DispersaOptions as D, type ErrorCode as E, type FileFunction as F, type IosRendererOptions as I, type LifecycleHooks as L, type ModifierInputs as M, type OutputConfig as O, type PermutationData as P, type ResolverDocument as R, type SelectorFunction as S, type TailwindRendererOptions as T, type ValidationOptions as V, type BuildResult as a, type ValidationMode as b, type BuildError as c, type BuildOutput as d, type FormatOptions as e, type MediaQueryFunction as f, type OutputTree as g, type Renderer as h, type RenderContext as i, type RenderMeta as j, type RenderOutput as k, defineRenderer as l };
@@ -325,7 +325,7 @@ type CssRendererOptions = {
325
325
  /**
326
326
  * Error code identifying the type of build error
327
327
  */
328
- type ErrorCode = 'TOKEN_REFERENCE' | 'CIRCULAR_REFERENCE' | 'VALIDATION' | 'COLOR_PARSE' | 'DIMENSION_FORMAT' | 'FILE_OPERATION' | 'CONFIGURATION' | 'BASE_PERMUTATION' | 'MODIFIER' | 'UNKNOWN';
328
+ type ErrorCode = 'TOKEN_REFERENCE' | 'CIRCULAR_REFERENCE' | 'VALIDATION' | 'FILE_OPERATION' | 'CONFIGURATION' | 'BASE_PERMUTATION' | 'MODIFIER' | 'UNKNOWN';
329
329
  /**
330
330
  * Structured error from a build operation
331
331
  *
@@ -473,6 +473,13 @@ type LifecycleHooks = {
473
473
  onBuildEnd?: (result: BuildResult) => void | Promise<void>;
474
474
  };
475
475
 
476
+ /**
477
+ * Function that generates an output file path based on modifier inputs.
478
+ *
479
+ * Used as the `file` property on `OutputConfig` and builder configs when
480
+ * the file name needs to vary per permutation.
481
+ */
482
+ type FileFunction = (modifierInputs: ModifierInputs) => string;
476
483
  /**
477
484
  * Output configuration for a single build target
478
485
  *
@@ -577,7 +584,7 @@ type OutputConfig<TOptions extends FormatOptions = FormatOptions> = Omit<OutputC
577
584
  * }
578
585
  * ```
579
586
  */
580
- file?: string | ((modifierInputs: ModifierInputs) => string);
587
+ file?: string | FileFunction;
581
588
  /**
582
589
  * Renderer-specific options passed to the formatter.
583
590
  */
@@ -701,4 +708,4 @@ type DispersaOptions = Omit<DispersaOptionsBase, 'validation'> & {
701
708
  validation?: ValidationOptions;
702
709
  };
703
710
 
704
- export { type AndroidRendererOptions as A, type BuildConfig as B, type CssRendererOptions as C, type DispersaOptions as D, type ErrorCode as E, type FormatOptions as F, type IosRendererOptions as I, type LifecycleHooks as L, type ModifierInputs as M, type OutputConfig as O, type PermutationData as P, type ResolverDocument as R, type SelectorFunction as S, type TailwindRendererOptions as T, type ValidationOptions as V, type BuildResult as a, type ValidationMode as b, type BuildError as c, type BuildOutput as d, type MediaQueryFunction as e, type OutputTree as f, type Renderer as g, type RenderContext as h, type RenderMeta as i, type RenderOutput as j, defineRenderer as k };
711
+ export { type AndroidRendererOptions as A, type BuildConfig as B, type CssRendererOptions as C, type DispersaOptions as D, type ErrorCode as E, type FileFunction as F, type IosRendererOptions as I, type LifecycleHooks as L, type ModifierInputs as M, type OutputConfig as O, type PermutationData as P, type ResolverDocument as R, type SelectorFunction as S, type TailwindRendererOptions as T, type ValidationOptions as V, type BuildResult as a, type ValidationMode as b, type BuildError as c, type BuildOutput as d, type FormatOptions as e, type MediaQueryFunction as f, type OutputTree as g, type Renderer as h, type RenderContext as i, type RenderMeta as j, type RenderOutput as k, defineRenderer as l };
package/dist/index.cjs CHANGED
@@ -86,7 +86,7 @@ var init_token_utils = __esm({
86
86
  });
87
87
 
88
88
  // src/shared/errors/index.ts
89
- exports.DispersaError = void 0; exports.TokenReferenceError = void 0; exports.CircularReferenceError = void 0; exports.ValidationError = void 0; exports.ColorParseError = void 0; exports.DimensionFormatError = void 0; exports.FileOperationError = void 0; exports.ConfigurationError = void 0; exports.BasePermutationError = void 0; exports.ModifierError = void 0;
89
+ exports.DispersaError = void 0; exports.TokenReferenceError = void 0; exports.CircularReferenceError = void 0; exports.ValidationError = void 0; exports.FileOperationError = void 0; exports.ConfigurationError = void 0; exports.BasePermutationError = void 0; exports.ModifierError = void 0;
90
90
  var init_errors = __esm({
91
91
  "src/shared/errors/index.ts"() {
92
92
  exports.DispersaError = class extends Error {
@@ -137,20 +137,6 @@ var init_errors = __esm({
137
137
  this.name = "ValidationError";
138
138
  }
139
139
  };
140
- exports.ColorParseError = class extends exports.DispersaError {
141
- constructor(colorValue) {
142
- super(`Color parsing failed: '${colorValue}'. Provide a valid CSS color or DTCG color object.`);
143
- this.colorValue = colorValue;
144
- this.name = "ColorParseError";
145
- }
146
- };
147
- exports.DimensionFormatError = class extends exports.DispersaError {
148
- constructor(dimensionValue) {
149
- super(`Dimension parsing failed: '${dimensionValue}'. Provide a valid DTCG dimension object.`);
150
- this.dimensionValue = dimensionValue;
151
- this.name = "DimensionFormatError";
152
- }
153
- };
154
140
  exports.FileOperationError = class extends exports.DispersaError {
155
141
  constructor(operation, filePath, originalError) {
156
142
  super(`Failed to ${operation} file: ${filePath}. ${originalError.message}`);
@@ -3907,12 +3893,6 @@ function toBuildError(error, outputName) {
3907
3893
  if (error instanceof exports.ValidationError) {
3908
3894
  return { message, code: "VALIDATION", severity: "error" };
3909
3895
  }
3910
- if (error instanceof exports.ColorParseError) {
3911
- return { message, code: "COLOR_PARSE", severity: "error" };
3912
- }
3913
- if (error instanceof exports.DimensionFormatError) {
3914
- return { message, code: "DIMENSION_FORMAT", severity: "error" };
3915
- }
3916
3896
  if (error instanceof exports.FileOperationError) {
3917
3897
  return { message, code: "FILE_OPERATION", path: error.filePath, severity: "error" };
3918
3898
  }
@@ -5907,6 +5887,32 @@ var TokenParser = class {
5907
5887
  };
5908
5888
 
5909
5889
  // src/build/pipeline/token-pipeline.ts
5890
+ var ROOT_REF_PATTERN = /\.\$root\}/g;
5891
+ function rewriteRootReferences(value) {
5892
+ if (typeof value === "string") {
5893
+ return ROOT_REF_PATTERN.test(value) ? value.replace(ROOT_REF_PATTERN, "}") : value;
5894
+ }
5895
+ if (Array.isArray(value)) {
5896
+ let changed = false;
5897
+ const mapped = value.map((item) => {
5898
+ const rewritten = rewriteRootReferences(item);
5899
+ if (rewritten !== item) changed = true;
5900
+ return rewritten;
5901
+ });
5902
+ return changed ? mapped : value;
5903
+ }
5904
+ if (typeof value === "object" && value !== null) {
5905
+ let changed = false;
5906
+ const result = {};
5907
+ for (const [k, v] of Object.entries(value)) {
5908
+ const rewritten = rewriteRootReferences(v);
5909
+ if (rewritten !== v) changed = true;
5910
+ result[k] = rewritten;
5911
+ }
5912
+ return changed ? result : value;
5913
+ }
5914
+ return value;
5915
+ }
5910
5916
  var TokenPipeline = class {
5911
5917
  options;
5912
5918
  validationHandler;
@@ -5931,8 +5937,9 @@ var TokenPipeline = class {
5931
5937
  * 5. Resolve JSON Pointer references
5932
5938
  * 6. Parse and flatten token structure
5933
5939
  * 7. Resolve alias references
5934
- * 8. Apply filters (if provided) runs first to remove tokens before transforms
5935
- * 9. Apply transforms (if provided) — runs on the already-filtered token set
5940
+ * 8. Strip $root from token names/paths (DTCG structural mechanism, transparent in output)
5941
+ * 9. Apply filters (if provided) — runs first to remove tokens before transforms
5942
+ * 10. Apply transforms (if provided) — runs on the already-filtered token set
5936
5943
  *
5937
5944
  * Each stage is explicitly typed to ensure correct order and prevent temporal coupling.
5938
5945
  *
@@ -5972,7 +5979,8 @@ var TokenPipeline = class {
5972
5979
  const refResolved = await this.resolveReferences(preprocessed);
5973
5980
  const flattened = this.flattenTokens(refResolved);
5974
5981
  const aliasResolved = this.resolveAliases(flattened);
5975
- const filtered = this.applyFilterStage(aliasResolved, filterList);
5982
+ const rootStripped = this.stripRootTokenNames(aliasResolved);
5983
+ const filtered = this.applyFilterStage(rootStripped, filterList);
5976
5984
  return this.applyTransformStage(filtered, transformList);
5977
5985
  }
5978
5986
  /**
@@ -6057,8 +6065,34 @@ var TokenPipeline = class {
6057
6065
  return { ...rest, aliasResolvedTokens };
6058
6066
  }
6059
6067
  /**
6060
- * Stage 8: Apply filters to final tokens (before transforms to skip unnecessary work)
6068
+ * Stage 8: Strip `$root` from token names and paths.
6069
+ *
6070
+ * `$root` is a DTCG structural mechanism that allows a group to carry a
6071
+ * default value alongside child tokens. References must use the full path
6072
+ * (`{color.action.brand.$root}`) for alias resolution, but `$root` should
6073
+ * be transparent in output. This stage re-keys tokens so downstream
6074
+ * consumers (filters, transforms, renderers) see clean names.
6061
6075
  */
6076
+ stripRootTokenNames(stage) {
6077
+ const tokens = stage.aliasResolvedTokens;
6078
+ const result = {};
6079
+ for (const [key, token] of Object.entries(tokens)) {
6080
+ const rewrittenOriginal = rewriteRootReferences(token.originalValue);
6081
+ if (!key.endsWith(".$root")) {
6082
+ result[key] = rewrittenOriginal !== token.originalValue ? { ...token, originalValue: rewrittenOriginal } : token;
6083
+ continue;
6084
+ }
6085
+ const strippedPath = token.path.filter((segment) => segment !== "$root");
6086
+ const strippedName = strippedPath.join(".");
6087
+ result[strippedName] = {
6088
+ ...token,
6089
+ path: strippedPath,
6090
+ name: strippedName,
6091
+ originalValue: rewrittenOriginal
6092
+ };
6093
+ }
6094
+ return { ...stage, aliasResolvedTokens: result };
6095
+ }
6062
6096
  applyFilterStage(stage, filterList) {
6063
6097
  let tokens = stage.aliasResolvedTokens;
6064
6098
  if (filterList !== void 0 && filterList.length > 0) {