@packall/core 0.0.1 → 0.1.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/dist/{Archive.d.ts → archive.d.ts} +5 -5
- package/dist/archive.d.ts.map +1 -0
- package/dist/{Bundle.d.ts → bundle.d.ts} +51 -36
- package/dist/bundle.d.ts.map +1 -0
- package/dist/{DependencyRange.d.ts → dependency-range.d.ts} +2 -2
- package/dist/dependency-range.d.ts.map +1 -0
- package/dist/{Download.d.ts → download.d.ts} +7 -7
- package/dist/download.d.ts.map +1 -0
- package/dist/enums/artifact-kind.d.ts +8 -0
- package/dist/enums/artifact-kind.d.ts.map +1 -0
- package/dist/enums/edge-kind.d.ts +29 -0
- package/dist/enums/edge-kind.d.ts.map +1 -0
- package/dist/enums/input-file-kind.d.ts +12 -0
- package/dist/enums/input-file-kind.d.ts.map +1 -0
- package/dist/enums/layout.d.ts +23 -0
- package/dist/enums/layout.d.ts.map +1 -0
- package/dist/enums/lockfile-format.d.ts +9 -0
- package/dist/enums/lockfile-format.d.ts.map +1 -0
- package/dist/enums/phase.d.ts +11 -0
- package/dist/enums/phase.d.ts.map +1 -0
- package/dist/{Errors.d.ts → errors.d.ts} +49 -7
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +44 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2416 -1180
- package/dist/index.js.map +1 -1
- package/dist/input-file.d.ts +77 -0
- package/dist/input-file.d.ts.map +1 -0
- package/dist/{Integrity.d.ts → integrity.d.ts} +3 -3
- package/dist/integrity.d.ts.map +1 -0
- package/dist/{Layout.d.ts → layout.d.ts} +1 -1
- package/dist/{Layout.d.ts.map → layout.d.ts.map} +1 -1
- package/dist/locked-resolve.d.ts +47 -0
- package/dist/locked-resolve.d.ts.map +1 -0
- package/dist/lockfile/bun.d.ts +3 -0
- package/dist/lockfile/bun.d.ts.map +1 -0
- package/dist/lockfile/detect.d.ts +31 -0
- package/dist/lockfile/detect.d.ts.map +1 -0
- package/dist/lockfile/json.d.ts +23 -0
- package/dist/lockfile/json.d.ts.map +1 -0
- package/dist/lockfile/names.d.ts +16 -0
- package/dist/lockfile/names.d.ts.map +1 -0
- package/dist/lockfile/npm.d.ts +3 -0
- package/dist/lockfile/npm.d.ts.map +1 -0
- package/dist/lockfile/parse.d.ts +23 -0
- package/dist/lockfile/parse.d.ts.map +1 -0
- package/dist/lockfile/pnpm.d.ts +20 -0
- package/dist/lockfile/pnpm.d.ts.map +1 -0
- package/dist/lockfile/tree-builder.d.ts +70 -0
- package/dist/lockfile/tree-builder.d.ts.map +1 -0
- package/dist/lockfile/types.d.ts +70 -0
- package/dist/lockfile/types.d.ts.map +1 -0
- package/dist/{Manifest.d.ts → manifest.d.ts} +7 -15
- package/dist/manifest.d.ts.map +1 -0
- package/dist/{Options.d.ts → options.d.ts} +9 -27
- package/dist/options.d.ts.map +1 -0
- package/dist/{Platform.d.ts → platform.d.ts} +9 -9
- package/dist/platform.d.ts.map +1 -0
- package/dist/{Progress.d.ts → progress.d.ts} +10 -9
- package/dist/progress.d.ts.map +1 -0
- package/dist/{Registry.d.ts → registry.d.ts} +42 -44
- package/dist/registry.d.ts.map +1 -0
- package/dist/{Resolve.d.ts → resolve.d.ts} +25 -26
- package/dist/resolve.d.ts.map +1 -0
- package/dist/schemas/lenient.d.ts +38 -0
- package/dist/schemas/lenient.d.ts.map +1 -0
- package/dist/schemas/package-json.d.ts +44 -0
- package/dist/schemas/package-json.d.ts.map +1 -0
- package/dist/{Spec.d.ts → spec.d.ts} +4 -4
- package/dist/spec.d.ts.map +1 -0
- package/dist/types/value-of.d.ts +9 -0
- package/dist/types/value-of.d.ts.map +1 -0
- package/dist/utils/is-record.d.ts +8 -0
- package/dist/utils/is-record.d.ts.map +1 -0
- package/package.json +3 -2
- package/src/{Archive.ts → archive.ts} +92 -92
- package/src/{Bundle.ts → bundle.ts} +616 -570
- package/src/{DependencyRange.ts → dependency-range.ts} +115 -115
- package/src/{Download.ts → download.ts} +133 -132
- package/src/enums/artifact-kind.ts +9 -0
- package/src/enums/edge-kind.ts +32 -0
- package/src/enums/input-file-kind.ts +13 -0
- package/src/enums/layout.ts +25 -0
- package/src/enums/lockfile-format.ts +10 -0
- package/src/enums/phase.ts +12 -0
- package/src/{Errors.ts → errors.ts} +87 -5
- package/src/index.ts +133 -35
- package/src/{InputFile.ts → input-file.ts} +127 -44
- package/src/{Integrity.ts → integrity.ts} +121 -122
- package/src/locked-resolve.ts +461 -0
- package/src/lockfile/bun.ts +207 -0
- package/src/lockfile/detect.ts +80 -0
- package/src/lockfile/json.ts +132 -0
- package/src/lockfile/names.ts +20 -0
- package/src/lockfile/npm.ts +308 -0
- package/src/lockfile/parse.ts +56 -0
- package/src/lockfile/pnpm.ts +225 -0
- package/src/lockfile/tree-builder.ts +166 -0
- package/src/lockfile/types.ts +74 -0
- package/src/{Manifest.ts → manifest.ts} +133 -133
- package/src/{Options.ts → options.ts} +7 -27
- package/src/{Platform.ts → platform.ts} +23 -23
- package/src/{Progress.ts → progress.ts} +106 -106
- package/src/{Registry.ts → registry.ts} +115 -121
- package/src/{Resolve.ts → resolve.ts} +537 -528
- package/src/schemas/lenient.ts +123 -0
- package/src/schemas/package-json.ts +27 -0
- package/src/{Spec.ts → spec.ts} +11 -19
- package/src/types/value-of.ts +8 -0
- package/src/utils/is-record.ts +8 -0
- package/dist/Archive.d.ts.map +0 -1
- package/dist/Bundle.d.ts.map +0 -1
- package/dist/DependencyRange.d.ts.map +0 -1
- package/dist/Download.d.ts.map +0 -1
- package/dist/Errors.d.ts.map +0 -1
- package/dist/InputFile.d.ts +0 -46
- package/dist/InputFile.d.ts.map +0 -1
- package/dist/Integrity.d.ts.map +0 -1
- package/dist/Manifest.d.ts.map +0 -1
- package/dist/Options.d.ts.map +0 -1
- package/dist/Platform.d.ts.map +0 -1
- package/dist/Progress.d.ts.map +0 -1
- package/dist/Registry.d.ts.map +0 -1
- package/dist/Resolve.d.ts.map +0 -1
- package/dist/Spec.d.ts.map +0 -1
- /package/src/{Layout.ts → layout.ts} +0 -0
|
@@ -1,115 +1,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
|
-
import type { Selector } from "./
|
|
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
|
+
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,132 +1,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 * 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
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
readonly
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
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
|
-
(pkg.manifest.dist.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ValueOf } from "../types/value-of.js"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Why one package depends on another.
|
|
5
|
+
*
|
|
6
|
+
* `EdgeKind` and `LockedRootKind` are one concept in two parts, which is why
|
|
7
|
+
* they share a file: `Dev` exists only at the top level, because lockfiles
|
|
8
|
+
* record dev dependencies for the project alone — a transitive devDependency is
|
|
9
|
+
* never installed, so it is never locked.
|
|
10
|
+
*/
|
|
11
|
+
export const EdgeKind = {
|
|
12
|
+
Prod: "prod",
|
|
13
|
+
Optional: "optional",
|
|
14
|
+
Peer: "peer"
|
|
15
|
+
} as const
|
|
16
|
+
|
|
17
|
+
export type EdgeKind = ValueOf<typeof EdgeKind>
|
|
18
|
+
|
|
19
|
+
/** The kind that only a direct dependency of the locked project can have. */
|
|
20
|
+
export const DirectOnlyKind = {
|
|
21
|
+
Dev: "dev"
|
|
22
|
+
} as const
|
|
23
|
+
|
|
24
|
+
export type DirectOnlyKind = ValueOf<typeof DirectOnlyKind>
|
|
25
|
+
|
|
26
|
+
/** Why a direct dependency of the locked project is a root. */
|
|
27
|
+
export const LockedRootKind = {
|
|
28
|
+
...EdgeKind,
|
|
29
|
+
...DirectOnlyKind
|
|
30
|
+
} as const
|
|
31
|
+
|
|
32
|
+
export type LockedRootKind = ValueOf<typeof LockedRootKind>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ValueOf } from "../types/value-of.js"
|
|
2
|
+
|
|
3
|
+
/** Which of the three shapes a `--file` input turned out to be. */
|
|
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
|
|
12
|
+
|
|
13
|
+
export type InputFileKind = ValueOf<typeof InputFileKind>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ValueOf } from "../types/value-of.js"
|
|
2
|
+
|
|
3
|
+
/** How the resulting tarball(s) are shaped. */
|
|
4
|
+
export const Layout = {
|
|
5
|
+
/**
|
|
6
|
+
* One tarball per requested spec, each carrying its own complete dependency
|
|
7
|
+
* closure. Shared dependencies repeat across tarballs, so the total is
|
|
8
|
+
* larger — but each one imports, rolls back and hands off independently.
|
|
9
|
+
*/
|
|
10
|
+
PerSpec: "per-spec",
|
|
11
|
+
/**
|
|
12
|
+
* One tarball for everything, with the union of all closures deduplicated.
|
|
13
|
+
* Smallest output and a single import step; all-or-nothing to roll back.
|
|
14
|
+
*/
|
|
15
|
+
Single: "single",
|
|
16
|
+
/**
|
|
17
|
+
* No archive at all — just the npm-layout directory tree, ready for
|
|
18
|
+
* `jf rt upload` or rsync.
|
|
19
|
+
*/
|
|
20
|
+
Dir: "dir"
|
|
21
|
+
} as const
|
|
22
|
+
|
|
23
|
+
export type Layout = ValueOf<typeof Layout>
|
|
24
|
+
|
|
25
|
+
export const Layouts: ReadonlyArray<Layout> = Object.values(Layout)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ValueOf } from "../types/value-of.js"
|
|
2
|
+
|
|
3
|
+
/** The phases a bundle run moves through, in order. */
|
|
4
|
+
export const Phase = {
|
|
5
|
+
Preflight: "preflight",
|
|
6
|
+
Resolve: "resolve",
|
|
7
|
+
Download: "download",
|
|
8
|
+
Archive: "archive",
|
|
9
|
+
Done: "done"
|
|
10
|
+
} as const
|
|
11
|
+
|
|
12
|
+
export type Phase = ValueOf<typeof Phase>
|