@data-fair/lib-node-registry 0.1.1 → 0.1.3
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/index.d.ts +28 -0
- package/index.js +134 -0
- package/index.js.map +1 -0
- package/package.json +10 -3
- package/index.ts +0 -185
package/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Readable } from 'node:stream';
|
|
2
|
+
export interface EnsureArtefactOpts {
|
|
3
|
+
registryUrl: string;
|
|
4
|
+
secretKey: string;
|
|
5
|
+
artefactId: string;
|
|
6
|
+
version: string;
|
|
7
|
+
cacheDir: string;
|
|
8
|
+
}
|
|
9
|
+
export interface EnsureArtefactResult {
|
|
10
|
+
path: string;
|
|
11
|
+
version: string;
|
|
12
|
+
downloaded: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function ensureArtefact(opts: EnsureArtefactOpts): Promise<EnsureArtefactResult>;
|
|
15
|
+
export interface EnsureArtefactFileOpts {
|
|
16
|
+
registryUrl: string;
|
|
17
|
+
secretKey: string;
|
|
18
|
+
artefactId: string;
|
|
19
|
+
cacheDir: string;
|
|
20
|
+
/** defaults to artefactId */
|
|
21
|
+
fileName?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface EnsureArtefactFileResult {
|
|
24
|
+
path: string;
|
|
25
|
+
downloaded: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare function ensureArtefactFile(opts: EnsureArtefactFileOpts): Promise<EnsureArtefactFileResult>;
|
|
28
|
+
export declare function extractTarball(stream: Readable, destDir: string): Promise<void>;
|
package/index.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { createGunzip } from 'node:zlib';
|
|
2
|
+
import { pipeline } from 'node:stream/promises';
|
|
3
|
+
import { createWriteStream } from 'node:fs';
|
|
4
|
+
import { mkdir, readFile, writeFile, rm, rename, stat, utimes } from 'node:fs/promises';
|
|
5
|
+
import { join, dirname } from 'node:path';
|
|
6
|
+
import * as tar from 'tar-stream';
|
|
7
|
+
import resolvePath from 'resolve-path';
|
|
8
|
+
import { axiosBuilder } from '@data-fair/lib-node/axios.js';
|
|
9
|
+
export async function ensureArtefact(opts) {
|
|
10
|
+
const ax = axiosBuilder({
|
|
11
|
+
baseURL: opts.registryUrl,
|
|
12
|
+
headers: { 'x-secret-key': opts.secretKey }
|
|
13
|
+
});
|
|
14
|
+
const encodedId = encodeURIComponent(opts.artefactId);
|
|
15
|
+
const versionRes = await ax.get(`/api/v1/artefacts/${encodedId}/versions/${opts.version}`);
|
|
16
|
+
const resolvedVersion = versionRes.data.version;
|
|
17
|
+
const artefactDir = join(opts.cacheDir, opts.artefactId);
|
|
18
|
+
const metaPath = join(artefactDir, '.current-version.json');
|
|
19
|
+
const extractDir = join(artefactDir, resolvedVersion);
|
|
20
|
+
// Check cache
|
|
21
|
+
try {
|
|
22
|
+
const raw = await readFile(metaPath, 'utf-8');
|
|
23
|
+
const meta = JSON.parse(raw);
|
|
24
|
+
if (meta.version === resolvedVersion) {
|
|
25
|
+
return { path: extractDir, version: resolvedVersion, downloaded: false };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// no cache or invalid metadata
|
|
30
|
+
}
|
|
31
|
+
// Download tarball
|
|
32
|
+
const tarballRes = await ax.get(`/api/v1/artefacts/${encodedId}/versions/${resolvedVersion}/tarball`, { responseType: 'stream' });
|
|
33
|
+
// Extract to temp dir then atomic rename
|
|
34
|
+
const tmpDir = `${extractDir}.tmp.${process.pid}`;
|
|
35
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
36
|
+
await mkdir(tmpDir, { recursive: true });
|
|
37
|
+
try {
|
|
38
|
+
await extractTarball(tarballRes.data, tmpDir);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
await rm(extractDir, { recursive: true, force: true });
|
|
45
|
+
await rename(tmpDir, extractDir);
|
|
46
|
+
// Clean up old version
|
|
47
|
+
try {
|
|
48
|
+
const raw = await readFile(metaPath, 'utf-8');
|
|
49
|
+
const oldMeta = JSON.parse(raw);
|
|
50
|
+
if (oldMeta.version !== resolvedVersion) {
|
|
51
|
+
await rm(join(artefactDir, oldMeta.version), { recursive: true, force: true });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// no old version to clean
|
|
56
|
+
}
|
|
57
|
+
// Write cache metadata
|
|
58
|
+
await writeFile(metaPath, JSON.stringify({ version: resolvedVersion }));
|
|
59
|
+
return { path: extractDir, version: resolvedVersion, downloaded: true };
|
|
60
|
+
}
|
|
61
|
+
export async function ensureArtefactFile(opts) {
|
|
62
|
+
const ax = axiosBuilder({
|
|
63
|
+
baseURL: opts.registryUrl,
|
|
64
|
+
headers: { 'x-secret-key': opts.secretKey }
|
|
65
|
+
});
|
|
66
|
+
const destPath = join(opts.cacheDir, opts.fileName ?? opts.artefactId);
|
|
67
|
+
let prevMtime;
|
|
68
|
+
try {
|
|
69
|
+
const st = await stat(destPath);
|
|
70
|
+
prevMtime = st.mtime;
|
|
71
|
+
}
|
|
72
|
+
catch { /* cold cache */ }
|
|
73
|
+
const headers = {};
|
|
74
|
+
if (prevMtime)
|
|
75
|
+
headers['if-modified-since'] = prevMtime.toUTCString();
|
|
76
|
+
const res = await ax.get(`/api/v1/artefacts/${encodeURIComponent(opts.artefactId)}/download`, { responseType: 'stream', headers, validateStatus: s => s === 200 || s === 304 });
|
|
77
|
+
if (res.status === 304) {
|
|
78
|
+
;
|
|
79
|
+
res.data.destroy();
|
|
80
|
+
return { path: destPath, downloaded: false };
|
|
81
|
+
}
|
|
82
|
+
await mkdir(dirname(destPath), { recursive: true });
|
|
83
|
+
const tmpPath = `${destPath}.tmp.${process.pid}`;
|
|
84
|
+
await rm(tmpPath, { force: true });
|
|
85
|
+
try {
|
|
86
|
+
await pipeline(res.data, createWriteStream(tmpPath));
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
await rm(tmpPath, { force: true });
|
|
90
|
+
throw err;
|
|
91
|
+
}
|
|
92
|
+
await rename(tmpPath, destPath);
|
|
93
|
+
const lastModified = res.headers['last-modified'];
|
|
94
|
+
if (lastModified) {
|
|
95
|
+
const mtime = new Date(lastModified);
|
|
96
|
+
if (!isNaN(mtime.getTime())) {
|
|
97
|
+
await utimes(destPath, new Date(), mtime);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return { path: destPath, downloaded: true };
|
|
101
|
+
}
|
|
102
|
+
export async function extractTarball(stream, destDir) {
|
|
103
|
+
const extract = tar.extract();
|
|
104
|
+
const entries = [];
|
|
105
|
+
extract.on('entry', (header, entryStream, next) => {
|
|
106
|
+
// npm tarballs prefix entries with "package/"
|
|
107
|
+
const entryPath = header.name.replace(/^package\//, '');
|
|
108
|
+
if (header.type === 'directory') {
|
|
109
|
+
entries.push(mkdir(resolvePath(destDir, entryPath), { recursive: true }).then(() => { }));
|
|
110
|
+
entryStream.resume();
|
|
111
|
+
entryStream.on('end', next);
|
|
112
|
+
}
|
|
113
|
+
else if (header.type === 'file') {
|
|
114
|
+
const fullPath = resolvePath(destDir, entryPath);
|
|
115
|
+
const p = mkdir(dirname(fullPath), { recursive: true }).then(() => {
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
const ws = createWriteStream(fullPath);
|
|
118
|
+
entryStream.pipe(ws);
|
|
119
|
+
ws.on('finish', resolve);
|
|
120
|
+
ws.on('error', reject);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
entries.push(p);
|
|
124
|
+
entryStream.on('end', next);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
entryStream.resume();
|
|
128
|
+
entryStream.on('end', next);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
await pipeline(stream, createGunzip(), extract);
|
|
132
|
+
await Promise.all(entries);
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=index.js.map
|
package/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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","sourcesContent":["import { createGunzip } from 'node:zlib'\nimport { pipeline } from 'node:stream/promises'\nimport { createWriteStream } from 'node:fs'\nimport { mkdir, readFile, writeFile, rm, rename, stat, utimes } from 'node:fs/promises'\nimport { join, dirname } from 'node:path'\nimport * as tar from 'tar-stream'\nimport resolvePath from 'resolve-path'\nimport { axiosBuilder } from '@data-fair/lib-node/axios.js'\nimport type { Readable } from 'node:stream'\n\nexport interface EnsureArtefactOpts {\n registryUrl: string\n secretKey: string\n artefactId: string\n version: string\n cacheDir: string\n}\n\nexport interface EnsureArtefactResult {\n path: string\n version: string\n downloaded: boolean\n}\n\ninterface CacheMeta {\n version: string\n}\n\nexport async function ensureArtefact (opts: EnsureArtefactOpts): Promise<EnsureArtefactResult> {\n const ax = axiosBuilder({\n baseURL: opts.registryUrl,\n headers: { 'x-secret-key': opts.secretKey }\n })\n\n const encodedId = encodeURIComponent(opts.artefactId)\n const versionRes = await ax.get(`/api/v1/artefacts/${encodedId}/versions/${opts.version}`)\n const resolvedVersion: string = versionRes.data.version\n\n const artefactDir = join(opts.cacheDir, opts.artefactId)\n const metaPath = join(artefactDir, '.current-version.json')\n const extractDir = join(artefactDir, resolvedVersion)\n\n // Check cache\n try {\n const raw = await readFile(metaPath, 'utf-8')\n const meta: CacheMeta = JSON.parse(raw)\n if (meta.version === resolvedVersion) {\n return { path: extractDir, version: resolvedVersion, downloaded: false }\n }\n } catch {\n // no cache or invalid metadata\n }\n\n // Download tarball\n const tarballRes = await ax.get(\n `/api/v1/artefacts/${encodedId}/versions/${resolvedVersion}/tarball`,\n { responseType: 'stream' }\n )\n\n // Extract to temp dir then atomic rename\n const tmpDir = `${extractDir}.tmp.${process.pid}`\n await rm(tmpDir, { recursive: true, force: true })\n await mkdir(tmpDir, { recursive: true })\n try {\n await extractTarball(tarballRes.data as Readable, tmpDir)\n } catch (err) {\n await rm(tmpDir, { recursive: true, force: true })\n throw err\n }\n await rm(extractDir, { recursive: true, force: true })\n await rename(tmpDir, extractDir)\n\n // Clean up old version\n try {\n const raw = await readFile(metaPath, 'utf-8')\n const oldMeta: CacheMeta = JSON.parse(raw)\n if (oldMeta.version !== resolvedVersion) {\n await rm(join(artefactDir, oldMeta.version), { recursive: true, force: true })\n }\n } catch {\n // no old version to clean\n }\n\n // Write cache metadata\n await writeFile(metaPath, JSON.stringify({ version: resolvedVersion } satisfies CacheMeta))\n\n return { path: extractDir, version: resolvedVersion, downloaded: true }\n}\n\nexport interface EnsureArtefactFileOpts {\n registryUrl: string\n secretKey: string\n artefactId: string\n cacheDir: string\n /** defaults to artefactId */\n fileName?: string\n}\n\nexport interface EnsureArtefactFileResult {\n path: string\n downloaded: boolean\n}\n\nexport async function ensureArtefactFile (opts: EnsureArtefactFileOpts): Promise<EnsureArtefactFileResult> {\n const ax = axiosBuilder({\n baseURL: opts.registryUrl,\n headers: { 'x-secret-key': opts.secretKey }\n })\n\n const destPath = join(opts.cacheDir, opts.fileName ?? opts.artefactId)\n\n let prevMtime: Date | undefined\n try {\n const st = await stat(destPath)\n prevMtime = st.mtime\n } catch { /* cold cache */ }\n\n const headers: Record<string, string> = {}\n if (prevMtime) headers['if-modified-since'] = prevMtime.toUTCString()\n\n const res = await ax.get(\n `/api/v1/artefacts/${encodeURIComponent(opts.artefactId)}/download`,\n { responseType: 'stream', headers, validateStatus: s => s === 200 || s === 304 }\n )\n\n if (res.status === 304) {\n ;(res.data as Readable).destroy()\n return { path: destPath, downloaded: false }\n }\n\n await mkdir(dirname(destPath), { recursive: true })\n const tmpPath = `${destPath}.tmp.${process.pid}`\n await rm(tmpPath, { force: true })\n try {\n await pipeline(res.data as Readable, createWriteStream(tmpPath))\n } catch (err) {\n await rm(tmpPath, { force: true })\n throw err\n }\n await rename(tmpPath, destPath)\n\n const lastModified = res.headers['last-modified']\n if (lastModified) {\n const mtime = new Date(lastModified)\n if (!isNaN(mtime.getTime())) {\n await utimes(destPath, new Date(), mtime)\n }\n }\n\n return { path: destPath, downloaded: true }\n}\n\nexport async function extractTarball (stream: Readable, destDir: string): Promise<void> {\n const extract = tar.extract()\n\n const entries: Promise<void>[] = []\n\n extract.on('entry', (header, entryStream, next) => {\n // npm tarballs prefix entries with \"package/\"\n const entryPath = header.name.replace(/^package\\//, '')\n\n if (header.type === 'directory') {\n entries.push(mkdir(resolvePath(destDir, entryPath), { recursive: true }).then(() => {}))\n entryStream.resume()\n entryStream.on('end', next)\n } else if (header.type === 'file') {\n const fullPath = resolvePath(destDir, entryPath)\n const p = mkdir(dirname(fullPath), { recursive: true }).then(() => {\n return new Promise<void>((resolve, reject) => {\n const ws = createWriteStream(fullPath)\n entryStream.pipe(ws)\n ws.on('finish', resolve)\n ws.on('error', reject)\n })\n })\n entries.push(p)\n entryStream.on('end', next)\n } else {\n entryStream.resume()\n entryStream.on('end', next)\n }\n })\n\n await pipeline(stream, createGunzip(), extract)\n await Promise.all(entries)\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@data-fair/lib-node-registry",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Node.js client library for the data-fair registry service.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"main": "index.js",
|
|
7
8
|
"exports": {
|
|
8
|
-
".": "./index.
|
|
9
|
+
".": "./index.js"
|
|
9
10
|
},
|
|
10
11
|
"files": [
|
|
11
|
-
"
|
|
12
|
+
"**/*.js",
|
|
13
|
+
"**/*.js.map",
|
|
14
|
+
"**/*.d.ts"
|
|
12
15
|
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"prepublishOnly": "cd .. && npm run build-lib-node",
|
|
18
|
+
"build": "cd .. && npm run build-lib-node"
|
|
19
|
+
},
|
|
13
20
|
"peerDependencies": {
|
|
14
21
|
"@data-fair/lib-node": ">=2.8.0"
|
|
15
22
|
},
|
package/index.ts
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { createGunzip } from 'node:zlib'
|
|
2
|
-
import { pipeline } from 'node:stream/promises'
|
|
3
|
-
import { createWriteStream } from 'node:fs'
|
|
4
|
-
import { mkdir, readFile, writeFile, rm, rename, stat, utimes } from 'node:fs/promises'
|
|
5
|
-
import { join, dirname } from 'node:path'
|
|
6
|
-
import * as tar from 'tar-stream'
|
|
7
|
-
import resolvePath from 'resolve-path'
|
|
8
|
-
import { axiosBuilder } from '@data-fair/lib-node/axios.js'
|
|
9
|
-
import type { Readable } from 'node:stream'
|
|
10
|
-
|
|
11
|
-
export interface EnsureArtefactOpts {
|
|
12
|
-
registryUrl: string
|
|
13
|
-
secretKey: string
|
|
14
|
-
artefactId: string
|
|
15
|
-
version: string
|
|
16
|
-
cacheDir: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface EnsureArtefactResult {
|
|
20
|
-
path: string
|
|
21
|
-
version: string
|
|
22
|
-
downloaded: boolean
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface CacheMeta {
|
|
26
|
-
version: string
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export async function ensureArtefact (opts: EnsureArtefactOpts): Promise<EnsureArtefactResult> {
|
|
30
|
-
const ax = axiosBuilder({
|
|
31
|
-
baseURL: opts.registryUrl,
|
|
32
|
-
headers: { 'x-secret-key': opts.secretKey }
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
const encodedId = encodeURIComponent(opts.artefactId)
|
|
36
|
-
const versionRes = await ax.get(`/api/v1/artefacts/${encodedId}/versions/${opts.version}`)
|
|
37
|
-
const resolvedVersion: string = versionRes.data.version
|
|
38
|
-
|
|
39
|
-
const artefactDir = join(opts.cacheDir, opts.artefactId)
|
|
40
|
-
const metaPath = join(artefactDir, '.current-version.json')
|
|
41
|
-
const extractDir = join(artefactDir, resolvedVersion)
|
|
42
|
-
|
|
43
|
-
// Check cache
|
|
44
|
-
try {
|
|
45
|
-
const raw = await readFile(metaPath, 'utf-8')
|
|
46
|
-
const meta: CacheMeta = JSON.parse(raw)
|
|
47
|
-
if (meta.version === resolvedVersion) {
|
|
48
|
-
return { path: extractDir, version: resolvedVersion, downloaded: false }
|
|
49
|
-
}
|
|
50
|
-
} catch {
|
|
51
|
-
// no cache or invalid metadata
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Download tarball
|
|
55
|
-
const tarballRes = await ax.get(
|
|
56
|
-
`/api/v1/artefacts/${encodedId}/versions/${resolvedVersion}/tarball`,
|
|
57
|
-
{ responseType: 'stream' }
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
// Extract to temp dir then atomic rename
|
|
61
|
-
const tmpDir = `${extractDir}.tmp.${process.pid}`
|
|
62
|
-
await rm(tmpDir, { recursive: true, force: true })
|
|
63
|
-
await mkdir(tmpDir, { recursive: true })
|
|
64
|
-
try {
|
|
65
|
-
await extractTarball(tarballRes.data as Readable, tmpDir)
|
|
66
|
-
} catch (err) {
|
|
67
|
-
await rm(tmpDir, { recursive: true, force: true })
|
|
68
|
-
throw err
|
|
69
|
-
}
|
|
70
|
-
await rm(extractDir, { recursive: true, force: true })
|
|
71
|
-
await rename(tmpDir, extractDir)
|
|
72
|
-
|
|
73
|
-
// Clean up old version
|
|
74
|
-
try {
|
|
75
|
-
const raw = await readFile(metaPath, 'utf-8')
|
|
76
|
-
const oldMeta: CacheMeta = JSON.parse(raw)
|
|
77
|
-
if (oldMeta.version !== resolvedVersion) {
|
|
78
|
-
await rm(join(artefactDir, oldMeta.version), { recursive: true, force: true })
|
|
79
|
-
}
|
|
80
|
-
} catch {
|
|
81
|
-
// no old version to clean
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Write cache metadata
|
|
85
|
-
await writeFile(metaPath, JSON.stringify({ version: resolvedVersion } satisfies CacheMeta))
|
|
86
|
-
|
|
87
|
-
return { path: extractDir, version: resolvedVersion, downloaded: true }
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export interface EnsureArtefactFileOpts {
|
|
91
|
-
registryUrl: string
|
|
92
|
-
secretKey: string
|
|
93
|
-
artefactId: string
|
|
94
|
-
cacheDir: string
|
|
95
|
-
/** defaults to artefactId */
|
|
96
|
-
fileName?: string
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface EnsureArtefactFileResult {
|
|
100
|
-
path: string
|
|
101
|
-
downloaded: boolean
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export async function ensureArtefactFile (opts: EnsureArtefactFileOpts): Promise<EnsureArtefactFileResult> {
|
|
105
|
-
const ax = axiosBuilder({
|
|
106
|
-
baseURL: opts.registryUrl,
|
|
107
|
-
headers: { 'x-secret-key': opts.secretKey }
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
const destPath = join(opts.cacheDir, opts.fileName ?? opts.artefactId)
|
|
111
|
-
|
|
112
|
-
let prevMtime: Date | undefined
|
|
113
|
-
try {
|
|
114
|
-
const st = await stat(destPath)
|
|
115
|
-
prevMtime = st.mtime
|
|
116
|
-
} catch { /* cold cache */ }
|
|
117
|
-
|
|
118
|
-
const headers: Record<string, string> = {}
|
|
119
|
-
if (prevMtime) headers['if-modified-since'] = prevMtime.toUTCString()
|
|
120
|
-
|
|
121
|
-
const res = await ax.get(
|
|
122
|
-
`/api/v1/artefacts/${encodeURIComponent(opts.artefactId)}/download`,
|
|
123
|
-
{ responseType: 'stream', headers, validateStatus: s => s === 200 || s === 304 }
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
if (res.status === 304) {
|
|
127
|
-
return { path: destPath, downloaded: false }
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
await mkdir(dirname(destPath), { recursive: true })
|
|
131
|
-
const tmpPath = `${destPath}.tmp.${process.pid}`
|
|
132
|
-
await rm(tmpPath, { force: true })
|
|
133
|
-
try {
|
|
134
|
-
await pipeline(res.data as Readable, createWriteStream(tmpPath))
|
|
135
|
-
} catch (err) {
|
|
136
|
-
await rm(tmpPath, { force: true })
|
|
137
|
-
throw err
|
|
138
|
-
}
|
|
139
|
-
await rename(tmpPath, destPath)
|
|
140
|
-
|
|
141
|
-
const lastModified = res.headers['last-modified']
|
|
142
|
-
if (lastModified) {
|
|
143
|
-
const mtime = new Date(lastModified)
|
|
144
|
-
if (!isNaN(mtime.getTime())) {
|
|
145
|
-
await utimes(destPath, new Date(), mtime)
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return { path: destPath, downloaded: true }
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export async function extractTarball (stream: Readable, destDir: string): Promise<void> {
|
|
153
|
-
const extract = tar.extract()
|
|
154
|
-
|
|
155
|
-
const entries: Promise<void>[] = []
|
|
156
|
-
|
|
157
|
-
extract.on('entry', (header, entryStream, next) => {
|
|
158
|
-
// npm tarballs prefix entries with "package/"
|
|
159
|
-
const entryPath = header.name.replace(/^package\//, '')
|
|
160
|
-
|
|
161
|
-
if (header.type === 'directory') {
|
|
162
|
-
entries.push(mkdir(resolvePath(destDir, entryPath), { recursive: true }).then(() => {}))
|
|
163
|
-
entryStream.resume()
|
|
164
|
-
entryStream.on('end', next)
|
|
165
|
-
} else if (header.type === 'file') {
|
|
166
|
-
const fullPath = resolvePath(destDir, entryPath)
|
|
167
|
-
const p = mkdir(dirname(fullPath), { recursive: true }).then(() => {
|
|
168
|
-
return new Promise<void>((resolve, reject) => {
|
|
169
|
-
const ws = createWriteStream(fullPath)
|
|
170
|
-
entryStream.pipe(ws)
|
|
171
|
-
ws.on('finish', resolve)
|
|
172
|
-
ws.on('error', reject)
|
|
173
|
-
})
|
|
174
|
-
})
|
|
175
|
-
entries.push(p)
|
|
176
|
-
entryStream.on('end', next)
|
|
177
|
-
} else {
|
|
178
|
-
entryStream.resume()
|
|
179
|
-
entryStream.on('end', next)
|
|
180
|
-
}
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
await pipeline(stream, createGunzip(), extract)
|
|
184
|
-
await Promise.all(entries)
|
|
185
|
-
}
|