@lightward/mechanic-cli 0.1.4 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -7
- package/dist/base-command.js +1 -1
- package/dist/client.d.ts +3 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/commands/auth/login.d.ts.map +1 -1
- package/dist/commands/auth/login.js +1 -4
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +4 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +1 -4
- 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 +24 -4
- package/dist/commands/tasks/pull.d.ts.map +1 -1
- package/dist/commands/tasks/pull.js +2 -2
- package/dist/commands/tasks/push.d.ts.map +1 -1
- package/dist/commands/tasks/push.js +6 -6
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +35 -11
- package/dist/tasks.d.ts +2 -1
- package/dist/tasks.d.ts.map +1 -1
- package/dist/tasks.js +22 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -184,10 +184,11 @@ the fast offline check for JSON validity, link state, and helper-folder drift.
|
|
|
184
184
|
Add `--json` when an editor integration needs task readiness, link state, and
|
|
185
185
|
remote sync state without parsing table output.
|
|
186
186
|
|
|
187
|
-
`tasks diff` compares
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
differences as a
|
|
187
|
+
`tasks diff` compares current Mechanic with your local file, groups output by
|
|
188
|
+
changed field, and only shows nearby changed lines. In diff output, `-` means
|
|
189
|
+
current Mechanic and `+` means local file. Differences are informational by
|
|
190
|
+
default; add `--exit-code` when a script or CI job should treat differences as a
|
|
191
|
+
nonzero result.
|
|
191
192
|
|
|
192
193
|
`tasks preview` is the confidence check before publishing. It sends one local
|
|
193
194
|
task to Mechanic's preview engine without saving it, then reports whether the
|
|
@@ -412,12 +413,15 @@ Mechanic app or by another local checkout.
|
|
|
412
413
|
|
|
413
414
|
- `tasks pull` records the latest remote content hash in `.mechanic/links.json`.
|
|
414
415
|
- `tasks pull <task>` can refresh a linked local file without `--force` when the
|
|
415
|
-
local file still matches the last
|
|
416
|
+
local file still matches the last synced hash. If local edits would be lost,
|
|
416
417
|
it stops and asks for an explicit `--force`.
|
|
417
418
|
- `tasks publish` sends that hash as `previous_content_hash`.
|
|
418
419
|
- The API rejects stale publishes with a conflict.
|
|
419
|
-
- `tasks diff` warns when
|
|
420
|
-
compares
|
|
420
|
+
- `tasks diff` warns when Mechanic changed since the file was last synced, then
|
|
421
|
+
compares current Mechanic with your local file. If only Mechanic changed, pull
|
|
422
|
+
normally to update your local file. If both Mechanic and your local file
|
|
423
|
+
changed, choose explicitly: pull with `--force` to replace local with
|
|
424
|
+
Mechanic, or publish with `--force` to overwrite Mechanic with local.
|
|
421
425
|
- `tasks publish --all` checks every linked remote task before writing anything,
|
|
422
426
|
so one stale task blocks the whole publish instead of partially publishing
|
|
423
427
|
earlier files first.
|
package/dist/base-command.js
CHANGED
|
@@ -38,7 +38,7 @@ export class BaseCommand extends Command {
|
|
|
38
38
|
async verifyClientShop(project, client) {
|
|
39
39
|
const verification = await client.verifyAuth();
|
|
40
40
|
const verifiedShopDomain = verification.shop?.shopify_domain;
|
|
41
|
-
if (verifiedShopDomain !== project.shopDomain) {
|
|
41
|
+
if (verifiedShopDomain && verifiedShopDomain !== project.shopDomain) {
|
|
42
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);
|
|
43
43
|
}
|
|
44
44
|
}
|
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;
|
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"}
|
|
@@ -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"}
|
|
@@ -21,10 +21,7 @@ export default class AuthLogin extends BaseCommand {
|
|
|
21
21
|
});
|
|
22
22
|
const verification = await client.verifyAuth();
|
|
23
23
|
const verifiedDomain = verification.shop?.shopify_domain;
|
|
24
|
-
if (
|
|
25
|
-
throw new CliError("Token verification did not return a shop domain.");
|
|
26
|
-
}
|
|
27
|
-
if (verifiedDomain !== project.shopDomain) {
|
|
24
|
+
if (verifiedDomain && verifiedDomain !== project.shopDomain) {
|
|
28
25
|
throw new CliError("API token does not match this Mechanic CLI project. Use a token for the shop in mechanic.json.", 2);
|
|
29
26
|
}
|
|
30
27
|
await saveToken(project.shopDomain, token);
|
|
@@ -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,15 +53,16 @@ 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
|
? (verifiedShopMatches ? project.shopDomain : "API token does not match this project")
|
|
64
|
-
:
|
|
65
|
+
: `verified for ${project.shopDomain}`);
|
|
65
66
|
if (verifiedShopMatches) {
|
|
66
67
|
const tokenDetails = [
|
|
67
68
|
verification.api_token?.name,
|
|
@@ -79,6 +80,7 @@ export default class Doctor extends BaseCommand {
|
|
|
79
80
|
if (token && verifiedShopMatches) {
|
|
80
81
|
const client = new MechanicClient({
|
|
81
82
|
baseUrl: project.apiBaseUrl,
|
|
83
|
+
expectedShopDomain: project.shopDomain,
|
|
82
84
|
token,
|
|
83
85
|
});
|
|
84
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
|
@@ -35,10 +35,7 @@ export default class Init extends BaseCommand {
|
|
|
35
35
|
});
|
|
36
36
|
const verification = await client.verifyAuth();
|
|
37
37
|
const verifiedDomain = verification.shop?.shopify_domain;
|
|
38
|
-
if (
|
|
39
|
-
throw new CliError("Token verification did not return a shop domain.");
|
|
40
|
-
}
|
|
41
|
-
if (verifiedDomain !== flags.shop) {
|
|
38
|
+
if (verifiedDomain && verifiedDomain !== flags.shop) {
|
|
42
39
|
throw new CliError("API token does not match the requested shop. Check --shop or use a token for that shop.", 2);
|
|
43
40
|
}
|
|
44
41
|
}
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/diff.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAapD,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;CAuI3B"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Args, Flags } from "@oclif/core";
|
|
2
2
|
import { BaseCommand } from "../../base-command.js";
|
|
3
3
|
import { linkForSlug } from "../../config.js";
|
|
4
|
-
import { diffTasks, displayTaskPath, readTaskFile, remoteChangedSinceLastPull, selectedTaskFiles, slugFromTaskFile, taskForPush, } from "../../tasks.js";
|
|
4
|
+
import { diffTasks, displayTaskPath, localChangedSinceLastSync, readTaskFile, remoteChangedSinceLastPull, selectedTaskFiles, slugFromTaskFile, taskForPush, } from "../../tasks.js";
|
|
5
5
|
export default class TasksDiff extends BaseCommand {
|
|
6
6
|
static summary = "Diff one task, or use --all to diff every local task.";
|
|
7
7
|
static description = "Compare one local task with its linked remote task, or use --all to compare every local task JSON file.";
|
|
@@ -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,
|
|
@@ -67,6 +69,7 @@ export default class TasksDiff extends BaseCommand {
|
|
|
67
69
|
const remoteTask = taskForPush(remote.task);
|
|
68
70
|
const diff = diffTasks(localTask, remoteTask);
|
|
69
71
|
const remoteChanged = remoteChangedSinceLastPull(link, remote);
|
|
72
|
+
const localChanged = localChangedSinceLastSync(link, localTask);
|
|
70
73
|
const hasDiff = Boolean(diff);
|
|
71
74
|
if (remoteChanged) {
|
|
72
75
|
hasRemoteChanges = true;
|
|
@@ -86,13 +89,23 @@ export default class TasksDiff extends BaseCommand {
|
|
|
86
89
|
if (remoteChanged || diff) {
|
|
87
90
|
const section = [`# ${relativeFile}`];
|
|
88
91
|
if (remoteChanged) {
|
|
89
|
-
section.push(this.color("yellow",
|
|
92
|
+
section.push(this.color("yellow", localChanged
|
|
93
|
+
? "Mechanic and your local file both changed since this file was last synced."
|
|
94
|
+
: "Mechanic changed since this file was last synced."), this.muted("Showing current Mechanic compared with your local file."), this.muted("Diff legend: - current Mechanic, + local file."), ...(localChanged ? [
|
|
95
|
+
this.muted("This compares the current versions; it does not show edit history."),
|
|
96
|
+
this.muted("A normal pull will stop because local changes would be overwritten."),
|
|
97
|
+
this.muted(`Run "mechanic tasks pull ${link.remote_id} --force" only if current Mechanic should replace your local file.`),
|
|
98
|
+
this.muted(`Run "mechanic tasks publish ${relativeFile} --force" only if your local file should overwrite Mechanic.`),
|
|
99
|
+
] : [
|
|
100
|
+
this.muted("Your local file has no unsynced changes."),
|
|
101
|
+
this.muted(`Run "mechanic tasks pull ${link.remote_id}" to update your local file from current Mechanic.`),
|
|
102
|
+
]), "");
|
|
90
103
|
}
|
|
91
104
|
if (diff) {
|
|
92
105
|
section.push(diff);
|
|
93
106
|
}
|
|
94
107
|
else {
|
|
95
|
-
section.push("No field differences between
|
|
108
|
+
section.push("No field differences between current Mechanic and your local file.");
|
|
96
109
|
}
|
|
97
110
|
differences.push(this.colorDiff(section.join("\n")));
|
|
98
111
|
}
|
|
@@ -116,7 +129,14 @@ export default class TasksDiff extends BaseCommand {
|
|
|
116
129
|
return;
|
|
117
130
|
}
|
|
118
131
|
this.log(differences.join("\n\n"));
|
|
119
|
-
|
|
132
|
+
let summary = "Differences found.";
|
|
133
|
+
if (!hasFieldDifferences && hasRemoteChanges) {
|
|
134
|
+
summary = "Remote changes found.";
|
|
135
|
+
}
|
|
136
|
+
else if (!hasFieldDifferences && hasUnlinkedTasks) {
|
|
137
|
+
summary = "Unlinked tasks found.";
|
|
138
|
+
}
|
|
139
|
+
this.log(summary);
|
|
120
140
|
if (flags["exit-code"]) {
|
|
121
141
|
process.exitCode = 1;
|
|
122
142
|
}
|
|
@@ -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"}
|
|
@@ -68,13 +68,13 @@ export default class TasksPull extends BaseCommand {
|
|
|
68
68
|
throw new CliError([
|
|
69
69
|
`${displayTaskPath(project, filePath)} has local changes that would be overwritten.`,
|
|
70
70
|
`Run "mechanic tasks diff ${displayTaskPath(project, filePath)}" to review them.`,
|
|
71
|
-
`Re-run "mechanic tasks pull ${envelope.id} --force" only if
|
|
71
|
+
`Re-run "mechanic tasks pull ${envelope.id} --force" only if current Mechanic should replace your local file.`,
|
|
72
72
|
].join("\n"), 2);
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
await writeTaskFilePathAndRefreshHelper(filePath, envelope.task);
|
|
77
|
-
links = updateLink({ ...project, links }, slug, envelope.id, envelope.content_hash, contentHash(taskForPush(envelope.task)));
|
|
77
|
+
links = updateLink({ ...project, links }, slug, envelope.id, envelope.content_hash, contentHash(taskForPush(envelope.task)), filePath);
|
|
78
78
|
await saveLinks(project, links);
|
|
79
79
|
rows.push([
|
|
80
80
|
this.taskId(project, envelope.id),
|
|
@@ -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":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAoBpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnF,KAAK,YAAY,GAAG;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,UAAU,CAAC;CAClB,CAAC;AAEF,KAAK,aAAa,GAAG;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,IAAI,EAAE,OAAO,EAAE,CAAC;CACjB,CAAC;AAEF,KAAK,OAAO,GAAG;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAgBF,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,WAAW;IAChD,OAAgB,MAAM,UAAQ;IAC9B,OAAgB,OAAO,SAAuE;IAC9F,OAAgB,WAAW,SAA6G;IAExI,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;;;MAKnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAMpB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAyCvE,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAgF9F,sBAAsB,CAC1B,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,SAAS,EAChB,aAAa,EAAE,YAAY,EAAE,GAC5B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAkC/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"}
|
|
@@ -5,7 +5,7 @@ import { linkForSlug, saveLinks, taskAdminUrl, updateLink } from "../../config.j
|
|
|
5
5
|
import { CliError } from "../../errors.js";
|
|
6
6
|
import { contentHash } from "../../hash.js";
|
|
7
7
|
import { stableStringify } from "../../json.js";
|
|
8
|
-
import { displayTaskPath, remoteChangedSinceLastPull, remoteChangedSinceLastPullDetails, remoteChangedSinceLastPullMessage, selectedTaskFiles, readRawTaskFile, slugFromTaskFile, taskForPush, taskSlug, unbundledHelperDirForTaskFile, validateTaskForPush, writeTaskFilePathAndRefreshHelper, } from "../../tasks.js";
|
|
8
|
+
import { displayTaskPath, localChangedSinceLastSync, remoteChangedSinceLastPull, remoteChangedSinceLastPullDetails, remoteChangedSinceLastPullMessage, selectedTaskFiles, readRawTaskFile, slugFromTaskFile, taskForPush, taskSlug, unbundledHelperDirForTaskFile, validateTaskForPush, writeTaskFilePathAndRefreshHelper, } from "../../tasks.js";
|
|
9
9
|
function createTaskIdempotencyKey(shopDomain, slug) {
|
|
10
10
|
const hex = createHash("sha256")
|
|
11
11
|
.update(`mechanic-cli-task-create\0${shopDomain}\0${slug}`)
|
|
@@ -82,7 +82,7 @@ export default class TasksPush extends BaseCommand {
|
|
|
82
82
|
const link = linkForSlug({ ...project, links }, slug);
|
|
83
83
|
if (!link) {
|
|
84
84
|
const created = await client.createTask(task, createTaskIdempotencyKey(project.shopDomain, slug));
|
|
85
|
-
links = updateLink({ ...project, links }, slug, created.id, created.content_hash, contentHash(taskForPush(created.task)));
|
|
85
|
+
links = updateLink({ ...project, links }, slug, created.id, created.content_hash, contentHash(taskForPush(created.task)), file);
|
|
86
86
|
await saveLinks(project, links);
|
|
87
87
|
await writeTaskFilePathAndRefreshHelper(file, created.task);
|
|
88
88
|
rows.push({
|
|
@@ -112,7 +112,7 @@ export default class TasksPush extends BaseCommand {
|
|
|
112
112
|
task,
|
|
113
113
|
...(force ? { force: true } : { previous_content_hash: link.last_remote_content_hash }),
|
|
114
114
|
});
|
|
115
|
-
links = updateLink({ ...project, links }, slug, updated.id, updated.content_hash, contentHash(taskForPush(updated.task)));
|
|
115
|
+
links = updateLink({ ...project, links }, slug, updated.id, updated.content_hash, contentHash(taskForPush(updated.task)), file);
|
|
116
116
|
await saveLinks(project, links);
|
|
117
117
|
await writeTaskFilePathAndRefreshHelper(file, updated.task);
|
|
118
118
|
rows.push({
|
|
@@ -129,7 +129,7 @@ export default class TasksPush extends BaseCommand {
|
|
|
129
129
|
async preflightRemoteChanges(client, project, links, preparedTasks) {
|
|
130
130
|
const remoteBySlug = new Map();
|
|
131
131
|
const conflicts = [];
|
|
132
|
-
for (const { slug, relativeFile } of preparedTasks) {
|
|
132
|
+
for (const { slug, relativeFile, task } of preparedTasks) {
|
|
133
133
|
const link = linkForSlug({ ...project, links }, slug);
|
|
134
134
|
if (!link) {
|
|
135
135
|
continue;
|
|
@@ -137,12 +137,12 @@ export default class TasksPush extends BaseCommand {
|
|
|
137
137
|
const remote = await client.getTask(link.remote_id);
|
|
138
138
|
remoteBySlug.set(slug, remote);
|
|
139
139
|
if (remoteChangedSinceLastPull(link, remote)) {
|
|
140
|
-
conflicts.push(remoteChangedSinceLastPullMessage(relativeFile, link.remote_id));
|
|
140
|
+
conflicts.push(remoteChangedSinceLastPullMessage(relativeFile, link.remote_id, localChangedSinceLastSync(link, task)));
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
if (conflicts.length > 0) {
|
|
144
144
|
throw new CliError([
|
|
145
|
-
"
|
|
145
|
+
"Mechanic changes found before publishing. Nothing was published.",
|
|
146
146
|
"",
|
|
147
147
|
...conflicts.map((conflict) => `- ${conflict}`),
|
|
148
148
|
].join("\n"), 2);
|
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, localContentHash?: 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,12 +88,12 @@ 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
|
const normalized = {
|
|
95
|
-
file: entry.file || `${
|
|
96
|
+
file: entry.file || `${tasksDirName}/${slug}.json`,
|
|
96
97
|
remote_id: entry.remote_id,
|
|
97
98
|
last_remote_content_hash: entry.last_remote_content_hash,
|
|
98
99
|
};
|
|
@@ -101,17 +102,17 @@ function normalizeLinkEntry(slug, entry) {
|
|
|
101
102
|
}
|
|
102
103
|
return [slug, normalized];
|
|
103
104
|
}
|
|
104
|
-
function normalizeLegacyLinkEntry(slug, entry) {
|
|
105
|
+
function normalizeLegacyLinkEntry(slug, entry, tasksDirName) {
|
|
105
106
|
if (!entry || typeof entry !== "object" || !entry.task_id || !entry.last_remote_hash) {
|
|
106
107
|
return null;
|
|
107
108
|
}
|
|
108
109
|
return [slug, {
|
|
109
|
-
file: `${
|
|
110
|
+
file: `${tasksDirName}/${slug}.json`,
|
|
110
111
|
remote_id: entry.task_id,
|
|
111
112
|
last_remote_content_hash: entry.last_remote_hash,
|
|
112
113
|
}];
|
|
113
114
|
}
|
|
114
|
-
function normalizeLinks(value, shopDomain) {
|
|
115
|
+
function normalizeLinks(value, shopDomain, tasksDirName = DEFAULT_TASKS_DIR) {
|
|
115
116
|
if (!value || typeof value !== "object") {
|
|
116
117
|
return emptyLinks();
|
|
117
118
|
}
|
|
@@ -119,7 +120,7 @@ function normalizeLinks(value, shopDomain) {
|
|
|
119
120
|
if (links.tasks && typeof links.tasks === "object") {
|
|
120
121
|
return {
|
|
121
122
|
tasks: Object.fromEntries(Object.entries(links.tasks).flatMap(([slug, entry]) => {
|
|
122
|
-
const normalized = normalizeLinkEntry(slug, entry);
|
|
123
|
+
const normalized = normalizeLinkEntry(slug, entry, tasksDirName);
|
|
123
124
|
return normalized ? [normalized] : [];
|
|
124
125
|
})),
|
|
125
126
|
};
|
|
@@ -128,7 +129,7 @@ function normalizeLinks(value, shopDomain) {
|
|
|
128
129
|
if (shopLinks && typeof shopLinks === "object") {
|
|
129
130
|
return {
|
|
130
131
|
tasks: Object.fromEntries(Object.entries(shopLinks).flatMap(([slug, entry]) => {
|
|
131
|
-
const normalized = normalizeLegacyLinkEntry(slug, entry);
|
|
132
|
+
const normalized = normalizeLegacyLinkEntry(slug, entry, tasksDirName);
|
|
132
133
|
return normalized ? [normalized] : [];
|
|
133
134
|
})),
|
|
134
135
|
};
|
|
@@ -147,7 +148,9 @@ export async function initProject(cwd, shopDomain, apiBaseUrl, appUrl, force = f
|
|
|
147
148
|
}
|
|
148
149
|
const existingConfig = force ? await readJson(configPath, {}) : {};
|
|
149
150
|
const preserveLinks = force && existingConfig.shop_domain === shopDomain;
|
|
150
|
-
const links = preserveLinks
|
|
151
|
+
const links = preserveLinks
|
|
152
|
+
? normalizeLinks(await readJson(linksPath, emptyLinks()), shopDomain, config.tasks_dir)
|
|
153
|
+
: emptyLinks();
|
|
151
154
|
config.api_base_url = normalizeApiBaseUrl(config.api_base_url);
|
|
152
155
|
config.app_url = normalizeAppUrl(config.app_url || defaultAppUrl(config.shop_domain));
|
|
153
156
|
await ensureDir(path.join(cwd, config.tasks_dir));
|
|
@@ -192,7 +195,7 @@ export async function loadProject(cwd = process.cwd()) {
|
|
|
192
195
|
}
|
|
193
196
|
const tasksDirName = config.tasks_dir || DEFAULT_TASKS_DIR;
|
|
194
197
|
const linksPath = path.join(cwd, ".mechanic", "links.json");
|
|
195
|
-
const links = normalizeLinks(await readJson(linksPath, emptyLinks()), config.shop_domain);
|
|
198
|
+
const links = normalizeLinks(await readJson(linksPath, emptyLinks()), config.shop_domain, tasksDirName);
|
|
196
199
|
const apiBaseUrl = normalizeApiBaseUrl(process.env.MECHANIC_API_BASE_URL || config.api_base_url);
|
|
197
200
|
const appUrl = normalizeAppUrl(process.env.MECHANIC_APP_URL || config.app_url || defaultAppUrl(config.shop_domain));
|
|
198
201
|
return {
|
|
@@ -217,9 +220,30 @@ export function linkForSlug(project, slug) {
|
|
|
217
220
|
export function slugForRemoteId(project, remoteId) {
|
|
218
221
|
return Object.entries(project.links.tasks).find(([, link]) => link.remote_id === remoteId)?.[0] || null;
|
|
219
222
|
}
|
|
220
|
-
|
|
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`;
|
|
221
245
|
const entry = {
|
|
222
|
-
file
|
|
246
|
+
file,
|
|
223
247
|
remote_id: remoteId,
|
|
224
248
|
last_remote_content_hash: contentHash,
|
|
225
249
|
};
|
package/dist/tasks.d.ts
CHANGED
|
@@ -26,8 +26,9 @@ export declare function writeTaskFile(project: Project, slug: string, task: Json
|
|
|
26
26
|
export declare function resolveTaskSelector(project: Project, input: string, options?: ResolveTaskSelectorOptions): Promise<ResolvedTaskSelector>;
|
|
27
27
|
export declare function selectedTaskFiles(project: Project, input: string | undefined, all: boolean): Promise<string[]>;
|
|
28
28
|
export declare function remoteChangedSinceLastPull(link: LinkEntry, remote: Pick<TaskEnvelope, "content_hash">): boolean;
|
|
29
|
+
export declare function localChangedSinceLastSync(link: LinkEntry, task: JsonObject): boolean;
|
|
29
30
|
export declare function remoteChangedSinceLastPullDetails(force?: boolean): string;
|
|
30
|
-
export declare function remoteChangedSinceLastPullMessage(relativeFile: string, remoteId: string): string;
|
|
31
|
+
export declare function remoteChangedSinceLastPullMessage(relativeFile: string, remoteId: string, localChanged?: boolean): string;
|
|
31
32
|
export declare function taskForPush(task: JsonObject): JsonObject;
|
|
32
33
|
export declare function nextAvailableSlug(project: Project, baseSlug: string): Promise<string>;
|
|
33
34
|
export declare function taskFromHelperDir(dirPath: string): Promise<JsonObject>;
|
package/dist/tasks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../src/tasks.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../src/tasks.ts"],"names":[],"mappings":"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"}
|
package/dist/tasks.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { CliError } from "./errors.js";
|
|
4
4
|
import { ensureDir, pathExists, readJson, readText, removeFile, writeJson, writeText } from "./fs.js";
|
|
5
|
+
import { contentHash } from "./hash.js";
|
|
5
6
|
import { stableStringify } from "./json.js";
|
|
6
7
|
const SPLIT_FILES = [
|
|
7
8
|
["script", "script.liquid"],
|
|
@@ -318,11 +319,19 @@ export async function selectedTaskFiles(project, input, all) {
|
|
|
318
319
|
export function remoteChangedSinceLastPull(link, remote) {
|
|
319
320
|
return remote.content_hash !== link.last_remote_content_hash;
|
|
320
321
|
}
|
|
322
|
+
export function localChangedSinceLastSync(link, task) {
|
|
323
|
+
const localHash = contentHash(taskForPush(task));
|
|
324
|
+
return (localHash !== link.last_remote_content_hash
|
|
325
|
+
&& localHash !== link.last_local_content_hash);
|
|
326
|
+
}
|
|
321
327
|
export function remoteChangedSinceLastPullDetails(force = false) {
|
|
322
|
-
return force ? "
|
|
328
|
+
return force ? "Mechanic changed since last sync; --force set" : "Mechanic changed since last sync";
|
|
323
329
|
}
|
|
324
|
-
export function remoteChangedSinceLastPullMessage(relativeFile, remoteId) {
|
|
325
|
-
|
|
330
|
+
export function remoteChangedSinceLastPullMessage(relativeFile, remoteId, localChanged = false) {
|
|
331
|
+
if (localChanged) {
|
|
332
|
+
return `${relativeFile} has changes in Mechanic and local changes since this file was last synced. Run "mechanic tasks diff ${relativeFile}" to review. Then run "mechanic tasks pull ${remoteId} --force" only if current Mechanic should replace your local file, or run "mechanic tasks publish ${relativeFile} --force" only if your local file should overwrite Mechanic.`;
|
|
333
|
+
}
|
|
334
|
+
return `${relativeFile} has changes in Mechanic since this file was last synced. Run "mechanic tasks diff ${relativeFile}" to review, then "mechanic tasks pull ${remoteId}" to update your local file from current Mechanic before publishing.`;
|
|
326
335
|
}
|
|
327
336
|
export function taskForPush(task) {
|
|
328
337
|
const { enabled: _enabled, id: _id, remote_id: _remoteId, ...payload } = task;
|
|
@@ -439,6 +448,7 @@ export function validateTask(task, source) {
|
|
|
439
448
|
export function validateTaskForPush(task, source) {
|
|
440
449
|
return validateTask(task, source);
|
|
441
450
|
}
|
|
451
|
+
const MAX_LINE_DIFF_CELLS = 250_000;
|
|
442
452
|
function comparableTaskValue(value) {
|
|
443
453
|
return value === undefined ? "(missing)" : String(stableStringify(value));
|
|
444
454
|
}
|
|
@@ -489,6 +499,14 @@ function lineDiff(remoteLines, localLines) {
|
|
|
489
499
|
return operations;
|
|
490
500
|
}
|
|
491
501
|
function formattedLineDiff(remoteLines, localLines) {
|
|
502
|
+
if ((remoteLines.length + 1) * (localLines.length + 1) > MAX_LINE_DIFF_CELLS) {
|
|
503
|
+
return [
|
|
504
|
+
"@@",
|
|
505
|
+
`- ${remoteLines.length} current Mechanic lines`,
|
|
506
|
+
`+ ${localLines.length} local file lines`,
|
|
507
|
+
"Large value changed; detailed line diff omitted to keep the CLI responsive.",
|
|
508
|
+
];
|
|
509
|
+
}
|
|
492
510
|
const operations = lineDiff(remoteLines, localLines);
|
|
493
511
|
const changeIndexes = operations.flatMap((operation, index) => (operation.kind === "context" ? [] : [index]));
|
|
494
512
|
if (changeIndexes.length === 0) {
|
|
@@ -540,7 +558,7 @@ export function diffTasks(local, remote) {
|
|
|
540
558
|
}
|
|
541
559
|
const sections = [`Changed fields: ${changedFields.join(", ")}`];
|
|
542
560
|
for (const field of changedFields) {
|
|
543
|
-
sections.push("", `## ${field}`, "---
|
|
561
|
+
sections.push("", `## ${field}`, "--- current Mechanic", "+++ local file", ...formattedValueDiff(comparableRemote[field], comparableLocal[field]));
|
|
544
562
|
}
|
|
545
563
|
return sections.join("\n");
|
|
546
564
|
}
|