-
Notifications
You must be signed in to change notification settings - Fork 89
/
Copy pathPackageUtil.ts
141 lines (125 loc) · 4.29 KB
/
PackageUtil.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
import { createReadStream } from 'node:fs';
import { Readable } from 'node:stream';
import { pipeline } from 'node:stream/promises';
import * as ssri from 'ssri';
// @ts-expect-error type error
import tar from '@fengmk2/tar';
import type { AuthorType, PackageJSONType } from '../repository/PackageRepository.js';
// /@cnpm%2ffoo
// /@cnpm%2Ffoo
// /@cnpm/foo
// /foo
// name max length is 214 chars
// https://www.npmjs.com/package/path-to-regexp#custom-matching-parameters
export const FULLNAME_REG_STRING = '@[^/]{1,220}/[^/]{1,220}|@[^%]+%2[fF][^/]{1,220}|[^@/]{1,220}';
export function getScopeAndName(fullname: string): string[] {
if (fullname.startsWith('@')) {
return fullname.split('/', 2);
}
return [ '', fullname ];
}
export function getFullname(scope: string, name: string): string {
return scope ? `${scope}/${name}` : name;
}
export function cleanUserPrefix(username: string): string {
return username.replace(/^.*:/, '');
}
export function getPrefixedName(prefix: string, username: string): string {
return prefix ? `${prefix}${username}` : username;
}
export async function calculateIntegrity(contentOrFile: Uint8Array | string) {
let integrityObj;
if (typeof contentOrFile === 'string') {
integrityObj = await ssri.fromStream(createReadStream(contentOrFile), {
algorithms: [ 'sha512', 'sha1' ],
});
} else {
integrityObj = ssri.fromData(contentOrFile, {
algorithms: [ 'sha512', 'sha1' ],
});
}
const integrity = integrityObj.sha512[0].toString() as string;
const shasum = integrityObj.sha1[0].hexDigest() as string;
return { integrity, shasum };
}
export function formatTarball(registry: string, scope: string, name: string, version: string) {
const fullname = getFullname(scope, name);
return `${registry}/${fullname}/-/${name}-${version}.tgz`;
}
export function detectInstallScript(manifest: any) {
// https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md#abbreviated-version-object
let hasInstallScript = false;
const scripts = manifest.scripts;
if (scripts) {
// https://www.npmjs.com/package/fix-has-install-script
if (scripts.install || scripts.preinstall || scripts.postinstall) {
hasInstallScript = true;
}
}
return hasInstallScript;
}
/** 判断一个版本压缩包中是否包含 npm-shrinkwrap.json */
export async function hasShrinkWrapInTgz(contentOrFile: Uint8Array | string): Promise<boolean> {
let readable: Readable;
if (typeof contentOrFile === 'string') {
readable = createReadStream(contentOrFile);
} else {
readable = new Readable({
read() {
this.push(contentOrFile);
this.push(null);
},
});
}
let hasShrinkWrap = false;
const abortController = new AbortController();
const parser = tar.t({
// options.strict 默认为 false,会忽略 Recoverable errors,例如 tar 解析失败
// 详见 https://github.com/isaacs/node-tar#warnings-and-errors
onentry(entry: any) {
if (entry.path === 'package/npm-shrinkwrap.json') {
hasShrinkWrap = true;
abortController.abort();
}
},
});
try {
await pipeline(readable, parser, { signal: abortController.signal });
return hasShrinkWrap;
} catch (e) {
if (e.code === 'ABORT_ERR') {
return hasShrinkWrap;
}
throw Object.assign(new Error('[hasShrinkWrapInTgz] Fail to parse input file'), { cause: e });
}
}
/** 写入 ES 时,格式化 author */
export function formatAuthor(author: string | AuthorType | undefined): AuthorType | undefined {
if (author === undefined) {
return author;
}
if (typeof author === 'string') {
return { name: author };
}
return author;
}
export async function extractPackageJSON(tarballBytes: Buffer): Promise<PackageJSONType> {
return new Promise((resolve, reject) => {
Readable.from(tarballBytes)
.pipe(tar.t({
filter: (name: string) => name === 'package/package.json',
onentry: async (entry: any) => {
const chunks: Buffer[] = [];
for await (const chunk of entry) {
chunks.push(chunk);
}
try {
const data = Buffer.concat(chunks);
return resolve(JSON.parse(data.toString()));
} catch {
reject(new Error('Error parsing package.json'));
}
},
}));
});
}