@swoff/cli 0.3.0 → 0.3.2

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 (49) hide show
  1. package/dist/__tests__/assemble-sw.test.js +11 -1
  2. package/dist/__tests__/assemble-sw.test.js.map +1 -1
  3. package/dist/__tests__/cli-integration.test.js +2 -3
  4. package/dist/__tests__/cli-integration.test.js.map +1 -1
  5. package/dist/__tests__/config-types.test.js +0 -1
  6. package/dist/__tests__/config-types.test.js.map +1 -1
  7. package/dist/__tests__/file-generators.test.js +36 -12
  8. package/dist/__tests__/file-generators.test.js.map +1 -1
  9. package/dist/__tests__/loader.test.js +1 -1
  10. package/dist/__tests__/loader.test.js.map +1 -1
  11. package/dist/__tests__/validator.test.js +8 -1
  12. package/dist/__tests__/validator.test.js.map +1 -1
  13. package/dist/lib/commands/add.js +2 -4
  14. package/dist/lib/commands/add.js.map +1 -1
  15. package/dist/lib/config/validator.js +5 -0
  16. package/dist/lib/config/validator.js.map +1 -1
  17. package/dist/lib/generators/file-generators/background-sync.js +4 -3
  18. package/dist/lib/generators/file-generators/background-sync.js.map +1 -1
  19. package/dist/lib/generators/file-generators/cache.js +6 -22
  20. package/dist/lib/generators/file-generators/cache.js.map +1 -1
  21. package/dist/lib/generators/file-generators/fetch-wrapper.js +3 -2
  22. package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
  23. package/dist/lib/generators/file-generators/indexeddb.js +3 -2
  24. package/dist/lib/generators/file-generators/indexeddb.js.map +1 -1
  25. package/dist/lib/generators/file-generators/invalidation-tags.js +4 -3
  26. package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -1
  27. package/dist/lib/generators/file-generators/mutation-queue.js +8 -7
  28. package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
  29. package/dist/lib/generators/file-generators/pwa-install.js +10 -54
  30. package/dist/lib/generators/file-generators/pwa-install.js.map +1 -1
  31. package/dist/lib/generators/file-generators/reconcile.js +4 -3
  32. package/dist/lib/generators/file-generators/reconcile.js.map +1 -1
  33. package/dist/lib/generators/file-generators/store.js +3 -2
  34. package/dist/lib/generators/file-generators/store.js.map +1 -1
  35. package/dist/lib/generators/file-generators/sw-injector.js +71 -18
  36. package/dist/lib/generators/file-generators/sw-injector.js.map +1 -1
  37. package/dist/lib/generators/file-generators/sw-template.js +9 -132
  38. package/dist/lib/generators/file-generators/sw-template.js.map +1 -1
  39. package/dist/lib/generators/sw-sections/activate-handler.js +10 -17
  40. package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -1
  41. package/dist/lib/generators/sw-sections/config-header.js +1 -1
  42. package/dist/lib/generators/sw-sections/config-header.js.map +1 -1
  43. package/dist/lib/generators/sw-sections/fetch-handler.js +22 -2
  44. package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
  45. package/dist/lib/generators/swoff-files-generator.js +2 -2
  46. package/dist/lib/generators/swoff-files-generator.js.map +1 -1
  47. package/dist/lib/shared/config-types.js +0 -2
  48. package/dist/lib/shared/config-types.js.map +1 -1
  49. package/package.json +1 -1
@@ -3,12 +3,13 @@
3
3
  */
4
4
  import { writeFile } from "./context.js";
5
5
  export function generateFetchWrapper(ctx) {
6
+ const ext = ctx.ext;
6
7
  const code = `/**
7
8
  * Swoff Fetch Wrapper
8
9
  * Framework-agnostic fetch with cache strategy, tags, and query deduplication.
9
10
  *
10
11
  * Usage:
11
- * import { fetchWithCache } from './swoff/fetch-wrapper.js';
12
+ * import { fetchWithCache } from './swoff/fetch-wrapper.${ext}';
12
13
  *
13
14
  * // GET - cached with tag
14
15
  * const todos = await fetchWithCache("/api/todos", { tags: ["todos"] }).then(r => r.json());
@@ -75,6 +76,6 @@ export async function fetchWithCacheOrQueue(input, options = {}) {
75
76
  return fetchWithCache(input, options);
76
77
  }
77
78
  `;
78
- writeFile(ctx, "fetch-wrapper.js", code);
79
+ writeFile(ctx, `fetch-wrapper.${ext}`, code);
79
80
  }
