@gtkx/config 0.21.0 → 1.0.0-rc.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.
Files changed (82) hide show
  1. package/README.md +137 -82
  2. package/dist/config-error.d.ts +16 -0
  3. package/dist/config-error.d.ts.map +1 -0
  4. package/dist/config-error.js +45 -0
  5. package/dist/config-error.js.map +1 -0
  6. package/dist/config.d.ts +73 -37
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +123 -82
  9. package/dist/config.js.map +1 -1
  10. package/dist/index.d.ts +2 -10
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +2 -8
  13. package/dist/index.js.map +1 -1
  14. package/dist/internal.d.ts +3 -0
  15. package/dist/internal.d.ts.map +1 -0
  16. package/dist/internal.js +3 -0
  17. package/dist/internal.js.map +1 -0
  18. package/dist/loader.d.ts +21 -16
  19. package/dist/loader.d.ts.map +1 -1
  20. package/dist/loader.js +26 -38
  21. package/dist/loader.js.map +1 -1
  22. package/dist/user-event-signals.d.ts +4 -0
  23. package/dist/user-event-signals.d.ts.map +1 -0
  24. package/dist/user-event-signals.js +54 -0
  25. package/dist/user-event-signals.js.map +1 -0
  26. package/dist/virtual.d.ts +9 -6
  27. package/dist/virtual.d.ts.map +1 -1
  28. package/dist/virtual.js +26 -18
  29. package/dist/virtual.js.map +1 -1
  30. package/dist/vite-plugin.d.ts +16 -0
  31. package/dist/vite-plugin.d.ts.map +1 -0
  32. package/dist/vite-plugin.js +31 -0
  33. package/dist/vite-plugin.js.map +1 -0
  34. package/package.json +18 -11
  35. package/src/config-error.ts +67 -0
  36. package/src/config.ts +227 -145
  37. package/src/index.ts +3 -83
  38. package/src/internal.ts +2 -0
  39. package/src/loader.ts +44 -55
  40. package/src/user-event-signals.ts +57 -0
  41. package/src/virtual.ts +28 -34
  42. package/src/vite-plugin.ts +51 -0
  43. package/dist/bundled-modules.d.ts +0 -2
  44. package/dist/bundled-modules.d.ts.map +0 -1
  45. package/dist/bundled-modules.js +0 -2
  46. package/dist/bundled-modules.js.map +0 -1
  47. package/dist/data-dir.d.ts +0 -4
  48. package/dist/data-dir.d.ts.map +0 -1
  49. package/dist/data-dir.js +0 -37
  50. package/dist/data-dir.js.map +0 -1
  51. package/dist/plugin.d.ts +0 -9
  52. package/dist/plugin.d.ts.map +0 -1
  53. package/dist/plugin.js +0 -24
  54. package/dist/plugin.js.map +0 -1
  55. package/dist/runtime.d.ts +0 -22
  56. package/dist/runtime.d.ts.map +0 -1
  57. package/dist/runtime.js +0 -22
  58. package/dist/runtime.js.map +0 -1
  59. package/dist/table-rules-ir.d.ts +0 -36
  60. package/dist/table-rules-ir.d.ts.map +0 -1
  61. package/dist/table-rules-ir.js +0 -2
  62. package/dist/table-rules-ir.js.map +0 -1
  63. package/dist/table-schema.d.ts +0 -107
  64. package/dist/table-schema.d.ts.map +0 -1
  65. package/dist/table-schema.js +0 -199
  66. package/dist/table-schema.js.map +0 -1
  67. package/dist/validators.d.ts +0 -4
  68. package/dist/validators.d.ts.map +0 -1
  69. package/dist/validators.js +0 -10
  70. package/dist/validators.js.map +0 -1
  71. package/dist/wrapper-protocol.d.ts +0 -12
  72. package/dist/wrapper-protocol.d.ts.map +0 -1
  73. package/dist/wrapper-protocol.js +0 -12
  74. package/dist/wrapper-protocol.js.map +0 -1
  75. package/env.d.ts +0 -26
  76. package/src/bundled-modules.ts +0 -1
  77. package/src/data-dir.ts +0 -39
  78. package/src/plugin.ts +0 -30
  79. package/src/table-rules-ir.ts +0 -42
  80. package/src/table-schema.ts +0 -342
  81. package/src/validators.ts +0 -15
  82. package/src/wrapper-protocol.ts +0 -21
