@lightward/mechanic-cli 0.1.13 → 0.1.15

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/README.md CHANGED
@@ -122,6 +122,19 @@ mechanic shop status
122
122
  Most commands that editor integrations or agents would call support `--json`.
123
123
  Use JSON output for automation instead of parsing tables or colored text.
124
124
 
125
+ When a `--json` command fails before producing its normal output (auth
126
+ problems, network failures, rate limits), it prints a JSON error envelope to
127
+ stdout instead, with exit codes unchanged:
128
+
129
+ ```json
130
+ { "error": { "message": "Mechanic API rate limit exceeded.", "status": 429, "retry_after_seconds": 7 } }
131
+ ```
132
+
133
+ `status` is present when the Mechanic API rejected the request, and
134
+ `retry_after_seconds` is present when a rate limit response includes
135
+ `Retry-After`. With `--json`, stdout is always a single JSON document: the
136
+ command's normal output, or this error envelope.
137
+
125
138
  ## Commands
126
139
 
127
140
  ```bash
@@ -141,7 +154,7 @@ mechanic tasks status [task] [--local] [--json]
141
154
  mechanic tasks pull [--force]
142
155
  mechanic tasks pull <task> [--force]
143
156
  mechanic tasks pull --all [--force]
144
- mechanic tasks preview <task> [--remote] [--verbose] [--json]
157
+ mechanic tasks preview [task] [--stdin] [--remote] [--verbose] [--json]
145
158
  mechanic tasks diff <task> [--exit-code] [--json]
146
159
  mechanic tasks diff --all [--exit-code] [--json]
147
160
  mechanic tasks publish <task> [--force] [--dry-run] [--json]
@@ -240,7 +253,25 @@ current task already in Mechanic instead of your local draft. Add `--verbose`
240
253
  to show event, task run, and action run result details in the terminal. Add
241
254
  `--json` when an agent, script, or CI job needs the raw preview response.
242
255
  Missing Shopify permissions are approved in the Mechanic app after publishing
243
- or enabling the task.
256
+ or enabling the task. Preview exits `0` when the preview passes, `1` when
257
+ sample runs fail, and `2` when the task is invalid.
258
+
259
+ `tasks preview --stdin` reads task JSON from stdin instead of local files, so
260
+ editors and other tools can preview in-memory task content without writing it
261
+ to disk first:
262
+
263
+ ```bash
264
+ cat build/order-tagger.json | mechanic tasks preview order-tagger --stdin
265
+ generate-task | mechanic tasks preview --stdin --json
266
+ ```
267
+
268
+ Pass a task selector alongside `--stdin` to preview the piped content in the
269
+ context of that linked Mechanic task, or omit the selector to preview the
270
+ content as a new unlinked task. `--stdin` trusts the piped content as the
271
+ source of truth, so it skips the stale helper directory check. Previews are
272
+ rate limited per token; tools that preview on every edit should debounce and
273
+ respect `429 Retry-After` responses. With `--json`, a rate limited preview
274
+ prints the JSON error envelope, including `retry_after_seconds`.
244
275
 
245
276
  `tasks publish` sends local task JSON back to Mechanic.
246
277
 
@@ -253,8 +284,8 @@ Mechanic, `.mechanic/links.json`, or local task JSON. New tasks created by
253
284
  they are ready to run.
254
285
 
255
286
  For automation or editor integrations, add `--json` to `tasks list`, `tasks
256
- status`, `tasks diff`, `tasks validate`, `tasks bundle`, `tasks unbundle`,
257
- `tasks publish`, or `shop status`.
287
+ status`, `tasks preview`, `tasks diff`, `tasks validate`, `tasks bundle`,
288
+ `tasks unbundle`, `tasks publish`, or `shop status`.
258
289
 
259
290
  `tasks unbundle` and `tasks bundle` operate on one task at a time. By default,
260
291
  `mechanic tasks unbundle order-tagger` writes to `tasks/order-tagger/`, and
@@ -1,7 +1,9 @@
1
- import { Command } from "@oclif/core";
1
+ import { Command, type Interfaces } from "@oclif/core";
2
2
  import { MechanicClient } from "./client.js";
3
3
  import type { Project } from "./types.js";
