@gtkx/config 2.0.0-beta.11 → 2.0.0-beta.12

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.
Files changed (67) hide show
  1. package/README.md +2 -2
  2. package/dist/builtin-elements.d.ts +11 -0
  3. package/dist/builtin-elements.d.ts.map +1 -0
  4. package/dist/builtin-elements.js +329 -0
  5. package/dist/builtin-elements.js.map +1 -0
  6. package/dist/config-dependencies.d.ts +5 -3
  7. package/dist/config-dependencies.d.ts.map +1 -1
  8. package/dist/config-dependencies.js +54 -22
  9. package/dist/config-dependencies.js.map +1 -1
  10. package/dist/config-error.d.ts.map +1 -1
  11. package/dist/config-error.js.map +1 -1
  12. package/dist/config-reloader.d.ts.map +1 -1
  13. package/dist/config-reloader.js +2 -8
  14. package/dist/config-reloader.js.map +1 -1
  15. package/dist/config-source.d.ts +3 -1
  16. package/dist/config-source.d.ts.map +1 -1
  17. package/dist/config-source.js +17 -2
  18. package/dist/config-source.js.map +1 -1
  19. package/dist/config.d.ts +34 -10
  20. package/dist/config.d.ts.map +1 -1
  21. package/dist/config.js +56 -2
  22. package/dist/config.js.map +1 -1
  23. package/dist/deploy.d.ts +64 -6
  24. package/dist/deploy.d.ts.map +1 -1
  25. package/dist/deploy.js +168 -10
  26. package/dist/deploy.js.map +1 -1
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/internal.d.ts +2 -2
  31. package/dist/internal.d.ts.map +1 -1
  32. package/dist/internal.js +2 -2
  33. package/dist/internal.js.map +1 -1
  34. package/dist/loader.d.ts +2 -0
  35. package/dist/loader.d.ts.map +1 -1
  36. package/dist/loader.js +15 -20
  37. package/dist/loader.js.map +1 -1
  38. package/dist/resource-base-path.d.ts.map +1 -1
  39. package/dist/schema-text.d.ts.map +1 -1
  40. package/dist/user-event-signals.d.ts.map +1 -1
  41. package/dist/virtual.d.ts +1 -1
  42. package/dist/virtual.d.ts.map +1 -1
  43. package/dist/virtual.js +3 -2
  44. package/dist/virtual.js.map +1 -1
  45. package/dist/vite-plugin.d.ts +2 -0
  46. package/dist/vite-plugin.d.ts.map +1 -1
  47. package/dist/vite-plugin.js +4 -3
  48. package/dist/vite-plugin.js.map +1 -1
  49. package/dist/vite-root.d.ts.map +1 -1
  50. package/package.json +14 -29
  51. package/src/builtin-elements.ts +338 -0
  52. package/src/config-dependencies.ts +61 -27
  53. package/src/config-error.ts +5 -3
  54. package/src/config-reloader.ts +2 -11
  55. package/src/config-source.ts +25 -2
  56. package/src/config.ts +65 -4
  57. package/src/deploy.ts +204 -25
  58. package/src/index.ts +9 -2
  59. package/src/internal.ts +2 -2
  60. package/src/loader.ts +24 -29
  61. package/src/virtual.ts +5 -4
  62. package/src/vite-plugin.ts +9 -2
  63. package/dist/static-dependencies.d.ts +0 -3
  64. package/dist/static-dependencies.d.ts.map +0 -1
  65. package/dist/static-dependencies.js +0 -350
  66. package/dist/static-dependencies.js.map +0 -1
  67. package/src/static-dependencies.ts +0 -464
