@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.
Files changed (126) hide show
  1. package/dist/{Archive.d.ts → archive.d.ts} +5 -5
  2. package/dist/archive.d.ts.map +1 -0
  3. package/dist/{Bundle.d.ts → bundle.d.ts} +51 -36
  4. package/dist/bundle.d.ts.map +1 -0
  5. package/dist/{DependencyRange.d.ts → dependency-range.d.ts} +2 -2
  6. package/dist/dependency-range.d.ts.map +1 -0
  7. package/dist/{Download.d.ts → download.d.ts} +7 -7
  8. package/dist/download.d.ts.map +1 -0
  9. package/dist/enums/artifact-kind.d.ts +8 -0
  10. package/dist/enums/artifact-kind.d.ts.map +1 -0
  11. package/dist/enums/edge-kind.d.ts +29 -0
  12. package/dist/enums/edge-kind.d.ts.map +1 -0
  13. package/dist/enums/input-file-kind.d.ts +12 -0
  14. package/dist/enums/input-file-kind.d.ts.map +1 -0
  15. package/dist/enums/layout.d.ts +23 -0
  16. package/dist/enums/layout.d.ts.map +1 -0
  17. package/dist/enums/lockfile-format.d.ts +9 -0
  18. package/dist/enums/lockfile-format.d.ts.map +1 -0
  19. package/dist/enums/phase.d.ts +11 -0
  20. package/dist/enums/phase.d.ts.map +1 -0
  21. package/dist/{Errors.d.ts → errors.d.ts} +49 -7
  22. package/dist/errors.d.ts.map +1 -0
  23. package/dist/index.d.ts +44 -28
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +2416 -1180
  26. package/dist/index.js.map +1 -1
  27. package/dist/input-file.d.ts +77 -0
  28. package/dist/input-file.d.ts.map +1 -0
  29. package/dist/{Integrity.d.ts → integrity.d.ts} +3 -3
  30. package/dist/integrity.d.ts.map +1 -0
  31. package/dist/{Layout.d.ts → layout.d.ts} +1 -1
  32. package/dist/{Layout.d.ts.map → layout.d.ts.map} +1 -1
  33. package/dist/locked-resolve.d.ts +47 -0
  34. package/dist/locked-resolve.d.ts.map +1 -0
  35. package/dist/lockfile/bun.d.ts +3 -0
  36. package/dist/lockfile/bun.d.ts.map +1 -0
  37. package/dist/lockfile/detect.d.ts +31 -0
  38. package/dist/lockfile/detect.d.ts.map +1 -0
  39. package/dist/lockfile/json.d.ts +23 -0
  40. package/dist/lockfile/json.d.ts.map +1 -0
  41. package/dist/lockfile/names.d.ts +16 -0
  42. package/dist/lockfile/names.d.ts.map +1 -0
  43. package/dist/lockfile/npm.d.ts +3 -0
  44. package/dist/lockfile/npm.d.ts.map +1 -0
  45. package/dist/lockfile/parse.d.ts +23 -0
  46. package/dist/lockfile/parse.d.ts.map +1 -0
  47. package/dist/lockfile/pnpm.d.ts +20 -0
  48. package/dist/lockfile/pnpm.d.ts.map +1 -0
  49. package/dist/lockfile/tree-builder.d.ts +70 -0
  50. package/dist/lockfile/tree-builder.d.ts.map +1 -0
  51. package/dist/lockfile/types.d.ts +70 -0
  52. package/dist/lockfile/types.d.ts.map +1 -0
  53. package/dist/{Manifest.d.ts → manifest.d.ts} +7 -15
  54. package/dist/manifest.d.ts.map +1 -0
  55. package/dist/{Options.d.ts → options.d.ts} +9 -27
  56. package/dist/options.d.ts.map +1 -0
  57. package/dist/{Platform.d.ts → platform.d.ts} +9 -9
  58. package/dist/platform.d.ts.map +1 -0
  59. package/dist/{Progress.d.ts → progress.d.ts} +10 -9
  60. package/dist/progress.d.ts.map +1 -0
  61. package/dist/{Registry.d.ts → registry.d.ts} +42 -44
  62. package/dist/registry.d.ts.map +1 -0
  63. package/dist/{Resolve.d.ts → resolve.d.ts} +25 -26
  64. package/dist/resolve.d.ts.map +1 -0
  65. package/dist/schemas/lenient.d.ts +38 -0
  66. package/dist/schemas/lenient.d.ts.map +1 -0
  67. package/dist/schemas/package-json.d.ts +44 -0
  68. package/dist/schemas/package-json.d.ts.map +1 -0
  69. package/dist/{Spec.d.ts → spec.d.ts} +4 -4
  70. package/dist/spec.d.ts.map +1 -0
  71. package/dist/types/value-of.d.ts +9 -0
  72. package/dist/types/value-of.d.ts.map +1 -0
  73. package/dist/utils/is-record.d.ts +8 -0
  74. package/dist/utils/is-record.d.ts.map +1 -0
  75. package/package.json +3 -2
  76. package/src/{Archive.ts → archive.ts} +92 -92
  77. package/src/{Bundle.ts → bundle.ts} +616 -570
  78. package/src/{DependencyRange.ts → dependency-range.ts} +115 -115
  79. package/src/{Download.ts → download.ts} +133 -132
  80. package/src/enums/artifact-kind.ts +9 -0
  81. package/src/enums/edge-kind.ts +32 -0
  82. package/src/enums/input-file-kind.ts +13 -0
  83. package/src/enums/layout.ts +25 -0
  84. package/src/enums/lockfile-format.ts +10 -0
  85. package/src/enums/phase.ts +12 -0
  86. package/src/{Errors.ts → errors.ts} +87 -5
  87. package/src/index.ts +133 -35
  88. package/src/{InputFile.ts → input-file.ts} +127 -44
  89. package/src/{Integrity.ts → integrity.ts} +121 -122
  90. package/src/locked-resolve.ts +461 -0
  91. package/src/lockfile/bun.ts +207 -0
  92. package/src/lockfile/detect.ts +80 -0
  93. package/src/lockfile/json.ts +132 -0
  94. package/src/lockfile/names.ts +20 -0
  95. package/src/lockfile/npm.ts +308 -0
  96. package/src/lockfile/parse.ts +56 -0
  97. package/src/lockfile/pnpm.ts +225 -0
  98. package/src/lockfile/tree-builder.ts +166 -0
  99. package/src/lockfile/types.ts +74 -0
  100. package/src/{Manifest.ts → manifest.ts} +133 -133
  101. package/src/{Options.ts → options.ts} +7 -27
  102. package/src/{Platform.ts → platform.ts} +23 -23
  103. package/src/{Progress.ts → progress.ts} +106 -106
  104. package/src/{Registry.ts → registry.ts} +115 -121
  105. package/src/{Resolve.ts → resolve.ts} +537 -528
  106. package/src/schemas/lenient.ts +123 -0
  107. package/src/schemas/package-json.ts +27 -0
  108. package/src/{Spec.ts → spec.ts} +11 -19
  109. package/src/types/value-of.ts +8 -0
  110. package/src/utils/is-record.ts +8 -0
  111. package/dist/Archive.d.ts.map +0 -1
  112. package/dist/Bundle.d.ts.map +0 -1
  113. package/dist/DependencyRange.d.ts.map +0 -1
  114. package/dist/Download.d.ts.map +0 -1
  115. package/dist/Errors.d.ts.map +0 -1
  116. package/dist/InputFile.d.ts +0 -46
  117. package/dist/InputFile.d.ts.map +0 -1
  118. package/dist/Integrity.d.ts.map +0 -1
  119. package/dist/Manifest.d.ts.map +0 -1
  120. package/dist/Options.d.ts.map +0 -1
  121. package/dist/Platform.d.ts.map +0 -1
  122. package/dist/Progress.d.ts.map +0 -1
  123. package/dist/Registry.d.ts.map +0 -1
  124. package/dist/Resolve.d.ts.map +0 -1
  125. package/dist/Spec.d.ts.map +0 -1
  126. /package/src/{Layout.ts → layout.ts} +0 -0
