@agoric/xsnap 0.14.3-dev-49dfe9b.0.49dfe9b → 0.14.3-dev-4cac2f2.0.4cac2f2

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 CHANGED
@@ -45,10 +45,11 @@ instead of compiling native sources locally.
45
45
 
46
46
  Optional environment overrides:
47
47
 
48
- - `XSNAP_BINARY_VERSION` (default: package version)
49
48
  - `XSNAP_BINARY_REPO` (default: `Agoric/xsnap-worker-binaries`)
50
49
  - `XSNAP_BINARY_BASE_URL` (advanced override)
51
- - `XSNAP_BINARY_MANIFEST_SHA256` (required trust anchor for unpinned versions)
50
+ - `XSNAP_BINARY_VERSION` (default: package `build.env`, then package version)
51
+ - `XSNAP_BINARY_MANIFEST_SHA256` (default: package `build.env` manifest hash;
52
+ required trust anchor when overriding `XSNAP_BINARY_VERSION`)
52
53
  - `XSNAP_CACHE_DIR` (advanced override for cached downloads)
53
54
 
54
55
  Some time later, possibly on a different computer…
package/build.env ADDED
@@ -0,0 +1,8 @@
1
+ # Bump this as needed for a different prebuilt binary release.
2
+ XSNAP_BINARY_VERSION=0.14.2
3
+ XSNAP_BINARY_MANIFEST_HASH=377030e37b10554e73d0aff655639051d33281c9e17e7da98275fb9efcaae6f2
4
+ XSNAP_PACKAGE=@agoric/xsnap
5
+ MODDABLE_URL=https://github.com/agoric-labs/moddable.git
6
+ MODDABLE_COMMIT_HASH=f6c5951fc055e4ca592b9166b9ae3cbb9cca6bf0
7
+ XSNAP_NATIVE_URL=https://github.com/agoric-labs/xsnap-pub
8
+ XSNAP_NATIVE_COMMIT_HASH=105bc6862050695b1723fa76df91564fe8111a37
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/xsnap",
3
- "version": "0.14.3-dev-49dfe9b.0.49dfe9b",
3
+ "version": "0.14.3-dev-4cac2f2.0.4cac2f2",
4
4
  "description": "Snapshotting VM worker based on Moddable's XS Javascript engine",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,7 +18,7 @@
18
18
  "repl": "node src/xsrepl.js",
19
19
  "install:prebuilt": "node src/install-prebuilt.js",
20
20
  "build": "yarn install:prebuilt",
21
- "check-version": "/bin/sh -c 'if test \"${npm_package_version}\" != \"$(./scripts/get_xsnap_version.sh)\"; then echo \"xsnap version mismatch; expected '${npm_package_version}'\"; exit 1; fi'",
21
+ "check-version": "/bin/sh -c 'expected=\"$(./scripts/get_expected_xsnap_binary_version.sh)\"; actual=\"$(./scripts/get_xsnap_version.sh)\"; if test \"$expected\" != \"$actual\"; then echo \"xsnap version mismatch; expected $expected\"; exit 1; fi'",
22
22
  "postinstall": "npm run install:prebuilt",
23
23
  "clean": "rm -rf xsnap-native/xsnap/build",
24
24
  "lint": "yarn run -T run-s --continue-on-error 'lint:*'",
@@ -30,7 +30,7 @@
30
30
  "test:xs": "exit 0"
31
31
  },
32
32
  "dependencies": {
33
- "@agoric/xsnap-lockdown": "0.14.1-dev-49dfe9b.0.49dfe9b",
33
+ "@agoric/xsnap-lockdown": "0.14.1-dev-4cac2f2.0.4cac2f2",
34
34
  "@endo/bundle-source": "^4.3.2",
35
35
  "@endo/errors": "^1.3.1",
36
36
  "@endo/eventual-send": "^1.5.0",
@@ -42,7 +42,7 @@
42
42
  "tmp": "^0.2.1"
43
43
  },