@@ -0,0 +1,338 @@
1
+ import type { ElementConfigOptions, ModuleExport } from "./config.ts";
2
+
3
+ const forTypes = (types: string[], config: ElementConfigOptions): Record<string, ElementConfigOptions> =>
4
+ Object.fromEntries(types.map((type) => [type, config]));
5
+
6
+ const internal = (name: string): ModuleExport => ({ module: "@gtkx/react/internal", export: name });
7
+
8
+ const SINGLE_CHILD_TYPES: string[] = [
9
+ "AdwBin",
10
+ "AdwClamp",
11
+ "AdwNavigationPage",
12
+ "AdwSplitButton",
13
+ "AdwStatusPage",
14
+ "AdwTabOverview",
15
+ "AdwToastOverlay",
16
+ "AdwToggle",
17
+ "GtkAspectFrame",
18
+ "GtkButton",
19
+ "GtkCheckButton",
20
+ "GtkDragIcon",
21
+ "GtkExpander",
22
+ "GtkFlowBoxChild",
23
+ "GtkFrame",
24
+ "GtkGraphicsOffload",
25
+ "GtkListBoxRow",
26
+ "GtkListHeader",
27
+ "GtkListItem",
28
+ "GtkMenuButton",
29
+ "GtkPopover",
30
+ "GtkPopoverBin",
31
+ "GtkRevealer",
32
+ "GtkScrolledWindow",
33
+ "GtkSearchBar",
34
+ "GtkTreeExpander",
35
+ "GtkViewport",
36
+ "GtkWindowHandle",
37
+ ];
38
+
39
+ const CONTENT_SETTER_TYPES: string[] = ["AdwBottomSheet", "AdwOverlaySplitView"];
40
+
41
+ /**
42
+ * Built-in GTK and Adwaita element metadata: base props, component wrappers, omitted properties,
43
+ * and parent-created objects. Contains no behaviors and can be imported without loading GI bindings.
44
+ */
45
+ const BUILTIN_ELEMENTS: Record<string, ElementConfigOptions> = {
46
+ ...forTypes(SINGLE_CHILD_TYPES, {
47
+ props: internal("ChildrenProps"),
48
+ omittedProps: ["child"],
49
+ }),
50
+ ...forTypes(["GtkHeaderBar", "GtkActionBar"], {
51
+ props: internal("GtkHeaderBarProps"),
52
+ }),
53
+ GtkWindow: {
54
+ props: internal("ChildrenProps"),
55
+ component: internal("createWindowComponent"),
56
+ omittedProps: ["child"],
57
+ },
58
+ GtkApplicationWindow: {
59
+ component: internal("createApplicationWindowComponent"),
60
+ },
61
+ GtkLabel: {
62
+ props: internal("ChildrenProps"),
63
+ },
64
+ GtkTextBuffer: {
65
+ props: internal("ChildrenProps"),
66
+ },
67
+ GtkTextTag: {
68
+ props: internal("ChildrenProps"),
69
+ },
70
+ GtkTextChildAnchor: {
71
+ props: { ...internal("GtkTextChildAnchorProps"), constructOnly: ["replacement"] },
72
+ },
73
+ GtkGridLayoutChild: {
74
+ isLazy: true,
75
+ },
76
+ GtkFixedLayoutChild: {
77
+ isLazy: true,
78
+ },
79
+ GtkOverlayLayoutChild: {
80
+ isLazy: true,
81
+ },
82
+ GtkStackPage: {
83
+ isLazy: true,
84
+ },
85
+ GtkNotebookPage: {
86
+ isLazy: true,
87
+ },
88
+ GActionGroup: {
89
+ props: internal("ActionGroupProps"),
90
+ },
91
+ GtkAccessible: {
92
+ props: { module: "@gtkx/react", export: "AccessibleProps" },
93
+ },
94
+ GtkWidget: {
95
+ props: { ...internal("GtkWidgetProps"), constructOnly: ["textChildAnchor"] },
96
+ },
97
+ GtkBox: {
98
+ props: internal("ChildrenProps"),
99
+ },
100
+ GtkListBox: {
101
+ props: internal("GtkListBoxProps"),
102
+ component: internal("createListBoxComponent"),
103
+ },
104
+ GtkFlowBox: {
105
+ props: internal("ChildrenProps"),
106
+ },
107
+ GtkOverlay: {
108
+ props: internal("GtkOverlayProps"),
109
+ omittedProps: ["child"],
110
+ },
111
+ GtkShortcutController: {
112
+ props: internal("GtkShortcutControllerProps"),
113
+ },
114
+ GtkCallbackAction: {
115
+ component: internal("createCallbackActionComponent"),
116
+ props: {
117
+ ...internal("GtkCallbackActionElementProps"),
118
+ composition: "factory",
119
+ constructOnly: ["callback"],
120
+ },
121
+ },
122
+ GtkCustomLayout: {
123
+ component: internal("createCustomLayoutComponent"),
124
+ props: {
125
+ ...internal("GtkCustomLayoutElementProps"),
126
+ composition: "factory",
127
+ constructOnly: ["requestMode", "measure", "allocate"],
128
+ },
129
+ },
130
+ GtkShortcutTrigger: {
131
+ component: internal("createShortcutTriggerComponent"),
132
+ props: {
133
+ ...internal("GtkShortcutTriggerElementProps"),
134
+ composition: "factory",
135
+ constructOnly: ["accelerator"],
136
+ },
137
+ },
138
+ GtkTextView: {
139
+ acceptedChildTypes: ["GtkTextBuffer", "GtkWidget"],
140
+ props: internal("ChildrenProps"),
141
+ },
142
+ GActionMap: {
143
+ props: internal("ActionMapProps"),
144
+ },
145
+ GMenu: {
146
+ acceptedChildTypes: ["GMenuItem"],
147
+ props: internal("MenuProps"),
148
+ component: internal("createMenuComponent"),
149
+ },
150
+ GMenuItem: {
151
+ component: internal("createMenuItemComponent"),
152
+ props: { ...internal("MenuItemProps"), constructOnly: ["label", "action"] },
153
+ },
154
+ GtkColumnView: {
155
+ acceptedChildTypes: ["GtkColumnViewColumn"],
156
+ props: internal("ChildrenProps"),
157
+ },
158
+ GtkGrid: {
159
+ props: internal("ChildrenProps"),
160
+ },
161
+ GtkFixed: {
162
+ props: internal("ChildrenProps"),
163
+ },
164
+ GtkSizeGroup: {
165
+ props: internal("GtkSizeGroupProps"),
166
+ component: internal("createPortaledComponent"),
167
+ },
168
+ GtkConstraintLayout: {
169
+ component: internal("createConstraintLayoutComponent"),
170
+ props: internal("GtkConstraintLayoutProps"),
171
+ },
172
+ GtkStack: {
173
+ props: internal("ChildrenProps"),
174
+ component: internal("createStackComponent"),
175
+ },
176
+ GtkNotebook: {
177
+ props: internal("ChildrenProps"),
178
+ },
179
+ GtkApplication: {
180
+ acceptedChildTypes: ["GtkWindow"],
181
+ props: { ...internal("GtkApplicationProps"), constructOnly: ["mainOptions"] },
182
+ component: internal("createApplicationComponent"),
183
+ },
184
+ GtkAboutDialog: {
185
+ props: { ...internal("GtkAboutDialogProps"), constructOnly: ["creditSections"] },
186
+ },
187
+ GtkScale: {
188
+ props: internal("GtkScaleProps"),
189
+ },
190
+ GtkCalendar: {
191
+ props: internal("GtkCalendarProps"),
192
+ },
193
+ GtkLevelBar: {
194
+ props: internal("GtkLevelBarProps"),
195
+ },
196
+ GtkDropTarget: {
197
+ props: internal("GtkDropTargetProps"),
198
+ },
199
+ GtkDrawingArea: {
200
+ props: internal("GtkDrawingAreaProps"),
201
+ },
202
+ GtkDragSource: {
203
+ props: internal("GtkDragSourceProps"),
204
+ },
205
+ ...forTypes(CONTENT_SETTER_TYPES, {
206
+ props: internal("ChildrenProps"),
207
+ omittedProps: ["content"],
208
+ }),
209
+ AdwClampScrollable: {
210
+ props: internal("ChildrenProps"),
211
+ omittedProps: ["child"],
212
+ },
213
+ AdwViewStackPage: {
214
+ isLazy: true,
215
+ },
216
+ AdwTabPage: {
217
+ isLazy: true,
218
+ props: { ...internal("AdwTabPageProps"), constructOnly: ["parent"] },
219
+ },
220
+ AdwTabBar: {
221
+ props: internal("AdwTabDropProps"),
222
+ },
223
+ AdwTabOverview: {
224
+ props: internal("AdwTabDropProps"),
225
+ omittedProps: ["child"],
226
+ },
227
+ AdwAboutDialog: {
228
+ props: {
229
+ ...internal("AdwAboutDialogProps"),
230
+ constructOnly: ["links", "creditSections", "acknowledgementSections", "legalSections", "otherApps"],
231
+ },
232
+ },
233
+ AdwLayout: {
234
+ isLazy: true,
235
+ },
236
+ AdwSidebar: {
237
+ acceptedChildTypes: ["AdwSidebarSection"],
238
+ props: internal("ChildrenProps"),
239
+ },
240
+ AdwSidebarSection: {
241
+ acceptedChildTypes: ["AdwSidebarItem"],
242
+ props: internal("ChildrenProps"),
243
+ },
244
+ AdwMultiLayoutView: {
245
+ props: internal("AdwMultiLayoutViewProps"),
246
+ component: internal("createMultiLayoutViewComponent"),
247
+ },
248
+ AdwPreferencesRow: {
249
+ props: internal("AdwPreferencesRowProps"),
250
+ },
251
+ AdwDialog: {
252
+ props: internal("AdwBreakpointsProps"),
253
+ component: internal("createDialogComponent"),
254
+ omittedProps: ["child"],
255
+ },
256
+ AdwApplicationWindow: {
257
+ props: internal("AdwBreakpointsProps"),
258
+ omittedProps: ["content"],
259
+ },
260
+ AdwWindow: {
261
+ props: internal("AdwBreakpointsProps"),
262
+ omittedProps: ["content"],
263
+ },
264
+ AdwBreakpointBin: {
265
+ props: internal("AdwBreakpointsProps"),
266
+ omittedProps: ["child"],
267
+ },
268
+ AdwActionRow: {
269
+ props: internal("AdwPreferencesRowProps"),
270
+ },
271
+ AdwEntryRow: {
272
+ props: internal("AdwPreferencesRowProps"),
273
+ },
274
+ AdwExpanderRow: {
275
+ props: internal("AdwExpanderRowProps"),
276
+ },
277
+ AdwNavigationSplitView: {
278
+ acceptedChildTypes: ["AdwNavigationPage"],
279
+ props: internal("ChildrenProps"),
280
+ omittedProps: ["content"],
281
+ },
282
+ AdwWrapBox: {
283
+ props: internal("ChildrenProps"),
284
+ },
285
+ AdwCarousel: {
286
+ props: internal("ChildrenProps"),
287
+ },
288
+ AdwPreferencesPage: {
289
+ acceptedChildTypes: ["AdwPreferencesGroup"],
290
+ props: internal("ChildrenProps"),
291
+ },
292
+ AdwPreferencesDialog: {
293
+ acceptedChildTypes: ["AdwPreferencesPage"],
294
+ props: internal("ChildrenProps"),
295
+ },
296
+ AdwPreferencesGroup: {
297
+ props: internal("ChildrenProps"),
298
+ },
299
+ AdwTabView: {
300
+ props: internal("ChildrenProps"),
301
+ },
302
+ AdwNavigationView: {
303
+ acceptedChildTypes: ["AdwNavigationPage"],
304
+ props: internal("ChildrenProps"),
305
+ },
306
+ AdwViewStack: {
307
+ props: internal("ChildrenProps"),
308
+ component: internal("createViewStackComponent"),
309
+ },
310
+ AdwToolbarView: {
311
+ props: internal("AdwToolbarViewProps"),
312
+ omittedProps: ["content"],
313
+ },
314
+ AdwHeaderBar: {
315
+ props: internal("GtkHeaderBarProps"),
316
+ },
317
+ AdwShortcutsDialog: {
318
+ acceptedChildTypes: ["AdwShortcutsSection"],
319
+ props: internal("ChildrenProps"),
320
+ },
321
+ AdwShortcutsSection: {
322
+ acceptedChildTypes: ["AdwShortcutsItem"],
323
+ props: internal("ChildrenProps"),
324
+ },
325
+ AdwToggleGroup: {
326
+ props: { ...internal("AdwToggleGroupProps"), composition: "intersection" },
327
+ acceptedChildTypes: ["AdwToggle"],
328
+ component: internal("createToggleGroupComponent"),
329
+ },
330
+ AdwAlertDialog: {
331
+ props: internal("AdwAlertDialogProps"),
332
+ omittedProps: ["extraChild"],
333
+ },
334
+ };
335
+
336
+ export { SINGLE_CHILD_TYPES, CONTENT_SETTER_TYPES, BUILTIN_ELEMENTS };
337
+
338
+ export type { ElementConfigOptions, ElementPropsExport, ModuleExport } from "./config.ts";
@@ -1,15 +1,17 @@
1
- import { createJiti, type Jiti, type TransformOptions, type TransformResult } from "jiti";
1
+ import { createJiti, type Jiti } from "jiti";
2
2
  import { AsyncLocalStorage } from "node:async_hooks";
