@vitus-labs/tools-core 1.8.1-alpha.0 → 1.8.1-alpha.2

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/lib/index.js CHANGED
@@ -1,40 +1,92 @@
1
1
  import fs from 'node:fs';
2
2
  import { createRequire } from 'node:module';
3
- import { findUpSync } from 'find-up';
4
- import { get as _get, merge } from 'lodash-es';
5
- const VITUS_LABS_FILE_NAME = 'vl-tools.config.js';
3
+ import path from 'node:path';
4
+ const VL_CONFIG_FILES = ['vl-tools.config.mjs', 'vl-tools.config.js'];
6
5
  const PACKAGE_FILE_NAME = 'package.json';
7
6
  const TYPESCRIPT_FILE_NAME = 'tsconfig.json';
8
7
  const require = createRequire(import.meta.url);
9
8
  // --------------------------------------------------------
9
+ // Utility helpers (replaces lodash-es and find-up)
10
+ // --------------------------------------------------------
11
+ const get = (obj, dotPath, defaultValue = {}) => {
12
+ const keys = dotPath.split('.');
13
+ let result = obj;
14
+ for (const key of keys) {
15
+ if (result == null)
16
+ return defaultValue;
17
+ result = result[key];
18
+ }
19
+ return result === undefined ? defaultValue : result;
20
+ };
21
+ const deepMerge = (target, source) => {
22
+ const result = { ...target };
23
+ for (const key of Object.keys(source)) {
24
+ const srcVal = source[key];
25
+ const tgtVal = result[key];
26
+ if (typeof srcVal === 'object' &&
27
+ srcVal !== null &&
28
+ !Array.isArray(srcVal) &&
29
+ typeof tgtVal === 'object' &&
30
+ tgtVal !== null &&
31
+ !Array.isArray(tgtVal)) {
32
+ result[key] = deepMerge(tgtVal, srcVal);
33
+ }
34
+ else {
35
+ result[key] = srcVal;
36
+ }
37
+ }
38
+ return result;
39
+ };
40
+ const findFileUp = (names, cwd = process.cwd()) => {
41
+ const fileNames = Array.isArray(names) ? names : [names];
42
+ let dir = path.resolve(cwd);
43
+ while (true) {
44
+ for (const name of fileNames) {
45
+ const filePath = path.join(dir, name);
46
+ try {
47
+ if (fs.statSync(filePath).isFile())
48
+ return filePath;
49
+ }
50
+ catch (_e) {
51
+ // file doesn't exist, continue
52
+ }
53
+ }
54
+ const parent = path.dirname(dir);
55
+ if (parent === dir)
56
+ return undefined;
57
+ dir = parent;
58
+ }
59
+ };
60
+ // --------------------------------------------------------
10
61
  // FIND & READ file helpers
11
62
  // --------------------------------------------------------
12
- const findFile = (filename) => findUpSync(filename, { type: 'file' });
63
+ const findFile = (filename) => findFileUp(filename);
64
+ const loadModule = (filePath) => {
65
+ try {
66
+ const imported = require(filePath);
67
+ // Handle ESM default export wrapping
68
+ return imported?.default ?? imported ?? {};
69
+ }
70
+ catch (_e) {
71
+ return {};
72
+ }
73
+ };
13
74
  const loadFileToJSON = (filename) => {
14
75
  const file = findFile(filename);
15
76
  if (!file)
16
77
  return {};
17
- let data = {};
18
78
  // try to read an exported module first
79
+ const data = loadModule(file);
80
+ if (data && Object.keys(data).length > 0)
81
+ return data;
82
+ // try to read a plain json file like tsconfig.json
19
83
  try {
20
- const importedFile = require(file);
21
- if (importedFile) {
22
- data = importedFile;
23
- }
84
+ return JSON.parse(fs.readFileSync(file, 'utf-8'));
24
85
  }
25
86
  catch (_e) {
26
87
  // ignore error
27
88
  }
28
- // try to read a plain json file like tsconfig.json
29
- if (!data) {
30
- try {
31
- data = JSON.parse(fs.readFileSync(file, 'utf-8'));
32
- }
33
- catch (_e) {
34
- // ignore error
35
- }
36
- }
37
- return data;
89
+ return {};
38
90
  };
39
91
  // --------------------------------------------------------
40
92
  // GET PACKAGE.JSON info
@@ -56,9 +108,6 @@ const getDependenciesList = (types) => {
56
108
  });
57
109
  return result;
58
110
  };
59
- // parse namespace name
60
- // const parseNamespace = (name) =>
61
- // name.startsWith('@') ? name.split('/')[0] : ''
62
111
  // converts package name to umd or iife valid format
63
112
  // example: napespace-package-name => namespacePackageName
