@gtkx/cli 1.2.0 → 1.2.2

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 (117) hide show
  1. package/dist/builder.d.ts.map +1 -1
  2. package/dist/builder.js +2 -0
  3. package/dist/builder.js.map +1 -1
  4. package/dist/deploy/freedesktop/copyright.d.ts +2 -2
  5. package/dist/deploy/freedesktop/copyright.d.ts.map +1 -1
  6. package/dist/deploy/freedesktop/copyright.js +105 -14
  7. package/dist/deploy/freedesktop/copyright.js.map +1 -1
  8. package/dist/deploy/node-runtime/download.d.ts +5 -1
  9. package/dist/deploy/node-runtime/download.d.ts.map +1 -1
  10. package/dist/deploy/node-runtime/download.js +6 -3
  11. package/dist/deploy/node-runtime/download.js.map +1 -1
  12. package/dist/deploy/node-runtime/index.d.ts +2 -1
  13. package/dist/deploy/node-runtime/index.d.ts.map +1 -1
  14. package/dist/deploy/node-runtime/index.js +10 -9
  15. package/dist/deploy/node-runtime/index.js.map +1 -1
  16. package/dist/deploy/node-runtime/license.d.ts +4 -0
  17. package/dist/deploy/node-runtime/license.d.ts.map +1 -0
  18. package/dist/deploy/node-runtime/license.js +19 -0
  19. package/dist/deploy/node-runtime/license.js.map +1 -0
  20. package/dist/deploy/notices/collect.d.ts +8 -0
  21. package/dist/deploy/notices/collect.d.ts.map +1 -0
  22. package/dist/deploy/notices/collect.js +17 -0
  23. package/dist/deploy/notices/collect.js.map +1 -0
  24. package/dist/deploy/notices/dependencies.d.ts +5 -0
  25. package/dist/deploy/notices/dependencies.d.ts.map +1 -0
  26. package/dist/deploy/notices/dependencies.js +39 -0
  27. package/dist/deploy/notices/dependencies.js.map +1 -0
  28. package/dist/deploy/notices/gtkx.d.ts +6 -0
  29. package/dist/deploy/notices/gtkx.d.ts.map +1 -0
  30. package/dist/deploy/notices/gtkx.js +68 -0
  31. package/dist/deploy/notices/gtkx.js.map +1 -0
  32. package/dist/deploy/notices/libraries.d.ts +4 -0
  33. package/dist/deploy/notices/libraries.d.ts.map +1 -0
  34. package/dist/deploy/notices/libraries.js +60 -0
  35. package/dist/deploy/notices/libraries.js.map +1 -0
  36. package/dist/deploy/notices/node-runtime.d.ts +4 -0
  37. package/dist/deploy/notices/node-runtime.d.ts.map +1 -0
  38. package/dist/deploy/notices/node-runtime.js +78 -0
  39. package/dist/deploy/notices/node-runtime.js.map +1 -0
  40. package/dist/deploy/notices/packages.d.ts +13 -0
  41. package/dist/deploy/notices/packages.d.ts.map +1 -0
  42. package/dist/deploy/notices/packages.js +115 -0
  43. package/dist/deploy/notices/packages.js.map +1 -0
  44. package/dist/deploy/notices/render.d.ts +4 -0
  45. package/dist/deploy/notices/render.d.ts.map +1 -0
  46. package/dist/deploy/notices/render.js +41 -0
  47. package/dist/deploy/notices/render.js.map +1 -0
  48. package/dist/deploy/notices/text.d.ts +5 -0
  49. package/dist/deploy/notices/text.d.ts.map +1 -0
  50. package/dist/deploy/notices/text.js +34 -0
  51. package/dist/deploy/notices/text.js.map +1 -0
  52. package/dist/deploy/payload/copy-tree.d.ts +2 -1
  53. package/dist/deploy/payload/copy-tree.d.ts.map +1 -1
  54. package/dist/deploy/payload/copy-tree.js +3 -1
  55. package/dist/deploy/payload/copy-tree.js.map +1 -1
  56. package/dist/deploy/payload/stage.d.ts +7 -3
  57. package/dist/deploy/payload/stage.d.ts.map +1 -1
  58. package/dist/deploy/payload/stage.js +25 -14
  59. package/dist/deploy/payload/stage.js.map +1 -1
  60. package/dist/deploy/run-deploy.d.ts.map +1 -1
  61. package/dist/deploy/run-deploy.js +22 -5
  62. package/dist/deploy/run-deploy.js.map +1 -1
  63. package/dist/deploy/settings/extra-files.d.ts +4 -0
  64. package/dist/deploy/settings/extra-files.d.ts.map +1 -0
  65. package/dist/deploy/settings/extra-files.js +11 -0
  66. package/dist/deploy/settings/extra-files.js.map +1 -0
  67. package/dist/deploy/settings/index.d.ts.map +1 -1
  68. package/dist/deploy/settings/index.js +2 -1
  69. package/dist/deploy/settings/index.js.map +1 -1
  70. package/dist/deploy/settings/package-manifest.d.ts +1 -0
  71. package/dist/deploy/settings/package-manifest.d.ts.map +1 -1
  72. package/dist/deploy/settings/package-manifest.js +7 -0
  73. package/dist/deploy/settings/package-manifest.js.map +1 -1
  74. package/dist/deploy/settings/paths.d.ts +2 -1
  75. package/dist/deploy/settings/paths.d.ts.map +1 -1
  76. package/dist/deploy/settings/paths.js +1 -1
  77. package/dist/deploy/settings/paths.js.map +1 -1
  78. package/dist/deploy/targets/flatpak-manifest.d.ts +3 -1
  79. package/dist/deploy/targets/flatpak-manifest.d.ts.map +1 -1
  80. package/dist/deploy/targets/flatpak-manifest.js +50 -4
  81. package/dist/deploy/targets/flatpak-manifest.js.map +1 -1
  82. package/dist/deploy/targets/flatpak-source.d.ts.map +1 -1
  83. package/dist/deploy/targets/flatpak-source.js +55 -1
  84. package/dist/deploy/targets/flatpak-source.js.map +1 -1
  85. package/dist/deploy/types.d.ts +22 -2
  86. package/dist/deploy/types.d.ts.map +1 -1
  87. package/dist/deploy/types.js.map +1 -1
  88. package/dist/vite-plugins/bundled-packages.d.ts +5 -0
  89. package/dist/vite-plugins/bundled-packages.d.ts.map +1 -0
  90. package/dist/vite-plugins/bundled-packages.js +63 -0
  91. package/dist/vite-plugins/bundled-packages.js.map +1 -0
  92. package/package.json +9 -9
  93. package/src/builder.ts +2 -0
  94. package/src/deploy/freedesktop/copyright.ts +153 -18
  95. package/src/deploy/node-runtime/download.ts +13 -4
  96. package/src/deploy/node-runtime/index.ts +17 -11
  97. package/src/deploy/node-runtime/license.ts +24 -0
  98. package/src/deploy/notices/collect.ts +26 -0
  99. package/src/deploy/notices/dependencies.ts +53 -0
  100. package/src/deploy/notices/gtkx.ts +92 -0
  101. package/src/deploy/notices/libraries.ts +81 -0
  102. package/src/deploy/notices/node-runtime.ts +101 -0
  103. package/src/deploy/notices/packages.ts +168 -0
  104. package/src/deploy/notices/render.ts +52 -0
  105. package/src/deploy/notices/text.ts +42 -0
  106. package/src/deploy/payload/copy-tree.ts +5 -1
  107. package/src/deploy/payload/stage.ts +40 -16
  108. package/src/deploy/run-deploy.ts +27 -5
  109. package/src/deploy/settings/extra-files.ts +21 -0
  110. package/src/deploy/settings/index.ts +2 -1
  111. package/src/deploy/settings/package-manifest.ts +10 -0
  112. package/src/deploy/settings/paths.ts +1 -1
  113. package/src/deploy/targets/flatpak-manifest.ts +69 -4
  114. package/src/deploy/targets/flatpak-source.ts +87 -1
  115. package/src/deploy/types.ts +27 -1
  116. package/src/vite-plugins/bundled-packages.ts +95 -0
  117. package/LICENSE +0 -373
