@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.
- package/README.md +10 -6
- package/dist/base-command.d.ts +1 -0
- package/dist/base-command.d.ts.map +1 -1
- package/dist/base-command.js +17 -2
- package/dist/client.d.ts +6 -4
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +4 -1
- package/dist/commands/auth/login.d.ts.map +1 -1
- package/dist/commands/auth/login.js +7 -6
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +13 -9
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +7 -6
- package/dist/commands/tasks/bundle.d.ts.map +1 -1
- package/dist/commands/tasks/bundle.js +17 -8
- package/dist/commands/tasks/diff.d.ts.map +1 -1
- package/dist/commands/tasks/diff.js +11 -2
- package/dist/commands/tasks/new.d.ts.map +1 -1
- package/dist/commands/tasks/new.js +2 -1
- package/dist/commands/tasks/pull.d.ts.map +1 -1
- package/dist/commands/tasks/pull.js +4 -2
- package/dist/commands/tasks/push.d.ts +2 -0
- package/dist/commands/tasks/push.d.ts.map +1 -1
- package/dist/commands/tasks/push.js +18 -3
- package/dist/commands/tasks/status.d.ts +0 -1
- package/dist/commands/tasks/status.d.ts.map +1 -1
- package/dist/commands/tasks/status.js +16 -13
- package/dist/commands/tasks/validate.d.ts +1 -0
- package/dist/commands/tasks/validate.d.ts.map +1 -1
- package/dist/commands/tasks/validate.js +40 -18
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +51 -19
- package/dist/tasks.d.ts.map +1 -1
- package/dist/tasks.js +10 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/update-check.d.ts +14 -0
- package/dist/update-check.d.ts.map +1 -0
- package/dist/update-check.js +136 -0
- 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 <
|
|
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
|
|
170
|
-
|
|
171
|
-
|
|
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
|
|
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.
|
package/dist/base-command.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/base-command.js
CHANGED
|
@@ -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(
|
|
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
|
|
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>;
|
package/dist/client.d.ts.map
CHANGED
|
@@ -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;
|
|
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;
|
|
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({
|
|
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 (
|
|
21
|
-
throw new CliError("
|
|
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;
|
|
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"}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -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
|
-
?
|
|
64
|
-
:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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;
|
|
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"}
|
package/dist/commands/init.js
CHANGED
|
@@ -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({
|
|
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 (
|
|
35
|
-
throw new CliError("
|
|
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;
|
|
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
|
|
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:
|
|
63
|
-
outputDisplay: out || `${
|
|
71
|
+
inputDisplay: normalizedInput,
|
|
72
|
+
outputDisplay: out || `${normalizedInput}.json`,
|
|
64
73
|
};
|
|
65
74
|
}
|
|
66
|
-
if (!looksLikeTaskPath(
|
|
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 (!
|
|
103
|
+
if (!normalizedInput.toLowerCase().endsWith(".json")) {
|
|
95
104
|
return {
|
|
96
105
|
dirPath: inputPath,
|
|
97
|
-
inputDisplay:
|
|
98
|
-
outputDisplay: out || `${
|
|
106
|
+
inputDisplay: normalizedInput,
|
|
107
|
+
outputDisplay: out || `${normalizedInput}.json`,
|
|
99
108
|
};
|
|
100
109
|
}
|
|
101
|
-
const helperInput =
|
|
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;
|
|
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
|
|
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
|
-
|
|
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;
|
|
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)}
|
|
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;
|
|
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
|
-
|
|
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":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/push.ts"],"names":[],"mappings":"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"}
|
|
@@ -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;
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
|
13
|
-
static description = "Validate one canonical task JSON file,
|
|
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: "
|
|
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((
|
|
27
|
-
|
|
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
|
-
|
|
37
|
-
throw new CliError(`Unable to
|
|
38
|
-
}
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
.
|
|
43
|
-
|
|
44
|
-
|
|
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
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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: `${
|
|
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
|
|
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
|
-
|
|
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
|
}
|
package/dist/tasks.d.ts.map
CHANGED
|
@@ -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
|
|
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
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEjD,MAAM,MAAM,cAAc,GAAG;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,wBAAwB,EAAE,MAAM,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,SAAS,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,UAAU,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,IAAI,EAAE;QACJ,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,YAAY,EAAE,MAAM,EAAE,CAAC;QACvB,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACpC,8BAA8B,EAAE,MAAM,EAAE,CAAC;KAC1C,CAAC;IACF,MAAM,EAAE;QACN,IAAI,EAAE,aAAa,GAAG,+BAA+B,GAAG,gBAAgB,CAAC;QACzE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,SAAS,EAAE,OAAO,CAAC;QACnB,cAAc,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;KACjC,CAAC;IACF,OAAO,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,gBAAgB,EAAE,MAAM,CAAC;QACzB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,2BAA2B,EAAE,OAAO,CAAC;KACtC,CAAC;IACF,WAAW,EAAE;QACX,mBAAmB,EAAE,MAAM,EAAE,CAAC;QAC9B,OAAO,EAAE;YACP,GAAG,EAAE,MAAM,EAAE,CAAC;YACd,YAAY,EAAE,MAAM,EAAE,CAAC;SACxB,CAAC;QACF,OAAO,EAAE;YACP,GAAG,EAAE,MAAM,EAAE,CAAC;YACd,YAAY,EAAE,MAAM,EAAE,CAAC;SACxB,CAAC;QACF,QAAQ,EAAE;YACR,QAAQ,EAAE,OAAO,CAAC;YAClB,oBAAoB,EAAE,OAAO,CAAC;YAC9B,MAAM,EAAE,MAAM,CAAC;YACf,OAAO,EAAE,MAAM,CAAC;SACjB,CAAC;QACF,QAAQ,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;IACF,WAAW,CAAC,EAAE,KAAK,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;QACvC,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC,CAAC;IACH,MAAM,EAAE,KAAK,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;QACjC,SAAS,EAAE,KAAK,CAAC;YACf,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;YACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;YACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;YACvB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;YAC7C,WAAW,EAAE,KAAK,CAAC;gBACjB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;gBAC5B,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;gBACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;gBACtB,MAAM,CAAC,EAAE,OAAO,CAAC;gBACjB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;aAC9C,CAAC,CAAC;SACJ,CAAC,CAAC;KACJ,CAAC,CAAC;CACJ,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE;QACJ,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,KAAK,EAAE;QACL,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC;IACF,OAAO,EAAE;QACP,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;KAC5B,CAAC;IACF,OAAO,EAAE;QACP,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,cAAc,EAAE;YAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAChE,OAAO,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QACzE,SAAS,EAAE;YAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;KACxG,CAAC;IACF,GAAG,EAAE;QACH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;KAC9B,CAAC;IACF,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC"}
|
|
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
|
+
}
|