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 +1 -1
- package/readme.markdown +1 -0
- package/src/cli.ts +25 -21
- package/src/config.ts +9 -6
- package/src/runtime.ts +2 -2
- package/src/vite.ts +4 -4
package/package.json
CHANGED
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: [
|
|
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
|
}
|