@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.
- package/README.md +137 -82
- package/dist/config.d.ts +76 -35
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +76 -83
- package/dist/config.js.map +1 -1
- package/dist/element-props.d.ts +180 -0
- package/dist/element-props.d.ts.map +1 -0
- package/dist/element-props.js +154 -0
- package/dist/element-props.js.map +1 -0
- package/dist/index.d.ts +3 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -8
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +3 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +3 -0
- package/dist/internal.js.map +1 -0
- package/dist/loader.d.ts +19 -15
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +25 -39
- package/dist/loader.js.map +1 -1
- package/dist/virtual.d.ts +2 -4
- package/dist/virtual.d.ts.map +1 -1
- package/dist/virtual.js +2 -14
- package/dist/virtual.js.map +1 -1
- package/dist/vite-plugin.d.ts +16 -0
- package/dist/vite-plugin.d.ts.map +1 -0
- package/dist/vite-plugin.js +32 -0
- package/dist/vite-plugin.js.map +1 -0
- package/env.d.ts +4 -22
- package/package.json +20 -7
- package/src/config.ts +134 -144
- package/src/element-props.ts +248 -0
- package/src/index.ts +14 -82
- package/src/internal.ts +2 -0
- package/src/loader.ts +36 -56
- package/src/virtual.ts +3 -32
- package/src/vite-plugin.ts +37 -0
- package/dist/bundled-modules.d.ts +0 -2
- package/dist/bundled-modules.d.ts.map +0 -1
- package/dist/bundled-modules.js +0 -2
- package/dist/bundled-modules.js.map +0 -1
- package/dist/data-dir.d.ts +0 -4
- package/dist/data-dir.d.ts.map +0 -1
- package/dist/data-dir.js +0 -37
- package/dist/data-dir.js.map +0 -1
- package/dist/plugin.d.ts +0 -9
- package/dist/plugin.d.ts.map +0 -1
- package/dist/plugin.js +0 -24
- package/dist/plugin.js.map +0 -1
- package/dist/runtime.d.ts +0 -22
- package/dist/runtime.d.ts.map +0 -1
- package/dist/runtime.js +0 -22
- package/dist/runtime.js.map +0 -1
- package/dist/table-rules-ir.d.ts +0 -36
- package/dist/table-rules-ir.d.ts.map +0 -1
- package/dist/table-rules-ir.js +0 -2
- package/dist/table-rules-ir.js.map +0 -1
- package/dist/table-schema.d.ts +0 -107
- package/dist/table-schema.d.ts.map +0 -1
- package/dist/table-schema.js +0 -199
- package/dist/table-schema.js.map +0 -1
- package/dist/validators.d.ts +0 -4
- package/dist/validators.d.ts.map +0 -1
- package/dist/validators.js +0 -10
- package/dist/validators.js.map +0 -1
- package/dist/wrapper-protocol.d.ts +0 -12
- package/dist/wrapper-protocol.d.ts.map +0 -1
- package/dist/wrapper-protocol.js +0 -12
- package/dist/wrapper-protocol.js.map +0 -1
- package/src/bundled-modules.ts +0 -1
- package/src/data-dir.ts +0 -39
- package/src/plugin.ts +0 -30
- package/src/table-rules-ir.ts +0 -42
- package/src/table-schema.ts +0 -342
- package/src/validators.ts +0 -15
- 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
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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";
|
package/src/internal.ts
ADDED
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
|
|
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:
|
|
12
|
+
config: Config;
|
|
8
13
|
configFile: string | undefined;
|
|
9
|
-
|
|
14
|
+
root: string;
|
|
10
15
|
};
|
|
11
16
|
|
|
12
|
-
export
|
|
13
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
38
|
+
const config = result.config;
|
|
39
|
+
const found = result.configFile !== undefined && existsSync(resolve(cwd, result.configFile));
|
|
46
40
|
|
|
47
|
-
|
|
41
|
+
if (found) validateConfig(config);
|
|
48
42
|
|
|
49
43
|
return {
|
|
50
|
-
config
|
|
51
|
-
configFile: result.configFile,
|
|
52
|
-
|
|
44
|
+
config,
|
|
45
|
+
configFile: found ? result.configFile : undefined,
|
|
46
|
+
root: result.cwd ?? cwd,
|
|
53
47
|
};
|
|
54
48
|
};
|
|
55
49
|
|
|
56
|
-
export type
|
|
57
|
-
allowMissing?: boolean;
|
|
58
|
-
};
|
|
50
|
+
export type ConfigLoader = (cwd: string) => Promise<ResolvedConfig>;
|
|
59
51
|
|
|
60
|
-
export const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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 =
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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 +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"}
|
package/dist/bundled-modules.js
DELETED
|
@@ -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"}
|
package/dist/data-dir.d.ts
DELETED
package/dist/data-dir.d.ts.map
DELETED
|
@@ -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
|
package/dist/data-dir.js.map
DELETED
|
@@ -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
|
package/dist/plugin.d.ts.map
DELETED
|
@@ -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
|