@opentray/vite-plugin 0.21.1 → 0.22.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.
package/README.md CHANGED
@@ -45,13 +45,16 @@ export default {
45
45
  };
46
46
  ```
47
47
 
48
- The cache identity includes the source image, the plugin implementation, the
49
- rendering recipe, and the `sharp`, `@shockpkg/icon-encoder`, and
50
- `figma-squircle` versions. ICNS output uses explicit macOS @1x/@2x tags rather
51
- than copying one PNG into incompatible representation slots. In a linked
52
- checkout the cache also hashes `packages/vite-plugin/src/app-icon.ts`, so editing
53
- the generator source invalidates the cache even when the bundle hash is
54
- unchanged. A linked consumer should rebuild this package before starting Vite.
48
+ Generation lives in the shared `@opentray/icon` kernel (WebAssembly image
49
+ stack jsquash codecs and a resvg rasterizer; no sharp/libvips), so the Vite
50
+ plugin, create-opentray, and the OpenTray runtime share one implementation and
51
+ one visual standard. The cache identity includes the source image, the kernel
52
+ implementation, the rendering recipe, and the codec/rasterizer stack versions.
53
+ ICNS output uses explicit macOS @1x/@2x tags rather than copying one PNG into
54
+ incompatible representation slots. In a linked checkout the cache also hashes
55
+ the kernel's source entry, so editing the generator source invalidates the
56
+ cache even when the bundle hash is unchanged. A linked consumer should rebuild
57
+ `@opentray/icon` before starting Vite.
55
58
 
56
59
  The output contains:
57
60
 
package/dist/index.d.mts CHANGED
@@ -1,57 +1,8 @@
1
1
  import { DarwinAppBundleOptions, OpenTrayArtifactInput, OpenTrayDarwinAppBundleResult, OpenTrayPackageManifest, OpenTrayPackageResult, OpenTrayPackagingApp } from "@opentray/packaging";
2
+ import { OpenTrayAppIconCacheMetadata, OpenTrayAppIconManifest, OpenTrayAppIconOptions, generateOpenTrayAppIcon } from "@opentray/icon";
2
3
  import { Plugin } from "vite";
3
- import { AppIcon } from "@opentray/spec";
4
4
 
5
5
  //#region src/app-icon.d.ts
6
- interface OpenTrayAppIconOptions {
7
- readonly sourcePath: string;
8
- readonly outputPath?: string;
9
- readonly icnsOutputPath?: string;
10
- readonly icoOutputPath?: string;
11
- readonly linuxOutputDirectory?: string;
12
- readonly manifestOutputPath?: string;
13
- readonly cachePath?: string;
14
- /** Advanced: override the module whose bytes identify the generator implementation. */
15
- readonly implementationPath?: string;
16
- /** Advanced: override the source file whose bytes identify the generator implementation. */
17
- readonly implementationSourcePath?: string;
18
- /**
19
- * Pre-composed source: skip glyph re-tiling; pass pixels through verbatim.
20
- */
21
- readonly composed?: boolean;
22
- /**
23
- * Separate macOS content source; ICNS encodes from this while ICO/Linux
24
- * use sourcePath.
25
- */
26
- readonly macosSourcePath?: string;
27
- }
28
- interface OpenTrayAppIconCacheMetadata {
29
- readonly schemaVersion: number;
30
- readonly sourceSha256: string;
31
- readonly macosSourceSha256?: string;
32
- readonly composed?: boolean;
33
- readonly sourceImplementationSha256: string | null;
34
- readonly implementationSha256: string;
35
- readonly recipeVersion: string;
36
- readonly sharpVersion: string;
37
- readonly iconEncoderVersion: string;
38
- readonly figmaSquircleVersion: string;
39
- readonly outputPath: string;
40
- readonly icnsOutputPath: string;
41
- readonly icoOutputPath: string;
42
- readonly linuxPngOutputPaths: readonly {
43
- readonly size: number;
44
- readonly path: string;
45
- }[];
46
- readonly manifestOutputPath: string;
47
- /** Absolute file sources ready to pass to OpenTray at runtime. */
48
- readonly appIcon: AppIcon;
49
- }
50
- interface OpenTrayAppIconManifest {
51
- readonly schemaVersion: 1;
52
- /** File paths are relative to the manifest file. */
53
- readonly appIcon: AppIcon;
54
- }
55
6
  interface OpenTrayAppIconPluginOptions {
56
7
  /** Brand source image. This is intentionally explicit so the plugin is app-agnostic. */
57
8
  readonly sourcePath: string;
@@ -73,8 +24,6 @@ interface OpenTrayAppIconPluginOptions {
73
24
  readonly manifestOutputPath?: string;
74
25
  readonly cachePath?: string;
75
26
  }
76
- /** Generate one strict cross-platform AppIcon asset set. */
77
- declare function generateOpenTrayAppIcon(options: OpenTrayAppIconOptions): Promise<OpenTrayAppIconCacheMetadata>;
78
27
  /** Create the Vite plugin used by both serve and build modes. */
79
28
  declare function openTrayAppIconPlugin(options: OpenTrayAppIconPluginOptions): Plugin;
80
29
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/app-icon.ts","../src/index.ts"],"mappings":";;;;;UAmDiB,sBAAA;EAAA,SACN,UAAA;EAAA,SACA,UAAA;EAAA,SACA,cAAA;EAAA,SACA,aAAA;EAAA,SACA,oBAAA;EAAA,SACA,kBAAA;EAAA,SACA,SAAA;EALA;EAAA,SAOA,kBAAA;EALA;EAAA,SAOA,wBAAA;EALA;;;EAAA,SASA,QAAA;EAAA;;;AAKe;EALf,SAKA,eAAA;AAAA;AAAA,UAGM,4BAAA;EAAA,SACN,aAAA;EAAA,SACA,YAAA;EAAA,SACA,iBAAA;EAAA,SACA,QAAA;EAAA,SACA,0BAAA;EAAA,SACA,oBAAA;EAAA,SACA,aAAA;EAAA,SACA,YAAA;EAAA,SACA,kBAAA;EAAA,SACA,oBAAA;EAAA,SACA,UAAA;EAAA,SACA,cAAA;EAAA,SACA,aAAA;EAAA,SACA,mBAAA;IAAA,SACE,IAAA;IAAA,SACA,IAAA;EAAA;EAAA,SAEF,kBAAA;EAEA;EAAA,SAAA,OAAA,EAAS,OAAO;AAAA;AAAA,UAGV,uBAAA;EAAA,SACN,aAAA;EAD6B;EAAA,SAG7B,OAAA,EAAS,OAAO;AAAA;AAAA,UAGV,4BAAA;EAHN;EAAA,SAKA,UAAA;EALgB;AAAA;AAG3B;;;EAH2B,SAWhB,QAAA;EANA;;;;EAAA,SAWA,eAAA;EAAA,SACA,UAAA;EAAA,SACA,cAAA;EAAA,SACA,aAAA;EAAA,SACA,oBAAA;EAAA,SACA,kBAAA;EAAA,SACA,SAAA;AAAA;;iBAIW,uBAAA,CACpB,OAAA,EAAS,sBAAA,GACR,OAAA,CAAQ,4BAAA;;iBA8DK,qBAAA,CACd,OAAA,EAAS,4BAAA,GACR,MAAM;;;UC5KQ,yBAAA;EAAA,SACN,GAAA,EAAK,oBAAA;EAAA,SACL,WAAA,EAAa,qBAAA;EAAA,SACb,eAAA,GAAkB,QAAA,CAAS,MAAA,SAAe,qBAAA;EAAA,SAC1C,eAAA,GAAkB,QAAA,CAAS,MAAA,SAAe,qBAAA;EAAA,SAC1C,KAAA;EAAA,SACA,YAAA;AAAA;AAAA,UAGM,sBAAA;EAAA,SACN,IAAA;EAAA,SACA,IAAA;EAAA,SACA,KAAA;IAAA,SACE,MAAA;EAAA;AAAA;AAAA,UAII,mBAAA;EAAA,SACN,IAAA;EAAA,SACA,OAAA;EAAA,SACA,QAAA;EAAA,SACA,cAAA;EAAA,SACA,IAAA;AAAA;AAAA,KAGC,cAAA,GAAiB,QAAQ,CAAC,MAAA;AAAA,UAErB,kBAAA;EAAA,SACN,IAAA;EAAA,SACA,KAAA;EACT,cAAA,CAAe,MAAA,EAAQ,sBAAA;EACvB,WAAA,CAAY,OAAA,WAAkB,MAAA,EAAQ,cAAA,GAAiB,OAAA;EAAA,SAC9C,aAAA,QAAqB,qBAAA;AAAA;AAAA,UAGf,kCAAA,SACP,IAAI,CAAC,sBAAA;EDyBJ;EAAA,SCvBA,UAAA;AAAA;AAAA,UAGM,2BAAA;EAAA,SACN,IAAA;EAAA,SACA,KAAA;EACT,cAAA,CAAe,MAAA,EAAQ,sBAAA;EACvB,WAAA,IAAe,OAAA;EAAA,SACN,aAAA,QAAqB,6BAAA;AAAA;;cAInB,uBAAA,GACX,OAAA,EAAS,kCAAA,KACR,2BAyBF;AAAA,cAEY,kBAAA,GAAsB,OAAA,EAAS,yBAAA,KAA4B,kBAiCvE;AAAA,cAEY,gBAAA,GAAoB,MAAsB,EAAd,cAAc"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/app-icon.ts","../src/index.ts"],"mappings":";;;;;UAyBiB,4BAAA;EAAA;EAAA,SAEN,UAAA;;;;;;WAMA,QAAA;EAOA;;;;EAAA,SAFA,eAAA;EAAA,SACA,UAAA;EAAA,SACA,cAAA;EAAA,SACA,aAAA;EAAA,SACA,oBAAA;EAAA,SACA,kBAAA;EAAA,SACA,SAAA;AAAA;;iBAIK,qBAAA,CACd,OAAA,EAAS,4BAAA,GACR,MAAM;;;UC9BQ,yBAAA;EAAA,SACN,GAAA,EAAK,oBAAA;EAAA,SACL,WAAA,EAAa,qBAAA;EAAA,SACb,eAAA,GAAkB,QAAA,CAAS,MAAA,SAAe,qBAAA;EAAA,SAC1C,eAAA,GAAkB,QAAA,CAAS,MAAA,SAAe,qBAAA;EAAA,SAC1C,KAAA;EAAA,SACA,YAAA;AAAA;AAAA,UAGM,sBAAA;EAAA,SACN,IAAA;EAAA,SACA,IAAA;EAAA,SACA,KAAA;IAAA,SACE,MAAA;EAAA;AAAA;AAAA,UAII,mBAAA;EAAA,SACN,IAAA;EAAA,SACA,OAAA;EAAA,SACA,QAAA;EAAA,SACA,cAAA;EAAA,SACA,IAAA;AAAA;AAAA,KAGC,cAAA,GAAiB,QAAQ,CAAC,MAAA;AAAA,UAErB,kBAAA;EAAA,SACN,IAAA;EAAA,SACA,KAAA;EACT,cAAA,CAAe,MAAA,EAAQ,sBAAA;EACvB,WAAA,CAAY,OAAA,WAAkB,MAAA,EAAQ,cAAA,GAAiB,OAAA;EAAA,SAC9C,aAAA,QAAqB,qBAAA;AAAA;AAAA,UAGf,kCAAA,SACP,IAAI,CAAC,sBAAA;EApCE;EAAA,SAsCN,UAAA;AAAA;AAAA,UAGM,2BAAA;EAAA,SACN,IAAA;EAAA,SACA,KAAA;EACT,cAAA,CAAe,MAAA,EAAQ,sBAAA;EACvB,WAAA,IAAe,OAAA;EAAA,SACN,aAAA,QAAqB,6BAAA;AAAA;;cAInB,uBAAA,GACX,OAAA,EAAS,kCAAA,KACR,2BAyBF;AAAA,cAEY,kBAAA,GAAsB,OAAA,EAAS,yBAAA,KAA4B,kBAiCvE;AAAA,cAEY,gBAAA,GAAoB,MAAsB,EAAd,cAAc"}
package/dist/index.mjs CHANGED
@@ -1,118 +1,7 @@
1
- import { createRequire } from "node:module";
2
1
  import path, { resolve } from "node:path";
3
2
  import { buildDarwinAppBundle, stageOpenTrayPackage } from "@opentray/packaging";
4
- import crypto from "node:crypto";
5
- import fs from "node:fs/promises";
6
- import { fileURLToPath } from "node:url";
7
- import { getSvgPath } from "figma-squircle";
8
- import sharp from "sharp";
9
- import { IconIcns, IconIco } from "@shockpkg/icon-encoder";
3
+ import { generateOpenTrayAppIcon } from "@opentray/icon";
10
4
  //#region src/app-icon.ts
11
- const ICON_SIZE = 1024;
12
- const TILE_INSET = 64;
13
- const TILE_SIZE = ICON_SIZE - TILE_INSET * 2;
14
- const TILE_RADIUS = 196;
15
- const TILE_SMOOTHING = 1;
16
- const SYMBOL_SIZE = 704;
17
- const APP_ICON_DENSITY = 72;
18
- const CACHE_SCHEMA_VERSION = 6;
19
- const RECIPE_VERSION = `squircle-v3:${ICON_SIZE}:${TILE_INSET}:${TILE_RADIUS}:${TILE_SMOOTHING}:${SYMBOL_SIZE}:${APP_ICON_DENSITY}dpi:icns-tagged`;
20
- const ICO_SIZES = [
21
- 16,
22
- 24,
23
- 32,
24
- 48,
25
- 64,
26
- 128,
27
- 256
28
- ];
29
- const LINUX_SIZES = [
30
- 16,
31
- 32,
32
- 48,
33
- 64,
34
- 128,
35
- 256,
36
- 512
37
- ];
38
- const ICNS_REPRESENTATIONS = [
39
- {
40
- tag: "ic12",
41
- size: 64
42
- },
43
- {
44
- tag: "ic07",
45
- size: 128
46
- },
47
- {
48
- tag: "ic13",
49
- size: 256
50
- },
51
- {
52
- tag: "ic08",
53
- size: 256
54
- },
55
- {
56
- tag: "ic04",
57
- size: 16
58
- },
59
- {
60
- tag: "ic14",
61
- size: 512
62
- },
63
- {
64
- tag: "ic09",
65
- size: 512
66
- },
67
- {
68
- tag: "ic05",
69
- size: 32
70
- },
71
- {
72
- tag: "ic10",
73
- size: 1024
74
- },
75
- {
76
- tag: "ic11",
77
- size: 32
78
- }
79
- ];
80
- const require = createRequire(import.meta.url);
81
- /** Generate one strict cross-platform AppIcon asset set. */
82
- async function generateOpenTrayAppIcon(options) {
83
- const outputPath = options.outputPath ?? path.join(path.dirname(options.sourcePath), "app-icon.png");
84
- const icnsOutputPath = options.icnsOutputPath ?? path.join(path.dirname(outputPath), "app-icon.icns");
85
- const icoOutputPath = options.icoOutputPath ?? path.join(path.dirname(outputPath), "app-icon.ico");
86
- const linuxOutputDirectory = options.linuxOutputDirectory ?? path.join(path.dirname(outputPath), "linux");
87
- const manifestOutputPath = options.manifestOutputPath ?? path.join(path.dirname(outputPath), "app-icon.json");
88
- const cachePath = options.cachePath ?? path.join(path.dirname(outputPath), "../../.cache/app-icon.json");
89
- const implementationPath = options.implementationPath ?? fileURLToPath(import.meta.url);
90
- const metadata = await createCacheMetadata({
91
- sourcePath: options.sourcePath,
92
- implementationPath,
93
- ...options.composed === true ? { composed: true } : {},
94
- ...options.macosSourcePath === void 0 ? {} : { macosSourcePath: options.macosSourcePath },
95
- outputPath,
96
- icnsOutputPath,
97
- icoOutputPath,
98
- linuxOutputDirectory,
99
- manifestOutputPath,
100
- ...options.implementationSourcePath === void 0 ? {} : { implementationSourcePath: options.implementationSourcePath }
101
- });
102
- if (await cacheMatches(cachePath, metadata)) return metadata;
103
- const rendered = await renderAppIcon(options.sourcePath, options.composed === true);
104
- const macosRendered = options.macosSourcePath === void 0 || options.macosSourcePath === options.sourcePath ? rendered : await renderAppIcon(options.macosSourcePath, options.composed === true);
105
- await fs.mkdir(path.dirname(outputPath), { recursive: true });
106
- await fs.mkdir(path.dirname(icnsOutputPath), { recursive: true });
107
- await fs.mkdir(path.dirname(icoOutputPath), { recursive: true });
108
- await fs.mkdir(path.dirname(cachePath), { recursive: true });
109
- await fs.writeFile(outputPath, rendered);
110
- await encodeNativeIcons(rendered, macosRendered, icnsOutputPath, icoOutputPath);
111
- await writeLinuxIcons(rendered, metadata.linuxPngOutputPaths);
112
- await writeManifest(metadata);
113
- await fs.writeFile(cachePath, `${JSON.stringify(metadata, null, 2)}\n`, "utf8");
114
- return metadata;
115
- }
116
5
  /** Create the Vite plugin used by both serve and build modes. */
117
6
  function openTrayAppIconPlugin(options) {
118
7
  let generation;
@@ -141,211 +30,6 @@ function openTrayAppIconPlugin(options) {
141
30
  }
142
31
  };
143
32
  }
144
- async function createCacheMetadata(options) {
145
- const sourceImplementationPath = options.implementationSourcePath ?? await resolveSourceImplementationPath(options.implementationPath);
146
- const linuxPngOutputPaths = LINUX_SIZES.map((size) => ({
147
- size,
148
- path: path.resolve(options.linuxOutputDirectory, `${size}x${size}`, "app-icon.png")
149
- }));
150
- const icnsOutputPath = path.resolve(options.icnsOutputPath);
151
- const icoOutputPath = path.resolve(options.icoOutputPath);
152
- const appIcon = [
153
- {
154
- platform: "darwin",
155
- format: "icns",
156
- source: {
157
- type: "file",
158
- path: icnsOutputPath
159
- }
160
- },
161
- {
162
- platform: "windows",
163
- format: "ico",
164
- source: {
165
- type: "file",
166
- path: icoOutputPath
167
- }
168
- },
169
- ...linuxPngOutputPaths.map(({ size, path: pngPath }) => ({
170
- platform: "linux",
171
- format: "png",
172
- size,
173
- source: {
174
- type: "file",
175
- path: pngPath
176
- }
177
- }))
178
- ];
179
- return {
180
- schemaVersion: CACHE_SCHEMA_VERSION,
181
- sourceSha256: await sha256(options.sourcePath),
182
- ...options.macosSourcePath === void 0 ? {} : { macosSourceSha256: await sha256(options.macosSourcePath) },
183
- ...options.composed === true ? { composed: true } : {},
184
- sourceImplementationSha256: sourceImplementationPath === null ? null : await sha256(sourceImplementationPath),
185
- implementationSha256: await sha256(options.implementationPath),
186
- recipeVersion: RECIPE_VERSION,
187
- sharpVersion: await packageVersion("sharp"),
188
- iconEncoderVersion: await packageVersion("@shockpkg/icon-encoder"),
189
- figmaSquircleVersion: await packageVersion("figma-squircle"),
190
- outputPath: path.resolve(options.outputPath),
191
- icnsOutputPath,
192
- icoOutputPath,
193
- linuxPngOutputPaths,
194
- manifestOutputPath: path.resolve(options.manifestOutputPath),
195
- appIcon
196
- };
197
- }
198
- async function renderAppIcon(sourcePath, composed = false) {
199
- if (composed) return sharp(sourcePath, { failOn: "none" }).resize(ICON_SIZE, ICON_SIZE, {
200
- fit: "contain",
201
- kernel: sharp.kernel.lanczos3
202
- }).withMetadata({ density: APP_ICON_DENSITY }).png({ compressionLevel: 9 }).toBuffer();
203
- const symbol = await sharp(sourcePath).trim({ threshold: 0 }).resize(SYMBOL_SIZE, SYMBOL_SIZE, {
204
- fit: "inside",
205
- kernel: sharp.kernel.lanczos3
206
- }).png().toBuffer({ resolveWithObject: true });
207
- const symbolLeft = Math.round((ICON_SIZE - symbol.info.width) / 2);
208
- const symbolTop = Math.round((ICON_SIZE - symbol.info.height) / 2);
209
- const squirclePath = getSvgPath({
210
- width: TILE_SIZE,
211
- height: TILE_SIZE,
212
- cornerRadius: TILE_RADIUS,
213
- cornerSmoothing: TILE_SMOOTHING,
214
- preserveSmoothing: true
215
- });
216
- const whiteTile = Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg" width="${TILE_SIZE}" height="${TILE_SIZE}"><path d="${squirclePath}" fill="#fff"/></svg>`);
217
- return sharp({ create: {
218
- width: ICON_SIZE,
219
- height: ICON_SIZE,
220
- channels: 4,
221
- background: {
222
- r: 0,
223
- g: 0,
224
- b: 0,
225
- alpha: 0
226
- }
227
- } }).composite([{
228
- input: whiteTile,
229
- top: TILE_INSET,
230
- left: TILE_INSET
231
- }, {
232
- input: symbol.data,
233
- top: symbolTop,
234
- left: symbolLeft
235
- }]).withMetadata({ density: APP_ICON_DENSITY }).png({ compressionLevel: 9 }).toBuffer();
236
- }
237
- async function encodeNativeIcons(rendered, macosRendered, icnsOutputPath, icoOutputPath) {
238
- const cacheFor = (source) => {
239
- const bySize = /* @__PURE__ */ new Map();
240
- return { async pngAt(size) {
241
- const cached = bySize.get(size);
242
- if (cached !== void 0) return cached;
243
- const png = await sharp(source).resize(size, size, { fit: "contain" }).withMetadata({ density: APP_ICON_DENSITY }).png({ compressionLevel: 9 }).toBuffer();
244
- bySize.set(size, png);
245
- return png;
246
- } };
247
- };
248
- const windows = cacheFor(rendered);
249
- const macos = cacheFor(macosRendered);
250
- const icns = new IconIcns();
251
- icns.toc = true;
252
- for (const { tag, size } of ICNS_REPRESENTATIONS) await icns.addFromPng(await macos.pngAt(size), [tag], false);
253
- await fs.writeFile(icnsOutputPath, icns.encode());
254
- const ico = new IconIco();
255
- for (const size of ICO_SIZES) await ico.addFromPng(await windows.pngAt(size), null, false);
256
- await fs.writeFile(icoOutputPath, ico.encode());
257
- }
258
- async function writeLinuxIcons(rendered, outputs) {
259
- await Promise.all(outputs.map(async ({ size, path: outputPath }) => {
260
- await fs.mkdir(path.dirname(outputPath), { recursive: true });
261
- await sharp(rendered).resize(size, size, { fit: "contain" }).withMetadata({ density: APP_ICON_DENSITY }).png({ compressionLevel: 9 }).toFile(outputPath);
262
- }));
263
- }
264
- async function writeManifest(metadata) {
265
- const manifestDirectory = path.dirname(metadata.manifestOutputPath);
266
- const relativeSource = (sourcePath) => ({
267
- type: "file",
268
- path: path.relative(manifestDirectory, sourcePath).split(path.sep).join("/")
269
- });
270
- const manifest = {
271
- schemaVersion: 1,
272
- appIcon: [
273
- {
274
- platform: "darwin",
275
- format: "icns",
276
- source: relativeSource(metadata.icnsOutputPath)
277
- },
278
- {
279
- platform: "windows",
280
- format: "ico",
281
- source: relativeSource(metadata.icoOutputPath)
282
- },
283
- ...metadata.linuxPngOutputPaths.map(({ size, path: pngPath }) => ({
284
- platform: "linux",
285
- format: "png",
286
- size,
287
- source: relativeSource(pngPath)
288
- }))
289
- ]
290
- };
291
- await fs.mkdir(manifestDirectory, { recursive: true });
292
- await fs.writeFile(metadata.manifestOutputPath, `${JSON.stringify(manifest, null, 2)}\n`);
293
- }
294
- async function cacheMatches(file, expected) {
295
- try {
296
- const parsed = JSON.parse(await fs.readFile(file, "utf8"));
297
- if (!isCacheMetadata(parsed) || !sameCacheIdentity(parsed, expected)) return false;
298
- await Promise.all([
299
- fs.access(expected.outputPath),
300
- fs.access(expected.icnsOutputPath),
301
- fs.access(expected.icoOutputPath),
302
- fs.access(expected.manifestOutputPath),
303
- ...expected.linuxPngOutputPaths.map(({ path: outputPath }) => fs.access(outputPath))
304
- ]);
305
- return true;
306
- } catch {
307
- return false;
308
- }
309
- }
310
- function sameCacheIdentity(actual, expected) {
311
- return actual.schemaVersion === expected.schemaVersion && actual.sourceSha256 === expected.sourceSha256 && actual.macosSourceSha256 === expected.macosSourceSha256 && actual.composed === expected.composed && actual.sourceImplementationSha256 === expected.sourceImplementationSha256 && actual.implementationSha256 === expected.implementationSha256 && actual.recipeVersion === expected.recipeVersion && actual.sharpVersion === expected.sharpVersion && actual.iconEncoderVersion === expected.iconEncoderVersion && actual.figmaSquircleVersion === expected.figmaSquircleVersion && actual.outputPath === expected.outputPath && actual.icnsOutputPath === expected.icnsOutputPath && actual.icoOutputPath === expected.icoOutputPath && actual.manifestOutputPath === expected.manifestOutputPath && JSON.stringify(actual.linuxPngOutputPaths) === JSON.stringify(expected.linuxPngOutputPaths) && JSON.stringify(actual.appIcon) === JSON.stringify(expected.appIcon);
312
- }
313
- function isCacheMetadata(value) {
314
- if (typeof value !== "object" || value === null) return false;
315
- const record = value;
316
- return typeof record.schemaVersion === "number" && typeof record.sourceSha256 === "string" && (record.macosSourceSha256 === void 0 || typeof record.macosSourceSha256 === "string") && (record.composed === void 0 || typeof record.composed === "boolean") && (record.sourceImplementationSha256 === null || typeof record.sourceImplementationSha256 === "string") && typeof record.implementationSha256 === "string" && typeof record.recipeVersion === "string" && typeof record.sharpVersion === "string" && typeof record.iconEncoderVersion === "string" && typeof record.figmaSquircleVersion === "string" && typeof record.outputPath === "string" && typeof record.icnsOutputPath === "string" && typeof record.icoOutputPath === "string" && Array.isArray(record.linuxPngOutputPaths) && typeof record.manifestOutputPath === "string" && Array.isArray(record.appIcon);
317
- }
318
- async function resolveSourceImplementationPath(implementationPath) {
319
- if (implementationPath.endsWith(`${path.sep}src${path.sep}app-icon.ts`)) return implementationPath;
320
- const candidate = path.join(path.dirname(path.dirname(implementationPath)), "src", "app-icon.ts");
321
- try {
322
- await fs.access(candidate);
323
- return candidate;
324
- } catch {
325
- return null;
326
- }
327
- }
328
- async function packageVersion(packageName) {
329
- const entryPath = require.resolve(packageName);
330
- let directory = path.dirname(entryPath);
331
- while (true) {
332
- const packagePath = path.join(directory, "package.json");
333
- try {
334
- const parsed = JSON.parse(await fs.readFile(packagePath, "utf8"));
335
- if (isPackageMetadata(parsed, packageName)) return parsed.version;
336
- } catch {}
337
- const parent = path.dirname(directory);
338
- if (parent === directory) break;
339
- directory = parent;
340
- }
341
- throw new Error(`Unable to resolve ${packageName} package version`);
342
- }
343
- function isPackageMetadata(value, packageName) {
344
- return typeof value === "object" && value !== null && "name" in value && value.name === packageName && "version" in value && typeof value.version === "string";
345
- }
346
- async function sha256(file) {
347
- return crypto.createHash("sha256").update(await fs.readFile(file)).digest("hex");
348
- }
349
33
  //#endregion
