@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.
- package/LICENSE +21 -0
- package/README.md +58 -0
- package/dist/archive-6sqTn1C4.js +274 -0
- package/dist/archive-6sqTn1C4.js.map +1 -0
- package/dist/archive.d.ts +47 -15
- package/dist/archive.d.ts.map +1 -1
- package/dist/bundle.d.ts +6 -3
- package/dist/bundle.d.ts.map +1 -1
- package/dist/dependency-range.d.ts.map +1 -1
- package/dist/download.d.ts +2 -1
- package/dist/download.d.ts.map +1 -1
- package/dist/enums/artifact-kind.d.ts.map +1 -1
- package/dist/enums/edge-kind.d.ts.map +1 -1
- package/dist/enums/input-file-kind.d.ts.map +1 -1
- package/dist/enums/layout.d.ts.map +1 -1
- package/dist/enums/lockfile-format.d.ts.map +1 -1
- package/dist/enums/phase.d.ts.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +12 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +169 -344
- package/dist/index.js.map +1 -1
- package/dist/input-file.d.ts.map +1 -1
- package/dist/integrity.d.ts +37 -5
- package/dist/integrity.d.ts.map +1 -1
- package/dist/layout.d.ts +41 -7
- package/dist/layout.d.ts.map +1 -1
- package/dist/locked-resolve.d.ts.map +1 -1
- package/dist/lockfile/bun.d.ts.map +1 -1
- package/dist/lockfile/detect.d.ts.map +1 -1
- package/dist/lockfile/json.d.ts.map +1 -1
- package/dist/lockfile/names.d.ts.map +1 -1
- package/dist/lockfile/npm.d.ts.map +1 -1
- package/dist/lockfile/parse.d.ts.map +1 -1
- package/dist/lockfile/pnpm.d.ts.map +1 -1
- package/dist/lockfile/tree-builder.d.ts.map +1 -1
- package/dist/lockfile/types.d.ts.map +1 -1
- package/dist/manifest.d.ts.map +1 -1
- package/dist/node/archiver.d.ts +5 -0
- package/dist/node/archiver.d.ts.map +1 -0
- package/dist/node/index.d.ts +13 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +55 -0
- package/dist/node/index.js.map +1 -0
- package/dist/options.d.ts +3 -3
- package/dist/options.d.ts.map +1 -1
- package/dist/platform.d.ts +5 -10
- package/dist/platform.d.ts.map +1 -1
- package/dist/progress.d.ts.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/resolve.d.ts.map +1 -1
- package/dist/schemas/lenient.d.ts.map +1 -1
- package/dist/schemas/package-json.d.ts.map +1 -1
- package/dist/spec.d.ts.map +1 -1
- package/dist/types/value-of.d.ts.map +1 -1
- package/dist/utils/is-record.d.ts.map +1 -1
- package/package.json +11 -10
- package/src/archive.ts +95 -92
- package/src/bundle.ts +736 -616
- package/src/dependency-range.ts +121 -115
- package/src/download.ts +155 -133
- package/src/enums/artifact-kind.ts +5 -5
- package/src/enums/edge-kind.ts +13 -13
- package/src/enums/input-file-kind.ts +9 -9
- package/src/enums/layout.ts +20 -20
- package/src/enums/lockfile-format.ts +6 -6
- package/src/enums/phase.ts +8 -8
- package/src/errors.ts +200 -198
- package/src/index.ts +126 -113
- package/src/input-file.ts +246 -234
- package/src/integrity.ts +165 -121
- package/src/layout.ts +63 -29
- package/src/locked-resolve.ts +471 -461
- package/src/lockfile/bun.ts +214 -207
- package/src/lockfile/detect.ts +80 -80
- package/src/lockfile/json.ts +91 -92
- package/src/lockfile/names.ts +9 -9
- package/src/lockfile/npm.ts +328 -308
- package/src/lockfile/parse.ts +32 -32
- package/src/lockfile/pnpm.ts +230 -225
- package/src/lockfile/tree-builder.ts +116 -116
- package/src/lockfile/types.ts +52 -52
- package/src/manifest.ts +129 -133
- package/src/node/archiver.ts +85 -0
- package/src/node/index.ts +13 -0
- package/src/options.ts +84 -84
- package/src/platform.ts +97 -101
- package/src/progress.ts +105 -106
- package/src/registry.ts +116 -115
- package/src/resolve.ts +560 -537
- package/src/schemas/lenient.ts +67 -60
- package/src/schemas/package-json.ts +11 -10
- package/src/spec.ts +123 -119
- package/src/types/value-of.ts +1 -1
- 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
|
-
*
|
|
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
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
106
|
-
|
|
104
|
+
const os = osByAlias[rawOs] ?? rawOs;
|
|
105
|
+
if (!KNOWN_OS.has(os)) return null;
|
|
107
106
|
|
|
108
|
-
|
|
107
|
+
if (second === undefined) return { os };
|
|
109
108
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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
|
-
|
|
126
|
+
if (list === undefined || list.length === 0) return true;
|
|
128
127
|
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
let hasPositive = false;
|
|
129
|
+
let positiveMatched = false;
|
|
131
130
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
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
|
-
|
|
151
|
-
|
|
149
|
+
constraints: PlatformConstraints,
|
|
150
|
+
target: PlatformTarget,
|
|
152
151
|
): boolean => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
+
});
|