@openvcs/git-plugin 0.1.0-edge.20260422.21 → 0.1.0-edge.20260423.25
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/bin/plugin-request-handler.js +24 -6
- package/bin/plugin.js +1 -1
- package/bin/submodules.js +42 -6
- package/package.json +2 -2
- package/src/plugin-request-handler.ts +32 -12
- package/src/plugin.ts +1 -1
- package/src/submodules.ts +49 -7
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
import { VcsDelegateBase, pluginError, } from '@openvcs/sdk/runtime';
|
|
4
4
|
import { asNumber, asRecord, asString, asStringArray, asTrimmedString, buildCloneArgs, parseStatusOutput, } from './plugin-helpers.js';
|
|
5
5
|
import { GitCommand } from './git.js';
|
|
6
|
+
/** Returns an optional boolean only when the input is already a boolean. */
|
|
7
|
+
function asOptionalBoolean(value) {
|
|
8
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
9
|
+
}
|
|
6
10
|
/** Reduces a file status string to the primary status code needed for discard routing. */
|
|
7
11
|
function getPrimaryDiscardStatus(status) {
|
|
8
12
|
const normalized = asTrimmedString(status);
|
|
@@ -237,8 +241,8 @@ export class GitVcsDelegates extends VcsDelegateBase {
|
|
|
237
241
|
branch: asTrimmedString(query.rev) || undefined,
|
|
238
242
|
skip: asNumber(query.skip, 0) || undefined,
|
|
239
243
|
limit: asNumber(query.limit, 0),
|
|
240
|
-
topo_order: query.topo_order
|
|
241
|
-
include_merges: query.include_merges
|
|
244
|
+
topo_order: asOptionalBoolean(query.topo_order),
|
|
245
|
+
include_merges: asOptionalBoolean(query.include_merges),
|
|
242
246
|
author_contains: asTrimmedString(query.author_contains) || undefined,
|
|
243
247
|
since_utc: asTrimmedString(query.since_utc) || undefined,
|
|
244
248
|
until_utc: asTrimmedString(query.until_utc) || undefined,
|
|
@@ -291,15 +295,29 @@ export class GitVcsDelegates extends VcsDelegateBase {
|
|
|
291
295
|
}
|
|
292
296
|
const status = git.runChecked(['status', '--porcelain=1', '-z', '-uall', '--', ...paths], 'git-discard-paths-failed');
|
|
293
297
|
const discardPlan = planDiscardPaths(status.stdout);
|
|
298
|
+
let failure;
|
|
294
299
|
if (discardPlan.unstageThenRemove.length > 0) {
|
|
295
|
-
|
|
300
|
+
try {
|
|
301
|
+
git.runChecked(['rm', '-f', '--cached', '--', ...discardPlan.unstageThenRemove], 'git-discard-paths-failed');
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
failure = error;
|
|
305
|
+
}
|
|
296
306
|
}
|
|
297
|
-
if (discardPlan.restore.length > 0) {
|
|
298
|
-
|
|
307
|
+
if (!failure && discardPlan.restore.length > 0) {
|
|
308
|
+
try {
|
|
309
|
+
git.runChecked(['restore', '--source=HEAD', '--staged', '--worktree', '--', ...discardPlan.restore], 'git-discard-paths-failed');
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
failure = error;
|
|
313
|
+
}
|
|
299
314
|
}
|
|
300
|
-
if (discardPlan.clean.length > 0) {
|
|
315
|
+
if (!failure && discardPlan.clean.length > 0) {
|
|
301
316
|
git.runChecked(['clean', '-f', '--', ...discardPlan.clean], 'git-discard-paths-failed');
|
|
302
317
|
}
|
|
318
|
+
if (failure) {
|
|
319
|
+
throw failure;
|
|
320
|
+
}
|
|
303
321
|
return null;
|
|
304
322
|
}
|
|
305
323
|
applyReversePatch(params, _context) {
|
package/bin/plugin.js
CHANGED
|
@@ -38,7 +38,7 @@ export function OnPluginStart() {
|
|
|
38
38
|
}
|
|
39
39
|
const delegates = new GitVcsDelegates(createGitRuntimeDependencies());
|
|
40
40
|
PluginDefinition.vcs = delegates.toDelegates();
|
|
41
|
-
const repoMenu = getOrCreateMenu('repository', 'Repository');
|
|
41
|
+
const repoMenu = getOrCreateMenu('repository', 'Repository', { surface: 'menubar' });
|
|
42
42
|
if (repoMenu) {
|
|
43
43
|
repoMenu.addItem({ label: 'Edit .gitignore', action: 'repo-edit-gitignore' });
|
|
44
44
|
repoMenu.addItem({ label: 'Edit .gitattributes', action: 'repo-edit-gitattributes' });
|
package/bin/submodules.js
CHANGED
|
@@ -18,7 +18,7 @@ function payloadString(payload, key) {
|
|
|
18
18
|
return String(payload[key] ?? '').trim();
|
|
19
19
|
}
|
|
20
20
|
/** Builds one modal row for a submodule entry. */
|
|
21
|
-
function buildSubmoduleRow(entry) {
|
|
21
|
+
export function buildSubmoduleRow(entry) {
|
|
22
22
|
const metaBits = [entry.commit ? `commit ${entry.commit}` : '', entry.branch ? `branch ${entry.branch}` : '']
|
|
23
23
|
.map((part) => String(part || '').trim())
|
|
24
24
|
.filter(Boolean);
|
|
@@ -51,12 +51,44 @@ function buildSubmoduleRow(entry) {
|
|
|
51
51
|
],
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
+
/** Builds an error fallback modal with a descriptive message. */
|
|
55
|
+
function buildErrorModal(message) {
|
|
56
|
+
return new ModalBuilder('Error').text(message).text('Please try again or check the Git repository state.');
|
|
57
|
+
}
|
|
58
|
+
/** Opens a fallback modal for a submodule UI failure and preserves the original error on fallback failure. */
|
|
59
|
+
export async function handleSubmoduleModalError(prefix, error, openFallback = (message) => buildErrorModal(message).open()) {
|
|
60
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
61
|
+
const fullMessage = `${prefix}: ${message}`;
|
|
62
|
+
console.error(`Git submodules: ${fullMessage}`);
|
|
63
|
+
try {
|
|
64
|
+
return await openFallback(fullMessage);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
54
70
|
/** Builds and opens the submodule manager modal. */
|
|
55
71
|
async function openSubmodulesModal() {
|
|
56
72
|
const git = createGitCommand();
|
|
57
|
-
|
|
73
|
+
let entries;
|
|
74
|
+
try {
|
|
75
|
+
entries = git.listSubmodules();
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
return handleSubmoduleModalError('failed to list submodules', error);
|
|
79
|
+
}
|
|
58
80
|
console.log('Git submodules: building modal', { count: entries.length });
|
|
59
|
-
const modal =
|
|
81
|
+
const modal = buildSubmodulesModal(entries);
|
|
82
|
+
try {
|
|
83
|
+
return await modal.open();
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
return handleSubmoduleModalError('failed to open modal', error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** Builds the submodule manager modal with given entries. */
|
|
90
|
+
function buildSubmodulesModal(entries) {
|
|
91
|
+
return new ModalBuilder('Manage Submodules')
|
|
60
92
|
.text('Review, add, update, sync, and remove submodules without leaving Git.')
|
|
61
93
|
.text('Use Update Remote to follow each submodule branch configured in .gitmodules.')
|
|
62
94
|
.separator()
|
|
@@ -109,7 +141,6 @@ async function openSubmodulesModal() {
|
|
|
109
141
|
emptyText: 'This repository has no submodules yet.',
|
|
110
142
|
items: entries.map((entry) => buildSubmoduleRow(entry)),
|
|
111
143
|
});
|
|
112
|
-
return modal.open();
|
|
113
144
|
}
|
|
114
145
|
/** Builds and opens the remove confirmation modal for one submodule. */
|
|
115
146
|
async function openRemoveConfirmationModal(path, name) {
|
|
@@ -129,7 +160,12 @@ async function openRemoveConfirmationModal(path, name) {
|
|
|
129
160
|
payload: { path: submodulePath, name: String(name || '').trim() || undefined },
|
|
130
161
|
},
|
|
131
162
|
], { gap: '.5rem', align: 'centered', wrap: true });
|
|
132
|
-
|
|
163
|
+
try {
|
|
164
|
+
return await modal.open();
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
return handleSubmoduleModalError('failed to open remove confirmation modal', error);
|
|
168
|
+
}
|
|
133
169
|
}
|
|
134
170
|
/** Removes one submodule and refreshes the toolkit modal. */
|
|
135
171
|
async function removeSubmodule(payload) {
|
|
@@ -200,7 +236,7 @@ async function syncAllSubmodules() {
|
|
|
200
236
|
}
|
|
201
237
|
/** Registers the Git submodule toolkit menu and action handlers. */
|
|
202
238
|
export function registerSubmoduleToolkit() {
|
|
203
|
-
const repoMenu = getOrCreateMenu('repository', 'Repository');
|
|
239
|
+
const repoMenu = getOrCreateMenu('repository', 'Repository', { surface: 'menubar' });
|
|
204
240
|
repoMenu?.addItem({ label: 'Submodules', action: 'repo-submodules' });
|
|
205
241
|
registerAction('repo-submodules', async () => {
|
|
206
242
|
console.log('Git submodules: repo-submodules action invoked');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openvcs/git-plugin",
|
|
3
|
-
"version": "0.1.0-edge.
|
|
3
|
+
"version": "0.1.0-edge.20260423.25",
|
|
4
4
|
"description": "OpenVCS Git plugin - Node.js runtime",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"homepage": "https://github.com/Open-VCS/OpenVCS-Plugin-Git",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"openvcs": {
|
|
19
19
|
"id": "openvcs.git",
|
|
20
20
|
"name": "Git",
|
|
21
|
-
"version": "0.1.0-edge.
|
|
21
|
+
"version": "0.1.0-edge.20260423.25",
|
|
22
22
|
"author": "OpenVCS Contributors",
|
|
23
23
|
"description": "Git VCS backend plugin for OpenVCS",
|
|
24
24
|
"default_enabled": true,
|
|
@@ -30,6 +30,11 @@ export interface DiscardPathPlan {
|
|
|
30
30
|
clean: string[];
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/** Returns an optional boolean only when the input is already a boolean. */
|
|
34
|
+
function asOptionalBoolean(value: unknown): boolean | undefined {
|
|
35
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
33
38
|
/** Reduces a file status string to the primary status code needed for discard routing. */
|
|
34
39
|
function getPrimaryDiscardStatus(status: string): string {
|
|
35
40
|
const normalized = asTrimmedString(status);
|
|
@@ -396,8 +401,8 @@ export class GitVcsDelegates extends VcsDelegateBase<GitRuntimeDependencies> {
|
|
|
396
401
|
branch: asTrimmedString(query.rev) || undefined,
|
|
397
402
|
skip: asNumber(query.skip, 0) || undefined,
|
|
398
403
|
limit: asNumber(query.limit, 0),
|
|
399
|
-
topo_order: query.topo_order
|
|
400
|
-
include_merges: query.include_merges
|
|
404
|
+
topo_order: asOptionalBoolean(query.topo_order),
|
|
405
|
+
include_merges: asOptionalBoolean(query.include_merges),
|
|
401
406
|
author_contains: asTrimmedString(query.author_contains) || undefined,
|
|
402
407
|
since_utc: asTrimmedString(query.since_utc) || undefined,
|
|
403
408
|
until_utc: asTrimmedString(query.until_utc) || undefined,
|
|
@@ -490,23 +495,38 @@ export class GitVcsDelegates extends VcsDelegateBase<GitRuntimeDependencies> {
|
|
|
490
495
|
);
|
|
491
496
|
const discardPlan = planDiscardPaths(status.stdout);
|
|
492
497
|
|
|
498
|
+
let failure: unknown;
|
|
499
|
+
|
|
493
500
|
if (discardPlan.unstageThenRemove.length > 0) {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
501
|
+
try {
|
|
502
|
+
git.runChecked(
|
|
503
|
+
['rm', '-f', '--cached', '--', ...discardPlan.unstageThenRemove],
|
|
504
|
+
'git-discard-paths-failed',
|
|
505
|
+
);
|
|
506
|
+
} catch (error) {
|
|
507
|
+
failure = error;
|
|
508
|
+
}
|
|
498
509
|
}
|
|
499
510
|
|
|
500
|
-
if (discardPlan.restore.length > 0) {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
511
|
+
if (!failure && discardPlan.restore.length > 0) {
|
|
512
|
+
try {
|
|
513
|
+
git.runChecked(
|
|
514
|
+
['restore', '--source=HEAD', '--staged', '--worktree', '--', ...discardPlan.restore],
|
|
515
|
+
'git-discard-paths-failed',
|
|
516
|
+
);
|
|
517
|
+
} catch (error) {
|
|
518
|
+
failure = error;
|
|
519
|
+
}
|
|
505
520
|
}
|
|
506
521
|
|
|
507
|
-
if (discardPlan.clean.length > 0) {
|
|
522
|
+
if (!failure && discardPlan.clean.length > 0) {
|
|
508
523
|
git.runChecked(['clean', '-f', '--', ...discardPlan.clean], 'git-discard-paths-failed');
|
|
509
524
|
}
|
|
525
|
+
|
|
526
|
+
if (failure) {
|
|
527
|
+
throw failure;
|
|
528
|
+
}
|
|
529
|
+
|
|
510
530
|
return null;
|
|
511
531
|
}
|
|
512
532
|
|
package/src/plugin.ts
CHANGED
|
@@ -58,7 +58,7 @@ export function OnPluginStart(): void {
|
|
|
58
58
|
const delegates = new GitVcsDelegates(createGitRuntimeDependencies());
|
|
59
59
|
PluginDefinition.vcs = delegates.toDelegates();
|
|
60
60
|
|
|
61
|
-
const repoMenu = getOrCreateMenu('repository', 'Repository');
|
|
61
|
+
const repoMenu = getOrCreateMenu('repository', 'Repository', { surface: 'menubar' });
|
|
62
62
|
if (repoMenu) {
|
|
63
63
|
repoMenu.addItem({ label: 'Edit .gitignore', action: 'repo-edit-gitignore' });
|
|
64
64
|
repoMenu.addItem({ label: 'Edit .gitattributes', action: 'repo-edit-gitattributes' });
|
package/src/submodules.ts
CHANGED
|
@@ -26,7 +26,7 @@ function payloadString(payload: ModalActionPayload, key: string): string {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/** Builds one modal row for a submodule entry. */
|
|
29
|
-
function buildSubmoduleRow(entry: SubmoduleEntry) {
|
|
29
|
+
export function buildSubmoduleRow(entry: SubmoduleEntry) {
|
|
30
30
|
const metaBits = [entry.commit ? `commit ${entry.commit}` : '', entry.branch ? `branch ${entry.branch}` : '']
|
|
31
31
|
.map((part) => String(part || '').trim())
|
|
32
32
|
.filter(Boolean);
|
|
@@ -62,13 +62,52 @@ function buildSubmoduleRow(entry: SubmoduleEntry) {
|
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/** Builds an error fallback modal with a descriptive message. */
|
|
66
|
+
function buildErrorModal(message: string): ModalBuilder {
|
|
67
|
+
return new ModalBuilder('Error').text(message).text('Please try again or check the Git repository state.');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Opens a fallback modal for a submodule UI failure and preserves the original error on fallback failure. */
|
|
71
|
+
export async function handleSubmoduleModalError(
|
|
72
|
+
prefix: string,
|
|
73
|
+
error: unknown,
|
|
74
|
+
openFallback: (message: string) => Promise<unknown> = (message) => buildErrorModal(message).open(),
|
|
75
|
+
): Promise<unknown> {
|
|
76
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
77
|
+
const fullMessage = `${prefix}: ${message}`;
|
|
78
|
+
console.error(`Git submodules: ${fullMessage}`);
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
return await openFallback(fullMessage);
|
|
82
|
+
} catch {
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
65
87
|
/** Builds and opens the submodule manager modal. */
|
|
66
88
|
async function openSubmodulesModal(): Promise<unknown> {
|
|
67
89
|
const git = createGitCommand();
|
|
68
|
-
|
|
90
|
+
|
|
91
|
+
let entries: SubmoduleEntry[];
|
|
92
|
+
try {
|
|
93
|
+
entries = git.listSubmodules();
|
|
94
|
+
} catch (error) {
|
|
95
|
+
return handleSubmoduleModalError('failed to list submodules', error);
|
|
96
|
+
}
|
|
97
|
+
|
|
69
98
|
console.log('Git submodules: building modal', { count: entries.length });
|
|
70
99
|
|
|
71
|
-
const modal =
|
|
100
|
+
const modal = buildSubmodulesModal(entries);
|
|
101
|
+
try {
|
|
102
|
+
return await modal.open();
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return handleSubmoduleModalError('failed to open modal', error);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Builds the submodule manager modal with given entries. */
|
|
109
|
+
function buildSubmodulesModal(entries: SubmoduleEntry[]): ModalBuilder {
|
|
110
|
+
return new ModalBuilder('Manage Submodules')
|
|
72
111
|
.text('Review, add, update, sync, and remove submodules without leaving Git.')
|
|
73
112
|
.text('Use Update Remote to follow each submodule branch configured in .gitmodules.')
|
|
74
113
|
.separator()
|
|
@@ -130,14 +169,13 @@ async function openSubmodulesModal(): Promise<unknown> {
|
|
|
130
169
|
emptyText: 'This repository has no submodules yet.',
|
|
131
170
|
items: entries.map((entry) => buildSubmoduleRow(entry)),
|
|
132
171
|
});
|
|
133
|
-
|
|
134
|
-
return modal.open();
|
|
135
172
|
}
|
|
136
173
|
|
|
137
174
|
/** Builds and opens the remove confirmation modal for one submodule. */
|
|
138
175
|
async function openRemoveConfirmationModal(path: string, name?: string): Promise<unknown> {
|
|
139
176
|
const submodulePath = String(path || '').trim();
|
|
140
177
|
if (!submodulePath) return;
|
|
178
|
+
|
|
141
179
|
console.log('Git submodules: building remove confirmation', { path: submodulePath, name });
|
|
142
180
|
|
|
143
181
|
const modal = new ModalBuilder('Confirm Submodule Removal')
|
|
@@ -156,7 +194,11 @@ async function openRemoveConfirmationModal(path: string, name?: string): Promise
|
|
|
156
194
|
{ gap: '.5rem', align: 'centered', wrap: true },
|
|
157
195
|
);
|
|
158
196
|
|
|
159
|
-
|
|
197
|
+
try {
|
|
198
|
+
return await modal.open();
|
|
199
|
+
} catch (error) {
|
|
200
|
+
return handleSubmoduleModalError('failed to open remove confirmation modal', error);
|
|
201
|
+
}
|
|
160
202
|
}
|
|
161
203
|
|
|
162
204
|
/** Removes one submodule and refreshes the toolkit modal. */
|
|
@@ -234,7 +276,7 @@ async function syncAllSubmodules(): Promise<void> {
|
|
|
234
276
|
|
|
235
277
|
/** Registers the Git submodule toolkit menu and action handlers. */
|
|
236
278
|
export function registerSubmoduleToolkit(): void {
|
|
237
|
-
const repoMenu = getOrCreateMenu('repository', 'Repository');
|
|
279
|
+
const repoMenu = getOrCreateMenu('repository', 'Repository', { surface: 'menubar' });
|
|
238
280
|
repoMenu?.addItem({ label: 'Submodules', action: 'repo-submodules' });
|
|
239
281
|
|
|
240
282
|
registerAction('repo-submodules', async () => {
|