@pagepocket/lib 0.8.6 → 0.9.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.
Files changed (64) hide show
  1. package/dist/cheerio/types.d.ts +5 -0
  2. package/dist/cheerio/types.js +1 -0
  3. package/dist/core/completion.js +2 -1
  4. package/dist/core/content-store.js +2 -2
  5. package/dist/css-rewrite.d.ts +1 -1
  6. package/dist/css-rewrite.js +2 -2
  7. package/dist/hackers/replay-dom-rewrite/script-part-1.d.ts +1 -0
  8. package/dist/hackers/replay-dom-rewrite/script-part-1.js +202 -0
  9. package/dist/hackers/replay-dom-rewrite/script-part-2.d.ts +1 -0
  10. package/dist/hackers/replay-dom-rewrite/script-part-2.js +173 -0
  11. package/dist/hackers/replay-dom-rewrite.js +3 -373
  12. package/dist/hackers/replay-svg-image.js +32 -8
  13. package/dist/hackers/replay-xhr.js +3 -3
  14. package/dist/path-resolver.js +2 -2
  15. package/dist/replace-elements/actions.d.ts +3 -3
  16. package/dist/replace-elements/actions.js +36 -16
  17. package/dist/replace-elements/match.d.ts +2 -2
  18. package/dist/replace-elements/match.js +22 -11
  19. package/dist/replace-elements/normalize.d.ts +1 -1
  20. package/dist/replace-elements/normalize.js +4 -2
  21. package/dist/replay/match-api.js +29 -17
  22. package/dist/replay/templates/replay-script-template.js +16 -332
  23. package/dist/replay/templates/replay-script-template.part-1.d.ts +5 -0
  24. package/dist/replay/templates/replay-script-template.part-1.js +101 -0
  25. package/dist/replay/templates/replay-script-template.part-2.d.ts +3 -0
  26. package/dist/replay/templates/replay-script-template.part-2.js +222 -0
  27. package/dist/replay/templates/replay-script-template.part-3.d.ts +3 -0
  28. package/dist/replay/templates/replay-script-template.part-3.js +9 -0
  29. package/dist/resource-proxy/pathname-variants.js +8 -5
  30. package/dist/resource-proxy.js +10 -10
  31. package/dist/resources.d.ts +3 -2
  32. package/dist/resources.js +6 -3
  33. package/dist/rewrite-links/js-imports.d.ts +1 -1
  34. package/dist/rewrite-links/link-rel.d.ts +2 -2
  35. package/dist/rewrite-links/meta-refresh.d.ts +1 -1
  36. package/dist/rewrite-links/meta-refresh.js +6 -3
  37. package/dist/rewrite-links/srcset.d.ts +1 -1
  38. package/dist/rewrite-links/srcset.js +4 -2
  39. package/dist/rewrite-links/url-resolve.d.ts +2 -2
  40. package/dist/rewrite-links/url-resolve.js +2 -2
  41. package/dist/rewrite-links.d.ts +1 -1
  42. package/dist/rewrite-links.js +12 -6
  43. package/dist/snapshot-builder/build-snapshot.js +2 -3
  44. package/dist/snapshot-builder/capture-index/index-capture.js +2 -1
  45. package/dist/snapshot-builder/emit-document.d.ts +1 -1
  46. package/dist/snapshot-builder/emit-document.js +1 -1
  47. package/dist/snapshot-builder/grouping.js +2 -2
  48. package/dist/snapshot-builder/path-map.d.ts +1 -1
  49. package/dist/snapshot-builder/path-map.js +1 -1
  50. package/dist/snapshot-builder/resources-path.js +8 -4
  51. package/dist/snapshot-builder/rewrite-resource.d.ts +2 -2
  52. package/dist/snapshot-builder/rewrite-resource.js +2 -2
  53. package/dist/types.d.ts +3 -3
  54. package/dist/units/internal/async-queue.d.ts +9 -0
  55. package/dist/units/internal/async-queue.js +57 -0
  56. package/dist/units/internal/deferred-tracker.d.ts +5 -0
  57. package/dist/units/internal/deferred-tracker.js +13 -0
  58. package/dist/units/internal/runtime.d.ts +37 -0
  59. package/dist/units/internal/runtime.js +113 -0
  60. package/dist/units/runner.js +3 -184
  61. package/dist/utils.d.ts +1 -1
  62. package/dist/utils.js +6 -6
  63. package/package.json +5 -4
  64. package/README.md +0 -357
