@lightward/mechanic-cli 0.1.3 → 0.1.6

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.
Files changed (41) hide show
  1. package/README.md +10 -6
  2. package/dist/base-command.d.ts +1 -0
  3. package/dist/base-command.d.ts.map +1 -1
  4. package/dist/base-command.js +17 -2
  5. package/dist/client.d.ts +6 -4
  6. package/dist/client.d.ts.map +1 -1
  7. package/dist/client.js +4 -1
  8. package/dist/commands/auth/login.d.ts.map +1 -1
  9. package/dist/commands/auth/login.js +7 -6
  10. package/dist/commands/doctor.d.ts.map +1 -1
  11. package/dist/commands/doctor.js +13 -9
  12. package/dist/commands/init.d.ts.map +1 -1
  13. package/dist/commands/init.js +7 -6
  14. package/dist/commands/tasks/bundle.d.ts.map +1 -1
  15. package/dist/commands/tasks/bundle.js +17 -8
  16. package/dist/commands/tasks/diff.d.ts.map +1 -1
  17. package/dist/commands/tasks/diff.js +11 -2
  18. package/dist/commands/tasks/new.d.ts.map +1 -1
  19. package/dist/commands/tasks/new.js +2 -1
  20. package/dist/commands/tasks/pull.d.ts.map +1 -1
  21. package/dist/commands/tasks/pull.js +4 -2
  22. package/dist/commands/tasks/push.d.ts +2 -0
  23. package/dist/commands/tasks/push.d.ts.map +1 -1
  24. package/dist/commands/tasks/push.js +18 -3
  25. package/dist/commands/tasks/status.d.ts +0 -1
  26. package/dist/commands/tasks/status.d.ts.map +1 -1
  27. package/dist/commands/tasks/status.js +16 -13
  28. package/dist/commands/tasks/validate.d.ts +1 -0
  29. package/dist/commands/tasks/validate.d.ts.map +1 -1
  30. package/dist/commands/tasks/validate.js +40 -18
  31. package/dist/config.d.ts +1 -1
  32. package/dist/config.d.ts.map +1 -1
  33. package/dist/config.js +51 -19
  34. package/dist/tasks.d.ts.map +1 -1
  35. package/dist/tasks.js +10 -1
  36. package/dist/types.d.ts +1 -0
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/update-check.d.ts +14 -0
  39. package/dist/update-check.d.ts.map +1 -0
  40. package/dist/update-check.js +136 -0
  41. package/package.json +1 -1
package/README.md CHANGED
@@ -38,6 +38,8 @@ Install from npm:
38
38
  npm install -g @lightward/mechanic-cli
39
39
  ```
40
40
 
41
+ In interactive terminals, Mechanic occasionally checks npm and prints the update command when a newer CLI is available.
42
+
41
43
  ## Quick Start
42
44
 
43
45
  Create an API token in Mechanic:
@@ -61,7 +63,8 @@ mechanic tasks pull
61
63
  ```
62
64
 
63
65
  To start from scratch instead, create a new blank local task. This writes a
64
- starter JSON file and matching helper directory:
66
+ starter JSON file and matching helper directory. You can use this in any
67
+ initialized CLI project; it does not require a fresh repository:
65
68
 
66
69
  ```bash
67
70
  mechanic tasks new order-tagger
@@ -124,7 +127,7 @@ mechanic tasks publish <task> [--force] [--dry-run] [--json]
124
127
  mechanic tasks publish --all [--force] [--dry-run] [--json]
125
128
  mechanic tasks unbundle <task> [--out <dir>] [--json]
126
129
  mechanic tasks bundle <task|dir|file> [--out <file>] [--json]
127
- mechanic tasks validate <file|dir> [--json]
130
+ mechanic tasks validate <task|dir> [--json]
128
131
  ```
129
132
 
130
133
  Most task commands accept a `<task>` selector. In day-to-day use, prefer the
@@ -166,9 +169,10 @@ explicitly pass `--all`.
166
169
 
167
170
  `tasks new` creates a new blank local starter task. It writes both
168
171
  `tasks/<slug>.json` and `tasks/<slug>/`, so you can edit the helper files first
169
- and bundle them into the JSON file before publishing. It does not create
170
- anything in Mechanic until you run `tasks publish`; new published tasks are
171
- created disabled.
172
+ and bundle them into the JSON file before publishing. It can be used any time in
173
+ an initialized CLI project, and it refuses to overwrite existing local task files
174
+ unless you pass `--force`. It does not create anything in Mechanic until you run
175
+ `tasks publish`; new published tasks are created disabled.
172
176
 
173
177
  `shop status` shows the current Mechanic run queue for the configured shop:
174
178
  running runs, waiting runs, queue lag, and the largest backlog groups by task,
@@ -381,7 +385,7 @@ After editing, bundle the helper directory back to canonical JSON:
381
385
 
382
386
  ```bash
383
387
  mechanic tasks bundle order-tagger
384
- mechanic tasks validate tasks/order-tagger.json
388
+ mechanic tasks validate order-tagger
385
389
  ```
386
390
 
387
391
  `tasks bundle` accepts the local task slug or either side of the pair.
@@ -2,6 +2,7 @@ import { Command } 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
+ protected finally(error: Error | undefined): Promise<void>;
5
6
  loadProject(): Promise<Project>;
6
7
  clientForProject(project: Project): Promise<MechanicClient>;
