agoric 0.21.2-upgrade-18-dev-6ddbef0.0 → 0.21.2-upgrade-19-dev-ae3bcff.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/src/install.js CHANGED
@@ -1,8 +1,10 @@
1
1
  /* eslint-env node */
2
2
  import path from 'path';
3
3
  import chalk from 'chalk';
4
+ import { execFileSync } from 'child_process';
4
5
  import { makePspawn } from './helpers.js';
5
6
  import DEFAULT_SDK_PACKAGE_NAMES from './sdk-package-names.js';
7
+ import { listWorkspaces } from './lib/packageManager.js';
6
8
 
7
9
  const REQUIRED_AGORIC_START_PACKAGES = [
8
10
  '@agoric/solo',
@@ -30,17 +32,7 @@ export default async function installMain(progname, rawArgs, powers, opts) {
30
32
  const rimraf = file => pspawn('rm', ['-rf', file]);
31
33
 
32
34
  async function getWorktreePackagePaths(cwd = '.', map = new Map()) {
33
- // run `yarn workspaces info` to get the list of directories to
34
- // use, instead of a hard-coded list
35
- const p = pspawn('yarn', ['workspaces', '--silent', 'info'], {
36
- cwd,
37
- stdio: ['inherit', 'pipe', 'inherit'],
38
- });
39
- const stdout = [];
40
- p.childProcess.stdout.on('data', out => stdout.push(out));
41
- await p;
42
- const d = JSON.parse(Buffer.concat(stdout).toString('utf-8'));
43
- for (const [name, { location }] of Object.entries(d)) {
35
+ for (const { name, location } of listWorkspaces({ execFileSync })) {
44
36
  map.set(name, path.resolve(cwd, location));
45
37
  }
46
38
  return map;
@@ -49,7 +41,7 @@ export default async function installMain(progname, rawArgs, powers, opts) {
49
41
  let subdirs;
50
42
  const workTrees = ['.'];
51
43
  let sdkWorktree;
52
- /** @type {Map<string, string>} */
44
+ /** @type {Map<string, string | null>} */
53
45
  const sdkPackageToPath = new Map();
54
46
  const linkFolder = path.resolve(`_agstate/yarn-links`);
55
47
  const linkFlags = [];
@@ -131,7 +123,6 @@ export default async function installMain(progname, rawArgs, powers, opts) {
131
123
  // Ensure we update the package.json before exiting.
132
124
  const updatePackageJson = async () => {
133
125
  // Don't update on exit anymore.
134
- // eslint-disable-next-line no-use-before-define
135
126
  process.off('beforeExit', updatePackageJsonOnExit);
136
127
  log.info(`updating ${pjson}`);
137
128
  await fs.writeFile(
@@ -170,7 +161,7 @@ export default async function installMain(progname, rawArgs, powers, opts) {
170
161
  .then(results => {
171
162
  // After all have settled, throw any errors.
172
163
  const failures = results.filter(
173
- ({ status }) => status !== 'fulfilled',
164
+ result => result.status !== 'fulfilled',
174
165
  );
175
166
  if (failures.length) {
176
167
  throw AggregateError(
@@ -284,6 +275,10 @@ export default async function installMain(progname, rawArgs, powers, opts) {
284
275
  // Create symlinks to the SDK packages.
285
276
  await Promise.all(
286
277
  [...sdkPackageToPath.entries()].map(async ([pjName, dir]) => {
278
+ if (typeof dir !== 'string') {
279
+ throw Error(`unexpected incomplete package mapping: ${pjName}`);
280
+ }
281
+
287
282
  const SUBOPTIMAL = false;
288
283
  await null;
289
284
  if (SUBOPTIMAL) {
@@ -301,7 +296,7 @@ export default async function installMain(progname, rawArgs, powers, opts) {
301
296
  log('linking', linkName);
302
297
  return fs
303
298
  .mkdir(linkDir, { recursive: true })
304
- .then(_ => fs.symlink(path.relative(linkDir, dir), linkName));
299
+ .then(() => fs.symlink(path.relative(linkDir, dir), linkName));
305
300
  }),
306
301
  );
307
302
 
package/src/lib/chain.js CHANGED
@@ -3,6 +3,11 @@
3
3
  import { normalizeBech32 } from '@cosmjs/encoding';
4
4
  import { execFileSync as execFileSyncAmbient } from 'child_process';
5
5
 
6
+ /**
7
+ * @import {MinimalNetworkConfig} from '@agoric/client-utils';
8
+ * @import {ParamsSDKType} from '@agoric/cosmic-proto/agoric/swingset/swingset.js';
9
+ */
10
+
6
11
  const agdBinary = 'agd';
7
12
 
8
13
  /**
@@ -34,11 +39,42 @@ export const normalizeAddressWithOptions = (
34
39
  };
35
40
  harden(normalizeAddressWithOptions);
36
41
 
42
+ /** @typedef {number | 'auto' | ['auto', adjustment?: number | undefined]} GasLimit */
43
+
44
+ /**
45
+ * @param {GasLimit} limit
46
+ * @returns {string[]}
47
+ */
48
+ const makeGasOpts = limit => {
49
+ if (Number.isFinite(limit) || limit === 'auto') {
50
+ return [`--gas=${limit}`];
51
+ }
52
+ if (Array.isArray(limit) && limit.length >= 1 && limit[0] === 'auto') {
53
+ const gasOpts = ['--gas=auto'];
54
+ if (limit.length > 1) {
55
+ const [adjustment, ...rest] = limit.slice(1);
56
+ const adjustmentIsValid =
57
+ adjustment === undefined ||
58
+ (Number.isFinite(adjustment) && Number(adjustment) > 0);
59
+ if (rest.length !== 0 || !adjustmentIsValid) {
60
+ throw Error('invalid gas input');
61
+ }
62
+ if (adjustment !== undefined) {
63
+ gasOpts.push(`--gas-adjustment=${adjustment}`);
64
+ }
65
+ }
66
+ return gasOpts;
67
+ }
68
+
69
+ throw Error('invalid gas input');
70
+ };
71
+
37
72
  /**
38
73
  * @param {ReadonlyArray<string>} swingsetArgs
39
- * @param {import('./rpc.js').MinimalNetworkConfig & {
74
+ * @param {MinimalNetworkConfig & {
40
75
  * from: string,
41
76
  * fees?: string,
77
+ * gas?: GasLimit,
42
78
  * dryRun?: boolean,
43
79
  * verbose?: boolean,
44
80
  * keyring?: {home?: string, backend: string}
@@ -50,6 +86,7 @@ export const execSwingsetTransaction = (swingsetArgs, opts) => {
50
86
  const {
51
87
  from,
52
88
  fees,
89
+ gas = ['auto', 1.2],
53
90
  dryRun = false,
54
91
  verbose = true,
55
92
  keyring = undefined,
@@ -67,6 +104,7 @@ export const execSwingsetTransaction = (swingsetArgs, opts) => {
67
104
  homeOpt,
68
105
  backendOpt,
69
106
  feeOpt,
107
+ makeGasOpts(gas),
70
108
  [`--from=${from}`, 'tx', 'swingset'],
71
109
  swingsetArgs,
72
110
  );
@@ -78,7 +116,7 @@ export const execSwingsetTransaction = (swingsetArgs, opts) => {
78
116
  stdout.write('\n');
79
117
  } else {
80
118
  const yesCmd = cmd.concat(['--yes']);
81
- if (verbose) console.log('Executing ', yesCmd);
119
+ if (verbose) console.log('Executing ', agdBinary, yesCmd);
82
120
  const out = execFileSync(agdBinary, yesCmd, { encoding: 'utf-8' });
83
121
 
84
122
  // agd puts this diagnostic on stdout rather than stderr :-/
@@ -94,7 +132,8 @@ harden(execSwingsetTransaction);
94
132
 
95
133
  /**
96
134
  *
97
- * @param {import('./rpc.js').MinimalNetworkConfig} net
135
+ * @param {MinimalNetworkConfig} net
136
+ * @returns {ParamsSDKType}
98
137
  */
99
138
  // TODO fetch by HTTP instead of shelling out https://github.com/Agoric/agoric-sdk/issues/9200
100
139
  export const fetchSwingsetParams = net => {
@@ -114,7 +153,7 @@ export const fetchSwingsetParams = net => {
114
153
  harden(fetchSwingsetParams);
115
154
 
116
155
  /**
117
- * @param {import('./rpc.js').MinimalNetworkConfig & {
156
+ * @param {MinimalNetworkConfig & {
118
157
  * execFileSync: typeof import('child_process').execFileSync,
119
158
  * delay: (ms: number) => Promise<void>,
120
159
  * period?: number,
@@ -154,7 +193,7 @@ export const pollBlocks = opts => async lookup => {
154
193
 
155
194
  /**
156
195
  * @param {string} txhash
157
- * @param {import('./rpc.js').MinimalNetworkConfig & {
196
+ * @param {MinimalNetworkConfig & {
158
197
  * execFileSync: typeof import('child_process').execFileSync,
159
198
  * delay: (ms: number) => Promise<void>,
160
199
  * period?: number,
package/src/lib/format.js CHANGED
@@ -1,23 +1,21 @@
1
- // @ts-check
2
-
3
1
  import { Fail, q } from '@endo/errors';
4
2
  import { makeBoardRemote } from '@agoric/vats/tools/board-utils.js';
5
3
 
6
- /** @import {BoardRemote} from '@agoric/vats/tools/board-utils.js' */
7
- /** @import {VBankAssetDetail} from '@agoric/vats/tools/board-utils.js'; */
4
+ /**
5
+ * @import {Amount, Brand} from '@agoric/ertp'
6
+ * @import {AgoricNamesRemotes, BoardRemote, VBankAssetDetail} from '@agoric/vats/tools/board-utils.js';
7
+ */
8
8
 
9
+ // TODO Move to packages/internal.
9
10
  /**
10
- * Like @endo/nat but coerces
11
+ * Parses the input and returns either a finite number or NaN.
11
12
  *
12
- * @param {string} str
13
- * @returns {bigint}
13
+ * @param {string} input
14
+ * @returns {number}
14
15
  */
15
- export const Natural = str => {
16
- const b = BigInt(str);
17
- if (b < 0) {
18
- throw RangeError(`${b} is negative`);
19
- }
20
- return b;
16
+ export const parseFiniteNumber = input => {
17
+ const result = /[0-9]/.test(input || '') ? Number(input) : NaN;
18
+ return Number.isFinite(result) ? result : NaN;
21
19
  };
22
20
 
23
21
  /**
@@ -102,14 +100,15 @@ export const purseBalanceTuples = (purses, assets) => {
102
100
  */
103
101
  export const fmtRecordOfLines = record => {
104
102
  const { stringify } = JSON;
103
+ /** @type {Array<[string, string[]]>} */
105
104
  const groups = Object.entries(record).map(([key, items]) => [
106
105
  key,
107
106
  items.map(item => ` ${stringify(item)}`),
108
107
  ]);
109
- const lineEntries = groups.map(
110
- // @ts-expect-error ???
111
- ([key, lines]) => ` ${stringify(key)}: [\n${lines.join(',\n')}\n ]`,
112
- );
108
+ const lineEntries = groups.map(([key, lines]) => {
109
+ const linesStr = lines.length === 0 ? `[]` : `[\n${lines.join(',\n')}\n ]`;
110
+ return ` ${stringify(key)}: ${linesStr}`;
111
+ });
113
112
  return `{\n${lineEntries.join(',\n')}\n}`;
114
113
  };
115
114
 
@@ -117,7 +116,7 @@ export const fmtRecordOfLines = record => {
117
116
  * Summarize the offerStatuses of the state as user-facing informative tuples
118
117
  *
119
118
  * @param {import('@agoric/smart-wallet/src/utils.js').CoalescedWalletState} state
120
- * @param {import('@agoric/vats/tools/board-utils.js').AgoricNamesRemotes} agoricNames
119
+ * @param {AgoricNamesRemotes} agoricNames
121
120
  */
122
121
  export const offerStatusTuples = (state, agoricNames) => {
123
122
  const { offerStatuses } = state;
@@ -174,7 +173,7 @@ export const offerStatusTuples = (state, agoricNames) => {
174
173
  /**
175
174
  * @param {import('@agoric/smart-wallet/src/smartWallet.js').CurrentWalletRecord} current
176
175
  * @param {ReturnType<import('@agoric/smart-wallet/src/utils.js').makeWalletStateCoalescer>['state']} coalesced
177
- * @param {import('@agoric/vats/tools/board-utils.js').AgoricNamesRemotes} agoricNames
176
+ * @param {AgoricNamesRemotes} agoricNames
178
177
  */
179
178
  export const summarize = (current, coalesced, agoricNames) => {
180
179
  return {
@@ -0,0 +1,7 @@
1
+ /** @file Utility library for use in other packages */
2
+
3
+ export * from './bundles.js';
4
+ export * from './casting.js';
5
+ export * from './chain.js';
6
+ export * from './format.js';
7
+ export * from './wallet.js';
@@ -0,0 +1,22 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * @import { execFileSync } from 'child_process';
5
+ */
6
+
7
+ /**
8
+ * Omits the root
9
+ *
10
+ * @param {{ execFileSync: execFileSync }} io
11
+ * @returns {Array<{ location: string, name: string }>}
12
+ */
13
+ export const listWorkspaces = ({ execFileSync }) => {
14
+ const out = execFileSync('npm', ['query', '.workspace'], {
15
+ stdio: ['ignore', 'pipe', 'inherit'],
16
+ shell: true,
17
+ encoding: 'utf-8',
18
+ });
19
+ /** @type {Array<{ location: string, name: string, description: string }>} */
20
+ const result = JSON.parse(out);
21
+ return result.filter(({ location }) => location !== '.');
22
+ };
package/src/lib/wallet.js CHANGED
@@ -1,14 +1,17 @@
1
1
  // @ts-check
2
2
  /* eslint-env node */
3
3
 
4
- import { Fail } from '@endo/errors';
5
4
  import { iterateReverse } from '@agoric/casting';
5
+ import { boardSlottingMarshaller } from '@agoric/client-utils';
6
6
  import { makeWalletStateCoalescer } from '@agoric/smart-wallet/src/utils.js';
7
- import { execSwingsetTransaction, pollBlocks, pollTx } from './chain.js';
8
- import { boardSlottingMarshaller, makeRpcUtils } from './rpc.js';
7
+ import { Fail } from '@endo/errors';
8
+ import { execSwingsetTransaction, pollTx } from './chain.js';
9
9
 
10
- /** @import {CurrentWalletRecord} from '@agoric/smart-wallet/src/smartWallet.js' */
11
- /** @import {AgoricNamesRemotes} from '@agoric/vats/tools/board-utils.js' */
10
+ /**
11
+ * @import {CurrentWalletRecord} from '@agoric/smart-wallet/src/smartWallet.js';
12
+ * @import {AgoricNamesRemotes} from '@agoric/vats/tools/board-utils.js';
13
+ * @import {MinimalNetworkConfig, VstorageKit} from '@agoric/client-utils';
14
+ */
12
15
 
13
16
  const marshaller = boardSlottingMarshaller();
14
17
 
@@ -22,15 +25,15 @@ const emptyCurrentRecord = {
22
25
 
23
26
  /**
24
27
  * @param {string} addr
25
- * @param {Pick<import('./rpc.js').RpcUtils, 'readLatestHead'>} io
28
+ * @param {Pick<VstorageKit, 'readPublished'>} io
26
29
  * @returns {Promise<import('@agoric/smart-wallet/src/smartWallet.js').CurrentWalletRecord>}
27
30
  */
28
- export const getCurrent = async (addr, { readLatestHead }) => {
31
+ export const getCurrent = async (addr, { readPublished }) => {
29
32
  // Partial because older writes may not have had all properties
30
33
  // NB: assumes changes are only additions
31
34
  let current =
32
35
  /** @type {Partial<import('@agoric/smart-wallet/src/smartWallet.js').CurrentWalletRecord> | undefined} */ (
33
- await readLatestHead(`published.wallet.${addr}.current`)
36
+ await readPublished(`wallet.${addr}.current`)
34
37
  );
35
38
  if (current === undefined) {
36
39
  throw Error(`undefined current node for ${addr}`);
@@ -57,12 +60,11 @@ export const getCurrent = async (addr, { readLatestHead }) => {
57
60
 
58
61
  /**
59
62
  * @param {string} addr
60
- * @param {Pick<import('./rpc.js').RpcUtils, 'readLatestHead'>} io
63
+ * @param {Pick<VstorageKit, 'readPublished'>} io
61
64
  * @returns {Promise<import('@agoric/smart-wallet/src/smartWallet.js').UpdateRecord>}
62
65
  */
63
- export const getLastUpdate = (addr, { readLatestHead }) => {
64
- // @ts-expect-error cast
65
- return readLatestHead(`published.wallet.${addr}`);
66
+ export const getLastUpdate = (addr, { readPublished }) => {
67
+ return readPublished(`wallet.${addr}`);
66
68
  };
67
69
 
68
70
  /**
@@ -142,12 +144,12 @@ export const coalesceWalletState = async (follower, invitationBrand) => {
142
144
  *
143
145
  * @throws { Error & { code: number } } if transaction fails
144
146
  * @param {import('@agoric/smart-wallet/src/smartWallet.js').BridgeAction} bridgeAction
145
- * @param {import('./rpc.js').MinimalNetworkConfig & {
147
+ * @param {MinimalNetworkConfig & {
146
148
  * from: string,
147
149
  * fees?: string,
148
150
  * verbose?: boolean,
149
151
  * keyring?: {home?: string, backend: string},
150
- * stdout: Pick<import('stream').Writable, 'write'>,
152
+ * stdout?: Pick<import('stream').Writable, 'write'>,
151
153
  * execFileSync: typeof import('child_process').execFileSync,
152
154
  * delay: (ms: number) => Promise<void>,
153
155
  * dryRun?: boolean,
@@ -211,76 +213,3 @@ export const findContinuingIds = (current, agoricNames) => {
211
213
  }
212
214
  return found;
213
215
  };
214
-
215
- export const makeWalletUtils = async (
216
- { fetch, execFileSync, delay },
217
- networkConfig,
218
- ) => {
219
- const { agoricNames, fromBoard, readLatestHead, vstorage } =
220
- await makeRpcUtils({ fetch }, networkConfig);
221
- /**
222
- * @param {string} from
223
- * @param {number|string} [minHeight]
224
- */
225
- const storedWalletState = async (from, minHeight = undefined) => {
226
- const m = boardSlottingMarshaller(fromBoard.convertSlotToVal);
227
-
228
- const history = await vstorage.readFully(
229
- `published.wallet.${from}`,
230
- minHeight,
231
- );
232
-
233
- /** @type {{ Invitation: Brand<'set'> }} */
234
- // @ts-expect-error XXX how to narrow AssetKind to set?
235
- const { Invitation } = agoricNames.brand;
236
- const coalescer = makeWalletStateCoalescer(Invitation);
237
- // update with oldest first
238
- for (const txt of history.reverse()) {
239
- const { body, slots } = JSON.parse(txt);
240
- const record = m.fromCapData({ body, slots });
241
- coalescer.update(record);
242
- }
243
- const coalesced = coalescer.state;
244
- harden(coalesced);
245
- return coalesced;
246
- };
247
-
248
- /**
249
- * Get OfferStatus by id, polling until available.
250
- *
251
- * @param {string} from
252
- * @param {string|number} id
253
- * @param {number|string} minHeight
254
- * @param {boolean} [untilNumWantsSatisfied]
255
- */
256
- const pollOffer = async (
257
- from,
258
- id,
259
- minHeight,
260
- untilNumWantsSatisfied = false,
261
- ) => {
262
- const lookup = async () => {
263
- const { offerStatuses } = await storedWalletState(from, minHeight);
264
- const offerStatus = [...offerStatuses.values()].find(s => s.id === id);
265
- if (!offerStatus) throw Error('retry');
266
- harden(offerStatus);
267
- if (untilNumWantsSatisfied && !('numWantsSatisfied' in offerStatus)) {
268
- throw Error('retry (no numWantsSatisfied yet)');
269
- }
270
- return offerStatus;
271
- };
272
- const retryMessage = 'offer not in wallet at block';
273
- const opts = { ...networkConfig, execFileSync, delay, retryMessage };
274
- return pollBlocks(opts)(lookup);
275
- };
276
-
277
- return {
278
- networkConfig,
279
- agoricNames,
280
- fromBoard,
281
- vstorage,
282
- readLatestHead,
283
- storedWalletState,
284
- pollOffer,
285
- };
286
- };
package/src/main.js CHANGED
@@ -66,7 +66,9 @@ const main = async (progname, rawArgs, powers) => {
66
66
  'verbosity that can be increased',
67
67
  (_value, _previous) => (cmdOpts.verbose += 1),
68
68
  );
69
- const baseCmd = (...args) => addCmdOpts(program.command(...args));
69
+ /** @type {typeof program.command} */
70
+ const baseCmd = (nameAndParams, ...rest) =>
71
+ addCmdOpts(program.command(nameAndParams, ...rest));
70
72
 
71
73
  addCmdOpts(
72
74
  program
package/src/scripts.js CHANGED
@@ -56,7 +56,7 @@ export const makeLookup =
56
56
 
57
57
  /**
58
58
  * @param {string[]} scripts
59
- * @param {{ allowUnsafePlugins: boolean, progname: string, rawArgs: string[], endowments?: Record<string, any> }} opts
59
+ * @param {{ allowUnsafePlugins?: boolean, progname: string, rawArgs: string[], endowments?: Record<string, any> }} opts
60
60
  * @param {{ fs: import('fs/promises'), console: Console }} powers
61
61
  */
62
62
  export const makeScriptLoader =
@@ -9,6 +9,7 @@ export default [
9
9
  "@agoric/builders",
10
10
  "@agoric/cache",
11
11
  "@agoric/casting",
12
+ "@agoric/client-utils",
12
13
  "@agoric/cosmic-proto",
13
14
  "@agoric/cosmic-swingset",
14
15
  "@agoric/cosmos",
@@ -17,6 +18,7 @@ export default [
17
18
  "@agoric/deployment",
18
19
  "@agoric/ertp",
19
20
  "@agoric/eslint-config",
21
+ "@agoric/fast-usdc",
20
22
  "@agoric/governance",
21
23
  "@agoric/import-manager",
22
24
  "@agoric/inter-protocol",
@@ -49,6 +51,5 @@ export default [
49
51
  "@agoric/xsnap-lockdown",
50
52
  "@agoric/zoe",
51
53
  "@agoric/zone",
52
- "agoric",
53
- "fast-usdc"
54
+ "agoric"
54
55
  ];
package/src/start.js CHANGED
@@ -276,13 +276,13 @@ export default async function startMain(progname, rawArgs, powers, opts) {
276
276
  await rmVerbose(serverDir);
277
277
  }
278
278
 
279
+ /** @type {(args: string[], spawnOpts?: Parameters<typeof pspawn>[2], dockerArgs?: string[]) => ReturnType<pspawn>} */
279
280
  let chainSpawn;
280
281
  if (!popts.dockerTag) {
281
- chainSpawn = (args, spawnOpts = undefined) => {
282
- return pspawn(cosmosChain, [...args, `--home=${serverDir}`], spawnOpts);
283
- };
282
+ chainSpawn = (args, spawnOpts) =>
283
+ pspawn(cosmosChain, [...args, `--home=${serverDir}`], spawnOpts);
284
284
  } else {
285
- chainSpawn = (args, spawnOpts = undefined, dockerArgs = []) =>
285
+ chainSpawn = (args, spawnOpts, dockerArgs = []) =>
286
286
  pspawn(
287
287
  'docker',
288
288
  [
@@ -482,12 +482,12 @@ export default async function startMain(progname, rawArgs, powers, opts) {
482
482
  await rmVerbose(serverDir);
483
483
  }
484
484
 
485
+ /** @type {(args: string[], spawnOpts?: Parameters<typeof pspawn>[2], dockerArgs?: string[]) => ReturnType<pspawn>} */
485
486
  let soloSpawn;
486
487
  if (!popts.dockerTag) {
487
- soloSpawn = (args, spawnOpts = undefined) =>
488
- pspawn(agSolo, args, spawnOpts);
488
+ soloSpawn = (args, spawnOpts) => pspawn(agSolo, args, spawnOpts);
489
489
  } else {
490
- soloSpawn = (args, spawnOpts = undefined, dockerArgs = []) =>
490
+ soloSpawn = (args, spawnOpts, dockerArgs = []) =>
491
491
  pspawn(
492
492
  'docker',
493
493
  [
@@ -62,6 +62,13 @@ const getLatestBlockHeight = url =>
62
62
  req.end();
63
63
  });
64
64
 
65
+ /**
66
+ * Test the "getting started" workflow. Note that this function may be imported
67
+ * by external repositories.
68
+ *
69
+ * @param {import('ava').ExecutionContext} t
70
+ * @param {{ init?: string[], install?: string[] }} [options]
71
+ */
65
72
  export const gettingStartedWorkflowTest = async (t, options = {}) => {
66
73
  const { init: initOptions = [], install: installOptions = [] } = options;
67
74
  const pspawn = makePspawn({ spawn });