@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
@@ -1,115 +1,121 @@
1
- /**
2
- * Classification of the right-hand side of a dependency entry.
3
- *
4
- * `"dependencies": { "x": "^1.0.0" }` is the easy case. Real package.json files
5
- * also contain aliases, git URLs, tarball URLs, `file:` links and
6
- * `workspace:` protocols — none of which can be fetched from a registry.
7
- *
8
- * Rather than crashing or silently producing a bundle that will not install,
9
- * we classify each edge and let the caller report precisely what was skipped
10
- * and why.
11
- */
12
- import semver from "semver"
13
- import type { Selector } from "./spec.js"
14
-
15
- /** What a dependency edge points at. */
16
- export type DependencyTarget =
17
- /** A normal registry dependency. `name` may differ from the key under an alias. */
18
- | {
19
- readonly _tag: "Registry"
20
- readonly name: string
21
- readonly selector: Selector
22
- /** Set when the edge is an `npm:` alias, e.g. `"lodash4": "npm:lodash@^4"`. */
23
- readonly aliasOf?: string | undefined
24
- }
25
- /** Something we cannot fetch from a registry. */
26
- | { readonly _tag: "Unsupported"; readonly name: string; readonly raw: string; readonly reason: string }
27
-
28
- const UNSUPPORTED_PROTOCOLS: ReadonlyArray<readonly [string, string]> = [
29
- ["file:", "local file path"],
30
- ["link:", "local link"],
31
- ["workspace:", "workspace protocol"],
32
- ["portal:", "portal protocol"],
33
- ["patch:", "patch protocol"],
34
- ["git:", "git dependency"],
35
- ["git+", "git dependency"],
36
- ["github:", "GitHub shorthand"],
37
- ["gitlab:", "GitLab shorthand"],
38
- ["bitbucket:", "Bitbucket shorthand"],
39
- ["http:", "remote tarball URL"],
40
- ["https:", "remote tarball URL"]
41
- ]
42
-
43
- /**
44
- * Classifies one `name -> range` entry.
45
- *
46
- * An empty range, `*`, and `latest` all mean "any published version"; npm
47
- * treats them interchangeably and so do we.
48
- */
49
- export const parseDependencyTarget = (name: string, raw: string): DependencyTarget => {
50
- const text = raw.trim()
51
-
52
- if (text.length === 0 || text === "*" || text === "x" || text === "latest") {
53
- return { _tag: "Registry", name, selector: { _tag: "Range", range: "*" } }
54
- }
55
-
56
- if (text.startsWith("npm:")) {
57
- return parseAlias(name, text)
58
- }
59
-
60
- for (const [prefix, reason] of UNSUPPORTED_PROTOCOLS) {
61
- if (text.startsWith(prefix)) {
62
- return { _tag: "Unsupported", name, raw: text, reason }
63
- }
64
- }
65
-
66
- // Bare `owner/repo` is GitHub shorthand, but only when it is not a valid
67
- // range — `>=1.0.0/2` is not a thing, so this ordering is safe.
68
- if (semver.validRange(text, { loose: true }) === null && /^[\w.-]+\/[\w.-]+/.test(text)) {
69
- return { _tag: "Unsupported", name, raw: text, reason: "git shorthand" }
70
- }
71
-
72
- const exact = semver.valid(text, { loose: true })
73
- if (exact !== null) {
74
- return { _tag: "Registry", name, selector: { _tag: "Exact", version: exact } }
75
- }
76
-
77
- if (semver.validRange(text, { loose: true }) !== null) {
78
- return { _tag: "Registry", name, selector: { _tag: "Range", range: text } }
79
- }
80
-
81
- // Whatever is left is most plausibly a dist-tag published by a private
82
- // registry, e.g. `"@acme/sdk": "stable"`.
83
- if (/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(text)) {
84
- return { _tag: "Registry", name, selector: { _tag: "Tag", tag: text } }
85
- }
86
-
87
- return { _tag: "Unsupported", name, raw: text, reason: "unrecognised version specifier" }
88
- }
89
-
90
- /**
91
- * Parses `npm:<name>[@<range>]`.
92
- *
93
- * The nested name may itself be scoped, so the `@` split has the same
94
- * "not at index 0" caveat as top-level spec parsing.
95
- */
96
- const parseAlias = (key: string, text: string): DependencyTarget => {
97
- const body = text.slice("npm:".length)
98
- if (body.length === 0) {
99
- return { _tag: "Unsupported", name: key, raw: text, reason: "empty npm: alias" }
100
- }
101
-
102
- const at = body.lastIndexOf("@")
103
- const target = at > 0 ? body.slice(0, at) : body
104
- const rangeText = at > 0 ? body.slice(at + 1) : ""
105
-
106
- if (target.length === 0) {
107
- return { _tag: "Unsupported", name: key, raw: text, reason: "empty npm: alias target" }
108
- }
109
-
110
- const inner = parseDependencyTarget(target, rangeText)
111
- if (inner._tag === "Unsupported") {
112
- return { ...inner, name: key, raw: text }
113
- }
114
- return { _tag: "Registry", name: target, selector: inner.selector, aliasOf: key }
115
- }
1
+ /**
2
+ * Classification of the right-hand side of a dependency entry.
3
+ *
4
+ * `"dependencies": { "x": "^1.0.0" }` is the easy case. Real package.json files
5
+ * also contain aliases, git URLs, tarball URLs, `file:` links and
6
+ * `workspace:` protocols — none of which can be fetched from a registry.
7
+ *
8
+ * Rather than crashing or silently producing a bundle that will not install,
9
+ * we classify each edge and let the caller report precisely what was skipped
10
+ * and why.
11
+ */
12
+ import semver from "semver";
13
+
14
+ import type { Selector } from "./spec.js";
15
+
16
+ /** What a dependency edge points at. */
17
+ export type DependencyTarget =
18
+ /** A normal registry dependency. `name` may differ from the key under an alias. */
19
+ | {
20
+ readonly _tag: "Registry";
21
+ readonly name: string;
22
+ readonly selector: Selector;
23
+ /** Set when the edge is an `npm:` alias, e.g. `"lodash4": "npm:lodash@^4"`. */
24
+ readonly aliasOf?: string | undefined;
25
+ }
26
+ /** Something we cannot fetch from a registry. */
27
+ | {
28
+ readonly _tag: "Unsupported";
29
+ readonly name: string;
30
+ readonly raw: string;
31
+ readonly reason: string;
32
+ };
33
+
34
+ const UNSUPPORTED_PROTOCOLS: ReadonlyArray<readonly [string, string]> = [
35
+ ["file:", "local file path"],
36
+ ["link:", "local link"],
37
+ ["workspace:", "workspace protocol"],
38
+ ["portal:", "portal protocol"],
39
+ ["patch:", "patch protocol"],
40
+ ["git:", "git dependency"],
41
+ ["git+", "git dependency"],
42
+ ["github:", "GitHub shorthand"],
43
+ ["gitlab:", "GitLab shorthand"],
44
+ ["bitbucket:", "Bitbucket shorthand"],
45
+ ["http:", "remote tarball URL"],
46
+ ["https:", "remote tarball URL"],
47
+ ];
48
+
49
+ /**
50
+ * Classifies one `name -> range` entry.
51
+ *
52
+ * An empty range, `*`, and `latest` all mean "any published version"; npm
53
+ * treats them interchangeably and so do we.
54
+ */
55
+ export const parseDependencyTarget = (name: string, raw: string): DependencyTarget => {
56
+ const text = raw.trim();
57
+
58
+ if (text.length === 0 || text === "*" || text === "x" || text === "latest") {
59
+ return { _tag: "Registry", name, selector: { _tag: "Range", range: "*" } };
60
+ }
61
+
62
+ if (text.startsWith("npm:")) {
63
+ return parseAlias(name, text);
64
+ }
65
+
66
+ for (const [prefix, reason] of UNSUPPORTED_PROTOCOLS) {
67
+ if (text.startsWith(prefix)) {
68
+ return { _tag: "Unsupported", name, raw: text, reason };
69
+ }
70
+ }
71
+
72
+ // Bare `owner/repo` is GitHub shorthand, but only when it is not a valid
73
+ // range `>=1.0.0/2` is not a thing, so this ordering is safe.
74
+ if (semver.validRange(text, { loose: true }) === null && /^[\w.-]+\/[\w.-]+/.test(text)) {
75
+ return { _tag: "Unsupported", name, raw: text, reason: "git shorthand" };
76
+ }
77
+
78
+ const exact = semver.valid(text, { loose: true });
79
+ if (exact !== null) {
80
+ return { _tag: "Registry", name, selector: { _tag: "Exact", version: exact } };
81
+ }
82
+
83
+ if (semver.validRange(text, { loose: true }) !== null) {
84
+ return { _tag: "Registry", name, selector: { _tag: "Range", range: text } };
85
+ }
86
+
87
+ // Whatever is left is most plausibly a dist-tag published by a private
88
+ // registry, e.g. `"@acme/sdk": "stable"`.
89
+ if (/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(text)) {
90
+ return { _tag: "Registry", name, selector: { _tag: "Tag", tag: text } };
91
+ }
92
+
93
+ return { _tag: "Unsupported", name, raw: text, reason: "unrecognised version specifier" };
94
+ };
95
+
96
+ /**
97
+ * Parses `npm:<name>[@<range>]`.
98
+ *
99
+ * The nested name may itself be scoped, so the `@` split has the same
100
+ * "not at index 0" caveat as top-level spec parsing.
101
+ */
102
+ const parseAlias = (key: string, text: string): DependencyTarget => {
103
+ const body = text.slice("npm:".length);
104
+ if (body.length === 0) {
105
+ return { _tag: "Unsupported", name: key, raw: text, reason: "empty npm: alias" };
106
+ }
107
+
108
+ const at = body.lastIndexOf("@");
109
+ const target = at > 0 ? body.slice(0, at) : body;
110
+ const rangeText = at > 0 ? body.slice(at + 1) : "";
111
+
112
+ if (target.length === 0) {
113
+ return { _tag: "Unsupported", name: key, raw: text, reason: "empty npm: alias target" };
114
+ }
115
+
116
+ const inner = parseDependencyTarget(target, rangeText);
117
+ if (inner._tag === "Unsupported") {
118
+ return { ...inner, name: key, raw: text };
119
+ }
120
+ return { _tag: "Registry", name: target, selector: inner.selector, aliasOf: key };
121
+ };
package/src/download.ts CHANGED
@@ -1,133 +1,155 @@
1
- /**
2
- * Fetching tarballs into the staging tree.
3
- *
4
- * Downloads run concurrently, every one is checksum-verified before it is
5
- * written, and the destination is always the scoped temp directory — nothing
6
- * lands next to the user's own files.
7
- */
8
- import * as Effect from "effect/Effect"
9
- import * as FileSystem from "effect/FileSystem"
10
- import * as Path from "effect/Path"
11
- import type { PlatformError } from "effect/PlatformError"
12
- import { Phase } from "./enums/phase.js"
13
- import type { BundlerError } from "./errors.js"
14
- import { IntegrityError, OutputError } from "./errors.js"
15
- import * as Integrity from "./integrity.js"
16
- import { packagePath } from "./layout.js"
17
- import * as Progress from "./progress.js"
18
- import { Registry } from "./registry.js"
19
- import type { ResolvedPackage } from "./resolve.js"
20
-
21
- /** What a completed download run produced. */
22
- export type DownloadReport = {
23
- /** `name@version` -> bytes written. */
24
- readonly sizes: ReadonlyMap<string, number>
25
- /** Packages the registry advertised no usable checksum for. */
26
- readonly unverified: ReadonlyArray<string>
27
- readonly totalBytes: number
28
- }
29
-
30
- /**
31
- * Downloads every package into `destDir`, laid out the way a registry serves
32
- * them.
33
- *
34
- * `verifyIntegrity` defaulting to on is deliberate: a corrupt tarball that
35
- * makes it into a corporate registry is far more expensive than a failed run.
36
- */
37
- export const downloadAll = (
38
- packages: ReadonlyArray<ResolvedPackage>,
39
- destDir: string,
40
- options: { readonly concurrency: number; readonly verifyIntegrity: boolean }
41
- ): Effect.Effect<
42
- DownloadReport,
43
- BundlerError | PlatformError,
44
- Registry | Progress.Progress | FileSystem.FileSystem | Path.Path
45
- > =>
46
- Effect.gen(function*() {
47
- const fs = yield* FileSystem.FileSystem
48
- const path = yield* Path.Path
49
- const registry = yield* Registry
50
-
51
- yield* Progress.emit({ _tag: "PhaseStarted", phase: Phase.Download, total: packages.length })
52
-
53
- let completed = 0
54
- const sizes = new Map<string, number>()
55
- const unverified: Array<string> = []
56
-
57
- const results = yield* Effect.forEach(
58
- packages,
59
- (pkg) =>
60
- Effect.gen(function*() {
61
- const key = `${pkg.name}@${pkg.version}`
62
-
63
- yield* Progress.emit({
64
- _tag: "DownloadStarted",
65
- name: pkg.name,
66
- version: pkg.version
67
- })
68
-
69
- const bytes = yield* registry.download(pkg.manifest)
70
-
71
- if (options.verifyIntegrity) {
72
- const result = Integrity.verify(bytes, pkg.manifest.dist)
73
- if (result._tag === "Mismatch") {
74
- return yield* Effect.fail(
75
- new IntegrityError(pkg.name, pkg.version, result.expected, result.actual)
76
- )
77
- }
78
- if (result._tag === "Unverifiable") {
79
- yield* Progress.emit({
80
- _tag: "Warning",
81
- message: `${key} could not be verified (${result.reason})`
82
- })
83
- }
84
- }
85
-
86
- const verifiable =
87
- (pkg.manifest.dist.integrity?.length ?? 0) > 0 ||
88
- (pkg.manifest.dist.shasum?.length ?? 0) > 0
89
-
90
- const relative = packagePath(pkg.name, pkg.version)
91
- const target = path.join(destDir, ...relative.split("/"))
92
-
93
- yield* fs.makeDirectory(path.dirname(target), { recursive: true }).pipe(
94
- Effect.mapError(
95
- (cause) =>
96
- new OutputError(path.dirname(target), "could not create directory", { cause })
97
- )
98
- )
99
- yield* fs.writeFile(target, bytes).pipe(
100
- Effect.mapError((cause) => new OutputError(target, "could not write tarball", { cause }))
101
- )
102
-
103
- // Counting here rather than inside the concurrent body would be
104
- // wrong; each fiber increments as it finishes, which is what makes
105
- // the progress counter monotonic.
106
- completed += 1
107
- yield* Progress.emit({
108
- _tag: "DownloadCompleted",
109
- name: pkg.name,
110
- version: pkg.version,
111
- bytes: bytes.byteLength,
112
- completedCount: completed,
113
- totalCount: packages.length
114
- })
115
-
116
- return { key, bytes: bytes.byteLength, verifiable } as const
117
- }),
118
- { concurrency: options.concurrency }
119
- )
120
-
121
- for (const result of results) {
122
- sizes.set(result.key, result.bytes)
123
- if (!result.verifiable) unverified.push(result.key)
124
- }
125
-
126
- yield* Progress.emit({ _tag: "PhaseCompleted", phase: Phase.Download })
127
-
128
- return {
129
- sizes,
130
- unverified,
131
- totalBytes: results.reduce((sum, result) => sum + result.bytes, 0)
132
- }
133
- })
1
+ /**
2
+ * Fetching tarballs into the staging tree.
3
+ *
4
+ * Downloads run concurrently, every one is checksum-verified before it is
5
+ * written, and the destination is always the scoped temp directory — nothing
6
+ * lands next to the user's own files.
7
+ */
8
+ import type * as Crypto from "effect/Crypto";
9
+ import * as Effect from "effect/Effect";
10
+ import * as FileSystem from "effect/FileSystem";
11
+ import * as Path from "effect/Path";
12
+ import type { PlatformError } from "effect/PlatformError";
13
+
14
+ import { Phase } from "./enums/phase.js";
15
+ import type { BundlerError } from "./errors.js";
16
+ import { IntegrityError, OutputError } from "./errors.js";
17
+ import * as Integrity from "./integrity.js";
18
+ import { packagePath } from "./layout.js";
19
+ import * as Progress from "./progress.js";
20
+ import { Registry } from "./registry.js";
21
+ import type { ResolvedPackage } from "./resolve.js";
22
+
23
+ /** What a completed download run produced. */
24
+ export type DownloadReport = {
25
+ /** `name@version` -> bytes written. */
26
+ readonly sizes: ReadonlyMap<string, number>;
27
+ /** Packages the registry advertised no usable checksum for. */
28
+ readonly unverified: ReadonlyArray<string>;
29
+ readonly totalBytes: number;
30
+ };
31
+
32
+ /**
33
+ * Downloads every package into `destDir`, laid out the way a registry serves
34
+ * them.
35
+ *
36
+ * `verifyIntegrity` defaulting to on is deliberate: a corrupt tarball that
37
+ * makes it into a corporate registry is far more expensive than a failed run.
38
+ */
39
+ export const downloadAll = (
40
+ packages: ReadonlyArray<ResolvedPackage>,
41
+ destDir: string,
42
+ options: { readonly concurrency: number; readonly verifyIntegrity: boolean },
43
+ ): Effect.Effect<
44
+ DownloadReport,
45
+ BundlerError | PlatformError,
46
+ Registry | Progress.Progress | FileSystem.FileSystem | Path.Path | Crypto.Crypto
47
+ > =>
48
+ Effect.gen(function* () {
49
+ const fs = yield* FileSystem.FileSystem;
50
+ const path = yield* Path.Path;
51
+ const registry = yield* Registry;
52
+
53
+ yield* Progress.emit({
54
+ _tag: "PhaseStarted",
55
+ phase: Phase.Download,
56
+ total: packages.length,
57
+ });
58
+
59
+ let completed = 0;
60
+ const sizes = new Map<string, number>();
61
+ const unverified: Array<string> = [];
62
+
63
+ const results = yield* Effect.forEach(
64
+ packages,
65
+ (pkg) =>
66
+ Effect.gen(function* () {
67
+ const key = `${pkg.name}@${pkg.version}`;
68
+
69
+ yield* Progress.emit({
70
+ _tag: "DownloadStarted",
71
+ name: pkg.name,
72
+ version: pkg.version,
73
+ });
74
+
75
+ const bytes = yield* registry.download(pkg.manifest);
76
+
77
+ if (options.verifyIntegrity) {
78
+ const result = yield* Integrity.verify(bytes, pkg.manifest.dist);
79
+ if (result._tag === "Mismatch") {
80
+ return yield* Effect.fail(
81
+ new IntegrityError(
82
+ pkg.name,
83
+ pkg.version,
84
+ result.expected,
85
+ result.actual,
86
+ ),
87
+ );
88
+ }
89
+ if (result._tag === "Unverifiable") {
90
+ yield* Progress.emit({
91
+ _tag: "Warning",
92
+ message: `${key} could not be verified (${result.reason})`,
93
+ });
94
+ }
95
+ }
96
+
97
+ const verifiable =
98
+ (pkg.manifest.dist.integrity?.length ?? 0) > 0 ||
99
+ (pkg.manifest.dist.shasum?.length ?? 0) > 0;
100
+
101
+ const relative = packagePath(pkg.name, pkg.version);
102
+ const target = path.join(destDir, ...relative.split("/"));
103
+
104
+ yield* fs
105
+ .makeDirectory(path.dirname(target), { recursive: true })
106
+ .pipe(
107
+ Effect.mapError(
108
+ (cause) =>
109
+ new OutputError(
110
+ path.dirname(target),
111
+ "could not create directory",
112
+ { cause },
113
+ ),
114
+ ),
115
+ );
116
+ yield* fs
117
+ .writeFile(target, bytes)
118
+ .pipe(
119
+ Effect.mapError(
120
+ (cause) =>
121
+ new OutputError(target, "could not write tarball", { cause }),
122
+ ),
123
+ );
124
+
125
+ // Counting here rather than inside the concurrent body would be
126
+ // wrong; each fiber increments as it finishes, which is what makes
127
+ // the progress counter monotonic.
128
+ completed += 1;
129
+ yield* Progress.emit({
130
+ _tag: "DownloadCompleted",
131
+ name: pkg.name,
132
+ version: pkg.version,
133
+ bytes: bytes.byteLength,
134
+ completedCount: completed,
135
+ totalCount: packages.length,
136
+ });
137
+
138
+ return { key, bytes: bytes.byteLength, verifiable } as const;
139
+ }),
140
+ { concurrency: options.concurrency },
141
+ );
142
+
143
+ for (const result of results) {
144
+ sizes.set(result.key, result.bytes);
145
+ if (!result.verifiable) unverified.push(result.key);
146
+ }
147
+
148
+ yield* Progress.emit({ _tag: "PhaseCompleted", phase: Phase.Download });
149
+
150
+ return {
151
+ sizes,
152
+ unverified,
153
+ totalBytes: results.reduce((sum, result) => sum + result.bytes, 0),
154
+ };
155
+ });
@@ -1,9 +1,9 @@
1
- import type { ValueOf } from "../types/value-of.js"
1
+ import type { ValueOf } from "../types/value-of.js";
2
2
 
