@gtkx/config 0.21.0 → 1.0.0-rc.1

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 (77) hide show
  1. package/README.md +137 -82
  2. package/dist/config.d.ts +76 -35
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +76 -83
  5. package/dist/config.js.map +1 -1
  6. package/dist/element-props.d.ts +180 -0
  7. package/dist/element-props.d.ts.map +1 -0
  8. package/dist/element-props.js +154 -0
  9. package/dist/element-props.js.map +1 -0
  10. package/dist/index.d.ts +3 -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 +19 -15
  19. package/dist/loader.d.ts.map +1 -1
  20. package/dist/loader.js +25 -39
  21. package/dist/loader.js.map +1 -1
  22. package/dist/virtual.d.ts +2 -4
  23. package/dist/virtual.d.ts.map +1 -1
  24. package/dist/virtual.js +2 -14
  25. package/dist/virtual.js.map +1 -1
  26. package/dist/vite-plugin.d.ts +16 -0
  27. package/dist/vite-plugin.d.ts.map +1 -0
  28. package/dist/vite-plugin.js +32 -0
  29. package/dist/vite-plugin.js.map +1 -0
  30. package/env.d.ts +4 -22
  31. package/package.json +20 -7
  32. package/src/config.ts +134 -144
  33. package/src/element-props.ts +248 -0
  34. package/src/index.ts +14 -82
  35. package/src/internal.ts +2 -0
  36. package/src/loader.ts +36 -56
  37. package/src/virtual.ts +3 -32
  38. package/src/vite-plugin.ts +37 -0
  39. package/dist/bundled-modules.d.ts +0 -2
  40. package/dist/bundled-modules.d.ts.map +0 -1
  41. package/dist/bundled-modules.js +0 -2
  42. package/dist/bundled-modules.js.map +0 -1
  43. package/dist/data-dir.d.ts +0 -4
  44. package/dist/data-dir.d.ts.map +0 -1
  45. package/dist/data-dir.js +0 -37
  46. package/dist/data-dir.js.map +0 -1
  47. package/dist/plugin.d.ts +0 -9
  48. package/dist/plugin.d.ts.map +0 -1
  49. package/dist/plugin.js +0 -24
  50. package/dist/plugin.js.map +0 -1
  51. package/dist/runtime.d.ts +0 -22
  52. package/dist/runtime.d.ts.map +0 -1
  53. package/dist/runtime.js +0 -22
  54. package/dist/runtime.js.map +0 -1
  55. package/dist/table-rules-ir.d.ts +0 -36
  56. package/dist/table-rules-ir.d.ts.map +0 -1
  57. package/dist/table-rules-ir.js +0 -2
  58. package/dist/table-rules-ir.js.map +0 -1
  59. package/dist/table-schema.d.ts +0 -107
  60. package/dist/table-schema.d.ts.map +0 -1
  61. package/dist/table-schema.js +0 -199
  62. package/dist/table-schema.js.map +0 -1
  63. package/dist/validators.d.ts +0 -4
  64. package/dist/validators.d.ts.map +0 -1
  65. package/dist/validators.js +0 -10
  66. package/dist/validators.js.map +0 -1
  67. package/dist/wrapper-protocol.d.ts +0 -12
  68. package/dist/wrapper-protocol.d.ts.map +0 -1
  69. package/dist/wrapper-protocol.js +0 -12
  70. package/dist/wrapper-protocol.js.map +0 -1
  71. package/src/bundled-modules.ts +0 -1
  72. package/src/data-dir.ts +0 -39
  73. package/src/plugin.ts +0 -30
  74. package/src/table-rules-ir.ts +0 -42
  75. package/src/table-schema.ts +0 -342
  76. package/src/validators.ts +0 -15
  77. package/src/wrapper-protocol.ts +0 -21
