@swmansion/argent 0.18.1-next.14 → 0.18.1-next.16

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/dist/cli-cmds.mjs CHANGED
@@ -1304,15 +1304,20 @@ function setAtPath(obj, dottedKey, value) {
1304
1304
  }
1305
1305
  function deleteAtPath(obj, dottedKey) {
1306
1306
  const parts = splitKey(dottedKey);
1307
- let cur = obj;
1307
+ const chain = [obj];
1308
1308
  for (let i2 = 0; i2 < parts.length - 1; i2++) {
1309
- const next = cur[parts[i2]];
1309
+ const next = chain[i2][parts[i2]];
1310
1310
  if (!isPlainObject(next)) return false;
1311
- cur = next;
1311
+ chain.push(next);
1312
1312
  }
1313
+ const parent = chain[parts.length - 1];
1313
1314
  const leaf = parts[parts.length - 1];
1314
- if (!Object.hasOwn(cur, leaf)) return false;
1315
- delete cur[leaf];
1315
+ if (!Object.hasOwn(parent, leaf)) return false;
1316
+ delete parent[leaf];
1317
+ for (let i2 = chain.length - 1; i2 >= 1; i2--) {
1318
+ if (Object.keys(chain[i2]).length > 0) break;
1319
+ delete chain[i2 - 1][parts[i2 - 1]];
1320
+ }
1316
1321
  return true;
1317
1322
  }
1318
1323
  var LOCK_STALE_MS2 = 1e4;
@@ -1447,6 +1452,9 @@ function asString(raw) {
1447
1452
  const trimmed = raw.trim();
1448
1453
  return trimmed === "" ? void 0 : trimmed;
1449
1454
  }
1455
+ function asNumber(raw) {
1456
+ return typeof raw === "number" && Number.isFinite(raw) ? raw : void 0;
1457
+ }
1450
1458
  function asStringArray(raw) {
1451
1459
  if (!Array.isArray(raw)) return void 0;
1452
1460
  const out = [];
@@ -1455,6 +1463,15 @@ function asStringArray(raw) {
1455
1463
  }
1456
1464
  return out;
1457
1465
  }
