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.
@@ -4,6 +4,21 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
 
6
6
  const VENDOR_KEY = 'wyvrnpm/generated';
7
+
8
+ // Local copy of the arch→VS-platform mapping. Duplicated here rather than
9
+ // imported from ./index so preset generation can run standalone (keeps
10
+ // the dependency graph simple — presets.js doesn't otherwise need the
11
+ // toolchain generator). Keep in sync with `archToVsPlatform` in index.js.
12
+ const ARCH_VS_PLATFORM = {
13
+ x86_64: 'x64',
14
+ x86: 'Win32',
15
+ armv8: 'ARM64',
16
+ armv7: 'ARM',
17
+ };
18
+
19
+ function isVsGenerator(name) {
20
+ return typeof name === 'string' && /^Visual Studio \d/.test(name);
21
+ }
7
22
  const SCHEMA_VERSION = 3; // requires CMake ≥ 3.21
8
23
 
9
24
  /**
@@ -52,6 +67,25 @@ function isWyvrnGenerated(presets) {
52
67
  * `CMAKE_TOOLCHAIN_FILE`. Usually
53
68
  * derived from the project's own
54
69
  * `build.configure` -D args.
70
+ * @param {string|null} [args.profileArch] Active profile arch — used to pin
71
+ * `architecture` for VS generators
72
+ * so `cmake --preset` produces
73
+ * binaries matching the profile
74
+ * (VS otherwise defaults to x64).
75
+ * @param {string[]|null} [args.configs] Build recipe's `configs` list
76
+ * (e.g. ['Debug','Release','MinSizeRel']).
77
+ * When provided, drives
78
+ * `CMAKE_CONFIGURATION_TYPES` so
79
+ * multi-config generators (Ninja
80
+ * Multi-Config, VS) actually
81
+ * produce every config the recipe
82
+ * declares. Null/empty → omitted
83
+ * (let CMake use its generator
84
+ * default). `extraCacheVariables`
85
+ * wins on explicit collision so
86
+ * authors that already bake the
87
+ * list into `build.configure`
88
+ * aren't overridden.
55
89
  */
