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 +1 -1
- package/src/index.js +14 -97
- package/src/validation.d.ts +21 -0
- package/src/validation.js +146 -0
package/package.json
CHANGED
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 {
|
|
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 =
|
|
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" &&
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|