package/src/config.ts CHANGED
@@ -1,191 +1,273 @@
1
- import { isValidApplicationId } from "@gtkx/utils";
2
- import {
3
- type ArrayPropRow,
4
- type ContainerPropRow,
5
- type ElementMapRule,
6
- type ObjectPropRow,
7
- type PerElementPropRows,
8
- type UserTableRows,
9
- type VirtualPropRow,
10
- validateArrayPropRows,
11
- validateContainerPropRows,
12
- validateElementMap,
13
- validateObjectPropRows,
14
- validateVirtualPropRows,
15
- } from "./table-schema.js";
16
-
17
- export const LIBRARIES_WILDCARD = "*";
18
-
19
- export const GIR_NAMESPACE_PATTERN: RegExp = /^[A-Za-z][A-Za-z0-9]*-\d+(?:\.\d+)*$/;
20
-
21
- export type GtkxConfig = UserTableRows & {
22
- libraries?: typeof LIBRARIES_WILDCARD | string[];
23
-
24
- girPath?: string[];
25
-
26
- applicationId?: string;
27
-
28
- reactCompiler?: boolean | ReactCompilerOptions;
29
- };
30
-
31
- export type ReactCompilerCompilationMode = "infer" | "syntax" | "annotation" | "all";
1
+ import { createDefineConfig, type DefineConfig } from "c12";
2
+ import { defu } from "defu";
3
+ import { resolve } from "node:path";
4
+ import { z } from "zod";
5
+ import { configError, isRecord, rawIssue } from "./config-error.js";
6
+ import { resolveUserEventSignals } from "./user-event-signals.js";
32
7
 
33
- export type ReactCompilerPanicThreshold = "none" | "critical_errors" | "all_errors";
8
+ type ReactCompilerCompilationMode = (typeof COMPILATION_MODES)[number];
9
+ type ReactCompilerPanicThreshold = (typeof PANIC_THRESHOLDS)[number];
34
10
 
