wyvrnpm 2.3.2 → 2.8.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 +465 -19
- package/bin/wyvrn.js +175 -9
- package/claude/skills/wyvrnpm.skill +0 -0
- package/cmake/macros.cmake +51 -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 +23 -2
- package/src/build/msvc-env.js +50 -7
- package/src/build/recipe.js +36 -3
- package/src/commands/add.js +140 -0
- package/src/commands/build.js +111 -7
- 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 +271 -5
- package/src/commands/profile.js +70 -7
- package/src/commands/publish.js +41 -4
- package/src/commands/show.js +14 -4
- package/src/compat.js +21 -9
- package/src/conf/index.js +391 -0
- package/src/conf/namespaces.js +94 -0
- package/src/download.js +136 -148
- package/src/logger.js +30 -2
- package/src/manifest.js +34 -7
- package/src/profile.js +148 -12
- package/src/resolve.js +47 -7
- package/src/settings-overrides.js +152 -0
- package/src/toolchain/index.js +71 -0
- package/src/toolchain/presets.js +108 -31
- package/src/toolchain/template.cmake +11 -0
- package/src/upload-built.js +11 -2
- package/src/zip-safe.js +52 -0
package/bin/wyvrn.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const yargs = require('yargs');
|
|
6
6
|
const init = require('../src/commands/init');
|
|
7
|
+
const add = require('../src/commands/add');
|
|
7
8
|
const install = require('../src/commands/install');
|
|
8
9
|
const clean = require('../src/commands/clean');
|
|
9
10
|
const publish = require('../src/commands/publish');
|
|
@@ -12,6 +13,7 @@ const profile = require('../src/commands/profile');
|
|
|
12
13
|
const build = require('../src/commands/build');
|
|
13
14
|
const show = require('../src/commands/show');
|
|
14
15
|
const installSkill = require('../src/commands/install-skill');
|
|
16
|
+
const cache = require('../src/commands/cache');
|
|
15
17
|
const { link, unlink } = require('../src/commands/link');
|
|
16
18
|
|
|
17
19
|
yargs
|
|
@@ -35,6 +37,34 @@ yargs
|
|
|
35
37
|
(argv) => init(argv),
|
|
36
38
|
)
|
|
37
39
|
|
|
40
|
+
// ── add ───────────────────────────────────────────────────────────────────
|
|
41
|
+
.command(
|
|
42
|
+
'add <packages...>',
|
|
43
|
+
'Add dependencies to wyvrn.json. Without @version, resolves latest and writes "^<latest>"; ' +
|
|
44
|
+
'with @version, writes the given string verbatim. Does not run install.',
|
|
45
|
+
(y) => {
|
|
46
|
+
y
|
|
47
|
+
.positional('packages', {
|
|
48
|
+
type: 'string',
|
|
49
|
+
array: true,
|
|
50
|
+
describe: 'One or more <name>[@<version>] specs',
|
|
51
|
+
})
|
|
52
|
+
.option('source', {
|
|
53
|
+
alias: 's',
|
|
54
|
+
type: 'string',
|
|
55
|
+
description: 'Base URL of a package source for `latest` lookup (can be repeated)',
|
|
56
|
+
array: true,
|
|
57
|
+
})
|
|
58
|
+
.option('platform', {
|
|
59
|
+
type: 'string',
|
|
60
|
+
description: 'v1 legacy platform path (used as fallback when looking up latest)',
|
|
61
|
+
choices: ['win_x64', 'win_x86', 'linux_x64', 'linux_x86', 'osx_x64', 'osx_arm64'],
|
|
62
|
+
default: 'win_x64',
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
(argv) => add(argv),
|
|
66
|
+
)
|
|
67
|
+
|
|
38
68
|
// ── install ───────────────────────────────────────────────────────────────
|
|
39
69
|
.command(
|
|
40
70
|
'install',
|
|
@@ -57,8 +87,8 @@ yargs
|
|
|
57
87
|
choices: ['never', 'missing', 'always'],
|
|
58
88
|
default: 'never',
|
|
59
89
|
description:
|
|
60
|
-
'Resolution
|
|
61
|
-
'
|
|
90
|
+
'Resolution strategy. never=fail when no exact match; ' +
|
|
91
|
+
'missing=source-build only when no match; always=ignore registry binaries and rebuild every dep from source (debug/verification).',
|
|
62
92
|
})
|
|
63
93
|
.option('option', {
|
|
64
94
|
alias: 'o',
|
|
@@ -68,6 +98,25 @@ yargs
|
|
|
68
98
|
'Override a package option. Repeatable. Format: <pkg>:<name>=<value>. ' +
|
|
69
99
|
'Example: -o zlib:minizip=false -o OpenSSL:fips=true',
|
|
70
100
|
})
|
|
101
|
+
.option('dry-run', {
|
|
102
|
+
type: 'boolean',
|
|
103
|
+
default: false,
|
|
104
|
+
description:
|
|
105
|
+
'Run the resolver + profile-hash math and print the install plan (every ' +
|
|
106
|
+
'dep, its pinned version, effective profileHash, per-dep option overrides) ' +
|
|
107
|
+
'without downloading, extracting, writing wyvrn.lock, or emitting the ' +
|
|
108
|
+
'toolchain. Combine with --format=json for CI-friendly output.',
|
|
109
|
+
})
|
|
110
|
+
.option('conf', {
|
|
111
|
+
type: 'string',
|
|
112
|
+
array: true,
|
|
113
|
+
description:
|
|
114
|
+
'Non-ABI build-time override. Repeatable. Format: ' +
|
|
115
|
+
'<namespace.leaf>=<value> — e.g. --conf cmake.cache.CHROMA_ENABLE_TEST=ON. ' +
|
|
116
|
+
'Bare KEY is sugar for KEY=ON (CMake -D convention); KEY= (empty RHS) ' +
|
|
117
|
+
'clears an inherited value. Does NOT fold into profileHash — see ' +
|
|
118
|
+
'claude/PLAN-CONF.md for layering rules.',
|
|
119
|
+
})
|
|
71
120
|
.option('upload-built', {
|
|
72
121
|
type: 'boolean',
|
|
73
122
|
default: false,
|
|
@@ -88,7 +137,11 @@ yargs
|
|
|
88
137
|
})
|
|
89
138
|
.option('token', {
|
|
90
139
|
type: 'string',
|
|
91
|
-
description: 'Bearer token for --upload-built when uploading to HTTP',
|
|
140
|
+
description: 'Bearer token for --upload-built when uploading to HTTP. Prefer --token-env in CI so the literal value never lands in argv.',
|
|
141
|
+
})
|
|
142
|
+
.option('token-env', {
|
|
143
|
+
type: 'string',
|
|
144
|
+
description: 'Name of an env var whose value is the bearer token for --upload-built. Preferred over --token in CI.',
|
|
92
145
|
})
|
|
93
146
|
.option('platform', {
|
|
94
147
|
type: 'string',
|
|
@@ -113,6 +166,8 @@ yargs
|
|
|
113
166
|
uploadBuilt: argv['upload-built'],
|
|
114
167
|
uploadSource: argv['upload-source'],
|
|
115
168
|
awsProfile: argv['aws-profile'],
|
|
169
|
+
tokenEnv: argv['token-env'],
|
|
170
|
+
dryRun: argv['dry-run'],
|
|
116
171
|
}),
|
|
117
172
|
)
|
|
118
173
|
|
|
@@ -156,6 +211,13 @@ yargs
|
|
|
156
211
|
default: false,
|
|
157
212
|
description: 'Remove the build directory before configuring',
|
|
158
213
|
})
|
|
214
|
+
.option('generator', {
|
|
215
|
+
alias: 'G',
|
|
216
|
+
type: 'string',
|
|
217
|
+
description:
|
|
218
|
+
'Override the preset\'s CMake generator (e.g. "Visual Studio 17 2022", "Ninja", "Ninja Multi-Config"). ' +
|
|
219
|
+
'CMake locks the generator into the build-dir cache, so pair this with --clean when switching generators.',
|
|
220
|
+
})
|
|
159
221
|
.option('install', {
|
|
160
222
|
type: 'boolean',
|
|
161
223
|
default: false,
|
|
@@ -184,6 +246,16 @@ yargs
|
|
|
184
246
|
.option('timeout', {
|
|
185
247
|
type: 'number',
|
|
186
248
|
default: 300,
|
|
249
|
+
})
|
|
250
|
+
.option('conf', {
|
|
251
|
+
type: 'string',
|
|
252
|
+
array: true,
|
|
253
|
+
description:
|
|
254
|
+
'Non-ABI build-time override forwarded to cmake --preset as -D / -U. ' +
|
|
255
|
+
'Repeatable. Format: <namespace.leaf>=<value> — e.g. ' +
|
|
256
|
+
'--conf cmake.cache.CHROMA_ENABLE_TEST=ON. CLI --conf does NOT ' +
|
|
257
|
+
'regenerate CMakePresets.json; edit wyvrn.local.json and re-run ' +
|
|
258
|
+
'`wyvrnpm install` for persistent overrides. See claude/PLAN-CONF.md.',
|
|
187
259
|
});
|
|
188
260
|
},
|
|
189
261
|
(argv) => build({
|
|
@@ -193,6 +265,74 @@ yargs
|
|
|
193
265
|
}),
|
|
194
266
|
)
|
|
195
267
|
|
|
268
|
+
// ── cache ─────────────────────────────────────────────────────────────────
|
|
269
|
+
.command(
|
|
270
|
+
'cache',
|
|
271
|
+
'Inspect and selectively prune the machine-wide source-build cache (F12). Use `clean --build-cache` to wipe everything.',
|
|
272
|
+
(y) => {
|
|
273
|
+
y
|
|
274
|
+
.command(
|
|
275
|
+
'list [pattern]',
|
|
276
|
+
'List cache entries. Optional glob pattern filters by package name.',
|
|
277
|
+
(y2) => {
|
|
278
|
+
y2
|
|
279
|
+
.positional('pattern', {
|
|
280
|
+
type: 'string',
|
|
281
|
+
description: 'Glob over package names, e.g. "boost*" (alias for --pattern)',
|
|
282
|
+
})
|
|
283
|
+
.option('pattern', {
|
|
284
|
+
type: 'string',
|
|
285
|
+
description: 'Glob over package names; overrides the positional',
|
|
286
|
+
})
|
|
287
|
+
.option('format', {
|
|
288
|
+
type: 'string',
|
|
289
|
+
choices: ['text', 'json'],
|
|
290
|
+
default: 'text',
|
|
291
|
+
description: 'Output format',
|
|
292
|
+
});
|
|
293
|
+
},
|
|
294
|
+
(argv) => cache.list(argv),
|
|
295
|
+
)
|
|
296
|
+
.command(
|
|
297
|
+
'prune',
|
|
298
|
+
'Remove cache entries per policy. Requires at least one of --keep-last / --older-than.',
|
|
299
|
+
(y2) => {
|
|
300
|
+
y2
|
|
301
|
+
.option('keep-last', {
|
|
302
|
+
type: 'number',
|
|
303
|
+
description:
|
|
304
|
+
'Retain the N most recently-touched entries per (name, version). ' +
|
|
305
|
+
'Older variants beyond N are pruned.',
|
|
306
|
+
})
|
|
307
|
+
.option('older-than', {
|
|
308
|
+
type: 'string',
|
|
309
|
+
description:
|
|
310
|
+
'Remove entries whose mtime is older than this threshold. ' +
|
|
311
|
+
'Format: <number><unit>, unit ∈ s/m/h/d (e.g. 30d, 72h).',
|
|
312
|
+
})
|
|
313
|
+
.option('pattern', {
|
|
314
|
+
type: 'string',
|
|
315
|
+
description: 'Limit scope to package names matching this glob (e.g. "boost*")',
|
|
316
|
+
})
|
|
317
|
+
.option('dry-run', {
|
|
318
|
+
type: 'boolean',
|
|
319
|
+
default: false,
|
|
320
|
+
description: 'Print what would be removed without actually deleting',
|
|
321
|
+
});
|
|
322
|
+
},
|
|
323
|
+
(argv) => cache.prune({
|
|
324
|
+
...argv,
|
|
325
|
+
keepLast: argv['keep-last'],
|
|
326
|
+
olderThan: argv['older-than'],
|
|
327
|
+
dryRun: argv['dry-run'],
|
|
328
|
+
}),
|
|
329
|
+
)
|
|
330
|
+
.demandCommand(1)
|
|
331
|
+
.help();
|
|
332
|
+
},
|
|
333
|
+
() => {},
|
|
334
|
+
)
|
|
335
|
+
|
|
196
336
|
// ── clean ─────────────────────────────────────────────────────────────────
|
|
197
337
|
.command(
|
|
198
338
|
'clean',
|
|
@@ -242,7 +382,11 @@ yargs
|
|
|
242
382
|
})
|
|
243
383
|
.option('token', {
|
|
244
384
|
type: 'string',
|
|
245
|
-
description: 'Bearer token for HTTP sources',
|
|
385
|
+
description: 'Bearer token for HTTP sources. Prefer --token-env in CI so the literal value never lands in argv.',
|
|
386
|
+
})
|
|
387
|
+
.option('token-env', {
|
|
388
|
+
type: 'string',
|
|
389
|
+
description: 'Name of an env var whose value is the bearer token for HTTP sources.',
|
|
246
390
|
})
|
|
247
391
|
.option('format', {
|
|
248
392
|
type: 'string',
|
|
@@ -251,7 +395,7 @@ yargs
|
|
|
251
395
|
description: 'Output format. "json" emits a single JSON object on stdout; log lines go to stderr.',
|
|
252
396
|
});
|
|
253
397
|
},
|
|
254
|
-
(argv) => show({ ...argv, awsProfile: argv['aws-profile'] }),
|
|
398
|
+
(argv) => show({ ...argv, awsProfile: argv['aws-profile'], tokenEnv: argv['token-env'] }),
|
|
255
399
|
)
|
|
256
400
|
|
|
257
401
|
// ── publish ───────────────────────────────────────────────────────────────
|
|
@@ -282,7 +426,11 @@ yargs
|
|
|
282
426
|
})
|
|
283
427
|
.option('token', {
|
|
284
428
|
type: 'string',
|
|
285
|
-
description: 'Bearer token for HTTP server authentication',
|
|
429
|
+
description: 'Bearer token for HTTP server authentication. Prefer --token-env in CI so the literal value never lands in argv.',
|
|
430
|
+
})
|
|
431
|
+
.option('token-env', {
|
|
432
|
+
type: 'string',
|
|
433
|
+
description: 'Name of an env var whose value is the bearer token for HTTP authentication.',
|
|
286
434
|
})
|
|
287
435
|
.option('force', {
|
|
288
436
|
alias: 'f',
|
|
@@ -298,6 +446,15 @@ yargs
|
|
|
298
446
|
'Override an option value at publish time. Repeatable. ' +
|
|
299
447
|
'Format: <pkg>:<name>=<value> (pkg must match the package being published).',
|
|
300
448
|
})
|
|
449
|
+
.option('conf', {
|
|
450
|
+
type: 'string',
|
|
451
|
+
array: true,
|
|
452
|
+
description:
|
|
453
|
+
'Non-ABI build-time override recorded as `publishedConf` on the ' +
|
|
454
|
+
'uploaded manifest (informational — does not affect profileHash). ' +
|
|
455
|
+
'Reads recipe conf + CLI only; wyvrn.local.json is intentionally ' +
|
|
456
|
+
'ignored for publish.',
|
|
457
|
+
})
|
|
301
458
|
.option('format', {
|
|
302
459
|
type: 'string',
|
|
303
460
|
choices: ['text', 'json'],
|
|
@@ -306,7 +463,7 @@ yargs
|
|
|
306
463
|
})
|
|
307
464
|
;
|
|
308
465
|
},
|
|
309
|
-
(argv) => publish({ ...argv, awsProfile: argv['aws-profile'] }),
|
|
466
|
+
(argv) => publish({ ...argv, awsProfile: argv['aws-profile'], tokenEnv: argv['token-env'] }),
|
|
310
467
|
)
|
|
311
468
|
|
|
312
469
|
// ── configure ─────────────────────────────────────────────────────────────
|
|
@@ -328,7 +485,8 @@ yargs
|
|
|
328
485
|
.option('name', { type: 'string', demandOption: true })
|
|
329
486
|
.option('url', { type: 'string', demandOption: true })
|
|
330
487
|
.option('profile', { type: 'string', description: 'AWS SSO profile (S3 only)' })
|
|
331
|
-
.option('token', { type: 'string', description: 'Bearer token (HTTP only)' })
|
|
488
|
+
.option('token', { type: 'string', description: 'Bearer token (HTTP only). Persists in plaintext in config.json — prefer --token-env for CI/shared setups.' })
|
|
489
|
+
.option('token-env', { type: 'string', description: 'Name of an env var whose value is the bearer token (HTTP only). Only the name is persisted; the value is resolved at runtime.' });
|
|
332
490
|
},
|
|
333
491
|
(argv) => configure.addSource(argv),
|
|
334
492
|
)
|
|
@@ -402,7 +560,15 @@ yargs
|
|
|
402
560
|
.option('compiler', { type: 'string', description: 'Compiler (msvc | gcc | clang | apple-clang)' })
|
|
403
561
|
.option('compiler-version', { type: 'string', description: 'Compiler version (193 | 11 | 14 | ...)' })
|
|
404
562
|
.option('cppstd', { type: 'string', description: 'C++ standard (14 | 17 | 20 | 23)' })
|
|
405
|
-
.option('runtime', { type: 'string', description: 'Runtime linkage (static | dynamic)' })
|
|
563
|
+
.option('runtime', { type: 'string', description: 'Runtime linkage (static | dynamic)' })
|
|
564
|
+
.option('conf', {
|
|
565
|
+
type: 'string',
|
|
566
|
+
array: true,
|
|
567
|
+
description:
|
|
568
|
+
'Profile-level non-ABI build knob — <namespace.leaf>=<value> ' +
|
|
569
|
+
'(repeatable). `KEY` alone means `KEY=ON`; `KEY=` removes the ' +
|
|
570
|
+
'leaf. Phase 1 allow-list: cmake.cache.*. See PLAN-CONF.md §4.',
|
|
571
|
+
});
|
|
406
572
|
},
|
|
407
573
|
(argv) => profile.set({ ...argv, compilerVersion: argv['compiler-version'] }),
|
|
408
574
|
)
|
|
Binary file
|
package/cmake/macros.cmake
CHANGED
|
@@ -25,6 +25,44 @@ macro(WYVRN_CONFIGURE_VERSION_HEADER)
|
|
|
25
25
|
)
|
|
26
26
|
endmacro()
|
|
27
27
|
|
|
28
|
+
# Apply WYVRN_ARCH_SUFFIX to a target's OUTPUT_NAME so the produced artefact
|
|
29
|
+
# carries an unambiguous arch marker. Opt-in — call it from your CMakeLists
|
|
30
|
+
# only if you want the suffixing:
|
|
31
|
+
#
|
|
32
|
+
# WYVRN_APPLY_ARCH_SUFFIX(String) # String64.lib / String32.lib
|
|
33
|
+
# WYVRN_APPLY_ARCH_SUFFIX(String BASE_NAME my-string) # my-string64.lib / my-string32.lib
|
|
34
|
+
#
|
|
35
|
+
# BASE_NAME defaults to the target name. The target linkage name (what
|
|
36
|
+
# consumers pass to target_link_libraries) is untouched — only the on-disk
|
|
37
|
+
# output file changes. Safe to call for any target type; does nothing if
|
|
38
|
+
# WYVRN_ARCH_SUFFIX is empty (unknown / unmapped arch).
|
|
39
|
+
function(WYVRN_APPLY_ARCH_SUFFIX _wyvrn_target)
|
|
40
|
+
set(options)
|
|
41
|
+
set(oneValueArgs BASE_NAME)
|
|
42
|
+
set(multiValueArgs)
|
|
43
|
+
cmake_parse_arguments(_WYVRN_ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
|
44
|
+
|
|
45
|
+
if(NOT TARGET ${_wyvrn_target})
|
|
46
|
+
message(FATAL_ERROR "WYVRN_APPLY_ARCH_SUFFIX: '${_wyvrn_target}' is not a target")
|
|
47
|
+
endif()
|
|
48
|
+
|
|
49
|
+
if("${WYVRN_ARCH_SUFFIX}" STREQUAL "")
|
|
50
|
+
# Unknown arch — leave the output name alone rather than stamping a
|
|
51
|
+
# misleading value. The caller can still set OUTPUT_NAME manually.
|
|
52
|
+
return()
|
|
53
|
+
endif()
|
|
54
|
+
|
|
55
|
+
if(_WYVRN_ARG_BASE_NAME)
|
|
56
|
+
set(_wyvrn_base "${_WYVRN_ARG_BASE_NAME}")
|
|
57
|
+
else()
|
|
58
|
+
set(_wyvrn_base "${_wyvrn_target}")
|
|
59
|
+
endif()
|
|
60
|
+
|
|
61
|
+
set_target_properties(${_wyvrn_target} PROPERTIES
|
|
62
|
+
OUTPUT_NAME "${_wyvrn_base}${WYVRN_ARCH_SUFFIX}"
|
|
63
|
+
)
|
|
64
|
+
endfunction()
|
|
65
|
+
|
|
28
66
|
function(WYVRN_INSTALL_PACKAGE_EXPORT)
|
|
29
67
|
set(options)
|
|
30
68
|
set(oneValueArgs
|
|
@@ -84,10 +122,23 @@ function(WYVRN_INSTALL_PACKAGE_EXPORT)
|
|
|
84
122
|
INSTALL_DESTINATION "${WYVRN_EXPORT_INSTALL_CMAKEDIR}"
|
|
85
123
|
)
|
|
86
124
|
|
|
125
|
+
# INTERFACE libraries (header-only) have no compiled binary, so the arch
|
|
126
|
+
# stamped by `write_basic_package_version_file()` by default makes CMake
|
|
127
|
+
# reject the package when the consumer's CMAKE_SIZEOF_VOID_P differs —
|
|
128
|
+
# even though the headers themselves are arch-portable. Pass
|
|
129
|
+
# ARCH_INDEPENDENT so a HeaderOnlyLib published from x86_64 is consumable
|
|
130
|
+
# from x86 (and vice versa). Binary kinds keep the arch check.
|
|
131
|
+
set(_wyvrn_version_args)
|
|
132
|
+
get_target_property(_wyvrn_target_type ${WYVRN_EXPORT_TARGET} TYPE)
|
|
133
|
+
if(_wyvrn_target_type STREQUAL "INTERFACE_LIBRARY")
|
|
134
|
+
list(APPEND _wyvrn_version_args ARCH_INDEPENDENT)
|
|
135
|
+
endif()
|
|
136
|
+
|
|
87
137
|
write_basic_package_version_file(
|
|
88
138
|
"${WYVRN_EXPORT_VERSION_OUTPUT}"
|
|
89
139
|
VERSION "${WYVRN_EXPORT_PACKAGE_VERSION}"
|
|
90
140
|
COMPATIBILITY SameMajorVersion
|
|
141
|
+
${_wyvrn_version_args}
|
|
91
142
|
)
|
|
92
143
|
|
|
93
144
|
install(FILES
|
package/cmake/variables.cmake
CHANGED
|
@@ -161,9 +161,19 @@ else()
|
|
|
161
161
|
message(STATUS "Unknown architecture")
|
|
162
162
|
endif()
|
|
163
163
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
# Output dirs are suffixed with WYVRN_ARCH_SUFFIX ("64" / "32" / "") so
|
|
165
|
+
# multi-arch builds from the same source tree don't clobber each other
|
|
166
|
+
# (e.g. building x64 then x86 used to silently overwrite the x64 DLLs
|
|
167
|
+
# in output/bin/<CONFIG>/). Single-arch consumers see no path change on
|
|
168
|
+
# x86 or unmapped arches — the suffix is empty there. Set defensively:
|
|
169
|
+
# if wyvrn_toolchain.cmake hasn't been included (standalone cmake runs),
|
|
170
|
+
# WYVRN_ARCH_SUFFIX is undefined and we fall back to the legacy layout.
|
|
171
|
+
if(NOT DEFINED WYVRN_ARCH_SUFFIX)
|
|
172
|
+
set(WYVRN_ARCH_SUFFIX "")
|
|
173
|
+
endif()
|
|
174
|
+
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/output/lib${WYVRN_ARCH_SUFFIX}/")
|
|
175
|
+
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/output/lib${WYVRN_ARCH_SUFFIX}/")
|
|
176
|
+
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/output/bin${WYVRN_ARCH_SUFFIX}")
|
|
167
177
|
|
|
168
178
|
add_compile_definitions(
|
|
169
179
|
$<$<CONFIG:Debug>:WYVRN_DEBUG>
|
package/package.json
CHANGED
package/src/auth.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolve an authentication token for HTTP-source reads/writes.
|
|
5
|
+
*
|
|
6
|
+
* Closes EVALUATION.md S8 — passing `--token <value>` on the command line
|
|
7
|
+
* leaks the token into any shell history, process table, or CI job log
|
|
8
|
+
* that captures argv. `--token-env <VAR>` and the config equivalent
|
|
9
|
+
* `tokenEnv` let users keep the literal value in env only.
|
|
10
|
+
*
|
|
11
|
+
* Resolution order (first match wins):
|
|
12
|
+
* 1. `token` — literal string from CLI or config (explicit beats env).
|
|
13
|
+
* 2. `tokenEnv` — name of an env var; its value becomes the token.
|
|
14
|
+
*
|
|
15
|
+
* Returns `undefined` when nothing is configured (valid — not every source
|
|
16
|
+
* needs auth). Throws when `tokenEnv` names a var that is unset or empty.
|
|
17
|
+
*
|
|
18
|
+
* @param {{ token?: string, tokenEnv?: string }} opts
|
|
19
|
+
* @returns {string|undefined}
|
|
20
|
+
*/
|
|
21
|
+
function resolveToken(opts = {}) {
|
|
22
|
+
const { token, tokenEnv } = opts;
|
|
23
|
+
|
|
24
|
+
if (typeof token === 'string' && token.length > 0) {
|
|
25
|
+
return token;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (typeof tokenEnv === 'string' && tokenEnv.length > 0) {
|
|
29
|
+
const value = process.env[tokenEnv];
|
|
30
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`--token-env ${tokenEnv} (or config tokenEnv=${tokenEnv}) is set, ` +
|
|
33
|
+
`but the environment variable is unset or empty.`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Merge CLI auth args + a source-config entry into a final `{ token }`
|
|
44
|
+
* object ready for passing to a provider. Centralises the "CLI wins,
|
|
45
|
+
* then env-backed CLI, then config literal, then config env-backed"
|
|
46
|
+
* precedence so call sites don't each re-implement it.
|
|
47
|
+
*
|
|
48
|
+
* @param {{ token?: string, tokenEnv?: string }} cli
|
|
49
|
+
* @param {{ token?: string, tokenEnv?: string } | null | undefined} source
|
|
50
|
+
* @returns {{ token: string|undefined }}
|
|
51
|
+
*/
|
|
52
|
+
function resolveSourceAuth(cli, source) {
|
|
53
|
+
// Try CLI first; if it produced nothing, fall back to the source's
|
|
54
|
+
// own stored auth. Each call to resolveToken handles its own
|
|
55
|
+
// precedence (literal > env) and throws on invalid tokenEnv.
|
|
56
|
+
const fromCli = resolveToken(cli);
|
|
57
|
+
if (fromCli !== undefined) return { token: fromCli };
|
|
58
|
+
|
|
59
|
+
const fromSource = source ? resolveToken(source) : undefined;
|
|
60
|
+
return { token: fromSource };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = {
|
|
64
|
+
resolveToken,
|
|
65
|
+
resolveSourceAuth,
|
|
66
|
+
};
|
package/src/build/cache.js
CHANGED
|
@@ -136,6 +136,196 @@ function clearUploadSidecars() {
|
|
|
136
136
|
return removed;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
// Cache inspection (F12)
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Dotted-version + 16+ hex-hash suffix. The hash tail is unambiguous
|
|
145
|
+
* (always hex, always 16 chars per src/profile.js) so parsing from the
|
|
146
|
+
* end avoids ambiguity when package names contain dashes
|
|
147
|
+
* (`boost-filesystem-1.80.0.0-abc1234567890def`).
|
|
148
|
+
*/
|
|
149
|
+
const CACHE_DIR_RE = /^(.+)-(\d+(?:\.\d+)+)-([a-f0-9]{16,})$/;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Recursively compute total size of a directory in bytes. Skips symlinks
|
|
153
|
+
* to avoid accidental cycles through user-linked packages.
|
|
154
|
+
*
|
|
155
|
+
* @param {string} dir
|
|
156
|
+
* @returns {number}
|
|
157
|
+
*/
|
|
158
|
+
function dirSizeBytes(dir) {
|
|
159
|
+
let total = 0;
|
|
160
|
+
let entries;
|
|
161
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
|
|
162
|
+
catch { return 0; }
|
|
163
|
+
for (const e of entries) {
|
|
164
|
+
if (e.isSymbolicLink()) continue;
|
|
165
|
+
const p = path.join(dir, e.name);
|
|
166
|
+
try {
|
|
167
|
+
if (e.isDirectory()) {
|
|
168
|
+
total += dirSizeBytes(p);
|
|
169
|
+
} else if (e.isFile()) {
|
|
170
|
+
total += fs.statSync(p).size;
|
|
171
|
+
}
|
|
172
|
+
} catch { /* concurrent deletion — ignore */ }
|
|
173
|
+
}
|
|
174
|
+
return total;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* List every entry in the source-build cache, enriched with metadata.
|
|
179
|
+
* Unparseable directory names are skipped (corrupt / manual cruft).
|
|
180
|
+
*
|
|
181
|
+
* @returns {Array<{
|
|
182
|
+
* name: string,
|
|
183
|
+
* version: string,
|
|
184
|
+
* profileHash: string,
|
|
185
|
+
* cacheDir: string,
|
|
186
|
+
* sizeBytes: number,
|
|
187
|
+
* mtimeMs: number,
|
|
188
|
+
* hasArtefact: boolean,
|
|
189
|
+
* uploads: Array<object>,
|
|
190
|
+
* }>}
|
|
191
|
+
*/
|
|
192
|
+
function listCacheEntries() {
|
|
193
|
+
const root = getBuildCacheRoot();
|
|
194
|
+
if (!fs.existsSync(root)) return [];
|
|
195
|
+
|
|
196
|
+
const out = [];
|
|
197
|
+
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
198
|
+
if (!entry.isDirectory()) continue;
|
|
199
|
+
|
|
200
|
+
const m = entry.name.match(CACHE_DIR_RE);
|
|
201
|
+
if (!m) continue; // corrupt / unknown — skip silently
|
|
202
|
+
const [, name, version, profileHash] = m;
|
|
203
|
+
|
|
204
|
+
const cacheDir = path.join(root, entry.name);
|
|
205
|
+
const artefact = path.join(cacheDir, LAYOUT.artefactZip);
|
|
206
|
+
const sidecarPath = path.join(cacheDir, LAYOUT.uploadSidecar);
|
|
207
|
+
|
|
208
|
+
let mtimeMs = 0;
|
|
209
|
+
try { mtimeMs = fs.statSync(cacheDir).mtimeMs; } catch { /* skip */ }
|
|
210
|
+
|
|
211
|
+
let uploads = [];
|
|
212
|
+
if (fs.existsSync(sidecarPath)) {
|
|
213
|
+
try {
|
|
214
|
+
const raw = JSON.parse(fs.readFileSync(sidecarPath, 'utf8'));
|
|
215
|
+
if (Array.isArray(raw?.uploads)) uploads = raw.uploads;
|
|
216
|
+
} catch { /* corrupt sidecar — surface empty */ }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
out.push({
|
|
220
|
+
name, version, profileHash, cacheDir,
|
|
221
|
+
sizeBytes: dirSizeBytes(cacheDir),
|
|
222
|
+
mtimeMs,
|
|
223
|
+
hasArtefact: fs.existsSync(artefact),
|
|
224
|
+
uploads,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return out;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Parse `--older-than` duration strings (`7d`, `24h`, `30m`, `3600s`).
|
|
232
|
+
* Returns the cutoff timestamp in ms-since-epoch: entries with
|
|
233
|
+
* `mtimeMs < cutoff` are older than the threshold.
|
|
234
|
+
*
|
|
235
|
+
* @param {string} spec
|
|
236
|
+
* @param {number} [now=Date.now()]
|
|
237
|
+
* @returns {number}
|
|
238
|
+
*/
|
|
239
|
+
function parseDurationToCutoff(spec, now = Date.now()) {
|
|
240
|
+
if (typeof spec !== 'string') {
|
|
241
|
+
throw new Error(`--older-than expects a string like "30d", got ${JSON.stringify(spec)}`);
|
|
242
|
+
}
|
|
243
|
+
const m = spec.trim().match(/^(\d+)\s*([smhd])$/i);
|
|
244
|
+
if (!m) {
|
|
245
|
+
throw new Error(
|
|
246
|
+
`--older-than ${JSON.stringify(spec)} must be <number><unit> where unit is s/m/h/d`,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
const n = parseInt(m[1], 10);
|
|
250
|
+
const unit = m[2].toLowerCase();
|
|
251
|
+
const ms = { s: 1_000, m: 60_000, h: 3_600_000, d: 86_400_000 }[unit];
|
|
252
|
+
return now - n * ms;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Compute which cache entries would be pruned under the given policy.
|
|
257
|
+
* Pure — does no I/O of its own. Caller passes in `entries` (from
|
|
258
|
+
* `listCacheEntries()`) so tests can synthesise them.
|
|
259
|
+
*
|
|
260
|
+
* Policy semantics:
|
|
261
|
+
* - `keepLast` is **per (name, version) group** — preserves the N most
|
|
262
|
+
* recent profileHash variants for each version so multi-profile
|
|
263
|
+
* dev rigs don't lose every profile on a single prune.
|
|
264
|
+
* - `olderThanMs` applies after `keepLast`; it only widens the set
|
|
265
|
+
* of eligible-for-pruning entries; never adds entries back.
|
|
266
|
+
* - `patternRegex` filters entries by name at the very start.
|
|
267
|
+
*
|
|
268
|
+
* Returns the prune set in descending mtime order (newest-of-the-
|
|
269
|
+
* doomed first) — handy for human-friendly "about to remove" output.
|
|
270
|
+
*
|
|
271
|
+
* @param {ReturnType<typeof listCacheEntries>} entries
|
|
272
|
+
* @param {{ keepLast?: number, olderThanMs?: number, patternRegex?: RegExp }} policy
|
|
273
|
+
* @returns {ReturnType<typeof listCacheEntries>}
|
|
274
|
+
*/
|
|
275
|
+
function computePruneSet(entries, { keepLast, olderThanMs, patternRegex } = {}) {
|
|
276
|
+
let candidates = entries;
|
|
277
|
+
if (patternRegex) candidates = candidates.filter((e) => patternRegex.test(e.name));
|
|
278
|
+
|
|
279
|
+
// Group by (name, version); within each group, sort by mtime desc,
|
|
280
|
+
// mark the top N as keepers when keepLast is set.
|
|
281
|
+
const keep = new Set();
|
|
282
|
+
if (typeof keepLast === 'number' && keepLast >= 0) {
|
|
283
|
+
const groups = new Map();
|
|
284
|
+
for (const e of candidates) {
|
|
285
|
+
const k = `${e.name}\x1f${e.version}`;
|
|
286
|
+
if (!groups.has(k)) groups.set(k, []);
|
|
287
|
+
groups.get(k).push(e);
|
|
288
|
+
}
|
|
289
|
+
for (const group of groups.values()) {
|
|
290
|
+
group.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
291
|
+
for (let i = 0; i < Math.min(keepLast, group.length); i++) {
|
|
292
|
+
keep.add(group[i].cacheDir);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
// No keepLast → every candidate is eligible for pruning subject to
|
|
297
|
+
// the other filters.
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const toPrune = candidates.filter((e) => !keep.has(e.cacheDir));
|
|
301
|
+
|
|
302
|
+
const filtered = typeof olderThanMs === 'number'
|
|
303
|
+
? toPrune.filter((e) => e.mtimeMs < olderThanMs)
|
|
304
|
+
: toPrune;
|
|
305
|
+
|
|
306
|
+
filtered.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
307
|
+
return filtered;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Delete a list of cache entries. Returns the subset that was actually
|
|
312
|
+
* removed (permissions failures surface as misses). Caller should have
|
|
313
|
+
* already computed the set via `computePruneSet`.
|
|
314
|
+
*
|
|
315
|
+
* @param {ReturnType<typeof listCacheEntries>} entries
|
|
316
|
+
* @returns {ReturnType<typeof listCacheEntries>}
|
|
317
|
+
*/
|
|
318
|
+
function removeCacheEntries(entries) {
|
|
319
|
+
const removed = [];
|
|
320
|
+
for (const e of entries) {
|
|
321
|
+
try {
|
|
322
|
+
fs.rmSync(e.cacheDir, { recursive: true, force: true });
|
|
323
|
+
removed.push(e);
|
|
324
|
+
} catch { /* skip failures — caller reports the delta */ }
|
|
325
|
+
}
|
|
326
|
+
return removed;
|
|
327
|
+
}
|
|
328
|
+
|
|
139
329
|
module.exports = {
|
|
140
330
|
getBuildCacheRoot,
|
|
141
331
|
getPackageBuildDir,
|
|
@@ -145,4 +335,10 @@ module.exports = {
|
|
|
145
335
|
listUploadSidecars,
|
|
146
336
|
clearUploadSidecars,
|
|
147
337
|
LAYOUT,
|
|
338
|
+
// Cache inspection (F12)
|
|
339
|
+
listCacheEntries,
|
|
340
|
+
computePruneSet,
|
|
341
|
+
removeCacheEntries,
|
|
342
|
+
parseDurationToCutoff,
|
|
343
|
+
CACHE_DIR_RE,
|
|
148
344
|
};
|