@gtkx/cli 2.0.0-beta.6 → 2.0.0-beta.8

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 (205) hide show
  1. package/README.md +43 -40
  2. package/dist/cli.js +1 -1
  3. package/dist/cli.js.map +1 -1
  4. package/dist/codegen/freshness.d.ts.map +1 -1
  5. package/dist/codegen/freshness.js +5 -1
  6. package/dist/codegen/freshness.js.map +1 -1
  7. package/dist/commands/deploy.d.ts +4 -0
  8. package/dist/commands/deploy.d.ts.map +1 -1
  9. package/dist/commands/deploy.js +6 -0
  10. package/dist/commands/deploy.js.map +1 -1
  11. package/dist/deploy/download.d.ts +7 -3
  12. package/dist/deploy/download.d.ts.map +1 -1
  13. package/dist/deploy/download.js +19 -15
  14. package/dist/deploy/download.js.map +1 -1
  15. package/dist/deploy/native-addon.d.ts +5 -0
  16. package/dist/deploy/native-addon.d.ts.map +1 -0
  17. package/dist/deploy/native-addon.js +134 -0
  18. package/dist/deploy/native-addon.js.map +1 -0
  19. package/dist/deploy/node-runtime/download.js +2 -2
  20. package/dist/deploy/node-runtime/download.js.map +1 -1
  21. package/dist/deploy/node-runtime/elf.d.ts +3 -1
  22. package/dist/deploy/node-runtime/elf.d.ts.map +1 -1
  23. package/dist/deploy/node-runtime/elf.js +3 -1
  24. package/dist/deploy/node-runtime/elf.js.map +1 -1
  25. package/dist/deploy/node-runtime/index.d.ts.map +1 -1
  26. package/dist/deploy/node-runtime/index.js +14 -2
  27. package/dist/deploy/node-runtime/index.js.map +1 -1
  28. package/dist/deploy/payload/stage.d.ts +2 -1
  29. package/dist/deploy/payload/stage.d.ts.map +1 -1
  30. package/dist/deploy/payload/stage.js +22 -2
  31. package/dist/deploy/payload/stage.js.map +1 -1
  32. package/dist/deploy/run-deploy.d.ts +1 -0
  33. package/dist/deploy/run-deploy.d.ts.map +1 -1
  34. package/dist/deploy/run-deploy.js +135 -79
  35. package/dist/deploy/run-deploy.js.map +1 -1
  36. package/dist/deploy/settings/arch.d.ts +9 -3
  37. package/dist/deploy/settings/arch.d.ts.map +1 -1
  38. package/dist/deploy/settings/arch.js +27 -6
  39. package/dist/deploy/settings/arch.js.map +1 -1
  40. package/dist/deploy/settings/index.d.ts +2 -1
  41. package/dist/deploy/settings/index.d.ts.map +1 -1
  42. package/dist/deploy/settings/index.js +2 -1
  43. package/dist/deploy/settings/index.js.map +1 -1
  44. package/dist/deploy/settings/libraries.d.ts.map +1 -1
  45. package/dist/deploy/settings/libraries.js +2 -1
  46. package/dist/deploy/settings/libraries.js.map +1 -1
  47. package/dist/deploy/settings/paths.d.ts +2 -1
  48. package/dist/deploy/settings/paths.d.ts.map +1 -1
  49. package/dist/deploy/settings/paths.js +6 -5
  50. package/dist/deploy/settings/paths.js.map +1 -1
  51. package/dist/deploy/targets/appimage.d.ts.map +1 -1
  52. package/dist/deploy/targets/appimage.js +1 -0
  53. package/dist/deploy/targets/appimage.js.map +1 -1
  54. package/dist/deploy/targets/flatpak-pnpm.js +4 -4
  55. package/dist/deploy/targets/flatpak-pnpm.js.map +1 -1
  56. package/dist/deploy/targets/flatpak-source.d.ts.map +1 -1
  57. package/dist/deploy/targets/flatpak-source.js +2 -0
  58. package/dist/deploy/targets/flatpak-source.js.map +1 -1
  59. package/dist/deploy/targets/flatpak.d.ts.map +1 -1
  60. package/dist/deploy/targets/flatpak.js +1 -0
  61. package/dist/deploy/targets/flatpak.js.map +1 -1
  62. package/dist/deploy/targets/nfpm-target.d.ts.map +1 -1
  63. package/dist/deploy/targets/nfpm-target.js +1 -0
  64. package/dist/deploy/targets/nfpm-target.js.map +1 -1
  65. package/dist/deploy/types.d.ts +4 -2
  66. package/dist/deploy/types.d.ts.map +1 -1
  67. package/dist/deploy/types.js.map +1 -1
  68. package/dist/deploy/vendored/appimagetool.js +2 -2
  69. package/dist/deploy/vendored/appimagetool.js.map +1 -1
  70. package/dist/deploy/vendored/nfpm.js +1 -1
  71. package/dist/deploy/vendored/nfpm.js.map +1 -1
  72. package/dist/dev/font-dir.d.ts +3 -0
  73. package/dist/dev/font-dir.d.ts.map +1 -0
  74. package/dist/dev/font-dir.js +9 -0
  75. package/dist/dev/font-dir.js.map +1 -0
  76. package/dist/dev/runner-main.d.ts.map +1 -1
  77. package/dist/dev/runner-main.js +2 -0
  78. package/dist/dev/runner-main.js.map +1 -1
  79. package/dist/dev/runner.d.ts.map +1 -1
  80. package/dist/dev/runner.js +19 -6
  81. package/dist/dev/runner.js.map +1 -1
  82. package/dist/internal/agent-rules.d.ts.map +1 -1
  83. package/dist/internal/agent-rules.js +6 -2
  84. package/dist/internal/agent-rules.js.map +1 -1
  85. package/dist/internal/banner.d.ts.map +1 -1
  86. package/dist/internal/banner.js +3 -0
  87. package/dist/internal/banner.js.map +1 -1
  88. package/dist/internal/font-path.d.ts +4 -0
  89. package/dist/internal/font-path.d.ts.map +1 -0
  90. package/dist/internal/font-path.js +12 -0
  91. package/dist/internal/font-path.js.map +1 -0
  92. package/dist/internal/font-staging.d.ts +6 -0
  93. package/dist/internal/font-staging.d.ts.map +1 -0
  94. package/dist/internal/font-staging.js +92 -0
  95. package/dist/internal/font-staging.js.map +1 -0
  96. package/dist/internal/source-imports.d.ts +2 -1
  97. package/dist/internal/source-imports.d.ts.map +1 -1
  98. package/dist/internal/source-imports.js +8 -3
  99. package/dist/internal/source-imports.js.map +1 -1
  100. package/dist/internal/xdg-banner.d.ts +4 -0
  101. package/dist/internal/xdg-banner.d.ts.map +1 -0
  102. package/dist/internal/xdg-banner.js +11 -0
  103. package/dist/internal/xdg-banner.js.map +1 -0
  104. package/dist/internal/xdg-data-dirs.d.ts +2 -1
  105. package/dist/internal/xdg-data-dirs.d.ts.map +1 -1
  106. package/dist/internal/xdg-data-dirs.js +1 -1
  107. package/dist/internal/xdg-data-dirs.js.map +1 -1
  108. package/dist/settings/schema.d.ts.map +1 -1
  109. package/dist/settings/schema.js +1 -6
  110. package/dist/settings/schema.js.map +1 -1
  111. package/dist/vite-plugins/asset-extensions.d.ts.map +1 -1
  112. package/dist/vite-plugins/asset-extensions.js +2 -0
  113. package/dist/vite-plugins/asset-extensions.js.map +1 -1
  114. package/dist/vite-plugins/asset-imports.d.ts.map +1 -1
  115. package/dist/vite-plugins/asset-imports.js +7 -2
  116. package/dist/vite-plugins/asset-imports.js.map +1 -1
  117. package/dist/vite-plugins/asset-specifier.d.ts +5 -1
  118. package/dist/vite-plugins/asset-specifier.d.ts.map +1 -1
  119. package/dist/vite-plugins/asset-specifier.js +10 -1
  120. package/dist/vite-plugins/asset-specifier.js.map +1 -1
  121. package/dist/vite-plugins/css.d.ts.map +1 -1
  122. package/dist/vite-plugins/css.js +3 -2
  123. package/dist/vite-plugins/css.js.map +1 -1
  124. package/dist/vite-plugins/font-name.d.ts +3 -0
  125. package/dist/vite-plugins/font-name.d.ts.map +1 -0
  126. package/dist/vite-plugins/font-name.js +406 -0
  127. package/dist/vite-plugins/font-name.js.map +1 -0
  128. package/dist/vite-plugins/font-worker-env.d.ts +4 -0
  129. package/dist/vite-plugins/font-worker-env.d.ts.map +1 -0
  130. package/dist/vite-plugins/font-worker-env.js +19 -0
  131. package/dist/vite-plugins/font-worker-env.js.map +1 -0
  132. package/dist/vite-plugins/font.d.ts +4 -0
  133. package/dist/vite-plugins/font.d.ts.map +1 -0
  134. package/dist/vite-plugins/font.js +99 -0
  135. package/dist/vite-plugins/font.js.map +1 -0
  136. package/dist/vite-plugins/icons.d.ts.map +1 -1
  137. package/dist/vite-plugins/icons.js +5 -10
  138. package/dist/vite-plugins/icons.js.map +1 -1
  139. package/dist/vite-plugins/index.d.ts.map +1 -1
  140. package/dist/vite-plugins/index.js +2 -0
  141. package/dist/vite-plugins/index.js.map +1 -1
  142. package/dist/vite-plugins/react-compiler.js +2 -2
  143. package/dist/vite-plugins/react-compiler.js.map +1 -1
  144. package/dist/vite-plugins/resources.js +3 -3
  145. package/dist/vite-plugins/resources.js.map +1 -1
  146. package/dist/vite-plugins/settings-worker-env.js +2 -2
  147. package/dist/vite-plugins/settings-worker-env.js.map +1 -1
  148. package/dist/vite-plugins/store-links.d.ts.map +1 -1
  149. package/dist/vite-plugins/store-links.js +2 -1
  150. package/dist/vite-plugins/store-links.js.map +1 -1
  151. package/dist/vite-plugins/undeclared-library.js +5 -5
  152. package/dist/vite-plugins/undeclared-library.js.map +1 -1
  153. package/dist/vitest-plugin.d.ts.map +1 -1
  154. package/dist/vitest-plugin.js +2 -0
  155. package/dist/vitest-plugin.js.map +1 -1
  156. package/env.d.ts +9 -0
  157. package/package.json +17 -15
  158. package/src/cli.ts +1 -1
  159. package/src/codegen/freshness.ts +5 -1
  160. package/src/commands/deploy.ts +6 -0
  161. package/src/deploy/download.ts +33 -17
  162. package/src/deploy/native-addon.ts +183 -0
  163. package/src/deploy/node-runtime/download.ts +2 -2
  164. package/src/deploy/node-runtime/elf.ts +4 -1
  165. package/src/deploy/node-runtime/index.ts +24 -4
  166. package/src/deploy/payload/stage.ts +34 -2
  167. package/src/deploy/run-deploy.ts +203 -93
  168. package/src/deploy/settings/arch.ts +40 -8
  169. package/src/deploy/settings/index.ts +4 -1
  170. package/src/deploy/settings/libraries.ts +2 -1
  171. package/src/deploy/settings/paths.ts +8 -6
  172. package/src/deploy/targets/appimage.ts +1 -0
  173. package/src/deploy/targets/flatpak-pnpm.ts +4 -4
  174. package/src/deploy/targets/flatpak-source.ts +2 -0
  175. package/src/deploy/targets/flatpak.ts +1 -0
  176. package/src/deploy/targets/nfpm-target.ts +1 -0
  177. package/src/deploy/types.ts +4 -1
  178. package/src/deploy/vendored/appimagetool.ts +2 -2
  179. package/src/deploy/vendored/nfpm.ts +1 -1
  180. package/src/dev/font-dir.ts +11 -0
  181. package/src/dev/runner-main.ts +2 -0
  182. package/src/dev/runner.ts +24 -6
  183. package/src/internal/agent-rules.ts +6 -2
  184. package/src/internal/banner.ts +4 -0
  185. package/src/internal/font-path.ts +15 -0
  186. package/src/internal/font-staging.ts +129 -0
  187. package/src/internal/source-imports.ts +18 -3
  188. package/src/internal/xdg-banner.ts +13 -0
  189. package/src/internal/xdg-data-dirs.ts +1 -1
  190. package/src/settings/schema.ts +1 -8
  191. package/src/vite-plugins/asset-extensions.ts +2 -0
  192. package/src/vite-plugins/asset-imports.ts +9 -1
  193. package/src/vite-plugins/asset-specifier.ts +17 -0
  194. package/src/vite-plugins/css.ts +4 -2
  195. package/src/vite-plugins/font-name.ts +582 -0
  196. package/src/vite-plugins/font-worker-env.ts +25 -0
  197. package/src/vite-plugins/font.ts +165 -0
  198. package/src/vite-plugins/icons.ts +6 -12
  199. package/src/vite-plugins/index.ts +2 -0
  200. package/src/vite-plugins/react-compiler.ts +2 -2
  201. package/src/vite-plugins/resources.ts +3 -3
  202. package/src/vite-plugins/settings-worker-env.ts +2 -2
  203. package/src/vite-plugins/store-links.ts +2 -1
  204. package/src/vite-plugins/undeclared-library.ts +5 -5
  205. package/src/vitest-plugin.ts +2 -0
