quicklook-kit 0.1.1 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quicklook-kit",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Build macOS QuickLook previews with any Vite-based web framework",
5
5
  "license": "MIT",
6
6
  "keywords": [
package/readme.markdown CHANGED
@@ -58,6 +58,7 @@ The config can also be `quicklook.config.js`, `.json`, `.yaml` or `.toml`, or a
58
58
  | `bundleId` | required | The extension gets `<bundleId>.Extension` |
59
59
  | `types` | required | File types to preview |
60
60
  | `framework` | required | `'sveltekit'`, `'astro'`, `'solid-start'`, `'nuxt'`, `'analog'`, or an integration |
61
+ | `icon` | none | Path to an `.icns` file for the app |
61
62
  | `macOS` | `'14.0'` | Minimum macOS version |
62
63
  | `integrations` | `[]` | See [Integrations](#integrations) |
63
64
  | `csp` | locked down | Directives here replace the defaults of the same name |
package/src/cli.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  import { createHash } from 'node:crypto';
5
5
  import { existsSync } from 'node:fs';
6
6
  import { cp, rm } from 'node:fs/promises';
7
- import { resolve } from 'node:path';
7
+ import { basename, resolve } from 'node:path';
8
8
  import process from 'node:process';
9
9
  import { fileURLToPath } from 'node:url';
10
10
  import { $, Glob } from 'bun';
@@ -13,11 +13,11 @@ import {
13
13
  type DeclaredType,
14
14
  loadConfig,
15
15
  serializeCsp,
16
- type XcodeSpec
16
+ type XcodeSpec,
17
17
  } from './config.ts';
18
18
 
19
19
  const config = await loadConfig();
20
- const { root, name, bundleId, macOS, types, framework } = config;
20
+ const { root, name, bundleId, macOS, types, framework, icon } = config;
21
21
  const target = name.replace(/[^A-Za-z0-9]/g, '');
22
22
  // Everything generated lives here, away from the framework's own output.
23
23
  const work = `${root}/.quicklook`;
@@ -35,7 +35,7 @@ const inlineScript = /<script\b(?![^>]*\ssrc=)[^>]*>([\s\S]*?)<\/script>/gi;
35
35
  async function run(command: string[]) {
36
36
  const child = Bun.spawn(['bun', 'run', ...command], {
37
37
  cwd: root,
38
- stdio: ['inherit', 'inherit', 'inherit']
38
+ stdio: ['inherit', 'inherit', 'inherit'],
39
39
  });
40
40
  const code = await child.exited;
41
41
  if (code !== 0) {
@@ -81,20 +81,24 @@ async function build() {
81
81
  CURRENT_PROJECT_VERSION: '1',
82
82
  // Ad-hoc signing: enough for QuickLook to load it, no Apple Developer account needed.
83
83
  CODE_SIGN_STYLE: 'Manual',
84
- CODE_SIGN_IDENTITY: '-'
85
- }
84
+ CODE_SIGN_IDENTITY: '-',
85
+ },
86
86
  },
87
87
  targets: {
88
88
  [target]: {
89
89
  type: 'application',
90
90
  platform: 'macOS',
91
- sources: [`${native}/App`],
91
+ sources: [
92
+ `${native}/App`,
93
+ ...(icon ? [{ path: resolve(root, icon), buildPhase: 'resources' }] : []),
94
+ ],
92
95
  settings: { PRODUCT_NAME: name, PRODUCT_BUNDLE_IDENTIFIER: bundleId },
93
96
  dependencies: [{ target: `${target}Extension`, embed: true, codeSign: true }],
94
97
  info: {
95
98
  path: `${work}/App/Info.plist`,
96
99
  properties: {
97
100
  CFBundleName: name,
101
+ ...(icon && { CFBundleIconFile: basename(icon, '.icns') }),
98
102
  LSMinimumSystemVersion: macOS,
99
103
  UTExportedTypeDeclarations: types
100
104
  .filter((type): type is DeclaredType => typeof type !== 'string')
@@ -102,10 +106,10 @@ async function build() {
102
106
  UTTypeIdentifier: type.id,
103
107
  UTTypeDescription: type.description,
104
108
  UTTypeConformsTo: type.conformsTo,
105
- UTTypeTagSpecification: { 'public.filename-extension': type.extensions }
106
- }))
107
- }
108
- }
109
+ UTTypeTagSpecification: { 'public.filename-extension': type.extensions },
110
+ })),
111
+ },
112
+ },
109
113
  },
110
114
  [`${target}Extension`]: {
111
115
  type: 'app-extension',
@@ -121,8 +125,8 @@ async function build() {
121
125
  'com.apple.security.files.user-selected.read-only': true,
122
126
  // WKWebView's content process won't launch in a sandbox without this,
123
127
  // even for a page that loads nothing. The page's CSP blocks all requests.
124
- 'com.apple.security.network.client': true
125
- }
128
+ 'com.apple.security.network.client': true,
129
+ },
126
130
  },
127
131
  info: {
128
132
  path: `${work}/Extension/Info.plist`,
@@ -136,15 +140,15 @@ async function build() {
136
140
  NSExtensionPrincipalClass: 'PreviewViewController',
137
141
  NSExtensionAttributes: {
138
142
  QLSupportedContentTypes: types.map((type) =>
139
- typeof type === 'string' ? type : type.id
143
+ typeof type === 'string' ? type : type.id,
140
144
  ),
141
- QLSupportsSearchableItems: false
142
- }
143
- }
144
- }
145
- }
146
- }
147
- }
145
+ QLSupportsSearchableItems: false,
146
+ },
147
+ },
148
+ },
149
+ },
150
+ },
151
+ },
148
152
  };