80
81
  //# sourceMappingURL=fetch-wrapper.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"fetch-wrapper.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/fetch-wrapper.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,oBAAoB,CAAC,GAAqB;IACxD,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuEd,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,kBAAkB,EAAE,IAAI,CAAC,CAAC;AAC3C,CAAC"}
1
+ {"version":3,"file":"fetch-wrapper.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/fetch-wrapper.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,oBAAoB,CAAC,GAAqB;IACxD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,IAAI,GAAG;;;;;6DAK8C,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkE/D,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,iBAAiB,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AAC/C,CAAC"}
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { writeFile } from "./context.js";
5
5
  export function generateIndexedDB(ctx) {
6
+ const ext = ctx.ext;
6
7
  const dbName = ctx.config.features.indexeddb?.name || "app-db";
7
8
  const stores = ctx.config.features.indexeddb?.stores || [];
8
9
  const storesCode = stores.length > 0
@@ -19,7 +20,7 @@ export function generateIndexedDB(ctx) {
19
20
  * Database initialization with schema migrations.
20
21
  *
21
22
  * Usage:
22
- * import { openDB } from './swoff/indexeddb.js';
23
+ * import { openDB } from './swoff/indexeddb.${ext}';
23
24
  *
24
25
  * const db = await openDB();
25
26
  */
@@ -68,6 +69,6 @@ export async function monitorStorage() {
68
69
  };
69
70
  }
70
71
  `;
71
- writeFile(ctx, "indexeddb.js", code);
72
+ writeFile(ctx, `indexeddb.${ext}`, code);
72
73
  }
73
74
  //# sourceMappingURL=indexeddb.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"indexeddb.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/indexeddb.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,iBAAiB,CAAC,GAAqB;IACrD,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI,IAAI,QAAQ,CAAC;IAC/D,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,IAAI,EAAE,CAAC;IAE3D,MAAM,UAAU,GACd,MAAM,CAAC,MAAM,GAAG,CAAC;QACf,CAAC,CAAC,MAAM;aACH,GAAG,CACF,CAAC,KAAa,EAAE,CAAS,EAAE,EAAE,CAC3B,0BAA0B,CAAC,GAAG,CAAC,sCAAsC,KAAK,iCAAiC,CAC9G;aACA,IAAI,CAAC,MAAM,CAAC;QACjB,CAAC,CAAC;;;;WAIG,CAAC;IAEV,MAAM,IAAI,GAAG;;;;;;;;;;mBAUI,MAAM;;;;;;;;;;;EAWvB,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCX,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,CAAC,CAAC;AACvC,CAAC"}
1
+ {"version":3,"file":"indexeddb.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/indexeddb.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,iBAAiB,CAAC,GAAqB;IACrD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI,IAAI,QAAQ,CAAC;IAC/D,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,IAAI,EAAE,CAAC;IAE3D,MAAM,UAAU,GACd,MAAM,CAAC,MAAM,GAAG,CAAC;QACf,CAAC,CAAC,MAAM;aACH,GAAG,CACF,CAAC,KAAa,EAAE,CAAS,EAAE,EAAE,CAC3B,0BAA0B,CAAC,GAAG,CAAC,sCAAsC,KAAK,iCAAiC,CAC9G;aACA,IAAI,CAAC,MAAM,CAAC;QACjB,CAAC,CAAC;;;;WAIG,CAAC;IAEV,MAAM,IAAI,GAAG;;;;;iDAKkC,GAAG;;;;;mBAKjC,MAAM;;;;;;;;;;;EAWvB,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCX,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,aAAa,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AAC3C,CAAC"}
@@ -3,12 +3,13 @@
3
3
  */
4
4
  import { writeFile } from "./context.js";
5
5
  export function generateInvalidationTags(ctx) {
6
+ const ext = ctx.ext;
6
7
  const code = `/**
7
8
  * Swoff Invalidation Tags Helper
8
9
  * URL-based tag generation from REST endpoints for automatic cache invalidation.
9
10
  *
10
11
  * Usage:
11
- * import { generateTags, invalidateUrl } from './swoff/invalidation-tags.js';
12
+ * import { generateTags, invalidateUrl } from './swoff/invalidation-tags.${ext}';
12
13
  *
13
14
  * // Generate tags from URL
14
15
  * generateTags("/api/todos"); // ["todos"]
@@ -24,7 +25,7 @@ export function generateInvalidationTags(ctx) {
24
25
  * await invalidateUrl("/api/todos/42");
25
26
  */
26
27
 
27
- import { invalidateByTag, invalidateByTags } from "./cache.js";
28
+ import { invalidateByTag, invalidateByTags } from "./cache.${ext}";
28
29
 
29
30
  export function generateTags(url) {
30
31
  const parsed = typeof url === "string" ? new URL(url, window.location.origin) : url;
@@ -86,6 +87,6 @@ export async function invalidateByMethod(method, url) {
86
87
  await invalidateByTags(tags);
87
88
  }
88
89
  `;
89
- writeFile(ctx, "invalidation-tags.js", code);
90
+ writeFile(ctx, `invalidation-tags.${ext}`, code);
90
91
  }
91
92
  //# sourceMappingURL=invalidation-tags.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"invalidation-tags.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/invalidation-tags.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,wBAAwB,CAAC,GAAqB;IAC5D,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkFd,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,sBAAsB,EAAE,IAAI,CAAC,CAAC;AAC/C,CAAC"}
1
+ {"version":3,"file":"invalidation-tags.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/invalidation-tags.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,wBAAwB,CAAC,GAAqB;IAC5D,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,IAAI,GAAG;;;;;8EAK+D,GAAG;;;;;;;;;;;;;;;;6DAgBpB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6D/D,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,qBAAqB,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AACnD,CAAC"}
@@ -3,12 +3,13 @@
3
3
  */
4
4
  import { writeFile } from "./context.js";
5
5
  export function generateMutationQueue(ctx) {
6
+ const ext = ctx.ext;
6
7
  const code = `/**
7
8
  * Swoff Mutation Queue
8
9
  * Queue offline writes and sync when connection returns.
9
10
  *
10
11
  * Usage:
11
- * import { queueMutation, processMutationQueue, getPendingCount } from './swoff/mutation-queue.js';
12
+ * import { queueMutation, processMutationQueue, getPendingCount } from './swoff/mutation-queue.${ext}';
12
13
  *
13
14
  * // Queue a mutation
14
15
  * await queueMutation({
@@ -116,7 +117,7 @@ export async function processMutationQueue() {
116
117
  }
117
118
 
118
119
  if (item.tags && item.tags.length > 0) {
119
- const { invalidateByTags } = await import("./cache.js");
120
+ const { invalidateByTags } = await import("./cache.${ext}");
120
121
  await invalidateByTags(item.tags);
121
122
  }
122
123
 
@@ -164,16 +165,16 @@ async function rollbackMutation(item) {
164
165
  if (!item.storeName) return;
165
166
 
166
167
  if (item.method === "POST" && item.tempId) {
167
- const { deleteRecord } = await import("./store.js");
168
+ const { deleteRecord } = await import("./store.${ext}");
168
169
  await deleteRecord(item.storeName, item.tempId);
169
170
  } else if (
170
171
  (item.method === "PUT" || item.method === "PATCH") &&
171
172
  item.previousData
172
173
  ) {
173
- const { putRecord } = await import("./store.js");
174
+ const { putRecord } = await import("./store.${ext}");
174
175
  await putRecord(item.storeName, { ...item.previousData, $synced: true });
175
176
  } else if (item.method === "DELETE" && item.tempId && item.previousData) {
176
- const { putRecord } = await import("./store.js");
177
+ const { putRecord } = await import("./store.${ext}");
177
178
  await putRecord(item.storeName, { ...item.previousData, $synced: true });
178
179
  }
179
180
 
@@ -190,7 +191,7 @@ async function rollbackMutation(item) {
190
191
  }
191
192
 
192
193
  async function reconcileRecord(storeName, tempId, serverData) {
193
- const { getRecord, putRecord, deleteRecord } = await import("./store.js");
194
+ const { getRecord, putRecord, deleteRecord } = await import("./store.${ext}");
194
195
  const existing = await getRecord(storeName, tempId);
195
196
  if (!existing) return;
196
197
 
@@ -221,6 +222,6 @@ export async function getPendingCount() {
221
222
 
222
223
  window.addEventListener("online", processMutationQueue);
223
224
  `;
224
- writeFile(ctx, "mutation-queue.js", code);
225
+ writeFile(ctx, `mutation-queue.${ext}`, code);
225
226
  }
226
227
  //# sourceMappingURL=mutation-queue.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mutation-queue.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/mutation-queue.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,qBAAqB,CAAC,GAAqB;IACzD,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyNd,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,mBAAmB,EAAE,IAAI,CAAC,CAAC;AAC5C,CAAC"}
1
+ {"version":3,"file":"mutation-queue.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/mutation-queue.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,qBAAqB,CAAC,GAAqB;IACzD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,IAAI,GAAG;;;;;oGAKqF,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+DA4GxC,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qDAgDb,GAAG;;;;;;kDAMN,GAAG;;;kDAGH,GAAG;;;;;;;;;;;;;;;;;yEAiBoB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8B3E,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,kBAAkB,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AAChD,CAAC"}
@@ -1,70 +1,26 @@
1
1
  /**
2
- * Generates pwa-install.js - PWA install prompt handler.
2
+ * Generates pwa-install.{js|ts} - PWA install prompt utility.
3
3
  */
4
4
  import { writeFile } from "./context.js";
5
5
  export function generatePwaInstall(ctx) {
6
- const preventDefault = ctx.config.features.pwa?.preventDefaultInstall ?? false;
6
+ const ext = ctx.ext;
7
7
  const code = `/**
8
- * Swoff PWA Install Prompt Handler
9
- * Captures beforeinstallprompt event and provides manual install trigger.
8
+ * Swoff PWA Install Prompt
9
+ * Manual install trigger and installability check.
10
+ * Event listeners are registered in sw-injector at app entry.
10
11
  *
11
12
  * Usage:
12
- * import { isInstallable, promptInstall } from './swoff/pwa-install.js';
13
+ * import { isInstallable, promptInstall } from './swoff/pwa-install.${ext}';
13
14
  *
14
- * // Listen for installable event
15
- * window.addEventListener('pwa-installable', (e) => {
16
- * console.log('PWA can be installed:', e.detail.isInstallable);
17
- * // Show your custom install button
18
- * });
19
- *
20
- * // When user clicks install button
21
- * async function onInstallClick() {
15
+ * if (isInstallable()) {
22
16
  * const result = await promptInstall();
23
- * console.log('User choice:', result);
17
+ * console.log('User choice:', result.outcome);
24
18
  * }
25
19
  *
26
- * Window events:
27
- * pwa-installable - PWA can be installed (detail: { isInstallable: true })
28
- * pwa-installed - User accepted install (detail: { outcome: 'accepted' })
29
- * pwa-dismissed - User dismissed install (detail: { outcome: 'dismissed' })
30
- *
31
- * Window properties:
20
+ * Window properties (set by sw-injector):
32
21
  * window.deferredInstallPrompt - The captured BeforeInstallPromptEvent
33
- * window.pwaInstallable - Whether PWA can be installed
34
22
  */
35
23
 
36
- const PREVENT_DEFAULT_INSTALL = ${preventDefault};
37
-
38
- window.addEventListener("beforeinstallprompt", (e) => {
39
- // Always capture the event so we never lose it
40
- window.deferredInstallPrompt = e;
41
- window.pwaInstallable = true;
42
-
43
- if (PREVENT_DEFAULT_INSTALL) {
44
- // Suppress browser's native prompt
45
- e.preventDefault();
46
- }
47
- // When false, browser shows native prompt naturally
48
- // but we still capture the event for manual triggering
49
-
50
- window.dispatchEvent(
51
- new CustomEvent("pwa-installable", {
52
- detail: { isInstallable: true },
53
- })
54
- );
55
- });
56
-
57
- window.addEventListener("appinstalled", () => {
58
- window.deferredInstallPrompt = null;
59
- window.pwaInstallable = false;
60
-
61
- window.dispatchEvent(
62
- new CustomEvent("pwa-installed", {
63
- detail: { outcome: "accepted" },
64
- })
65
- );
66
- });
67
-
68
24
  export function isInstallable() {
69
25
  return !!window.deferredInstallPrompt;
70
26
  }
@@ -97,6 +53,6 @@ export async function promptInstall() {
97
53
  return choice;
98
54
  }
99
55
  `;
100
- writeFile(ctx, "pwa-install.js", code);
56
+ writeFile(ctx, `pwa-install.${ext}`, code);
101
57
  }
102
58
  //# sourceMappingURL=pwa-install.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pwa-install.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/pwa-install.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,kBAAkB,CAAC,GAAqB;IACtD,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,qBAAqB,IAAI,KAAK,CAAC;IAE/E,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCA6BmB,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+D/C,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,gBAAgB,EAAE,IAAI,CAAC,CAAC;AACzC,CAAC"}
1
+ {"version":3,"file":"pwa-install.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/pwa-install.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,kBAAkB,CAAC,GAAqB;IACtD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IAEpB,MAAM,IAAI,GAAG;;;;;;yEAM0D,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0C3E,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,eAAe,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AAC7C,CAAC"}
@@ -3,17 +3,18 @@
3
3
  */
4
4
  import { writeFile } from "./context.js";
5
5
  export function generateReconcile(ctx) {
6
+ const ext = ctx.ext;
6
7
  const code = `/**
7
8
  * Swoff ID Reconciliation
8
9
  * Update local records with server data after mutation sync.
9
10
  *
10
11
  * Usage:
11
- * import { reconcileRecord } from './swoff/reconcile.js';
12
+ * import { reconcileRecord } from './swoff/reconcile.${ext}';
12
13
  *
13
14
  * await reconcileRecord('todos', 'temp_abc123', serverData);
14
15
  */
15
16
 
16
- import { getRecord, putRecord, deleteRecord } from './store.js';
17
+ import { getRecord, putRecord, deleteRecord } from './store.${ext}';
17
18
 
18
19
  export async function reconcileRecord(storeName, tempId, serverData) {
19
20
  const existing = await getRecord(storeName, tempId);
@@ -49,6 +50,6 @@ export async function reconcileReferences(storeName, oldId, newId) {
49
50
  // }
50
51
  }
51
52
  `;
52
- writeFile(ctx, "reconcile.js", code);
53
+ writeFile(ctx, `reconcile.${ext}`, code);
53
54
  }
54
55
  //# sourceMappingURL=reconcile.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/reconcile.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,iBAAiB,CAAC,GAAqB;IACrD,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6Cd,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,CAAC,CAAC;AACvC,CAAC"}
1
+ {"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/reconcile.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,iBAAiB,CAAC,GAAqB;IACrD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,IAAI,GAAG;;;;;0DAK2C,GAAG;;;;;8DAKC,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmChE,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,aAAa,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AAC3C,CAAC"}
@@ -3,13 +3,14 @@
3
3
  */
4
4
  import { writeFile } from "./context.js";
5
5
  export function generateStore(ctx) {
6
+ const ext = ctx.ext;
6
7
  const dbName = ctx.config.features.indexeddb?.name || "app-db";
7
8
  const code = `/**
8
9
  * Swoff IndexedDB Store
9
10
  * Generic CRUD operations for app's IndexedDB database.
10
11
  *
11
12
  * Usage:
12
- * import { getRecord, putRecord, deleteRecord, openAppDB } from './swoff/store.js';
13
+ * import { getRecord, putRecord, deleteRecord, openAppDB } from './swoff/store.${ext}';
13
14
  *
14
15
  * const record = await getRecord('todos', 'todo-123');
15
16
  * await putRecord('todos', { id: 'todo-123', title: 'New task', $synced: false });
@@ -70,6 +71,6 @@ export async function getAllRecords(storeName) {
70
71
  });
71
72
  }
72
73
  `;
73
- writeFile(ctx, "store.js", code);
74
+ writeFile(ctx, `store.${ext}`, code);
74
75
  }
75
76
  //# sourceMappingURL=store.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/store.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,aAAa,CAAC,GAAqB;IACjD,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI,IAAI,QAAQ,CAAC;IAE/D,MAAM,IAAI,GAAG;;;;;;;;;;;;mBAYI,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqDxB,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;AACnC,CAAC"}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/store.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,aAAa,CAAC,GAAqB;IACjD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI,IAAI,QAAQ,CAAC;IAE/D,MAAM,IAAI,GAAG;;;;;oFAKqE,GAAG;;;;;;;mBAOpE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqDxB,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,SAAS,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AACvC,CAAC"}
@@ -1,14 +1,17 @@
1
1
  /**
2
- * Generates sw-injector.{js|ts} - client-side SW registration with version checking.
2
+ * Generates sw-injector.{js|ts} - client-side SW registration + PWA + cross-tab sync.
3
3
  */
4
4
  import { writeFile } from "./context.js";
5
5
  export function generateSwInjector(ctx) {
6
6
  const autoRegister = ctx.config.serviceWorker.autoRegister;
7
7
  const autoActivate = ctx.config.serviceWorker.autoActivate;
8
+ const pwaEnabled = ctx.config.features.pwa.enabled;
9
+ const preventDefaultInstall = ctx.config.features.pwa.preventDefaultInstall;
10
+ const crossTabSync = ctx.config.features.crossTabSync;
8
11
  const ext = ctx.ext;
9
12
  const code = `/**
10
- * Swoff Service Worker Injector
11
- * Framework-agnostic client registration with version checking.
13
+ * Swoff SW Injector
14
+ * Framework-agnostic SW registration, PWA install support, and cross-tab sync.
12
15
  *
13
16
  * Usage:
14
17
  * import { initServiceWorker } from './swoff/sw-injector.${ext}';
@@ -16,15 +19,15 @@ export function generateSwInjector(ctx) {
16
19
  * // Call in your app entry point (e.g., main.tsx, app.js):
17
20
  * initServiceWorker();
18
21
  *
19
- * // Or defer until after onboarding, but still call initServiceWorker():
20
- * // (edit the shouldRegister function below instead)
21
- *
22
22
  * Window events:
23
23
  * sw-version-detected - Version info available on window
24
24
  * sw-update-available - New version ready for user consent (detail: { version })
25
25
  * sw-progress - Download progress (detail: { percent, downloaded, total })
26
26
  * sw-ready - SW active and controlling page
27
27
  * sw-error - SW registration failed
28
+ * pwa-installable - PWA can be installed (detail: { isInstallable: true })
29
+ * pwa-installed - User accepted install (detail: { outcome: 'accepted' })
30
+ * cache-invalidated - Cache entries with given tags cleared (detail: { tags })
28
31
  *
29
32
  * Window properties:
30
33
  * window.latestSWVersion - Latest version from version.json
@@ -34,11 +37,15 @@ export function generateSwInjector(ctx) {
34
37
  * window.swMinSupportedVersion - Minimum supported version from version.json
35
38
  * window.swReady - SW is active
36
39
  * window.swError - Registration failed
40
+ * window.deferredInstallPrompt - Captured BeforeInstallPromptEvent
41
+ * window.pwaInstallable - Whether PWA can be installed
37
42
  */
38
43
 
39
44
  const AUTO_REGISTER = ${autoRegister};
40
45
  const AUTO_ACTIVATE = ${autoActivate};
41
46
 
47
+ // --- SW Registration ---
48
+
42
49
  async function checkForUpdate() {
43
50
  const response = await fetch("/version.json");
44
51
  if (!response.ok) {
@@ -47,6 +54,16 @@ async function checkForUpdate() {
47
54
  return response.json();
48
55
  }
49
56
 
57
+ async function waitForController() {
58
+ return new Promise((resolve) => {
59
+ if (navigator.serviceWorker.controller) {
60
+ resolve();
61
+ } else {
62
+ navigator.serviceWorker.addEventListener("controllerchange", resolve, { once: true });
63
+ }
64
+ });
65
+ }
66
+
50
67
  async function doRegisterServiceWorker(version) {
51
68
  const swUrl = \`/sw-v\${version}.js\`;
52
69
  const registration = await navigator.serviceWorker.register(swUrl);
@@ -54,16 +71,12 @@ async function doRegisterServiceWorker(version) {
54
71
  window.currentSWVersion = version;
55
72
  window.swRegisteredVersion = version;
56
73
  window.dispatchEvent(new CustomEvent("sw-version-detected"));
74
+ await waitForController();
57
75
  window.dispatchEvent(new CustomEvent("sw-ready"));
58
76
  return registration;
59
77
  }
60
78
 
61
79
  function shouldRegister() {
62
- // Add custom preconditions here. Return false to prevent registration.
63
- // Examples:
64
- // if (!localStorage.getItem("onboarding-complete")) return false;
65
- // if (navigator.connection?.effectiveType === "slow-2g") return false;
66
-
67
80
  return true;
68
81
  }
69
82
 
@@ -86,7 +99,10 @@ export async function initServiceWorker() {
86
99
  if (registration && registration.active) {
87
100
  window.currentSWVersion = currentVersion;
88
101
  window.dispatchEvent(new CustomEvent("sw-version-detected"));
89
- window.dispatchEvent(new CustomEvent("sw-ready"));
102
+ await waitForController();
103
+ if (!window.swReady) {
104
+ window.dispatchEvent(new CustomEvent("sw-ready"));
105
+ }
90
106
  }
91
107
  } else if (currentVersion && currentVersion !== manifest.version) {
92
108
  window.swAvailableVersion = manifest.version;
@@ -128,18 +144,18 @@ export async function initServiceWorker() {
128
144
  await doRegisterServiceWorker(manifest.version);
129
145
  }
130
146
  } catch (error) {
131
- // Offline or version.json fetch failed — try using existing registration
132
147
  try {
133
148
  const existing = await navigator.serviceWorker.getRegistration();
134
149
  if (existing && existing.active) {
135
150
  window.currentSWVersion = localStorage.getItem("swRegisteredVersion") || "unknown";
136
151
  window.dispatchEvent(new CustomEvent("sw-version-detected"));
137
- window.dispatchEvent(new CustomEvent("sw-ready"));
152
+ await waitForController();
153
+ if (!window.swReady) {
154
+ window.dispatchEvent(new CustomEvent("sw-ready"));
155
+ }
138
156
  return;
139
157
  }
140
- } catch {
141
- // Registration check also failed, nothing we can do
142
- }
158
+ } catch {}
143
159
 
144
160
  console.error("Service Worker initialization failed:", error);
145
161
  window.swError = true;
@@ -169,6 +185,35 @@ export async function skipWaiting() {
169
185
  }
170
186
  }
171
187
 
188
+ // --- PWA Install Support ---
189
+ ${pwaEnabled ? `
190
+ window.addEventListener("beforeinstallprompt", (e) => {
191
+ window.deferredInstallPrompt = e;
192
+ window.pwaInstallable = true;
193
+
194
+ if (${preventDefaultInstall}) {
195
+ e.preventDefault();
196
+ }
197
+
198
+ window.dispatchEvent(
199
+ new CustomEvent("pwa-installable", {
200
+ detail: { isInstallable: true },
201
+ })
202
+ );
203
+ });
204
+
205
+ window.addEventListener("appinstalled", () => {
206
+ window.deferredInstallPrompt = null;
207
+ window.pwaInstallable = false;
208
+
209
+ window.dispatchEvent(
210
+ new CustomEvent("pwa-installed", {
211
+ detail: { outcome: "accepted" },
212
+ })
213
+ );
214
+ });
215
+ ` : ""}
216
+ // --- SW Message Listener ---
172
217
  if (typeof window !== "undefined" && "serviceWorker" in navigator) {
173
218
  navigator.serviceWorker.addEventListener("message", (event) => {
174
219
  if (event.data.type === "SW_PROGRESS") {
@@ -193,7 +238,15 @@ if (typeof window !== "undefined" && "serviceWorker" in navigator) {
193
238
  }
194
239
  window.dispatchEvent(new CustomEvent("mutation-queue-changed"));
195
240
  }
196
- });
241
+ ${crossTabSync ? `
242
+ if (event.data.type === "TAG_INVALIDATED" && event.data.tag) {
243
+ window.dispatchEvent(
244
+ new CustomEvent("cache-invalidated", {
245
+ detail: { tags: [event.data.tag] },
246
+ })
247
+ );
248
+ }
249
+ ` : ""} });
197
250
  }
198
251
  `;
199
252
  writeFile(ctx, `sw-injector.${ext}`, code);
@@ -1 +1 @@
1
- {"version":3,"file":"sw-injector.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/sw-injector.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,kBAAkB,CAAC,GAAqB;IACtD,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC;IAC3D,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC;IAC3D,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IAEpB,MAAM,IAAI,GAAG;;;;;8DAK+C,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;wBAyBzC,YAAY;wBACZ,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8JnC,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,eAAe,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AAC7C,CAAC"}
1
+ {"version":3,"file":"sw-injector.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/sw-injector.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,kBAAkB,CAAC,GAAqB;IACtD,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC;IAC3D,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC;IAC3D,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC;IACnD,MAAM,qBAAqB,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,qBAAqB,CAAC;IAC5E,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC;IACtD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IAEpB,MAAM,IAAI,GAAG;;;;;8DAK+C,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA2BzC,YAAY;wBACZ,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgJlC,UAAU,CAAC,CAAC,CAAC;;;;;QAKP,qBAAqB;;;;;;;;;;;;;;;;;;;;;CAqB5B,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;EA0BJ,YAAY,CAAC,CAAC,CAAC;;;;;;;;CAQhB,CAAC,CAAC,CAAC,EAAE;;CAEL,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,eAAe,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AAC7C,CAAC"}
@@ -1,139 +1,16 @@
1
1
  /**
2
- * Generates sw-template.js - the SW template with placeholders.
2
+ * Generates sw-template.js - the SW template with config features baked in.
3
+ * Placeholders are replaced during build by sw-generator.js.
3
4
  */
4
5
  import { writeFile } from "./context.js";
6
+ import { assembleSW } from "../sw-sections/assemble-sw.js";
5
7
  export function generateSwTemplate(ctx) {
6
- const code = `/**
7
- * Swoff Service Worker Template
8
- *
9
- * This file is processed by swoff/sw-generator.js to create
10
- * a versioned service worker. Placeholders are replaced during build.
11
- *
12
- * Placeholders:
13
- * [[CACHE_NAME]] - Replaced with versioned cache name
14
- * [[ASSETS_LIST]] - Replaced with assets to cache
15
- * [[AUTO_SKIP_WAITING]] - Replaced with autoActivate config
16
- *
17
- * You can customize this template before running the build script.
18
- */
19
-
20
- let CACHE_NAME = "";
21
- let ASSETS_TO_CACHE = [];
22
-
23
- // [[CACHE_NAME]]
24
- // [[ASSETS_LIST]]
25
- // [[AUTO_SKIP_WAITING]]
26
-
27
- const CACHE_NAME_RUNTIME = "swoff-runtime";
28
-
29
- // Install - download assets with progress tracking
30
- self.addEventListener("install", (event) => {
31
- event.waitUntil(
32
- (async () => {
33
- const cache = await caches.open(CACHE_NAME);
34
- let downloaded = 0;
35
- for (const asset of ASSETS_TO_CACHE) {
36
- try {
37
- const request = new Request(asset.url, asset.options);
38
- await cache.add(request);
39
- downloaded++;
40
- const percent = Math.round((downloaded / ASSETS_TO_CACHE.length) * 100);
41
- const clients = await self.clients.matchAll({ includeUncontrolled: true });
42
- clients.forEach((client) => {
43
- client.postMessage({
44
- type: "SW_PROGRESS",
45
- percent,
46
- downloaded,
47
- total: ASSETS_TO_CACHE.length,
48
- });
49
- });
50
- } catch (err) {
51
- console.error(\`Failed to cache \${asset.url}:\`, err);
52
- }
53
- }
54
- if (AUTO_SKIP_WAITING) self.skipWaiting();
55
- })(),
56
- );
57
- });
58
-
59
- // Activate - clean old caches
60
- self.addEventListener("activate", (event) => {
61
- event.waitUntil(
62
- caches.keys().then((keys) =>
63
- Promise.all(
64
- keys.filter((key) => key !== CACHE_NAME && key !== CACHE_NAME_RUNTIME).map((key) => caches.delete(key))
65
- )
66
- )
67
- );
68
- });
69
-
70
- // Message - skip waiting and cache invalidation
71
- self.addEventListener("message", (event) => {
72
- if (event.data.type === "SKIP_WAITING") {
73
- self.skipWaiting();
74
- }
75
- });
76
-
77
- // Fetch - cache strategies
78
- self.addEventListener("fetch", (event) => {
79
- if (event.request.method !== "GET" && event.request.method !== "HEAD") return;
80
-
81
- event.respondWith(
82
- (async () => {
83
- const cache = await caches.open(CACHE_NAME);
84
- const runtimeCache = await caches.open(CACHE_NAME_RUNTIME);
85
- const url = new URL(event.request.url);
86
-
87
- const byPath = await cache.match(url.pathname);
88
- if (byPath) return byPath;
89
-
90
- const cached = await runtimeCache.match(event.request);
91
- if (cached) return cached;
92
-
93
- if (event.request.mode === "navigate") {
94
- const spa = await cache.match("/index.html");
95
- if (spa) return spa;
96
- }
97
-
98
- const response = await fetch(event.request);
99
- if (response.ok) {
100
- await runtimeCache.put(event.request, response.clone());
101
- }
102
- return response;
103
- })(),
104
- );
105
- });
106
-
107
- const SWOFF = {
108
- cache: {
109
- async get(key) {
110
- const cache = await caches.open(CACHE_NAME);
111
- return cache.match(key);
112
- },
113
- async put(request, response) {
114
- const cache = await caches.open(CACHE_NAME);
115
- await cache.put(request, response);
116
- },
117
- async delete(request) {
118
- const cache = await caches.open(CACHE_NAME);
119
- return cache.delete(request);
120
- }
121
- },
122
- network: {
123
- async fetch(request, options = {}) {
124
- try {
125
- return await fetch(request, options);
126
- } catch (error) {
127
- throw new Error(\`Network request failed: \${error.message}\`);
128
- }
129
- }
130
- }
131
- };
132
-
133
- if (typeof self !== 'undefined') {
134
- self.SWOFF = SWOFF;
135
- }
136
- `;
8
+ const placeholderVersion = "0.0.0";
9
+ const full = assembleSW(ctx.config, placeholderVersion);
10
+ let code = full;
11
+ code = code.replace(`CACHE_NAME = 'sw-v${placeholderVersion}'`, "// [[CACHE_NAME]]");
12
+ code = code.replace(/^ASSETS_TO_CACHE = \[[\s\S]*?\]$/m, "// [[ASSETS_LIST]]");
13
+ code = code.replace(/^const AUTO_SKIP_WAITING = (true|false);?$/m, "// [[AUTO_SKIP_WAITING]]");
137
14
  writeFile(ctx, "sw-template.js", code);
138
15
  }
139
16
  //# sourceMappingURL=sw-template.js.map