@packall/core 0.1.0 → 0.3.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 (96) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -0
  3. package/dist/archive-DmF9XNl7.js +284 -0
  4. package/dist/archive-DmF9XNl7.js.map +1 -0
  5. package/dist/archive.d.ts +47 -15
  6. package/dist/archive.d.ts.map +1 -1
  7. package/dist/bundle.d.ts +6 -3
  8. package/dist/bundle.d.ts.map +1 -1
  9. package/dist/dependency-range.d.ts.map +1 -1
  10. package/dist/download.d.ts +2 -1
  11. package/dist/download.d.ts.map +1 -1
  12. package/dist/enums/artifact-kind.d.ts.map +1 -1
  13. package/dist/enums/edge-kind.d.ts.map +1 -1
  14. package/dist/enums/input-file-kind.d.ts.map +1 -1
  15. package/dist/enums/layout.d.ts.map +1 -1
  16. package/dist/enums/lockfile-format.d.ts.map +1 -1
  17. package/dist/enums/phase.d.ts.map +1 -1
  18. package/dist/errors.d.ts +11 -1
  19. package/dist/errors.d.ts.map +1 -1
  20. package/dist/index.d.ts +12 -10
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +169 -344
  23. package/dist/index.js.map +1 -1
  24. package/dist/input-file.d.ts.map +1 -1
  25. package/dist/integrity.d.ts +37 -5
  26. package/dist/integrity.d.ts.map +1 -1
  27. package/dist/layout.d.ts +41 -7
  28. package/dist/layout.d.ts.map +1 -1
  29. package/dist/locked-resolve.d.ts.map +1 -1
  30. package/dist/lockfile/bun.d.ts.map +1 -1
  31. package/dist/lockfile/detect.d.ts.map +1 -1
  32. package/dist/lockfile/json.d.ts.map +1 -1
  33. package/dist/lockfile/names.d.ts.map +1 -1
  34. package/dist/lockfile/npm.d.ts.map +1 -1
  35. package/dist/lockfile/parse.d.ts.map +1 -1
  36. package/dist/lockfile/pnpm.d.ts.map +1 -1
  37. package/dist/lockfile/tree-builder.d.ts.map +1 -1
  38. package/dist/lockfile/types.d.ts.map +1 -1
  39. package/dist/manifest.d.ts.map +1 -1
  40. package/dist/node/archiver.d.ts +5 -0
  41. package/dist/node/archiver.d.ts.map +1 -0
  42. package/dist/node/index.d.ts +13 -0
  43. package/dist/node/index.d.ts.map +1 -0
  44. package/dist/node/index.js +55 -0
  45. package/dist/node/index.js.map +1 -0
  46. package/dist/options.d.ts +3 -3
  47. package/dist/options.d.ts.map +1 -1
  48. package/dist/platform.d.ts +5 -10
  49. package/dist/platform.d.ts.map +1 -1
  50. package/dist/progress.d.ts.map +1 -1
  51. package/dist/registry.d.ts.map +1 -1
  52. package/dist/resolve.d.ts.map +1 -1
  53. package/dist/schemas/lenient.d.ts.map +1 -1
  54. package/dist/schemas/package-json.d.ts.map +1 -1
  55. package/dist/spec.d.ts.map +1 -1
  56. package/dist/types/value-of.d.ts.map +1 -1
  57. package/dist/utils/is-record.d.ts.map +1 -1
  58. package/package.json +11 -10
  59. package/src/archive.ts +95 -92
  60. package/src/bundle.ts +736 -616
  61. package/src/dependency-range.ts +121 -115
  62. package/src/download.ts +155 -133
  63. package/src/enums/artifact-kind.ts +5 -5
  64. package/src/enums/edge-kind.ts +13 -13
  65. package/src/enums/input-file-kind.ts +9 -9
  66. package/src/enums/layout.ts +20 -20
  67. package/src/enums/lockfile-format.ts +6 -6
  68. package/src/enums/phase.ts +8 -8
  69. package/src/errors.ts +211 -199
  70. package/src/index.ts +126 -113
  71. package/src/input-file.ts +246 -234
  72. package/src/integrity.ts +165 -121
  73. package/src/layout.ts +63 -29
  74. package/src/locked-resolve.ts +471 -461
  75. package/src/lockfile/bun.ts +214 -207
  76. package/src/lockfile/detect.ts +80 -80
  77. package/src/lockfile/json.ts +91 -92
  78. package/src/lockfile/names.ts +9 -9
  79. package/src/lockfile/npm.ts +328 -308
  80. package/src/lockfile/parse.ts +32 -32
  81. package/src/lockfile/pnpm.ts +230 -225
  82. package/src/lockfile/tree-builder.ts +116 -116
  83. package/src/lockfile/types.ts +52 -52
  84. package/src/manifest.ts +129 -133
  85. package/src/node/archiver.ts +85 -0
  86. package/src/node/index.ts +13 -0
  87. package/src/options.ts +84 -84
  88. package/src/platform.ts +97 -101
  89. package/src/progress.ts +105 -106
  90. package/src/registry.ts +116 -115
  91. package/src/resolve.ts +560 -537
  92. package/src/schemas/lenient.ts +67 -60
  93. package/src/schemas/package-json.ts +11 -10
  94. package/src/spec.ts +123 -119
  95. package/src/types/value-of.ts +1 -1
  96. package/src/utils/is-record.ts +1 -1
