@packall/core 0.5.2 → 0.5.3
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-D1AFjtiX.js → archive-HuF2fp2A.js} +10 -2
- package/dist/archive-HuF2fp2A.js.map +1 -0
- package/dist/archive.d.ts +9 -18
- package/dist/archive.d.ts.map +1 -1
- package/dist/bundle.d.ts +0 -18
- package/dist/bundle.d.ts.map +1 -1
- package/dist/dependency-range.d.ts.map +1 -1
- package/dist/download.d.ts +0 -7
- package/dist/download.d.ts.map +1 -1
- package/dist/errors.d.ts +0 -12
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +0 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -36
- package/dist/index.js.map +1 -1
- package/dist/input-file.d.ts +0 -15
- package/dist/input-file.d.ts.map +1 -1
- package/dist/integrity.d.ts +0 -14
- package/dist/integrity.d.ts.map +1 -1
- package/dist/layout.d.ts +22 -57
- package/dist/layout.d.ts.map +1 -1
- package/dist/locked-resolve.d.ts +0 -19
- package/dist/locked-resolve.d.ts.map +1 -1
- package/dist/lockfile/bun.d.ts.map +1 -1
- package/dist/lockfile/detect.d.ts +0 -7
- package/dist/lockfile/detect.d.ts.map +1 -1
- package/dist/lockfile/json.d.ts.map +1 -1
- package/dist/lockfile/npm.d.ts.map +1 -1
- package/dist/lockfile/parse.d.ts +0 -14
- 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 +0 -7
- package/dist/lockfile/types.d.ts.map +1 -1
- package/dist/manifest.d.ts.map +1 -1
- package/dist/node/archiver.d.ts.map +1 -1
- package/dist/node/index.d.ts +0 -11
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +1 -1
- package/dist/node/index.js.map +1 -1
- package/dist/node/platform.d.ts +0 -9
- package/dist/node/platform.d.ts.map +1 -1
- package/dist/options.d.ts +0 -7
- package/dist/options.d.ts.map +1 -1
- package/dist/platform.d.ts +0 -10
- package/dist/platform.d.ts.map +1 -1
- package/dist/progress.d.ts +0 -8
- package/dist/progress.d.ts.map +1 -1
- package/dist/registry.d.ts +5 -12
- package/dist/registry.d.ts.map +1 -1
- package/dist/resolve.d.ts +0 -18
- package/dist/resolve.d.ts.map +1 -1
- package/dist/schemas/lenient.d.ts.map +1 -1
- package/dist/schemas/package-json.d.ts +3 -12
- package/dist/schemas/package-json.d.ts.map +1 -1
- package/dist/spec.d.ts.map +1 -1
- package/package.json +12 -2
- package/src/archive.ts +9 -18
- package/src/bundle.ts +3 -18
- package/src/dependency-range.ts +0 -11
- package/src/download.ts +0 -7
- package/src/errors.ts +3 -13
- package/src/index.ts +2 -14
- package/src/input-file.ts +0 -15
- package/src/integrity.ts +0 -14
- package/src/layout.ts +22 -58
- package/src/locked-resolve.ts +5 -19
- package/src/lockfile/bun.ts +0 -8
- package/src/lockfile/detect.ts +0 -7
- package/src/lockfile/json.ts +0 -9
- package/src/lockfile/npm.ts +0 -8
- package/src/lockfile/parse.ts +0 -14
- package/src/lockfile/pnpm.ts +0 -7
- package/src/lockfile/tree-builder.ts +0 -6
- package/src/lockfile/types.ts +0 -7
- package/src/manifest.ts +0 -4
- package/src/node/archiver.ts +4 -12
- package/src/node/index.ts +0 -12
- package/src/node/platform.ts +0 -10
- package/src/options.ts +0 -7
- package/src/platform.ts +0 -11
- package/src/progress.ts +0 -8
- package/src/registry.ts +5 -12
- package/src/resolve.ts +4 -18
- package/src/schemas/lenient.ts +0 -15
- package/src/schemas/package-json.ts +4 -13
- package/src/spec.ts +0 -7
- package/dist/archive-D1AFjtiX.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@packall/core",
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"description": "Registry-agnostic engine
|
|
3
|
+
"version": "0.5.3",
|
|
4
|
+
"description": "Registry-agnostic engine that resolves, downloads, verifies and packages an npm dependency closure for offline import.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"airgap",
|
|
7
|
+
"bundle",
|
|
8
|
+
"dependencies",
|
|
9
|
+
"effect",
|
|
10
|
+
"integrity",
|
|
11
|
+
"npm",
|
|
12
|
+
"offline",
|
|
13
|
+
"tarball"
|
|
14
|
+
],
|
|
5
15
|
"homepage": "https://packall.vercel.app",
|
|
6
16
|
"bugs": {
|
|
7
17
|
"url": "https://gitlab.com/yelev/packall/-/issues"
|
package/src/archive.ts
CHANGED
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Turning a staged directory tree into a gzipped tarball.
|
|
3
|
-
*
|
|
4
|
-
* A service rather than a function, because packing is the one step that has
|
|
5
|
-
* to be implemented differently per platform. Everywhere else the engine
|
|
6
|
-
* reaches the disk through `effect/FileSystem`, which is what lets an in-memory
|
|
7
|
-
* filesystem carry the whole pipeline — but a tar writer is not filesystem
|
|
8
|
-
* access, it is a format, and `node-tar` reads the paths it is given with
|
|
9
|
-
* Node's own `fs` rather than through the injected service. Left as a direct
|
|
10
|
-
* call, packing would quietly bypass the abstraction the rest of the engine
|
|
11
|
-
* depends on, which stays invisible until something other than Node provides
|
|
12
|
-
* the filesystem.
|
|
13
|
-
*
|
|
14
|
-
* The implementations live outside this module — `@packall/core/node` for
|
|
15
|
-
* Node, a Web Streams writer in the browser — so nothing platform-specific is
|
|
16
|
-
* reachable from the engine's own graph.
|
|
17
|
-
*/
|
|
18
1
|
import * as Context from "effect/Context";
|
|
19
2
|
import * as Effect from "effect/Effect";
|
|
20
3
|
import type * as FileSystem from "effect/FileSystem";
|
|
@@ -56,7 +39,15 @@ export declare namespace Archiver {
|
|
|
56
39
|
};
|
|
57
40
|
}
|
|
58
41
|
|
|
59
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Service tag for archive creation.
|
|
44
|
+
*
|
|
45
|
+
* Packing is a service rather than a plain function because it is the one step
|
|
46
|
+
* that cannot go through `effect/FileSystem`: a tar writer reads the paths it
|
|
47
|
+
* is handed with the host's own file APIs, so a direct call would bypass the
|
|
48
|
+
* injected filesystem the rest of the engine runs on. Provide `layerArchiver`
|
|
49
|
+
* from `@packall/core/node`, or an implementation of your own.
|
|
50
|
+
*/
|
|
60
51
|
export class Archiver extends Context.Service<Archiver, Archiver.Service>()(
|
|
61
52
|
"@packall/core/Archiver",
|
|
62
53
|
) {}
|
package/src/bundle.ts
CHANGED
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* End-to-end orchestration: specs in, tarballs out.
|
|
3
|
-
*
|
|
4
|
-
* Two properties this module is responsible for — see `notes/requirements.md`
|
|
5
|
-
* § FR-5 and § NFR-9 for why they are requirements, and the mechanisms below
|
|
6
|
-
* for why the code reads as it does:
|
|
7
|
-
*
|
|
8
|
-
* 1. **Nothing is written outside the output directory.** Every intermediate
|
|
9
|
-
* file lives in a scoped temp directory obtained from
|
|
10
|
-
* `makeTempDirectoryScoped`, so the scope closing — on success, on failure,
|
|
11
|
-
* or on Ctrl-C — takes the whole staging tree with it. The working
|
|
12
|
-
* directory is never touched.
|
|
13
|
-
*
|
|
14
|
-
* 2. **Each package is downloaded exactly once**, no matter how many bundles
|
|
15
|
-
* it ends up in. Per-spec archives are assembled by hard-linking out of one
|
|
16
|
-
* shared download tree, so `per-spec` costs extra disk in the *output*, not
|
|
17
|
-
* extra network.
|
|
18
|
-
*/
|
|
19
1
|
import type * as Crypto from "effect/Crypto";
|
|
20
2
|
import * as Effect from "effect/Effect";
|
|
21
3
|
import * as FileSystem from "effect/FileSystem";
|
|
@@ -320,6 +302,9 @@ const runBundle = (
|
|
|
320
302
|
|
|
321
303
|
yield* prepareOutputDir(resolution, options);
|
|
322
304
|
|
|
305
|
+
// Scoped, not just temporary: the scope closing — on success, on failure,
|
|
306
|
+
// or on Ctrl-C — takes the whole staging tree with it, which is what keeps
|
|
307
|
+
// a crashed run from leaving half a bundle beside the user's own files.
|
|
323
308
|
const staging = yield* fs.makeTempDirectoryScoped({ prefix: "packall-" });
|
|
324
309
|
const packagesDir = path.join(staging, "packages");
|
|
325
310
|
yield* fs.makeDirectory(packagesDir, { recursive: true });
|
package/src/dependency-range.ts
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
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
1
|
import semver from "semver";
|
|
13
2
|
|
|
14
3
|
import type { Selector } from "./spec.js";
|
package/src/download.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
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
1
|
import type * as Crypto from "effect/Crypto";
|
|
9
2
|
import * as Effect from "effect/Effect";
|
|
10
3
|
import * as FileSystem from "effect/FileSystem";
|
package/src/errors.ts
CHANGED
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Failure types for the bundler.
|
|
3
|
-
*
|
|
4
|
-
* These are deliberately plain classes carrying a literal `_tag` rather than
|
|
5
|
-
* `Data.TaggedError`. They work with `Effect.catchTag` / `Effect.catchTags`
|
|
6
|
-
* exactly the same way, and they keep the public error surface independent of
|
|
7
|
-
* any single Effect release — useful while Effect v4 is still in beta.
|
|
8
|
-
*
|
|
9
|
-
* Every error carries enough context to be actionable without a stack trace,
|
|
10
|
-
* because the primary consumer is somebody staring at a terminal on a
|
|
11
|
-
* locked-down network trying to work out why a download did not happen.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
1
|
/** Discriminant union of every failure the bundler can produce. */
|
|
15
2
|
export type BundlerError =
|
|
16
3
|
| InvalidSpecError
|
|
@@ -29,6 +16,9 @@ export type BundlerError =
|
|
|
29
16
|
| ArchiveError
|
|
30
17
|
| OutputError;
|
|
31
18
|
|
|
19
|
+
// Plain classes with a literal `_tag` rather than `Data.TaggedError`. They work
|
|
20
|
+
// with `catchTag` / `catchTags` identically, and this keeps the public error
|
|
21
|
+
// surface off a helper that may still move while Effect v4 is in beta.
|
|
32
22
|
abstract class BundlerErrorBase extends Error {
|
|
33
23
|
abstract readonly _tag: string;
|
|
34
24
|
constructor(message: string, options?: ErrorOptions) {
|
package/src/index.ts
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `@packall/core` — the registry-agnostic bundling engine.
|
|
3
|
-
*
|
|
4
|
-
* Nothing in here knows about npm specifically. Point it at any implementation
|
|
5
|
-
* of the `Registry` service and it will resolve, download, verify and package
|
|
6
|
-
* a dependency closure.
|
|
7
|
-
*
|
|
8
|
-
* This is the package's whole public API. Everything not named here is an
|
|
9
|
-
* implementation detail, including modules that happen to be reachable.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
1
|
export type * from "./errors.js";
|
|
13
2
|
export {
|
|
14
3
|
ArchiveError,
|
|
@@ -88,9 +77,8 @@ export type { IntegrityAlgorithm, IntegrityHash, VerificationResult } from "./in
|
|
|
88
77
|
export { digestOf, hexDigestOf, parseIntegrity, verify as verifyIntegrity } from "./integrity.js";
|
|
89
78
|
|
|
90
79
|
// The packing backend. The Node implementation is deliberately *not* re-exported
|
|
91
|
-
// here — it
|
|
92
|
-
//
|
|
93
|
-
// browser.
|
|
80
|
+
// here — it pulls in `node-tar`, so reaching it has to be an explicit import of
|
|
81
|
+
// `@packall/core/node`.
|
|
94
82
|
export type { ArchiveResult, CreateArchiveOptions } from "./archive.js";
|
|
95
83
|
export { Archiver, createArchive } from "./archive.js";
|
|
96
84
|
|
package/src/input-file.ts
CHANGED
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `--file` handling.
|
|
3
|
-
*
|
|
4
|
-
* One flag, three accepted shapes, detected from the content rather than the
|
|
5
|
-
* extension so that `--file deps.json` and `--file my-packages` both do the
|
|
6
|
-
* obvious thing:
|
|
7
|
-
*
|
|
8
|
-
* - a **package.json** — bundle everything it depends on
|
|
9
|
-
* - a **lockfile** — npm, pnpm or bun; bundle the exact versions it pins
|
|
10
|
-
* - a **newline-delimited list** — one spec per line, `#` and `//` comments
|
|
11
|
-
* allowed, blank lines ignored
|
|
12
|
-
*
|
|
13
|
-
* A lockfile is checked for first, because `package-lock.json` and
|
|
14
|
-
* `package.json` are both JSON objects and only the content tells them apart.
|
|
15
|
-
*/
|
|
16
1
|
import * as Effect from "effect/Effect";
|
|
17
2
|
import * as FileSystem from "effect/FileSystem";
|
|
18
3
|
import * as Option from "effect/Option";
|
package/src/integrity.ts
CHANGED
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Checksum verification for downloaded tarballs.
|
|
3
|
-
*
|
|
4
|
-
* A failed check discards the download rather than warning and carrying on —
|
|
5
|
-
* see `notes/requirements.md` § FR-9 for why that strictness is a requirement
|
|
6
|
-
* rather than a preference.
|
|
7
|
-
*
|
|
8
|
-
* Built on Effect's `Crypto` service rather than `node:crypto` directly. The
|
|
9
|
-
* four digests `Crypto` offers — SHA-1, SHA-256, SHA-384, SHA-512 — are exactly
|
|
10
|
-
* the four npm publishes, so nothing is given up, and the same verification
|
|
11
|
-
* then runs unchanged against WebCrypto in a browser. Parsing stays here
|
|
12
|
-
* because the subset of Subresource Integrity npm actually emits is small and
|
|
13
|
-
* entirely mechanical; `ssri` would be a dependency for a regex.
|
|
14
|
-
*/
|
|
15
1
|
import * as Crypto from "effect/Crypto";
|
|
16
2
|
import * as Effect from "effect/Effect";
|
|
17
3
|
import * as Encoding from "effect/Encoding";
|
package/src/layout.ts
CHANGED
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The on-disk shape of a bundle.
|
|
3
|
-
*
|
|
4
|
-
* Under every layout but `flat`, everything is laid out exactly the way a
|
|
5
|
-
* registry serves it:
|
|
6
|
-
*
|
|
7
|
-
* lodash/-/lodash-4.17.21.tgz
|
|
8
|
-
* @babel/core/-/core-7.24.0.tgz
|
|
9
|
-
*
|
|
10
|
-
* That is not an Artifactory convention — it is the path structure in every
|
|
11
|
-
* `dist.tarball` URL npm publishes. See `notes/requirements.md` § FR-12 for
|
|
12
|
-
* what that buys.
|
|
13
|
-
*
|
|
14
|
-
* The one shape worth noticing before editing anything here: a scoped
|
|
15
|
-
* package's file name drops the scope, so `@babel/core` becomes
|
|
16
|
-
* `core-7.24.0.tgz` under an `@babel/core/-/` directory.
|
|
17
|
-
*
|
|
18
|
-
* `flat` layout is the exception; `bundlePath` is where the two part company.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
1
|
import { Layout } from "./enums/layout.js";
|
|
22
2
|
|
|
23
3
|
/** Splits `@scope/name` into its parts. `scope` is undefined when unscoped. */
|
|
@@ -30,7 +10,12 @@ export const splitName = (
|
|
|
30
10
|
return { scope: name.slice(0, slash), bare: name.slice(slash + 1) };
|
|
31
11
|
};
|
|
32
12
|
|
|
33
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* `@babel/core` + `7.24.0` -> `core-7.24.0.tgz`
|
|
15
|
+
*
|
|
16
|
+
* The scope is dropped, because that is what the registry serves. It is also
|
|
17
|
+
* why a flat directory of tarballs cannot use this name — see {@link flatName}.
|
|
18
|
+
*/
|
|
34
19
|
export const tarballFileName = (name: string, version: string): string => {
|
|
35
20
|
const { bare } = splitName(name);
|
|
36
21
|
return `${bare}-${version}.tgz`;
|
|
@@ -51,43 +36,22 @@ export const MANIFEST_FILE = "bundle-manifest.json";
|
|
|
51
36
|
export const README_FILE = "IMPORT.md";
|
|
52
37
|
|
|
53
38
|
/**
|
|
54
|
-
* A flat, collision-free file name for one `name@version
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* or `foo-bar` at `1.0.0`?
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* Dropping it — which this did, matching what `npm pack` produces — meant
|
|
71
|
-
* `@something/somewhat` and `something-somewhat` were both
|
|
72
|
-
* `something-somewhat-1.0.0.tgz`. Two different packages, one path: the second
|
|
73
|
-
* landed on the first, and the run reported two artifacts for the one file that
|
|
74
|
-
* survived. A bundle short a package, called complete, found at the far end of
|
|
75
|
-
* an air gap.
|
|
76
|
-
*
|
|
77
|
-
* Keeping the `@` makes that impossible by construction rather than by a check,
|
|
78
|
-
* because an unscoped npm name may not begin with one. It is also the more
|
|
79
|
-
* honest name: the scope is part of what the package *is*, and a directory of
|
|
80
|
-
* archives that has forgotten which ones were scoped is a directory somebody
|
|
81
|
-
* has to cross-reference against a manifest.
|
|
82
|
-
*
|
|
83
|
-
* It does not make every collision impossible — `@a/b-c` and `@a-b/c` still
|
|
84
|
-
* meet — so the run still checks before downloading anything. That pair is
|
|
85
|
-
* pathological; the scoped-against-unscoped one is `@babel/core` beside
|
|
86
|
-
* `babel-core`, which is a real migration somebody could be bundling.
|
|
87
|
-
*
|
|
88
|
-
* The leading `@` is safe here in a way it is not inside the archive: tar's
|
|
89
|
-
* splice convention applies to the *file list* it is given, which is what
|
|
90
|
-
* `archive.ts` guards with `./`. These are output paths, not entries.
|
|
39
|
+
* A flat, collision-free file name for one `name@version`:
|
|
40
|
+
* `@types/react@19.2.18` becomes `@types-react@19.2.18.tgz`.
|
|
41
|
+
*
|
|
42
|
+
* **The scope's `@` stays.** Dropping it — which this did, matching `npm pack`
|
|
43
|
+
* — put `@something/somewhat` and `something-somewhat` on the same file name,
|
|
44
|
+
* and the run reported two artifacts for the one file that survived. Keeping it
|
|
45
|
+
* makes that impossible by construction rather than by a check, because an
|
|
46
|
+
* unscoped npm name may not begin with `@`.
|
|
47
|
+
*
|
|
48
|
+
* The version joins with `@` rather than `-` because `foo-bar-1.0.0` is
|
|
49
|
+
* ambiguous by eye: `foo` at `bar-1.0.0`, or `foo-bar` at `1.0.0`?
|
|
50
|
+
*
|
|
51
|
+
* `@a/b-c` against `@a-b/c` still collides, so callers check before
|
|
52
|
+
* downloading. The leading `@` is safe here in a way it is not inside an
|
|
53
|
+
* archive: tar's splice convention applies to the entry list, which the
|
|
54
|
+
* archiver guards with `./`. These are output paths, not entries.
|
|
91
55
|
*/
|
|
92
56
|
export const flatName = (name: string, version: string): string => {
|
|
93
57
|
const { scope, bare } = splitName(name);
|
package/src/locked-resolve.ts
CHANGED
|
@@ -1,22 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resolution driven by a lockfile instead of by semver.
|
|
3
|
-
*
|
|
4
|
-
* The distinction matters more than it first looks. Feeding a lockfile's
|
|
5
|
-
* versions in as exact root specs and letting the ordinary walk run would still
|
|
6
|
-
* produce the wrong bundle: every *transitive* edge is a range, and
|
|
7
|
-
* `Resolve.walk` answers a range with whatever is newest today. Lock `tslib` at
|
|
8
|
-
* 2.6.0 and let something depending on `tslib@^2` be re-resolved, and the
|
|
9
|
-
* bundle grows a second copy at 2.8.1 that CI never installed.
|
|
10
|
-
*
|
|
11
|
-
* So the lockfile is treated as the graph, not as a set of hints. The package
|
|
12
|
-
* set *is* what the file pins, and every edge is satisfied from inside it.
|
|
13
|
-
* Nothing here calls `selectVersions`; the only thing the registry is asked for
|
|
14
|
-
* is the metadata that a lockfile does not record — `dist`, and the `os`/`cpu`
|
|
15
|
-
* constraints that `--platform` narrows on.
|
|
16
|
-
*
|
|
17
|
-
* The result is an ordinary `Resolution`, so layout, archiving, the manifest
|
|
18
|
-
* and the summary cannot tell which path produced it.
|
|
19
|
-
*/
|
|
20
1
|
import * as Effect from "effect/Effect";
|
|
21
2
|
import semver from "semver";
|
|
22
3
|
|
|
@@ -78,6 +59,11 @@ export const resolveLocked = (
|
|
|
78
59
|
// Everything the lockfile can reach, before the platform filter has had a
|
|
79
60
|
// say. Cheap — no network at all — and it is what decides the fetch list.
|
|
80
61
|
const reachable = reachableKeys(lock, roots, options);
|
|
62
|
+
// Nothing here re-resolves a range. Handing these versions to `Resolve.walk`
|
|
63
|
+
// instead would answer every *transitive* range with whatever is newest
|
|
64
|
+
// today, so a lockfile pinning `tslib@2.6.0` would still gain a 2.8.1 that
|
|
65
|
+
// CI never installed. The registry is asked only for what a lockfile does
|
|
66
|
+
// not record.
|
|
81
67
|
const manifests = yield* fetchManifests(lock, reachable, options);
|
|
82
68
|
|
|
83
69
|
const warnings: Array<ResolutionWarning> = lock.warnings.map((message) => ({ message }));
|
package/src/lockfile/bun.ts
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `bun.lock`.
|
|
3
|
-
*
|
|
4
|
-
* Like npm, bun records ranges on its edges and resolves them through the
|
|
5
|
-
* install-path nesting — but it nests with a plain `/`, and package names
|
|
6
|
-
* contain `/` too, so the parent of a key can only be found by asking which
|
|
7
|
-
* prefixes are themselves entries.
|
|
8
|
-
*/
|
|
9
1
|
import { EdgeKind, LockedRootKind } from "../enums/edge-kind.js";
|
|
10
2
|
import { LockfileFormat } from "../enums/lockfile-format.js";
|
|
11
3
|
import { isRecord } from "../utils/is-record.js";
|
package/src/lockfile/detect.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Working out which lockfile — if any — some content is.
|
|
3
|
-
*
|
|
4
|
-
* Detection is by content, matching how `--file` treats everything else: a
|
|
5
|
-
* lockfile renamed to `deps.txt` still parses, and a `package-lock.json` that
|
|
6
|
-
* is actually a package.json still gets read as one.
|
|
7
|
-
*/
|
|
8
1
|
import { LockfileFormat } from "../enums/lockfile-format.js";
|
|
9
2
|
import { isRecord } from "../utils/is-record.js";
|
|
10
3
|
import { tryParseJsonc } from "./json.js";
|
package/src/lockfile/json.ts
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Narrowing helpers for the untyped JSON and YAML a lockfile parses into.
|
|
3
|
-
*
|
|
4
|
-
* Everything here takes `unknown` and hands back something narrowed, which is
|
|
5
|
-
* what keeps the parsers free of casts against files nobody controls. The
|
|
6
|
-
* shape-level narrowing is Effect Schema — the same decoders the registry
|
|
7
|
-
* response boundary uses — so "tolerated" means one thing across the codebase
|
|
8
|
-
* rather than being re-decided per parser.
|
|
9
|
-
*/
|
|
10
1
|
import * as Option from "effect/Option";
|
|
11
2
|
import * as Schema from "effect/Schema";
|
|
12
3
|
|
package/src/lockfile/npm.ts
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `package-lock.json` / `npm-shrinkwrap.json`.
|
|
3
|
-
*
|
|
4
|
-
* npm records *ranges* on its edges and relies on the directory nesting to say
|
|
5
|
-
* which copy of a package a range resolved to, so the node resolution walk-up —
|
|
6
|
-
* look beside me, then in my parent, then my grandparent — is reimplemented
|
|
7
|
-
* here.
|
|
8
|
-
*/
|
|
9
1
|
import { EdgeKind, LockedRootKind } from "../enums/edge-kind.js";
|
|
10
2
|
import { LockfileFormat } from "../enums/lockfile-format.js";
|
|
11
3
|
import { isRecord } from "../utils/is-record.js";
|
package/src/lockfile/parse.ts
CHANGED
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Lockfile parsing — npm, pnpm and bun.
|
|
3
|
-
*
|
|
4
|
-
* `--file package.json` reads *ranges*, and a range resolves to whatever is
|
|
5
|
-
* newest at the moment the command runs. That is the right answer for "bundle
|
|
6
|
-
* me something installable" and the wrong one for "bundle exactly what CI
|
|
7
|
-
* installed" — which is what a lockfile records and what this module reads.
|
|
8
|
-
*
|
|
9
|
-
* Three formats, one output shape. Each parser's real job is the same: turn a
|
|
10
|
-
* manager-specific encoding of "package X at path P depends on Y" into a flat
|
|
11
|
-
* graph keyed by `name@version`, with every edge already pointing at a concrete
|
|
12
|
-
* version. Once that is done, nothing downstream has to know which manager
|
|
13
|
-
* wrote the file.
|
|
14
|
-
*/
|
|
15
1
|
import { LockfileFormat } from "../enums/lockfile-format.js";
|
|
16
2
|
import { LockfileError } from "../errors.js";
|
|
17
3
|
import { parseBunLockfile } from "./bun.js";
|
package/src/lockfile/pnpm.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `pnpm-lock.yaml`.
|
|
3
|
-
*
|
|
4
|
-
* The easy one: pnpm's snapshots already name exact versions, so there is no
|
|
5
|
-
* nesting walk-up to reimplement. The fiddly parts are the three package-key
|
|
6
|
-
* encodings still in the wild and the parenthesised peer-resolution suffix.
|
|
7
|
-
*/
|
|
8
1
|
import semver from "semver";
|
|
9
2
|
import { parse as parseYaml } from "yaml";
|
|
10
3
|
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The bookkeeping every lockfile parser shares.
|
|
3
|
-
*
|
|
4
|
-
* Packages, edges and skip warnings accumulate here while a parser walks a
|
|
5
|
-
* file, which is what lets each parser stay focused on its own encoding.
|
|
6
|
-
*/
|
|
7
1
|
import semver from "semver";
|
|
8
2
|
|
|
9
3
|
import { parseDependencyTarget } from "../dependency-range.js";
|
package/src/lockfile/types.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The one output shape every lockfile parser produces.
|
|
3
|
-
*
|
|
4
|
-
* npm, pnpm and bun encode "package X at path P depends on Y" in three
|
|
5
|
-
* different ways; once flattened into this graph, nothing downstream has to
|
|
6
|
-
* know which manager wrote the file.
|
|
7
|
-
*/
|
|
8
1
|
import type { EdgeKind, LockedRootKind } from "../enums/edge-kind.js";
|
|
9
2
|
import type { LockfileFormat } from "../enums/lockfile-format.js";
|
|
10
3
|
|
package/src/manifest.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The manifest written into every bundle — the audit trail, answerable without
|
|
3
|
-
* unpacking a single tarball. See `notes/requirements.md` § FR-10.
|
|
4
|
-
*/
|
|
5
1
|
import { bundlePath } from "./layout.js";
|
|
6
2
|
import type { BundleOptions } from "./options.js";
|
|
7
3
|
import { formatPlatformFilter } from "./platform.js";
|
package/src/node/archiver.ts
CHANGED
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The Node `Archiver`, backed by `node-tar`.
|
|
3
|
-
*
|
|
4
|
-
* Behind `@packall/core/node` rather than the main barrel: importing
|
|
5
|
-
* `@packall/core` must not drag `node-tar` — and through it `node:fs` — into a
|
|
6
|
-
* browser bundle. Anything running on Node reaches for this explicitly.
|
|
7
|
-
*
|
|
8
|
-
* Uses `node-tar`, the same library npm itself packs with. Writing a USTAR
|
|
9
|
-
* encoder by hand would be a fun afternoon and a bad idea: scoped package
|
|
10
|
-
* paths routinely exceed the 100-byte name field, so correctness depends on
|
|
11
|
-
* prefix-splitting and PAX headers that `tar` already gets right.
|
|
12
|
-
*/
|
|
13
1
|
import * as Effect from "effect/Effect";
|
|
14
2
|
import * as FileSystem from "effect/FileSystem";
|
|
15
3
|
import * as Layer from "effect/Layer";
|
|
@@ -20,6 +8,10 @@ import type { ArchiveResult, CreateArchiveOptions } from "../archive.js";
|
|
|
20
8
|
import { Archiver } from "../archive.js";
|
|
21
9
|
import { ArchiveError } from "../errors.js";
|
|
22
10
|
|
|
11
|
+
// node-tar is what npm itself packs with, and the dependency is load-bearing
|
|
12
|
+
// rather than convenient: scoped package paths routinely exceed tar's 100-byte
|
|
13
|
+
// name field, so anything hand-written here would have to get prefix-splitting
|
|
14
|
+
// and PAX headers right before npm could read the result.
|
|
23
15
|
const createArchive = (
|
|
24
16
|
options: CreateArchiveOptions,
|
|
25
17
|
): Effect.Effect<ArchiveResult, ArchiveError | PlatformError, FileSystem.FileSystem> =>
|
package/src/node/index.ts
CHANGED
|
@@ -1,14 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `@packall/core/node` — the engine's Node-specific layers.
|
|
3
|
-
*
|
|
4
|
-
* Everything here is separated from the main barrel for one reason: importing
|
|
5
|
-
* `@packall/core` must stay safe in a browser. The engine itself reaches the
|
|
6
|
-
* outside world only through services — `Registry`, `Progress`, `Archiver`,
|
|
7
|
-
* `FileSystem`, `Path`, `Crypto` — so the platform lives at the edges, and this
|
|
8
|
-
* is the Node edge. A browser supplies its own.
|
|
9
|
-
*
|
|
10
|
-
* This is the entry point's whole public API.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
1
|
export { layerArchiver } from "./archiver.js";
|
|
14
2
|
export { currentPlatform } from "./platform.js";
|
package/src/node/platform.ts
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The machine we are currently running on.
|
|
3
|
-
*
|
|
4
|
-
* One function, in its own file, for the reason the whole `/node` subpath
|
|
5
|
-
* exists: it reads `process`, and importing `@packall/core` has to stay safe in
|
|
6
|
-
* a browser. Everything else in `platform.ts` is string work over what a
|
|
7
|
-
* manifest declares — it has no host and needs none, so it stays on the main
|
|
8
|
-
* entry where the engine can reach it.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
1
|
import type { PlatformTarget } from "../platform.js";
|
|
12
2
|
|
|
13
3
|
/** The machine we are currently running on. */
|
package/src/options.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Option types shared by the resolver and the bundler, plus their defaults.
|
|
3
|
-
*
|
|
4
|
-
* The rule the defaults below are derived from is stated once, beside the flags
|
|
5
|
-
* that expose them — see `descriptors.ts` in `@packall/cli`, and
|
|
6
|
-
* `notes/requirements.md` § FR-13.
|
|
7
|
-
*/
|
|
8
1
|
import { Layout } from "./enums/layout.js";
|
|
9
2
|
import type { PlatformFilter } from "./platform.js";
|
|
10
3
|
import { allPlatforms } from "./platform.js";
|
package/src/platform.ts
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
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
1
|
/** A target to keep, e.g. `linux-x64`, `linux-x64-musl`, or just `linux`. */
|
|
13
2
|
export type PlatformTarget = {
|
|
14
3
|
readonly os: string;
|
package/src/progress.ts
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
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
1
|
import * as Context from "effect/Context";
|
|
10
2
|
import * as Effect from "effect/Effect";
|
|
11
3
|
import * as Layer from "effect/Layer";
|
package/src/registry.ts
CHANGED
|
@@ -1,15 +1,3 @@
|
|
|
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
1
|
import * as Context from "effect/Context";
|
|
14
2
|
import type * as Effect from "effect/Effect";
|
|
15
3
|
|
|
@@ -100,6 +88,11 @@ export declare namespace Registry {
|
|
|
100
88
|
/**
|
|
101
89
|
* Service tag for the registry backend.
|
|
102
90
|
*
|
|
91
|
+
* The engine's only route to a registry. Version selection, the dependency
|
|
92
|
+
* walk, layout and archiving are all written against these four operations, so
|
|
93
|
+
* backing a registry that does not speak the npm protocol means implementing
|
|
94
|
+
* this interface and nothing else.
|
|
95
|
+
*
|
|
103
96
|
* @example
|
|
104
97
|
* ```ts
|
|
105
98
|
* import { Effect } from "effect"
|
package/src/resolve.ts
CHANGED
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Version selection and the dependency closure walk.
|
|
3
|
-
*
|
|
4
|
-
* This is the part that decides *what* ends up in a bundle. Its only side
|
|
5
|
-
* effect is asking the `Registry` service for metadata, which makes the whole
|
|
6
|
-
* of it testable against a fake registry with no network at all.
|
|
7
|
-
*
|
|
8
|
-
* The walk is breadth-first in waves: resolve the current frontier
|
|
9
|
-
* concurrently, collect the newly discovered edges, repeat. Wave-based BFS
|
|
10
|
-
* gives real parallelism without a dynamic worker pool, and because each wave
|
|
11
|
-
* merges its results serially there is no shared mutable state being written
|
|
12
|
-
* from more than one fiber.
|
|
13
|
-
*
|
|
14
|
-
* One subtlety worth calling out: metadata is cached across walks, but each
|
|
15
|
-
* walk still traverses the full graph. That is what lets `per-spec` layout ask
|
|
16
|
-
* "what does *this* root version depend on" many times over while the network
|
|
17
|
-
* is touched exactly once per package.
|
|
18
|
-
*/
|
|
19
1
|
import * as Effect from "effect/Effect";
|
|
20
2
|
import semver from "semver";
|
|
21
3
|
|
|
@@ -352,6 +334,10 @@ const walk = (
|
|
|
352
334
|
}
|
|
353
335
|
if (wave.length === 0) break;
|
|
354
336
|
|
|
337
|
+
// A wave resolves concurrently and then merges serially below. That is
|
|
338
|
+
// what gives real parallelism without a worker pool, and it is why
|
|
339
|
+
// nothing writes to `state` from more than one fiber — moving the merge
|
|
340
|
+
// inside `resolveTask` would reintroduce exactly that.
|
|
355
341
|
const results = yield* Effect.forEach(
|
|
356
342
|
wave,
|
|
357
343
|
(task) => resolveTask(state, task, options),
|
package/src/schemas/lenient.ts
CHANGED
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Schemas for JSON nobody controls.
|
|
3
|
-
*
|
|
4
|
-
* Registry responses, lockfiles and hand-edited package.json files all arrive
|
|
5
|
-
* as untyped JSON, and the rule at every one of those boundaries is the same:
|
|
6
|
-
* drop what cannot be understood, keep the rest. A bundler that rejects a
|
|
7
|
-
* packument because one published version carries a field it has never seen is
|
|
8
|
-
* useless against exactly the private registries it exists to serve.
|
|
9
|
-
*
|
|
10
|
-
* Effect Schema does the narrowing, which is what keeps those boundaries free
|
|
11
|
-
* of casts and hand-rolled `typeof` ladders. What this module adds on top of
|
|
12
|
-
* the stock combinators is that tolerance: `Schema.Record(String, String)`
|
|
13
|
-
* fails the whole record over one numeric value, where the wire format wants
|
|
14
|
-
* that entry skipped and its neighbours kept.
|
|
15
|
-
*/
|
|
16
1
|
import * as Effect from "effect/Effect";
|
|
17
2
|
import * as Option from "effect/Option";
|
|
18
3
|
import * as Schema from "effect/Schema";
|