149
153
  for (const integration of config.integrations) {
150
154
  await integration.hooks?.['xcode:spec']?.(spec, config);
package/src/config.ts CHANGED
@@ -52,6 +52,8 @@ export interface QuickLookConfig {
52
52
  /** App name, shown in Finder and System Settings. */
53
53
  name: string;
54
54
  bundleId: string;
55
+ /** Path to the app's .icns icon, relative to the project root. */
56
+ icon?: string;
55
57
  /** Minimum macOS version. Defaults to 14.0. */
56
58
  macOS?: string;
57
59
  types: FileType[];
@@ -70,7 +72,8 @@ export interface QuickLookConfig {
70
72
  export type PageConfig = Pick<ResolvedConfig, 'name' | 'bundleId' | 'types' | 'public'>;
71
73
 
72
74
  export interface ResolvedConfig
73
- extends Required<Omit<QuickLookConfig, 'framework' | 'integrations'>> {
75
+ extends Required<Omit<QuickLookConfig, 'framework' | 'integrations' | 'icon'>>,
76
+ Pick<QuickLookConfig, 'icon'> {
74
77
  root: string;
75
78
  framework: Integration & { build: NonNullable<Integration['build']> };
76
79
  /** The framework first, then the configured integrations, in order. */
@@ -84,7 +87,7 @@ export const defaultCsp: Csp = {
84
87
  'style-src': ["'self'", "'unsafe-inline'"],
85
88
  'img-src': ["'self'", 'data:', 'blob:'],
86
89
  'font-src': ["'self'", 'data:'],
87
- 'media-src': ["'self'", 'data:', 'blob:']
90
+ 'media-src': ["'self'", 'data:', 'blob:'],
88
91
  };
89
92
 
90
93
  export const defineConfig = (config: QuickLookConfig) => config;
@@ -97,7 +100,7 @@ export const serializeCsp = (csp: Csp) =>
97
100
  /** `csp` with `sources` appended to `directive`. */
98
101
  export const addSources = (csp: Csp, directive: string, ...sources: string[]): Csp => ({
99
102
  ...csp,
100
- [directive]: [...(csp[directive] ?? []), ...sources]
103
+ [directive]: [...(csp[directive] ?? []), ...sources],
101
104
  });
102
105
 
103
106
  /**
@@ -114,7 +117,7 @@ const frameworkBuilds: Record<string, [dev: string[], command: string[], outDir:
114
117
  // `solidStart({ ssr: false })` and Nitro's static preset prerendering `/`.
115
118
  'solid-start': [['vite', 'dev'], ['vite', 'build'], '.output/public'],
116
119
  // @sveltejs/adapter-static, writing `build`.
117
- sveltekit: [['vite', 'dev'], ['vite', 'build'], 'build']
120
+ sveltekit: [['vite', 'dev'], ['vite', 'build'], 'build'],
118
121
  };
119
122
 
120
123
  function builtinFramework(name: string, options?: unknown): Integration | undefined {
@@ -125,7 +128,7 @@ function builtinFramework(name: string, options?: unknown): Integration | undefi
125
128
  const [dev, command, outDir] = builtin;
126
129
  return {
127
130
  name,
128
- build: { dev, command, outDir: (options as { outDir?: string } | undefined)?.outDir ?? outDir }
131
+ build: { dev, command, outDir: (options as { outDir?: string } | undefined)?.outDir ?? outDir },
129
132
  };
130
133
  }
131
134
 
@@ -167,7 +170,7 @@ export async function loadConfig(root = process.cwd()): Promise<ResolvedConfig>
167
170
  csp: { ...defaultCsp, ...config.csp },
168
171
  root,
169
172
  framework: { ...framework, build: framework.build },
170
- integrations
173
+ integrations,
171
174
  };
172
175
  for (const integration of integrations) {
173
176
  await integration.hooks?.['config:resolved']?.(resolved);
package/src/runtime.ts CHANGED
@@ -22,7 +22,7 @@ export function nativeFile(): QuickLookFile | undefined {
22
22
  return {
23
23
  name: injected.name,
24
24
  extension: injected.extension,
25
- bytes: Uint8Array.from(atob(injected.base64), (c) => c.charCodeAt(0))
25
+ bytes: Uint8Array.from(atob(injected.base64), (c) => c.charCodeAt(0)),
26
26
  };
27
27
  }
28
28
 
@@ -31,6 +31,6 @@ export async function browserFile(file: File): Promise<QuickLookFile> {
31
31
  return {
32
32
  name: file.name,
33
33
  extension: file.name.includes('.') ? (file.name.split('.').pop() ?? '').toLowerCase() : '',
34
- bytes: new Uint8Array(await file.arrayBuffer())
34
+ bytes: new Uint8Array(await file.arrayBuffer()),
35
35
  };
36
36
  }
package/src/vite.ts CHANGED
@@ -20,8 +20,8 @@ export { browserFile, nativeFile } from 'quicklook-kit/runtime';`;
20
20
  addSources(config.csp, 'script-src', "'unsafe-inline'"),
21
21
  'connect-src',
22
22
  "'self'",
23
- 'ws:'
24
- )
23
+ 'ws:',
24
+ ),
25
25
  );
26
26
 
27
27
  return [
@@ -34,8 +34,8 @@ export { browserFile, nativeFile } from 'quicklook-kit/runtime';`;
34
34
  response.setHeader('Content-Security-Policy-Report-Only', devCsp);
35
35
  next();
36
36
  });
37
- }
37
+ },
38
38
  },
39
- ...config.integrations.flatMap((integration) => integration.hooks?.vite?.() ?? [])
39
+ ...config.integrations.flatMap((integration) => integration.hooks?.vite?.() ?? []),
40
40
  ];
41
41
  }