@bamboocss/config 1.30.1 → 1.32.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/diff-config.cjs +3 -4
- package/dist/diff-config.mjs +3 -4
- package/dist/index.cjs +297 -65
- package/dist/index.d.cts +1766 -4
- package/dist/index.d.mts +1766 -4
- package/dist/index.mjs +295 -66
- package/dist/merge-config.cjs +56 -13
- package/dist/merge-config.mjs +57 -14
- package/package.json +6 -6
package/dist/diff-config.cjs
CHANGED
|
@@ -51,14 +51,12 @@ function createMatcher(id, patterns) {
|
|
|
51
51
|
const all = [
|
|
52
52
|
"clean",
|
|
53
53
|
"cwd",
|
|
54
|
-
"eject",
|
|
55
54
|
"outdir",
|
|
56
55
|
"forceConsistentTypeExtension",
|
|
57
56
|
"outExtension",
|
|
58
57
|
"emitTokensOnly",
|
|
59
58
|
"presets",
|
|
60
|
-
"plugins"
|
|
61
|
-
"hooks"
|
|
59
|
+
"plugins"
|
|
62
60
|
];
|
|
63
61
|
const format = [
|
|
64
62
|
"hash",
|
|
@@ -103,7 +101,7 @@ const artifactConfigDeps = {
|
|
|
103
101
|
"types-entry": [],
|
|
104
102
|
"types-gen": [],
|
|
105
103
|
"types-gen-system": [],
|
|
106
|
-
themes: ["
|
|
104
|
+
themes: ["theme.variants"].concat(tokens),
|
|
107
105
|
"static-css": [
|
|
108
106
|
"staticCss",
|
|
109
107
|
"patterns",
|
|
@@ -117,6 +115,7 @@ const artifactMatchers = Object.entries(artifactConfigDeps).map(([key, paths]) =
|
|
|
117
115
|
if (!paths.length) return () => void 0;
|
|
118
116
|
return createMatcher(key, paths.concat(all));
|
|
119
117
|
});
|
|
118
|
+
Array.from(new Set([...all, ...Object.values(artifactConfigDeps).flat()]));
|
|
120
119
|
//#endregion
|
|
121
120
|
//#region src/diff-config.ts
|
|
122
121
|
const runIfFn = (fn) => typeof fn === "function" ? fn() : fn;
|
package/dist/diff-config.mjs
CHANGED
|
@@ -27,14 +27,12 @@ function createMatcher(id, patterns) {
|
|
|
27
27
|
const all = [
|
|
28
28
|
"clean",
|
|
29
29
|
"cwd",
|
|
30
|
-
"eject",
|
|
31
30
|
"outdir",
|
|
32
31
|
"forceConsistentTypeExtension",
|
|
33
32
|
"outExtension",
|
|
34
33
|
"emitTokensOnly",
|
|
35
34
|
"presets",
|
|
36
|
-
"plugins"
|
|
37
|
-
"hooks"
|
|
35
|
+
"plugins"
|
|
38
36
|
];
|
|
39
37
|
const format = [
|
|
40
38
|
"hash",
|
|
@@ -79,7 +77,7 @@ const artifactConfigDeps = {
|
|
|
79
77
|
"types-entry": [],
|
|
80
78
|
"types-gen": [],
|
|
81
79
|
"types-gen-system": [],
|
|
82
|
-
themes: ["
|
|
80
|
+
themes: ["theme.variants"].concat(tokens),
|
|
83
81
|
"static-css": [
|
|
84
82
|
"staticCss",
|
|
85
83
|
"patterns",
|
|
@@ -93,6 +91,7 @@ const artifactMatchers = Object.entries(artifactConfigDeps).map(([key, paths]) =
|
|
|
93
91
|
if (!paths.length) return () => void 0;
|
|
94
92
|
return createMatcher(key, paths.concat(all));
|
|
95
93
|
});
|
|
94
|
+
Array.from(new Set([...all, ...Object.values(artifactConfigDeps).flat()]));
|
|
96
95
|
//#endregion
|
|
97
96
|
//#region src/diff-config.ts
|
|
98
97
|
const runIfFn = (fn) => typeof fn === "function" ? fn() : fn;
|
package/dist/index.cjs
CHANGED
|
@@ -28,14 +28,14 @@ let escalade_sync = require("escalade/sync");
|
|
|
28
28
|
escalade_sync = __toESM(escalade_sync);
|
|
29
29
|
let path = require("path");
|
|
30
30
|
path = __toESM(path);
|
|
31
|
+
let _bamboocss_preset_base = require("@bamboocss/preset-base");
|
|
32
|
+
let _bamboocss_preset_bamboo = require("@bamboocss/preset-bamboo");
|
|
31
33
|
let microdiff = require("microdiff");
|
|
32
34
|
microdiff = __toESM(microdiff);
|
|
33
35
|
let fs = require("fs");
|
|
34
36
|
fs = __toESM(fs);
|
|
35
37
|
let typescript = require("typescript");
|
|
36
38
|
typescript = __toESM(typescript);
|
|
37
|
-
let _bamboocss_preset_base = require("@bamboocss/preset-base");
|
|
38
|
-
let _bamboocss_preset_bamboo = require("@bamboocss/preset-bamboo");
|
|
39
39
|
//#region src/is-bamboo-config.ts
|
|
40
40
|
const configName = "bamboo";
|
|
41
41
|
const bambooConfigFiles = new Set([
|
|
@@ -86,6 +86,25 @@ async function bundleConfig(options) {
|
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
88
|
//#endregion
|
|
89
|
+
//#region src/bundled-preset.ts
|
|
90
|
+
const bundledPresets = {
|
|
91
|
+
"@bamboocss/preset-base": _bamboocss_preset_base.preset,
|
|
92
|
+
"@bamboocss/preset-bamboo": _bamboocss_preset_bamboo.preset,
|
|
93
|
+
"@bamboocss/dev/presets": _bamboocss_preset_bamboo.preset
|
|
94
|
+
};
|
|
95
|
+
const bundledPresetsNames = Object.keys(bundledPresets);
|
|
96
|
+
const isBundledPreset = (preset) => bundledPresetsNames.includes(preset);
|
|
97
|
+
const getBundledPreset = (preset) => {
|
|
98
|
+
return typeof preset === "string" && isBundledPreset(preset) ? bundledPresets[preset] : void 0;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* What `presets` loads when a config does not list any.
|
|
102
|
+
*
|
|
103
|
+
* Exported so a config that adds a preset can keep them without restating them:
|
|
104
|
+
* `presets: [...defaultPresets, myPreset]`. Spread it — the array is shared.
|
|
105
|
+
*/
|
|
106
|
+
const defaultPresets = [_bamboocss_preset_base.preset, _bamboocss_preset_bamboo.preset];
|
|
107
|
+
//#endregion
|
|
89
108
|
//#region src/create-matcher.ts
|
|
90
109
|
/**
|
|
91
110
|
* Acts like a .gitignore matcher
|
|
@@ -113,14 +132,12 @@ function createMatcher(id, patterns) {
|
|
|
113
132
|
const all = [
|
|
114
133
|
"clean",
|
|
115
134
|
"cwd",
|
|
116
|
-
"eject",
|
|
117
135
|
"outdir",
|
|
118
136
|
"forceConsistentTypeExtension",
|
|
119
137
|
"outExtension",
|
|
120
138
|
"emitTokensOnly",
|
|
121
139
|
"presets",
|
|
122
|
-
"plugins"
|
|
123
|
-
"hooks"
|
|
140
|
+
"plugins"
|
|
124
141
|
];
|
|
125
142
|
const format = [
|
|
126
143
|
"hash",
|
|
@@ -165,7 +182,7 @@ const artifactConfigDeps = {
|
|
|
165
182
|
"types-entry": [],
|
|
166
183
|
"types-gen": [],
|
|
167
184
|
"types-gen-system": [],
|
|
168
|
-
themes: ["
|
|
185
|
+
themes: ["theme.variants"].concat(tokens),
|
|
169
186
|
"static-css": [
|
|
170
187
|
"staticCss",
|
|
171
188
|
"patterns",
|
|
@@ -179,6 +196,7 @@ const artifactMatchers = Object.entries(artifactConfigDeps).map(([key, paths]) =
|
|
|
179
196
|
if (!paths.length) return () => void 0;
|
|
180
197
|
return createMatcher(key, paths.concat(all));
|
|
181
198
|
});
|
|
199
|
+
Array.from(new Set([...all, ...Object.values(artifactConfigDeps).flat()]));
|
|
182
200
|
//#endregion
|
|
183
201
|
//#region src/diff-config.ts
|
|
184
202
|
const runIfFn = (fn) => typeof fn === "function" ? fn() : fn;
|
|
@@ -505,18 +523,35 @@ const tryCatch = (name, fn) => {
|
|
|
505
523
|
};
|
|
506
524
|
//#endregion
|
|
507
525
|
//#region src/validation/utils.ts
|
|
508
|
-
|
|
509
|
-
|
|
526
|
+
/**
|
|
527
|
+
* A reference to another token: `token(colors.red.300)`.
|
|
528
|
+
*
|
|
529
|
+
* Deliberately a copy of the regex in `@bamboocss/token-dictionary`, which this package does not
|
|
530
|
+
* depend on. The two must agree: validation is what reports a missing or circular reference, so a
|
|
531
|
+
* spelling only the dictionary understands is one this never checks — which is silence, not an
|
|
532
|
+
* error, and exactly what a spelling change here is most likely to cause.
|
|
533
|
+
*/
|
|
534
|
+
const REFERENCE_REGEX = /token\(([^(),]+)\)/g;
|
|
510
535
|
const isValidToken = (token) => (0, _bamboocss_shared.isObject)(token) && Object.hasOwnProperty.call(token, "value");
|
|
511
|
-
const isTokenReference = (value) => typeof value === "string" &&
|
|
536
|
+
const isTokenReference = (value) => typeof value === "string" && getReferences(value).length > 0;
|
|
537
|
+
/**
|
|
538
|
+
* The retired curly reference — `{colors.red.300}`, or `{$spacing-2}` under a custom
|
|
539
|
+
* `formatTokenName`. A copy of the regex in `@bamboocss/token-dictionary`, which this package
|
|
540
|
+
* does not depend on.
|
|
541
|
+
*
|
|
542
|
+
* Reported here as well as there because a *token* value carrying one is the worse case: the
|
|
543
|
+
* text is emitted into the stylesheet rather than dropped, and validation is the only thing that
|
|
544
|
+
* can name which token it came from.
|
|
545
|
+
*/
|
|
546
|
+
const CURLY_REFERENCE = /\{[^{}\s:;"']+\}/;
|
|
547
|
+
const findCurlyReference = (value) => value.includes("{") ? CURLY_REFERENCE.exec(value)?.[0] ?? void 0 : void 0;
|
|
548
|
+
/** The retired `token(path, fallback)` form. See `findCurlyReference` for why these fail. */
|
|
549
|
+
const FALLBACK_REFERENCE = /token\([^(),]+,[^()]*\)/;
|
|
550
|
+
const findFallbackReference = (value) => value.includes("token(") ? FALLBACK_REFERENCE.exec(value)?.[0] ?? void 0 : void 0;
|
|
512
551
|
const formatPath = (path) => path;
|
|
513
552
|
function getReferences(value) {
|
|
514
553
|
if (typeof value !== "string") return [];
|
|
515
|
-
|
|
516
|
-
if (!matches) return [];
|
|
517
|
-
return matches.map((match) => match.replace(curlyBracketRegex, "")).map((value) => {
|
|
518
|
-
return value.trim().split("/")[0];
|
|
519
|
-
});
|
|
554
|
+
return [...value.matchAll(REFERENCE_REGEX)].map((match) => match[1].trim().split("/")[0]).filter(Boolean);
|
|
520
555
|
}
|
|
521
556
|
const serializeTokenValue = (value) => {
|
|
522
557
|
if ((0, _bamboocss_shared.isString)(value)) return value;
|
|
@@ -573,28 +608,71 @@ const tokenKeys = [
|
|
|
573
608
|
"deprecated"
|
|
574
609
|
];
|
|
575
610
|
/**
|
|
611
|
+
* Options whose scalar form is shorthand for setting every member of their object form.
|
|
612
|
+
*
|
|
613
|
+
* `hash: true` says both `cssVar` and `className`; `prefix: 'bb'` says both; `preflight: true`
|
|
614
|
+
* says "on, with the defaults". Expanding them is what lets the object forms compose: a preset
|
|
615
|
+
* that sets `prefix.className` and an app that sets `prefix.cssVar` should end up with both,
|
|
616
|
+
* and before this the app's object replaced the preset's wholesale — silently, since the two
|
|
617
|
+
* name different members. `hash`'s members are optional, so writing the partial form that
|
|
618
|
+
* triggered it is the natural thing to do.
|
|
619
|
+
*
|
|
620
|
+
* `preflight: false` has no object form — there is no member meaning "off" — so it stays a
|
|
621
|
+
* scalar and wins outright when it is the value the winning config states.
|
|
622
|
+
*/
|
|
623
|
+
const SCALAR_SHORTHANDS = {
|
|
624
|
+
hash: (value) => typeof value === "boolean" ? {
|
|
625
|
+
cssVar: value,
|
|
626
|
+
className: value
|
|
627
|
+
} : value,
|
|
628
|
+
prefix: (value) => typeof value === "string" ? {
|
|
629
|
+
cssVar: value,
|
|
630
|
+
className: value
|
|
631
|
+
} : value,
|
|
632
|
+
preflight: (value) => value === true ? {} : value
|
|
633
|
+
};
|
|
634
|
+
/**
|
|
635
|
+
* Merge one of those, winner-first per member.
|
|
636
|
+
*
|
|
637
|
+
* `records` arrives in precedence order — the user's config, then each preset — which is the
|
|
638
|
+
* order `assign` wants, since it only fills keys the target does not already have.
|
|
639
|
+
*/
|
|
640
|
+
function mergeScalarShorthand(key, records) {
|
|
641
|
+
const normalize = SCALAR_SHORTHANDS[key];
|
|
642
|
+
const values = records.map((record) => record[key]).filter((value) => value !== void 0);
|
|
643
|
+
if (!values.length) return void 0;
|
|
644
|
+
if (values[0] === false) return false;
|
|
645
|
+
const objects = values.map(normalize).filter((value) => value !== null && typeof value === "object");
|
|
646
|
+
if (!objects.length) return values[0];
|
|
647
|
+
const merged = objects.reduce((acc, object) => (0, _bamboocss_shared.assign)(acc, object), {});
|
|
648
|
+
return isEmptyObject(merged) ? values[0] : merged;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
576
651
|
* Merge all configs into a single config
|
|
577
652
|
*/
|
|
578
653
|
function mergeConfigs(configs) {
|
|
579
|
-
const userConfig = configs.at(-1);
|
|
580
|
-
const pluginHooks = userConfig.plugins ?? [];
|
|
581
|
-
if (userConfig.hooks) pluginHooks.push({
|
|
582
|
-
name: _bamboocss_shared.BAMBOO_CONFIG_NAME,
|
|
583
|
-
hooks: userConfig.hooks
|
|
584
|
-
});
|
|
585
654
|
const reversed = Array.from(configs).reverse();
|
|
655
|
+
const theme = mergeExtensions(reversed.map((config) => config.theme ?? {}));
|
|
656
|
+
const themeVariants = mergeExtensions(reversed.map((config) => config.theme?.variants ?? {}));
|
|
657
|
+
if (isEmptyObject(themeVariants)) delete theme.variants;
|
|
658
|
+
else theme.variants = themeVariants;
|
|
659
|
+
const global = compact({
|
|
660
|
+
css: mergeExtensions(reversed.map((config) => config.global?.css ?? {})),
|
|
661
|
+
vars: mergeExtensions(reversed.map((config) => config.global?.vars ?? {})),
|
|
662
|
+
fontface: mergeExtensions(reversed.map((config) => config.global?.fontface ?? {})),
|
|
663
|
+
positionTry: mergeExtensions(reversed.map((config) => config.global?.positionTry ?? {}))
|
|
664
|
+
});
|
|
586
665
|
const withoutEmpty = compact((0, _bamboocss_shared.assign)({
|
|
587
666
|
conditions: mergeExtensions(reversed.map((config) => config.conditions ?? {})),
|
|
588
|
-
theme
|
|
667
|
+
theme,
|
|
589
668
|
patterns: mergeExtensions(reversed.map((config) => config.patterns ?? {})),
|
|
590
669
|
utilities: mergeExtensions(reversed.map((config) => config.utilities ?? {})),
|
|
591
|
-
|
|
592
|
-
globalVars: mergeExtensions(reversed.map((config) => config.globalVars ?? {})),
|
|
593
|
-
globalFontface: mergeExtensions(reversed.map((config) => config.globalFontface ?? {})),
|
|
594
|
-
globalPositionTry: mergeExtensions(reversed.map((config) => config.globalPositionTry ?? {})),
|
|
670
|
+
global,
|
|
595
671
|
staticCss: mergeExtensions(reversed.map((config) => config.staticCss ?? {})),
|
|
596
|
-
|
|
597
|
-
|
|
672
|
+
prune: mergeExtensions(reversed.map((config) => config.prune ?? {})),
|
|
673
|
+
hash: mergeScalarShorthand("hash", reversed),
|
|
674
|
+
prefix: mergeScalarShorthand("prefix", reversed),
|
|
675
|
+
preflight: mergeScalarShorthand("preflight", reversed)
|
|
598
676
|
}, ...reversed));
|
|
599
677
|
/**
|
|
600
678
|
* Properly merge tokens between flat/nested forms by setting the flat form as the default
|
|
@@ -679,18 +757,6 @@ async function getResolvedConfig(config, cwd, hooks) {
|
|
|
679
757
|
return merged;
|
|
680
758
|
}
|
|
681
759
|
//#endregion
|
|
682
|
-
//#region src/bundled-preset.ts
|
|
683
|
-
const bundledPresets = {
|
|
684
|
-
"@bamboocss/preset-base": _bamboocss_preset_base.preset,
|
|
685
|
-
"@bamboocss/preset-bamboo": _bamboocss_preset_bamboo.preset,
|
|
686
|
-
"@bamboocss/dev/presets": _bamboocss_preset_bamboo.preset
|
|
687
|
-
};
|
|
688
|
-
const bundledPresetsNames = Object.keys(bundledPresets);
|
|
689
|
-
const isBundledPreset = (preset) => bundledPresetsNames.includes(preset);
|
|
690
|
-
const getBundledPreset = (preset) => {
|
|
691
|
-
return typeof preset === "string" && isBundledPreset(preset) ? bundledPresets[preset] : void 0;
|
|
692
|
-
};
|
|
693
|
-
//#endregion
|
|
694
760
|
//#region src/validation/validate-artifact.ts
|
|
695
761
|
const validateArtifactNames = (names, addError) => {
|
|
696
762
|
names.recipes.forEach((recipeName) => {
|
|
@@ -785,6 +851,145 @@ const validateRecipes = (options) => {
|
|
|
785
851
|
return artifacts;
|
|
786
852
|
};
|
|
787
853
|
//#endregion
|
|
854
|
+
//#region src/validation/validate-removed.ts
|
|
855
|
+
/**
|
|
856
|
+
* Config options that no longer exist, and what replaced them.
|
|
857
|
+
*
|
|
858
|
+
* An unknown key is otherwise *silently ignored* — nothing walks the config for keys it does not
|
|
859
|
+
* recognise. So removing an option without this leaves the worst possible upgrade: the build
|
|
860
|
+
* reverts to the default and says nothing, and an assertion the user asked for simply stops being
|
|
861
|
+
* enforced. That is exactly the shape a renamed prune flag would have taken.
|
|
862
|
+
*
|
|
863
|
+
* Keyed by the removed name so the message can say what to write instead, rather than reporting a
|
|
864
|
+
* bare "unknown option". Entries can be dropped a release or two after removal, once nobody is
|
|
865
|
+
* upgrading across them.
|
|
866
|
+
*/
|
|
867
|
+
const REMOVED = {
|
|
868
|
+
pruneUnusedTokens: (value) => value === "strict" ? `\`pruneUnusedTokens: 'strict'\` is now \`prune: { tokens: 'accounted', unresolvedPath: 'error' }\`.` : `\`pruneUnusedTokens\` is now \`prune: { tokens: '${value === false ? "off" : "reachable"}' }\`.`,
|
|
869
|
+
pruneUnusedKeyframes: (value) => `\`pruneUnusedKeyframes\` is now \`prune: { keyframes: ${value === false ? "false" : "true"} }\`.`,
|
|
870
|
+
prunePreflight: (value) => `\`prunePreflight\` is now \`prune: { preflight: ${value === false ? "false" : "true"} }\`.`,
|
|
871
|
+
globalCss: () => `\`globalCss\` is now \`global: { css }\`.`,
|
|
872
|
+
globalFontface: () => `\`globalFontface\` is now \`global: { fontface }\`.`,
|
|
873
|
+
globalPositionTry: () => `\`globalPositionTry\` is now \`global: { positionTry }\`.`,
|
|
874
|
+
globalVars: () => `\`globalVars\` is now \`global: { vars }\`.`,
|
|
875
|
+
themes: () => `\`themes\` is now \`theme.variants\`. One character from \`theme\`, both spellings valid, so the typo resolved to a different feature instead of an error.`,
|
|
876
|
+
eject: (value) => value ? `\`eject: true\` is now \`presets: []\`. \`presets\` is the complete list — an unset \`presets\` loads \`defaultPresets\`, and listing your own no longer keeps a default underneath it.` : `\`eject: false\` was the default and no longer exists — delete it. \`presets\` is now the complete list.`,
|
|
877
|
+
hooks: () => `\`hooks\` is now a plugin: \`plugins: [{ name: 'my-app', hooks: { ... } }]\`. One mechanism had two spellings, and the nameless one left every diagnostic about a hook with nothing to print. Ordering is now just the order of the array, rather than "plugins in sequence, then the config's own last".`,
|
|
878
|
+
lightningcss: (value) => value ? `\`lightningcss: true\` is now \`plugins: [pluginLightningcss()]\` from \`@bamboocss/plugin-lightningcss\`, which you install yourself. The flag forced a static import, so every project carried the native binary whether or not it was on.` : `\`lightningcss: false\` was the default and no longer exists — delete it.`
|
|
879
|
+
};
|
|
880
|
+
/** Values that no longer exist for an option that does. Keyed by option, then by old value. */
|
|
881
|
+
const RETIRED_VALUES = { validation: { none: `\`validation: 'none'\` is now \`validation: 'off'\`, matching \`prune.unresolvedPath\`.` } };
|
|
882
|
+
/** Removed keys nested one level down, keyed by their parent. */
|
|
883
|
+
const COMPOSITION_MOVED = (old, prop) => () => `\`theme.${old}\` is now \`theme.mixins\`, applied through \`css({ mixin: '…' })\` rather than \`css({ ${prop}: '…' })\`. The three keys ran through one registration and differed only in which properties the value could set — an arbitrary partition that cost a bundle spanning two of them a second key and a second application.`;
|
|
884
|
+
const REMOVED_NESTED = {
|
|
885
|
+
prune: { unresolved: (value) => `\`prune.unresolved\` is now \`prune.unresolvedPath\`, and the accounting pass it used to switch on is now \`prune.tokens: 'accounted'\` — write \`prune: { tokens: 'accounted', unresolvedPath: '${value === "error" ? "error" : "warn"}' }\`. They are separate because \`'off'\` used to mean two things at once: no accounting, and no report.` },
|
|
886
|
+
theme: {
|
|
887
|
+
textStyles: COMPOSITION_MOVED("textStyles", "textStyle"),
|
|
888
|
+
layerStyles: COMPOSITION_MOVED("layerStyles", "layerStyle"),
|
|
889
|
+
animationStyles: COMPOSITION_MOVED("animationStyles", "animationStyle")
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
/**
|
|
893
|
+
* Options a config still sets that no longer exist — a hard error, and not silenceable.
|
|
894
|
+
*
|
|
895
|
+
* Runs ahead of `validation` and ignores it, for the reason `assertNoRetiredSyntax` does. The
|
|
896
|
+
* rest of `validateConfig` reports opinions about a config that will still build; this reports a
|
|
897
|
+
* config that is *provably* not the one being read. An unknown key might be forward-compatible —
|
|
898
|
+
* a setting for a version you have not installed yet — but a key on this list is only reachable
|
|
899
|
+
* by a config written against a version that is behind the one running it.
|
|
900
|
+
*
|
|
901
|
+
* It used to warn, which is the wrong severity for exactly the upgrade this exists to catch. A
|
|
902
|
+
* warning scrolls past in CI, and a removed option is silent in every other way: the build
|
|
903
|
+
* reverts to the default and the assertion the user asked for stops being enforced. These
|
|
904
|
+
* removals ship in minor versions, so a Renovate auto-merge sails through a warning without a
|
|
905
|
+
* person ever reading it — the one case where nothing else can catch it.
|
|
906
|
+
*
|
|
907
|
+
* Every occurrence is collected before throwing, because the point is to fix a config once
|
|
908
|
+
* rather than to be told about it one key at a time. Delete this a release or two after removal,
|
|
909
|
+
* along with `validate-retired-syntax.ts`.
|
|
910
|
+
*/
|
|
911
|
+
function assertNoRemovedOptions(config) {
|
|
912
|
+
const found = [];
|
|
913
|
+
validateRemovedOptions(config, (scope, message) => {
|
|
914
|
+
found.push(`- [${scope}] ${message}`);
|
|
915
|
+
});
|
|
916
|
+
if (!found.length) return;
|
|
917
|
+
throw new _bamboocss_shared.BambooError("CONFIG_ERROR", `${found.length} config option(s) no longer exist:\n\n${found.join("\n")}\n\nNothing walks a config for keys it does not recognise, so an option left in place here is reported nowhere else — the build reverts to the default in silence, and any assertion the option asked for stops being enforced. Make the edits above and the build proceeds. This is not governed by \`validation\`, which grades a config that still builds.\n\nThis reads the config after presets are merged, so a key you cannot find in your own file came from one of them — upgrade that preset, or drop the key with a \`config:resolved\` hook.`);
|
|
918
|
+
}
|
|
919
|
+
function validateRemovedOptions(config, addError) {
|
|
920
|
+
const dict = config;
|
|
921
|
+
for (const [name, describe] of Object.entries(REMOVED)) {
|
|
922
|
+
if (!Object.hasOwn(config, name)) continue;
|
|
923
|
+
addError("config", describe(dict[name]));
|
|
924
|
+
}
|
|
925
|
+
for (const [parent, removed] of Object.entries(REMOVED_NESTED)) {
|
|
926
|
+
const value = dict[parent];
|
|
927
|
+
if (value == null || typeof value !== "object") continue;
|
|
928
|
+
for (const [name, describe] of Object.entries(removed)) {
|
|
929
|
+
if (!Object.hasOwn(value, name)) continue;
|
|
930
|
+
addError("config", describe(value[name]));
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
for (const [name, retired] of Object.entries(RETIRED_VALUES)) {
|
|
934
|
+
const value = dict[name];
|
|
935
|
+
if (typeof value !== "string") continue;
|
|
936
|
+
const message = retired[value];
|
|
937
|
+
if (message) addError("config", message);
|
|
938
|
+
}
|
|
939
|
+
if (typeof dict.prune?.tokens === "boolean") addError("config", `\`prune.tokens\` takes a strategy now, not a boolean — write \`'${dict.prune.tokens ? "reachable" : "off"}'\`. \`'accounted'\` is the new one: keeps computed from the token paths in your source rather than from what the css reaches.`);
|
|
940
|
+
for (const [name, pattern] of Object.entries(config.patterns ?? {})) {
|
|
941
|
+
const dictPattern = pattern;
|
|
942
|
+
if (!dictPattern) continue;
|
|
943
|
+
if (Object.hasOwn(dictPattern, "strict")) addError("patterns", `\`${name}.strict\` is now \`${name}.cssProps: '${dictPattern.strict ? "none" : "all"}'\`.`);
|
|
944
|
+
if (Object.hasOwn(dictPattern, "blocklist")) addError("patterns", `\`${name}.blocklist\` is now \`${name}.cssProps: { except: [...] }\`, which is no longer experimental and no longer silently dropped when the pattern also set \`strict\`.`);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
//#endregion
|
|
948
|
+
//#region src/validation/validate-retired-syntax.ts
|
|
949
|
+
/**
|
|
950
|
+
* Token values still written in a retired reference syntax.
|
|
951
|
+
*
|
|
952
|
+
* A hard error, and one that runs ahead of `validation` rather than under it. The rest of
|
|
953
|
+
* `validateConfig` reports opinions about a config that will still build; this reports a spelling
|
|
954
|
+
* that no longer means anything, whose output is broken either way — in a token value the text is
|
|
955
|
+
* emitted into the stylesheet as-is, and nothing downstream reports it. `validation: 'off'` is an
|
|
956
|
+
* opt-out of opinions, not of that.
|
|
957
|
+
*
|
|
958
|
+
* Safe to throw on because the spelling was never available for anything else: until it was
|
|
959
|
+
* removed, `{…}` in a value was consumed unconditionally — braces stripped, unresolved paths
|
|
960
|
+
* emitted bare — so no literal `{a.b}` could have survived to mean itself.
|
|
961
|
+
*
|
|
962
|
+
* Every occurrence is collected before throwing, because the point is to fix a config once rather
|
|
963
|
+
* than to be told about it one token at a time. Delete this a release or two after removal, along
|
|
964
|
+
* with `validate-removed.ts`.
|
|
965
|
+
*/
|
|
966
|
+
function assertNoRetiredSyntax(config) {
|
|
967
|
+
const found = [];
|
|
968
|
+
const collect = (source, label) => {
|
|
969
|
+
if (!source) return;
|
|
970
|
+
(0, _bamboocss_shared.walkObject)(source, (token, path) => {
|
|
971
|
+
if (!isValidToken(token)) return;
|
|
972
|
+
const value = serializeTokenValue(token.value ?? token);
|
|
973
|
+
const at = `${label}.${formatPath(path.join("."))}`;
|
|
974
|
+
const curly = findCurlyReference(value);
|
|
975
|
+
if (curly) found.push(`- \`${at}\`: \`${curly}\` → \`token(${curly.slice(1, -1)})\``);
|
|
976
|
+
const fallback = findFallbackReference(value);
|
|
977
|
+
if (fallback) {
|
|
978
|
+
const path = fallback.slice(6, fallback.lastIndexOf(",")).trim();
|
|
979
|
+
found.push(`- \`${at}\`: \`${fallback}\` → \`token(${path})\``);
|
|
980
|
+
}
|
|
981
|
+
}, { stop: isValidToken });
|
|
982
|
+
};
|
|
983
|
+
collect(config.theme?.tokens, "theme.tokens");
|
|
984
|
+
collect(config.theme?.semanticTokens, "theme.semanticTokens");
|
|
985
|
+
for (const [name, variant] of Object.entries(config.theme?.variants ?? {})) {
|
|
986
|
+
collect(variant?.tokens, `theme.variants.${name}.tokens`);
|
|
987
|
+
collect(variant?.semanticTokens, `theme.variants.${name}.semanticTokens`);
|
|
988
|
+
}
|
|
989
|
+
if (!found.length) return;
|
|
990
|
+
throw new _bamboocss_shared.BambooError("CONFIG_ERROR", `${found.length} token value(s) use a retired reference syntax:\n\n${found.join("\n")}\n\n\`{colors.red.300}\` and \`token(colors.red.300, fallback)\` were both removed so a token is referenced one way. Neither is ignored quietly — the text is emitted into the stylesheet as-is.`);
|
|
991
|
+
}
|
|
992
|
+
//#endregion
|
|
788
993
|
//#region src/validation/validate-token-references.ts
|
|
789
994
|
const validateTokenReferences = (props) => {
|
|
790
995
|
const { valueAtPath, refsByPath, addError, typeByPath } = props;
|
|
@@ -905,13 +1110,29 @@ const validateTokens = (options) => {
|
|
|
905
1110
|
* - Check for missing tokens references
|
|
906
1111
|
* - Check for conditions selectors (must contain '&')
|
|
907
1112
|
* - Check for breakpoints units (must be the same)
|
|
1113
|
+
* - Throw on options that have been removed, which are otherwise ignored in silence
|
|
1114
|
+
* - Throw on token values still written in the retired curly reference syntax
|
|
1115
|
+
*
|
|
1116
|
+
* The two throwing checks run first and answer to nothing below them. `validation` grades
|
|
1117
|
+
* *opinions about a config that still builds*; those two are evidence that the config is not
|
|
1118
|
+
* the one being read, which is a different question and not one a severity setting should
|
|
1119
|
+
* decide.
|
|
908
1120
|
*/
|
|
909
1121
|
const validateConfig = (config) => {
|
|
910
|
-
|
|
1122
|
+
assertNoRetiredSyntax(config);
|
|
1123
|
+
assertNoRemovedOptions(config);
|
|
911
1124
|
const warnings = /* @__PURE__ */ new Set();
|
|
912
1125
|
const addError = (scope, message) => {
|
|
913
1126
|
warnings.add(`[${scope}] ` + message);
|
|
914
1127
|
};
|
|
1128
|
+
const report = () => {
|
|
1129
|
+
if (!warnings.size) return;
|
|
1130
|
+
const errors = `⚠️ Invalid config:\n${Array.from(warnings).map((err) => "- " + err).join("\n")}\n`;
|
|
1131
|
+
if (config.validation === "error") throw new _bamboocss_shared.BambooError("CONFIG_ERROR", errors);
|
|
1132
|
+
_bamboocss_logger.logger.warn("config", errors);
|
|
1133
|
+
return warnings;
|
|
1134
|
+
};
|
|
1135
|
+
if (config.validation === "off") return report();
|
|
915
1136
|
validateBreakpoints(config.theme?.breakpoints, addError);
|
|
916
1137
|
validateConditions(config.conditions, addError);
|
|
917
1138
|
const artifacts = {
|
|
@@ -941,12 +1162,7 @@ const validateConfig = (config) => {
|
|
|
941
1162
|
}
|
|
942
1163
|
validatePatterns(config.patterns, artifacts);
|
|
943
1164
|
validateArtifactNames(artifacts, addError);
|
|
944
|
-
|
|
945
|
-
const errors = `⚠️ Invalid config:\n${Array.from(warnings).map((err) => "- " + err).join("\n")}\n`;
|
|
946
|
-
if (config.validation === "error") throw new _bamboocss_shared.BambooError("CONFIG_ERROR", errors);
|
|
947
|
-
_bamboocss_logger.logger.warn("config", errors);
|
|
948
|
-
return warnings;
|
|
949
|
-
}
|
|
1165
|
+
return report();
|
|
950
1166
|
};
|
|
951
1167
|
//#endregion
|
|
952
1168
|
//#region src/resolve-config.ts
|
|
@@ -956,28 +1172,41 @@ const hookUtils = {
|
|
|
956
1172
|
traverse: _bamboocss_shared.traverse
|
|
957
1173
|
};
|
|
958
1174
|
/**
|
|
959
|
-
*
|
|
960
|
-
*
|
|
961
|
-
*
|
|
1175
|
+
* The one way this rename can break a config without saying so.
|
|
1176
|
+
*
|
|
1177
|
+
* `presets` still exists and still takes a list, so nothing in `validate-removed` notices
|
|
1178
|
+
* that its meaning changed. A config that listed `[myPreset]` used to get `preset-base`
|
|
1179
|
+
* underneath it and now does not — and what `preset-base` carries is the utility table, so
|
|
1180
|
+
* the failure is every class name silently changing (`c_red_300` becomes `color_red_300`)
|
|
1181
|
+
* rather than an error. That is the shape this codebase treats as the worst upgrade there
|
|
1182
|
+
* is, so it gets a message.
|
|
1183
|
+
*
|
|
1184
|
+
* Skipped for an empty list, which is a deliberate eject and the replacement for
|
|
1185
|
+
* `eject: true`. Drop this a release or two after the rename.
|
|
1186
|
+
*/
|
|
1187
|
+
function warnIfBaseDropped(listed, resolved) {
|
|
1188
|
+
if (!listed?.length) return;
|
|
1189
|
+
if (resolved.some((preset) => preset?.name === "@bamboocss/preset-base")) return;
|
|
1190
|
+
_bamboocss_logger.logger.warn("config", "`presets` is now the complete list, and this one does not include `@bamboocss/preset-base` — so its utilities, conditions and patterns are not loaded, and generated class names change. Listing a preset used to keep `preset-base` underneath it.\n\n import { defaultPresets } from '@bamboocss/dev/presets'\n presets: [...defaultPresets, yourPreset]\n\nIf dropping it is deliberate, this is the intended behaviour and the warning goes away once `preset-base` is listed explicitly.");
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Resolve the final config (including presets).
|
|
1194
|
+
*
|
|
1195
|
+
* `presets` is authoritative: what the config lists is what is loaded, and an unset
|
|
1196
|
+
* `presets` loads `defaultPresets`. There is no implicit preset a listed one sits on top
|
|
1197
|
+
* of — `eject` used to control that, badly. Under it, listing any preset kept
|
|
1198
|
+
* `@bamboocss/preset-base` and silently dropped `@bamboocss/preset-bamboo`, so `presets`
|
|
1199
|
+
* was neither additive nor replacing, and `presets: []` meant "base only" rather than
|
|
1200
|
+
* "none". Both of those had to be discovered by reading this function.
|
|
962
1201
|
*/
|
|
963
1202
|
async function resolveConfig(result, cwd) {
|
|
964
|
-
const
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
else if (!result.config.eject) presets.add(_bamboocss_preset_bamboo.preset);
|
|
970
|
-
result.config.presets = Array.from(presets);
|
|
971
|
-
const userConfig = result.config;
|
|
972
|
-
const pluginHooks = userConfig.plugins ?? [];
|
|
973
|
-
if (userConfig.hooks) pluginHooks.push({
|
|
974
|
-
name: _bamboocss_shared.BAMBOO_CONFIG_NAME,
|
|
975
|
-
hooks: userConfig.hooks
|
|
976
|
-
});
|
|
977
|
-
const earlyHooks = mergeHooks(pluginHooks);
|
|
978
|
-
const mergedConfig = await getResolvedConfig(result.config, cwd, earlyHooks);
|
|
979
|
-
const hooks = mergedConfig.hooks ?? {};
|
|
1203
|
+
const listed = result.config.presets;
|
|
1204
|
+
result.config.presets = listed ? Array.from(new Set(listed.map((preset) => getBundledPreset(preset) ?? preset))) : [...defaultPresets];
|
|
1205
|
+
warnIfBaseDropped(listed, result.config.presets);
|
|
1206
|
+
const hooks = mergeHooks(result.config.plugins ?? []);
|
|
1207
|
+
const mergedConfig = await getResolvedConfig(result.config, cwd, hooks);
|
|
980
1208
|
if (mergedConfig.logLevel) _bamboocss_logger.logger.level = mergedConfig.logLevel;
|
|
1209
|
+
if (mergedConfig.logFilter) _bamboocss_logger.logger.filter = mergedConfig.logFilter;
|
|
981
1210
|
validateConfig(mergedConfig);
|
|
982
1211
|
const loadConfigResult = {
|
|
983
1212
|
...result,
|
|
@@ -1015,6 +1244,7 @@ async function loadConfig(options) {
|
|
|
1015
1244
|
//#endregion
|
|
1016
1245
|
exports.bundleConfig = bundleConfig;
|
|
1017
1246
|
exports.convertTsPathsToRegexes = convertTsPathsToRegexes;
|
|
1247
|
+
exports.defaultPresets = defaultPresets;
|
|
1018
1248
|
exports.diffConfigs = diffConfigs;
|
|
1019
1249
|
exports.findConfig = findConfig;
|
|
1020
1250
|
exports.getConfigDependencies = getConfigDependencies;
|
|
@@ -1022,4 +1252,6 @@ exports.getResolvedConfig = getResolvedConfig;
|
|
|
1022
1252
|
exports.loadConfig = loadConfig;
|
|
1023
1253
|
exports.mergeConfigs = mergeConfigs;
|
|
1024
1254
|
exports.mergeHooks = mergeHooks;
|
|
1255
|
+
exports.presetBamboo = _bamboocss_preset_bamboo.preset;
|
|
1256
|
+
exports.presetBase = _bamboocss_preset_base.preset;
|
|
1025
1257
|
exports.resolveConfig = resolveConfig;
|