@@ -0,0 +1,248 @@
1
+ import { z } from "zod";
2
+
3
+ const ARG_REFS = ["child", "item", "index", "sibling", "adopted"] as const;
4
+
5
+ type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
6
+
7
+ /**
8
+ * Named reference to a value available while invoking a {@link Call}: the child
9
+ * widget, the current list item, its index, the preceding sibling, or the
10
+ * adopted instance a container returned for the child.
11
+ */
12
+ export type ArgRef = (typeof ARG_REFS)[number];
13
+
14
+ /**
15
+ * A single argument passed to a method {@link Call}: an {@link ArgRef}, a value
16
+ * read from a React prop (`{ prop }`), a field read off the current item with an
17
+ * optional fallback (`{ field, or }`), or a constant (`{ literal }`).
18
+ */
19
+ export type Arg = ArgRef | { prop: string } | { field: string; or?: JsonValue } | { literal: JsonValue };
20
+
21
+ /**
22
+ * A GObject method invocation: either a bare method name called with default
23
+ * arguments, or a method name paired with an explicit list of {@link Arg}s.
24
+ */
25
+ export type Call = string | { method: string; args: Arg[] };
26
+
27
+ const NAME_MESSAGE = "must be a non-empty string";
28
+
29
+ const JSON_MESSAGE = "must be a JSON-serializable value";
30
+
31
+ const ARG_MESSAGE = "must be one of: reference name, { prop }, { field }, { literal }";
32
+
33
+ const ARG_REF_SET: Set<string> = new Set(ARG_REFS);
34
+
35
+ export const isRecord = (value: unknown): value is Record<string, unknown> =>
36
+ typeof value === "object" && value !== null && !Array.isArray(value);
37
+
38
+ const isJsonValue = (value: unknown): boolean => {
39
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
40
+ return true;
41
+ }
42
+ if (Array.isArray(value)) return value.every(isJsonValue);
43
+ if (isRecord(value)) return Object.values(value).every(isJsonValue);
44
+ return false;
45
+ };
46
+
47
+ type IssuePath = Array<string | number>;
48
+
49
+ type CollectedIssue = { path: IssuePath; message: string };
50
+
51
+ export const rawIssue = (input: unknown, path: IssuePath, message: string, standalone = false) => ({
52
+ code: "custom" as const,
53
+ input,
54
+ path,
55
+ message,
56
+ continue: true as const,
57
+ ...(standalone ? { params: { standalone: true } } : {}),
58
+ });
59
+
60
+ const unknownKeyIssues = (value: Record<string, unknown>, allowed: string[]): CollectedIssue[] =>
61
+ Object.keys(value)
62
+ .filter((key) => !allowed.includes(key))
63
+ .map((key) => ({ path: [key], message: "is not a recognized key" }));
64
+
65
+ const requireName = (value: unknown): boolean => typeof value === "string" && value.length > 0;
66
+
67
+ const collectPropArgIssues = (value: Record<string, unknown>): CollectedIssue[] => {
68
+ const issues = unknownKeyIssues(value, ["prop"]);
69
+ if (!requireName(value.prop)) issues.push({ path: ["prop"], message: NAME_MESSAGE });
70
+ return issues;
71
+ };
72
+
73
+ const collectFieldArgIssues = (value: Record<string, unknown>): CollectedIssue[] => {
74
+ const issues = unknownKeyIssues(value, ["field", "or"]);
75
+ if (!requireName(value.field)) issues.push({ path: ["field"], message: NAME_MESSAGE });
76
+ if ("or" in value && !isJsonValue(value.or)) issues.push({ path: ["or"], message: JSON_MESSAGE });
77
+ return issues;
78
+ };
79
+
80
+ const collectLiteralArgIssues = (value: Record<string, unknown>): CollectedIssue[] => {
81
+ const issues = unknownKeyIssues(value, ["literal"]);
82
+ if (!isJsonValue(value.literal)) issues.push({ path: ["literal"], message: JSON_MESSAGE });
83
+ return issues;
84
+ };
85
+
86
+ const collectArgIssues = (value: unknown): CollectedIssue[] => {
87
+ if (typeof value === "string") {
88
+ return ARG_REF_SET.has(value) ? [] : [{ path: [], message: `has unknown reference "${value}"` }];
89
+ }
90
+ if (!isRecord(value)) return [{ path: [], message: ARG_MESSAGE }];
91
+ if ("prop" in value) return collectPropArgIssues(value);
92
+ if ("field" in value) return collectFieldArgIssues(value);
93
+ if ("literal" in value) return collectLiteralArgIssues(value);
94
+ return [{ path: [], message: ARG_MESSAGE }];
95
+ };
96
+
97
+ const collectArgsIssues = (args: unknown[]): CollectedIssue[] =>
98
+ args.flatMap((arg, index) =>
99
+ collectArgIssues(arg).map((issue) => ({ path: ["args", index, ...issue.path], message: issue.message })),
100
+ );
101
+
102
+ const collectCallIssues = (value: unknown): CollectedIssue[] => {
103
+ if (typeof value === "string") return value.length === 0 ? [{ path: [], message: NAME_MESSAGE }] : [];
104
+ if (!isRecord(value)) return [{ path: [], message: "must be an object" }];
105
+ const issues = unknownKeyIssues(value, ["method", "args"]);
106
+ if (!requireName(value.method)) issues.push({ path: ["method"], message: NAME_MESSAGE });
107
+ if (!Array.isArray(value.args)) issues.push({ path: ["args"], message: "must be an array" });
108
+ else issues.push(...collectArgsIssues(value.args));
109
+ return issues;
110
+ };
111
+
112
+ const callSchema = z.custom<Call>().check((ctx) => {
113
+ ctx.issues.push(...collectCallIssues(ctx.value).map((issue) => rawIssue(ctx.value, issue.path, issue.message)));
114
+ });
115
+
116
+ const nameSchema = z.string({ error: NAME_MESSAGE }).min(1, { error: NAME_MESSAGE });
117
+
118
+ const adoptSchema = z.union([z.literal(true), nameSchema], {
119
+ error: "must be `true` or the name of a getter method",
120
+ });
121
+
122
+ const containerSchema = z.strictObject({
123
+ kind: z.literal("container"),
124
+ prop: nameSchema,
125
+ child: nameSchema,
126
+ append: callSchema.optional(),
127
+ remove: callSchema.optional(),
128
+ insert: callSchema.optional(),
129
+ reorder: callSchema.optional(),
130
+ autowrap: nameSchema.optional(),
131
+ adopt: adoptSchema.optional(),
132
+ });
133
+
134
+ const valueSchema = z.strictObject({
135
+ kind: z.literal("value"),
136
+ prop: nameSchema,
137
+ call: callSchema,
138
+ after: nameSchema.optional(),
139
+ });
140
+
141
+ const controlledTextSchema = z.strictObject({
142
+ kind: z.literal("controlled-text"),
143
+ prop: nameSchema,
144
+ });
145
+
146
+ const lazySchema = z.strictObject({
147
+ kind: z.literal("lazy"),
148
+ prop: nameSchema,
149
+ lookup: nameSchema.optional(),
150
+ });
151
+
152
+ const listSchema = z.strictObject({
153
+ kind: z.literal("list"),
154
+ prop: nameSchema,
155
+ add: z.union([callSchema, z.array(callSchema).min(1)]),
156
+ remove: callSchema.optional(),
157
+ clear: callSchema.optional(),
158
+ });
159
+
160
+ const elementPropSchema = z
161
+ .discriminatedUnion("kind", [containerSchema, valueSchema, controlledTextSchema, lazySchema, listSchema], {
162
+ error: "must be one of container, value, controlled-text, lazy, list",
163
+ })
164
+ .check((ctx) => {
165
+ const prop = ctx.value;
166
+ if (prop.kind === "container" && prop.append === undefined && prop.remove === undefined) {
167
+ ctx.issues.push(rawIssue(prop, [], "must define at least one of `append` or `remove`"));
168
+ }
169
+ });
170
+
171
+ export const elementPropsSchema = z.record(nameSchema, z.array(elementPropSchema));
172
+
173
+ /**
174
+ * Rule describing how children of a given type are attached to and removed from a
175
+ * container element. `prop` is the React prop holding the children and `child` the
176
+ * child GObject type. `append`/`remove` add and remove a child, `insert` places
177
+ * one at an index or after a sibling, `reorder` moves an existing child, `autowrap`
178
+ * names a widget type each child is wrapped in before attaching, and `adopt` marks
179
+ * pre-existing children as adopted (`true`) or names the getter returning them.
180
+ */
181
+ export type ContainerProp = z.infer<typeof containerSchema>;
182
+
183
+ /**
184
+ * Rule that applies a scalar prop value by invoking `call` whenever the value
185
+ * changes, optionally running the method named by `after` once it is set.
186
+ */
187
+ export type ValueProp = z.infer<typeof valueSchema>;
188
+
189
+ /**
190
+ * Rule for a controlled text prop: `prop` is written directly to the GObject
191
+ * property and kept in sync with the element's own edits.
192
+ */
193
+ export type ControlledTextProp = z.infer<typeof controlledTextSchema>;
194
+
195
+ /**
196
+ * Rule for a prop applied after construction rather than at construction time,
197
+ * optionally guarded by `lookup`, a method that must succeed for the value before
198
+ * it is assigned.
199
+ */
200
+ export type LazyProp = z.infer<typeof lazySchema>;
201
+
202
+ /**
203
+ * Rule mapping an array prop to method calls: `add` runs per added item (a single
204
+ * call, or a sequence of calls applied in order when one item needs several), `remove`
205
+ * per removed item, and `clear` empties the collection before re-adding.
206
+ */
207
+ export type ListProp = z.infer<typeof listSchema>;
208
+
209
+ /**
210
+ * Any prop rule applied directly to an element instance rather than through
211
+ * container child attachment: a {@link ValueProp}, {@link ControlledTextProp},
212
+ * {@link LazyProp}, or {@link ListProp}.
213
+ */
214
+ export type AppliedProp = ValueProp | ControlledTextProp | LazyProp | ListProp;
215
+
216
+ /**
217
+ * A single entry in an element's prop mapping, discriminated by `kind`: a
218
+ * container, value, controlled-text, lazy, or list rule.
219
+ */
220
+ export type ElementProp = z.infer<typeof elementPropSchema>;
221
+
222
+ const CONFIG_PREFIX = "gtkx.config.ts:";
223
+
224
+ const dottedPath = (segments: PropertyKey[]): string =>
225
+ segments.reduce<string>((acc, segment) => {
226
+ if (typeof segment === "number") return `${acc}[${segment}]`;
227
+ return acc === "" ? String(segment) : `${acc}.${String(segment)}`;
228
+ }, "");
229
+
230
+ const isStandaloneIssue = (issue: z.core.$ZodIssue): boolean =>
231
+ "params" in issue && isRecord(issue.params) && issue.params.standalone === true;
232
+
233
+ const formatIssue = (issue: z.core.$ZodIssue, fullPath: PropertyKey[]): string => {
234
+ if (issue.code === "unrecognized_keys") {
235
+ const [key] = issue.keys;
236
+ const path = dottedPath(key === undefined ? fullPath : [...fullPath, key]);
237
+ return `${CONFIG_PREFIX} \`${path}\` is not a recognized key`;
238
+ }
239
+ if (isStandaloneIssue(issue)) return `${CONFIG_PREFIX} ${issue.message}`;
240
+ const path = dottedPath(fullPath);
241
+ return path === "" ? `${CONFIG_PREFIX} ${issue.message}` : `${CONFIG_PREFIX} \`${path}\` ${issue.message}`;
242
+ };
243
+
244
+ export const configError = (error: z.ZodError): Error => {
245
+ const issue = error.issues[0];
246
+ if (issue === undefined) return new Error(`${CONFIG_PREFIX} invalid configuration`);
247
+ return new Error(formatIssue(issue, issue.path));
248
+ };
package/src/index.ts CHANGED
@@ -1,88 +1,20 @@
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
8
  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";
