@gtkx/cli 2.0.0-beta.8 → 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 (42) hide show
  1. package/dist/deploy/depends.d.ts.map +1 -1
  2. package/dist/deploy/depends.js +24 -2
  3. package/dist/deploy/depends.js.map +1 -1
  4. package/dist/deploy/node-runtime/guard.d.ts.map +1 -1
  5. package/dist/deploy/node-runtime/guard.js +7 -14
  6. package/dist/deploy/node-runtime/guard.js.map +1 -1
  7. package/dist/deploy/node-runtime/index.d.ts +2 -1
  8. package/dist/deploy/node-runtime/index.d.ts.map +1 -1
  9. package/dist/deploy/node-runtime/index.js +15 -4
  10. package/dist/deploy/node-runtime/index.js.map +1 -1
  11. package/dist/deploy/node-runtime/sonames.d.ts +5 -0
  12. package/dist/deploy/node-runtime/sonames.d.ts.map +1 -0
  13. package/dist/deploy/node-runtime/sonames.js +13 -0
  14. package/dist/deploy/node-runtime/sonames.js.map +1 -0
  15. package/dist/deploy/run-deploy.d.ts.map +1 -1
  16. package/dist/deploy/run-deploy.js +4 -1
  17. package/dist/deploy/run-deploy.js.map +1 -1
  18. package/dist/internal/icon-staging.d.ts +3 -0
  19. package/dist/internal/icon-staging.d.ts.map +1 -0
  20. package/dist/internal/icon-staging.js +40 -0
  21. package/dist/internal/icon-staging.js.map +1 -0
  22. package/dist/vite-plugins/icon-worker-env.d.ts +4 -0
  23. package/dist/vite-plugins/icon-worker-env.d.ts.map +1 -0
  24. package/dist/vite-plugins/icon-worker-env.js +23 -0
  25. package/dist/vite-plugins/icon-worker-env.js.map +1 -0
  26. package/dist/vite-plugins/worker.d.ts.map +1 -1
  27. package/dist/vite-plugins/worker.js +21 -13
  28. package/dist/vite-plugins/worker.js.map +1 -1
  29. package/dist/vitest-plugin.d.ts +2 -2
  30. package/dist/vitest-plugin.d.ts.map +1 -1
  31. package/dist/vitest-plugin.js +4 -2
  32. package/dist/vitest-plugin.js.map +1 -1
  33. package/package.json +10 -10
  34. package/src/deploy/depends.ts +30 -3
  35. package/src/deploy/node-runtime/guard.ts +10 -14
  36. package/src/deploy/node-runtime/index.ts +19 -4
  37. package/src/deploy/node-runtime/sonames.ts +17 -0
  38. package/src/deploy/run-deploy.ts +6 -1
  39. package/src/internal/icon-staging.ts +58 -0
  40. package/src/vite-plugins/icon-worker-env.ts +36 -0
  41. package/src/vite-plugins/worker.ts +31 -15
  42. package/src/vitest-plugin.ts +4 -2
@@ -0,0 +1,23 @@
1
+ import { createConfigLoader, viteProjectRoot } from "@gtkx/config/internal";
2
+ import { stageProjectIcons } from "../internal/icon-staging.js";
3
+ import { prependXdgDataDir } from "../internal/xdg-data-dirs.js";
4
+ function gtkxIconWorkerEnv(configFile) {
5
+ const loadConfig = createConfigLoader({ configFile });
6
+ return {
7
+ name: "gtkx:icon-worker-env",
8
+ enforce: "pre",
9
+ async config(config) {
10
+ const loaded = await loadConfig.load(viteProjectRoot(config));
11
+ const shareDir = stageProjectIcons(loaded.root, loaded.config.applicationId, loaded.config.applicationIcon);
12
+ if (shareDir === null) {
13
+ return;
14
+ }
15
+ const existing = config.test?.env?.XDG_DATA_DIRS ?? process.env.XDG_DATA_DIRS;
16
+ return {
17
+ test: { env: { XDG_DATA_DIRS: prependXdgDataDir(shareDir, existing) } },
18
+ };
19
+ },
20
+ };
21
+ }
22
+ export { gtkxIconWorkerEnv };
23
+ //# sourceMappingURL=icon-worker-env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icon-worker-env.js","sourceRoot":"","sources":["../../src/vite-plugins/icon-worker-env.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE,SAAS,iBAAiB,CAAC,UAAmB;IAC1C,MAAM,UAAU,GAAG,kBAAkB,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC;IAEtD,OAAO;QACH,IAAI,EAAE,sBAAsB;QAC5B,OAAO,EAAE,KAAK;QAEd,KAAK,CAAC,MAAM,CAAC,MAAkB;YAC3B,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;YAE9D,MAAM,QAAQ,GAAG,iBAAiB,CAC9B,MAAM,CAAC,IAAI,EACX,MAAM,CAAC,MAAM,CAAC,aAAa,EAC3B,MAAM,CAAC,MAAM,CAAC,eAAe,CAChC,CAAC;YAEF,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;gBACpB,OAAO;YACX,CAAC;YAED,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,aAAa,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;YAE9E,OAAO;gBACH,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE,aAAa,EAAE,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,EAAE;aAC1E,CAAC;QACN,CAAC;KACJ,CAAC;AACN,CAAC;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC","sourcesContent":["import type { UserConfig } from \"vite\";\nimport type { Plugin } from \"vitest/config\";\nimport { createConfigLoader, viteProjectRoot } from \"@gtkx/config/internal\";\nimport { stageProjectIcons } from \"../internal/icon-staging.js\";\nimport { prependXdgDataDir } from \"../internal/xdg-data-dirs.js\";\n\nfunction gtkxIconWorkerEnv(configFile?: string): Plugin {\n const loadConfig = createConfigLoader({ configFile });\n\n return {\n name: \"gtkx:icon-worker-env\",\n enforce: \"pre\",\n\n async config(config: UserConfig) {\n const loaded = await loadConfig.load(viteProjectRoot(config));\n\n const shareDir = stageProjectIcons(\n loaded.root,\n loaded.config.applicationId,\n loaded.config.applicationIcon,\n );\n\n if (shareDir === null) {\n return;\n }\n\n const existing = config.test?.env?.XDG_DATA_DIRS ?? process.env.XDG_DATA_DIRS;\n\n return {\n test: { env: { XDG_DATA_DIRS: prependXdgDataDir(shareDir, existing) } },\n };\n },\n };\n}\n\nexport { gtkxIconWorkerEnv };\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/vite-plugins/worker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAU,MAAM,MAAM,CAAC;AA+N3C,iBAAS,UAAU,IAAI,MAAM,CAW5B;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/vite-plugins/worker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAU,MAAM,MAAM,CAAC;AA2O3C,iBAAS,UAAU,IAAI,MAAM,CAe5B;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
@@ -33,9 +33,10 @@ const nonCodeRanges = (code) => code
33
33
  const isCodePosition = (scan, index) => scan.nonCode.every((range) => index < range.start || index >= range.end);
34
34
  const isWorkerConstruction = (code, matchIndex) => WORKER_CONSTRUCTION.test(code.slice(0, matchIndex));
35
35
  const hasWorkerCandidate = (code) => code.includes("import.meta.url") && code.includes("Worker");
