generated from JS-DevTools/template-node-typescript
-
Notifications
You must be signed in to change notification settings - Fork 76
/
Copy pathread-manifest.ts
147 lines (124 loc) · 4.14 KB
/
read-manifest.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import fs from "node:fs/promises";
import path from "node:path";
import validatePackageName from "validate-npm-package-name";
import semverValid from "semver/functions/valid";
import { list as tarList } from "tar/list";
import type { ReadEntry } from "tar";
import * as errors from "./errors.js";
/** A package manifest (package.json) and associated details. */
export interface PackageManifest {
packageSpec: string;
name: string;
version: string;
scope: string | undefined;
publishConfig: PackagePublishConfig | undefined;
}
/** Any publish configuration defined in package.json. */
export interface PackagePublishConfig {
tag?: string;
access?: string;
registry?: string;
provenance?: boolean;
}
const SCOPE_RE = /^(@.+)\/.+$/u;
const MANIFEST_BASENAME = "package.json";
const TARBALL_EXTNAME = ".tgz";
const isManifest = (file: unknown): file is string => {
return typeof file === "string" && path.basename(file) === MANIFEST_BASENAME;
};
const isDirectory = (file: unknown): file is string => {
return typeof file === "string" && path.extname(file) === "";
};
const isTarball = (file: unknown): file is string => {
return typeof file === "string" && path.extname(file) === TARBALL_EXTNAME;
};
const normalizeVersion = (version: unknown): string | undefined => {
return semverValid(version as string) ?? undefined;
};
const validateName = (name: unknown): name is string => {
return validatePackageName(name as string).validForNewPackages;
};
const readPackageJson = async (...pathSegments: string[]): Promise<string> => {
const file = path.resolve(...pathSegments);
try {
return await fs.readFile(file, "utf8");
} catch (error) {
throw new errors.PackageJsonReadError(file, error);
}
};
const readTarballPackageJson = async (file: string): Promise<string> => {
const data: Buffer[] = [];
const onReadEntry = (entry: ReadEntry) => {
if (entry.path === "package/package.json") {
entry.on("data", (chunk) => data.push(chunk));
}
};
try {
await tarList({ file, onReadEntry });
if (data.length === 0) {
throw new Error("package.json not found inside archive");
}
} catch (error) {
throw new errors.PackageTarballReadError(file, error);
}
return Buffer.concat(data).toString();
};
/**
* Reads the package manifest (package.json) and returns its parsed contents.
*
* @param packagePath The path to the package being published.
* @returns The parsed package metadata.
*/
export async function readManifest(
packagePath: unknown
): Promise<PackageManifest> {
let packageSpec: string | undefined;
let manifestContents: string;
if (!packagePath) {
packageSpec = "";
manifestContents = await readPackageJson(MANIFEST_BASENAME);
} else if (isManifest(packagePath)) {
packageSpec = path.resolve(path.dirname(packagePath));
manifestContents = await readPackageJson(packagePath);
} else if (isDirectory(packagePath)) {
packageSpec = path.resolve(packagePath);
manifestContents = await readPackageJson(packagePath, MANIFEST_BASENAME);
} else if (isTarball(packagePath)) {
packageSpec = path.resolve(packagePath);
manifestContents = await readTarballPackageJson(packageSpec);
} else {
throw new errors.InvalidPackageError(packagePath);
}
let manifestJson: Record<string, unknown>;
let name: unknown;
let version: unknown;
let publishConfig: unknown;
try {
manifestJson = JSON.parse(manifestContents) as Record<string, unknown>;
name = manifestJson["name"];
version = normalizeVersion(manifestJson["version"]);
publishConfig = manifestJson["publishConfig"] ?? {};
} catch (error) {
throw new errors.PackageJsonParseError(packageSpec, error);
}
if (!validateName(name)) {
throw new errors.InvalidPackageNameError(name);
}
if (typeof version !== "string") {
throw new errors.InvalidPackageVersionError(manifestJson["version"]);
}
if (
typeof publishConfig !== "object" ||
Array.isArray(publishConfig) ||
!publishConfig
) {
throw new errors.InvalidPackagePublishConfigError(publishConfig);
}
return {
packageSpec,
name,
version,
publishConfig,
scope: SCOPE_RE.exec(name)?.[1],
};
}