@gtkx/cli 1.2.2 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/README.md +6 -4
  2. package/dist/builder.d.ts +0 -1
  3. package/dist/builder.d.ts.map +1 -1
  4. package/dist/builder.js +6 -6
  5. package/dist/builder.js.map +1 -1
  6. package/dist/codegen/freshness.js +1 -1
  7. package/dist/codegen/freshness.js.map +1 -1
  8. package/dist/codegen/run-codegen.d.ts +2 -2
  9. package/dist/codegen/run-codegen.d.ts.map +1 -1
  10. package/dist/codegen/run-codegen.js +28 -9
  11. package/dist/codegen/run-codegen.js.map +1 -1
  12. package/dist/commands/build.d.ts +0 -4
  13. package/dist/commands/build.d.ts.map +1 -1
  14. package/dist/commands/build.js +0 -5
  15. package/dist/commands/build.js.map +1 -1
  16. package/dist/commands/codegen.d.ts.map +1 -1
  17. package/dist/commands/codegen.js +1 -3
  18. package/dist/commands/codegen.js.map +1 -1
  19. package/dist/commands/docs.d.ts.map +1 -1
  20. package/dist/commands/docs.js +3 -0
  21. package/dist/commands/docs.js.map +1 -1
  22. package/dist/deploy/depends.d.ts +2 -2
  23. package/dist/deploy/depends.d.ts.map +1 -1
  24. package/dist/deploy/depends.js +30 -15
  25. package/dist/deploy/depends.js.map +1 -1
  26. package/dist/deploy/nfpm/config.js +1 -1
  27. package/dist/deploy/nfpm/config.js.map +1 -1
  28. package/dist/deploy/node-runtime/elf.d.ts +1 -1
  29. package/dist/deploy/node-runtime/elf.d.ts.map +1 -1
  30. package/dist/deploy/node-runtime/elf.js +2 -2
  31. package/dist/deploy/node-runtime/elf.js.map +1 -1
  32. package/dist/deploy/node-runtime/index.js +2 -2
  33. package/dist/deploy/node-runtime/index.js.map +1 -1
  34. package/dist/deploy/notices/collect.d.ts +3 -1
  35. package/dist/deploy/notices/collect.d.ts.map +1 -1
  36. package/dist/deploy/notices/collect.js +2 -2
  37. package/dist/deploy/notices/collect.js.map +1 -1
  38. package/dist/deploy/notices/packages.d.ts +2 -1
  39. package/dist/deploy/notices/packages.d.ts.map +1 -1
  40. package/dist/deploy/notices/packages.js +4 -27
  41. package/dist/deploy/notices/packages.js.map +1 -1
  42. package/dist/deploy/payload/icons.d.ts +7 -3
  43. package/dist/deploy/payload/icons.d.ts.map +1 -1
  44. package/dist/deploy/payload/icons.js +46 -28
  45. package/dist/deploy/payload/icons.js.map +1 -1
  46. package/dist/deploy/payload/stage.d.ts.map +1 -1
  47. package/dist/deploy/payload/stage.js +3 -2
  48. package/dist/deploy/payload/stage.js.map +1 -1
  49. package/dist/deploy/run-deploy.d.ts.map +1 -1
  50. package/dist/deploy/run-deploy.js +63 -11
  51. package/dist/deploy/run-deploy.js.map +1 -1
  52. package/dist/deploy/settings/build-manifest.d.ts +9 -0
  53. package/dist/deploy/settings/build-manifest.d.ts.map +1 -0
  54. package/dist/deploy/settings/build-manifest.js +80 -0
  55. package/dist/deploy/settings/build-manifest.js.map +1 -0
  56. package/dist/deploy/settings/index.d.ts.map +1 -1
  57. package/dist/deploy/settings/index.js +9 -3
  58. package/dist/deploy/settings/index.js.map +1 -1
  59. package/dist/deploy/settings/libraries.d.ts +8 -0
  60. package/dist/deploy/settings/libraries.d.ts.map +1 -0
  61. package/dist/deploy/settings/libraries.js +39 -0
  62. package/dist/deploy/settings/libraries.js.map +1 -0
  63. package/dist/deploy/settings/paths.d.ts +3 -0
  64. package/dist/deploy/settings/paths.d.ts.map +1 -1
  65. package/dist/deploy/settings/paths.js +6 -32
  66. package/dist/deploy/settings/paths.js.map +1 -1
  67. package/dist/deploy/targets/appimage.d.ts.map +1 -1
  68. package/dist/deploy/targets/appimage.js +19 -13
  69. package/dist/deploy/targets/appimage.js.map +1 -1
  70. package/dist/deploy/targets/flatpak-manifest.d.ts +2 -1
  71. package/dist/deploy/targets/flatpak-manifest.d.ts.map +1 -1
  72. package/dist/deploy/targets/flatpak-manifest.js +5 -1
  73. package/dist/deploy/targets/flatpak-manifest.js.map +1 -1
  74. package/dist/deploy/targets/flatpak-pnpm.js +4 -4
  75. package/dist/deploy/targets/flatpak-pnpm.js.map +1 -1
  76. package/dist/deploy/targets/flatpak-source.d.ts.map +1 -1
  77. package/dist/deploy/targets/flatpak-source.js +9 -6
  78. package/dist/deploy/targets/flatpak-source.js.map +1 -1
  79. package/dist/deploy/types.d.ts +4 -4
  80. package/dist/deploy/types.d.ts.map +1 -1
  81. package/dist/deploy/types.js.map +1 -1
  82. package/dist/dev/icon-dir.d.ts +1 -1
  83. package/dist/dev/icon-dir.d.ts.map +1 -1
  84. package/dist/dev/icon-dir.js +20 -7
  85. package/dist/dev/icon-dir.js.map +1 -1
  86. package/dist/dev/runner-deps.d.ts.map +1 -1
  87. package/dist/dev/runner-deps.js +5 -1
  88. package/dist/dev/runner-deps.js.map +1 -1
  89. package/dist/dev/runner-main.js +4 -4
  90. package/dist/dev/runner-main.js.map +1 -1
  91. package/dist/dev/runner.d.ts +1 -1
  92. package/dist/dev/runner.d.ts.map +1 -1
  93. package/dist/dev/runner.js +1 -1
  94. package/dist/dev/runner.js.map +1 -1
  95. package/dist/dev/schema-dir.d.ts +1 -1
  96. package/dist/dev/schema-dir.d.ts.map +1 -1
  97. package/dist/dev/schema-dir.js +3 -1
  98. package/dist/dev/schema-dir.js.map +1 -1
  99. package/dist/internal/build-manifest.d.ts +20 -0
  100. package/dist/internal/build-manifest.d.ts.map +1 -0
  101. package/dist/internal/build-manifest.js +6 -0
  102. package/dist/internal/build-manifest.js.map +1 -0
  103. package/dist/internal/icon-path.d.ts +13 -0
  104. package/dist/internal/icon-path.d.ts.map +1 -0
  105. package/dist/internal/icon-path.js +86 -0
  106. package/dist/internal/icon-path.js.map +1 -0
  107. package/dist/internal/source-imports.d.ts +8 -0
  108. package/dist/internal/source-imports.d.ts.map +1 -0
  109. package/dist/internal/source-imports.js +144 -0
  110. package/dist/internal/source-imports.js.map +1 -0
  111. package/dist/mcp/widget-properties.d.ts.map +1 -1
  112. package/dist/mcp/widget-properties.js +3 -12
  113. package/dist/mcp/widget-properties.js.map +1 -1
  114. package/dist/settings/render.d.ts +7 -1
  115. package/dist/settings/render.d.ts.map +1 -1
  116. package/dist/settings/render.js +69 -20
  117. package/dist/settings/render.js.map +1 -1
  118. package/dist/settings/schema.d.ts +4 -3
  119. package/dist/settings/schema.d.ts.map +1 -1
  120. package/dist/settings/schema.js +122 -15
  121. package/dist/settings/schema.js.map +1 -1
  122. package/dist/vite-plugins/asset-emitter.d.ts +1 -1
  123. package/dist/vite-plugins/asset-emitter.d.ts.map +1 -1
  124. package/dist/vite-plugins/asset-emitter.js.map +1 -1
  125. package/dist/vite-plugins/asset-extensions.d.ts.map +1 -1
  126. package/dist/vite-plugins/asset-extensions.js +1 -0
  127. package/dist/vite-plugins/asset-extensions.js.map +1 -1
  128. package/dist/vite-plugins/asset-imports.d.ts +2 -1
  129. package/dist/vite-plugins/asset-imports.d.ts.map +1 -1
  130. package/dist/vite-plugins/asset-imports.js +37 -16
  131. package/dist/vite-plugins/asset-imports.js.map +1 -1
  132. package/dist/vite-plugins/asset-specifier.d.ts +13 -1
  133. package/dist/vite-plugins/asset-specifier.d.ts.map +1 -1
  134. package/dist/vite-plugins/asset-specifier.js +39 -1
  135. package/dist/vite-plugins/asset-specifier.js.map +1 -1
  136. package/dist/vite-plugins/build-manifest.d.ts +5 -0
  137. package/dist/vite-plugins/build-manifest.d.ts.map +1 -0
  138. package/dist/vite-plugins/{bundled-packages.js → build-manifest.js} +20 -14
  139. package/dist/vite-plugins/build-manifest.js.map +1 -0
  140. package/dist/vite-plugins/built-url.d.ts +1 -1
  141. package/dist/vite-plugins/built-url.d.ts.map +1 -1
  142. package/dist/vite-plugins/built-url.js +37 -24
  143. package/dist/vite-plugins/built-url.js.map +1 -1
  144. package/dist/vite-plugins/icons.d.ts +2 -1
  145. package/dist/vite-plugins/icons.d.ts.map +1 -1
  146. package/dist/vite-plugins/icons.js +25 -12
  147. package/dist/vite-plugins/icons.js.map +1 -1
  148. package/dist/vite-plugins/index.d.ts +2 -1
  149. package/dist/vite-plugins/index.d.ts.map +1 -1
  150. package/dist/vite-plugins/index.js +7 -5
  151. package/dist/vite-plugins/index.js.map +1 -1
  152. package/dist/vite-plugins/native.d.ts.map +1 -1
  153. package/dist/vite-plugins/native.js +23 -4
  154. package/dist/vite-plugins/native.js.map +1 -1
  155. package/dist/vite-plugins/resource-init-module.d.ts +7 -3
  156. package/dist/vite-plugins/resource-init-module.d.ts.map +1 -1
  157. package/dist/vite-plugins/resource-init-module.js +39 -14
  158. package/dist/vite-plugins/resource-init-module.js.map +1 -1
  159. package/dist/vite-plugins/resource-shared.d.ts +3 -1
  160. package/dist/vite-plugins/resource-shared.d.ts.map +1 -1
  161. package/dist/vite-plugins/resource-shared.js +3 -1
  162. package/dist/vite-plugins/resource-shared.js.map +1 -1
  163. package/dist/vite-plugins/resources.d.ts +1 -1
  164. package/dist/vite-plugins/resources.d.ts.map +1 -1
  165. package/dist/vite-plugins/resources.js +922 -86
  166. package/dist/vite-plugins/resources.js.map +1 -1
  167. package/dist/vite-plugins/settings-worker-env.d.ts.map +1 -1
  168. package/dist/vite-plugins/settings-worker-env.js +6 -3
  169. package/dist/vite-plugins/settings-worker-env.js.map +1 -1
  170. package/dist/vite-plugins/settings.d.ts +3 -1
  171. package/dist/vite-plugins/settings.d.ts.map +1 -1
  172. package/dist/vite-plugins/settings.js +63 -23
  173. package/dist/vite-plugins/settings.js.map +1 -1
  174. package/dist/vite-plugins/store-links.js +1 -1
  175. package/dist/vite-plugins/store-links.js.map +1 -1
  176. package/dist/vite-plugins/undeclared-library.d.ts.map +1 -1
  177. package/dist/vite-plugins/undeclared-library.js +25 -13
  178. package/dist/vite-plugins/undeclared-library.js.map +1 -1
  179. package/env.d.ts +11 -192
  180. package/package.json +14 -14
  181. package/src/builder.ts +6 -7
  182. package/src/codegen/freshness.ts +1 -1
  183. package/src/codegen/run-codegen.ts +33 -10
  184. package/src/commands/build.ts +0 -5
  185. package/src/commands/codegen.ts +1 -3
  186. package/src/commands/docs.ts +3 -0
  187. package/src/deploy/depends.ts +47 -18
  188. package/src/deploy/nfpm/config.ts +1 -1
  189. package/src/deploy/node-runtime/elf.ts +3 -3
  190. package/src/deploy/node-runtime/index.ts +2 -2
  191. package/src/deploy/notices/collect.ts +4 -2
  192. package/src/deploy/notices/packages.ts +5 -43
  193. package/src/deploy/payload/icons.ts +64 -34
  194. package/src/deploy/payload/stage.ts +5 -2
  195. package/src/deploy/run-deploy.ts +87 -12
  196. package/src/deploy/settings/build-manifest.ts +122 -0
  197. package/src/deploy/settings/index.ts +9 -4
  198. package/src/deploy/settings/libraries.ts +57 -0
  199. package/src/deploy/settings/paths.ts +10 -47
  200. package/src/deploy/targets/appimage.ts +32 -15
  201. package/src/deploy/targets/flatpak-manifest.ts +7 -1
  202. package/src/deploy/targets/flatpak-pnpm.ts +4 -4
  203. package/src/deploy/targets/flatpak-source.ts +9 -10
  204. package/src/deploy/types.ts +4 -4
  205. package/src/dev/icon-dir.ts +31 -7
  206. package/src/dev/runner-deps.ts +5 -1
  207. package/src/dev/runner-main.ts +4 -4
  208. package/src/dev/runner.ts +2 -2
  209. package/src/dev/schema-dir.ts +3 -1
  210. package/src/internal/build-manifest.ts +32 -0
  211. package/src/internal/icon-path.ts +127 -0
  212. package/src/internal/source-imports.ts +200 -0
  213. package/src/mcp/widget-properties.ts +3 -14
  214. package/src/settings/render.ts +99 -21
  215. package/src/settings/schema.ts +190 -17
  216. package/src/vite-plugins/asset-emitter.ts +1 -1
  217. package/src/vite-plugins/asset-extensions.ts +1 -0
  218. package/src/vite-plugins/asset-imports.ts +60 -19
  219. package/src/vite-plugins/asset-specifier.ts +73 -1
  220. package/src/vite-plugins/{bundled-packages.ts → build-manifest.ts} +30 -19
  221. package/src/vite-plugins/built-url.ts +49 -28
  222. package/src/vite-plugins/icons.ts +33 -14
  223. package/src/vite-plugins/index.ts +12 -5
  224. package/src/vite-plugins/native.ts +31 -4
  225. package/src/vite-plugins/resource-init-module.ts +44 -15
  226. package/src/vite-plugins/resource-shared.ts +4 -0
  227. package/src/vite-plugins/resources.ts +1459 -100
  228. package/src/vite-plugins/settings-worker-env.ts +7 -3
  229. package/src/vite-plugins/settings.ts +89 -21
  230. package/src/vite-plugins/store-links.ts +1 -1
  231. package/src/vite-plugins/undeclared-library.ts +31 -13
  232. package/dist/vite-plugins/bundled-packages.d.ts +0 -5
  233. package/dist/vite-plugins/bundled-packages.d.ts.map +0 -1
  234. package/dist/vite-plugins/bundled-packages.js.map +0 -1