@@ -39,7 +39,7 @@ const resolveAppimageTooling = async (arch: string): Promise<AppimageTooling> =>
39
39
  url: `${APPIMAGETOOL_URL}/appimagetool-${arch}.AppImage`,
40
40
  dest: join(dir, `appimagetool-${arch}`),
41
41
  label: `appimagetool ${APPIMAGETOOL_VERSION}`,
42
- sha256: digestFor(APPIMAGETOOL_DIGESTS, arch, "appimagetool"),
42
+ digest: digestFor(APPIMAGETOOL_DIGESTS, arch, "appimagetool"),
43
43
  mode: EXECUTABLE_MODE,
44
44
  });
45
45
 
@@ -47,7 +47,7 @@ const resolveAppimageTooling = async (arch: string): Promise<AppimageTooling> =>
47
47
  url: `${RUNTIME_URL}/runtime-${arch}`,
48
48
  dest: join(dir, `runtime-${arch}`),
49
49
  label: `the AppImage runtime ${RUNTIME_VERSION}`,
50
- sha256: digestFor(RUNTIME_DIGESTS, arch, "the AppImage runtime"),
50
+ digest: digestFor(RUNTIME_DIGESTS, arch, "the AppImage runtime"),
51
51
  mode: EXECUTABLE_MODE,