44
44
  "devDependencies": {
45
- "@agoric/internal": "0.3.3-dev-49dfe9b.0.49dfe9b",
45
+ "@agoric/internal": "0.3.3-dev-4cac2f2.0.4cac2f2",
46
46
  "@endo/base64": "^1.1.1",
47
47
  "@endo/nat": "^5.2.0",
48
48
  "ava": "^8.0.1",
@@ -53,6 +53,7 @@
53
53
  "files": [
54
54
  "LICENSE*",
55
55
  "api.js",
56
+ "build.env",
56
57
  "scripts/**",
57
58
  "src/**"
58
59
  ],
@@ -65,5 +66,5 @@
65
66
  "engines": {
66
67
  "node": "^22.11 || ^24.14"
67
68
  },
68
- "gitHead": "49dfe9b1a1241d639c7afb2a7e429dab374bf73e"
69
+ "gitHead": "4cac2f2827c82d40fbf65afaaa987face9be50c6"
69
70
  }
@@ -0,0 +1,7 @@
1
+ #! /bin/bash
2
+ set -ueo pipefail
3
+
4
+ # shellcheck source=/dev/null
5
+ source "$(dirname -- "${BASH_SOURCE[0]}")/../build.env"
6
+
7
+ echo "$XSNAP_BINARY_VERSION"
package/src/build.js CHANGED
@@ -7,7 +7,6 @@ import osTop from 'node:os';
7
7
 
8
8
  /**
9
9
  * @import {ChildProcess} from 'node:child_process';
10
- * @import {promises} from 'node:fs';
11
10
  */
12
11
 
13
12
  const { freeze } = Object;
