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/config.js
CHANGED
|
@@ -1,60 +1,63 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const os = require('os');
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Returns the directory where wyvrnpm stores its config.
|
|
9
|
-
* Windows : %LOCALAPPDATA%\wyvrnpm
|
|
10
|
-
* Other : ~/.config/wyvrnpm
|
|
11
|
-
*/
|
|
12
|
-
function getConfigDir() {
|
|
13
|
-
if (process.env.LOCALAPPDATA) {
|
|
14
|
-
return path.join(process.env.LOCALAPPDATA, 'wyvrnpm');
|
|
15
|
-
}
|
|
16
|
-
return path.join(os.homedir(), '.config', 'wyvrnpm');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Full path to config.json. */
|
|
20
|
-
function getConfigPath() {
|
|
21
|
-
return path.join(getConfigDir(), 'config.json');
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Read and parse config.json.
|
|
26
|
-
* Returns a default empty config if the file doesn't exist yet.
|
|
27
|
-
* @returns {{ installSources: SourceEntry[], publishSources: SourceEntry[] }}
|
|
28
|
-
*/
|
|
29
|
-
function readConfig() {
|
|
30
|
-
const configPath = getConfigPath();
|
|
31
|
-
if (!fs.existsSync(configPath)) {
|
|
32
|
-
return { installSources: [], publishSources: [] };
|
|
33
|
-
}
|
|
34
|
-
try {
|
|
35
|
-
const raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
36
|
-
return {
|
|
37
|
-
installSources: raw.installSources
|
|
38
|
-
publishSources: raw.publishSources
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Returns the directory where wyvrnpm stores its config.
|
|
9
|
+
* Windows : %LOCALAPPDATA%\wyvrnpm
|
|
10
|
+
* Other : ~/.config/wyvrnpm
|
|
11
|
+
*/
|
|
12
|
+
function getConfigDir() {
|
|
13
|
+
if (process.env.LOCALAPPDATA) {
|
|
14
|
+
return path.join(process.env.LOCALAPPDATA, 'wyvrnpm');
|
|
15
|
+
}
|
|
16
|
+
return path.join(os.homedir(), '.config', 'wyvrnpm');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Full path to config.json. */
|
|
20
|
+
function getConfigPath() {
|
|
21
|
+
return path.join(getConfigDir(), 'config.json');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Read and parse config.json.
|
|
26
|
+
* Returns a default empty config if the file doesn't exist yet.
|
|
27
|
+
* @returns {{ installSources: SourceEntry[], publishSources: SourceEntry[], linkedPackages: Object<string, string>, defaultProfile: string }}
|
|
28
|
+
*/
|
|
29
|
+
function readConfig() {
|
|
30
|
+
const configPath = getConfigPath();
|
|
31
|
+
if (!fs.existsSync(configPath)) {
|
|
32
|
+
return { installSources: [], publishSources: [], linkedPackages: {}, defaultProfile: 'default' };
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
36
|
+
return {
|
|
37
|
+
installSources: raw.installSources ?? [],
|
|
38
|
+
publishSources: raw.publishSources ?? [],
|
|
39
|
+
linkedPackages: raw.linkedPackages ?? {},
|
|
40
|
+
// Migrate: if old config had an inline `profile` object, drop it; use file-based profiles.
|
|
41
|
+
defaultProfile: raw.defaultProfile ?? 'default',
|
|
42
|
+
};
|
|
43
|
+
} catch {
|
|
44
|
+
console.warn('[wyvrn] Warning: could not parse config.json — using defaults');
|
|
45
|
+
return { installSources: [], publishSources: [], linkedPackages: {}, defaultProfile: 'default' };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Persist a config object to disk.
|
|
51
|
+
* @param {{ installSources: SourceEntry[], publishSources: SourceEntry[] }} config
|
|
52
|
+
*/
|
|
53
|
+
function writeConfig(config) {
|
|
54
|
+
const dir = getConfigDir();
|
|
55
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
56
|
+
fs.writeFileSync(getConfigPath(), JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { getConfigDir, getConfigPath, readConfig, writeConfig };
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @typedef {{ name: string, url: string, profile?: string, token?: string }} SourceEntry
|
|
63
|
+
*/
|
package/src/download.js
CHANGED
|
@@ -1,164 +1,325 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { pipeline } = require('stream/promises');
|
|
6
|
-
const { Readable } = require('stream');
|
|
7
|
-
const StreamZip = require('node-stream-zip');
|
|
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
|
-
if (
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { pipeline } = require('stream/promises');
|
|
6
|
+
const { Readable } = require('stream');
|
|
7
|
+
const StreamZip = require('node-stream-zip');
|
|
8
|
+
|
|
9
|
+
const { isLink, getLinkTarget } = require('./link-utils');
|
|
10
|
+
const { sha256Of } = require('./profile');
|
|
11
|
+
const { getProvider } = require('./providers');
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// v1 (legacy) download helpers
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
async function downloadFile(url, destPath, httpClient, timeoutMs) {
|
|
18
|
+
const controller = new AbortController();
|
|
19
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
20
|
+
let response;
|
|
21
|
+
try {
|
|
22
|
+
response = await httpClient(url, { signal: controller.signal });
|
|
23
|
+
} finally {
|
|
24
|
+
clearTimeout(timer);
|
|
25
|
+
}
|
|
26
|
+
if (!response.ok) throw new Error(`HTTP ${response.status} for ${url}`);
|
|
27
|
+
const fileStream = fs.createWriteStream(destPath);
|
|
28
|
+
await pipeline(Readable.fromWeb(response.body), fileStream);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function tryDownloadFromSources(name, version, packageSources, platform, httpClient, timeoutMs, destZipPath) {
|
|
32
|
+
for (const source of packageSources) {
|
|
33
|
+
const base = source.replace(/\/$/, '');
|
|
34
|
+
const candidates = [
|
|
35
|
+
`${base}/${platform}/${name}/${version}/wyvrn.zip`,
|
|
36
|
+
`${base}/${platform}/${name}/${version}/razer.zip`,
|
|
37
|
+
`${base}/common/${name}/${version}/wyvrn.zip`,
|
|
38
|
+
`${base}/common/${name}/${version}/razer.zip`,
|
|
39
|
+
];
|
|
40
|
+
for (const url of candidates) {
|
|
41
|
+
try {
|
|
42
|
+
await downloadFile(url, destZipPath, httpClient, timeoutMs);
|
|
43
|
+
return true;
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.warn(`[wyvrn] Could not download ${name}@${version} from ${url}: ${err.message}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// v2 download helpers
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Try to download a v2 prebuilt binary for a package.
|
|
58
|
+
*
|
|
59
|
+
* Resolution order:
|
|
60
|
+
* 1. Exact profile hash match
|
|
61
|
+
* 2. Compatible build from versions.json (same OS + arch, any compiler)
|
|
62
|
+
* 3. Return null (caller should fall back to v1)
|
|
63
|
+
*
|
|
64
|
+
* @param {{
|
|
65
|
+
* name: string,
|
|
66
|
+
* version: string,
|
|
67
|
+
* profileHash: string,
|
|
68
|
+
* profile: import('./profile').WyvrnProfile,
|
|
69
|
+
* }} dep
|
|
70
|
+
* @param {string[]} packageSources
|
|
71
|
+
* @param {string} destZipPath
|
|
72
|
+
* @param {number} timeoutMs
|
|
73
|
+
* @param {object} authOptions - { awsProfile?, token? }
|
|
74
|
+
* @returns {Promise<{
|
|
75
|
+
* found: boolean,
|
|
76
|
+
* contentSha256: string|null,
|
|
77
|
+
* gitSha: string|null,
|
|
78
|
+
* gitRepo: string|null,
|
|
79
|
+
* profileHash: string,
|
|
80
|
+
* source: string, // which source URL was used
|
|
81
|
+
* }|null>} null = not found in v2
|
|
82
|
+
*/
|
|
83
|
+
async function tryDownloadV2(dep, packageSources, destZipPath, timeoutMs, authOptions = {}) {
|
|
84
|
+
const { name, version, profileHash, profile } = dep;
|
|
85
|
+
const { awsProfile, token } = authOptions;
|
|
86
|
+
|
|
87
|
+
for (const source of packageSources) {
|
|
88
|
+
let provider;
|
|
89
|
+
try { provider = getProvider(source); } catch { continue; }
|
|
90
|
+
|
|
91
|
+
// 1) Exact profile hash
|
|
92
|
+
const exactMeta = await provider.v2GetMeta({ source, name, version, profileHash, awsProfile, token });
|
|
93
|
+
if (exactMeta) {
|
|
94
|
+
const ok = await provider.v2DownloadZip({ source, name, version, profileHash, awsProfile, token }, destZipPath, timeoutMs);
|
|
95
|
+
if (ok) {
|
|
96
|
+
return {
|
|
97
|
+
found: true,
|
|
98
|
+
contentSha256: exactMeta.contentSha256 ?? null,
|
|
99
|
+
gitSha: exactMeta.gitSha ?? null,
|
|
100
|
+
gitRepo: exactMeta.gitRepo ?? null,
|
|
101
|
+
profileHash,
|
|
102
|
+
source,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 2) Fallback: scan versions.json for a compatible build (same OS + arch)
|
|
108
|
+
const idx = await provider.v2GetVersionsIndex({ source, name, awsProfile, token });
|
|
109
|
+
if (!idx?.versions?.[version]?.profiles) continue;
|
|
110
|
+
|
|
111
|
+
const versionEntry = idx.versions[version];
|
|
112
|
+
for (const [ph, buildEntry] of Object.entries(versionEntry.profiles)) {
|
|
113
|
+
// buildSettings is the copy of the profile stored for debugging at publish time
|
|
114
|
+
const bp = buildEntry.buildSettings ?? buildEntry.profile;
|
|
115
|
+
if (!bp) continue;
|
|
116
|
+
if (bp.os !== profile.os) continue;
|
|
117
|
+
if (bp.arch !== profile.arch) continue;
|
|
118
|
+
// Same OS + arch — accept as a compatible fallback
|
|
119
|
+
console.warn(
|
|
120
|
+
`[wyvrn] No exact profile match for ${name}@${version}; using compatible build [${ph}]` +
|
|
121
|
+
` (${bp.compiler} ${bp['compiler.version']})`,
|
|
122
|
+
);
|
|
123
|
+
const ok = await provider.v2DownloadZip({ source, name, version, profileHash: ph, awsProfile, token }, destZipPath, timeoutMs);
|
|
124
|
+
if (ok) {
|
|
125
|
+
return {
|
|
126
|
+
found: true,
|
|
127
|
+
contentSha256: buildEntry.contentSha256 ?? null,
|
|
128
|
+
gitSha: versionEntry.source?.gitSha ?? null,
|
|
129
|
+
gitRepo: versionEntry.source?.gitRepo ?? null,
|
|
130
|
+
profileHash: ph,
|
|
131
|
+
source,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// Extraction helper
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
async function extractZip(destZipPath, extractDir) {
|
|
145
|
+
const zip = new StreamZip.async({ file: destZipPath });
|
|
146
|
+
await zip.extract(null, extractDir);
|
|
147
|
+
await zip.close();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Main downloadDependencies (v2-aware)
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Downloads and extracts all resolved dependencies.
|
|
156
|
+
* Tries v2 first (profile-aware + SHA256 verification), falls back to v1.
|
|
157
|
+
*
|
|
158
|
+
* @param {Map<string, string | {version:string, profileHash:string|null, profile:import('./profile').WyvrnProfile|null}>} deps
|
|
159
|
+
* Either a plain `Map<name, version>` (legacy callers / tests) or the enriched map
|
|
160
|
+
* produced by install.js: `Map<name, { version, profileHash, profile }>`.
|
|
161
|
+
* @param {string[]} packageSources
|
|
162
|
+
* @param {string} platform - v1 legacy platform string
|
|
163
|
+
* @param {string} razerDir
|
|
164
|
+
* @param {Function} httpClient - fetch-compatible
|
|
165
|
+
* @param {number} timeout - seconds
|
|
166
|
+
* @param {object} [authOptions] - { awsProfile?, token? }
|
|
167
|
+
* @returns {Promise<Map<string, {version:string, profileHash?:string, contentSha256?:string, gitSha?:string, resolvedFrom:string}>>}
|
|
168
|
+
*/
|
|
169
|
+
async function downloadDependencies(deps, packageSources, platform, razerDir, httpClient, timeout, authOptions = {}) {
|
|
170
|
+
const timeoutMs = timeout * 1000;
|
|
171
|
+
const { awsProfile, token } = authOptions;
|
|
172
|
+
|
|
173
|
+
if (!fs.existsSync(razerDir)) {
|
|
174
|
+
fs.mkdirSync(razerDir, { recursive: true });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** @type {Map<string, object>} */
|
|
178
|
+
const lockEntries = new Map();
|
|
179
|
+
|
|
180
|
+
for (const [name, depInfoOrVersion] of deps) {
|
|
181
|
+
// Normalise: accept both plain version string (legacy) and enriched object
|
|
182
|
+
const isLegacyEntry = typeof depInfoOrVersion === 'string';
|
|
183
|
+
const version = isLegacyEntry ? depInfoOrVersion : depInfoOrVersion.version;
|
|
184
|
+
const profileHash = isLegacyEntry ? null : (depInfoOrVersion.profileHash ?? null);
|
|
185
|
+
const profile = isLegacyEntry ? null : (depInfoOrVersion.profile ?? null);
|
|
186
|
+
|
|
187
|
+
const extractDir = path.join(razerDir, name);
|
|
188
|
+
const versionFile = path.join(extractDir, '.wyvrn-version');
|
|
189
|
+
const metaFile = path.join(extractDir, '.wyvrn-meta.json');
|
|
190
|
+
|
|
191
|
+
// Skip linked packages
|
|
192
|
+
if (isLink(extractDir)) {
|
|
193
|
+
const linkTarget = getLinkTarget(extractDir);
|
|
194
|
+
console.log(`[wyvrn] Linked: ${name} → ${linkTarget} (skipping download)`);
|
|
195
|
+
lockEntries.set(name, { version, resolvedFrom: 'link' });
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Skip if already at correct version + profile hash
|
|
200
|
+
if (fs.existsSync(extractDir)) {
|
|
201
|
+
const installedVersion = fs.existsSync(versionFile)
|
|
202
|
+
? fs.readFileSync(versionFile, 'utf8').trim()
|
|
203
|
+
: null;
|
|
204
|
+
const installedMeta = fs.existsSync(metaFile)
|
|
205
|
+
? JSON.parse(fs.readFileSync(metaFile, 'utf8'))
|
|
206
|
+
: null;
|
|
207
|
+
|
|
208
|
+
const sameVersion = installedVersion === version;
|
|
209
|
+
const sameProfile = !profileHash || installedMeta?.profileHash === profileHash;
|
|
210
|
+
|
|
211
|
+
if (sameVersion && sameProfile) {
|
|
212
|
+
console.log(`[wyvrn] Already present: ${name}@${version}${profileHash ? ` [${installedMeta?.profileHash}]` : ''} — skipping`);
|
|
213
|
+
lockEntries.set(name, {
|
|
214
|
+
version,
|
|
215
|
+
profileHash: installedMeta?.profileHash ?? null,
|
|
216
|
+
contentSha256: installedMeta?.contentSha256 ?? null,
|
|
217
|
+
gitSha: installedMeta?.gitSha ?? null,
|
|
218
|
+
resolvedFrom: installedMeta?.resolvedFrom ?? 'cached',
|
|
219
|
+
});
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (!sameVersion) {
|
|
223
|
+
console.log(`[wyvrn] Version changed: ${name} ${installedVersion} → ${version}, re-downloading`);
|
|
224
|
+
} else {
|
|
225
|
+
console.log(`[wyvrn] Profile changed for ${name}@${version}, re-downloading`);
|
|
226
|
+
}
|
|
227
|
+
fs.rmSync(extractDir, { recursive: true, force: true });
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
console.log(`[wyvrn] Downloading: ${name}@${version}`);
|
|
231
|
+
const destZipPath = path.join(razerDir, `${name}.zip`);
|
|
232
|
+
|
|
233
|
+
let downloadResult = null;
|
|
234
|
+
let resolvedFrom = 'v1';
|
|
235
|
+
|
|
236
|
+
// ── Try v2 first ────────────────────────────────────────────────────────
|
|
237
|
+
if (profileHash && profile) {
|
|
238
|
+
downloadResult = await tryDownloadV2(
|
|
239
|
+
{ name, version, profileHash, profile },
|
|
240
|
+
packageSources,
|
|
241
|
+
destZipPath,
|
|
242
|
+
timeoutMs,
|
|
243
|
+
{ awsProfile, token },
|
|
244
|
+
);
|
|
245
|
+
if (downloadResult) resolvedFrom = 'v2';
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ── Fall back to v1 ─────────────────────────────────────────────────────
|
|
249
|
+
if (!downloadResult) {
|
|
250
|
+
const ok = await tryDownloadFromSources(name, version, packageSources, platform, httpClient, timeoutMs, destZipPath);
|
|
251
|
+
if (!ok) {
|
|
252
|
+
console.warn(`[wyvrn] Warning: package ${name}@${version} was not found in any configured source`);
|
|
253
|
+
lockEntries.set(name, { version, resolvedFrom: 'not-found' });
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ── SHA256 integrity check (v2 only) ────────────────────────────────────
|
|
259
|
+
if (downloadResult?.contentSha256) {
|
|
260
|
+
const actual = sha256Of(destZipPath);
|
|
261
|
+
if (actual !== downloadResult.contentSha256) {
|
|
262
|
+
console.error(
|
|
263
|
+
`[wyvrn] Error: SHA256 mismatch for ${name}@${version}\n` +
|
|
264
|
+
` expected: ${downloadResult.contentSha256}\n` +
|
|
265
|
+
` actual: ${actual}`,
|
|
266
|
+
);
|
|
267
|
+
try { fs.unlinkSync(destZipPath); } catch { /* ignore */ }
|
|
268
|
+
lockEntries.set(name, { version, resolvedFrom: 'sha256-mismatch' });
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
console.log(`[wyvrn] SHA256 verified: ${actual.slice(0, 16)}...`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ── Extract ─────────────────────────────────────────────────────────────
|
|
275
|
+
try {
|
|
276
|
+
fs.mkdirSync(extractDir, { recursive: true });
|
|
277
|
+
await extractZip(destZipPath, extractDir);
|
|
278
|
+
|
|
279
|
+
// Write .wyvrn-version (v1 compat)
|
|
280
|
+
fs.writeFileSync(versionFile, version, 'utf8');
|
|
281
|
+
|
|
282
|
+
// Write .wyvrn-meta.json (v2 extended metadata)
|
|
283
|
+
const meta = {
|
|
284
|
+
version,
|
|
285
|
+
resolvedFrom,
|
|
286
|
+
profileHash: downloadResult?.profileHash ?? null,
|
|
287
|
+
contentSha256: downloadResult?.contentSha256 ?? null,
|
|
288
|
+
gitSha: downloadResult?.gitSha ?? null,
|
|
289
|
+
gitRepo: downloadResult?.gitRepo ?? null,
|
|
290
|
+
installedAt: new Date().toISOString(),
|
|
291
|
+
};
|
|
292
|
+
fs.writeFileSync(metaFile, JSON.stringify(meta, null, 2) + '\n', 'utf8');
|
|
293
|
+
|
|
294
|
+
console.log(
|
|
295
|
+
`[wyvrn] Extracted: ${name}@${version}` +
|
|
296
|
+
(downloadResult?.profileHash ? ` [${downloadResult.profileHash}]` : '') +
|
|
297
|
+
` (${resolvedFrom}) → ${extractDir}`,
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
if (downloadResult?.gitSha) {
|
|
301
|
+
console.log(`[wyvrn] Source commit : ${downloadResult.gitSha}`);
|
|
302
|
+
if (downloadResult?.gitRepo) {
|
|
303
|
+
console.log(`[wyvrn] Source repo : ${downloadResult.gitRepo}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
lockEntries.set(name, {
|
|
308
|
+
version,
|
|
309
|
+
resolvedFrom,
|
|
310
|
+
profileHash: downloadResult?.profileHash ?? null,
|
|
311
|
+
contentSha256: downloadResult?.contentSha256 ?? null,
|
|
312
|
+
gitSha: downloadResult?.gitSha ?? null,
|
|
313
|
+
});
|
|
314
|
+
} catch (err) {
|
|
315
|
+
console.warn(`[wyvrn] Failed to extract ${name}@${version}: ${err.message}`);
|
|
316
|
+
lockEntries.set(name, { version, resolvedFrom: 'extract-failed' });
|
|
317
|
+
} finally {
|
|
318
|
+
try { fs.unlinkSync(destZipPath); } catch { /* ignore */ }
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return lockEntries;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
module.exports = { downloadDependencies };
|
package/src/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
module.exports = {
|
|
4
|
-
init:
|
|
5
|
-
install:
|
|
6
|
-
clean:
|
|
7
|
-
publish:
|
|
8
|
-
configure: require('./commands/configure'),
|
|
9
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
init: require('./commands/init'),
|
|
5
|
+
install: require('./commands/install'),
|
|
6
|
+
clean: require('./commands/clean'),
|
|
7
|
+
publish: require('./commands/publish'),
|
|
8
|
+
configure: require('./commands/configure'),
|
|
9
|
+
profile: require('./commands/profile'),
|
|
10
|
+
};
|