1466
+ var PARSER_EXPECTATIONS = /* @__PURE__ */ new Map([
1467
+ [asBoolean, "a boolean (true or false)"],
1468
+ [asString, "a non-empty string"],
1469
+ [asNumber, "a number"],
1470
+ [asStringArray, "an array of strings"]
1471
+ ]);
1472
+ function describeExpectedValue(def) {
1473
+ return def.expected ?? PARSER_EXPECTATIONS.get(def.parse);
1474
+ }
1458
1475
  var CONFIG_SCHEMA = [
1459
1476
  {
1460
1477
  key: "telemetry.enabled",
@@ -1560,12 +1577,18 @@ var ConfigScopeError = class extends Error {
1560
1577
  allowed;
1561
1578
  };
1562
1579
  var ConfigValidationError = class extends Error {
1563
- constructor(key) {
1564
- super(`Invalid value for config key "${key}".`);
1580
+ constructor(key, expected, example) {
1581
+ super(
1582
+ expected ? `Invalid value for config key "${key}": expected ${expected}.` : `Invalid value for config key "${key}".`
1583
+ );
1565
1584
  this.key = key;
1585
+ this.expected = expected;
1586
+ this.example = example;
1566
1587
  this.name = "ConfigValidationError";
1567
1588
  }
1568
1589
  key;
1590
+ expected;
1591
+ example;
1569
1592
  };
1570
1593
  var ConfigManagedElsewhereError = class extends Error {
1571
1594
  constructor(key, command) {
@@ -1582,7 +1605,8 @@ function setConfigValue(key, rawValue, scope = "global", options = {}, registry
1582
1605
  if (def.manageCommand) throw new ConfigManagedElsewhereError(key, def.manageCommand);
1583
1606
  if (!def.scopes.includes(scope)) throw new ConfigScopeError(key, scope, def.scopes);
1584
1607
  const parsed = def.parse(rawValue);
1585
- if (parsed === void 0) throw new ConfigValidationError(key);
1608
+ if (parsed === void 0)
1609
+ throw new ConfigValidationError(def.key, describeExpectedValue(def), def.example);
1586
1610
  updateConfig((config2) => setAtPath(config2, key, parsed), scope, options);
1587
1611
  return parsed;
1588
1612
  }
@@ -1607,6 +1631,8 @@ function listConfig(options = {}, registry = CONFIG_SCHEMA) {
1607
1631
  description: def.description,
1608
1632
  scopes: def.scopes,
1609
1633
  ...def.manageCommand ? { manageCommand: def.manageCommand } : {},
1634
+ ...describeExpectedValue(def) ? { expected: describeExpectedValue(def) } : {},
1635
+ ...def.example ? { example: def.example } : {},
1610
1636
  effective: getConfigValue(def, options),
1611
1637
  project: readScopeValue(def, "project", options),
1612
1638
  global: readScopeValue(def, "global", options)
@@ -9153,8 +9179,10 @@ function renderUnderStepLine(s, n2, text2) {
9153
9179
  function renderSummary(report, opts = {}) {
9154
9180
  const warnings = report.steps.filter((s) => s.warning).length;
9155
9181
  const warningsNote = warnings ? `, ${warnings} warning${warnings === 1 ? "" : "s"}` : "";
9156
- const where = opts.withDevice ? ` on ${report.device}` : "";
9157
- return `${report.ok ? "PASS" : "FAIL"}${where} \u2014 ${report.passed} passed, ${report.failed} failed, ${report.errored} errored, ${report.skipped} skipped${warningsNote}`;
9182
+ const where = opts.withDevice && report.device ? ` on ${report.device}` : "";
9183
+ const nothingCounted = report.ok && report.passed + report.failed + report.errored + report.skipped === 0;
9184
+ const note = nothingCounted ? " (no test steps)" : "";
9185
+ return `${report.ok ? "PASS" : "FAIL"}${where} \u2014 ${report.passed} passed, ${report.failed} failed, ${report.errored} errored, ${report.skipped} skipped${warningsNote}${note}`;
9158
9186
  }
9159
9187
  function renderArtifactLines(report) {
9160
9188
  const lines = [];
@@ -9222,7 +9250,7 @@ function exitAfterFlush(code, streams = [process.stdout, process.stderr]) {
9222
9250
  }
9223
9251
  function renderReport(report) {
9224
9252
  const lines = [];
9225
- lines.push(`Flow "${report.flow}" on ${report.device}`);
9253
+ lines.push(`Flow "${report.flow}"${report.device ? ` on ${report.device}` : ""}`);
9226
9254
  if (report.executionPrerequisite) {
9227
9255
  lines.push(` assumes: ${report.executionPrerequisite}`);
9228
9256
  }
@@ -11055,6 +11083,7 @@ the raw value stored at each scope.`);
11055
11083
  }
11056
11084
  function scopeDetail(e) {
11057
11085
  const parts = [`scopes: ${e.scopes.join(", ")}`];
11086
+ if (e.expected) parts.push(`value: ${e.expected}${e.example ? `, e.g. ${e.example}` : ""}`);
11058
11087
  if (e.project !== void 0) parts.push(`project=${formatValuePlain(e.project)}`);
11059
11088
  if (e.global !== void 0) parts.push(`global=${formatValuePlain(e.global)}`);
11060
11089
  return parts.join(" \xB7 ");
@@ -11119,7 +11148,7 @@ parsed (e.g. \`true\`, \`42\`, \`["a","b"]\`); anything else is stored as a stri
11119
11148
  if (warning) console.error(import_picocolors2.default.yellow(warning));
11120
11149
  console.log(`Set ${import_picocolors2.default.bold(key)} = ${formatValuePlain(stored)} (${scopeLabel(targetScope)}).`);
11121
11150
  } catch (err) {
11122
- reportError(err);
11151
+ reportError(err, () => suggestCorrectedSet(err, key, rawValue, scope));
11123
11152
  }
11124
11153
  }
11125
11154
  function cmdUnset(argv) {
@@ -11212,7 +11241,20 @@ function formatValue(value) {
11212
11241
  }
11213
11242
  return formatValuePlain(value);
11214
11243
  }
11215
- function reportError(err) {
11244
+ function quoteForShell(value) {
11245
+ if (/^[A-Za-z0-9._/@:+-]+$/.test(value)) return value;
11246
+ return `'${value.replace(/'/g, `'\\''`)}'`;
11247
+ }
11248
+ function suggestCorrectedSet(err, key, rawValue, scope) {
11249
+ if (!(err instanceof ConfigValidationError)) return null;
11250
+ const def = getConfigDefinition(key);
11251
+ if (!def) return null;
11252
+ const wrapped = def.parse([rawValue]);
11253
+ if (wrapped === void 0) return null;
11254
+ const scopeFlag = scope ? ` --scope ${scope}` : "";
11255
+ return `argent config set ${key} ${quoteForShell(JSON.stringify([rawValue]))}${scopeFlag}`;
11256
+ }
11257
+ function reportError(err, suggest) {
11216
11258
  if (err instanceof ConfigManagedElsewhereError) {
11217
11259
  console.error(`Error: ${err.message} Use \`${err.command}\` instead.`);
11218
11260
  } else if (err instanceof UnknownConfigKeyError || err instanceof ConfigScopeError || err instanceof ConfigValidationError) {
@@ -11220,6 +11262,15 @@ function reportError(err) {
11220
11262
  if (err instanceof UnknownConfigKeyError) {
11221
11263
  console.error(`Run \`argent config list\` to see available keys.`);
11222
11264
  }
11265
+ if (err instanceof ConfigValidationError) {
11266
+ const corrected = suggest?.() ?? null;
11267
+ if (corrected) {
11268
+ console.error(`Did you mean: ${corrected}`);
11269
+ } else if (err.example) {
11270
+ console.error(`Example: argent config set ${err.key} ${quoteForShell(err.example)}`);
11271
+ }
11272
+ console.error(`Run \`argent config list\` to see each key's expected value.`);
11273
+ }
11223
11274
  } else {
11224
11275
  console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
11225
11276
  }
@@ -19293,9 +19293,11 @@ async function flowRunToMcpContent(result, ctx) {
19293
19293
  }
19294
19294
  }
19295
19295
  if (result.ok !== void 0) {
19296
+ const counted = (result.passed ?? 0) + (result.failed ?? 0) + (result.errored ?? 0) + (result.skipped ?? 0);
19297
+ const note = result.ok && counted === 0 ? " (no test steps)" : "";
19296
19298
  blocks.push({
19297
19299
  type: "text",
19298
- text: `${result.ok ? "PASS" : "FAIL"} \u2014 ${result.passed ?? 0} passed, ${result.failed ?? 0} failed, ${result.errored ?? 0} errored, ${result.skipped ?? 0} skipped`
19300
+ text: `${result.ok ? "PASS" : "FAIL"} \u2014 ${result.passed ?? 0} passed, ${result.failed ?? 0} failed, ${result.errored ?? 0} errored, ${result.skipped ?? 0} skipped${note}`
19299
19301
  });
19300
19302
  } else {
19301
19303
  blocks.push({ type: "text", text: `Flow "${result.flow}" complete.` });
@@ -148170,6 +148170,7 @@ var path31 = __toESM(require("node:path"));
148170
148170
  // ../tool-server/src/tools/flows/flow-device.ts
148171
148171
  init_src();
148172
148172
  var DEVICE_BIND_KEYS = ["udid", "device_id"];
148173
+ var DEVICE_ARG_KEYS = [...DEVICE_BIND_KEYS, "device"];
148173
148174
  function deviceEntryId(d) {
148174
148175
  if (d.platform === "ios") return d.udid;
148175
148176
  if (d.platform === "chromium") return d.id;
@@ -148225,6 +148226,43 @@ function stripDeviceKeys(args) {
148225
148226
  for (const k of DEVICE_BIND_KEYS) delete out[k];
148226
148227
  return out;
148227
148228
  }
148229
+ function stepRequiresDevice(registry2, step) {
148230
+ switch (step.kind) {
148231
+ case "echo":
148232
+ case "wait":
148233
+ return false;
148234
+ case "tool":
148235
+ return toolRequiresDevice(registry2, step.name);
148236
+ case "when":
148237
+ case "run":
148238
+ case "launch":
148239
+ case "tap":
148240
+ case "long-press":
148241
+ case "type":
148242
+ case "await":
148243
+ case "assert":
148244
+ case "scroll-to":
148245
+ case "pinch":
148246
+ case "rotate":
148247
+ case "snapshot":
148248
+ return true;
148249
+ default: {
148250
+ const unclassified = step;
148251
+ void unclassified;
148252
+ return true;
148253
+ }
148254
+ }
148255
+ }
148256
+ function flowRequiresDevice(registry2, steps) {
148257
+ return steps.some((step) => stepRequiresDevice(registry2, step));
148258
+ }
148259
+ function toolRequiresDevice(registry2, toolName) {
148260
+ const toolDef = registry2.getTool(toolName);
148261
+ if (!toolDef) return true;
148262
+ const props = toolDef.inputSchema?.properties;
148263
+ if (!props) return false;
148264
+ return DEVICE_ARG_KEYS.some((k) => k in props);
148265
+ }
148228
148266
  function bindDeviceArgs(registry2, toolName, deviceId, args) {
148229
148267
  const toolDef = registry2.getTool(toolName);
148230
148268
  const props = toolDef?.inputSchema?.properties;
@@ -151834,7 +151872,8 @@ async function treeSourceGate(registry2, device, bundleId, signal) {
151834
151872
  return null;
151835
151873
  }
151836
151874
  async function runLaunch(state3, app) {
151837
- const { registry: registry2, device, signal } = state3;
151875
+ const env = deviceEnv(state3);
151876
+ const { registry: registry2, device, signal } = env;
151838
151877
  if (device.platform === "chromium") {
151839
151878
  if (state3.chromiumLaunched) {
151840
151879
  return {
@@ -151871,7 +151910,7 @@ async function runLaunch(state3, app) {
151871
151910
  };
151872
151911
  }
151873
151912
  try {
151874
- await invokeOnDevice(state3, "restart-app", { bundleId });
151913
+ await invokeOnDevice(env, "restart-app", { bundleId });
151875
151914
  } catch (err) {
151876
151915
  if (signal?.aborted) return ABORTED_OUTCOME;
151877
151916
  return { ok: false, reason: `restart-app failed: ${errMsg2(err)}` };
@@ -151882,6 +151921,12 @@ async function runLaunch(state3, app) {
151882
151921
  if (gate) return { ok: false, reason: gate };
151883
151922
  return { ok: true };
151884
151923
  }
151924
+ function deviceEnv(state3) {
151925
+ if (!state3.device) {
151926
+ throw new Error("internal: a step that acts on a device ran in a flow resolved as device-free");
151927
+ }
151928
+ return { ...state3, device: state3.device };
151929
+ }
151885
151930
  function createRunFlowTool(registry2) {
151886
151931
  return {
151887
151932
  id: "flow-execute",
@@ -151940,11 +151985,13 @@ returns a notice with the prerequisite instead of running.`,
151940
151985
  }
151941
151986
  const resolved = await resolveRunDevice(registry2, ctx, flow, params, flowsDir);
151942
151987
  const device = resolved.device;
151943
- const env = { registry: registry2, ctx, device, signal };
151944
- const statusBarPinned = await pinStatusBar(device);
151945
- if (device.platform === "chromium") await frontChromiumPage(registry2, device);
151988
+ const statusBarPinned = device !== null && await pinStatusBar(device);
151989
+ if (device?.platform === "chromium") await frontChromiumPage(registry2, device);
151946
151990
  const state3 = {
151947
- ...env,
151991
+ registry: registry2,
151992
+ ctx,
151993
+ device,
151994
+ signal,
151948
151995
  flowsDir,
151949
151996
  topFlowName: params.name,
151950
151997
  updateBaselines: Boolean(params.updateBaselines),
@@ -151964,10 +152011,16 @@ returns a notice with the prerequisite instead of running.`,
151964
152011
  });
151965
152012
  } finally {
151966
152013
  aborted2 = state3.signal?.aborted === true;
151967
- if (state3.pinned) await restoreStatusBar(device);
152014
+ if (state3.pinned && device) await restoreStatusBar(device);
151968
152015
  if (resolved.booted) await teardownBootedChromium(registry2, resolved.booted);
151969
152016
  }
151970
- return summarize(params.name, device.id, flow.executionPrerequisite, state3.reports, aborted2);
152017
+ return summarize(
152018
+ params.name,
152019
+ device?.id ?? "",
152020
+ flow.executionPrerequisite,
152021
+ state3.reports,
152022
+ aborted2
152023
+ );
151971
152024
  }
151972
152025
  };
151973
152026
  }
@@ -151978,6 +152031,9 @@ async function resolveRunDevice(registry2, ctx, flow, params, flowDir) {
151978
152031
  const booted = await bootChromiumForFlow(spec, flowDir);
151979
152032
  return { device: resolveDevice(booted.deviceId), booted };
151980
152033
  }
152034
+ if (!flowRequiresDevice(registry2, flow.steps)) {
152035
+ return { device: null, booted: null };
152036
+ }
151981
152037
  }
151982
152038
  const device = await resolveFlowDevice(registry2, ctx, {
151983
152039
  device: params.device,
@@ -152133,6 +152189,20 @@ async function execSteps(state3, steps, scope) {
152133
152189
  if (step.kind === "when") reportBlockSkipped(state3, step.steps, childScope(scope));
152134
152190
  continue;
152135
152191
  }
152192
+ if (!state3.device && stepRequiresDevice(state3.registry, step)) {
152193
+ state3.stopped = true;
152194
+ pushReport(state3, {
152195
+ index,
152196
+ kind: step.kind,
152197
+ status: "error",
152198
+ flow: scope.flow,
152199
+ target: stepTarget(step),
152200
+ ...depthOf(scope),
152201
+ reason: `step needs a device but the flow was resolved as device-free \u2014 pass an explicit device`
152202
+ });
152203
+ if (step.kind === "when") reportBlockSkipped(state3, step.steps, childScope(scope));
152204
+ continue;
152205
+ }
152136
152206
  if (state3.signal?.aborted) {
152137
152207
  state3.stopped = true;
152138
152208
  pushReport(state3, {
@@ -152193,10 +152263,11 @@ async function execWhenStep(state3, step, scope) {
152193
152263
  const inner = childScope(scope);
152194
152264
  let met;
152195
152265
  if (step.condition.kind === "platform") {
152196
- const platform = state3.device.platform === "ios-remote" ? "ios" : state3.device.platform;
152266
+ const guardEnv = deviceEnv(state3);
152267
+ const platform = guardEnv.device.platform === "ios-remote" ? "ios" : guardEnv.device.platform;
152197
152268
  met = platform === step.condition.platform;
152198
152269
  } else {
152199
- const probe3 = await probeWhenCondition(state3, step.condition);
152270
+ const probe3 = await probeWhenCondition(deviceEnv(state3), step.condition);
152200
152271
  if (probe3.aborted) {
152201
152272
  pushReport(state3, { ...marker, status: "skip", reason: "run aborted" });
152202
152273
  reportBlockSkipped(state3, step.steps, inner, "run aborted");
@@ -152288,7 +152359,7 @@ async function execLeafStep(state3, step, index, scope) {
152288
152359
  case "pinch":
152289
152360
  case "rotate": {
152290
152361
  try {
152291
- const r = await runDirective(state3, step);
152362
+ const r = await runDirective(deviceEnv(state3), step);
152292
152363
  if (r.aborted) return { ...base, status: "skip", reason: r.reason };
152293
152364
  return { ...base, status: r.ok ? "pass" : "fail", reason: r.reason };
152294
152365
  } catch (err) {
@@ -152303,7 +152374,7 @@ async function execLeafStep(state3, step, index, scope) {
152303
152374
  }
152304
152375
  case "snapshot": {
152305
152376
  try {
152306
- const r = await runSnapshot(state3, {
152377
+ const r = await runSnapshot(deviceEnv(state3), {
152307
152378
  flowsDir: state3.flowsDir,
152308
152379
  flowName: state3.topFlowName,
152309
152380
  name: step.name,
@@ -152323,7 +152394,7 @@ async function execLeafStep(state3, step, index, scope) {
152323
152394
  }
152324
152395
  }
152325
152396
  case "tool": {
152326
- const args = bindDeviceArgs(registry2, step.name, device.id, step.args);
152397
+ const args = bindDeviceArgs(registry2, step.name, device?.id ?? "", step.args);
152327
152398
  const outputHint = registry2.getTool(step.name)?.outputHint;
152328
152399
  if (step.delayMs && !await sleepOrAbort(step.delayMs, signal)) {
152329
152400
  return { ...base, status: "skip", tool: step.name, reason: "run aborted during delay" };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swmansion/argent",
3
- "version": "0.18.1-next.14",
3
+ "version": "0.18.1-next.16",
4
4
  "mcpName": "io.github.software-mansion/argent",
5
5
  "description": "MCP server for iOS Simulator and Android Emulator control",
6
6
  "license": "Apache-2.0",