wyvrnpm 1.1.1 → 1.2.0
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/package.json +1 -1
- package/src/commands/publish.js +72 -1
- package/src/providers/file.js +7 -1
- package/src/providers/http.js +39 -0
- package/src/providers/s3.js +18 -0
- package/src/resolve.js +42 -0
package/package.json
CHANGED
package/src/commands/publish.js
CHANGED
|
@@ -8,6 +8,69 @@ const { readManifest } = require('../manifest');
|
|
|
8
8
|
const { getProvider } = require('../providers');
|
|
9
9
|
const { readConfig } = require('../config');
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Parse a .wyvrnignore file and return an array of pattern strings.
|
|
13
|
+
* Lines starting with '#' and blank lines are ignored.
|
|
14
|
+
*/
|
|
15
|
+
function loadIgnorePatterns(ignoreFile) {
|
|
16
|
+
if (!fs.existsSync(ignoreFile)) return [];
|
|
17
|
+
return fs
|
|
18
|
+
.readFileSync(ignoreFile, 'utf8')
|
|
19
|
+
.split(/\r?\n/)
|
|
20
|
+
.map((l) => l.trim())
|
|
21
|
+
.filter((l) => l.length > 0 && !l.startsWith('#'));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Convert a glob-style ignore pattern to a RegExp.
|
|
26
|
+
* Supports '*', '**', '?' wildcards and leading '/' to anchor to root.
|
|
27
|
+
*/
|
|
28
|
+
function patternToRegex(pattern) {
|
|
29
|
+
// A leading slash means "anchored to root"; strip it and keep the rest
|
|
30
|
+
const anchored = pattern.startsWith('/');
|
|
31
|
+
const p = anchored ? pattern.slice(1) : pattern;
|
|
32
|
+
|
|
33
|
+
let re = p
|
|
34
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // escape regex special chars
|
|
35
|
+
.replace(/\*\*/g, '\x00') // placeholder for **
|
|
36
|
+
.replace(/\*/g, '[^/]*') // * → match within segment
|
|
37
|
+
.replace(/\?/g, '[^/]') // ? → single non-slash char
|
|
38
|
+
.replace(/\x00/g, '.*'); // ** → match across segments
|
|
39
|
+
|
|
40
|
+
// If anchored, match from the start; otherwise match any path segment
|
|
41
|
+
return anchored ? new RegExp(`^${re}(/|$)`) : new RegExp(`(^|/)${re}(/|$)`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Recursively add files from `dir` into `zip`, skipping any path that
|
|
46
|
+
* matches one of the compiled ignore regexes.
|
|
47
|
+
*
|
|
48
|
+
* @param {AdmZip} zip
|
|
49
|
+
* @param {string} dir Absolute path of the directory being zipped
|
|
50
|
+
* @param {string} base Absolute path of the root publish directory
|
|
51
|
+
* @param {RegExp[]} ignoreRe Compiled ignore patterns
|
|
52
|
+
*/
|
|
53
|
+
function addFolderFiltered(zip, dir, base, ignoreRe) {
|
|
54
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
55
|
+
const fullPath = path.join(dir, entry);
|
|
56
|
+
// Relative path from publish root, always using forward slashes
|
|
57
|
+
const relPath = path.relative(base, fullPath).replace(/\\/g, '/');
|
|
58
|
+
|
|
59
|
+
if (ignoreRe.some((re) => re.test(relPath))) {
|
|
60
|
+
console.log(`[wyvrn] Ignoring: ${relPath}`);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const stat = fs.statSync(fullPath);
|
|
65
|
+
if (stat.isDirectory()) {
|
|
66
|
+
addFolderFiltered(zip, fullPath, base, ignoreRe);
|
|
67
|
+
} else {
|
|
68
|
+
const zipDir = path.dirname(relPath).replace(/\\/g, '/');
|
|
69
|
+
zip.addLocalFile(fullPath, zipDir === '.' ? '' : zipDir);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
11
74
|
async function publish(argv) {
|
|
12
75
|
let { source, profile, platform, path: publishPath, token, manifest: manifestArg } = argv;
|
|
13
76
|
|
|
@@ -74,12 +137,20 @@ async function publish(argv) {
|
|
|
74
137
|
`[wyvrn] Publishing ${name}@${version} (${targetPlatform}) via ${provider.constructor.providerName} provider`,
|
|
75
138
|
);
|
|
76
139
|
|
|
140
|
+
// Load .wyvrnignore patterns from the publish directory
|
|
141
|
+
const ignoreFile = path.join(srcDir, '.wyvrnignore');
|
|
142
|
+
const ignorePatterns = loadIgnorePatterns(ignoreFile);
|
|
143
|
+
const ignoreRe = ignorePatterns.map(patternToRegex);
|
|
144
|
+
if (ignorePatterns.length > 0) {
|
|
145
|
+
console.log(`[wyvrn] Loaded ${ignorePatterns.length} pattern(s) from .wyvrnignore`);
|
|
146
|
+
}
|
|
147
|
+
|
|
77
148
|
// Build a temporary zip of the publish directory
|
|
78
149
|
const tmpZipPath = path.join(os.tmpdir(), `wyvrnpm-${name}-${version}-${Date.now()}.zip`);
|
|
79
150
|
try {
|
|
80
151
|
console.log(`[wyvrn] Zipping ${srcDir} ...`);
|
|
81
152
|
const zip = new AdmZip();
|
|
82
|
-
zip
|
|
153
|
+
addFolderFiltered(zip, srcDir, srcDir, ignoreRe);
|
|
83
154
|
zip.writeZip(tmpZipPath);
|
|
84
155
|
|
|
85
156
|
await provider.publish(
|
package/src/providers/file.js
CHANGED
|
@@ -43,11 +43,17 @@ class FileProvider extends BaseProvider {
|
|
|
43
43
|
async publish(files, options) {
|
|
44
44
|
const { source, platform, name, version } = options;
|
|
45
45
|
|
|
46
|
-
const
|
|
46
|
+
const packageDir = path.join(this._resolvePath(source), platform, name);
|
|
47
|
+
const destDir = path.join(packageDir, version);
|
|
47
48
|
fs.mkdirSync(destDir, { recursive: true });
|
|
48
49
|
|
|
49
50
|
this._copy(files.manifest, path.join(destDir, 'wyvrn.json'));
|
|
50
51
|
this._copy(files.zip, path.join(destDir, 'wyvrn.zip'));
|
|
52
|
+
|
|
53
|
+
// Write latest.json at the package root so consumers can resolve "latest"
|
|
54
|
+
const latestPath = path.join(packageDir, 'latest.json');
|
|
55
|
+
console.log(`[wyvrn] → ${latestPath}`);
|
|
56
|
+
fs.writeFileSync(latestPath, JSON.stringify({ version }), 'utf8');
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
_copy(src, dest) {
|
package/src/providers/http.js
CHANGED
|
@@ -18,9 +18,13 @@ class HttpProvider extends BaseProvider {
|
|
|
18
18
|
const { source, platform, name, version, token } = options;
|
|
19
19
|
const base = source.replace(/\/$/, '');
|
|
20
20
|
const urlBase = `${base}/${platform}/${name}/${version}`;
|
|
21
|
+
const packageBase = `${base}/${platform}/${name}`;
|
|
21
22
|
|
|
22
23
|
await this._put(`${urlBase}/wyvrn.json`, files.manifest, 'application/json', token);
|
|
23
24
|
await this._put(`${urlBase}/wyvrn.zip`, files.zip, 'application/zip', token);
|
|
25
|
+
|
|
26
|
+
// Write latest.json at the package root so consumers can resolve "latest"
|
|
27
|
+
await this._putBuffer(`${packageBase}/latest.json`, Buffer.from(JSON.stringify({ version })), 'application/json', token);
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
_put(url, filePath, contentType, token) {
|
|
@@ -58,6 +62,41 @@ class HttpProvider extends BaseProvider {
|
|
|
58
62
|
req.end();
|
|
59
63
|
});
|
|
60
64
|
}
|
|
65
|
+
|
|
66
|
+
_putBuffer(url, buffer, contentType, token) {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
const parsed = new URL(url);
|
|
69
|
+
const lib = parsed.protocol === 'https:' ? https : http;
|
|
70
|
+
|
|
71
|
+
const headers = {
|
|
72
|
+
'Content-Type': contentType,
|
|
73
|
+
'Content-Length': buffer.length,
|
|
74
|
+
};
|
|
75
|
+
if (token) headers['Authorization'] = `Bearer ${token}`;
|
|
76
|
+
|
|
77
|
+
console.log(`[wyvrn] → PUT ${url}`);
|
|
78
|
+
const req = lib.request(
|
|
79
|
+
{
|
|
80
|
+
hostname: parsed.hostname,
|
|
81
|
+
port: parsed.port || undefined,
|
|
82
|
+
path: parsed.pathname,
|
|
83
|
+
method: 'PUT',
|
|
84
|
+
headers,
|
|
85
|
+
},
|
|
86
|
+
(res) => {
|
|
87
|
+
res.resume();
|
|
88
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
89
|
+
resolve();
|
|
90
|
+
} else {
|
|
91
|
+
reject(new Error(`HTTP PUT ${url} failed: ${res.statusCode} ${res.statusMessage}`));
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
req.on('error', reject);
|
|
96
|
+
req.write(buffer);
|
|
97
|
+
req.end();
|
|
98
|
+
});
|
|
99
|
+
}
|
|
61
100
|
}
|
|
62
101
|
|
|
63
102
|
module.exports = HttpProvider;
|
package/src/providers/s3.js
CHANGED
|
@@ -67,12 +67,18 @@ class S3Provider extends BaseProvider {
|
|
|
67
67
|
|
|
68
68
|
// Destination key base: [prefix/]platform/name/version
|
|
69
69
|
const keyBase = [prefix, platform, name, version].filter(Boolean).join('/');
|
|
70
|
+
// Package root (without version) for latest.json
|
|
71
|
+
const packageBase = [prefix, platform, name].filter(Boolean).join('/');
|
|
70
72
|
|
|
71
73
|
const clientConfig = profile ? { credentials: fromSSO({ profile }) } : {};
|
|
72
74
|
const client = new S3Client(clientConfig);
|
|
73
75
|
|
|
74
76
|
await this._put(client, bucket, `${keyBase}/wyvrn.json`, files.manifest, 'application/json', PutObjectCommand);
|
|
75
77
|
await this._put(client, bucket, `${keyBase}/wyvrn.zip`, files.zip, 'application/zip', PutObjectCommand);
|
|
78
|
+
|
|
79
|
+
// Write latest.json at the package root so consumers can resolve "latest"
|
|
80
|
+
const latestContent = Buffer.from(JSON.stringify({ version }));
|
|
81
|
+
await this._putBuffer(client, bucket, `${packageBase}/latest.json`, latestContent, 'application/json', PutObjectCommand);
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
async _put(client, bucket, key, filePath, contentType, PutObjectCommand) {
|
|
@@ -86,6 +92,18 @@ class S3Provider extends BaseProvider {
|
|
|
86
92
|
}),
|
|
87
93
|
);
|
|
88
94
|
}
|
|
95
|
+
|
|
96
|
+
async _putBuffer(client, bucket, key, content, contentType, PutObjectCommand) {
|
|
97
|
+
console.log(`[wyvrn] → s3://${bucket}/${key}`);
|
|
98
|
+
await client.send(
|
|
99
|
+
new PutObjectCommand({
|
|
100
|
+
Bucket: bucket,
|
|
101
|
+
Key: key,
|
|
102
|
+
Body: content,
|
|
103
|
+
ContentType: contentType,
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
}
|
|
89
107
|
}
|
|
90
108
|
|
|
91
109
|
module.exports = S3Provider;
|
package/src/resolve.js
CHANGED
|
@@ -22,6 +22,36 @@ function compareVersions(a, b) {
|
|
|
22
22
|
return 0;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Resolves the "latest" tag for a package by fetching latest.json from the registry.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} name
|
|
29
|
+
* @param {string[]} packageSources
|
|
30
|
+
* @param {string} platform
|
|
31
|
+
* @param {Function} httpClient
|
|
32
|
+
* @returns {Promise<string>} The resolved version string.
|
|
33
|
+
*/
|
|
34
|
+
async function resolveLatestVersion(name, packageSources, platform, httpClient) {
|
|
35
|
+
for (const source of packageSources) {
|
|
36
|
+
const base = source.replace(/\/$/, '');
|
|
37
|
+
const candidates = [
|
|
38
|
+
`${base}/${platform}/${name}/latest.json`,
|
|
39
|
+
`${base}/common/${name}/latest.json`,
|
|
40
|
+
];
|
|
41
|
+
for (const url of candidates) {
|
|
42
|
+
try {
|
|
43
|
+
const response = await httpClient(url);
|
|
44
|
+
if (!response.ok) continue;
|
|
45
|
+
const data = await response.json();
|
|
46
|
+
if (data && data.version) return data.version;
|
|
47
|
+
} catch {
|
|
48
|
+
// Try next candidate.
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
throw new Error(`Could not resolve "latest" version for "${name}" from any source`);
|
|
53
|
+
}
|
|
54
|
+
|
|
25
55
|
/**
|
|
26
56
|
* Fetches razer.json for a single package from the first source that responds.
|
|
27
57
|
*
|
|
@@ -96,6 +126,17 @@ async function resolveDependencies(rootDeps, packageSources, platform, httpClien
|
|
|
96
126
|
version = lockedVersions.get(name);
|
|
97
127
|
}
|
|
98
128
|
|
|
129
|
+
// Resolve the "latest" tag to a concrete version before proceeding.
|
|
130
|
+
if (version === 'latest') {
|
|
131
|
+
try {
|
|
132
|
+
version = await resolveLatestVersion(name, packageSources, platform, httpClient);
|
|
133
|
+
console.log(`[wyvrn] Resolved "${name}@latest" → ${version}`);
|
|
134
|
+
} catch (err) {
|
|
135
|
+
console.warn(`[wyvrn] Warning: ${err.message}`);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
99
140
|
if (resolved.has(name)) {
|
|
100
141
|
const existing = resolved.get(name);
|
|
101
142
|
if (existing === version) {
|
|
@@ -154,5 +195,6 @@ async function resolveDependencies(rootDeps, packageSources, platform, httpClien
|
|
|
154
195
|
|
|
155
196
|
module.exports = {
|
|
156
197
|
compareVersions,
|
|
198
|
+
resolveLatestVersion,
|
|
157
199
|
resolveDependencies,
|
|
158
200
|
};
|