@packall/core 0.0.1 → 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 +64 -0
- package/dist/archive.d.ts.map +1 -0
- package/dist/{Bundle.d.ts → bundle.d.ts} +56 -38
- 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} +9 -8
- 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 +46 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2121 -1060
- 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 +80 -0
- package/dist/integrity.d.ts.map +1 -0
- package/dist/layout.d.ts +87 -0
- package/dist/layout.d.ts.map +1 -0
- 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/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 → options.d.ts} +12 -30
- package/dist/options.d.ts.map +1 -0
- package/dist/{Platform.d.ts → platform.d.ts} +14 -19
- 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 +13 -11
- package/src/archive.ts +95 -0
- package/src/bundle.ts +736 -0
- package/src/dependency-range.ts +121 -0
- package/src/download.ts +155 -0
- 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 +300 -0
- package/src/index.ts +146 -35
- package/src/input-file.ts +324 -0
- package/src/integrity.ts +165 -0
- package/src/layout.ts +137 -0
- package/src/locked-resolve.ts +471 -0
- package/src/lockfile/bun.ts +214 -0
- package/src/lockfile/detect.ts +80 -0
- package/src/lockfile/json.ts +131 -0
- package/src/lockfile/names.ts +20 -0
- package/src/lockfile/npm.ts +328 -0
- package/src/lockfile/parse.ts +56 -0
- package/src/lockfile/pnpm.ts +230 -0
- package/src/lockfile/tree-builder.ts +166 -0
- package/src/lockfile/types.ts +74 -0
- package/src/manifest.ts +129 -0
- package/src/node/archiver.ts +85 -0
- package/src/node/index.ts +13 -0
- package/src/options.ts +103 -0
- package/src/platform.ts +178 -0
- package/src/progress.ts +105 -0
- package/src/registry.ts +116 -0
- package/src/resolve.ts +560 -0
- package/src/schemas/lenient.ts +130 -0
- package/src/schemas/package-json.ts +28 -0
- package/src/spec.ts +182 -0
- package/src/types/value-of.ts +8 -0
- package/src/utils/is-record.ts +8 -0
- package/dist/Archive.d.ts +0 -32
- 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 +0 -48
- package/dist/Integrity.d.ts.map +0 -1
- package/dist/Layout.d.ts +0 -53
- package/dist/Layout.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/Archive.ts +0 -92
- package/src/Bundle.ts +0 -570
- package/src/DependencyRange.ts +0 -115
- package/src/Download.ts +0 -132
- package/src/Errors.ts +0 -216
- package/src/InputFile.ts +0 -229
- package/src/Integrity.ts +0 -122
- package/src/Layout.ts +0 -103
- package/src/Manifest.ts +0 -133
- package/src/Options.ts +0 -123
- package/src/Platform.ts +0 -182
- package/src/Progress.ts +0 -106
- package/src/Registry.ts +0 -121
- package/src/Resolve.ts +0 -528
- package/src/Spec.ts +0 -186
package/src/platform.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform matching for optional dependencies.
|
|
3
|
+
*
|
|
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.
|
|
6
|
+
*
|
|
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.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** A target to keep, e.g. `linux-x64`, `linux-x64-musl`, or just `linux`. */
|
|
13
|
+
export type PlatformTarget = {
|
|
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
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Which platforms optional dependencies should be resolved for.
|
|
23
|
+
*
|
|
24
|
+
* - `All` — keep everything, regardless of `os`/`cpu`/`libc` (the default).
|
|
25
|
+
* - `Targets` — keep only packages that could install on one of these.
|
|
26
|
+
*/
|
|
27
|
+
export type PlatformFilter =
|
|
28
|
+
| { readonly _tag: "All" }
|
|
29
|
+
| { readonly _tag: "Targets"; readonly targets: ReadonlyArray<PlatformTarget> };
|
|
30
|
+
|
|
31
|
+
/** Constraints declared by a package, as they appear in its manifest. */
|
|
32
|
+
export type PlatformConstraints = {
|
|
33
|
+
readonly os?: ReadonlyArray<string> | undefined;
|
|
34
|
+
readonly cpu?: ReadonlyArray<string> | undefined;
|
|
35
|
+
readonly libc?: ReadonlyArray<string> | undefined;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const allPlatforms: PlatformFilter = { _tag: "All" };
|
|
39
|
+
|
|
40
|
+
export const platformTargets = (targets: ReadonlyArray<PlatformTarget>): PlatformFilter => ({
|
|
41
|
+
_tag: "Targets",
|
|
42
|
+
targets,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/** The machine we are currently running on. */
|
|
46
|
+
export const currentPlatform = (): PlatformTarget => ({
|
|
47
|
+
os: process.platform,
|
|
48
|
+
cpu: process.arch,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* npm's documented `os` values, plus the names people actually type.
|
|
53
|
+
*
|
|
54
|
+
* An OS on its own is a valid target, so a typo has to be caught here — the
|
|
55
|
+
* whole failure mode this tool exists to prevent is a bundle that looks fine
|
|
56
|
+
* and installs nothing. `--platform windows` silently matching no binding at
|
|
57
|
+
* all would be precisely that, so it is corrected rather than accepted.
|
|
58
|
+
*/
|
|
59
|
+
const osByAlias: Readonly<Record<string, string | undefined>> = {
|
|
60
|
+
win: "win32",
|
|
61
|
+
windows: "win32",
|
|
62
|
+
mac: "darwin",
|
|
63
|
+
macos: "darwin",
|
|
64
|
+
osx: "darwin",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const KNOWN_OS: ReadonlySet<string> = new Set([
|
|
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"]);
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Parses `linux`, `linux-x64`, `darwin-arm64`, `win32-x64`, `linux-x64-musl`,
|
|
85
|
+
* `linux-musl`.
|
|
86
|
+
*
|
|
87
|
+
* An OS on its own means every architecture for that OS, which is almost always
|
|
88
|
+
* what you want: "the Windows and Linux bindings" is a far more natural way to
|
|
89
|
+
* describe a target set than enumerating four `os-arch` pairs, and it keeps
|
|
90
|
+
* working when a package adds an arm64 build.
|
|
91
|
+
*
|
|
92
|
+
* Returns `null` rather than throwing so the CLI can report every bad value at
|
|
93
|
+
* once alongside the list of accepted forms.
|
|
94
|
+
*/
|
|
95
|
+
export const parsePlatformTarget = (input: string): PlatformTarget | 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;
|
|
103
|
+
|
|
104
|
+
const os = osByAlias[rawOs] ?? rawOs;
|
|
105
|
+
if (!KNOWN_OS.has(os)) return null;
|
|
106
|
+
|
|
107
|
+
if (second === undefined) return { os };
|
|
108
|
+
|
|
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
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!LIBC.has(third)) return null;
|
|
115
|
+
return { os, cpu: second, libc: third };
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* npm's `os`/`cpu`/`libc` fields use a list of allowed values, where a leading
|
|
120
|
+
* `!` negates. An empty or absent list means "no constraint".
|
|
121
|
+
*
|
|
122
|
+
* The semantics npm implements: if any negated entry matches, reject. Otherwise
|
|
123
|
+
* if there are any positive entries, at least one must match.
|
|
124
|
+
*/
|
|
125
|
+
const listAllows = (list: ReadonlyArray<string> | undefined, value: string): boolean => {
|
|
126
|
+
if (list === undefined || list.length === 0) return true;
|
|
127
|
+
|
|
128
|
+
let hasPositive = false;
|
|
129
|
+
let positiveMatched = false;
|
|
130
|
+
|
|
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;
|
|
135
|
+
|
|
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
|
+
}
|
|
143
|
+
|
|
144
|
+
return hasPositive ? positiveMatched : true;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/** Would this package install on this specific target? */
|
|
148
|
+
export const matchesTarget = (
|
|
149
|
+
constraints: PlatformConstraints,
|
|
150
|
+
target: PlatformTarget,
|
|
151
|
+
): boolean => {
|
|
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
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Should this package be included, given the active filter?
|
|
162
|
+
*
|
|
163
|
+
* Under `All` this is unconditionally `true` — that is the whole point.
|
|
164
|
+
*/
|
|
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
|
+
};
|
|
169
|
+
|
|
170
|
+
/** Renders a filter for the manifest and for `--dry-run` output. */
|
|
171
|
+
export const formatPlatformFilter = (filter: PlatformFilter): string => {
|
|
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
ADDED
|
@@ -0,0 +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
|
+
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
|
+
});
|
package/src/registry.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The registry seam.
|
|
3
|
+
*
|
|
4
|
+
* This is the **only** place in the engine that knows a registry exists.
|
|
5
|
+
* Everything downstream — range resolution, the dependency walk, layout,
|
|
6
|
+
* archiving — is expressed against these three operations, so swapping npm for
|
|
7
|
+
* something else means implementing this interface and nothing more.
|
|
8
|
+
*
|
|
9
|
+
* `@packall/registry-npm` provides the implementation that speaks the plain
|
|
10
|
+
* npm registry protocol, which is what Artifactory, Nexus, Verdaccio, GitHub
|
|
11
|
+
* Packages and registry.npmjs.org all serve.
|
|
12
|
+
*/
|
|
13
|
+
import * as Context from "effect/Context";
|
|
14
|
+
import type * as Effect from "effect/Effect";
|
|
15
|
+
|
|
16
|
+
import type { BundlerError } from "./errors.js";
|
|
17
|
+
|
|
18
|
+
/** Where the tarball lives and how to prove it arrived intact. */
|
|
19
|
+
export type PackageDist = {
|
|
20
|
+
readonly tarball: string;
|
|
21
|
+
/** SRI string, e.g. `sha512-...`. Preferred over `shasum`. */
|
|
22
|
+
readonly integrity?: string | undefined;
|
|
23
|
+
/** Legacy hex sha1. Still the only checksum on very old versions. */
|
|
24
|
+
readonly shasum?: string | undefined;
|
|
25
|
+
readonly unpackedSize?: number | undefined;
|
|
26
|
+
readonly fileCount?: number | undefined;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A single published version, as the registry describes it.
|
|
31
|
+
*
|
|
32
|
+
* This is a narrowed view of a package.json — only the fields that affect what
|
|
33
|
+
* has to be downloaded. Unknown fields are ignored rather than rejected,
|
|
34
|
+
* because registries add metadata over time and a bundler that breaks on new
|
|
35
|
+
* metadata is worse than useless.
|
|
36
|
+
*/
|
|
37
|
+
export type PackageManifest = {
|
|
38
|
+
readonly name: string;
|
|
39
|
+
readonly version: string;
|
|
40
|
+
|
|
41
|
+
readonly dependencies?: Readonly<Record<string, string>> | undefined;
|
|
42
|
+
readonly optionalDependencies?: Readonly<Record<string, string>> | undefined;
|
|
43
|
+
readonly peerDependencies?: Readonly<Record<string, string>> | undefined;
|
|
44
|
+
readonly peerDependenciesMeta?:
|
|
45
|
+
| Readonly<Record<string, { readonly optional?: boolean | undefined }>>
|
|
46
|
+
| undefined;
|
|
47
|
+
readonly devDependencies?: Readonly<Record<string, string>> | undefined;
|
|
48
|
+
readonly bundleDependencies?: ReadonlyArray<string> | undefined;
|
|
49
|
+
|
|
50
|
+
/** Platform constraints, used when `--platform` narrows optional deps. */
|
|
51
|
+
readonly os?: ReadonlyArray<string> | undefined;
|
|
52
|
+
readonly cpu?: ReadonlyArray<string> | undefined;
|
|
53
|
+
readonly libc?: ReadonlyArray<string> | undefined;
|
|
54
|
+
|
|
55
|
+
readonly deprecated?: string | undefined;
|
|
56
|
+
|
|
57
|
+
readonly dist: PackageDist;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** Everything the registry knows about one package. */
|
|
61
|
+
export type Packument = {
|
|
62
|
+
readonly name: string;
|
|
63
|
+
/** `{ latest: "1.2.3", next: "2.0.0-beta.1" }` */
|
|
64
|
+
readonly distTags: Readonly<Record<string, string>>;
|
|
65
|
+
/** Every published version, keyed by version string. */
|
|
66
|
+
readonly versions: Readonly<Record<string, PackageManifest>>;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export declare namespace Registry {
|
|
70
|
+
/** The set of operations a registry backend has to provide. */
|
|
71
|
+
export type Service = {
|
|
72
|
+
/** Short identifier for the manifest and for error messages, e.g. `"npm"`. */
|
|
73
|
+
readonly kind: string;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The registry URL that will actually be used for a given package, after
|
|
77
|
+
* per-scope configuration is applied. Used for reporting only.
|
|
78
|
+
*/
|
|
79
|
+
readonly registryFor: (packageName: string) => string;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Cheap liveness check, run once before any real work.
|
|
83
|
+
*
|
|
84
|
+
* This is what turns "hangs forever behind a dead VPN" into a five-second
|
|
85
|
+
* error naming the host we could not reach.
|
|
86
|
+
*/
|
|
87
|
+
readonly preflight: Effect.Effect<void, BundlerError>;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Full metadata for a package. Implementations are expected to memoise:
|
|
91
|
+
* the dependency walk asks for the same popular packages repeatedly.
|
|
92
|
+
*/
|
|
93
|
+
readonly packument: (packageName: string) => Effect.Effect<Packument, BundlerError>;
|
|
94
|
+
|
|
95
|
+
/** Fetches the tarball bytes for one published version. */
|
|
96
|
+
readonly download: (manifest: PackageManifest) => Effect.Effect<Uint8Array, BundlerError>;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Service tag for the registry backend.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* import { Effect } from "effect"
|
|
106
|
+
* import { Registry } from "@packall/core"
|
|
107
|
+
*
|
|
108
|
+
* const program = Effect.gen(function*() {
|
|
109
|
+
* const registry = yield* Registry
|
|
110
|
+
* return yield* registry.packument("lodash")
|
|
111
|
+
* })
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export class Registry extends Context.Service<Registry, Registry.Service>()(
|
|
115
|
+
"@packall/core/Registry",
|
|
116
|
+
) {}
|