@packall/core 0.1.0 → 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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -0
  3. package/dist/archive-6sqTn1C4.js +274 -0
  4. package/dist/archive-6sqTn1C4.js.map +1 -0
  5. package/dist/archive.d.ts +47 -15
  6. package/dist/archive.d.ts.map +1 -1
  7. package/dist/bundle.d.ts +6 -3
  8. package/dist/bundle.d.ts.map +1 -1
  9. package/dist/dependency-range.d.ts.map +1 -1
  10. package/dist/download.d.ts +2 -1
  11. package/dist/download.d.ts.map +1 -1
  12. package/dist/enums/artifact-kind.d.ts.map +1 -1
  13. package/dist/enums/edge-kind.d.ts.map +1 -1
  14. package/dist/enums/input-file-kind.d.ts.map +1 -1
  15. package/dist/enums/layout.d.ts.map +1 -1
  16. package/dist/enums/lockfile-format.d.ts.map +1 -1
  17. package/dist/enums/phase.d.ts.map +1 -1
  18. package/dist/errors.d.ts.map +1 -1
  19. package/dist/index.d.ts +12 -10
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +169 -344
  22. package/dist/index.js.map +1 -1
  23. package/dist/input-file.d.ts.map +1 -1
  24. package/dist/integrity.d.ts +37 -5
  25. package/dist/integrity.d.ts.map +1 -1
  26. package/dist/layout.d.ts +41 -7
  27. package/dist/layout.d.ts.map +1 -1
  28. package/dist/locked-resolve.d.ts.map +1 -1
  29. package/dist/lockfile/bun.d.ts.map +1 -1
  30. package/dist/lockfile/detect.d.ts.map +1 -1
  31. package/dist/lockfile/json.d.ts.map +1 -1
  32. package/dist/lockfile/names.d.ts.map +1 -1
  33. package/dist/lockfile/npm.d.ts.map +1 -1
  34. package/dist/lockfile/parse.d.ts.map +1 -1
  35. package/dist/lockfile/pnpm.d.ts.map +1 -1
  36. package/dist/lockfile/tree-builder.d.ts.map +1 -1
  37. package/dist/lockfile/types.d.ts.map +1 -1
  38. package/dist/manifest.d.ts.map +1 -1
  39. package/dist/node/archiver.d.ts +5 -0
  40. package/dist/node/archiver.d.ts.map +1 -0
  41. package/dist/node/index.d.ts +13 -0
  42. package/dist/node/index.d.ts.map +1 -0
  43. package/dist/node/index.js +55 -0
  44. package/dist/node/index.js.map +1 -0
  45. package/dist/options.d.ts +3 -3
  46. package/dist/options.d.ts.map +1 -1
  47. package/dist/platform.d.ts +5 -10
  48. package/dist/platform.d.ts.map +1 -1
  49. package/dist/progress.d.ts.map +1 -1
  50. package/dist/registry.d.ts.map +1 -1
  51. package/dist/resolve.d.ts.map +1 -1
  52. package/dist/schemas/lenient.d.ts.map +1 -1
  53. package/dist/schemas/package-json.d.ts.map +1 -1
  54. package/dist/spec.d.ts.map +1 -1
  55. package/dist/types/value-of.d.ts.map +1 -1
  56. package/dist/utils/is-record.d.ts.map +1 -1
  57. package/package.json +11 -10
  58. package/src/archive.ts +95 -92
  59. package/src/bundle.ts +736 -616
  60. package/src/dependency-range.ts +121 -115
  61. package/src/download.ts +155 -133
  62. package/src/enums/artifact-kind.ts +5 -5
  63. package/src/enums/edge-kind.ts +13 -13
  64. package/src/enums/input-file-kind.ts +9 -9
  65. package/src/enums/layout.ts +20 -20
  66. package/src/enums/lockfile-format.ts +6 -6
  67. package/src/enums/phase.ts +8 -8
  68. package/src/errors.ts +200 -198
  69. package/src/index.ts +126 -113
  70. package/src/input-file.ts +246 -234
  71. package/src/integrity.ts +165 -121
  72. package/src/layout.ts +63 -29
  73. package/src/locked-resolve.ts +471 -461
  74. package/src/lockfile/bun.ts +214 -207
  75. package/src/lockfile/detect.ts +80 -80
  76. package/src/lockfile/json.ts +91 -92
  77. package/src/lockfile/names.ts +9 -9
  78. package/src/lockfile/npm.ts +328 -308
  79. package/src/lockfile/parse.ts +32 -32
  80. package/src/lockfile/pnpm.ts +230 -225
  81. package/src/lockfile/tree-builder.ts +116 -116
  82. package/src/lockfile/types.ts +52 -52
  83. package/src/manifest.ts +129 -133
  84. package/src/node/archiver.ts +85 -0
  85. package/src/node/index.ts +13 -0
  86. package/src/options.ts +84 -84
  87. package/src/platform.ts +97 -101
  88. package/src/progress.ts +105 -106
  89. package/src/registry.ts +116 -115
  90. package/src/resolve.ts +560 -537
  91. package/src/schemas/lenient.ts +67 -60
  92. package/src/schemas/package-json.ts +11 -10
  93. package/src/spec.ts +123 -119
  94. package/src/types/value-of.ts +1 -1
  95. package/src/utils/is-record.ts +1 -1