4
4
  export declare abstract class BaseCommand extends Command {
5
+ private wroteJson;
6
+ protected catch(error: Interfaces.CommandError): Promise<unknown>;
5
7
  protected finally(error: Error | undefined): Promise<void>;
6
8
  loadProject(): Promise<Project>;
7
9
  clientForProject(project: Project): Promise<MechanicClient>;
@@ -1 +1 @@
1
- {"version":3,"file":"base-command.d.ts","sourceRoot":"","sources":["../src/base-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAM,MAAM,aAAa,CAAC;AAI1C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAI7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,8BAAsB,WAAY,SAAQ,OAAO;cACtB,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBnE,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAI/B,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC;IAQ3D,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC;IAMnE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAY/E,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAI5B,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAenC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAI1C,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAI3B,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAIhC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAI7B,eAAe,IAAI,MAAM;IAQzB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAI9B,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAIhD,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI;CAgB9B"}
1
+ {"version":3,"file":"base-command.d.ts","sourceRoot":"","sources":["../src/base-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAM,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAI3D,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAI7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,8BAAsB,WAAY,SAAQ,OAAO;IAC/C,OAAO,CAAC,SAAS,CAAS;cAED,KAAK,CAAC,KAAK,EAAE,UAAU,CAAC,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC;cAQvD,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBnE,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAI/B,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC;IAQ3D,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC;IAMnE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAY/E,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAI5B,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAenC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAI1C,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAI3B,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAKhC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAI7B,eAAe,IAAI,MAAM;IAQzB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAI9B,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAIhD,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI;CAgB9B"}
@@ -4,9 +4,16 @@ import terminalLink from "terminal-link";
4
4
  import { requireToken } from "./auth.js";
5
5
  import { MechanicClient } from "./client.js";
6
6
  import { loadProject, taskAdminUrl } from "./config.js";
7
- import { CliError } from "./errors.js";
7
+ import { CliError, errorJson } from "./errors.js";
8
8
  import { getUpdateNotice, shouldCheckForUpdates } from "./update-check.js";
9
9
  export class BaseCommand extends Command {
10
+ wroteJson = false;
11
+ async catch(error) {
12
+ if (this.argv.includes("--json") && !this.wroteJson) {
13
+ this.outputJson(errorJson(error));
14
+ }
15
+ return super.catch(error);
16
+ }
10
17
  async finally(error) {
11
18
  await super.finally(error);
12
19
  if (error || this.id === "version" || !shouldCheckForUpdates(this.argv)) {
@@ -66,6 +73,7 @@ export class BaseCommand extends Command {
66
73
  return this.color("dim", text);
67
74
  }
68
75
  outputJson(value) {
76
+ this.wroteJson = true;
69
77
  this.log(JSON.stringify(value, null, 2));
70
78
  }
71
79
  success(text) {
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,UAAU,EACV,wBAAwB,EACxB,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAKpB,eAAO,MAAM,UAAU,QAAkD,CAAC;AAE1E,KAAK,cAAc,GAAG;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,CAAC,EAAE;QACL,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAChC,GAAG,IAAI,CAAC;IACT,SAAS,CAAC,EAAE;QACV,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC5B,CAAC;CACH,CAAC;AAwGF,qBAAa,cAAc;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBAEX,EAAE,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;IAM7G,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,CAAC,CAAC;IAgD5E,UAAU,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAIvC,aAAa,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAI5C,mBAAmB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAMvE,SAAS,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAItC,WAAW,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAI3C,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAOnE,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMxC,WAAW,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAI3C,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAOlE,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMxC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAI1C,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IASxE,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAM3D,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAQ5E,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,CAAC;CAM3H"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,UAAU,EACV,wBAAwB,EACxB,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAKpB,eAAO,MAAM,UAAU,QAAkD,CAAC;AAE1E,KAAK,cAAc,GAAG;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,CAAC,EAAE;QACL,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAChC,GAAG,IAAI,CAAC;IACT,SAAS,CAAC,EAAE;QACV,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC5B,CAAC;CACH,CAAC;AAoHF,qBAAa,cAAc;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBAEX,EAAE,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;IAM7G,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,CAAC,CAAC;IAgD5E,UAAU,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAIvC,aAAa,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAI5C,mBAAmB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAMvE,SAAS,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAItC,WAAW,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAI3C,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAOnE,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMxC,WAAW,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAI3C,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAOlE,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMxC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAI1C,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IASxE,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAM3D,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAQ5E,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,CAAC;CAM3H"}
package/dist/client.js CHANGED
@@ -20,17 +20,25 @@ function parseBody(text) {
20
20
  return { raw: text };
21
21
  }
22
22
  }
23
- function retryDelayMilliseconds(response, attempt) {
23
+ function retryAfterSeconds(response) {
24
24
  const retryAfter = response.headers.get("retry-after");
25
- if (retryAfter) {
26
- const parsedSeconds = Number.parseFloat(retryAfter);
27
- if (Number.isFinite(parsedSeconds) && parsedSeconds >= 0) {
28
- return parsedSeconds * 1000;
29
- }
30
- const parsedDate = Date.parse(retryAfter);
31
- if (Number.isFinite(parsedDate)) {
32
- return Math.max(parsedDate - Date.now(), 0);
33
- }
25
+ if (!retryAfter) {
26
+ return undefined;
27
+ }
28
+ const parsedSeconds = Number.parseFloat(retryAfter);
29
+ if (Number.isFinite(parsedSeconds) && parsedSeconds >= 0) {
30
+ return parsedSeconds;
31
+ }
32
+ const parsedDate = Date.parse(retryAfter);
33
+ if (Number.isFinite(parsedDate)) {
34
+ return Math.max(parsedDate - Date.now(), 0) / 1000;
35
+ }
36
+ return undefined;
37
+ }
38
+ function retryDelayMilliseconds(response, attempt) {
39
+ const seconds = retryAfterSeconds(response);
40
+ if (seconds !== undefined) {
41
+ return seconds * 1000;
34
42
  }
35
43
  return 250 * (2 ** (attempt - 1));
36
44
  }
@@ -131,7 +139,7 @@ export class MechanicClient {
131
139
  await sleep(retryDelayMilliseconds(response, attempt));
132
140
  continue;
133
141
  }
134
- throw new HttpError(apiErrorMessage(response, parsed), response.status, parsed);
142
+ throw new HttpError(apiErrorMessage(response, parsed), response.status, parsed, retryAfterSeconds(response));
135
143
  }
136
144
  throw new HttpError("Request failed", 0, null);
137
145
  }
@@ -9,7 +9,7 @@ export default class GlobalsDelete extends BaseCommand {
9
9
  "$ mechanic globals delete warehouse_id --force",
10
10
  ];
11
11
  static args = {
12
- key: Args.string({ required: true, description: "Global key, using lower snake-case." }),
12
+ key: Args.string({ ignoreStdin: true, required: true, description: "Global key, using lower snake-case." }),
13
13
  };
14
14
  static flags = {
15
15
  force: Flags.boolean({
@@ -11,7 +11,7 @@ export default class GlobalsSet extends BaseCommand {
11
11
  "$ mechanic globals set reorder_threshold --json 5",
12
12
  ];
13
13
  static args = {
14
- key: Args.string({ required: true, description: "Global key, using lower snake-case." }),
14
+ key: Args.string({ ignoreStdin: true, required: true, description: "Global key, using lower snake-case." }),
15
15
  };
16
16
  static flags = {
17
17
  json: Flags.string({
@@ -9,7 +9,7 @@ export default class SecretsDelete extends BaseCommand {
9
9
  "$ mechanic secrets delete api_token --force",
10
10
  ];
11
11
  static args = {
12
- key: Args.string({ required: true, description: "Secret key, using lower snake-case." }),
12
+ key: Args.string({ ignoreStdin: true, required: true, description: "Secret key, using lower snake-case." }),
13
13
  };
14
14
  static flags = {
15
15
  force: Flags.boolean({
@@ -1,8 +1,6 @@
1
- import { stdin } from "node:process";
2
1
  import { BaseCommand } from "../../base-command.js";
3
- type SecretStdin = typeof stdin;
4
- export declare function readStdin(input?: SecretStdin): Promise<string>;
5
- export declare function readSecretValueFromStdin(input?: SecretStdin): Promise<string>;
2
+ import { type StdinStream } from "../../stdin.js";
3
+ export declare function readSecretValueFromStdin(input?: StdinStream): Promise<string>;
6
4
  export declare function readSecretValueFromEnv(envName: string, env?: NodeJS.ProcessEnv): string;
7
5
  export default class SecretsSet extends BaseCommand {
8
6
  static summary: string;
@@ -22,5 +20,4 @@ export default class SecretsSet extends BaseCommand {
22
20
  "value-env"?: string;
23
21
  }): Promise<string>;
24
22
  }
25
- export {};
26
23
  //# sourceMappingURL=set.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"set.d.ts","sourceRoot":"","sources":["../../../src/commands/secrets/set.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAErC,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAIpD,KAAK,WAAW,GAAG,OAAO,KAAK,CAAC;AAEhC,wBAAgB,SAAS,CAAC,KAAK,GAAE,WAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmBrE;AAED,wBAAsB,wBAAwB,CAC5C,KAAK,GAAE,WAAmB,GACzB,OAAO,CAAC,MAAM,CAAC,CAQjB;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,CAYR;AAED,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,WAAW;IACjD,OAAgB,OAAO,SAA0B;IACjD,OAAgB,WAAW,SAAwE;IACnG,OAAgB,QAAQ,WAKtB;IAEF,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;;MAanB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBpB,WAAW,CAAC,KAAK,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;CAsB5F"}
1
+ {"version":3,"file":"set.d.ts","sourceRoot":"","sources":["../../../src/commands/secrets/set.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGpD,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7D,wBAAsB,wBAAwB,CAC5C,KAAK,GAAE,WAAmB,GACzB,OAAO,CAAC,MAAM,CAAC,CAWjB;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,CAYR;AAED,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,WAAW;IACjD,OAAgB,OAAO,SAA0B;IACjD,OAAgB,WAAW,SAAwE;IACnG,OAAgB,QAAQ,WAKtB;IAEF,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;;MAanB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBpB,WAAW,CAAC,KAAK,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;CAsB5F"}
@@ -4,24 +4,9 @@ import { promptForHiddenValue } from "../../auth.js";
4
4
  import { BaseCommand } from "../../base-command.js";
5
5
  import { validateConfigKey } from "../../config-keys.js";
6
6
  import { CliError } from "../../errors.js";
7
- export function readStdin(input = stdin) {
8
- if (input.isTTY) {
9
- throw new CliError("Cannot read secret value from stdin while stdin is a TTY. Pipe a value or use --value-env.", 2);
10
- }
11
- return new Promise((resolve, reject) => {
12
- let value = "";
13
- input.setEncoding("utf8");
14
- input.on("data", (chunk) => {
15
- value += chunk;
16
- });
17
- input.on("end", () => {
18
- resolve(value);
19
- });
20
- input.on("error", reject);
21
- });
22
- }
7
+ import { readStdin } from "../../stdin.js";
23
8
  export async function readSecretValueFromStdin(input = stdin) {
24
- const value = await readStdin(input);
9
+ const value = await readStdin("Cannot read secret value from stdin while stdin is a TTY. Pipe a value or use --value-env.", input);
25
10
  if (value.length === 0) {
26
11
  throw new CliError("Secret value cannot be blank.", 2);
27
12
  }
@@ -47,7 +32,7 @@ export default class SecretsSet extends BaseCommand {
47
32
  "$ mechanic secrets set api_token --value-env API_TOKEN --force",
48
33
  ];
49
34
  static args = {
50
- key: Args.string({ required: true, description: "Secret key, using lower snake-case." }),
35
+ key: Args.string({ ignoreStdin: true, required: true, description: "Secret key, using lower snake-case." }),
51
36
  };
52
37
  static flags = {
53
38
  "from-stdin": Flags.boolean({
@@ -1 +1 @@
1
- {"version":3,"file":"deprecations.d.ts","sourceRoot":"","sources":["../../../src/commands/shop/deprecations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGpD,OAAO,KAAK,EAAE,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAExE,KAAK,eAAe,GAAG,wBAAwB,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAUxE,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,WAAW;IACvD,OAAgB,OAAO,SAA6D;IACpF,OAAgB,WAAW,SAAqH;IAEhJ,OAAgB,IAAI;;MAKlB;IAEF,OAAgB,KAAK;;MAInB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAyCpB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAUzE,iBAAiB,CAAC,QAAQ,EAAE,wBAAwB,GAAG,MAAM,EAAE,EAAE;IAcjE,uBAAuB,CAAC,QAAQ,EAAE,wBAAwB,GAAG,MAAM;IAOnE,cAAc,CAAC,WAAW,EAAE,eAAe,GAAG,MAAM;CAcrD"}
1
+ {"version":3,"file":"deprecations.d.ts","sourceRoot":"","sources":["../../../src/commands/shop/deprecations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGpD,OAAO,KAAK,EAAE,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAExE,KAAK,eAAe,GAAG,wBAAwB,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAUxE,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,WAAW;IACvD,OAAgB,OAAO,SAA6D;IACpF,OAAgB,WAAW,SAAqH;IAEhJ,OAAgB,IAAI;;MAMlB;IAEF,OAAgB,KAAK;;MAInB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAyCpB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAUzE,iBAAiB,CAAC,QAAQ,EAAE,wBAAwB,GAAG,MAAM,EAAE,EAAE;IAcjE,uBAAuB,CAAC,QAAQ,EAAE,wBAAwB,GAAG,MAAM;IAOnE,cAAc,CAAC,WAAW,EAAE,eAAe,GAAG,MAAM;CAcrD"}
@@ -13,6 +13,7 @@ export default class ShopDeprecations extends BaseCommand {
13
13
  static description = "Show unresolved Shopify API deprecations reported by this shop's tasks, optionally filtered to one linked task.";
14
14
  static args = {
15
15
  task: Args.string({
16
+ ignoreStdin: true,
16
17
  description: "Optional task selector, linked task ID, local slug, task JSON file, or helper directory.",
17
18
  required: false,
18
19
  }),
@@ -25,7 +25,7 @@ export default class TasksBundle extends BaseCommand {
25
25
  "$ mechanic tasks bundle order-tagger --out tasks/order-tagger.json",
26
26
  ];
27
27
  static args = {
28
- dir: Args.string({ required: false, description: "Local task slug, helper task directory, or matching task JSON file." }),
28
+ dir: Args.string({ ignoreStdin: true, required: false, description: "Local task slug, helper task directory, or matching task JSON file." }),
29
29
  };
30
30
  static flags = {
31
31
  json: Flags.boolean({
@@ -6,7 +6,7 @@ export default class TasksDiff extends BaseCommand {
6
6
  static summary = "Diff one task, or use --all to diff every local task.";
7
7
  static description = "Compare one local task with its linked remote task, or use --all to compare every local task JSON file.";
8
8
  static args = {
9
- file: Args.string({ required: false, description: "Local task slug, task file, helper directory, or linked task ID." }),
9
+ file: Args.string({ ignoreStdin: true, required: false, description: "Local task slug, task file, helper directory, or linked task ID." }),
10
10
  };
11
11
  static flags = {
12
12
  all: Flags.boolean({ description: "Diff every task JSON file in this project." }),
@@ -43,7 +43,7 @@ export default class TasksNew extends BaseCommand {
43
43
  "$ mechanic tasks new \"Order tagger\"",
44
44
  ];
45
45
  static args = {
46
- name: Args.string({ required: true, description: "Task name or slug for the new local task." }),
46
+ name: Args.string({ ignoreStdin: true, required: true, description: "Task name or slug for the new local task." }),
47
47
  };
48
48
  static flags = {
49
49
  force: Flags.boolean({ description: "Overwrite the local starter task JSON file and helper directory files." }),
@@ -17,7 +17,7 @@ export default class TasksOpen extends BaseCommand {
17
17
  static summary = "Open a remote task in Mechanic.";
18
18
  static description = "Open a remote Mechanic task by local task slug, task ID, linked local task JSON file, or helper directory.";
19
19
  static args = {
20
- target: Args.string({ required: true, description: "Local task slug, remote task ID, linked local task JSON file, or helper directory." }),
20
+ target: Args.string({ ignoreStdin: true, required: true, description: "Local task slug, remote task ID, linked local task JSON file, or helper directory." }),
21
21
  };
22
22
  async taskIdForTarget(project, target) {
23
23
  const selector = await resolveTaskSelector(project, target, {
@@ -1,4 +1,5 @@
1
1
  import { BaseCommand } from "../../base-command.js";
2
+ import { type StdinStream } from "../../stdin.js";
2
3
  import type { MechanicClient } from "../../client.js";
3
4
  import type { JsonObject, Project, TaskPreviewResponse } from "../../types.js";
4
5
  type PreviewDiagnostic = NonNullable<TaskPreviewResponse["diagnostics"]>[number];
@@ -11,19 +12,24 @@ type PreparedPreview = {
11
12
  type PreviewRenderOptions = {
12
13
  verbose: boolean;
13
14
  };
15
+ export declare function taskJsonFromStdin(input?: StdinStream): Promise<JsonObject>;
14
16
  export default class TasksPreview extends BaseCommand {
15
17
  static summary: string;
16
18
  static description: string;
19
+ static examples: string[];
17
20
  static args: {
18
- target: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
21
+ target: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
19
22
  };
20
23
  static flags: {
21
24
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
22
25
  remote: import("@oclif/core/interfaces").BooleanFlag<boolean>;
26
+ stdin: import("@oclif/core/interfaces").BooleanFlag<boolean>;
23
27
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
24
28
  };
25
29
  run(): Promise<void>;
26
30
  exitForStatus(response: TaskPreviewResponse): void;
31
+ prepareStdinPreview(project: Project, target: string | undefined): Promise<PreparedPreview>;
32
+ taskFromStdin(): Promise<JsonObject>;
27
33
  preparePreview(project: Project, target: string, remote: boolean): Promise<PreparedPreview>;
28
34
  requestPreview(client: MechanicClient, prepared: PreparedPreview, remote: boolean): Promise<TaskPreviewResponse>;
29
35
  renderPreview(project: Project, prepared: PreparedPreview, response: TaskPreviewResponse, options: PreviewRenderOptions): void;
@@ -1 +1 @@
1
- {"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/preview.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/preview.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAIpD,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAW7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE/E,KAAK,iBAAiB,GAAG,WAAW,CAAC,mBAAmB,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjF,KAAK,eAAe,GAAG;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC1B,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAWF,wBAAsB,iBAAiB,CAAC,KAAK,GAAE,WAAmB,GAAG,OAAO,CAAC,UAAU,CAAC,CAiBvF;AAED,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,WAAW;IACnD,OAAgB,OAAO,SAAmD;IAC1E,OAAgB,WAAW,SAKd;IAEb,OAAgB,QAAQ,WAKtB;IAEF,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;;;MAKnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAyB1B,aAAa,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAS5C,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,eAAe,CAAC;IA+B3F,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;IAIpC,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC;IA0D3F,cAAc,CAClB,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,eAAe,EACzB,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,mBAAmB,CAAC;IA4B/B,aAAa,CACX,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE,oBAAoB,GAC5B,IAAI;IAgCP,oBAAoB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAuDzD,gBAAgB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IA+BrD,iBAAiB,CAAC,QAAQ,EAAE,mBAAmB,EAAE,OAAO,EAAE,oBAAoB,GAAG,IAAI;IAyBrF,uBAAuB,CAAC,QAAQ,EAAE,iBAAiB,CAAC,UAAU,CAAC,GAAG,MAAM;IAWxE,iBAAiB,CAAC,UAAU,EAAE,iBAAiB,GAAG,MAAM,EAAE;IAU1D,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAiBvE,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI;IAgBjD,cAAc,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,GAAG,MAAM;IAY1C,iBAAiB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IA4BtD,sBAAsB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAmB3D,YAAY,CAAC,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,mBAAmB,GAAG,MAAM;IAI9E,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,mBAAmB,GAAG,MAAM;IA+BjG,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE;IAIxC,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAWlD,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBpH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAapC,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC,QAAQ,CAAC,GAAG,MAAM;CAU3D"}
@@ -1,7 +1,10 @@
1
1
  import { Args, Flags } from "@oclif/core";
2
+ import { stdin } from "node:process";
2
3
  import { BaseCommand } from "../../base-command.js";
3
4
  import { HttpError, CliError } from "../../errors.js";
4
5
  import { pathExists } from "../../fs.js";
6
+ import { parseJson } from "../../json.js";
7
+ import { readStdin } from "../../stdin.js";
5
8
  import { displayTaskPath, readRawTaskFile, resolveTaskSelector, rawTaskFromHelperDir, taskForPush, slugFromTaskFile, unbundledHelperDirForTaskFile, validateTaskForPush, } from "../../tasks.js";
6
9
  function errorBodyAsPreview(body) {
7
10
  if (!body || typeof body !== "object") {
@@ -10,25 +13,49 @@ function errorBodyAsPreview(body) {
10
13
  const candidate = body;
11
14
  return candidate.status === "invalid" ? candidate : null;
12
15
  }
16
+ export async function taskJsonFromStdin(input = stdin) {
17
+ const contents = await readStdin("Cannot read task JSON from stdin while stdin is a TTY. Pipe task JSON or preview a local task file instead.", input);
18
+ if (contents.trim().length === 0) {
19
+ throw new CliError("No task JSON received on stdin.", 2);
20
+ }
21
+ const task = parseJson(contents, "stdin");
22
+ if (!task || typeof task !== "object" || Array.isArray(task)) {
23
+ throw new CliError("stdin must contain a single task JSON object.", 2);
24
+ }
25
+ return task;
26
+ }
13
27
  export default class TasksPreview extends BaseCommand {
14
28
  static summary = "Preview one task with Mechanic sample events.";
15
29
  static description = [
16
30
  "Preview one local task JSON file or helper directory without publishing it.",
17
31
  "The report shows sample event results, action failures, and Shopify permissions detected by the previewed paths.",
32
+ "Use --stdin to preview task JSON piped from an editor or another tool without writing it to disk.",
18
33
  "Use --remote to preview the current task already in Mechanic, or --json for agents, scripts, and CI.",
19
34
  ].join("\n");
35
+ static examples = [
36
+ "$ mechanic tasks preview order-tagger",
37
+ "$ mechanic tasks preview order-tagger --verbose",
38
+ "$ cat build/order-tagger.json | mechanic tasks preview order-tagger --stdin --json",
39
+ "$ generate-task | mechanic tasks preview --stdin --json",
40
+ ];
20
41
  static args = {
21
- target: Args.string({ required: true, description: "Local task slug, task file, helper directory, or linked task ID." }),
42
+ target: Args.string({ ignoreStdin: true, description: "Local task slug, task file, helper directory, or linked task ID. Optional with --stdin." }),
22
43
  };
23
44
  static flags = {
24
45
  json: Flags.boolean({ description: "Print the raw preview response as JSON for agents or CI." }),
25
- remote: Flags.boolean({ description: "Preview the current task in Mechanic instead of local content." }),
46
+ remote: Flags.boolean({ description: "Preview the current task in Mechanic instead of local content.", exclusive: ["stdin"] }),
47
+ stdin: Flags.boolean({ description: "Read task JSON from stdin instead of local task files. Pass a target to keep its linked task context.", exclusive: ["remote"] }),
26
48
  verbose: Flags.boolean({ char: "v", description: "Show event, task run, and action run result details." }),
27
49
  };
28
50
  async run() {
29
51
  const { args, flags } = await this.parse(TasksPreview);
52
+ if (!args.target && !flags.stdin) {
53
+ throw new CliError("Provide a task selector, or use --stdin to preview task JSON from stdin.", 2);
54
+ }
30
55
  const project = await this.loadProject();
31
- const prepared = await this.preparePreview(project, args.target, Boolean(flags.remote));
56
+ const prepared = flags.stdin
57
+ ? await this.prepareStdinPreview(project, args.target)
58
+ : await this.preparePreview(project, args.target, Boolean(flags.remote));
32
59
  const client = await this.verifiedClientForProject(project);
33
60
  const response = await this.requestPreview(client, prepared, Boolean(flags.remote));
34
61
  if (flags.json) {
@@ -47,6 +74,34 @@ export default class TasksPreview extends BaseCommand {
47
74
  throw new CliError("Preview found failed runs.", 1);
48
75
  }
49
76
  }
77
+ async prepareStdinPreview(project, target) {
78
+ let label = "stdin";
79
+ let slug;
80
+ let remoteId;
81
+ if (target) {
82
+ const selector = await resolveTaskSelector(project, target, {
83
+ allowHelperDir: true,
84
+ allowRemoteId: true,
85
+ });
86
+ const targetLabel = selector.helperDir
87
+ ? displayTaskPath(project, selector.helperDir)
88
+ : selector.file ? displayTaskPath(project, selector.file) : target;
89
+ label = `stdin (${targetLabel})`;
90
+ slug = selector.slug;
91
+ remoteId = selector.remoteId;
92
+ }
93
+ const task = await this.taskFromStdin();
94
+ this.validateLocalTask(task, label);
95
+ return {
96
+ label,
97
+ slug,
98
+ task: taskForPush(task),
99
+ remoteId,
100
+ };
101
+ }
102
+ async taskFromStdin() {
103
+ return taskJsonFromStdin();
104
+ }
50
105
  async preparePreview(project, target, remote) {
51
106
  const selector = await resolveTaskSelector(project, target, {
52
107
  allowHelperDir: true,
@@ -5,7 +5,7 @@ export default class TasksPublish extends TasksPush {
5
5
  static summary = "Publish one local task to Mechanic.";
6
6
  static description = "Publish one local task to Mechanic, or use --all to publish every local task JSON file.";
7
7
  static args = {
8
- file: Args.string({ required: false, description: "Local task slug, task file, helper directory, or linked task ID." }),
8
+ file: Args.string({ ignoreStdin: true, required: false, description: "Local task slug, task file, helper directory, or linked task ID." }),
9
9
  };
10
10
  static flags = {
11
11
  all: Flags.boolean({ description: "Publish every task JSON file in this project." }),
@@ -11,7 +11,7 @@ export default class TasksPull extends BaseCommand {
11
11
  static summary = "Pull remote tasks into local task JSON files.";
12
12
  static description = "Pull every remote task into local canonical task JSON files, or pass a task ID, linked file, or linked slug to pull one task.";
13
13
  static args = {
14
- task: Args.string({ required: false, description: "Local task slug, remote task ID, or linked local task JSON file." }),
14
+ task: Args.string({ ignoreStdin: true, required: false, description: "Local task slug, remote task ID, or linked local task JSON file." }),
15
15
  };
16
16
  static flags = {
17
17
  all: Flags.boolean({ description: "Pull every remote task for this shop. This is the default when no task ID is passed." }),
@@ -42,7 +42,7 @@ export default class TasksPush extends BaseCommand {
42
42
  static summary = "Publish one local task, or use --all to publish every local task.";
43
43
  static description = "Publish one local task to Mechanic, or use --all to publish every local task JSON file in this project.";
44
44
  static args = {
45
- file: Args.string({ required: false, description: "Local task slug, task file, helper directory, or linked task ID." }),
45
+ file: Args.string({ ignoreStdin: true, required: false, description: "Local task slug, task file, helper directory, or linked task ID." }),
46
46
  };
47
47
  static flags = {
48
48
  all: Flags.boolean({ description: "Publish every task JSON file in this project." }),
@@ -27,7 +27,7 @@ export default class TasksStatus extends BaseCommand {
27
27
  static summary = "Show whether local task files are ready and in sync with Mechanic.";
28
28
  static description = `Show whether local task files are linked, ready to publish, and in sync with their remote Mechanic tasks. Repo-wide status checks remote state for up to ${IMPLICIT_REMOTE_CHECK_LIMIT} tasks; larger projects show local status only. Use --local to skip remote checks.`;
29
29
  static args = {
30
- file: Args.string({ required: false, description: "Local task slug, task file, helper directory, or linked task ID. Defaults to every task JSON file." }),
30
+ file: Args.string({ ignoreStdin: true, required: false, description: "Local task slug, task file, helper directory, or linked task ID. Defaults to every task JSON file." }),
31
31
  };
32
32
  static flags = {
33
33
  json: Flags.boolean({
@@ -14,7 +14,7 @@ export default class TasksUnbundle extends BaseCommand {
14
14
  "$ mechanic tasks unbundle order-tagger --out helpers/order-tagger",
15
15
  ];
16
16
  static args = {
17
- file: Args.string({ required: false, description: "Local task slug, task file, helper directory, or linked task ID." }),
17
+ file: Args.string({ ignoreStdin: true, required: false, description: "Local task slug, task file, helper directory, or linked task ID." }),
18
18
  };
19
19
  static flags = {
20
20
  json: Flags.boolean({
@@ -15,7 +15,7 @@ export default class TasksValidate extends BaseCommand {
15
15
  static summary = "Validate one local task or directory of task JSON files.";
16
16
  static description = "Validate one local task slug, canonical task JSON file, helper task directory, or directory of task JSON files.";
17
17
  static args = {
18
- target: Args.string({ required: true, description: "Local task slug, task JSON file, helper dir, or directory of task JSON files." }),
18
+ target: Args.string({ ignoreStdin: true, required: true, description: "Local task slug, task JSON file, helper dir, or directory of task JSON files." }),
19
19
  };
20
20
  static flags = {
21
21
  json: Flags.boolean({
package/dist/errors.d.ts CHANGED
@@ -1,10 +1,22 @@
1
1
  export declare class CliError extends Error {
2
2
  readonly exitCode: number;
3
+ readonly oclif: {
4
+ exit: number;
5
+ };
3
6
  constructor(message: string, exitCode?: number);
4
7
  }
5
8
  export declare class HttpError extends CliError {
6
9
  readonly status: number;
7
10
  readonly body: unknown;
8
- constructor(message: string, status: number, body: unknown);
11
+ readonly retryAfterSeconds?: number;
12
+ constructor(message: string, status: number, body: unknown, retryAfterSeconds?: number);
9
13
  }
14
+ export type ErrorJson = {
15
+ error: {
16
+ message: string;
17
+ status?: number;
18
+ retry_after_seconds?: number;
19
+ };
20
+ };
21
+ export declare function errorJson(error: unknown): ErrorJson;
10
22
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAEd,OAAO,EAAE,MAAM,EAAE,QAAQ,SAAI;CAK1C;AAED,qBAAa,SAAU,SAAQ,QAAQ;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;gBAEX,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO;CAM3D"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;gBAErB,OAAO,EAAE,MAAM,EAAE,QAAQ,SAAI;CAQ1C;AAED,qBAAa,SAAU,SAAQ,QAAQ;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;gBAExB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,iBAAiB,CAAC,EAAE,MAAM;CAOvF;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,EAAE;QACL,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,mBAAmB,CAAC,EAAE,MAAM,CAAC;KAC9B,CAAC;CACH,CAAC;AAEF,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAanD"}
package/dist/errors.js CHANGED
@@ -1,18 +1,35 @@
1
1
  export class CliError extends Error {
2
2
  exitCode;
3
+ oclif;
3
4
  constructor(message, exitCode = 1) {
4
5
  super(message);
5
6
  this.name = "Error";
6
7
  this.exitCode = exitCode;
8
+ // oclif's top-level error handler exits with `error.oclif.exit ?? 1`, so
9
+ // exitCode alone does not survive to the real process exit status.
10
+ this.oclif = { exit: exitCode };
7
11
  }
8
12
  }
9
13
  export class HttpError extends CliError {
10
14
  status;
11
15
  body;
12
- constructor(message, status, body) {
16
+ retryAfterSeconds;
17
+ constructor(message, status, body, retryAfterSeconds) {
13
18
  super(message, status === 409 ? 2 : 1);
14
19
  this.name = "HttpError";
15
20
  this.status = status;
16
21
  this.body = body;
22
+ this.retryAfterSeconds = retryAfterSeconds;
17
23
  }
18
24
  }
25
+ export function errorJson(error) {
26
+ const message = error instanceof Error && error.message ? error.message : String(error);
27
+ const envelope = { error: { message } };
28
+ if (error instanceof HttpError) {
29
+ envelope.error.status = error.status;
30
+ if (error.retryAfterSeconds !== undefined) {
31
+ envelope.error.retry_after_seconds = error.retryAfterSeconds;
32
+ }
33
+ }
34
+ return envelope;
35
+ }
@@ -0,0 +1,4 @@
1
+ import { stdin } from "node:process";
2
+ export type StdinStream = typeof stdin;
3
+ export declare function readStdin(ttyMessage: string, input?: StdinStream): Promise<string>;
4
+ //# sourceMappingURL=stdin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdin.d.ts","sourceRoot":"","sources":["../src/stdin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAGrC,MAAM,MAAM,WAAW,GAAG,OAAO,KAAK,CAAC;AAEvC,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,GAAE,WAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBzF"}
package/dist/stdin.js ADDED
@@ -0,0 +1,18 @@
1
+ import { stdin } from "node:process";
2
+ import { CliError } from "./errors.js";
3
+ export function readStdin(ttyMessage, input = stdin) {
4
+ if (input.isTTY) {
5
+ throw new CliError(ttyMessage, 2);
6
+ }
7
+ return new Promise((resolve, reject) => {
8
+ let value = "";
9
+ input.setEncoding("utf8");
10
+ input.on("data", (chunk) => {
11
+ value += chunk;
12
+ });
13
+ input.on("end", () => {
14
+ resolve(value);
15
+ });
16
+ input.on("error", reject);
17
+ });
18
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightward/mechanic-cli",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Develop, preview, diff, and publish Mechanic Shopify automation tasks from local files",
5
5
  "type": "module",
6
6
  "keywords": [