@bamboocss/config 1.11.4 → 1.12.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/index.d.mts CHANGED
@@ -1,8 +1,5 @@
1
- import { diffConfigs } from "./diff-config.mjs";
2
- import { n as convertTsPathsToRegexes, t as PathMapping } from "./ts-config-paths-CvGId8kq.mjs";
3
- import { n as mergeHooks, t as mergeConfigs } from "./merge-config-oEiBYbfB.mjs";
4
1
  import { CompilerOptions, TypeAcquisition } from "typescript";
5
- import { BambooHooks, Config, ConfigTsOptions, LoadConfigResult as LoadConfigResult$1 } from "@bamboocss/types";
2
+ import { BambooHooks as BambooHooks$1, BambooPlugin, Config, ConfigTsOptions, DiffConfigResult, LoadConfigResult as LoadConfigResult$1 } from "@bamboocss/types";
6
3
 
7
4
  //#region src/types.d.ts
8
5
  interface ConfigFileOptions {
@@ -18,6 +15,13 @@ interface BundleConfigResult<T = Config> {
18
15
  //#region src/bundle-config.d.ts
19
16
  declare function bundleConfig(options: ConfigFileOptions): Promise<BundleConfigResult>;
20
17
  //#endregion
18
+ //#region src/diff-config.d.ts
19
+ type ConfigOrFn = Config | (() => Config);
20
+ /**
21
+ * Diff the two config objects and return the list of affected properties
22
+ */
23
+ declare function diffConfigs(config: ConfigOrFn, prevConfig: Config | undefined): DiffConfigResult;
24
+ //#endregion
21
25
  //#region src/find-config.d.ts
22
26
  declare function findConfig(options: Partial<ConfigFileOptions>): string;
23
27
  //#endregion
@@ -41,6 +45,16 @@ interface TSConfig {
41
45
  * @returns the same `tsconfig.json` object.
42
46
  */
43
47
  //#endregion
48
+ //#region src/ts-config-paths.d.ts
49
+ interface PathMapping {
50
+ pattern: RegExp;
51
+ paths: string[];
52
+ }
53
+ /**
54
+ * @see https://github.com/aleclarson/vite-tsconfig-paths/blob/e8f0acf7adfcfbf77edbe937f64b4e5d39557ad0/src/mappings.ts
55
+ */
56
+ declare function convertTsPathsToRegexes(paths: Record<string, string[]>, baseUrl: string): PathMapping[];
57
+ //#endregion
44
58
  //#region src/get-mod-deps.d.ts
45
59
  interface GetDepsOptions {
46
60
  filename: string;
@@ -58,14 +72,14 @@ declare function getConfigDependencies(filePath: string, tsOptions?: ConfigTsOpt
58
72
  };
59
73
  //#endregion
60
74
  //#region src/get-resolved-config.d.ts
61
- type Extendable<T> = T & {
75
+ type Extendable$1<T> = T & {
62
76
  extend?: T;
63
77
  };
64
- type ExtendableConfig = Extendable<Config>;
78
+ type ExtendableConfig$1 = Extendable$1<Config>;
65
79
  /**
66
80
  * Recursively merge all presets into a single config (depth-first using stack)
67
81
  */
68
- declare function getResolvedConfig(config: ExtendableConfig, cwd: string, hooks?: Partial<BambooHooks>): Promise<Config>;
82
+ declare function getResolvedConfig(config: ExtendableConfig$1, cwd: string, hooks?: Partial<BambooHooks$1>): Promise<Config>;
69
83
  //#endregion
70
84
  //#region src/load-config.d.ts
71
85
  /**
@@ -81,4 +95,17 @@ declare function loadConfig(options: ConfigFileOptions): Promise<LoadConfigResul
81
95
  */
82
96
  declare function resolveConfig(result: BundleConfigResult, cwd: string): Promise<LoadConfigResult$1>;
83
97
  //#endregion
98
+ //#region src/merge-hooks.d.ts
99
+ declare const mergeHooks: (plugins: BambooPlugin[]) => BambooHooks;
100
+ //#endregion
101
+ //#region src/merge-config.d.ts
102
+ type Extendable<T> = T & {
103
+ extend?: T;
104
+ };
105
+ type ExtendableConfig = Extendable<Config>;
106
+ /**
107
+ * Merge all configs into a single config
108
+ */
109
+ declare function mergeConfigs(configs: ExtendableConfig[]): any;
110
+ //#endregion
84
111
  export { type BundleConfigResult, type GetDepsOptions, bundleConfig, convertTsPathsToRegexes, diffConfigs, findConfig, getConfigDependencies, getResolvedConfig, loadConfig, mergeConfigs, mergeHooks, resolveConfig };
package/dist/index.mjs CHANGED
@@ -1,11 +1,9 @@
1
- import { t as diffConfigs } from "./diff-config-KDQWMnQN.mjs";
2
- import { resolveTsPathPattern } from "./resolve-ts-path-pattern.mjs";
3
- import { a as isValidToken, i as isTokenReference, n as formatPath, o as serializeTokenValue, r as getReferences, s as mergeHooks, t as mergeConfigs } from "./merge-config-DI__LOWx.mjs";
4
1
  import { logger } from "@bamboocss/logger";
5
- import { BAMBOO_CONFIG_NAME, BambooError, getUnit, isObject, isString, omit, parseJson, pick, stringifyJson, traverse, walkObject } from "@bamboocss/shared";
2
+ import { BAMBOO_CONFIG_NAME, BambooError, assign, dashCase, getUnit, isObject, isString, mergeAndConcat, mergeWith, omit, parseJson, pick, stringifyJson, traverse, walkObject } from "@bamboocss/shared";
6
3
  import { bundleNRequire } from "bundle-n-require";
7
4
  import findUp from "escalade/sync";
8
- import path, { resolve } from "path";
5
+ import path, { posix, resolve, sep } from "path";
6
+ import microdiff from "microdiff";
9
7
  import fs from "fs";
10
8
  import ts from "typescript";
11
9
  import { preset as presetBase } from "@bamboocss/preset-base";
@@ -60,6 +58,183 @@ async function bundleConfig(options) {
60
58
  };
61
59
  }
62
60
  //#endregion
61
+ //#region src/create-matcher.ts
62
+ /**
63
+ * Acts like a .gitignore matcher
64
+ * e.g a list of string to search for nested path with glob and exclusion allowed
65
+ * ["outdir", "theme.recipes", '*.css', '!aaa.*.bbb']
66
+ */
67
+ function createMatcher(id, patterns) {
68
+ if (!patterns?.length) return () => void 0;
69
+ const includePatterns = [];
70
+ const excludePatterns = [];
71
+ new Set(patterns).forEach((pattern) => {
72
+ const regexString = pattern.replace(/\*/g, ".*");
73
+ if (pattern.startsWith("!")) excludePatterns.push(regexString.slice(1));
74
+ else includePatterns.push(regexString);
75
+ });
76
+ const include = new RegExp(includePatterns.join("|"));
77
+ const exclude = new RegExp(excludePatterns.join("|"));
78
+ return (path) => {
79
+ if (excludePatterns.length && exclude.test(path)) return;
80
+ return include.test(path) ? id : void 0;
81
+ };
82
+ }
83
+ //#endregion
84
+ //#region src/config-deps.ts
85
+ const all = [
86
+ "clean",
87
+ "cwd",
88
+ "eject",
89
+ "outdir",
90
+ "forceConsistentTypeExtension",
91
+ "outExtension",
92
+ "emitTokensOnly",
93
+ "presets",
94
+ "plugins",
95
+ "hooks"
96
+ ];
97
+ const format = [
98
+ "syntax",
99
+ "hash",
100
+ "prefix",
101
+ "separator",
102
+ "strictTokens",
103
+ "strictPropertyValues",
104
+ "shorthands"
105
+ ];
106
+ const tokens = [
107
+ "utilities",
108
+ "conditions",
109
+ "theme.tokens",
110
+ "theme.semanticTokens",
111
+ "theme.breakpoints",
112
+ "theme.containerNames",
113
+ "theme.containerSizes"
114
+ ];
115
+ const jsx = [
116
+ "jsxFramework",
117
+ "jsxFactory",
118
+ "jsxStyleProps",
119
+ "syntax"
120
+ ];
121
+ const common = tokens.concat(jsx, format);
122
+ const artifactConfigDeps = {
123
+ helpers: ["syntax", "jsxFramework"],
124
+ keyframes: ["theme.keyframes", "layers"],
125
+ "design-tokens": ["layers", "!utilities.*.className"].concat(tokens),
126
+ types: ["!utilities.*.className"].concat(common),
127
+ "css-fn": common,
128
+ cva: ["syntax"],
129
+ sva: ["syntax"],
130
+ cx: [],
131
+ "create-recipe": [
132
+ "separator",
133
+ "prefix",
134
+ "hash"
135
+ ],
136
+ "recipes-index": ["theme.recipes", "theme.slotRecipes"],
137
+ recipes: ["theme.recipes", "theme.slotRecipes"],
138
+ "patterns-index": ["syntax", "patterns"],
139
+ patterns: ["syntax", "patterns"],
140
+ "jsx-is-valid-prop": common,
141
+ "jsx-factory": jsx,
142
+ "jsx-helpers": jsx,
143
+ "jsx-patterns": jsx.concat("patterns"),
144
+ "jsx-patterns-index": jsx.concat("patterns"),
145
+ "jsx-create-style-context": jsx,
146
+ "css-index": ["syntax"],
147
+ "package.json": ["forceConsistentTypeExtension", "outExtension"],
148
+ "types-styles": ["shorthands"],
149
+ "types-conditions": ["conditions"],
150
+ "types-jsx": jsx,
151
+ "types-entry": [],
152
+ "types-gen": [],
153
+ "types-gen-system": [],
154
+ themes: ["themes"].concat(tokens),
155
+ "static-css": [
156
+ "staticCss",
157
+ "patterns",
158
+ "theme.recipes",
159
+ "theme.slotRecipes"
160
+ ].concat(tokens),
161
+ styles: [],
162
+ "styles.css": []
163
+ };
164
+ const artifactMatchers = Object.entries(artifactConfigDeps).map(([key, paths]) => {
165
+ if (!paths.length) return () => void 0;
166
+ return createMatcher(key, paths.concat(all));
167
+ });
168
+ //#endregion
169
+ //#region src/diff-config.ts
170
+ const runIfFn = (fn) => typeof fn === "function" ? fn() : fn;
171
+ /**
172
+ * Check if recipes were empty before and non-empty now (or vice versa)
173
+ */
174
+ const hasRecipeStateTransition = (prevConfig, nextConfig) => {
175
+ const prevRecipes = prevConfig.theme?.recipes ?? {};
176
+ const prevSlotRecipes = prevConfig.theme?.slotRecipes ?? {};
177
+ const prevHasRecipes = Object.keys(prevRecipes).length > 0 || Object.keys(prevSlotRecipes).length > 0;
178
+ const nextRecipes = nextConfig.theme?.recipes ?? {};
179
+ const nextSlotRecipes = nextConfig.theme?.slotRecipes ?? {};
180
+ return prevHasRecipes !== (Object.keys(nextRecipes).length > 0 || Object.keys(nextSlotRecipes).length > 0);
181
+ };
182
+ /**
183
+ * Diff the two config objects and return the list of affected properties
184
+ */
185
+ function diffConfigs(config, prevConfig) {
186
+ const affected = {
187
+ artifacts: /* @__PURE__ */ new Set(),
188
+ hasConfigChanged: false,
189
+ diffs: []
190
+ };
191
+ if (!prevConfig) {
192
+ affected.hasConfigChanged = true;
193
+ return affected;
194
+ }
195
+ const configDiff = microdiff(prevConfig, runIfFn(config));
196
+ if (!configDiff.length) return affected;
197
+ affected.hasConfigChanged = true;
198
+ affected.diffs = configDiff;
199
+ configDiff.forEach((change) => {
200
+ const changePath = change.path.join(".");
201
+ artifactMatchers.forEach((matcher) => {
202
+ const id = matcher(changePath);
203
+ if (!id) return;
204
+ if (id === "recipes") {
205
+ const name = dashCase(change.path.slice(1, 3).join("."));
206
+ affected.artifacts.add(name);
207
+ }
208
+ if (id === "patterns") {
209
+ const name = dashCase(change.path.slice(0, 2).join("."));
210
+ affected.artifacts.add(name);
211
+ }
212
+ affected.artifacts.add(id);
213
+ });
214
+ });
215
+ if (affected.artifacts.has("recipes") || affected.artifacts.has("recipes-index")) {
216
+ if (hasRecipeStateTransition(prevConfig, runIfFn(config))) affected.artifacts.add("create-recipe");
217
+ }
218
+ return affected;
219
+ }
220
+ //#endregion
221
+ //#region src/resolve-ts-path-pattern.ts
222
+ /**
223
+ * @see https://github.com/aleclarson/vite-tsconfig-paths/blob/e8f0acf7adfcfbf77edbe937f64b4e5d39557ad0/src/index.ts#LL231C57-L231C57
224
+ */
225
+ const resolveTsPathPattern = (pathMappings, moduleSpecifier) => {
226
+ for (const mapping of pathMappings) {
227
+ const match = moduleSpecifier.match(mapping.pattern);
228
+ if (!match) continue;
229
+ for (const pathTemplate of mapping.paths) {
230
+ let starCount = 0;
231
+ return pathTemplate.replace(/\*/g, () => {
232
+ return match[Math.min(++starCount, match.length - 1)];
233
+ }).split(sep).join(posix.sep);
234
+ }
235
+ }
236
+ };
237
+ //#endregion
63
238
  //#region src/ts-config-paths.ts
64
239
  /**
65
240
  * @see https://github.com/aleclarson/vite-tsconfig-paths/blob/e8f0acf7adfcfbf77edbe937f64b4e5d39557ad0/src/mappings.ts
@@ -195,6 +370,273 @@ function getConfigDependencies(filePath, tsOptions = { pathMappings: [] }, compi
195
370
  };
196
371
  }
197
372
  //#endregion
373
+ //#region src/merge-hooks.ts
374
+ const mergeHooks = (plugins) => {
375
+ const hooksFns = {};
376
+ plugins.forEach(({ name, hooks }) => {
377
+ Object.entries(hooks ?? {}).forEach(([key, value]) => {
378
+ if (!hooksFns[key]) hooksFns[key] = [];
379
+ hooksFns[key].push([name, value]);
380
+ });
381
+ });
382
+ return Object.fromEntries(Object.entries(hooksFns).map(([key, entries]) => {
383
+ const fns = entries.map(([name, fn]) => tryCatch(name, fn));
384
+ const reducer = key in reducers ? reducers[key] : void 0;
385
+ if (reducer) return [key, reducer(fns)];
386
+ return [key, syncHooks.includes(key) ? callAll(...fns) : callAllAsync(...fns)];
387
+ }));
388
+ };
389
+ const createReducer = (reducer) => reducer;
390
+ const reducers = {
391
+ "config:resolved": createReducer((fns) => async (_args) => {
392
+ const args = Object.assign({}, _args);
393
+ const original = _args.config;
394
+ let config = args.config;
395
+ for (const hookFn of fns) {
396
+ const result = await hookFn(Object.assign(args, {
397
+ config,
398
+ original
399
+ }));
400
+ if (result !== void 0) config = result;
401
+ }
402
+ return config;
403
+ }),
404
+ "parser:before": createReducer((fns) => (_args) => {
405
+ const args = Object.assign({}, _args);
406
+ const original = _args.content;
407
+ let content = args.content;
408
+ for (const hookFn of fns) {
409
+ const result = hookFn(Object.assign(args, {
410
+ content,
411
+ original
412
+ }));
413
+ if (result !== void 0) content = result;
414
+ }
415
+ return content;
416
+ }),
417
+ "parser:preprocess": createReducer((fns) => (_args) => {
418
+ const args = Object.assign({}, _args);
419
+ const original = _args.data;
420
+ let data = args.data;
421
+ for (const hookFn of fns) {
422
+ const result = hookFn(Object.assign(args, {
423
+ data,
424
+ original
425
+ }));
426
+ if (result !== void 0) data = result;
427
+ }
428
+ return data;
429
+ }),
430
+ "cssgen:done": createReducer((fns) => (_args) => {
431
+ const args = Object.assign({}, _args);
432
+ const original = _args.content;
433
+ let content = args.content;
434
+ for (const hookFn of fns) {
435
+ const result = hookFn(Object.assign(args, {
436
+ content,
437
+ original
438
+ }));
439
+ if (result !== void 0) content = result;
440
+ }
441
+ return content;
442
+ }),
443
+ "codegen:prepare": createReducer((fns) => async (_args) => {
444
+ const args = Object.assign({}, _args);
445
+ const original = _args.artifacts;
446
+ let artifacts = args.artifacts;
447
+ for (const hookFn of fns) {
448
+ const result = await hookFn(Object.assign(args, {
449
+ artifacts,
450
+ original
451
+ }));
452
+ if (result) artifacts = result;
453
+ }
454
+ return artifacts;
455
+ }),
456
+ "preset:resolved": createReducer((fns) => async (_args) => {
457
+ const args = Object.assign({}, _args);
458
+ const original = _args.preset;
459
+ let preset = args.preset;
460
+ for (const hookFn of fns) {
461
+ const result = await hookFn(Object.assign(args, {
462
+ preset,
463
+ original
464
+ }));
465
+ if (result !== void 0) preset = result;
466
+ }
467
+ return preset;
468
+ }),
469
+ "css:optimize": createReducer((fns) => (_args) => {
470
+ const args = Object.assign({}, _args);
471
+ const original = _args.css;
472
+ let css = args.css;
473
+ for (const hookFn of fns) {
474
+ const result = hookFn(Object.assign(args, {
475
+ css,
476
+ original
477
+ }));
478
+ if (result !== void 0) css = result;
479
+ }
480
+ return css;
481
+ })
482
+ };
483
+ const syncHooks = [
484
+ "context:created",
485
+ "parser:before",
486
+ "parser:preprocess",
487
+ "parser:after",
488
+ "cssgen:done",
489
+ "css:optimize"
490
+ ];
491
+ const callAllAsync = (...fns) => async (...a) => {
492
+ for (const fn of fns) await fn?.(...a);
493
+ };
494
+ const callAll = (...fns) => (...a) => {
495
+ fns.forEach((fn) => fn?.(...a));
496
+ };
497
+ const tryCatch = (name, fn) => {
498
+ return (...args) => {
499
+ try {
500
+ return fn(...args);
501
+ } catch (e) {
502
+ logger.caughtError("hooks", `Error in plugin "${name}"`, e);
503
+ }
504
+ };
505
+ };
506
+ //#endregion
507
+ //#region src/validation/utils.ts
508
+ const REFERENCE_REGEX = /({([^}]*)})/g;
509
+ const curlyBracketRegex = /[{}]/g;
510
+ const isValidToken = (token) => isObject(token) && Object.hasOwnProperty.call(token, "value");
511
+ const isTokenReference = (value) => typeof value === "string" && REFERENCE_REGEX.test(value);
512
+ const formatPath = (path) => path;
513
+ function getReferences(value) {
514
+ if (typeof value !== "string") return [];
515
+ const matches = value.match(REFERENCE_REGEX);
516
+ if (!matches) return [];
517
+ return matches.map((match) => match.replace(curlyBracketRegex, "")).map((value) => {
518
+ return value.trim().split("/")[0];
519
+ });
520
+ }
521
+ const serializeTokenValue = (value) => {
522
+ if (isString(value)) return value;
523
+ if (isObject(value)) return Object.values(value).map((v) => serializeTokenValue(v)).join(" ");
524
+ if (Array.isArray(value)) return value.map((v) => serializeTokenValue(v)).join(" ");
525
+ return value.toString();
526
+ };
527
+ //#endregion
528
+ //#region src/merge-config.ts
529
+ /**
530
+ * Collect all `extend` properties into an array (to avoid mutation)
531
+ */
532
+ function getExtends(items) {
533
+ return items.reduce((merged, { extend }) => {
534
+ if (!extend) return merged;
535
+ return mergeWith(merged, extend, (originalValue, newValue) => {
536
+ if (newValue === void 0) return originalValue ?? [];
537
+ if (originalValue === void 0) return [newValue];
538
+ if (Array.isArray(originalValue)) return [newValue, ...originalValue];
539
+ return [newValue, originalValue];
540
+ });
541
+ }, {});
542
+ }
543
+ /**
544
+ * Separate the `extend` properties from the rest of the object
545
+ */
546
+ function mergeRecords(records) {
547
+ return {
548
+ ...records.reduce((acc, record) => assign(acc, record), {}),
549
+ extend: getExtends(records)
550
+ };
551
+ }
552
+ /**
553
+ * Merge all `extend` properties into the rest of the object
554
+ */
555
+ function mergeExtensions(records) {
556
+ const { extend = [], ...restProps } = mergeRecords(records);
557
+ return mergeWith(restProps, extend, (obj, extensions) => {
558
+ return mergeAndConcat({}, obj, ...extensions);
559
+ });
560
+ }
561
+ const isEmptyObject = (obj) => typeof obj === "object" && Object.keys(obj).length === 0;
562
+ const compact = (obj) => {
563
+ return Object.keys(obj).reduce((acc, key) => {
564
+ if (obj[key] !== void 0 && !isEmptyObject(obj[key])) acc[key] = obj[key];
565
+ return acc;
566
+ }, {});
567
+ };
568
+ const tokenKeys = [
569
+ "description",
570
+ "extensions",
571
+ "type",
572
+ "value",
573
+ "deprecated"
574
+ ];
575
+ /**
576
+ * Merge all configs into a single config
577
+ */
578
+ function mergeConfigs(configs) {
579
+ const userConfig = configs.at(-1);
580
+ const pluginHooks = userConfig.plugins ?? [];
581
+ if (userConfig.hooks) pluginHooks.push({
582
+ name: BAMBOO_CONFIG_NAME,
583
+ hooks: userConfig.hooks
584
+ });
585
+ const reversed = Array.from(configs).reverse();
586
+ const withoutEmpty = compact(assign({
587
+ conditions: mergeExtensions(reversed.map((config) => config.conditions ?? {})),
588
+ theme: mergeExtensions(reversed.map((config) => config.theme ?? {})),
589
+ patterns: mergeExtensions(reversed.map((config) => config.patterns ?? {})),
590
+ utilities: mergeExtensions(reversed.map((config) => config.utilities ?? {})),
591
+ globalCss: mergeExtensions(reversed.map((config) => config.globalCss ?? {})),
592
+ globalVars: mergeExtensions(reversed.map((config) => config.globalVars ?? {})),
593
+ globalFontface: mergeExtensions(reversed.map((config) => config.globalFontface ?? {})),
594
+ globalPositionTry: mergeExtensions(reversed.map((config) => config.globalPositionTry ?? {})),
595
+ staticCss: mergeExtensions(reversed.map((config) => config.staticCss ?? {})),
596
+ themes: mergeExtensions(reversed.map((config) => config.themes ?? {})),
597
+ hooks: mergeHooks(pluginHooks)
598
+ }, ...reversed));
599
+ /**
600
+ * Properly merge tokens between flat/nested forms by setting the flat form as the default
601
+ * preset:
602
+ * ```
603
+ * tokens: {
604
+ * black: {
605
+ * value: "black"
606
+ * }
607
+ * }
608
+ * // color: "black"
609
+ * ```
610
+ *
611
+ * config:
612
+ * ```
613
+ * tokens: {
614
+ * black: {
615
+ * 0: { value: "black" },
616
+ * 10: { value: "black/10" },
617
+ * 20: { value: "black/20" },
618
+ * // ...
619
+ * }
620
+ * }
621
+ *
622
+ * // color: "black.20"
623
+ * ```
624
+ */
625
+ if (withoutEmpty.theme?.tokens) walkObject(withoutEmpty.theme.tokens, (args) => args, { stop(token) {
626
+ if (!isValidToken(token)) return false;
627
+ if (Object.keys(token).filter((k) => !tokenKeys.includes(k)).length > 0) {
628
+ token.DEFAULT ||= {};
629
+ tokenKeys.forEach((key) => {
630
+ if (token[key] == null) return;
631
+ token.DEFAULT[key] ||= token[key];
632
+ delete token[key];
633
+ });
634
+ }
635
+ return true;
636
+ } });
637
+ return withoutEmpty;
638
+ }
639
+ //#endregion
198
640
  //#region src/get-resolved-config.ts
199
641
  const hookUtils$1 = {
200
642
  omit,