3
3
  /** What a bundle run wrote to the output directory. */
4
4
  export const ArtifactKind = {
5
- Archive: "archive",
6
- Directory: "directory"
7
- } as const
5
+ Archive: "archive",
6
+ Directory: "directory",
7
+ } as const;
8
8
 
9
- export type ArtifactKind = ValueOf<typeof ArtifactKind>
9
+ export type ArtifactKind = ValueOf<typeof ArtifactKind>;
@@ -1,4 +1,4 @@
1
- import type { ValueOf } from "../types/value-of.js"
1
+ import type { ValueOf } from "../types/value-of.js";
2
2
 
3
3
  /**
4
4
  * Why one package depends on another.
@@ -9,24 +9,24 @@ import type { ValueOf } from "../types/value-of.js"
9
9
  * never installed, so it is never locked.
10
10
  */
11
11
  export const EdgeKind = {
12
- Prod: "prod",
13
- Optional: "optional",
14
- Peer: "peer"
15
- } as const
12
+ Prod: "prod",
13
+ Optional: "optional",
14
+ Peer: "peer",
15
+ } as const;
16
16
 
17
- export type EdgeKind = ValueOf<typeof EdgeKind>
17
+ export type EdgeKind = ValueOf<typeof EdgeKind>;
18
18
 