@@ -40,7 +39,7 @@ const ModdableSDK = {
40
39
  *
41
40
  * @param {string} command
42
41
  * @param {{
43
- * spawn: typeof import('node:child_process').spawn,
42
+ * spawn: typeof childProcessTop.spawn,
44
43
  * }} io
45
44
  */
46
45
  function makeCLI(command, { spawn }) {
@@ -114,21 +113,35 @@ const SOURCE_STAMP_FILE = '.agoric-source-stamp.json';
114
113
 
115
114
  /**
116
115
  * @param {string} text
117
- * @returns {Record<string, string>}
116
+ * @returns {{ record: Record<string, string>, entries: ([string, string] | string)[] }}
118
117
  */
119
- const parseEnvText = text => {
118
+ const parseEnvTextEntries = text => {
119
+ /** @type {([string, string] | string)[]} */
120
+ const entries = [];
120
121
  /** @type {Record<string, string>} */
121
- const envMap = {};
122
+ const record = {};
122
123
  for (const line of text.split('\n')) {
123
124
  const trimmed = line.trim();
124
- if (!trimmed || trimmed.startsWith('#')) continue;
125
+ if (!trimmed || trimmed.startsWith('#')) {
126
+ entries.push(line);
127
+ continue;
128
+ }
125
129
  const index = trimmed.indexOf('=');
126
- if (index <= 0) continue;
130
+ if (index <= 0) {
131
+ entries.push(line);
132
+ continue;
133
+ }
134
+
127
135
  const key = trimmed.slice(0, index);
128
136
  const value = trimmed.slice(index + 1);
129
- envMap[key] = value;
137
+ entries.push([key, value]);
138
+ record[key] = value;
139
+ }
140
+ if (entries.at(-1) === '') {
141
+ // Avoid empty trailing line in showEnv.
142
+ entries.pop();
130
143
  }
131
- return envMap;
144
+ return { record, entries };
132
145
  };
133
146
 
134
147
  /**
@@ -142,28 +155,61 @@ const parseEnvText = text => {
142
155
  */
143
156
 
144
157
  /**
158
+ * @param {{ name: string; version: string; }} pkg
159
+ * @param {Record<string, string>} oldBuildEnv
160
+ * @param {Readonly<([string, string] | string)[]>} buildEnvEntries
145
161
  * @param {SourceDescriptor[]} sources
146
162
  * @param {{
147
163
  * stdout: typeof process.stdout,
148
164
  * }} io
149
165
  */
150
- const showEnv = async (sources, { stdout }) => {
166
+ const showEnv = async (
167
+ pkg,
168
+ oldBuildEnv,
169
+ buildEnvEntries,
170
+ sources,
171
+ { stdout },
172
+ ) => {
151
173
  await null;
174
+ const buildEnv = { ...oldBuildEnv };
175
+ if (buildEnv.XSNAP_PACKAGE == null) {
176
+ buildEnv.XSNAP_PACKAGE = pkg.name;
177
+ }
178
+ if (buildEnv.XSNAP_BINARY_VERSION == null) {
179
+ buildEnv.XSNAP_BINARY_VERSION = pkg.version;
180
+ }
181
+
152
182
  for (const { envPrefix, url, commitHash, archiveUrl } of sources) {
153
- stdout.write(`${envPrefix}URL=${url}\n`);
154
- stdout.write(`${envPrefix}COMMIT_HASH=${commitHash}\n`);
183
+ buildEnv[`${envPrefix}URL`] = url;
184
+ buildEnv[`${envPrefix}COMMIT_HASH`] = commitHash;
155
185
  const defaultUrl = defaultArchiveUrl(url, commitHash);
156
186
  if (archiveUrl && archiveUrl !== defaultUrl) {
157
- stdout.write(`${envPrefix}ARCHIVE_URL=${archiveUrl}\n`);
187
+ buildEnv[`${envPrefix}ARCHIVE_URL`] = archiveUrl;
158
188
  }
159
189
  }
190
+
191
+ // Gathered all the replacements, so iterate on the entries.
192
+ for (const line of buildEnvEntries) {
193
+ if (typeof line === 'string') {
194
+ stdout.write(`${line}\n`);
195
+ continue;
196
+ }
197
+ const [key, value] = line;
198
+ stdout.write(`${key}=${buildEnv[key] ?? value}\n`);
199
+ delete buildEnv[key];
200
+ }
201
+
202
+ // Append any new key-values.
203
+ for (const [key, value] of Object.entries(buildEnv)) {
204
+ stdout.write(`${key}=${value}\n`);
205
+ }
160
206
  };
161
207
 
162
208
  /**
163
209
  * @param {SourceDescriptor[]} sources
164
210
  * @param {{
165
- * fs: Pick<typeof import('fs'), 'existsSync'> &
166
- * Pick<typeof promises, 'mkdir' | 'rm' | 'readFile' | 'writeFile' | 'rename'>,
211
+ * fs: Pick<typeof fsTop, 'existsSync'> &
212
+ * Pick<typeof fsTop.promises, 'mkdir' | 'rm' | 'readFile' | 'writeFile' | 'rename'>,
167
213
  * curl: ReturnType<typeof makeCLI>,
168
214
  * tar: ReturnType<typeof makeCLI>,
169
215
  * }} io
@@ -217,17 +263,19 @@ const updateSources = async (sources, { fs, curl, tar }) => {
217
263
  };
218
264
 
219
265
  /**
266
+ * @param {{ name: string; version: string; }} pkg
220
267
  * @param {ModdablePlatform} platform
221
268
  * @param {boolean} force
222
269
  * @param {{
223
- * fs: Pick<typeof import('fs'), 'existsSync'> &
224
- * Pick<typeof promises, 'readFile' | 'writeFile'>,
270
+ * fs: Pick<typeof fsTop, 'existsSync'> &
271
+ * Pick<typeof fsTop.promises, 'readFile' | 'writeFile'>,
225
272
  * make: ReturnType<typeof makeCLI>,
226
273
  * }} io
227
274
  */
228
- const buildXsnap = async (platform, force, { fs, make }) => {
229
- const pjson = await fs.readFile(asset('../package.json'), 'utf-8');
230
- const pkg = JSON.parse(pjson);
275
+ const buildXsnap = async (pkg, platform, force, { fs, make }) => {
276
+ // I solemnly swear I will do no synchronous work followed by a variable
277
+ // number turns of the event loop.
278
+ await null;
231
279
 
232
280
  const configEnvs = [
233
281
  `XSNAP_VERSION=${pkg.version}`,
@@ -268,16 +316,15 @@ const buildXsnap = async (platform, force, { fs, make }) => {
268
316
  * @param {{
269
317
  * env: Record<string, string | undefined>,
270
318
  * stdout: typeof process.stdout,
271
- * spawn: typeof import('node:child_process').spawn,
319
+ * spawn: typeof childProcessTop.spawn,
272
320
  * fs: Pick<typeof import('fs'), 'existsSync'> &
273
- * Pick<typeof promises, 'readFile' | 'writeFile' | 'mkdir' | 'rm' | 'rename'>,
321
+ * Pick<typeof fsTop.promises, 'readFile' | 'writeFile' | 'mkdir' | 'rm' | 'rename'>,
274
322
  * os: Pick<typeof import('os'), 'type'>,
275
323
  * }} io
276
324
  */
277
325
  async function main(args, { env, stdout, spawn, fs, os }) {
278
- // I solemnly swear I will do no synchronous work followed by a variable
279
- // number turns of the event loop.
280
- await null;
326
+ const pjson = await fs.readFile(asset('../package.json'), 'utf-8');
327
+ const pkg = JSON.parse(pjson);
281
328
 
282
329
  const osType = os.type();
283
330
  const platform = ModdableSDK.platforms[osType];
@@ -291,10 +338,13 @@ async function main(args, { env, stdout, spawn, fs, os }) {
291
338
 
292
339
  /** @type {Record<string, string>} */
293
340
  let pinnedEnvFromFile = {};
341
+ /** @type {(string | [string, string])[]} */
342
+ let pinnedEnvTextEntries = [];
294
343
  let hasPinnedEnvFile = false;
295
344
  try {
296
345
  const text = await fs.readFile(asset('../build.env'), 'utf-8');
297
- pinnedEnvFromFile = parseEnvText(text);
346
+ ({ record: pinnedEnvFromFile, entries: pinnedEnvTextEntries } =
347
+ parseEnvTextEntries(text));
298
348
  hasPinnedEnvFile = true;
299
349
  } catch (_err) {
300
350
  // Allow explicit environment overrides to run without a checked-in build.env.
@@ -363,7 +413,7 @@ async function main(args, { env, stdout, spawn, fs, os }) {
363
413
  * @returns {Promise<boolean>} whether the source needs to be refreshed from
364
414
  * its archive URL based on whether the existing source stamp matches the
365
415
  * expected URL and commit hash
366
- * @throws if the source stamp exists but cannot be read or parsed
416
+ * @throws {Error} if the source stamp exists but cannot be read or parsed
367
417
  */
368
418
  const needsSourceRefresh = async source => {
369
419
  await null;
@@ -423,7 +473,9 @@ async function main(args, { env, stdout, spawn, fs, os }) {
423
473
 
424
474
  // --show-env reports effective URL/hash pins without making changes.
425
475
  if (args.includes('--show-env')) {
426
- await showEnv(sources, { stdout });
476
+ await showEnv(pkg, pinnedEnvFromFile, pinnedEnvTextEntries, sources, {
477
+ stdout,
478
+ });
427
479
  return;
428
480
  }
429
481
 
@@ -442,7 +494,7 @@ async function main(args, { env, stdout, spawn, fs, os }) {
442
494
  isRejected(
443
495
  npm.run(['run', '-s', 'check-version'], { cwd: asset('..') }),
444
496
  ));
445
- await buildXsnap(platform, force, { fs, make });
497
+ await buildXsnap(pkg, platform, force, { fs, make });
446
498
  } else if (!hasBin) {
447
499
  throw Error(
448
500
  'XSnap has neither sources nor a pre-built binary. Docker? .dockerignore? npm files?',
@@ -6,7 +6,6 @@ import osTop from 'node:os';
6
6
  import pathTop from 'node:path';
7
7
  import { fileURLToPath } from 'node:url';
8
8
  import * as childProcess from 'node:child_process';
9
- import { manifestHashes } from './prebuilt-manifest-sha256.js';
10
9
 
11
10
  /**
12
11
  * @import {promises as fsPromises} from 'fs';
@@ -202,17 +201,22 @@ const validateTarball = async (tarballPath, expectedFiles) => {
202
201
  };
203
202
 
204
203
  /**
205
- * @param {string} version
206
- * @param {NodeJS.ProcessEnv} env
204
+ * @param {string} text
205
+ * @returns {Record<string, string>}
207
206
  */
208
- const getExpectedManifestHash = (version, env) => {
209
- const expected = env.XSNAP_BINARY_MANIFEST_SHA256 || manifestHashes[version];
210
- if (!expected) {
211
- throw Error(
212
- `Missing trusted manifest hash for xsnap binary version ${version}; set XSNAP_BINARY_MANIFEST_SHA256 for explicit overrides`,
213
- );
207
+ const parseEnvText = text => {
208
+ /** @type {Record<string, string>} */
209
+ const envMap = {};
210
+ for (const line of text.split('\n')) {
211
+ const trimmed = line.trim();
212
+ if (!trimmed || trimmed.startsWith('#')) continue;
213
+ const index = trimmed.indexOf('=');
214
+ if (index <= 0) continue;
215
+ const key = trimmed.slice(0, index);
216
+ const value = trimmed.slice(index + 1);
217
+ envMap[key] = value;
214
218
  }
215
- return expected;
219
+ return envMap;
216
220
  };
217
221
 
218
222
  const main = async () => {
@@ -222,10 +226,44 @@ const main = async () => {
222
226
  return;
223
227
  }
224
228
 
225
- const version = env.XSNAP_BINARY_VERSION || env.npm_package_version;
229
+ await null;
230
+
231
+ const packageJsonPath = env.npm_package_json || asset('../package.json');
232
+ const buildEnvPath = pathTop.join(
233
+ pathTop.dirname(packageJsonPath),
234
+ 'build.env',
235
+ );
236
+
237
+ let buildEnv = {};
238
+ try {
239
+ const buildEnvContents = await fsTop.promises.readFile(buildEnvPath, {
240
+ encoding: 'utf8',
241
+ });
242
+ buildEnv = parseEnvText(buildEnvContents);
243
+ } catch (e) {
244
+ if (e?.code !== 'ENOENT') {
245
+ throw Error(`Cannot parse ${buildEnvPath}: ${e}`);
246
+ }
247
+ }
248
+
249
+ const version =
250
+ env.XSNAP_BINARY_VERSION ||
251
+ buildEnv.XSNAP_BINARY_VERSION ||
252
+ env.npm_package_version;
253
+ const expectedManifestHash =
254
+ env.XSNAP_BINARY_MANIFEST_SHA256 ||
255
+ env.XSNAP_BINARY_MANIFEST_HASH ||
256
+ buildEnv.XSNAP_BINARY_MANIFEST_HASH;
257
+
226
258
  if (!version) {
227
259
  throw Error(
228
- 'Missing XSNAP_BINARY_VERSION and npm_package_version; cannot resolve prebuilt release',
260
+ 'Missing $XSNAP_BINARY_VERSION, build.env $XSNAP_BINARY_VERSION, or npm package version; cannot resolve prebuilt release',
261
+ );
262
+ }
263
+
264
+ if (!expectedManifestHash) {
265
+ throw Error(
266
+ `Missing $XSNAP_BINARY_MANIFEST_SHA256 or build.env $XSNAP_BINARY_MANIFEST_HASH; cannot resolve prebuilt manifest hash`,
229
267
  );
230
268
  }
231
269
 
@@ -249,7 +287,6 @@ const main = async () => {
249
287
  const tarballPath = pathTop.join(cacheRoot, tarballName);
250
288
  const manifestPath = pathTop.join(cacheRoot, manifestName);
251
289
  const extractRoot = pathTop.join(cacheRoot, 'bundle');
252
- const expectedManifestHash = getExpectedManifestHash(version, env);
253
290
 
254
291
  const releaseSrc = pathTop.join(
255
292
  extractRoot,
@@ -1,6 +0,0 @@
1
- export const manifestHashes = {
2
- '0.14.2': '377030e37b10554e73d0aff655639051d33281c9e17e7da98275fb9efcaae6f2',
3
- '0.15.0': 'b481d7d5e66ed17a00624c9fea2f666b097e3bbef0d244201dd26a09e34064f1',
4
- '0.16.0-u23.0':
5
- '97400cbffc970b7e9172882d8f6c5b861f0c714b929822195b5818ca4f447197',
6
- };