wyvrnpm 2.4.1 → 2.9.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/README.md +414 -16
- package/bin/wyvrn.js +154 -11
- package/claude/skills/wyvrnpm.skill +0 -0
- package/cmake/variables.cmake +13 -3
- package/package.json +1 -1
- package/src/auth.js +66 -0
- package/src/build/cache.js +196 -0
- package/src/build/index.js +26 -4
- package/src/build/msvc-env.js +50 -7
- package/src/build/recipe.js +36 -3
- package/src/commands/add.js +2 -1
- package/src/commands/build.js +162 -36
- package/src/commands/cache.js +189 -0
- package/src/commands/configure.js +15 -4
- package/src/commands/init.js +36 -0
- package/src/commands/install-skill.js +8 -0
- package/src/commands/install.js +674 -400
- package/src/commands/link.js +320 -319
- package/src/commands/profile.js +237 -174
- package/src/commands/publish.js +435 -383
- package/src/commands/show.js +24 -9
- package/src/conf/index.js +391 -0
- package/src/conf/namespaces.js +94 -0
- package/src/context.js +230 -0
- package/src/download.js +135 -147
- package/src/http-fetch.js +53 -0
- package/src/logger.js +30 -2
- package/src/manifest.js +34 -7
- package/src/profile.js +148 -12
- package/src/providers/file.js +5 -19
- package/src/providers/http.js +26 -26
- package/src/providers/s3.js +29 -25
- package/src/resolve.js +47 -7
- package/src/settings-overrides.js +152 -0
- package/src/toolchain/presets.js +71 -31
- package/src/upload-built.js +11 -2
- package/src/zip-safe.js +52 -0
package/src/commands/publish.js
CHANGED
|
@@ -1,383 +1,435 @@
|
|
|
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 {
|
|
9
|
-
const {
|
|
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
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
} else {
|
|
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
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
// ──
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
log.info(`
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
if (
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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 { getProvider } = require('../providers');
|
|
9
|
+
const {
|
|
10
|
+
readRecipeConf,
|
|
11
|
+
mergeConfLayers,
|
|
12
|
+
unflattenConf,
|
|
13
|
+
} = require('../conf');
|
|
14
|
+
const { hashProfile, sha256Of, getGitSha, getGitRepo, isShaOnRemote } = require('../profile');
|
|
15
|
+
const { defaultCompatBlock } = require('../compat');
|
|
16
|
+
const { buildContext } = require('../context');
|
|
17
|
+
const {
|
|
18
|
+
normalizeOptionsDeclaration,
|
|
19
|
+
resolveEffectiveOptions,
|
|
20
|
+
} = require('../options');
|
|
21
|
+
const { loadIgnorePatterns, isIgnored } = require('../ignore');
|
|
22
|
+
const { hasBuildRecipe, normalizeRecipe } = require('../build/recipe');
|
|
23
|
+
const log = require('../logger');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Walk `dir` recursively and add every file to `zip`, skipping anything that
|
|
27
|
+
* matches `.wyvrnignore` patterns OR whose zip-relative path is already in
|
|
28
|
+
* `reservedRelPaths`. The reserved set lets the install-tree overlay win
|
|
29
|
+
* on path collisions (e.g. `include/foo.h` shipped by both source and
|
|
30
|
+
* install trees).
|
|
31
|
+
*/
|
|
32
|
+
function addFolderFiltered(zip, dir, base, patterns, reservedRelPaths = new Set()) {
|
|
33
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
34
|
+
const fullPath = path.join(dir, entry);
|
|
35
|
+
const relPath = path.relative(base, fullPath).replace(/\\/g, '/');
|
|
36
|
+
const stat = fs.statSync(fullPath);
|
|
37
|
+
const isDir = stat.isDirectory();
|
|
38
|
+
if (isIgnored(relPath, isDir, patterns)) {
|
|
39
|
+
log.info(` Ignoring: ${relPath}${isDir ? '/' : ''}`);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (isDir) {
|
|
43
|
+
addFolderFiltered(zip, fullPath, base, patterns, reservedRelPaths);
|
|
44
|
+
} else if (reservedRelPaths.has(relPath)) {
|
|
45
|
+
// Install tree has a canonical version of this file; let that win.
|
|
46
|
+
log.info(` Install-tree wins: ${relPath}`);
|
|
47
|
+
} else {
|
|
48
|
+
const zipDir = path.dirname(relPath).replace(/\\/g, '/');
|
|
49
|
+
zip.addLocalFile(fullPath, zipDir === '.' ? '' : zipDir);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Collect every file under `dir` as a `{ fullPath, relPath }` pair. `relPath`
|
|
56
|
+
* is forward-slash-separated and relative to `dir` — so the install tree's
|
|
57
|
+
* `install/cmake/FooConfig.cmake` lands as `cmake/FooConfig.cmake` at the
|
|
58
|
+
* zip root, which is where `find_package(Foo CONFIG)` expects it on the
|
|
59
|
+
* consumer side after extraction into `wyvrn_internal/<name>/`.
|
|
60
|
+
*/
|
|
61
|
+
function collectInstallTreeFiles(dir) {
|
|
62
|
+
const files = [];
|
|
63
|
+
function walk(current) {
|
|
64
|
+
for (const entry of fs.readdirSync(current)) {
|
|
65
|
+
const full = path.join(current, entry);
|
|
66
|
+
const stat = fs.statSync(full);
|
|
67
|
+
if (stat.isDirectory()) {
|
|
68
|
+
walk(full);
|
|
69
|
+
} else {
|
|
70
|
+
const rel = path.relative(dir, full).replace(/\\/g, '/');
|
|
71
|
+
files.push({ fullPath: full, relPath: rel });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
walk(dir);
|
|
76
|
+
return files;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function addInstallTree(zip, installDir) {
|
|
80
|
+
const files = collectInstallTreeFiles(installDir);
|
|
81
|
+
const added = new Set();
|
|
82
|
+
for (const { fullPath, relPath } of files) {
|
|
83
|
+
const zipDir = path.dirname(relPath).replace(/\\/g, '/');
|
|
84
|
+
zip.addLocalFile(fullPath, zipDir === '.' ? '' : zipDir);
|
|
85
|
+
added.add(relPath);
|
|
86
|
+
}
|
|
87
|
+
return added;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Source resolution
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
function resolveSource(argv, ctx) {
|
|
95
|
+
let { source, awsProfile } = argv;
|
|
96
|
+
const { config } = ctx;
|
|
97
|
+
let sourceEntry = null;
|
|
98
|
+
|
|
99
|
+
if (source) {
|
|
100
|
+
const named = config.publishSources.find((s) => s.name === source);
|
|
101
|
+
if (named) {
|
|
102
|
+
log.info(`Using publish source "${named.name}" from config`);
|
|
103
|
+
source = named.url;
|
|
104
|
+
awsProfile = awsProfile ?? named.profile;
|
|
105
|
+
sourceEntry = named;
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
if (config.publishSources.length === 0) {
|
|
109
|
+
log.error(
|
|
110
|
+
'--source is required (or configure one with: ' +
|
|
111
|
+
'wyvrnpm configure add-source --kind publish ...)',
|
|
112
|
+
);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
const first = config.publishSources[0];
|
|
116
|
+
log.info(`Using publish source "${first.name}" from config`);
|
|
117
|
+
source = first.url;
|
|
118
|
+
awsProfile = awsProfile ?? first.profile;
|
|
119
|
+
sourceEntry = first;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Auth flows through ctx.auth.for — same literal > --token-env >
|
|
123
|
+
// config.token > config.tokenEnv precedence, centralised once in
|
|
124
|
+
// buildContext.
|
|
125
|
+
const { token } = ctx.auth.for(sourceEntry);
|
|
126
|
+
|
|
127
|
+
return { source, awsProfile, token };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Main publish command
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
async function publish(argv) {
|
|
135
|
+
// Shared per-invocation state (config, profile, manifest lazy, auth,
|
|
136
|
+
// CLI options/conf parsing, JSON-mode toggle). See src/context.js +
|
|
137
|
+
// claude/PLAN-COMMAND-CONTEXT.md. Also throws on malformed --option /
|
|
138
|
+
// --conf so bad inputs surface before we hit the registry.
|
|
139
|
+
let ctx;
|
|
140
|
+
try {
|
|
141
|
+
ctx = buildContext(argv);
|
|
142
|
+
} catch (err) {
|
|
143
|
+
log.error(err.message);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const { source, awsProfile, token } = resolveSource(argv, ctx);
|
|
148
|
+
const { path: publishPath, force } = argv;
|
|
149
|
+
const jsonOut = ctx.flags.format === 'json';
|
|
150
|
+
|
|
151
|
+
// ── Load manifest ─────────────────────────────────────────────────────────
|
|
152
|
+
// ctx.manifest is a lazy getter — throws on first access if the file is
|
|
153
|
+
// missing or malformed, which is exactly what we want here.
|
|
154
|
+
const { manifestPath } = ctx;
|
|
155
|
+
let manifest;
|
|
156
|
+
try {
|
|
157
|
+
manifest = ctx.manifest;
|
|
158
|
+
} catch (err) {
|
|
159
|
+
log.error(err.message);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const { name, version } = manifest;
|
|
164
|
+
if (!name || !version) {
|
|
165
|
+
log.error('Manifest must have "name" and "version" fields');
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const srcDir = path.resolve(publishPath || '.');
|
|
170
|
+
if (!fs.existsSync(srcDir)) {
|
|
171
|
+
log.error(`Publish path does not exist: ${srcDir}`);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ── Resolve provider ──────────────────────────────────────────────────────
|
|
176
|
+
let provider;
|
|
177
|
+
try {
|
|
178
|
+
provider = getProvider(source);
|
|
179
|
+
} catch (err) {
|
|
180
|
+
log.error(err.message);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ── Load build profile ────────────────────────────────────────────────────
|
|
185
|
+
// Resolved once in buildContext(). argv.profile → config.defaultProfile
|
|
186
|
+
// → "default" fallback is applied there. `--profile` to publish still
|
|
187
|
+
// works identically — it just flows through ctx.
|
|
188
|
+
const {
|
|
189
|
+
name: activeProfileName,
|
|
190
|
+
profile: buildProfile,
|
|
191
|
+
fromFile,
|
|
192
|
+
} = ctx.profiles.host;
|
|
193
|
+
|
|
194
|
+
// ── Resolve this package's own declared options ─────────────────────────
|
|
195
|
+
// Publishing needs to commit to a specific value for every option the
|
|
196
|
+
// recipe declares. Sources for overrides: CLI `-o <pkg>:...` flags
|
|
197
|
+
// (scoped to the package being published), else the recipe's defaults.
|
|
198
|
+
const ownDeclaration = manifest.options
|
|
199
|
+
? normalizeOptionsDeclaration(manifest.options, `${name}@${version}`)
|
|
200
|
+
: null;
|
|
201
|
+
const cliOptionsByPkg = ctx.cliOptions;
|
|
202
|
+
const effectiveOptions = resolveEffectiveOptions(
|
|
203
|
+
ownDeclaration,
|
|
204
|
+
{}, // consumer-side overrides don't apply to self-publish
|
|
205
|
+
cliOptionsByPkg[name] ?? {},
|
|
206
|
+
`${name}@${version}`,
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
const profileHash = hashProfile(buildProfile, effectiveOptions);
|
|
210
|
+
|
|
211
|
+
log.info(
|
|
212
|
+
`Publishing ${name}@${version}\n` +
|
|
213
|
+
`Profile : "${activeProfileName}"${fromFile ? '' : ' (auto-detected — save with `wyvrnpm configure profile detect`)'}\n` +
|
|
214
|
+
` ${buildProfile.os}/${buildProfile.arch} ` +
|
|
215
|
+
`${buildProfile.compiler} ${buildProfile['compiler.version']} ` +
|
|
216
|
+
`C++${buildProfile['compiler.cppstd']}` +
|
|
217
|
+
(buildProfile['compiler.runtime'] ? ` [${buildProfile['compiler.runtime']}]` : '') + '\n' +
|
|
218
|
+
(effectiveOptions ? `Options : ${JSON.stringify(effectiveOptions)}\n` : '') +
|
|
219
|
+
`Profile hash: ${profileHash}\n` +
|
|
220
|
+
`Provider : ${provider.constructor.providerName}`,
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
// ── Check v2 existence ────────────────────────────────────────────────────
|
|
224
|
+
const v2AlreadyExists = await provider.v2Exists({ source, name, version, profileHash, awsProfile, token });
|
|
225
|
+
if (v2AlreadyExists) {
|
|
226
|
+
if (force) {
|
|
227
|
+
log.warn(`${name}@${version} [${profileHash}] already exists — overwriting (--force)`);
|
|
228
|
+
} else {
|
|
229
|
+
log.error(
|
|
230
|
+
`${name}@${version} [${profileHash}] already exists.\n` +
|
|
231
|
+
' Bump the version, use a different profile, or pass --force to overwrite.',
|
|
232
|
+
);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ── Read the author's lockfile snapshot for `publishedLock` audit ────────
|
|
238
|
+
// Dependencies in wyvrn.json are published verbatim (ranges stay as ranges).
|
|
239
|
+
// The exact versions from wyvrn.lock are attached as `publishedLock` metadata
|
|
240
|
+
// on the uploaded manifest so future forensics can tell what the author
|
|
241
|
+
// actually tested against — without constraining consumer resolution.
|
|
242
|
+
const lockPath = path.join(path.dirname(manifestPath), 'wyvrn.lock');
|
|
243
|
+
const publishedLock = {};
|
|
244
|
+
if (fs.existsSync(lockPath)) {
|
|
245
|
+
try {
|
|
246
|
+
const lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
|
|
247
|
+
for (const [pkgName, entry] of Object.entries(lock.packages ?? {})) {
|
|
248
|
+
const ver = typeof entry === 'string' ? entry : entry.version;
|
|
249
|
+
if (ver) publishedLock[pkgName] = ver;
|
|
250
|
+
}
|
|
251
|
+
log.info(`Lock snapshot: ${Object.keys(publishedLock).length} package(s) recorded in publishedLock`);
|
|
252
|
+
} catch {
|
|
253
|
+
log.warn('could not read wyvrn.lock — publishedLock audit block will be empty');
|
|
254
|
+
}
|
|
255
|
+
} else {
|
|
256
|
+
log.warn('no wyvrn.lock found — run `wyvrnpm install` before publishing so publishedLock records tested versions');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ── Git metadata ──────────────────────────────────────────────────────────
|
|
260
|
+
const gitSha = getGitSha(srcDir);
|
|
261
|
+
const gitRepo = getGitRepo(srcDir);
|
|
262
|
+
if (gitSha) log.info(`Git commit : ${gitSha}`);
|
|
263
|
+
if (gitRepo) log.info(`Git remote : ${gitRepo}`);
|
|
264
|
+
|
|
265
|
+
// Warn (but don't block) if HEAD isn't reachable from origin. Downstream
|
|
266
|
+
// `--build=missing` consumers will fail to fetch this commit — the fix
|
|
267
|
+
// is almost always `git push` before publishing.
|
|
268
|
+
if (gitSha && gitRepo && !isShaOnRemote(gitSha, srcDir)) {
|
|
269
|
+
log.warn(
|
|
270
|
+
`commit ${gitSha.slice(0, 12)} is not reachable from any remote ` +
|
|
271
|
+
`branch or tag on origin.\n` +
|
|
272
|
+
` Consumers building this package from source will fail to retrieve it.\n` +
|
|
273
|
+
` Fix: push your branch (or tag the commit) before publishing.`,
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ── .wyvrnignore ──────────────────────────────────────────────────────────
|
|
278
|
+
const ignoreFile = path.join(srcDir, '.wyvrnignore');
|
|
279
|
+
const ignorePatterns = loadIgnorePatterns(ignoreFile);
|
|
280
|
+
if (ignorePatterns.length > 0) {
|
|
281
|
+
log.info(`Loaded ${ignorePatterns.length} pattern(s) from .wyvrnignore`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ── Build zip ─────────────────────────────────────────────────────────────
|
|
285
|
+
const tmpZipPath = path.join(os.tmpdir(), `wyvrnpm-${name}-${version}-${Date.now()}.zip`);
|
|
286
|
+
|
|
287
|
+
// ── Inject default `compatibility` block if the source manifest lacks one ──
|
|
288
|
+
// Written to a temp file so the source wyvrn.json is never modified.
|
|
289
|
+
// A published manifest must always carry a compat block so install-time
|
|
290
|
+
// resolution has something to evaluate (default = all fields `exact`,
|
|
291
|
+
// identical semantics to pre-phase-2 strict behaviour).
|
|
292
|
+
const tmpManifestPath = path.join(
|
|
293
|
+
os.tmpdir(),
|
|
294
|
+
`wyvrnpm-manifest-${name}-${version}-${Date.now()}.json`,
|
|
295
|
+
);
|
|
296
|
+
// ── publishedConf (PLAN-CONF.md §4.6) ────────────────────────────────────
|
|
297
|
+
// Publish reads ONLY the recipe conf + CLI --conf — wyvrn.local.json is
|
|
298
|
+
// intentionally ignored (plan §4.5 / §8.4) so dev-local overlays never
|
|
299
|
+
// leak into shared artefacts. Recorded on the published manifest as
|
|
300
|
+
// informational metadata: it tells maintainers what the publisher's
|
|
301
|
+
// CMake cache vars were at build time but does NOT drive resolution on
|
|
302
|
+
// the consumer side.
|
|
303
|
+
let publishedConf = {};
|
|
304
|
+
try {
|
|
305
|
+
const recipeConfFlat = readRecipeConf(manifest);
|
|
306
|
+
// ctx.cliConf is pre-parsed by buildContext. Malformed --conf would
|
|
307
|
+
// have thrown up there before we reached this block.
|
|
308
|
+
publishedConf = mergeConfLayers(recipeConfFlat, {}, ctx.cliConf);
|
|
309
|
+
} catch (err) {
|
|
310
|
+
log.error(`conf resolution failed: ${err.message}`);
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
if (Object.keys(publishedConf).length > 0) {
|
|
314
|
+
log.info(`Published conf: ${JSON.stringify(publishedConf)}`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const manifestForPublish = {
|
|
318
|
+
...manifest,
|
|
319
|
+
compatibility: manifest.compatibility ?? defaultCompatBlock(),
|
|
320
|
+
...(Object.keys(publishedConf).length > 0
|
|
321
|
+
? { publishedConf: unflattenConf(publishedConf) }
|
|
322
|
+
: {}),
|
|
323
|
+
};
|
|
324
|
+
fs.writeFileSync(tmpManifestPath, JSON.stringify(manifestForPublish, null, 2), 'utf8');
|
|
325
|
+
if (!manifest.compatibility) {
|
|
326
|
+
log.info('Injecting default `compatibility` block (all fields `exact`) — override by adding one to wyvrn.json');
|
|
327
|
+
} else {
|
|
328
|
+
log.info(`Using package-declared compatibility: ${JSON.stringify(manifest.compatibility)}`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// ── Install-tree overlay ──────────────────────────────────────────────────
|
|
332
|
+
// Publish zips the source tree (filtered by .wyvrnignore), but consumers
|
|
333
|
+
// actually need the install tree — `lib/cmake/<Name>/<Name>Config.cmake`,
|
|
334
|
+
// the installed headers, compiled libs — so find_package() works after
|
|
335
|
+
// extraction. When a build recipe is declared, locate the per-profile
|
|
336
|
+
// install dir (populated by `wyvrnpm build --config <cfg> --install`) and
|
|
337
|
+
// overlay its contents on top of the source zip. Install-tree files win
|
|
338
|
+
// on path collisions (e.g. `include/`).
|
|
339
|
+
let installOverlayDir = null;
|
|
340
|
+
let installOverlayPaths = new Set();
|
|
341
|
+
if (hasBuildRecipe(manifest)) {
|
|
342
|
+
try {
|
|
343
|
+
const recipe = normalizeRecipe(manifest.build, effectiveOptions, manifest);
|
|
344
|
+
const candidate = path.join(
|
|
345
|
+
srcDir, 'build', `wyvrn-${activeProfileName}`, recipe.installDir,
|
|
346
|
+
);
|
|
347
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
|
|
348
|
+
installOverlayDir = candidate;
|
|
349
|
+
} else {
|
|
350
|
+
log.warn(
|
|
351
|
+
`build recipe is declared but no install tree found at ${candidate}.\n` +
|
|
352
|
+
' Consumers\' find_package() will likely fail. Fix: run\n' +
|
|
353
|
+
' wyvrnpm build --config Debug --install\n' +
|
|
354
|
+
' wyvrnpm build --config Release --install\n' +
|
|
355
|
+
' wyvrnpm build --config RelWithDebInfo --install\n' +
|
|
356
|
+
' wyvrnpm build --config MinSizeRel --install\n' +
|
|
357
|
+
' then re-run publish.',
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
} catch (err) {
|
|
361
|
+
log.warn(`could not locate install tree: ${err.message} — publishing source tree only`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
log.info(`Zipping ${srcDir} ...`);
|
|
367
|
+
const zip = new AdmZip();
|
|
368
|
+
if (installOverlayDir) {
|
|
369
|
+
// Collect install-tree paths first so the source-tree walker can skip
|
|
370
|
+
// anything the install tree will add canonically.
|
|
371
|
+
const peek = collectInstallTreeFiles(installOverlayDir);
|
|
372
|
+
installOverlayPaths = new Set(peek.map((f) => f.relPath));
|
|
373
|
+
}
|
|
374
|
+
addFolderFiltered(zip, srcDir, srcDir, ignorePatterns, installOverlayPaths);
|
|
375
|
+
if (installOverlayDir) {
|
|
376
|
+
log.info(`Overlaying install tree: ${installOverlayDir}`);
|
|
377
|
+
const added = addInstallTree(zip, installOverlayDir);
|
|
378
|
+
log.info(` Added ${added.size} file(s) from install tree`);
|
|
379
|
+
}
|
|
380
|
+
zip.writeZip(tmpZipPath);
|
|
381
|
+
|
|
382
|
+
const contentSha256 = sha256Of(tmpZipPath);
|
|
383
|
+
log.info(`Content SHA256 : ${contentSha256}`);
|
|
384
|
+
|
|
385
|
+
// ── v2 publish ────────────────────────────────────────────────────────
|
|
386
|
+
log.info('Publishing (v2) ...');
|
|
387
|
+
await provider.v2Publish(
|
|
388
|
+
{ manifest: tmpManifestPath, zip: tmpZipPath },
|
|
389
|
+
{
|
|
390
|
+
source,
|
|
391
|
+
name,
|
|
392
|
+
version,
|
|
393
|
+
profileHash,
|
|
394
|
+
// buildSettings stored in metadata for debugging — NOT wyvrn.json
|
|
395
|
+
buildSettings: effectiveOptions
|
|
396
|
+
? { ...buildProfile, options: effectiveOptions }
|
|
397
|
+
: buildProfile,
|
|
398
|
+
contentSha256,
|
|
399
|
+
gitSha,
|
|
400
|
+
gitRepo,
|
|
401
|
+
publishedLock,
|
|
402
|
+
awsProfile,
|
|
403
|
+
token,
|
|
404
|
+
},
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
log.success(`Successfully published ${name}@${version} [${profileHash}] to ${source}`);
|
|
408
|
+
|
|
409
|
+
if (jsonOut) {
|
|
410
|
+
const payload = {
|
|
411
|
+
command: 'publish',
|
|
412
|
+
wyvrnpmVersion: require('../../package.json').version,
|
|
413
|
+
name,
|
|
414
|
+
version,
|
|
415
|
+
profileHash,
|
|
416
|
+
source,
|
|
417
|
+
contentSha256,
|
|
418
|
+
options: effectiveOptions ?? null,
|
|
419
|
+
gitSha: gitSha ?? null,
|
|
420
|
+
gitRepo: gitRepo ?? null,
|
|
421
|
+
publishedAt: new Date().toISOString(),
|
|
422
|
+
};
|
|
423
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
424
|
+
}
|
|
425
|
+
} finally {
|
|
426
|
+
if (fs.existsSync(tmpZipPath)) fs.unlinkSync(tmpZipPath);
|
|
427
|
+
if (fs.existsSync(tmpManifestPath)) fs.unlinkSync(tmpManifestPath);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
module.exports = publish;
|
|
432
|
+
// Exported for tests only
|
|
433
|
+
module.exports.addFolderFiltered = addFolderFiltered;
|
|
434
|
+
module.exports.addInstallTree = addInstallTree;
|
|
435
|
+
module.exports.collectInstallTreeFiles = collectInstallTreeFiles;
|