@deeeed/metamask-harness 0.18.0 → 0.19.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/CHANGELOG.md +187 -0
- package/README.md +4 -7
- package/adapters/core/inject.sh +1 -6
- package/adapters/extension/inject.mjs +1 -4
- package/adapters/extension/verify.sh +1 -1
- package/adapters/mobile/inject.sh +5 -4
- package/adapters/mobile/verify.sh +15 -19
- package/adapters/shared/harness-source-fingerprint.mjs +14 -12
- package/dist/adapters/harness-freshness.js +49 -0
- package/dist/cli.js +0 -4
- package/dist/command-contract.js +51 -40
- package/dist/command-journal.js +50 -12
- package/dist/commands/call.js +95 -20
- package/dist/commands/check.js +1 -1
- package/dist/commands/completion-candidates.js +7 -19
- package/dist/commands/fixtures.js +50 -1
- package/dist/commands/last.js +9 -1
- package/dist/commands/launch/extension.js +2 -2
- package/dist/commands/launch/mobile.js +2 -0
- package/dist/commands/list-executables.js +56 -20
- package/dist/commands/manifest.js +28 -12
- package/dist/commands/parse-args.js +55 -3
- package/dist/commands/provision.js +0 -1
- package/dist/commands/run-engine.js +514 -336
- package/dist/commands/run.js +25 -37
- package/dist/commands/shared.js +1 -1
- package/dist/heal-bounds.js +5 -0
- package/dist/mm-harness-cli.js +22 -30
- package/dist/run-diagnostics.js +1 -1
- package/dist/run-recording.js +1 -1
- package/dist/runner.js +24 -2
- package/docs/CONTRIBUTING.md +2 -3
- package/docs/QA.md +0 -1
- package/docs/RECIPES.md +52 -91
- package/library/README.md +5 -5
- package/library/actions/core/perps/_controller.mjs +33 -1
- package/library/actions/core/perps/assert_orders.mjs +6 -7
- package/library/actions/core/perps/assert_positions.mjs +6 -7
- package/library/actions/core/perps/close_orders.mjs +2 -0
- package/library/actions/core/perps/close_positions.mjs +2 -0
- package/library/actions/core/perps/ensure_orders.mjs +4 -2
- package/library/actions/core/perps/ensure_positions.mjs +4 -2
- package/library/actions/core/perps/place_order.mjs +7 -3
- package/library/actions/extension/perps/assert_orders.mjs +2 -1
- package/library/actions/extension/perps/assert_positions.mjs +2 -1
- package/library/actions/extension/perps/perps.mjs +43 -14
- package/library/actions/mobile/perps/assert_orders.mjs +2 -1
- package/library/actions/mobile/perps/assert_positions.mjs +2 -1
- package/library/actions/mobile/perps/perps.mjs +40 -12
- package/library/library.json +1 -1
- package/library/manifests/core.action-manifest.json +1170 -413
- package/library/manifests/extension.action-manifest.json +1495 -641
- package/library/manifests/mobile.action-manifest.json +1704 -769
- package/library/recipes/app/lifecycle.android-smoke.mobile.recipe.json +63 -81
- package/library/recipes/perps/clean-market-testnet.core.recipe.json +44 -0
- package/library/recipes/perps/clean-market-testnet.recipe.json +49 -0
- package/library/recipes/perps/lifecycle.recipe.json +136 -180
- package/library/recipes/perps/order-lifecycle.core.recipe.json +71 -67
- package/library/recipes/perps/performance.background-resume.mobile.recipe.json +51 -67
- package/library/recipes/perps/performance.cold-start.mobile.recipe.json +51 -67
- package/library/recipes/perps/performance.mobile.recipe.json +37 -51
- package/library/recipes/perps/performance.warm-start.mobile.recipe.json +44 -59
- package/library/recipes/perps/read-markets.core.recipe.json +29 -31
- package/library/recipes/perps/smoke.core.recipe.json +29 -32
- package/library/recipes/perps/smoke.extension.recipe.json +41 -44
- package/library/recipes/perps/smoke.mobile.recipe.json +42 -44
- package/library/recipes/perps/trading-lifecycle.core.recipe.json +69 -65
- package/library/recipes/runner/action-validation.extension.recipe.json +312 -405
- package/library/recipes/runner/action-validation.mobile.recipe.json +316 -409
- package/library/recipes/runner/smoke.core.recipe.json +18 -20
- package/library/recipes/runner/smoke.extension.recipe.json +23 -24
- package/library/recipes/runner/smoke.mobile.recipe.json +23 -24
- package/library/recipes/wallet/smoke.extension.recipe.json +33 -35
- package/library/recipes/wallet/smoke.mobile.recipe.json +33 -35
- package/package.json +3 -3
- package/scripts/completions.sh +1 -4
- package/dist/adapters/extension/harness-freshness.js +0 -39
- package/dist/commands/flows.js +0 -91
- package/library/flows/perps.flows.json +0 -64
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { resolveActionManifest } from "../manifest.js";
|
|
3
4
|
import { importRecipeProtocol } from "../paths.js";
|
|
4
5
|
import { color } from "../cli-color.js";
|
|
5
6
|
import { EXIT } from "./shared.js";
|
|
6
|
-
import { optionFlag, optionString, resolveAdapter, shellQuoteArg } from "./parse-args.js";
|
|
7
|
+
import { optionFlag, optionString, optionStrings, resolveAdapter, shellQuoteArg } from "./parse-args.js";
|
|
7
8
|
import { describeManifestActions, renderHumanActionCatalog } from "./manifest.js";
|
|
8
9
|
import {
|
|
9
10
|
describeRunnableRecipe,
|
|
@@ -19,9 +20,9 @@ function catalogDescription(description) {
|
|
|
19
20
|
async function handleListExecutables(command, options) {
|
|
20
21
|
const json = optionFlag(options, "json");
|
|
21
22
|
const { adapter } = resolveAdapter(options);
|
|
22
|
-
const librarySources = await resolveMetaMaskLibrarySources(
|
|
23
|
+
const librarySources = await resolveMetaMaskLibrarySources(optionStrings(options, "library"));
|
|
23
24
|
if (command === "run") {
|
|
24
|
-
const recipes = listRunnableRecipes(adapter, librarySources);
|
|
25
|
+
const recipes = await listRunnableRecipes(adapter, librarySources);
|
|
25
26
|
if (json) {
|
|
26
27
|
console.log(JSON.stringify({ schemaVersion: 1, command, action: "list", adapter, recipes }, null, 2));
|
|
27
28
|
return EXIT.ok;
|
|
@@ -31,9 +32,11 @@ async function handleListExecutables(command, options) {
|
|
|
31
32
|
console.log(out("comment", "Inspect: mm-harness run <recipe> --describe"));
|
|
32
33
|
for (const recipe of recipes) {
|
|
33
34
|
const shadowed = recipe.shadows.length > 0 ? ` shadows=${recipe.shadows.join(",")}` : "";
|
|
35
|
+
const variant = recipe.variant ? ` variant=${recipe.variant}` : " variant=all";
|
|
36
|
+
const parameters = recipe.parameters.length > 0 ? ` params=${recipe.parameters.map((parameter) => parameter.name).join(",")}` : "";
|
|
34
37
|
const summary = catalogDescription(recipe.description);
|
|
35
38
|
const description = summary ? ` \u2014 ${summary}` : "";
|
|
36
|
-
console.log(` ${out("cmd", recipe.name)} ${out("dim", `[${recipe.source}]${shadowed}`)}${description}`);
|
|
39
|
+
console.log(` ${out("cmd", recipe.name)} ${out("dim", `[${recipe.source}]${variant}${shadowed}${parameters}`)}${description}`);
|
|
37
40
|
}
|
|
38
41
|
return EXIT.ok;
|
|
39
42
|
}
|
|
@@ -83,14 +86,18 @@ async function handleListExecutables(command, options) {
|
|
|
83
86
|
async function handleDescribeRecipe(recipeArg, options) {
|
|
84
87
|
const json = optionFlag(options, "json");
|
|
85
88
|
const { adapter } = resolveAdapter(options);
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
|
|
89
|
+
const libraryEntries = optionStrings(options, "library");
|
|
90
|
+
const directRecipePath = path.resolve(recipeArg);
|
|
91
|
+
const librarySources = await resolveMetaMaskLibrarySources(
|
|
92
|
+
libraryEntries,
|
|
93
|
+
fs.existsSync(directRecipePath) ? directRecipePath : void 0
|
|
94
|
+
);
|
|
95
|
+
const result = await describeRunnableRecipe(recipeArg, adapter, librarySources);
|
|
96
|
+
const targetEntry = optionString(options, "target");
|
|
90
97
|
const replayTarget = targetEntry ? path.resolve(targetEntry) : void 0;
|
|
91
98
|
const contextFlags = [
|
|
92
99
|
`--adapter ${adapter}`,
|
|
93
|
-
...
|
|
100
|
+
...libraryEntries?.map((entry) => `--library ${shellQuoteArg(entry)}`) ?? [],
|
|
94
101
|
...replayTarget ? [`--target ${shellQuoteArg(replayTarget)}`] : []
|
|
95
102
|
].join(" ");
|
|
96
103
|
const discoveryCommand = `mm-harness run --list ${contextFlags}`;
|
|
@@ -114,7 +121,14 @@ async function handleDescribeRecipe(recipeArg, options) {
|
|
|
114
121
|
return EXIT.usage;
|
|
115
122
|
}
|
|
116
123
|
const recipe = result.recipe;
|
|
117
|
-
const
|
|
124
|
+
const parameterArgs = recipe.parameters.filter((parameter) => parameter.required && !Object.hasOwn(parameter, "default")).map((parameter) => `${parameter.name}=${shellQuoteArg(exampleParameterValue(parameter))}`);
|
|
125
|
+
const runCommand = [
|
|
126
|
+
"mm-harness run",
|
|
127
|
+
shellQuoteArg(recipeArg),
|
|
128
|
+
...parameterArgs,
|
|
129
|
+
contextFlags
|
|
130
|
+
].filter(Boolean).join(" ");
|
|
131
|
+
const nextCommand = `${runCommand} --plan`;
|
|
118
132
|
if (json) {
|
|
119
133
|
console.log(JSON.stringify({
|
|
120
134
|
schemaVersion: 1,
|
|
@@ -122,6 +136,7 @@ async function handleDescribeRecipe(recipeArg, options) {
|
|
|
122
136
|
action: "describe",
|
|
123
137
|
status: "pass",
|
|
124
138
|
recipe,
|
|
139
|
+
runCommand,
|
|
125
140
|
nextCommand
|
|
126
141
|
}, null, 2));
|
|
127
142
|
return EXIT.ok;
|
|
@@ -133,24 +148,45 @@ async function handleDescribeRecipe(recipeArg, options) {
|
|
|
133
148
|
console.log(` ${out("label", "adapter:")} ${recipe.adapter}`);
|
|
134
149
|
console.log(` ${out("label", "source:")} ${recipe.source} \xB7 ${recipe.file}`);
|
|
135
150
|
console.log(` ${out("label", "path:")} ${out("path", recipe.path)}`);
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
151
|
+
if (recipe.$schema) console.log(` ${out("label", "schema:")} ${recipe.$schema}`);
|
|
152
|
+
console.log(` ${out("label", "variant:")} ${recipe.variant ?? "all adapters"}`);
|
|
153
|
+
if (recipe.shadows.length > 0) {
|
|
154
|
+
console.log(` ${out("label", "shadows:")} ${recipe.shadows.join(", ")}`);
|
|
155
|
+
}
|
|
156
|
+
if (recipe.parameters.length === 0) {
|
|
157
|
+
console.log(` ${out("label", "parameters:")} none`);
|
|
139
158
|
} else {
|
|
140
|
-
console.log(` ${out("label",
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
159
|
+
console.log(` ${out("label", `parameters (${recipe.parameters.length})`)}`);
|
|
160
|
+
for (const parameter of recipe.parameters) {
|
|
161
|
+
const required = parameter.required ? " required" : "";
|
|
162
|
+
const defaultValue = Object.hasOwn(parameter, "default") ? ` default=${compactValue(parameter.default)}` : "";
|
|
163
|
+
const choices = parameter.enum ? ` choices=${compactValue(parameter.enum)}` : "";
|
|
164
|
+
const description = parameter.description ? ` \u2014 ${parameter.description}` : "";
|
|
165
|
+
console.log(
|
|
166
|
+
` ${out("cmd", parameter.name)}${parameter.type ? ` (${parameter.type})` : ""}${required}${defaultValue}${choices}${description}`
|
|
167
|
+
);
|
|
144
168
|
}
|
|
145
169
|
}
|
|
146
170
|
renderComposition("actions", recipe.actions, out);
|
|
147
|
-
|
|
148
|
-
|
|
171
|
+
renderComposition("called recipes", recipe.nestedRecipes, out);
|
|
172
|
+
if (recipe.unresolvedRecipes.length > 0) {
|
|
173
|
+
renderComposition("unresolved recipes", recipe.unresolvedRecipes, out);
|
|
149
174
|
}
|
|
150
|
-
|
|
175
|
+
console.log(` ${out("comment", `Run: ${runCommand}`)}`);
|
|
151
176
|
console.log(` ${out("comment", `Next: ${nextCommand}`)}`);
|
|
152
177
|
return EXIT.ok;
|
|
153
178
|
}
|
|
179
|
+
function exampleParameterValue(parameter) {
|
|
180
|
+
if (parameter.enum && parameter.enum.length > 0) return stringifyAssignmentValue(parameter.enum[0]);
|
|
181
|
+
if (parameter.type === "boolean") return "false";
|
|
182
|
+
if (parameter.type === "number" || parameter.type === "integer") return "0";
|
|
183
|
+
if (parameter.type === "array") return "[]";
|
|
184
|
+
if (parameter.type === "object") return "{}";
|
|
185
|
+
return "value";
|
|
186
|
+
}
|
|
187
|
+
function stringifyAssignmentValue(value) {
|
|
188
|
+
return typeof value === "string" ? value : JSON.stringify(value) ?? String(value);
|
|
189
|
+
}
|
|
154
190
|
function compactValue(value) {
|
|
155
191
|
const rendered = JSON.stringify(value);
|
|
156
192
|
if (rendered === void 0) return String(value);
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
isRecord,
|
|
6
6
|
optionFlag,
|
|
7
7
|
optionString,
|
|
8
|
+
optionStrings,
|
|
8
9
|
resolveAdapter,
|
|
9
10
|
shellQuoteArg
|
|
10
11
|
} from "./parse-args.js";
|
|
@@ -15,7 +16,7 @@ import {
|
|
|
15
16
|
import { metaMaskActionExecutionCapabilities } from "../recipe-security.js";
|
|
16
17
|
async function handleActions({ options, positional }) {
|
|
17
18
|
const { adapter, target } = resolveAdapter(options);
|
|
18
|
-
const librarySources = await resolveMetaMaskLibrarySources(
|
|
19
|
+
const librarySources = await resolveMetaMaskLibrarySources(optionStrings(options, "library"));
|
|
19
20
|
const { manifest, actionSources } = await resolveActionManifest(
|
|
20
21
|
adapter,
|
|
21
22
|
optionString(options, "actionManifest"),
|
|
@@ -92,13 +93,15 @@ async function handleActions({ options, positional }) {
|
|
|
92
93
|
}
|
|
93
94
|
const relatedActions = action && actions.length === 1 ? findRelatedActions(all, actions[0]) : void 0;
|
|
94
95
|
if (json) {
|
|
96
|
+
const detailed = Boolean(action && actions.length === 1);
|
|
95
97
|
console.log(JSON.stringify({
|
|
96
98
|
schemaVersion: 1,
|
|
97
99
|
command: "actions",
|
|
98
100
|
adapter,
|
|
99
101
|
...query ? { query } : {},
|
|
100
102
|
category,
|
|
101
|
-
|
|
103
|
+
detail: detailed ? "full" : "summary",
|
|
104
|
+
actions: detailed ? actions : actions.map(summarizeDescribedAction),
|
|
102
105
|
...relatedActions?.length ? { relatedActions } : {}
|
|
103
106
|
}, null, 2));
|
|
104
107
|
} else {
|
|
@@ -145,6 +148,10 @@ function findRelatedActions(entries, selected, limit = 5) {
|
|
|
145
148
|
}).filter(({ score }) => score > 0).sort((left, right) => right.score - left.score || left.name.localeCompare(right.name)).slice(0, limit).map(({ name }) => name);
|
|
146
149
|
}
|
|
147
150
|
const GENERIC_OPERATION_TERMS = /* @__PURE__ */ new Set(["assert", "call", "close", "ensure", "place", "read", "start", "teardown"]);
|
|
151
|
+
function summarizeDescribedAction(entry) {
|
|
152
|
+
const { schema: _schema, examples: _examples, ...summary } = entry;
|
|
153
|
+
return summary;
|
|
154
|
+
}
|
|
148
155
|
function renderHumanActionCatalog(actions, options) {
|
|
149
156
|
const out = (style, text) => color(style, text, { stream: process.stdout });
|
|
150
157
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -170,11 +177,25 @@ function renderHumanActionExample(entry, adapter, target, executable) {
|
|
|
170
177
|
const node = authoredExampleNode(entry.examples);
|
|
171
178
|
if (!node) return void 0;
|
|
172
179
|
const out = (style, text) => color(style, text, { stream: process.stdout });
|
|
180
|
+
const command = actionExampleCommand(entry, adapter, target, executable);
|
|
181
|
+
if (!command) return void 0;
|
|
182
|
+
const lines = [
|
|
183
|
+
out("label", "Example call:"),
|
|
184
|
+
` ${out("cmd", command)}`,
|
|
185
|
+
out("label", "Recipe node:"),
|
|
186
|
+
...JSON.stringify(node, null, 2).split("\n").map((line) => ` ${line}`)
|
|
187
|
+
];
|
|
188
|
+
return lines.join("\n");
|
|
189
|
+
}
|
|
190
|
+
function actionExampleCommand(entry, adapter, target, executable) {
|
|
191
|
+
const node = authoredExampleNode(entry.examples);
|
|
192
|
+
if (!node) return void 0;
|
|
193
|
+
const commandName = executable.endsWith("/mm-harness") ? "mm-harness" : executable;
|
|
173
194
|
const args = entry.fields.flatMap(
|
|
174
195
|
(field) => Object.hasOwn(node, field) ? [shellQuoteArg(`${field}=${actionCallValue(node[field])}`)] : []
|
|
175
196
|
);
|
|
176
|
-
|
|
177
|
-
shellQuoteArg(
|
|
197
|
+
return [
|
|
198
|
+
shellQuoteArg(commandName),
|
|
178
199
|
"call",
|
|
179
200
|
shellQuoteArg(entry.name),
|
|
180
201
|
...args,
|
|
@@ -183,13 +204,6 @@ function renderHumanActionExample(entry, adapter, target, executable) {
|
|
|
183
204
|
"--target",
|
|
184
205
|
shellQuoteArg(target)
|
|
185
206
|
].join(" ");
|
|
186
|
-
const lines = [
|
|
187
|
-
out("label", "Example call:"),
|
|
188
|
-
` ${out("cmd", command)}`,
|
|
189
|
-
out("label", "Recipe node:"),
|
|
190
|
-
...JSON.stringify(node, null, 2).split("\n").map((line) => ` ${line}`)
|
|
191
|
-
];
|
|
192
|
-
return lines.join("\n");
|
|
193
207
|
}
|
|
194
208
|
function authoredExampleNode(examples) {
|
|
195
209
|
if (!Array.isArray(examples)) return void 0;
|
|
@@ -318,6 +332,7 @@ function levenshtein(left, right) {
|
|
|
318
332
|
return prior[right.length] ?? right.length;
|
|
319
333
|
}
|
|
320
334
|
export {
|
|
335
|
+
actionExampleCommand,
|
|
321
336
|
describeManifestActions,
|
|
322
337
|
findRelatedActions,
|
|
323
338
|
fuzzyResolveActions,
|
|
@@ -325,5 +340,6 @@ export {
|
|
|
325
340
|
renderHumanActionCatalog,
|
|
326
341
|
renderHumanActionExample,
|
|
327
342
|
searchActions,
|
|
328
|
-
summarizeActionCategories
|
|
343
|
+
summarizeActionCategories,
|
|
344
|
+
summarizeDescribedAction
|
|
329
345
|
};
|
|
@@ -38,6 +38,7 @@ function parseArgs(argv, command) {
|
|
|
38
38
|
"allDevices",
|
|
39
39
|
"help"
|
|
40
40
|
]);
|
|
41
|
+
const repeatableOptions = /* @__PURE__ */ new Set(["library"]);
|
|
41
42
|
for (let i = 0; i < argv.length; i += 1) {
|
|
42
43
|
const arg = argv[i];
|
|
43
44
|
if (!arg.startsWith("--")) {
|
|
@@ -70,15 +71,56 @@ function parseArgs(argv, command) {
|
|
|
70
71
|
continue;
|
|
71
72
|
}
|
|
72
73
|
if (inlineValue !== void 0) {
|
|
73
|
-
options
|
|
74
|
+
assignOption(options, key, inlineValue, repeatableOptions);
|
|
74
75
|
continue;
|
|
75
76
|
}
|
|
76
77
|
if (i + 1 >= argv.length) throw usageError(`Missing value for ${arg}`);
|
|
77
|
-
options
|
|
78
|
+
assignOption(options, key, argv[i + 1], repeatableOptions);
|
|
78
79
|
i += 1;
|
|
79
80
|
}
|
|
80
81
|
return { positional, options, rawArgv: [...argv] };
|
|
81
82
|
}
|
|
83
|
+
function assignOption(options, key, value, repeatableOptions) {
|
|
84
|
+
if (!repeatableOptions.has(key)) {
|
|
85
|
+
options[key] = value;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const existing = options[key];
|
|
89
|
+
if (existing === void 0) {
|
|
90
|
+
options[key] = [value];
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (!Array.isArray(existing)) throw usageError(`--${key} has conflicting values.`);
|
|
94
|
+
options[key] = [...existing, value];
|
|
95
|
+
}
|
|
96
|
+
function parseRecipeParamAssignments(values) {
|
|
97
|
+
const params = {};
|
|
98
|
+
for (const assignment of values) {
|
|
99
|
+
const separator = assignment.indexOf("=");
|
|
100
|
+
if (separator <= 0) {
|
|
101
|
+
throw usageError(`recipe parameter ${JSON.stringify(assignment)} must use key=value.`);
|
|
102
|
+
}
|
|
103
|
+
const key = assignment.slice(0, separator).trim();
|
|
104
|
+
if (!key) throw usageError(`recipe parameter ${JSON.stringify(assignment)} has an empty key.`);
|
|
105
|
+
if (Object.hasOwn(params, key)) {
|
|
106
|
+
throw usageError(`recipe parameter ${JSON.stringify(key)} was provided more than once.`);
|
|
107
|
+
}
|
|
108
|
+
const raw = assignment.slice(separator + 1);
|
|
109
|
+
let value;
|
|
110
|
+
try {
|
|
111
|
+
value = JSON.parse(raw);
|
|
112
|
+
} catch {
|
|
113
|
+
value = raw;
|
|
114
|
+
}
|
|
115
|
+
Object.defineProperty(params, key, {
|
|
116
|
+
value,
|
|
117
|
+
enumerable: true,
|
|
118
|
+
configurable: true,
|
|
119
|
+
writable: true
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return params;
|
|
123
|
+
}
|
|
82
124
|
function parseRecordVideoMode(value) {
|
|
83
125
|
if (value === void 0 || value === "" || value === "true") return "full-run";
|
|
84
126
|
if (value === "off" || value === "false") return false;
|
|
@@ -101,6 +143,14 @@ function optionString(options, key) {
|
|
|
101
143
|
if (typeof value !== "string") throw usageError(`--${key} requires a value.`);
|
|
102
144
|
return value;
|
|
103
145
|
}
|
|
146
|
+
function optionStrings(options, key) {
|
|
147
|
+
const value = options[key];
|
|
148
|
+
if (value === void 0) return void 0;
|
|
149
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
150
|
+
throw usageError(`--${key} requires a value.`);
|
|
151
|
+
}
|
|
152
|
+
return value;
|
|
153
|
+
}
|
|
104
154
|
function optionFlag(options, key) {
|
|
105
155
|
const value = options[key];
|
|
106
156
|
return value === true;
|
|
@@ -131,7 +181,7 @@ function adapterOption(options) {
|
|
|
131
181
|
return adapter;
|
|
132
182
|
}
|
|
133
183
|
function targetPath(options) {
|
|
134
|
-
return path.resolve(optionString(options, "target") ??
|
|
184
|
+
return path.resolve(optionString(options, "target") ?? process.cwd());
|
|
135
185
|
}
|
|
136
186
|
function actionManifestPathOption(options, adapter) {
|
|
137
187
|
const configured = optionString(options, "actionManifest");
|
|
@@ -196,8 +246,10 @@ export {
|
|
|
196
246
|
isRecord,
|
|
197
247
|
optionFlag,
|
|
198
248
|
optionString,
|
|
249
|
+
optionStrings,
|
|
199
250
|
parseArgs,
|
|
200
251
|
parsePort,
|
|
252
|
+
parseRecipeParamAssignments,
|
|
201
253
|
requiredOption,
|
|
202
254
|
resolveAdapter,
|
|
203
255
|
runtimeOptionsFromCli,
|