@gtkx/cli 1.3.0 → 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 (217) hide show
  1. package/README.md +5 -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/run-codegen.d.ts +1 -2
  7. package/dist/codegen/run-codegen.d.ts.map +1 -1
  8. package/dist/codegen/run-codegen.js +11 -6
  9. package/dist/codegen/run-codegen.js.map +1 -1
  10. package/dist/commands/build.d.ts +0 -4
  11. package/dist/commands/build.d.ts.map +1 -1
  12. package/dist/commands/build.js +0 -5
  13. package/dist/commands/build.js.map +1 -1
  14. package/dist/commands/codegen.d.ts.map +1 -1
  15. package/dist/commands/codegen.js +1 -3
  16. package/dist/commands/codegen.js.map +1 -1
  17. package/dist/deploy/depends.d.ts +2 -2
  18. package/dist/deploy/depends.d.ts.map +1 -1
  19. package/dist/deploy/depends.js +30 -15
  20. package/dist/deploy/depends.js.map +1 -1
  21. package/dist/deploy/nfpm/config.js +1 -1
  22. package/dist/deploy/nfpm/config.js.map +1 -1
  23. package/dist/deploy/node-runtime/elf.d.ts +1 -1
  24. package/dist/deploy/node-runtime/elf.d.ts.map +1 -1
  25. package/dist/deploy/node-runtime/elf.js +2 -2
  26. package/dist/deploy/node-runtime/elf.js.map +1 -1
  27. package/dist/deploy/node-runtime/index.js +2 -2
  28. package/dist/deploy/node-runtime/index.js.map +1 -1
  29. package/dist/deploy/notices/collect.d.ts +3 -1
  30. package/dist/deploy/notices/collect.d.ts.map +1 -1
  31. package/dist/deploy/notices/collect.js +2 -2
  32. package/dist/deploy/notices/collect.js.map +1 -1
  33. package/dist/deploy/notices/packages.d.ts +2 -1
  34. package/dist/deploy/notices/packages.d.ts.map +1 -1
  35. package/dist/deploy/notices/packages.js +4 -27
  36. package/dist/deploy/notices/packages.js.map +1 -1
  37. package/dist/deploy/payload/icons.d.ts +7 -3
  38. package/dist/deploy/payload/icons.d.ts.map +1 -1
  39. package/dist/deploy/payload/icons.js +46 -28
  40. package/dist/deploy/payload/icons.js.map +1 -1
  41. package/dist/deploy/payload/stage.d.ts.map +1 -1
  42. package/dist/deploy/payload/stage.js +3 -2
  43. package/dist/deploy/payload/stage.js.map +1 -1
  44. package/dist/deploy/run-deploy.d.ts.map +1 -1
  45. package/dist/deploy/run-deploy.js +63 -11
  46. package/dist/deploy/run-deploy.js.map +1 -1
  47. package/dist/deploy/settings/build-manifest.d.ts +9 -0
  48. package/dist/deploy/settings/build-manifest.d.ts.map +1 -0
  49. package/dist/deploy/settings/build-manifest.js +80 -0
  50. package/dist/deploy/settings/build-manifest.js.map +1 -0
  51. package/dist/deploy/settings/index.d.ts.map +1 -1
  52. package/dist/deploy/settings/index.js +9 -3
  53. package/dist/deploy/settings/index.js.map +1 -1
  54. package/dist/deploy/settings/libraries.d.ts +8 -0
  55. package/dist/deploy/settings/libraries.d.ts.map +1 -0
  56. package/dist/deploy/settings/libraries.js +39 -0
  57. package/dist/deploy/settings/libraries.js.map +1 -0
  58. package/dist/deploy/settings/paths.d.ts +3 -0
  59. package/dist/deploy/settings/paths.d.ts.map +1 -1
  60. package/dist/deploy/settings/paths.js +6 -32
  61. package/dist/deploy/settings/paths.js.map +1 -1
  62. package/dist/deploy/targets/appimage.d.ts.map +1 -1
  63. package/dist/deploy/targets/appimage.js +19 -13
  64. package/dist/deploy/targets/appimage.js.map +1 -1
  65. package/dist/deploy/targets/flatpak-manifest.d.ts +2 -1
  66. package/dist/deploy/targets/flatpak-manifest.d.ts.map +1 -1
  67. package/dist/deploy/targets/flatpak-manifest.js +5 -1
  68. package/dist/deploy/targets/flatpak-manifest.js.map +1 -1
  69. package/dist/deploy/targets/flatpak-source.d.ts.map +1 -1
  70. package/dist/deploy/targets/flatpak-source.js +9 -6
  71. package/dist/deploy/targets/flatpak-source.js.map +1 -1
  72. package/dist/deploy/types.d.ts +4 -4
  73. package/dist/deploy/types.d.ts.map +1 -1
  74. package/dist/deploy/types.js.map +1 -1
  75. package/dist/dev/icon-dir.d.ts +1 -1
  76. package/dist/dev/icon-dir.d.ts.map +1 -1
  77. package/dist/dev/icon-dir.js +20 -7
  78. package/dist/dev/icon-dir.js.map +1 -1
  79. package/dist/dev/runner-deps.d.ts.map +1 -1
  80. package/dist/dev/runner-deps.js +5 -1
  81. package/dist/dev/runner-deps.js.map +1 -1
  82. package/dist/dev/runner-main.js +4 -4
  83. package/dist/dev/runner-main.js.map +1 -1
  84. package/dist/dev/runner.d.ts +1 -1
  85. package/dist/dev/runner.d.ts.map +1 -1
  86. package/dist/dev/runner.js +1 -1
  87. package/dist/dev/runner.js.map +1 -1
  88. package/dist/dev/schema-dir.d.ts +1 -1
  89. package/dist/dev/schema-dir.d.ts.map +1 -1
  90. package/dist/dev/schema-dir.js +3 -1
  91. package/dist/dev/schema-dir.js.map +1 -1
  92. package/dist/internal/build-manifest.d.ts +20 -0
  93. package/dist/internal/build-manifest.d.ts.map +1 -0
  94. package/dist/internal/build-manifest.js +6 -0
  95. package/dist/internal/build-manifest.js.map +1 -0
  96. package/dist/internal/icon-path.d.ts +13 -0
  97. package/dist/internal/icon-path.d.ts.map +1 -0
  98. package/dist/internal/icon-path.js +86 -0
  99. package/dist/internal/icon-path.js.map +1 -0
  100. package/dist/internal/source-imports.d.ts +8 -0
  101. package/dist/internal/source-imports.d.ts.map +1 -0
  102. package/dist/internal/source-imports.js +144 -0
  103. package/dist/internal/source-imports.js.map +1 -0
  104. package/dist/mcp/widget-properties.d.ts.map +1 -1
  105. package/dist/mcp/widget-properties.js +3 -12
  106. package/dist/mcp/widget-properties.js.map +1 -1
  107. package/dist/settings/render.d.ts +7 -1
  108. package/dist/settings/render.d.ts.map +1 -1
  109. package/dist/settings/render.js +69 -20
  110. package/dist/settings/render.js.map +1 -1
  111. package/dist/settings/schema.d.ts +4 -3
  112. package/dist/settings/schema.d.ts.map +1 -1
  113. package/dist/settings/schema.js +122 -15
  114. package/dist/settings/schema.js.map +1 -1
  115. package/dist/vite-plugins/asset-emitter.d.ts +1 -1
  116. package/dist/vite-plugins/asset-emitter.d.ts.map +1 -1
  117. package/dist/vite-plugins/asset-emitter.js.map +1 -1
  118. package/dist/vite-plugins/asset-extensions.d.ts.map +1 -1
  119. package/dist/vite-plugins/asset-extensions.js +1 -0
  120. package/dist/vite-plugins/asset-extensions.js.map +1 -1
  121. package/dist/vite-plugins/asset-imports.d.ts +2 -1
  122. package/dist/vite-plugins/asset-imports.d.ts.map +1 -1
  123. package/dist/vite-plugins/asset-imports.js +37 -16
  124. package/dist/vite-plugins/asset-imports.js.map +1 -1
  125. package/dist/vite-plugins/asset-specifier.d.ts +13 -1
  126. package/dist/vite-plugins/asset-specifier.d.ts.map +1 -1
  127. package/dist/vite-plugins/asset-specifier.js +39 -1
  128. package/dist/vite-plugins/asset-specifier.js.map +1 -1
  129. package/dist/vite-plugins/build-manifest.d.ts +5 -0
  130. package/dist/vite-plugins/build-manifest.d.ts.map +1 -0
  131. package/dist/vite-plugins/{bundled-packages.js → build-manifest.js} +20 -14
  132. package/dist/vite-plugins/build-manifest.js.map +1 -0
  133. package/dist/vite-plugins/built-url.d.ts +1 -1
  134. package/dist/vite-plugins/built-url.d.ts.map +1 -1
  135. package/dist/vite-plugins/built-url.js +37 -24
  136. package/dist/vite-plugins/built-url.js.map +1 -1
  137. package/dist/vite-plugins/icons.d.ts +2 -1
  138. package/dist/vite-plugins/icons.d.ts.map +1 -1
  139. package/dist/vite-plugins/icons.js +25 -12
  140. package/dist/vite-plugins/icons.js.map +1 -1
  141. package/dist/vite-plugins/index.d.ts +2 -1
  142. package/dist/vite-plugins/index.d.ts.map +1 -1
  143. package/dist/vite-plugins/index.js +7 -5
  144. package/dist/vite-plugins/index.js.map +1 -1
  145. package/dist/vite-plugins/native.d.ts.map +1 -1
  146. package/dist/vite-plugins/native.js +23 -4
  147. package/dist/vite-plugins/native.js.map +1 -1
  148. package/dist/vite-plugins/resource-init-module.d.ts +7 -3
  149. package/dist/vite-plugins/resource-init-module.d.ts.map +1 -1
  150. package/dist/vite-plugins/resource-init-module.js +39 -14
  151. package/dist/vite-plugins/resource-init-module.js.map +1 -1
  152. package/dist/vite-plugins/resource-shared.d.ts +3 -1
  153. package/dist/vite-plugins/resource-shared.d.ts.map +1 -1
  154. package/dist/vite-plugins/resource-shared.js +3 -1
  155. package/dist/vite-plugins/resource-shared.js.map +1 -1
  156. package/dist/vite-plugins/resources.d.ts +1 -1
  157. package/dist/vite-plugins/resources.d.ts.map +1 -1
  158. package/dist/vite-plugins/resources.js +922 -86
  159. package/dist/vite-plugins/resources.js.map +1 -1
  160. package/dist/vite-plugins/settings-worker-env.d.ts.map +1 -1
  161. package/dist/vite-plugins/settings-worker-env.js +6 -3
  162. package/dist/vite-plugins/settings-worker-env.js.map +1 -1
  163. package/dist/vite-plugins/settings.d.ts +3 -1
  164. package/dist/vite-plugins/settings.d.ts.map +1 -1
  165. package/dist/vite-plugins/settings.js +63 -23
  166. package/dist/vite-plugins/settings.js.map +1 -1
  167. package/env.d.ts +11 -192
  168. package/package.json +10 -10
  169. package/src/builder.ts +6 -7
  170. package/src/codegen/run-codegen.ts +10 -7
  171. package/src/commands/build.ts +0 -5
  172. package/src/commands/codegen.ts +1 -3
  173. package/src/deploy/depends.ts +47 -18
  174. package/src/deploy/nfpm/config.ts +1 -1
  175. package/src/deploy/node-runtime/elf.ts +3 -3
  176. package/src/deploy/node-runtime/index.ts +2 -2
  177. package/src/deploy/notices/collect.ts +4 -2
  178. package/src/deploy/notices/packages.ts +5 -43
  179. package/src/deploy/payload/icons.ts +64 -34
  180. package/src/deploy/payload/stage.ts +5 -2
  181. package/src/deploy/run-deploy.ts +87 -12
  182. package/src/deploy/settings/build-manifest.ts +122 -0
  183. package/src/deploy/settings/index.ts +9 -4
  184. package/src/deploy/settings/libraries.ts +57 -0
  185. package/src/deploy/settings/paths.ts +10 -47
  186. package/src/deploy/targets/appimage.ts +32 -15
  187. package/src/deploy/targets/flatpak-manifest.ts +7 -1
  188. package/src/deploy/targets/flatpak-source.ts +9 -10
  189. package/src/deploy/types.ts +4 -4
  190. package/src/dev/icon-dir.ts +31 -7
  191. package/src/dev/runner-deps.ts +5 -1
  192. package/src/dev/runner-main.ts +4 -4
  193. package/src/dev/runner.ts +2 -2
  194. package/src/dev/schema-dir.ts +3 -1
  195. package/src/internal/build-manifest.ts +32 -0
  196. package/src/internal/icon-path.ts +127 -0
  197. package/src/internal/source-imports.ts +200 -0
  198. package/src/mcp/widget-properties.ts +3 -14
  199. package/src/settings/render.ts +99 -21
  200. package/src/settings/schema.ts +190 -17
  201. package/src/vite-plugins/asset-emitter.ts +1 -1
  202. package/src/vite-plugins/asset-extensions.ts +1 -0
  203. package/src/vite-plugins/asset-imports.ts +60 -19
  204. package/src/vite-plugins/asset-specifier.ts +73 -1
  205. package/src/vite-plugins/{bundled-packages.ts → build-manifest.ts} +30 -19
  206. package/src/vite-plugins/built-url.ts +49 -28
  207. package/src/vite-plugins/icons.ts +33 -14
  208. package/src/vite-plugins/index.ts +12 -5
  209. package/src/vite-plugins/native.ts +31 -4
  210. package/src/vite-plugins/resource-init-module.ts +44 -15
  211. package/src/vite-plugins/resource-shared.ts +4 -0
  212. package/src/vite-plugins/resources.ts +1459 -100
  213. package/src/vite-plugins/settings-worker-env.ts +7 -3
  214. package/src/vite-plugins/settings.ts +89 -21
  215. package/dist/vite-plugins/bundled-packages.d.ts +0 -5
  216. package/dist/vite-plugins/bundled-packages.d.ts.map +0 -1
  217. package/dist/vite-plugins/bundled-packages.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { existsSync, mkdirSync, rmSync, statSync } from "node:fs";