35
- export type ReactCompilerOptions = {
11
+ type ReactCompilerOptions = {
36
12
  compilationMode?: ReactCompilerCompilationMode;
37
13
  panicThreshold?: ReactCompilerPanicThreshold;
38
14
  };
39
15
 
40
- export type ResolvedReactCompilerOptions = ReactCompilerOptions & {
16
+ /**
17
+ * React Compiler options resolved for the build, with the compilation target
18
+ * fixed to React 19.
19
+ */
20
+ type ResolvedReactCompilerOptions = ReactCompilerOptions & {
41
21
  target: "19";
42
22
  };
43
23
 
24
+ /**
25
+ * User-facing configuration for a GTKX project, as authored in `gtkx.config.ts`:
26
+ * the GIR libraries to bind, extra `.gir` search paths, the GApplication id,
27
+ * a module of per-element configuration (lazy flags and custom behaviors),
28
+ * per-element component wrappers keyed by GLib type name, the React Compiler and
29
+ * codegen settings, and additional user event signals to suppress during React
30
+ * commits.
31
+ */
32
+ type Config = z.infer<typeof configSchema>;
33
+
34
+ /**
35
+ * Configuration reduced to the values needed at runtime: the GApplication
36
+ * identifier, the resolved React Compiler options (`null` when disabled), the
37
+ * user event signals suppressed while a React commit is in progress, and the
38
+ * module path holding per-element configuration (`null` when unset).
39
+ */
40
+ type ResolvedConfig = {
41
+ applicationId: string;
42
+ reactCompiler: ResolvedReactCompilerOptions | null;
43
+ userEventSignals: Record<string, string[]>;
44
+ elements: string | null;
45
+ lazyElements: string[];
46
+ };
47
+
48
+ const LIBRARIES_WILDCARD = "*";
49
+ const GIR_LIBRARY_PATTERN = /^[A-Za-z][A-Za-z0-9]*-\d+(?:\.\d+)*$/;
50
+ const APPLICATION_ID_PATTERN = /^[A-Za-z_][A-Za-z0-9_-]*(\.[A-Za-z_][A-Za-z0-9_-]*)+$/;
51
+ const APPLICATION_ID_MAX_LENGTH = 255;
52
+ const COMPILATION_MODES = ["infer", "syntax", "annotation", "all"] as const;
53
+ const PANIC_THRESHOLDS = ["none", "critical_errors", "all_errors"] as const;
44
54
  const REACT_COMPILER_TARGET = "19";
55
+ const COMPILATION_MODE_SET: Set<string> = new Set(COMPILATION_MODES);
56
+ const PANIC_THRESHOLD_SET: Set<string> = new Set(PANIC_THRESHOLDS);
45
57
 
46
- export const resolveReactCompilerOptions = (
47
- setting: GtkxConfig["reactCompiler"],
48
- ): ResolvedReactCompilerOptions | null => {
49
- if (setting === false) return null;
50
- const overrides = setting === undefined || setting === true ? {} : setting;
51
- return { ...overrides, target: REACT_COMPILER_TARGET };
52
- };
58
+ const librariesSchema = z.custom<typeof LIBRARIES_WILDCARD | string[]>().check((ctx) => {
59
+ const value = ctx.value;
53
60
 
54
- const validateLibraryEntry = (library: unknown): void => {
55
- if (typeof library === "string" && GIR_NAMESPACE_PATTERN.test(library)) {
61
+ if (value === LIBRARIES_WILDCARD) {
56
62
  return;
57
63
  }
58
- if (library === LIBRARIES_WILDCARD) {
59
- throw new Error(
60
- 'gtkx.config.ts: to generate every library, set `libraries: "*"` as a bare string, not an array entry',
64
+
65
+ if (!Array.isArray(value) || value.length === 0) {
66
+ ctx.issues.push(rawIssue(value, [], `must be "${LIBRARIES_WILDCARD}", a non-empty string array, or omitted`));
67
+
68
+ return;
69
+ }
70
+
71
+ for (const [index, entry] of value.entries()) {
72
+ ctx.issues.push(...libraryEntryIssues(value, index, entry));
73
+ }
74
+ });
75
+
76
+ const applicationIdSchema = z.custom<string>().check((ctx) => {
77
+ const value = ctx.value;
78
+
79
+ if (typeof value !== "string" || !isValidApplicationId(value)) {
80
+ ctx.issues.push(
81
+ rawIssue(
82
+ value,
83
+ [],
84
+ `invalid \`applicationId\` "${value}", must satisfy g_application_id_is_valid ` +
85
+ '(e.g. "org.example.MyApp")',
86
+ true,
87
+ ),
61
88
  );
62
89
  }
63
- throw new Error(
64
- `gtkx.config.ts: invalid library identifier "${String(library)}" — must be of the form "Name-Version" (e.g. "Gtk-4.0")`,
65
- );
66
- };
90
+ });
67
91
 
68
- const validateLibraries = (libraries: GtkxConfig["libraries"]): void => {
69
- if (libraries === undefined || libraries === LIBRARIES_WILDCARD) {
92
+ const reactCompilerSchema = z.custom<boolean | ReactCompilerOptions>().check((ctx) => {
93
+ const value = ctx.value;
94
+
95
+ if (typeof value === "boolean") {
70
96
  return;
71
97
  }
72
- if (!Array.isArray(libraries) || libraries.length === 0) {
73
- throw new Error('gtkx.config.ts: `libraries` must be "*", a non-empty string array, or omitted');
74
- }
75
- for (const library of libraries) {
76
- validateLibraryEntry(library);
98
+
99
+ if (!isRecord(value)) {
100
+ ctx.issues.push(rawIssue(value, [], "must be a boolean or an options object"));
101
+
102
+ return;
77
103
  }
78
- };
79
104
 
80
- const validateGirPath = (girPath: GtkxConfig["girPath"]): void => {
81
- if (girPath !== undefined && !Array.isArray(girPath)) {
82
- throw new Error("gtkx.config.ts: `girPath` must be an array of strings if provided");
105
+ const compilationMode = value.compilationMode;
106
+
107
+ if (!isValidReactCompilerOption(compilationMode, COMPILATION_MODE_SET)) {
108
+ ctx.issues.push(
109
+ rawIssue(
110
+ value,
111
+ [],
112
+ `invalid \`reactCompiler.compilationMode\` "${String(compilationMode)}", ` +
113
+ `must be one of ${COMPILATION_MODES.join(", ")}`,
114
+ true,
115
+ ),
116
+ );
83
117
  }
84
- };
85
118
 
86
- const validateApplicationId = (applicationId: GtkxConfig["applicationId"]): void => {
87
- if (applicationId === undefined) return;
88
- if (typeof applicationId !== "string" || !isValidApplicationId(applicationId)) {
89
- throw new Error(
90
- `gtkx.config.ts: invalid \`applicationId\` "${String(applicationId)}" — ` +
91
- `must satisfy g_application_id_is_valid (e.g. "org.example.MyApp")`,
119
+ const panicThreshold = value.panicThreshold;
120
+
121
+ if (!isValidReactCompilerOption(panicThreshold, PANIC_THRESHOLD_SET)) {
122
+ ctx.issues.push(
123
+ rawIssue(
124
+ value,
125
+ [],
126
+ `invalid \`reactCompiler.panicThreshold\` "${String(panicThreshold)}", ` +
127
+ `must be one of ${PANIC_THRESHOLDS.join(", ")}`,
128
+ true,
129
+ ),
92
130
  );
93
131
  }
94
- };
132
+ });
95
133
 
96
- const REACT_COMPILER_COMPILATION_MODES: ReactCompilerCompilationMode[] = ["infer", "syntax", "annotation", "all"];
134
+ const userEventSignalsSchema = z.record(
135
+ z.string(),
136
+ z.array(
137
+ z.string({ error: "must be a non-empty signal name" }).min(1, { error: "must be a non-empty signal name" }),
138
+ {
139
+ error: "must be an array of signal names",
140
+ },
141
+ ),
142
+ { error: "must be a record of GLib type names to signal name arrays" },
143
+ );
144
+
145
+ const moduleExportSchema = z.object(
146
+ {
147
+ module: z.string({ error: "must be a module specifier" }).min(1, { error: "must be a module specifier" }),
148
+ export: z.string({ error: "must be an export name" }).min(1, { error: "must be an export name" }),
149
+ },
150
+ { error: "must be a { module, export } object" },
151
+ );
152
+
153
+ const elementConfigSchema = z.object({
154
+ component: moduleExportSchema.optional(),
155
+ props: moduleExportSchema.optional(),
156
+ lazy: z.boolean({ error: "must be a boolean" }).optional(),
157
+ });
97
158
 
98
- const REACT_COMPILER_PANIC_THRESHOLDS: ReactCompilerPanicThreshold[] = ["none", "critical_errors", "all_errors"];
159
+ const elementsSchema = z.object({
160
+ behaviors: z
161
+ .string({ error: "must be a path to a module exporting element behaviors" })
162
+ .min(1, { error: "must be a path to a module exporting element behaviors" })
163
+ .optional(),
164
+ config: z.record(z.string(), elementConfigSchema).optional(),
165
+ });
99
166
 
100
- const validateReactCompilerEnum = <T extends string>(value: T | undefined, allowed: T[], field: string): void => {
101
- if (value !== undefined && !allowed.includes(value)) {
102
- throw new Error(
103
- `gtkx.config.ts: invalid \`reactCompiler.${field}\` "${String(value)}" — must be one of ${allowed.join(", ")}`,
104
- );
167
+ const configSchema = z.object({
168
+ libraries: librariesSchema.optional(),
169
+ girPath: z.array(z.string(), { error: "must be an array of strings if provided" }).optional(),
170
+ applicationId: applicationIdSchema,
171
+ reactCompiler: reactCompilerSchema.optional(),
172
+ codegen: z.boolean({ error: "must be a boolean" }).optional(),
173
+ userEventSignals: userEventSignalsSchema.optional(),
174
+ elements: elementsSchema.optional(),
175
+ });
176
+
177
+ /**
178
+ * Identity helper that returns the given configuration typed as {@link Config},
179
+ * enabling editor autocompletion and type checking in `gtkx.config.ts`.
180
+ */
181
+ const defineConfig: DefineConfig<Config> = createDefineConfig<Config>();
182
+
183
+ const libraryEntryIssues = (value: unknown[], index: number, entry: unknown): ReturnType<typeof rawIssue>[] => {
184
+ if (typeof entry === "string" && GIR_LIBRARY_PATTERN.test(entry)) {
185
+ return [];
105
186
  }
106
- };
107
187
 
108
- const validateReactCompiler = (reactCompiler: GtkxConfig["reactCompiler"]): void => {
109
- if (reactCompiler === undefined || typeof reactCompiler === "boolean") return;
110
- if (typeof reactCompiler !== "object" || reactCompiler === null || Array.isArray(reactCompiler)) {
111
- throw new Error("gtkx.config.ts: `reactCompiler` must be a boolean or an options object");
188
+ if (entry === LIBRARIES_WILDCARD) {
189
+ const message =
190
+ `to generate every library, set \`libraries: "${LIBRARIES_WILDCARD}"\` as a bare string, ` +
191
+ "not an array entry";
192
+
193
+ return [rawIssue(value, [index], message, true)];
112
194
  }
113
- validateReactCompilerEnum(reactCompiler.compilationMode, REACT_COMPILER_COMPILATION_MODES, "compilationMode");
114
- validateReactCompilerEnum(reactCompiler.panicThreshold, REACT_COMPILER_PANIC_THRESHOLDS, "panicThreshold");
195
+
196
+ const message =
197
+ `invalid library identifier "${String(entry)}", must be of the form "Name-Version" ` +
198
+ '(e.g. "Gtk-4.0")';
199
+
200
+ return [rawIssue(value, [index], message, true)];
115
201
  };
116
202
 
117
- export const validateGtkxConfig = (config: GtkxConfig): void => {
118
- validateLibraries(config.libraries);
119
- validateGirPath(config.girPath);
120
- validateApplicationId(config.applicationId);
121
- validateContainerPropRows(config.containerProps);
122
- validateArrayPropRows(config.arrayProps);
123
- validateObjectPropRows(config.objectProps);
124
- validateVirtualPropRows(config.virtualProps);
125
- validateElementMap(config.elementMap);
126
- validateReactCompiler(config.reactCompiler);
203
+ const isValidApplicationId = (applicationId: string): boolean => {
204
+ if (applicationId.length === 0 || applicationId.length > APPLICATION_ID_MAX_LENGTH) {
205
+ return false;
206
+ }
207
+
208
+ return APPLICATION_ID_PATTERN.test(applicationId);
127
209
  };
128
210
 
129
- export type GtkxConfigEnv = {
130
- mode?: string;
211
+ const resolveReactCompilerOptions = (setting: Config["reactCompiler"]): ResolvedReactCompilerOptions | null => {
212
+ if (setting === false) {
213
+ return null;
214
+ }
215
+
216
+ const overrides = setting === undefined || setting === true ? {} : setting;
217
+
218
+ return { ...overrides, target: REACT_COMPILER_TARGET };
131
219
  };
132
220
 
133
- export type GtkxConfigFn = (env: GtkxConfigEnv) => GtkxConfig;
134
-
135
- export type GtkxConfigFnPromise = (env: GtkxConfigEnv) => Promise<GtkxConfig>;
136
-
137
- export type GtkxConfigExport = GtkxConfig | Promise<GtkxConfig> | GtkxConfigFn | GtkxConfigFnPromise;
138
-
139
- export function defineConfig(config: GtkxConfig): GtkxConfig;
140
- export function defineConfig(config: Promise<GtkxConfig>): Promise<GtkxConfig>;
141
- export function defineConfig(config: GtkxConfigFn): GtkxConfigFn;
142
- export function defineConfig(config: GtkxConfigFnPromise): GtkxConfigFnPromise;
143
- export function defineConfig(config: GtkxConfigExport): GtkxConfigExport;
144
- export function defineConfig(config: GtkxConfigExport): GtkxConfigExport {
145
- return config;
146
- }
147
-
148
- const isMergeableObject = (value: unknown): value is Record<string, unknown> =>
149
- typeof value === "object" && value !== null && !Array.isArray(value);
150
-
151
- const mergeConfigValue = (base: unknown, override: unknown): unknown => {
152
- if (override === undefined) return base;
153
- if (base === undefined) return override;
154
- if (Array.isArray(base) && Array.isArray(override)) return [...base, ...override];
155
- if (isMergeableObject(base) && isMergeableObject(override)) {
156
- const merged: Record<string, unknown> = { ...base };
157
- for (const key of Object.keys(override)) {
158
- merged[key] = mergeConfigValue(base[key], override[key]);
159
- }
160
- return merged;
221
+ const isValidReactCompilerOption = (value: unknown, allowed: Set<string>): boolean =>
222
+ value === undefined || (typeof value === "string" && allowed.has(value));
223
+
224
+ const validateConfig = (config: Config): void => {
225
+ const result = configSchema.safeParse(config);
226
+
227
+ if (!result.success) {
228
+ throw configError(result.error);
161
229
  }
162
- return override;
163
230
  };
164
231
 
165
- export const mergeConfig = (base: GtkxConfig, override: GtkxConfig): GtkxConfig =>
166
- mergeConfigValue(base, override) as GtkxConfig;
167
-
168
- export type ResolvedGtkxConfig = {
169
- libraries: typeof LIBRARIES_WILDCARD | string[];
170
- girPath: string[];
171
- applicationId: string | undefined;
172
- containerProps: PerElementPropRows<ContainerPropRow>;
173
- arrayProps: PerElementPropRows<ArrayPropRow>;
174
- objectProps: PerElementPropRows<ObjectPropRow>;
175
- virtualProps: PerElementPropRows<VirtualPropRow>;
176
- elementMap: ElementMapRule[];
177
- reactCompiler: ResolvedReactCompilerOptions | null;
232
+ /**
233
+ * Deep-merges two configurations, with `override` taking precedence over `base`.
234
+ * @param base The lower-priority configuration.
235
+ * @param override The higher-priority configuration whose values win on conflict.
236
+ */
237
+ const mergeConfig = (base: Config, override: Config): Config => defu(override, base);
238
+
239
+ const resolveElementsModule = (behaviors: string | undefined, root: string | undefined): string | null => {
240
+ if (behaviors === undefined) {
241
+ return null;
242
+ }
243
+
244
+ return root === undefined ? behaviors : resolve(root, behaviors);
178
245
  };
179
246
 
180
- export const resolveGtkxConfig = (config: GtkxConfig): ResolvedGtkxConfig => ({
181
- libraries: config.libraries ?? [],
182
- girPath: config.girPath ?? [],
247
+ const resolveLazyElements = (elements: Config["elements"]): string[] =>
248
+ Object.entries(elements?.config ?? {})
249
+ .filter(([, entry]) => entry.lazy === true)
250
+ .map(([type]) => type);
251
+
252
+ const resolveConfig = (config: Config, root?: string): ResolvedConfig => ({
183
253
  applicationId: config.applicationId,
184
- containerProps: config.containerProps ?? {},
185
- arrayProps: config.arrayProps ?? {},
186
- objectProps: config.objectProps ?? {},
187
- virtualProps: config.virtualProps ?? {},
188
- elementMap: config.elementMap ?? [],
189
254
  reactCompiler: resolveReactCompilerOptions(config.reactCompiler),
255
+ userEventSignals: resolveUserEventSignals(config.userEventSignals),
256
+ elements: resolveElementsModule(config.elements?.behaviors, root),
257
+ lazyElements: resolveLazyElements(config.elements),
190
258
  });
191
259
 
260
+ export {
261
+ LIBRARIES_WILDCARD,
262
+ GIR_LIBRARY_PATTERN,
263
+ defineConfig,
264
+ isValidApplicationId,
265
+ resolveReactCompilerOptions,
266
+ validateConfig,
267
+ mergeConfig,
268
+ resolveLazyElements,
269
+ resolveConfig,
270
+ type ResolvedReactCompilerOptions,
271
+ type Config,
272
+ type ResolvedConfig,
273
+ };
package/src/index.ts CHANGED
@@ -1,88 +1,8 @@
1
- export { gtkxBundledModulePatterns } from "./bundled-modules.js";
2
1
  export {
2
+ type Config,
3
3
  defineConfig,
4
- GIR_NAMESPACE_PATTERN,
5
- type GtkxConfig,
6
- type GtkxConfigEnv,
7
- type GtkxConfigExport,
8
- type GtkxConfigFn,
9
- type GtkxConfigFnPromise,
10
- LIBRARIES_WILDCARD,
11
4
  mergeConfig,
12
- type ReactCompilerCompilationMode,
13
- type ReactCompilerOptions,
14
- type ReactCompilerPanicThreshold,
15
- type ResolvedGtkxConfig,
5
+ type ResolvedConfig,
16
6
  type ResolvedReactCompilerOptions,
17
- resolveGtkxConfig,
18
- resolveReactCompilerOptions,
19
- validateGtkxConfig,
20
7
  } from "./config.js";
21
- export { DATA_IMPORT_KEY, DATA_IMPORT_PREFIX, resolveDataDir } from "./data-dir.js";
22
- export {
23
- createGtkxConfigLoader,
24
- type GtkxConfigLoader,
25
- GtkxConfigNotFoundError,
26
- type LoadedConfig,
27
- type LoadGtkxConfigOptions,
28
- type LoadResolvedGtkxConfigOptions,
29
- loadGtkxConfig,
30
- loadResolvedGtkxConfig,
31
- } from "./loader.js";
32
- export { createGtkxConfigPlugin, type GtkxConfigPluginOptions } from "./plugin.js";
33
- export type {
34
- AddMethodArg,
35
- AddMethodRule,
36
- PageMetaSetter,
37
- PropCondition,
38
- PropRule,
39
- SetterPropGroup,
40
- SetterPropStep,
41
- SignalPropRule,
42
- } from "./table-rules-ir.js";
43
- export type {
44
- ArrayPropRow,
45
- AttachShape,
46
- AttachShapeTable,
47
- AttachVerb,
48
- CallArg,
49
- CallStep,
50
- ConstructSetter,
51
- ConstructStep,
52
- ContainerPropRow,
53
- DetachGuard,
54
- ElementMapRule,
55
- MethodVerb,
56
- ObjectPropRow,
57
- OrderedInsertVerb,
58
- PerElementPropRows,
59
- PresenceCondition,
60
- UserTableRows,
61
- VerbArgs,
62
- VirtualPropRow,
63
- } from "./table-schema.js";
64
- export {
65
- CAMEL_CASE_NAME_PATTERN,
66
- PASCAL_CASE_NAME_PATTERN,
67
- validateArrayOf,
68
- } from "./validators.js";
69
- export {
70
- GTKX_CONFIG_VIRTUAL_ID,
71
- RESOLVED_GTKX_CONFIG_VIRTUAL_ID,
72
- renderGtkxConfigModule,
73
- type SerializedGtkxConfig,
74
- serializeGtkxConfig,
75
- } from "./virtual.js";
76
- export {
77
- BUFFER_TEXT_KIND,
78
- CONTAINER_PROP_KIND,
79
- LABEL_TEXT_KIND,
80
- LAYOUT_CHILD_KIND,
81
- META_OBJECT_KIND,
82
- OVERLAY_KIND,
83
- SLOT_KIND,
84
- TAB_LABEL_KIND,
85
- TEXT_ANCHOR_KIND,
86
- TEXT_PAINTABLE_KIND,
87
- WRAPPER_NODE_ELEMENT,
88
- } from "./wrapper-protocol.js";
8
+ export { type LoadedConfig, loadConfig } from "./loader.js";
@@ -0,0 +1,2 @@
1
+ export { GIR_LIBRARY_PATTERN, isValidApplicationId, LIBRARIES_WILDCARD, resolveLazyElements } from "./config.js";
2
+ export { type ConfigLoader, createConfigLoader } from "./loader.js";
package/src/loader.ts CHANGED
@@ -1,88 +1,77 @@
1
+ import { loadConfig as loadConfigFile } from "c12";
1
2
  import { existsSync } from "node:fs";
2
3
  import { resolve } from "node:path";
3
- import { loadConfig } from "c12";
4
- import { type GtkxConfig, type ResolvedGtkxConfig, resolveGtkxConfig, validateGtkxConfig } from "./config.js";
4
+ import { type Config, resolveConfig, type ResolvedConfig, validateConfig } from "./config.js";
5
5
 
6
- export type LoadedConfig = {
7
- config: GtkxConfig;
6
+ /**
7
+ * Result of loading a `gtkx.config.ts` file: the parsed configuration, the
8
+ * resolved config file path (`undefined` when none was found), and the project
9
+ * root it was loaded from.
10
+ */
11
+ type LoadedConfig = {
12
+ config: Config;
8
13
  configFile: string | undefined;
9
- rootDir: string;
14
+ root: string;
10
15
  };
11
16
 
12
- export class GtkxConfigNotFoundError extends Error {
13
- constructor(cwd: string) {
14
- super(
15
- `No gtkx.config.ts found in ${cwd}.\n` +
16
- `Create one with:\n` +
17
- `\n` +
18
- ` // gtkx.config.ts\n` +
19
- ` import { defineConfig } from "@gtkx/config";\n` +
20
- `\n` +
21
- ` export default defineConfig({\n` +
22
- ` libraries: ["Gtk-4.0", "Adw-1"],\n` +
23
- ` });\n`,
24
- );
25
- this.name = "GtkxConfigNotFoundError";
26
- }
27
- }
28
-
29
- export type LoadGtkxConfigOptions = {
30
- mode?: string;
17
+ type LoadConfigOptions = {
18
+ mode?: string | undefined;
31
19
  };
32
20
 
33
- export const loadGtkxConfig = async (cwd: string, options: LoadGtkxConfigOptions = {}): Promise<LoadedConfig> => {
34
- const result = await loadConfig<GtkxConfig>({
21
+ type ConfigLoader = (cwd: string) => Promise<ResolvedConfig>;
22
+
23
+ /**
24
+ * Loads and validates the `gtkx.config.ts` file for a project, returning the
25
+ * parsed configuration together with the config file path and project root.
26
+ * @param cwd Directory from which to search for the configuration file.
27
+ * @param options Loading options, such as the environment mode.
28
+ */
29
+ const loadConfig = async (cwd: string, options: LoadConfigOptions = {}): Promise<LoadedConfig> => {
30
+ const result = await loadConfigFile<Config>({
35
31
  name: "gtkx",
36
32
  cwd,
37
33
  rcFile: false,
38
34
  globalRc: false,
39
35
  packageJson: false,
40
36
  context: { mode: options.mode },
37
+ ...((options.mode !== undefined) && { envName: options.mode }),
41
38
  });
42
39
 
43
- if (!result.configFile || !result.config || !existsSync(resolve(cwd, result.configFile))) {
44
- throw new GtkxConfigNotFoundError(cwd);
45
- }
40
+ const config = result.config;
41
+ const isFound = result.configFile !== undefined && existsSync(resolve(cwd, result.configFile));
46
42
 
47
- validateGtkxConfig(result.config);
43
+ if (isFound) {
44
+ validateConfig(config);
45
+ }
48
46
 
49
47
  return {
50
- config: result.config,
51
- configFile: result.configFile,
52
- rootDir: result.cwd ?? cwd,
48
+ config,
49
+ configFile: isFound ? result.configFile : undefined,
50
+ root: result.cwd ?? cwd,
53
51
  };
54
52
  };
55
53
 
56
- export type LoadResolvedGtkxConfigOptions = LoadGtkxConfigOptions & {
57
- allowMissing?: boolean;
58
- };
54
+ const createConfigLoader = (options: LoadConfigOptions = {}): ConfigLoader => {
55
+ const cache: Map<string, Promise<ResolvedConfig>> = new Map();
59
56
 
60
- export const loadResolvedGtkxConfig = async (
61
- cwd: string,
62
- options: LoadResolvedGtkxConfigOptions = {},
63
- ): Promise<ResolvedGtkxConfig> => {
64
- try {
65
- const { config } = await loadGtkxConfig(cwd, options.mode === undefined ? {} : { mode: options.mode });
66
- return resolveGtkxConfig(config);
67
- } catch (error) {
68
- if (options.allowMissing && error instanceof GtkxConfigNotFoundError) {
69
- return resolveGtkxConfig({});
70
- }
71
- throw error;
72
- }
73
- };
57
+ const loadResolved = async (root: string): Promise<ResolvedConfig> => {
58
+ const { config } = await loadConfig(root, options);
59
+ validateConfig(config);
74
60
 
75
- export type GtkxConfigLoader = (cwd: string) => Promise<ResolvedGtkxConfig>;
61
+ return resolveConfig(config, root);
62
+ };
76
63
 
77
- export const createGtkxConfigLoader = (options: LoadResolvedGtkxConfigOptions = {}): GtkxConfigLoader => {
78
- const cache = new Map<string, Promise<ResolvedGtkxConfig>>();
79
- return (cwd: string): Promise<ResolvedGtkxConfig> => {
64
+ return (cwd: string): Promise<ResolvedConfig> => {
80
65
  const root = resolve(cwd);
81
66
  let pending = cache.get(root);
67
+
82
68
  if (!pending) {
83
- pending = loadResolvedGtkxConfig(root, options);
69
+ pending = loadResolved(root);
84
70
  cache.set(root, pending);
85
71
  }
72
+
86
73
  return pending;
87
74
  };
88
75
  };
76
+
77
+ export { loadConfig, createConfigLoader, type LoadedConfig, type LoadConfigOptions, type ConfigLoader };