52
52
  });
53
53
 
@@ -56,7 +56,7 @@ const downloadNfpm = async (dir: string, binary: string): Promise<string> => {
56
56
  url: `${NFPM_BASE_URL}/${assetName}`,
57
57
  dest: join(dir, assetName),
58
58
  label: `nfpm ${NFPM_VERSION}`,
59
- sha256: digestFor(process.arch),
59
+ digest: digestFor(process.arch),
60
60
  });
61
61
 
62
62
  extractNfpm(archive, binary);
@@ -0,0 +1,11 @@
1
+ import { stageProjectFonts } from "../internal/font-staging.js";
2
+ import { prependXdgDataDir } from "../internal/xdg-data-dirs.js";
3
+
4
+ const prepareDevFontDir = (root: string): string => {
5
+ const shareDir = stageProjectFonts(root);
6
+ process.env.XDG_DATA_DIRS = prependXdgDataDir(shareDir, process.env.XDG_DATA_DIRS);
7
+
8
+ return shareDir;
9
+ };
10
+
11
+ export { prepareDevFontDir };
@@ -2,6 +2,7 @@ import { loadConfig } from "@gtkx/config";
2
2
  import { error } from "@gtkx/utils";
3
3
  import { resolve } from "node:path";
4
4
  import { DEV_CONFIG_ENV, DEV_ENTRY_ENV } from "./entry-env.js";