7
8
  verifiedClientForProject(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;AAG7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,8BAAsB,WAAY,SAAQ,OAAO;IACzC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAI/B,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC;IAO3D,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,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"}
@@ -5,13 +5,28 @@ import { requireToken } from "./auth.js";
5
5
  import { MechanicClient } from "./client.js";
6
6
  import { loadProject, taskAdminUrl } from "./config.js";
7
7
  import { CliError } from "./errors.js";
8
+ import { getUpdateNotice, shouldCheckForUpdates } from "./update-check.js";
8
9
  export class BaseCommand extends Command {
10
+ async finally(error) {
11
+ await super.finally(error);
12
+ if (error || !shouldCheckForUpdates(this.argv)) {
13
+ return;
14
+ }
15
+ const notice = await getUpdateNotice();
16
+ if (!notice) {
17
+ return;
18
+ }
19
+ this.log("");
20
+ this.log(`${this.color("yellow", "Update available")} Mechanic CLI ${this.muted(notice.currentVersion)} -> ${this.success(notice.latestVersion)}`);
21
+ this.log(`Update with: ${this.taskName("npm install -g @lightward/mechanic-cli")}`);
22
+ }
9
23
  async loadProject() {
10
24
  return loadProject(process.cwd());
11
25
  }
12
26
  async clientForProject(project) {
13
27
  return new MechanicClient({
14
28
  baseUrl: project.apiBaseUrl,
29
+ expectedShopDomain: project.shopDomain,
15
30
  token: await requireToken(project.shopDomain),
16
31
  });
17
32
  }
@@ -23,8 +38,8 @@ export class BaseCommand extends Command {
23
38
  async verifyClientShop(project, client) {
24
39
  const verification = await client.verifyAuth();
25
40
  const verifiedShopDomain = verification.shop?.shopify_domain;
26
- if (verifiedShopDomain !== project.shopDomain) {
27
- throw new CliError(`API token belongs to ${verifiedShopDomain || "an unknown shop"}, but mechanic.json is configured for ${project.shopDomain}.`, 2);
41
+ if (verifiedShopDomain && verifiedShopDomain !== project.shopDomain) {
42
+ throw new CliError("API token does not match this Mechanic CLI project. Use a token for the shop in mechanic.json, or re-run mechanic init for the intended shop.", 2);
28
43
  }
29
44
  }
30
45
  accent(text) {
package/dist/client.d.ts CHANGED
@@ -7,9 +7,9 @@ type RequestOptions = {
7
7
  retry?: boolean;
8
8
  };
9
9
  export type AuthVerification = {
10
- shop: {
11
- shopify_domain: string;
12
- };
10
+ shop?: {
11
+ shopify_domain?: string | null;
12
+ } | null;
13
13
  api_token?: {
14
14
  name?: string | null;
15
15
  created_by?: string | null;
@@ -17,9 +17,11 @@ export type AuthVerification = {
17
17
  };
18
18
  export declare class MechanicClient {
19
19
  readonly baseUrl: string;
20
+ readonly expectedShopDomain?: string;
20
21
  readonly token: string;
21
- constructor({ baseUrl, token }: {
22
+ constructor({ baseUrl, expectedShopDomain, token }: {
22
23
  baseUrl: string;
24
+ expectedShopDomain?: string;
23
25
  token: string;
24
26
  });
25
27
  request<T>(pathname: string, options?: RequestOptions): Promise<T>;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAKtH,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,EAAE;QACJ,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,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,KAAK,EAAE,MAAM,CAAC;gBAEX,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;IAK5D,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,CAAC,CAAC;IA+C5E,UAAU,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAIvC,aAAa,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAI5C,SAAS,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAItC,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,EAAE,UAAU,EAAE,kBAAkB,EAAE,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAKtH,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,SAAS,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAItC,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
@@ -87,9 +87,11 @@ function apiErrorMessage(response, parsed) {
87
87
  }
88
88
  export class MechanicClient {
89
89
  baseUrl;
90
+ expectedShopDomain;
90
91
  token;
91
- constructor({ baseUrl, token }) {
92
+ constructor({ baseUrl, expectedShopDomain, token }) {
92
93
  this.baseUrl = baseUrl;
94
+ this.expectedShopDomain = expectedShopDomain;
93
95
  this.token = token;
94
96
  }
95
97
  async request(pathname, options = {}) {
@@ -106,6 +108,7 @@ export class MechanicClient {
106
108
  Accept: "application/json",
107
109
  Authorization: `Bearer ${this.token}`,
108
110
  "User-Agent": USER_AGENT,
111
+ ...(this.expectedShopDomain ? { "X-Mechanic-Shop-Domain": this.expectedShopDomain } : {}),
109
112
  ...(options.body === undefined ? {} : { "Content-Type": "application/json" }),
110
113
  ...(options.headers || {}),
111
114
  },
@@ -1 +1 @@
1
- {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/login.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAIpD,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,WAAW;IAChD,OAAgB,WAAW,SAA4C;IAEvE,OAAgB,KAAK;;MAInB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAuBpB,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;CASxC"}
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/login.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAIpD,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,WAAW;IAChD,OAAgB,WAAW,SAA4C;IAEvE,OAAgB,KAAK;;MAInB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAwBpB,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;CASxC"}
@@ -14,14 +14,15 @@ export default class AuthLogin extends BaseCommand {
14
14
  const { flags } = await this.parse(AuthLogin);
15
15
  const token = flags.token || process.env.MECHANIC_API_TOKEN || (await this.promptForToken());
16
16
  const project = await this.loadProject();
17
- const client = new MechanicClient({ baseUrl: project.apiBaseUrl, token });
17
+ const client = new MechanicClient({
18
+ baseUrl: project.apiBaseUrl,
19
+ expectedShopDomain: project.shopDomain,
20
+ token,
21
+ });
18
22
  const verification = await client.verifyAuth();
19
23
  const verifiedDomain = verification.shop?.shopify_domain;
20
- if (!verifiedDomain) {
21
- throw new CliError("Token verification did not return a shop domain.");
22
- }
23
- if (verifiedDomain !== project.shopDomain) {
24
- throw new CliError(`Token belongs to ${verifiedDomain}, but this project is configured for ${project.shopDomain}.`);
24
+ if (verifiedDomain && verifiedDomain !== project.shopDomain) {
25
+ throw new CliError("API token does not match this Mechanic CLI project. Use a token for the shop in mechanic.json.", 2);
25
26
  }
26
27
  await saveToken(project.shopDomain, token);
27
28
  this.log(`${this.success("Stored")} API token for ${this.accent(project.shopDomain)}`);
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAiBjD,MAAM,CAAC,OAAO,OAAO,MAAO,SAAQ,WAAW;IAC7C,OAAgB,WAAW,SAA2E;IAEhG,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAkH1B,WAAW,CAAC,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM;CAUpD"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAiBjD,MAAM,CAAC,OAAO,OAAO,MAAO,SAAQ,WAAW;IAC7C,OAAgB,WAAW,SAA2E;IAEhG,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAsH1B,WAAW,CAAC,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM;CAUpD"}
@@ -53,21 +53,24 @@ export default class Doctor extends BaseCommand {
53
53
  if (token) {
54
54
  const client = new MechanicClient({
55
55
  baseUrl: project.apiBaseUrl,
56
+ expectedShopDomain: project.shopDomain,
56
57
  token,
57
58
  });
58
59
  try {
59
60
  const verification = await client.verifyAuth();
60
61
  const verifiedDomain = verification.shop?.shopify_domain;
61
- verifiedShopMatches = verifiedDomain === project.shopDomain;
62
+ verifiedShopMatches = !verifiedDomain || verifiedDomain === project.shopDomain;
62
63
  addRow("Token shop", verifiedShopMatches ? "ok" : "fail", verifiedDomain
63
- ? `${verifiedDomain}${verifiedShopMatches ? "" : ` does not match ${project.shopDomain}`}`
64
- : "API did not return a shop domain");
65
- const tokenDetails = [
66
- verification.api_token?.name,
67
- verification.api_token?.created_by ? `created by ${verification.api_token.created_by}` : null,
68
- ].filter(Boolean).join(", ");
69
- if (tokenDetails) {
70
- addRow("Token identity", "ok", tokenDetails);
64
+ ? (verifiedShopMatches ? project.shopDomain : "API token does not match this project")
65
+ : `verified for ${project.shopDomain}`);
66
+ if (verifiedShopMatches) {
67
+ const tokenDetails = [
68
+ verification.api_token?.name,
69
+ verification.api_token?.created_by ? `created by ${verification.api_token.created_by}` : null,
70
+ ].filter(Boolean).join(", ");
71
+ if (tokenDetails) {
72
+ addRow("Token identity", "ok", tokenDetails);
73
+ }
71
74
  }
72
75
  }
73
76
  catch (error) {
@@ -77,6 +80,7 @@ export default class Doctor extends BaseCommand {
77
80
  if (token && verifiedShopMatches) {
78
81
  const client = new MechanicClient({
79
82
  baseUrl: project.apiBaseUrl,
83
+ expectedShopDomain: project.shopDomain,
80
84
  token,
81
85
  });
82
86
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AASjD,MAAM,CAAC,OAAO,OAAO,IAAK,SAAQ,WAAW;IAC3C,OAAgB,WAAW,SAAkD;IAE7E,OAAgB,KAAK;;;;;;MAQnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CA+D3B"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AASjD,MAAM,CAAC,OAAO,OAAO,IAAK,SAAQ,WAAW;IAC3C,OAAgB,WAAW,SAAkD;IAE7E,OAAgB,KAAK;;;;;;MAQnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAgE3B"}
@@ -28,14 +28,15 @@ export default class Init extends BaseCommand {
28
28
  : null);
29
29
  let storedToken = false;
30
30
  if (token) {
31
- const client = new MechanicClient({ baseUrl: normalizeApiBaseUrl(flags["api-base-url"]), token });
31
+ const client = new MechanicClient({
32
+ baseUrl: normalizeApiBaseUrl(flags["api-base-url"]),
33
+ expectedShopDomain: flags.shop,
34
+ token,
35
+ });
32
36
  const verification = await client.verifyAuth();
33
37
  const verifiedDomain = verification.shop?.shopify_domain;
34
- if (!verifiedDomain) {
35
- throw new CliError("Token verification did not return a shop domain.");
36
- }
37
- if (verifiedDomain !== flags.shop) {
38
- throw new CliError(`Token belongs to ${verifiedDomain}, but this project is configured for ${flags.shop}.`);
38
+ if (verifiedDomain && verifiedDomain !== flags.shop) {
39
+ throw new CliError("API token does not match the requested shop. Check --shop or use a token for that shop.", 2);
39
40
  }
40
41
  }
41
42
  const project = await initProject(process.cwd(), flags.shop, flags["api-base-url"], flags["app-url"], Boolean(flags.force));
@@ -1 +1 @@
1
- {"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/bundle.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AASpD,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,WAAW;IAClD,OAAgB,OAAO,SAA+D;IACtF,OAAgB,WAAW,SAAqG;IAChI,OAAgB,QAAQ,WAKtB;IAEF,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;MAOnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;YAgCZ,kBAAkB;CAuEjC"}
1
+ {"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/bundle.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAoBpD,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,WAAW;IAClD,OAAgB,OAAO,SAA+D;IACtF,OAAgB,WAAW,SAAqG;IAChI,OAAgB,QAAQ,WAKtB;IAEF,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;MAOnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;YAgCZ,kBAAkB;CAwEjC"}
@@ -7,6 +7,14 @@ import { bundleTask, displayTaskPath, resolveTaskSelector } from "../../tasks.js
7
7
  function looksLikeTaskPath(input) {
8
8
  return input.includes("/") || input.includes("\\") || input.toLowerCase().endsWith(".json");
9
9
  }
10
+ function stripTrailingSeparators(input) {
11
+ let result = input;
12
+ const root = path.parse(path.resolve(input)).root;
13
+ while (result.length > root.length && /[\\/]$/.test(result)) {
14
+ result = result.slice(0, -1);
15
+ }
16
+ return result;
17
+ }
10
18
  export default class TasksBundle extends BaseCommand {
11
19
  static summary = "Bundle one helper task directory into one task JSON file.";
12
20
  static description = "Bundle one local task slug or editable helper task directory into one canonical task JSON file.";
@@ -55,15 +63,16 @@ export default class TasksBundle extends BaseCommand {
55
63
  this.log(`Bundled ${this.taskName(inputDisplay)} -> ${this.taskName(outputDisplay)}`);
56
64
  }
57
65
  async resolveBundlePaths(input, out) {
58
- const inputPath = path.resolve(input);
66
+ const normalizedInput = stripTrailingSeparators(input);
67
+ const inputPath = path.resolve(normalizedInput);
59
68
  if (await pathExists(path.join(inputPath, "task.json"))) {
60
69
  return {
61
70
  dirPath: inputPath,
62
- inputDisplay: input,
63
- outputDisplay: out || `${input}.json`,
71
+ inputDisplay: normalizedInput,
72
+ outputDisplay: out || `${normalizedInput}.json`,
64
73
  };
65
74
  }
66
- if (!looksLikeTaskPath(input)) {
75
+ if (!looksLikeTaskPath(normalizedInput)) {
67
76
  let project = null;
68
77
  try {
69
78
  project = await this.loadProject();
@@ -91,14 +100,14 @@ export default class TasksBundle extends BaseCommand {
91
100
  }
92
101
  }
93
102
  }
94
- if (!input.toLowerCase().endsWith(".json")) {
103
+ if (!normalizedInput.toLowerCase().endsWith(".json")) {
95
104
  return {
96
105
  dirPath: inputPath,
97
- inputDisplay: input,
98
- outputDisplay: out || `${input}.json`,
106
+ inputDisplay: normalizedInput,
107
+ outputDisplay: out || `${normalizedInput}.json`,
99
108
  };
100
109
  }
101
- const helperInput = input.slice(0, -".json".length);
110
+ const helperInput = normalizedInput.slice(0, -".json".length);
102
111
  const helperPath = path.resolve(helperInput);
103
112
  if (!(await pathExists(path.join(helperPath, "task.json")))) {
104
113
  throw new CliError([
@@ -1 +1 @@
1
- {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/diff.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAYpD,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,WAAW;IAChD,OAAgB,OAAO,SAA2D;IAClF,OAAgB,WAAW,SAA6G;IAExI,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;;MAMnB;IAEF,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IA2BzB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAmH3B"}
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/diff.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAYpD,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,WAAW;IAChD,OAAgB,OAAO,SAA2D;IAClF,OAAgB,WAAW,SAA6G;IAExI,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;;MAMnB;IAEF,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IA2BzB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CA4H3B"}
@@ -44,11 +44,13 @@ export default class TasksDiff extends BaseCommand {
44
44
  const results = [];
45
45
  let hasFieldDifferences = false;
46
46
  let hasRemoteChanges = false;
47
+ let hasUnlinkedTasks = false;
47
48
  for (const file of files) {
48
49
  const slug = slugFromTaskFile(file);
49
50
  const link = linkForSlug(project, slug);
50
51
  const relativeFile = displayTaskPath(project, file);
51
52
  if (!link) {
53
+ hasUnlinkedTasks = true;
52
54
  results.push({
53
55
  file: relativeFile,
54
56
  slug,
@@ -86,7 +88,7 @@ export default class TasksDiff extends BaseCommand {
86
88
  if (remoteChanged || diff) {
87
89
  const section = [`# ${relativeFile}`];
88
90
  if (remoteChanged) {
89
- section.push(this.color("yellow", "Remote changed since your last pull."), this.muted("Showing the current Mechanic task compared with your local file."), this.muted(`Run "mechanic tasks pull ${link.remote_id}" before publishing, or use --force only if the local file should win.`), "");
91
+ section.push(this.color("yellow", "Remote changed since your last pull."), this.muted("Showing what would change if your local file replaced the current Mechanic task."), this.muted("Diff legend: - current Mechanic, + local file."), this.muted(`Run "mechanic tasks pull ${link.remote_id}" to keep the Mechanic version, or use --force only if the local file should win.`), "");
90
92
  }
91
93
  if (diff) {
92
94
  section.push(diff);
@@ -116,7 +118,14 @@ export default class TasksDiff extends BaseCommand {
116
118
  return;
117
119
  }
118
120
  this.log(differences.join("\n\n"));
119
- this.log(hasFieldDifferences ? "Differences found." : "Remote changes found.");
121
+ let summary = "Differences found.";
122
+ if (!hasFieldDifferences && hasRemoteChanges) {
123
+ summary = "Remote changes found.";
124
+ }
125
+ else if (!hasFieldDifferences && hasUnlinkedTasks) {
126
+ summary = "Unlinked tasks found.";
127
+ }
128
+ this.log(summary);
120
129
  if (flags["exit-code"]) {
121
130
  process.exitCode = 1;
122
131
  }
@@ -1 +1 @@
1
- {"version":3,"file":"new.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/new.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAuCpD,MAAM,CAAC,OAAO,OAAO,QAAS,SAAQ,WAAW;IAC/C,OAAgB,OAAO,SAAoC;IAC3D,OAAgB,WAAW,SAAiJ;IAC5K,OAAgB,QAAQ,WAGtB;IAEF,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;MAGnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;YA0CZ,kBAAkB;CA2BjC"}
1
+ {"version":3,"file":"new.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/new.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAwCpD,MAAM,CAAC,OAAO,OAAO,QAAS,SAAQ,WAAW;IAC/C,OAAgB,OAAO,SAAoC;IAC3D,OAAgB,WAAW,SAAiJ;IAC5K,OAAgB,QAAQ,WAGtB;IAEF,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;MAGnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;YA0CZ,kBAAkB;CA2BjC"}
@@ -18,6 +18,7 @@ function starterTask(name) {
18
18
  return {
19
19
  halt_action_run_sequence_on_error: false,
20
20
  name,
21
+ docs: "",
21
22
  options: {},
22
23
  perform_action_runs_in_sequence: false,
23
24
  preview_event_definitions: [],
@@ -80,7 +81,7 @@ export default class TasksNew extends BaseCommand {
80
81
  this.log(` edit ${this.taskName(displayTaskPath(project, path.join(helperDir, "script.liquid")))}`);
81
82
  this.log(` mechanic tasks preview ${this.taskName(slug)}`);
82
83
  this.log(` mechanic tasks bundle ${this.taskName(slug)}`);
83
- this.log(` mechanic tasks publish ${this.taskName(slug)} --dry-run`);
84
+ this.log(` mechanic tasks publish ${this.taskName(slug)}`);
84
85
  }
85
86
  async checkExistingPaths(project, filePath, helperDir, force) {
86
87
  if (force) {
@@ -1 +1 @@
1
- {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/pull.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAoBpD,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,WAAW;IAChD,OAAgB,OAAO,SAAmD;IAC1E,OAAgB,WAAW,SAAmI;IAE9J,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;MAGnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CA4F3B"}
1
+ {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/pull.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAoBpD,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,WAAW;IAChD,OAAgB,OAAO,SAAmD;IAC1E,OAAgB,WAAW,SAAmI;IAE9J,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;MAGnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAuG3B"}
@@ -62,7 +62,9 @@ export default class TasksPull extends BaseCommand {
62
62
  const remoteTaskForPull = taskForPush(envelope.task);
63
63
  const localMatchesCurrentRemote = stableStringify(localTaskForPull) === stableStringify(remoteTaskForPull);
64
64
  const localHash = contentHash(localTaskForPull);
65
- if (!localMatchesCurrentRemote && localHash !== link?.last_remote_content_hash) {
65
+ const localMatchesLastWritten = (localHash === link?.last_remote_content_hash
66
+ || localHash === link?.last_local_content_hash);
67
+ if (!localMatchesCurrentRemote && !localMatchesLastWritten) {
66
68
  throw new CliError([
67
69
  `${displayTaskPath(project, filePath)} has local changes that would be overwritten.`,
68
70
  `Run "mechanic tasks diff ${displayTaskPath(project, filePath)}" to review them.`,
@@ -72,7 +74,7 @@ export default class TasksPull extends BaseCommand {
72
74
  }
73
75
  }
74
76
  await writeTaskFilePathAndRefreshHelper(filePath, envelope.task);
75
- links = updateLink({ ...project, links }, slug, envelope.id, envelope.content_hash);
77
+ links = updateLink({ ...project, links }, slug, envelope.id, envelope.content_hash, contentHash(taskForPush(envelope.task)), filePath);
76
78
  await saveLinks(project, links);
77
79
  rows.push([
78
80
  this.taskId(project, envelope.id),
@@ -18,6 +18,7 @@ type PushRow = {
18
18
  task_id: string | null;
19
19
  content_hash: string | null;
20
20
  details: string;
21
+ url?: string;
21
22
  };
22
23
  type CreateCollision = {
23
24
  remoteId: string;
@@ -54,6 +55,7 @@ export default class TasksPush extends BaseCommand {
54
55
  rows: PushRow[];
55
56
  }>;
56
57
  pushRowsToTable(project: Project, statusHeader: string, rows: PushRow[], label: (status: string) => string): string[][];
58
+ printCreatedTaskLinks(rows: PushRow[]): void;
57
59
  planLabel(plan: string): string;
58
60
  }
59
61
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/push.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/push.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAmBpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnF,KAAK,YAAY,GAAG;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,UAAU,CAAC;CAClB,CAAC;AAEF,KAAK,aAAa,GAAG;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,IAAI,EAAE,OAAO,EAAE,CAAC;CACjB,CAAC;AAEF,KAAK,OAAO,GAAG;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAgBF,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,WAAW;IAChD,OAAgB,MAAM,UAAQ;IAC9B,OAAgB,OAAO,SAAuE;IAC9F,OAAgB,WAAW,SAA6G;IAExI,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;;;MAKnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAMpB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAyCvE,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAgF9F,sBAAsB,CAC1B,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,SAAS,EAChB,aAAa,EAAE,YAAY,EAAE,GAC5B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IA8B/B,wBAAwB,CAC5B,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,SAAS,EAChB,aAAa,EAAE,YAAY,EAAE,GAC5B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAwClC,6BAA6B,CACjC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,SAAS,EAChB,aAAa,EAAE,YAAY,EAAE,GAC5B,OAAO,CAAC,IAAI,CAAC;IAwBV,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,kBAAkB,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC;IA6FpG,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;QACtF,OAAO,EAAE,OAAO,CAAC;QACjB,IAAI,EAAE,OAAO,EAAE,CAAC;KACjB,CAAC;IA6EF,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,EAAE,EAAE;IAavH,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAe5C,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAehC"}
@@ -1,8 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { Args, Flags } from "@oclif/core";
3
3
  import { BaseCommand } from "../../base-command.js";
4
- import { linkForSlug, saveLinks, updateLink } from "../../config.js";
4
+ import { linkForSlug, saveLinks, taskAdminUrl, updateLink } from "../../config.js";
5
5
  import { CliError } from "../../errors.js";
6
+ import { contentHash } from "../../hash.js";
6
7
  import { stableStringify } from "../../json.js";
7
8
  import { displayTaskPath, remoteChangedSinceLastPull, remoteChangedSinceLastPullDetails, remoteChangedSinceLastPullMessage, selectedTaskFiles, readRawTaskFile, slugFromTaskFile, taskForPush, taskSlug, unbundledHelperDirForTaskFile, validateTaskForPush, writeTaskFilePathAndRefreshHelper, } from "../../tasks.js";
8
9
  function createTaskIdempotencyKey(shopDomain, slug) {
@@ -67,6 +68,7 @@ export default class TasksPush extends BaseCommand {
67
68
  return;
68
69
  }
69
70
  this.table(this.pushRowsToTable(project, "Action", rows, (status) => this.actionLabel(status)));
71
+ this.printCreatedTaskLinks(rows);
70
72
  }
71
73
  async publishTasks(project, preparation, force) {
72
74
  const client = await this.verifiedClientForProject(project);
@@ -80,7 +82,7 @@ export default class TasksPush extends BaseCommand {
80
82
  const link = linkForSlug({ ...project, links }, slug);
81
83
  if (!link) {
82
84
  const created = await client.createTask(task, createTaskIdempotencyKey(project.shopDomain, slug));
83
- links = updateLink({ ...project, links }, slug, created.id, created.content_hash);
85
+ links = updateLink({ ...project, links }, slug, created.id, created.content_hash, contentHash(taskForPush(created.task)), file);
84
86
  await saveLinks(project, links);
85
87
  await writeTaskFilePathAndRefreshHelper(file, created.task);
86
88
  rows.push({
@@ -89,6 +91,7 @@ export default class TasksPush extends BaseCommand {
89
91
  task_id: created.id,
90
92
  content_hash: created.content_hash,
91
93
  details: "created disabled; enable in Mechanic",
94
+ url: taskAdminUrl(project, created.id),
92
95
  });
93
96
  continue;
94
97
  }
@@ -109,7 +112,7 @@ export default class TasksPush extends BaseCommand {
109
112
  task,
110
113
  ...(force ? { force: true } : { previous_content_hash: link.last_remote_content_hash }),
111
114
  });
112
- links = updateLink({ ...project, links }, slug, updated.id, updated.content_hash);
115
+ links = updateLink({ ...project, links }, slug, updated.id, updated.content_hash, contentHash(taskForPush(updated.task)), file);
113
116
  await saveLinks(project, links);
114
117
  await writeTaskFilePathAndRefreshHelper(file, updated.task);
115
118
  rows.push({
@@ -118,6 +121,7 @@ export default class TasksPush extends BaseCommand {
118
121
  task_id: updated.id,
119
122
  content_hash: updated.content_hash,
120
123
  details: "",
124
+ url: taskAdminUrl(project, updated.id),
121
125
  });
122
126
  }
123
127
  return rows;
@@ -352,6 +356,17 @@ export default class TasksPush extends BaseCommand {
352
356
  ]),
353
357
  ];
354
358
  }
359
+ printCreatedTaskLinks(rows) {
360
+ const createdRows = rows.filter((row) => row.status === "created" && row.url);
361
+ if (createdRows.length === 0) {
362
+ return;
363
+ }
364
+ this.log("");
365
+ this.log(this.accent(createdRows.length === 1 ? "Open created task:" : "Open created tasks:"));
366
+ for (const row of createdRows) {
367
+ this.log(` ${this.taskName(row.file)} ${this.taskName(row.url || "")}`);
368
+ }
369
+ }
355
370
  planLabel(plan) {
356
371
  switch (plan) {
357
372
  case "would create":
@@ -16,7 +16,6 @@ export default class TasksStatus extends BaseCommand {
16
16
  static flags: {
17
17
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
18
  local: import("@oclif/core/interfaces").BooleanFlag<boolean>;
19
- remote: import("@oclif/core/interfaces").BooleanFlag<boolean>;
20
19
  };
21
20
  run(): Promise<void>;
22
21
  helperStatus(project: Project, file: string, task: JsonObject): Promise<HelperStatus>;
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/status.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAgB9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAE1D,KAAK,YAAY,GAAG;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAqBF,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,WAAW;IAClD,OAAgB,OAAO,SAAwE;IAC/F,OAAgB,WAAW,SAAkJ;IAE7K,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;;MAYnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IA2GpB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC;IA4BrF,YAAY,CAChB,MAAM,EAAE,cAAc,GAAG,IAAI,EAC7B,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,GACnC,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAsB/C,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAejC,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;CAgBnC"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/status.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAe9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAE1D,KAAK,YAAY,GAAG;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAuBF,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,WAAW;IAClD,OAAgB,OAAO,SAAwE;IAC/F,OAAgB,WAAW,SAA+Q;IAE1S,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;MAOnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAsHpB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC;IA4BrF,YAAY,CAChB,MAAM,EAAE,cAAc,GAAG,IAAI,EAC7B,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,GACnC,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAsB/C,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAejC,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;CAgBnC"}
@@ -2,16 +2,16 @@ import { Args, Flags } from "@oclif/core";
2
2
  import path from "node:path";
3
3
  import { BaseCommand } from "../../base-command.js";
4
4
  import { linkForSlug } from "../../config.js";
5
- import { CliError } from "../../errors.js";
6
5
  import { pathExists } from "../../fs.js";
7
6
  import { stableStringify } from "../../json.js";
8
7
  import { displayTaskPath, helperDirForTaskFile, readRawTaskFile, remoteChangedSinceLastPull, remoteChangedSinceLastPullDetails, selectedTaskFiles, slugFromTaskFile, taskForPush, unbundledHelperDirForTaskFile, validateTaskForPush, } from "../../tasks.js";
8
+ const IMPLICIT_REMOTE_CHECK_LIMIT = 25;
9
9
  function errorMessage(error) {
10
10
  return error instanceof Error ? error.message : String(error);
11
11
  }
12
12
  export default class TasksStatus extends BaseCommand {
13
13
  static summary = "Show whether local task files are ready and in sync with Mechanic.";
14
- static description = "Show whether local task files are linked, ready to publish, and in sync with their remote Mechanic tasks. Use --local to skip remote checks.";
14
+ 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.`;
15
15
  static args = {
16
16
  file: Args.string({ required: false, description: "Local task slug, task file, helper directory, or linked task ID. Defaults to every task JSON file." }),
17
17
  };
@@ -22,22 +22,15 @@ export default class TasksStatus extends BaseCommand {
22
22
  local: Flags.boolean({
23
23
  description: "Only check local files and links; do not call Mechanic.",
24
24
  }),
25
- remote: Flags.boolean({
26
- char: "r",
27
- description: "Check linked remote tasks. This is the default; kept for compatibility.",
28
- hidden: true,
29
- }),
30
25
  };
31
26
  async run() {
32
27
  const { args, flags } = await this.parse(TasksStatus);
33
- if (flags.local && flags.remote) {
34
- throw new CliError("Use either --local or --remote, not both.", 2);
35
- }
36
28
  const project = await this.loadProject();
37
29
  const files = await selectedTaskFiles(project, args.file, !args.file);
38
30
  const statuses = [];
39
31
  let client = null;
40
- const checkRemote = !flags.local;
32
+ const remoteSkippedForLimit = !flags.local && !args.file && files.length > IMPLICIT_REMOTE_CHECK_LIMIT;
33
+ const checkRemote = !flags.local && !remoteSkippedForLimit;
41
34
  const getClient = async () => {
42
35
  client ||= await this.verifiedClientForProject(project);
43
36
  return client;
@@ -95,11 +88,16 @@ export default class TasksStatus extends BaseCommand {
95
88
  });
96
89
  }
97
90
  if (flags.json) {
98
- this.outputJson({
91
+ const output = {
99
92
  shop_domain: project.shopDomain,
100
93
  remote_checked: checkRemote,
101
94
  tasks: statuses,
102
- });
95
+ };
96
+ if (remoteSkippedForLimit) {
97
+ output.remote_skipped_reason = "too_many_tasks";
98
+ output.remote_check_limit = IMPLICIT_REMOTE_CHECK_LIMIT;
99
+ }
100
+ this.outputJson(output);
103
101
  return;
104
102
  }
105
103
  const rows = checkRemote
@@ -117,6 +115,11 @@ export default class TasksStatus extends BaseCommand {
117
115
  row.push(status.link.remote_id ? this.taskId(project, status.link.remote_id) : "--", status.details.join("; "));
118
116
  rows.push(row);
119
117
  }
118
+ if (remoteSkippedForLimit) {
119
+ this.log(this.color("yellow", `Skipped remote checks for ${files.length} tasks (limit ${IMPLICIT_REMOTE_CHECK_LIMIT}).`));
120
+ this.log(`Run "${this.taskName("mechanic tasks status <task>")}" to check one task's remote state.`);
121
+ this.log("");
122
+ }
120
123
  this.table(rows);
121
124
  }
122
125
  async helperStatus(project, file, task) {
@@ -11,5 +11,6 @@ export default class TasksValidate extends BaseCommand {
11
11
  };
12
12
  run(): Promise<void>;
13
13
  validateTaskFile(file: string): Promise<ValidationResult>;
14
+ validateTaskDirectory(target: string): Promise<ValidationResult[]>;
14
15
  }
15
16
  //# sourceMappingURL=validate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/validate.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGpD,OAAO,EAKL,KAAK,gBAAgB,EACtB,MAAM,gBAAgB,CAAC;AAMxB,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,WAAW;IACpD,OAAgB,OAAO,SAAgF;IACvG,OAAgB,WAAW,SAA0G;IAErI,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;MAInB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAiDpB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAchE"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/validate.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGpD,OAAO,EAML,KAAK,gBAAgB,EACtB,MAAM,gBAAgB,CAAC;AAUxB,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,WAAW;IACpD,OAAgB,OAAO,SAA8D;IACrF,OAAgB,WAAW,SAAqH;IAEhJ,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;MAInB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAoDpB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAezD,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;CAgBzE"}
@@ -4,15 +4,18 @@ import path from "node:path";
4
4
  import { BaseCommand } from "../../base-command.js";
5
5
  import { CliError } from "../../errors.js";
6
6
  import { pathExists } from "../../fs.js";
7
- import { rawTaskFromHelperDir, readRawTaskFile, unbundledHelperDirForTaskFile, validateTask, } from "../../tasks.js";
7
+ import { rawTaskFromHelperDir, readRawTaskFile, resolveTaskSelector, unbundledHelperDirForTaskFile, validateTask, } from "../../tasks.js";
8
8
  function errorMessage(error) {
9
9
  return error instanceof Error ? error.message : String(error);
10
10
  }
11
+ function looksLikeValidationPath(input) {
12
+ return path.isAbsolute(input) || input.endsWith(".json") || input.includes("/") || input.includes(path.sep);
13
+ }
11
14
  export default class TasksValidate extends BaseCommand {
12
- static summary = "Validate one task file, helper directory, or directory of task JSON files.";
13
- static description = "Validate one canonical task JSON file, one helper task directory, or a directory of task JSON files.";
15
+ static summary = "Validate one local task or directory of task JSON files.";
16
+ static description = "Validate one local task slug, canonical task JSON file, helper task directory, or directory of task JSON files.";
14
17
  static args = {
15
- target: Args.string({ required: true, description: "Task JSON file, helper dir, or directory of task JSON files." }),
18
+ target: Args.string({ required: true, description: "Local task slug, task JSON file, helper dir, or directory of task JSON files." }),
16
19
  };
17
20
  static flags = {
18
21
  json: Flags.boolean({
@@ -23,25 +26,30 @@ export default class TasksValidate extends BaseCommand {
23
26
  const { args, flags } = await this.parse(TasksValidate);
24
27
  const target = path.resolve(args.target);
25
28
  const results = [];
26
- const stat = await fs.stat(target).catch((error) => {
27
- throw new CliError(`Unable to access ${target}: ${errorMessage(error)}`, 2);
28
- });
29
- if (stat.isFile()) {
29
+ const stat = await fs.stat(target).catch(() => null);
30
+ if (stat?.isFile()) {
30
31
  results.push(await this.validateTaskFile(target));
31
32
  }
32
- else if (await pathExists(path.join(target, "task.json"))) {
33
+ else if (stat?.isDirectory() && await pathExists(path.join(target, "task.json"))) {
33
34
  results.push(validateTask(await rawTaskFromHelperDir(target), target));
34
35
  }
36
+ else if (stat?.isDirectory()) {
37
+ results.push(...await this.validateTaskDirectory(target));
38
+ }
35
39
  else {
36
- const entries = await fs.readdir(target, { withFileTypes: true }).catch((error) => {
37
- throw new CliError(`Unable to read directory ${target}: ${errorMessage(error)}`, 2);
38
- });
39
- const files = entries
40
- .filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
41
- .map((entry) => path.join(target, entry.name))
42
- .sort();
43
- for (const file of files) {
44
- results.push(await this.validateTaskFile(file));
40
+ if (looksLikeValidationPath(args.target)) {
41
+ throw new CliError(`Unable to access ${target}: no such file or directory`, 2);
42
+ }
43
+ const project = await this.loadProject();
44
+ const selector = await resolveTaskSelector(project, args.target, { allowHelperDir: true });
45
+ if (selector.helperDir) {
46
+ results.push(validateTask(await rawTaskFromHelperDir(selector.helperDir), selector.helperDir));
47
+ }
48
+ else if (selector.file) {
49
+ results.push(await this.validateTaskFile(selector.file));
50
+ }
51
+ else {
52
+ throw new CliError(`No local task file or helper directory found for ${args.target}.`, 2);
45
53
  }
46
54
  }
47
55
  if (results.length === 0) {
@@ -75,4 +83,18 @@ export default class TasksValidate extends BaseCommand {
75
83
  }
76
84
  return result;
77
85
  }
86
+ async validateTaskDirectory(target) {
87
+ const entries = await fs.readdir(target, { withFileTypes: true }).catch((error) => {
88
+ throw new CliError(`Unable to read directory ${target}: ${errorMessage(error)}`, 2);
89
+ });
90
+ const files = entries
91
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
92
+ .map((entry) => path.join(target, entry.name))
93
+ .sort();
94
+ const results = [];
95
+ for (const file of files) {
96
+ results.push(await this.validateTaskFile(file));
97
+ }
98
+ return results;
99
+ }
78
100
  }
package/dist/config.d.ts CHANGED
@@ -11,5 +11,5 @@ export declare function loadProject(cwd?: string): Promise<Project>;
11
11
  export declare function saveLinks(project: Project, links: LinksFile): Promise<void>;
12
12
  export declare function linkForSlug(project: Project, slug: string): LinkEntry | null;
13
13
  export declare function slugForRemoteId(project: Project, remoteId: string): string | null;
14
- export declare function updateLink(project: Project, slug: string, remoteId: string, contentHash: string): LinksFile;
14
+ export declare function updateLink(project: Project, slug: string, remoteId: string, contentHash: string, localContentHash?: string, filePath?: string): LinksFile;
15
15
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEhF,eAAO,MAAM,oBAAoB,QAAkE,CAAC;AA0BpG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAyBzD;AA+BD,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,SAA4B,GAAG,MAAM,CAG9F;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAMrE;AAED,wBAAgB,aAAa,CAC3B,UAAU,EAAE,MAAM,EAClB,UAAU,SAAuB,EACjC,MAAM,SAA4D,GACjE,cAAc,CAOhB;AAED,wBAAgB,UAAU,IAAI,SAAS,CAEtC;AAkED,wBAAsB,WAAW,CAC/B,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,MAAM,EACf,KAAK,UAAQ,GACZ,OAAO,CAAC,OAAO,CAAC,CAyBlB;AAED,wBAAsB,4BAA4B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA0BhF;AAED,wBAAsB,WAAW,CAAC,GAAG,SAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAkCvE;AAED,wBAAsB,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAGjF;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAE5E;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEjF;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,CAW3G"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEhF,eAAO,MAAM,oBAAoB,QAAkE,CAAC;AA0BpG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAyBzD;AA+BD,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,SAA4B,GAAG,MAAM,CAG9F;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAMrE;AAED,wBAAgB,aAAa,CAC3B,UAAU,EAAE,MAAM,EAClB,UAAU,SAAuB,EACjC,MAAM,SAA4D,GACjE,cAAc,CAOhB;AAED,wBAAgB,UAAU,IAAI,SAAS,CAEtC;AA6ED,wBAAsB,WAAW,CAC/B,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,MAAM,EACf,KAAK,UAAQ,GACZ,OAAO,CAAC,OAAO,CAAC,CA2BlB;AAED,wBAAsB,4BAA4B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA0BhF;AAED,wBAAsB,WAAW,CAAC,GAAG,SAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAkCvE;AAED,wBAAsB,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAGjF;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAE5E;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEjF;AAyBD,wBAAgB,UAAU,CACxB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,gBAAgB,CAAC,EAAE,MAAM,EACzB,QAAQ,CAAC,EAAE,MAAM,GAChB,SAAS,CAmBX"}
package/dist/config.js CHANGED
@@ -1,3 +1,4 @@
1
+ import nodeFs from "node:fs";
1
2
  import path from "node:path";
2
3
  import { CliError } from "./errors.js";
3
4
  import { ensureDir, pathExists, readJson, readText, writeJson, writeText } from "./fs.js";
@@ -87,27 +88,31 @@ export function defaultConfig(shopDomain, apiBaseUrl = DEFAULT_API_BASE_URL, app
87
88
  export function emptyLinks() {
88
89
  return { tasks: {} };
89
90
  }
90
- function normalizeLinkEntry(slug, entry) {
91
+ function normalizeLinkEntry(slug, entry, tasksDirName) {
91
92
  if (!entry || typeof entry !== "object" || !entry.remote_id || !entry.last_remote_content_hash) {
92
93
  return null;
93
94
  }
94
- return [slug, {
95
- file: entry.file || `${DEFAULT_TASKS_DIR}/${slug}.json`,
96
- remote_id: entry.remote_id,
97
- last_remote_content_hash: entry.last_remote_content_hash,
98
- }];
95
+ const normalized = {
96
+ file: entry.file || `${tasksDirName}/${slug}.json`,
97
+ remote_id: entry.remote_id,
98
+ last_remote_content_hash: entry.last_remote_content_hash,
99
+ };
100
+ if (typeof entry.last_local_content_hash === "string") {
101
+ normalized.last_local_content_hash = entry.last_local_content_hash;
102
+ }
103
+ return [slug, normalized];
99
104
  }
100
- function normalizeLegacyLinkEntry(slug, entry) {
105
+ function normalizeLegacyLinkEntry(slug, entry, tasksDirName) {
101
106
  if (!entry || typeof entry !== "object" || !entry.task_id || !entry.last_remote_hash) {
102
107
  return null;
103
108
  }
104
109
  return [slug, {
105
- file: `${DEFAULT_TASKS_DIR}/${slug}.json`,
110
+ file: `${tasksDirName}/${slug}.json`,
106
111
  remote_id: entry.task_id,
107
112
  last_remote_content_hash: entry.last_remote_hash,
108
113
  }];
109
114
  }
110
- function normalizeLinks(value, shopDomain) {
115
+ function normalizeLinks(value, shopDomain, tasksDirName = DEFAULT_TASKS_DIR) {
111
116
  if (!value || typeof value !== "object") {
112
117
  return emptyLinks();
113
118
  }
@@ -115,7 +120,7 @@ function normalizeLinks(value, shopDomain) {
115
120
  if (links.tasks && typeof links.tasks === "object") {
116
121
  return {
117
122
  tasks: Object.fromEntries(Object.entries(links.tasks).flatMap(([slug, entry]) => {
118
- const normalized = normalizeLinkEntry(slug, entry);
123
+ const normalized = normalizeLinkEntry(slug, entry, tasksDirName);
119
124
  return normalized ? [normalized] : [];
120
125
  })),
121
126
  };
@@ -124,7 +129,7 @@ function normalizeLinks(value, shopDomain) {
124
129
  if (shopLinks && typeof shopLinks === "object") {
125
130
  return {
126
131
  tasks: Object.fromEntries(Object.entries(shopLinks).flatMap(([slug, entry]) => {
127
- const normalized = normalizeLegacyLinkEntry(slug, entry);
132
+ const normalized = normalizeLegacyLinkEntry(slug, entry, tasksDirName);
128
133
  return normalized ? [normalized] : [];
129
134
  })),
130
135
  };
@@ -143,7 +148,9 @@ export async function initProject(cwd, shopDomain, apiBaseUrl, appUrl, force = f
143
148
  }
144
149
  const existingConfig = force ? await readJson(configPath, {}) : {};
145
150
  const preserveLinks = force && existingConfig.shop_domain === shopDomain;
146
- const links = preserveLinks ? normalizeLinks(await readJson(linksPath, emptyLinks()), shopDomain) : emptyLinks();
151
+ const links = preserveLinks
152
+ ? normalizeLinks(await readJson(linksPath, emptyLinks()), shopDomain, config.tasks_dir)
153
+ : emptyLinks();
147
154
  config.api_base_url = normalizeApiBaseUrl(config.api_base_url);
148
155
  config.app_url = normalizeAppUrl(config.app_url || defaultAppUrl(config.shop_domain));
149
156
  await ensureDir(path.join(cwd, config.tasks_dir));
@@ -188,7 +195,7 @@ export async function loadProject(cwd = process.cwd()) {
188
195
  }
189
196
  const tasksDirName = config.tasks_dir || DEFAULT_TASKS_DIR;
190
197
  const linksPath = path.join(cwd, ".mechanic", "links.json");
191
- const links = normalizeLinks(await readJson(linksPath, emptyLinks()), config.shop_domain);
198
+ const links = normalizeLinks(await readJson(linksPath, emptyLinks()), config.shop_domain, tasksDirName);
192
199
  const apiBaseUrl = normalizeApiBaseUrl(process.env.MECHANIC_API_BASE_URL || config.api_base_url);
193
200
  const appUrl = normalizeAppUrl(process.env.MECHANIC_APP_URL || config.app_url || defaultAppUrl(config.shop_domain));
194
201
  return {
@@ -213,15 +220,40 @@ export function linkForSlug(project, slug) {
213
220
  export function slugForRemoteId(project, remoteId) {
214
221
  return Object.entries(project.links.tasks).find(([, link]) => link.remote_id === remoteId)?.[0] || null;
215
222
  }
216
- export function updateLink(project, slug, remoteId, contentHash) {
223
+ function realpathIfExists(filePath) {
224
+ try {
225
+ return nodeFs.realpathSync.native(filePath);
226
+ }
227
+ catch {
228
+ return filePath;
229
+ }
230
+ }
231
+ function relativeProjectPath(project, filePath) {
232
+ const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(project.cwd, filePath);
233
+ const relativePath = path.relative(project.cwd, absolutePath);
234
+ if (!relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
235
+ return relativePath.split(path.sep).join("/");
236
+ }
237
+ const realProjectCwd = realpathIfExists(project.cwd);
238
+ const realFilePath = realpathIfExists(absolutePath);
239
+ const realRelativePath = path.relative(realProjectCwd, realFilePath);
240
+ return realRelativePath.split(path.sep).join("/");
241
+ }
242
+ export function updateLink(project, slug, remoteId, contentHash, localContentHash, filePath) {
243
+ const existingFile = project.links.tasks[slug]?.file;
244
+ const file = filePath ? relativeProjectPath(project, filePath) : existingFile || `${project.tasksDirName}/${slug}.json`;
245
+ const entry = {
246
+ file,
247
+ remote_id: remoteId,
248
+ last_remote_content_hash: contentHash,
249
+ };
250
+ if (localContentHash && localContentHash !== contentHash) {
251
+ entry.last_local_content_hash = localContentHash;
252
+ }
217
253
  return {
218
254
  tasks: {
219
255
  ...project.links.tasks,
220
- [slug]: {
221
- file: `${project.tasksDirName}/${slug}.json`,
222
- remote_id: remoteId,
223
- last_remote_content_hash: contentHash,
224
- },
256
+ [slug]: entry,
225
257
  },
226
258
  };
227
259
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../src/tasks.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../src/tasks.ts"],"names":[],"mappings":"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"}
package/dist/tasks.js CHANGED
@@ -322,7 +322,7 @@ export function remoteChangedSinceLastPullDetails(force = false) {
322
322
  return force ? "remote changed since last pull; --force set" : "remote changed since last pull";
323
323
  }
324
324
  export function remoteChangedSinceLastPullMessage(relativeFile, remoteId) {
325
- return `${relativeFile} has remote changes since the last pull. Run "mechanic tasks diff ${relativeFile}" to review, then "mechanic tasks pull ${remoteId}" to keep the remote version, or re-run with --force only if the local file should win.`;
325
+ return `${relativeFile} has changes in Mechanic since the last pull. Run "mechanic tasks diff ${relativeFile}" to review, then "mechanic tasks pull ${remoteId}" to keep the Mechanic version, or re-run with --force only if the local file should win.`;
326
326
  }
327
327
  export function taskForPush(task) {
328
328
  const { enabled: _enabled, id: _id, remote_id: _remoteId, ...payload } = task;
@@ -439,6 +439,7 @@ export function validateTask(task, source) {
439
439
  export function validateTaskForPush(task, source) {
440
440
  return validateTask(task, source);
441
441
  }
442
+ const MAX_LINE_DIFF_CELLS = 250_000;
442
443
  function comparableTaskValue(value) {
443
444
  return value === undefined ? "(missing)" : String(stableStringify(value));
444
445
  }
@@ -489,6 +490,14 @@ function lineDiff(remoteLines, localLines) {
489
490
  return operations;
490
491
  }
491
492
  function formattedLineDiff(remoteLines, localLines) {
493
+ if ((remoteLines.length + 1) * (localLines.length + 1) > MAX_LINE_DIFF_CELLS) {
494
+ return [
495
+ "@@",
496
+ `- ${remoteLines.length} current Mechanic lines`,
497
+ `+ ${localLines.length} local file lines`,
498
+ "Large value changed; detailed line diff omitted to keep the CLI responsive.",
499
+ ];
500
+ }
492
501
  const operations = lineDiff(remoteLines, localLines);
493
502
  const changeIndexes = operations.flatMap((operation, index) => (operation.kind === "context" ? [] : [index]));
494
503
  if (changeIndexes.length === 0) {
package/dist/types.d.ts CHANGED
@@ -9,6 +9,7 @@ export type LinkEntry = {
9
9
  file: string;
10
10
  remote_id: string;
11
11
  last_remote_content_hash: string;
12
+ last_local_content_hash?: string;
12
13
  };
13
14
  export type LinksFile = {
14
15
  tasks: Record<string, LinkEntry>;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export type UpdateNotice = {
2
+ currentVersion: string;
3
+ latestVersion: string;
4
+ };
5
+ export type UpdateNoticeOptions = {
6
+ cachePath?: string;
7
+ currentVersion?: string;
8
+ fetchLatestVersion?: () => Promise<string | null>;
9
+ now?: Date;
10
+ };
11
+ export declare function isVersionNewer(candidate: string, current: string): boolean;
12
+ export declare function getUpdateNotice(options?: UpdateNoticeOptions): Promise<UpdateNotice | null>;
13
+ export declare function shouldCheckForUpdates(argv?: string[], env?: NodeJS.ProcessEnv, isTty?: boolean): boolean;
14
+ //# sourceMappingURL=update-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-check.d.ts","sourceRoot":"","sources":["../src/update-check.ts"],"names":[],"mappings":"AAwBA,MAAM,MAAM,YAAY,GAAG;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAClD,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,CAAC;AAsBF,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CA+B1E;AA2CD,wBAAsB,eAAe,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CA8CrG;AAED,wBAAgB,qBAAqB,CACnC,IAAI,WAAwB,EAC5B,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,KAAK,UAAgC,GACpC,OAAO,CAeT"}
@@ -0,0 +1,136 @@
1
+ import { createRequire } from "node:module";
2
+ import { appConfigPath, readJson, writeJson } from "./fs.js";
3
+ const require = createRequire(import.meta.url);
4
+ const packageJson = require("../package.json");
5
+ const PACKAGE_NAME = packageJson.name || "@lightward/mechanic-cli";
6
+ const CURRENT_VERSION = packageJson.version || "0.0.0";
7
+ const NPM_LATEST_URL = `https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME).replace("%40", "@")}/latest`;
8
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
9
+ const NOTIFY_INTERVAL_MS = 24 * 60 * 60 * 1000;
10
+ const FETCH_TIMEOUT_MS = 1500;
11
+ function parseVersion(version) {
12
+ const [core, prerelease] = version.trim().replace(/^v/i, "").split("-", 2);
13
+ const parts = core.split(".");
14
+ if (parts.length < 2 || parts.length > 3 || parts.some((part) => !/^\d+$/.test(part))) {
15
+ return null;
16
+ }
17
+ const numbers = parts.map((part) => Number.parseInt(part, 10));
18
+ return {
19
+ numbers: [
20
+ numbers[0] || 0,
21
+ numbers[1] || 0,
22
+ numbers[2] || 0,
23
+ ],
24
+ prerelease: prerelease || null,
25
+ };
26
+ }
27
+ export function isVersionNewer(candidate, current) {
28
+ const candidateParts = parseVersion(candidate);
29
+ const currentParts = parseVersion(current);
30
+ if (!candidateParts || !currentParts) {
31
+ return false;
32
+ }
33
+ for (const index of [0, 1, 2]) {
34
+ if (candidateParts.numbers[index] > currentParts.numbers[index]) {
35
+ return true;
36
+ }
37
+ if (candidateParts.numbers[index] < currentParts.numbers[index]) {
38
+ return false;
39
+ }
40
+ }
41
+ if (!candidateParts.prerelease && currentParts.prerelease) {
42
+ return true;
43
+ }
44
+ if (candidateParts.prerelease && !currentParts.prerelease) {
45
+ return false;
46
+ }
47
+ return Boolean(candidateParts.prerelease
48
+ && currentParts.prerelease
49
+ && candidateParts.prerelease > currentParts.prerelease);
50
+ }
51
+ function millisecondsSince(value, now) {
52
+ if (!value) {
53
+ return Number.POSITIVE_INFINITY;
54
+ }
55
+ const parsed = Date.parse(value);
56
+ return Number.isFinite(parsed) ? now.getTime() - parsed : Number.POSITIVE_INFINITY;
57
+ }
58
+ async function fetchLatestPackageVersion() {
59
+ const controller = new AbortController();
60
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
61
+ try {
62
+ const response = await fetch(NPM_LATEST_URL, {
63
+ headers: { Accept: "application/json" },
64
+ signal: controller.signal,
65
+ });
66
+ if (!response.ok) {
67
+ return null;
68
+ }
69
+ const body = await response.json();
70
+ return typeof body.version === "string" ? body.version : null;
71
+ }
72
+ catch {
73
+ return null;
74
+ }
75
+ finally {
76
+ clearTimeout(timeout);
77
+ }
78
+ }
79
+ async function writeCache(cachePath, cache) {
80
+ try {
81
+ await writeJson(cachePath, cache);
82
+ }
83
+ catch {
84
+ // Update checks are best-effort and must never affect command behavior.
85
+ }
86
+ }
87
+ export async function getUpdateNotice(options = {}) {
88
+ const now = options.now || new Date();
89
+ const currentVersion = options.currentVersion || CURRENT_VERSION;
90
+ const cachePath = options.cachePath || appConfigPath("update-check.json");
91
+ const fetchLatestVersion = options.fetchLatestVersion || fetchLatestPackageVersion;
92
+ const cache = await readJson(cachePath, {}).catch(() => ({}));
93
+ const checkedRecently = millisecondsSince(cache.checked_at, now) < CHECK_INTERVAL_MS;
94
+ let cacheChanged = false;
95
+ let latestVersion = cache.latest_version || null;
96
+ if (!checkedRecently) {
97
+ const fetchedVersion = await fetchLatestVersion();
98
+ cache.checked_at = now.toISOString();
99
+ cacheChanged = true;
100
+ if (fetchedVersion) {
101
+ latestVersion = fetchedVersion;
102
+ cache.latest_version = fetchedVersion;
103
+ }
104
+ }
105
+ if (!latestVersion || !isVersionNewer(latestVersion, currentVersion)) {
106
+ if (cacheChanged) {
107
+ await writeCache(cachePath, cache);
108
+ }
109
+ return null;
110
+ }
111
+ const notifiedRecently = millisecondsSince(cache.last_notified_at, now) < NOTIFY_INTERVAL_MS;
112
+ if (notifiedRecently) {
113
+ if (cacheChanged) {
114
+ await writeCache(cachePath, cache);
115
+ }
116
+ return null;
117
+ }
118
+ cache.last_notified_at = now.toISOString();
119
+ await writeCache(cachePath, cache);
120
+ return {
121
+ currentVersion,
122
+ latestVersion,
123
+ };
124
+ }
125
+ export function shouldCheckForUpdates(argv = process.argv.slice(2), env = process.env, isTty = process.stdout.isTTY === true) {
126
+ if (!isTty) {
127
+ return false;
128
+ }
129
+ if (env.CI
130
+ || env.GITHUB_ACTIONS
131
+ || env.MECHANIC_SKIP_UPDATE_CHECK === "1"
132
+ || env.MECHANIC_UPDATE_CHECK === "0") {
133
+ return false;
134
+ }
135
+ return !argv.some((arg) => arg === "--json" || arg.startsWith("--json="));
136
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightward/mechanic-cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.6",
4
4
  "description": "Develop, preview, diff, and publish Mechanic Shopify automation tasks from local files",
5
5
  "type": "module",
6
6
  "keywords": [