@lls/offline 24.22.0-debug → 24.22.0-eaf69c80

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.
@@ -11,12 +11,11 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
11
11
  }
12
12
  } else {
13
13
  if (methodName && !methodName.startsWith("async") && !(methodName in cbks)) {
14
- console.error(`syncWorker: ${dbg}`, eventId, methodName, error);
14
+ console.error(`syncWorker: unknown methodName cbk ${dbg}`, eventId, methodName, error);
15
15
  }
16
16
  }
17
17
  methodName && cbks[methodName]?.(arg);
18
18
  };
19
- offlineWorker2.onmessage = fn;
20
19
  const makeFn = (methodName) => {
21
20
  return ({ ports, ...arg } = {}) => {
22
21
  return new Promise((resolve, reject) => {
@@ -31,23 +30,36 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
31
30
  cbks[key] = fn2;
32
31
  }
33
32
  });
34
- return obj;
33
+ return {
34
+ ...obj,
35
+ handler: {
36
+ canHandle: (methodName) => {
37
+ return methodName in cbks || methodNames.includes(methodName);
38
+ },
39
+ handle: fn
40
+ }
41
+ };
35
42
  };
36
43
  var a = (a2) => a2;
37
44
  var makeSyncHandler = a(
38
- (sender, services) => async ({ data: { eventId, methodName, arg } = {}, ports }) => {
39
- try {
40
- if (typeof methodName === "string" && methodName in services && services[methodName]) {
41
- const results = await services[methodName](arg, { ports });
42
- sender.postMessage({ eventId, methodName, results });
43
- return;
45
+ (sender, services) => ({
46
+ canHandle: (methodName) => {
47
+ return methodName in services;
48
+ },
49
+ handle: async ({ data: { eventId, methodName, arg } = {}, ports }) => {
50
+ try {
51
+ if (typeof methodName === "string" && methodName in services && services[methodName]) {
52
+ const results = await services[methodName](arg, { ports });
53
+ sender.postMessage({ eventId, methodName, results });
54
+ return;
55
+ }
56
+ throw new Error(`invalid methodName ${Object.keys(services)} |${String(methodName)}`);
57
+ } catch (e) {
58
+ console.debug("failed", e);
59
+ sender.postMessage({ eventId, methodName, error: e });
44
60
  }
45
- throw new Error(`invalid methodName ${Object.keys(services)} |${String(methodName)}`);
46
- } catch (e) {
47
- console.debug("failed", e);
48
- sender.postMessage({ eventId, methodName, error: e });
49
61
  }
50
- }
62
+ })
51
63
  );
52
64
  var jsonToBuf = (json) => new TextEncoder().encode(JSON.stringify(json));
53
65
 
@@ -273,8 +285,8 @@ swSelf.addEventListener("fetch", (ev) => {
273
285
  const mimeType = extension && extension in ACCEPTED_EXTENSIONS ? ACCEPTED_EXTENSIONS[extension] : "";
274
286
  const isValidScreenCall = canHandleScreenCall(url);
275
287
  if (!mimeType && !isValidScreenCall) {
276
- if (!/\.(css|tsx?|jsx?|woff2|fr)/.test(url)) {
277
- console.debug("no ext", url.match(/\.[^.]+$/)?.[0]);
288
+ if (!/\.(css|tsx?|jsx?|woff2|fr|manifest)(\??|$)|@(vite|react|id)/.test(url)) {
289
+ console.debug("no ext", extension || url);
278
290
  }
279
291
  return;
280
292
  }
@@ -300,13 +312,15 @@ swSelf.addEventListener("activate", (_event) => {
300
312
  var offlineWorker;
301
313
  var offlineWorkerSync;
302
314
  var initChannel = async (__, { ports }) => {
303
- console.info(`FETCH WORKER innitChannel v${13}`);
315
+ console.info(`FETCH WORKER innitChannel v${/* @__PURE__ */ new Date()}`);
304
316
  offlineWorker = ports[0];
305
- offlineWorkerSync = makeSyncWorker(
317
+ const { handler, ...sender } = makeSyncWorker(
306
318
  offlineWorker,
307
319
  ["readZipEntry", "loadBook", "slugToFileMeta", "loadPageContent", "searchBooks"],
308
320
  "fetchworker"
309
321
  );
322
+ offlineWorkerSync = sender;
323
+ offlineWorker.onmessage = handler.handle;
310
324
  offlineWorkerSync.on("setKnownLinks", setKnownLinks);
311
325
  offlineWorker.postMessage({ methodName: "sendAppReady" });
312
326
  };
@@ -1,21 +1,77 @@
1
1
  // src/utils/worker.ts
2
+ var makeSyncWorker = (offlineWorker, methodNames, dbg) => {
3
+ const dic = {};
4
+ const cbks = {};
5
+ const fn = ({ data: { eventId, methodName, results, error, arg } = {} } = {}) => {
6
+ if (eventId && eventId in dic) {
7
+ if (error) {
8
+ dic[eventId].reject(error);
9
+ } else {
10
+ dic[eventId].resolve(results);
11
+ }
12
+ } else {
13
+ if (methodName && !methodName.startsWith("async") && !(methodName in cbks)) {
14
+ console.error(`syncWorker: unknown methodName cbk ${dbg}`, eventId, methodName, error);
15
+ }
16
+ }
17
+ methodName && cbks[methodName]?.(arg);
18
+ };
19
+ const makeFn = (methodName) => {
20
+ return ({ ports, ...arg } = {}) => {
21
+ return new Promise((resolve, reject) => {
22
+ const eventId = `${methodName}:${Date.now()}`;
23
+ dic[eventId] = { resolve, reject };
24
+ offlineWorker.postMessage({ eventId, methodName, arg }, ports);
25
+ });
26
+ };
27
+ };
28
+ const obj = Object.assign(Object.fromEntries(methodNames.map((name) => [name, makeFn(name)])), {
29
+ on: (key2, fn2) => {
30
+ cbks[key2] = fn2;
31
+ }
32
+ });
33
+ return {
34
+ ...obj,
35
+ handler: {
36
+ canHandle: (methodName) => {
37
+ return methodName in cbks || methodNames.includes(methodName);
38
+ },
39
+ handle: fn
40
+ }
41
+ };
42
+ };
2
43
  var a = (a2) => a2;
3
44
  var makeSyncHandler = a(
4
- (sender, services) => async ({ data: { eventId, methodName, arg } = {}, ports }) => {
5
- try {
6
- if (typeof methodName === "string" && methodName in services && services[methodName]) {
7
- const results = await services[methodName](arg, { ports });
8
- sender.postMessage({ eventId, methodName, results });
9
- return;
45
+ (sender2, services) => ({
46
+ canHandle: (methodName) => {
47
+ return methodName in services;
48
+ },
49
+ handle: async ({ data: { eventId, methodName, arg } = {}, ports }) => {
50
+ try {
51
+ if (typeof methodName === "string" && methodName in services && services[methodName]) {
52
+ const results = await services[methodName](arg, { ports });
53
+ sender2.postMessage({ eventId, methodName, results });
54
+ return;
55
+ }
56
+ throw new Error(`invalid methodName ${Object.keys(services)} |${String(methodName)}`);
57
+ } catch (e) {
58
+ console.debug("failed", e);
59
+ sender2.postMessage({ eventId, methodName, error: e });
10
60
  }
11
- throw new Error(`invalid methodName ${Object.keys(services)} |${String(methodName)}`);
12
- } catch (e) {
13
- console.debug("failed", e);
14
- sender.postMessage({ eventId, methodName, error: e });
15
61
  }
16
- }
62
+ })
17
63
  );
18
64
  var bufToJson = (buf) => JSON.parse(new TextDecoder("UTF-8").decode(new Uint8Array(buf)));
65
+ var combineHandlers = (...handlers) => {
66
+ return ({ data: { methodName, ...data } = {}, ports }) => {
67
+ for (const h of handlers) {
68
+ if (h.canHandle(String(methodName))) {
69
+ return h.handle({ data: { methodName, ...data }, ports });
70
+ }
71
+ }
72
+ console.error("no handlers matched", methodName);
73
+ };
74
+ };
19
75
 
20
76
  // src/worker/wwApi.ts
21
77
  var STATE = {
@@ -734,6 +790,9 @@ var loadZip = async (buf, bookId) => {
734
790
  const unzipper = new Unzip();
735
791
  unzipper.register(UnzipInflate);
736
792
  unzipper.onfile = (file) => {
793
+ if (!file.name.includes(".")) {
794
+ return;
795
+ }
737
796
  m.set(file.name, {
738
797
  decoded: null,
739
798
  zipEntry: {
@@ -771,10 +830,18 @@ var loadZip = async (buf, bookId) => {
771
830
 
772
831
  // src/worker/offline-worker.ts
773
832
  var CHECK_UPDATES = true;
774
- var fetchZipFile = async (zipUrl, forceRefresh) => {
775
- const opfsRoot = await navigator.storage.getDirectory();
833
+ var NATIVE = false;
834
+ var LOADED_BOOKS = /* @__PURE__ */ new Set();
835
+ var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
776
836
  const fileName = zipUrl.split("/").slice(-1)[0];
777
837
  const readArrayBuffer = async () => {
838
+ if (NATIVE) {
839
+ console.debug("try reading native", fileName);
840
+ const back = await sender.nativeReadFile({ fname: fileName });
841
+ console.debug("done readnig");
842
+ return back;
843
+ }
844
+ const opfsRoot = await navigator.storage.getDirectory();
778
845
  const handle = await opfsRoot.getFileHandle(fileName);
779
846
  const accessHandle = await handle.createSyncAccessHandle();
780
847
  const size = accessHandle.getSize();
@@ -800,7 +867,7 @@ var fetchZipFile = async (zipUrl, forceRefresh) => {
800
867
  }
801
868
  return buf;
802
869
  } catch (e) {
803
- if (e.message !== "forceRefresh" && !e.message.includes("refetching")) {
870
+ if (e instanceof Error && e.message !== "forceRefresh" && !e.message.includes("refetching")) {
804
871
  console.debug("opfs db not found, creating it", e, zipUrl);
805
872
  }
806
873
  const response = await fetch(zipUrl);
@@ -826,6 +893,11 @@ var fetchZipFile = async (zipUrl, forceRefresh) => {
826
893
  console.debug(error);
827
894
  throw new Error(error);
828
895
  }
896
+ if (NATIVE) {
897
+ await sender.nativeWriteFile({ fname: fileName, buffer: arrayBuffer });
898
+ return await readArrayBuffer();
899
+ }
900
+ const opfsRoot = await navigator.storage.getDirectory();
829
901
  const handle = await opfsRoot.getFileHandle(fileName, { create: true });
830
902
  const accessHandle = await handle.createSyncAccessHandle();
831
903
  await accessHandle.truncate(0);
@@ -843,7 +915,12 @@ var slugToFileMeta2 = async ({ slug }) => {
843
915
  var PRELOAD_AS_BOOK_ID = "preload";
844
916
  var isOldZip = (bookId) => typeof bookId === "number";
845
917
  var loadBook2 = async ({ id: bookId, forceRefresh }) => {
846
- const buf = await fetchZipFile(`https://ci.lls.fr/artefacts/content/development/${bookId}.zip`, forceRefresh);
918
+ if (LOADED_BOOKS.has(bookId)) {
919
+ self.postMessage({ methodName: "opfsProgress", arg: { total: 1, bytes: 1 } });
920
+ return;
921
+ }
922
+ LOADED_BOOKS.add(bookId);
923
+ const buf = await fetchZipFile(`https://ci.lls.fr/artefacts/content/development/${bookId}.zip`, { forceRefresh });
847
924
  try {
848
925
  const entryNames = await loadZip(new Uint8Array(buf), bookId);
849
926
  fetchWorker.postMessage({ methodName: "setKnownLinks", arg: { entryNames, bookId } });
@@ -875,7 +952,7 @@ var loadPageContent2 = async ({
875
952
  var fetchWorker;
876
953
  var initChannel = (__, { ports }) => {
877
954
  fetchWorker = ports[0];
878
- const defaultHandler = makeSyncHandler(fetchWorker, {
955
+ const { canHandle, handle } = makeSyncHandler(fetchWorker, {
879
956
  readZipEntry,
880
957
  loadBook: loadBook2,
881
958
  slugToFileMeta: slugToFileMeta2,
@@ -887,23 +964,47 @@ var initChannel = (__, { ports }) => {
887
964
  self.postMessage({ methodName: "ready" });
888
965
  return;
889
966
  }
890
- defaultHandler(dataObj);
967
+ if (canHandle(dataObj.data?.methodName || "")) {
968
+ return handle(dataObj);
969
+ }
970
+ console.error("unknown methodName", dataObj.data?.methodName);
891
971
  };
892
972
  fetchWorker.onmessage = fn;
893
973
  };
894
- var loadBooks = async () => {
895
- const opfsRoot = await navigator.storage.getDirectory();
896
- const entries = await opfsRoot.values();
974
+ var loadBooks = async ({ primaryOnly = false } = {}) => {
975
+ let entries = [];
976
+ if (NATIVE) {
977
+ entries = await sender.nativeListZips();
978
+ } else {
979
+ const opfsRoot = await navigator.storage.getDirectory();
980
+ entries = await opfsRoot.values();
981
+ }
897
982
  for await (const entry of entries) {
983
+ if (/preload/.test(entry.name)) {
984
+ continue;
985
+ }
898
986
  if (/\d+\.zip$/.test(entry.name)) {
899
- const bookId = entry.name.match(/\d+/)[0];
900
- await loadBook2({ id: Number.parseInt(bookId, 10) });
987
+ if (!primaryOnly) {
988
+ const bookId = entry.name.match(/\d+/)[0];
989
+ await loadBook2({ id: Number.parseInt(bookId, 10) });
990
+ }
901
991
  } else if (/zip$/.test(entry.name)) {
902
992
  const methodUri = entry.name.replace(".zip", "");
903
993
  await loadBook2({ id: methodUri });
994
+ } else {
995
+ console.info(`invalid book entry ${entry.name}`);
904
996
  }
905
997
  }
906
998
  };
999
+ var configure = ({ native, checkUpdates }) => {
1000
+ console.info("configuring!!", native, checkUpdates);
1001
+ if (typeof native !== "undefined") {
1002
+ NATIVE = !!native;
1003
+ }
1004
+ if (typeof native !== "undefined") {
1005
+ CHECK_UPDATES = !!checkUpdates;
1006
+ }
1007
+ };
907
1008
  var loadPreload2 = async () => {
908
1009
  const zipUrl = "https://lls-ci-fr.s3.eu-west-3.amazonaws.com/artefacts/content/development/preload.zip";
909
1010
  const buf = await fetchZipFile(zipUrl);
@@ -916,10 +1017,13 @@ var loadPreload2 = async () => {
916
1017
  console.error("failed", e);
917
1018
  }
918
1019
  };
919
- self.onmessage = makeSyncHandler(self, {
1020
+ var mainHandle = makeSyncHandler(self, {
920
1021
  initChannel,
921
1022
  loadPreload: loadPreload2,
922
1023
  readZipEntry,
923
1024
  loadBooks,
924
- loadBook: loadBook2
1025
+ loadBook: loadBook2,
1026
+ configure
925
1027
  });
1028
+ var { handler, ...sender } = makeSyncWorker(self, ["nativeWriteFile", "nativeReadFile", "nativeListZips"]);
1029
+ self.onmessage = combineHandlers(mainHandle, handler);
package/lib/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  // src/utils/worker.ts
2
- var makeSyncWorker = (offlineWorker, methodNames, dbg) => {
2
+ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
3
3
  const dic = {};
4
4
  const cbks = {};
5
- const fn2 = ({ data: { eventId, methodName, results, error, arg } = {} } = {}) => {
5
+ const fn = ({ data: { eventId, methodName, results, error, arg } = {} } = {}) => {
6
6
  if (eventId && eventId in dic) {
7
7
  if (error) {
8
8
  dic[eventId].reject(error);
@@ -11,51 +11,74 @@ var makeSyncWorker = (offlineWorker, methodNames, dbg) => {
11
11
  }
12
12
  } else {
13
13
  if (methodName && !methodName.startsWith("async") && !(methodName in cbks)) {
14
- console.error(`syncWorker: ${dbg}`, eventId, methodName, error);
14
+ console.error(`syncWorker: unknown methodName cbk ${dbg}`, eventId, methodName, error);
15
15
  }
16
16
  }
17
17
  methodName && cbks[methodName]?.(arg);
18
18
  };
19
- offlineWorker.onmessage = fn2;
20
19
  const makeFn = (methodName) => {
21
20
  return ({ ports, ...arg } = {}) => {
22
21
  return new Promise((resolve, reject) => {
23
22
  const eventId = `${methodName}:${Date.now()}`;
24
23
  dic[eventId] = { resolve, reject };
25
- offlineWorker.postMessage({ eventId, methodName, arg }, ports);
24
+ offlineWorker2.postMessage({ eventId, methodName, arg }, ports);
26
25
  });
27
26
  };
28
27
  };
29
28
  const obj = Object.assign(Object.fromEntries(methodNames.map((name) => [name, makeFn(name)])), {
30
- on: (key, fn3) => {
31
- cbks[key] = fn3;
29
+ on: (key, fn2) => {
30
+ cbks[key] = fn2;
32
31
  }
33
32
  });
34
- return obj;
33
+ return {
34
+ ...obj,
35
+ handler: {
36
+ canHandle: (methodName) => {
37
+ return methodName in cbks || methodNames.includes(methodName);
38
+ },
39
+ handle: fn
40
+ }
41
+ };
35
42
  };
36
43
  var a = (a2) => a2;
37
44
  var makeSyncHandler = a(
38
- (sender, services) => async ({ data: { eventId, methodName, arg } = {}, ports }) => {
39
- try {
40
- if (typeof methodName === "string" && methodName in services && services[methodName]) {
41
- const results = await services[methodName](arg, { ports });
42
- sender.postMessage({ eventId, methodName, results });
43
- return;
45
+ (sender, services) => ({
46
+ canHandle: (methodName) => {
47
+ return methodName in services;
48
+ },
49
+ handle: async ({ data: { eventId, methodName, arg } = {}, ports }) => {
50
+ try {
51
+ if (typeof methodName === "string" && methodName in services && services[methodName]) {
52
+ const results = await services[methodName](arg, { ports });
53
+ sender.postMessage({ eventId, methodName, results });
54
+ return;
55
+ }
56
+ throw new Error(`invalid methodName ${Object.keys(services)} |${String(methodName)}`);
57
+ } catch (e) {
58
+ console.debug("failed", e);
59
+ sender.postMessage({ eventId, methodName, error: e });
44
60
  }
45
- throw new Error(`invalid methodName ${Object.keys(services)} |${String(methodName)}`);
46
- } catch (e) {
47
- console.debug("failed", e);
48
- sender.postMessage({ eventId, methodName, error: e });
49
61
  }
50
- }
62
+ })
51
63
  );
64
+ var combineHandlers = (...handlers) => {
65
+ return ({ data: { methodName, ...data } = {}, ports }) => {
66
+ for (const h of handlers) {
67
+ if (h.canHandle(String(methodName))) {
68
+ return h.handle({ data: { methodName, ...data }, ports });
69
+ }
70
+ }
71
+ console.error("no handlers matched", methodName);
72
+ };
73
+ };
52
74
 
53
75
  // src/hooks/useOfflineWorker.ts
54
76
  import { useCallback, useEffect, useState } from "react";
55
- var offlineWorkerSync = makeSyncWorker(
77
+ var offlineWorker = new Worker("/src/worker/offline-worker.js", { type: "module" });
78
+ var { handler, ...offlineWorkerSync } = makeSyncWorker(
56
79
  // @ts-ignore: TODO
57
- new Worker("/src/worker/offline-worker.js", { type: "module" }),
58
- ["initChannel", "readZipEntry", "loadPreload", "loadBooks", "loadBook"],
80
+ offlineWorker,
81
+ ["initChannel", "readZipEntry", "configure", "loadPreload", "loadBooks", "loadBook"],
59
82
  "app:offlineWorker"
60
83
  );
61
84
  var setWorkersReady;
@@ -66,7 +89,13 @@ var setBooksLoaded;
66
89
  var setBooksLoadedPromise = new Promise((resolve) => {
67
90
  setBooksLoaded = resolve;
68
91
  });
69
- var fn = async () => {
92
+ var installWorker = async ({
93
+ primaryOnly = false,
94
+ nativeReadFile,
95
+ nativeWriteFile,
96
+ nativeListZips,
97
+ checkUpdates
98
+ } = {}) => {
70
99
  let fetchWorker = null;
71
100
  await navigator.serviceWorker.getRegistrations().then(async (registrations) => {
72
101
  for (const registration of registrations) {
@@ -82,30 +111,49 @@ var fn = async () => {
82
111
  console.debug("failed to get fetch worker");
83
112
  return;
84
113
  }
114
+ if (nativeReadFile) {
115
+ const wwHandler = makeSyncHandler(offlineWorker, { nativeReadFile, nativeWriteFile, nativeListZips });
116
+ offlineWorker.onmessage = combineHandlers(handler, wwHandler);
117
+ offlineWorkerSync.configure({ native: true, checkUpdates });
118
+ } else {
119
+ offlineWorkerSync.configure({ native: false, checkUpdates });
120
+ offlineWorker.onmessage = handler.handle;
121
+ }
85
122
  offlineWorkerSync.on("ready", setWorkersReady);
86
123
  const channel = new MessageChannel();
87
124
  offlineWorkerSync.initChannel({ ports: [channel.port1] });
88
125
  fetchWorker.postMessage({ methodName: "initChannel" }, [channel.port2]);
89
126
  offlineWorkerSync.on("opfsProgress", () => {
90
127
  });
91
- await offlineWorkerSync.loadPreload();
128
+ if (!primaryOnly) {
129
+ await offlineWorkerSync.loadPreload();
130
+ }
92
131
  await offlineWorkerSync.loadBooks();
93
132
  setBooksLoaded();
94
133
  };
95
- fn();
96
- var useOfflineWorker_default = () => {
134
+ var useOfflineWorker_default = ({
135
+ primaryOnly = false,
136
+ nativeReadFile,
137
+ nativeWriteFile,
138
+ nativeListZips,
139
+ checkUpdates
140
+ } = {}) => {
97
141
  const [ready, setReady] = useState(false);
98
142
  useEffect(() => {
99
- const fn2 = async () => {
143
+ const fn = async () => {
144
+ await installWorker({ primaryOnly, nativeReadFile, nativeWriteFile, nativeListZips, checkUpdates });
100
145
  await Promise.all([workersReadyPromise, setBooksLoadedPromise]);
101
146
  setReady(true);
102
147
  };
103
- fn2();
104
- }, []);
105
- const loadBook = useCallback(async ({ id, forceRefresh }, cbk) => {
106
- offlineWorkerSync.on("opfsProgress", cbk);
107
- return await offlineWorkerSync.loadBook({ id, forceRefresh });
108
- }, []);
148
+ fn();
149
+ }, [primaryOnly, nativeReadFile, nativeWriteFile, nativeListZips, checkUpdates]);
150
+ const loadBook = useCallback(
151
+ async ({ id, forceRefresh }, cbk) => {
152
+ offlineWorkerSync.on("opfsProgress", cbk);
153
+ return await offlineWorkerSync.loadBook({ id, forceRefresh });
154
+ },
155
+ []
156
+ );
109
157
  return {
110
158
  ready,
111
159
  loadBook
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lls/offline",
3
- "version": "24.22.0-debug",
3
+ "version": "24.22.0-eaf69c80",
4
4
  "description": "offline",
5
5
  "main": "./lib/index.js",
6
6
  "module": "./lib/index.js",
@@ -36,10 +36,12 @@
36
36
  "devDependencies": {
37
37
  "@biomejs/biome": "1.9.4",
38
38
  "@happy-dom/global-registrator": "15.11.0",
39
+ "@ladle/react": "5.0.3",
39
40
  "@testing-library/react": "16.3.0",
40
41
  "@types/react": "18.3.12",
41
42
  "@types/react-dom": "18.3.1",
42
43
  "@vitejs/plugin-react": "4.5.1",
44
+ "@vitejs/plugin-react-swc": "3.10.1",
43
45
  "esbuild": "0.24.0",
44
46
  "happy-dom": "15.11.0",
45
47
  "lodash": "4.17.21",
@@ -48,17 +50,21 @@
48
50
  "react-dom": "18.3.1",
49
51
  "typescript": "5.8.3",
50
52
  "vite": "7.0.0",
53
+ "vite-tsconfig-paths": "5.1.4",
51
54
  "zod": "3.25.50",
52
- "@lls/vitescripts": "24.22.0"
55
+ "@lls/vitescripts": "24.22.0-eaf69c80"
53
56
  },
54
57
  "dependencies": {
55
58
  "fflate": "0.8.2"
56
59
  },
57
60
  "scripts": {
58
- "start": "echo no devserver",
61
+ "start": "pnpm ladle serve",
59
62
  "dts": "pnpm tsc --project tsconfig.production.json",
60
- "build": "node build",
61
- "test": "bun test --bail",
62
- "lint": "pnpm biome check --apply src __tests__"
63
+ "build": "NODE_ENV=production node build.js",
64
+ "deploy-ladle": "PUBLISH=1 pnpm ladle build",
65
+ "deploy-ladle-pr": "pnpm ladle build && s5cmd sync --delete --acl public-read --destination-region eu-west-1 'dist/*' s3://build.lelivrescolaire.fr/storybook/viewer-pr-$PR_NUMBER/",
66
+ "test": "bun test --bail __tests__/unit __tests__/e2e",
67
+ "lint": "pnpm biome check --apply src __tests__ stories",
68
+ "precommit": "pnpm lint"
63
69
  }
64
70
  }