@jxsuite/protocol 6.2.0 → 6.2.2
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/package.json +3 -2
- package/src/git-scope.ts +92 -0
- package/src/index.ts +1 -0
- package/src/routes.ts +10 -2
- package/src/types.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/protocol",
|
|
3
|
-
"version": "6.2.
|
|
3
|
+
"version": "6.2.2",
|
|
4
4
|
"description": "The Studio Backend Protocol — wire types and the canonical route table every Jx Studio backend implements",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"type": "module",
|
|
15
15
|
"exports": {
|
|
16
16
|
".": "./src/index.ts",
|
|
17
|
+
"./git-scope": "./src/git-scope.ts",
|
|
17
18
|
"./problem": "./src/problem.ts",
|
|
18
19
|
"./problems": "./src/problems.ts",
|
|
19
20
|
"./routes": "./src/routes.ts",
|
|
@@ -27,6 +28,6 @@
|
|
|
27
28
|
"test:coverage": "bun test --isolate --coverage"
|
|
28
29
|
},
|
|
29
30
|
"dependencies": {
|
|
30
|
-
"@jxsuite/schema": "^5.
|
|
31
|
+
"@jxsuite/schema": "^5.2.0"
|
|
31
32
|
}
|
|
32
33
|
}
|
package/src/git-scope.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a project nested inside a larger repository sees of it.
|
|
3
|
+
*
|
|
4
|
+
* Git answers in two coordinate systems. `status` and `diff --name-only` print paths from the
|
|
5
|
+
* REPOSITORY ROOT whatever the cwd is (porcelain ignores `status.relativePaths`), while `add`,
|
|
6
|
+
* `restore`, `checkout` and the `readFile` Studio pairs them with read paths from the PROJECT ROOT.
|
|
7
|
+
* Handed straight across, a site at `Sites/acme/` asked git to stage
|
|
8
|
+
* `Sites/acme/Sites/acme/pages/index.md`.
|
|
9
|
+
*
|
|
10
|
+
* So every path a backend gives Studio is project-relative, and a project's status and commit cover
|
|
11
|
+
* that project (`desktop.md` §5.1, `studio.md` §22.8). Both backends run these same rules so they
|
|
12
|
+
* cannot disagree about one working tree. At a repository's root the prefix is empty and each of
|
|
13
|
+
* them is the identity.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
* @docs extending/embedding/platform-adapter
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Runs one git command in the project root, resolving stdout and rejecting on a non-zero exit. */
|
|
20
|
+
export type GitRunner = (args: string[]) => Promise<string>;
|
|
21
|
+
|
|
22
|
+
/** `git rev-parse --show-prefix`: the project's folder within its repository, `""` at the root. */
|
|
23
|
+
export async function readGitPrefix(run: GitRunner): Promise<string> {
|
|
24
|
+
const out = await run(["rev-parse", "--show-prefix"]);
|
|
25
|
+
// A prefix is empty or ends in `/`, so this can only remove the newline git adds.
|
|
26
|
+
return out.trimEnd();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A root-relative path git printed, as the project-relative path every other verb reads.
|
|
31
|
+
*
|
|
32
|
+
* A path outside the project stays as git printed it: it has no project-relative spelling the file
|
|
33
|
+
* routes would accept, and a `conflicts` list still has to name it.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} prefix - From {@link readGitPrefix}
|
|
36
|
+
* @param {string} path - Relative to the repository root
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
export function projectRelativePath(prefix: string, path: string): string {
|
|
40
|
+
return path.startsWith(prefix) ? path.slice(prefix.length) : path;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The `git commit` that publishes THIS project's staged changes and nothing else.
|
|
45
|
+
*
|
|
46
|
+
* Review changes lists only the project, and a commit made from a subfolder still takes the whole
|
|
47
|
+
* index. In a repository of several sites, where each one's changes are staged the first time its
|
|
48
|
+
* Review changes shows them, a plain commit would carry another site's changes under this site's
|
|
49
|
+
* summary. So those stay staged, for the project they belong to:
|
|
50
|
+
*
|
|
51
|
+
* - **At the root**, everything is the project: the index, or every tracked change when nothing is
|
|
52
|
+
* staged (the route's "staged, else all dirty").
|
|
53
|
+
* - **During a merge** the commit is the merge, which git refuses to split, and which is the
|
|
54
|
+
* repository's rather than the project's.
|
|
55
|
+
* - **Nothing of the project's staged**: its own tracked changes, never `-a`, which reaches past it.
|
|
56
|
+
* - **Only the project's staged**: the index, exactly.
|
|
57
|
+
* - **Others staged too**: `--only` the project's paths. Those paths are committed from the working
|
|
58
|
+
* tree rather than the index, so this is reserved for the case that needs it.
|
|
59
|
+
*
|
|
60
|
+
* `--no-renames` because a staged rename's old path has to be committed too, or `--only` would keep
|
|
61
|
+
* it in the tree and turn the rename into a copy; `--no-relative` because `diff.relative` would
|
|
62
|
+
* otherwise hide exactly the paths this is looking for.
|
|
63
|
+
*
|
|
64
|
+
* @param {GitRunner} run - Runs git in the project root
|
|
65
|
+
* @param {string} message - The commit message
|
|
66
|
+
* @returns {Promise<string[]>} The argv to hand git
|
|
67
|
+
*/
|
|
68
|
+
export async function projectCommitArgs(run: GitRunner, message: string): Promise<string[]> {
|
|
69
|
+
const [prefix, stagedOut, merging] = await Promise.all([
|
|
70
|
+
readGitPrefix(run),
|
|
71
|
+
run(["diff", "--cached", "--name-only", "--no-renames", "--no-relative", "-z"]),
|
|
72
|
+
run(["rev-parse", "--quiet", "--verify", "MERGE_HEAD"]).then(
|
|
73
|
+
() => true,
|
|
74
|
+
() => false,
|
|
75
|
+
),
|
|
76
|
+
]);
|
|
77
|
+
const commit = ["commit", "-m", message];
|
|
78
|
+
const staged = stagedOut.split("\0").filter(Boolean);
|
|
79
|
+
if (prefix === "") {
|
|
80
|
+
return staged.length > 0 ? commit : [...commit, "-a"];
|
|
81
|
+
}
|
|
82
|
+
if (merging) {
|
|
83
|
+
return commit;
|
|
84
|
+
}
|
|
85
|
+
const own = staged
|
|
86
|
+
.filter((path) => path.startsWith(prefix))
|
|
87
|
+
.map((path) => path.slice(prefix.length));
|
|
88
|
+
if (own.length === 0) {
|
|
89
|
+
return [...commit, "--", "."];
|
|
90
|
+
}
|
|
91
|
+
return own.length === staged.length ? commit : [...commit, "--only", "--", ...own];
|
|
92
|
+
}
|
package/src/index.ts
CHANGED
package/src/routes.ts
CHANGED
|
@@ -371,12 +371,20 @@ export const STUDIO_ROUTES = {
|
|
|
371
371
|
),
|
|
372
372
|
|
|
373
373
|
// ─── Git ──────────────────────────────────────────────────────────────────
|
|
374
|
-
gitStatus: route(
|
|
374
|
+
gitStatus: route(
|
|
375
|
+
"GET",
|
|
376
|
+
"/__studio/git/status",
|
|
377
|
+
"The project's working-tree status (GitStatusResult), project-relative",
|
|
378
|
+
),
|
|
375
379
|
gitBranches: route("GET", "/__studio/git/branches", "Branch list (GitBranchesResult)"),
|
|
376
380
|
gitLog: route("GET", "/__studio/git/log", "Recent commits (GitLogEntry[])"),
|
|
377
381
|
gitStage: route("POST", "/__studio/git/stage", "Stage files"),
|
|
378
382
|
gitUnstage: route("POST", "/__studio/git/unstage", "Unstage files"),
|
|
379
|
-
gitCommit: route(
|
|
383
|
+
gitCommit: route(
|
|
384
|
+
"POST",
|
|
385
|
+
"/__studio/git/commit",
|
|
386
|
+
"Commit the project's staged (else all its dirty) files",
|
|
387
|
+
),
|
|
380
388
|
gitPush: route("POST", "/__studio/git/push", "Push (cloud: sync check — commits land on push)"),
|
|
381
389
|
gitPull: route("POST", "/__studio/git/pull", "Pull/fast-forward; 409 {conflicts} on overlap"),
|
|
382
390
|
gitFetch: route("POST", "/__studio/git/fetch", "Refresh remote tracking state"),
|
package/src/types.ts
CHANGED
|
@@ -206,6 +206,10 @@ export interface GitFileStatus {
|
|
|
206
206
|
* code.
|
|
207
207
|
*/
|
|
208
208
|
status: string;
|
|
209
|
+
/**
|
|
210
|
+
* Relative to the PROJECT root, never the repository's: the path every other git verb and file
|
|
211
|
+
* route reads. A project nested in a larger repository lists only its own changes.
|
|
212
|
+
*/
|
|
209
213
|
path: string;
|
|
210
214
|
/** Whether this change is in the index. Absent reads as unstaged. */
|
|
211
215
|
staged?: boolean;
|