36
- const workerUrlExpression = (fileName) => {
37
- const specifier = JSON.stringify(`./${fileName}`);
38
- return `new URL(${specifier}, import.meta.url)`;
36
+ const workerUrlReference = (referenceId) => `import.meta.ROLLUP_FILE_URL_${referenceId}`;
37
+ const workerUrlExpression = (relativePath) => {
38
+ const specifier = relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
39
+ return `new URL(${JSON.stringify(specifier)}, import.meta.url)`;
39
40
  };
40
41
  const inlineWorkerExample = (specifier) => `new Worker(new URL(${JSON.stringify(specifier)}, import.meta.url))`;
41
42
  const hoistedWorkerError = (scan, hoisted) => new Error(`${stripQuery(scan.id)}: the worker URL for ${JSON.stringify(hoisted.specifier)} is bound to ` +
@@ -59,14 +60,18 @@ const suggestionFor = async (emit, scan, specifier) => {
59
60
  return null;
60
61
  };
61
62
  const claimWorker = (emit, resolvedId) => {
62
- const existing = emit.emitted.get(resolvedId);
63
+ const existing = emit.references.byModule.get(resolvedId);
63
64
  if (existing !== undefined) {
64
65
  return existing;
65
66
  }
66
- const fileName = workerFileName(resolvedId);
67
- emit.emitted.set(resolvedId, fileName);
68
- emit.context.emitFile({ type: "chunk", id: resolvedId, fileName });
69
- return fileName;
67
+ const referenceId = emit.context.emitFile({
68
+ type: "chunk",
69
+ id: resolvedId,
70
+ fileName: workerFileName(resolvedId),
71
+ });
72
+ emit.references.byModule.set(resolvedId, referenceId);
73
+ emit.references.claimed.add(referenceId);
74
+ return referenceId;
70
75
  };
71
76
  const isRewritableUrl = (scan, match) => isCodePosition(scan, match.index) && isWorkerConstruction(scan.code, match.index);
72
77
  const replacementFor = async (emit, scan, match) => {
@@ -78,7 +83,7 @@ const replacementFor = async (emit, scan, match) => {
78
83
  if (resolved === null) {
79
84
  throw unresolvedWorkerError(scan, specifier, await suggestionFor(emit, scan, specifier));
80
85
  }
81
- return { start: match.index, end: match.index + match[0].length, fileName: claimWorker(emit, resolved.id) };
86
+ return { start: match.index, end: match.index + match[0].length, referenceId: claimWorker(emit, resolved.id) };
82
87
  };
83
88
  const collectReplacements = async (emit, scan) => {
84
89
  const pending = scan.code.matchAll(WORKER_URL).map((match) => replacementFor(emit, scan, match)).toArray();
@@ -105,8 +110,8 @@ const findHoistedWorkerUrl = (scan) => scan.code
105
110
  const applyReplacements = (code, replacements) => {
106
111
  let output = "";
107
112
  let cursor = 0;
108
- for (const { start, end, fileName } of replacements) {
109
- output += code.slice(cursor, start) + workerUrlExpression(fileName);
113
+ for (const { start, end, referenceId } of replacements) {
114
+ output += code.slice(cursor, start) + workerUrlReference(referenceId);
110
115
  cursor = end;
111
116
  }
112
117
  return output + code.slice(cursor);
@@ -124,12 +129,15 @@ const transformWorkerUrls = async (emit, code, id) => {
124
129
  return replacements.length === 0 ? null : { code: applyReplacements(code, replacements), map: null };
125
130
  };
126
131
  function gtkxWorker() {
127
- const emitted = new Map();
132
+ const references = { byModule: new Map(), claimed: new Set() };
128
133
  return {
129
134
  name: "gtkx:worker",
130
135
  apply: "build",
131
136
  transform(code, id) {
132
- return transformWorkerUrls({ context: this, emitted }, code, id);
137
+ return transformWorkerUrls({ context: this, references }, code, id);
138
+ },
139
+ resolveFileUrl({ referenceId, relativePath }) {
140
+ return references.claimed.has(referenceId) ? workerUrlExpression(relativePath) : null;
133
141
  },
134
142
  };
135
143
  }
@@ -1 +1 @@
1
- {"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/vite-plugins/worker.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Plugin, Rollup } from \"vite\";\nimport { createHash } from \"node:crypto\";\nimport { basename, extname } from \"node:path\";\nimport { ESM_EXTENSION } from \"./esm-extension.js\";\nimport { stripQuery } from \"./strip-query.js\";\n\ntype WorkerReplacement = {\n start: number;\n end: number;\n fileName: string;\n};\n\ntype EmitContext = {\n context: Rollup.PluginContext;\n emitted: Map<string, string>;\n};\n\ntype SourceRange = {\n start: number;\n end: number;\n};\n\ntype ScanContext = {\n code: string;\n id: string;\n nonCode: SourceRange[];\n};\n\ntype HoistedWorkerUrl = {\n name: string;\n specifier: string;\n};\n\ntype TransformResult = {\n code: string;\n map: null;\n} | null;\n\nconst RELATIVE_SPECIFIER = String.raw`(?<quote>[\"'])(?<specifier>\\.{1,2}\\/[^\"']+)\\k<quote>`;\nconst URL_CALL = String.raw`new\\s+URL\\s*\\(\\s*${RELATIVE_SPECIFIER}\\s*,\\s*import\\.meta\\.url\\s*\\)`;\nconst URL_DECLARATION = String.raw`(?:const|let|var)\\s+(?<name>[A-Za-z_$][\\w$]*)\\s*(?::[^=;\\n]+)?=\\s*`;\nconst BLOCK_COMMENT = String.raw`\\/\\*[\\s\\S]*?\\*\\/`;\nconst LINE_COMMENT = String.raw`\\/\\/[^\\n]*`;\nconst DOUBLE_QUOTED = String.raw`\"(?:[^\"\\\\\\n]|\\\\[\\s\\S])*\"`;\nconst SINGLE_QUOTED = String.raw`'(?:[^'\\\\\\n]|\\\\[\\s\\S])*'`;\nconst TEMPLATE_LITERAL = String.raw`\\`(?:[^\\`\\\\]|\\\\[\\s\\S])*\\``;\nconst WORKER_URL = new RegExp(URL_CALL, \"g\");\nconst HOISTED_WORKER_URL = new RegExp(URL_DECLARATION + URL_CALL, \"g\");\nconst WORKER_CONSTRUCTION = /new\\s+(?:\\w+\\.)?Worker\\s*\\(\\s*$/;\n\nconst NON_CODE = new RegExp(\n [BLOCK_COMMENT, LINE_COMMENT, DOUBLE_QUOTED, SINGLE_QUOTED, TEMPLATE_LITERAL].join(\"|\"),\n \"g\",\n);\n\nconst WORKER_DIR = \"workers\";\n\nconst SUGGESTED_EXTENSIONS: Record<string, string[]> = {\n \".js\": [\".ts\", \".tsx\"],\n \".jsx\": [\".tsx\"],\n \".mjs\": [\".mts\"],\n};\n\nconst workerFileName = (id: string): string => {\n const path = stripQuery(id);\n const stem = basename(path, extname(path));\n const digest = createHash(\"sha256\").update(id).digest(\"hex\").slice(0, 8);\n\n return `${WORKER_DIR}/${stem}-${digest}${ESM_EXTENSION}`;\n};\n\nconst nonCodeRanges = (code: string): SourceRange[] =>\n code\n .matchAll(NON_CODE)\n .map((match) => ({ start: match.index, end: match.index + match[0].length }))\n .toArray();\n\nconst isCodePosition = (scan: ScanContext, index: number): boolean =>\n scan.nonCode.every((range) => index < range.start || index >= range.end);\n\nconst isWorkerConstruction = (code: string, matchIndex: number): boolean =>\n WORKER_CONSTRUCTION.test(code.slice(0, matchIndex));\n\nconst hasWorkerCandidate = (code: string): boolean => code.includes(\"import.meta.url\") && code.includes(\"Worker\");\n\nconst workerUrlExpression = (fileName: string): string => {\n const specifier = JSON.stringify(`./${fileName}`);\n\n return `new URL(${specifier}, import.meta.url)`;\n};\n\nconst inlineWorkerExample = (specifier: string): string =>\n `new Worker(new URL(${JSON.stringify(specifier)}, import.meta.url))`;\n\nconst hoistedWorkerError = (scan: ScanContext, hoisted: HoistedWorkerUrl): Error =>\n new Error(\n `${stripQuery(scan.id)}: the worker URL for ${JSON.stringify(hoisted.specifier)} is bound to ` +\n `\"${hoisted.name}\" before it reaches \"new Worker\". gtkx build emits a worker chunk only for a URL ` +\n `written inside the construction. Write ${inlineWorkerExample(hoisted.specifier)} instead.`,\n );\n\nconst unresolvedWorkerError = (scan: ScanContext, specifier: string, suggestion: string | null): Error =>\n new Error(\n `${stripQuery(scan.id)}: the worker specifier ${JSON.stringify(specifier)} does not resolve to a module, ` +\n \"so no worker chunk can be emitted. \" +\n (suggestion === null\n ? \"Correct the path so it names the worker source file as it exists on disk.\"\n : `Write ${inlineWorkerExample(suggestion)} instead.`),\n );\n\nconst suggestionFor = async (emit: EmitContext, scan: ScanContext, specifier: string): Promise<string | null> => {\n const extension = extname(specifier);\n const stem = specifier.slice(0, specifier.length - extension.length);\n const candidates = SUGGESTED_EXTENSIONS[extension] ?? [];\n\n for (const candidate of candidates) {\n const resolved = await emit.context.resolve(stem + candidate, scan.id);\n\n if (resolved !== null) {\n return stem + candidate;\n }\n }\n\n return null;\n};\n\nconst claimWorker = (emit: EmitContext, resolvedId: string): string => {\n const existing = emit.emitted.get(resolvedId);\n\n if (existing !== undefined) {\n return existing;\n }\n\n const fileName = workerFileName(resolvedId);\n emit.emitted.set(resolvedId, fileName);\n emit.context.emitFile({ type: \"chunk\", id: resolvedId, fileName });\n\n return fileName;\n};\n\nconst isRewritableUrl = (scan: ScanContext, match: RegExpExecArray): boolean =>\n isCodePosition(scan, match.index) && isWorkerConstruction(scan.code, match.index);\n\nconst replacementFor = async (\n emit: EmitContext,\n scan: ScanContext,\n match: RegExpExecArray,\n): Promise<WorkerReplacement | null> => {\n const specifier = match.groups?.specifier;\n\n if (specifier === undefined || !isRewritableUrl(scan, match)) {\n return null;\n }\n\n const resolved = await emit.context.resolve(specifier, scan.id);\n\n if (resolved === null) {\n throw unresolvedWorkerError(scan, specifier, await suggestionFor(emit, scan, specifier));\n }\n\n return { start: match.index, end: match.index + match[0].length, fileName: claimWorker(emit, resolved.id) };\n};\n\nconst collectReplacements = async (emit: EmitContext, scan: ScanContext): Promise<WorkerReplacement[]> => {\n const pending = scan.code.matchAll(WORKER_URL).map((match) => replacementFor(emit, scan, match)).toArray();\n const found = await Promise.all(pending);\n\n return found.filter((entry) => entry !== null);\n};\n\nconst hasWorkerArgument = (scan: ScanContext, name: string): boolean => {\n const escaped = name.replaceAll(\"$\", String.raw`\\$`);\n const pattern = new RegExp(String.raw`new\\s+(?:\\w+\\.)?Worker\\s*\\(\\s*${escaped}\\s*[,)]`, \"g\");\n\n return scan.code.matchAll(pattern).some((match) => isCodePosition(scan, match.index));\n};\n\nconst hoistedUrlFor = (scan: ScanContext, match: RegExpExecArray): HoistedWorkerUrl | null => {\n const name = match.groups?.name;\n const specifier = match.groups?.specifier;\n\n if (name === undefined || specifier === undefined || !isCodePosition(scan, match.index)) {\n return null;\n }\n\n return { name, specifier };\n};\n\nconst findHoistedWorkerUrl = (scan: ScanContext): HoistedWorkerUrl | null =>\n scan.code\n .matchAll(HOISTED_WORKER_URL)\n .map((match) => hoistedUrlFor(scan, match))\n .find((entry) => entry !== null && hasWorkerArgument(scan, entry.name)) ?? null;\n\nconst applyReplacements = (code: string, replacements: WorkerReplacement[]): string => {\n let output = \"\";\n let cursor = 0;\n\n for (const { start, end, fileName } of replacements) {\n output += code.slice(cursor, start) + workerUrlExpression(fileName);\n cursor = end;\n }\n\n return output + code.slice(cursor);\n};\n\nconst transformWorkerUrls = async (emit: EmitContext, code: string, id: string): Promise<TransformResult> => {\n if (!hasWorkerCandidate(code)) {\n return null;\n }\n\n const scan: ScanContext = { code, id, nonCode: nonCodeRanges(code) };\n const hoisted = findHoistedWorkerUrl(scan);\n\n if (hoisted !== null) {\n throw hoistedWorkerError(scan, hoisted);\n }\n\n const replacements = await collectReplacements(emit, scan);\n\n return replacements.length === 0 ? null : { code: applyReplacements(code, replacements), map: null };\n};\n\nfunction gtkxWorker(): Plugin {\n const emitted: Map<string, string> = new Map();\n\n return {\n name: \"gtkx:worker\",\n apply: \"build\",\n\n transform(code, id) {\n return transformWorkerUrls({ context: this, emitted }, code, id);\n },\n };\n}\n\nexport { gtkxWorker };\n"]}
1
+ {"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/vite-plugins/worker.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAuC9C,MAAM,kBAAkB,GAAG,MAAM,CAAC,GAAG,CAAA,sDAAsD,CAAC;AAC5F,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAA,oBAAoB,kBAAkB,+BAA+B,CAAC;AACjG,MAAM,eAAe,GAAG,MAAM,CAAC,GAAG,CAAA,oEAAoE,CAAC;AACvG,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAA,kBAAkB,CAAC;AACnD,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAA,YAAY,CAAC;AAC5C,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAA,0BAA0B,CAAC;AAC3D,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAA,0BAA0B,CAAC;AAC3D,MAAM,gBAAgB,GAAG,MAAM,CAAC,GAAG,CAAA,2BAA2B,CAAC;AAC/D,MAAM,UAAU,GAAG,IAAI,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;AAC7C,MAAM,kBAAkB,GAAG,IAAI,MAAM,CAAC,eAAe,GAAG,QAAQ,EAAE,GAAG,CAAC,CAAC;AACvE,MAAM,mBAAmB,GAAG,iCAAiC,CAAC;AAE9D,MAAM,QAAQ,GAAG,IAAI,MAAM,CACvB,CAAC,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EACvF,GAAG,CACN,CAAC;AAEF,MAAM,UAAU,GAAG,SAAS,CAAC;AAE7B,MAAM,oBAAoB,GAA6B;IACnD,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,CAAC,MAAM,CAAC;IAChB,MAAM,EAAE,CAAC,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,EAAU,EAAU,EAAE;IAC1C,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAEzE,OAAO,GAAG,UAAU,IAAI,IAAI,IAAI,MAAM,GAAG,aAAa,EAAE,CAAC;AAC7D,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,CAAC,IAAY,EAAiB,EAAE,CAClD,IAAI;KACC,QAAQ,CAAC,QAAQ,CAAC;KAClB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;KAC5E,OAAO,EAAE,CAAC;AAEnB,MAAM,cAAc,GAAG,CAAC,IAAiB,EAAE,KAAa,EAAW,EAAE,CACjE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,KAAK,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC;AAE7E,MAAM,oBAAoB,GAAG,CAAC,IAAY,EAAE,UAAkB,EAAW,EAAE,CACvE,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;AAExD,MAAM,kBAAkB,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,iBAAiB,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAElH,MAAM,kBAAkB,GAAG,CAAC,WAAmB,EAAU,EAAE,CAAC,+BAA+B,WAAW,EAAE,CAAC;AAEzG,MAAM,mBAAmB,GAAG,CAAC,YAAoB,EAAU,EAAE;IACzD,MAAM,SAAS,GAAG,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,YAAY,EAAE,CAAC;IAEpF,OAAO,WAAW,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,oBAAoB,CAAC;AACpE,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,CAAC,SAAiB,EAAU,EAAE,CACtD,sBAAsB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,qBAAqB,CAAC;AAEzE,MAAM,kBAAkB,GAAG,CAAC,IAAiB,EAAE,OAAyB,EAAS,EAAE,CAC/E,IAAI,KAAK,CACL,GAAG,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,wBAAwB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,eAAe;IAC9F,IAAI,OAAO,CAAC,IAAI,mFAAmF;IACnG,0CAA0C,mBAAmB,CAAC,OAAO,CAAC,SAAS,CAAC,WAAW,CAC9F,CAAC;AAEN,MAAM,qBAAqB,GAAG,CAAC,IAAiB,EAAE,SAAiB,EAAE,UAAyB,EAAS,EAAE,CACrG,IAAI,KAAK,CACL,GAAG,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,0BAA0B,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,iCAAiC;IAC1G,qCAAqC;IACrC,CAAC,UAAU,KAAK,IAAI;QAChB,CAAC,CAAC,2EAA2E;QAC7E,CAAC,CAAC,SAAS,mBAAmB,CAAC,UAAU,CAAC,WAAW,CAAC,CAC7D,CAAC;AAEN,MAAM,aAAa,GAAG,KAAK,EAAE,IAAiB,EAAE,IAAiB,EAAE,SAAiB,EAA0B,EAAE;IAC5G,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IACrE,MAAM,UAAU,GAAG,oBAAoB,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;IAEzD,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACjC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,GAAG,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QAEvE,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACpB,OAAO,IAAI,GAAG,SAAS,CAAC;QAC5B,CAAC;IACL,CAAC;IAED,OAAO,IAAI,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,IAAiB,EAAE,UAAkB,EAAU,EAAE;IAClE,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAE1D,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,QAAQ,CAAC;IACpB,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC;QACtC,IAAI,EAAE,OAAO;QACb,EAAE,EAAE,UAAU;QACd,QAAQ,EAAE,cAAc,CAAC,UAAU,CAAC;KACvC,CAAC,CAAC;IAEH,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IACtD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAEzC,OAAO,WAAW,CAAC;AACvB,CAAC,CAAC;AAEF,MAAM,eAAe,GAAG,CAAC,IAAiB,EAAE,KAAsB,EAAW,EAAE,CAC3E,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,oBAAoB,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;AAEtF,MAAM,cAAc,GAAG,KAAK,EACxB,IAAiB,EACjB,IAAiB,EACjB,KAAsB,EACW,EAAE;IACnC,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC;IAE1C,IAAI,SAAS,KAAK,SAAS,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;QAC3D,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IAEhE,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,qBAAqB,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC;IAC7F,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,WAAW,EAAE,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;AACnH,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,KAAK,EAAE,IAAiB,EAAE,IAAiB,EAAgC,EAAE;IACrG,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IAC3G,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAEzC,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;AACnD,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,CAAC,IAAiB,EAAE,IAAY,EAAW,EAAE;IACnE,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAA,IAAI,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAA,iCAAiC,OAAO,SAAS,EAAE,GAAG,CAAC,CAAC;IAE7F,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;AAC1F,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,CAAC,IAAiB,EAAE,KAAsB,EAA2B,EAAE;IACzF,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC;IAChC,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC;IAE1C,IAAI,IAAI,KAAK,SAAS,IAAI,SAAS,KAAK,SAAS,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QACtF,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AAC/B,CAAC,CAAC;AAEF,MAAM,oBAAoB,GAAG,CAAC,IAAiB,EAA2B,EAAE,CACxE,IAAI,CAAC,IAAI;KACJ,QAAQ,CAAC,kBAAkB,CAAC;KAC5B,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;KAC1C,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,IAAI,IAAI,iBAAiB,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC;AAExF,MAAM,iBAAiB,GAAG,CAAC,IAAY,EAAE,YAAiC,EAAU,EAAE;IAClF,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,KAAK,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,YAAY,EAAE,CAAC;QACrD,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC;QACtE,MAAM,GAAG,GAAG,CAAC;IACjB,CAAC;IAED,OAAO,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AACvC,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,KAAK,EAAE,IAAiB,EAAE,IAAY,EAAE,EAAU,EAA4B,EAAE;IACxG,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,MAAM,IAAI,GAAgB,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;IACrE,MAAM,OAAO,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAE3C,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACnB,MAAM,kBAAkB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,mBAAmB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAE3D,OAAO,YAAY,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,IAAI,EAAE,YAAY,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;AACzG,CAAC,CAAC;AAEF,SAAS,UAAU;IACf,MAAM,UAAU,GAAqB,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAE,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC;IAEjF,OAAO;QACH,IAAI,EAAE,aAAa;QACnB,KAAK,EAAE,OAAO;QAEd,SAAS,CAAC,IAAI,EAAE,EAAE;YACd,OAAO,mBAAmB,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;QACxE,CAAC;QAED,cAAc,CAAC,EAAE,WAAW,EAAE,YAAY,EAAE;YACxC,OAAO,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC1F,CAAC;KACJ,CAAC;AACN,CAAC;AAED,OAAO,EAAE,UAAU,EAAE,CAAC","sourcesContent":["import type { Plugin, Rollup } from \"vite\";\nimport { createHash } from \"node:crypto\";\nimport { basename, extname } from \"node:path\";\nimport { ESM_EXTENSION } from \"./esm-extension.js\";\nimport { stripQuery } from \"./strip-query.js\";\n\ntype WorkerReplacement = {\n start: number;\n end: number;\n referenceId: string;\n};\n\ntype WorkerReferences = {\n byModule: Map<string, string>;\n claimed: Set<string>;\n};\n\ntype EmitContext = {\n context: Rollup.PluginContext;\n references: WorkerReferences;\n};\n\ntype SourceRange = {\n start: number;\n end: number;\n};\n\ntype ScanContext = {\n code: string;\n id: string;\n nonCode: SourceRange[];\n};\n\ntype HoistedWorkerUrl = {\n name: string;\n specifier: string;\n};\n\ntype TransformResult = {\n code: string;\n map: null;\n} | null;\n\nconst RELATIVE_SPECIFIER = String.raw`(?<quote>[\"'])(?<specifier>\\.{1,2}\\/[^\"']+)\\k<quote>`;\nconst URL_CALL = String.raw`new\\s+URL\\s*\\(\\s*${RELATIVE_SPECIFIER}\\s*,\\s*import\\.meta\\.url\\s*\\)`;\nconst URL_DECLARATION = String.raw`(?:const|let|var)\\s+(?<name>[A-Za-z_$][\\w$]*)\\s*(?::[^=;\\n]+)?=\\s*`;\nconst BLOCK_COMMENT = String.raw`\\/\\*[\\s\\S]*?\\*\\/`;\nconst LINE_COMMENT = String.raw`\\/\\/[^\\n]*`;\nconst DOUBLE_QUOTED = String.raw`\"(?:[^\"\\\\\\n]|\\\\[\\s\\S])*\"`;\nconst SINGLE_QUOTED = String.raw`'(?:[^'\\\\\\n]|\\\\[\\s\\S])*'`;\nconst TEMPLATE_LITERAL = String.raw`\\`(?:[^\\`\\\\]|\\\\[\\s\\S])*\\``;\nconst WORKER_URL = new RegExp(URL_CALL, \"g\");\nconst HOISTED_WORKER_URL = new RegExp(URL_DECLARATION + URL_CALL, \"g\");\nconst WORKER_CONSTRUCTION = /new\\s+(?:\\w+\\.)?Worker\\s*\\(\\s*$/;\n\nconst NON_CODE = new RegExp(\n [BLOCK_COMMENT, LINE_COMMENT, DOUBLE_QUOTED, SINGLE_QUOTED, TEMPLATE_LITERAL].join(\"|\"),\n \"g\",\n);\n\nconst WORKER_DIR = \"workers\";\n\nconst SUGGESTED_EXTENSIONS: Record<string, string[]> = {\n \".js\": [\".ts\", \".tsx\"],\n \".jsx\": [\".tsx\"],\n \".mjs\": [\".mts\"],\n};\n\nconst workerFileName = (id: string): string => {\n const path = stripQuery(id);\n const stem = basename(path, extname(path));\n const digest = createHash(\"sha256\").update(id).digest(\"hex\").slice(0, 8);\n\n return `${WORKER_DIR}/${stem}-${digest}${ESM_EXTENSION}`;\n};\n\nconst nonCodeRanges = (code: string): SourceRange[] =>\n code\n .matchAll(NON_CODE)\n .map((match) => ({ start: match.index, end: match.index + match[0].length }))\n .toArray();\n\nconst isCodePosition = (scan: ScanContext, index: number): boolean =>\n scan.nonCode.every((range) => index < range.start || index >= range.end);\n\nconst isWorkerConstruction = (code: string, matchIndex: number): boolean =>\n WORKER_CONSTRUCTION.test(code.slice(0, matchIndex));\n\nconst hasWorkerCandidate = (code: string): boolean => code.includes(\"import.meta.url\") && code.includes(\"Worker\");\n\nconst workerUrlReference = (referenceId: string): string => `import.meta.ROLLUP_FILE_URL_${referenceId}`;\n\nconst workerUrlExpression = (relativePath: string): string => {\n const specifier = relativePath.startsWith(\".\") ? relativePath : `./${relativePath}`;\n\n return `new URL(${JSON.stringify(specifier)}, import.meta.url)`;\n};\n\nconst inlineWorkerExample = (specifier: string): string =>\n `new Worker(new URL(${JSON.stringify(specifier)}, import.meta.url))`;\n\nconst hoistedWorkerError = (scan: ScanContext, hoisted: HoistedWorkerUrl): Error =>\n new Error(\n `${stripQuery(scan.id)}: the worker URL for ${JSON.stringify(hoisted.specifier)} is bound to ` +\n `\"${hoisted.name}\" before it reaches \"new Worker\". gtkx build emits a worker chunk only for a URL ` +\n `written inside the construction. Write ${inlineWorkerExample(hoisted.specifier)} instead.`,\n );\n\nconst unresolvedWorkerError = (scan: ScanContext, specifier: string, suggestion: string | null): Error =>\n new Error(\n `${stripQuery(scan.id)}: the worker specifier ${JSON.stringify(specifier)} does not resolve to a module, ` +\n \"so no worker chunk can be emitted. \" +\n (suggestion === null\n ? \"Correct the path so it names the worker source file as it exists on disk.\"\n : `Write ${inlineWorkerExample(suggestion)} instead.`),\n );\n\nconst suggestionFor = async (emit: EmitContext, scan: ScanContext, specifier: string): Promise<string | null> => {\n const extension = extname(specifier);\n const stem = specifier.slice(0, specifier.length - extension.length);\n const candidates = SUGGESTED_EXTENSIONS[extension] ?? [];\n\n for (const candidate of candidates) {\n const resolved = await emit.context.resolve(stem + candidate, scan.id);\n\n if (resolved !== null) {\n return stem + candidate;\n }\n }\n\n return null;\n};\n\nconst claimWorker = (emit: EmitContext, resolvedId: string): string => {\n const existing = emit.references.byModule.get(resolvedId);\n\n if (existing !== undefined) {\n return existing;\n }\n\n const referenceId = emit.context.emitFile({\n type: \"chunk\",\n id: resolvedId,\n fileName: workerFileName(resolvedId),\n });\n\n emit.references.byModule.set(resolvedId, referenceId);\n emit.references.claimed.add(referenceId);\n\n return referenceId;\n};\n\nconst isRewritableUrl = (scan: ScanContext, match: RegExpExecArray): boolean =>\n isCodePosition(scan, match.index) && isWorkerConstruction(scan.code, match.index);\n\nconst replacementFor = async (\n emit: EmitContext,\n scan: ScanContext,\n match: RegExpExecArray,\n): Promise<WorkerReplacement | null> => {\n const specifier = match.groups?.specifier;\n\n if (specifier === undefined || !isRewritableUrl(scan, match)) {\n return null;\n }\n\n const resolved = await emit.context.resolve(specifier, scan.id);\n\n if (resolved === null) {\n throw unresolvedWorkerError(scan, specifier, await suggestionFor(emit, scan, specifier));\n }\n\n return { start: match.index, end: match.index + match[0].length, referenceId: claimWorker(emit, resolved.id) };\n};\n\nconst collectReplacements = async (emit: EmitContext, scan: ScanContext): Promise<WorkerReplacement[]> => {\n const pending = scan.code.matchAll(WORKER_URL).map((match) => replacementFor(emit, scan, match)).toArray();\n const found = await Promise.all(pending);\n\n return found.filter((entry) => entry !== null);\n};\n\nconst hasWorkerArgument = (scan: ScanContext, name: string): boolean => {\n const escaped = name.replaceAll(\"$\", String.raw`\\$`);\n const pattern = new RegExp(String.raw`new\\s+(?:\\w+\\.)?Worker\\s*\\(\\s*${escaped}\\s*[,)]`, \"g\");\n\n return scan.code.matchAll(pattern).some((match) => isCodePosition(scan, match.index));\n};\n\nconst hoistedUrlFor = (scan: ScanContext, match: RegExpExecArray): HoistedWorkerUrl | null => {\n const name = match.groups?.name;\n const specifier = match.groups?.specifier;\n\n if (name === undefined || specifier === undefined || !isCodePosition(scan, match.index)) {\n return null;\n }\n\n return { name, specifier };\n};\n\nconst findHoistedWorkerUrl = (scan: ScanContext): HoistedWorkerUrl | null =>\n scan.code\n .matchAll(HOISTED_WORKER_URL)\n .map((match) => hoistedUrlFor(scan, match))\n .find((entry) => entry !== null && hasWorkerArgument(scan, entry.name)) ?? null;\n\nconst applyReplacements = (code: string, replacements: WorkerReplacement[]): string => {\n let output = \"\";\n let cursor = 0;\n\n for (const { start, end, referenceId } of replacements) {\n output += code.slice(cursor, start) + workerUrlReference(referenceId);\n cursor = end;\n }\n\n return output + code.slice(cursor);\n};\n\nconst transformWorkerUrls = async (emit: EmitContext, code: string, id: string): Promise<TransformResult> => {\n if (!hasWorkerCandidate(code)) {\n return null;\n }\n\n const scan: ScanContext = { code, id, nonCode: nonCodeRanges(code) };\n const hoisted = findHoistedWorkerUrl(scan);\n\n if (hoisted !== null) {\n throw hoistedWorkerError(scan, hoisted);\n }\n\n const replacements = await collectReplacements(emit, scan);\n\n return replacements.length === 0 ? null : { code: applyReplacements(code, replacements), map: null };\n};\n\nfunction gtkxWorker(): Plugin {\n const references: WorkerReferences = { byModule: new Map(), claimed: new Set() };\n\n return {\n name: \"gtkx:worker\",\n apply: \"build\",\n\n transform(code, id) {\n return transformWorkerUrls({ context: this, references }, code, id);\n },\n\n resolveFileUrl({ referenceId, relativePath }) {\n return references.claimed.has(referenceId) ? workerUrlExpression(relativePath) : null;\n },\n };\n}\n\nexport { gtkxWorker };\n"]}
@@ -2,8 +2,8 @@ import type { Plugin } from "vite";
2
2
  import { type PluginOptions } from "@gtkx/vitest";
3
3
  /**
4
4
  * Vite plugin stack for testing a GTKX application: the generated bindings the tests import,
5
- * the plugins the app is built with, the project's compiled GSettings schemas exposed to every
6
- * test worker, and a headless display per worker.
5
+ * the plugins the app is built with, the project's compiled GSettings schemas and its configured
6
+ * application icon exposed to every test worker, and a headless display per worker.
7
7
  *
8
8
  * @returns The plugins to spread into a Vitest config.
9
9
  */
@@ -1 +1 @@
1
- {"version":3,"file":"vitest-plugin.d.ts","sourceRoot":"","sources":["../src/vitest-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAmB,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAM9D;;;;;;GAMG;AACH,QAAA,MAAM,IAAI,GAAI,UAAS,aAAkB,KAAG,MAAM,EAMjD,CAAC;AAEF,eAAe,IAAI,CAAC"}
1
+ {"version":3,"file":"vitest-plugin.d.ts","sourceRoot":"","sources":["../src/vitest-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAmB,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAO9D;;;;;;GAMG;AACH,QAAA,MAAM,IAAI,GAAI,UAAS,aAAkB,KAAG,MAAM,EAOjD,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -1,12 +1,13 @@
1
1
  import gtkxVitest, {} from "@gtkx/vitest";
2
2
  import { gtkxEnsureStore } from "./vite-plugins/ensure-store.js";
3
3
  import { gtkxFontWorkerEnv } from "./vite-plugins/font-worker-env.js";
4
+ import { gtkxIconWorkerEnv } from "./vite-plugins/icon-worker-env.js";
4
5
  import { gtkxVitePlugins } from "./vite-plugins/index.js";
5
6
  import { gtkxSettingsWorkerEnv } from "./vite-plugins/settings-worker-env.js";
6
7
  /**
7
8
  * Vite plugin stack for testing a GTKX application: the generated bindings the tests import,
8
- * the plugins the app is built with, the project's compiled GSettings schemas exposed to every
9
- * test worker, and a headless display per worker.
9
+ * the plugins the app is built with, the project's compiled GSettings schemas and its configured
10
+ * application icon exposed to every test worker, and a headless display per worker.
10
11
  *
11
12
  * @returns The plugins to spread into a Vitest config.
12
13
  */
@@ -15,6 +16,7 @@ const gtkx = (options = {}) => [
15
16
  ...gtkxVitePlugins({ configFile: options.configFile }),
16
17
  gtkxSettingsWorkerEnv(options.configFile),
17
18
  gtkxFontWorkerEnv(options.configFile),
19
+ gtkxIconWorkerEnv(options.configFile),
18
20
  gtkxVitest(options),
19
21
  ];
20
22
  export default gtkx;
@@ -1 +1 @@
1
- {"version":3,"file":"vitest-plugin.js","sourceRoot":"","sources":["../src/vitest-plugin.ts"],"names":[],"mappings":"AACA,OAAO,UAAU,EAAE,EAAsB,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAE9E;;;;;;GAMG;AACH,MAAM,IAAI,GAAG,CAAC,UAAyB,EAAE,EAAY,EAAE,CAAC;IACpD,eAAe,CAAC,OAAO,CAAC,UAAU,CAAC;IACnC,GAAG,eAAe,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC;IACtD,qBAAqB,CAAC,OAAO,CAAC,UAAU,CAAC;IACzC,iBAAiB,CAAC,OAAO,CAAC,UAAU,CAAC;IACrC,UAAU,CAAC,OAAO,CAAC;CACtB,CAAC;AAEF,eAAe,IAAI,CAAC","sourcesContent":["import type { Plugin } from \"vite\";\nimport gtkxVitest, { type PluginOptions } from \"@gtkx/vitest\";\nimport { gtkxEnsureStore } from \"./vite-plugins/ensure-store.js\";\nimport { gtkxFontWorkerEnv } from \"./vite-plugins/font-worker-env.js\";\nimport { gtkxVitePlugins } from \"./vite-plugins/index.js\";\nimport { gtkxSettingsWorkerEnv } from \"./vite-plugins/settings-worker-env.js\";\n\n/**\n * Vite plugin stack for testing a GTKX application: the generated bindings the tests import,\n * the plugins the app is built with, the project's compiled GSettings schemas exposed to every\n * test worker, and a headless display per worker.\n *\n * @returns The plugins to spread into a Vitest config.\n */\nconst gtkx = (options: PluginOptions = {}): Plugin[] => [\n gtkxEnsureStore(options.configFile),\n ...gtkxVitePlugins({ configFile: options.configFile }),\n gtkxSettingsWorkerEnv(options.configFile),\n gtkxFontWorkerEnv(options.configFile),\n gtkxVitest(options),\n];\n\nexport default gtkx;\n"]}
1
+ {"version":3,"file":"vitest-plugin.js","sourceRoot":"","sources":["../src/vitest-plugin.ts"],"names":[],"mappings":"AACA,OAAO,UAAU,EAAE,EAAsB,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAE9E;;;;;;GAMG;AACH,MAAM,IAAI,GAAG,CAAC,UAAyB,EAAE,EAAY,EAAE,CAAC;IACpD,eAAe,CAAC,OAAO,CAAC,UAAU,CAAC;IACnC,GAAG,eAAe,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC;IACtD,qBAAqB,CAAC,OAAO,CAAC,UAAU,CAAC;IACzC,iBAAiB,CAAC,OAAO,CAAC,UAAU,CAAC;IACrC,iBAAiB,CAAC,OAAO,CAAC,UAAU,CAAC;IACrC,UAAU,CAAC,OAAO,CAAC;CACtB,CAAC;AAEF,eAAe,IAAI,CAAC","sourcesContent":["import type { Plugin } from \"vite\";\nimport gtkxVitest, { type PluginOptions } from \"@gtkx/vitest\";\nimport { gtkxEnsureStore } from \"./vite-plugins/ensure-store.js\";\nimport { gtkxFontWorkerEnv } from \"./vite-plugins/font-worker-env.js\";\nimport { gtkxIconWorkerEnv } from \"./vite-plugins/icon-worker-env.js\";\nimport { gtkxVitePlugins } from \"./vite-plugins/index.js\";\nimport { gtkxSettingsWorkerEnv } from \"./vite-plugins/settings-worker-env.js\";\n\n/**\n * Vite plugin stack for testing a GTKX application: the generated bindings the tests import,\n * the plugins the app is built with, the project's compiled GSettings schemas and its configured\n * application icon exposed to every test worker, and a headless display per worker.\n *\n * @returns The plugins to spread into a Vitest config.\n */\nconst gtkx = (options: PluginOptions = {}): Plugin[] => [\n gtkxEnsureStore(options.configFile),\n ...gtkxVitePlugins({ configFile: options.configFile }),\n gtkxSettingsWorkerEnv(options.configFile),\n gtkxFontWorkerEnv(options.configFile),\n gtkxIconWorkerEnv(options.configFile),\n gtkxVitest(options),\n];\n\nexport default gtkx;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtkx/cli",
3
- "version": "2.0.0-beta.8",
3
+ "version": "2.0.0-beta.9",
4
4
  "description": "Adwaita-first GNOME app toolchain for React and TypeScript.",
5
5
  "keywords": [
6
6
  "gtkx",
@@ -72,18 +72,18 @@
72
72
  "dependencies": {
73
73
  "@babel/core": "^8.0.1",
74
74
  "@babel/preset-typescript": "^8.0.1",
75
- "@gtkx/codegen": "2.0.0-beta.8",
76
- "@gtkx/config": "2.0.0-beta.8",
77
- "@gtkx/mcp": "2.0.0-beta.8",
78
- "@gtkx/native": "2.0.0-beta.8",
79
- "@gtkx/runtime": "2.0.0-beta.8",
80
- "@gtkx/utils": "2.0.0-beta.8",
81
- "@gtkx/vitest": "2.0.0-beta.8",
75
+ "@gtkx/codegen": "2.0.0-beta.9",
76
+ "@gtkx/config": "2.0.0-beta.9",
77
+ "@gtkx/mcp": "2.0.0-beta.9",
78
+ "@gtkx/native": "2.0.0-beta.9",
79
+ "@gtkx/runtime": "2.0.0-beta.9",
80
+ "@gtkx/utils": "2.0.0-beta.9",
81
+ "@gtkx/vitest": "2.0.0-beta.9",
82
82
  "@swc/core": "^1.16.2",
83
83
  "babel-plugin-react-compiler": "^1.0.0",
84
84
  "citty": "^0.2.2",
85
85
  "confbox": "^0.3.1",
86
- "create-gtkx": "2.0.0-beta.8",
86
+ "create-gtkx": "2.0.0-beta.9",
87
87
  "fast-xml-builder": "^1.3.1",
88
88
  "fast-xml-parser": "^5.11.1",
89
89
  "hosted-git-info": "^10.1.1",
@@ -94,7 +94,7 @@
94
94
  "yaml": "^2.9.0"
95
95
  },
96
96
  "devDependencies": {
97
- "@gtkx/testing": "2.0.0-beta.8",
97
+ "@gtkx/testing": "2.0.0-beta.9",
98
98
  "@types/babel__core": "^7.20.5",
99
99
  "@types/hosted-git-info": "^3.0.5",
100
100
  "@types/react-refresh": "^0.14.7",
@@ -1,13 +1,17 @@
1
1
  import type { DeploySettings } from "./types.js";
2
+ import { PACKAGED_SONAMES, type PackagedSoname } from "./node-runtime/sonames.js";
2
3
 
3
4
  type Relations = {
4
5
  deb: string[];
5
6
  rpm: string[];
6
7
  };
7
8
 
8
- type LibraryPackages = {
9
+ type PackageNames = {
9
10
  deb: string;
10
11
  rpm: string;
12
+ };
13
+
14
+ type LibraryPackages = PackageNames & {
11
15
  extra: Relations;
12
16
  };
13
17
 
@@ -25,6 +29,17 @@ const DEPENDS_BY_LIBRARY: Record<string, LibraryPackages> = {
25
29
  "Gtk-4.0": { deb: "libgtk-4-1", rpm: "gtk4", extra: { deb: [], rpm: [GLES_SONAME] } },
26
30
  };
27
31
 
32
+ const RUNTIME_PACKAGES: Record<PackagedSoname, PackageNames> = {
33
+ "libatomic.so.1": { deb: "libatomic1", rpm: `libatomic.so.1${SONAME_SUFFIX}` },
34
+ "libgcc_s.so.1": { deb: "libgcc-s1", rpm: `libgcc_s.so.1${SONAME_SUFFIX}` },
35
+ "libstdc++.so.6": { deb: "libstdc++6", rpm: `libstdc++.so.6${SONAME_SUFFIX}` },
36
+ };
37
+
38
+ const RUNTIME_DEPENDS: Relations = {
39
+ deb: PACKAGED_SONAMES.map((soname) => RUNTIME_PACKAGES[soname].deb),
40
+ rpm: PACKAGED_SONAMES.map((soname) => RUNTIME_PACKAGES[soname].rpm),
41
+ };
42
+
28
43
  const debRelation = (name: string, minimum: string | undefined): string =>
29
44
  minimum === undefined ? name : `${name} (>= ${minimum})`;
30
45
 
@@ -63,8 +78,20 @@ const resolveDepends = (settings: DeploySettings, glibcMinimum: string | null):
63
78
  const extra = settings.deploy.depends ?? {};
64
79
 
65
80
  return {
66
- deb: dedupe([...fromLibraries.deb, ...BASE_DEPENDS.deb, ...fromGlibc.deb, ...(extra.deb ?? [])]),
67
- rpm: dedupe([...fromLibraries.rpm, ...BASE_DEPENDS.rpm, ...fromGlibc.rpm, ...(extra.rpm ?? [])]),
81
+ deb: dedupe([
82
+ ...fromLibraries.deb,
83
+ ...BASE_DEPENDS.deb,
84
+ ...fromGlibc.deb,
85
+ ...RUNTIME_DEPENDS.deb,
86
+ ...(extra.deb ?? []),
87
+ ]),
88
+ rpm: dedupe([
89
+ ...fromLibraries.rpm,
90
+ ...BASE_DEPENDS.rpm,
91
+ ...fromGlibc.rpm,
92
+ ...RUNTIME_DEPENDS.rpm,
93
+ ...(extra.rpm ?? []),
94
+ ]),
68
95
  };
69
96
  };
70
97
 
@@ -1,19 +1,16 @@
1
1
  import type { ElfInfo } from "./elf.js";
2
+ import { RUNTIME_SONAMES } from "./sonames.js";
2
3
 
3
- const PORTABLE_LIBRARIES = new Set([
4
- "ld-linux-aarch64.so.1",
5
- "ld-linux-x86-64.so.2",
6
- "libc.so.6",
7
- "libdl.so.2",
8
- "libgcc_s.so.1",
9
- "libm.so.6",
10
- "libpthread.so.0",
11
- "librt.so.1",
12
- "libstdc++.so.6",
13
- ]);
4
+ const DOWNLOAD_REMEDY =
5
+ "The packages GTKX generates declare the libraries an official release links against, and this release " +
6
+ "links against more. Report it to GTKX, and pin `deploy.node.version` to a release it already packages.";
7
+
8
+ const SOURCE_REMEDY = 'Use `deploy.node.source: "download"` to fetch an official self-contained build instead.';
9
+
10
+ const remedyFor = (source: string): string => (source === "download" ? DOWNLOAD_REMEDY : SOURCE_REMEDY);
14
11
 
15
12
  const assertPortableNode = (info: ElfInfo, source: string): void => {
16
- const foreign = info.needed.filter((library) => !PORTABLE_LIBRARIES.has(library));
13
+ const foreign = info.needed.filter((library) => !RUNTIME_SONAMES.has(library));
17
14
 
18
15
  if (foreign.length === 0) {
19
16
  return;
@@ -21,8 +18,7 @@ const assertPortableNode = (info: ElfInfo, source: string): void => {
21
18
 
22
19
  throw new Error(
23
20
  `Cannot bundle this Node.js binary: \`deploy.node.source: "${source}"\` picked one linked against ` +
24
- `${foreign.join(", ")}, which the target machine will not have. ` +
25
- 'Use `deploy.node.source: "download"` to fetch an official self-contained build instead.',
21
+ `${foreign.join(", ")}, which the packages GTKX generates do not require. ${remedyFor(source)}`,
26
22
  );
27
23
  };
28
24
 
@@ -1,13 +1,13 @@
1
1
  import { MINIMUM_NODE_VERSION } from "@gtkx/config/internal";
2
2
  import { info, tryResolveExecutable, warn } from "@gtkx/utils";
3
3
  import { execFileSync } from "node:child_process";
4
- import { chmodSync, copyFileSync, mkdirSync, statSync } from "node:fs";
4
+ import { chmodSync, copyFileSync, existsSync, mkdirSync, statSync } from "node:fs";
5
5
  import { join, resolve } from "node:path";
6
6
  import type { DeployConfig, DeploySettings, NodeRuntime } from "../types.js";
7
7
  import { runCliTool } from "../../internal/run-cli-tool.js";
8
8
  import { elfMachineFor, hostArchName } from "../settings/arch.js";
9
9
  import { downloadNode } from "./download.js";
10
- import { type ElfInfo, readElfInfo } from "./elf.js";
10
+ import { type ElfInfo, readElfInfo, readOptionalElfInfo } from "./elf.js";
11
11
  import { assertPortableNode } from "./guard.js";
12
12
  import { licenseBesideNode } from "./license.js";
13
13
 
@@ -114,6 +114,21 @@ const sourcePathFor = (settings: DeploySettings): string => {
114
114
  return resolve(settings.paths.root, node.path);
115
115
  };
116
116
 
117
+ const assertPortableRuntimeSource = (settings: DeploySettings): void => {
118
+ const source = nodeSourceFor(settings);
119
+
120
+ if (source === "download") {
121
+ return;
122
+ }
123
+
124
+ const sourcePath = sourcePathFor(settings);
125
+ const elf = existsSync(sourcePath) ? readOptionalElfInfo(sourcePath) : null;
126
+
127
+ if (elf !== null) {
128
+ assertPortableNode(elf, source);
129
+ }
130
+ };
131
+
117
132
  const resolveNodeVersion = (settings: DeploySettings): string => {
118
133
  const node = settings.deploy.node ?? {};
119
134
  const source = nodeSourceFor(settings);
@@ -167,7 +182,6 @@ const stageFromSource = async (
167
182
  }
168
183
 
169
184
  const sourcePath = sourcePathFor(settings);
170
- assertPortableNode(readElfInfo(sourcePath), source);
171
185
 
172
186
  return { path: stageNode(settings, sourcePath), licenseFile: licenseBesideNode(sourcePath) };
173
187
  };
@@ -197,6 +211,7 @@ const resolveNodeRuntime = async (settings: DeploySettings): Promise<NodeRuntime
197
211
  const version = resolveNodeVersion(settings);
198
212
  const staged = await stageFromSource(settings, version, source);
199
213
  const elf = readElfInfo(staged.path);
214
+ assertPortableNode(elf, source);
200
215
  const isStripped = shouldStripRuntime(settings, node, elf) && didStripBinary(staged.path);
201
216
  const glibcMinimum = elf.glibcMinimum ?? "unknown";
202
217
  info(`Bundled Node.js v${version} (${megabytes(staged.path)} MiB, runtime glibc >= ${glibcMinimum})`);
@@ -204,4 +219,4 @@ const resolveNodeRuntime = async (settings: DeploySettings): Promise<NodeRuntime
204
219
  return { ...staged, version, glibcMinimum: elf.glibcMinimum, isStripped };
205
220
  };
206
221
 
207
- export { resolveNodeRuntime, resolveNodeVersion, sourcePathFor };
222
+ export { assertPortableRuntimeSource, resolveNodeRuntime, resolveNodeVersion, sourcePathFor };
@@ -0,0 +1,17 @@
1
+ const GLIBC_SONAMES = [
2
+ "ld-linux-aarch64.so.1",
3
+ "ld-linux-x86-64.so.2",
4
+ "libc.so.6",
5
+ "libdl.so.2",
6
+ "libm.so.6",
7
+ "libpthread.so.0",
8
+ "librt.so.1",
9
+ ] as const;
10
+
11
+ const PACKAGED_SONAMES = ["libatomic.so.1", "libgcc_s.so.1", "libstdc++.so.6"] as const;
12
+
13
+ type PackagedSoname = (typeof PACKAGED_SONAMES)[number];
14
+
15
+ const RUNTIME_SONAMES: Set<string> = new Set([...GLIBC_SONAMES, ...PACKAGED_SONAMES]);
16
+
17
+ export { PACKAGED_SONAMES, type PackagedSoname, RUNTIME_SONAMES };
@@ -32,7 +32,7 @@ import { renderMetainfo } from "./freedesktop/metainfo.js";
32
32
  import { renderMimePackage } from "./freedesktop/mime-package.js";
33
33
  import { validateDesktopEntry, validateMetainfo } from "./freedesktop/validate.js";
34
34
  import { resolveStagedAddon } from "./native-addon.js";
35
- import { resolveNodeRuntime } from "./node-runtime/index.js";
35
+ import { assertPortableRuntimeSource, resolveNodeRuntime } from "./node-runtime/index.js";
36
36
  import { collectNotices } from "./notices/collect.js";
37
37
  import { type StagedMetadata, stageOverlays, stagePayload } from "./payload/stage.js";
38
38
  import { DEFAULT_TARGETS, parseTargetList, targetsFor } from "./registry.js";
@@ -319,6 +319,11 @@ const preflight = ({ plan, settings, shouldPrintManifests, shouldSkipBuild }: Pr
319
319
 
320
320
  const report = probeTools([...required, ...packagerTools]);
321
321
  assertTools(report);
322
+
323
+ if (isNodeRequired(targets, settings)) {
324
+ assertPortableRuntimeSource(settings);
325
+ }
326
+
322
327
  warnMissingOptional(report);
323
328
  warnFlatpakPermissions(targets, settings);
324
329
  warnLibraryMinimums(targets, settings);
@@ -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 };
@@ -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 };