3
- import { createRequire, registerHooks } from "node:module";
3
+ import { registerHooks } from "node:module";
4
4
  import { extname, sep } from "node:path";
5
- import { fileURLToPath } from "node:url";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
+ import { isRecord } from "./config-error.ts";
7
+ import { configSourceCandidates } from "./config-source.ts";
6
8
 
7
- type CaptureState = { cacheKey: string; dependencies: Set<string>; transformer: Jiti };
9
+ type CaptureState = { cacheKey: string; dependencies: Set<string>; resolver: Jiti };
8
10
  type CapturedConfig<T> = { dependencies: string[]; value: T };
9
11
 
10
12
  const captureStorage: AsyncLocalStorage<CaptureState> = new AsyncLocalStorage();
11
13
  const dependenciesByValue: WeakMap<object, string[]> = new WeakMap();
12
- const CACHE_BUSTED_EXTENSIONS: ReadonlySet<string> = new Set([".cjs", ".js", ".json", ".mjs"]);
14
+ const CACHE_BUSTED_EXTENSIONS: ReadonlySet<string> = new Set([".js", ".json", ".mjs", ".mts", ".ts"]);
13
15
 
14
16
  const isTrackedPath = (path: string): boolean => !path.includes(`${sep}node_modules${sep}`);
15
17
 
