ts-node-pack 0.2.0 → 0.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-node-pack",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Pack a TypeScript package into a Node-compatible npm tarball without modifying the source tree",
5
5
  "keywords": [
6
6
  "cli",
package/src/index.js CHANGED
@@ -15,7 +15,8 @@ import { basename, dirname, join, relative, resolve } from "node:path";
15
15
  import { promisify } from "node:util";
16
16
  import packlist from "npm-packlist";
17
17
  import tsBlankSpace from "ts-blank-space";
18
- import { TS_SPECIFIER_PATTERNS, rewriteTsSpecifiers } from "./rewrite-specifiers.js";
18
+ import { rewriteTsSpecifiers } from "./rewrite-specifiers.js";
19
+ import { validate } from "./validation.js";
19
20
 
20
21
  const execFileAsync = promisify(execFile);
21
22
 
@@ -595,7 +596,7 @@ export function rewritePackageJson(pkg) {
595
596
  } else if (result.typings) {
596
597
  result.typings = rewriteTsToDts(result.typings);
597
598
  } else if (mainWasTs) {
598
- result.types = result.main.replace(/\.js$/, ".d.ts");
599
+ result.types = rewriteTsToDts(originalMain);
599
600
  }
600
601
 
601
602
  // Rewrite bin
@@ -617,7 +618,7 @@ export function rewritePackageJson(pkg) {
617
618
  // Rewrite files array
618
619
  if (Array.isArray(result.files)) {
619
620
  result.files = result.files.flatMap((f) => {
620
- if (typeof f === "string" && /\.tsx?$/.test(f)) {
621
+ if (typeof f === "string" && TS_SOURCE_EXT_RE.test(f)) {
621
622
  return [rewriteTsToJs(f), rewriteTsToDts(f)];
622
623
  }
623
624
  return [f];
@@ -627,14 +628,22 @@ export function rewritePackageJson(pkg) {
627
628
  return result;
628
629
  }
629
630
 
631
+ // `.ts`/`.tsx` → `.js`; `.mts` → `.mjs`. Matches what `ts-blank-space`
632
+ // writes in Phase 6 and the specifier-rewrite pass in rewrite-specifiers.ts.
630
633
  function rewriteTsToJs(p) {
631
- return typeof p === "string" ? p.replace(/\.tsx?$/, ".js") : p;
634
+ if (typeof p !== "string") return p;
635
+ return p.replace(/\.mts$/, ".mjs").replace(/\.tsx?$/, ".js");
632
636
  }
633
637
 
638
+ // `.ts`/`.tsx` → `.d.ts`; `.mts` → `.d.mts`. Matches the declaration files
639
+ // tsc emits from an .mts source.
634
640
  function rewriteTsToDts(p) {
635
- return typeof p === "string" ? p.replace(/\.tsx?$/, ".d.ts") : p;
641
+ if (typeof p !== "string") return p;
642
+ return p.replace(/\.mts$/, ".d.mts").replace(/\.tsx?$/, ".d.ts");
636
643
  }
637
644
 
645
+ const TS_SOURCE_EXT_RE = /\.(tsx?|mts)$/;
646
+
638
647
  function rewriteExportsValue(value, isTypesKey) {
639
648
  if (typeof value === "string") {
640
649
  return isTypesKey ? rewriteTsToDts(value) : rewriteTsToJs(value);
@@ -652,98 +661,6 @@ function rewriteExportsValue(value, isTypesKey) {
652
661
  return value;
653
662
  }
654
663
 
655
- async function validate(stagingDir, pkg, log) {
656
- const errors = [];
657
-
658
- // Check .js and .d.ts files for remaining .ts specifiers
659
- const allFiles = await findFiles(
660
- stagingDir,
661
- (name) => name.endsWith(".js") || name.endsWith(".d.ts"),
662
- );
663
-
664
- for (const filePath of allFiles) {
665
- const raw = await readFile(filePath, "utf8");
666
- // Strip block and line comments before scanning so JSDoc examples that
667
- // literally contain strings like `import './foo.js'` don't register as
668
- // false positives. Comment stripping here is intentionally coarse
669
- // (doesn't understand strings-containing-comment-markers) — good enough
670
- // for tsc-emitted output, where comments are well-behaved.
671
- const content = raw.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1");
672
- const relPath = relative(stagingDir, filePath);
673
-
674
- for (const pattern of TS_SPECIFIER_PATTERNS) {
675
- for (const m of content.matchAll(pattern)) {
676
- errors.push(`${relPath}: remaining .ts specifier: ${m[0]}`);
677
- }
678
- }
679
- }
680
-
681
- // Check package.json for .ts references in entry points
682
- const entryFields = ["main", "module", "types", "typings"];
683
- for (const field of entryFields) {
684
- if (typeof pkg[field] === "string" && /(?<!\.d)\.tsx?$/.test(pkg[field])) {
685
- errors.push(`package.json "${field}" still references .ts: ${pkg[field]}`);
686
- }
687
- }
688
-
689
- // Non-fatal: yarn/npm tolerate `bin`/`main` pointing at missing
690
- // files. Real transformation bugs are caught by the specifier
691
- // checks above.
692
- const referencedFiles = collectReferencedFiles(pkg);
693
- for (const ref of referencedFiles) {
694
- if (!existsSync(join(stagingDir, ref))) {
695
- log(` Warning: referenced file missing from tarball: ${ref}`);
696
- }
697
- }
698
-
699
- if (errors.length > 0) {
700
- const msg = "Validation failed:\n " + errors.join("\n ");
701
- throw new Error(msg);
702
- }
703
-
704
- log(`Validated ${allFiles.length} file(s), no issues found`);
705
- }
706
-
707
- function collectReferencedFiles(pkg) {
708
- const refs = new Set ();
709
-
710
- for (const field of ["main", "module", "types", "typings"]) {
711
- if (typeof pkg[field] === "string") {
712
- refs.add(pkg[field].replace(/^\.\//, ""));
713
- }
714
- }
715
-
716
- if (typeof pkg.bin === "string") {
717
- refs.add(pkg.bin.replace(/^\.\//, ""));
718
- } else if (typeof pkg.bin === "object" && pkg.bin !== null) {
719
- for (const v of Object.values(pkg.bin)) {
720
- if (typeof v === "string") refs.add(v.replace(/^\.\//, ""));
721
- }
722
- }
723
-
724
- if (pkg.exports) {
725
- collectExportsRefs(pkg.exports, refs);
726
- }
727
-
728
- return refs;
729
- }
730
-
731
- function collectExportsRefs(value, refs) {
732
- if (typeof value === "string") {
733
- refs.add(value.replace(/^\.\//, ""));
734
- return;
735
- }
736
- if (Array.isArray(value)) {
737
- for (const v of value) collectExportsRefs(v, refs);
738
- return;
739
- }
740
- if (typeof value === "object" && value !== null) {
741
- for (const v of Object.values(value)) {
742
- collectExportsRefs(v, refs);
743
- }
744
- }
745
- }
746
-
747
664
  async function pack(stagingDir) {
748
665
  const { stdout } = await execFileAsync("npm", ["pack"], {
749
666
  cwd: stagingDir,
@@ -0,0 +1,21 @@
1
+ export declare function validate(stagingDir: any, pkg: any, log?: (...args: unknown[]) => void): Promise<void>;
2
+ /**
3
+ * Collect the relative file paths referenced by path-bearing fields of a
4
+ * package.json manifest, partitioned by how strictly a missing target
5
+ * should be treated.
6
+ *
7
+ * - `strict`: `main`, `module`, `types`, `typings`, and every leaf of
8
+ * `exports`. A missing target here is a bug in the tarball we produced —
9
+ * Node's module resolution will fail and `npm publish` warns on
10
+ * dangling `types`.
11
+ * - `lenient`: `bin` entries. yarn and npm both tolerate a `bin` pointing
12
+ * at a missing file (see the `bin-missing` fixture, which mirrors
13
+ * agoric/portfolio-api's intentional shape).
14
+ *
15
+ * Exported for direct unit-testing of the policy; the only runtime caller
16
+ * is `validate()` above.
17
+ */
18
+ export declare function collectReferencedFiles(pkg: any): {
19
+ strict: Set<string>;
20
+ lenient: Set<string>;
21
+ };
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Post-rewrite validation of a staged package. `validate()` is the one
3
+ * caller of `collectReferencedFiles`; both live here because the
4
+ * strict-vs-lenient partition is a policy decision owned by validation.
5
+ */
6
+ import { existsSync } from "node:fs";
7
+ import { readdir, readFile } from "node:fs/promises";
8
+ import { join, relative } from "node:path";
9
+ import { TS_SPECIFIER_PATTERNS } from "./rewrite-specifiers.js";
10
+
11
+ export async function validate(
12
+ stagingDir,
13
+ pkg,
14
+ log = () => {},
15
+ ) {
16
+ const errors = [];
17
+
18
+ // Check .js/.mjs and .d.ts/.d.mts files for remaining .ts specifiers
19
+ const allFiles = await findFiles(
20
+ stagingDir,
21
+ (name) =>
22
+ name.endsWith(".js") ||
23
+ name.endsWith(".mjs") ||
24
+ name.endsWith(".d.ts") ||
25
+ name.endsWith(".d.mts"),
26
+ );
27
+
28
+ for (const filePath of allFiles) {
29
+ const raw = await readFile(filePath, "utf8");
30
+ // Strip block and line comments before scanning so JSDoc examples that
31
+ // literally contain strings like `import './foo.js'` don't register as
32
+ // false positives. Comment stripping here is intentionally coarse
33
+ // (doesn't understand strings-containing-comment-markers) — good enough
34
+ // for tsc-emitted output, where comments are well-behaved.
35
+ const content = raw.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1");
36
+ const relPath = relative(stagingDir, filePath);
37
+
38
+ for (const pattern of TS_SPECIFIER_PATTERNS) {
39
+ for (const m of content.matchAll(pattern)) {
40
+ errors.push(`${relPath}: remaining .ts specifier: ${m[0]}`);
41
+ }
42
+ }
43
+ }
44
+
45
+ // Check package.json for .ts references in entry points
46
+ const entryFields = ["main", "module", "types", "typings"];
47
+ for (const field of entryFields) {
48
+ if (typeof pkg[field] === "string" && /(?<!\.d)\.(tsx?|mts)$/.test(pkg[field])) {
49
+ errors.push(`package.json "${field}" still references .ts: ${pkg[field]}`);
50
+ }
51
+ }
52
+
53
+ // main/module/types/typings/exports must resolve to a file that is
54
+ // actually in the staging dir. This catches rewrite bugs that specifier
55
+ // scanning can't — e.g. the @endo/ses-ava regression where a synthesized
56
+ // `types: ./index.d.ts` pointed at a .d.ts ts-node-pack never emitted.
57
+ // `bin` is deliberately excluded: yarn/npm both tolerate a missing bin
58
+ // target (see the bin-missing fixture — agoric/portfolio-api ships that
59
+ // way intentionally), so we only warn.
60
+ const { strict: strictRefs, lenient: lenientRefs } = collectReferencedFiles(pkg);
61
+ for (const ref of strictRefs) {
62
+ if (!existsSync(join(stagingDir, ref))) {
63
+ errors.push(`referenced file missing from tarball: ${ref}`);
64
+ }
65
+ }
66
+ for (const ref of lenientRefs) {
67
+ if (!existsSync(join(stagingDir, ref))) {
68
+ log(` Warning: referenced file missing from tarball: ${ref}`);
69
+ }
70
+ }
71
+
72
+ if (errors.length > 0) {
73
+ const msg = "Validation failed:\n " + errors.join("\n ");
74
+ throw new Error(msg);
75
+ }
76
+
77
+ log(`Validated ${allFiles.length} file(s), no issues found`);
78
+ }
79
+
80
+ /**
81
+ * Collect the relative file paths referenced by path-bearing fields of a
82
+ * package.json manifest, partitioned by how strictly a missing target
83
+ * should be treated.
84
+ *
85
+ * - `strict`: `main`, `module`, `types`, `typings`, and every leaf of
86
+ * `exports`. A missing target here is a bug in the tarball we produced —
87
+ * Node's module resolution will fail and `npm publish` warns on
88
+ * dangling `types`.
89
+ * - `lenient`: `bin` entries. yarn and npm both tolerate a `bin` pointing
90
+ * at a missing file (see the `bin-missing` fixture, which mirrors
91
+ * agoric/portfolio-api's intentional shape).
92
+ *
93
+ * Exported for direct unit-testing of the policy; the only runtime caller
94
+ * is `validate()` above.
95
+ */
96
+ export function collectReferencedFiles(pkg) {
97
+ const strict = new Set ();
98
+ const lenient = new Set ();
99
+ const addStrict = (p) => strict.add(stripDotSlash(p));
100
+ const addLenient = (p) => lenient.add(stripDotSlash(p));
101
+
102
+ for (const field of ["main", "module", "types", "typings"]) {
103
+ if (typeof pkg[field] === "string") addStrict(pkg[field]);
104
+ }
105
+
106
+ if (typeof pkg.bin === "string") {
107
+ addLenient(pkg.bin);
108
+ } else if (typeof pkg.bin === "object" && pkg.bin !== null) {
109
+ for (const v of Object.values(pkg.bin)) {
110
+ if (typeof v === "string") addLenient(v);
111
+ }
112
+ }
113
+
114
+ if (pkg.exports) collectExportsRefs(pkg.exports, strict);
115
+
116
+ return { strict, lenient };
117
+ }
118
+
119
+ function collectExportsRefs(value, refs) {
120
+ if (typeof value === "string") {
121
+ refs.add(stripDotSlash(value));
122
+ return;
123
+ }
124
+ if (Array.isArray(value)) {
125
+ for (const v of value) collectExportsRefs(v, refs);
126
+ return;
127
+ }
128
+ if (typeof value === "object" && value !== null) {
129
+ for (const v of Object.values(value)) collectExportsRefs(v, refs);
130
+ }
131
+ }
132
+
133
+ function stripDotSlash(p) {
134
+ return p.replace(/^\.\//, "");
135
+ }
136
+
137
+ async function findFiles(dir, test) {
138
+ const results = [];
139
+ const entries = await readdir(dir, { withFileTypes: true, recursive: true });
140
+ for (const entry of entries) {
141
+ if (entry.isFile() && test(entry.name)) {
142
+ results.push(join(entry.parentPath, entry.name));
143
+ }
144
+ }
145
+ return results;
146
+ }