@the-open-engine-company/zeroshot 10.8.0 → 10.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/lib/install.js CHANGED
@@ -7,7 +7,7 @@ const { URL } = require('url');
7
7
  const { installSkills } = require('./skills');
8
8
  const {
9
9
  archiveName,
10
- extractExecutable,
10
+ extractExecutables,
11
11
  parseChecksumManifest,
12
12
  targetForHost,
13
13
  verifyArchive,
@@ -25,8 +25,40 @@ const HOST_TARGETS = Object.freeze(
25
25
  );
26
26
 
27
27
  function selectTarget(platform = process.platform, arch = process.arch) {
28
- const { target, executable } = targetForHost(TARGETS, platform, arch);
29
- return { target, executable };
28
+ const { target, executable, resticExecutable } = targetForHost(TARGETS, platform, arch);
29
+ return { target, executable, resticExecutable };
30
+ }
31
+
32
+ function installExecutables(nativeDirectory, entries) {
33
+ fs.mkdirSync(nativeDirectory, { recursive: true });
34
+ const stage = fs.mkdtempSync(path.join(nativeDirectory, '.install-'));
35
+ const replaced = [];
36
+ const backups = [];
37
+ try {
38
+ for (const [name, contents] of entries) {
39
+ fs.writeFileSync(path.join(stage, name), contents, { mode: 0o755, flag: 'wx' });
40
+ }
41
+ for (const [name] of entries) {
42
+ const destination = path.join(nativeDirectory, name);
43
+ if (fs.existsSync(destination)) {
44
+ const backup = path.join(stage, `${name}.previous`);
45
+ fs.renameSync(destination, backup);
46
+ backups.push([destination, backup]);
47
+ }
48
+ }
49
+ for (const [name] of entries) {
50
+ const destination = path.join(nativeDirectory, name);
51
+ fs.renameSync(path.join(stage, name), destination);
52
+ replaced.push(destination);
53
+ if (process.platform !== 'win32') fs.chmodSync(destination, 0o755);
54
+ }
55
+ } catch (error) {
56
+ for (const destination of replaced.reverse()) fs.rmSync(destination, { force: true });
57
+ for (const [destination, backup] of backups.reverse()) fs.renameSync(backup, destination);
58
+ throw error;
59
+ } finally {
60
+ fs.rmSync(stage, { recursive: true, force: true });
61
+ }
30
62
  }
31
63
 
32
64
  function download(url, maximumBytes, redirects = 0) {
@@ -108,19 +140,11 @@ async function install(options = {}) {
108
140
  const manifest = await fetchBuffer(`${baseUrl}/SHA256SUMS`, MAX_MANIFEST_BYTES);
109
141
  const archive = await fetchBuffer(`${baseUrl}/${filename}`, MAX_ARCHIVE_BYTES);
110
142
  verifyArchive(filename, archive, manifest.toString('utf8'));
111
- const executable = extractExecutable(archive, selected.executable);
143
+ const executables = extractExecutables(archive, [selected.executable, selected.resticExecutable]);
112
144
 
113
145
  const nativeDirectory = path.join(packageRoot, 'bin', 'native');
114
146
  const destination = path.join(nativeDirectory, selected.executable);
115
- const temporary = `${destination}.${process.pid}.tmp`;
116
- fs.mkdirSync(nativeDirectory, { recursive: true });
117
- try {
118
- fs.writeFileSync(temporary, executable, { mode: 0o755, flag: 'wx' });
119
- fs.renameSync(temporary, destination);
120
- if (process.platform !== 'win32') fs.chmodSync(destination, 0o755);
121
- } finally {
122
- fs.rmSync(temporary, { force: true });
123
- }
147
+ installExecutables(nativeDirectory, executables);
124
148
  const skillResults = installSkills({
125
149
  packageRoot,
126
150
  homeDirectory: options.homeDirectory,
@@ -136,7 +160,7 @@ module.exports = {
136
160
  RELEASE_BASE_URL,
137
161
  RELEASE_TAG_PREFIX,
138
162
  archiveName,
139
- extractExecutable,
163
+ extractExecutables,
140
164
  install,
141
165
  parseChecksumManifest,
142
166
  selectTarget,
@@ -58,43 +58,68 @@ function parseTarSize(header) {
58
58
  return Number.parseInt(value, 8);
59
59
  }
60
60
 
61
- function extractExecutable(archive, expectedName) {
62
- let tar;
61
+ function decompressArchive(archive) {
63
62
  try {
64
- tar = zlib.gunzipSync(archive);
63
+ return zlib.gunzipSync(archive);
65
64
  } catch (error) {
66
65
  throw new Error(`ARCHIVE_INVALID: cannot decompress release archive: ${error.message}`);
67
66
  }
67
+ }
68
+
69
+ function readTarEntry(tar, offset, expected, executables) {
70
+ const header = tar.subarray(offset, offset + 512);
71
+ if (header.every((byte) => byte === 0)) {
72
+ if (!tar.subarray(offset).every((byte) => byte === 0)) {
73
+ throw new Error('ARCHIVE_INVALID: unexpected data after tar terminator');
74
+ }
75
+ return null;
76
+ }
77
+ const name = header.subarray(0, 100).toString('utf8').replace(/\0.*$/, '');
78
+ const size = parseTarSize(header.subarray(124, 136));
79
+ const start = offset + 512;
80
+ const end = start + size;
81
+ if (end > tar.length) throw new Error('ARCHIVE_INVALID: truncated tar entry');
82
+ if (!expected.has(name) || ![0, '0'.charCodeAt(0)].includes(header[156])) {
83
+ throw new Error(`ARCHIVE_INVALID: unexpected archive entry ${name}`);
84
+ }
85
+ if (executables.has(name)) throw new Error(`ARCHIVE_INVALID: duplicate ${name}`);
86
+ executables.set(name, Buffer.from(tar.subarray(start, end)));
87
+ return start + Math.ceil(size / 512) * 512;
88
+ }
89
+
90
+ function requireExpectedExecutables(expected, executables) {
91
+ for (const name of expected) {
92
+ if (!executables.has(name)) {
93
+ throw new Error(`ARCHIVE_INVALID: archive does not contain ${name}`);
94
+ }
95
+ }
96
+ }
97
+
98
+ function extractExecutables(archive, expectedNames) {
99
+ const expected = new Set(expectedNames);
100
+ if (expected.size !== expectedNames.length || expected.size === 0) {
101
+ throw new Error('ARCHIVE_INVALID: expected executable names must be unique');
102
+ }
103
+ const tar = decompressArchive(archive);
68
104
  let offset = 0;
69
- let executable = null;
105
+ let terminated = false;
106
+ const executables = new Map();
70
107
  while (offset + 512 <= tar.length) {
71
- const header = tar.subarray(offset, offset + 512);
72
- if (header.every((byte) => byte === 0)) {
73
- if (!tar.subarray(offset).every((byte) => byte === 0)) {
74
- throw new Error('ARCHIVE_INVALID: unexpected data after tar terminator');
75
- }
108
+ const next = readTarEntry(tar, offset, expected, executables);
109
+ if (next === null) {
110
+ terminated = true;
76
111
  break;
77
112
  }
78
- const name = header.subarray(0, 100).toString('utf8').replace(/\0.*$/, '');
79
- const size = parseTarSize(header.subarray(124, 136));
80
- const start = offset + 512;
81
- const end = start + size;
82
- if (end > tar.length) throw new Error('ARCHIVE_INVALID: truncated tar entry');
83
- if (name === expectedName) {
84
- if (executable) throw new Error(`ARCHIVE_INVALID: duplicate ${expectedName}`);
85
- executable = Buffer.from(tar.subarray(start, end));
86
- } else {
87
- throw new Error(`ARCHIVE_INVALID: unexpected archive entry ${name}`);
88
- }
89
- offset = start + Math.ceil(size / 512) * 512;
113
+ offset = next;
90
114
  }
91
- if (!executable) throw new Error(`ARCHIVE_INVALID: archive does not contain ${expectedName}`);
92
- return executable;
115
+ if (!terminated) throw new Error('ARCHIVE_INVALID: archive has no tar terminator');
116
+ requireExpectedExecutables(expected, executables);
117
+ return executables;
93
118
  }
94
119
 
95
120
  module.exports = {
96
121
  archiveName,
97
- extractExecutable,
122
+ extractExecutables,
98
123
  parseChecksumManifest,
99
124
  sha256,
100
125
  targetForHost,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@the-open-engine-company/zeroshot",
3
- "version": "10.8.0",
3
+ "version": "10.9.0",
4
4
  "description": "Verified prebuilt binary installer for the standalone Zeroshot engine",
5
5
  "homepage": "https://the-open-engine.github.io/zeroshot/",
6
6
  "license": "MIT",
package/targets.json CHANGED
@@ -3,30 +3,35 @@
3
3
  "platform": "linux",
4
4
  "arch": "x64",
5
5
  "target": "x86_64-unknown-linux-musl",
6
- "executable": "zeroshot"
6
+ "executable": "zeroshot",
7
+ "resticExecutable": "restic"
7
8
  },
8
9
  {
9
10
  "platform": "linux",
10
11
  "arch": "arm64",
11
12
  "target": "aarch64-unknown-linux-musl",
12
- "executable": "zeroshot"
13
+ "executable": "zeroshot",
14
+ "resticExecutable": "restic"
13
15
  },
14
16
  {
15
17
  "platform": "darwin",
16
18
  "arch": "x64",
17
19
  "target": "x86_64-apple-darwin",
18
- "executable": "zeroshot"
20
+ "executable": "zeroshot",
21
+ "resticExecutable": "restic"
19
22
  },
20
23
  {
21
24
  "platform": "darwin",
22
25
  "arch": "arm64",
23
26
  "target": "aarch64-apple-darwin",
24
- "executable": "zeroshot"
27
+ "executable": "zeroshot",
28
+ "resticExecutable": "restic"
25
29
  },
26
30
  {
27
31
  "platform": "win32",
28
32
  "arch": "x64",
29
33
  "target": "x86_64-pc-windows-msvc",
30
- "executable": "zeroshot.exe"
34
+ "executable": "zeroshot.exe",
35
+ "resticExecutable": "restic.exe"
31
36
  }
32
37
  ]