@rstackjs/load-config 0.1.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,13 +13,27 @@ A config loading utility for the Rstack ecosystem, designed for loading JavaScri
13
13
  ## Installation
14
14
 
15
15
  ```bash
16
+ # pnpm
17
+ pnpm add @rstackjs/load-config -D
18
+ # yarn
19
+ yarn add @rstackjs/load-config -D
20
+ # npm
16
21
  npm add @rstackjs/load-config -D
22
+ # bun
23
+ bun add @rstackjs/load-config -D
17
24
  ```
18
25
 
19
26
  [jiti](https://github.com/unjs/jiti) is an optional peer dependency. Install it only when you use the `jiti` or `auto` loaders:
20
27
 
21
28
  ```bash
29
+ # pnpm
30
+ pnpm add jiti -D
31
+ # yarn
32
+ yarn add jiti -D
33
+ # npm
22
34
  npm add jiti -D
35
+ # bun
36
+ bun add jiti -D
23
37
  ```
24
38
 
25
39
  ## Usage
@@ -221,6 +235,27 @@ await loadConfig({
221
235
 
222
236
  When using the `native` loader and `fresh` is enabled, `dependencies` contains absolute paths for files imported by the config file.
223
237
 
238
+ ### withConfigMeta
239
+
240
+ Preserve the original config path and dependencies when loading through an adapter:
241
+
242
+ ```ts
243
+ import { withConfigMeta } from '@rstackjs/load-config';
244
+
245
+ const config = { name: 'my-tool' };
246
+
247
+ export default withConfigMeta(config, {
248
+ filePath: '/project/project.config.ts',
249
+ dependencies: ['/project/shared.ts'],
250
+ });
251
+ ```
252
+
253
+ `loadConfig` uses the supplied `filePath` and merges `dependencies` with the adapter file and its collected dependencies, removing duplicates.
254
+
255
+ - Use absolute paths. `filePath: null` means no underlying config was found; `dependencies` is optional.
256
+ - The helper modifies and returns the original config object. Repeated calls replace its metadata. Frozen or non-extensible objects are not supported.
257
+ - Call it on the final config object: object spread and JSON serialization discard the metadata.
258
+
224
259
  ## License
225
260
 
226
261
  [MIT](./LICENSE).
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  import type { LoadConfigOptions, LoadConfigResult } from './types.js';
2
- export type { ConfigDefinition, ConfigLoader, LoadConfigOptions, LoadConfigResult, } from './types.js';
2
+ export { withConfigMeta } from './meta.js';
3
+ export type { ConfigDefinition, ConfigFileMeta, ConfigLoader, LoadConfigOptions, LoadConfigResult, } from './types.js';
3
4
  export declare function loadConfig<Config = unknown, Params extends unknown[] = []>({ cwd, path, configFileNames, loader, exportName, configParams, fresh, }?: LoadConfigOptions<Params>): Promise<LoadConfigResult<Config>>;
package/dist/index.js CHANGED
@@ -37,13 +37,23 @@ const loadWithJiti = async (configPath, exportName, fresh)=>{
37
37
  dependencies: []
38
38
  };
39
39
  };
40
+ const CONFIG_META = Symbol.for('@rstackjs/load-config/meta');
41
+ function withConfigMeta(config, meta) {
42
+ Object.defineProperty(config, CONFIG_META, {
43
+ configurable: true,
44
+ value: {
45
+ filePath: meta.filePath,
46
+ dependencies: [
47
+ ...meta.dependencies ?? []
48
+ ]
49
+ }
50
+ });
51
+ return config;
52
+ }
53
+ function getConfigMeta(config) {
54
+ if (null !== config && 'object' == typeof config && Object.hasOwn(config, CONFIG_META)) return config[CONFIG_META];
55
+ }
40
56
  const JS_CONFIG_REGEXP = /\.(?:js|mjs|cjs)$/;
41
- const tryFreshImport = async (configFileURL)=>{
42
- try {
43
- const { freshImport } = await import("./freshImport.js");
44
- return await freshImport(configFileURL);
45
- } catch {}
46
- };
47
57
  const loadWithNative = async (configPath, fresh)=>{
48
58
  const configFileURL = pathToFileURL(configPath).href;
49
59
  if (!fresh) {
@@ -53,7 +63,8 @@ const loadWithNative = async (configPath, fresh)=>{
53
63
  dependencies: []
54
64
  };
55
65
  }
56
- const freshImportResult = await tryFreshImport(configFileURL);
66
+ const { freshImport } = await import("./freshImport.js");
67
+ const freshImportResult = await freshImport(configFileURL);
57
68
  if (freshImportResult) return {
58
69
  configModule: freshImportResult.result,
59
70
  dependencies: freshImportResult.dependencies.sort()
@@ -77,6 +88,7 @@ const resolveConfigPath = (root, customConfig, configFileNames = [])=>{
77
88
  return null;
78
89
  };
79
90
  async function loadConfig({ cwd = process.cwd(), path, configFileNames = [], loader = 'auto', exportName = 'default', configParams = [], fresh = false } = {}) {
91
+ if (!path && 0 === configFileNames.length) throw new Error('Either `path` or at least one `configFileNames` entry must be provided.');
80
92
  const configPath = resolveConfigPath(cwd, path, configFileNames);
81
93
  if (!configPath) return {
82
94
  content: {},
@@ -99,19 +111,28 @@ async function loadConfig({ cwd = process.cwd(), path, configFileNames = [], loa
99
111
  }
100
112
  if (!loadedConfig) loadedConfig = await loadWithJiti(configPath, exportName, fresh);
101
113
  const { configExport, dependencies } = loadedConfig;
114
+ let content;
102
115
  if (isConfigFunction(configExport)) {
103
116
  const result = await configExport(...configParams);
104
117
  if (void 0 === result) throw new Error('The config function must return a config object.');
105
- return {
106
- content: result,
107
- filePath: configPath,
108
- dependencies
109
- };
110
- }
111
- return {
112
- content: configExport,
118
+ content = result;
119
+ } else content = configExport;
120
+ const meta = getConfigMeta(content);
121
+ const result = {
122
+ content,
113
123
  filePath: configPath,
114
124
  dependencies
115
125
  };
126
+ if (meta) {
127
+ result.filePath = meta.filePath;
128
+ result.dependencies = [
129
+ ...new Set([
130
+ ...meta.dependencies ?? [],
131
+ configPath,
132
+ ...dependencies
133
+ ])
134
+ ];
135
+ }
136
+ return result;
116
137
  }
117
- export { loadConfig };
138
+ export { loadConfig, withConfigMeta };
package/dist/meta.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import type { ConfigFileMeta } from './types.js';
2
+ /**
3
+ * Attach file metadata in place and return the original configuration object.
4
+ * Repeated calls replace the metadata. Requires an extensible, unfrozen object.
5
+ * Call this after merging the config: object spread does not preserve metadata.
6
+ */
7
+ export declare function withConfigMeta<Config extends object>(config: Config, meta: ConfigFileMeta): Config;
8
+ export declare function getConfigMeta(config: unknown): ConfigFileMeta | undefined;
package/dist/types.d.ts CHANGED
@@ -14,7 +14,7 @@ export type LoadConfigOptions<Params extends unknown[] = []> = {
14
14
  path?: string;
15
15
  /**
16
16
  * Config file names to search in `cwd` when `path` is not provided.
17
- * The package-level loader has no built-in framework defaults.
17
+ * Required when `path` is not provided.
18
18
  * @default []
19
19
  */
20
20
  configFileNames?: string[];
@@ -43,19 +43,33 @@ export type LoadConfigOptions<Params extends unknown[] = []> = {
43
43
  */
44
44
  fresh?: boolean;
45
45
  };
46
+ export type ConfigFileMeta = {
47
+ /**
48
+ * Absolute path of the actual configuration file.
49
+ * Use `null` when no underlying configuration file was found.
50
+ */
51
+ filePath: string | null;
52
+ /**
53
+ * Absolute paths of additional configuration dependencies.
54
+ * These are merged with the adapter file and its collected dependencies.
55
+ */
56
+ dependencies?: readonly string[];
57
+ };
46
58
  export type LoadConfigResult<Config = unknown> = {
47
59
  /**
48
60
  * The loaded configuration object.
49
61
  */
50
62
  content: Config;
51
63
  /**
52
- * The path to the loaded configuration file.
53
- * Return `null` if the configuration file is not found.
64
+ * The path to the loaded configuration file, or the source set by `withConfigMeta`.
65
+ * Returns `null` if no configuration file was found, including an explicit
66
+ * `null` source set by `withConfigMeta`.
54
67
  */
55
68
  filePath: string | null;
56
69
  /**
57
- * Absolute file paths of statically imported (relative) dependencies of the
58
- * config file.
70
+ * Absolute paths of collected configuration dependencies. When `withConfigMeta`
71
+ * is used, also includes explicit dependencies and the loaded adapter file,
72
+ * with duplicates removed.
59
73
  */
60
74
  dependencies: string[];
61
75
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rstackjs/load-config",
3
- "version": "0.1.1",
3
+ "version": "1.0.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/rstackjs/load-config"
@@ -21,8 +21,7 @@
21
21
  "@types/node": "^24.13.2",
22
22
  "fresh-import": "^0.2.1",
23
23
  "jiti": "^2.7.0",
24
- "prettier": "^3.9.4",
25
- "rstack": "0.0.2",
24
+ "rstack": "^0.6.4",
26
25
  "typescript": "^7.0.2"
27
26
  },
28
27
  "peerDependencies": {
@@ -39,10 +38,11 @@
39
38
  },
40
39
  "scripts": {
41
40
  "build": "rs lib",
41
+ "check": "rs check --type-check",
42
42
  "dev": "rs lib -w",
43
- "lint": "rs lint && prettier -c .",
44
- "lint:write": "rs lint --fix && prettier -w .",
43
+ "format": "rs fmt",
44
+ "lint": "rs lint",
45
45
  "test": "rs test",
46
- "bump": "pnpx bumpp"
46
+ "bump": "pnpm version -m \"release: v%s\""
47
47
  }
48
48
  }