@gtkx/cli 2.0.0-beta.7 → 2.0.0-beta.9

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 (155) hide show
  1. package/dist/commands/deploy.d.ts +4 -0
  2. package/dist/commands/deploy.d.ts.map +1 -1
  3. package/dist/commands/deploy.js +6 -0
  4. package/dist/commands/deploy.js.map +1 -1
  5. package/dist/deploy/depends.d.ts.map +1 -1
  6. package/dist/deploy/depends.js +24 -2
  7. package/dist/deploy/depends.js.map +1 -1
  8. package/dist/deploy/download.d.ts +7 -3
  9. package/dist/deploy/download.d.ts.map +1 -1
  10. package/dist/deploy/download.js +19 -15
  11. package/dist/deploy/download.js.map +1 -1
  12. package/dist/deploy/native-addon.d.ts +5 -0
  13. package/dist/deploy/native-addon.d.ts.map +1 -0
  14. package/dist/deploy/native-addon.js +134 -0
  15. package/dist/deploy/native-addon.js.map +1 -0
  16. package/dist/deploy/node-runtime/download.js +2 -2
  17. package/dist/deploy/node-runtime/download.js.map +1 -1
  18. package/dist/deploy/node-runtime/elf.d.ts +3 -1
  19. package/dist/deploy/node-runtime/elf.d.ts.map +1 -1
  20. package/dist/deploy/node-runtime/elf.js +3 -1
  21. package/dist/deploy/node-runtime/elf.js.map +1 -1
  22. package/dist/deploy/node-runtime/guard.d.ts.map +1 -1
  23. package/dist/deploy/node-runtime/guard.js +7 -14
  24. package/dist/deploy/node-runtime/guard.js.map +1 -1
  25. package/dist/deploy/node-runtime/index.d.ts +2 -1
  26. package/dist/deploy/node-runtime/index.d.ts.map +1 -1
  27. package/dist/deploy/node-runtime/index.js +29 -6
  28. package/dist/deploy/node-runtime/index.js.map +1 -1
  29. package/dist/deploy/node-runtime/sonames.d.ts +5 -0
  30. package/dist/deploy/node-runtime/sonames.d.ts.map +1 -0
  31. package/dist/deploy/node-runtime/sonames.js +13 -0
  32. package/dist/deploy/node-runtime/sonames.js.map +1 -0
  33. package/dist/deploy/payload/stage.d.ts +2 -1
  34. package/dist/deploy/payload/stage.d.ts.map +1 -1
  35. package/dist/deploy/payload/stage.js +22 -2
  36. package/dist/deploy/payload/stage.js.map +1 -1
  37. package/dist/deploy/run-deploy.d.ts +1 -0
  38. package/dist/deploy/run-deploy.d.ts.map +1 -1
  39. package/dist/deploy/run-deploy.js +139 -80
  40. package/dist/deploy/run-deploy.js.map +1 -1
  41. package/dist/deploy/settings/arch.d.ts +9 -3
  42. package/dist/deploy/settings/arch.d.ts.map +1 -1
  43. package/dist/deploy/settings/arch.js +27 -6
  44. package/dist/deploy/settings/arch.js.map +1 -1
  45. package/dist/deploy/settings/index.d.ts +2 -1
  46. package/dist/deploy/settings/index.d.ts.map +1 -1
  47. package/dist/deploy/settings/index.js +2 -1
  48. package/dist/deploy/settings/index.js.map +1 -1
  49. package/dist/deploy/settings/paths.d.ts +2 -1
  50. package/dist/deploy/settings/paths.d.ts.map +1 -1
  51. package/dist/deploy/settings/paths.js +6 -5
  52. package/dist/deploy/settings/paths.js.map +1 -1
  53. package/dist/deploy/targets/appimage.d.ts.map +1 -1
  54. package/dist/deploy/targets/appimage.js +1 -0
  55. package/dist/deploy/targets/appimage.js.map +1 -1
  56. package/dist/deploy/targets/flatpak-pnpm.js +4 -4
  57. package/dist/deploy/targets/flatpak-pnpm.js.map +1 -1
  58. package/dist/deploy/targets/flatpak.d.ts.map +1 -1
  59. package/dist/deploy/targets/flatpak.js +1 -0
  60. package/dist/deploy/targets/flatpak.js.map +1 -1
  61. package/dist/deploy/targets/nfpm-target.d.ts.map +1 -1
  62. package/dist/deploy/targets/nfpm-target.js +1 -0
  63. package/dist/deploy/targets/nfpm-target.js.map +1 -1
  64. package/dist/deploy/types.d.ts +4 -2
  65. package/dist/deploy/types.d.ts.map +1 -1
  66. package/dist/deploy/types.js.map +1 -1
  67. package/dist/deploy/vendored/appimagetool.js +2 -2
  68. package/dist/deploy/vendored/appimagetool.js.map +1 -1
  69. package/dist/deploy/vendored/nfpm.js +1 -1
  70. package/dist/deploy/vendored/nfpm.js.map +1 -1
  71. package/dist/dev/font-dir.d.ts +1 -1
  72. package/dist/dev/font-dir.d.ts.map +1 -1
  73. package/dist/dev/font-dir.js +0 -3
  74. package/dist/dev/font-dir.js.map +1 -1
  75. package/dist/dev/runner.d.ts.map +1 -1
  76. package/dist/dev/runner.js +19 -6
  77. package/dist/dev/runner.js.map +1 -1
  78. package/dist/internal/font-staging.d.ts +5 -2
  79. package/dist/internal/font-staging.d.ts.map +1 -1
  80. package/dist/internal/font-staging.js +76 -27
  81. package/dist/internal/font-staging.js.map +1 -1
  82. package/dist/internal/icon-staging.d.ts +3 -0
  83. package/dist/internal/icon-staging.d.ts.map +1 -0
  84. package/dist/internal/icon-staging.js +40 -0
  85. package/dist/internal/icon-staging.js.map +1 -0
  86. package/dist/internal/source-imports.js +1 -1
  87. package/dist/internal/source-imports.js.map +1 -1
  88. package/dist/vite-plugins/font-worker-env.d.ts.map +1 -1
  89. package/dist/vite-plugins/font-worker-env.js +2 -5
  90. package/dist/vite-plugins/font-worker-env.js.map +1 -1
  91. package/dist/vite-plugins/font.d.ts.map +1 -1
  92. package/dist/vite-plugins/font.js +16 -4
  93. package/dist/vite-plugins/font.js.map +1 -1
  94. package/dist/vite-plugins/icon-worker-env.d.ts +4 -0
  95. package/dist/vite-plugins/icon-worker-env.d.ts.map +1 -0
  96. package/dist/vite-plugins/icon-worker-env.js +23 -0
  97. package/dist/vite-plugins/icon-worker-env.js.map +1 -0
  98. package/dist/vite-plugins/icons.js +2 -2
  99. package/dist/vite-plugins/icons.js.map +1 -1
  100. package/dist/vite-plugins/react-compiler.js +2 -2
  101. package/dist/vite-plugins/react-compiler.js.map +1 -1
  102. package/dist/vite-plugins/resources.js +2 -2
  103. package/dist/vite-plugins/resources.js.map +1 -1
  104. package/dist/vite-plugins/settings-worker-env.js +2 -2
  105. package/dist/vite-plugins/settings-worker-env.js.map +1 -1
  106. package/dist/vite-plugins/store-links.d.ts.map +1 -1
  107. package/dist/vite-plugins/store-links.js +2 -1
  108. package/dist/vite-plugins/store-links.js.map +1 -1
  109. package/dist/vite-plugins/undeclared-library.js +2 -2
  110. package/dist/vite-plugins/undeclared-library.js.map +1 -1
  111. package/dist/vite-plugins/worker.d.ts.map +1 -1
  112. package/dist/vite-plugins/worker.js +21 -13
  113. package/dist/vite-plugins/worker.js.map +1 -1
  114. package/dist/vitest-plugin.d.ts +2 -2
  115. package/dist/vitest-plugin.d.ts.map +1 -1
  116. package/dist/vitest-plugin.js +4 -2
  117. package/dist/vitest-plugin.js.map +1 -1
  118. package/package.json +13 -13
  119. package/src/commands/deploy.ts +6 -0
  120. package/src/deploy/depends.ts +30 -3
  121. package/src/deploy/download.ts +33 -17
  122. package/src/deploy/native-addon.ts +183 -0
  123. package/src/deploy/node-runtime/download.ts +2 -2
  124. package/src/deploy/node-runtime/elf.ts +4 -1
  125. package/src/deploy/node-runtime/guard.ts +10 -14
  126. package/src/deploy/node-runtime/index.ts +42 -7
  127. package/src/deploy/node-runtime/sonames.ts +17 -0
  128. package/src/deploy/payload/stage.ts +34 -2
  129. package/src/deploy/run-deploy.ts +209 -94
  130. package/src/deploy/settings/arch.ts +40 -8
  131. package/src/deploy/settings/index.ts +4 -1
  132. package/src/deploy/settings/paths.ts +8 -6
  133. package/src/deploy/targets/appimage.ts +1 -0
  134. package/src/deploy/targets/flatpak-pnpm.ts +4 -4
  135. package/src/deploy/targets/flatpak.ts +1 -0
  136. package/src/deploy/targets/nfpm-target.ts +1 -0
  137. package/src/deploy/types.ts +4 -1
  138. package/src/deploy/vendored/appimagetool.ts +2 -2
  139. package/src/deploy/vendored/nfpm.ts +1 -1
  140. package/src/dev/font-dir.ts +1 -6
  141. package/src/dev/runner.ts +24 -6
  142. package/src/internal/font-staging.ts +99 -27
  143. package/src/internal/icon-staging.ts +58 -0
  144. package/src/internal/source-imports.ts +1 -1
  145. package/src/vite-plugins/font-worker-env.ts +2 -6
  146. package/src/vite-plugins/font.ts +21 -3
  147. package/src/vite-plugins/icon-worker-env.ts +36 -0
  148. package/src/vite-plugins/icons.ts +2 -2
  149. package/src/vite-plugins/react-compiler.ts +2 -2
  150. package/src/vite-plugins/resources.ts +2 -2
  151. package/src/vite-plugins/settings-worker-env.ts +2 -2
  152. package/src/vite-plugins/store-links.ts +2 -1
  153. package/src/vite-plugins/undeclared-library.ts +2 -2
  154. package/src/vite-plugins/worker.ts +31 -15
  155. package/src/vitest-plugin.ts +4 -2
