@lightward/mechanic-cli 0.1.3 → 0.1.4

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 (38) 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 +16 -1
  5. package/dist/client.d.ts +3 -1
  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 +6 -2
  10. package/dist/commands/doctor.d.ts.map +1 -1
  11. package/dist/commands/doctor.js +9 -7
  12. package/dist/commands/init.d.ts.map +1 -1
  13. package/dist/commands/init.js +6 -2
  14. package/dist/commands/tasks/diff.d.ts.map +1 -1
  15. package/dist/commands/tasks/diff.js +1 -1
  16. package/dist/commands/tasks/new.d.ts.map +1 -1
  17. package/dist/commands/tasks/new.js +2 -1
  18. package/dist/commands/tasks/pull.d.ts.map +1 -1
  19. package/dist/commands/tasks/pull.js +4 -2
  20. package/dist/commands/tasks/push.d.ts +2 -0
  21. package/dist/commands/tasks/push.d.ts.map +1 -1
  22. package/dist/commands/tasks/push.js +18 -3
  23. package/dist/commands/tasks/status.d.ts +0 -1
  24. package/dist/commands/tasks/status.d.ts.map +1 -1
  25. package/dist/commands/tasks/status.js +16 -13
  26. package/dist/commands/tasks/validate.d.ts +1 -0
  27. package/dist/commands/tasks/validate.d.ts.map +1 -1
  28. package/dist/commands/tasks/validate.js +40 -18
  29. package/dist/config.d.ts +1 -1
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/config.js +19 -11
  32. package/dist/tasks.js +1 -1
  33. package/dist/types.d.ts +1 -0
  34. package/dist/types.d.ts.map +1 -1
  35. package/dist/update-check.d.ts +14 -0
  36. package/dist/update-check.d.ts.map +1 -0
  37. package/dist/update-check.js +136 -0
  38. 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
  }
@@ -24,7 +39,7 @@ export class BaseCommand extends Command {
24
39
  const verification = await client.verifyAuth();
25
40
  const verifiedShopDomain = verification.shop?.shopify_domain;
26
41
  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);
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
@@ -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,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,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;IA4BpB,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;CASxC"}
@@ -14,14 +14,18 @@ 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
24
  if (!verifiedDomain) {
21
25
  throw new CliError("Token verification did not return a shop domain.");
22
26
  }
23
27
  if (verifiedDomain !== project.shopDomain) {
24
- throw new CliError(`Token belongs to ${verifiedDomain}, but this project is configured for ${project.shopDomain}.`);
28
+ throw new CliError("API token does not match this Mechanic CLI project. Use a token for the shop in mechanic.json.", 2);
25
29
  }
26
30
  await saveToken(project.shopDomain, token);
27
31
  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;IAoH1B,WAAW,CAAC,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM;CAUpD"}
@@ -60,14 +60,16 @@ export default class Doctor extends BaseCommand {
60
60
  const verifiedDomain = verification.shop?.shopify_domain;
61
61
  verifiedShopMatches = verifiedDomain === project.shopDomain;
62
62
  addRow("Token shop", verifiedShopMatches ? "ok" : "fail", verifiedDomain
63
- ? `${verifiedDomain}${verifiedShopMatches ? "" : ` does not match ${project.shopDomain}`}`
63
+ ? (verifiedShopMatches ? project.shopDomain : "API token does not match this project")
64
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);
65
+ if (verifiedShopMatches) {
66
+ const tokenDetails = [
67
+ verification.api_token?.name,
68
+ verification.api_token?.created_by ? `created by ${verification.api_token.created_by}` : null,
69
+ ].filter(Boolean).join(", ");
70
+ if (tokenDetails) {
71
+ addRow("Token identity", "ok", tokenDetails);
72
+ }
71
73
  }
72
74
  }
73
75
  catch (error) {
@@ -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;CAoE3B"}
@@ -28,14 +28,18 @@ 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
38
  if (!verifiedDomain) {
35
39
  throw new CliError("Token verification did not return a shop domain.");
36
40
  }
37
41
  if (verifiedDomain !== flags.shop) {
38
- throw new CliError(`Token belongs to ${verifiedDomain}, but this project is configured for ${flags.shop}.`);
42
+ throw new CliError("API token does not match the requested shop. Check --shop or use a token for that shop.", 2);
39
43
  }
40
44
  }
41
45
  const project = await initProject(process.cwd(), flags.shop, flags["api-base-url"], flags["app-url"], Boolean(flags.force));
@@ -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;CAoH3B"}
@@ -86,7 +86,7 @@ export default class TasksDiff extends BaseCommand {
86
86
  if (remoteChanged || diff) {
87
87
  const section = [`# ${relativeFile}`];
88
88
  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.`), "");
89
+ 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
90
  }
91
91
  if (diff) {
92
92
  section.push(diff);
@@ -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;CAsG3B"}
@@ -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)));
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":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAkBpD,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;CACjB,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;IAwCvE,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAgE9F,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,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAehC"}
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;IA8E9F,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)));
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)));
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): 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":"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;AAwED,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,CACxB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,gBAAgB,CAAC,EAAE,MAAM,GACxB,SAAS,CAiBX"}
package/dist/config.js CHANGED
@@ -91,11 +91,15 @@ function normalizeLinkEntry(slug, entry) {
91
91
  if (!entry || typeof entry !== "object" || !entry.remote_id || !entry.last_remote_content_hash) {
92
92
  return null;
93
93
  }
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
- }];
94
+ const normalized = {
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
+ };
99
+ if (typeof entry.last_local_content_hash === "string") {
100
+ normalized.last_local_content_hash = entry.last_local_content_hash;
101
+ }
102
+ return [slug, normalized];
99
103
  }
100
104
  function normalizeLegacyLinkEntry(slug, entry) {
101
105
  if (!entry || typeof entry !== "object" || !entry.task_id || !entry.last_remote_hash) {
@@ -213,15 +217,19 @@ export function linkForSlug(project, slug) {
213
217
  export function slugForRemoteId(project, remoteId) {
214
218
  return Object.entries(project.links.tasks).find(([, link]) => link.remote_id === remoteId)?.[0] || null;
215
219
  }
216
- export function updateLink(project, slug, remoteId, contentHash) {
220
+ export function updateLink(project, slug, remoteId, contentHash, localContentHash) {
221
+ const entry = {
222
+ file: `${project.tasksDirName}/${slug}.json`,
223
+ remote_id: remoteId,
224
+ last_remote_content_hash: contentHash,
225
+ };
226
+ if (localContentHash && localContentHash !== contentHash) {
227
+ entry.last_local_content_hash = localContentHash;
228
+ }
217
229
  return {
218
230
  tasks: {
219
231
  ...project.links.tasks,
220
- [slug]: {
221
- file: `${project.tasksDirName}/${slug}.json`,
222
- remote_id: remoteId,
223
- last_remote_content_hash: contentHash,
224
- },
232
+ [slug]: entry,
225
233
  },
226
234
  };
227
235
  }
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;
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.4",
4
4
  "description": "Develop, preview, diff, and publish Mechanic Shopify automation tasks from local files",
5
5
  "type": "module",
6
6
  "keywords": [