package/src/archive.ts CHANGED
@@ -1,92 +1,95 @@
1
- /**
2
- * Turning a staged directory tree into a gzipped tarball.
3
- *
4
- * Uses `node-tar`, the same library npm itself packs with. Writing a USTAR
5
- * encoder by hand would be a fun afternoon and a bad idea: scoped package
6
- * paths routinely exceed the 100-byte name field, so correctness depends on
7
- * prefix-splitting and PAX headers that `tar` already gets right.
8
- */
9
- import * as Effect from "effect/Effect"
10
- import * as FileSystem from "effect/FileSystem"
11
- import type { PlatformError } from "effect/PlatformError"
12
- import { create } from "tar"
13
- import { ArchiveError } from "./errors.js"
14
- import * as Progress from "./progress.js"
15
-
16
- /** A finished archive. */
17
- export type ArchiveResult = {
18
- readonly path: string
19
- readonly bytes: number
20
- }
21
-
22
- /**
23
- * Packs `entries` (paths relative to `cwd`) into a gzipped tar at `outPath`.
24
- *
25
- * `portable` normalises uid/gid/mtime so the same inputs produce byte-identical
26
- * output which matters when a security team wants to diff two bundles or
27
- * re-derive one from a manifest.
28
- */
29
- export const createArchive = (options: {
30
- readonly cwd: string
31
- readonly entries: ReadonlyArray<string>
32
- readonly outPath: string
33
- readonly gzipLevel?: number | undefined
34
- }): Effect.Effect<ArchiveResult, ArchiveError | PlatformError, FileSystem.FileSystem | Progress.Progress> =>
35
- Effect.gen(function*() {
36
- const fs = yield* FileSystem.FileSystem
37
-
38
- yield* Progress.emit({
39
- _tag: "ArchiveStarted",
40
- path: options.outPath,
41
- entryCount: options.entries.length
42
- })
43
-
44
- yield* Effect.tryPromise({
45
- try: () =>
46
- create(
47
- {
48
- gzip: options.gzipLevel === undefined ? true : { level: options.gzipLevel },
49
- file: options.outPath,
50
- cwd: options.cwd,
51
- portable: true,
52
- // Undo the `./` added below, so recorded entry names stay exactly
53
- // the registry paths the manifest lists.
54
- onWriteEntry: (entry) => {
55
- entry.path = entry.path.replace(/^\.\//, "")
56
- }
57
- },
58
- // Sorted so archive order is stable across runs and platforms.
59
- options.entries.toSorted().map(dotSlash)
60
- ),
61
- catch: (cause) => new ArchiveError(options.outPath, describe(cause), { cause })
62
- })
63
-
64
- const info = yield* fs.stat(options.outPath)
65
- const bytes = Number(info.size)
66
-
67
- yield* Progress.emit({ _tag: "ArchiveCompleted", path: options.outPath, bytes })
68
-
69
- return { path: options.outPath, bytes }
70
- })
71
-
72
- /**
73
- * Guards every entry against tar's `@` convention.
74
- *
75
- * In a tar file list, a leading `@` means "splice in the entries of this other
76
- * archive" — a GNU convention `node-tar` implements by stripping the `@` and
77
- * looking for what is left. Every scoped package is a top-level entry starting
78
- * with `@`, so this hit the bundler in both possible ways:
79
- *
80
- * - `@oxc-project` became `oxc-project`, which does not exist, and the run died
81
- * with an ENOENT naming a path that appears nowhere in the staging tree.
82
- * - `@esbuild` became `esbuild`, which *does* exist — the unscoped package of
83
- * the same name sitting right next to it. No error, and every scoped tarball
84
- * silently missing from a bundle that reported success. That is the dangerous
85
- * one: you would not find out until the install failed behind the firewall.
86
- *
87
- * `./@scope` is not subject to the convention and resolves identically.
88
- */
89
- const dotSlash = (entry: string): string => (entry.startsWith("./") ? entry : `./${entry}`)
90
-
91
- const describe = (cause: unknown): string =>
92
- cause instanceof Error ? cause.message : String(cause)
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
+ import * as Context from "effect/Context";
19
+ import * as Effect from "effect/Effect";
20
+ import type * as FileSystem from "effect/FileSystem";
21
+ import type { PlatformError } from "effect/PlatformError";
22
+
23
+ import type { ArchiveError } from "./errors.js";
24
+ import * as Progress from "./progress.js";
25
+
26
+ /** A finished archive. */
27
+ export type ArchiveResult = {
28
+ readonly path: string;
29
+ readonly bytes: number;
30
+ };
31
+
32
+ /** What to pack, and where to put it. */
33
+ export type CreateArchiveOptions = {
34
+ readonly cwd: string;
35
+ /** Entry paths, relative to `cwd`. */
36
+ readonly entries: ReadonlyArray<string>;
37
+ readonly outPath: string;
38
+ readonly gzipLevel?: number | undefined;
39
+ };
40
+
41
+ export declare namespace Archiver {
42
+ /** The packing backend. */
43
+ export type Service = {
44
+ /**
45
+ * Packs `entries` (paths relative to `cwd`) into a gzipped tar at
46
+ * `outPath`.
47
+ *
48
+ * Implementations must produce byte-identical output for identical inputs
49
+ * normalised uid/gid/mtime, entries in a stable order — because a security
50
+ * team diffing two bundles, or re-deriving one from its manifest, is a
51
+ * thing that actually happens.
52
+ */
53
+ readonly create: (
54
+ options: CreateArchiveOptions,
55
+ ) => Effect.Effect<ArchiveResult, ArchiveError | PlatformError, FileSystem.FileSystem>;
56
+ };
57
+ }
58
+
59
+ /** Service tag for archive creation. */
60
+ export class Archiver extends Context.Service<Archiver, Archiver.Service>()(
61
+ "@packall/core/Archiver",
62
+ ) {}
63
+
64
+ /**
65
+ * Packs an archive with whichever backend is installed.
66
+ *
67
+ * The progress events are emitted here rather than inside the backends, so
68
+ * every implementation reports identically and a new one cannot forget to.
69
+ */
70
+ export const createArchive = (
71
+ options: CreateArchiveOptions,
72
+ ): Effect.Effect<
73
+ ArchiveResult,
74
+ ArchiveError | PlatformError,
75
+ Archiver | FileSystem.FileSystem | Progress.Progress
76
+ > =>
77
+ Effect.gen(function* () {
78
+ const archiver = yield* Archiver;
79
+
80
+ yield* Progress.emit({
81
+ _tag: "ArchiveStarted",
82
+ path: options.outPath,
83
+ entryCount: options.entries.length,
84
+ });
85
+
86
+ const result = yield* archiver.create(options);
87
+
88
+ yield* Progress.emit({
89
+ _tag: "ArchiveCompleted",
90
+ path: result.path,
91
+ bytes: result.bytes,
92
+ });
93
+
94
+ return result;
95
+ });