@@ -1,133 +1,133 @@
1
- /**
2
- * The manifest written into every bundle.
3
- *
4
- * This is the audit trail. Before anything is imported into a corporate
5
- * registry somebody is usually required to answer "what exactly is in here and
6
- * where did it come from" — this file answers that without unpacking a single
7
- * tarball.
8
- */
9
- import type { Resolution, ResolvedPackage } from "./Resolve.js"
10
- import { packagePath } from "./Layout.js"
11
- import type { BundleOptions } from "./Options.js"
12
- import { formatPlatformFilter } from "./Platform.js"
13
- import { formatSpec } from "./Spec.js"
14
-
15
- /** Schema version, bumped when the shape changes incompatibly. */
16
- export const MANIFEST_VERSION = 1
17
-
18
- export interface ManifestEntry {
19
- readonly name: string
20
- readonly version: string
21
- /** Path of this tarball inside the bundle. */
22
- readonly path: string
23
- /** The URL it was downloaded from. */
24
- readonly tarball: string
25
- /** SRI integrity string as advertised by the source registry, when present. */
26
- readonly integrity?: string | undefined
27
- /** Legacy sha1, present on older published versions. */
28
- readonly shasum?: string | undefined
29
- /** Actual size on disk, in bytes. */
30
- readonly bytes?: number | undefined
31
- /** Human-readable justifications: `root:react@18.2.0`, `prod:scheduler@0.23.0`. */
32
- readonly reasons: ReadonlyArray<string>
33
- }
34
-
35
- export interface BundleManifest {
36
- readonly manifestVersion: number
37
- readonly tool: { readonly name: string; readonly version: string }
38
- readonly createdAt: string
39
- readonly registry: { readonly kind: string; readonly url: string }
40
- /** What the user asked for, verbatim. */
41
- readonly requested: ReadonlyArray<string>
42
- /** What each request expanded to. */
43
- readonly roots: ReadonlyArray<{
44
- readonly spec: string
45
- readonly versions: ReadonlyArray<string>
46
- readonly packageCount: number
47
- }>
48
- readonly options: {
49
- readonly layout: string
50
- readonly optionalDependencies: boolean
51
- readonly peerDependencies: boolean
52
- readonly platforms: string
53
- readonly allVersions: boolean
54
- readonly includePrerelease: boolean
55
- readonly integrityVerified: boolean
56
- }
57
- readonly packages: ReadonlyArray<ManifestEntry>
58
- readonly warnings: ReadonlyArray<string>
59
- readonly totals: {
60
- readonly packages: number
61
- readonly bytes: number
62
- }
63
- }
64
-
65
- /** Renders a package's reasons into short, greppable strings. */
66
- export const formatReasons = (pkg: ResolvedPackage): ReadonlyArray<string> =>
67
- pkg.reasons.map((reason) =>
68
- reason._tag === "Root" ? `root:${reason.spec}` : `${reason.kind}:${reason.from}`
69
- )
70
-
71
- /** Builds the manifest for a completed (or dry) run. */
72
- export const buildManifest = (input: {
73
- readonly resolution: Resolution
74
- /** Only the packages actually included in *this* archive. */
75
- readonly included: ReadonlyArray<ResolvedPackage>
76
- readonly options: BundleOptions
77
- readonly registry: { readonly kind: string; readonly url: string }
78
- readonly toolVersion: string
79
- readonly sizes: ReadonlyMap<string, number>
80
- readonly createdAt?: Date | undefined
81
- }): BundleManifest => {
82
- const includedNames = new Set(input.included.map((p) => `${p.name}@${p.version}`))
83
-
84
- const packages: ReadonlyArray<ManifestEntry> = input.included.map((pkg) => {
85
- const key = `${pkg.name}@${pkg.version}`
86
- return {
87
- name: pkg.name,
88
- version: pkg.version,
89
- path: packagePath(pkg.name, pkg.version),
90
- tarball: pkg.manifest.dist.tarball,
91
- integrity: pkg.manifest.dist.integrity,
92
- shasum: pkg.manifest.dist.shasum,
93
- bytes: input.sizes.get(key),
94
- reasons: formatReasons(pkg)
95
- }
96
- })
97
-
98
- const totalBytes = packages.reduce((sum, entry) => sum + (entry.bytes ?? 0), 0)
99
-
100
- return {
101
- manifestVersion: MANIFEST_VERSION,
102
- tool: { name: "packall", version: input.toolVersion },
103
- createdAt: (input.createdAt ?? new Date()).toISOString(),
104
- registry: input.registry,
105
- requested: input.resolution.roots.map((root) => formatSpec(root.spec)),
106
- roots: input.resolution.roots.map((root) => ({
107
- spec: formatSpec(root.spec),
108
- versions: root.versions,
109
- packageCount: root.closure.filter((key) => includedNames.has(key)).length
110
- })),
111
- options: {
112
- layout: input.options.layout,
113
- optionalDependencies: input.options.scope.optional,
114
- peerDependencies: input.options.scope.peer,
115
- platforms: formatPlatformFilter(input.options.scope.platforms),
116
- allVersions: input.options.allVersions,
117
- includePrerelease: input.options.includePrerelease,
118
- integrityVerified: input.options.verifyIntegrity
119
- },
120
- packages,
121
- warnings: input.resolution.warnings.map((warning) =>
122
- warning.from === undefined ? warning.message : `${warning.from}: ${warning.message}`
123
- ),
124
- totals: {
125
- packages: packages.length,
126
- bytes: totalBytes
127
- }
128
- }
129
- }
130
-
131
- /** Serialises a manifest with stable key order and a trailing newline. */
132
- export const serializeManifest = (manifest: BundleManifest): string =>
133
- `${JSON.stringify(manifest, null, 2)}\n`
1
+ /**
2
+ * The manifest written into every bundle.
3
+ *
4
+ * This is the audit trail. Before anything is imported into a corporate
5
+ * registry somebody is usually required to answer "what exactly is in here and
6
+ * where did it come from" — this file answers that without unpacking a single
7
+ * tarball.
8
+ */
9
+ import { packagePath } from "./layout.js"
10
+ import type { BundleOptions } from "./options.js"
11
+ import { formatPlatformFilter } from "./platform.js"
12
+ import type { Resolution, ResolvedPackage } from "./resolve.js"
13
+ import { formatSpec } from "./spec.js"
14
+
15
+ /** Schema version, bumped when the shape changes incompatibly. */
16
+ export const MANIFEST_VERSION = 1
17
+
18
+ export type ManifestEntry = {
19
+ readonly name: string
20
+ readonly version: string
21
+ /** Path of this tarball inside the bundle. */
22
+ readonly path: string
23
+ /** The URL it was downloaded from. */
24
+ readonly tarball: string
25
+ /** SRI integrity string as advertised by the source registry, when present. */
26
+ readonly integrity?: string | undefined
27
+ /** Legacy sha1, present on older published versions. */
28
+ readonly shasum?: string | undefined
29
+ /** Actual size on disk, in bytes. */
30
+ readonly bytes?: number | undefined
31
+ /** Human-readable justifications: `root:react@18.2.0`, `prod:scheduler@0.23.0`. */
32
+ readonly reasons: ReadonlyArray<string>
33
+ }
34
+
35
+ export type BundleManifest = {
36
+ readonly manifestVersion: number
37
+ readonly tool: { readonly name: string; readonly version: string }
38
+ readonly createdAt: string
39
+ readonly registry: { readonly kind: string; readonly url: string }
40
+ /** What the user asked for, verbatim. */
41
+ readonly requested: ReadonlyArray<string>
42
+ /** What each request expanded to. */
43
+ readonly roots: ReadonlyArray<{
44
+ readonly spec: string
45
+ readonly versions: ReadonlyArray<string>
46
+ readonly packageCount: number
47
+ }>
48
+ readonly options: {
49
+ readonly layout: string
50
+ readonly optionalDependencies: boolean
51
+ readonly peerDependencies: boolean
52
+ readonly platforms: string
53
+ readonly allVersions: boolean
54
+ readonly includePrerelease: boolean
55
+ readonly integrityVerified: boolean
56
+ }
57
+ readonly packages: ReadonlyArray<ManifestEntry>
58
+ readonly warnings: ReadonlyArray<string>
59
+ readonly totals: {
60
+ readonly packages: number
61
+ readonly bytes: number
62
+ }
63
+ }
64
+
65
+ /** Renders a package's reasons into short, greppable strings. */
66
+ export const formatReasons = (pkg: ResolvedPackage): ReadonlyArray<string> =>
67
+ pkg.reasons.map((reason) =>
68
+ reason._tag === "Root" ? `root:${reason.spec}` : `${reason.kind}:${reason.from}`
69
+ )
70
+
71
+ /** Builds the manifest for a completed (or dry) run. */
72
+ export const buildManifest = (input: {
73
+ readonly resolution: Resolution
74
+ /** Only the packages actually included in *this* archive. */
75
+ readonly included: ReadonlyArray<ResolvedPackage>
76
+ readonly options: BundleOptions
77
+ readonly registry: { readonly kind: string; readonly url: string }
78
+ readonly toolVersion: string
79
+ readonly sizes: ReadonlyMap<string, number>
80
+ readonly createdAt?: Date | undefined
81
+ }): BundleManifest => {
82
+ const includedNames = new Set(input.included.map((pkg) => `${pkg.name}@${pkg.version}`))
83
+
84
+ const packages: ReadonlyArray<ManifestEntry> = input.included.map((pkg) => {
85
+ const key = `${pkg.name}@${pkg.version}`
86
+ return {
87
+ name: pkg.name,
88
+ version: pkg.version,
89
+ path: packagePath(pkg.name, pkg.version),
90
+ tarball: pkg.manifest.dist.tarball,
91
+ integrity: pkg.manifest.dist.integrity,
92
+ shasum: pkg.manifest.dist.shasum,
93
+ bytes: input.sizes.get(key),
94
+ reasons: formatReasons(pkg)
95
+ }
96
+ })
97
+
98
+ const totalBytes = packages.reduce((sum, entry) => sum + (entry.bytes ?? 0), 0)
99
+
100
+ return {
101
+ manifestVersion: MANIFEST_VERSION,
102
+ tool: { name: "packall", version: input.toolVersion },
103
+ createdAt: (input.createdAt ?? new Date()).toISOString(),
104
+ registry: input.registry,
105
+ requested: input.resolution.roots.map((root) => formatSpec(root.spec)),
106
+ roots: input.resolution.roots.map((root) => ({
107
+ spec: formatSpec(root.spec),
108
+ versions: root.versions,
109
+ packageCount: root.closure.filter((key) => includedNames.has(key)).length
110
+ })),
111
+ options: {
112
+ layout: input.options.layout,
113
+ optionalDependencies: input.options.scope.optional,
114
+ peerDependencies: input.options.scope.peer,
115
+ platforms: formatPlatformFilter(input.options.scope.platforms),
116
+ allVersions: input.options.allVersions,
117
+ includePrerelease: input.options.includePrerelease,
118
+ integrityVerified: input.options.verifyIntegrity
119
+ },
120
+ packages,
121
+ warnings: input.resolution.warnings.map((warning) =>
122
+ warning.from === undefined ? warning.message : `${warning.from}: ${warning.message}`
123
+ ),
124
+ totals: {
125
+ packages: packages.length,
126
+ bytes: totalBytes
127
+ }
128
+ }
129
+ }
130
+
131
+ /** Serialises a manifest with stable key order and a trailing newline. */
132
+ export const serializeManifest = (manifest: BundleManifest): string =>
133
+ `${JSON.stringify(manifest, null, 2)}\n`
@@ -5,32 +5,12 @@
5
5
  * actually installs offline on a machine that is not the one you built it on.
