@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 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.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.ts"
9
+ ".": "./index.js"
9
10
  },
10
11
  "files": [
11
- "*.ts"
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
- }