5
+ import { prepareDevFontDir } from "./font-dir.js";
5
6
  import { prepareDevIconDir } from "./icon-dir.js";
6
7
  import { prepareDevLocaleDir } from "./locale-dir.js";
7
8
  import { createDevRunner } from "./runner.js";
@@ -33,6 +34,7 @@ const main = async (): Promise<void> => {
33
34
  prepareDevLocaleDir(root, config.applicationId);
34
35
  prepareDevSchemaDir(root);
35
36
  prepareDevIconDir(root, config.applicationId, config.applicationIcon);
37
+ prepareDevFontDir(root);
36
38
  const entryPath = resolve(cwd, entryArg);
37
39
  const { defaultDevRunnerDeps } = await import("./runner-deps.js");
38
40
  const runner = createDevRunner(defaultDevRunnerDeps(configFile));
package/src/dev/runner.ts CHANGED
@@ -2,6 +2,7 @@ import type { ApplicationInstance } from "@gtkx/runtime/internal";
2
2
  import type { InlineConfig, Plugin } from "vite";
3
3
  import { error, warn } from "@gtkx/utils";
4
4
  import { isCatalogSource } from "../i18n/catalogs.js";
5
+ import { hasUnstagedFontImport } from "../internal/font-staging.js";
5
6
  import { loadModuleExclusively, withExclusiveLoad } from "../internal/module-loads.js";
6
7
  import { createChangeQueue, type WatchedChange } from "./change-queue.js";
7
8
  import { createFailureTracker, type FailureTracker } from "./failure-tracker.js";
@@ -371,6 +372,11 @@ const restartForServerConfig = async (session: DevSession, changedPath: string):
371
372
  await requestRestart(session);
372
373
  };
373
374
 