package/src/platform.ts CHANGED
@@ -1,27 +1,22 @@
1
1
  /**
2
2
  * Platform matching for optional dependencies.
3
3
  *
4
- * This module exists because of one specific, very annoying failure mode.
4
+ * The default is "keep every platform" and narrowing is opt-in — see
5
+ * `notes/requirements.md` § FR-13 for the failure mode that decides it.
5
6
  *
6
- * Packages with native code esbuild, rollup, sharp, swc, lightningcss,
7
- * `@parcel/watcher` publish their binary as a *set* of sibling packages, one
8
- * per platform, and list all of them in `optionalDependencies`. npm installs
9
- * only the one matching the current machine and silently skips the rest.
10
- *
11
- * So if you bundle on a Mac and skip the non-matching optional deps, the
12
- * resulting bundle installs fine on your Mac and fails on the Linux CI box
13
- * inside the firewall — which is the only place it actually matters. Hence the
14
- * default is "take every platform" and narrowing is opt-in.
7
+ * The mechanics that decision rests on are here rather than there: a package
8
+ * declares `os`, `cpu` and `libc` in its own manifest, and this module keeps a
9
+ * package when the filter *could* install it, not when it matches exactly.
15
10
  */
16
11
 
17
12
  /** A target to keep, e.g. `linux-x64`, `linux-x64-musl`, or just `linux`. */
18
13
  export type PlatformTarget = {
19
- readonly os: string
20
- /** Undefined means "every architecture for this OS". */
21
- readonly cpu?: string | undefined
22
- /** `glibc` / `musl`. Undefined means "don't care". */
23
- readonly libc?: string | undefined
24
- }
14
+ readonly os: string;
15
+ /** Undefined means "every architecture for this OS". */
16
+ readonly cpu?: string | undefined;
17
+ /** `glibc` / `musl`. Undefined means "don't care". */
18
+ readonly libc?: string | undefined;
19
+ };
25
20
 
26
21
  /**
27
22
  * Which platforms optional dependencies should be resolved for.
@@ -30,28 +25,28 @@ export type PlatformTarget = {
30
25
  * - `Targets` — keep only packages that could install on one of these.
31
26
  */
32
27
  export type PlatformFilter =
33
- | { readonly _tag: "All" }
34
- | { readonly _tag: "Targets"; readonly targets: ReadonlyArray<PlatformTarget> }
28
+ | { readonly _tag: "All" }
29
+ | { readonly _tag: "Targets"; readonly targets: ReadonlyArray<PlatformTarget> };
35
30
 
36
31
  /** Constraints declared by a package, as they appear in its manifest. */
37
32
  export type PlatformConstraints = {
38
- readonly os?: ReadonlyArray<string> | undefined
39
- readonly cpu?: ReadonlyArray<string> | undefined
40
- readonly libc?: ReadonlyArray<string> | undefined
41
- }
33
+ readonly os?: ReadonlyArray<string> | undefined;
34
+ readonly cpu?: ReadonlyArray<string> | undefined;
35
+ readonly libc?: ReadonlyArray<string> | undefined;
36
+ };
42
37
 
43
- export const allPlatforms: PlatformFilter = { _tag: "All" }
38
+ export const allPlatforms: PlatformFilter = { _tag: "All" };
44
39
 
