wyvrnpm 1.2.1 → 2.0.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/README.md +513 -413
- package/bin/wyvrn.js +277 -163
- package/package.json +37 -37
- package/src/commands/install.js +156 -109
- package/src/commands/link.js +316 -0
- package/src/commands/profile.js +171 -0
- package/src/commands/publish.js +232 -180
- package/src/config.js +63 -60
- package/src/download.js +325 -164
- package/src/index.js +10 -9
- package/src/link-utils.js +95 -0
- package/src/manifest.js +133 -77
- package/src/profile.js +377 -0
- package/src/providers/base.js +133 -56
- package/src/providers/file.js +181 -71
- package/src/providers/http.js +268 -118
- package/src/providers/s3.js +273 -136
- package/src/resolve.js +262 -200
package/src/providers/s3.js
CHANGED
|
@@ -1,136 +1,273 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const RE_S3_URI
|
|
8
|
-
|
|
9
|
-
const
|
|
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
|
-
bucket:
|
|
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
|
-
async
|
|
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
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const BaseProvider = require('./base');
|
|
6
|
+
|
|
7
|
+
const RE_S3_URI = /^s3:\/\//;
|
|
8
|
+
const RE_S3_VIRTUAL_HOSTED = /^https?:\/\/([^.]+)\.s3[.-][^/]*\.amazonaws\.com/;
|
|
9
|
+
const RE_S3_PATH_STYLE = /^https?:\/\/s3[.-][^/]*\.amazonaws\.com\/([^/]+)/;
|
|
10
|
+
|
|
11
|
+
function pinDependencies(rawDeps, lockedDeps) {
|
|
12
|
+
if (!rawDeps || !lockedDeps || Object.keys(lockedDeps).length === 0) return rawDeps;
|
|
13
|
+
const result = {};
|
|
14
|
+
for (const [pkgName, value] of Object.entries(rawDeps)) {
|
|
15
|
+
const locked = lockedDeps[pkgName];
|
|
16
|
+
if (locked) {
|
|
17
|
+
result[pkgName] = typeof value === 'object' && value !== null
|
|
18
|
+
? { ...value, version: locked }
|
|
19
|
+
: locked;
|
|
20
|
+
} else {
|
|
21
|
+
result[pkgName] = value;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class S3Provider extends BaseProvider {
|
|
28
|
+
static canHandle(source) {
|
|
29
|
+
return (
|
|
30
|
+
RE_S3_URI.test(source) ||
|
|
31
|
+
RE_S3_VIRTUAL_HOSTED.test(source) ||
|
|
32
|
+
RE_S3_PATH_STYLE.test(source)
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static get providerName() {
|
|
37
|
+
return 'S3';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── helpers ──────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
_parseSource(source) {
|
|
43
|
+
if (RE_S3_URI.test(source)) {
|
|
44
|
+
const rest = source.slice(5);
|
|
45
|
+
const slash = rest.indexOf('/');
|
|
46
|
+
if (slash === -1) return { bucket: rest, prefix: '' };
|
|
47
|
+
return { bucket: rest.slice(0, slash), prefix: rest.slice(slash + 1).replace(/\/$/, '') };
|
|
48
|
+
}
|
|
49
|
+
const url = new URL(source);
|
|
50
|
+
const vhMatch = url.hostname.match(/^([^.]+)\.s3/);
|
|
51
|
+
if (vhMatch) {
|
|
52
|
+
return { bucket: vhMatch[1], prefix: url.pathname.slice(1).replace(/\/$/, '') };
|
|
53
|
+
}
|
|
54
|
+
const parts = url.pathname.slice(1).split('/');
|
|
55
|
+
return { bucket: parts[0], prefix: parts.slice(1).join('/').replace(/\/$/, '') };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_key(prefix, ...parts) {
|
|
59
|
+
return [prefix, ...parts].filter(Boolean).join('/');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
_loadSdk() {
|
|
63
|
+
try {
|
|
64
|
+
const { S3Client, PutObjectCommand, HeadObjectCommand, GetObjectCommand } =
|
|
65
|
+
require('@aws-sdk/client-s3');
|
|
66
|
+
const { fromSSO } = require('@aws-sdk/credential-providers');
|
|
67
|
+
return { S3Client, PutObjectCommand, HeadObjectCommand, GetObjectCommand, fromSSO };
|
|
68
|
+
} catch {
|
|
69
|
+
throw new Error(
|
|
70
|
+
'AWS SDK packages are required for S3 publishing.\n' +
|
|
71
|
+
'Run: npm install @aws-sdk/client-s3 @aws-sdk/credential-providers',
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
_makeClient(awsProfile) {
|
|
77
|
+
const { S3Client, fromSSO } = this._loadSdk();
|
|
78
|
+
return new S3Client(awsProfile ? { credentials: fromSSO({ profile: awsProfile }) } : {});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async _putFile(client, bucket, key, filePath, contentType, PutObjectCommand) {
|
|
82
|
+
console.log(`[wyvrn] → s3://${bucket}/${key}`);
|
|
83
|
+
await client.send(new PutObjectCommand({
|
|
84
|
+
Bucket: bucket, Key: key,
|
|
85
|
+
Body: fs.readFileSync(filePath),
|
|
86
|
+
ContentType: contentType,
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async _putBuffer(client, bucket, key, content, contentType, PutObjectCommand) {
|
|
91
|
+
console.log(`[wyvrn] → s3://${bucket}/${key}`);
|
|
92
|
+
await client.send(new PutObjectCommand({
|
|
93
|
+
Bucket: bucket, Key: key,
|
|
94
|
+
Body: content,
|
|
95
|
+
ContentType: contentType,
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async _getJson(client, bucket, key, GetObjectCommand) {
|
|
100
|
+
try {
|
|
101
|
+
const resp = await client.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
|
|
102
|
+
const chunks = [];
|
|
103
|
+
for await (const chunk of resp.Body) chunks.push(chunk);
|
|
104
|
+
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
105
|
+
} catch (err) {
|
|
106
|
+
if (err.name === 'NoSuchKey' || err.$metadata?.httpStatusCode === 404) return null;
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async _downloadToFile(client, bucket, key, destPath, GetObjectCommand) {
|
|
112
|
+
try {
|
|
113
|
+
const resp = await client.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
|
|
114
|
+
const out = fs.createWriteStream(destPath);
|
|
115
|
+
await new Promise((resolve, reject) => {
|
|
116
|
+
resp.Body.pipe(out);
|
|
117
|
+
out.on('finish', resolve);
|
|
118
|
+
out.on('error', reject);
|
|
119
|
+
resp.Body.on('error', reject);
|
|
120
|
+
});
|
|
121
|
+
return true;
|
|
122
|
+
} catch (err) {
|
|
123
|
+
if (err.name === 'NoSuchKey' || err.$metadata?.httpStatusCode === 404) return false;
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── v1 (legacy) API ───────────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
async exists({ source, platform, name, version, awsProfile }) {
|
|
131
|
+
const { HeadObjectCommand } = this._loadSdk();
|
|
132
|
+
const client = this._makeClient(awsProfile);
|
|
133
|
+
const { bucket, prefix } = this._parseSource(source);
|
|
134
|
+
const key = this._key(prefix, platform, name, version, 'wyvrn.json');
|
|
135
|
+
try {
|
|
136
|
+
await client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));
|
|
137
|
+
return true;
|
|
138
|
+
} catch (err) {
|
|
139
|
+
if (err.name === 'NotFound' || err.$metadata?.httpStatusCode === 404) return false;
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async publish(files, { source, platform, name, version, awsProfile }) {
|
|
145
|
+
const { PutObjectCommand } = this._loadSdk();
|
|
146
|
+
const client = this._makeClient(awsProfile);
|
|
147
|
+
const { bucket, prefix } = this._parseSource(source);
|
|
148
|
+
|
|
149
|
+
const keyBase = this._key(prefix, platform, name, version);
|
|
150
|
+
const packageBase = this._key(prefix, platform, name);
|
|
151
|
+
|
|
152
|
+
await this._putFile(client, bucket, `${keyBase}/wyvrn.json`, files.manifest, 'application/json', PutObjectCommand);
|
|
153
|
+
await this._putFile(client, bucket, `${keyBase}/wyvrn.zip`, files.zip, 'application/zip', PutObjectCommand);
|
|
154
|
+
await this._putBuffer(
|
|
155
|
+
client, bucket, `${packageBase}/latest.json`,
|
|
156
|
+
Buffer.from(JSON.stringify({ version })), 'application/json', PutObjectCommand,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── v2 API ────────────────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
async v2Exists({ source, name, version, profileHash, awsProfile }) {
|
|
163
|
+
const { HeadObjectCommand } = this._loadSdk();
|
|
164
|
+
const client = this._makeClient(awsProfile);
|
|
165
|
+
const { bucket, prefix } = this._parseSource(source);
|
|
166
|
+
const key = this._key(prefix, 'v2', name, version, profileHash, 'wyvrn.json');
|
|
167
|
+
try {
|
|
168
|
+
await client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));
|
|
169
|
+
return true;
|
|
170
|
+
} catch (err) {
|
|
171
|
+
if (err.name === 'NotFound' || err.$metadata?.httpStatusCode === 404) return false;
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async v2Publish(files, options) {
|
|
177
|
+
const {
|
|
178
|
+
source, name, version, profileHash, buildSettings,
|
|
179
|
+
contentSha256, gitSha, gitRepo, lockedDependencies, awsProfile,
|
|
180
|
+
} = options;
|
|
181
|
+
const { PutObjectCommand, GetObjectCommand } = this._loadSdk();
|
|
182
|
+
const client = this._makeClient(awsProfile);
|
|
183
|
+
const { bucket, prefix } = this._parseSource(source);
|
|
184
|
+
|
|
185
|
+
const v2root = this._key(prefix, 'v2', name);
|
|
186
|
+
const buildRoot = `${v2root}/${version}/${profileHash}`;
|
|
187
|
+
|
|
188
|
+
// 1) Upload zip
|
|
189
|
+
await this._putFile(client, bucket, `${buildRoot}/wyvrn.zip`, files.zip, 'application/zip', PutObjectCommand);
|
|
190
|
+
|
|
191
|
+
// 2) Upload enriched wyvrn.json
|
|
192
|
+
// buildSettings is stored for debugging purposes only.
|
|
193
|
+
// Dependencies are replaced with locked versions so the server never sees "latest".
|
|
194
|
+
const rawManifest = JSON.parse(fs.readFileSync(files.manifest, 'utf8'));
|
|
195
|
+
const pinnedDeps = pinDependencies(rawManifest.dependencies, lockedDependencies);
|
|
196
|
+
const v2Meta = {
|
|
197
|
+
...rawManifest,
|
|
198
|
+
...(pinnedDeps !== undefined ? { dependencies: pinnedDeps } : {}),
|
|
199
|
+
schemaVersion: 2,
|
|
200
|
+
profileHash,
|
|
201
|
+
buildSettings,
|
|
202
|
+
contentSha256,
|
|
203
|
+
gitSha: gitSha ?? null,
|
|
204
|
+
gitRepo: gitRepo ?? null,
|
|
205
|
+
publishedAt: new Date().toISOString(),
|
|
206
|
+
};
|
|
207
|
+
await this._putBuffer(
|
|
208
|
+
client, bucket, `${buildRoot}/wyvrn.json`,
|
|
209
|
+
Buffer.from(JSON.stringify(v2Meta, null, 2)), 'application/json', PutObjectCommand,
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
// 3) source.json
|
|
213
|
+
if (gitSha || gitRepo) {
|
|
214
|
+
await this._putBuffer(
|
|
215
|
+
client, bucket, `${v2root}/${version}/source.json`,
|
|
216
|
+
Buffer.from(JSON.stringify({ gitRepo: gitRepo ?? null, gitSha: gitSha ?? null }, null, 2)),
|
|
217
|
+
'application/json', PutObjectCommand,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// 4) Read-modify-write versions.json
|
|
222
|
+
let versionsIdx = await this._getJson(client, bucket, `${v2root}/versions.json`, GetObjectCommand)
|
|
223
|
+
?? { name, latest: version, versions: {} };
|
|
224
|
+
if (!versionsIdx.versions) versionsIdx.versions = {};
|
|
225
|
+
if (!versionsIdx.versions[version]) versionsIdx.versions[version] = { profiles: {} };
|
|
226
|
+
versionsIdx.versions[version].profiles[profileHash] = {
|
|
227
|
+
contentSha256,
|
|
228
|
+
publishedAt: new Date().toISOString(),
|
|
229
|
+
buildSettings,
|
|
230
|
+
};
|
|
231
|
+
if (gitSha || gitRepo) {
|
|
232
|
+
versionsIdx.versions[version].source = { gitRepo: gitRepo ?? null, gitSha: gitSha ?? null };
|
|
233
|
+
}
|
|
234
|
+
versionsIdx.latest = version;
|
|
235
|
+
await this._putBuffer(
|
|
236
|
+
client, bucket, `${v2root}/versions.json`,
|
|
237
|
+
Buffer.from(JSON.stringify(versionsIdx, null, 2)), 'application/json', PutObjectCommand,
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
// 5) latest.json
|
|
241
|
+
await this._putBuffer(
|
|
242
|
+
client, bucket, `${v2root}/latest.json`,
|
|
243
|
+
Buffer.from(JSON.stringify({ version, profileHash, contentSha256 })),
|
|
244
|
+
'application/json', PutObjectCommand,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async v2GetMeta({ source, name, version, profileHash, awsProfile }) {
|
|
249
|
+
const { GetObjectCommand } = this._loadSdk();
|
|
250
|
+
const client = this._makeClient(awsProfile);
|
|
251
|
+
const { bucket, prefix } = this._parseSource(source);
|
|
252
|
+
const key = this._key(prefix, 'v2', name, version, profileHash, 'wyvrn.json');
|
|
253
|
+
return this._getJson(client, bucket, key, GetObjectCommand);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async v2GetVersionsIndex({ source, name, awsProfile }) {
|
|
257
|
+
const { GetObjectCommand } = this._loadSdk();
|
|
258
|
+
const client = this._makeClient(awsProfile);
|
|
259
|
+
const { bucket, prefix } = this._parseSource(source);
|
|
260
|
+
const key = this._key(prefix, 'v2', name, 'versions.json');
|
|
261
|
+
return this._getJson(client, bucket, key, GetObjectCommand);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async v2DownloadZip({ source, name, version, profileHash, awsProfile }, destPath) {
|
|
265
|
+
const { GetObjectCommand } = this._loadSdk();
|
|
266
|
+
const client = this._makeClient(awsProfile);
|
|
267
|
+
const { bucket, prefix } = this._parseSource(source);
|
|
268
|
+
const key = this._key(prefix, 'v2', name, version, profileHash, 'wyvrn.zip');
|
|
269
|
+
return this._downloadToFile(client, bucket, key, destPath, GetObjectCommand);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
module.exports = S3Provider;
|