6
6
  * Every switch that makes the bundle smaller is opt-in.
7
7
  */
8
- import type { PlatformFilter } from "./Platform.js"
9
- import { allPlatforms } from "./Platform.js"
10
-
11
- /** How the resulting tarball(s) are shaped. */
12
- export type Layout =
13
- /**
14
- * One tarball per requested spec, each carrying its own complete dependency
15
- * closure. Shared dependencies repeat across tarballs, so the total is
16
- * larger — but each one imports, rolls back and hands off independently.
17
- */
18
- | "per-spec"
19
- /**
20
- * One tarball for everything, with the union of all closures deduplicated.
21
- * Smallest output and a single import step; all-or-nothing to roll back.
22
- */
23
- | "single"
24
- /**
25
- * No archive at all — just the npm-layout directory tree, ready for
26
- * `jf rt upload` or rsync.
27
- */
28
- | "dir"
29
-
30
- export const layouts: ReadonlyArray<Layout> = ["per-spec", "single", "dir"]
8
+ import { Layout } from "./enums/layout.js"
9
+ import type { PlatformFilter } from "./platform.js"
10
+ import { allPlatforms } from "./platform.js"
31
11
 
32
12
  /** Which dependency edges the resolver follows. */
33
- export interface DependencyScope {
13
+ export type DependencyScope = {
34
14
  /**
35
15
  * Follow `optionalDependencies`. On by default — this is where per-platform
36
16
  * native binaries live, and omitting them is the most common reason an
@@ -53,7 +33,7 @@ export const defaultScope: DependencyScope = {
53
33
  }
54
34
 
55
35
  /** Options governing version selection and the dependency walk. */
56
- export interface ResolveOptions {
36
+ export type ResolveOptions = {
57
37
  readonly scope: DependencyScope
58
38
  /**
59
39
  * When a root spec is a range, take *every* published version that satisfies
@@ -79,7 +59,7 @@ export const defaultResolveOptions: ResolveOptions = {
79
59
  }
80
60
 
81
61
  /** Options for a full bundle run. */
82
- export interface BundleOptions extends ResolveOptions {
62
+ export type BundleOptions = ResolveOptions & {
83
63
  readonly layout: Layout
84
64
  /** Directory the finished artifacts are written to. */
85
65
  readonly outDir: string
@@ -116,7 +96,7 @@ export interface BundleOptions extends ResolveOptions {
116
96
 
117
97
  export const defaultBundleOptions: Omit<BundleOptions, "outDir"> = {
118
98
  ...defaultResolveOptions,
119
- layout: "per-spec",
99
+ layout: Layout.PerSpec,
120
100
  dryRun: false,
121
101
  verifyIntegrity: true,
122
102
  force: false
@@ -15,7 +15,7 @@
15
15
  */
16
16
 
17
17
  /** A target to keep, e.g. `linux-x64`, `linux-x64-musl`, or just `linux`. */
18
- export interface PlatformTarget {
18
+ export type PlatformTarget = {
19
19
  readonly os: string
20
20
  /** Undefined means "every architecture for this OS". */
21
21
  readonly cpu?: string | undefined
@@ -33,6 +33,13 @@ export type PlatformFilter =
33
33
  | { readonly _tag: "All" }
34
34
  | { readonly _tag: "Targets"; readonly targets: ReadonlyArray<PlatformTarget> }
35
35
 
36
+ /** Constraints declared by a package, as they appear in its manifest. */
37
+ export type PlatformConstraints = {
38
+ readonly os?: ReadonlyArray<string> | undefined
39
+ readonly cpu?: ReadonlyArray<string> | undefined
40
+ readonly libc?: ReadonlyArray<string> | undefined
41
+ }
42
+
36
43
  export const allPlatforms: PlatformFilter = { _tag: "All" }
37
44
 
38
45
  export const platformTargets = (targets: ReadonlyArray<PlatformTarget>): PlatformFilter => ({
@@ -54,7 +61,7 @@ export const currentPlatform = (): PlatformTarget => ({
54
61
  * and installs nothing. `--platform windows` silently matching no binding at
55
62
  * all would be precisely that, so it is corrected rather than accepted.
56
63
  */
57
- const OS_ALIASES: Readonly<Record<string, string>> = {
64
+ const osByAlias: Readonly<Record<string, string | undefined>> = {
58
65
  win: "win32",
59
66
  windows: "win32",
60
67
  mac: "darwin",
@@ -76,7 +83,7 @@ const KNOWN_OS: ReadonlySet<string> = new Set([
76
83
  "win32"
77
84
  ])
78
85
 
79
- const LIBC = new Set(["musl", "glibc"])
86
+ const LIBC: ReadonlySet<string> = new Set(["musl", "glibc"])
80
87
 
81
88
  /**
82
89
  * Parses `linux`, `linux-x64`, `darwin-arm64`, `win32-x64`, `linux-x64-musl`,
@@ -91,24 +98,22 @@ const LIBC = new Set(["musl", "glibc"])
91
98
  * once alongside the list of accepted forms.
92
99
  */
93
100
  export const parsePlatformTarget = (input: string): PlatformTarget | null => {
94
- const parts = input.trim().toLowerCase().split("-").filter((p) => p.length > 0)
95
- if (parts.length === 0 || parts.length > 3) return null
101
+ const parts = input.trim().toLowerCase().split("-").filter((part) => part.length > 0)
102
+ const [rawOs, second, third] = parts
103
+ if (rawOs === undefined || parts.length > 3) return null
96
104
 
97
- const rawOs = parts[0]!
98
- const os = OS_ALIASES[rawOs] ?? rawOs
105
+ const os = osByAlias[rawOs] ?? rawOs
99
106
  if (!KNOWN_OS.has(os)) return null
100
107
 
101
- if (parts.length === 1) return { os }
108
+ if (second === undefined) return { os }
102
109
 
103
110
  // `linux-musl` is an OS plus a libc, not an OS plus an architecture.
104
- if (parts.length === 2 && LIBC.has(parts[1]!)) return { os, libc: parts[1]! }
105
-
106
- const cpu = parts[1]!
107
- if (parts.length === 2) return { os, cpu }
111
+ if (third === undefined) {
112
+ return LIBC.has(second) ? { os, libc: second } : { os, cpu: second }
113
+ }
108
114
 
109
- const libc = parts[2]!
110
- if (!LIBC.has(libc)) return null
111
- return { os, cpu, libc }
115
+ if (!LIBC.has(third)) return null
116
+ return { os, cpu: second, libc: third }
112
117
  }
113
118
 
114
119
  /**
@@ -140,13 +145,6 @@ const listAllows = (list: ReadonlyArray<string> | undefined, value: string): boo
140
145
  return hasPositive ? positiveMatched : true
141
146
  }
142
147
 
143
- /** Constraints declared by a package, as they appear in its manifest. */
144
- export interface PlatformConstraints {
145
- readonly os?: ReadonlyArray<string> | undefined
146
- readonly cpu?: ReadonlyArray<string> | undefined
147
- readonly libc?: ReadonlyArray<string> | undefined
148
- }
149
-
150
148
  /** Would this package install on this specific target? */
151
149
  export const matchesTarget = (
152
150
  constraints: PlatformConstraints,
@@ -177,6 +175,8 @@ export const isIncluded = (
177
175
  export const formatPlatformFilter = (filter: PlatformFilter): string => {
178
176
  if (filter._tag === "All") return "all"
179
177
  return filter.targets
180
- .map((t) => [t.os, t.cpu, t.libc].filter((part) => part !== undefined).join("-"))
178
+ .map((target) =>
179
+ [target.os, target.cpu, target.libc].filter((part) => part !== undefined).join("-")
180
+ )
181
181
  .join(",")
182
182
  }
@@ -1,106 +1,106 @@
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
- /** The phases a bundle run moves through, in order. */
15
- export type Phase = "preflight" | "resolve" | "download" | "archive" | "done"
16
-
17
- /** A structured progress event. */
18
- export type ProgressEvent =
19
- /** A phase began. `total` is set only when it is known up front. */
20
- | { readonly _tag: "PhaseStarted"; readonly phase: Phase; readonly total?: number | undefined }
21
- /** A phase finished. */
22
- | { readonly _tag: "PhaseCompleted"; readonly phase: Phase }
23
- /** The resolver discovered a package that has to be included. */
24
- | {
25
- readonly _tag: "PackageResolved"
26
- readonly name: string
27
- readonly version: string
28
- /** Running count of resolved packages, for a live counter. */
29
- readonly resolvedCount: number
30
- /** Packages still queued for resolution. */
31
- readonly pendingCount: number
32
- }
33
- /** A tarball download started. */
34
- | { readonly _tag: "DownloadStarted"; readonly name: string; readonly version: string }
35
- /** A tarball download finished. */
36
- | {
37
- readonly _tag: "DownloadCompleted"
38
- readonly name: string
39
- readonly version: string
40
- readonly bytes: number
41
- readonly completedCount: number
42
- readonly totalCount: number
43
- }
44
- /** A download failed and will be retried. */
45
- | {
46
- readonly _tag: "DownloadRetrying"
47
- readonly name: string
48
- readonly version: string
49
- readonly attempt: number
50
- readonly reason: string
51
- }
52
- /** An archive is being written. */
53
- | { readonly _tag: "ArchiveStarted"; readonly path: string; readonly entryCount: number }
54
- /** An archive finished. */
55
- | { readonly _tag: "ArchiveCompleted"; readonly path: string; readonly bytes: number }
56
- /** Something noteworthy but non-fatal, e.g. a deprecated package. */
57
- | { readonly _tag: "Warning"; readonly message: string }
58
-
59
- /** The reporting sink. */
60
- export interface ProgressService {
61
- readonly emit: (event: ProgressEvent) => Effect.Effect<void>
62
- }
63
-
64
- /** Service tag for progress reporting. */
65
- export class Progress extends Context.Service<Progress, ProgressService>()(
66
- "@packall/core/Progress"
67
- ) {}
68
-
69
- /** Emits an event to whichever reporter is installed. */
70
- export const emit = (event: ProgressEvent): Effect.Effect<void, never, Progress> =>
71
- Effect.gen(function*() {
72
- const progress = yield* Progress
73
- yield* progress.emit(event)
74
- })
75
-
76
- /**
77
- * Discards every event.
78
- *
79
- * The default for library consumers and for tests that do not care about
80
- * progress — silence should never require ceremony.
81
- */
82
- export const layerSilent: Layer.Layer<Progress> = Layer.succeed(Progress)({
83
- emit: () => Effect.void
84
- })
85
-
86
- /** Sends every event to a callback. Used by the CLI renderer. */
87
- export const layerCallback = (
88
- onEvent: (event: ProgressEvent) => void
89
- ): Layer.Layer<Progress> =>
90
- Layer.succeed(Progress)({
91
- emit: (event) => Effect.sync(() => onEvent(event))
92
- })
93
-
94
- /**
95
- * Accumulates every event into a `Ref`, for assertions.
96
- *
97
- * Returned as `[layer, ref]` so a test can provide the layer and then read the
98
- * transcript afterwards.
99
- */
100
- export const makeCollector = Effect.gen(function*() {
101
- const ref = yield* Ref.make<ReadonlyArray<ProgressEvent>>([])
102
- const layer = Layer.succeed(Progress)({
103
- emit: (event: ProgressEvent) => Ref.update(ref, (events) => [...events, event])
104
- })
105
- return { layer, events: Ref.get(ref) } as const
106
- })
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
+ import type { Phase } from "./enums/phase.js"
14
+
15
+ /** A structured progress event. */
16
+ export type ProgressEvent =
17
+ /** A phase began. `total` is set only when it is known up front. */
18
+ | { readonly _tag: "PhaseStarted"; readonly phase: Phase; readonly total?: number | undefined }
19
+ /** A phase finished. */
20
+ | { readonly _tag: "PhaseCompleted"; readonly phase: Phase }
21
+ /** The resolver discovered a package that has to be included. */
22
+ | {
23
+ readonly _tag: "PackageResolved"
24
+ readonly name: string
25
+ readonly version: string
26
+ /** Running count of resolved packages, for a live counter. */
27
+ readonly resolvedCount: number
28
+ /** Packages still queued for resolution. */
29
+ readonly pendingCount: number
30
+ }
31
+ /** A tarball download started. */
32
+ | { readonly _tag: "DownloadStarted"; readonly name: string; readonly version: string }
33
+ /** A tarball download finished. */
34
+ | {
35
+ readonly _tag: "DownloadCompleted"
36
+ readonly name: string
37
+ readonly version: string
38
+ readonly bytes: number
39
+ readonly completedCount: number
40
+ readonly totalCount: number
41
+ }
42
+ /** A download failed and will be retried. */
43
+ | {
44
+ readonly _tag: "DownloadRetrying"
45
+ readonly name: string
46
+ readonly version: string
47
+ readonly attempt: number
48
+ readonly reason: string
49
+ }
50
+ /** An archive is being written. */
51
+ | { readonly _tag: "ArchiveStarted"; readonly path: string; readonly entryCount: number }
52
+ /** An archive finished. */
53
+ | { readonly _tag: "ArchiveCompleted"; readonly path: string; readonly bytes: number }
54
+ /** Something noteworthy but non-fatal, e.g. a deprecated package. */
55
+ | { readonly _tag: "Warning"; readonly message: string }
56
+
57
+ export declare namespace Progress {
58
+ /** The reporting sink. */
59
+ export type Service = {
60
+ readonly emit: (event: ProgressEvent) => Effect.Effect<void>
61
+ }
62
+ }
63
+
64
+ /** Service tag for progress reporting. */
65
+ export class Progress extends Context.Service<Progress, Progress.Service>()(
66
+ "@packall/core/Progress"
67
+ ) {}
68
+
69
+ /** Emits an event to whichever reporter is installed. */
70
+ export const emit = (event: ProgressEvent): Effect.Effect<void, never, Progress> =>
71
+ Effect.gen(function*() {
72
+ const progress = yield* Progress
73
+ yield* progress.emit(event)
74
+ })
75
+
76
+ /**
77
+ * Discards every event.
78
+ *
79
+ * The default for library consumers and for tests that do not care about
80
+ * progress — silence should never require ceremony.
81
+ */
82
+ export const layerSilent: Layer.Layer<Progress> = Layer.succeed(Progress)({
83
+ emit: () => Effect.void
84
+ })
85
+
86
+ /** Sends every event to a callback. Used by the CLI renderer. */
87
+ export const layerCallback = (
88
+ onEvent: (event: ProgressEvent) => void
89
+ ): Layer.Layer<Progress> =>
90
+ Layer.succeed(Progress)({
91
+ emit: (event) => Effect.sync(() => onEvent(event))
92
+ })
93
+
94
+ /**
95
+ * Accumulates every event into a `Ref`, for assertions.
96
+ *
97
+ * Returned as `{ layer, events }` so a test can provide the layer and then read
98
+ * the transcript afterwards.
99
+ */
100
+ export const makeCollector = Effect.gen(function*() {
101
+ const ref = yield* Ref.make<ReadonlyArray<ProgressEvent>>([])
102
+ const layer = Layer.succeed(Progress)({
103
+ emit: (event: ProgressEvent) => Ref.update(ref, (events) => [...events, event])
104
+ })
105
+ return { layer, events: Ref.get(ref) } as const
106
+ })