@@ -1,7 +1,7 @@
1
1
  import type { BuildOptions, StoredResource } from "./types.js";
2
2
  export declare const maybeRewriteStylesheet: (input: {
3
3
  resource: StoredResource;
4
- resolve: (absoluteUrl: string) => string | null;
4
+ resolve: (absoluteUrl: string) => string | undefined;
5
5
  contentStore: BuildOptions["capture"]["contentStore"];
6
6
  rewriteCSS: boolean;
7
7
  }) => Promise<{
@@ -10,7 +10,7 @@ export declare const maybeRewriteStylesheet: (input: {
10
10
  }>;
11
11
  export declare const maybeRewriteScript: (input: {
12
12
  resource: StoredResource;
13
- resolve: (absoluteUrl: string) => string | null;
13
+ resolve: (absoluteUrl: string) => string | undefined;
14
14
  contentStore: BuildOptions["capture"]["contentStore"];
15
15
  }) => Promise<{
16
16
  contentRef: import("../types.js").ContentRef;
@@ -9,7 +9,7 @@ export const maybeRewriteStylesheet = async (input) => {
9
9
  const stream = await input.contentStore.open(input.resource.contentRef);
10
10
  const bytes = await streamToUint8Array(stream);
11
11
  const decoded = decodeUtf8(bytes);
12
- if (decoded === null) {
12
+ if (typeof decoded === "undefined") {
13
13
  return { contentRef: input.resource.contentRef, size: input.resource.size };
14
14
  }
15
15
  const rewritten = await rewriteCssText({
@@ -35,7 +35,7 @@ export const maybeRewriteScript = async (input) => {
35
35
  const stream = await input.contentStore.open(input.resource.contentRef);
36
36
  const bytes = await streamToUint8Array(stream);
37
37
  const decoded = decodeUtf8(bytes);
38
- if (decoded === null) {
38
+ if (typeof decoded === "undefined") {
39
39
  return { contentRef: input.resource.contentRef, size: input.resource.size };
40
40
  }
41
41
  const rewritten = await rewriteJsText(decoded, input.resolve, input.resource.request.url);
package/dist/types.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * NOTE: This repo previously sourced these from a dedicated `@pagepocket/interceptor` package.
5
5
  * That package has been removed; the contracts live in `@pagepocket/lib` now.
6
6
  */
7
- import type { Cheerio, CheerioAPI } from "cheerio";
7
+ import type { AnyCheerio, AnyCheerioApi } from "./cheerio/types.js";
8
8
  export type ResourceType = "document" | "stylesheet" | "script" | "image" | "font" | "media" | "xhr" | "fetch" | "other" | (string & {});
9
9
  export type BodySource = {
10
10
  kind: "buffer";
@@ -205,8 +205,8 @@ export interface ReplaceElementRule {
205
205
  apply?: ApplyOptions;
206
206
  }
207
207
  export interface ReplaceElementContext {
208
- $: CheerioAPI;
209
- $el: Cheerio<any>;
208
+ $: AnyCheerioApi;
209
+ $el: AnyCheerio;
210
210
  url: string;
211
211
  entryUrl: string;
212
212
  ruleIndex: number;
@@ -0,0 +1,9 @@
1
+ export declare const emptyAsyncIterable: <T>() => AsyncIterable<T>;
2
+ export declare class AsyncQueue<T> {
3
+ private values;
4
+ private pending;
5
+ private done;
6
+ push(value: T): void;
7
+ close(): void;
8
+ iterate(): AsyncGenerator<T>;
9
+ }
@@ -0,0 +1,57 @@
1
+ export const emptyAsyncIterable = () => ({
2
+ [Symbol.asyncIterator]() {
3
+ return {
4
+ next: async () => ({ value: undefined, done: true })
5
+ };
6
+ }
7
+ });
8
+ export class AsyncQueue {
9
+ constructor() {
10
+ this.values = [];
11
+ this.pending = [];
12
+ this.done = false;
13
+ }
14
+ push(value) {
15
+ if (this.done) {
16
+ return;
17
+ }
18
+ const resolve = this.pending.shift();
19
+ if (resolve) {
20
+ resolve({ value, done: false });
21
+ return;
22
+ }
23
+ this.values.push(value);
24
+ }
25
+ close() {
26
+ if (this.done) {
27
+ return;
28
+ }
29
+ this.done = true;
30
+ for (const resolve of this.pending) {
31
+ resolve({ value: undefined, done: true });
32
+ }
33
+ this.pending = [];
34
+ }
35
+ async *iterate() {
36
+ while (true) {
37
+ if (this.values.length > 0) {
38
+ const v = this.values.shift();
39
+ if (v === undefined) {
40
+ continue;
41
+ }
42
+ yield v;
43
+ continue;
44
+ }
45
+ if (this.done) {
46
+ return;
47
+ }
48
+ const next = await new Promise((resolve) => {
49
+ this.pending.push(resolve);
50
+ });
51
+ if (next.done) {
52
+ return;
53
+ }
54
+ yield next.value;
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,5 @@
1
+ export declare class DeferredTracker {
2
+ private promises;
3
+ add(p: Promise<unknown>): void;
4
+ awaitAll(): Promise<void>;
5
+ }
@@ -0,0 +1,13 @@
1
+ export class DeferredTracker {
2
+ constructor() {
3
+ this.promises = [];
4
+ }
5
+ add(p) {
6
+ this.promises.push(p);
7
+ }
8
+ async awaitAll() {
9
+ const ps = this.promises;
10
+ this.promises = [];
11
+ await Promise.allSettled(ps);
12
+ }
13
+ }
@@ -0,0 +1,37 @@
1
+ import type { ChannelToken, ReplaceElementsConfig } from "@pagepocket/contracts";
2
+ import { type ElementPatchRegistry, type UnitContext, type UnitPatch, type UnitRuntime } from "../contracts-bridge.js";
3
+ import type { CaptureOptions, EntryInfo, PagePocketOptions } from "../types.js";
4
+ declare class ElementPatchRegistryImpl implements ElementPatchRegistry {
5
+ private items;
6
+ contribute(_source: {
7
+ type: "unit" | "plugin";
8
+ name: string;
9
+ }, rules: ReplaceElementsConfig): void;
10
+ contributeLazy(_source: {
11
+ type: "unit" | "plugin";
12
+ name: string;
13
+ }, build: () => ReplaceElementsConfig | Promise<ReplaceElementsConfig>): void;
14
+ compile(): Promise<ReplaceElementsConfig>;
15
+ }
16
+ export declare class RuntimeImpl implements UnitRuntime {
17
+ readonly entry: EntryInfo;
18
+ readonly options: CaptureOptions;
19
+ readonly pocketOptions: PagePocketOptions;
20
+ private channels;
21
+ private deferred;
22
+ readonly elements: ElementPatchRegistryImpl;
23
+ constructor(input: {
24
+ entry: EntryInfo;
25
+ options: CaptureOptions;
26
+ pocketOptions: PagePocketOptions;
27
+ });
28
+ publish<T>(t: ChannelToken<T>, value: T): void;
29
+ subscribe<T>(t: ChannelToken<T>): AsyncIterable<T>;
30
+ hasPublisher(t: ChannelToken<unknown>): boolean;
31
+ defer(promise: Promise<unknown>): void;
32
+ _ensureChannel(t: ChannelToken<unknown>): void;
33
+ _closeAllChannels(): Promise<void>;
34
+ _awaitDeferred(): Promise<void>;
35
+ }
36
+ export declare const mergePatchIntoFreshContext: (patch: UnitPatch) => UnitContext;
37
+ export {};
@@ -0,0 +1,113 @@
1
+ import { TERMINAL_RESULT_KEY } from "../contracts-bridge.js";
2
+ import { AsyncQueue, emptyAsyncIterable } from "./async-queue.js";
3
+ import { DeferredTracker } from "./deferred-tracker.js";
4
+ class ElementPatchRegistryImpl {
5
+ constructor() {
6
+ this.items = [];
7
+ }
8
+ contribute(_source, rules) {
9
+ this.items.push({ rules });
10
+ }
11
+ contributeLazy(_source, build) {
12
+ this.items.push({ build });
13
+ }
14
+ async compile() {
15
+ const out = [];
16
+ for (const item of this.items) {
17
+ if (item.rules) {
18
+ out.push(...item.rules);
19
+ continue;
20
+ }
21
+ if (item.build) {
22
+ const rules = await item.build();
23
+ out.push(...rules);
24
+ }
25
+ }
26
+ return out;
27
+ }
28
+ }
29
+ export class RuntimeImpl {
30
+ constructor(input) {
31
+ this.channels = new Map();
32
+ this.deferred = new DeferredTracker();
33
+ this.elements = new ElementPatchRegistryImpl();
34
+ this.entry = input.entry;
35
+ this.options = input.options;
36
+ this.pocketOptions = input.pocketOptions;
37
+ }
38
+ publish(t, value) {
39
+ const state = this.channels.get(t.id);
40
+ if (!state || state.closed) {
41
+ return;
42
+ }
43
+ state.hasPublisher = true;
44
+ for (const sub of state.subs) {
45
+ sub.push(value);
46
+ }
47
+ }
48
+ subscribe(t) {
49
+ if (!this.channels.has(t.id)) {
50
+ this.channels.set(t.id, { hasPublisher: false, subs: new Set(), closed: false });
51
+ }
52
+ const state = this.channels.get(t.id);
53
+ if (!state || state.closed) {
54
+ return emptyAsyncIterable();
55
+ }
56
+ const q = new AsyncQueue();
57
+ const sub = {
58
+ push: (v) => q.push(v),
59
+ close: () => q.close()
60
+ };
61
+ state.subs.add(sub);
62
+ const owner = this;
63
+ return (async function* () {
64
+ try {
65
+ for await (const v of q.iterate()) {
66
+ yield v;
67
+ }
68
+ }
69
+ finally {
70
+ const s = owner.channels.get(t.id);
71
+ s?.subs.delete(sub);
72
+ q.close();
73
+ }
74
+ })();
75
+ }
76
+ hasPublisher(t) {
77
+ return this.channels.get(t.id)?.hasPublisher === true;
78
+ }
79
+ defer(promise) {
80
+ this.deferred.add(promise);
81
+ }
82
+ _ensureChannel(t) {
83
+ if (this.channels.has(t.id)) {
84
+ return;
85
+ }
86
+ this.channels.set(t.id, { hasPublisher: false, subs: new Set(), closed: false });
87
+ }
88
+ async _closeAllChannels() {
89
+ for (const state of this.channels.values()) {
90
+ if (state.closed) {
91
+ continue;
92
+ }
93
+ state.closed = true;
94
+ for (const sub of state.subs) {
95
+ sub.close();
96
+ }
97
+ state.subs.clear();
98
+ }
99
+ }
100
+ async _awaitDeferred() {
101
+ await this.deferred.awaitAll();
102
+ }
103
+ }
104
+ export const mergePatchIntoFreshContext = (patch) => {
105
+ const next = { value: {} };
106
+ for (const [k, v] of Object.entries(patch)) {
107
+ if (k === TERMINAL_RESULT_KEY) {
108
+ continue;
109
+ }
110
+ next.value[k] = v;
111
+ }
112
+ return next;
113
+ };
@@ -1,179 +1,5 @@
1
1
  import { TERMINAL_RESULT_KEY } from "./contracts-bridge.js";
2
- const emptyAsyncIterable = () => ({
3
- [Symbol.asyncIterator]() {
4
- return {
5
- next: async () => ({ value: undefined, done: true })
6
- };
7
- }
8
- });
9
- class AsyncQueue {
10
- constructor() {
11
- this.values = [];
12
- this.pending = [];
13
- this.done = false;
14
- }
15
- push(value) {
16
- if (this.done) {
17
- return;
18
- }
19
- const resolve = this.pending.shift();
20
- if (resolve) {
21
- resolve({ value, done: false });
22
- return;
23
- }
24
- this.values.push(value);
25
- }
26
- close() {
27
- if (this.done) {
28
- return;
29
- }
30
- this.done = true;
31
- for (const resolve of this.pending) {
32
- resolve({ value: undefined, done: true });
33
- }
34
- this.pending = [];
35
- }
36
- async *iterate() {
37
- while (true) {
38
- if (this.values.length > 0) {
39
- const v = this.values.shift();
40
- if (v === undefined) {
41
- continue;
42
- }
43
- yield v;
44
- continue;
45
- }
46
- if (this.done) {
47
- return;
48
- }
49
- const next = await new Promise((resolve) => {
50
- this.pending.push(resolve);
51
- });
52
- if (next.done) {
53
- return;
54
- }
55
- yield next.value;
56
- }
57
- }
58
- }
59
- class DeferredTracker {
60
- constructor() {
61
- this.promises = [];
62
- }
63
- add(p) {
64
- this.promises.push(p);
65
- }
66
- async awaitAll() {
67
- const ps = this.promises;
68
- this.promises = [];
69
- await Promise.allSettled(ps);
70
- }
71
- }
72
- class ElementPatchRegistryImpl {
73
- constructor() {
74
- this.items = [];
75
- }
76
- contribute(_source, rules) {
77
- this.items.push({ rules });
78
- }
79
- contributeLazy(_source, build) {
80
- this.items.push({ build });
81
- }
82
- async compile() {
83
- const out = [];
84
- for (const item of this.items) {
85
- if (item.rules) {
86
- out.push(...item.rules);
87
- continue;
88
- }
89
- if (item.build) {
90
- const rules = await item.build();
91
- out.push(...rules);
92
- }
93
- }
94
- return out;
95
- }
96
- }
97
- class RuntimeImpl {
98
- constructor(input) {
99
- this.channels = new Map();
100
- this.deferred = new DeferredTracker();
101
- this.elements = new ElementPatchRegistryImpl();
102
- this.entry = input.entry;
103
- this.options = input.options;
104
- this.pocketOptions = input.pocketOptions;
105
- }
106
- publish(t, value) {
107
- const state = this.channels.get(t.id);
108
- if (!state || state.closed) {
109
- // No subscribers or closed: drop.
110
- return;
111
- }
112
- state.hasPublisher = true;
113
- for (const sub of state.subs) {
114
- sub.push(value);
115
- }
116
- }
117
- subscribe(t) {
118
- // subscribe MUST be safe.
119
- // Important: subscribe must also be able to receive future publishes.
120
- // If no publisher ever publishes, the iterator will just complete on close.
121
- if (!this.channels.has(t.id)) {
122
- this.channels.set(t.id, { hasPublisher: false, subs: new Set(), closed: false });
123
- }
124
- const state = this.channels.get(t.id);
125
- if (!state || state.closed) {
126
- return emptyAsyncIterable();
127
- }
128
- const q = new AsyncQueue();
129
- const sub = {
130
- push: (v) => q.push(v),
131
- close: () => q.close()
132
- };
133
- state.subs.add(sub);
134
- const owner = this;
135
- return (async function* () {
136
- try {
137
- for await (const v of q.iterate()) {
138
- yield v;
139
- }
140
- }
141
- finally {
142
- // best-effort cleanup
143
- const s = owner.channels.get(t.id);
144
- s?.subs.delete(sub);
145
- q.close();
146
- }
147
- })();
148
- }
149
- hasPublisher(t) {
150
- return this.channels.get(t.id)?.hasPublisher === true;
151
- }
152
- defer(promise) {
153
- this.deferred.add(promise);
154
- }
155
- _ensureChannel(t) {
156
- if (this.channels.has(t.id)) {
157
- return;
158
- }
159
- this.channels.set(t.id, { hasPublisher: false, subs: new Set(), closed: false });
160
- }
161
- async _closeAllChannels() {
162
- for (const state of this.channels.values()) {
163
- if (state.closed) {
164
- continue;
165
- }
166
- state.closed = true;
167
- for (const sub of state.subs) {
168
- sub.close();
169
- }
170
- state.subs.clear();
171
- }
172
- }
173
- async _awaitDeferred() {
174
- await this.deferred.awaitAll();
175
- }
176
- }
2
+ import { mergePatchIntoFreshContext, RuntimeImpl } from "./internal/runtime.js";
177
3
  export const runCapture = async (input) => {
178
4
  const rt = new RuntimeImpl({
179
5
  entry: input.entry,
@@ -198,15 +24,8 @@ export const runCapture = async (input) => {
198
24
  pluginSetupValues.set(plugin, v);
199
25
  }
200
26
  }
201
- const mergePatch = (ctx, patch) => {
202
- const next = { value: {} };
203
- for (const [k, v] of Object.entries(patch)) {
204
- if (k === TERMINAL_RESULT_KEY) {
205
- continue;
206
- }
207
- next.value[k] = v;
208
- }
209
- return next;
27
+ const mergePatch = (_ctx, patch) => {
28
+ return mergePatchIntoFreshContext(patch);
210
29
  };
211
30
  let ctx = { value: {} };
212
31
  let result;
package/dist/utils.d.ts CHANGED
@@ -5,7 +5,7 @@ export declare const stripLeadingSlash: (value: string) => string;
5
5
  export declare const ensureLeadingSlash: (value: string) => string;
6
6
  export declare const sanitizePosixPath: (value: string) => string;
7
7
  export declare const bytesToBase64: (bytes: Uint8Array) => string;
8
- export declare const decodeUtf8: (bytes: Uint8Array) => string | null;
8
+ export declare const decodeUtf8: (bytes: Uint8Array) => string | undefined;
9
9
  export declare const isUtf8Text: (bytes: Uint8Array, mimeType?: string) => boolean;
10
10
  export declare const toUint8Array: (body: BodySource) => Promise<Uint8Array>;
11
11
  export declare const bodyToTextOrBase64: (bytes: Uint8Array, mimeType?: string) => {
package/dist/utils.js CHANGED
@@ -46,12 +46,12 @@ export const decodeUtf8 = (bytes) => {
46
46
  return decoder.decode(bytes);
47
47
  }
48
48
  catch {
49
- return null;
49
+ return undefined;
50
50
  }
51
51
  };
52
52
  export const isUtf8Text = (bytes, mimeType) => {
53
53
  const decoded = decodeUtf8(bytes);
54
- if (decoded === null) {
54
+ if (typeof decoded === "undefined") {
55
55
  return false;
56
56
  }
57
57
  if (mimeType) {
@@ -95,7 +95,7 @@ export const bodyToTextOrBase64 = (bytes, mimeType) => {
95
95
  }
96
96
  return { encoding: "base64", base64: bytesToBase64(bytes) };
97
97
  };
98
- const toUrlOrNull = (input, baseUrl) => {
98
+ const toUrlOrUndefined = (input, baseUrl) => {
99
99
  try {
100
100
  if (baseUrl) {
101
101
  return new URL(input, baseUrl);
@@ -103,7 +103,7 @@ const toUrlOrNull = (input, baseUrl) => {
103
103
  return new URL(input);
104
104
  }
105
105
  catch {
106
- return null;
106
+ return undefined;
107
107
  }
108
108
  };
109
109
  const stripTrailingSlash = (value) => value.replace(/\/+$/, "");
@@ -142,8 +142,8 @@ const areProtocolsEquivalent = (a, b) => {
142
142
  */
143
143
  export const urlEquivalent = (a, b, options = {}) => {
144
144
  const { baseUrl, ignoreHash = true, ignoreDefaultPort = true, normalizeTrailingSlash: shouldNormalizeTrailingSlash = true } = options;
145
- const left = toUrlOrNull(String(a), baseUrl);
146
- const right = toUrlOrNull(String(b), baseUrl);
145
+ const left = toUrlOrUndefined(String(a), baseUrl);
146
+ const right = toUrlOrUndefined(String(b), baseUrl);
147
147
  if (!left || !right) {
148
148
  return false;
149
149
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pagepocket/lib",
3
- "version": "0.8.6",
3
+ "version": "0.9.0",
4
4
  "description": "Library for rewriting HTML snapshots and inlining local resources.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -19,9 +19,10 @@
19
19
  "license": "ISC",
20
20
  "dependencies": {
21
21
  "cheerio": "^1.0.0-rc.12",
22
- "@pagepocket/shared": "0.8.6",
23
- "@pagepocket/contracts": "0.8.6",
24
- "@pagepocket/uni-fs": "0.8.6"
22
+ "domhandler": "^5.0.3",
23
+ "@pagepocket/uni-fs": "0.9.0",
24
+ "@pagepocket/shared": "0.9.0",
25
+ "@pagepocket/contracts": "0.9.0"
25
26
  },
26
27
  "devDependencies": {
27
28
  "@playwright/test": "^1.50.1",