375
+ const restartForFont = async (session: DevSession, changedPath: string): Promise<void> => {
376
+ session.deps.log(`Font import added: ${changedPath}`);
377
+ await requestRestart(session);
378
+ };
379
+
374
380
  const restartForCatalog = async (session: DevSession, changedPath: string): Promise<void> => {
375
381
  if (session.deps.hasWrittenCatalog(changedPath)) {
376
382
  return;
@@ -380,20 +386,32 @@ const restartForCatalog = async (session: DevSession, changedPath: string): Prom
380
386
  await requestRestart(session);
381
387
  };
382
388
 
383
- const applyChange = async (session: DevSession, change: WatchedChange): Promise<void> => {
384
- if (session.controller.isShuttingDown()) {
385
- return;
386
- }
389
+ const didRestartForChange = async (session: DevSession, change: WatchedChange): Promise<boolean> => {
390
+ const { root } = session.server.config;
387
391
 
388
- if (isCatalogSource(session.server.config.root, change.path)) {
392
+ if (isCatalogSource(root, change.path)) {
389
393
  await restartForCatalog(session, change.path);
390
394
 
391
- return;
395
+ return true;
392
396
  }
393
397
 
394
398
  if (isServerConfigFile(session.server.config, change.path)) {
395
399
  await restartForServerConfig(session, change.path);
396
400
 
401
+ return true;
402
+ }
403
+
404
+ if (change.event !== "unlink" && hasUnstagedFontImport(root, change.path)) {
405
+ await restartForFont(session, change.path);
406
+
407
+ return true;
408
+ }
409
+
410
+ return false;
411
+ };
412
+
413
+ const applyChange = async (session: DevSession, change: WatchedChange): Promise<void> => {
414
+ if (session.controller.isShuttingDown() || await didRestartForChange(session, change)) {
397
415
  return;
398
416
  }
399
417
 
@@ -18,9 +18,13 @@ const TOP_LEVEL_HEADING = /^# /m;
18
18
  const rulesBody = (heading: string): string[] => [
19
19
  `${heading} GTKX`,
20
20
  "",
21
- "This is not the GTK you have seen before. Most GTK code in your training data is C, PyGObject, Vala or " +
22
- "GJS, and almost none of it is valid here. Check the rules below against what you are about to write.",
21
+ "GTKX applications are Adwaita-first, with GTK4 underneath. Most GNOME UI code in your training data is C, " +
22
+ "PyGObject, Vala or GJS, and almost none of it is valid here. Check the rules below against what you are " +
23
+ "about to write.",
23
24
  "",
25
+ "- Start application shells with `AdwApplication` and `AdwApplicationWindow`. Freeform windows normally use " +
26
+ "`AdwToolbarView` and `AdwHeaderBar`; navigation containers own their Adwaita chrome. Prefer Adwaita for " +
27
+ "structure, navigation, dialogs, rows, and adaptive patterns; use GTK4 for lower-level primitives.",
24
28
  "- Children are JSX, never `.append()`, `pack_start()`, `set_child()` or `add()`.",
25
29
  '- Signals are props: `onClicked`, not `widget.connect("clicked", ...)`.',
26
30
  "- Props are camelCase: `marginTop`, not `margin-top` or `margin_top`.",
@@ -15,6 +15,10 @@ const prependBanner = (options: Rollup.OutputOptions, banner: Banner): Rollup.Ou
15
15
  const prefix = await renderBanner(banner, chunk);
16
16
  const suffix = existing === undefined ? "" : await renderBanner(existing, chunk);
17
17
 
18
+ if (prefix.length === 0) {
19
+ return suffix;
20
+ }
21
+
18
22
  return suffix.length === 0 ? prefix : `${prefix}\n${suffix}`;
19
23
  },
20
24
  };
@@ -0,0 +1,15 @@
1
+ import { createHash } from "node:crypto";
2
+ import { basename, extname } from "node:path";
3
+
4
+ const FONTS_DIR = "fonts";
5
+ const DIGEST_LENGTH = 8;
6
+
7
+ const fontFileName = (sourcePath: string, content: Buffer): string => {
8
+ const extension = extname(sourcePath).toLowerCase();
9
+ const stem = basename(sourcePath, extname(sourcePath));
10
+ const digest = createHash("sha1").update(content).digest("hex").slice(0, DIGEST_LENGTH);
11
+
12
+ return `${stem}-${digest}${extension}`;
13
+ };
14
+
15
+ export { fontFileName, FONTS_DIR };
@@ -0,0 +1,129 @@
1
+ import { sortStrings } from "@gtkx/utils";
2
+ import { lstatSync, mkdirSync, readFileSync, symlinkSync } from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import { dirname, isAbsolute, join, resolve } from "node:path";
5
+ import { parseFontSpecifier } from "../vite-plugins/asset-specifier.js";
6
+ import { fontFileName, FONTS_DIR } from "./font-path.js";
7
+ import { discoverSourceImports, type SourceImport, sourceLanguage } from "./source-imports.js";
8
+ import { createRetainedStagingDir } from "./staging-dir.js";
9
+
10
+ type FontStagingStatus = "absent" | "staged" | "unmanaged";
11
+
12
+ const FONT_DIR_ENV = "GTKX_DEV_FONT_DIR";
13
+ const FONT_QUERY = "?font";
14
+ const fontStaging = createRetainedStagingDir("fonts");
15
+
16
+ const isRelativeImport = (source: string): boolean => source.startsWith("./") || source.startsWith("../");
17
+
18
+ const existingPath = (path: string): string | null =>
19
+ lstatSync(path, { throwIfNoEntry: false }) === undefined ? null : path;
20
+
21
+ const resolveRelativeImport = (importer: string, assetSource: string): string | null =>
22
+ existingPath(resolve(dirname(importer), assetSource));
23
+
24
+ const resolveAbsoluteImport = (root: string, assetSource: string): string | null =>
25
+ existingPath(assetSource) ?? existingPath(join(root, assetSource));
26
+
27
+ const resolvePackageImport = (importer: string, assetSource: string): string | null => {
28
+ try {
29
+ return createRequire(importer).resolve(assetSource);
30
+ } catch {
31
+ return null;
32
+ }
33
+ };
34
+
35
+ const resolveFontImport = (root: string, importer: string, assetSource: string): string | null => {
36
+ if (isRelativeImport(assetSource)) {
37
+ return resolveRelativeImport(importer, assetSource);
38
+ }
39
+
40
+ if (isAbsolute(assetSource)) {
41
+ return resolveAbsoluteImport(root, assetSource);
42
+ }
43
+
44
+ return resolvePackageImport(importer, assetSource);
45
+ };
46
+
47
+ const fontFileFor = (root: string, { importer, source }: SourceImport): string | null => {
48
+ const specifier = parseFontSpecifier(source);
49
+
50
+ return specifier === null ? null : resolveFontImport(root, importer, specifier.assetSource);
51
+ };
52
+
53
+ const projectFontFiles = (root: string): string[] => {
54
+ const files = discoverSourceImports(root)
55
+ .map((entry) => fontFileFor(root, entry))
56
+ .filter((path): path is string => path !== null);
57
+
58
+ return sortStrings(new Set(files));
59
+ };
60
+
61
+ const stagedPath = (fontsDir: string, sourcePath: string, content: Buffer): string =>
62
+ join(fontsDir, fontFileName(sourcePath, content));
63
+
64
+ const stageFont = (fontsDir: string, sourcePath: string): void => {
65
+ const target = stagedPath(fontsDir, sourcePath, readFileSync(sourcePath));
66
+
67
+ if (existingPath(target) !== null) {
68
+ return;
69
+ }
70
+
71
+ symlinkSync(sourcePath, target, "file");
72
+ };
73
+
74
+ const stagedFontsDir = (): string | null => {
75
+ const shareDir = process.env[FONT_DIR_ENV];
76
+
77
+ return shareDir === undefined || shareDir.length === 0 ? null : join(shareDir, FONTS_DIR);
78
+ };
79
+
80
+ const stagedFontStatus = (sourcePath: string, content: Buffer): FontStagingStatus => {
81
+ const fontsDir = stagedFontsDir();
82
+
83
+ if (fontsDir === null) {
84
+ return "unmanaged";
85
+ }
86
+
87
+ return existingPath(stagedPath(fontsDir, sourcePath, content)) === null ? "absent" : "staged";
88
+ };
89
+
90
+ const hasFontImportMention = (filePath: string): boolean => {
91
+ if (sourceLanguage(filePath) === undefined) {
92
+ return false;
93
+ }
94
+
95
+ try {
96
+ return readFileSync(filePath, "utf8").includes(FONT_QUERY);
97
+ } catch {
98
+ return false;
99
+ }
100
+ };
101
+
102
+ const isUnstagedFont = (fontsDir: string, sourcePath: string): boolean =>
103
+ existingPath(stagedPath(fontsDir, sourcePath, readFileSync(sourcePath))) === null;
104
+
105
+ const hasUnstagedFontImport = (root: string, filePath: string): boolean => {
106
+ const fontsDir = stagedFontsDir();
107
+
108
+ if (fontsDir === null || !hasFontImportMention(filePath)) {
109
+ return false;
110
+ }
111
+
112
+ return projectFontFiles(root).some((sourcePath) => isUnstagedFont(fontsDir, sourcePath));
113
+ };
114
+
115
+ const stageProjectFonts = (root: string): string => {
116
+ const shareDir = fontStaging.retain();
117
+ const fontsDir = join(shareDir, FONTS_DIR);
118
+ mkdirSync(fontsDir, { recursive: true });
119
+
120
+ for (const sourcePath of projectFontFiles(root)) {
121
+ stageFont(fontsDir, sourcePath);
122
+ }
123
+
124
+ process.env[FONT_DIR_ENV] = shareDir;
125
+
126
+ return shareDir;
127
+ };
128
+
129
+ export { hasUnstagedFontImport, stagedFontStatus, stageProjectFonts };
@@ -1,6 +1,6 @@
1
1
  import type { ESTree, ParseResult, ParserOptions } from "vite";
2
2
  import { sortStringsBy } from "@gtkx/utils";
3
- import { globSync, readFileSync } from "node:fs";
3
+ import { existsSync, globSync, readFileSync } from "node:fs";
4
4
  import { extname, join } from "node:path";
5
5
  import { parseSync, Visitor } from "vite";
6
6
 
@@ -29,11 +29,19 @@ const sourceLanguage = (path: string): SourceLanguage | undefined => {
29
29
  return SOURCE_LANGUAGES.get(extname(path).toLowerCase());
30
30
  };
31
31
 
32
+ const SOURCE_DIR = "src";
33
+
34
+ const sourceDirFor = (root: string): string => {
35
+ const sourceDir = join(root, SOURCE_DIR);
36
+
37
+ return existsSync(sourceDir) ? sourceDir : root;
38
+ };
39
+
32
40
  const discoverSourceFiles = (dir: string): string[] =>
33
41
  sortStringsBy(
34
42
  globSync("**/{*,.*}", {
35
43
  cwd: dir,
36
- exclude: ["**/{build,coverage,dist,node_modules,out-tsc}/**", "**/.gtkx-output-*/**"],
44
+ exclude: ["**/{build,coverage,dist,node_modules,out-tsc}/**", "**/.gtkx-output-*/**", "**/.git/**"],
37
45
  withFileTypes: true,
38
46
  })
39
47
  .filter((entry) => entry.isFile())
@@ -132,4 +140,11 @@ const discoverSourceImports = (dir: string): SourceImport[] => {
132
140
  return sortStringsBy(imports, importKey);
133
141
  };
134
142
 
135
- export { discoverSourceFiles, discoverSourceImports, parseRuntimeImportsIn, type SourceImport, sourceLanguage };
143
+ export {
144
+ discoverSourceFiles,
145
+ discoverSourceImports,
146
+ parseRuntimeImportsIn,
147
+ sourceDirFor,
148
+ sourceLanguage,
149
+ type SourceImport,
150
+ };
@@ -0,0 +1,13 @@
1
+ import type { Rollup } from "vite";
2
+ import { outputRootUrlExpression } from "./banner.js";
3
+ import { XDG_DATA_DIRS_DEFAULT } from "./xdg-data-dirs.js";
4
+
5
+ const xdgDataDirsBanner = (chunk: Rollup.RenderedChunk): string => [
6
+ "globalThis.process.env.XDG_DATA_DIRS = (() => {",
7
+ ` const root = ${outputRootUrlExpression(chunk)};`,
8
+ ` const dirs = globalThis.process.env.XDG_DATA_DIRS || ${JSON.stringify(XDG_DATA_DIRS_DEFAULT)};`,
9
+ " return dirs.split(\":\").includes(root) ? dirs : `${root}:${dirs}`;",
10
+ "})();",
11
+ ].join("\n");
12
+
13
+ export { xdgDataDirsBanner };
@@ -10,4 +10,4 @@ const prependXdgDataDir = (dir: string, existing: string | undefined): string =>
10
10
  return `${dir}:${base}`;
11
11
  };
12
12
 
13
- export { prependXdgDataDir };
13
+ export { prependXdgDataDir, XDG_DATA_DIRS_DEFAULT };
@@ -12,7 +12,7 @@ import {
12
12
  import { tmpdir } from "node:os";
13
13
  import { basename, dirname, join, relative, resolve } from "node:path";
14
14
  import { I18N_TYPES_FILENAME, i18nTypesPath } from "../i18n/types.js";
15
- import { discoverSourceImports, type SourceImport } from "../internal/source-imports.js";
15
+ import { discoverSourceImports, sourceDirFor, type SourceImport } from "../internal/source-imports.js";
16
16
  import { removeTempDir } from "../internal/staging-dir.js";
17
17
  import {
18
18
  isBareRelativeAsset,
@@ -30,7 +30,6 @@ type SchemaEnvResult = {
30
30
 
31
31
  const SCHEMA_SUFFIX = ".gschema.xml";
32
32
  const STAGED_NAME_LENGTH = 16;
33
- const SOURCE_DIR = "src";
34
33
  const RESOURCE_QUERY = "?resource=";
35
34
  const ICON_QUERY = "?icon=";
36
35
 
@@ -69,12 +68,6 @@ const projectRelativeSchemaPath = (root: string, filePath: string): string | nul
69
68
 
70
69
  const getRelativeModuleSpecifier = (filePath: string): string => `*/${basename(filePath)}`;
71
70
 
72
- const sourceDirFor = (root: string): string => {
73
- const sourceDir = join(root, SOURCE_DIR);
74
-
75
- return existsSync(sourceDir) ? sourceDir : root;
76
- };
77
-
78
71
  const isRelativeImport = (source: string): boolean => source.startsWith("./") || source.startsWith("../");
79
72
 
80
73
  const schemaFileFor = ({ importer, source }: SourceImport): string | null =>
@@ -18,6 +18,8 @@ const ASSET_EXTENSIONS = [
18
18
  "eot",
19
19
  "ttf",
20
20
  "otf",
21
+ "ttc",
22
+ "otc",
21
23
  "ico",
22
24
  "avif",
23
25
  "data",
@@ -5,6 +5,7 @@ import { ASSET_MENTION_RE } from "./asset-extensions.js";
5
5
  import {
6
6
  isAssetSpecifier,
7
7
  isUrlSpecifier,
8
+ parseFontSpecifier,
8
9
  parseIconSpecifier,
9
10
  parseResourceSpecifier,
10
11
  } from "./asset-specifier.js";
@@ -31,8 +32,11 @@ const RESOURCE_ADVICE =
31
32
 
32
33
  const ICON_ADVICE = "An icon asset exports only its default icon-theme name, and nothing else.";
33
34
 
35
+ const FONT_ADVICE = "A font asset exports only its default font family name, and nothing else.";
36
+
34
37
  const RELATIVE_ADVICE =
35
- "Add ?resource for a GResource, ?icon for a themed icon, or ?url for an emitted file.";
38
+ "Add ?resource for a GResource, ?icon for a themed icon, ?font for a bundled font, or ?url for an " +
39
+ "emitted file.";
36
40
 
37
41
  const boundName = (entry: BindingEntry): string | null => {
38
42
  const { name } = entry.importName;
@@ -87,6 +91,10 @@ const adviceFor = (source: string): string => {
87
91
  return ICON_ADVICE;
88
92
  }
89
93
 
94
+ if (parseFontSpecifier(source) !== null) {
95
+ return FONT_ADVICE;
96
+ }
97
+
90
98
  if (isUrlSpecifier(source)) {
91
99
  return QUERY_ADVICE;
92
100
  }
@@ -11,9 +11,14 @@ type IconSpecifier = {
11
11
  iconName: string | null;
12
12
  };
13
13
 
14
+ type FontSpecifier = {
15
+ assetSource: string;
16
+ };
17
+
14
18
  const RELATIVE_PREFIX_RE = /^\.\.?(?:\/|$)/;
15
19
  const RESOURCE_QUERY_RE = /^resource(?:=([^&#]+))?$/;
16
20
  const ICON_QUERY_RE = /^icon(?:=([^&#]+))?$/;
21
+ const FONT_QUERY_RE = /^font$/;
17
22
  const URL_QUERY_RE = /(?:^|&)url(?:&|$)/;
18
23
 
19
24
  const getQuery = (source: string): string | null => {
@@ -61,6 +66,17 @@ const parseIconSpecifier = (source: string): IconSpecifier | null => {
61
66
  return { assetSource, iconName: match[1] ?? null };
62
67
  };
63
68
 
69
+ const parseFontSpecifier = (source: string): FontSpecifier | null => {
70
+ const query = getQuery(source);
71
+ const assetSource = stripQuery(source);
72
+
73
+ if (query === null || !ASSET_RE.test(assetSource) || !FONT_QUERY_RE.test(query)) {
74
+ return null;
75
+ }
76
+
77
+ return { assetSource };
78
+ };
79
+
64
80
  const isUrlSpecifier = (source: string): boolean => {
65
81
  const query = getQuery(source);
66
82
 
@@ -71,6 +87,7 @@ export {
71
87
  isAssetSpecifier,
72
88
  isBareRelativeAsset,
73
89
  isUrlSpecifier,
90
+ parseFontSpecifier,
74
91
  parseIconSpecifier,
75
92
  parseResourceSpecifier,
76
93
  };
@@ -4,6 +4,7 @@ import { createVirtualNamespace } from "./virtual-module.js";
4
4
 
5
5
  type CssResolveContext = Parameters<typeof resolveToVirtual>[0];
6
6
  type CssResolveRequest = Parameters<typeof resolveToVirtual>[1];
7
+ type CssLoadContext = { addWatchFile: (file: string) => void };
7
8
 
8
9
  const CSS_RE = /\.css$/i;
9
10
  const INJECT_SUFFIX = "?inject";
@@ -24,13 +25,14 @@ const resolveCssId = async (ctx: CssResolveContext, request: CssResolveRequest):
24
25
  return virtualId + INJECT_SUFFIX;
25
26
  };
26
27
 
27
- const loadInjectedCss = (id: string): string | undefined => {
28
+ const loadInjectedCss = (ctx: CssLoadContext, id: string): string | undefined => {
28
29
  if (!isVirtual(id) || !id.endsWith(INJECT_SUFFIX)) {
29
30
  return;
30
31
  }
31
32
 
32
33
  const filePath = fromVirtualId(id.slice(0, -INJECT_SUFFIX.length));
33
34
  const content = readFileSync(filePath, "utf8");
35
+ ctx.addWatchFile(filePath);
34
36
 
35
37
  return ["import { injectGlobal } from \"@gtkx/css\";", `injectGlobal(${JSON.stringify(content)});`].join("\n");
36
38
  };
@@ -45,7 +47,7 @@ function gtkxCss(): Plugin {
45
47
  },
46
48
 
47
49
  load(id) {
48
- return loadInjectedCss(id);
50
+ return loadInjectedCss(this, id);
49
51
  },
50
52
  };
51
53
  }