@@ -25,6 +27,18 @@ const addUrl = (url: string): void => {
25
27
  }
26
28
  };
27
29
 
30
+ const addConfigSource = (path: string): void => {
31
+ for (const candidate of configSourceCandidates(path)) {
32
+ addPath(candidate);
33
+ }
34
+ };
35
+
36
+ const addFailedSource = (error: unknown): void => {
37
+ if (isRecord(error) && typeof error.url === "string" && error.url.startsWith("file:")) {
38
+ addConfigSource(fileURLToPath(error.url));
39
+ }
40
+ };
41
+
28
42
  const cacheBustedUrl = (url: string): string => {
29
43
  const state = captureStorage.getStore();
30
44
 
@@ -47,49 +61,61 @@ const cacheBustedUrl = (url: string): string => {
47
61
  const registerResolutionHook = (): ReturnType<typeof registerHooks> =>
48
62
  registerHooks({
49
63
  resolve(specifier, context, nextResolve) {
50
- const result = nextResolve(specifier, context);
64
+ let result;
65
+
66
+ try {
67
+ result = nextResolve(specifier, context);
68
+ } catch (error) {
69
+ const state = captureStorage.getStore();
70
+ const fallback = captureStorage.exit(() =>
71
+ state?.resolver.esmResolve(specifier, {
72
+ ...(context.parentURL !== undefined && { parentURL: context.parentURL }),
73
+ try: true,
74
+ }),
75
+ );
76
+
77
+ if (fallback === undefined) {
78
+ addFailedSource(error);
79
+ throw error;
80
+ }
81
+
82
+ result = nextResolve(fallback, context);
83
+ }
84
+
51
85
  addUrl(result.url);
52
86
 
53
87
  return { ...result, url: cacheBustedUrl(result.url) };
54
88
  },
55
89
  });
56
90
 
57
- const clearNativeModuleCache = (dependencies: Iterable<string>): void => {
58
- const cache = createRequire(import.meta.url).cache;
59
-
60
- for (const path of dependencies) {
61
- Reflect.deleteProperty(cache, path);
62
- }
63
- };
64
-
65
- const transformConfigModule = (options: TransformOptions): TransformResult => {
66
- const state = captureStorage.getStore();
67
-
68
- if (state === undefined) {
69
- throw new Error("Configuration transformation started outside a dependency capture");
91
+ const importConfigModule: Jiti["import"] = async <T>(path: string, options?: { default?: true }): Promise<T> => {
92
+ if ([".cjs", ".cts"].includes(extname(path))) {
93
+ throw new Error(`Configuration modules must use ESM: ${path}`);
70
94
  }
71
95
 
72
- if (options.filename !== undefined) {
73
- addPath(options.filename);
74
- }
96
+ const value = (await import(pathToFileURL(path).href, {
97
+ ...(extname(path) === ".json" && { with: { type: "json" } }),
98
+ })) as Record<string, unknown>;
75
99
 
76
- return { code: state.transformer.transform(options) };
100
+ return (options?.default ? (value.default ?? { ...value }) : value) as T;
77
101
  };
78
102
 
103
+ const createConfigImporter = (): Jiti =>
104
+ Object.assign(createJiti(import.meta.url, { fsCache: false, moduleCache: false }), { import: importConfigModule });
105
+
79
106
  const setConfigDependencies = (value: object, dependencies: Iterable<string>): void => {
80
107
  const paths = [...new Set(dependencies)];
81
108
  dependenciesByValue.set(value, paths);
82
- clearNativeModuleCache(paths);
83
109
  };
84
110
 
85
111
  const configDependenciesFor = (value: unknown): string[] =>
86
- typeof value === "object" && value !== null ? dependenciesByValue.get(value) ?? [] : [];
112
+ typeof value === "object" && value !== null ? (dependenciesByValue.get(value) ?? []) : [];
87
113
 
88
114
  const captureConfigDependencies = async <T>(operation: () => Promise<T>): Promise<CapturedConfig<T>> => {
89
115
  const state: CaptureState = {
90
116
  cacheKey: process.hrtime.bigint().toString(),
91
117
  dependencies: new Set(),
92
- transformer: createJiti(import.meta.url, { fsCache: false, moduleCache: false }),
118
+ resolver: createJiti(import.meta.url, { fsCache: false, moduleCache: false }),
93
119
  };
94
120
  const hook = registerResolutionHook();
95
121
 
@@ -108,9 +134,17 @@ const captureConfigDependencies = async <T>(operation: () => Promise<T>): Promis
108
134
  }
109
135
  };
110
136
 
137
+ const loadConfigModule = async (path: string): Promise<unknown> => {
138
+ const captured = await captureConfigDependencies(() => importConfigModule(path, { default: true }));
139
+
140
+ return captured.value;
141
+ };
142
+
111
143
  export {
144
+ addConfigSource,
112
145
  captureConfigDependencies,
113
146
  configDependenciesFor,
147
+ createConfigImporter,
148
+ loadConfigModule,
114
149
  setConfigDependencies,
115
- transformConfigModule,
116
150
  };
@@ -8,9 +8,11 @@ const isRecord = (value: unknown): value is Record<string, unknown> =>
8
8
  const configPrefix = (configFile: string): string => `${configFile}:`;
9
9
 
10
10
  const missingConfigFileError = (cwd: string, configFile?: string): Error =>
11
- new Error(configFile === undefined
12
- ? `${configPrefix(DEFAULT_CONFIG_FILE)} no configuration file found in ${cwd}`
13
- : `${configPrefix(configFile)} no configuration file found in ${cwd}`);
11
+ new Error(
12
+ configFile === undefined
13
+ ? `${configPrefix(DEFAULT_CONFIG_FILE)} no configuration file found in ${cwd}`
14
+ : `${configPrefix(configFile)} no configuration file found in ${cwd}`,
15
+ );
14
16
 
15
17
  const configError = (error: z.ZodError, configFile = DEFAULT_CONFIG_FILE): Error =>
16
18
  new Error(`${configPrefix(configFile)}\n${z.prettifyError(error)}`);
@@ -1,7 +1,6 @@
1
- import { relative, resolve } from "node:path";
1
+ import { resolve } from "node:path";
2
2
  import { configDependenciesFor } from "./config-dependencies.ts";
3
3
  import { loadConfig, type LoadConfigOptions, type LoadedConfig } from "./loader.ts";
4
- import { resolveConfigDependencies } from "./static-dependencies.ts";
5
4
 
6
5
  type ConfigReloader = {
7
6
  reload: () => Promise<LoadedConfig>;
@@ -24,16 +23,8 @@ const createConfigReloader = async (
24
23
  selectedConfigFile = resolve(cwd, options.configFile);
25
24
  }
26
25
 
27
- const selectedConfigName = relative(cwd, selectedConfigFile);
28
- const resolvePaths = (): string[] => [
29
- ...new Set(
30
- [selectedConfigFile, ...knownDependencies]
31
- .flatMap((path) => resolveConfigDependencies(path, selectedConfigName, cwd)),
32
- ),
33
- ];
34
-
35
26
  return {
36
- resolvePaths,
27
+ resolvePaths: () => [...new Set([selectedConfigFile, ...knownDependencies])],
37
28
  reload: async () => {
38
29
  let loaded: LoadedConfig;
39
30
 
@@ -1,7 +1,30 @@
1
- import { basename, extname, isAbsolute, resolve } from "node:path";
1
+ import { SUPPORTED_EXTENSIONS } from "c12";
2
+ import { basename, extname, isAbsolute, join, resolve } from "node:path";
2
3
 
3
4
  type ConfigResolutionOptions = { configFile?: string | undefined; cwd?: string | undefined };
4
5
 
6
+ const CONFIG_LOCATIONS = ["gtkx.config", ".config/gtkx", ".config/gtkx.config"];
7
+ const CONFIG_EXTENSIONS = SUPPORTED_EXTENSIONS.filter((extension) => extension !== ".cjs" && extension !== ".cts");
8
+
9
+ const configCandidatePaths = (directory: string): string[] =>
10
+ CONFIG_LOCATIONS.flatMap((location) =>
11
+ CONFIG_EXTENSIONS.map((extension) => join(directory, `${location}${extension}`)),
12
+ );
13
+
14
+ const configSourceCandidates = (path: string): string[] => {
15
+ const extension = extname(path);
16
+ const fallback = extension === ".js" ? ".ts" : extension === ".mjs" ? ".mts" : undefined;
17
+
18
+ if (extension.length > 0) {
19
+ return fallback === undefined ? [path] : [path, `${path.slice(0, -extension.length)}${fallback}`];
20
+ }
21
+
22
+ return [
23
+ path,
24
+ ...CONFIG_EXTENSIONS.flatMap((candidate) => [`${path}${candidate}`, join(path, `index${candidate}`)]),
25
+ ];
26
+ };
27
+
5
28
  const isLocalConfigSource = (source: string): boolean => source.startsWith(".") || isAbsolute(source);
6
29
 
7
30
  const isDirectoryConfigSource = (source: string): boolean => {
@@ -22,4 +45,4 @@ const localConfigSourcePath = (source: string, options: ConfigResolutionOptions)
22
45
  : resolve(cwd, source);
23
46
  };
24
47
 
25
- export { localConfigSourcePath, type ConfigResolutionOptions };
48
+ export { configCandidatePaths, configSourceCandidates, localConfigSourcePath, type ConfigResolutionOptions };