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.
@@ -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
- class BaseProvider {
11
- /**
12
- * Returns true if this provider can handle the given source string.
13
- * Checked in registration order; first match wins.
14
- * @param {string} _source
15
- * @returns {boolean}
16
- */
17
- static canHandle(_source) {
18
- return false;
19
- }
20
-
21
- /**
22
- * Publish package files to the destination.
23
- *
24
- * @param {{ manifest: string, zip: string }} files
25
- * Absolute paths to the local wyvrn.json and wyvrn.zip to upload.
26
- * @param {{ source: string, platform: string, name: string, version: string, profile?: string, token?: string }} options
27
- * `source` – the raw --source value (URL / URI)
28
- * `platform` target platform string (e.g. win_x64)
29
- * `name` – package name from manifest
30
- * `version` package version from manifest
31
- * `profile` – AWS SSO profile (S3 provider)
32
- * `token` – Bearer token (HTTP provider)
33
- * @returns {Promise<void>}
34
- */
35
- async publish(_files, _options) {
36
- throw new Error(`publish() is not implemented in ${this.constructor.name}`);
37
- }
38
-
39
- /**
40
- * Returns true if the given package version already exists at the destination.
41
- * Providers should override this; the default assumes it does not exist.
42
- *
43
- * @param {{ source: string, platform: string, name: string, version: string, profile?: string, token?: string }} _options
44
- * @returns {Promise<boolean>}
45
- */
46
- async exists(_options) {
47
- return false;
48
- }
49
-
50
- /** Human-readable provider name shown in log output. */
51
- static get providerName() {
52
- return 'BaseProvider';
53
- }
54
- }
55
-
56
- module.exports = BaseProvider;
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;
@@ -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
- class FileProvider extends BaseProvider {
19
- static canHandle(source) {
20
- return (
21
- source.startsWith('file://') ||
22
- source.startsWith('//') ||
23
- source.startsWith('\\\\') ||
24
- source.startsWith('/') ||
25
- source.startsWith('./') ||
26
- source.startsWith('../') ||
27
- /^[a-zA-Z]:[/\\]/.test(source) // Windows drive letter
28
- );
29
- }
30
-
31
- static get providerName() {
32
- return 'File';
33
- }
34
-
35
- /** Normalise a source string to an absolute file system path. */
36
- _resolvePath(source) {
37
- if (source.startsWith('file://')) {
38
- return path.resolve(new URL(source).pathname);
39
- }
40
- return path.resolve(source);
41
- }
42
-
43
- async exists(options) {
44
- const { source, platform, name, version } = options;
45
- const destDir = path.join(this._resolvePath(source), platform, name, version);
46
- return fs.existsSync(path.join(destDir, 'wyvrn.json'));
47
- }
48
-
49
- async publish(files, options) {
50
- const { source, platform, name, version } = options;
51
-
52
- const packageDir = path.join(this._resolvePath(source), platform, name);
53
- const destDir = path.join(packageDir, version);
54
- fs.mkdirSync(destDir, { recursive: true });
55
-
56
- this._copy(files.manifest, path.join(destDir, 'wyvrn.json'));
57
- this._copy(files.zip, path.join(destDir, 'wyvrn.zip'));
58
-
59
- // Write latest.json at the package root so consumers can resolve "latest"
60
- const latestPath = path.join(packageDir, 'latest.json');
61
- console.log(`[wyvrn] → ${latestPath}`);
62
- fs.writeFileSync(latestPath, JSON.stringify({ version }), 'utf8');
63
- }
64
-
65
- _copy(src, dest) {
66
- console.log(`[wyvrn] → ${dest}`);
67
- fs.copyFileSync(src, dest);
68
- }
69
- }
70
-
71
- module.exports = FileProvider;
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;