64
113
  const camelspaceBundleName = (name) => {
@@ -76,13 +125,8 @@ const camelspaceBundleName = (name) => {
76
125
  const getPkgData = () => {
77
126
  const pkg = getPackageJSON();
78
127
  const { name } = pkg;
79
- // const namespace = parseNamespace(name)
80
128
  return {
81
129
  ...pkg,
82
- // nameWithoutPrefix: name.replace(namespace, '').replace('/', ''),
83
- // namespace,
84
- // namespaceName: namespace.replace('@', ''),
85
- // rootPath: findFilePath('package.json'),
86
130
  bundleName: camelspaceBundleName(name),
87
131
  externalDependencies: getDependenciesList([
88
132
  'dependencies',
@@ -92,11 +136,38 @@ const getPkgData = () => {
92
136
  };
93
137
  // --------------------------------------------------------
94
138
  // LOAD EXTERNAL CONFIGURATION
139
+ // Cascading: finds all vl-tools.config.{mjs,js} files from
140
+ // cwd upward, then deep-merges them (root first, closest
141
+ // package config wins).
95
142
  // --------------------------------------------------------
96
- const getExternalConfig = () => loadFileToJSON(VITUS_LABS_FILE_NAME);
143
+ const findAllConfigFiles = () => {
144
+ const files = [];
145
+ let cwd = process.cwd();
146
+ while (true) {
147
+ const file = findFileUp(VL_CONFIG_FILES, cwd);
148
+ if (!file)
149
+ break;
150
+ files.push(file);
151
+ const parentDir = path.dirname(path.dirname(file));
152
+ if (parentDir === path.dirname(file))
153
+ break;
154
+ cwd = parentDir;
155
+ }
156
+ // Root config first, closest config last (overrides)
157
+ return files.reverse();
158
+ };
159
+ const getExternalConfig = () => {
160
+ const files = findAllConfigFiles();
161
+ let config = {};
162
+ for (const file of files) {
163
+ const loaded = loadModule(file);
164
+ config = deepMerge(config, loaded);
165
+ }
166
+ return config;
167
+ };
97
168
  const loadConfigParam = (filename) => (key, defaultValue = {}) => {
98
169
  const externalConfig = loadFileToJSON(filename);
99
- return _get(externalConfig, key, defaultValue);
170
+ return get(externalConfig, key, defaultValue);
100
171
  };
101
172
  const loadVLToolsConfig = () => {
102
173
  const externalConfig = getExternalConfig();
@@ -104,15 +175,16 @@ const loadVLToolsConfig = () => {
104
175
  get config() {
105
176
  return object;
106
177
  },
107
- get: (param, defaultValue) => _get(object, param, defaultValue || {}),
108
- merge: (param) => cloneAndEnhance(merge(object, param)),
178
+ get: (param, defaultValue) => get(object, param, defaultValue || {}),
179
+ merge: (param) => cloneAndEnhance(deepMerge(param, object)),
109
180
  });
110
181
  const getOutput = (key) => {
111
- const result = _get(externalConfig, key, {});
182
+ const result = get(externalConfig, key, {});
112
183
  return cloneAndEnhance(result);
113
184
  };
114
185
  return getOutput;
115
186
  };
187
+ const defineConfig = (config) => config;
116
188
  const swapGlobals = (globals) => Object.entries(globals).reduce((acc, [key, value]) => {
117
189
  acc[value] = key;
118
190
  return acc;
@@ -120,5 +192,5 @@ const swapGlobals = (globals) => Object.entries(globals).reduce((acc, [key, valu
120
192
  const PKG = getPkgData();
121
193
  const VL_CONFIG = loadVLToolsConfig();
122
194
  const TS_CONFIG = loadFileToJSON(TYPESCRIPT_FILE_NAME);
123
- export { findFile, loadConfigParam, loadFileToJSON, loadVLToolsConfig, swapGlobals, PKG, VL_CONFIG, TS_CONFIG, };
195
+ export { defineConfig, findFile, loadConfigParam, loadFileToJSON, loadVLToolsConfig, swapGlobals, PKG, VL_CONFIG, TS_CONFIG, };
124
196
  //# sourceMappingURL=index.js.map
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,MAAM,eAAe,GAAG,CAAC,qBAAqB,EAAE,oBAAoB,CAAC,CAAA;AACrE,MAAM,iBAAiB,GAAG,cAAc,CAAA;AACxC,MAAM,oBAAoB,GAAG,eAAe,CAAA;AAE5C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAE9C,2DAA2D;AAC3D,mDAAmD;AACnD,2DAA2D;AAC3D,MAAM,GAAG,GAAG,CAAC,GAAQ,EAAE,OAAe,EAAE,eAAoB,EAAE,EAAO,EAAE;IACrE,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC/B,IAAI,MAAM,GAAG,GAAG,CAAA;IAEhB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,MAAM,IAAI,IAAI;YAAE,OAAO,YAAY,CAAA;QACvC,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;IACtB,CAAC;IAED,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAA;AACrD,CAAC,CAAA;AAED,MAAM,SAAS,GAAG,CAChB,MAA2B,EAC3B,MAA2B,EACN,EAAE;IACvB,MAAM,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,CAAA;IAE5B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;QAC1B,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;QAE1B,IACE,OAAO,MAAM,KAAK,QAAQ;YAC1B,MAAM,KAAK,IAAI;YACf,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YACtB,OAAO,MAAM,KAAK,QAAQ;YAC1B,MAAM,KAAK,IAAI;YACf,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EACtB,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QACzC,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,CAAA;QACtB,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AAED,MAAM,UAAU,GAAG,CACjB,KAAwB,EACxB,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,EACC,EAAE;IACtB,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;IACxD,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAE3B,OAAO,IAAI,EAAE,CAAC;QACZ,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACrC,IAAI,CAAC;gBACH,IAAI,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE;oBAAE,OAAO,QAAQ,CAAA;YACrD,CAAC;YAAC,OAAO,EAAE,EAAE,CAAC;gBACZ,+BAA+B;YACjC,CAAC;QACH,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAChC,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,SAAS,CAAA;QACpC,GAAG,GAAG,MAAM,CAAA;IACd,CAAC;AACH,CAAC,CAAA;AAED,2DAA2D;AAC3D,2BAA2B;AAC3B,2DAA2D;AAC3D,MAAM,QAAQ,GAAG,CAAC,QAAgB,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAA;AAE3D,MAAM,UAAU,GAAG,CAAC,QAAgB,EAAuB,EAAE;IAC3D,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;QAClC,qCAAqC;QACrC,OAAO,QAAQ,EAAE,OAAO,IAAI,QAAQ,IAAI,EAAE,CAAA;IAC5C,CAAC;IAAC,OAAO,EAAE,EAAE,CAAC;QACZ,OAAO,EAAE,CAAA;IACX,CAAC;AACH,CAAC,CAAA;AAED,MAAM,cAAc,GAAG,CAAC,QAAgB,EAAuB,EAAE;IAC/D,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAA;IAE/B,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAA;IAEpB,uCAAuC;IACvC,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;IAC7B,IAAI,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IAErD,mDAAmD;IACnD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAA;IACnD,CAAC;IAAC,OAAO,EAAE,EAAE,CAAC;QACZ,eAAe;IACjB,CAAC;IAED,OAAO,EAAE,CAAA;AACX,CAAC,CAAA;AAED,2DAA2D;AAC3D,wBAAwB;AACxB,2DAA2D;AAC3D,MAAM,cAAc,GAAG,GAAG,EAAE;IAC1B,MAAM,IAAI,GAAG,cAAc,CAAC,iBAAiB,CAAC,CAAA;IAE9C,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AAED,2DAA2D;AAC3D,iCAAiC;AACjC,2DAA2D;AAE3D,6CAA6C;AAC7C,MAAM,mBAAmB,GAAG,CAAC,KAAU,EAAE,EAAE;IACzC,MAAM,GAAG,GAAG,cAAc,EAAE,CAAA;IAC5B,IAAI,MAAM,GAAQ,EAAE,CAAA;IAEpB,KAAK,CAAC,OAAO,CAAC,CAAC,IAAS,EAAE,EAAE;QAC1B,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,CAAA;QACtB,MAAM,GAAG,CAAC,GAAG,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAA;IAClD,CAAC,CAAC,CAAA;IAEF,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AAED,oDAAoD;AACpD,0DAA0D;AAC1D,MAAM,oBAAoB,GAAG,CAAC,IAAY,EAAE,EAAE;IAC5C,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IAC1D,MAAM,WAAW,GAAG,CAAC,KAAU,EAAE,EAAE,CACjC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAS,EAAE,CAAM,EAAE,EAAE,CAC9B,CAAC,KAAK,CAAC;QACL,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAC/D,CAAA;IACH,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACnC,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAE1C,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AAED,2DAA2D;AAC3D,oBAAoB;AACpB,2DAA2D;AAC3D,MAAM,UAAU,GAAG,GAAwB,EAAE;IAC3C,MAAM,GAAG,GAAG,cAAc,EAAE,CAAA;IAC5B,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,CAAA;IAEpB,OAAO;QACL,GAAG,GAAG;QACN,UAAU,EAAE,oBAAoB,CAAC,IAAI,CAAC;QACtC,oBAAoB,EAAE,mBAAmB,CAAC;YACxC,cAAc;YACd,kBAAkB;SACnB,CAAC;KACH,CAAA;AACH,CAAC,CAAA;AAED,2DAA2D;AAC3D,8BAA8B;AAC9B,2DAA2D;AAC3D,yDAAyD;AACzD,wBAAwB;AACxB,2DAA2D;AAC3D,MAAM,kBAAkB,GAAG,GAAa,EAAE;IACxC,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,IAAI,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IAEvB,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,UAAU,CAAC,eAAe,EAAE,GAAG,CAAC,CAAA;QAC7C,IAAI,CAAC,IAAI;YAAE,MAAK;QAChB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAChB,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;QAClD,IAAI,SAAS,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,MAAK;QAC3C,GAAG,GAAG,SAAS,CAAA;IACjB,CAAC;IAED,qDAAqD;IACrD,OAAO,KAAK,CAAC,OAAO,EAAE,CAAA;AACxB,CAAC,CAAA;AAED,MAAM,iBAAiB,GAAG,GAAwB,EAAE;IAClD,MAAM,KAAK,GAAG,kBAAkB,EAAE,CAAA;IAClC,IAAI,MAAM,GAAwB,EAAE,CAAA;IAEpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;QAC/B,MAAM,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACpC,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AAED,MAAM,eAAe,GACnB,CAAC,QAAgB,EAAE,EAAE,CACrB,CAAC,GAAW,EAAE,YAAY,GAAG,EAAE,EAAE,EAAE;IACjC,MAAM,cAAc,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAA;IAE/C,OAAO,GAAG,CAAC,cAAc,EAAE,GAAG,EAAE,YAAY,CAAC,CAAA;AAC/C,CAAC,CAAA;AAEH,MAAM,iBAAiB,GAAG,GAAG,EAAE;IAC7B,MAAM,cAAc,GAAG,iBAAiB,EAAE,CAAA;IAE1C,MAAM,eAAe,GAAG,CAAC,MAA2B,EAAE,EAAE,CAAC,CAAC;QACxD,IAAI,MAAM;YACR,OAAO,MAAM,CAAA;QACf,CAAC;QACD,GAAG,EAAE,CAAC,KAAa,EAAE,YAAkB,EAAE,EAAE,CACzC,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,IAAI,EAAE,CAAC;QACxC,KAAK,EAAE,CAAC,KAA0B,EAAE,EAAE,CACpC,eAAe,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;KAC5C,CAAC,CAAA;IAEF,MAAM,SAAS,GAAG,CAAC,GAAW,EAAE,EAAE;QAChC,MAAM,MAAM,GAAG,GAAG,CAAC,cAAc,EAAE,GAAG,EAAE,EAAE,CAAC,CAAA;QAE3C,OAAO,eAAe,CAAC,MAAM,CAAC,CAAA;IAChC,CAAC,CAAA;IAED,OAAO,SAAS,CAAA;AAClB,CAAC,CAAA;AAED,MAAM,YAAY,GAAG,CAAgC,MAAS,EAAK,EAAE,CAAC,MAAM,CAAA;AAE5E,MAAM,WAAW,GAAG,CAAC,OAA+B,EAAE,EAAE,CACtD,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,CAC5B,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;IACpB,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,CAAA;IAChB,OAAO,GAAG,CAAA;AACZ,CAAC,EACD,EAAE,CACH,CAAA;AAEH,MAAM,GAAG,GAAG,UAAU,EAAE,CAAA;AACxB,MAAM,SAAS,GAAG,iBAAiB,EAAE,CAAA;AACrC,MAAM,SAAS,GAAG,cAAc,CAAC,oBAAoB,CAAC,CAAA;AAEtD,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,WAAW,EACX,GAAG,EACH,SAAS,EACT,SAAS,GACV,CAAA"}
@@ -6,6 +6,7 @@ declare const loadVLToolsConfig: () => (key: string) => {
6
6
  get: (param: string, defaultValue?: any) => any;
7
7
  merge: (param: Record<string, any>) => /*elided*/ any;
8
8
  };
9
+ declare const defineConfig: <T extends Record<string, any>>(config: T) => T;
9
10
  declare const swapGlobals: (globals: Record<string, string>) => Record<string, string>;
10
11
  declare const PKG: Record<string, any>;
11
12
  declare const VL_CONFIG: (key: string) => {
@@ -14,5 +15,5 @@ declare const VL_CONFIG: (key: string) => {
14
15
  merge: (param: Record<string, any>) => /*elided*/ any;
15
16
  };
16
17
  declare const TS_CONFIG: Record<string, any>;
17
- export { findFile, loadConfigParam, loadFileToJSON, loadVLToolsConfig, swapGlobals, PKG, VL_CONFIG, TS_CONFIG, };
18
+ export { defineConfig, findFile, loadConfigParam, loadFileToJSON, loadVLToolsConfig, swapGlobals, PKG, VL_CONFIG, TS_CONFIG, };
18
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAcA,QAAA,MAAM,QAAQ,GAAI,UAAU,MAAM,uBAA2C,CAAA;AAE7E,QAAA,MAAM,cAAc,GAAI,UAAU,MAAM,KAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CA4B5D,CAAA;AA2ED,QAAA,MAAM,eAAe,GAClB,UAAU,MAAM,MAChB,KAAK,MAAM,EAAE,iBAAiB,QAI9B,CAAA;AAEH,QAAA,MAAM,iBAAiB,cAaG,MAAM;;iBANf,MAAM,iBAAiB,GAAG;mBAExB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;CAWrC,CAAA;AAED,QAAA,MAAM,WAAW,GAAI,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,2BAOjD,CAAA;AAEH,QAAA,MAAM,GAAG,qBAAe,CAAA;AACxB,QAAA,MAAM,SAAS,QAnBW,MAAM;;iBANf,MAAM,iBAAiB,GAAG;mBAExB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;CAuBD,CAAA;AACrC,QAAA,MAAM,SAAS,qBAAuC,CAAA;AAEtD,OAAO,EACL,QAAQ,EACR,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,WAAW,EACX,GAAG,EACH,SAAS,EACT,SAAS,GACV,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AA8EA,QAAA,MAAM,QAAQ,GAAI,UAAU,MAAM,uBAAyB,CAAA;AAY3D,QAAA,MAAM,cAAc,GAAI,UAAU,MAAM,KAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAiB5D,CAAA;AAgGD,QAAA,MAAM,eAAe,GAClB,UAAU,MAAM,MAChB,KAAK,MAAM,EAAE,iBAAiB,QAI9B,CAAA;AAEH,QAAA,MAAM,iBAAiB,cAaG,MAAM;;iBANf,MAAM,iBAAiB,GAAG;mBAExB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;CAWrC,CAAA;AAED,QAAA,MAAM,YAAY,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,KAAG,CAAW,CAAA;AAE5E,QAAA,MAAM,WAAW,GAAI,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,2BAOjD,CAAA;AAEH,QAAA,MAAM,GAAG,qBAAe,CAAA;AACxB,QAAA,MAAM,SAAS,QArBW,MAAM;;iBANf,MAAM,iBAAiB,GAAG;mBAExB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;CAyBD,CAAA;AACrC,QAAA,MAAM,SAAS,qBAAuC,CAAA;AAEtD,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,WAAW,EACX,GAAG,EACH,SAAS,EACT,SAAS,GACV,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitus-labs/tools-core",
3
- "version": "1.8.1-alpha.0+120486e",
3
+ "version": "1.8.1-alpha.2+6f381e3",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,14 +29,9 @@
29
29
  "build": "tsc",
30
30
  "typecheck": "tsc --noEmit"
31
31
  },
32
- "dependencies": {
33
- "find-up": "^8.0.0",
34
- "lodash-es": "^4.17.23"
35
- },
36
32
  "devDependencies": {
37
- "@types/lodash-es": "^4.17.12",
38
- "@vitus-labs/tools-typescript": "1.8.1-alpha.0+120486e",
33
+ "@vitus-labs/tools-typescript": "1.8.1-alpha.2+6f381e3",
39
34
  "typescript": "^5.9.3"
40
35
  },
41
- "gitHead": "120486efcc0272ae215bd365af859e66ae0830d3"
36
+ "gitHead": "6f381e35e5829bc1fae660817f19bfa5098674d6"
42
37
  }
package/src/index.test.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  import { beforeEach, describe, expect, it, vi } from 'vitest'
2
2
 
3
- const mockFindUpSync = vi.fn()
4
- vi.mock('find-up', () => ({
5
- findUpSync: mockFindUpSync,
6
- }))
7
-
3
+ const mockStatSync = vi.fn()
8
4
  const mockReadFileSync = vi.fn()
9
5
  vi.mock('node:fs', () => ({
10
- default: { readFileSync: mockReadFileSync },
6
+ default: {
7
+ statSync: mockStatSync,
8
+ readFileSync: mockReadFileSync,
9
+ },
10
+ statSync: mockStatSync,
11
11
  readFileSync: mockReadFileSync,
12
12
  }))
13
13
 
@@ -16,25 +16,32 @@ vi.mock('node:module', () => ({
16
16
  createRequire: vi.fn(() => mockRequireFn),
17
17
  }))
18
18
 
19
+ // Helper: make specific paths "exist" for findFileUp
20
+ const makeFilesExist = (paths: string[]) => {
21
+ mockStatSync.mockImplementation((filePath: string) => {
22
+ if (paths.includes(filePath)) return { isFile: () => true }
23
+ throw new Error('ENOENT')
24
+ })
25
+ }
26
+
19
27
  // Default mock setup that satisfies module-level getPkgData() —
20
28
  // package.json must always be found with a valid `name` field,
21
29
  // otherwise camelspaceBundleName(undefined) crashes at import time.
22
30
  const setupDefaultMocks = () => {
23
- mockFindUpSync.mockImplementation((filename: string) => {
24
- if (filename === 'package.json') return '/mock/package.json'
25
- return undefined
26
- })
27
- mockRequireFn.mockImplementation((path: string) => {
28
- if (path === '/mock/package.json') return { name: 'mock-pkg' }
31
+ vi.spyOn(process, 'cwd').mockReturnValue('/mock/project')
32
+ makeFilesExist(['/mock/project/package.json'])
33
+ mockRequireFn.mockImplementation((p: string) => {
34
+ if (p === '/mock/project/package.json') return { name: 'mock-pkg' }
29
35
  return null
30
36
  })
31
37
  }
32
38
 
33
39
  describe('tools-core', () => {
34
40
  beforeEach(() => {
35
- mockFindUpSync.mockReset()
41
+ mockStatSync.mockReset()
36
42
  mockReadFileSync.mockReset()
37
43
  mockRequireFn.mockReset()
44
+ vi.restoreAllMocks()
38
45
  })
39
46
 
40
47
  describe('swapGlobals', () => {
@@ -58,6 +65,22 @@ describe('tools-core', () => {
58
65
  })
59
66
  })
60
67
 
68
+ describe('defineConfig', () => {
69
+ let defineConfig: <T extends Record<string, any>>(config: T) => T
70
+
71
+ beforeEach(async () => {
72
+ vi.resetModules()
73
+ setupDefaultMocks()
74
+ const mod = await import('./index.js')
75
+ defineConfig = mod.defineConfig
76
+ })
77
+
78
+ it('should return the same config object', () => {
79
+ const config = { stories: { framework: 'next' } }
80
+ expect(defineConfig(config)).toBe(config)
81
+ })
82
+ })
83
+
61
84
  describe('findFile', () => {
62
85
  let findFile: (filename: string) => string | undefined
63
86
 
@@ -68,19 +91,17 @@ describe('tools-core', () => {
68
91
  findFile = mod.findFile
69
92
  })
70
93
 
71
- it('should call findUpSync with the filename', () => {
72
- mockFindUpSync.mockReturnValue('/path/to/file.json')
73
- findFile('file.json')
74
- expect(mockFindUpSync).toHaveBeenCalledWith('file.json', { type: 'file' })
94
+ it('should return the path when file is found in cwd', () => {
95
+ makeFilesExist(['/mock/project/package.json', '/mock/project/some.json'])
96
+ expect(findFile('some.json')).toBe('/mock/project/some.json')
75
97
  })
76
98
 
77
- it('should return the path when file is found', () => {
78
- mockFindUpSync.mockReturnValue('/path/to/file.json')
79
- expect(findFile('file.json')).toBe('/path/to/file.json')
99
+ it('should walk up directories to find the file', () => {
100
+ makeFilesExist(['/mock/project/package.json', '/mock/some.json'])
101
+ expect(findFile('some.json')).toBe('/mock/some.json')
80
102
  })
81
103
 
82
104
  it('should return undefined when file is not found', () => {
83
- mockFindUpSync.mockReturnValue(undefined)
84
105
  expect(findFile('missing.json')).toBeUndefined()
85
106
  })
86
107
  })
@@ -96,27 +117,46 @@ describe('tools-core', () => {
96
117
  })
97
118
 
98
119
  it('should return empty object when file is not found', () => {
99
- mockFindUpSync.mockReturnValue(undefined)
100
120
  expect(loadFileToJSON('missing.json')).toEqual({})
101
121
  })
102
122
 
103
123
  it('should load file using require when available', () => {
104
- mockFindUpSync.mockReturnValue('/path/to/config.js')
105
- mockRequireFn.mockReturnValue({ key: 'value' })
124
+ makeFilesExist(['/mock/project/package.json', '/mock/project/config.js'])
125
+ mockRequireFn.mockImplementation((p: string) => {
126
+ if (p === '/mock/project/config.js') return { key: 'value' }
127
+ return null
128
+ })
106
129
  expect(loadFileToJSON('config.js')).toEqual({ key: 'value' })
107
130
  })
108
131
 
109
- it('should return empty object when require returns falsy', () => {
110
- mockFindUpSync.mockReturnValue('/path/to/config.json')
111
- mockRequireFn.mockReturnValue(null)
112
- expect(loadFileToJSON('config.json')).toEqual({})
132
+ it('should unwrap ESM default export', () => {
133
+ makeFilesExist(['/mock/project/package.json', '/mock/project/config.mjs'])
134
+ mockRequireFn.mockImplementation((p: string) => {
135
+ if (p === '/mock/project/config.mjs')
136
+ return { default: { key: 'value' } }
137
+ return null
138
+ })
139
+ expect(loadFileToJSON('config.mjs')).toEqual({ key: 'value' })
140
+ })
141
+
142
+ it('should fall back to JSON.parse when require returns empty', () => {
143
+ makeFilesExist(['/mock/project/package.json', '/mock/project/data.json'])
144
+ mockRequireFn.mockImplementation((p: string) => {
145
+ if (p === '/mock/project/data.json') throw new Error('require failed')
146
+ return null
147
+ })
148
+ mockReadFileSync.mockReturnValue('{"parsed": true}')
149
+ expect(loadFileToJSON('data.json')).toEqual({ parsed: true })
113
150
  })
114
151
 
115
152
  it('should return empty object when require throws', () => {
116
- mockFindUpSync.mockReturnValue('/path/to/bad.json')
153
+ makeFilesExist(['/mock/project/package.json', '/mock/project/bad.json'])
117
154
  mockRequireFn.mockImplementation(() => {
118
155
  throw new Error('require failed')
119
156
  })
157
+ mockReadFileSync.mockImplementation(() => {
158
+ throw new Error('read failed')
159
+ })
120
160
  expect(loadFileToJSON('bad.json')).toEqual({})
121
161
  })
122
162
  })
@@ -134,71 +174,145 @@ describe('tools-core', () => {
134
174
  })
135
175
 
136
176
  it('should return a function that gets a nested config value', () => {
137
- mockFindUpSync.mockReturnValue('/path/to/config.js')
138
- mockRequireFn.mockReturnValue({ build: { sourceDir: 'src' } })
177
+ makeFilesExist(['/mock/project/package.json', '/mock/project/config.js'])
178
+ mockRequireFn.mockImplementation((p: string) => {
179
+ if (p === '/mock/project/config.js')
180
+ return { build: { sourceDir: 'src' } }
181
+ return null
182
+ })
139
183
  const getParam = loadConfigParam('config.js')
140
184
  expect(getParam('build.sourceDir')).toBe('src')
141
185
  })
142
186
 
143
187
  it('should return defaultValue when key is not found', () => {
144
- mockFindUpSync.mockReturnValue('/path/to/config.js')
145
- mockRequireFn.mockReturnValue({})
188
+ makeFilesExist(['/mock/project/package.json', '/mock/project/config.js'])
189
+ mockRequireFn.mockImplementation((p: string) => {
190
+ if (p === '/mock/project/config.js') return {}
191
+ return null
192
+ })
146
193
  const getParam = loadConfigParam('config.js')
147
194
  expect(getParam('missing.key', 'default')).toBe('default')
148
195
  })
149
196
  })
150
197
 
151
198
  describe('loadVLToolsConfig', () => {
152
- let loadVLToolsConfig: () => (key: string) => any
153
-
154
199
  beforeEach(async () => {
155
200
  vi.resetModules()
156
201
  setupDefaultMocks()
157
- const mod = await import('./index.js')
158
- loadVLToolsConfig = mod.loadVLToolsConfig
159
202
  })
160
203
 
161
- it('should return a function that provides .config, .get(), .merge()', () => {
162
- mockFindUpSync.mockReturnValue('/path/to/vl-tools.config.js')
163
- mockRequireFn.mockReturnValue({ build: { sourceDir: 'src' } })
164
- const vlConfig = loadVLToolsConfig()
204
+ it('should return a function that provides .config, .get(), .merge()', async () => {
205
+ makeFilesExist([
206
+ '/mock/project/package.json',
207
+ '/mock/project/vl-tools.config.js',
208
+ ])
209
+ mockRequireFn.mockImplementation((p: string) => {
210
+ if (p === '/mock/project/package.json') return { name: 'mock-pkg' }
211
+ if (p === '/mock/project/vl-tools.config.js')
212
+ return { build: { sourceDir: 'src' } }
213
+ return null
214
+ })
215
+
216
+ const mod = await import('./index.js')
217
+ const vlConfig = mod.loadVLToolsConfig()
165
218
  const buildConfig = vlConfig('build')
166
219
  expect(buildConfig.config).toEqual({ sourceDir: 'src' })
167
220
  expect(buildConfig.get('sourceDir')).toBe('src')
168
221
  })
169
222
 
170
- it('should support chained merge calls', () => {
171
- mockFindUpSync.mockReturnValue('/path/to/vl-tools.config.js')
172
- mockRequireFn.mockReturnValue({ build: { sourceDir: 'src' } })
173
- const vlConfig = loadVLToolsConfig()
223
+ it('should support chained merge calls', async () => {
224
+ makeFilesExist([
225
+ '/mock/project/package.json',
226
+ '/mock/project/vl-tools.config.js',
227
+ ])
228
+ mockRequireFn.mockImplementation((p: string) => {
229
+ if (p === '/mock/project/package.json') return { name: 'mock-pkg' }
230
+ if (p === '/mock/project/vl-tools.config.js')
231
+ return { build: { sourceDir: 'src' } }
232
+ return null
233
+ })
234
+
235
+ const mod = await import('./index.js')
236
+ const vlConfig = mod.loadVLToolsConfig()
174
237
  const merged = vlConfig('build').merge({ outputDir: 'lib' })
175
238
  expect(merged.config).toEqual({ sourceDir: 'src', outputDir: 'lib' })
176
239
  })
177
240
 
178
- it('should return empty config when file is not found', () => {
179
- mockFindUpSync.mockReturnValue(undefined)
180
- const vlConfig = loadVLToolsConfig()
241
+ it('should return empty config when file is not found', async () => {
242
+ const mod = await import('./index.js')
243
+ const vlConfig = mod.loadVLToolsConfig()
181
244
  const result = vlConfig('build')
182
245
  expect(result.config).toEqual({})
183
246
  })
184
247
 
185
- it('should return default value with get when key is missing', () => {
186
- mockFindUpSync.mockReturnValue(undefined)
187
- const vlConfig = loadVLToolsConfig()
248
+ it('should return default value with get when key is missing', async () => {
249
+ const mod = await import('./index.js')
250
+ const vlConfig = mod.loadVLToolsConfig()
188
251
  const result = vlConfig('build')
189
252
  expect(result.get('missing')).toEqual({})
190
253
  })
254
+
255
+ it('should prefer .mjs over .js config files', async () => {
256
+ makeFilesExist([
257
+ '/mock/project/package.json',
258
+ '/mock/project/vl-tools.config.mjs',
259
+ '/mock/project/vl-tools.config.js',
260
+ ])
261
+ mockRequireFn.mockImplementation((p: string) => {
262
+ if (p === '/mock/project/package.json') return { name: 'mock-pkg' }
263
+ if (p === '/mock/project/vl-tools.config.mjs')
264
+ return { build: { sourceDir: 'mjs-src' } }
265
+ if (p === '/mock/project/vl-tools.config.js')
266
+ return { build: { sourceDir: 'js-src' } }
267
+ return null
268
+ })
269
+
270
+ const mod = await import('./index.js')
271
+ const vlConfig = mod.loadVLToolsConfig()
272
+ expect(vlConfig('build').config).toEqual({ sourceDir: 'mjs-src' })
273
+ })
274
+
275
+ it('should cascade configs from root to package (deep merge)', async () => {
276
+ vi.spyOn(process, 'cwd').mockReturnValue('/mock/packages/ui')
277
+ makeFilesExist([
278
+ '/mock/packages/ui/package.json',
279
+ '/mock/vl-tools.config.js',
280
+ '/mock/packages/ui/vl-tools.config.js',
281
+ ])
282
+ mockRequireFn.mockImplementation((p: string) => {
283
+ if (p === '/mock/packages/ui/package.json') return { name: 'mock-pkg' }
284
+ // Root config: base settings
285
+ if (p === '/mock/vl-tools.config.js')
286
+ return {
287
+ stories: { framework: 'vite', port: 6006 },
288
+ build: { sourceDir: 'src' },
289
+ }
290
+ // Package config: override framework only
291
+ if (p === '/mock/packages/ui/vl-tools.config.js')
292
+ return { stories: { framework: 'next' } }
293
+ return null
294
+ })
295
+
296
+ const mod = await import('./index.js')
297
+ const vlConfig = mod.loadVLToolsConfig()
298
+ const stories = vlConfig('stories')
299
+
300
+ // framework overridden by package config
301
+ expect(stories.get('framework')).toBe('next')
302
+ // port inherited from root config
303
+ expect(stories.get('port')).toBe(6006)
304
+ // build inherited from root config
305
+ expect(vlConfig('build').config).toEqual({ sourceDir: 'src' })
306
+ })
191
307
  })
192
308
 
193
309
  describe('module-level constants', () => {
194
310
  it('should export PKG with bundleName from scoped package name', async () => {
195
311
  vi.resetModules()
196
- mockFindUpSync.mockImplementation((filename: string) => {
197
- if (filename === 'package.json') return '/path/to/package.json'
198
- return undefined
199
- })
200
- mockRequireFn.mockImplementation((path: string) => {
201
- if (path === '/path/to/package.json') {
312
+ vi.spyOn(process, 'cwd').mockReturnValue('/path/to')
313
+ makeFilesExist(['/path/to/package.json'])
314
+ mockRequireFn.mockImplementation((p: string) => {
315
+ if (p === '/path/to/package.json') {
202
316
  return {
203
317
  name: '@test/pkg',
204
318
  version: '1.0.0',
@@ -216,12 +330,10 @@ describe('tools-core', () => {
216
330
 
217
331
  it('should handle simple hyphenated package names in bundleName', async () => {
218
332
  vi.resetModules()
219
- mockFindUpSync.mockImplementation((filename: string) => {
220
- if (filename === 'package.json') return '/path/to/package.json'
221
- return undefined
222
- })
223
- mockRequireFn.mockImplementation((path: string) => {
224
- if (path === '/path/to/package.json') {
333
+ vi.spyOn(process, 'cwd').mockReturnValue('/path/to')
334
+ makeFilesExist(['/path/to/package.json'])
335
+ mockRequireFn.mockImplementation((p: string) => {
336
+ if (p === '/path/to/package.json') {
225
337
  return { name: 'my-cool-library' }
226
338
  }
227
339
  return null
@@ -233,12 +345,10 @@ describe('tools-core', () => {
233
345
 
234
346
  it('should include peerDependencies in externalDependencies', async () => {
235
347
  vi.resetModules()
236
- mockFindUpSync.mockImplementation((filename: string) => {
237
- if (filename === 'package.json') return '/path/to/package.json'
238
- return undefined
239
- })
240
- mockRequireFn.mockImplementation((path: string) => {
241
- if (path === '/path/to/package.json') {
348
+ vi.spyOn(process, 'cwd').mockReturnValue('/path/to')
349
+ makeFilesExist(['/path/to/package.json'])
350
+ mockRequireFn.mockImplementation((p: string) => {
351
+ if (p === '/path/to/package.json') {
242
352
  return {
243
353
  name: 'my-lib',
244
354
  peerDependencies: { 'styled-components': '^6' },
@@ -260,14 +370,14 @@ describe('tools-core', () => {
260
370
 
261
371
  it('should export TS_CONFIG from tsconfig.json', async () => {
262
372
  vi.resetModules()
263
- mockFindUpSync.mockImplementation((filename: string) => {
264
- if (filename === 'package.json') return '/mock/package.json'
265
- if (filename === 'tsconfig.json') return '/path/to/tsconfig.json'
266
- return undefined
267
- })
268
- mockRequireFn.mockImplementation((path: string) => {
269
- if (path === '/mock/package.json') return { name: 'mock-pkg' }
270
- if (path === '/path/to/tsconfig.json') {
373
+ vi.spyOn(process, 'cwd').mockReturnValue('/mock/project')
374
+ makeFilesExist([
375
+ '/mock/project/package.json',
376
+ '/mock/project/tsconfig.json',
377
+ ])
378
+ mockRequireFn.mockImplementation((p: string) => {
379
+ if (p === '/mock/project/package.json') return { name: 'mock-pkg' }
380
+ if (p === '/mock/project/tsconfig.json') {
271
381
  return { compilerOptions: { strict: true } }
272
382
  }
273
383
  return null
package/src/index.ts CHANGED
@@ -1,47 +1,110 @@
1
1
  import fs from 'node:fs'
2
2
  import { createRequire } from 'node:module'
3
- import { findUpSync } from 'find-up'
4
- import { get as _get, merge } from 'lodash-es'
3
+ import path from 'node:path'
5
4
 
6
- const VITUS_LABS_FILE_NAME = 'vl-tools.config.js'
5
+ const VL_CONFIG_FILES = ['vl-tools.config.mjs', 'vl-tools.config.js']
7
6
  const PACKAGE_FILE_NAME = 'package.json'
8
7
  const TYPESCRIPT_FILE_NAME = 'tsconfig.json'
9
8
 
10
9
  const require = createRequire(import.meta.url)
11
10
 
11
+ // --------------------------------------------------------
12
+ // Utility helpers (replaces lodash-es and find-up)
13
+ // --------------------------------------------------------
14
+ const get = (obj: any, dotPath: string, defaultValue: any = {}): any => {
15
+ const keys = dotPath.split('.')
16
+ let result = obj
17
+
18
+ for (const key of keys) {
19
+ if (result == null) return defaultValue
20
+ result = result[key]
21
+ }
22
+
23
+ return result === undefined ? defaultValue : result
24
+ }
25
+
26
+ const deepMerge = (
27
+ target: Record<string, any>,
28
+ source: Record<string, any>,
29
+ ): Record<string, any> => {
30
+ const result = { ...target }
31
+
32
+ for (const key of Object.keys(source)) {
33
+ const srcVal = source[key]
34
+ const tgtVal = result[key]
35
+
36
+ if (
37
+ typeof srcVal === 'object' &&
38
+ srcVal !== null &&
39
+ !Array.isArray(srcVal) &&
40
+ typeof tgtVal === 'object' &&
41
+ tgtVal !== null &&
42
+ !Array.isArray(tgtVal)
43
+ ) {
44
+ result[key] = deepMerge(tgtVal, srcVal)
45
+ } else {
46
+ result[key] = srcVal
47
+ }
48
+ }
49
+
50
+ return result
51
+ }
52
+
53
+ const findFileUp = (
54
+ names: string | string[],
55
+ cwd = process.cwd(),
56
+ ): string | undefined => {
57
+ const fileNames = Array.isArray(names) ? names : [names]
58
+ let dir = path.resolve(cwd)
59
+
60
+ while (true) {
61
+ for (const name of fileNames) {
62
+ const filePath = path.join(dir, name)
63
+ try {
64
+ if (fs.statSync(filePath).isFile()) return filePath
65
+ } catch (_e) {
66
+ // file doesn't exist, continue
67
+ }
68
+ }
69
+
70
+ const parent = path.dirname(dir)
71
+ if (parent === dir) return undefined
72
+ dir = parent
73
+ }
74
+ }
75
+
12
76
  // --------------------------------------------------------
13
77
  // FIND & READ file helpers
14
78
  // --------------------------------------------------------
15
- const findFile = (filename: string) => findUpSync(filename, { type: 'file' })
79
+ const findFile = (filename: string) => findFileUp(filename)
80
+
81
+ const loadModule = (filePath: string): Record<string, any> => {
82
+ try {
83
+ const imported = require(filePath)
84
+ // Handle ESM default export wrapping
85
+ return imported?.default ?? imported ?? {}
86
+ } catch (_e) {
87
+ return {}
88
+ }
89
+ }
16
90
 
17
91
  const loadFileToJSON = (filename: string): Record<string, any> => {
18
92
  const file = findFile(filename)
19
93
 
20
94
  if (!file) return {}
21
95
 
22
- let data: Record<string, any> = {}
23
-
24
96
  // try to read an exported module first
25
- try {
26
- const importedFile = require(file)
97
+ const data = loadModule(file)
98
+ if (data && Object.keys(data).length > 0) return data
27
99
 
28
- if (importedFile) {
29
- data = importedFile
30
- }
100
+ // try to read a plain json file like tsconfig.json
101
+ try {
102
+ return JSON.parse(fs.readFileSync(file, 'utf-8'))
31
103
  } catch (_e) {
32
104
  // ignore error
33
105
  }
34
106
 
35
- // try to read a plain json file like tsconfig.json
36
- if (!data) {
37
- try {
38
- data = JSON.parse(fs.readFileSync(file, 'utf-8'))
39
- } catch (_e) {
40
- // ignore error
41
- }
42
- }
43
-
44
- return data
107
+ return {}
45
108
  }
46
109
 
47
110
  // --------------------------------------------------------
@@ -70,10 +133,6 @@ const getDependenciesList = (types: any) => {
70
133
  return result
71
134
  }
72
135
 
73
- // parse namespace name
74
- // const parseNamespace = (name) =>
75
- // name.startsWith('@') ? name.split('/')[0] : ''
76
-
77
136
  // converts package name to umd or iife valid format
78
137
  // example: napespace-package-name => namespacePackageName
79
138
  const camelspaceBundleName = (name: string) => {
@@ -96,14 +155,9 @@ const camelspaceBundleName = (name: string) => {
96
155
  const getPkgData = (): Record<string, any> => {
97
156
  const pkg = getPackageJSON()
98
157
  const { name } = pkg
99
- // const namespace = parseNamespace(name)
100
158
 
101
159
  return {
102
160
  ...pkg,
103
- // nameWithoutPrefix: name.replace(namespace, '').replace('/', ''),
104
- // namespace,
105
- // namespaceName: namespace.replace('@', ''),
106
- // rootPath: findFilePath('package.json'),
107
161
  bundleName: camelspaceBundleName(name),
108
162
  externalDependencies: getDependenciesList([
109
163
  'dependencies',
@@ -114,15 +168,45 @@ const getPkgData = (): Record<string, any> => {
114
168
 
115
169
  // --------------------------------------------------------
116
170
  // LOAD EXTERNAL CONFIGURATION
171
+ // Cascading: finds all vl-tools.config.{mjs,js} files from
172
+ // cwd upward, then deep-merges them (root first, closest
173
+ // package config wins).
117
174
  // --------------------------------------------------------
118
- const getExternalConfig = () => loadFileToJSON(VITUS_LABS_FILE_NAME)
175
+ const findAllConfigFiles = (): string[] => {
176
+ const files: string[] = []
177
+ let cwd = process.cwd()
178
+
179
+ while (true) {
180
+ const file = findFileUp(VL_CONFIG_FILES, cwd)
181
+ if (!file) break
182
+ files.push(file)
183
+ const parentDir = path.dirname(path.dirname(file))
184
+ if (parentDir === path.dirname(file)) break
185
+ cwd = parentDir
186
+ }
187
+
188
+ // Root config first, closest config last (overrides)
189
+ return files.reverse()
190
+ }
191
+
192
+ const getExternalConfig = (): Record<string, any> => {
193
+ const files = findAllConfigFiles()
194
+ let config: Record<string, any> = {}
195
+
196
+ for (const file of files) {
197
+ const loaded = loadModule(file)
198
+ config = deepMerge(config, loaded)
199
+ }
200
+
201
+ return config
202
+ }
119
203
 
120
204
  const loadConfigParam =
121
205
  (filename: string) =>
122
206
  (key: string, defaultValue = {}) => {
123
207
  const externalConfig = loadFileToJSON(filename)
124
208
 
125
- return _get(externalConfig, key, defaultValue)
209
+ return get(externalConfig, key, defaultValue)
126
210
  }
127
211
 
128
212
  const loadVLToolsConfig = () => {
@@ -133,13 +217,13 @@ const loadVLToolsConfig = () => {
133
217
  return object
134
218
  },
135
219
  get: (param: string, defaultValue?: any) =>
136
- _get(object, param, defaultValue || {}),
220
+ get(object, param, defaultValue || {}),
137
221
  merge: (param: Record<string, any>) =>
138
- cloneAndEnhance(merge(object, param)),
222
+ cloneAndEnhance(deepMerge(param, object)),
139
223
  })
140
224
 
141
225
  const getOutput = (key: string) => {
142
- const result = _get(externalConfig, key, {})
226
+ const result = get(externalConfig, key, {})
143
227
 
144
228
  return cloneAndEnhance(result)
145
229
  }
@@ -147,6 +231,8 @@ const loadVLToolsConfig = () => {
147
231
  return getOutput
148
232
  }
149
233
 
234
+ const defineConfig = <T extends Record<string, any>>(config: T): T => config
235
+
150
236
  const swapGlobals = (globals: Record<string, string>) =>
151
237
  Object.entries(globals).reduce<Record<string, string>>(
152
238
  (acc, [key, value]) => {
@@ -161,6 +247,7 @@ const VL_CONFIG = loadVLToolsConfig()
161
247
  const TS_CONFIG = loadFileToJSON(TYPESCRIPT_FILE_NAME)
162
248
 
163
249
  export {
250
+ defineConfig,
164
251
  findFile,
165
252
  loadConfigParam,
166
253
  loadFileToJSON,