@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
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
export type BundlerError =
|
|
16
16
|
| InvalidSpecError
|
|
17
17
|
| InvalidInputFileError
|
|
18
|
+
| LockfileError
|
|
19
|
+
| LockfileIncompleteError
|
|
20
|
+
| LockfileOutOfDateError
|
|
18
21
|
| RegistryUnreachableError
|
|
19
22
|
| PackageNotFoundError
|
|
20
23
|
| VersionNotFoundError
|
|
@@ -27,8 +30,8 @@ export type BundlerError =
|
|
|
27
30
|
|
|
28
31
|
abstract class BundlerErrorBase extends Error {
|
|
29
32
|
abstract readonly _tag: string
|
|
30
|
-
constructor(message: string, options?:
|
|
31
|
-
super(message, options
|
|
33
|
+
constructor(message: string, options?: ErrorOptions) {
|
|
34
|
+
super(message, options)
|
|
32
35
|
this.name = new.target.name
|
|
33
36
|
}
|
|
34
37
|
}
|
|
@@ -57,6 +60,85 @@ export class InvalidInputFileError extends BundlerErrorBase {
|
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
|
|
63
|
+
/** A lockfile could not be read, or is in a format we do not support. */
|
|
64
|
+
export class LockfileError extends BundlerErrorBase {
|
|
65
|
+
readonly _tag = "LockfileError" as const
|
|
66
|
+
readonly path: string
|
|
67
|
+
readonly reason: string
|
|
68
|
+
constructor(path: string, reason: string, options?: { cause?: unknown }) {
|
|
69
|
+
// Opens with "Lockfile" so the renderer's tag prefix does not stutter, and
|
|
70
|
+
// because not every reason is a read failure — "does not cover this
|
|
71
|
+
// package" is about a file that read perfectly well.
|
|
72
|
+
super(`Lockfile ${path}: ${reason}`, options)
|
|
73
|
+
this.path = path
|
|
74
|
+
this.reason = reason
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The lockfile parsed, but does not pin everything the bundle needs.
|
|
80
|
+
*
|
|
81
|
+
* Falling back to a range here would defeat the entire point of reading a
|
|
82
|
+
* lockfile — you would get a bundle that is *mostly* what CI installed, with no
|
|
83
|
+
* indication of which parts were guessed. So this is fatal, and it names every
|
|
84
|
+
* gap at once so one `npm install` fixes all of them.
|
|
85
|
+
*/
|
|
86
|
+
export class LockfileIncompleteError extends BundlerErrorBase {
|
|
87
|
+
readonly _tag = "LockfileIncompleteError" as const
|
|
88
|
+
readonly path: string
|
|
89
|
+
readonly missing: ReadonlyArray<string>
|
|
90
|
+
constructor(
|
|
91
|
+
path: string,
|
|
92
|
+
missing: ReadonlyArray<string>,
|
|
93
|
+
detail: string,
|
|
94
|
+
remedy: string
|
|
95
|
+
) {
|
|
96
|
+
const shown = missing.slice(0, 10).map((entry) => ` ${entry}`).join("\n")
|
|
97
|
+
const rest = missing.length > 10 ? `\n … and ${missing.length - 10} more` : ""
|
|
98
|
+
super(
|
|
99
|
+
`${path} is incomplete — ${detail}:\n${shown}${rest}\n` +
|
|
100
|
+
` ${remedy}\n` +
|
|
101
|
+
` Or pass --lockfile off to resolve version ranges fresh instead.`
|
|
102
|
+
)
|
|
103
|
+
this.path = path
|
|
104
|
+
this.missing = missing
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The lockfile pins versions the registry no longer serves.
|
|
110
|
+
*
|
|
111
|
+
* Unpublished, or never mirrored into a private registry. Either way the
|
|
112
|
+
* bundle cannot be built as specified, and quietly substituting a nearby
|
|
113
|
+
* version would produce exactly the mismatch this feature exists to prevent.
|
|
114
|
+
*/
|
|
115
|
+
export class LockfileOutOfDateError extends BundlerErrorBase {
|
|
116
|
+
readonly _tag = "LockfileOutOfDateError" as const
|
|
117
|
+
readonly path: string
|
|
118
|
+
readonly registry: string
|
|
119
|
+
readonly missing: ReadonlyArray<{ readonly name: string; readonly version: string }>
|
|
120
|
+
constructor(
|
|
121
|
+
path: string,
|
|
122
|
+
registry: string,
|
|
123
|
+
missing: ReadonlyArray<{ readonly name: string; readonly version: string; readonly detail: string }>
|
|
124
|
+
) {
|
|
125
|
+
const shown = missing
|
|
126
|
+
.slice(0, 10)
|
|
127
|
+
.map((entry) => ` ${entry.name}@${entry.version} — ${entry.detail}`)
|
|
128
|
+
.join("\n")
|
|
129
|
+
const rest = missing.length > 10 ? `\n … and ${missing.length - 10} more` : ""
|
|
130
|
+
super(
|
|
131
|
+
`${path} pins ${missing.length} version${missing.length === 1 ? "" : "s"} ` +
|
|
132
|
+
`that ${registry} does not serve:\n${shown}${rest}\n` +
|
|
133
|
+
` Refresh the lockfile against this registry, or pass --lockfile off to\n` +
|
|
134
|
+
` resolve version ranges fresh instead.`
|
|
135
|
+
)
|
|
136
|
+
this.path = path
|
|
137
|
+
this.registry = registry
|
|
138
|
+
this.missing = missing.map(({ name, version }) => ({ name, version }))
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
60
142
|
/**
|
|
61
143
|
* The registry could not be reached at all — DNS failure, refused connection,
|
|
62
144
|
* proxy blackhole, or a preflight that timed out.
|
|
@@ -67,11 +149,11 @@ export class InvalidInputFileError extends BundlerErrorBase {
|
|
|
67
149
|
export class RegistryUnreachableError extends BundlerErrorBase {
|
|
68
150
|
readonly _tag = "RegistryUnreachableError" as const
|
|
69
151
|
readonly registry: string
|
|
70
|
-
readonly
|
|
152
|
+
readonly timeoutMs: number | undefined
|
|
71
153
|
constructor(
|
|
72
154
|
registry: string,
|
|
73
155
|
detail: string,
|
|
74
|
-
options?: { cause?: unknown;
|
|
156
|
+
options?: { cause?: unknown; timeoutMs?: number }
|
|
75
157
|
) {
|
|
76
158
|
super(
|
|
77
159
|
`Cannot reach registry ${registry}: ${detail}.\n` +
|
|
@@ -79,7 +161,7 @@ export class RegistryUnreachableError extends BundlerErrorBase {
|
|
|
79
161
|
options
|
|
80
162
|
)
|
|
81
163
|
this.registry = registry
|
|
82
|
-
this.
|
|
164
|
+
this.timeoutMs = options?.timeoutMs
|
|
83
165
|
}
|
|
84
166
|
}
|
|
85
167
|
|
package/src/index.ts
CHANGED
|
@@ -4,41 +4,139 @@
|
|
|
4
4
|
* Nothing in here knows about npm specifically. Point it at any implementation
|
|
5
5
|
* of the `Registry` service and it will resolve, download, verify and package
|
|
6
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.
|
|
7
10
|
*/
|
|
8
11
|
|
|
9
|
-
export *
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
export
|
|
28
|
-
export {
|
|
29
|
-
export
|
|
30
|
-
export {
|
|
31
|
-
export
|
|
32
|
-
export {
|
|
33
|
-
|
|
34
|
-
export type {
|
|
12
|
+
export type * from "./errors.js"
|
|
13
|
+
export {
|
|
14
|
+
ArchiveError,
|
|
15
|
+
AuthenticationError,
|
|
16
|
+
IntegrityError,
|
|
17
|
+
InvalidInputFileError,
|
|
18
|
+
InvalidSpecError,
|
|
19
|
+
LockfileError,
|
|
20
|
+
LockfileIncompleteError,
|
|
21
|
+
LockfileOutOfDateError,
|
|
22
|
+
NoMatchingVersionsError,
|
|
23
|
+
OutputError,
|
|
24
|
+
PackageNotFoundError,
|
|
25
|
+
RegistryResponseError,
|
|
26
|
+
RegistryUnreachableError,
|
|
27
|
+
VersionNotFoundError
|
|
28
|
+
} from "./errors.js"
|
|
29
|
+
|
|
30
|
+
export { ArtifactKind } from "./enums/artifact-kind.js"
|
|
31
|
+
export { DirectOnlyKind, EdgeKind, LockedRootKind } from "./enums/edge-kind.js"
|
|
32
|
+
export { InputFileKind } from "./enums/input-file-kind.js"
|
|
33
|
+
export { Layout, Layouts } from "./enums/layout.js"
|
|
34
|
+
export { LockfileFormat } from "./enums/lockfile-format.js"
|
|
35
|
+
export { Phase } from "./enums/phase.js"
|
|
36
|
+
|
|
37
|
+
export type { ValueOf } from "./types/value-of.js"
|
|
38
|
+
export { isRecord } from "./utils/is-record.js"
|
|
39
|
+
|
|
40
|
+
// The tolerant decoders every boundary that reads untrusted JSON is built from
|
|
41
|
+
// — registry responses here, package.json and lockfiles inside the engine.
|
|
42
|
+
export {
|
|
43
|
+
OptionalFlagRecordOrAbsentSchema,
|
|
44
|
+
OptionalFlagRecordSchema,
|
|
45
|
+
OptionalStringArraySchema,
|
|
46
|
+
OptionalStringRecordSchema,
|
|
47
|
+
StringArraySchema,
|
|
48
|
+
StringRecordSchema,
|
|
49
|
+
tolerant
|
|
50
|
+
} from "./schemas/lenient.js"
|
|
51
|
+
|
|
52
|
+
export type { PackageSpec, Selector } from "./spec.js"
|
|
53
|
+
export { dedupeSpecs, formatSelector, formatSpec, parseSpec, parseSpecs } from "./spec.js"
|
|
54
|
+
|
|
55
|
+
export type { DependencyTarget } from "./dependency-range.js"
|
|
56
|
+
export { parseDependencyTarget } from "./dependency-range.js"
|
|
57
|
+
|
|
58
|
+
export type { PlatformConstraints, PlatformFilter, PlatformTarget } from "./platform.js"
|
|
59
|
+
export {
|
|
60
|
+
allPlatforms,
|
|
61
|
+
currentPlatform,
|
|
62
|
+
formatPlatformFilter,
|
|
63
|
+
isIncluded,
|
|
64
|
+
parsePlatformTarget,
|
|
65
|
+
platformTargets
|
|
66
|
+
} from "./platform.js"
|
|
67
|
+
|
|
68
|
+
export type { BundleOptions, DependencyScope, ResolveOptions } from "./options.js"
|
|
69
|
+
export { defaultBundleOptions, defaultResolveOptions, defaultScope } from "./options.js"
|
|
70
|
+
|
|
71
|
+
export type { Packument, PackageDist, PackageManifest } from "./registry.js"
|
|
72
|
+
// `Registry.Service` and `Progress.Service` travel with their class through
|
|
73
|
+
// declaration merging, so the one name carries the tag and its shape.
|
|
74
|
+
export { Registry } from "./registry.js"
|
|
75
|
+
|
|
76
|
+
export type { ProgressEvent } from "./progress.js"
|
|
77
|
+
// Aliased: `emit` and `layerSilent` say what they do inside `progress.ts`, and
|
|
78
|
+
// nothing at all on a package's public surface.
|
|
79
|
+
export {
|
|
80
|
+
emit as emitProgress,
|
|
81
|
+
layerCallback as layerCallbackProgress,
|
|
82
|
+
layerSilent as layerSilentProgress,
|
|
83
|
+
makeCollector as makeProgressCollector,
|
|
84
|
+
Progress
|
|
85
|
+
} from "./progress.js"
|
|
86
|
+
|
|
87
|
+
export type { IntegrityHash, VerificationResult } from "./integrity.js"
|
|
88
|
+
export { digestOf, hexDigestOf, parseIntegrity, verify as verifyIntegrity } from "./integrity.js"
|
|
89
|
+
|
|
90
|
+
export {
|
|
91
|
+
importGuide,
|
|
92
|
+
MANIFEST_FILE,
|
|
93
|
+
packagePath,
|
|
94
|
+
perSpecArchiveName,
|
|
95
|
+
README_FILE,
|
|
96
|
+
singleArchiveName,
|
|
97
|
+
splitName,
|
|
98
|
+
tarballFileName
|
|
99
|
+
} from "./layout.js"
|
|
100
|
+
|
|
101
|
+
export type { BundleManifest, ManifestEntry } from "./manifest.js"
|
|
102
|
+
export { buildManifest, MANIFEST_VERSION, serializeManifest } from "./manifest.js"
|
|
103
|
+
|
|
104
|
+
export type {
|
|
105
|
+
PackageKey,
|
|
106
|
+
Reason,
|
|
107
|
+
Resolution,
|
|
108
|
+
ResolutionWarning,
|
|
109
|
+
ResolvedPackage,
|
|
110
|
+
RootResolution
|
|
111
|
+
} from "./resolve.js"
|
|
112
|
+
export { indexPackages, packageKey, resolve, selectVersions } from "./resolve.js"
|
|
113
|
+
|
|
114
|
+
export type { LockedResolveOptions } from "./locked-resolve.js"
|
|
115
|
+
export { resolveLocked } from "./locked-resolve.js"
|
|
116
|
+
|
|
117
|
+
export type { LockfileDetection } from "./lockfile/detect.js"
|
|
118
|
+
export { detectLockfile } from "./lockfile/detect.js"
|
|
119
|
+
export type {
|
|
120
|
+
LockedEdge,
|
|
121
|
+
LockedPackage,
|
|
122
|
+
LockedRoot,
|
|
123
|
+
LockedTree,
|
|
124
|
+
LockfileParseOptions
|
|
125
|
+
} from "./lockfile/types.js"
|
|
126
|
+
export { formatByLockfileName, lockfileNamesFor } from "./lockfile/names.js"
|
|
127
|
+
export { parseLockfile } from "./lockfile/parse.js"
|
|
128
|
+
|
|
129
|
+
export type { InputFileOptions, InputFileResult } from "./input-file.js"
|
|
130
|
+
export { defaultInputFileOptions, parseInputFile, readInputFile } from "./input-file.js"
|
|
131
|
+
|
|
132
|
+
export type { BundleArtifact, BundleContext, BundleResult, PlannedOutput, PlanSummary } from "./bundle.js"
|
|
35
133
|
export {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
134
|
+
bundle,
|
|
135
|
+
bundleLocked,
|
|
136
|
+
bundleResolved,
|
|
137
|
+
plan,
|
|
138
|
+
planLocked,
|
|
139
|
+
plannedOutputs,
|
|
140
|
+
summarize,
|
|
141
|
+
withLayout
|
|
142
|
+
} from "./bundle.js"
|
|
@@ -1,34 +1,62 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `--file` handling.
|
|
3
3
|
*
|
|
4
|
-
* One flag,
|
|
4
|
+
* One flag, three accepted shapes, detected from the content rather than the
|
|
5
5
|
* extension so that `--file deps.json` and `--file my-packages` both do the
|
|
6
6
|
* obvious thing:
|
|
7
7
|
*
|
|
8
8
|
* - a **package.json** — bundle everything it depends on
|
|
9
|
+
* - a **lockfile** — npm, pnpm or bun; bundle the exact versions it pins
|
|
9
10
|
* - a **newline-delimited list** — one spec per line, `#` and `//` comments
|
|
10
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.
|
|
11
15
|
*/
|
|
12
16
|
import * as Effect from "effect/Effect"
|
|
13
17
|
import * as FileSystem from "effect/FileSystem"
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
import
|
|
17
|
-
import type {
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
import * as Option from "effect/Option"
|
|
19
|
+
import { parseDependencyTarget } from "./dependency-range.js"
|
|
20
|
+
import { InputFileKind } from "./enums/input-file-kind.js"
|
|
21
|
+
import type { LockfileFormat } from "./enums/lockfile-format.js"
|
|
22
|
+
import type { InvalidSpecError, LockfileError } from "./errors.js"
|
|
23
|
+
import { InvalidInputFileError } from "./errors.js"
|
|
24
|
+
import { detectLockfile } from "./lockfile/detect.js"
|
|
25
|
+
import { parseLockfile } from "./lockfile/parse.js"
|
|
26
|
+
import type { LockedTree } from "./lockfile/types.js"
|
|
27
|
+
import type { PackageJson } from "./schemas/package-json.js"
|
|
28
|
+
import { decodePackageJson } from "./schemas/package-json.js"
|
|
29
|
+
import type { PackageSpec } from "./spec.js"
|
|
30
|
+
import { parseSpecs } from "./spec.js"
|
|
22
31
|
|
|
23
|
-
export
|
|
32
|
+
export type InputFileResult = {
|
|
24
33
|
readonly kind: InputFileKind
|
|
25
34
|
readonly specs: ReadonlyArray<PackageSpec>
|
|
26
35
|
/** Entries that were recognised but cannot be bundled from a registry. */
|
|
27
36
|
readonly warnings: ReadonlyArray<string>
|
|
37
|
+
/**
|
|
38
|
+
* The pinned graph, set only for a lockfile.
|
|
39
|
+
*
|
|
40
|
+
* `specs` carries the same roots for display, but a lockfile run must resolve
|
|
41
|
+
* from this — see `locked-resolve` for why re-resolving the roots' ranges
|
|
42
|
+
* would quietly produce a different bundle.
|
|
43
|
+
*/
|
|
44
|
+
readonly lockfile?: LockedTree | undefined
|
|
45
|
+
/**
|
|
46
|
+
* Names from the blocks a package.json always installs — `dependencies` and,
|
|
47
|
+
* unless `--prod`, `devDependencies`.
|
|
48
|
+
*
|
|
49
|
+
* Used to check a lockfile against the manifest beside it. Deliberately
|
|
50
|
+
* excludes optional and peer entries: both are legitimately absent from a
|
|
51
|
+
* lockfile (an optional dependency that failed to build, a peer the consumer
|
|
52
|
+
* is expected to supply), so including them would turn a useful staleness
|
|
53
|
+
* check into a false alarm.
|
|
54
|
+
*/
|
|
55
|
+
readonly required?: ReadonlyArray<string> | undefined
|
|
28
56
|
}
|
|
29
57
|
|
|
30
58
|
/** Which dependency blocks of a package.json become root specs. */
|
|
31
|
-
export
|
|
59
|
+
export type InputFileOptions = {
|
|
32
60
|
/**
|
|
33
61
|
* Include `devDependencies`.
|
|
34
62
|
*
|
|
@@ -38,6 +66,13 @@ export interface InputFileOptions {
|
|
|
38
66
|
* turns it off.
|
|
39
67
|
*/
|
|
40
68
|
readonly includeDev: boolean
|
|
69
|
+
/**
|
|
70
|
+
* For a lockfile, which workspace member's dependencies are the roots.
|
|
71
|
+
*
|
|
72
|
+
* Set when a package.json found its lockfile by walking up: the file then
|
|
73
|
+
* covers the whole repository, and only one member of it was asked for.
|
|
74
|
+
*/
|
|
75
|
+
readonly importer?: string | undefined
|
|
41
76
|
}
|
|
42
77
|
|
|
43
78
|
export const defaultInputFileOptions: InputFileOptions = { includeDev: true }
|
|
@@ -46,7 +81,7 @@ export const defaultInputFileOptions: InputFileOptions = { includeDev: true }
|
|
|
46
81
|
export const readInputFile = (
|
|
47
82
|
filePath: string,
|
|
48
83
|
options: InputFileOptions = defaultInputFileOptions
|
|
49
|
-
): Effect.Effect<InputFileResult, InvalidInputFileError, FileSystem.FileSystem> =>
|
|
84
|
+
): Effect.Effect<InputFileResult, InvalidInputFileError | LockfileError, FileSystem.FileSystem> =>
|
|
50
85
|
Effect.gen(function*() {
|
|
51
86
|
const fs = yield* FileSystem.FileSystem
|
|
52
87
|
|
|
@@ -64,12 +99,17 @@ export const readInputFile = (
|
|
|
64
99
|
return yield* Effect.try({
|
|
65
100
|
try: () => parseInputFile(filePath, content, options),
|
|
66
101
|
catch: (error) =>
|
|
67
|
-
|
|
102
|
+
// A LockfileError already names the file and says what is wrong with
|
|
103
|
+
// it; wrapping it would only stutter.
|
|
104
|
+
error instanceof InvalidInputFileError || isLockfileError(error)
|
|
68
105
|
? error
|
|
69
|
-
: new InvalidInputFileError(filePath,
|
|
106
|
+
: new InvalidInputFileError(filePath, describeError(error), { cause: error })
|
|
70
107
|
})
|
|
71
108
|
})
|
|
72
109
|
|
|
110
|
+
const isLockfileError = (error: unknown): error is LockfileError =>
|
|
111
|
+
error instanceof Error && "_tag" in error && error._tag === "LockfileError"
|
|
112
|
+
|
|
73
113
|
/**
|
|
74
114
|
* Parses file content that has already been read.
|
|
75
115
|
*
|
|
@@ -86,6 +126,17 @@ export const parseInputFile = (
|
|
|
86
126
|
throw new InvalidInputFileError(filePath, "file is empty")
|
|
87
127
|
}
|
|
88
128
|
|
|
129
|
+
// Checked first: a package-lock.json is also a JSON object whose top level
|
|
130
|
+
// looks a lot like a package.json, and reading one as the other would produce
|
|
131
|
+
// a bundle with none of the pinning that was asked for.
|
|
132
|
+
const detected = detectLockfile(trimmed)
|
|
133
|
+
if (detected._tag === "Unsupported") {
|
|
134
|
+
throw new InvalidInputFileError(filePath, `${detected.label} — ${detected.hint}`)
|
|
135
|
+
}
|
|
136
|
+
if (detected._tag === "Supported") {
|
|
137
|
+
return fromLockfile(filePath, content, detected.format, options)
|
|
138
|
+
}
|
|
139
|
+
|
|
89
140
|
return looksLikeJson(trimmed)
|
|
90
141
|
? fromPackageJson(filePath, trimmed, options)
|
|
91
142
|
: fromList(filePath, content)
|
|
@@ -94,33 +145,47 @@ export const parseInputFile = (
|
|
|
94
145
|
const looksLikeJson = (trimmed: string): boolean => trimmed.startsWith("{")
|
|
95
146
|
|
|
96
147
|
/* -------------------------------------------------------------------------- */
|
|
97
|
-
/*
|
|
148
|
+
/* Lockfile */
|
|
98
149
|
/* -------------------------------------------------------------------------- */
|
|
99
150
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
151
|
+
const fromLockfile = (
|
|
152
|
+
filePath: string,
|
|
153
|
+
content: string,
|
|
154
|
+
format: LockfileFormat,
|
|
155
|
+
options: InputFileOptions
|
|
156
|
+
): InputFileResult => {
|
|
157
|
+
const lockfile = parseLockfile(filePath, content, format, { importer: options.importer })
|
|
158
|
+
|
|
159
|
+
// The same devDependency rule as a package.json, applied to the pinned roots
|
|
160
|
+
// so that `specs` shows exactly what the run will bundle.
|
|
161
|
+
const roots = lockfile.roots.filter((root) => options.includeDev || root.kind !== "dev")
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
kind: InputFileKind.Lockfile,
|
|
165
|
+
specs: roots.map((root) => ({
|
|
166
|
+
name: root.name,
|
|
167
|
+
selector: { _tag: "Exact", version: root.version },
|
|
168
|
+
raw: `${root.name}@${root.version}`
|
|
169
|
+
})),
|
|
170
|
+
warnings: lockfile.warnings,
|
|
171
|
+
lockfile
|
|
172
|
+
}
|
|
106
173
|
}
|
|
107
174
|
|
|
175
|
+
/* -------------------------------------------------------------------------- */
|
|
176
|
+
/* package.json */
|
|
177
|
+
/* -------------------------------------------------------------------------- */
|
|
178
|
+
|
|
108
179
|
const fromPackageJson = (
|
|
109
180
|
filePath: string,
|
|
110
181
|
content: string,
|
|
111
182
|
options: InputFileOptions
|
|
112
183
|
): InputFileResult => {
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
parsed = JSON.parse(content) as PackageJsonShape
|
|
116
|
-
} catch (error) {
|
|
117
|
-
throw new InvalidInputFileError(filePath, `not valid JSON — ${describe(error)}`, {
|
|
118
|
-
cause: error
|
|
119
|
-
})
|
|
120
|
-
}
|
|
184
|
+
const parsed = parsePackageJson(filePath, content)
|
|
121
185
|
|
|
122
186
|
const specs: Array<PackageSpec> = []
|
|
123
187
|
const warnings: Array<string> = []
|
|
188
|
+
const required: Array<string> = []
|
|
124
189
|
const seen = new Set<string>()
|
|
125
190
|
|
|
126
191
|
const collect = (
|
|
@@ -138,6 +203,9 @@ const fromPackageJson = (
|
|
|
138
203
|
)
|
|
139
204
|
continue
|
|
140
205
|
}
|
|
206
|
+
// `target.name` rather than the key, so an `npm:` alias is recorded under
|
|
207
|
+
// the name a lockfile would actually pin it as.
|
|
208
|
+
if (label === "dependencies" || label === "devDependencies") required.push(target.name)
|
|
141
209
|
const key = `${target.name}|${JSON.stringify(target.selector)}`
|
|
142
210
|
if (seen.has(key)) continue
|
|
143
211
|
seen.add(key)
|
|
@@ -166,7 +234,28 @@ const fromPackageJson = (
|
|
|
166
234
|
)
|
|
167
235
|
}
|
|
168
236
|
|
|
169
|
-
return { kind:
|
|
237
|
+
return { kind: InputFileKind.PackageJson, specs, warnings, required }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Reads a package.json, or gives up on its contents rather than on the run.
|
|
242
|
+
*
|
|
243
|
+
* Invalid JSON is an error worth raising — somebody pointed `--file` at a
|
|
244
|
+
* broken file. A well-formed JSON document this schema cannot recognise is
|
|
245
|
+
* not: it decodes to no blocks, and the caller reports "declares no
|
|
246
|
+
* dependencies to bundle", which is both true and more useful.
|
|
247
|
+
*/
|
|
248
|
+
const parsePackageJson = (filePath: string, content: string): PackageJson => {
|
|
249
|
+
let parsed: unknown
|
|
250
|
+
try {
|
|
251
|
+
parsed = JSON.parse(content)
|
|
252
|
+
} catch (error) {
|
|
253
|
+
throw new InvalidInputFileError(filePath, `not valid JSON — ${describeError(error)}`, {
|
|
254
|
+
cause: error
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return Option.getOrElse(decodePackageJson(parsed), (): PackageJson => ({}))
|
|
170
259
|
}
|
|
171
260
|
|
|
172
261
|
/* -------------------------------------------------------------------------- */
|
|
@@ -174,14 +263,11 @@ const fromPackageJson = (
|
|
|
174
263
|
/* -------------------------------------------------------------------------- */
|
|
175
264
|
|
|
176
265
|
const fromList = (filePath: string, content: string): InputFileResult => {
|
|
177
|
-
const lines: Array<string> = []
|
|
178
266
|
const rawLines = content.split(/\r?\n/)
|
|
179
|
-
|
|
180
|
-
for (const line of rawLines) {
|
|
267
|
+
const lines = rawLines.flatMap((line) => {
|
|
181
268
|
const withoutComment = stripComment(line).trim()
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
269
|
+
return withoutComment.length === 0 ? [] : withoutComment
|
|
270
|
+
})
|
|
185
271
|
|
|
186
272
|
if (lines.length === 0) {
|
|
187
273
|
throw new InvalidInputFileError(filePath, "no package specs found (only blank lines and comments)")
|
|
@@ -192,7 +278,7 @@ const fromList = (filePath: string, content: string): InputFileResult => {
|
|
|
192
278
|
throw new InvalidInputFileError(filePath, formatSpecErrors(errors, rawLines))
|
|
193
279
|
}
|
|
194
280
|
|
|
195
|
-
return { kind:
|
|
281
|
+
return { kind: InputFileKind.List, specs, warnings: [] }
|
|
196
282
|
}
|
|
197
283
|
|
|
198
284
|
/**
|
|
@@ -203,13 +289,10 @@ const fromList = (filePath: string, content: string): InputFileResult => {
|
|
|
203
289
|
*/
|
|
204
290
|
const stripComment = (line: string): string => {
|
|
205
291
|
const hash = line.indexOf("#")
|
|
206
|
-
|
|
292
|
+
const result = hash === -1 ? line : line.slice(0, hash)
|
|
207
293
|
const slashes = result.indexOf("//")
|
|
208
|
-
if (slashes
|
|
209
|
-
|
|
210
|
-
} else if (slashes === 0) {
|
|
211
|
-
result = ""
|
|
212
|
-
}
|
|
294
|
+
if (slashes === 0) return ""
|
|
295
|
+
if (slashes > 0 && result[slashes - 1] !== ":") return result.slice(0, slashes)
|
|
213
296
|
return result
|
|
214
297
|
}
|
|
215
298
|
|
|
@@ -225,5 +308,5 @@ const formatSpecErrors = (
|
|
|
225
308
|
return `${errors.length} invalid spec(s):\n${details.join("\n")}`
|
|
226
309
|
}
|
|
227
310
|
|
|
228
|
-
const
|
|
311
|
+
const describeError = (error: unknown): string =>
|
|
229
312
|
error instanceof Error ? error.message : String(error)
|