@@ -1,13 +1,8 @@
1
1
  import { stageProjectFonts } from "../internal/font-staging.js";
2
2
  import { prependXdgDataDir } from "../internal/xdg-data-dirs.js";
3
3
 
4
- const prepareDevFontDir = (root: string): string | null => {
4
+ const prepareDevFontDir = (root: string): string => {
5
5
  const shareDir = stageProjectFonts(root);
6
-
7
- if (shareDir === null) {
8
- return null;
9
- }
10
-
11
6
  process.env.XDG_DATA_DIRS = prependXdgDataDir(shareDir, process.env.XDG_DATA_DIRS);
12
7
 
13
8
  return shareDir;
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
 
@@ -1,57 +1,129 @@
1
1
  import { sortStrings } from "@gtkx/utils";
2
- import { mkdirSync, mkdtempSync, readFileSync, symlinkSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { dirname, join, resolve } from "node:path";
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
5
  import { parseFontSpecifier } from "../vite-plugins/asset-specifier.js";
6
6
  import { fontFileName, FONTS_DIR } from "./font-path.js";
7
- import { discoverSourceImports, sourceDirFor, type SourceImport } from "./source-imports.js";
8
- import { removeTempDir } from "./staging-dir.js";
7
+ import { discoverSourceImports, type SourceImport, sourceLanguage } from "./source-imports.js";
8
+ import { createRetainedStagingDir } from "./staging-dir.js";
9
9
 
10
- const FONT_STAGING_PREFIX = "gtkx-fonts-";
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");
11
15
 
12
16
  const isRelativeImport = (source: string): boolean => source.startsWith("./") || source.startsWith("../");
13
17
 
14
- const fontFileFor = ({ importer, source }: SourceImport): string | null => {
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 => {
15
48
  const specifier = parseFontSpecifier(source);
16
49
 
17
- return specifier === null || !isRelativeImport(source)
18
- ? null
19
- : resolve(dirname(importer), specifier.assetSource);
50
+ return specifier === null ? null : resolveFontImport(root, importer, specifier.assetSource);
20
51
  };
21
52
 
22
- const findImportedFontFiles = (imports: SourceImport[]): string[] => {
23
- const files = imports
24
- .map((entry) => fontFileFor(entry))
53
+ const projectFontFiles = (root: string): string[] => {
54
+ const files = discoverSourceImports(root)
55
+ .map((entry) => fontFileFor(root, entry))
25
56
  .filter((path): path is string => path !== null);
26
57
 
27
58
  return sortStrings(new Set(files));
28
59
  };
29
60
 
61
+ const stagedPath = (fontsDir: string, sourcePath: string, content: Buffer): string =>
62
+ join(fontsDir, fontFileName(sourcePath, content));
63
+
30
64
  const stageFont = (fontsDir: string, sourcePath: string): void => {
31
- const fileName = fontFileName(sourcePath, readFileSync(sourcePath));
65
+ const target = stagedPath(fontsDir, sourcePath, readFileSync(sourcePath));
66
+
67
+ if (existingPath(target) !== null) {
68
+ return;
69
+ }
32
70
 
33
- symlinkSync(sourcePath, join(fontsDir, fileName), "file");
71
+ symlinkSync(sourcePath, target, "file");
34
72
  };
35
73
 
36
- const stageProjectFonts = (root: string): string | null => {
37
- const fontFiles = findImportedFontFiles(discoverSourceImports(sourceDirFor(root)));
74
+ const stagedFontsDir = (): string | null => {
75
+ const shareDir = process.env[FONT_DIR_ENV];
38
76
 
39
- if (fontFiles.length === 0) {
40
- return null;
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";
41
85
  }
42
86
 
43
- const shareDir = mkdtempSync(join(tmpdir(), FONT_STAGING_PREFIX));
44
- mkdirSync(join(shareDir, FONTS_DIR));
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 });
45
119
 
46
- for (const sourcePath of fontFiles) {
47
- stageFont(join(shareDir, FONTS_DIR), sourcePath);
120
+ for (const sourcePath of projectFontFiles(root)) {
121
+ stageFont(fontsDir, sourcePath);
48
122
  }
49
123
 
50
- process.once("exit", () => {
51
- removeTempDir(shareDir);
52
- });
124
+ process.env[FONT_DIR_ENV] = shareDir;
53
125
 
54
126
  return shareDir;
55
127
  };
56
128
 
57
- export { stageProjectFonts };
129
+ export { hasUnstagedFontImport, stagedFontStatus, stageProjectFonts };
@@ -0,0 +1,58 @@
1
+ import { mkdirSync, rmSync, symlinkSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { relativeIconPath, resolveApplicationIcon, type ResolvedApplicationIcon } from "./icon-path.js";
4
+ import { createRetainedStagingDir, type RetainedStagingDir } from "./staging-dir.js";
5
+
6
+ const ICONS_DIR = "icons";
7
+ const stagingDirs: Map<string, RetainedStagingDir> = new Map();
8
+
9
+ const stagingFor = (root: string): RetainedStagingDir => {
10
+ const existing = stagingDirs.get(root);
11
+
12
+ if (existing !== undefined) {
13
+ return existing;
14
+ }
15
+
16
+ const staging = createRetainedStagingDir("icons");
17
+ stagingDirs.set(root, staging);
18
+
19
+ return staging;
20
+ };
21
+
22
+ const resetIconsDir = (shareDir: string): string => {
23
+ const iconsDir = join(shareDir, ICONS_DIR);
24
+ rmSync(iconsDir, { recursive: true, force: true });
25
+
26
+ return iconsDir;
27
+ };
28
+
29
+ const stageIconSource = (
30
+ iconsDir: string,
31
+ applicationId: string,
32
+ source: Exclude<ResolvedApplicationIcon, { kind: "none" }>,
33
+ ): void => {
34
+ if (source.kind === "theme") {
35
+ symlinkSync(source.path, iconsDir, "dir");
36
+
37
+ return;
38
+ }
39
+
40
+ const target = join(iconsDir, relativeIconPath(applicationId, source.path));
41
+ mkdirSync(dirname(target), { recursive: true });
42
+ symlinkSync(source.path, target, "file");
43
+ };
44
+
45
+ const stageProjectIcons = (root: string, applicationId: string, configured: string | undefined): string | null => {
46
+ const source = resolveApplicationIcon(root, applicationId, configured);
47
+
48
+ if (source.kind === "none") {
49
+ return null;
50
+ }
51
+
52
+ const shareDir = stagingFor(root).retain();
53
+ stageIconSource(resetIconsDir(shareDir), applicationId, source);
54
+
55
+ return shareDir;
56
+ };
57
+
58
+ export { stageProjectIcons };
@@ -41,7 +41,7 @@ const discoverSourceFiles = (dir: string): string[] =>
41
41
  sortStringsBy(
42
42
  globSync("**/{*,.*}", {
43
43
  cwd: dir,
44
- exclude: ["**/{build,coverage,dist,node_modules,out-tsc}/**", "**/.gtkx-output-*/**"],
44
+ exclude: ["**/{build,coverage,dist,node_modules,out-tsc}/**", "**/.gtkx-output-*/**", "**/.git/**"],
45
45
  withFileTypes: true,
46
46
  })
47
47
  .filter((entry) => entry.isFile())
@@ -1,6 +1,6 @@
1
1
  import type { UserConfig } from "vite";
2
2
  import type { Plugin } from "vitest/config";
3
- import { createConfigLoader } from "@gtkx/config/internal";
3
+ import { createConfigLoader, viteProjectRoot } from "@gtkx/config/internal";
4
4
  import { stageProjectFonts } from "../internal/font-staging.js";
5
5
  import { prependXdgDataDir } from "../internal/xdg-data-dirs.js";
6
6
 
@@ -12,13 +12,9 @@ function gtkxFontWorkerEnv(configFile?: string): Plugin {
12
12
  enforce: "pre",
13
13
 
14
14
  async config(config: UserConfig) {
15
- const loaded = await loadConfig.load(config.root ?? process.cwd());
15
+ const loaded = await loadConfig.load(viteProjectRoot(config));
16
16
  const shareDir = stageProjectFonts(loaded.root);
17
17
 
18
- if (shareDir === null) {
19
- return;
20
- }
21
-
22
18
  return {
23
19
  test: { env: { XDG_DATA_DIRS: prependXdgDataDir(shareDir, process.env.XDG_DATA_DIRS) } },
24
20
  };
@@ -4,6 +4,7 @@ import { parseSync } from "vite";
4
4
  import type { AssetEmitter } from "./asset-emitter.js";
5
5
  import { prependBanner } from "../internal/banner.js";
6
6
  import { fontFileName, FONTS_DIR } from "../internal/font-path.js";
7
+ import { stagedFontStatus } from "../internal/font-staging.js";
7
8
  import { sourceLanguage } from "../internal/source-imports.js";
8
9
  import { xdgDataDirsBanner } from "../internal/xdg-banner.js";
9
10
  import { parseFontSpecifier } from "./asset-specifier.js";
@@ -13,7 +14,9 @@ import { createVirtualNamespace } from "./virtual-module.js";
13
14
 
14
15
  type PluginState = {
15
16
  isBuild: boolean;
17
+ root: string;
16
18
  emitted: Set<string>;
19
+ importers: Map<string, string>;
17
20
  };
18
21
 
19
22
  type ResolveContext = Parameters<typeof resolveToVirtual>[0];
@@ -36,8 +39,14 @@ const unreadableFontError = (filePath: string): string =>
36
39
  `Cannot read a font family name from ${filePath}; ?font expects a TrueType, OpenType, ` +
37
40
  "TrueType Collection, WOFF, or WOFF2 font";
38
41
 
42
+ const unstagedFontError = (filePath: string, importer: string): string =>
43
+ `${filePath} was not staged before the toolkit started, so ${importer} would resolve its family to a ` +
44
+ "fallback font. Fonts are staged from ?font imports found in source files under the project root; move " +
45
+ "the import there, or restart to pick it up.";
46
+
39
47
  const resolveFontId = async (
40
48
  ctx: ResolveContext,
49
+ state: PluginState,
41
50
  request: ResolveRequest,
42
51
  ): Promise<RetainedFontModuleId | undefined> => {
43
52
  if (parseFontSpecifier(request.source) === null) {
@@ -46,7 +55,13 @@ const resolveFontId = async (
46
55
 
47
56
  const virtualId = await resolveToVirtual(ctx, request);
48
57
 
49
- return virtualId === undefined ? undefined : { id: virtualId, moduleSideEffects: true };
58
+ if (virtualId === undefined) {
59
+ return undefined;
60
+ }
61
+
62
+ state.importers.set(virtualId, request.importer ?? state.root);
63
+
64
+ return { id: virtualId, moduleSideEffects: true };
50
65
  };
51
66
 
52
67
  const emitFont = (ctx: LoadContext, state: PluginState, filePath: string, content: Buffer): void => {
@@ -77,6 +92,8 @@ const loadFontModule = (ctx: LoadContext, state: PluginState, id: string): strin
77
92
 
78
93
  if (state.isBuild) {
79
94
  emitFont(ctx, state, filePath, content);
95
+ } else if (stagedFontStatus(filePath, content) === "absent") {
96
+ return ctx.error(unstagedFontError(filePath, state.importers.get(id) ?? state.root));
80
97
  }
81
98
 
82
99
  return `export default ${JSON.stringify(family)};`;
@@ -112,7 +129,7 @@ const fontBanner = (state: PluginState) => (chunk: Rollup.RenderedChunk): string
112
129
  state.emitted.size === 0 ? "" : xdgDataDirsBanner(chunk);
113
130
 
114
131
  function gtkxFont(): Plugin {
115
- const state: PluginState = { isBuild: false, emitted: new Set() };
132
+ const state: PluginState = { isBuild: false, root: "", emitted: new Set(), importers: new Map() };
116
133
 
117
134
  return {
118
135
  name: "gtkx:font",
@@ -120,10 +137,11 @@ function gtkxFont(): Plugin {
120
137
 
121
138
  configResolved(config) {
122
139
  state.isBuild = config.command === "build";
140
+ state.root = config.root;
123
141
  },
124
142
 
125
143
  resolveId(source, importer, options) {
126
- return resolveFontId(this, { source, importer, options });
144
+ return resolveFontId(this, state, { source, importer, options });
127
145
  },
128
146
 
129
147
  load(id) {
@@ -0,0 +1,36 @@
1
+ import type { UserConfig } from "vite";
2
+ import type { Plugin } from "vitest/config";
3
+ import { createConfigLoader, viteProjectRoot } from "@gtkx/config/internal";
4
+ import { stageProjectIcons } from "../internal/icon-staging.js";
5
+ import { prependXdgDataDir } from "../internal/xdg-data-dirs.js";
6
+
7
+ function gtkxIconWorkerEnv(configFile?: string): Plugin {
8
+ const loadConfig = createConfigLoader({ configFile });
9
+
10
+ return {
11
+ name: "gtkx:icon-worker-env",
12
+ enforce: "pre",
13
+
14
+ async config(config: UserConfig) {
15
+ const loaded = await loadConfig.load(viteProjectRoot(config));
16
+
17
+ const shareDir = stageProjectIcons(
18
+ loaded.root,
19
+ loaded.config.applicationId,
20
+ loaded.config.applicationIcon,
21
+ );
22
+
23
+ if (shareDir === null) {
24
+ return;
25
+ }
26
+
27
+ const existing = config.test?.env?.XDG_DATA_DIRS ?? process.env.XDG_DATA_DIRS;
28
+
29
+ return {
30
+ test: { env: { XDG_DATA_DIRS: prependXdgDataDir(shareDir, existing) } },
31
+ };
32
+ },
33
+ };
34
+ }
35
+
36
+ export { gtkxIconWorkerEnv };
@@ -1,6 +1,6 @@
1
1
  import type { ConfigLoader } from "@gtkx/config";
2
2
  import type { Plugin, UserConfig } from "vite";
3
- import { createConfigLoader } from "@gtkx/config/internal";
3
+ import { createConfigLoader, viteProjectRoot } from "@gtkx/config/internal";
4
4
  import { info } from "@gtkx/utils";
5
5
  import { readFileSync } from "node:fs";
6
6
  import { join } from "node:path";
@@ -37,7 +37,7 @@ const findIconFiles = (state: PluginState): ListedFile[] => {
37
37
  };
38
38
 
39
39
  const applyUserConfig = async (state: PluginState, config: UserConfig, loadConfig: ConfigLoader): Promise<void> => {
40
- const { config: gtkxConfig, root } = await loadConfig.load(config.root ?? process.cwd());
40
+ const { config: gtkxConfig, root } = await loadConfig.load(viteProjectRoot(config));
41
41
  state.applicationId = gtkxConfig.applicationId;
42
42
  state.source = resolveApplicationIcon(root, gtkxConfig.applicationId, gtkxConfig.applicationIcon);
43
43
  };
@@ -3,7 +3,7 @@ import type { ResolvedReactCompilerOptions } from "@gtkx/config/internal";
3
3
  import type { Plugin, ResolvedConfig, UserConfig } from "vite";
4
4
  import { transformAsync } from "@babel/core";
5
5
  import babelPresetTypescriptNs from "@babel/preset-typescript";
6
- import { createConfigLoader } from "@gtkx/config/internal";
6
+ import { createConfigLoader, viteProjectRoot } from "@gtkx/config/internal";
7
7
  import babelPluginReactCompilerNs from "babel-plugin-react-compiler";
8
8
 
9
9
  type ReactCompilerState = {
@@ -62,7 +62,7 @@ function gtkxReactCompiler(loadConfig: ConfigLoader = createConfigLoader()): Plu
62
62
  enforce: "pre",
63
63
 
64
64
  async config(config: UserConfig) {
65
- const resolved = await loadConfig.resolve(config.root ?? process.cwd());
65
+ const resolved = await loadConfig.resolve(viteProjectRoot(config));
66
66
  state.options = resolved.reactCompiler;
67
67
  },
68
68
 
@@ -1,6 +1,6 @@
1
1
  import type { ConfigLoader } from "@gtkx/config";
2
2
  import type { ModuleNode, Plugin, ResolvedConfig, Rolldown, UserConfig, ViteDevServer } from "vite";
3
- import { createConfigLoader, resourceBasePath } from "@gtkx/config/internal";
3
+ import { createConfigLoader, resourceBasePath, viteProjectRoot } from "@gtkx/config/internal";
4
4
  import { error, info, isPathInside, isRecord, sortStrings, toPosixPath } from "@gtkx/utils";
5
5
  import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
6
6
  import { basename, dirname, extname, isAbsolute, join, posix, relative, sep } from "node:path";
@@ -1283,7 +1283,7 @@ const reconcileImporter = async (ctx: ResolveContext, state: PluginState, id: st
1283
1283
  };
1284
1284
 
1285
1285
  const resolveResourceConfig = async (state: PluginState, config: UserConfig, loadConfig: ConfigLoader) => {
1286
- const loaded = await loadConfig.load(config.root ?? process.cwd());
1286
+ const loaded = await loadConfig.load(viteProjectRoot(config));
1287
1287
  state.prefix = resourceBasePath(loaded.config.applicationId);
1288
1288
  state.root = loaded.root;
1289
1289
 
@@ -1,6 +1,6 @@
1
1
  import type { UserConfig } from "vite";
2
2
  import type { Plugin } from "vitest/config";
3
- import { createConfigLoader } from "@gtkx/config/internal";
3
+ import { createConfigLoader, viteProjectRoot } from "@gtkx/config/internal";
4
4
  import { prependSchemaDir, stageAndCompileProjectSchemas } from "../settings/schema.js";
5
5
 
6
6
  function gtkxSettingsWorkerEnv(configFile?: string): Plugin {
@@ -11,7 +11,7 @@ function gtkxSettingsWorkerEnv(configFile?: string): Plugin {
11
11
  enforce: "pre",
12
12
 
13
13
  async config(config: UserConfig) {
14
- const loaded = await loadConfig.load(config.root ?? process.cwd());
14
+ const loaded = await loadConfig.load(viteProjectRoot(config));
15
15
  const dir = stageAndCompileProjectSchemas(loaded.root);
16
16
 
17
17
  if (dir === null) {
@@ -1,5 +1,6 @@
1
1
  import type { Plugin, UserConfig } from "vite";
2
2
  import { ensureStoreLinks } from "@gtkx/codegen/internal";
3
+ import { viteProjectRoot } from "@gtkx/config/internal";
3
4
 
4
5
  const GENERATED_MODULE_PREFIX = /^@gtkx\/(?:gi\/|jsx(?:\/|$))/;
5
6
 
@@ -11,7 +12,7 @@ function gtkxStoreLinks(): Plugin {
11
12
  enforce: "pre",
12
13
 
13
14
  config(config: UserConfig) {
14
- state.root = config.root ?? process.cwd();
15
+ state.root = viteProjectRoot(config);
15
16
  },
16
17
 
17
18
  resolveId(source) {
@@ -2,7 +2,7 @@ import type { StoreOptions } from "@gtkx/codegen";
2
2
  import type { ConfigLoader } from "@gtkx/config";
3
3
  import type { Plugin, UserConfig } from "vite";
4
4
  import { discoverGirNamespaces, resolveGirPath, resolveStore } from "@gtkx/codegen";
5
- import { createConfigLoader } from "@gtkx/config/internal";
5
+ import { createConfigLoader, viteProjectRoot } from "@gtkx/config/internal";
6
6
  import { existsSync, readFileSync } from "node:fs";
7
7
  import { join } from "node:path";
8
8
 
@@ -129,7 +129,7 @@ function gtkxUndeclaredLibrary(loadConfig: ConfigLoader = createConfigLoader()):
129
129
  enforce: "pre",
130
130
 
131
131
  config(config: UserConfig) {
132
- state.root = config.root ?? process.cwd();
132
+ state.root = viteProjectRoot(config);
133
133
  },
134
134
 
135
135
  async resolveId(source, importer, options) {
@@ -7,12 +7,17 @@ import { stripQuery } from "./strip-query.js";
7
7
  type WorkerReplacement = {
8
8
  start: number;
9
9
  end: number;
10
- fileName: string;
10
+ referenceId: string;
11
+ };
12
+
13
+ type WorkerReferences = {
14
+ byModule: Map<string, string>;
15
+ claimed: Set<string>;
11
16
  };
12
17
 
13
18
  type EmitContext = {
14
19
  context: Rollup.PluginContext;
15
- emitted: Map<string, string>;
20
+ references: WorkerReferences;
16
21
  };
17
22
 
18
23
  type SourceRange = {
@@ -83,10 +88,12 @@ const isWorkerConstruction = (code: string, matchIndex: number): boolean =>
83
88
 
84
89
  const hasWorkerCandidate = (code: string): boolean => code.includes("import.meta.url") && code.includes("Worker");
85
90
 
86
- const workerUrlExpression = (fileName: string): string => {
87
- const specifier = JSON.stringify(`./${fileName}`);
91
+ const workerUrlReference = (referenceId: string): string => `import.meta.ROLLUP_FILE_URL_${referenceId}`;
92
+
93
+ const workerUrlExpression = (relativePath: string): string => {
94
+ const specifier = relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
88
95
 
89
- return `new URL(${specifier}, import.meta.url)`;
96
+ return `new URL(${JSON.stringify(specifier)}, import.meta.url)`;
90
97
  };
91
98
 
92
99
  const inlineWorkerExample = (specifier: string): string =>
@@ -125,17 +132,22 @@ const suggestionFor = async (emit: EmitContext, scan: ScanContext, specifier: st
125
132
  };
126
133
 
127
134
  const claimWorker = (emit: EmitContext, resolvedId: string): string => {
128
- const existing = emit.emitted.get(resolvedId);
135
+ const existing = emit.references.byModule.get(resolvedId);
129
136
 
130
137
  if (existing !== undefined) {
131
138
  return existing;
132
139
  }
133
140
 
134
- const fileName = workerFileName(resolvedId);
135
- emit.emitted.set(resolvedId, fileName);
136
- emit.context.emitFile({ type: "chunk", id: resolvedId, fileName });
141
+ const referenceId = emit.context.emitFile({
142
+ type: "chunk",
143
+ id: resolvedId,
144
+ fileName: workerFileName(resolvedId),
145
+ });
137
146
 
138
- return fileName;
147
+ emit.references.byModule.set(resolvedId, referenceId);
148
+ emit.references.claimed.add(referenceId);
149
+
150
+ return referenceId;
139
151
  };
140
152
 
141
153
  const isRewritableUrl = (scan: ScanContext, match: RegExpExecArray): boolean =>
@@ -158,7 +170,7 @@ const replacementFor = async (
158
170
  throw unresolvedWorkerError(scan, specifier, await suggestionFor(emit, scan, specifier));
159
171
  }
160
172
 
161
- return { start: match.index, end: match.index + match[0].length, fileName: claimWorker(emit, resolved.id) };
173
+ return { start: match.index, end: match.index + match[0].length, referenceId: claimWorker(emit, resolved.id) };
162
174
  };
163
175
 
164
176
  const collectReplacements = async (emit: EmitContext, scan: ScanContext): Promise<WorkerReplacement[]> => {
@@ -196,8 +208,8 @@ const applyReplacements = (code: string, replacements: WorkerReplacement[]): str
196
208
  let output = "";
197
209
  let cursor = 0;
198
210
 
199
- for (const { start, end, fileName } of replacements) {
200
- output += code.slice(cursor, start) + workerUrlExpression(fileName);
211
+ for (const { start, end, referenceId } of replacements) {
212
+ output += code.slice(cursor, start) + workerUrlReference(referenceId);
201
213
  cursor = end;
202
214
  }
203
215
 
@@ -222,14 +234,18 @@ const transformWorkerUrls = async (emit: EmitContext, code: string, id: string):
222
234
  };
223
235
 
224
236
  function gtkxWorker(): Plugin {
225
- const emitted: Map<string, string> = new Map();
237
+ const references: WorkerReferences = { byModule: new Map(), claimed: new Set() };
226
238
 
227
239
  return {
228
240
  name: "gtkx:worker",
229
241
  apply: "build",
230
242
 
231
243
  transform(code, id) {
232
- return transformWorkerUrls({ context: this, emitted }, code, id);
244
+ return transformWorkerUrls({ context: this, references }, code, id);
245
+ },
246
+
247
+ resolveFileUrl({ referenceId, relativePath }) {
248
+ return references.claimed.has(referenceId) ? workerUrlExpression(relativePath) : null;
233
249
  },
234
250
  };
235
251
  }