9
+ AppliedProp,
10
+ Arg,
11
+ ArgRef,
12
+ Call,
13
+ ContainerProp,
14
+ ControlledTextProp,
15
+ ElementProp,
16
+ LazyProp,
17
+ ListProp,
18
+ ValueProp,
19
+ } from "./element-props.js";
20
+ export { type LoadedConfig, loadConfig } from "./loader.js";
@@ -0,0 +1,2 @@
1
+ export { GIR_LIBRARY_PATTERN, isValidApplicationId, LIBRARIES_WILDCARD } from "./config.js";
2
+ export { type ConfigLoader, createConfigLoader } from "./loader.js";
package/src/loader.ts CHANGED
@@ -1,86 +1,66 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
- import { loadConfig } from "c12";
4
- import { type GtkxConfig, type ResolvedGtkxConfig, resolveGtkxConfig, validateGtkxConfig } from "./config.js";
3
+ import { loadConfig as loadConfigFile } from "c12";
4
+ import { type Config, type ResolvedConfig, resolveConfig, validateConfig } from "./config.js";
5
5
 
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
+ */
6
11
  export type LoadedConfig = {
7
- config: GtkxConfig;
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
+ export 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
+ /**
22
+ * Loads and validates the `gtkx.config.ts` file for a project, returning the
23
+ * parsed configuration together with the config file path and project root.
24
+ * @param cwd Directory from which to search for the configuration file.
25
+ * @param options Loading options, such as the environment mode.
26
+ */
27
+ export const loadConfig = async (cwd: string, options: LoadConfigOptions = {}): Promise<LoadedConfig> => {
28
+ const result = await loadConfigFile<Config>({
35
29
  name: "gtkx",
36
30
  cwd,
37
31
  rcFile: false,
38
32
  globalRc: false,
39
33
  packageJson: false,
40
34
  context: { mode: options.mode },
35
+ ...(options.mode !== undefined ? { envName: options.mode } : {}),
41
36
  });
42
37
 
43
- if (!result.configFile || !result.config || !existsSync(resolve(cwd, result.configFile))) {
44
- throw new GtkxConfigNotFoundError(cwd);
45
- }
38
+ const config = result.config;
39
+ const found = result.configFile !== undefined && existsSync(resolve(cwd, result.configFile));
46
40
 
47
- validateGtkxConfig(result.config);
41
+ if (found) validateConfig(config);
48
42
 
49
43
  return {
50
- config: result.config,
51
- configFile: result.configFile,
52
- rootDir: result.cwd ?? cwd,
44
+ config,
45
+ configFile: found ? result.configFile : undefined,
46
+ root: result.cwd ?? cwd,
53
47
  };
54
48
  };
55
49
 
56
- export type LoadResolvedGtkxConfigOptions = LoadGtkxConfigOptions & {
57
- allowMissing?: boolean;
58
- };
50
+ export type ConfigLoader = (cwd: string) => Promise<ResolvedConfig>;
59
51
 
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
- };
74
-
75
- export type GtkxConfigLoader = (cwd: string) => Promise<ResolvedGtkxConfig>;
76
-
77
- export const createGtkxConfigLoader = (options: LoadResolvedGtkxConfigOptions = {}): GtkxConfigLoader => {
78
- const cache = new Map<string, Promise<ResolvedGtkxConfig>>();
79
- return (cwd: string): Promise<ResolvedGtkxConfig> => {
52
+ export const createConfigLoader = (options: LoadConfigOptions = {}): ConfigLoader => {
53
+ const cache = new Map<string, Promise<ResolvedConfig>>();
54
+ const loadResolved = async (root: string): Promise<ResolvedConfig> => {
55
+ const { config } = await loadConfig(root, options);
56
+ validateConfig(config);
57
+ return resolveConfig(config);
58
+ };
59
+ return (cwd: string): Promise<ResolvedConfig> => {
80
60
  const root = resolve(cwd);
81
61
  let pending = cache.get(root);
82
62
  if (!pending) {
83
- pending = loadResolvedGtkxConfig(root, options);
63
+ pending = loadResolved(root);
84
64
  cache.set(root, pending);
85
65
  }
86
66
  return pending;
package/src/virtual.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ResolvedGtkxConfig } from "./config.js";
1
+ import type { ResolvedConfig } from "./config.js";
2
2
 
3
3
  export const GTKX_CONFIG_VIRTUAL_ID = "virtual:gtkx-config";
4
4
 
@@ -6,37 +6,8 @@ export const RESOLVED_GTKX_CONFIG_VIRTUAL_ID: string = `\0${GTKX_CONFIG_VIRTUAL_
6
6
 
7
7
  const METADATA_SPECIFIER = "@gtkx/jsx/metadata";
8
8
 
9
- export type SerializedGtkxConfig = Pick<
10
- ResolvedGtkxConfig,
11
- | "libraries"
12
- | "girPath"
13
- | "applicationId"
14
- | "containerProps"
15
- | "arrayProps"
16
- | "objectProps"
17
- | "virtualProps"
18
- | "elementMap"
19
- | "reactCompiler"
20
- >;
21
-
22
- const serializeConfigValue = (value: unknown): string => (value === undefined ? "undefined" : JSON.stringify(value));
23
-
24
- export const serializeGtkxConfig = (config: ResolvedGtkxConfig): SerializedGtkxConfig => ({
25
- libraries: config.libraries,
26
- girPath: config.girPath,
27
- applicationId: config.applicationId,
28
- containerProps: config.containerProps,
29
- arrayProps: config.arrayProps,
30
- objectProps: config.objectProps,
31
- virtualProps: config.virtualProps,
32
- elementMap: config.elementMap,
33
- reactCompiler: config.reactCompiler,
34
- });
35
-
36
- export const renderGtkxConfigModule = (config: ResolvedGtkxConfig): string =>
9
+ export const renderConfigModule = (config: ResolvedConfig): string =>
37
10
  [
38
11
  `export * from ${JSON.stringify(METADATA_SPECIFIER)};`,
39
- ...Object.entries(serializeGtkxConfig(config)).map(
40
- ([key, value]) => `export const ${key} = ${serializeConfigValue(value)};`,
41
- ),
12
+ `export const applicationId = ${JSON.stringify(config.applicationId)};`,
42
13
  ].join("\n");
@@ -0,0 +1,37 @@
1
+ import type { Plugin, UserConfig } from "vite";
2
+ import { type ConfigLoader, createConfigLoader } from "./loader.js";
3
+ import { GTKX_CONFIG_VIRTUAL_ID, RESOLVED_GTKX_CONFIG_VIRTUAL_ID, renderConfigModule } from "./virtual.js";
4
+
5
+ /**
6
+ * Creates the Vite plugin that resolves and serves the `virtual:gtkx-config`
7
+ * module, exposing the JSX metadata and the resolved application ID from the
8
+ * project's configuration.
9
+ * @param options Plugin name, an optional custom {@link ConfigLoader}, and an
10
+ * optional hook returning extra Vite user config.
11
+ */
12
+ const createConfigPlugin = (options: {
13
+ name: string;
14
+ loadConfig?: ConfigLoader;
15
+ config?: () => Omit<UserConfig, "plugins">;
16
+ }): Plugin => {
17
+ const loadConfig = options.loadConfig ?? createConfigLoader();
18
+ let root: string | undefined;
19
+
20
+ return {
21
+ name: options.name,
22
+ config(config: UserConfig) {
23
+ root = config.root ?? root;
24
+ return options.config?.();
25
+ },
26
+ resolveId(id: string) {
27
+ if (id === GTKX_CONFIG_VIRTUAL_ID) return RESOLVED_GTKX_CONFIG_VIRTUAL_ID;
28
+ return undefined;
29
+ },
30
+ async load(id: string) {
31
+ if (id !== RESOLVED_GTKX_CONFIG_VIRTUAL_ID) return undefined;
32
+ return renderConfigModule(await loadConfig(root ?? process.cwd()));
33
+ },
34
+ };
35
+ };
36
+
37
+ export default createConfigPlugin;
@@ -1,2 +0,0 @@
1
- export declare const gtkxBundledModulePatterns: RegExp[];
2
- //# sourceMappingURL=bundled-modules.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bundled-modules.d.ts","sourceRoot":"","sources":["../src/bundled-modules.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,yBAAyB,EAAE,MAAM,EAAyE,CAAC"}
@@ -1,2 +0,0 @@
1
- export const gtkxBundledModulePatterns = [/@gtkx\/(config|ffi|gi|react|jsx|testing|css)/, /[/\\]\.gtkx[/\\]/];
2
- //# sourceMappingURL=bundled-modules.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bundled-modules.js","sourceRoot":"","sources":["../src/bundled-modules.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,yBAAyB,GAAa,CAAC,8CAA8C,EAAE,kBAAkB,CAAC,CAAC"}
@@ -1,4 +0,0 @@
1
- export declare const DATA_IMPORT_PREFIX = "#data";
2
- export declare const DATA_IMPORT_KEY: string;
3
- export declare const resolveDataDir: (root: string) => string | null;
4
- //# sourceMappingURL=data-dir.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"data-dir.d.ts","sourceRoot":"","sources":["../src/data-dir.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,kBAAkB,UAAU,CAAC;AAE1C,eAAO,MAAM,eAAe,EAAE,MAAkC,CAAC;AAuBjE,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,KAAG,MAAM,GAAG,IAUtD,CAAC"}
package/dist/data-dir.js DELETED
@@ -1,37 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- export const DATA_IMPORT_PREFIX = "#data";
4
- export const DATA_IMPORT_KEY = `${DATA_IMPORT_PREFIX}/*`;
5
- const CONDITION_PRIORITY = ["default", "import", "node"];
6
- const targetString = (entry) => {
7
- if (typeof entry === "string")
8
- return entry;
9
- if (entry !== null && typeof entry === "object" && !Array.isArray(entry)) {
10
- const conditions = entry;
11
- for (const condition of CONDITION_PRIORITY) {
12
- const value = conditions[condition];
13
- if (typeof value === "string")
14
- return value;
15
- }
16
- }
17
- return null;
18
- };
19
- const DATA_TARGET_PATTERN = /^\.?\/?(.+?)\/\*$/;
20
- const directoryFromTarget = (target) => {
21
- const match = DATA_TARGET_PATTERN.exec(target);
22
- return match?.[1] ?? null;
23
- };
24
- export const resolveDataDir = (root) => {
25
- let manifest;
26
- try {
27
- manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf-8"));
28
- }
29
- catch {
30
- return null;
31
- }
32
- const target = targetString(manifest.imports?.[DATA_IMPORT_KEY]);
33
- if (target === null)
34
- return null;
35
- return directoryFromTarget(target);
36
- };
37
- //# sourceMappingURL=data-dir.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"data-dir.js","sourceRoot":"","sources":["../src/data-dir.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAE1C,MAAM,CAAC,MAAM,eAAe,GAAW,GAAG,kBAAkB,IAAI,CAAC;AAEjE,MAAM,kBAAkB,GAAG,CAAC,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAU,CAAC;AAElE,MAAM,YAAY,GAAG,CAAC,KAAc,EAAiB,EAAE;IACnD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACvE,MAAM,UAAU,GAAG,KAAgC,CAAC;QACpD,KAAK,MAAM,SAAS,IAAI,kBAAkB,EAAE,CAAC;YACzC,MAAM,KAAK,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;YACpC,IAAI,OAAO,KAAK,KAAK,QAAQ;gBAAE,OAAO,KAAK,CAAC;QAChD,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,mBAAmB,CAAC;AAEhD,MAAM,mBAAmB,GAAG,CAAC,MAAc,EAAiB,EAAE;IAC1D,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC/C,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AAC9B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,IAAY,EAAiB,EAAE;IAC1D,IAAI,QAA+C,CAAC;IACpD,IAAI,CAAC;QACD,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7E,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;IACjE,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACjC,OAAO,mBAAmB,CAAC,MAAM,CAAC,CAAC;AACvC,CAAC,CAAC"}
package/dist/plugin.d.ts DELETED
@@ -1,9 +0,0 @@
1
- import type { Plugin, UserConfig } from "vite";
2
- import { type GtkxConfigLoader } from "./loader.js";
3
- export interface GtkxConfigPluginOptions {
4
- name: string;
5
- loadConfig?: GtkxConfigLoader;
6
- config?: (config: UserConfig) => Omit<UserConfig, "plugins"> | null | undefined;
7
- }
8
- export declare const createGtkxConfigPlugin: (options: GtkxConfigPluginOptions) => Plugin;
9
- //# sourceMappingURL=plugin.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAC/C,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG5E,MAAM,WAAW,uBAAuB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;CACnF;AAED,eAAO,MAAM,sBAAsB,GAAI,SAAS,uBAAuB,KAAG,MAmBzE,CAAC"}
package/dist/plugin.js DELETED
@@ -1,24 +0,0 @@
1
- import { createGtkxConfigLoader } from "./loader.js";
2
- import { GTKX_CONFIG_VIRTUAL_ID, RESOLVED_GTKX_CONFIG_VIRTUAL_ID, renderGtkxConfigModule } from "./virtual.js";
3
- export const createGtkxConfigPlugin = (options) => {
4
- const loadConfig = options.loadConfig ?? createGtkxConfigLoader();
5
- let root;
6
- return {
7
- name: options.name,
8
- config(config) {
9
- root = config.root ?? root;
10
- return options.config?.(config) ?? undefined;
11
- },
12
- resolveId(id) {
13
- if (id === GTKX_CONFIG_VIRTUAL_ID)
14
- return RESOLVED_GTKX_CONFIG_VIRTUAL_ID;
15
- return undefined;
16
- },
17
- async load(id) {
18
- if (id !== RESOLVED_GTKX_CONFIG_VIRTUAL_ID)
19
- return undefined;
20
- return renderGtkxConfigModule(await loadConfig(root ?? process.cwd()));
21
- },
22
- };
23
- };
24
- //# sourceMappingURL=plugin.js.map