@wells-nelson/build-tools 1.0.2 → 1.0.3

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
@@ -36,6 +36,17 @@ export default defineConfig(({ mode, command }) => ({
36
36
  }));
37
37
  ```
38
38
 
39
+ ### Content Security Policy
40
+
41
+ `contentSecurityPolicy({ supabaseUrl })` returns SvelteKit `csp` options: hashes for SvelteKit's inline
42
+ scripts and no other scripts, network access only to the app and its Supabase project (REST and
43
+ Realtime), images from the app, `data:`/`blob:` and Google profile pictures. Pass `connect` / `img` for
44
+ more sources.
45
+
46
+ ```js
47
+ sveltekit({ csp: contentSecurityPolicy({ supabaseUrl: env.VITE_SUPABASE_URL }), ... })
48
+ ```
49
+
39
50
  ## Service worker (`@wells-nelson/build-tools/service-worker`)
40
51
 
41
52
  ```ts
@@ -53,6 +64,10 @@ offline they fall back to the cached shell. A new worker skips waiting and takes
53
64
  pages. `networkOnly` paths are never cached (default: SvelteKit's `_app/version.json`, so
54
65
  `updated` from `$app/state` notices new deploys). `dev` caches nothing.
55
66
 
67
+ On activate a worker deletes only its own older caches (`<cachePrefix>-cache-*`). Give each app
68
+ on an origin its own `cachePrefix`: GitHub Pages project sites of one user all share
69
+ `https://<user>.github.io`.
70
+
56
71
  ## Templates
57
72
 
58
73
  `templates/deploy-pages.yml` (replace `__REPO__`) and `templates/manifest.webmanifest`.
@@ -29,13 +29,16 @@ export interface ServiceWorkerScopeLike {
29
29
  }
30
30
  export interface AppShellCacheOptions {
31
31
  sw: ServiceWorkerScopeLike;
32
- /** Build version; each deploy gets a fresh cache and old ones are deleted. */
32
+ /** Build version; each deploy gets a fresh cache and this app's old ones are deleted. */
33
33
  version: string;
34
34
  /** Files to precache, relative to the scope (SvelteKit's `immutable` and `assets`). */
35
35
  files: Array<string | {
36
36
  path: string;
37
37
  }>;
38
- /** Cache name prefix (default 'app'). */
38
+ /**
39
+ * Cache name prefix (default 'app'). Give every app on the same origin its own prefix: a worker
40
+ * only deletes caches named `<cachePrefix>-cache-*`, so apps sharing an origin keep each other's.
41
+ */
39
42
  cachePrefix?: string;
40
43
  /**
41
44
  * Development: cache nothing and delete what an earlier worker cached, so every code change
@@ -44,9 +44,12 @@ export function registerAppShellCache({ sw, version, files, cachePrefix = 'app',
44
44
  });
45
45
  sw.addEventListener('activate', (event) => {
46
46
  event.waitUntil((async () => {
47
- for (const key of await caches.keys())
48
- if (key !== CACHE)
47
+ // Only this app's older caches: other apps on the same origin (e.g. two GitHub Pages
48
+ // project sites under one user) keep theirs.
49
+ for (const key of await caches.keys()) {
50
+ if (key !== CACHE && key.startsWith(`${cachePrefix}-cache-`))
49
51
  await caches.delete(key);
52
+ }
50
53
  await sw.clients.claim();
51
54
  })());
52
55
  });
package/dist/vite.d.ts CHANGED
@@ -44,3 +44,25 @@ export declare function devJsonModule({ id, file, enabled }: {
44
44
  file: string;
45
45
  enabled: boolean;
46
46
  }): Plugin;
47
+ /**
48
+ * SvelteKit `csp` options for an offline-first app on static hosting. SvelteKit adds a
49
+ * `<meta http-equiv="content-security-policy">` with hashes of its own inline scripts, so only
50
+ * the app's bundled scripts run: injected markup can't load or run other scripts. Network access
51
+ * is limited to the app's origin and its Supabase project (REST and Realtime); images also allow
52
+ * data/blob URLs and Google profile pictures. Styles allow inline (Svelte transitions use them).
53
+ *
54
+ * ```js
55
+ * sveltekit({ csp: contentSecurityPolicy({ supabaseUrl: env.VITE_SUPABASE_URL }), ... })
56
+ * ```
57
+ */
58
+ export declare function contentSecurityPolicy({ supabaseUrl, connect, img }?: {
59
+ /** The project URL (`VITE_SUPABASE_URL`); without it any `*.supabase.co` project is allowed. */
60
+ supabaseUrl?: string;
61
+ /** Extra `connect-src` sources. */
62
+ connect?: string[];
63
+ /** Extra `img-src` sources. */
64
+ img?: string[];
65
+ }): {
66
+ mode: "hash";
67
+ directives: Record<string, any[]>;
68
+ };
package/dist/vite.js CHANGED
@@ -80,3 +80,41 @@ export function devJsonModule({ id, file, enabled }) {
80
80
  }
81
81
  };
82
82
  }
83
+ /**
84
+ * SvelteKit `csp` options for an offline-first app on static hosting. SvelteKit adds a
85
+ * `<meta http-equiv="content-security-policy">` with hashes of its own inline scripts, so only
86
+ * the app's bundled scripts run: injected markup can't load or run other scripts. Network access
87
+ * is limited to the app's origin and its Supabase project (REST and Realtime); images also allow
88
+ * data/blob URLs and Google profile pictures. Styles allow inline (Svelte transitions use them).
89
+ *
90
+ * ```js
91
+ * sveltekit({ csp: contentSecurityPolicy({ supabaseUrl: env.VITE_SUPABASE_URL }), ... })
92
+ * ```
93
+ */
94
+ export function contentSecurityPolicy({ supabaseUrl, connect = [], img = [] } = {}) {
95
+ let supabase = ['https://*.supabase.co', 'wss://*.supabase.co'];
96
+ if (supabaseUrl) {
97
+ try {
98
+ const { host, protocol } = new URL(supabaseUrl);
99
+ supabase = [`${protocol}//${host}`, `${protocol === 'http:' ? 'ws' : 'wss'}://${host}`];
100
+ }
101
+ catch {
102
+ /* keep the wildcard */
103
+ }
104
+ }
105
+ // Typed loosely (`any[]`) so it fits SvelteKit's `CspDirectives` without depending on SvelteKit.
106
+ const directives = {
107
+ 'default-src': ['self'],
108
+ 'script-src': ['self'],
109
+ 'style-src': ['self', 'unsafe-inline'],
110
+ 'img-src': ['self', 'data:', 'blob:', 'https://*.googleusercontent.com', ...img],
111
+ 'font-src': ['self'],
112
+ 'connect-src': ['self', ...supabase, ...connect],
113
+ 'worker-src': ['self'],
114
+ 'manifest-src': ['self'],
115
+ 'object-src': ['none'],
116
+ 'base-uri': ['self'],
117
+ 'form-action': ['self']
118
+ };
119
+ return { mode: 'hash', directives };
120
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wells-nelson/build-tools",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Build and deployment tooling for Wells Nelson SvelteKit PWAs: postbuild, PWA icon generator, Vite config pieces, service worker, and templates.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -26,6 +26,9 @@ jobs:
26
26
  node-version: 24
27
27
  cache: npm
28
28
  - run: npm ci
29
+ # Nothing ships unless it type-checks and its tests pass.
30
+ - run: npm run check --if-present
31
+ - run: npm test --if-present
29
32
  - run: npm run build
30
33
  env:
31
34
  # Repository variables (Settings > Secrets and variables > Actions > Variables).