@@ -1,10 +1,27 @@
1
1
  import { sortStrings, warn } from "@gtkx/utils";
2
2
  import { createHash } from "node:crypto";
3
- import { copyFileSync, type Dirent, mkdirSync, mkdtempSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import {
4
+ copyFileSync,
5
+ type Dirent,
6
+ existsSync,
7
+ mkdirSync,
8
+ mkdtempSync,
9
+ readdirSync,
10
+ readFileSync,
11
+ realpathSync,
12
+ writeFileSync,
13
+ } from "node:fs";
4
14
  import { tmpdir } from "node:os";
5
- import { dirname, join, relative } from "node:path";
15
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
6
16
  import { DATA_IMPORT_PREFIX } from "../internal/data-dir.js";
17
+ import { discoverSourceImports, type SourceImport } from "../internal/source-imports.js";
7
18
  import { removeTempDir } from "../internal/staging-dir.js";
19
+ import {
20
+ isBareRelativeAsset,
21
+ isDataAsset,
22
+ parseIconSpecifier,
23
+ parseResourceSpecifier,
24
+ } from "../vite-plugins/asset-specifier.js";
8
25
  import { compileSchemas } from "./compile.js";
9
26
  import { type ParsedSchemaFile, parseSchemaXml, SchemaParseError } from "./parser.js";
10
27
  import { renderEnvModule } from "./render.js";
@@ -16,6 +33,9 @@ type SchemaEnvResult = {
16
33
 
17
34
  const SCHEMA_SUFFIX = ".gschema.xml";
18
35
  const STAGED_NAME_LENGTH = 16;
36
+ const SOURCE_DIR = "src";
37
+ const RESOURCE_QUERY = "?resource=";
38
+ const ICON_QUERY = "?icon=";
19
39
 
20
40
  const prependSchemaDir = (dir: string, existing: string | undefined): string => {
21
41
  if (existing === undefined || existing.length === 0) {
@@ -38,9 +58,150 @@ const stageSchema = (dir: string, filePath: string): void => {
38
58
 
39
59
  const toForwardSlashes = (value: string): string => value.replaceAll(/[/\\]/g, "/");
40
60
 
61
+ const projectRelativeSchemaPath = (root: string, filePath: string): string | null => {
62
+ let rel: string;
63
+
64
+ try {
65
+ rel = relative(realpathSync(root), realpathSync(filePath));
66
+ } catch {
67
+ return null;
68
+ }
69
+
70
+ if (rel === "" || rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
71
+ return null;
72
+ }
73
+
74
+ return toForwardSlashes(rel);
75
+ };
76
+
41
77
  const getModuleSpecifier = (dataDirAbs: string, filePath: string): string =>
42
78
  `${DATA_IMPORT_PREFIX}/${toForwardSlashes(relative(dataDirAbs, filePath))}`;
43
79
 
80
+ const getRelativeModuleSpecifier = (filePath: string): string => `*/${basename(filePath)}`;
81
+
82
+ const sourceDirFor = (root: string): string => {
83
+ const sourceDir = join(root, SOURCE_DIR);
84
+
85
+ return existsSync(sourceDir) ? sourceDir : root;
86
+ };
87
+
88
+ const isRelativeImport = (source: string): boolean => source.startsWith("./") || source.startsWith("../");
89
+
90
+ const schemaFileFor = ({ importer, source }: SourceImport): string | null =>
91
+ isRelativeImport(source) && source.endsWith(SCHEMA_SUFFIX) ? resolve(dirname(importer), source) : null;
92
+
93
+ const findImportedSchemaFiles = (imports: SourceImport[]): string[] => {
94
+ const files = imports
95
+ .map((entry) => schemaFileFor(entry))
96
+ .filter((path): path is string => path !== null);
97
+
98
+ return sortStrings(new Set(files));
99
+ };
100
+
101
+ const legacyAssetSpecifier = (source: string): string | null =>
102
+ isDataAsset(source) ? source : null;
103
+
104
+ const blockedAssetSpecifier = (source: string): string | null => {
105
+ if (!isBareRelativeAsset(source)) {
106
+ return null;
107
+ }
108
+
109
+ const separator = source.lastIndexOf("/");
110
+ const name = source.slice(separator + 1);
111
+
112
+ if (name.includes("*")) {
113
+ throw new Error(
114
+ `Cannot generate an asset declaration for ${JSON.stringify(source)}: filenames cannot contain *`,
115
+ );
116
+ }
117
+
118
+ return `*/${name}`;
119
+ };
120
+
121
+ const resourceModuleSpecifier = (source: string): string | null => {
122
+ const parsed = parseResourceSpecifier(source);
123
+ const queryIndex = source.indexOf(RESOURCE_QUERY);
124
+
125
+ if (queryIndex === -1 || typeof parsed?.resourcePath !== "string") {
126
+ return null;
127
+ }
128
+
129
+ const query = source.slice(queryIndex);
130
+
131
+ if (query.includes("*")) {
132
+ throw new Error(
133
+ `Cannot generate an asset declaration for ${JSON.stringify(source)}: resource paths cannot contain *`,
134
+ );
135
+ }
136
+
137
+ return `*${query}`;
138
+ };
139
+
140
+ const iconModuleSpecifier = (source: string): string | null => {
141
+ const parsed = parseIconSpecifier(source);
142
+ const queryIndex = source.indexOf(ICON_QUERY);
143
+
144
+ if (queryIndex === -1 || typeof parsed?.iconName !== "string") {
145
+ return null;
146
+ }
147
+
148
+ const query = source.slice(queryIndex);
149
+
150
+ if (query.includes("*")) {
151
+ throw new Error(
152
+ `Cannot generate an asset declaration for ${JSON.stringify(source)}: icon names cannot contain *`,
153
+ );
154
+ }
155
+
156
+ return `*${query}`;
157
+ };
158
+
159
+ const findAssetModuleSpecifiers = (
160
+ imports: SourceImport[],
161
+ isV2ResourceImports: boolean,
162
+ ): { blocked: string[]; icons: string[]; legacy: string[]; resources: string[] } => {
163
+ const collect = (getSpecifier: (source: string) => string | null): string[] =>
164
+ sortStrings(new Set(imports.map((entry) => getSpecifier(entry.source)).filter((value) => value !== null)));
165
+
166
+ if (isV2ResourceImports) {
167
+ return {
168
+ blocked: collect(blockedAssetSpecifier),
169
+ icons: collect(iconModuleSpecifier),
170
+ legacy: [],
171
+ resources: collect(resourceModuleSpecifier),
172
+ };
173
+ }
174
+
175
+ return { blocked: [], icons: [], legacy: collect(legacyAssetSpecifier), resources: [] };
176
+ };
177
+
178
+ const canonicalPath = (path: string): string => {
179
+ try {
180
+ return realpathSync(path);
181
+ } catch {
182
+ return resolve(path);
183
+ }
184
+ };
185
+
186
+ const assertUniqueSchemaBasenames = (schemaFiles: string[]): void => {
187
+ const owners: Map<string, string> = new Map();
188
+ const canonicalFiles = sortStrings(new Set(schemaFiles.map((path) => canonicalPath(path))));
189
+
190
+ for (const filePath of canonicalFiles) {
191
+ const name = basename(filePath);
192
+ const owner = owners.get(name);
193
+
194
+ if (owner !== undefined && owner !== filePath) {
195
+ throw new Error(
196
+ `Cannot generate types for both ${owner} and ${filePath}: relative GSettings schema imports are ` +
197
+ `typed by basename, and both files are named ${name}. Rename one of them.`,
198
+ );
199
+ }
200
+
201
+ owners.set(name, filePath);
202
+ }
203
+ };
204
+
44
205
  const readVisibleEntries = (dir: string): Dirent[] => {
45
206
  try {
46
207
  return readdirSync(dir, { withFileTypes: true }).filter((entry) => !entry.name.startsWith("."));
@@ -71,11 +232,11 @@ const findSchemaFiles = (dataDir: string): string[] => {
71
232
  };
72
233
 
73
234
  const stageAndCompileProjectSchemas = (root: string, dataDir: string | null): string | null => {
74
- if (dataDir === null) {
75
- return null;
76
- }
77
-
78
- const schemaFiles = findSchemaFiles(join(root, dataDir));
235
+ const imports = discoverSourceImports(sourceDirFor(root));
236
+ const imported = findImportedSchemaFiles(imports);
237
+ assertUniqueSchemaBasenames(imported);
238
+ const legacy = dataDir === null ? [] : findSchemaFiles(join(root, dataDir));
239
+ const schemaFiles = sortStrings(new Set([...legacy, ...imported]));
79
240
 
80
241
  if (schemaFiles.length === 0) {
81
242
  return null;
@@ -98,9 +259,7 @@ const stageAndCompileProjectSchemas = (root: string, dataDir: string | null): st
98
259
 
99
260
  const schemaEnvPath = (rootDir: string): string => join(rootDir, "node_modules", ".gtkx", "env.d.ts");
100
261
 
101
- const parseSchemaFileOrWarn = (filePath: string, dataDirAbs: string): ParsedSchemaFile | null => {
102
- const specifier = getModuleSpecifier(dataDirAbs, filePath);
103
-
262
+ const parseSchemaFileOrWarn = (filePath: string, specifier: string): ParsedSchemaFile | null => {
104
263
  try {
105
264
  return parseSchemaXml(readFileSync(filePath, "utf8"), specifier);
106
265
  } catch (error) {
@@ -114,11 +273,11 @@ const parseSchemaFileOrWarn = (filePath: string, dataDirAbs: string): ParsedSche
114
273
  }
115
274
  };
116
275
 
117
- const parseProjectSchemas = (schemaFiles: string[], dataDirAbs: string): ParsedSchemaFile[] => {
276
+ const parseProjectSchemas = (schemaFiles: string[], specifierFor: (filePath: string) => string): ParsedSchemaFile[] => {
118
277
  const parsed: ParsedSchemaFile[] = [];
119
278
 
120
279
  for (const filePath of schemaFiles) {
121
- const result = parseSchemaFileOrWarn(filePath, dataDirAbs);
280
+ const result = parseSchemaFileOrWarn(filePath, specifierFor(filePath));
122
281
 
123
282
  if (result !== null) {
124
283
  parsed.push(result);
@@ -147,11 +306,24 @@ const didWriteChanges = (path: string, content: string): boolean => {
147
306
  return true;
148
307
  };
149
308
 
150
- const emitSchemaEnv = (rootDir: string, dataDir: string | null): SchemaEnvResult => {
309
+ const emitSchemaEnv = (
310
+ rootDir: string,
311
+ dataDir: string | null,
312
+ isV2ResourceImports = false,
313
+ ): SchemaEnvResult => {
151
314
  const dataDirAbs = dataDir === null ? null : join(rootDir, dataDir);
152
- const schemaFiles = dataDirAbs === null ? [] : findSchemaFiles(dataDirAbs);
153
- const parsed = dataDirAbs === null ? [] : parseProjectSchemas(schemaFiles, dataDirAbs);
154
- const content = renderEnvModule(parsed);
315
+ const legacyFiles = dataDirAbs === null ? [] : findSchemaFiles(dataDirAbs);
316
+ const imports = discoverSourceImports(sourceDirFor(rootDir));
317
+
318
+ const legacy = dataDirAbs === null
319
+ ? []
320
+ : parseProjectSchemas(legacyFiles, (filePath) => getModuleSpecifier(dataDirAbs, filePath));
321
+
322
+ const importedFiles = findImportedSchemaFiles(imports);
323
+ assertUniqueSchemaBasenames(importedFiles);
324
+ const imported = parseProjectSchemas(importedFiles, getRelativeModuleSpecifier);
325
+ const assets = findAssetModuleSpecifiers(imports, isV2ResourceImports);
326
+ const content = renderEnvModule([...legacy, ...imported], assets);
155
327
  const path = schemaEnvPath(rootDir);
156
328
  const isWritten = didWriteChanges(path, content);
157
329
 
@@ -162,7 +334,8 @@ export {
162
334
  SCHEMA_SUFFIX,
163
335
  prependSchemaDir,
164
336
  stageSchema,
165
- findSchemaFiles,
337
+ assertUniqueSchemaBasenames,
338
+ projectRelativeSchemaPath,
166
339
  stageAndCompileProjectSchemas,
167
340
  emitSchemaEnv,
168
341
  };
@@ -1,5 +1,5 @@
1
1
  type AssetEmitter = {
2
- emitFile: (file: { type: "asset"; fileName: string; source: Buffer }) => void;
2
+ emitFile: (file: { type: "asset"; fileName: string; source: Buffer | string }) => void;
3
3
  };
4
4
 
5
5
  export { type AssetEmitter };
@@ -4,6 +4,7 @@ const ASSET_EXTENSIONS = [
4
4
  "jpeg",
5
5
  "gif",
6
6
  "svg",
7
+ "xpm",
7
8
  "webp",
8
9
  "webm",
9
10
  "mp4",
@@ -1,8 +1,17 @@
1
- import type { ParseResult, ParserOptions, Plugin } from "vite";
1
+ import type { ConfigLoader } from "@gtkx/config";
2
+ import type { ParseResult, ParserOptions, Plugin, UserConfig } from "vite";
3
+ import { createConfigLoader } from "@gtkx/config/internal";
2
4
  import { extname } from "node:path";
3
5
  import { parseSync } from "vite";
4
6
  import { ASSET_MENTION_RE } from "./asset-extensions.js";
5
- import { DATA_PREFIX, isAssetSpecifier, isDataAsset } from "./asset-specifier.js";
7
+ import {
8
+ DATA_PREFIX,
9
+ isAssetSpecifier,
10
+ isDataAsset,
11
+ isUrlSpecifier,
12
+ parseIconSpecifier,
13
+ parseResourceSpecifier,
14
+ } from "./asset-specifier.js";
6
15
  import { RESOURCE_PATH_EXPORT } from "./resource-shared.js";
7
16
  import { stripQuery } from "./strip-query.js";
8
17
 
@@ -13,6 +22,7 @@ type ExportStatement = ParsedModule["staticExports"][number];
13
22
  type ExportEntry = ExportStatement["entries"][number];
14
23
  type BindingEntry = { importName: { name: string | null }; isType: boolean };
15
24
  type NamedBinding = { name: string; source: string };
25
+ type PluginState = { isV2: boolean };
16
26
 
17
27
  const SOURCE_LANGUAGES: Map<string, SourceLanguage> = new Map([
18
28
  [".cjs", "jsx"],
@@ -37,8 +47,12 @@ const BUNDLE_ADVICE =
37
47
  "nothing else.";
38
48
 
39
49
  const QUERY_ADVICE =
40
- "A query suffix hands the import to the Vite asset pipeline instead of the GResource bundle; drop the " +
41
- `query to keep ${RESOURCE_BINDING}${URL_FALLBACK}`;
50
+ `A URL asset exports only its default filesystem path; import the default instead of ${RESOURCE_BINDING}.`;
51
+
52
+ const RESOURCE_ADVICE =
53
+ `A resource asset exports only its default GResource path and ${RESOURCE_BINDING}, and nothing else.`;
54
+
55
+ const ICON_ADVICE = "An icon asset exports only its default icon-theme name, and nothing else.";
42
56
 
43
57
  const RELATIVE_ADVICE =
44
58
  `Only assets imported through "${DATA_PREFIX}" are staged into the GResource bundle and export ` +
@@ -76,34 +90,54 @@ const bindings = (parsed: ParsedModule): NamedBinding[] => [
76
90
  ...parsed.staticExports.flatMap((statement) => exportBindings(statement)),
77
91
  ];
78
92
 
79
- const isBacked = ({ name, source }: NamedBinding): boolean =>
80
- isDataAsset(source) && name === RESOURCE_PATH_EXPORT;
93
+ const isBacked = ({ name, source }: NamedBinding, isV2: boolean): boolean => {
94
+ if (name !== RESOURCE_PATH_EXPORT) {
95
+ return false;
96
+ }
97
+
98
+ return isV2 ? parseResourceSpecifier(source) !== null : isDataAsset(source);
99
+ };
100
+
101
+ const isCheckedSpecifier = (source: string): boolean => isAssetSpecifier(source) || isUrlSpecifier(source);
102
+
103
+ const unbackedBinding = (parsed: ParsedModule, isV2: boolean): NamedBinding | undefined =>
104
+ bindings(parsed).find((binding) => isCheckedSpecifier(binding.source) && !isBacked(binding, isV2));
81
105
 
82
- const unbackedBinding = (parsed: ParsedModule): NamedBinding | undefined =>
83
- bindings(parsed).find((binding) => isAssetSpecifier(binding.source) && !isBacked(binding));
106
+ const isBundledResource = (source: string, isV2: boolean): boolean =>
107
+ isV2
108
+ ? parseResourceSpecifier(source) !== null || parseIconSpecifier(source) !== null
109
+ : isDataAsset(source);
84
110
 
85
- const bundleAdvice = (source: string): string => {
86
- if (isDataAsset(source)) {
87
- return BUNDLE_ADVICE;
111
+ const v2BundleAdvice = (source: string): string =>
112
+ parseIconSpecifier(source) === null ? RESOURCE_ADVICE : ICON_ADVICE;
113
+
114
+ const bundleAdvice = (source: string, isV2: boolean): string => {
115
+ if (isBundledResource(source, isV2)) {
116
+ return isV2 ? v2BundleAdvice(source) : BUNDLE_ADVICE;
117
+ }
118
+
119
+ if (isUrlSpecifier(source)) {
120
+ return QUERY_ADVICE;
88
121
  }
89
122
 
90
123
  return source.startsWith(DATA_PREFIX) ? QUERY_ADVICE : RELATIVE_ADVICE;
91
124
  };
92
125
 
93
- const unbackedBindingError = (path: string, binding: NamedBinding): Error =>
126
+ const unbackedBindingError = (path: string, binding: NamedBinding, isV2: boolean): Error =>
94
127
  new Error(
95
128
  `${path}: ${JSON.stringify(binding.source)} does not export ${JSON.stringify(binding.name)}, which ` +
96
129
  "gtkx build rejects and gtkx dev would bind as undefined. " +
97
- bundleAdvice(binding.source),
130
+ bundleAdvice(binding.source, isV2),
98
131
  );
99
132
 
100
133
  const isCheckedSource = (path: string): boolean => !path.startsWith(VIRTUAL_PREFIX) && !NODE_MODULES.test(path);
134
+ const hasCheckedAssetMention = (code: string): boolean => ASSET_MENTION_RE.test(code) || code.includes("?url");
101
135
 
102
- const checkAssetImports = (code: string, id: string): void => {
136
+ const checkAssetImports = (code: string, id: string, isV2: boolean): void => {
103
137
  const path = stripQuery(id);
104
138
  const lang = SOURCE_LANGUAGES.get(extname(path).toLowerCase());
105
139
 
106
- if (lang === undefined || !isCheckedSource(path) || !ASSET_MENTION_RE.test(code)) {
140
+ if (lang === undefined || !isCheckedSource(path) || !hasCheckedAssetMention(code)) {
107
141
  return;
108
142
  }
109
143
 
@@ -113,20 +147,27 @@ const checkAssetImports = (code: string, id: string): void => {
113
147
  return;
114
148
  }
115
149
 
116
- const binding = unbackedBinding(parsed.module);
150
+ const binding = unbackedBinding(parsed.module, isV2);
117
151
 
118
152
  if (binding !== undefined) {
119
- throw unbackedBindingError(path, binding);
153
+ throw unbackedBindingError(path, binding, isV2);
120
154
  }
121
155
  };
122
156
 
123
- function gtkxAssetImports(): Plugin {
157
+ function gtkxAssetImports(loadConfig: ConfigLoader = createConfigLoader()): Plugin {
158
+ const state: PluginState = { isV2: false };
159
+
124
160
  return {
125
161
  name: "gtkx:asset-imports",
126
162
  enforce: "pre",
127
163
 
164
+ async config(config: UserConfig) {
165
+ const loaded = await loadConfig.load(config.root ?? process.cwd());
166
+ state.isV2 = loaded.config.future?.v2ResourceImports === true;
167
+ },
168
+
128
169
  transform(code, id) {
129
- checkAssetImports(code, id);
170
+ checkAssetImports(code, id, state.isV2);
130
171
  },
131
172
  };
132
173
  }
@@ -1,9 +1,81 @@
1
1
  import { DATA_IMPORT_PREFIX } from "../internal/data-dir.js";
2
2
  import { ASSET_PATH_RE, ASSET_RE } from "./asset-extensions.js";
3
+ import { stripQuery } from "./strip-query.js";
4
+
5
+ type ResourceSpecifier = {
6
+ assetSource: string;
7
+ resourcePath: string | null;
8
+ };
9
+
10
+ type IconSpecifier = {
11
+ assetSource: string;
12
+ iconName: string | null;
13
+ };
3
14
 
4
15
  const DATA_PREFIX = `${DATA_IMPORT_PREFIX}/`;
16
+ const RELATIVE_PREFIX_RE = /^\.\.?(?:\/|$)/;
17
+ const RESOURCE_QUERY_RE = /^resource(?:=([^&#]+))?$/;
18
+ const ICON_QUERY_RE = /^icon(?:=([^&#]+))?$/;
19
+ const URL_QUERY_RE = /(?:^|&)url(?:&|$)/;
20
+
21
+ const getQuery = (source: string): string | null => {
22
+ const queryIndex = source.indexOf("?");
23
+
24
+ return queryIndex === -1 ? null : source.slice(queryIndex + 1);
25
+ };
5
26
 
6
27
  const isAssetSpecifier = (source: string): boolean => ASSET_PATH_RE.test(source);
7
28
  const isDataAsset = (source: string): boolean => source.startsWith(DATA_PREFIX) && ASSET_RE.test(source);
8
29
 
9
- export { DATA_PREFIX, isAssetSpecifier, isDataAsset };
30
+ const isBareRelativeAsset = (source: string): boolean =>
31
+ getQuery(source) === null && RELATIVE_PREFIX_RE.test(source) && ASSET_RE.test(source);
32
+
33
+ const parseResourceSpecifier = (source: string): ResourceSpecifier | null => {
34
+ const query = getQuery(source);
35
+ const assetSource = stripQuery(source);
36
+
37
+ if (query === null || !ASSET_RE.test(assetSource)) {
38
+ return null;
39
+ }
40
+
41
+ const match = RESOURCE_QUERY_RE.exec(query);
42
+
43
+ if (match === null) {
44
+ return null;
45
+ }
46
+
47
+ return { assetSource, resourcePath: match[1] ?? null };
48
+ };
49
+
50
+ const parseIconSpecifier = (source: string): IconSpecifier | null => {
51
+ const query = getQuery(source);
52
+ const assetSource = stripQuery(source);
53
+
54
+ if (query === null || !ASSET_RE.test(assetSource)) {
55
+ return null;
56
+ }
57
+
58
+ const match = ICON_QUERY_RE.exec(query);
59
+
60
+ if (match === null) {
61
+ return null;
62
+ }
63
+
64
+ return { assetSource, iconName: match[1] ?? null };
65
+ };
66
+
67
+ const isUrlSpecifier = (source: string): boolean => {
68
+ const query = getQuery(source);
69
+
70
+ return query !== null && URL_QUERY_RE.test(query);
71
+ };
72
+
73
+ export {
74
+ DATA_PREFIX,
75
+ isAssetSpecifier,
76
+ isBareRelativeAsset,
77
+ isDataAsset,
78
+ isUrlSpecifier,
79
+ parseIconSpecifier,
80
+ parseResourceSpecifier,
81
+ };
@@ -2,6 +2,14 @@ import type { Plugin } from "vite";
2
2
  import { isRecord, sortStringsBy } from "@gtkx/utils";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { dirname, join, relative, resolve } from "node:path";
5
+ import {
6
+ BUILD_MANIFEST_FILENAME,
7
+ BUILD_MANIFEST_FORMAT_VERSION,
8
+ BUILD_MANIFEST_GENERATOR,
9
+ type BuildManifest,
10
+ type BuildManifestCollector,
11
+ type RecordedPackage,
12
+ } from "../internal/build-manifest.js";
5
13
  import { stripQuery } from "./strip-query.js";
6
14
 
7
15
  type PackageIdentity = {
@@ -9,22 +17,17 @@ type PackageIdentity = {
9
17
  version: string | null;
10
18
  };
11
19
 
12
- type RecordedPackage = PackageIdentity & {
13
- dir: string;
14
- };
15
-
16
- type PackagesState = {
20
+ type ManifestState = {
17
21
  outDir: string;
18
22
  };
19
23
 
20
- const BUNDLED_PACKAGES_FILENAME = "gtkx-packages.json";
21
- const MANIFEST_FILENAME = "package.json";
24
+ const PACKAGE_MANIFEST_FILENAME = "package.json";
22
25
  const DEFAULT_OUT_DIR = "dist";
23
26
  const JSON_INDENT = 4;
24
27
 
25
28
  const identityIn = (dir: string): PackageIdentity | null => {
26
29
  try {
27
- const parsed: unknown = JSON.parse(readFileSync(join(dir, MANIFEST_FILENAME), "utf8"));
30
+ const parsed: unknown = JSON.parse(readFileSync(join(dir, PACKAGE_MANIFEST_FILENAME), "utf8"));
28
31
 
29
32
  if (!isRecord(parsed) || typeof parsed.name !== "string") {
30
33
  return null;
@@ -54,26 +57,27 @@ const packageForModule = (id: string): RecordedPackage | null => {
54
57
  return path.startsWith("/") ? packageIn(dirname(path)) : null;
55
58
  };
56
59
 
57
- const keyFor = (entry: RecordedPackage): string => `${entry.name}@${entry.version ?? ""}`;
60
+ const packageKey = (entry: RecordedPackage): string => `${entry.name}@${entry.version ?? ""}`;
58
61
 
59
62
  const relativeTo = (outDir: string, entry: RecordedPackage): RecordedPackage => ({
60
63
  ...entry,
61
64
  dir: relative(outDir, entry.dir),
62
65
  });
63
66
 
64
- const renderPackages = (outDir: string, ids: string[]): string => {
67
+ const packagesFor = (outDir: string, ids: string[]): RecordedPackage[] => {
65
68
  const found = ids.map((id) => packageForModule(id)).filter((entry) => entry !== null);
66
- const unique: Map<string, RecordedPackage> = new Map(found.map((entry) => [keyFor(entry), entry]));
67
- const packages = sortStringsBy(unique.values(), keyFor).map((entry) => relativeTo(outDir, entry));
69
+ const unique: Map<string, RecordedPackage> = new Map(found.map((entry) => [packageKey(entry), entry]));
68
70
 
69
- return `${JSON.stringify({ packages }, null, JSON_INDENT)}\n`;
71
+ return sortStringsBy(unique.values(), packageKey).map((entry) => relativeTo(outDir, entry));
70
72
  };
71
73
 
72
- function gtkxBundledPackages(root: string): Plugin {
73
- const state: PackagesState = { outDir: join(root, DEFAULT_OUT_DIR) };
74
+ const renderManifest = (manifest: BuildManifest): string => `${JSON.stringify(manifest, null, JSON_INDENT)}\n`;
75
+
76
+ function gtkxBuildManifest(root: string, collector: BuildManifestCollector): Plugin {
77
+ const state: ManifestState = { outDir: join(root, DEFAULT_OUT_DIR) };
74
78
 
75
79
  return {
76
- name: "gtkx:bundled-packages",
80
+ name: "gtkx:build-manifest",
77
81
  apply: "build",
78
82
 
79
83
  configResolved(config) {
@@ -83,13 +87,20 @@ function gtkxBundledPackages(root: string): Plugin {
83
87
  generateBundle(_options, bundle) {
84
88
  const ids = Object.values(bundle).flatMap((output) => (output.type === "chunk" ? output.moduleIds : []));
85
89
 
90
+ const manifest: BuildManifest = {
91
+ generator: BUILD_MANIFEST_GENERATOR,
92
+ formatVersion: BUILD_MANIFEST_FORMAT_VERSION,
93
+ schemas: collector.schemas,
94
+ packages: packagesFor(state.outDir, ids),
95
+ };
96
+
86
97
  this.emitFile({
87
98
  type: "asset",
88
- fileName: BUNDLED_PACKAGES_FILENAME,
89
- source: renderPackages(state.outDir, ids),
99
+ fileName: BUILD_MANIFEST_FILENAME,
100
+ source: renderManifest(manifest),
90
101
  });
91
102
  },
92
103
  };
93
104
  }
94
105
 
95
- export { BUNDLED_PACKAGES_FILENAME, gtkxBundledPackages };
106
+ export { gtkxBuildManifest };