56
90
  function buildConfigurePreset({
57
91
  profileName,
@@ -60,16 +94,21 @@ function buildConfigurePreset({
60
94
  generator = null,
61
95
  extraCacheVariables = null,
62
96
  installDir = null,
97
+ profileArch = null,
98
+ configs = null,
63
99
  }) {
64
100
  const binaryDir = `\${sourceDir}/build/wyvrn-${profileName}`;
65
- // Spread `extraCacheVariables` FIRST, then the wyvrn-owned entries. This
66
- // means the recipe cannot redirect `CMAKE_TOOLCHAIN_FILE` our dep
67
- // injection is always the authoritative toolchain pointer. A malicious
68
- // or sloppy recipe trying to override it is silently ignored, not
69
- // quietly obeyed. Same protection applies to `CMAKE_INSTALL_PREFIX`:
70
- // if a recipe declares `build.installDir`, we resolve it under the
71
- // binary dir; any cacheVariables override is overwritten below.
101
+ // CMAKE_CONFIGURATION_TYPES is a defensible default from the recipe's
102
+ // declared `configs` list. Applied FIRST (lowest precedence) so an
103
+ // explicit `-DCMAKE_CONFIGURATION_TYPES=...` in `build.configure`
104
+ // still wins authors who already pin the list shouldn't silently
105
+ // lose their override. The wyvrn-owned entries (toolchain, install
106
+ // prefix) still win over both below.
107
+ const configsDefault = (Array.isArray(configs) && configs.length > 0)
108
+ ? { CMAKE_CONFIGURATION_TYPES: configs.join(';') }
109
+ : {};
72
110
  const cacheVariables = {
111
+ ...configsDefault,
73
112
  ...(extraCacheVariables ?? {}),
74
113
  CMAKE_TOOLCHAIN_FILE: `\${sourceDir}/${toolchainRelPath}`,
75
114
  };
@@ -90,31 +129,49 @@ function buildConfigurePreset({
90
129
  cacheVariables,
91
130
  };
92
131
  if (generator) preset.generator = generator;
132
+
133
+ // Visual Studio generators default to x64 when `architecture` is
134
+ // unspecified — which silently overrides the profile arch and makes
135
+ // find_package() reject 32-bit installed packages with a pointer-size
136
+ // mismatch. Pin `architecture` from the profile so `cmake --preset`
137
+ // produces the right artefacts end-to-end.
138
+ if (isVsGenerator(generator) && profileArch && ARCH_VS_PLATFORM[profileArch]) {
139
+ preset.architecture = {
140
+ value: ARCH_VS_PLATFORM[profileArch],
141
+ strategy: 'set',
142
+ };
143
+ }
144
+
93
145
  return preset;
94
146
  }
95
147
 
96
148
  /**
97
- * Two build presets per configure preset: Debug and Release.
149
+ * One build preset per configuration the recipe declares (or the
150
+ * legacy default pair `Debug` + `Release` when no list is provided).
151
+ *
152
+ * The preset name suffix is the configuration name lower-cased — matches
153
+ * the previous Debug/Release naming for those two, and extends to
154
+ * `wyvrn-<profile>-relwithdebinfo` / `-minsizerel` for the multi-config
155
+ * cases. Authors whose recipes include `MinSizeRel` in `build.configs`
156
+ * can now `cmake --build --preset wyvrn-<profile>-minsizerel` directly.
98
157
  *
99
158
  * @param {string} profileName
159
+ * @param {string[]|null} [configs] Recipe's declared configurations. When
160
+ * null/empty, emits the pre-2.8.1 `Debug` + `Release` pair so existing
161
+ * CMakePresets.json files remain byte-identical.
100
162
  * @returns {Array<object>}
101
163
  */
102
- function buildBuildPresets(profileName) {
103
- const config = `wyvrn-${profileName}`;
104
- return [
105
- {
106
- name: `${config}-debug`,
107
- displayName: `wyvrn ${profileName} (Debug)`,
108
- configurePreset: config,
109
- configuration: 'Debug',
110
- },
111
- {
112
- name: `${config}-release`,
113
- displayName: `wyvrn ${profileName} (Release)`,
114
- configurePreset: config,
115
- configuration: 'Release',
116
- },
117
- ];
164
+ function buildBuildPresets(profileName, configs = null) {
165
+ const base = `wyvrn-${profileName}`;
166
+ const list = (Array.isArray(configs) && configs.length > 0)
167
+ ? configs
168
+ : ['Debug', 'Release'];
169
+ return list.map((cfg) => ({
170
+ name: `${base}-${cfg.toLowerCase()}`,
171
+ displayName: `wyvrn ${profileName} (${cfg})`,
172
+ configurePreset: base,
173
+ configuration: cfg,
174
+ }));
118
175
  }
119
176
 
120
177
  /**
@@ -127,6 +184,8 @@ function buildFreshPresetsFile({
127
184
  generator = null,
128
185
  extraCacheVariables = null,
129
186
  installDir = null,
187
+ profileArch = null,
188
+ configs = null,
130
189
  }) {
131
190
  return {
132
191
  version: SCHEMA_VERSION,
@@ -138,9 +197,9 @@ function buildFreshPresetsFile({
138
197
  },
139
198
  },
140
199
  configurePresets: [buildConfigurePreset({
141
- profileName, profileHash, toolchainRelPath, generator, extraCacheVariables, installDir,
200
+ profileName, profileHash, toolchainRelPath, generator, extraCacheVariables, installDir, profileArch, configs,
142
201
  })],
143
- buildPresets: buildBuildPresets(profileName),
202
+ buildPresets: buildBuildPresets(profileName, configs),
144
203
  };
145
204
  }
146
205
 
@@ -161,18 +220,34 @@ function mergeIntoExisting(existing, {
161
220
  generator = null,
162
221
  extraCacheVariables = null,
163
222
  installDir = null,
223
+ profileArch = null,
224
+ configs = null,
164
225
  }) {
165
226
  const newConfigure = buildConfigurePreset({
166
- profileName, profileHash, toolchainRelPath, generator, extraCacheVariables, installDir,
227
+ profileName, profileHash, toolchainRelPath, generator, extraCacheVariables, installDir, profileArch, configs,
167
228
  });
168
- const newBuilds = buildBuildPresets(profileName);
229
+ const newBuilds = buildBuildPresets(profileName, configs);
169
230
  const newConfigureNames = new Set([newConfigure.name]);
170
- const newBuildNames = new Set(newBuilds.map((b) => b.name));
231
+ // When replacing THIS profile's build presets, drop every pre-existing
232
+ // one that targets the same configurePreset — the fresh `newBuilds`
233
+ // list fully replaces them. Scoping to configurePreset avoids stepping
234
+ // on presets for OTHER profiles in the same file, which is the whole
235
+ // point of the merge path.
236
+ //
237
+ // Without this, editing `build.configs` from 4 configs down to 2 would
238
+ // leave orphan build presets pointing at configurations CMake won't
239
+ // generate.
240
+ const myBase = `wyvrn-${profileName}`;
241
+ const isMyBuildPreset = (p) => p && p.configurePreset === myBase;
171
242
 
172
243
  const configurePresets = (existing.configurePresets ?? []).filter((p) => !newConfigureNames.has(p.name));
173
244
  configurePresets.push(newConfigure);
174
245
 
175
- const buildPresets = (existing.buildPresets ?? []).filter((p) => !newBuildNames.has(p.name));
246
+ // Drop every pre-existing build preset that targets THIS profile's
247
+ // configurePreset — the fresh `newBuilds` list (one per current
248
+ // `configs` entry) fully replaces them. Presets for OTHER profiles
249
+ // stay untouched so the multi-profile merge story is preserved.
250
+ const buildPresets = (existing.buildPresets ?? []).filter((p) => !isMyBuildPreset(p));
176
251
  buildPresets.push(...newBuilds);
177
252
 
178
253
  return {
@@ -223,8 +298,10 @@ function generateCMakePresets({
223
298
  generator = null,
224
299
  extraCacheVariables = null,
225
300
  installDir = null,
301
+ profileArch = null,
302
+ configs = null,
226
303
  }) {
227
- const args = { profileName, profileHash, toolchainRelPath, generator, extraCacheVariables, installDir };
304
+ const args = { profileName, profileHash, toolchainRelPath, generator, extraCacheVariables, installDir, profileArch, configs };
228
305
  const candidates = [
229
306
  { filePath: path.join(projectRoot, 'CMakePresets.json'), isUser: false },
230
307
  { filePath: path.join(projectRoot, 'CMakeUserPresets.json'), isUser: true },
@@ -17,6 +17,17 @@ set(WYVRN_PROFILE_NAME "{{PROFILE_NAME}}")
17
17
  set(WYVRN_PROFILE_HASH "{{PROFILE_HASH}}")
18
18
  set(WYVRN_PROFILE_OS "{{PROFILE_OS}}")
19
19
  set(WYVRN_PROFILE_ARCH "{{PROFILE_ARCH}}")
20
+
21
+ # "64" for 64-bit arches (x86_64, armv8, …), "32" for 32-bit (x86, armv7, …),
22
+ # empty for arches outside the known mapping. Derived at generation time from
23
+ # the profile so it is valid BEFORE `project()` — unlike CMAKE_SIZEOF_VOID_P,
24
+ # which is only set after the first project() call. Intended for suffixing
25
+ # artefact names (e.g. "String${WYVRN_ARCH_SUFFIX}" → String64 / String32) so
26
+ # x64 and x86 builds can coexist on disk without overwriting each other.
27
+ # Call WYVRN_APPLY_ARCH_SUFFIX(<target>) from macros.cmake for the common
28
+ # per-target shape, or reference the variable directly for other uses.
29
+ set(WYVRN_ARCH_SUFFIX "{{PROFILE_ARCH_SUFFIX}}")
30
+
20
31
  set(WYVRN_PROFILE_COMPILER "{{PROFILE_COMPILER}}")
21
32
  set(WYVRN_PROFILE_COMPILER_VERSION "{{PROFILE_COMPILER_VERSION}}")
22
33
  set(WYVRN_PROFILE_CPPSTD "{{PROFILE_CPPSTD}}")
@@ -7,6 +7,7 @@ const path = require('path');
7
7
  const { getProvider } = require('./providers');
8
8
  const { defaultCompatBlock } = require('./compat');
9
9
  const { getBuildPaths } = require('./build/cache');
10
+ const { resolveSourceAuth } = require('./auth');
10
11
  const log = require('./logger');
11
12
 
12
13
  const WYVRNPM_VERSION = require('../package.json').version;
@@ -193,6 +194,7 @@ function resolveUploadDestination(argv, config) {
193
194
  let src = argv.uploadSource;
194
195
  let awsProfile = argv.awsProfile;
195
196
  let token = argv.token;
197
+ let sourceEntry = null;
196
198
 
197
199
  const publishSources = config.publishSources ?? [];
198
200
 
@@ -202,17 +204,24 @@ function resolveUploadDestination(argv, config) {
202
204
  if (named) {
203
205
  src = named.url;
204
206
  awsProfile = awsProfile ?? named.profile;
205
- token = token ?? named.token;
207
+ sourceEntry = named;
206
208
  log.info(`upload-built: using publish source "${named.name}" from config`);
207
209
  }
208
210
  } else if (publishSources.length > 0) {
209
211
  const first = publishSources[0];
210
212
  src = first.url;
211
213
  awsProfile = awsProfile ?? first.profile;
212
- token = token ?? first.token;
214
+ sourceEntry = first;
213
215
  log.info(`upload-built: using first configured publish source "${first.name}"`);
214
216
  }
215
217
 
218
+ // Fall back to the matched source entry's own token / tokenEnv only if
219
+ // the caller didn't pass one already (argv.token wins). resolveSourceAuth
220
+ // handles the literal-vs-env-backed precedence per side.
221
+ if (token === undefined || token === null) {
222
+ token = resolveSourceAuth({}, sourceEntry).token;
223
+ }
224
+
216
225
  if (!src) return null;
217
226
  return { uploadSource: src, uploadAuth: { awsProfile, token } };
218
227
  }
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Defense-in-depth against Zip Slip (CVE-2018-1002200 family). Both
5
+ * node-stream-zip and adm-zip claim to handle this upstream, but we
6
+ * don't want to take the library's word for it on a supply-chain-
7
+ * critical code path. Pre-scan zip entry names before extraction and
8
+ * reject anything that could write outside the target directory.
9
+ *
10
+ * The ZIP spec uses forward slashes only, but Windows-produced archives
11
+ * sometimes leak backslashes — normalise both.
12
+ *
13
+ * @param {string} entryName zip-internal path as reported by the zip library
14
+ * @param {string} [context] optional archive identifier for the error message
15
+ * @throws {Error} if the entry name is absolute, contains a path-traversal
16
+ * segment, or starts with a Windows drive-letter prefix.
17
+ */
18
+ function assertSafeZipEntryName(entryName, context) {
19
+ const where = context ? ` in ${context}` : '';
20
+
21
+ if (/^[A-Za-z]:[\\/]/.test(entryName)) {
22
+ throw new Error(`unsafe zip entry${where}: drive-letter absolute path ${JSON.stringify(entryName)}`);
23
+ }
24
+ if (entryName.startsWith('/') || entryName.startsWith('\\')) {
25
+ throw new Error(`unsafe zip entry${where}: absolute path ${JSON.stringify(entryName)}`);
26
+ }
27
+
28
+ const segments = entryName.split(/[\\/]+/);
29
+ for (const seg of segments) {
30
+ if (seg === '..') {
31
+ throw new Error(`unsafe zip entry${where}: path-traversal segment in ${JSON.stringify(entryName)}`);
32
+ }
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Convenience: validate every name in an iterable. Throws on the first
38
+ * offender (fail-fast) rather than collecting all violations.
39
+ *
40
+ * @param {Iterable<string>} names
41
+ * @param {string} [context]
42
+ */
43
+ function assertAllSafeZipEntryNames(names, context) {
44
+ for (const name of names) {
45
+ assertSafeZipEntryName(name, context);
46
+ }
47
+ }
48
+
49
+ module.exports = {
50
+ assertSafeZipEntryName,
51
+ assertAllSafeZipEntryNames,
52
+ };