2
- import { join } from "node:path";
2
+ import { basename, join } from "node:path";
3
3
  import type {
4
4
  DeployArtifact,
5
5
  DeployManifest,
@@ -10,18 +10,21 @@ import type {
10
10
  } from "../types.js";
11
11
  import { runCliTool } from "../../internal/run-cli-tool.js";
12
12
  import { copyInto, copyTree, EXECUTABLE_MODE, writeInto } from "../payload/copy-tree.js";
13
- import { getIconSize } from "../payload/icons.js";
13
+ import { type ApplicationIconVariant, classifyApplicationIcon } from "../payload/icons.js";
14
14
  import { FILE_TOOL } from "../tools.js";
15
15
  import { resolveAppimageTooling } from "../vendored/appimagetool.js";
16
16
 
17
+ type RankedIcon = {
18
+ file: StagedFile;
19
+ variant: ApplicationIconVariant;
20
+ };
21
+
17
22
  const PREFIX = "/usr";
18
23
  const TARGET_DIR = "appimage";
19
24
  const APPRUN_FILENAME = "AppRun";
20
25
  const APPDIR_NAME = "AppDir";
21
26
  const USR_DIR = "usr";
22
- const ICON_EXTENSIONS = [".svg", ".png", ".xpm"];
23
27
  const EXTRACT_AND_RUN = "APPIMAGE_EXTRACT_AND_RUN";
24
- const SCALABLE_RANK = Number.MAX_SAFE_INTEGER;
25
28
 
26
29
  const appimageTarget: DeployTarget = {
27
30
  name: "appimage",
@@ -50,31 +53,45 @@ const renderManifests = (payload: DeployPayload): DeployManifest[] => [
50
53
  { path: appRunPathFor(payload.settings), contents: renderAppRun(payload.settings) },
51
54
  ];
52
55
 
53
- const iconRank = (file: StagedFile): number =>
54
- file.rel.endsWith(".svg") ? SCALABLE_RANK : (getIconSize(file.rel) ?? 0);
56
+ const rankedIcon = (applicationId: string, file: StagedFile): RankedIcon | null => {
57
+ const variant = classifyApplicationIcon(applicationId, file.rel);
58
+
59
+ return variant === null ? null : { file, variant };
60
+ };
61
+
62
+ const isLargerIcon = (candidate: RankedIcon, best: RankedIcon): boolean =>
63
+ candidate.variant.scalable === best.variant.scalable
64
+ ? candidate.variant.effectivePixels > best.variant.effectivePixels
65
+ : candidate.variant.scalable;
66
+
67
+ const largerIcon = (best: RankedIcon | undefined, candidate: RankedIcon): RankedIcon =>
68
+ best === undefined || isLargerIcon(candidate, best) ? candidate : best;
55
69
 
56
- const largestIcon = (icons: StagedFile[]): StagedFile | undefined => {
57
- let best: StagedFile | undefined;
70
+ const largestIcon = (applicationId: string, files: StagedFile[]): StagedFile | undefined => {
71
+ let best: RankedIcon | undefined;
58
72
 
59
- for (const file of icons) {
60
- if (best === undefined || iconRank(file) > iconRank(best)) {
61
- best = file;
73
+ for (const file of files) {
74
+ const candidate = rankedIcon(applicationId, file);
75
+
76
+ if (candidate === null) {
77
+ continue;
62
78
  }
79
+
80
+ best = largerIcon(best, candidate);
63
81
  }
64
82
 
65
- return best;
83
+ return best?.file;
66
84
  };
67
85
 
68
86
  const rootIconFor = (payload: DeployPayload): { rel: string; abs: string } => {
69
87
  const { applicationId } = payload.settings;
70
- const names = new Set(ICON_EXTENSIONS.map((extension) => `${applicationId}${extension}`));
71
- const icon = largestIcon(payload.stage.filter((file) => names.has(file.rel.split("/").at(-1) ?? "")));
88
+ const icon = largestIcon(applicationId, payload.stage);
72
89
 
73
90
  if (icon === undefined) {
74
91
  throw new Error(`Cannot build an AppImage without a ${applicationId} icon in the staged icon tree`);
75
92
  }
76
93
 
77
- return { rel: icon.rel.split("/").at(-1) ?? "", abs: icon.abs };
94
+ return { rel: basename(icon.rel), abs: icon.abs };
78
95
  };
79
96
 
80
97
  const buildAppDir = (payload: DeployPayload): string => {
@@ -110,6 +110,12 @@ const runtimeKeys = (settings: DeploySettings): FlatpakManifest => {
110
110
  };
111
111
  };
112
112
 
113
+ const runtimeLabelFor = (settings: DeploySettings): string => {
114
+ const flatpak = settings.deploy.flatpak ?? {};
115
+
116
+ return `${flatpak.runtime ?? DEFAULT_RUNTIME}//${flatpak.runtimeVersion ?? DEFAULT_RUNTIME_VERSION}`;
117
+ };
118
+
113
119
  const renderFlatpakManifest = (payload: DeployPayload): FlatpakManifest => {
114
120
  const settings = payload.settings;
115
121
 
@@ -123,4 +129,4 @@ const renderFlatpakManifest = (payload: DeployPayload): FlatpakManifest => {
123
129
  };
124
130
  };
125
131
 
126
- export { branchFor, finishArgsFor, hasDisplaySocket, renderFlatpakManifest };
132
+ export { branchFor, finishArgsFor, hasDisplaySocket, renderFlatpakManifest, runtimeLabelFor };
@@ -120,12 +120,11 @@ const metadataInstallCommands = (settings: DeploySettings): string[] => [
120
120
  const schemaInstallCommands = (settings: DeploySettings): string[] =>
121
121
  settings.paths.schemaFiles.map((file) => {
122
122
  const name = posix.basename(file);
123
+ const source = projectRelative(settings, file);
124
+ const destination = `${DESTINATION}/share/glib-2.0/schemas/${shellArgument(name)}`;
125
+ assertInsideProject(settings, { destination, source, resolved: file });
123
126
 
124
- return installCommand(
125
- projectRelative(settings, file),
126
- `${DESTINATION}/share/glib-2.0/schemas/${shellArgument(name)}`,
127
- "m644",
128
- );
127
+ return installCommand(source, destination, "m644");
129
128
  });
130
129
 
131
130
  const iconInstallCommand = (settings: DeploySettings, source: string, rel: string): string =>
@@ -136,17 +135,17 @@ const iconInstallCommand = (settings: DeploySettings, source: string, rel: strin
136
135
  );
137
136
 
138
137
  const iconInstallCommands = (settings: DeploySettings): string[] => {
139
- const { iconsDir, iconFile } = settings.paths;
138
+ const source = settings.paths.applicationIcon;
140
139
 
141
- if (iconFile !== null) {
142
- return [iconInstallCommand(settings, iconFile, iconPathFor(settings, iconFile))];
140
+ if (source.kind === "file") {
141
+ return [iconInstallCommand(settings, source.path, iconPathFor(settings, source.path))];
143
142
  }
144
143
 
145
- if (iconsDir === null) {
144
+ if (source.kind === "none") {
146
145
  return [];
147
146
  }
148
147
 
149
- return listFilesRecursive(iconsDir).map((icon) => iconInstallCommand(settings, icon.absPath, icon.rel));
148
+ return listFilesRecursive(source.path).map((icon) => iconInstallCommand(settings, icon.absPath, icon.rel));
150
149
  };
151
150
 
152
151
  const assertInsideProject = (settings: DeploySettings, installed: InstalledFile): void => {
@@ -1,4 +1,5 @@
1
1
  import type { Config } from "@gtkx/config";
2
+ import type { ResolvedApplicationIcon } from "../internal/icon-path.js";
2
3
 
3
4
  type DeployConfig = NonNullable<Config["deploy"]>;
4
5
  type DeployTargetName = NonNullable<DeployConfig["targets"]>[number];
@@ -21,7 +22,7 @@ type NodeRuntime = {
21
22
  path: string;
22
23
  licenseFile: string | null;
23
24
  version: string;
24
- glibcFloor: string | null;
25
+ glibcMinimum: string | null;
25
26
  isStripped: boolean;
26
27
  };
27
28
 
@@ -106,9 +107,7 @@ type DeployPaths = {
106
107
  overlay: string;
107
108
  targets: string;
108
109
  output: string;
109
- dataDir: string | null;
110
- iconsDir: string | null;
111
- iconFile: string | null;
110
+ applicationIcon: ResolvedApplicationIcon;
112
111
  licenseFile: string | null;
113
112
  schemaFiles: string[];
114
113
  };
@@ -145,6 +144,7 @@ type DeploySettings = {
145
144
  arch: DeployArch;
146
145
  paths: DeployPaths;
147
146
  libraries: string[];
147
+ minimumLibraryVersions: Record<string, string>;
148
148
  deploy: DeployConfig;
149
149
  };
150
150
 
@@ -1,18 +1,42 @@
1
- import { existsSync } from "node:fs";
2
- import { join } from "node:path";
1
+ import { mkdirSync, symlinkSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import {
4
+ relativeIconPath,
5
+ resolveApplicationIcon,
6
+ type ResolvedApplicationIcon,
7
+ } from "../internal/icon-path.js";
8
+ import { createRetainedStagingDir } from "../internal/staging-dir.js";
3
9
  import { prependXdgDataDir } from "../internal/xdg-data-dirs.js";
4
10
 
5
- const prepareDevIconDir = (root: string, dataDir: string | null): string | null => {
6
- if (dataDir === null) {
7
- return null;
11
+ const stageIconSource = (shareDir: string, applicationId: string, source: ResolvedApplicationIcon): void => {
12
+ if (source.kind === "theme") {
13
+ symlinkSync(source.path, join(shareDir, "icons"), "dir");
14
+
15
+ return;
16
+ }
17
+
18
+ if (source.kind === "none") {
19
+ return;
8
20
  }
9
21
 
10
- const shareDir = join(root, dataDir);
22
+ const target = join(shareDir, "icons", relativeIconPath(applicationId, source.path));
23
+ mkdirSync(dirname(target), { recursive: true });
24
+ symlinkSync(source.path, target, "file");
25
+ };
26
+
27
+ const prepareDevIconDir = (
28
+ root: string,
29
+ applicationId: string,
30
+ configured: string | undefined,
31
+ ): string | null => {
32
+ const source = resolveApplicationIcon(root, applicationId, configured);
11
33
 
12
- if (!existsSync(join(shareDir, "icons"))) {
34
+ if (source.kind === "none") {
13
35
  return null;
14
36
  }
15
37
 
38
+ const shareDir = createRetainedStagingDir("icons").retain();
39
+ stageIconSource(shareDir, applicationId, source);
16
40
  process.env.XDG_DATA_DIRS = prependXdgDataDir(shareDir, process.env.XDG_DATA_DIRS);
17
41
 
18
42
  return shareDir;
@@ -86,7 +86,11 @@ const defaultDevRunnerDeps = (): DevRunnerDeps => ({
86
86
  isRefreshBoundary,
87
87
  staleExportName,
88
88
  readFileRevision,
89
- plugins: () => [...gtkxVitePlugins(DEV_MODE), ...gtkxFastRefresh(), gtkxReactDomPrebundle()],
89
+ plugins: (entryPath) => [
90
+ ...gtkxVitePlugins(DEV_MODE, entryPath),
91
+ ...gtkxFastRefresh(),
92
+ gtkxReactDomPrebundle(),
93
+ ],
90
94
  log: info,
91
95
  exit: (code: number): never => process.exit(code),
92
96
  });
@@ -1,6 +1,6 @@
1
+ import { loadConfig } from "@gtkx/config";
1
2
  import { error } from "@gtkx/utils";
2
3
  import { resolve } from "node:path";
3
- import { resolveDataDir } from "../internal/data-dir.js";
4
4
  import { DEV_ENTRY_ENV } from "./entry-env.js";
5
5
  import { prepareDevIconDir } from "./icon-dir.js";
6
6
  import { createDevRunner } from "./runner.js";
@@ -21,9 +21,9 @@ const main = async (): Promise<void> => {
21
21
  process.exit(1);
22
22
  }
23
23
 
24
- const dataDir = resolveDataDir(cwd);
25
- prepareDevSchemaDir(cwd, dataDir);
26
- prepareDevIconDir(cwd, dataDir);
24
+ const { config, root } = await loadConfig(cwd, { mode: "development" });
25
+ prepareDevSchemaDir(root, config.future?.v2ResourceImports === true);
26
+ prepareDevIconDir(root, config.applicationId, config.applicationIcon);
27
27
  const entryPath = resolve(cwd, entryArg);
28
28
  const { defaultDevRunnerDeps } = await import("./runner-deps.js");
29
29
  const runner = createDevRunner(defaultDevRunnerDeps());
package/src/dev/runner.ts CHANGED
@@ -32,7 +32,7 @@ type DevRunnerDeps = {
32
32
  isRefreshBoundary(module: Record<string, unknown>): boolean;
33
33
  staleExportName(previous: Record<string, unknown>, current: Record<string, unknown>): string | null;
34
34
  readFileRevision(path: string): Promise<string>;
35
- plugins(): Plugin[];
35
+ plugins(entryPath: string): Plugin[];
36
36
  log(message: string): void;
37
37
  exit(code: number): never;
38
38
  };
@@ -602,7 +602,7 @@ const createSession = (server: DevServer, deps: DevRunnerDeps): DevSession => {
602
602
 
603
603
  const createDevRunner = (deps: DevRunnerDeps): DevRunner => ({
604
604
  async run(entryPath: string): Promise<void> {
605
- const server = await deps.createServer(createDevServerConfig(process.cwd(), deps.plugins()));
605
+ const server = await deps.createServer(createDevServerConfig(process.cwd(), deps.plugins(entryPath)));
606
606
  const session = createSession(server, deps);
607
607
  deps.installShutdownHandlers(onShutdownSignal(session));
608
608
 
@@ -1,6 +1,8 @@
1
+ import { resolveDataDir } from "../internal/data-dir.js";
1
2
  import { prependSchemaDir, stageAndCompileProjectSchemas } from "../settings/schema.js";
2
3
 
3
- const prepareDevSchemaDir = (root: string, dataDir: string | null): string | null => {
4
+ const prepareDevSchemaDir = (root: string, isV2ResourceImports: boolean): string | null => {
5
+ const dataDir = isV2ResourceImports ? null : resolveDataDir(root);
4
6
  const dir = stageAndCompileProjectSchemas(root, dataDir);
5
7
 
6
8
  if (dir === null) {
@@ -0,0 +1,32 @@
1
+ type RecordedPackage = {
2
+ name: string;
3
+ version: string | null;
4
+ dir: string;
5
+ };
6
+
7
+ type BuildManifest = {
8
+ generator: typeof BUILD_MANIFEST_GENERATOR;
9
+ formatVersion: typeof BUILD_MANIFEST_FORMAT_VERSION;
10
+ schemas: string[];
11
+ packages: RecordedPackage[];
12
+ };
13
+
14
+ type BuildManifestCollector = {
15
+ schemas: string[];
16
+ };
17
+
18
+ const BUILD_MANIFEST_FILENAME = "gtkx-schemas.json";
19
+ const BUILD_MANIFEST_GENERATOR = "gtkx-build";
20
+ const BUILD_MANIFEST_FORMAT_VERSION = 1;
21
+
22
+ const createBuildManifestCollector = (): BuildManifestCollector => ({ schemas: [] });
23
+
24
+ export {
25
+ BUILD_MANIFEST_FILENAME,
26
+ BUILD_MANIFEST_FORMAT_VERSION,
27
+ BUILD_MANIFEST_GENERATOR,
28
+ createBuildManifestCollector,
29
+ type BuildManifest,
30
+ type BuildManifestCollector,
31
+ type RecordedPackage,
32
+ };
@@ -0,0 +1,127 @@
1
+ import { type Stats, statSync } from "node:fs";
2
+ import { basename, extname, isAbsolute, join, relative, resolve } from "node:path";
3
+
4
+ type ResolvedApplicationIcon = { kind: "file"; path: string } |
5
+ { kind: "none" } |
6
+ { kind: "theme"; path: string };
7
+
8
+ const ICON_EXTENSIONS: Set<string> = new Set([".svg", ".png", ".xpm"]);
9
+ const SCALABLE_DIR = "hicolor/scalable/apps";
10
+ const SIZE_PATTERN = /^(?<width>[1-9]\d{0,3})x(?<height>[1-9]\d{0,3})$/;
11
+ const SIZE_SEPARATORS = /[-_.]/;
12
+
13
+ const isInside = (parent: string, candidate: string): boolean => {
14
+ const rel = relative(parent, candidate);
15
+
16
+ return rel.length > 0 && !rel.startsWith("..") && !isAbsolute(rel);
17
+ };
18
+
19
+ const getStats = (path: string): Stats | undefined => {
20
+ try {
21
+ return statSync(path, { throwIfNoEntry: false });
22
+ } catch {
23
+ return undefined;
24
+ }
25
+ };
26
+
27
+ const iconExtension = (file: string): string => {
28
+ const extension = extname(file).toLowerCase();
29
+
30
+ if (!ICON_EXTENSIONS.has(extension)) {
31
+ throw new Error(`Cannot use ${file} as an application icon: expected an SVG, PNG, or XPM file`);
32
+ }
33
+
34
+ return extension;
35
+ };
36
+
37
+ const defaultIconFiles = (root: string, applicationId: string): string[] =>
38
+ [...ICON_EXTENSIONS]
39
+ .map((extension) => join(root, `${applicationId}${extension}`))
40
+ .filter((path) => getStats(path)?.isFile() === true);
41
+
42
+ const resolveDefaultApplicationIcon = (root: string, applicationId: string): ResolvedApplicationIcon => {
43
+ const files = defaultIconFiles(root, applicationId);
44
+
45
+ if (files.length === 0) {
46
+ return { kind: "none" };
47
+ }
48
+
49
+ if (files.length > 1) {
50
+ throw new Error(
51
+ `Found multiple default application icons for ${applicationId}; set \`applicationIcon\` to choose one`,
52
+ );
53
+ }
54
+
55
+ const path = files[0];
56
+
57
+ if (path === undefined) {
58
+ return { kind: "none" };
59
+ }
60
+
61
+ return { kind: "file", path };
62
+ };
63
+
64
+ const resolveApplicationIcon = (
65
+ root: string,
66
+ applicationId: string,
67
+ configured: string | undefined,
68
+ ): ResolvedApplicationIcon => {
69
+ if (configured === undefined) {
70
+ return resolveDefaultApplicationIcon(root, applicationId);
71
+ }
72
+
73
+ if (isAbsolute(configured)) {
74
+ throw new Error(`Cannot use "${configured}" as the application icon: expected a project-relative path`);
75
+ }
76
+
77
+ const path = resolve(root, configured);
78
+
79
+ if (!isInside(root, path)) {
80
+ throw new Error(`Cannot use "${configured}" as the application icon: it is outside ${root}`);
81
+ }
82
+
83
+ const stats = getStats(path);
84
+
85
+ if (stats?.isDirectory() === true) {
86
+ return { kind: "theme", path };
87
+ }
88
+
89
+ if (stats?.isFile() !== true) {
90
+ throw new Error(`Cannot read the application icon "${configured}": no such file or directory under ${root}`);
91
+ }
92
+
93
+ iconExtension(path);
94
+
95
+ return { kind: "file", path };
96
+ };
97
+
98
+ const squareSize = (token: string): number | null => {
99
+ const groups = SIZE_PATTERN.exec(token)?.groups;
100
+
101
+ if (groups === undefined || groups.width !== groups.height) {
102
+ return null;
103
+ }
104
+
105
+ return Number(groups.width);
106
+ };
107
+
108
+ const iconThemeDir = (file: string, extension: string): string => {
109
+ if (extension === ".svg") {
110
+ return SCALABLE_DIR;
111
+ }
112
+
113
+ const size = basename(file)
114
+ .split(SIZE_SEPARATORS)
115
+ .map((token) => squareSize(token))
116
+ .find((value) => value !== null);
117
+
118
+ return size === undefined ? SCALABLE_DIR : `hicolor/${String(size)}x${String(size)}/apps`;
119
+ };
120
+
121
+ const relativeIconPath = (applicationId: string, file: string): string => {
122
+ const extension = iconExtension(file);
123
+
124
+ return `${iconThemeDir(file, extension)}/${applicationId}${extension}`;
125
+ };
126
+
127
+ export { relativeIconPath, resolveApplicationIcon, type ResolvedApplicationIcon };
@@ -0,0 +1,200 @@
1
+ import type { ParseResult, ParserOptions } from "vite";
2
+ import { isRecord, sortStringsBy } from "@gtkx/utils";
3
+ import { type Dirent, readdirSync, readFileSync } from "node:fs";
4
+ import { extname, join } from "node:path";
5
+ import { parseSync } from "vite";
6
+
7
+ type SourceImport = {
8
+ importer: string;
9
+ source: string;
10
+ };
11
+
12
+ type SourceLanguage = NonNullable<ParserOptions["lang"]>;
13
+ type ParsedModule = ParseResult["module"];
14
+
15
+ const SOURCE_LANGUAGES: Map<string, SourceLanguage> = new Map([
16
+ [".cjs", "jsx"],
17
+ [".cts", "ts"],
18
+ [".js", "jsx"],
19
+ [".jsx", "jsx"],
20
+ [".mjs", "jsx"],
21
+ [".mts", "ts"],
22
+ [".ts", "ts"],
23
+ [".tsx", "tsx"],
24
+ ]);
25
+
26
+ const EXCLUDED_DIRECTORIES: Set<string> = new Set([
27
+ ".git",
28
+ ".gtkx",
29
+ "build",
30
+ "coverage",
31
+ "dist",
32
+ "node_modules",
33
+ "out-tsc",
34
+ ]);
35
+
36
+ const sourceLanguage = (path: string): SourceLanguage | undefined => {
37
+ if (path.endsWith(".d.ts") || path.endsWith(".d.cts") || path.endsWith(".d.mts")) {
38
+ return undefined;
39
+ }
40
+
41
+ return SOURCE_LANGUAGES.get(extname(path).toLowerCase());
42
+ };
43
+
44
+ const collectSourceEntry = (dir: string, entry: Dirent, found: string[]): void => {
45
+ const path = join(dir, entry.name);
46
+
47
+ if (entry.isDirectory()) {
48
+ if (!EXCLUDED_DIRECTORIES.has(entry.name)) {
49
+ sourceFilesIn(path, found);
50
+ }
51
+
52
+ return;
53
+ }
54
+
55
+ if (entry.isFile() && sourceLanguage(path) !== undefined) {
56
+ found.push(path);
57
+ }
58
+ };
59
+
60
+ const sourceFilesIn = (dir: string, found: string[]): void => {
61
+ let entries: Dirent[];
62
+
63
+ try {
64
+ entries = readdirSync(dir, { withFileTypes: true });
65
+ } catch {
66
+ return;
67
+ }
68
+
69
+ for (const entry of entries) {
70
+ collectSourceEntry(dir, entry, found);
71
+ }
72
+ };
73
+
74
+ const staticImportSources = (module: ParsedModule): string[] => [
75
+ ...module.staticImports.map((statement) => statement.moduleRequest.value),
76
+ ...module.staticExports.flatMap((statement) =>
77
+ statement.entries
78
+ .map((entry) => entry.moduleRequest?.value)
79
+ .filter((source): source is string => source !== undefined),
80
+ ),
81
+ ];
82
+
83
+ const staticRuntimeImportSources = (module: ParsedModule): string[] => [
84
+ ...module.staticImports
85
+ .filter((statement) => statement.entries.length === 0 || statement.entries.some((entry) => !entry.isType))
86
+ .map((statement) => statement.moduleRequest.value),
87
+ ...module.staticExports.flatMap((statement) =>
88
+ statement.entries
89
+ .filter((entry) => !entry.isType)
90
+ .map((entry) => entry.moduleRequest?.value)
91
+ .filter((source): source is string => source !== undefined),
92
+ ),
93
+ ];
94
+
95
+ const templateImportSource = (node: Record<string, unknown>): string | null => {
96
+ if (!Array.isArray(node.expressions) || node.expressions.length > 0 || !Array.isArray(node.quasis)) {
97
+ return null;
98
+ }
99
+
100
+ const quasis: unknown[] = node.quasis;
101
+ const first: unknown = quasis[0];
102
+
103
+ if (!isRecord(first) || !isRecord(first.value)) {
104
+ return null;
105
+ }
106
+
107
+ return typeof first.value.cooked === "string" ? first.value.cooked : null;
108
+ };
109
+
110
+ const literalImportSource = (node: unknown): string | null => {
111
+ if (!isRecord(node)) {
112
+ return null;
113
+ }
114
+
115
+ if (node.type === "Literal") {
116
+ return typeof node.value === "string" ? node.value : null;
117
+ }
118
+
119
+ return node.type === "TemplateLiteral" ? templateImportSource(node) : null;
120
+ };
121
+
122
+ const collectDynamicImportArray = (nodes: unknown[], found: string[]): void => {
123
+ for (const child of nodes) {
124
+ collectDynamicImportSources(child, found);
125
+ }
126
+ };
127
+
128
+ const collectDynamicImportRecord = (node: Record<string, unknown>, found: string[]): void => {
129
+ if (node.type === "ImportExpression") {
130
+ const source = literalImportSource(node.source);
131
+
132
+ if (source !== null) {
133
+ found.push(source);
134
+ }
135
+
136
+ return;
137
+ }
138
+
139
+ collectDynamicImportArray(Object.values(node), found);
140
+ };
141
+
142
+ const collectDynamicImportSources = (node: unknown, found: string[]): void => {
143
+ if (Array.isArray(node)) {
144
+ collectDynamicImportArray(node, found);
145
+
146
+ return;
147
+ }
148
+
149
+ if (!isRecord(node)) {
150
+ return;
151
+ }
152
+
153
+ collectDynamicImportRecord(node, found);
154
+ };
155
+
156
+ const parseImportsInWith = (
157
+ path: string,
158
+ staticSources: (module: ParsedModule) => string[],
159
+ ): SourceImport[] | null => {
160
+ const lang = sourceLanguage(path);
161
+
162
+ if (lang === undefined) {
163
+ return [];
164
+ }
165
+
166
+ let parsed: ParseResult;
167
+
168
+ try {
169
+ parsed = parseSync(path, readFileSync(path, "utf8"), { lang });
170
+ } catch {
171
+ return null;
172
+ }
173
+
174
+ if (parsed.errors.length > 0) {
175
+ return null;
176
+ }
177
+
178
+ const sources = staticSources(parsed.module);
179
+ collectDynamicImportSources(parsed.program, sources);
180
+
181
+ return [...new Set(sources)].map((source) => ({ importer: path, source }));
182
+ };
183
+
184
+ const parseImportsIn = (path: string): SourceImport[] | null => parseImportsInWith(path, staticImportSources);
185
+
186
+ const parseRuntimeImportsIn = (path: string): SourceImport[] | null =>
187
+ parseImportsInWith(path, staticRuntimeImportSources);
188
+
189
+ const importsIn = (path: string): SourceImport[] => parseImportsIn(path) ?? [];
190
+ const importKey = (entry: SourceImport): string => `${entry.importer}\0${entry.source}`;
191
+
192
+ const discoverSourceImports = (dir: string): SourceImport[] => {
193
+ const files: string[] = [];
194
+ sourceFilesIn(dir, files);
195
+ const imports = files.flatMap((path) => importsIn(path));
196
+
197
+ return sortStringsBy(imports, importKey);
198
+ };
199
+
200
+ export { discoverSourceImports, parseRuntimeImportsIn, type SourceImport };