45
40
  export const platformTargets = (targets: ReadonlyArray<PlatformTarget>): PlatformFilter => ({
46
- _tag: "Targets",
47
- targets
48
- })
41
+ _tag: "Targets",
42
+ targets,
43
+ });
49
44
 
50
45
  /** The machine we are currently running on. */
51
46
  export const currentPlatform = (): PlatformTarget => ({
52
- os: process.platform,
53
- cpu: process.arch
54
- })
47
+ os: process.platform,
48
+ cpu: process.arch,
49
+ });
55
50
 
56
51
  /**
57
52
  * npm's documented `os` values, plus the names people actually type.
@@ -62,28 +57,28 @@ export const currentPlatform = (): PlatformTarget => ({
62
57
  * all would be precisely that, so it is corrected rather than accepted.
63
58
  */
64
59
  const osByAlias: Readonly<Record<string, string | undefined>> = {
65
- win: "win32",
66
- windows: "win32",
67
- mac: "darwin",
68
- macos: "darwin",
69
- osx: "darwin"
70
- }
60
+ win: "win32",
61
+ windows: "win32",
62
+ mac: "darwin",
63
+ macos: "darwin",
64
+ osx: "darwin",
65
+ };
71
66
 
72
67
  const KNOWN_OS: ReadonlySet<string> = new Set([
73
- "aix",
74
- "android",
75
- "cygwin",
76
- "darwin",
77
- "freebsd",
78
- "haiku",
79
- "linux",
80
- "netbsd",
81
- "openbsd",
82
- "sunos",
83
- "win32"
84
- ])
85
-
86
- const LIBC: ReadonlySet<string> = new Set(["musl", "glibc"])
68
+ "aix",
69
+ "android",
70
+ "cygwin",
71
+ "darwin",
72
+ "freebsd",
73
+ "haiku",
74
+ "linux",
75
+ "netbsd",
76
+ "openbsd",
77
+ "sunos",
78
+ "win32",
79
+ ]);
80
+
81
+ const LIBC: ReadonlySet<string> = new Set(["musl", "glibc"]);
87
82
 
88
83
  /**
89
84
  * Parses `linux`, `linux-x64`, `darwin-arm64`, `win32-x64`, `linux-x64-musl`,
@@ -98,23 +93,27 @@ const LIBC: ReadonlySet<string> = new Set(["musl", "glibc"])
98
93
  * once alongside the list of accepted forms.
99
94
  */
100
95
  export const parsePlatformTarget = (input: string): PlatformTarget | null => {
101
- const parts = input.trim().toLowerCase().split("-").filter((part) => part.length > 0)
102
- const [rawOs, second, third] = parts
103
- if (rawOs === undefined || parts.length > 3) return null
96
+ const parts = input
97
+ .trim()
98
+ .toLowerCase()
99
+ .split("-")
100
+ .filter((part) => part.length > 0);
101
+ const [rawOs, second, third] = parts;
102
+ if (rawOs === undefined || parts.length > 3) return null;
104
103
 
105
- const os = osByAlias[rawOs] ?? rawOs
106
- if (!KNOWN_OS.has(os)) return null
104
+ const os = osByAlias[rawOs] ?? rawOs;
105
+ if (!KNOWN_OS.has(os)) return null;
107
106
 
108
- if (second === undefined) return { os }
107
+ if (second === undefined) return { os };
109
108
 
110
- // `linux-musl` is an OS plus a libc, not an OS plus an architecture.
111
- if (third === undefined) {
112
- return LIBC.has(second) ? { os, libc: second } : { os, cpu: second }
113
- }
109
+ // `linux-musl` is an OS plus a libc, not an OS plus an architecture.
110
+ if (third === undefined) {
111
+ return LIBC.has(second) ? { os, libc: second } : { os, cpu: second };
112
+ }
114
113
 
115
- if (!LIBC.has(third)) return null
116
- return { os, cpu: second, libc: third }
117
- }
114
+ if (!LIBC.has(third)) return null;
115
+ return { os, cpu: second, libc: third };
116
+ };
118
117
 
119
118
  /**
120
119
  * npm's `os`/`cpu`/`libc` fields use a list of allowed values, where a leading
@@ -124,59 +123,56 @@ export const parsePlatformTarget = (input: string): PlatformTarget | null => {
124
123
  * if there are any positive entries, at least one must match.
125
124
  */