@@ -12,10 +12,76 @@ const DEFAULT_RUNTIME_VERSION = "50";
12
12
  const DEFAULT_SDK = "org.gnome.Sdk";
13
13
  const DEFAULT_FINISH_ARGS = ["--share=ipc", "--socket=wayland", "--socket=fallback-x11", "--device=dri"];
14
14
  const DEFAULT_CLEANUP = ["/include", "/share/pkgconfig", "*.la", "*.a"];
15
+ const DISPLAY_SOCKETS = ["fallback-x11", "wayland", "x11"];
16
+ const SOCKET_ARG = "--socket=";
17
+ const NO_SOCKET_ARG = "--nosocket=";
18
+
19
+ const NEGATED_ARGS: [string, string][] = [
20
+ ["--device=", "--nodevice="],
21
+ ["--share=", "--unshare="],
22
+ [SOCKET_ARG, NO_SOCKET_ARG],
23
+ ];
15
24
 
16
25
  const branchFor = (settings: DeploySettings): string => settings.deploy.flatpak?.branch ?? DEFAULT_BRANCH;
17
26
  const isSourceMode = (settings: DeploySettings): boolean => settings.deploy.flatpak?.mode === "source";
18
27
 
28
+ const negationFor = (arg: string): string | null => {
29
+ const negated = NEGATED_ARGS.find(([prefix]) => arg.startsWith(prefix));
30
+
31
+ if (negated === undefined) {
32
+ return null;
33
+ }
34
+
35
+ const [prefix, negation] = negated;
36
+
37
+ return `${negation}${arg.slice(prefix.length)}`;
38
+ };
39
+
40
+ const mergeArgs = (defaults: string[], overrides: string[]): string[] => {
41
+ const kept = defaults.filter((arg) => {
42
+ const negation = negationFor(arg);
43
+
44
+ return negation === null || !overrides.includes(negation);
45
+ });
46
+
47
+ return [...new Set([...kept, ...overrides])];
48
+ };
49
+
50
+ const finishArgsFor = (settings: DeploySettings): string[] =>
51
+ mergeArgs(DEFAULT_FINISH_ARGS, settings.deploy.flatpak?.finishArgs ?? []);
52
+
53
+ const cleanupFor = (settings: DeploySettings): string[] => {
54
+ const cleanup = settings.deploy.flatpak?.cleanup;
55
+
56
+ if (cleanup === undefined) {
57
+ return DEFAULT_CLEANUP;
58
+ }
59
+
60
+ return cleanup.length === 0 ? [] : mergeArgs(DEFAULT_CLEANUP, cleanup);
61
+ };
62
+
63
+ const grantedSockets = (args: string[]): Set<string> => {
64
+ const granted: Set<string> = new Set();
65
+
66
+ for (const arg of args) {
67
+ if (arg.startsWith(SOCKET_ARG)) {
68
+ granted.add(arg.slice(SOCKET_ARG.length));
69
+ }
70
+
71
+ if (arg.startsWith(NO_SOCKET_ARG)) {
72
+ granted.delete(arg.slice(NO_SOCKET_ARG.length));
73
+ }
74
+ }
75
+
76
+ return granted;
77
+ };
78
+
79
+ const hasDisplaySocket = (args: string[]): boolean => {
80
+ const granted = grantedSockets(args);
81
+
82
+ return DISPLAY_SOCKETS.some((socket) => granted.has(socket));
83
+ };
84
+
19
85
  const sdkExtensionsFor = (settings: DeploySettings): string[] => {
20
86
  const flatpak = settings.deploy.flatpak ?? {};
21
87
  const nodeExtension = isSourceMode(settings) ? [nodeExtensionFor(settings)] : [];
@@ -46,16 +112,15 @@ const runtimeKeys = (settings: DeploySettings): FlatpakManifest => {
46
112
 
47
113
  const renderFlatpakManifest = (payload: DeployPayload): FlatpakManifest => {
48
114
  const settings = payload.settings;
49
- const flatpak = settings.deploy.flatpak ?? {};
50
115
 
51
116
  return {
52
117
  id: settings.applicationId,
53
118
  ...runtimeKeys(settings),
54
119
  command: settings.binaryName,
55
- "finish-args": flatpak.finishArgs ?? DEFAULT_FINISH_ARGS,
56
- cleanup: flatpak.cleanup ?? DEFAULT_CLEANUP,
120
+ "finish-args": finishArgsFor(settings),
121
+ cleanup: cleanupFor(settings),
57
122
  modules: modulesFor(payload),
58
123
  };
59
124
  };
60
125
 
61
- export { branchFor, renderFlatpakManifest };
126
+ export { branchFor, finishArgsFor, hasDisplaySocket, renderFlatpakManifest };
@@ -1,13 +1,19 @@
1
- import { posix, relative } from "node:path";
1
+ import { existsSync, realpathSync } from "node:fs";
2
+ import { posix, relative, resolve } from "node:path";
2
3
  import type { DeployPayload, DeploySettings } from "../types.js";
3
4
  import { listFilesRecursive } from "../../internal/list-files.js";
4
5
  import { BUNDLE_FILENAME } from "../../vite-plugins/esm-extension.js";
5
6
  import { renderDbusService } from "../freedesktop/dbus-service.js";
6
7
  import { renderDesktopEntry } from "../freedesktop/desktop-entry.js";
7
8
  import { renderMetainfo } from "../freedesktop/metainfo.js";
9
+ import { renderMimePackage } from "../freedesktop/mime-package.js";
8
10
  import { optional } from "../nfpm/optional.js";
11
+ import { renderNotices } from "../notices/render.js";
12
+ import { executableModeFor } from "../payload/copy-tree.js";
9
13
  import { iconPathFor } from "../payload/icons.js";
10
14
  import { renderLauncher } from "../payload/launcher.js";
15
+ import { licenseDestination, nodeLicenseDestination, NOTICES_FILENAME, noticesDestination } from "../payload/stage.js";
16
+ import { isInside } from "../settings/paths.js";
11
17
  import { pnpmPathFor, type PnpmPin, pnpmSources } from "./flatpak-pnpm.js";
12
18
  import { DESTINATION, type FlatpakModule, postInstallCommands, validationCommands } from "./flatpak-prebuilt.js";
13
19
  import {
@@ -20,6 +26,12 @@ import {
20
26
  resolveGitSource,
21
27
  } from "./flatpak-sources.js";
22
28
 
29
+ type InstalledFile = {
30
+ destination: string;
31
+ source: string;
32
+ resolved: string;
33
+ };
34
+
23
35
  const LAUNCHER_FILENAME = "launcher.sh";
24
36
  const MODULE_BUILD_ROOT = "/run/build";
25
37
  const RUNTIME_PREFIX = "/app";
@@ -27,6 +39,7 @@ const NPM_CACHE_DIR = "flatpak-node/npm-cache";
27
39
  const YARN_MIRROR_DIR = "flatpak-node/yarn-mirror";
28
40
  const PLAIN_ARGUMENT = /^[\w./-]+$/;
29
41
  const QUOTE_ESCAPE = String.raw`'\''`;
42
+ const OCTAL_RADIX = 8;
30
43
 
31
44
  const inlineSource = (fileName: string, contents: string): FlatpakModule => ({
32
45
  type: "inline",
@@ -74,6 +87,21 @@ const activationInstallCommands = (settings: DeploySettings): string[] =>
74
87
  )]
75
88
  : [];
76
89
 
90
+ const mimeSources = (settings: DeploySettings): FlatpakModule[] => {
91
+ const contents = renderMimePackage(settings);
92
+
93
+ return contents === null ? [] : [inlineSource(`${settings.applicationId}.xml`, contents)];
94
+ };
95
+
96
+ const mimeInstallCommands = (settings: DeploySettings): string[] =>
97
+ settings.fileAssociations.length === 0
98
+ ? []
99
+ : [installCommand(
100
+ `${settings.applicationId}.xml`,
101
+ `${DESTINATION}/share/mime/packages/${settings.applicationId}.xml`,
102
+ "m644",
103
+ )];
104
+
77
105
  const metadataInstallCommands = (settings: DeploySettings): string[] => [
78
106
  installCommand(
79
107
  `${settings.applicationId}.desktop`,
@@ -86,6 +114,7 @@ const metadataInstallCommands = (settings: DeploySettings): string[] => [
86
114
  "m644",
87
115
  ),
88
116
  ...activationInstallCommands(settings),
117
+ ...mimeInstallCommands(settings),
89
118
  ];
90
119
 
91
120
  const schemaInstallCommands = (settings: DeploySettings): string[] =>
@@ -120,6 +149,58 @@ const iconInstallCommands = (settings: DeploySettings): string[] => {
120
149
  return listFilesRecursive(iconsDir).map((icon) => iconInstallCommand(settings, icon.absPath, icon.rel));
121
150
  };
122
151
 
152
+ const assertInsideProject = (settings: DeploySettings, installed: InstalledFile): void => {
153
+ const { resolved } = installed;
154
+ const target = existsSync(resolved) ? realpathSync(resolved) : resolved;
155
+
156
+ if (isInside(settings.paths.root, target)) {
157
+ return;
158
+ }
159
+
160
+ throw new Error(
161
+ `Cannot install "${installed.destination}" from "${installed.source}": a source-mode manifest builds from ` +
162
+ `your git checkout, so every file it installs has to live inside ${settings.paths.root} and be committed. ` +
163
+ "Move it into the project, or drop it from the source build.",
164
+ );
165
+ };
166
+
167
+ const licenseInstallCommands = (settings: DeploySettings): string[] => {
168
+ const licenseFile = settings.paths.licenseFile;
169
+
170
+ if (licenseFile === null) {
171
+ return [];
172
+ }
173
+
174
+ const destination = licenseDestination(settings);
175
+ assertInsideProject(settings, { destination, source: licenseFile, resolved: licenseFile });
176
+ const target = `${DESTINATION}/${shellArgument(destination)}`;
177
+
178
+ return [installCommand(projectRelative(settings, licenseFile), target, "m644")];
179
+ };
180
+
181
+ const noticesInstallCommands = (settings: DeploySettings): string[] => {
182
+ const extensionLicense = `${nodeExtensionPathFor(settings)}/LICENSE`;
183
+ const nodeTarget = `${DESTINATION}/${shellArgument(nodeLicenseDestination(settings))}`;
184
+
185
+ return [
186
+ installCommand(NOTICES_FILENAME, `${DESTINATION}/${shellArgument(noticesDestination(settings))}`, "m644"),
187
+ `test ! -f ${extensionLicense} || ${installCommand(extensionLicense, nodeTarget, "m644")}`,
188
+ ];
189
+ };
190
+
191
+ const extraFileInstallCommands = (settings: DeploySettings): string[] =>
192
+ settings.extraFiles.map((file) => {
193
+ const resolved = resolve(settings.paths.root, file.source);
194
+ assertInsideProject(settings, { destination: file.destination, source: file.source, resolved });
195
+ const mode = file.mode ?? executableModeFor(resolved);
196
+
197
+ return installCommand(
198
+ projectRelative(settings, resolved),
199
+ `${DESTINATION}/${shellArgument(file.destination)}`,
200
+ `m${mode.toString(OCTAL_RADIX)}`,
201
+ );
202
+ });
203
+
123
204
  const moduleDirFor = (settings: DeploySettings): string => `${MODULE_BUILD_ROOT}/${settings.binaryName}`;
124
205
 
125
206
  const offlineEnvFor = (manager: PackageManager, settings: DeploySettings): Record<string, string> => {
@@ -161,7 +242,9 @@ const flatpakSourceModule = (payload: DeployPayload): FlatpakModule => {
161
242
  GENERATED_SOURCES,
162
243
  inlineSource(`${settings.applicationId}.desktop`, renderDesktopEntry(settings)),
163
244
  inlineSource(`${settings.applicationId}.metainfo.xml`, renderMetainfo(settings)),
245
+ ...mimeSources(settings),
164
246
  inlineSource(LAUNCHER_FILENAME, renderLauncher(settings)),
247
+ inlineSource(NOTICES_FILENAME, renderNotices(settings, payload.notices)),
165
248
  ...activationSource(settings),
166
249
  ],
167
250
  "build-commands": [
@@ -171,6 +254,9 @@ const flatpakSourceModule = (payload: DeployPayload): FlatpakModule => {
171
254
  ...metadataInstallCommands(settings),
172
255
  ...schemaInstallCommands(settings),
173
256
  ...iconInstallCommands(settings),
257
+ ...licenseInstallCommands(settings),
258
+ ...noticesInstallCommands(settings),
259
+ ...extraFileInstallCommands(settings),
174
260
  ...(settings.deploy.flatpak?.buildCommands ?? []),
175
261
  ...validationCommands(settings),
176
262
  ],
@@ -19,11 +19,27 @@ type StagedFile = {
19
19
 
20
20
  type NodeRuntime = {
21
21
  path: string;
22
+ licenseFile: string | null;
22
23
  version: string;
23
24
  glibcFloor: string | null;
24
25
  isStripped: boolean;
25
26
  };
26
27
 
28
+ type Notice = {
29
+ subject: string;
30
+ license: string;
31
+ source: string | null;
32
+ copyright: string[];
33
+ text: string | null;
34
+ };
35
+
36
+ type NoticeSection = {
37
+ title: string;
38
+ files: string[];
39
+ summary: string[];
40
+ notices: Notice[];
41
+ };
42
+
27
43
  type DeployDeveloper = {
28
44
  id: string | null;
29
45
  name: string;
@@ -51,6 +67,12 @@ type DeployFileAssociation = {
51
67
  description: string | null;
52
68
  };
53
69
 
70
+ type DeployExtraFile = {
71
+ destination: string;
72
+ source: string;
73
+ mode: number | null;
74
+ };
75
+
54
76
  type DeployDesktopAction = {
55
77
  id: string;
56
78
  name: string;
@@ -118,7 +140,7 @@ type DeploySettings = {
118
140
  desktopActions: DeployDesktopAction[];
119
141
  desktopEntry: Record<string, string>;
120
142
  isDbusActivatable: boolean;
121
- extraFiles: Record<string, string>;
143
+ extraFiles: DeployExtraFile[];
122
144
  versions: DeployVersions;
123
145
  arch: DeployArch;
124
146
  paths: DeployPaths;
@@ -130,6 +152,7 @@ type DeployPayload = {
130
152
  settings: DeploySettings;
131
153
  node: NodeRuntime | null;
132
154
  stage: StagedFile[];
155
+ notices: NoticeSection[];
133
156
  overlays: Record<DeployTargetName, StagedFile[]>;
134
157
  };
135
158
 
@@ -157,6 +180,7 @@ export type {
157
180
  DeployConfig,
158
181
  DeployDesktopAction,
159
182
  DeployDeveloper,
183
+ DeployExtraFile,
160
184
  DeployFileAssociation,
161
185
  DeployManifest,
162
186
  DeployPaths,
@@ -169,6 +193,8 @@ export type {
169
193
  DeployTool,
170
194
  DeployVersions,
171
195
  NodeRuntime,
196
+ Notice,
197
+ NoticeSection,
172
198
  PackageFamily,
173
199
  StagedFile,
174
200
  };
@@ -0,0 +1,95 @@
1
+ import type { Plugin } from "vite";
2
+ import { isRecord, sortStringsBy } from "@gtkx/utils";
3
+ import { readFileSync } from "node:fs";
4
+ import { dirname, join, relative, resolve } from "node:path";
5
+ import { stripQuery } from "./strip-query.js";
6
+
7
+ type PackageIdentity = {
8
+ name: string;
9
+ version: string | null;
10
+ };
11
+
12
+ type RecordedPackage = PackageIdentity & {
13
+ dir: string;
14
+ };
15
+
16
+ type PackagesState = {
17
+ outDir: string;
18
+ };
19
+
20
+ const BUNDLED_PACKAGES_FILENAME = "gtkx-packages.json";
21
+ const MANIFEST_FILENAME = "package.json";
22
+ const DEFAULT_OUT_DIR = "dist";
23
+ const JSON_INDENT = 4;
24
+
25
+ const identityIn = (dir: string): PackageIdentity | null => {
26
+ try {
27
+ const parsed: unknown = JSON.parse(readFileSync(join(dir, MANIFEST_FILENAME), "utf8"));
28
+
29
+ if (!isRecord(parsed) || typeof parsed.name !== "string") {
30
+ return null;
31
+ }
32
+
33
+ return { name: parsed.name, version: typeof parsed.version === "string" ? parsed.version : null };
34
+ } catch {
35
+ return null;
36
+ }
37
+ };
38
+
39
+ const packageIn = (dir: string): RecordedPackage | null => {
40
+ const identity = identityIn(dir);
41
+
42
+ if (identity !== null) {
43
+ return { ...identity, dir };
44
+ }
45
+
46
+ const parent = dirname(dir);
47
+
48
+ return parent === dir ? null : packageIn(parent);
49
+ };
50
+
51
+ const packageForModule = (id: string): RecordedPackage | null => {
52
+ const path = stripQuery(id);
53
+
54
+ return path.startsWith("/") ? packageIn(dirname(path)) : null;
55
+ };
56
+
57
+ const keyFor = (entry: RecordedPackage): string => `${entry.name}@${entry.version ?? ""}`;
58
+
59
+ const relativeTo = (outDir: string, entry: RecordedPackage): RecordedPackage => ({
60
+ ...entry,
61
+ dir: relative(outDir, entry.dir),
62
+ });
63
+
64
+ const renderPackages = (outDir: string, ids: string[]): string => {
65
+ const found = ids.map((id) => packageForModule(id)).filter((entry) => entry !== null);
66
+ const unique: Map<string, RecordedPackage> = new Map(found.map((entry) => [keyFor(entry), entry]));
67
+ const packages = sortStringsBy(unique.values(), keyFor).map((entry) => relativeTo(outDir, entry));
68
+
69
+ return `${JSON.stringify({ packages }, null, JSON_INDENT)}\n`;
70
+ };
71
+
72
+ function gtkxBundledPackages(root: string): Plugin {
73
+ const state: PackagesState = { outDir: join(root, DEFAULT_OUT_DIR) };
74
+
75
+ return {
76
+ name: "gtkx:bundled-packages",
77
+ apply: "build",
78
+
79
+ configResolved(config) {
80
+ state.outDir = resolve(config.root, config.build.outDir);
81
+ },
82
+
83
+ generateBundle(_options, bundle) {
84
+ const ids = Object.values(bundle).flatMap((output) => (output.type === "chunk" ? output.moduleIds : []));
85
+
86
+ this.emitFile({
87
+ type: "asset",
88
+ fileName: BUNDLED_PACKAGES_FILENAME,
89
+ source: renderPackages(state.outDir, ids),
90
+ });
91
+ },
92
+ };
93
+ }
94
+
95
+ export { BUNDLED_PACKAGES_FILENAME, gtkxBundledPackages };