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.
@@ -0,0 +1,171 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ detectProfile,
5
+ hashProfile,
6
+ formatProfile,
7
+ readProfile,
8
+ writeProfile,
9
+ deleteProfile,
10
+ listProfiles,
11
+ getProfilesDir,
12
+ mergeProfile,
13
+ } = require('../profile');
14
+ const { readConfig, writeConfig } = require('../config');
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // list
18
+ // ---------------------------------------------------------------------------
19
+
20
+ /**
21
+ * wyvrnpm configure profile list
22
+ * Show all saved profiles and which one is the default.
23
+ */
24
+ function list() {
25
+ const config = readConfig();
26
+ const names = listProfiles();
27
+ const dir = getProfilesDir();
28
+ const defName = config.defaultProfile ?? 'default';
29
+
30
+ console.log(`[wyvrn] Profiles directory: ${dir}`);
31
+ console.log(`[wyvrn] Default profile : ${defName}\n`);
32
+
33
+ if (names.length === 0) {
34
+ console.log(' (no profiles saved — run `wyvrnpm configure profile detect` to create one)');
35
+ return;
36
+ }
37
+
38
+ for (const name of names) {
39
+ const marker = name === defName ? ' *' : '';
40
+ const p = readProfile(name);
41
+ const summary = p
42
+ ? `${p.os}/${p.arch} ${p.compiler} ${p['compiler.version']} C++${p['compiler.cppstd']}` +
43
+ (p['compiler.runtime'] ? ` [${p['compiler.runtime']}]` : '')
44
+ : '(unreadable)';
45
+ console.log(` ${(name + marker).padEnd(24)} ${summary}`);
46
+ }
47
+ }
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // show
51
+ // ---------------------------------------------------------------------------
52
+
53
+ /**
54
+ * wyvrnpm configure profile show [--name <name>]
55
+ * Display a specific profile (defaults to the configured default).
56
+ */
57
+ function show(argv) {
58
+ const config = readConfig();
59
+ const name = argv.name ?? config.defaultProfile ?? 'default';
60
+ const stored = readProfile(name);
61
+
62
+ if (stored) {
63
+ console.log(`[wyvrn] Profile "${name}":`);
64
+ console.log(formatProfile(stored));
65
+ console.log(`\nProfile hash : ${hashProfile(stored)}`);
66
+ } else {
67
+ const detected = detectProfile();
68
+ console.log(`[wyvrn] Profile "${name}" not found — showing auto-detected environment:`);
69
+ console.log(formatProfile(detected));
70
+ console.log(`\nProfile hash : ${hashProfile(detected)}`);
71
+ console.log(`\nRun \`wyvrnpm configure profile detect --name ${name}\` to save this.`);
72
+ }
73
+ }
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // detect
77
+ // ---------------------------------------------------------------------------
78
+
79
+ /**
80
+ * wyvrnpm configure profile detect [--name <name>] [--dry-run]
81
+ * Auto-detect the current build environment and save it as a named profile.
82
+ */
83
+ function detect(argv) {
84
+ const config = readConfig();
85
+ const name = argv.name ?? config.defaultProfile ?? 'default';
86
+ const p = detectProfile();
87
+
88
+ console.log('[wyvrn] Detected profile:');
89
+ console.log(formatProfile(p));
90
+ console.log(`\nProfile hash : ${hashProfile(p)}`);
91
+
92
+ if (!argv.dryRun) {
93
+ writeProfile(name, p);
94
+ console.log(`\n[wyvrn] Saved as profile "${name}".`);
95
+ }
96
+ }
97
+
98
+ // ---------------------------------------------------------------------------
99
+ // set
100
+ // ---------------------------------------------------------------------------
101
+
102
+ /**
103
+ * wyvrnpm configure profile set [--name <name>] [field options]
104
+ * Manually override individual fields in a named profile.
105
+ * Starts from the existing saved profile, or auto-detects if none exists yet.
106
+ */
107
+ function set(argv) {
108
+ const config = readConfig();
109
+ const name = argv.name ?? config.defaultProfile ?? 'default';
110
+ const base = readProfile(name) ?? detectProfile();
111
+
112
+ const updated = mergeProfile(base, {
113
+ os: argv.os ?? null,
114
+ arch: argv.arch ?? null,
115
+ compiler: argv.compiler ?? null,
116
+ 'compiler.version': argv.compilerVersion ?? null,
117
+ 'compiler.cppstd': argv.cppstd ?? null,
118
+ 'compiler.runtime': argv.runtime ?? null,
119
+ });
120
+
121
+ writeProfile(name, updated);
122
+
123
+ console.log(`[wyvrn] Profile "${name}" updated:`);
124
+ console.log(formatProfile(updated));
125
+ console.log(`\nProfile hash : ${hashProfile(updated)}`);
126
+ }
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // setDefault
130
+ // ---------------------------------------------------------------------------
131
+
132
+ /**
133
+ * wyvrnpm configure profile set-default <name>
134
+ * Change which named profile is used by default.
135
+ */
136
+ function setDefault(argv) {
137
+ const name = argv.name;
138
+ if (!readProfile(name)) {
139
+ console.error(`[wyvrn] Error: profile "${name}" does not exist. Create it first.`);
140
+ process.exit(1);
141
+ }
142
+ const config = readConfig();
143
+ config.defaultProfile = name;
144
+ writeConfig(config);
145
+ console.log(`[wyvrn] Default profile set to "${name}".`);
146
+ }
147
+
148
+ // ---------------------------------------------------------------------------
149
+ // del
150
+ // ---------------------------------------------------------------------------
151
+
152
+ /**
153
+ * wyvrnpm configure profile delete <name>
154
+ * Remove a saved profile file.
155
+ */
156
+ function del(argv) {
157
+ const name = argv.name;
158
+ if (name === 'default') {
159
+ console.error('[wyvrn] Error: cannot delete the "default" profile.');
160
+ process.exit(1);
161
+ }
162
+ const removed = deleteProfile(name);
163
+ if (removed) {
164
+ console.log(`[wyvrn] Profile "${name}" deleted.`);
165
+ } else {
166
+ console.error(`[wyvrn] Error: profile "${name}" not found.`);
167
+ process.exit(1);
168
+ }
169
+ }
170
+
171
+ module.exports = { list, show, detect, set, setDefault, del };
@@ -1,180 +1,232 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const os = require('os');
6
- const AdmZip = require('adm-zip');
7
- const { readManifest } = require('../manifest');
8
- const { getProvider } = require('../providers');
9
- const { readConfig } = require('../config');
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
-
74
- async function publish(argv) {
75
- let { source, profile, platform, path: publishPath, token, manifest: manifestArg, force } = argv;
76
-
77
- // Resolve source + auth: CLI > config entry by name > first config entry > raw URL
78
- if (source) {
79
- // Check if --source matches a named config entry rather than a raw URL/URI
80
- const config = readConfig();
81
- const named = config.publishSources.find((s) => s.name === source);
82
- if (named) {
83
- console.log(`[wyvrn] Using publish source "${named.name}" from config`);
84
- source = named.url;
85
- profile = profile ?? named.profile;
86
- token = token ?? named.token;
87
- }
88
- } else {
89
- // No --source given — fall back to first configured publish source
90
- const config = readConfig();
91
- if (config.publishSources.length === 0) {
92
- console.error(
93
- '[wyvrn] Error: --source is required (or configure one with: ' +
94
- 'wyvrnpm configure add-source --kind publish ...)',
95
- );
96
- process.exit(1);
97
- }
98
- const first = config.publishSources[0];
99
- console.log(`[wyvrn] Using publish source "${first.name}" from config`);
100
- source = first.url;
101
- profile = profile ?? first.profile;
102
- token = token ?? first.token;
103
- }
104
-
105
- // Resolve and read the manifest
106
- const manifestPath = path.resolve(manifestArg || './wyvrn.json');
107
- const manifest = await readManifest(manifestPath);
108
- if (!manifest) {
109
- console.error(`[wyvrn] Error: Could not read manifest at ${manifestPath}`);
110
- process.exit(1);
111
- }
112
-
113
- const { name, version } = manifest;
114
- if (!name || !version) {
115
- console.error('[wyvrn] Error: Manifest must have "name" and "version" fields');
116
- process.exit(1);
117
- }
118
-
119
- const targetPlatform = platform || 'common';
120
- const srcDir = path.resolve(publishPath || '.');
121
-
122
- if (!fs.existsSync(srcDir)) {
123
- console.error(`[wyvrn] Error: Publish path does not exist: ${srcDir}`);
124
- process.exit(1);
125
- }
126
-
127
- // Resolve provider from source URL/URI
128
- let provider;
129
- try {
130
- provider = getProvider(source);
131
- } catch (err) {
132
- console.error(`[wyvrn] Error: ${err.message}`);
133
- process.exit(1);
134
- }
135
-
136
- console.log(
137
- `[wyvrn] Publishing ${name}@${version} (${targetPlatform}) via ${provider.constructor.providerName} provider`,
138
- );
139
-
140
- // Prevent overwriting an already-published version unless --force is passed
141
- const alreadyExists = await provider.exists({ source, platform: targetPlatform, name, version, profile, token });
142
- if (alreadyExists) {
143
- if (force) {
144
- console.warn(`[wyvrn] Warning: ${name}@${version} already exists overwriting (--force)`);
145
- } else {
146
- console.error(
147
- `[wyvrn] Error: ${name}@${version} already exists. Bump the version or use --force to overwrite.`,
148
- );
149
- process.exit(1);
150
- }
151
- }
152
-
153
- // Load .wyvrnignore patterns from the publish directory
154
- const ignoreFile = path.join(srcDir, '.wyvrnignore');
155
- const ignorePatterns = loadIgnorePatterns(ignoreFile);
156
- const ignoreRe = ignorePatterns.map(patternToRegex);
157
- if (ignorePatterns.length > 0) {
158
- console.log(`[wyvrn] Loaded ${ignorePatterns.length} pattern(s) from .wyvrnignore`);
159
- }
160
-
161
- // Build a temporary zip of the publish directory
162
- const tmpZipPath = path.join(os.tmpdir(), `wyvrnpm-${name}-${version}-${Date.now()}.zip`);
163
- try {
164
- console.log(`[wyvrn] Zipping ${srcDir} ...`);
165
- const zip = new AdmZip();
166
- addFolderFiltered(zip, srcDir, srcDir, ignoreRe);
167
- zip.writeZip(tmpZipPath);
168
-
169
- await provider.publish(
170
- { manifest: manifestPath, zip: tmpZipPath },
171
- { source, platform: targetPlatform, name, version, profile, token },
172
- );
173
-
174
- console.log(`[wyvrn] Successfully published ${name}@${version} to ${source}`);
175
- } finally {
176
- if (fs.existsSync(tmpZipPath)) fs.unlinkSync(tmpZipPath);
177
- }
178
- }
179
-
180
- module.exports = publish;
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const AdmZip = require('adm-zip');
7
+
8
+ const { readManifest } = require('../manifest');
9
+ const { getProvider } = require('../providers');
10
+ const { readConfig } = require('../config');
11
+ const { loadProfile, hashProfile, sha256Of, getGitSha, getGitRepo } = require('../profile');
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // .wyvrnignore helpers (unchanged from v1)
15
+ // ---------------------------------------------------------------------------
16
+
17
+ function loadIgnorePatterns(ignoreFile) {
18
+ if (!fs.existsSync(ignoreFile)) return [];
19
+ return fs
20
+ .readFileSync(ignoreFile, 'utf8')
21
+ .split(/\r?\n/)
22
+ .map((l) => l.trim())
23
+ .filter((l) => l.length > 0 && !l.startsWith('#'));
24
+ }
25
+
26
+ function patternToRegex(pattern) {
27
+ const anchored = pattern.startsWith('/');
28
+ const p = anchored ? pattern.slice(1) : pattern;
29
+ let re = p
30
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
31
+ .replace(/\*\*/g, '\x00')
32
+ .replace(/\*/g, '[^/]*')
33
+ .replace(/\?/g, '[^/]')
34
+ .replace(/\x00/g, '.*');
35
+ return anchored ? new RegExp(`^${re}(/|$)`) : new RegExp(`(^|/)${re}(/|$)`);
36
+ }
37
+
38
+ function addFolderFiltered(zip, dir, base, ignoreRe) {
39
+ for (const entry of fs.readdirSync(dir)) {
40
+ const fullPath = path.join(dir, entry);
41
+ const relPath = path.relative(base, fullPath).replace(/\\/g, '/');
42
+ if (ignoreRe.some((re) => re.test(relPath))) {
43
+ console.log(`[wyvrn] Ignoring: ${relPath}`);
44
+ continue;
45
+ }
46
+ const stat = fs.statSync(fullPath);
47
+ if (stat.isDirectory()) {
48
+ addFolderFiltered(zip, fullPath, base, ignoreRe);
49
+ } else {
50
+ const zipDir = path.dirname(relPath).replace(/\\/g, '/');
51
+ zip.addLocalFile(fullPath, zipDir === '.' ? '' : zipDir);
52
+ }
53
+ }
54
+ }
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Source resolution
58
+ // ---------------------------------------------------------------------------
59
+
60
+ function resolveSource(argv) {
61
+ let { source, awsProfile, token } = argv;
62
+ const config = readConfig();
63
+
64
+ if (source) {
65
+ const named = config.publishSources.find((s) => s.name === source);
66
+ if (named) {
67
+ console.log(`[wyvrn] Using publish source "${named.name}" from config`);
68
+ source = named.url;
69
+ awsProfile = awsProfile ?? named.profile;
70
+ token = token ?? named.token;
71
+ }
72
+ } else {
73
+ if (config.publishSources.length === 0) {
74
+ console.error(
75
+ '[wyvrn] Error: --source is required (or configure one with: ' +
76
+ 'wyvrnpm configure add-source --kind publish ...)',
77
+ );
78
+ process.exit(1);
79
+ }
80
+ const first = config.publishSources[0];
81
+ console.log(`[wyvrn] Using publish source "${first.name}" from config`);
82
+ source = first.url;
83
+ awsProfile = awsProfile ?? first.profile;
84
+ token = token ?? first.token;
85
+ }
86
+
87
+ return { source, awsProfile, token };
88
+ }
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // Main publish command
92
+ // ---------------------------------------------------------------------------
93
+
94
+ async function publish(argv) {
95
+ const { source, awsProfile, token } = resolveSource(argv);
96
+ const {
97
+ profile: profileName,
98
+ path: publishPath,
99
+ force,
100
+ manifest: manifestArg,
101
+ } = argv;
102
+
103
+ // ── Load manifest ─────────────────────────────────────────────────────────
104
+ const manifestPath = path.resolve(manifestArg || './wyvrn.json');
105
+ const manifest = await readManifest(manifestPath);
106
+ if (!manifest) {
107
+ console.error(`[wyvrn] Error: Could not read manifest at ${manifestPath}`);
108
+ process.exit(1);
109
+ }
110
+
111
+ const { name, version } = manifest;
112
+ if (!name || !version) {
113
+ console.error('[wyvrn] Error: Manifest must have "name" and "version" fields');
114
+ process.exit(1);
115
+ }
116
+
117
+ const srcDir = path.resolve(publishPath || '.');
118
+ if (!fs.existsSync(srcDir)) {
119
+ console.error(`[wyvrn] Error: Publish path does not exist: ${srcDir}`);
120
+ process.exit(1);
121
+ }
122
+
123
+ // ── Resolve provider ──────────────────────────────────────────────────────
124
+ let provider;
125
+ try {
126
+ provider = getProvider(source);
127
+ } catch (err) {
128
+ console.error(`[wyvrn] Error: ${err.message}`);
129
+ process.exit(1);
130
+ }
131
+
132
+ // ── Load build profile ────────────────────────────────────────────────────
133
+ const config = readConfig();
134
+ const activeProfileName = profileName ?? config.defaultProfile ?? 'default';
135
+ const { profile: buildProfile, fromFile } = loadProfile(activeProfileName);
136
+ const profileHash = hashProfile(buildProfile);
137
+
138
+ console.log(
139
+ `[wyvrn] Publishing ${name}@${version}\n` +
140
+ `[wyvrn] Profile : "${activeProfileName}"${fromFile ? '' : ' (auto-detected save with `wyvrnpm configure profile detect`)'}\n` +
141
+ `[wyvrn] ${buildProfile.os}/${buildProfile.arch} ` +
142
+ `${buildProfile.compiler} ${buildProfile['compiler.version']} ` +
143
+ `C++${buildProfile['compiler.cppstd']}` +
144
+ (buildProfile['compiler.runtime'] ? ` [${buildProfile['compiler.runtime']}]` : '') + '\n' +
145
+ `[wyvrn] Profile hash: ${profileHash}\n` +
146
+ `[wyvrn] Provider : ${provider.constructor.providerName}`,
147
+ );
148
+
149
+ // ── Check v2 existence ────────────────────────────────────────────────────
150
+ const v2AlreadyExists = await provider.v2Exists({ source, name, version, profileHash, awsProfile, token });
151
+ if (v2AlreadyExists) {
152
+ if (force) {
153
+ console.warn(`[wyvrn] Warning: ${name}@${version} [${profileHash}] already exists overwriting (--force)`);
154
+ } else {
155
+ console.error(
156
+ `[wyvrn] Error: ${name}@${version} [${profileHash}] already exists.\n` +
157
+ ' Bump the version, use a different profile, or pass --force to overwrite.',
158
+ );
159
+ process.exit(1);
160
+ }
161
+ }
162
+
163
+ // ── Read locked dependency versions from wyvrn.lock ──────────────────────
164
+ const lockPath = path.join(path.dirname(manifestPath), 'wyvrn.lock');
165
+ const lockedDependencies = {};
166
+ if (fs.existsSync(lockPath)) {
167
+ try {
168
+ const lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
169
+ for (const [pkgName, entry] of Object.entries(lock.packages ?? {})) {
170
+ const ver = typeof entry === 'string' ? entry : entry.version;
171
+ if (ver) lockedDependencies[pkgName] = ver;
172
+ }
173
+ console.log(`[wyvrn] Lock file : ${Object.keys(lockedDependencies).length} pinned package(s)`);
174
+ } catch {
175
+ console.warn('[wyvrn] Warning: could not read wyvrn.lock — dependency versions will be published as-is from wyvrn.json');
176
+ }
177
+ } else {
178
+ console.warn('[wyvrn] Warning: no wyvrn.lock found — run `wyvrnpm install` before publishing to pin dependency versions');
179
+ }
180
+
181
+ // ── Git metadata ──────────────────────────────────────────────────────────
182
+ const gitSha = getGitSha(srcDir);
183
+ const gitRepo = getGitRepo(srcDir);
184
+ if (gitSha) console.log(`[wyvrn] Git commit : ${gitSha}`);
185
+ if (gitRepo) console.log(`[wyvrn] Git remote : ${gitRepo}`);
186
+
187
+ // ── .wyvrnignore ──────────────────────────────────────────────────────────
188
+ const ignoreFile = path.join(srcDir, '.wyvrnignore');
189
+ const ignorePatterns = loadIgnorePatterns(ignoreFile);
190
+ const ignoreRe = ignorePatterns.map(patternToRegex);
191
+ if (ignorePatterns.length > 0) {
192
+ console.log(`[wyvrn] Loaded ${ignorePatterns.length} pattern(s) from .wyvrnignore`);
193
+ }
194
+
195
+ // ── Build zip ─────────────────────────────────────────────────────────────
196
+ const tmpZipPath = path.join(os.tmpdir(), `wyvrnpm-${name}-${version}-${Date.now()}.zip`);
197
+ try {
198
+ console.log(`[wyvrn] Zipping ${srcDir} ...`);
199
+ const zip = new AdmZip();
200
+ addFolderFiltered(zip, srcDir, srcDir, ignoreRe);
201
+ zip.writeZip(tmpZipPath);
202
+
203
+ const contentSha256 = sha256Of(tmpZipPath);
204
+ console.log(`[wyvrn] Content SHA256 : ${contentSha256}`);
205
+
206
+ // ── v2 publish ────────────────────────────────────────────────────────
207
+ console.log('[wyvrn] Publishing (v2) ...');
208
+ await provider.v2Publish(
209
+ { manifest: manifestPath, zip: tmpZipPath },
210
+ {
211
+ source,
212
+ name,
213
+ version,
214
+ profileHash,
215
+ // buildSettings stored in metadata for debugging — NOT wyvrn.json
216
+ buildSettings: buildProfile,
217
+ contentSha256,
218
+ gitSha,
219
+ gitRepo,
220
+ lockedDependencies,
221
+ awsProfile,
222
+ token,
223
+ },
224
+ );
225
+
226
+ console.log(`[wyvrn] Successfully published ${name}@${version} [${profileHash}] to ${source}`);
227
+ } finally {
228
+ if (fs.existsSync(tmpZipPath)) fs.unlinkSync(tmpZipPath);
229
+ }
230
+ }
231
+
232
+ module.exports = publish;