@agoric/cosmic-swingset 0.40.0 → 0.40.1-dev-800c2bb.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.
@@ -0,0 +1,194 @@
1
+ // @ts-check
2
+
3
+ import { assert, Fail } from '@agoric/assert';
4
+
5
+ // XXX Do these "StorageAPI" functions belong in their own package?
6
+
7
+ /**
8
+ * @template {unknown} [T=unknown]
9
+ * @typedef {{
10
+ * has: (key: string) => boolean,
11
+ * get: (key: string) => T | undefined,
12
+ * getNextKey: (previousKey: string) => string | undefined,
13
+ * set: (key: string, value: T ) => void,
14
+ * delete: (key: string) => void,
15
+ * }} KVStore
16
+ */
17
+
18
+ /**
19
+ * Assert function to ensure that an object implements the StorageAPI
20
+ * interface: methods { has, getNextKey, get, set, delete }
21
+ * (cf. packages/SwingSet/docs/state.md#transactions).
22
+ *
23
+ * @param {*} kvStore The object to be tested
24
+ *
25
+ * @throws {Error} if, upon inspection, the parameter does not satisfy the above
26
+ * criteria.
27
+ *
28
+ * @returns {void}
29
+ */
30
+ export function insistStorageAPI(kvStore) {
31
+ for (const n of ['has', 'getNextKey', 'get', 'set', 'delete']) {
32
+ n in kvStore || Fail`kvStore.${n} is missing, cannot use`;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Create a StorageAPI object that buffers writes to a wrapped StorageAPI object
38
+ * until told to commit (or abort) them.
39
+ *
40
+ * @template {unknown} [T=unknown]
41
+ * @param {KVStore<T>} kvStore The StorageAPI object to wrap
42
+ * @param {{
43
+ * onGet?: (key: string, value: T) => void, // a callback invoked after getting a value from kvStore
44
+ * onPendingSet?: (key: string, value: T) => void, // a callback invoked after a new uncommitted value is set
45
+ * onPendingDelete?: (key: string) => void, // a callback invoked after a new uncommitted delete
46
+ * onCommit?: () => void, // a callback invoked after pending operations have been committed
47
+ * onAbort?: () => void, // a callback invoked after pending operations have been aborted
48
+ * }} listeners Optional callbacks to be invoked when respective events occur
49
+ *
50
+ * @returns {{kvStore: KVStore<T>, commit: () => void, abort: () => void}}
51
+ */
52
+ export function makeBufferedStorage(kvStore, listeners = {}) {
53
+ insistStorageAPI(kvStore);
54
+
55
+ const { onGet, onPendingSet, onPendingDelete, onCommit, onAbort } = listeners;
56
+
57
+ // To avoid confusion, additions and deletions are prevented from sharing
58
+ // the same key at any given time.
59
+ const additions = new Map();
60
+ const deletions = new Set();
61
+
62
+ const buffered = {
63
+ has(key) {
64
+ assert.typeof(key, 'string');
65
+ if (additions.has(key)) return true;
66
+ if (deletions.has(key)) return false;
67
+ return kvStore.has(key);
68
+ },
69
+ get(key) {
70
+ assert.typeof(key, 'string');
71
+ if (additions.has(key)) return additions.get(key);
72
+ if (deletions.has(key)) return undefined;
73
+ const value = kvStore.get(key);
74
+ // @ts-expect-error value may be undefined
75
+ if (onGet !== undefined) onGet(key, value);
76
+ return value;
77
+ },
78
+ set(key, value) {
79
+ assert.typeof(key, 'string');
80
+ additions.set(key, value);
81
+ deletions.delete(key);
82
+ if (onPendingSet !== undefined) onPendingSet(key, value);
83
+ },
84
+ delete(key) {
85
+ assert.typeof(key, 'string');
86
+ additions.delete(key);
87
+ deletions.add(key);
88
+ if (onPendingDelete !== undefined) onPendingDelete(key);
89
+ },
90
+
91
+ /**
92
+ * @param {string} previousKey
93
+ */
94
+ getNextKey(previousKey) {
95
+ assert.typeof(previousKey, 'string');
96
+ throw Error('not implemented');
97
+ },
98
+ };
99
+ function commit() {
100
+ for (const [key, value] of additions) {
101
+ kvStore.set(key, value);
102
+ }
103
+ for (const key of deletions) {
104
+ kvStore.delete(key);
105
+ }
106
+ additions.clear();
107
+ deletions.clear();
108
+ if (onCommit !== undefined) onCommit();
109
+ }
110
+ function abort() {
111
+ additions.clear();
112
+ deletions.clear();
113
+ if (onAbort !== undefined) onAbort();
114
+ }
115
+ return { kvStore: buffered, commit, abort };
116
+ }
117
+
118
+ /**
119
+ * @template {unknown} [T=unknown]
120
+ * @param {KVStore<T>} kvStore
121
+ */
122
+ export const makeReadCachingStorage = kvStore => {
123
+ // In addition to the wrapping write buffer, keep a simple cache of
124
+ // read values for has and get.
125
+ let cache;
126
+ function resetCache() {
127
+ cache = new Map();
128
+ }
129
+ resetCache();
130
+
131
+ const deleted = Symbol('deleted');
132
+ const undef = Symbol('undefined');
133
+
134
+ /** @type {KVStore<T>} */
135
+ const storage = harden({
136
+ has(key) {
137
+ const value = cache.get(key);
138
+ if (value !== undefined) {
139
+ return value !== deleted;
140
+ } else {
141
+ const ret = kvStore.has(key);
142
+ if (!ret) {
143
+ cache.set(key, deleted);
144
+ }
145
+ return ret;
146
+ }
147
+ },
148
+ get(key) {
149
+ let value = cache.get(key);
150
+ if (value !== undefined) {
151
+ return value === deleted || value === undef ? undefined : value;
152
+ }
153
+
154
+ // Fetch the value and cache it until the next commit or abort.
155
+ value = kvStore.get(key);
156
+ cache.set(key, value === undefined ? undef : value);
157
+ return value;
158
+ },
159
+ set(key, value) {
160
+ // Set the value and cache it until the next commit or abort (which is
161
+ // expected immediately, since the buffered wrapper only calls set
162
+ // *during* a commit).
163
+ // `undefined` is a valid value technically different than deletion,
164
+ // depending on how the underlying store does its serialization
165
+ cache.set(key, value === undefined ? undef : value);
166
+ kvStore.set(key, value);
167
+ },
168
+ delete(key) {
169
+ // Delete the value and cache the deletion until next commit or abort.
170
+ // Deletion results in `undefined` on `get`, but `false` on `has`
171
+ cache.set(key, deleted);
172
+ kvStore.delete(key);
173
+ },
174
+ getNextKey(_previousKey) {
175
+ throw Error('not implemented');
176
+ },
177
+ });
178
+ const {
179
+ kvStore: buffered,
180
+ commit,
181
+ abort,
182
+ } = makeBufferedStorage(storage, {
183
+ // Enqueue a write of any retrieved value, to handle callers like mailbox.js
184
+ // that expect local mutations to be automatically written back.
185
+ onGet(key, value) {
186
+ buffered.set(key, value);
187
+ },
188
+
189
+ // Reset the read cache upon commit or abort.
190
+ onCommit: resetCache,
191
+ onAbort: resetCache,
192
+ });
193
+ return harden({ ...buffered, commit, abort });
194
+ };
@@ -1,3 +1,5 @@
1
+ // @jessie-check
2
+
1
3
  // Detect if this is run as a script.
2
4
  import url from 'url';
3
5
  import process from 'process';
@@ -74,7 +74,7 @@ export default function stableStringify(obj, opts) {
74
74
  }
75
75
  if (seen.indexOf(node) !== -1) {
76
76
  if (cycles) return JSON.stringify('__cycle__');
77
- throw new TypeError('Converting circular structure to JSON');
77
+ throw TypeError('Converting circular structure to JSON');
78
78
  } else seen.push(node);
79
79
 
80
80
  const keys = objectKeys(node).sort(cmp && cmp(node));
@@ -0,0 +1,134 @@
1
+ // @ts-check
2
+
3
+ import { Fail } from '@agoric/assert';
4
+
5
+ /**
6
+ * @typedef {object} QueueStorage
7
+ * @property {() => void} commit
8
+ * @property {() => void} abort
9
+ * @property {(key: string) => string | undefined} get
10
+ * @property {(key: string, value: string) => void} set
11
+ * @property {(key: string) => void} delete
12
+ */
13
+
14
+ /**
15
+ * @typedef {{[idx: number]: string | undefined, head?: string, tail?: string}} QueueStorageDump
16
+ * @param {QueueStorageDump} [init]
17
+ * @returns {{storage: QueueStorage; dump: () => QueueStorageDump}}
18
+ */
19
+ export const makeQueueStorageMock = init => {
20
+ const storage = new Map(init && Object.entries(init));
21
+ return harden({
22
+ storage: {
23
+ get(key) {
24
+ return storage.get(key);
25
+ },
26
+ set(key, value) {
27
+ typeof value === 'string' || Fail`invalid value type ${value}`;
28
+ storage.set(key, value);
29
+ },
30
+ delete(key) {
31
+ storage.delete(key);
32
+ },
33
+ commit() {},
34
+ abort() {},
35
+ },
36
+ dump: () => harden(Object.fromEntries(storage.entries())),
37
+ });
38
+ };
39
+
40
+ /**
41
+ * Create a queue backed by some sort of scoped storage.
42
+ *
43
+ * The queue writes the following bare keys, and expect any prefixing/scoping
44
+ * to be handled by the storage:
45
+ * - `head`: the index of the first entry of the queue.
46
+ * - `tail`: the index *past* the last entry in the queue.
47
+ * - `<index>`: the contents of the queue at the given index.
48
+ *
49
+ * For the cosmos inbound queues (`actionQueue` or `highPriorityQueue`), the
50
+ * golang side will push into the queue, updating the index stored at key
51
+ * `${queuePath}.tail` and setting data for key `${queuePath}.${index}`.
52
+ * The JS side will shift the queue, updating the index at key
53
+ * `${queuePath}.head` and reading and deleting `${queuePath}.${index}`.
54
+ *
55
+ * Parallel access is not supported, only a single outstanding operation at a
56
+ * time.
57
+ *
58
+ * @template {unknown} [T=unknown]
59
+ * @param {QueueStorage} storage a scoped queue storage
60
+ */
61
+ export const makeQueue = storage => {
62
+ const getHead = () => BigInt(storage.get('head') || 0);
63
+ const getTail = () => BigInt(storage.get('tail') || 0);
64
+
65
+ /** @type {IterableIterator<T> | null} */
66
+ let currentIterator = null;
67
+ const queue = {
68
+ size: () => {
69
+ return Number(getTail() - getHead());
70
+ },
71
+ /** @param {T} obj */
72
+ push: obj => {
73
+ const tail = getTail();
74
+ storage.set('tail', String(tail + 1n));
75
+ storage.set(`${tail}`, JSON.stringify(obj));
76
+ storage.commit();
77
+ currentIterator = null;
78
+ },
79
+ /** @returns {IterableIterator<T>} */
80
+ consumeAll: () => {
81
+ let head = getHead();
82
+ const tail = getTail();
83
+ let done = !(head < tail);
84
+ const iterator = {
85
+ [Symbol.iterator]: () => iterator,
86
+ next: () => {
87
+ currentIterator === iterator || Fail`invalid iterator`;
88
+ if (!done) {
89
+ if (head < tail) {
90
+ // Still within the queue.
91
+ const headKey = `${head}`;
92
+ const value = JSON.parse(
93
+ /** @type {string} */ (storage.get(headKey)),
94
+ );
95
+ storage.delete(headKey);
96
+ head += 1n;
97
+ return { value, done };
98
+ }
99
+ // Reached the end, so clean up our indices.
100
+ storage.delete('head');
101
+ storage.delete('tail');
102
+ storage.commit();
103
+ done = true;
104
+ }
105
+ return { value: undefined, done };
106
+ },
107
+ return: () => {
108
+ currentIterator === iterator || Fail`invalid iterator`;
109
+ if (!done) {
110
+ // We're done consuming, so save our state.
111
+ storage.set('head', String(head));
112
+ storage.commit();
113
+ done = true;
114
+ }
115
+ return { value: undefined, done };
116
+ },
117
+ throw: err => {
118
+ currentIterator === iterator || Fail`invalid iterator`;
119
+ if (!done) {
120
+ // Don't change our state.
121
+ storage.abort();
122
+ done = true;
123
+ throw err;
124
+ }
125
+ return { value: undefined, done };
126
+ },
127
+ };
128
+ currentIterator = iterator;
129
+ return iterator;
130
+ },
131
+ };
132
+ return queue;
133
+ };
134
+ harden(makeQueue);
@@ -0,0 +1,133 @@
1
+ // @ts-check
2
+
3
+ import { resolve as pathResolve } from 'path';
4
+
5
+ export const makeProcessValue = ({ env, args }) => {
6
+ /**
7
+ * @param {string} flagName
8
+ * @param {string | undefined} [deflt]
9
+ * @returns {string | undefined}
10
+ */
11
+ const getFlag = (flagName, deflt) => {
12
+ let flagValue = deflt;
13
+ const envValue =
14
+ env[`AG_CHAIN_COSMOS_${flagName.toUpperCase().replace(/-/g, '_')}`];
15
+ if (envValue !== undefined) {
16
+ flagValue = envValue;
17
+ }
18
+ const flag = `--${flagName}`;
19
+ const flagEquals = `--${flagName}=`;
20
+ for (let i = 0; i < args.length; i += 1) {
21
+ const arg = args[i];
22
+ if (arg === flag) {
23
+ if (args.length > i + 1 && !args[i + 1].startsWith('--')) {
24
+ i += 1;
25
+ flagValue = args[i];
26
+ } else {
27
+ flagValue = '';
28
+ }
29
+ } else if (arg.startsWith(flagEquals)) {
30
+ flagValue = arg.substr(flagEquals.length);
31
+ }
32
+ }
33
+ return flagValue;
34
+ };
35
+
36
+ /**
37
+ * @param {object} options
38
+ * @param {string} [options.envName]
39
+ * @param {string} [options.flagName]
40
+ * @param {boolean} [options.emptyFlagIsTrue]
41
+ * @returns {boolean | undefined}
42
+ */
43
+ const getBoolean = ({ envName, flagName, emptyFlagIsTrue = true }) => {
44
+ let option;
45
+ if (flagName) {
46
+ option = getFlag(flagName);
47
+ } else if (envName) {
48
+ option = env[envName];
49
+ } else {
50
+ return undefined;
51
+ }
52
+
53
+ switch (option) {
54
+ case '0':
55
+ case 'false':
56
+ case false:
57
+ return false;
58
+ case '1':
59
+ case 'true':
60
+ case true:
61
+ return true;
62
+ default:
63
+ if (option === '' && flagName && emptyFlagIsTrue) {
64
+ return true;
65
+ }
66
+ if (option === undefined && envName && flagName) {
67
+ return getBoolean({ envName, emptyFlagIsTrue });
68
+ }
69
+ return undefined;
70
+ }
71
+ };
72
+
73
+ /**
74
+ * @param {object} options
75
+ * @param {string} [options.envName]
76
+ * @param {string} [options.flagName]
77
+ * @returns {number | undefined}
78
+ */
79
+ const getInteger = ({ envName, flagName }) => {
80
+ let option;
81
+ if (flagName) {
82
+ option = getFlag(flagName);
83
+ } else if (envName) {
84
+ option = env[envName];
85
+ } else {
86
+ return undefined;
87
+ }
88
+
89
+ if (option) {
90
+ const value = Number.parseInt(option, 10);
91
+ return String(value) === option ? value : undefined;
92
+ } else if (option === undefined && flagName && envName) {
93
+ return getInteger({ envName });
94
+ }
95
+
96
+ return undefined;
97
+ };
98
+
99
+ /**
100
+ * @param {object} options
101
+ * @param {string} [options.envName]
102
+ * @param {string} [options.flagName]
103
+ * @param {string} [options.trueValue]
104
+ * @returns {string | undefined}
105
+ */
106
+ const getPath = ({ envName, flagName, trueValue }) => {
107
+ if (trueValue !== undefined) {
108
+ const boolValue = getBoolean({ envName, flagName });
109
+ if (boolValue !== undefined) {
110
+ return boolValue ? trueValue : undefined;
111
+ }
112
+ }
113
+
114
+ let option;
115
+ if (flagName) {
116
+ option = getFlag(flagName);
117
+ } else if (envName) {
118
+ option = env[envName];
119
+ } else {
120
+ return undefined;
121
+ }
122
+
123
+ if (option) {
124
+ return pathResolve(option);
125
+ } else if (trueValue && envName && flagName) {
126
+ return getPath({ envName, trueValue });
127
+ } else {
128
+ return undefined;
129
+ }
130
+ };
131
+
132
+ return harden({ getFlag, getBoolean, getInteger, getPath });
133
+ };
@@ -0,0 +1,207 @@
1
+ #! /usr/bin/env node
2
+ // @ts-check
3
+ // @jessie-check
4
+
5
+ import '@endo/init/unsafe-fast.js';
6
+
7
+ import os from 'os';
8
+ import process from 'process';
9
+ import { Transform } from 'stream';
10
+ import fsPower from 'fs';
11
+ import fsPromisesPower from 'fs/promises';
12
+ import pathPower from 'path';
13
+
14
+ import BufferLineTransform from '@agoric/internal/src/node/buffer-line-transform.js';
15
+ import { Fail } from '@agoric/assert';
16
+ import { importSwingStore } from '@agoric/swing-store';
17
+
18
+ import { isEntrypoint } from './helpers/is-entrypoint.js';
19
+ import { makeProcessValue } from './helpers/process-value.js';
20
+
21
+ /**
22
+ * @typedef {object} StateSyncImporterOptions
23
+ * @property {string} stateDir the directory containing the SwingStore to export
24
+ * @property {string} exportDir the directory where to place the exported artifacts and manifest
25
+ * @property {number} [blockHeight] block height to check for
26
+ * @property {boolean} [includeHistorical] whether to include historical artifacts in the export
27
+ */
28
+
29
+ /**
30
+ * @param {StateSyncImporterOptions} options
31
+ * @param {object} powers
32
+ * @param {Pick<import('fs/promises'), 'readFile'> & Pick<import('fs'), 'createReadStream'>} powers.fs
33
+ * @param {import('path')['resolve']} powers.pathResolve
34
+ * @param {typeof import('@agoric/swing-store')['importSwingStore']} [powers.importSwingStore]
35
+ * @param {null | ((...args: any[]) => void)} [powers.log]
36
+ * @returns {Promise<void>}
37
+ */
38
+ export const performStateSyncImport = async (
39
+ { stateDir, exportDir, blockHeight, includeHistorical },
40
+ {
41
+ fs: { createReadStream, readFile },
42
+ pathResolve,
43
+ importSwingStore: importDB = importSwingStore,
44
+ log = console.log,
45
+ },
46
+ ) => {
47
+ /** @param {string} allegedRelativeFilename */
48
+ const safeExportFileResolve = allegedRelativeFilename => {
49
+ const resolvedPath = pathResolve(exportDir, allegedRelativeFilename);
50
+ resolvedPath.startsWith(exportDir) ||
51
+ Fail`Exported file ${allegedRelativeFilename} must be in export dir ${exportDir}`;
52
+ return resolvedPath;
53
+ };
54
+
55
+ const manifestPath = safeExportFileResolve('export-manifest.json');
56
+ /** @type {Readonly<import('./export-kernel-db.js').StateSyncManifest>} */
57
+ const manifest = await readFile(manifestPath, { encoding: 'utf-8' }).then(
58
+ data => JSON.parse(data),
59
+ );
60
+
61
+ if (blockHeight !== undefined && manifest.blockHeight !== blockHeight) {
62
+ Fail`State-sync manifest for unexpected block height ${manifest.blockHeight} (expected ${blockHeight})`;
63
+ }
64
+
65
+ if (!manifest.data) {
66
+ throw Fail`State-sync manifest missing export data`;
67
+ }
68
+
69
+ if (!manifest.artifacts) {
70
+ throw Fail`State-sync manifest missing required artifacts`;
71
+ }
72
+
73
+ const artifacts = harden(Object.fromEntries(manifest.artifacts));
74
+
75
+ if (
76
+ includeHistorical &&
77
+ manifest.mode !== 'archival' &&
78
+ manifest.mode !== 'debug'
79
+ ) {
80
+ throw Fail`State-sync manifest missing historical artifacts`;
81
+ }
82
+
83
+ // Represent the data in `exportDir` as a SwingSetExporter object.
84
+ /** @type {import('@agoric/swing-store').SwingStoreExporter} */
85
+ const exporter = harden({
86
+ async *getExportData() {
87
+ log?.('importing export data');
88
+ const exportData = createReadStream(
89
+ safeExportFileResolve(/** @type {string} */ (manifest.data)),
90
+ );
91
+ const exportDataEntries = exportData
92
+ .pipe(new BufferLineTransform())
93
+ .setEncoding('utf8')
94
+ .pipe(
95
+ new Transform({
96
+ objectMode: true,
97
+ transform(data, encoding, callback) {
98
+ try {
99
+ callback(null, JSON.parse(data));
100
+ } catch (error) {
101
+ callback(error);
102
+ }
103
+ },
104
+ }),
105
+ );
106
+ yield* exportDataEntries;
107
+ },
108
+ async *getArtifact(name) {
109
+ log?.(`importing artifact ${name}`);
110
+ const fileName = artifacts[name];
111
+ if (!fileName) {
112
+ Fail`invalid artifact ${name}`;
113
+ }
114
+ const stream = createReadStream(safeExportFileResolve(fileName));
115
+ yield* stream;
116
+ },
117
+ async *getArtifactNames() {
118
+ yield* Object.keys(artifacts);
119
+ },
120
+ async close() {
121
+ // TODO
122
+ },
123
+ });
124
+
125
+ const swingstore = await importDB(exporter, stateDir, { includeHistorical });
126
+
127
+ const { hostStorage } = swingstore;
128
+
129
+ hostStorage.kvStore.set('host.height', String(manifest.blockHeight));
130
+ await hostStorage.commit();
131
+ await hostStorage.close();
132
+ };
133
+
134
+ /**
135
+ * @param {string[]} args
136
+ * @param {object} powers
137
+ * @param {Partial<Record<string, string>>} powers.env
138
+ * @param {string} powers.homedir
139
+ * @param {Console} powers.console
140
+ * @param {Pick<import('fs/promises'), 'readFile' | 'stat'> & Pick<import('fs'), 'createReadStream'>} powers.fs
141
+ * @param {import('path')['resolve']} powers.pathResolve
142
+ */
143
+ export const main = async (
144
+ args,
145
+ { env, homedir, console, fs, pathResolve },
146
+ ) => {
147
+ const processValue = makeProcessValue({ env, args });
148
+
149
+ const stateDir =
150
+ processValue.getFlag('state-dir') ||
151
+ // We try to find the actual cosmos state directory (default=~/.ag-chain-cosmos)
152
+ `${processValue.getFlag(
153
+ 'home',
154
+ `${homedir}/.ag-chain-cosmos`,
155
+ )}/data/agoric`;
156
+
157
+ const stateDirStat = await fs.stat(stateDir);
158
+ if (!stateDirStat.isDirectory()) {
159
+ throw Error('state-dir must be an exiting directory');
160
+ }
161
+
162
+ const exportDir = pathResolve(
163
+ /** @type {string} */ (processValue.getFlag('export-dir', '.')),
164
+ );
165
+
166
+ const includeHistorical = processValue.getBoolean({
167
+ flagName: 'include-historical',
168
+ });
169
+
170
+ const checkBlockHeight = processValue.getInteger({
171
+ flagName: 'check-block-height',
172
+ });
173
+
174
+ const verbose = processValue.getBoolean({
175
+ flagName: 'verbose',
176
+ });
177
+
178
+ await performStateSyncImport(
179
+ {
180
+ stateDir,
181
+ exportDir,
182
+ blockHeight: checkBlockHeight,
183
+ includeHistorical,
184
+ },
185
+ {
186
+ fs,
187
+ pathResolve,
188
+ log: verbose ? console.log : undefined,
189
+ },
190
+ );
191
+ };
192
+
193
+ if (isEntrypoint(import.meta.url)) {
194
+ main(process.argv.splice(2), {
195
+ homedir: os.homedir(),
196
+ env: process.env,
197
+ console,
198
+ fs: { ...fsPower, ...fsPromisesPower },
199
+ pathResolve: pathPower.resolve,
200
+ }).then(
201
+ _res => 0,
202
+ rej => {
203
+ console.error(`error running export-kernel-db:`, rej);
204
+ process.exit(process.exitCode || rej.exitCode || 1);
205
+ },
206
+ );
207
+ }