350
34
  //#region src/index.ts
351
35
  /** Vite lifecycle adapter for the shared Darwin app bundle contract. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/app-icon.ts","../src/index.ts"],"sourcesContent":["// Orthogonal intents (maintained 2026-07-20; original user request: move the\n// skill-creator-v2 app-icon build chain into OpenTray's Vite plugin):\n// 1. Normalize a brand source image into a readable application icon with a\n// white safe-area tile and a transparent outer margin.\n// 2. Produce standard macOS ICNS, Windows ICO, and Linux theme PNG assets.\n// 3. Cache the output by source, source implementation, bundled implementation,\n// recipe, encoder, and output identity so stale generated assets cannot leak\n// into a dev runtime.\n// Compromise: published packages do not ship TypeScript sources, so their cache\n// uses a null source hash and the bundled plugin hash as the implementation\n// authority; linked consumers hash both layers.\n\nimport crypto from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { createRequire } from \"node:module\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { getSvgPath } from \"figma-squircle\";\nimport sharp from \"sharp\";\nimport type { Plugin } from \"vite\";\n\nimport type { AppIcon } from \"@opentray/spec\";\nimport { IconIcns, IconIco } from \"@shockpkg/icon-encoder\";\n\nconst ICON_SIZE = 1024;\nconst TILE_INSET = 64;\nconst TILE_SIZE = ICON_SIZE - TILE_INSET * 2;\nconst TILE_RADIUS = 196;\nconst TILE_SMOOTHING = 1;\nconst SYMBOL_SIZE = 704;\nconst APP_ICON_DENSITY = 72;\nconst CACHE_SCHEMA_VERSION = 6;\nconst RECIPE_VERSION = `squircle-v3:${ICON_SIZE}:${TILE_INSET}:${TILE_RADIUS}:${TILE_SMOOTHING}:${SYMBOL_SIZE}:${APP_ICON_DENSITY}dpi:icns-tagged`;\nconst ICO_SIZES = [16, 24, 32, 48, 64, 128, 256] as const;\nconst LINUX_SIZES = [16, 32, 48, 64, 128, 256, 512] as const;\nconst ICNS_REPRESENTATIONS = [\n { tag: \"ic12\", size: 64 },\n { tag: \"ic07\", size: 128 },\n { tag: \"ic13\", size: 256 },\n { tag: \"ic08\", size: 256 },\n { tag: \"ic04\", size: 16 },\n { tag: \"ic14\", size: 512 },\n { tag: \"ic09\", size: 512 },\n { tag: \"ic05\", size: 32 },\n { tag: \"ic10\", size: 1024 },\n { tag: \"ic11\", size: 32 },\n] as const;\n\nconst require = createRequire(import.meta.url);\n\nexport interface OpenTrayAppIconOptions {\n readonly sourcePath: string;\n readonly outputPath?: string;\n readonly icnsOutputPath?: string;\n readonly icoOutputPath?: string;\n readonly linuxOutputDirectory?: string;\n readonly manifestOutputPath?: string;\n readonly cachePath?: string;\n /** Advanced: override the module whose bytes identify the generator implementation. */\n readonly implementationPath?: string;\n /** Advanced: override the source file whose bytes identify the generator implementation. */\n readonly implementationSourcePath?: string;\n /**\n * Pre-composed source: skip glyph re-tiling; pass pixels through verbatim.\n */\n readonly composed?: boolean;\n /**\n * Separate macOS content source; ICNS encodes from this while ICO/Linux\n * use sourcePath.\n */\n readonly macosSourcePath?: string;\n}\n\nexport interface OpenTrayAppIconCacheMetadata {\n readonly schemaVersion: number;\n readonly sourceSha256: string;\n readonly macosSourceSha256?: string;\n readonly composed?: boolean;\n readonly sourceImplementationSha256: string | null;\n readonly implementationSha256: string;\n readonly recipeVersion: string;\n readonly sharpVersion: string;\n readonly iconEncoderVersion: string;\n readonly figmaSquircleVersion: string;\n readonly outputPath: string;\n readonly icnsOutputPath: string;\n readonly icoOutputPath: string;\n readonly linuxPngOutputPaths: readonly {\n readonly size: number;\n readonly path: string;\n }[];\n readonly manifestOutputPath: string;\n /** Absolute file sources ready to pass to OpenTray at runtime. */\n readonly appIcon: AppIcon;\n}\n\nexport interface OpenTrayAppIconManifest {\n readonly schemaVersion: 1;\n /** File paths are relative to the manifest file. */\n readonly appIcon: AppIcon;\n}\n\nexport interface OpenTrayAppIconPluginOptions {\n /** Brand source image. This is intentionally explicit so the plugin is app-agnostic. */\n readonly sourcePath: string;\n /**\n * Pre-composed source: the image already carries its background and\n * squircle mask, so glyph re-tiling (trim → symbol → white tile) is\n * skipped and the pixels pass through verbatim.\n */\n readonly composed?: boolean;\n /**\n * Separate macOS content source (e.g. the best-practice 824-in-1024\n * variant): ICNS encodes from this while ICO/Linux use sourcePath.\n */\n readonly macosSourcePath?: string;\n readonly outputPath?: string;\n readonly icnsOutputPath?: string;\n readonly icoOutputPath?: string;\n readonly linuxOutputDirectory?: string;\n readonly manifestOutputPath?: string;\n readonly cachePath?: string;\n}\n\n/** Generate one strict cross-platform AppIcon asset set. */\nexport async function generateOpenTrayAppIcon(\n options: OpenTrayAppIconOptions\n): Promise<OpenTrayAppIconCacheMetadata> {\n const outputPath =\n options.outputPath ??\n path.join(path.dirname(options.sourcePath), \"app-icon.png\");\n const icnsOutputPath =\n options.icnsOutputPath ??\n path.join(path.dirname(outputPath), \"app-icon.icns\");\n const icoOutputPath =\n options.icoOutputPath ??\n path.join(path.dirname(outputPath), \"app-icon.ico\");\n const linuxOutputDirectory =\n options.linuxOutputDirectory ??\n path.join(path.dirname(outputPath), \"linux\");\n const manifestOutputPath =\n options.manifestOutputPath ??\n path.join(path.dirname(outputPath), \"app-icon.json\");\n const cachePath =\n options.cachePath ??\n path.join(path.dirname(outputPath), \"../../.cache/app-icon.json\");\n const implementationPath =\n options.implementationPath ?? fileURLToPath(import.meta.url);\n const metadata = await createCacheMetadata({\n sourcePath: options.sourcePath,\n implementationPath,\n ...(options.composed === true ? { composed: true } : {}),\n ...(options.macosSourcePath === undefined\n ? {}\n : { macosSourcePath: options.macosSourcePath }),\n outputPath,\n icnsOutputPath,\n icoOutputPath,\n linuxOutputDirectory,\n manifestOutputPath,\n ...(options.implementationSourcePath === undefined\n ? {}\n : { implementationSourcePath: options.implementationSourcePath }),\n });\n\n if (await cacheMatches(cachePath, metadata)) return metadata;\n\n const rendered = await renderAppIcon(options.sourcePath, options.composed === true);\n const macosRendered =\n options.macosSourcePath === undefined || options.macosSourcePath === options.sourcePath\n ? rendered\n : await renderAppIcon(options.macosSourcePath, options.composed === true);\n await fs.mkdir(path.dirname(outputPath), { recursive: true });\n await fs.mkdir(path.dirname(icnsOutputPath), { recursive: true });\n await fs.mkdir(path.dirname(icoOutputPath), { recursive: true });\n await fs.mkdir(path.dirname(cachePath), { recursive: true });\n await fs.writeFile(outputPath, rendered);\n await encodeNativeIcons(rendered, macosRendered, icnsOutputPath, icoOutputPath);\n await writeLinuxIcons(rendered, metadata.linuxPngOutputPaths);\n await writeManifest(metadata);\n await fs.writeFile(\n cachePath,\n `${JSON.stringify(metadata, null, 2)}\\n`,\n \"utf8\"\n );\n return metadata;\n}\n\n/** Create the Vite plugin used by both serve and build modes. */\nexport function openTrayAppIconPlugin(\n options: OpenTrayAppIconPluginOptions\n): Plugin {\n let generation: Promise<OpenTrayAppIconCacheMetadata> | undefined;\n\n return {\n name: \"opentray/app-icon\",\n enforce: \"pre\",\n async configResolved(config) {\n const outputPath =\n options.outputPath ??\n path.resolve(config.root, \"static/icons/app-icon.png\");\n const icnsOutputPath =\n options.icnsOutputPath ??\n path.resolve(config.root, \"static/icons/app-icon.icns\");\n const icoOutputPath =\n options.icoOutputPath ??\n path.resolve(config.root, \"static/icons/app-icon.ico\");\n const linuxOutputDirectory =\n options.linuxOutputDirectory ??\n path.resolve(config.root, \"static/icons/linux\");\n const manifestOutputPath =\n options.manifestOutputPath ??\n path.resolve(config.root, \"static/icons/app-icon.json\");\n const cachePath =\n options.cachePath ?? path.resolve(config.root, \".cache/app-icon.json\");\n generation ??= generateOpenTrayAppIcon({\n sourcePath: path.resolve(options.sourcePath),\n ...(options.composed === true ? { composed: true } : {}),\n ...(options.macosSourcePath === undefined\n ? {}\n : { macosSourcePath: path.resolve(options.macosSourcePath) }),\n outputPath,\n icnsOutputPath,\n icoOutputPath,\n linuxOutputDirectory,\n manifestOutputPath,\n cachePath,\n });\n await generation;\n },\n };\n}\n\nasync function createCacheMetadata(options: {\n sourcePath: string;\n implementationPath: string;\n implementationSourcePath?: string;\n /** Pass-through mode: skip glyph re-tiling (part of cache identity). */\n composed?: boolean;\n /** Separate macOS content source (part of cache identity). */\n macosSourcePath?: string;\n outputPath: string;\n icnsOutputPath: string;\n icoOutputPath: string;\n linuxOutputDirectory: string;\n manifestOutputPath: string;\n}): Promise<OpenTrayAppIconCacheMetadata> {\n const sourceImplementationPath =\n options.implementationSourcePath ??\n (await resolveSourceImplementationPath(options.implementationPath));\n const linuxPngOutputPaths = LINUX_SIZES.map((size) => ({\n size,\n path: path.resolve(\n options.linuxOutputDirectory,\n `${size}x${size}`,\n \"app-icon.png\"\n ),\n }));\n const icnsOutputPath = path.resolve(options.icnsOutputPath);\n const icoOutputPath = path.resolve(options.icoOutputPath);\n const appIcon: AppIcon = [\n {\n platform: \"darwin\",\n format: \"icns\",\n source: { type: \"file\", path: icnsOutputPath },\n },\n {\n platform: \"windows\",\n format: \"ico\",\n source: { type: \"file\", path: icoOutputPath },\n },\n ...linuxPngOutputPaths.map(({ size, path: pngPath }) => ({\n platform: \"linux\" as const,\n format: \"png\" as const,\n size,\n source: { type: \"file\" as const, path: pngPath },\n })),\n ];\n return {\n schemaVersion: CACHE_SCHEMA_VERSION,\n sourceSha256: await sha256(options.sourcePath),\n ...(options.macosSourcePath === undefined\n ? {}\n : { macosSourceSha256: await sha256(options.macosSourcePath) }),\n ...(options.composed === true ? { composed: true } : {}),\n sourceImplementationSha256:\n sourceImplementationPath === null\n ? null\n : await sha256(sourceImplementationPath),\n implementationSha256: await sha256(options.implementationPath),\n recipeVersion: RECIPE_VERSION,\n sharpVersion: await packageVersion(\"sharp\"),\n iconEncoderVersion: await packageVersion(\"@shockpkg/icon-encoder\"),\n figmaSquircleVersion: await packageVersion(\"figma-squircle\"),\n outputPath: path.resolve(options.outputPath),\n icnsOutputPath,\n icoOutputPath,\n linuxPngOutputPaths,\n manifestOutputPath: path.resolve(options.manifestOutputPath),\n appIcon,\n };\n}\n\nasync function renderAppIcon(sourcePath: string, composed = false): Promise<Buffer> {\n if (composed) {\n // Pre-composed art: normalize to the 1024 canvas and pass through —\n // the background and squircle mask are already baked in.\n return sharp(sourcePath, { failOn: \"none\" })\n .resize(ICON_SIZE, ICON_SIZE, { fit: \"contain\", kernel: sharp.kernel.lanczos3 })\n .withMetadata({ density: APP_ICON_DENSITY })\n .png({ compressionLevel: 9 })\n .toBuffer();\n }\n const symbol = await sharp(sourcePath)\n .trim({ threshold: 0 })\n .resize(SYMBOL_SIZE, SYMBOL_SIZE, {\n fit: \"inside\",\n kernel: sharp.kernel.lanczos3,\n })\n .png()\n .toBuffer({ resolveWithObject: true });\n const symbolLeft = Math.round((ICON_SIZE - symbol.info.width) / 2);\n const symbolTop = Math.round((ICON_SIZE - symbol.info.height) / 2);\n const squirclePath = getSvgPath({\n width: TILE_SIZE,\n height: TILE_SIZE,\n cornerRadius: TILE_RADIUS,\n cornerSmoothing: TILE_SMOOTHING,\n preserveSmoothing: true,\n });\n const whiteTile = Buffer.from(\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${TILE_SIZE}\" height=\"${TILE_SIZE}\"><path d=\"${squirclePath}\" fill=\"#fff\"/></svg>`\n );\n return sharp({\n create: {\n width: ICON_SIZE,\n height: ICON_SIZE,\n channels: 4,\n background: { r: 0, g: 0, b: 0, alpha: 0 },\n },\n })\n .composite([\n { input: whiteTile, top: TILE_INSET, left: TILE_INSET },\n { input: symbol.data, top: symbolTop, left: symbolLeft },\n ])\n .withMetadata({ density: APP_ICON_DENSITY })\n .png({ compressionLevel: 9 })\n .toBuffer();\n}\n\nasync function encodeNativeIcons(\n rendered: Buffer,\n macosRendered: Buffer,\n icnsOutputPath: string,\n icoOutputPath: string\n): Promise<void> {\n const cacheFor = (source: Buffer): {\n pngAt(size: number): Promise<Buffer>;\n } => {\n const bySize = new Map<number, Buffer>();\n return {\n async pngAt(size: number): Promise<Buffer> {\n const cached = bySize.get(size);\n if (cached !== undefined) return cached;\n const png = await sharp(source)\n .resize(size, size, { fit: \"contain\" })\n .withMetadata({ density: APP_ICON_DENSITY })\n .png({ compressionLevel: 9 })\n .toBuffer();\n bySize.set(size, png);\n return png;\n },\n };\n };\n const windows = cacheFor(rendered);\n const macos = cacheFor(macosRendered);\n\n const icns = new IconIcns();\n icns.toc = true;\n for (const { tag, size } of ICNS_REPRESENTATIONS) {\n await icns.addFromPng(await macos.pngAt(size), [tag], false);\n }\n await fs.writeFile(icnsOutputPath, icns.encode());\n\n const ico = new IconIco();\n for (const size of ICO_SIZES) {\n await ico.addFromPng(await windows.pngAt(size), null, false);\n }\n await fs.writeFile(icoOutputPath, ico.encode());\n}\n\nasync function writeLinuxIcons(\n rendered: Buffer,\n outputs: OpenTrayAppIconCacheMetadata[\"linuxPngOutputPaths\"]\n): Promise<void> {\n await Promise.all(\n outputs.map(async ({ size, path: outputPath }) => {\n await fs.mkdir(path.dirname(outputPath), { recursive: true });\n await sharp(rendered)\n .resize(size, size, { fit: \"contain\" })\n .withMetadata({ density: APP_ICON_DENSITY })\n .png({ compressionLevel: 9 })\n .toFile(outputPath);\n })\n );\n}\n\nasync function writeManifest(\n metadata: OpenTrayAppIconCacheMetadata\n): Promise<void> {\n const manifestDirectory = path.dirname(metadata.manifestOutputPath);\n const relativeSource = (\n sourcePath: string\n ): { type: \"file\"; path: string } => ({\n type: \"file\",\n path: path\n .relative(manifestDirectory, sourcePath)\n .split(path.sep)\n .join(\"/\"),\n });\n const manifest: OpenTrayAppIconManifest = {\n schemaVersion: 1,\n appIcon: [\n {\n platform: \"darwin\",\n format: \"icns\",\n source: relativeSource(metadata.icnsOutputPath),\n },\n {\n platform: \"windows\",\n format: \"ico\",\n source: relativeSource(metadata.icoOutputPath),\n },\n ...metadata.linuxPngOutputPaths.map(({ size, path: pngPath }) => ({\n platform: \"linux\" as const,\n format: \"png\" as const,\n size,\n source: relativeSource(pngPath),\n })),\n ],\n };\n await fs.mkdir(manifestDirectory, { recursive: true });\n await fs.writeFile(\n metadata.manifestOutputPath,\n `${JSON.stringify(manifest, null, 2)}\\n`\n );\n}\n\nasync function cacheMatches(\n file: string,\n expected: OpenTrayAppIconCacheMetadata\n): Promise<boolean> {\n try {\n const parsed: unknown = JSON.parse(await fs.readFile(file, \"utf8\"));\n if (!isCacheMetadata(parsed) || !sameCacheIdentity(parsed, expected))\n return false;\n await Promise.all([\n fs.access(expected.outputPath),\n fs.access(expected.icnsOutputPath),\n fs.access(expected.icoOutputPath),\n fs.access(expected.manifestOutputPath),\n ...expected.linuxPngOutputPaths.map(({ path: outputPath }) =>\n fs.access(outputPath)\n ),\n ]);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction sameCacheIdentity(\n actual: OpenTrayAppIconCacheMetadata,\n expected: OpenTrayAppIconCacheMetadata\n): boolean {\n return (\n actual.schemaVersion === expected.schemaVersion &&\n actual.sourceSha256 === expected.sourceSha256 &&\n actual.macosSourceSha256 === expected.macosSourceSha256 &&\n actual.composed === expected.composed &&\n actual.sourceImplementationSha256 === expected.sourceImplementationSha256 &&\n actual.implementationSha256 === expected.implementationSha256 &&\n actual.recipeVersion === expected.recipeVersion &&\n actual.sharpVersion === expected.sharpVersion &&\n actual.iconEncoderVersion === expected.iconEncoderVersion &&\n actual.figmaSquircleVersion === expected.figmaSquircleVersion &&\n actual.outputPath === expected.outputPath &&\n actual.icnsOutputPath === expected.icnsOutputPath &&\n actual.icoOutputPath === expected.icoOutputPath &&\n actual.manifestOutputPath === expected.manifestOutputPath &&\n JSON.stringify(actual.linuxPngOutputPaths) ===\n JSON.stringify(expected.linuxPngOutputPaths) &&\n JSON.stringify(actual.appIcon) === JSON.stringify(expected.appIcon)\n );\n}\n\nfunction isCacheMetadata(\n value: unknown\n): value is OpenTrayAppIconCacheMetadata {\n if (typeof value !== \"object\" || value === null) return false;\n const record = value as Record<string, unknown>;\n return (\n typeof record.schemaVersion === \"number\" &&\n typeof record.sourceSha256 === \"string\" &&\n (record.macosSourceSha256 === undefined ||\n typeof record.macosSourceSha256 === \"string\") &&\n (record.composed === undefined || typeof record.composed === \"boolean\") &&\n (record.sourceImplementationSha256 === null ||\n typeof record.sourceImplementationSha256 === \"string\") &&\n typeof record.implementationSha256 === \"string\" &&\n typeof record.recipeVersion === \"string\" &&\n typeof record.sharpVersion === \"string\" &&\n typeof record.iconEncoderVersion === \"string\" &&\n typeof record.figmaSquircleVersion === \"string\" &&\n typeof record.outputPath === \"string\" &&\n typeof record.icnsOutputPath === \"string\" &&\n typeof record.icoOutputPath === \"string\" &&\n Array.isArray(record.linuxPngOutputPaths) &&\n typeof record.manifestOutputPath === \"string\" &&\n Array.isArray(record.appIcon)\n );\n}\n\nasync function resolveSourceImplementationPath(\n implementationPath: string\n): Promise<string | null> {\n if (implementationPath.endsWith(`${path.sep}src${path.sep}app-icon.ts`)) {\n return implementationPath;\n }\n const candidate = path.join(\n path.dirname(path.dirname(implementationPath)),\n \"src\",\n \"app-icon.ts\"\n );\n try {\n await fs.access(candidate);\n return candidate;\n } catch {\n return null;\n }\n}\n\nasync function packageVersion(packageName: string): Promise<string> {\n const entryPath = require.resolve(packageName);\n let directory = path.dirname(entryPath);\n while (true) {\n const packagePath = path.join(directory, \"package.json\");\n try {\n const parsed: unknown = JSON.parse(\n await fs.readFile(packagePath, \"utf8\")\n );\n if (isPackageMetadata(parsed, packageName)) return parsed.version;\n } catch {\n // Continue towards the package root; package exports may hide package.json.\n }\n const parent = path.dirname(directory);\n if (parent === directory) break;\n directory = parent;\n }\n throw new Error(`Unable to resolve ${packageName} package version`);\n}\n\nfunction isPackageMetadata(\n value: unknown,\n packageName: string\n): value is { name: string; version: string } {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"name\" in value &&\n value.name === packageName &&\n \"version\" in value &&\n typeof value.version === \"string\"\n );\n}\n\nasync function sha256(file: string): Promise<string> {\n return crypto\n .createHash(\"sha256\")\n .update(await fs.readFile(file))\n .digest(\"hex\");\n}\n","// Orthogonal intents (maintained 2026-07-20; original user request: expose the\n// OpenTray Vite packaging adapter and move skill-creator-v2 app-icon generation into it):\n// 1. Stage Vite bundle metadata and native runtime artifacts through one adapter.\n// 2. Re-export the app-icon generator without coupling the packaging contract to a consumer.\n// Compromise: both exports share the package entrypoint because Vite resolves one public adapter\n// module and the icon plugin has no runtime dependency on the artifact manifest.\n\nimport { resolve } from \"node:path\";\n\nimport {\n buildDarwinAppBundle,\n stageOpenTrayPackage,\n type DarwinAppBundleOptions,\n type OpenTrayArtifactInput,\n type OpenTrayPackagingApp,\n type OpenTrayPackageManifest,\n type OpenTrayPackageResult,\n type OpenTrayDarwinAppBundleResult,\n} from \"@opentray/packaging\";\n\nexport interface OpenTrayVitePluginOptions {\n readonly app: OpenTrayPackagingApp;\n readonly runtimeHost: OpenTrayArtifactInput;\n readonly nativeArtifacts?: Readonly<Record<string, OpenTrayArtifactInput>>;\n readonly companionAssets?: Readonly<Record<string, OpenTrayArtifactInput>>;\n readonly entry?: string;\n readonly manifestPath?: string;\n}\n\nexport interface ViteResolvedConfigLike {\n readonly root: string;\n readonly mode: string;\n readonly build: {\n readonly outDir: string;\n };\n}\n\nexport interface ViteOutputChunkLike {\n readonly type?: string;\n readonly isEntry?: boolean;\n readonly fileName?: string;\n readonly facadeModuleId?: string | null;\n readonly name?: string;\n}\n\nexport type ViteBundleLike = Readonly<Record<string, unknown>>;\n\nexport interface OpenTrayVitePlugin {\n readonly name: \"opentray-packaging\";\n readonly apply: \"build\";\n configResolved(config: ViteResolvedConfigLike): void;\n writeBundle(options: unknown, bundle: ViteBundleLike): Promise<void>;\n readonly getLastResult: () => OpenTrayPackageResult | undefined;\n}\n\nexport interface OpenTrayViteAppBundlePluginOptions\n extends Omit<DarwinAppBundleOptions, \"bundlePath\" | \"reinitialize\"> {\n /** Optional output path. Defaults to `<vite outDir>/<appName>.app`. */\n readonly bundlePath?: string;\n}\n\nexport interface OpenTrayViteAppBundlePlugin {\n readonly name: \"opentray-app-bundle\";\n readonly apply: \"build\";\n configResolved(config: ViteResolvedConfigLike): void;\n writeBundle(): Promise<void>;\n readonly getLastResult: () => OpenTrayDarwinAppBundleResult | undefined;\n}\n\n/** Vite lifecycle adapter for the shared Darwin app bundle contract. */\nexport const openTrayAppBundlePlugin = (\n options: OpenTrayViteAppBundlePluginOptions,\n): OpenTrayViteAppBundlePlugin => {\n let config: ViteResolvedConfigLike | undefined;\n let lastResult: OpenTrayDarwinAppBundleResult | undefined;\n return {\n name: \"opentray-app-bundle\",\n apply: \"build\",\n configResolved(resolvedConfig) {\n config = resolvedConfig;\n },\n async writeBundle() {\n const resolvedConfig = config ?? {\n root: process.cwd(),\n mode: \"production\",\n build: { outDir: \"dist\" },\n };\n const bundlePath =\n options.bundlePath ?? resolve(\n resolvedConfig.root,\n resolvedConfig.build.outDir,\n `${options.appName}.app`,\n );\n lastResult = await buildDarwinAppBundle({ ...options, bundlePath });\n },\n getLastResult: () => lastResult,\n };\n};\n\nexport const openTrayVitePlugin = (options: OpenTrayVitePluginOptions): OpenTrayVitePlugin => {\n let config: ViteResolvedConfigLike | undefined;\n let lastResult: OpenTrayPackageResult | undefined;\n\n return {\n name: \"opentray-packaging\",\n apply: \"build\",\n configResolved(resolvedConfig) {\n config = resolvedConfig;\n },\n async writeBundle(_options, bundle) {\n const resolvedConfig = config ?? {\n root: process.cwd(),\n mode: \"production\",\n build: { outDir: \"dist\" },\n };\n lastResult = await stageOpenTrayPackage({\n app: options.app,\n outDir: resolve(resolvedConfig.root, resolvedConfig.build.outDir),\n entry: options.entry ?? resolveViteEntry(bundle),\n adapter: { name: \"vite\", mode: resolvedConfig.mode },\n runtimeHost: options.runtimeHost,\n ...(options.nativeArtifacts === undefined\n ? {}\n : { nativeArtifacts: options.nativeArtifacts }),\n ...(options.companionAssets === undefined\n ? {}\n : { companionAssets: options.companionAssets }),\n ...(options.manifestPath === undefined ? {} : { manifestPath: options.manifestPath }),\n });\n },\n getLastResult: () => lastResult,\n };\n};\n\nexport const resolveViteEntry = (bundle: ViteBundleLike): string => {\n const entry = Object.values(bundle)\n .map(asOutputChunk)\n .find((chunk): chunk is ViteOutputChunkLike => chunk?.isEntry === true);\n const identity = entry?.facadeModuleId ?? entry?.fileName ?? entry?.name;\n if (identity === undefined || identity.length === 0) {\n throw new Error(\"OpenTray Vite packaging requires a Vite entry chunk or explicit entry option\");\n }\n return identity;\n};\n\nconst asOutputChunk = (value: unknown): ViteOutputChunkLike | undefined => {\n if (typeof value !== \"object\" || value === null) {\n return undefined;\n }\n const record = value as Record<string, unknown>;\n return {\n ...(record.type === \"chunk\" ? { type: \"chunk\" } : {}),\n ...(typeof record.isEntry === \"boolean\" ? { isEntry: record.isEntry } : {}),\n ...(typeof record.fileName === \"string\" ? { fileName: record.fileName } : {}),\n ...(typeof record.facadeModuleId === \"string\" || record.facadeModuleId === null\n ? { facadeModuleId: record.facadeModuleId }\n : {}),\n ...(typeof record.name === \"string\" ? { name: record.name } : {}),\n };\n};\n\nexport type { OpenTrayPackageManifest, OpenTrayPackageResult };\n\nexport {\n generateOpenTrayAppIcon,\n openTrayAppIconPlugin,\n type OpenTrayAppIconCacheMetadata,\n type OpenTrayAppIconManifest,\n type OpenTrayAppIconOptions,\n type OpenTrayAppIconPluginOptions,\n} from \"./app-icon\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/app-icon.ts","../src/index.ts"],"sourcesContent":["// Thin shell over the shared icon kernel (shared-icon-kernel change,\n// 2026-09): generation, composition, glyph defaults, and platform encoders\n// live in @opentray/icon — one implementation shared with create-opentray and\n// the runtime daemon. This module keeps the historical public surface of the\n// Vite adapter (openTrayAppIconPlugin plus re-exports) and contributes only\n// the Vite lifecycle glue.\n\nimport path from \"node:path\";\n\nimport type { Plugin } from \"vite\";\n\nimport {\n generateOpenTrayAppIcon,\n type OpenTrayAppIconCacheMetadata,\n type OpenTrayAppIconOptions,\n} from \"@opentray/icon\";\n\nexport type {\n OpenTrayAppIconCacheMetadata,\n OpenTrayAppIconManifest,\n OpenTrayAppIconOptions,\n} from \"@opentray/icon\";\n\nexport { generateOpenTrayAppIcon };\n\nexport interface OpenTrayAppIconPluginOptions {\n /** Brand source image. This is intentionally explicit so the plugin is app-agnostic. */\n readonly sourcePath: string;\n /**\n * Pre-composed source: the image already carries its background and\n * squircle mask, so glyph re-tiling (trim → symbol → white tile) is\n * skipped and the pixels pass through verbatim.\n */\n readonly composed?: boolean;\n /**\n * Separate macOS content source (e.g. the best-practice 824-in-1024\n * variant): ICNS encodes from this while ICO/Linux use sourcePath.\n */\n readonly macosSourcePath?: string;\n readonly outputPath?: string;\n readonly icnsOutputPath?: string;\n readonly icoOutputPath?: string;\n readonly linuxOutputDirectory?: string;\n readonly manifestOutputPath?: string;\n readonly cachePath?: string;\n}\n\n/** Create the Vite plugin used by both serve and build modes. */\nexport function openTrayAppIconPlugin(\n options: OpenTrayAppIconPluginOptions\n): Plugin {\n let generation: Promise<OpenTrayAppIconCacheMetadata> | undefined;\n\n return {\n name: \"opentray/app-icon\",\n enforce: \"pre\",\n async configResolved(config) {\n const outputPath =\n options.outputPath ??\n path.resolve(config.root, \"static/icons/app-icon.png\");\n const icnsOutputPath =\n options.icnsOutputPath ??\n path.resolve(config.root, \"static/icons/app-icon.icns\");\n const icoOutputPath =\n options.icoOutputPath ??\n path.resolve(config.root, \"static/icons/app-icon.ico\");\n const linuxOutputDirectory =\n options.linuxOutputDirectory ??\n path.resolve(config.root, \"static/icons/linux\");\n const manifestOutputPath =\n options.manifestOutputPath ??\n path.resolve(config.root, \"static/icons/app-icon.json\");\n const cachePath =\n options.cachePath ?? path.resolve(config.root, \".cache/app-icon.json\");\n generation ??= generateOpenTrayAppIcon({\n sourcePath: path.resolve(options.sourcePath),\n ...(options.composed === true ? { composed: true } : {}),\n ...(options.macosSourcePath === undefined\n ? {}\n : { macosSourcePath: path.resolve(options.macosSourcePath) }),\n outputPath,\n icnsOutputPath,\n icoOutputPath,\n linuxOutputDirectory,\n manifestOutputPath,\n cachePath,\n });\n await generation;\n },\n };\n}\n","// Orthogonal intents (maintained 2026-07-20; original user request: expose the\n// OpenTray Vite packaging adapter and move skill-creator-v2 app-icon generation into it):\n// 1. Stage Vite bundle metadata and native runtime artifacts through one adapter.\n// 2. Re-export the app-icon generator without coupling the packaging contract to a consumer.\n// Compromise: both exports share the package entrypoint because Vite resolves one public adapter\n// module and the icon plugin has no runtime dependency on the artifact manifest.\n\nimport { resolve } from \"node:path\";\n\nimport {\n buildDarwinAppBundle,\n stageOpenTrayPackage,\n type DarwinAppBundleOptions,\n type OpenTrayArtifactInput,\n type OpenTrayPackagingApp,\n type OpenTrayPackageManifest,\n type OpenTrayPackageResult,\n type OpenTrayDarwinAppBundleResult,\n} from \"@opentray/packaging\";\n\nexport interface OpenTrayVitePluginOptions {\n readonly app: OpenTrayPackagingApp;\n readonly runtimeHost: OpenTrayArtifactInput;\n readonly nativeArtifacts?: Readonly<Record<string, OpenTrayArtifactInput>>;\n readonly companionAssets?: Readonly<Record<string, OpenTrayArtifactInput>>;\n readonly entry?: string;\n readonly manifestPath?: string;\n}\n\nexport interface ViteResolvedConfigLike {\n readonly root: string;\n readonly mode: string;\n readonly build: {\n readonly outDir: string;\n };\n}\n\nexport interface ViteOutputChunkLike {\n readonly type?: string;\n readonly isEntry?: boolean;\n readonly fileName?: string;\n readonly facadeModuleId?: string | null;\n readonly name?: string;\n}\n\nexport type ViteBundleLike = Readonly<Record<string, unknown>>;\n\nexport interface OpenTrayVitePlugin {\n readonly name: \"opentray-packaging\";\n readonly apply: \"build\";\n configResolved(config: ViteResolvedConfigLike): void;\n writeBundle(options: unknown, bundle: ViteBundleLike): Promise<void>;\n readonly getLastResult: () => OpenTrayPackageResult | undefined;\n}\n\nexport interface OpenTrayViteAppBundlePluginOptions\n extends Omit<DarwinAppBundleOptions, \"bundlePath\" | \"reinitialize\"> {\n /** Optional output path. Defaults to `<vite outDir>/<appName>.app`. */\n readonly bundlePath?: string;\n}\n\nexport interface OpenTrayViteAppBundlePlugin {\n readonly name: \"opentray-app-bundle\";\n readonly apply: \"build\";\n configResolved(config: ViteResolvedConfigLike): void;\n writeBundle(): Promise<void>;\n readonly getLastResult: () => OpenTrayDarwinAppBundleResult | undefined;\n}\n\n/** Vite lifecycle adapter for the shared Darwin app bundle contract. */\nexport const openTrayAppBundlePlugin = (\n options: OpenTrayViteAppBundlePluginOptions,\n): OpenTrayViteAppBundlePlugin => {\n let config: ViteResolvedConfigLike | undefined;\n let lastResult: OpenTrayDarwinAppBundleResult | undefined;\n return {\n name: \"opentray-app-bundle\",\n apply: \"build\",\n configResolved(resolvedConfig) {\n config = resolvedConfig;\n },\n async writeBundle() {\n const resolvedConfig = config ?? {\n root: process.cwd(),\n mode: \"production\",\n build: { outDir: \"dist\" },\n };\n const bundlePath =\n options.bundlePath ?? resolve(\n resolvedConfig.root,\n resolvedConfig.build.outDir,\n `${options.appName}.app`,\n );\n lastResult = await buildDarwinAppBundle({ ...options, bundlePath });\n },\n getLastResult: () => lastResult,\n };\n};\n\nexport const openTrayVitePlugin = (options: OpenTrayVitePluginOptions): OpenTrayVitePlugin => {\n let config: ViteResolvedConfigLike | undefined;\n let lastResult: OpenTrayPackageResult | undefined;\n\n return {\n name: \"opentray-packaging\",\n apply: \"build\",\n configResolved(resolvedConfig) {\n config = resolvedConfig;\n },\n async writeBundle(_options, bundle) {\n const resolvedConfig = config ?? {\n root: process.cwd(),\n mode: \"production\",\n build: { outDir: \"dist\" },\n };\n lastResult = await stageOpenTrayPackage({\n app: options.app,\n outDir: resolve(resolvedConfig.root, resolvedConfig.build.outDir),\n entry: options.entry ?? resolveViteEntry(bundle),\n adapter: { name: \"vite\", mode: resolvedConfig.mode },\n runtimeHost: options.runtimeHost,\n ...(options.nativeArtifacts === undefined\n ? {}\n : { nativeArtifacts: options.nativeArtifacts }),\n ...(options.companionAssets === undefined\n ? {}\n : { companionAssets: options.companionAssets }),\n ...(options.manifestPath === undefined ? {} : { manifestPath: options.manifestPath }),\n });\n },\n getLastResult: () => lastResult,\n };\n};\n\nexport const resolveViteEntry = (bundle: ViteBundleLike): string => {\n const entry = Object.values(bundle)\n .map(asOutputChunk)\n .find((chunk): chunk is ViteOutputChunkLike => chunk?.isEntry === true);\n const identity = entry?.facadeModuleId ?? entry?.fileName ?? entry?.name;\n if (identity === undefined || identity.length === 0) {\n throw new Error(\"OpenTray Vite packaging requires a Vite entry chunk or explicit entry option\");\n }\n return identity;\n};\n\nconst asOutputChunk = (value: unknown): ViteOutputChunkLike | undefined => {\n if (typeof value !== \"object\" || value === null) {\n return undefined;\n }\n const record = value as Record<string, unknown>;\n return {\n ...(record.type === \"chunk\" ? { type: \"chunk\" } : {}),\n ...(typeof record.isEntry === \"boolean\" ? { isEntry: record.isEntry } : {}),\n ...(typeof record.fileName === \"string\" ? { fileName: record.fileName } : {}),\n ...(typeof record.facadeModuleId === \"string\" || record.facadeModuleId === null\n ? { facadeModuleId: record.facadeModuleId }\n : {}),\n ...(typeof record.name === \"string\" ? { name: record.name } : {}),\n };\n};\n\nexport type { OpenTrayPackageManifest, OpenTrayPackageResult };\n\nexport {\n generateOpenTrayAppIcon,\n openTrayAppIconPlugin,\n type OpenTrayAppIconCacheMetadata,\n type OpenTrayAppIconManifest,\n type OpenTrayAppIconOptions,\n type OpenTrayAppIconPluginOptions,\n} from \"./app-icon\";\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opentray/vite-plugin",
3
- "version": "0.21.1",
3
+ "version": "0.22.0",
4
4
  "description": "Vite adapter for the OpenTray runtime artifact packaging contract.",
5
5
  "keywords": [
6
6
  "app-icon",
@@ -27,11 +27,9 @@
27
27
  "./package.json": "./package.json"
28
28
  },
29
29
  "dependencies": {
30
- "@shockpkg/icon-encoder": "^3.2.3",
31
- "figma-squircle": "^1.1.0",
32
- "sharp": "^0.33.5",
33
- "@opentray/packaging": "0.21.1",
34
- "@opentray/spec": "0.21.1"
30
+ "@opentray/icon": "0.22.0",
31
+ "@opentray/spec": "0.22.0",
32
+ "@opentray/packaging": "0.22.0"
35
33
  },
36
34
  "devDependencies": {
37
35
  "tsdown": "^0.22.1",