wyvrnpm 2.0.4 → 2.3.2
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 +639 -5
- package/bin/wyvrn.js +220 -10
- package/claude/skills/wyvrnpm.skill +0 -0
- package/cmake/cpp.cmake +9 -0
- package/cmake/functions.cmake +224 -0
- package/cmake/macros.cmake +233 -0
- package/cmake/options.cmake +23 -0
- package/cmake/variables.cmake +171 -0
- package/package.json +3 -1
- package/src/build/cache.js +148 -0
- package/src/build/clone.js +170 -0
- package/src/build/cmake.js +342 -0
- package/src/build/index.js +275 -0
- package/src/build/msvc-env.js +217 -0
- package/src/build/recipe.js +155 -0
- package/src/commands/build.js +283 -0
- package/src/commands/clean.js +56 -16
- package/src/commands/configure.js +6 -5
- package/src/commands/init.js +3 -2
- package/src/commands/install-skill.js +107 -0
- package/src/commands/install.js +262 -19
- package/src/commands/link.js +18 -15
- package/src/commands/profile.js +15 -12
- package/src/commands/publish.js +216 -65
- package/src/commands/show.js +237 -0
- package/src/compat.js +261 -0
- package/src/config.js +3 -1
- package/src/download.js +431 -87
- package/src/ignore.js +118 -0
- package/src/logger.js +94 -0
- package/src/manifest.js +12 -7
- package/src/options.js +303 -0
- package/src/profile.js +56 -4
- package/src/providers/base.js +16 -1
- package/src/providers/file.js +12 -6
- package/src/providers/http.js +15 -10
- package/src/providers/s3.js +14 -9
- package/src/resolve.js +179 -19
- package/src/toolchain/deps.js +164 -0
- package/src/toolchain/index.js +141 -0
- package/src/toolchain/presets.js +263 -0
- package/src/toolchain/template.cmake +66 -0
- package/src/upload-built.js +256 -0
- package/src/version-range.js +301 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawn } = require('child_process');
|
|
6
|
+
|
|
7
|
+
const { readConfig } = require('../config');
|
|
8
|
+
const { loadProfile } = require('../profile');
|
|
9
|
+
const { readManifest } = require('../manifest');
|
|
10
|
+
const { normalizeRecipe, hasBuildRecipe } = require('../build/recipe');
|
|
11
|
+
const { loadMsvcEnv } = require('../build/msvc-env');
|
|
12
|
+
const install = require('./install');
|
|
13
|
+
const log = require('../logger');
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Pure helpers (unit-testable — no process spawning, no filesystem writes)
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Decide whether `wyvrn install` must run before build.
|
|
21
|
+
*
|
|
22
|
+
* - toolchain-missing : wyvrn_toolchain.cmake does not exist → definitely stale.
|
|
23
|
+
* - lock-newer : wyvrn.lock was modified after the toolchain was written,
|
|
24
|
+
* so the toolchain's package list is out of date.
|
|
25
|
+
* - fresh : no action needed.
|
|
26
|
+
*
|
|
27
|
+
* @param {{ rootDir: string, manifestPath: string }} args
|
|
28
|
+
* @returns {{ stale: boolean, reason: string }}
|
|
29
|
+
*/
|
|
30
|
+
function isToolchainStale({ rootDir, manifestPath }) {
|
|
31
|
+
const lockPath = path.join(path.dirname(manifestPath), 'wyvrn.lock');
|
|
32
|
+
const toolchainPath = path.join(rootDir, 'wyvrn_internal', 'wyvrn_toolchain.cmake');
|
|
33
|
+
|
|
34
|
+
if (!fs.existsSync(toolchainPath)) {
|
|
35
|
+
return { stale: true, reason: 'toolchain-missing' };
|
|
36
|
+
}
|
|
37
|
+
if (!fs.existsSync(lockPath)) {
|
|
38
|
+
return { stale: false, reason: 'no-lock' };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const lockMtime = fs.statSync(lockPath).mtimeMs;
|
|
42
|
+
const toolchainMtime = fs.statSync(toolchainPath).mtimeMs;
|
|
43
|
+
if (lockMtime > toolchainMtime) {
|
|
44
|
+
return { stale: true, reason: 'lock-newer-than-toolchain' };
|
|
45
|
+
}
|
|
46
|
+
return { stale: false, reason: 'fresh' };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the configure preset name from CLI override or profile convention.
|
|
51
|
+
*
|
|
52
|
+
* @param {{ presetOverride?: string, profileName: string }} args
|
|
53
|
+
* @returns {string}
|
|
54
|
+
*/
|
|
55
|
+
function resolvePresetName({ presetOverride, profileName }) {
|
|
56
|
+
if (presetOverride) return presetOverride;
|
|
57
|
+
return `wyvrn-${profileName}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Compute the build directory for a preset, matching the pattern emitted
|
|
62
|
+
* by [src/toolchain/presets.js](../toolchain/presets.js) — `${sourceDir}/build/<preset>`.
|
|
63
|
+
*
|
|
64
|
+
* @param {{ rootDir: string, presetName: string }} args
|
|
65
|
+
* @returns {string}
|
|
66
|
+
*/
|
|
67
|
+
function resolveBinaryDir({ rootDir, presetName }) {
|
|
68
|
+
return path.join(rootDir, 'build', presetName);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Args passed to `cmake` for the configure step.
|
|
73
|
+
* @param {{ presetName: string }} args
|
|
74
|
+
* @returns {string[]}
|
|
75
|
+
*/
|
|
76
|
+
function buildConfigureArgs({ presetName }) {
|
|
77
|
+
return ['--preset', presetName];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Args passed to `cmake` for the build step.
|
|
82
|
+
*
|
|
83
|
+
* @param {{
|
|
84
|
+
* binaryDir: string,
|
|
85
|
+
* config: string,
|
|
86
|
+
* target?: string,
|
|
87
|
+
* verbose?: boolean,
|
|
88
|
+
* passthru?: string[],
|
|
89
|
+
* }} args
|
|
90
|
+
* @returns {string[]}
|
|
91
|
+
*/
|
|
92
|
+
function buildBuildArgs({ binaryDir, config, target, verbose, passthru = [] }) {
|
|
93
|
+
const args = ['--build', binaryDir, '--config', config];
|
|
94
|
+
if (verbose) args.push('--verbose');
|
|
95
|
+
if (target) args.push('--target', target);
|
|
96
|
+
if (passthru.length > 0) args.push('--', ...passthru);
|
|
97
|
+
return args;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Args passed to `cmake --install` for the install step.
|
|
102
|
+
*
|
|
103
|
+
* @param {{
|
|
104
|
+
* binaryDir: string,
|
|
105
|
+
* config: string,
|
|
106
|
+
* prefix?: string, // override CMAKE_INSTALL_PREFIX at install time
|
|
107
|
+
* }} args
|
|
108
|
+
* @returns {string[]}
|
|
109
|
+
*/
|
|
110
|
+
function buildInstallArgs({ binaryDir, config, prefix }) {
|
|
111
|
+
const args = ['--install', binaryDir, '--config', config];
|
|
112
|
+
if (prefix) args.push('--prefix', prefix);
|
|
113
|
+
return args;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// Spawn wrapper
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Run `cmake` with the given args. Inherits stdio for real-time output.
|
|
122
|
+
* Rejects if the process exits non-zero or fails to spawn.
|
|
123
|
+
*
|
|
124
|
+
* When `env` is provided, it replaces the ambient environment (used to
|
|
125
|
+
* activate MSVC via vcvarsall for non-VS generators on Windows).
|
|
126
|
+
*
|
|
127
|
+
* @param {string[]} args
|
|
128
|
+
* @param {NodeJS.ProcessEnv|null} [env]
|
|
129
|
+
* @returns {Promise<void>}
|
|
130
|
+
*/
|
|
131
|
+
function runCmake(args, env = null) {
|
|
132
|
+
return new Promise((resolve, reject) => {
|
|
133
|
+
const opts = { stdio: 'inherit' };
|
|
134
|
+
if (env) opts.env = env;
|
|
135
|
+
const proc = spawn('cmake', args, opts);
|
|
136
|
+
proc.on('error', (err) => {
|
|
137
|
+
reject(new Error(
|
|
138
|
+
`Failed to spawn cmake: ${err.message}\n` +
|
|
139
|
+
' Is CMake installed and on PATH?',
|
|
140
|
+
));
|
|
141
|
+
});
|
|
142
|
+
proc.on('close', (code) => {
|
|
143
|
+
if (code === 0) resolve();
|
|
144
|
+
else reject(new Error(`cmake ${args.join(' ')} exited with code ${code}`));
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// Command entry
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* `wyvrnpm build` — configure + build via the generated CMake preset.
|
|
155
|
+
*
|
|
156
|
+
* Defaults:
|
|
157
|
+
* - preset : `wyvrn-<profileName>` (from --profile or config.defaultProfile)
|
|
158
|
+
* - config : Release
|
|
159
|
+
* - auto-install when toolchain is missing / older than wyvrn.lock
|
|
160
|
+
*
|
|
161
|
+
* @param {object} argv
|
|
162
|
+
*/
|
|
163
|
+
async function build(argv) {
|
|
164
|
+
const rootDir = path.resolve(argv.root);
|
|
165
|
+
const manifestPath = path.resolve(argv.manifest);
|
|
166
|
+
|
|
167
|
+
const config = readConfig();
|
|
168
|
+
const profileName = argv.profile ?? config.defaultProfile ?? 'default';
|
|
169
|
+
const presetName = resolvePresetName({ presetOverride: argv.preset, profileName });
|
|
170
|
+
const buildConfig = argv.config ?? 'Release';
|
|
171
|
+
|
|
172
|
+
// ── Stale check / auto-install ────────────────────────────────────────────
|
|
173
|
+
// `--auto-install` gates the "run `wyvrnpm install` first when the
|
|
174
|
+
// toolchain is missing or older than wyvrn.lock" behaviour. The separate
|
|
175
|
+
// `--install` flag below runs `cmake --install` AFTER build; the two are
|
|
176
|
+
// deliberately distinct concepts.
|
|
177
|
+
const staleness = isToolchainStale({ rootDir, manifestPath });
|
|
178
|
+
if (staleness.stale) {
|
|
179
|
+
if (argv.autoInstall === false) {
|
|
180
|
+
log.error(
|
|
181
|
+
`toolchain is stale (${staleness.reason}) and --no-auto-install was passed.\n` +
|
|
182
|
+
' Run: wyvrnpm install',
|
|
183
|
+
);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
log.info(`Toolchain ${staleness.reason} — running install first`);
|
|
187
|
+
await install({
|
|
188
|
+
manifest: argv.manifest,
|
|
189
|
+
root: argv.root,
|
|
190
|
+
profile: argv.profile,
|
|
191
|
+
source: argv.source,
|
|
192
|
+
platform: argv.platform ?? 'win_x64',
|
|
193
|
+
timeout: argv.timeout ?? 300,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const binaryDir = resolveBinaryDir({ rootDir, presetName });
|
|
198
|
+
|
|
199
|
+
// ── Clean ─────────────────────────────────────────────────────────────────
|
|
200
|
+
if (argv.clean && fs.existsSync(binaryDir)) {
|
|
201
|
+
log.info(`Removing ${binaryDir}`);
|
|
202
|
+
fs.rmSync(binaryDir, { recursive: true, force: true });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ── MSVC env activation (Windows + non-VS generator) ───────────────────
|
|
206
|
+
// When the preset's generator is Ninja / Ninja Multi-Config / Make / etc.
|
|
207
|
+
// and the active profile is MSVC, we need `vcvarsall.bat` to have been
|
|
208
|
+
// sourced — otherwise cmake's compiler detection fails with "CXX compiler
|
|
209
|
+
// identification is unknown". VS generators sidestep this because MSBuild
|
|
210
|
+
// finds MSVC via its own toolset resolution.
|
|
211
|
+
//
|
|
212
|
+
// Same mechanism the source-build path already uses (src/build/msvc-env.js).
|
|
213
|
+
let cmakeEnv = null;
|
|
214
|
+
try {
|
|
215
|
+
const { profile: activeProfile } = loadProfile(profileName);
|
|
216
|
+
// Which generator will the preset actually use? Read it from the
|
|
217
|
+
// project's own recipe if one exists. If not, default to null and let
|
|
218
|
+
// cmake/CMakePresets decide — in which case MSVC env activation is
|
|
219
|
+
// skipped (because VS is usually the default on Windows).
|
|
220
|
+
const manifest = readManifest(manifestPath);
|
|
221
|
+
let presetGenerator = null;
|
|
222
|
+
if (hasBuildRecipe(manifest)) {
|
|
223
|
+
try {
|
|
224
|
+
const recipe = normalizeRecipe(manifest.build, null);
|
|
225
|
+
if (recipe.generators.length > 0) [presetGenerator] = recipe.generators;
|
|
226
|
+
} catch { /* templates need options → skip env activation */ }
|
|
227
|
+
}
|
|
228
|
+
cmakeEnv = await loadMsvcEnv({
|
|
229
|
+
profile: activeProfile,
|
|
230
|
+
skipForGenerator: presetGenerator,
|
|
231
|
+
});
|
|
232
|
+
} catch (err) {
|
|
233
|
+
log.warn(`Could not resolve MSVC env activation: ${err.message}`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ── Configure ─────────────────────────────────────────────────────────────
|
|
237
|
+
const configureArgs = buildConfigureArgs({ presetName });
|
|
238
|
+
log.info(`cmake ${configureArgs.join(' ')}`);
|
|
239
|
+
await runCmake(configureArgs, cmakeEnv);
|
|
240
|
+
|
|
241
|
+
// ── Build ─────────────────────────────────────────────────────────────────
|
|
242
|
+
const buildArgs = buildBuildArgs({
|
|
243
|
+
binaryDir,
|
|
244
|
+
config: buildConfig,
|
|
245
|
+
target: argv.target,
|
|
246
|
+
verbose: argv.verbose,
|
|
247
|
+
passthru: argv['--'] ?? [],
|
|
248
|
+
});
|
|
249
|
+
log.info(`cmake ${buildArgs.join(' ')}`);
|
|
250
|
+
await runCmake(buildArgs, cmakeEnv);
|
|
251
|
+
|
|
252
|
+
// ── Install (optional) ────────────────────────────────────────────────────
|
|
253
|
+
// `--install` runs `cmake --install <binaryDir>` after the build. Prefix
|
|
254
|
+
// defaults to whatever was baked into the cache at configure time; pass
|
|
255
|
+
// `--install-prefix` to override without reconfiguring.
|
|
256
|
+
if (argv.install) {
|
|
257
|
+
const installArgs = buildInstallArgs({
|
|
258
|
+
binaryDir,
|
|
259
|
+
config: buildConfig,
|
|
260
|
+
prefix: argv.installPrefix,
|
|
261
|
+
});
|
|
262
|
+
log.info(`cmake ${installArgs.join(' ')}`);
|
|
263
|
+
await runCmake(installArgs, cmakeEnv);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const suffix = argv.install
|
|
267
|
+
? ` + installed${argv.installPrefix ? ` to ${argv.installPrefix}` : ''}`
|
|
268
|
+
: '';
|
|
269
|
+
log.success(`Build complete: ${binaryDir} (${buildConfig})${suffix}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
module.exports = build;
|
|
273
|
+
module.exports.default = build;
|
|
274
|
+
|
|
275
|
+
// Exported for unit tests
|
|
276
|
+
module.exports._helpers = {
|
|
277
|
+
isToolchainStale,
|
|
278
|
+
resolvePresetName,
|
|
279
|
+
resolveBinaryDir,
|
|
280
|
+
buildConfigureArgs,
|
|
281
|
+
buildBuildArgs,
|
|
282
|
+
buildInstallArgs,
|
|
283
|
+
};
|
package/src/commands/clean.js
CHANGED
|
@@ -3,37 +3,77 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
|
|
6
|
+
const {
|
|
7
|
+
clearBuildCache,
|
|
8
|
+
getBuildCacheRoot,
|
|
9
|
+
clearUploadSidecars,
|
|
10
|
+
} = require('../build/cache');
|
|
11
|
+
const log = require('../logger');
|
|
12
|
+
|
|
6
13
|
/**
|
|
7
|
-
* Removes the wyvrn_internal
|
|
14
|
+
* Removes the project's `wyvrn_internal/` package cache and `wyvrn.lock` file.
|
|
15
|
+
* With `--build-cache`, also clears the machine-wide source-build cache
|
|
16
|
+
* under `%LOCALAPPDATA%\wyvrnpm\bc\` (shared across projects).
|
|
17
|
+
*
|
|
18
|
+
* `--uploaded-built` wipes just the consumer's local RECORD of what they
|
|
19
|
+
* have uploaded via `wyvrnpm install --build=missing --upload-built` (the
|
|
20
|
+
* `.uploaded-to.json` sidecars under the build cache). It does NOT touch
|
|
21
|
+
* the registry, the artefact zips, or the source clones. Privacy / local
|
|
22
|
+
* audit cleanup only.
|
|
8
23
|
*
|
|
9
|
-
* @param {object}
|
|
10
|
-
* @param {string}
|
|
11
|
-
* @param {string}
|
|
24
|
+
* @param {object} argv
|
|
25
|
+
* @param {string} argv.root Project root directory.
|
|
26
|
+
* @param {string} argv.manifest Path to the manifest file (used to locate wyvrn.lock).
|
|
27
|
+
* @param {boolean} [argv.buildCache] When true, also nukes the source-build cache.
|
|
28
|
+
* @param {boolean} [argv.uploadedBuilt] When true, wipes upload sidecars only.
|
|
12
29
|
* @returns {Promise<void>}
|
|
13
30
|
*/
|
|
14
31
|
async function clean(argv) {
|
|
15
|
-
const rootDir
|
|
32
|
+
const rootDir = path.resolve(argv.root);
|
|
16
33
|
const razerDir = path.join(rootDir, 'wyvrn_internal');
|
|
17
34
|
const lockPath = path.join(path.dirname(path.resolve(argv.manifest)), 'wyvrn.lock');
|
|
18
35
|
|
|
36
|
+
// --uploaded-built is a targeted cleanup mode. When passed on its own,
|
|
37
|
+
// skip the default wyvrn_internal/ + wyvrn.lock scrub so a user can
|
|
38
|
+
// "forget uploads" without also wiping their project-local install.
|
|
39
|
+
const targetedOnly = argv.uploadedBuilt && !argv.buildCache;
|
|
40
|
+
|
|
19
41
|
let removedAnything = false;
|
|
20
42
|
|
|
21
|
-
if (
|
|
22
|
-
fs.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
43
|
+
if (!targetedOnly) {
|
|
44
|
+
if (fs.existsSync(razerDir)) {
|
|
45
|
+
fs.rmSync(razerDir, { recursive: true, force: true });
|
|
46
|
+
log.info(`Removed ${razerDir}`);
|
|
47
|
+
removedAnything = true;
|
|
48
|
+
} else {
|
|
49
|
+
log.info(`Nothing to clean at ${razerDir}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (fs.existsSync(lockPath)) {
|
|
53
|
+
fs.unlinkSync(lockPath);
|
|
54
|
+
log.info(`Removed ${lockPath}`);
|
|
55
|
+
removedAnything = true;
|
|
56
|
+
}
|
|
27
57
|
}
|
|
28
58
|
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
59
|
+
if (argv.buildCache) {
|
|
60
|
+
const removed = clearBuildCache();
|
|
61
|
+
if (removed) {
|
|
62
|
+
log.info(`Removed source-build cache ${removed}`);
|
|
63
|
+
removedAnything = true;
|
|
64
|
+
} else {
|
|
65
|
+
log.info(`No source-build cache at ${getBuildCacheRoot()}`);
|
|
66
|
+
}
|
|
67
|
+
} else if (argv.uploadedBuilt) {
|
|
68
|
+
// clearBuildCache() would have nuked the sidecars as a side-effect, so
|
|
69
|
+
// only bother with the sidecar sweep when the cache itself is surviving.
|
|
70
|
+
const removed = clearUploadSidecars();
|
|
71
|
+
log.info(`Removed ${removed} upload record${removed === 1 ? '' : 's'} from the build cache`);
|
|
72
|
+
if (removed > 0) removedAnything = true;
|
|
33
73
|
}
|
|
34
74
|
|
|
35
75
|
if (!removedAnything) {
|
|
36
|
-
|
|
76
|
+
log.info('Nothing to clean.');
|
|
37
77
|
}
|
|
38
78
|
}
|
|
39
79
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { readConfig, writeConfig, getConfigPath } = require('../config');
|
|
4
|
+
const log = require('../logger');
|
|
4
5
|
|
|
5
6
|
/** wyvrnpm configure list */
|
|
6
7
|
function list() {
|
|
@@ -47,14 +48,14 @@ function addSource(argv) {
|
|
|
47
48
|
const idx = config[key].findIndex((s) => s.name === name);
|
|
48
49
|
if (idx !== -1) {
|
|
49
50
|
config[key][idx] = entry;
|
|
50
|
-
|
|
51
|
+
log.info(`Updated ${kind} source "${name}"`);
|
|
51
52
|
} else {
|
|
52
53
|
config[key].push(entry);
|
|
53
|
-
|
|
54
|
+
log.info(`Added ${kind} source "${name}"`);
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
writeConfig(config);
|
|
57
|
-
|
|
58
|
+
log.info(`Saved to ${getConfigPath()}`);
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
/**
|
|
@@ -69,12 +70,12 @@ function removeSource(argv) {
|
|
|
69
70
|
config[key] = config[key].filter((s) => s.name !== name);
|
|
70
71
|
|
|
71
72
|
if (config[key].length === before) {
|
|
72
|
-
|
|
73
|
+
log.error(`No ${kind} source named "${name}"`);
|
|
73
74
|
process.exit(1);
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
writeConfig(config);
|
|
77
|
-
|
|
78
|
+
log.info(`Removed ${kind} source "${name}"`);
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
module.exports = { list, addSource, removeSource };
|
package/src/commands/init.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { defaultManifest, writeManifest } = require('../manifest');
|
|
6
|
+
const log = require('../logger');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Initialises a new wyvrn.json manifest in the project root.
|
|
@@ -18,7 +19,7 @@ async function init(argv) {
|
|
|
18
19
|
const rootDir = path.resolve(argv.root);
|
|
19
20
|
|
|
20
21
|
if (fs.existsSync(manifestPath)) {
|
|
21
|
-
|
|
22
|
+
log.info(`Manifest already exists at ${manifestPath}`);
|
|
22
23
|
return;
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -27,7 +28,7 @@ async function init(argv) {
|
|
|
27
28
|
|
|
28
29
|
writeManifest(manifestPath, manifest);
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
log.success(`Created manifest at ${manifestPath}`);
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
module.exports = init;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const AdmZip = require('adm-zip');
|
|
7
|
+
|
|
8
|
+
const log = require('../logger');
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// `wyvrnpm install-skill --claude`
|
|
12
|
+
//
|
|
13
|
+
// Copies the bundled wyvrnpm Agent Skill (claude/skills/wyvrnpm.skill) into
|
|
14
|
+
// the user's Claude Code skills directory so Claude picks it up automatically.
|
|
15
|
+
//
|
|
16
|
+
// Works for both install shapes:
|
|
17
|
+
// - `npm i -g wyvrnpm` → skill zip ships under <pkg>/claude/skills/
|
|
18
|
+
// - `git clone` of this repo → same path, just not from node_modules
|
|
19
|
+
//
|
|
20
|
+
// Destination, per target:
|
|
21
|
+
// --claude → %USERPROFILE%\.claude\skills\wyvrnpm\ (Windows)
|
|
22
|
+
// ~/.claude/skills/wyvrnpm/ (macOS / Linux)
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
const SKILL_NAME = 'wyvrnpm';
|
|
26
|
+
|
|
27
|
+
function bundledSkillPath() {
|
|
28
|
+
// __dirname = <pkg>/src/commands; the .skill zip lives at
|
|
29
|
+
// <pkg>/claude/skills/wyvrnpm.skill.
|
|
30
|
+
return path.resolve(__dirname, '..', '..', 'claude', 'skills', `${SKILL_NAME}.skill`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function claudeSkillsDir() {
|
|
34
|
+
const home = os.homedir();
|
|
35
|
+
if (!home) throw new Error('cannot resolve user home directory');
|
|
36
|
+
return path.join(home, '.claude', 'skills');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function rmrf(target) {
|
|
40
|
+
if (!fs.existsSync(target)) return;
|
|
41
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function installForClaude({ force, dryRun }) {
|
|
45
|
+
const zipPath = bundledSkillPath();
|
|
46
|
+
if (!fs.existsSync(zipPath)) {
|
|
47
|
+
log.error(`bundled skill not found at ${zipPath}`);
|
|
48
|
+
log.error('this is a packaging bug — please file an issue');
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const skillsDir = claudeSkillsDir();
|
|
53
|
+
const destDir = path.join(skillsDir, SKILL_NAME);
|
|
54
|
+
const destZip = path.join(skillsDir, `${SKILL_NAME}.skill`);
|
|
55
|
+
|
|
56
|
+
log.info(`Source : ${zipPath}`);
|
|
57
|
+
log.info(`Destination : ${destDir}`);
|
|
58
|
+
|
|
59
|
+
if (fs.existsSync(destDir) && !force) {
|
|
60
|
+
log.warn(`${destDir} already exists`);
|
|
61
|
+
log.warn('re-run with --force to overwrite');
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (dryRun) {
|
|
66
|
+
log.info('(--dry-run) would remove existing skill dir and zip, then extract fresh copy');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
71
|
+
rmrf(destDir);
|
|
72
|
+
rmrf(destZip);
|
|
73
|
+
|
|
74
|
+
const zip = new AdmZip(zipPath);
|
|
75
|
+
zip.extractAllTo(skillsDir, /* overwrite */ true);
|
|
76
|
+
fs.copyFileSync(zipPath, destZip);
|
|
77
|
+
|
|
78
|
+
if (!fs.existsSync(path.join(destDir, 'SKILL.md'))) {
|
|
79
|
+
log.error(`extraction produced no SKILL.md under ${destDir}`);
|
|
80
|
+
log.error('the bundled .skill zip may be malformed');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
log.success(`Installed ${SKILL_NAME} skill to ${destDir}`);
|
|
85
|
+
log.info('Claude Code will pick it up on next session start.');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function installSkill(argv) {
|
|
89
|
+
const targets = [];
|
|
90
|
+
if (argv.claude) targets.push('claude');
|
|
91
|
+
|
|
92
|
+
if (targets.length === 0) {
|
|
93
|
+
log.error('no target specified — pass --claude');
|
|
94
|
+
log.error('(more targets may be added in future releases)');
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (const target of targets) {
|
|
99
|
+
if (target === 'claude') {
|
|
100
|
+
installForClaude({ force: !!argv.force, dryRun: !!argv['dry-run'] });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = installSkill;
|
|
106
|
+
module.exports.bundledSkillPath = bundledSkillPath;
|
|
107
|
+
module.exports.claudeSkillsDir = claudeSkillsDir;
|