126
125
  const listAllows = (list: ReadonlyArray<string> | undefined, value: string): boolean => {
127
- if (list === undefined || list.length === 0) return true
126
+ if (list === undefined || list.length === 0) return true;
128
127
 
129
- let hasPositive = false
130
- let positiveMatched = false
128
+ let hasPositive = false;
129
+ let positiveMatched = false;
131
130
 
132
- for (const entry of list) {
133
- const normalized = entry.trim().toLowerCase()
134
- if (normalized.length === 0) continue
135
- if (normalized === "any" || normalized === "*") return true
131
+ for (const entry of list) {
132
+ const normalized = entry.trim().toLowerCase();
133
+ if (normalized.length === 0) continue;
134
+ if (normalized === "any" || normalized === "*") return true;
136
135
 
137
- if (normalized.startsWith("!")) {
138
- if (normalized.slice(1) === value) return false
139
- } else {
140
- hasPositive = true
141
- if (normalized === value) positiveMatched = true
142
- }
143
- }
136
+ if (normalized.startsWith("!")) {
137
+ if (normalized.slice(1) === value) return false;
138
+ } else {
139
+ hasPositive = true;
140
+ if (normalized === value) positiveMatched = true;
141
+ }
142
+ }
144
143
 
145
- return hasPositive ? positiveMatched : true
146
- }
144
+ return hasPositive ? positiveMatched : true;
145
+ };
147
146
 
148
147
  /** Would this package install on this specific target? */
149
148
  export const matchesTarget = (
150
- constraints: PlatformConstraints,
151
- target: PlatformTarget
149
+ constraints: PlatformConstraints,
150
+ target: PlatformTarget,
152
151
  ): boolean => {
153
- if (!listAllows(constraints.os, target.os)) return false
154
- // An undefined cpu means the target is the whole OS, so any architecture the
155
- // package declares qualifies.
156
- if (target.cpu !== undefined && !listAllows(constraints.cpu, target.cpu)) return false
157
- if (target.libc !== undefined && !listAllows(constraints.libc, target.libc)) return false
158
- return true
159
- }
152
+ if (!listAllows(constraints.os, target.os)) return false;
153
+ // An undefined cpu means the target is the whole OS, so any architecture the
154
+ // package declares qualifies.
155
+ if (target.cpu !== undefined && !listAllows(constraints.cpu, target.cpu)) return false;
156
+ if (target.libc !== undefined && !listAllows(constraints.libc, target.libc)) return false;
157
+ return true;
158
+ };
160
159
 
161
160
  /**
162
161
  * Should this package be included, given the active filter?
163
162
  *
164
163
  * Under `All` this is unconditionally `true` — that is the whole point.
165
164
  */
166
- export const isIncluded = (
167
- constraints: PlatformConstraints,
168
- filter: PlatformFilter
169
- ): boolean => {
170
- if (filter._tag === "All") return true
171
- return filter.targets.some((target) => matchesTarget(constraints, target))
172
- }
165
+ export const isIncluded = (constraints: PlatformConstraints, filter: PlatformFilter): boolean => {
166
+ if (filter._tag === "All") return true;
167
+ return filter.targets.some((target) => matchesTarget(constraints, target));
168
+ };
173
169
 
174
170
  /** Renders a filter for the manifest and for `--dry-run` output. */
