@packall/core 0.3.3 → 0.4.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/bundle.d.ts CHANGED
@@ -38,7 +38,7 @@ export type BundleArtifact = {
38
38
  readonly path: string;
39
39
  readonly bytes: number;
40
40
  readonly packageCount: number;
41
- /** Which spec this artifact covers. Absent for `single` and `dir`. */
41
+ /** Which spec this artifact covers. Only `per-spec` sets it. */
42
42
  readonly spec?: string | undefined;
43
43
  readonly version?: string | undefined;
44
44
  };
@@ -76,11 +76,12 @@ export type PlanSummary = {
76
76
  /**
77
77
  * The files a run will write, known before anything is downloaded.
78
78
  *
79
- * `dir` layout is the odd one: it merges a package tree into `outDir`, and
80
- * package paths are `name/-/name-version.tgz` the same package at the same
81
- * version is the same bytes, so an overlap there is idempotent rather than
82
- * destructive. Only the two summary files are genuinely replaced, and those are
83
- * the ones worth guarding.
79
+ * `dir` and `flat` are the odd ones: they merge a package tree into `outDir`
80
+ * rather than writing an archive. Either way a package's path is derived from
81
+ * its name and version, so the same package at the same version is the same
82
+ * bytes at the same path an overlap is idempotent rather than destructive.
83
+ * Only the two summary files are genuinely replaced, and those are the ones
84
+ * worth guarding.
84
85
  */
85
86
  export type PlannedOutput = {
86
87
  readonly file: string;
@@ -1 +1 @@
1
- {"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,12 @@ import type { ValueOf } from "../types/value-of.js";
3
3
  export declare const ArtifactKind: {
4
4
  readonly Archive: "archive";
5
5
  readonly Directory: "directory";
6
+ /**
7
+ * A loose file that is not an archive — the manifest and the import guide,
8
+ * which `dir` and `flat` write beside the tarballs rather than sealing
9
+ * inside one.
10
+ */
11
+ readonly File: "file";
6
12
  };
7
13
  export type ArtifactKind = ValueOf<typeof ArtifactKind>;
8
14
  //# sourceMappingURL=artifact-kind.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"artifact-kind.d.ts","sourceRoot":"","sources":["../../src/enums/artifact-kind.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD,uDAAuD;AACvD,eAAO,MAAM,YAAY;aACxB,OAAO,EAAE,SAAS;aAClB,SAAS,EAAE,WAAW;CACb,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,YAAY,CAAC,CAAC"}
1
+ {"version":3,"file":"artifact-kind.d.ts","sourceRoot":"","sources":["../../src/enums/artifact-kind.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD,uDAAuD;AACvD,eAAO,MAAM,YAAY;aACxB,OAAO,EAAE,SAAS;aAClB,SAAS,EAAE,WAAW;IACtB;;;;OAIG;aACH,IAAI,EAAE,MAAM;CACH,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,YAAY,CAAC,CAAC"}
@@ -17,6 +17,12 @@ export declare const Layout: {
17
17
  * `jf rt upload` or rsync.
18
18
  */
19
19
  readonly Dir: "dir";
20
+ /**
21
+ * Every tarball side by side in one directory — no archive, and no
22
+ * `name/-/` nesting. For tooling that globs `*.tgz` out of one directory
23
+ * rather than walking a registry tree.
24
+ */
25
+ readonly Flat: "flat";
20
26
  };
21
27
  export type Layout = ValueOf<typeof Layout>;
22
28
  export declare const Layouts: ReadonlyArray<Layout>;
@@ -1 +1 @@
1
- {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/enums/layout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD,+CAA+C;AAC/C,eAAO,MAAM,MAAM;IAClB;;;;OAIG;aACH,OAAO,EAAE,UAAU;IACnB;;;OAGG;aACH,MAAM,EAAE,QAAQ;IAChB;;;OAGG;aACH,GAAG,EAAE,KAAK;CACD,CAAC;AAEX,MAAM,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,MAAM,CAAC,CAAC;AAE5C,eAAO,MAAM,OAAO,EAAE,aAAa,CAAC,MAAM,CAAyB,CAAC"}
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/enums/layout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD,+CAA+C;AAC/C,eAAO,MAAM,MAAM;IAClB;;;;OAIG;aACH,OAAO,EAAE,UAAU;IACnB;;;OAGG;aACH,MAAM,EAAE,QAAQ;IAChB;;;OAGG;aACH,GAAG,EAAE,KAAK;IACV;;;;OAIG;aACH,IAAI,EAAE,MAAM;CACH,CAAC;AAEX,MAAM,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,MAAM,CAAC,CAAC;AAE5C,eAAO,MAAM,OAAO,EAAE,aAAa,CAAC,MAAM,CAAyB,CAAC"}
package/dist/index.js CHANGED
@@ -15,7 +15,8 @@ import * as Path from "effect/Path";
15
15
  /** What a bundle run wrote to the output directory. */
16
16
  const ArtifactKind = {
17
17
  Archive: "archive",
18
- Directory: "directory"
18
+ Directory: "directory",
19
+ File: "file"
19
20
  };
20
21
 
21
22
  //#endregion
@@ -56,7 +57,8 @@ const InputFileKind = {
56
57
  const Layout = {
57
58
  PerSpec: "per-spec",
58
59
  Single: "single",
59
- Dir: "dir"
60
+ Dir: "dir",
61
+ Flat: "flat"
60
62
  };
61
63
  const Layouts = Object.values(Layout);
62
64
 
@@ -660,22 +662,6 @@ const strengthByAlgorithm = {
660
662
 
661
663
  //#endregion
662
664
  //#region src/layout.ts
663
- /**
664
- * The on-disk shape of a bundle.
665
- *
666
- * Everything is laid out exactly the way a registry serves it:
667
- *
668
- * lodash/-/lodash-4.17.21.tgz
669
- * @babel/core/-/core-7.24.0.tgz
670
- *
671
- * That is not an Artifactory convention — it is the path structure in every
672
- * `dist.tarball` URL npm publishes. See `notes/requirements.md` § FR-12 for
673
- * what that buys.
674
- *
675
- * The one shape worth noticing before editing anything here: a scoped
676
- * package's file name drops the scope, so `@babel/core` becomes
677
- * `core-7.24.0.tgz` under an `@babel/core/-/` directory.
678
- */
679
665
  /** Splits `@scope/name` into its parts. `scope` is undefined when unscoped. */
680
666
  const splitName = (name) => {
681
667
  if (!name.startsWith("@")) return {
@@ -708,7 +694,7 @@ const MANIFEST_FILE = "bundle-manifest.json";
708
694
  /** File name of the short import guide that ships inside every bundle. */
709
695
  const README_FILE = "IMPORT.md";
710
696
  /**
711
- * Name of the archive produced for one root spec in `per-spec` layout.
697
+ * A flat, collision-free file name for one `name@version`.
712
698
  *
713
699
  * It is the spec, with the one character a file name cannot hold taken out:
714
700
  * `@types/react@19.2.18` becomes `@types-react@19.2.18.tgz`. So the name of the
@@ -738,18 +724,41 @@ const README_FILE = "IMPORT.md";
738
724
  * has to cross-reference against a manifest.
739
725
  *
740
726
  * It does not make every collision impossible — `@a/b-c` and `@a-b/c` still
741
- * meet — so `assertDistinctOutputs` still runs. That pair is pathological; the
742
- * scoped-against-unscoped one is `@babel/core` beside `babel-core`, which is a
743
- * real migration somebody could be bundling.
727
+ * meet — so the run still checks before downloading anything. That pair is
728
+ * pathological; the scoped-against-unscoped one is `@babel/core` beside
729
+ * `babel-core`, which is a real migration somebody could be bundling.
744
730
  *
745
731
  * The leading `@` is safe here in a way it is not inside the archive: tar's
746
732
  * splice convention applies to the *file list* it is given, which is what
747
- * `archive.ts` guards with `./`. This is the output path, not an entry.
733
+ * `archive.ts` guards with `./`. These are output paths, not entries.
748
734
  */
749
- const perSpecArchiveName = (name, version) => {
735
+ const flatName = (name, version) => {
750
736
  const { scope, bare } = splitName(name);
751
737
  return `${scope === void 0 ? bare : `${scope}-${bare}`}@${version}.tgz`;
752
738
  };
739
+ /**
740
+ * Name of the archive produced for one root spec in `per-spec` layout.
741
+ *
742
+ * The same rule as {@link flatName}, which is where the reasoning lives — a
743
+ * per-spec archive and a `flat` package tarball are both "one file in a
744
+ * directory, named for what is in it", and there is no reason for a directory
745
+ * of bundles and a directory of packages to be read differently.
746
+ */
747
+ const perSpecArchiveName = flatName;
748
+ /**
749
+ * The path of one package tarball within a bundle. The one place the two
750
+ * shapes are chosen between, so a manifest and the tree it describes cannot
751
+ * drift apart.
752
+ *
753
+ * `flat` keeps a file name and no directories, and uses {@link flatName}
754
+ * rather than the registry file name: with the directories gone that name is
755
+ * all there is to tell two packages apart, and the registry one drops the
756
+ * scope, which would put `@types/node` and `node` on the same file. The name
757
+ * is free to differ — `npm publish <tarball>` reads a package's name and
758
+ * version from the `package.json` inside it, not from what the file is
759
+ * called.
760
+ */
761
+ const bundlePath = (layout, name, version) => layout === Layout.Flat ? flatName(name, version) : packagePath(name, version);
753
762
  /** Name of the archive produced in `single` layout. */
754
763
  const singleArchiveName = (base = "bundle") => `${base}.tgz`;
755
764
  /**
@@ -757,8 +766,14 @@ const singleArchiveName = (base = "bundle") => `${base}.tgz`;
757
766
  *
758
767
  * Kept short and copy-pasteable on purpose: whoever opens this is mid-task on a
759
768
  * restricted network and does not want prose.
769
+ *
770
+ * Layout-aware, because the instructions genuinely differ — a registry-layout
771
+ * tree uploads into Artifactory as it stands and a flat directory does not, and
772
+ * a guide that said otherwise would be discovered to be wrong by somebody who
773
+ * can no longer look anything up.
760
774
  */
761
- const importGuide = (options) => `# Importing this bundle
775
+ const importGuide = (options) => options.layout === Layout.Flat ? flatGuide(options) : treeGuide(options);
776
+ const treeGuide = (options) => `# Importing this bundle
762
777
 
763
778
  ${options.packageCount} package tarball(s), laid out exactly as an npm registry serves them:
764
779
 
@@ -786,6 +801,32 @@ Publish each tarball individually:
786
801
 
787
802
  node -e "const m=require('./bundle/${MANIFEST_FILE}');console.log(m.packages.length+' packages')"
788
803
 
804
+ Every entry in ${MANIFEST_FILE} carries the integrity string the source registry
805
+ advertised, and each tarball was checked against it at download time.
806
+ `;
807
+ const flatGuide = (options) => `# Importing this bundle
808
+
809
+ ${options.packageCount} package tarball(s), side by side in this one directory:
810
+
811
+ <name>@<version>.tgz
812
+ @<scope>-<name>@<version>.tgz
813
+
814
+ Created ${options.createdAt} by packall ${options.toolVersion}.
815
+ See ${MANIFEST_FILE} for the full list with checksums.
816
+
817
+ ## Publishing into a registry
818
+
819
+ \`npm publish\` reads each package's name and version from the \`package.json\`
820
+ inside its tarball, so neither the file names nor the order matter:
821
+
822
+ for f in *.tgz; do npm publish --registry <url> "$f"; done
823
+
824
+ For a tree that uploads into Artifactory as it stands, use --layout dir instead.
825
+
826
+ ## Verifying before import
827
+
828
+ node -e "const m=require('./${MANIFEST_FILE}');console.log(m.packages.length+' packages')"
829
+
789
830
  Every entry in ${MANIFEST_FILE} carries the integrity string the source registry
790
831
  advertised, and each tarball was checked against it at download time.
791
832
  `;
@@ -804,7 +845,7 @@ const buildManifest = (input) => {
804
845
  return {
805
846
  name: pkg.name,
806
847
  version: pkg.version,
807
- path: packagePath(pkg.name, pkg.version),
848
+ path: bundlePath(input.options.layout, pkg.name, pkg.version),
808
849
  tarball: pkg.manifest.dist.tarball,
809
850
  integrity: pkg.manifest.dist.integrity,
810
851
  shasum: pkg.manifest.dist.shasum,
@@ -2554,7 +2595,7 @@ const bundleResolved = (resolution, options) => Effect.gen(function* () {
2554
2595
  * first second rather than after a twenty-minute transfer.
2555
2596
  */
2556
2597
  const plannedOutputs = (resolution, options) => {
2557
- if (options.layout === Layout.Dir) return [{ file: MANIFEST_FILE }, { file: README_FILE }];
2598
+ if (options.layout === Layout.Dir || options.layout === Layout.Flat) return [{ file: MANIFEST_FILE }, { file: README_FILE }];
2558
2599
  if (options.layout === Layout.Single) return [{ file: singleArchiveName(options.archiveName ?? "bundle") }];
2559
2600
  return perSpecTargets(resolution).map(({ root, version }) => ({
2560
2601
  file: perSpecArchiveName(root.spec.name, version),
@@ -2609,6 +2650,7 @@ const emit$1 = (input, staging) => {
2609
2650
  case Layout.Single: return emitSingle(input);
2610
2651
  case Layout.Dir: return emitDirectory(input);
2611
2652
  case Layout.PerSpec: return emitPerSpec(input, staging);
2653
+ case Layout.Flat: return emitFlat(input, staging);
2612
2654
  }
2613
2655
  };
2614
2656
  /** One tarball containing the deduplicated union of every closure. */
@@ -2658,6 +2700,47 @@ const emitDirectory = (input) => Effect.gen(function* () {
2658
2700
  }];
2659
2701
  });
2660
2702
  /**
2703
+ * The flat directory: every tarball side by side, no archive, no nesting.
2704
+ *
2705
+ * The tarballs are the untouched registry ones — only their names are ours,
2706
+ * and `bundlePath` says why.
2707
+ */
2708
+ const emitFlat = (input, staging) => Effect.gen(function* () {
2709
+ const fs = yield* FileSystem.FileSystem;
2710
+ const path = yield* Path.Path;
2711
+ const flatDir = path.join(staging, "flat");
2712
+ yield* fs.makeDirectory(flatDir, { recursive: true });
2713
+ for (const pkg of input.resolution.packages) {
2714
+ const parts = packagePath(pkg.name, pkg.version).split("/");
2715
+ yield* linkOrCopy(path.join(input.packagesDir, ...parts), path.join(flatDir, flatName(pkg.name, pkg.version)));
2716
+ }
2717
+ yield* writeBundleMetadata({
2718
+ dir: flatDir,
2719
+ resolution: input.resolution,
2720
+ included: input.resolution.packages,
2721
+ options: input.options,
2722
+ registryInfo: input.registryInfo,
2723
+ sizes: input.report.sizes
2724
+ });
2725
+ yield* fs.copy(flatDir, input.options.outDir, { overwrite: true }).pipe(Effect.mapError((cause) => new OutputError(input.options.outDir, "could not write output tree", { cause })));
2726
+ const tarballs = input.resolution.packages.map((pkg) => ({
2727
+ kind: ArtifactKind.Archive,
2728
+ path: path.join(input.options.outDir, flatName(pkg.name, pkg.version)),
2729
+ bytes: input.report.sizes.get(`${pkg.name}@${pkg.version}`) ?? 0,
2730
+ packageCount: 1
2731
+ }));
2732
+ const summaries = yield* Effect.forEach([MANIFEST_FILE, README_FILE], (file) => Effect.gen(function* () {
2733
+ const info = yield* fs.stat(path.join(flatDir, file));
2734
+ return {
2735
+ kind: ArtifactKind.File,
2736
+ path: path.join(input.options.outDir, file),
2737
+ bytes: Number(info.size),
2738
+ packageCount: 0
2739
+ };
2740
+ }));
2741
+ return [...tarballs, ...summaries];
2742
+ });
2743
+ /**
2661
2744
  * One tarball per resolved root *version*.
2662
2745
  *
2663
2746
  * Under `--all-versions react@^18` this produces `react-18.0.0.tgz`,
@@ -2743,7 +2826,8 @@ const writeBundleMetadata = (input) => Effect.gen(function* () {
2743
2826
  yield* fs.writeFileString(path.join(input.dir, README_FILE), importGuide({
2744
2827
  packageCount: input.included.length,
2745
2828
  createdAt: createdAt.toISOString(),
2746
- toolVersion: input.options.toolVersion
2829
+ toolVersion: input.options.toolVersion,
2830
+ layout: input.options.layout
2747
2831
  }));
2748
2832
  });
2749
2833
  /** Sorted top-level entries of a directory, used as the tar entry list. */
@@ -2751,6 +2835,28 @@ const topLevelEntries = (dir) => Effect.gen(function* () {
2751
2835
  return (yield* (yield* FileSystem.FileSystem).readDirectory(dir)).toSorted();
2752
2836
  });
2753
2837
  /**
2838
+ * `assertDistinctOutputs`, over package tarballs instead of archives.
2839
+ *
2840
+ * `flat` is the only layout with nothing but a file name to tell two packages
2841
+ * apart — everywhere else a tarball keeps its unique `name/-/` directory. What
2842
+ * is left to catch is the pathological pair, `@a/b-c` against `@a-b/c`; the
2843
+ * plausible one is impossible by construction, which is what `flatName` buys.
2844
+ */
2845
+ const assertDistinctFlatNames = (resolution, options) => Effect.gen(function* () {
2846
+ if (options.layout !== Layout.Flat) return;
2847
+ const owners = /* @__PURE__ */ new Map();
2848
+ for (const pkg of resolution.packages) {
2849
+ const file = flatName(pkg.name, pkg.version);
2850
+ const claimed = owners.get(file);
2851
+ if (claimed === void 0) owners.set(file, [`${pkg.name}@${pkg.version}`]);
2852
+ else claimed.push(`${pkg.name}@${pkg.version}`);
2853
+ }
2854
+ const collisions = [...owners].filter(([, packages]) => packages.length > 1);
2855
+ if (collisions.length === 0) return;
2856
+ const described = collisions.map(([file, packages]) => `${packages.join(" and ")} would both be ${file}`).join("; ");
2857
+ return yield* Effect.fail(new OutputError(options.outDir, `two packages want the same file name — ${described}. \`flat\` layout has only the file name to tell them apart, and the \`/\` in a scope becomes a \`-\` to make one. Use --layout dir, which keeps each package in its own directory.`));
2858
+ });
2859
+ /**
2754
2860
  * Two different packages must not want the same file name.
2755
2861
  *
2756
2862
  * Most of what this used to catch is now impossible: an archive is named for
@@ -2810,6 +2916,7 @@ const prepareOutputDir = (resolution, options) => Effect.gen(function* () {
2810
2916
  const path = yield* Path.Path;
2811
2917
  const outDir = options.outDir;
2812
2918
  yield* assertDistinctOutputs(resolution, options);
2919
+ yield* assertDistinctFlatNames(resolution, options);
2813
2920
  if (!options.force) {
2814
2921
  const clashes = [];
2815
2922
  for (const planned of plannedOutputs(resolution, options)) {