@nodegx/mcp 0.2.7 → 0.2.8

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.
@@ -2964,6 +2964,62 @@ function matches(value, expected) {
2964
2964
  }
2965
2965
  return JSON.stringify(value) === JSON.stringify(expected);
2966
2966
  }
2967
+ function watchShape(step, n) {
2968
+ const w = step.watch && typeof step.watch === "object" ? step.watch : {};
2969
+ if (w.look === void 0) throw new UsageError(`Step ${n}: watch takes { look: T } or { look: [T, T, \u2026] }.`);
2970
+ const unknown = Object.keys(w).filter((k) => !["look", "for_ms", "expect"].includes(k));
2971
+ if (unknown.length) throw new UsageError(`Step ${n}: watch does not take ${unknown.map((k) => JSON.stringify(k)).join(", ")} \u2014 it takes look, for_ms and expect.`);
2972
+ if (step.expect !== void 0 && w.expect !== void 0) throw new UsageError(`Step ${n}: give watch one expect, beside it or inside it, not both.`);
2973
+ const many = Array.isArray(w.look);
2974
+ const targets = many ? w.look : [w.look];
2975
+ if (targets.length === 0 || targets.length > 8) throw new UsageError(`Step ${n}: watch looks at 1 to 8 targets.`);
2976
+ return {
2977
+ many,
2978
+ targets,
2979
+ forMs: Math.min(1e4, Math.max(100, Number(step.for_ms ?? w.for_ms ?? 1500))),
2980
+ expect: step.expect !== void 0 ? step.expect : w.expect,
2981
+ expr: `Promise.all([${targets.map((t) => lookExpression(t)).join(", ")}])`
2982
+ };
2983
+ }
2984
+ function checkStep(step, n, { outDir } = {}) {
2985
+ const verb = verbOf(step, n);
2986
+ const at = (fn) => {
2987
+ try {
2988
+ return fn();
2989
+ } catch (error) {
2990
+ if (!error.usage) throw new UsageError(`Step ${n}: ${error instanceof Error ? error.message : String(error)}`);
2991
+ throw error.message.startsWith("Step ") ? error : new UsageError(`Step ${n}: ${error.message}`);
2992
+ }
2993
+ };
2994
+ if (verb === "tap") at(() => finder(step.tap));
2995
+ else if (verb === "type") {
2996
+ if (typeof step.type !== "string") throw new UsageError(`Step ${n}: type takes a string.`);
2997
+ if (step.into !== void 0) at(() => finder(step.into));
2998
+ } else if (verb === "key") {
2999
+ if (!KEYS[step.key]) throw new UsageError(`Step ${n}: key "${step.key}" is not one of ${Object.keys(KEYS).join(", ")}.`);
3000
+ } else if (verb === "wait_for") at(() => waitExpression(step.wait_for));
3001
+ else if (verb === "read") {
3002
+ const r = step.read && typeof step.read === "object" ? step.read : null;
3003
+ if (!r || !RUN_READS.some((k) => r[k] !== void 0)) at(() => readExpression(step.read));
3004
+ } else if (verb === "navigate") {
3005
+ if (typeof step.navigate !== "string") throw new UsageError(`Step ${n}: navigate takes a route string.`);
3006
+ } else if (verb === "watch") watchShape(step, n);
3007
+ else if (verb === "viewport") at(() => parseViewports(String(step.viewport)));
3008
+ else if (verb === "screenshot") {
3009
+ if (!outDir) throw new UsageError(`Step ${n}: screenshot needs --out-dir (render_report's out_dir rule).`);
3010
+ } else if (verb === "seed_storage") {
3011
+ const seed = step.seed_storage;
3012
+ if (typeof seed === "string") {
3013
+ if (typeof step.edit !== "string") throw new UsageError(`Step ${n}: seed_storage "${seed}" edits a saved value and needs edit: "js that changes v".`);
3014
+ } else if (!seed || typeof seed !== "object" || Array.isArray(seed)) {
3015
+ throw new UsageError(`Step ${n}: seed_storage takes { key: value } or a key with edit: "js that changes v".`);
3016
+ }
3017
+ } else if (verb === "stub_route") parseStub(step, n);
3018
+ else if (verb === "reduced_motion") {
3019
+ if (typeof step.reduced_motion !== "boolean") throw new UsageError(`Step ${n}: reduced_motion takes true or false.`);
3020
+ }
3021
+ return verb;
3022
+ }
2967
3023
  async function runSteps(page, steps, { outDir, stubs = routeStubs(page), network, beforeBoot = 0 }) {
2968
3024
  const rows = [];
2969
3025
  const pendingSeeds = [];
@@ -3089,17 +3145,7 @@ async function runSteps(page, steps, { outDir, stubs = routeStubs(page), network
3089
3145
  }
3090
3146
  row.url = await evaluate("location.pathname + location.hash");
3091
3147
  } else if (verb === "watch") {
3092
- const w = step.watch && typeof step.watch === "object" ? step.watch : {};
3093
- if (w.look === void 0) throw new UsageError(`Step ${i + 1}: watch takes { look: T } or { look: [T, T, \u2026] }.`);
3094
- const unknown = Object.keys(w).filter((k) => !["look", "for_ms", "expect"].includes(k));
3095
- if (unknown.length) throw new UsageError(`Step ${i + 1}: watch does not take ${unknown.map((k) => JSON.stringify(k)).join(", ")} \u2014 it takes look, for_ms and expect.`);
3096
- if (step.expect !== void 0 && w.expect !== void 0) throw new UsageError(`Step ${i + 1}: give watch one expect, beside it or inside it, not both.`);
3097
- const forMs = Math.min(1e4, Math.max(100, Number(step.for_ms ?? w.for_ms ?? 1500)));
3098
- const expect = step.expect !== void 0 ? step.expect : w.expect;
3099
- const many = Array.isArray(w.look);
3100
- const targets = many ? w.look : [w.look];
3101
- if (targets.length === 0 || targets.length > 8) throw new UsageError(`Step ${i + 1}: watch looks at 1 to 8 targets.`);
3102
- const expr = `Promise.all([${targets.map((t) => lookExpression(t)).join(", ")}])`;
3148
+ const { many, targets, forMs, expect, expr } = watchShape(step, i + 1);
3103
3149
  const keyOf = (v) => v && typeof v === "object" ? JSON.stringify({ text: v.text, visible: v.visible, backgroundName: v.backgroundName, colorName: v.colorName, opacity: v.opacity === void 0 ? void 0 : Math.round(v.opacity * 10) / 10 }) : "null";
3104
3150
  const states = targets.map(() => []);
3105
3151
  const t0 = Date.now();
@@ -3182,8 +3228,8 @@ async function main() {
3182
3228
  return i === -1 ? void 0 : argv[i + 1];
3183
3229
  };
3184
3230
  const projectDir = argv.find((a, i) => !a.startsWith("--") && !VALUE_FLAGS.has(argv[i - 1]));
3185
- const usage = (message) => {
3186
- process.stdout.write(JSON.stringify({ error: { message, problems: [message], actionable: true } }));
3231
+ const usage = (message, problems2 = [message]) => {
3232
+ process.stdout.write(JSON.stringify({ error: { message, problems: problems2, actionable: true } }));
3187
3233
  process.exit(2);
3188
3234
  };
3189
3235
  if (!projectDir) usage("drive-steps.js <project-dir> --steps <json|@file> [--page p] [--viewport v] [--out-dir d]");
@@ -3193,12 +3239,22 @@ async function main() {
3193
3239
  if (!raw) throw new UsageError("--steps is required: a JSON array of steps.");
3194
3240
  steps = JSON.parse(raw.startsWith("@") ? fs.readFileSync(raw.slice(1), "utf8") : raw);
3195
3241
  if (!Array.isArray(steps) || steps.length === 0) throw new UsageError("--steps must be a non-empty JSON array.");
3196
- steps.forEach((s, i) => {
3197
- if (verbOf(s, i + 1) === "stub_route") parseStub(s, i + 1);
3198
- });
3199
3242
  } catch (error) {
3200
3243
  usage(error.message);
3201
3244
  }
3245
+ const problems = [];
3246
+ steps.forEach((step, i) => {
3247
+ try {
3248
+ checkStep(step, i + 1, { outDir: flag("--out-dir") });
3249
+ } catch (error) {
3250
+ if (!error.usage) throw error;
3251
+ problems.push(error.message);
3252
+ }
3253
+ });
3254
+ if (problems.length) {
3255
+ problems.push("No step ran: every step is checked before the first one does.");
3256
+ usage(problems.join(" "), problems);
3257
+ }
3202
3258
  let viewport;
3203
3259
  try {
3204
3260
  [viewport] = parseViewports(flag("--viewport") || "desktop");
@@ -3250,9 +3306,9 @@ async function main() {
3250
3306
  process.stdout.write(JSON.stringify(result));
3251
3307
  } catch (error) {
3252
3308
  if (error.usage) usage(error.message);
3253
- const problems = error.problems || [error instanceof Error ? error.message : String(error)];
3254
- process.stdout.write(JSON.stringify({ error: { message: problems.join(" "), problems, actionable: Boolean(error.actionable) } }));
3309
+ const problems2 = error.problems || [error instanceof Error ? error.message : String(error)];
3310
+ process.stdout.write(JSON.stringify({ error: { message: problems2.join(" "), problems: problems2, actionable: Boolean(error.actionable) } }));
3255
3311
  }
3256
3312
  }
3257
3313
  if (require.main === module) main();
3258
- module.exports = { runSteps, locateExpression, routeStubs, networkLog, parseStub, VERBS };
3314
+ module.exports = { runSteps, checkStep, locateExpression, routeStubs, networkLog, parseStub, VERBS };