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/base.js
CHANGED
|
@@ -1,56 +1,133 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Base provider class. All publish providers must extend this.
|
|
5
|
-
*
|
|
6
|
-
* To add a new provider:
|
|
7
|
-
* 1. Create src/providers/<name>.js that extends BaseProvider
|
|
8
|
-
* 2. Register it in src/providers/index.js
|
|
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
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base provider class. All publish providers must extend this.
|
|
5
|
+
*
|
|
6
|
+
* To add a new provider:
|
|
7
|
+
* 1. Create src/providers/<name>.js that extends BaseProvider
|
|
8
|
+
* 2. Register it in src/providers/index.js
|
|
9
|
+
*
|
|
10
|
+
* ─── Schema versions ────────────────────────────────────────────────────────
|
|
11
|
+
*
|
|
12
|
+
* v1 (legacy): {source}/{platform}/{name}/{version}/wyvrn.{json,zip}
|
|
13
|
+
* {source}/{platform}/{name}/latest.json
|
|
14
|
+
*
|
|
15
|
+
* v2: {source}/v2/{name}/{version}/{profileHash}/wyvrn.{json,zip}
|
|
16
|
+
* {source}/v2/{name}/{version}/source.json
|
|
17
|
+
* {source}/v2/{name}/versions.json
|
|
18
|
+
* {source}/v2/{name}/latest.json
|
|
19
|
+
*
|
|
20
|
+
* Publishing with v2 tooling writes both v2 AND v1 paths by default so that
|
|
21
|
+
* v1 clients can still consume the packages.
|
|
22
|
+
*/
|
|
23
|
+
class BaseProvider {
|
|
24
|
+
/**
|
|
25
|
+
* Returns true if this provider can handle the given source string.
|
|
26
|
+
* Checked in registration order; first match wins.
|
|
27
|
+
* @param {string} _source
|
|
28
|
+
* @returns {boolean}
|
|
29
|
+
*/
|
|
30
|
+
static canHandle(_source) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ── v1 (legacy) API ────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Publish package files to the v1 destination path.
|
|
38
|
+
*
|
|
39
|
+
* @param {{ manifest: string, zip: string }} files
|
|
40
|
+
* @param {{ source: string, platform: string, name: string, version: string, awsProfile?: string, token?: string }} options
|
|
41
|
+
* @returns {Promise<void>}
|
|
42
|
+
*/
|
|
43
|
+
async publish(_files, _options) {
|
|
44
|
+
throw new Error(`publish() is not implemented in ${this.constructor.name}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Returns true if the given package version already exists at the v1 destination.
|
|
49
|
+
*
|
|
50
|
+
* @param {{ source: string, platform: string, name: string, version: string, awsProfile?: string, token?: string }} _options
|
|
51
|
+
* @returns {Promise<boolean>}
|
|
52
|
+
*/
|
|
53
|
+
async exists(_options) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ── v2 API ─────────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Returns true if a prebuilt binary matching the given profile hash already
|
|
61
|
+
* exists at the v2 destination.
|
|
62
|
+
*
|
|
63
|
+
* @param {{ source: string, name: string, version: string, profileHash: string, awsProfile?: string, token?: string }} _options
|
|
64
|
+
* @returns {Promise<boolean>}
|
|
65
|
+
*/
|
|
66
|
+
async v2Exists(_options) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Publish to the v2 path.
|
|
72
|
+
* Writes wyvrn.json + wyvrn.zip under profileHash, updates versions.json and latest.json.
|
|
73
|
+
*
|
|
74
|
+
* @param {{ manifest: string, zip: string }} files - local file paths
|
|
75
|
+
* @param {{
|
|
76
|
+
* source: string,
|
|
77
|
+
* name: string,
|
|
78
|
+
* version: string,
|
|
79
|
+
* profileHash: string,
|
|
80
|
+
* buildSettings: import('../profile').WyvrnProfile, // stored in metadata for debugging
|
|
81
|
+
* contentSha256: string,
|
|
82
|
+
* gitSha: string|null,
|
|
83
|
+
* gitRepo: string|null,
|
|
84
|
+
* awsProfile?: string,
|
|
85
|
+
* token?: string,
|
|
86
|
+
* }} options
|
|
87
|
+
* @returns {Promise<void>}
|
|
88
|
+
*/
|
|
89
|
+
async v2Publish(_files, _options) {
|
|
90
|
+
throw new Error(`v2Publish() is not implemented in ${this.constructor.name}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Fetch the wyvrn.json metadata object for a specific v2 build.
|
|
95
|
+
* Returns null if the build does not exist.
|
|
96
|
+
*
|
|
97
|
+
* @param {{ source: string, name: string, version: string, profileHash: string, awsProfile?: string, token?: string }} _options
|
|
98
|
+
* @returns {Promise<object|null>}
|
|
99
|
+
*/
|
|
100
|
+
async v2GetMeta(_options) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Fetch and return the parsed versions.json index for a package.
|
|
106
|
+
* Returns null if no index exists yet.
|
|
107
|
+
*
|
|
108
|
+
* @param {{ source: string, name: string, awsProfile?: string, token?: string }} _options
|
|
109
|
+
* @returns {Promise<object|null>}
|
|
110
|
+
*/
|
|
111
|
+
async v2GetVersionsIndex(_options) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Download the prebuilt binary zip for a specific v2 build to destPath.
|
|
117
|
+
*
|
|
118
|
+
* @param {{ source: string, name: string, version: string, profileHash: string, awsProfile?: string, token?: string }} options
|
|
119
|
+
* @param {string} destPath - absolute path to write the zip to
|
|
120
|
+
* @param {number} timeoutMs
|
|
121
|
+
* @returns {Promise<boolean>} true on success
|
|
122
|
+
*/
|
|
123
|
+
async v2DownloadZip(_options, _destPath, _timeoutMs) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Human-readable provider name shown in log output. */
|
|
128
|
+
static get providerName() {
|
|
129
|
+
return 'BaseProvider';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = BaseProvider;
|
package/src/providers/file.js
CHANGED
|
@@ -1,71 +1,181 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const BaseProvider = require('./base');
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* File system provider — handles local directories and SMB/UNC shares.
|
|
9
|
-
*
|
|
10
|
-
* Recognised source formats:
|
|
11
|
-
* /abs/unix/path
|
|
12
|
-
* ./relative/path ../relative/path
|
|
13
|
-
* C:\Windows\path C:/Windows/path (drive-letter)
|
|
14
|
-
* \\server\share\path (SMB UNC — backslash)
|
|
15
|
-
* //server/share/path (SMB UNC — forward slash)
|
|
16
|
-
* file:///path (file URI)
|
|
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
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const BaseProvider = require('./base');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* File system provider — handles local directories and SMB/UNC shares.
|
|
9
|
+
*
|
|
10
|
+
* Recognised source formats:
|
|
11
|
+
* /abs/unix/path
|
|
12
|
+
* ./relative/path ../relative/path
|
|
13
|
+
* C:\Windows\path C:/Windows/path (drive-letter)
|
|
14
|
+
* \\server\share\path (SMB UNC — backslash)
|
|
15
|
+
* //server/share/path (SMB UNC — forward slash)
|
|
16
|
+
* file:///path (file URI)
|
|
17
|
+
*/
|
|
18
|
+
function pinDependencies(rawDeps, lockedDeps) {
|
|
19
|
+
if (!rawDeps || !lockedDeps || Object.keys(lockedDeps).length === 0) return rawDeps;
|
|
20
|
+
const result = {};
|
|
21
|
+
for (const [pkgName, value] of Object.entries(rawDeps)) {
|
|
22
|
+
const locked = lockedDeps[pkgName];
|
|
23
|
+
if (locked) {
|
|
24
|
+
result[pkgName] = typeof value === 'object' && value !== null
|
|
25
|
+
? { ...value, version: locked }
|
|
26
|
+
: locked;
|
|
27
|
+
} else {
|
|
28
|
+
result[pkgName] = value;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class FileProvider extends BaseProvider {
|
|
35
|
+
static canHandle(source) {
|
|
36
|
+
return (
|
|
37
|
+
source.startsWith('file://') ||
|
|
38
|
+
source.startsWith('//') ||
|
|
39
|
+
source.startsWith('\\\\') ||
|
|
40
|
+
source.startsWith('/') ||
|
|
41
|
+
source.startsWith('./') ||
|
|
42
|
+
source.startsWith('../') ||
|
|
43
|
+
/^[a-zA-Z]:[/\\]/.test(source)
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static get providerName() {
|
|
48
|
+
return 'File';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
_resolvePath(source) {
|
|
52
|
+
if (source.startsWith('file://')) {
|
|
53
|
+
return path.resolve(new URL(source).pathname);
|
|
54
|
+
}
|
|
55
|
+
return path.resolve(source);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_copy(src, dest) {
|
|
59
|
+
console.log(`[wyvrn] → ${dest}`);
|
|
60
|
+
fs.copyFileSync(src, dest);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
_writeJson(filePath, data) {
|
|
64
|
+
console.log(`[wyvrn] → ${filePath}`);
|
|
65
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
_readJson(filePath) {
|
|
69
|
+
if (!fs.existsSync(filePath)) return null;
|
|
70
|
+
try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
|
|
71
|
+
catch { return null; }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── v1 (legacy) API ───────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
async exists({ source, platform, name, version }) {
|
|
77
|
+
const destDir = path.join(this._resolvePath(source), platform, name, version);
|
|
78
|
+
return fs.existsSync(path.join(destDir, 'wyvrn.json'));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async publish(files, { source, platform, name, version }) {
|
|
82
|
+
const packageDir = path.join(this._resolvePath(source), platform, name);
|
|
83
|
+
const destDir = path.join(packageDir, version);
|
|
84
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
85
|
+
|
|
86
|
+
this._copy(files.manifest, path.join(destDir, 'wyvrn.json'));
|
|
87
|
+
this._copy(files.zip, path.join(destDir, 'wyvrn.zip'));
|
|
88
|
+
|
|
89
|
+
const latestPath = path.join(packageDir, 'latest.json');
|
|
90
|
+
console.log(`[wyvrn] → ${latestPath}`);
|
|
91
|
+
fs.writeFileSync(latestPath, JSON.stringify({ version }), 'utf8');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── v2 API ────────────────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
_v2Root(source, name) {
|
|
97
|
+
return path.join(this._resolvePath(source), 'v2', name);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async v2Exists({ source, name, version, profileHash }) {
|
|
101
|
+
const p = path.join(this._v2Root(source, name), version, profileHash, 'wyvrn.json');
|
|
102
|
+
return fs.existsSync(p);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async v2Publish(files, options) {
|
|
106
|
+
const {
|
|
107
|
+
source, name, version, profileHash, buildSettings,
|
|
108
|
+
contentSha256, gitSha, gitRepo, lockedDependencies,
|
|
109
|
+
} = options;
|
|
110
|
+
|
|
111
|
+
const v2root = this._v2Root(source, name);
|
|
112
|
+
const buildDir = path.join(v2root, version, profileHash);
|
|
113
|
+
fs.mkdirSync(buildDir, { recursive: true });
|
|
114
|
+
|
|
115
|
+
// 1) zip
|
|
116
|
+
this._copy(files.zip, path.join(buildDir, 'wyvrn.zip'));
|
|
117
|
+
|
|
118
|
+
// 2) enriched wyvrn.json (buildSettings stored for debugging only)
|
|
119
|
+
// Dependencies are replaced with locked versions so the server never sees "latest".
|
|
120
|
+
const rawManifest = JSON.parse(fs.readFileSync(files.manifest, 'utf8'));
|
|
121
|
+
const pinnedDeps = pinDependencies(rawManifest.dependencies, lockedDependencies);
|
|
122
|
+
const v2Meta = {
|
|
123
|
+
...rawManifest,
|
|
124
|
+
...(pinnedDeps !== undefined ? { dependencies: pinnedDeps } : {}),
|
|
125
|
+
schemaVersion: 2,
|
|
126
|
+
profileHash,
|
|
127
|
+
buildSettings,
|
|
128
|
+
contentSha256,
|
|
129
|
+
gitSha: gitSha ?? null,
|
|
130
|
+
gitRepo: gitRepo ?? null,
|
|
131
|
+
publishedAt: new Date().toISOString(),
|
|
132
|
+
};
|
|
133
|
+
this._writeJson(path.join(buildDir, 'wyvrn.json'), v2Meta);
|
|
134
|
+
|
|
135
|
+
// 3) source.json
|
|
136
|
+
if (gitSha || gitRepo) {
|
|
137
|
+
const versionDir = path.join(v2root, version);
|
|
138
|
+
fs.mkdirSync(versionDir, { recursive: true });
|
|
139
|
+
this._writeJson(path.join(versionDir, 'source.json'), {
|
|
140
|
+
gitRepo: gitRepo ?? null, gitSha: gitSha ?? null,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 4) versions.json index
|
|
145
|
+
const versionsPath = path.join(v2root, 'versions.json');
|
|
146
|
+
let versionsIdx = this._readJson(versionsPath) ?? { name, latest: version, versions: {} };
|
|
147
|
+
if (!versionsIdx.versions) versionsIdx.versions = {};
|
|
148
|
+
if (!versionsIdx.versions[version]) versionsIdx.versions[version] = { profiles: {} };
|
|
149
|
+
versionsIdx.versions[version].profiles[profileHash] = {
|
|
150
|
+
contentSha256,
|
|
151
|
+
publishedAt: new Date().toISOString(),
|
|
152
|
+
buildSettings,
|
|
153
|
+
};
|
|
154
|
+
if (gitSha || gitRepo) {
|
|
155
|
+
versionsIdx.versions[version].source = { gitRepo: gitRepo ?? null, gitSha: gitSha ?? null };
|
|
156
|
+
}
|
|
157
|
+
versionsIdx.latest = version;
|
|
158
|
+
this._writeJson(versionsPath, versionsIdx);
|
|
159
|
+
|
|
160
|
+
// 5) v2 latest.json
|
|
161
|
+
this._writeJson(path.join(v2root, 'latest.json'), { version, profileHash, contentSha256 });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async v2GetMeta({ source, name, version, profileHash }) {
|
|
165
|
+
const p = path.join(this._v2Root(source, name), version, profileHash, 'wyvrn.json');
|
|
166
|
+
return this._readJson(p);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async v2GetVersionsIndex({ source, name }) {
|
|
170
|
+
return this._readJson(path.join(this._v2Root(source, name), 'versions.json'));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async v2DownloadZip({ source, name, version, profileHash }, destPath) {
|
|
174
|
+
const src = path.join(this._v2Root(source, name), version, profileHash, 'wyvrn.zip');
|
|
175
|
+
if (!fs.existsSync(src)) return false;
|
|
176
|
+
fs.copyFileSync(src, destPath);
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = FileProvider;
|