19
19
  /** The kind that only a direct dependency of the locked project can have. */
20
20
  export const DirectOnlyKind = {
21
- Dev: "dev"
22
- } as const
21
+ Dev: "dev",
22
+ } as const;
23
23
 
24
- export type DirectOnlyKind = ValueOf<typeof DirectOnlyKind>
24
+ export type DirectOnlyKind = ValueOf<typeof DirectOnlyKind>;
25
25
 
26
26
  /** Why a direct dependency of the locked project is a root. */
27
27
  export const LockedRootKind = {
28
- ...EdgeKind,
29
- ...DirectOnlyKind
30
- } as const
28
+ ...EdgeKind,
29
+ ...DirectOnlyKind,
30
+ } as const;
31
31
 
32
- export type LockedRootKind = ValueOf<typeof LockedRootKind>
32
+ export type LockedRootKind = ValueOf<typeof LockedRootKind>;
@@ -1,13 +1,13 @@
1
- import type { ValueOf } from "../types/value-of.js"
1
+ import type { ValueOf } from "../types/value-of.js";
2
2
 
3
3
  /** Which of the three shapes a `--file` input turned out to be. */
4
4
  export const InputFileKind = {
5
- /** Bundle everything a package.json depends on. */
6
- PackageJson: "package.json",
7
- /** Bundle the exact versions a lockfile pins. */
8
- Lockfile: "lockfile",
9
- /** A newline-delimited list of specs. */
10
- List: "list"
11
- } as const
5
+ /** Bundle everything a package.json depends on. */
6
+ PackageJson: "package.json",
7
+ /** Bundle the exact versions a lockfile pins. */
8
+ Lockfile: "lockfile",
9
+ /** A newline-delimited list of specs. */
10
+ List: "list",
11
+ } as const;
12
12
 
13
- export type InputFileKind = ValueOf<typeof InputFileKind>
13
+ export type InputFileKind = ValueOf<typeof InputFileKind>;