175
171
  export const formatPlatformFilter = (filter: PlatformFilter): string => {
176
- if (filter._tag === "All") return "all"
177
- return filter.targets
178
- .map((target) =>
179
- [target.os, target.cpu, target.libc].filter((part) => part !== undefined).join("-")
180
- )
181
- .join(",")
182
- }
172
+ if (filter._tag === "All") return "all";
173
+ return filter.targets
174
+ .map((target) =>
175
+ [target.os, target.cpu, target.libc].filter((part) => part !== undefined).join("-"),
176
+ )
177
+ .join(",");
178
+ };
package/src/progress.ts CHANGED
@@ -1,106 +1,105 @@
1
- /**
2
- * Progress reporting.
3
- *
4
- * The engine emits structured events; it never writes to a terminal. That
5
- * separation is what lets the CLI draw a live TTY view, CI print plain lines,
6
- * and the test suite assert on an array of events without any of them
7
- * interfering with each other.
8
- */
9
- import * as Context from "effect/Context"
10
- import * as Effect from "effect/Effect"
11
- import * as Layer from "effect/Layer"
12
- import * as Ref from "effect/Ref"
13
- import type { Phase } from "./enums/phase.js"
14
-
15
- /** A structured progress event. */
16
- export type ProgressEvent =
17
- /** A phase began. `total` is set only when it is known up front. */
18
- | { readonly _tag: "PhaseStarted"; readonly phase: Phase; readonly total?: number | undefined }
19
- /** A phase finished. */
20
- | { readonly _tag: "PhaseCompleted"; readonly phase: Phase }
21
- /** The resolver discovered a package that has to be included. */
22
- | {
23
- readonly _tag: "PackageResolved"
24
- readonly name: string
25
- readonly version: string
26
- /** Running count of resolved packages, for a live counter. */
27
- readonly resolvedCount: number
28
- /** Packages still queued for resolution. */
29
- readonly pendingCount: number
30
- }
31
- /** A tarball download started. */
32
- | { readonly _tag: "DownloadStarted"; readonly name: string; readonly version: string }
33
- /** A tarball download finished. */
34
- | {
35
- readonly _tag: "DownloadCompleted"
36
- readonly name: string
37
- readonly version: string
38
- readonly bytes: number
39
- readonly completedCount: number
40
- readonly totalCount: number
41
- }
42
- /** A download failed and will be retried. */
43
- | {
44
- readonly _tag: "DownloadRetrying"
45
- readonly name: string
46
- readonly version: string
47
- readonly attempt: number
48
- readonly reason: string
49
- }
50
- /** An archive is being written. */
51
- | { readonly _tag: "ArchiveStarted"; readonly path: string; readonly entryCount: number }
52
- /** An archive finished. */
53
- | { readonly _tag: "ArchiveCompleted"; readonly path: string; readonly bytes: number }
54
- /** Something noteworthy but non-fatal, e.g. a deprecated package. */
55
- | { readonly _tag: "Warning"; readonly message: string }
56
-
57
- export declare namespace Progress {
58
- /** The reporting sink. */
59
- export type Service = {
60
- readonly emit: (event: ProgressEvent) => Effect.Effect<void>
61
- }
62
- }
63
-
64
- /** Service tag for progress reporting. */
65
- export class Progress extends Context.Service<Progress, Progress.Service>()(
66
- "@packall/core/Progress"
67
- ) {}
68
-
69
- /** Emits an event to whichever reporter is installed. */
70
- export const emit = (event: ProgressEvent): Effect.Effect<void, never, Progress> =>
71
- Effect.gen(function*() {
72
- const progress = yield* Progress
73
- yield* progress.emit(event)
74
- })
75
-
76
- /**
77
- * Discards every event.
78
- *
79
- * The default for library consumers and for tests that do not care about
80
- * progress silence should never require ceremony.
81
- */
82
- export const layerSilent: Layer.Layer<Progress> = Layer.succeed(Progress)({
83
- emit: () => Effect.void
84
- })
85
-
86
- /** Sends every event to a callback. Used by the CLI renderer. */
87
- export const layerCallback = (
88
- onEvent: (event: ProgressEvent) => void
89
- ): Layer.Layer<Progress> =>
90
- Layer.succeed(Progress)({
91
- emit: (event) => Effect.sync(() => onEvent(event))
92
- })
93
-
94
- /**
95
- * Accumulates every event into a `Ref`, for assertions.
96
- *
97
- * Returned as `{ layer, events }` so a test can provide the layer and then read
98
- * the transcript afterwards.
99
- */
100
- export const makeCollector = Effect.gen(function*() {
101
- const ref = yield* Ref.make<ReadonlyArray<ProgressEvent>>([])
102
- const layer = Layer.succeed(Progress)({
103
- emit: (event: ProgressEvent) => Ref.update(ref, (events) => [...events, event])
104
- })
105
- return { layer, events: Ref.get(ref) } as const
106
- })
1
+ /**
2
+ * Progress reporting.
3
+ *
4
+ * The engine emits structured events; it never writes to a terminal. That
5
+ * separation is what lets the CLI draw a live TTY view, CI print plain lines,
6
+ * and the test suite assert on an array of events without any of them
7
+ * interfering with each other.
8
+ */
9
+ import * as Context from "effect/Context";
10
+ import * as Effect from "effect/Effect";
11
+ import * as Layer from "effect/Layer";
12
+ import * as Ref from "effect/Ref";
13
+
14
+ import type { Phase } from "./enums/phase.js";
15
+
16
+ /** A structured progress event. */
17
+ export type ProgressEvent =
18
+ /** A phase began. `total` is set only when it is known up front. */
19
+ | { readonly _tag: "PhaseStarted"; readonly phase: Phase; readonly total?: number | undefined }
20
+ /** A phase finished. */
21
+ | { readonly _tag: "PhaseCompleted"; readonly phase: Phase }
22
+ /** The resolver discovered a package that has to be included. */
23
+ | {
24
+ readonly _tag: "PackageResolved";
25
+ readonly name: string;
26
+ readonly version: string;
27
+ /** Running count of resolved packages, for a live counter. */
28
+ readonly resolvedCount: number;
29
+ /** Packages still queued for resolution. */
30
+ readonly pendingCount: number;
31
+ }
32
+ /** A tarball download started. */
33
+ | { readonly _tag: "DownloadStarted"; readonly name: string; readonly version: string }
34
+ /** A tarball download finished. */
35
+ | {
36
+ readonly _tag: "DownloadCompleted";
37
+ readonly name: string;
38
+ readonly version: string;
39
+ readonly bytes: number;
40
+ readonly completedCount: number;
41
+ readonly totalCount: number;
42
+ }
43
+ /** A download failed and will be retried. */
44
+ | {
45
+ readonly _tag: "DownloadRetrying";
46
+ readonly name: string;
47
+ readonly version: string;
48
+ readonly attempt: number;
49
+ readonly reason: string;
50
+ }
51
+ /** An archive is being written. */
52
+ | { readonly _tag: "ArchiveStarted"; readonly path: string; readonly entryCount: number }
53
+ /** An archive finished. */
54
+ | { readonly _tag: "ArchiveCompleted"; readonly path: string; readonly bytes: number }
55
+ /** Something noteworthy but non-fatal, e.g. a deprecated package. */
56
+ | { readonly _tag: "Warning"; readonly message: string };
57
+
58
+ export declare namespace Progress {
59
+ /** The reporting sink. */
60
+ export type Service = {
61
+ readonly emit: (event: ProgressEvent) => Effect.Effect<void>;
62
+ };
63
+ }
64
+
65
+ /** Service tag for progress reporting. */
66
+ export class Progress extends Context.Service<Progress, Progress.Service>()(
67
+ "@packall/core/Progress",
68
+ ) {}
69
+
70
+ /** Emits an event to whichever reporter is installed. */
71
+ export const emit = (event: ProgressEvent): Effect.Effect<void, never, Progress> =>
72
+ Effect.gen(function* () {
73
+ const progress = yield* Progress;
74
+ yield* progress.emit(event);
75
+ });
76
+
77
+ /**
78
+ * Discards every event.
79
+ *
80
+ * The default for library consumers and for tests that do not care about
81
+ * progress — silence should never require ceremony.
82
+ */
83
+ export const layerSilent: Layer.Layer<Progress> = Layer.succeed(Progress)({
84
+ emit: () => Effect.void,
85
+ });
86
+
87
+ /** Sends every event to a callback. Used by the CLI renderer. */
88
+ export const layerCallback = (onEvent: (event: ProgressEvent) => void): Layer.Layer<Progress> =>
89
+ Layer.succeed(Progress)({
90
+ emit: (event) => Effect.sync(() => onEvent(event)),
91
+ });
92
+
93
+ /**
94
+ * Accumulates every event into a `Ref`, for assertions.
95
+ *
96
+ * Returned as `{ layer, events }` so a test can provide the layer and then read
97
+ * the transcript afterwards.
98
+ */
99
+ export const makeCollector = Effect.gen(function* () {
100
+ const ref = yield* Ref.make<ReadonlyArray<ProgressEvent>>([]);
101
+ const layer = Layer.succeed(Progress)({
102
+ emit: (event: ProgressEvent) => Ref.update(ref, (events) => [...events, event]),
103
+ });
104
+ return { layer, events: Ref.get(ref) } as const;
105
+ });