@made-by-moonlight/athene-plugin-workspace-clone 0.9.1
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/LICENSE +22 -0
- package/dist/__tests__/index.test.d.ts +2 -0
- package/dist/__tests__/index.test.d.ts.map +1 -0
- package/dist/__tests__/index.test.js +603 -0
- package/dist/__tests__/index.test.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +260 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Composio, Inc.
|
|
4
|
+
Copyright (c) 2026 slievr (Athene fork)
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/index.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import * as childProcess from "node:child_process";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
const { getShellMock, recordActivityEventMock } = vi.hoisted(() => ({
|
|
5
|
+
getShellMock: vi.fn(() => ({ cmd: "sh", args: (c) => ["-c", c] })),
|
|
6
|
+
recordActivityEventMock: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
vi.mock("@made-by-moonlight/athene-core", async () => {
|
|
9
|
+
const actual = (await vi.importActual("@made-by-moonlight/athene-core"));
|
|
10
|
+
return {
|
|
11
|
+
...actual,
|
|
12
|
+
getShell: getShellMock,
|
|
13
|
+
recordActivityEvent: recordActivityEventMock,
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
// Mock node:child_process with custom promisify support
|
|
17
|
+
vi.mock("node:child_process", () => {
|
|
18
|
+
const mockExecFile = vi.fn();
|
|
19
|
+
mockExecFile[Symbol.for("nodejs.util.promisify.custom")] = vi.fn();
|
|
20
|
+
return { execFile: mockExecFile };
|
|
21
|
+
});
|
|
22
|
+
// Mock node:fs
|
|
23
|
+
vi.mock("node:fs", () => ({
|
|
24
|
+
existsSync: vi.fn(),
|
|
25
|
+
rmSync: vi.fn(),
|
|
26
|
+
mkdirSync: vi.fn(),
|
|
27
|
+
readdirSync: vi.fn(),
|
|
28
|
+
}));
|
|
29
|
+
// Mock node:os
|
|
30
|
+
vi.mock("node:os", () => ({
|
|
31
|
+
homedir: () => "/mock-home",
|
|
32
|
+
}));
|
|
33
|
+
// Force POSIX path semantics in tests so "/mock-home/..." assertions match on
|
|
34
|
+
// Windows too. Only this test file uses the posix override.
|
|
35
|
+
vi.mock("node:path", async () => {
|
|
36
|
+
const actual = (await vi.importActual("node:path"));
|
|
37
|
+
return { ...actual.posix, default: actual.posix };
|
|
38
|
+
});
|
|
39
|
+
// Get reference to the promisify-custom mock — this is what the plugin actually calls
|
|
40
|
+
const mockExecFileAsync = childProcess.execFile[Symbol.for("nodejs.util.promisify.custom")];
|
|
41
|
+
/** Queue a successful git command with the given stdout. */
|
|
42
|
+
function mockGitSuccess(stdout) {
|
|
43
|
+
mockExecFileAsync.mockResolvedValueOnce({ stdout: stdout + "\n", stderr: "" });
|
|
44
|
+
}
|
|
45
|
+
/** Queue a failed git command. */
|
|
46
|
+
function mockGitError(message) {
|
|
47
|
+
mockExecFileAsync.mockRejectedValueOnce(new Error(message));
|
|
48
|
+
}
|
|
49
|
+
/** Create a ProjectConfig for testing. */
|
|
50
|
+
function makeProject(overrides) {
|
|
51
|
+
return {
|
|
52
|
+
name: "test-project",
|
|
53
|
+
repo: "test/repo",
|
|
54
|
+
path: "/repo/path",
|
|
55
|
+
defaultBranch: "main",
|
|
56
|
+
sessionPrefix: "test",
|
|
57
|
+
...overrides,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
// Import after mocks are set up
|
|
61
|
+
import clonePlugin, { manifest, create } from "../index.js";
|
|
62
|
+
import * as core from "@made-by-moonlight/athene-core";
|
|
63
|
+
const mockGetShell = core.getShell;
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
vi.clearAllMocks();
|
|
66
|
+
});
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// manifest
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
describe("manifest", () => {
|
|
71
|
+
it("has name 'clone' and slot 'workspace'", () => {
|
|
72
|
+
expect(manifest.name).toBe("clone");
|
|
73
|
+
expect(manifest.slot).toBe("workspace");
|
|
74
|
+
expect(manifest.version).toBe("0.1.0");
|
|
75
|
+
expect(manifest.description).toBe("Workspace plugin: git clone isolation");
|
|
76
|
+
});
|
|
77
|
+
it("default export includes manifest and create", () => {
|
|
78
|
+
expect(clonePlugin.manifest).toBe(manifest);
|
|
79
|
+
expect(clonePlugin.create).toBe(create);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// create() factory
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
describe("create()", () => {
|
|
86
|
+
it("returns a Workspace with name 'clone'", () => {
|
|
87
|
+
const workspace = create();
|
|
88
|
+
expect(workspace.name).toBe("clone");
|
|
89
|
+
});
|
|
90
|
+
it("uses ~/.ao-clones as default base dir", async () => {
|
|
91
|
+
const workspace = create();
|
|
92
|
+
// Setup: remote URL lookup
|
|
93
|
+
mockGitSuccess("https://github.com/test/repo.git");
|
|
94
|
+
// existsSync: workspace path does not exist yet
|
|
95
|
+
fs.existsSync.mockReturnValue(false);
|
|
96
|
+
// git clone
|
|
97
|
+
mockGitSuccess("");
|
|
98
|
+
// git checkout -b
|
|
99
|
+
mockGitSuccess("");
|
|
100
|
+
const info = await workspace.create({
|
|
101
|
+
projectId: "myproject",
|
|
102
|
+
sessionId: "session-1",
|
|
103
|
+
branch: "feat/test",
|
|
104
|
+
project: makeProject(),
|
|
105
|
+
});
|
|
106
|
+
expect(info.path).toBe("/mock-home/.ao-clones/myproject/session-1");
|
|
107
|
+
});
|
|
108
|
+
it("uses custom cloneDir when configured", async () => {
|
|
109
|
+
const workspace = create({ cloneDir: "~/custom-clones" });
|
|
110
|
+
mockGitSuccess("https://github.com/test/repo.git");
|
|
111
|
+
fs.existsSync.mockReturnValue(false);
|
|
112
|
+
mockGitSuccess("");
|
|
113
|
+
mockGitSuccess("");
|
|
114
|
+
const info = await workspace.create({
|
|
115
|
+
projectId: "myproject",
|
|
116
|
+
sessionId: "session-2",
|
|
117
|
+
branch: "feat/custom",
|
|
118
|
+
project: makeProject(),
|
|
119
|
+
});
|
|
120
|
+
expect(info.path).toBe("/mock-home/custom-clones/myproject/session-2");
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// workspace.create()
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
describe("workspace.create()", () => {
|
|
127
|
+
it("gets remote URL via git remote get-url origin", async () => {
|
|
128
|
+
const workspace = create();
|
|
129
|
+
// 1: git remote get-url origin
|
|
130
|
+
mockGitSuccess("https://github.com/test/repo.git");
|
|
131
|
+
fs.existsSync.mockReturnValue(false);
|
|
132
|
+
// 2: git clone
|
|
133
|
+
mockGitSuccess("");
|
|
134
|
+
// 3: git checkout -b
|
|
135
|
+
mockGitSuccess("");
|
|
136
|
+
await workspace.create({
|
|
137
|
+
projectId: "proj",
|
|
138
|
+
sessionId: "sess",
|
|
139
|
+
branch: "feat/branch",
|
|
140
|
+
project: makeProject(),
|
|
141
|
+
});
|
|
142
|
+
// First call should be git remote get-url origin
|
|
143
|
+
expect(mockExecFileAsync).toHaveBeenNthCalledWith(1, "git", ["remote", "get-url", "origin"], {
|
|
144
|
+
cwd: "/repo/path",
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
it("falls back to local path when remote URL lookup fails", async () => {
|
|
148
|
+
const workspace = create();
|
|
149
|
+
// 1: git remote get-url origin FAILS
|
|
150
|
+
mockGitError("fatal: not a git repository");
|
|
151
|
+
fs.existsSync.mockReturnValue(false);
|
|
152
|
+
// 2: git clone (uses local path as remoteUrl)
|
|
153
|
+
mockGitSuccess("");
|
|
154
|
+
// 3: git checkout -b
|
|
155
|
+
mockGitSuccess("");
|
|
156
|
+
await workspace.create({
|
|
157
|
+
projectId: "proj",
|
|
158
|
+
sessionId: "sess",
|
|
159
|
+
branch: "feat/branch",
|
|
160
|
+
project: makeProject(),
|
|
161
|
+
});
|
|
162
|
+
// Clone should use local path as the remote URL
|
|
163
|
+
const cloneCall = mockExecFileAsync.mock.calls[1];
|
|
164
|
+
expect(cloneCall[0]).toBe("git");
|
|
165
|
+
const cloneArgs = cloneCall[1];
|
|
166
|
+
// The remote URL argument (after --reference repoPath --branch defaultBranch) is the 6th arg
|
|
167
|
+
expect(cloneArgs[5]).toBe("/repo/path");
|
|
168
|
+
});
|
|
169
|
+
it("calls git clone with --reference flag", async () => {
|
|
170
|
+
const workspace = create();
|
|
171
|
+
mockGitSuccess("https://github.com/test/repo.git");
|
|
172
|
+
fs.existsSync.mockReturnValue(false);
|
|
173
|
+
mockGitSuccess("");
|
|
174
|
+
mockGitSuccess("");
|
|
175
|
+
await workspace.create({
|
|
176
|
+
projectId: "proj",
|
|
177
|
+
sessionId: "sess",
|
|
178
|
+
branch: "feat/branch",
|
|
179
|
+
project: makeProject({ defaultBranch: "develop" }),
|
|
180
|
+
});
|
|
181
|
+
// The clone call (second call overall)
|
|
182
|
+
expect(mockExecFileAsync).toHaveBeenNthCalledWith(2, "git", [
|
|
183
|
+
"clone",
|
|
184
|
+
"--reference",
|
|
185
|
+
"/repo/path",
|
|
186
|
+
"--branch",
|
|
187
|
+
"develop",
|
|
188
|
+
"https://github.com/test/repo.git",
|
|
189
|
+
"/mock-home/.ao-clones/proj/sess",
|
|
190
|
+
]);
|
|
191
|
+
});
|
|
192
|
+
it("creates feature branch via checkout -b", async () => {
|
|
193
|
+
const workspace = create();
|
|
194
|
+
mockGitSuccess("https://github.com/test/repo.git");
|
|
195
|
+
fs.existsSync.mockReturnValue(false);
|
|
196
|
+
mockGitSuccess("");
|
|
197
|
+
// git checkout -b feat/new-branch
|
|
198
|
+
mockGitSuccess("");
|
|
199
|
+
await workspace.create({
|
|
200
|
+
projectId: "proj",
|
|
201
|
+
sessionId: "sess",
|
|
202
|
+
branch: "feat/new-branch",
|
|
203
|
+
project: makeProject(),
|
|
204
|
+
});
|
|
205
|
+
// Third call: checkout -b
|
|
206
|
+
expect(mockExecFileAsync).toHaveBeenNthCalledWith(3, "git", ["checkout", "-b", "feat/new-branch"], { cwd: "/mock-home/.ao-clones/proj/sess" });
|
|
207
|
+
});
|
|
208
|
+
it("falls back to plain checkout when branch already exists", async () => {
|
|
209
|
+
const workspace = create();
|
|
210
|
+
mockGitSuccess("https://github.com/test/repo.git");
|
|
211
|
+
fs.existsSync.mockReturnValue(false);
|
|
212
|
+
mockGitSuccess("");
|
|
213
|
+
// git checkout -b fails (branch exists)
|
|
214
|
+
mockGitError("fatal: A branch named 'feat/existing' already exists");
|
|
215
|
+
// git checkout (plain) succeeds
|
|
216
|
+
mockGitSuccess("");
|
|
217
|
+
const info = await workspace.create({
|
|
218
|
+
projectId: "proj",
|
|
219
|
+
sessionId: "sess",
|
|
220
|
+
branch: "feat/existing",
|
|
221
|
+
project: makeProject(),
|
|
222
|
+
});
|
|
223
|
+
// Fourth call: plain checkout
|
|
224
|
+
expect(mockExecFileAsync).toHaveBeenNthCalledWith(4, "git", ["checkout", "feat/existing"], {
|
|
225
|
+
cwd: "/mock-home/.ao-clones/proj/sess",
|
|
226
|
+
});
|
|
227
|
+
expect(recordActivityEventMock).toHaveBeenCalledWith(expect.objectContaining({
|
|
228
|
+
source: "workspace",
|
|
229
|
+
kind: "workspace.branch_collision",
|
|
230
|
+
level: "warn",
|
|
231
|
+
projectId: "proj",
|
|
232
|
+
sessionId: "sess",
|
|
233
|
+
data: expect.objectContaining({
|
|
234
|
+
plugin: "workspace-clone",
|
|
235
|
+
branch: "feat/existing",
|
|
236
|
+
errorMessage: expect.stringContaining("already exists"),
|
|
237
|
+
}),
|
|
238
|
+
}));
|
|
239
|
+
expect(info.branch).toBe("feat/existing");
|
|
240
|
+
});
|
|
241
|
+
it("does not emit branch_collision when checkout -b fails for a non-collision reason", async () => {
|
|
242
|
+
const workspace = create();
|
|
243
|
+
mockGitSuccess("https://github.com/test/repo.git");
|
|
244
|
+
fs.existsSync.mockReturnValue(false);
|
|
245
|
+
mockGitSuccess("");
|
|
246
|
+
// git checkout -b fails for a non-collision reason
|
|
247
|
+
mockGitError("fatal: cannot lock ref 'refs/heads/feat/locked': Permission denied");
|
|
248
|
+
// git checkout (plain) succeeds
|
|
249
|
+
mockGitSuccess("");
|
|
250
|
+
const info = await workspace.create({
|
|
251
|
+
projectId: "proj",
|
|
252
|
+
sessionId: "sess",
|
|
253
|
+
branch: "feat/locked",
|
|
254
|
+
project: makeProject(),
|
|
255
|
+
});
|
|
256
|
+
expect(info.branch).toBe("feat/locked");
|
|
257
|
+
const branchCollisionCalls = recordActivityEventMock.mock.calls.filter(([event]) => event.kind === "workspace.branch_collision");
|
|
258
|
+
expect(branchCollisionCalls).toHaveLength(0);
|
|
259
|
+
});
|
|
260
|
+
it("cleans up partial clone on clone failure", async () => {
|
|
261
|
+
const workspace = create();
|
|
262
|
+
mockGitSuccess("https://github.com/test/repo.git");
|
|
263
|
+
// existsSync: first call for "already exists" check => false
|
|
264
|
+
// second call inside catch for cleanup check => true
|
|
265
|
+
fs.existsSync
|
|
266
|
+
.mockReturnValueOnce(false)
|
|
267
|
+
.mockReturnValueOnce(true);
|
|
268
|
+
// git clone fails
|
|
269
|
+
mockGitError("fatal: could not read from remote repository");
|
|
270
|
+
await expect(workspace.create({
|
|
271
|
+
projectId: "proj",
|
|
272
|
+
sessionId: "sess",
|
|
273
|
+
branch: "feat/branch",
|
|
274
|
+
project: makeProject(),
|
|
275
|
+
})).rejects.toThrow('Failed to clone repo for session "sess"');
|
|
276
|
+
// rmSync should be called to clean up
|
|
277
|
+
expect(fs.rmSync).toHaveBeenCalledWith("/mock-home/.ao-clones/proj/sess", {
|
|
278
|
+
recursive: true,
|
|
279
|
+
force: true,
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
it("cleans up clone on checkout failure and throws", async () => {
|
|
283
|
+
const workspace = create();
|
|
284
|
+
mockGitSuccess("https://github.com/test/repo.git");
|
|
285
|
+
fs.existsSync.mockReturnValue(false);
|
|
286
|
+
// git clone succeeds
|
|
287
|
+
mockGitSuccess("");
|
|
288
|
+
// git checkout -b fails
|
|
289
|
+
mockGitError("error: pathspec did not match");
|
|
290
|
+
// git checkout (fallback) also fails
|
|
291
|
+
mockGitError("error: pathspec 'bad-branch' did not match");
|
|
292
|
+
await expect(workspace.create({
|
|
293
|
+
projectId: "proj",
|
|
294
|
+
sessionId: "sess",
|
|
295
|
+
branch: "bad-branch",
|
|
296
|
+
project: makeProject(),
|
|
297
|
+
})).rejects.toThrow('Failed to checkout branch "bad-branch" in clone');
|
|
298
|
+
// rmSync should be called to clean up the orphaned clone
|
|
299
|
+
expect(fs.rmSync).toHaveBeenCalledWith("/mock-home/.ao-clones/proj/sess", {
|
|
300
|
+
recursive: true,
|
|
301
|
+
force: true,
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
it("throws if workspace path already exists", async () => {
|
|
305
|
+
const workspace = create();
|
|
306
|
+
mockGitSuccess("https://github.com/test/repo.git");
|
|
307
|
+
// existsSync returns true — path already exists
|
|
308
|
+
fs.existsSync.mockReturnValue(true);
|
|
309
|
+
await expect(workspace.create({
|
|
310
|
+
projectId: "proj",
|
|
311
|
+
sessionId: "sess",
|
|
312
|
+
branch: "feat/branch",
|
|
313
|
+
project: makeProject(),
|
|
314
|
+
})).rejects.toThrow('Workspace path "/mock-home/.ao-clones/proj/sess" already exists for session "sess"');
|
|
315
|
+
});
|
|
316
|
+
it("rejects invalid projectId with special characters", async () => {
|
|
317
|
+
const workspace = create();
|
|
318
|
+
await expect(workspace.create({
|
|
319
|
+
projectId: "bad/project",
|
|
320
|
+
sessionId: "sess",
|
|
321
|
+
branch: "feat/branch",
|
|
322
|
+
project: makeProject(),
|
|
323
|
+
})).rejects.toThrow('Invalid projectId "bad/project"');
|
|
324
|
+
});
|
|
325
|
+
it("rejects projectId with dots", async () => {
|
|
326
|
+
const workspace = create();
|
|
327
|
+
await expect(workspace.create({
|
|
328
|
+
projectId: "bad.project",
|
|
329
|
+
sessionId: "sess",
|
|
330
|
+
branch: "feat/branch",
|
|
331
|
+
project: makeProject(),
|
|
332
|
+
})).rejects.toThrow('Invalid projectId "bad.project"');
|
|
333
|
+
});
|
|
334
|
+
it("rejects invalid sessionId with special characters", async () => {
|
|
335
|
+
const workspace = create();
|
|
336
|
+
await expect(workspace.create({
|
|
337
|
+
projectId: "proj",
|
|
338
|
+
sessionId: "bad session!",
|
|
339
|
+
branch: "feat/branch",
|
|
340
|
+
project: makeProject(),
|
|
341
|
+
})).rejects.toThrow('Invalid sessionId "bad session!"');
|
|
342
|
+
});
|
|
343
|
+
it("rejects sessionId with path traversal", async () => {
|
|
344
|
+
const workspace = create();
|
|
345
|
+
await expect(workspace.create({
|
|
346
|
+
projectId: "proj",
|
|
347
|
+
sessionId: "../escape",
|
|
348
|
+
branch: "feat/branch",
|
|
349
|
+
project: makeProject(),
|
|
350
|
+
})).rejects.toThrow('Invalid sessionId "../escape"');
|
|
351
|
+
});
|
|
352
|
+
it("returns correct WorkspaceInfo", async () => {
|
|
353
|
+
const workspace = create();
|
|
354
|
+
mockGitSuccess("https://github.com/test/repo.git");
|
|
355
|
+
fs.existsSync.mockReturnValue(false);
|
|
356
|
+
mockGitSuccess("");
|
|
357
|
+
mockGitSuccess("");
|
|
358
|
+
const info = await workspace.create({
|
|
359
|
+
projectId: "my-project",
|
|
360
|
+
sessionId: "session-42",
|
|
361
|
+
branch: "feat/awesome",
|
|
362
|
+
project: makeProject(),
|
|
363
|
+
});
|
|
364
|
+
expect(info).toEqual({
|
|
365
|
+
path: "/mock-home/.ao-clones/my-project/session-42",
|
|
366
|
+
branch: "feat/awesome",
|
|
367
|
+
sessionId: "session-42",
|
|
368
|
+
projectId: "my-project",
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
it("creates project clone directory with recursive option", async () => {
|
|
372
|
+
const workspace = create();
|
|
373
|
+
mockGitSuccess("https://github.com/test/repo.git");
|
|
374
|
+
fs.existsSync.mockReturnValue(false);
|
|
375
|
+
mockGitSuccess("");
|
|
376
|
+
mockGitSuccess("");
|
|
377
|
+
await workspace.create({
|
|
378
|
+
projectId: "proj",
|
|
379
|
+
sessionId: "sess",
|
|
380
|
+
branch: "feat/branch",
|
|
381
|
+
project: makeProject(),
|
|
382
|
+
});
|
|
383
|
+
expect(fs.mkdirSync).toHaveBeenCalledWith("/mock-home/.ao-clones/proj", { recursive: true });
|
|
384
|
+
});
|
|
385
|
+
it("expands ~ in project path", async () => {
|
|
386
|
+
const workspace = create();
|
|
387
|
+
mockGitSuccess("https://github.com/test/repo.git");
|
|
388
|
+
fs.existsSync.mockReturnValue(false);
|
|
389
|
+
mockGitSuccess("");
|
|
390
|
+
mockGitSuccess("");
|
|
391
|
+
await workspace.create({
|
|
392
|
+
projectId: "proj",
|
|
393
|
+
sessionId: "sess",
|
|
394
|
+
branch: "feat/branch",
|
|
395
|
+
project: makeProject({ path: "~/my-repos/project" }),
|
|
396
|
+
});
|
|
397
|
+
// git remote get-url should use expanded path
|
|
398
|
+
expect(mockExecFileAsync).toHaveBeenNthCalledWith(1, "git", ["remote", "get-url", "origin"], {
|
|
399
|
+
cwd: "/mock-home/my-repos/project",
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
// ---------------------------------------------------------------------------
|
|
404
|
+
// workspace.destroy()
|
|
405
|
+
// ---------------------------------------------------------------------------
|
|
406
|
+
describe("workspace.destroy()", () => {
|
|
407
|
+
it("removes directory with rmSync when it exists", async () => {
|
|
408
|
+
const workspace = create();
|
|
409
|
+
fs.existsSync.mockReturnValue(true);
|
|
410
|
+
await workspace.destroy("/mock-home/.ao-clones/proj/sess");
|
|
411
|
+
expect(fs.rmSync).toHaveBeenCalledWith("/mock-home/.ao-clones/proj/sess", {
|
|
412
|
+
recursive: true,
|
|
413
|
+
force: true,
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
it("does not throw when directory does not exist", async () => {
|
|
417
|
+
const workspace = create();
|
|
418
|
+
fs.existsSync.mockReturnValue(false);
|
|
419
|
+
await expect(workspace.destroy("/mock-home/.ao-clones/proj/nonexistent")).resolves.toBeUndefined();
|
|
420
|
+
expect(fs.rmSync).not.toHaveBeenCalled();
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
// ---------------------------------------------------------------------------
|
|
424
|
+
// workspace.list()
|
|
425
|
+
// ---------------------------------------------------------------------------
|
|
426
|
+
describe("workspace.list()", () => {
|
|
427
|
+
it("returns empty array when project directory does not exist", async () => {
|
|
428
|
+
const workspace = create();
|
|
429
|
+
fs.existsSync.mockReturnValue(false);
|
|
430
|
+
const result = await workspace.list("myproject");
|
|
431
|
+
expect(result).toEqual([]);
|
|
432
|
+
});
|
|
433
|
+
it("returns workspace entries with branch info", async () => {
|
|
434
|
+
const workspace = create();
|
|
435
|
+
fs.existsSync.mockReturnValue(true);
|
|
436
|
+
fs.readdirSync.mockReturnValue([
|
|
437
|
+
{ name: "session-1", isDirectory: () => true },
|
|
438
|
+
{ name: "session-2", isDirectory: () => true },
|
|
439
|
+
]);
|
|
440
|
+
// git branch --show-current for each directory
|
|
441
|
+
mockGitSuccess("feat/feature-a");
|
|
442
|
+
mockGitSuccess("feat/feature-b");
|
|
443
|
+
const result = await workspace.list("myproject");
|
|
444
|
+
expect(result).toEqual([
|
|
445
|
+
{
|
|
446
|
+
path: "/mock-home/.ao-clones/myproject/session-1",
|
|
447
|
+
branch: "feat/feature-a",
|
|
448
|
+
sessionId: "session-1",
|
|
449
|
+
projectId: "myproject",
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
path: "/mock-home/.ao-clones/myproject/session-2",
|
|
453
|
+
branch: "feat/feature-b",
|
|
454
|
+
sessionId: "session-2",
|
|
455
|
+
projectId: "myproject",
|
|
456
|
+
},
|
|
457
|
+
]);
|
|
458
|
+
// Verify git branch --show-current was called for each entry
|
|
459
|
+
expect(mockExecFileAsync).toHaveBeenNthCalledWith(1, "git", ["branch", "--show-current"], {
|
|
460
|
+
cwd: "/mock-home/.ao-clones/myproject/session-1",
|
|
461
|
+
});
|
|
462
|
+
expect(mockExecFileAsync).toHaveBeenNthCalledWith(2, "git", ["branch", "--show-current"], {
|
|
463
|
+
cwd: "/mock-home/.ao-clones/myproject/session-2",
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
it("skips non-directory entries", async () => {
|
|
467
|
+
const workspace = create();
|
|
468
|
+
fs.existsSync.mockReturnValue(true);
|
|
469
|
+
fs.readdirSync.mockReturnValue([
|
|
470
|
+
{ name: "session-1", isDirectory: () => true },
|
|
471
|
+
{ name: "some-file.txt", isDirectory: () => false },
|
|
472
|
+
{ name: ".DS_Store", isDirectory: () => false },
|
|
473
|
+
]);
|
|
474
|
+
// Only one git call for the single directory
|
|
475
|
+
mockGitSuccess("main");
|
|
476
|
+
const result = await workspace.list("myproject");
|
|
477
|
+
expect(result).toHaveLength(1);
|
|
478
|
+
expect(result[0].sessionId).toBe("session-1");
|
|
479
|
+
expect(mockExecFileAsync).toHaveBeenCalledTimes(1);
|
|
480
|
+
});
|
|
481
|
+
it("skips invalid git repos with console warning", async () => {
|
|
482
|
+
const workspace = create();
|
|
483
|
+
fs.existsSync.mockReturnValue(true);
|
|
484
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
485
|
+
fs.readdirSync.mockReturnValue([
|
|
486
|
+
{ name: "valid-session", isDirectory: () => true },
|
|
487
|
+
{ name: "corrupt-session", isDirectory: () => true },
|
|
488
|
+
]);
|
|
489
|
+
// First directory succeeds
|
|
490
|
+
mockGitSuccess("feat/working");
|
|
491
|
+
// Second directory fails (not a valid git repo)
|
|
492
|
+
mockGitError("fatal: not a git repository");
|
|
493
|
+
const result = await workspace.list("myproject");
|
|
494
|
+
expect(result).toHaveLength(1);
|
|
495
|
+
expect(result[0].sessionId).toBe("valid-session");
|
|
496
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('[workspace-clone] Skipping "corrupt-session"'));
|
|
497
|
+
warnSpy.mockRestore();
|
|
498
|
+
});
|
|
499
|
+
it("emits corrupt_clone_skipped only once per clone path", async () => {
|
|
500
|
+
const workspace = create();
|
|
501
|
+
fs.existsSync.mockReturnValue(true);
|
|
502
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
503
|
+
fs.readdirSync.mockReturnValue([
|
|
504
|
+
{ name: "corrupt-repeat", isDirectory: () => true },
|
|
505
|
+
]);
|
|
506
|
+
mockGitError("fatal: not a git repository");
|
|
507
|
+
mockGitError("fatal: not a git repository");
|
|
508
|
+
await expect(workspace.list("myproject")).resolves.toEqual([]);
|
|
509
|
+
await expect(workspace.list("myproject")).resolves.toEqual([]);
|
|
510
|
+
const corruptCloneCalls = recordActivityEventMock.mock.calls.filter(([event]) => event.kind === "workspace.corrupt_clone_skipped");
|
|
511
|
+
expect(corruptCloneCalls).toHaveLength(1);
|
|
512
|
+
expect(corruptCloneCalls[0][0]).toEqual(expect.objectContaining({
|
|
513
|
+
projectId: "myproject",
|
|
514
|
+
sessionId: "corrupt-repeat",
|
|
515
|
+
source: "workspace",
|
|
516
|
+
kind: "workspace.corrupt_clone_skipped",
|
|
517
|
+
data: expect.objectContaining({
|
|
518
|
+
clonePath: "/mock-home/.ao-clones/myproject/corrupt-repeat",
|
|
519
|
+
}),
|
|
520
|
+
}));
|
|
521
|
+
warnSpy.mockRestore();
|
|
522
|
+
});
|
|
523
|
+
it("rejects invalid projectId with special characters", async () => {
|
|
524
|
+
const workspace = create();
|
|
525
|
+
await expect(workspace.list("bad/project")).rejects.toThrow('Invalid projectId "bad/project"');
|
|
526
|
+
});
|
|
527
|
+
it("rejects projectId with spaces", async () => {
|
|
528
|
+
const workspace = create();
|
|
529
|
+
await expect(workspace.list("bad project")).rejects.toThrow('Invalid projectId "bad project"');
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
// ---------------------------------------------------------------------------
|
|
533
|
+
// workspace.postCreate()
|
|
534
|
+
// ---------------------------------------------------------------------------
|
|
535
|
+
describe("workspace.postCreate()", () => {
|
|
536
|
+
it("runs each postCreate command using getShell()", async () => {
|
|
537
|
+
const workspace = create();
|
|
538
|
+
const info = {
|
|
539
|
+
path: "/mock-home/.ao-clones/proj/sess",
|
|
540
|
+
branch: "feat/branch",
|
|
541
|
+
sessionId: "sess",
|
|
542
|
+
projectId: "proj",
|
|
543
|
+
};
|
|
544
|
+
const project = makeProject({
|
|
545
|
+
postCreate: ["pnpm install", "pnpm build"],
|
|
546
|
+
});
|
|
547
|
+
mockGetShell.mockReturnValue({ cmd: "sh", args: (c) => ["-c", c] });
|
|
548
|
+
// Two commands
|
|
549
|
+
mockGitSuccess("");
|
|
550
|
+
mockGitSuccess("");
|
|
551
|
+
await workspace.postCreate(info, project);
|
|
552
|
+
expect(mockGetShell).toHaveBeenCalled();
|
|
553
|
+
expect(mockExecFileAsync).toHaveBeenCalledTimes(2);
|
|
554
|
+
expect(mockExecFileAsync).toHaveBeenNthCalledWith(1, "sh", ["-c", "pnpm install"], {
|
|
555
|
+
cwd: "/mock-home/.ao-clones/proj/sess",
|
|
556
|
+
});
|
|
557
|
+
expect(mockExecFileAsync).toHaveBeenNthCalledWith(2, "sh", ["-c", "pnpm build"], {
|
|
558
|
+
cwd: "/mock-home/.ao-clones/proj/sess",
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
it("uses Windows shell (pwsh) when getShell returns pwsh", async () => {
|
|
562
|
+
const workspace = create();
|
|
563
|
+
const info = {
|
|
564
|
+
path: "/mock-home/.ao-clones/proj/sess",
|
|
565
|
+
branch: "feat/branch",
|
|
566
|
+
sessionId: "sess",
|
|
567
|
+
projectId: "proj",
|
|
568
|
+
};
|
|
569
|
+
const project = makeProject({ postCreate: ["npm install"] });
|
|
570
|
+
mockGetShell.mockReturnValueOnce({
|
|
571
|
+
cmd: "pwsh",
|
|
572
|
+
args: (c) => ["-NoLogo", "-NonInteractive", "-Command", c],
|
|
573
|
+
});
|
|
574
|
+
mockGitSuccess("");
|
|
575
|
+
await workspace.postCreate(info, project);
|
|
576
|
+
expect(mockExecFileAsync).toHaveBeenCalledWith("pwsh", ["-NoLogo", "-NonInteractive", "-Command", "npm install"], { cwd: "/mock-home/.ao-clones/proj/sess" });
|
|
577
|
+
});
|
|
578
|
+
it("does nothing when postCreate is undefined", async () => {
|
|
579
|
+
const workspace = create();
|
|
580
|
+
const info = {
|
|
581
|
+
path: "/mock-home/.ao-clones/proj/sess",
|
|
582
|
+
branch: "feat/branch",
|
|
583
|
+
sessionId: "sess",
|
|
584
|
+
projectId: "proj",
|
|
585
|
+
};
|
|
586
|
+
const project = makeProject(); // no postCreate
|
|
587
|
+
await workspace.postCreate(info, project);
|
|
588
|
+
expect(mockExecFileAsync).not.toHaveBeenCalled();
|
|
589
|
+
});
|
|
590
|
+
it("does nothing when postCreate is empty array", async () => {
|
|
591
|
+
const workspace = create();
|
|
592
|
+
const info = {
|
|
593
|
+
path: "/mock-home/.ao-clones/proj/sess",
|
|
594
|
+
branch: "feat/branch",
|
|
595
|
+
sessionId: "sess",
|
|
596
|
+
projectId: "proj",
|
|
597
|
+
};
|
|
598
|
+
const project = makeProject({ postCreate: [] });
|
|
599
|
+
await workspace.postCreate(info, project);
|
|
600
|
+
expect(mockExecFileAsync).not.toHaveBeenCalled();
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
//# sourceMappingURL=index.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../src/__tests__/index.test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Workspace } from "@made-by-moonlight/athene-core";
|
|
2
|
+
export declare const manifest: {
|
|
3
|
+
name: string;
|
|
4
|
+
slot: "workspace";
|
|
5
|
+
description: string;
|
|
6
|
+
version: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function create(config?: Record<string, unknown>): Workspace;
|
|
9
|
+
declare const _default: {
|
|
10
|
+
manifest: {
|
|
11
|
+
name: string;
|
|
12
|
+
slot: "workspace";
|
|
13
|
+
description: string;
|
|
14
|
+
version: string;
|
|
15
|
+
};
|
|
16
|
+
create: typeof create;
|
|
17
|
+
};
|
|
18
|
+
export default _default;
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAKL,KAAK,SAAS,EAGf,MAAM,gCAAgC,CAAC;AAIxC,eAAO,MAAM,QAAQ;;;;;CAKpB,CAAC;AAmCF,wBAAgB,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAuOlE;;;;;;;;;;AAED,wBAAsE"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { existsSync, rmSync, mkdirSync, readdirSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { getShell, recordActivityEvent, } from "@made-by-moonlight/athene-core";
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
export const manifest = {
|
|
9
|
+
name: "clone",
|
|
10
|
+
slot: "workspace",
|
|
11
|
+
description: "Workspace plugin: git clone isolation",
|
|
12
|
+
version: "0.1.0",
|
|
13
|
+
};
|
|
14
|
+
/** Run a git command in a given directory */
|
|
15
|
+
async function git(cwd, ...args) {
|
|
16
|
+
const { stdout } = await execFileAsync("git", args, { cwd });
|
|
17
|
+
return stdout.trimEnd();
|
|
18
|
+
}
|
|
19
|
+
/** Only allow safe characters in path segments to prevent directory traversal */
|
|
20
|
+
const SAFE_PATH_SEGMENT = /^[a-zA-Z0-9_-]+$/;
|
|
21
|
+
function assertSafePathSegment(value, label) {
|
|
22
|
+
if (!SAFE_PATH_SEGMENT.test(value)) {
|
|
23
|
+
throw new Error(`Invalid ${label} "${value}": must match ${SAFE_PATH_SEGMENT}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Expand ~ to home directory */
|
|
27
|
+
function expandPath(p) {
|
|
28
|
+
if (p.startsWith("~/")) {
|
|
29
|
+
return join(homedir(), p.slice(2));
|
|
30
|
+
}
|
|
31
|
+
return p;
|
|
32
|
+
}
|
|
33
|
+
function getErrorMessage(err) {
|
|
34
|
+
return err instanceof Error ? err.message : String(err);
|
|
35
|
+
}
|
|
36
|
+
function isBranchAlreadyExistsError(err) {
|
|
37
|
+
return getErrorMessage(err).toLowerCase().includes("already exists");
|
|
38
|
+
}
|
|
39
|
+
const emittedCorruptClonePaths = new Set();
|
|
40
|
+
export function create(config) {
|
|
41
|
+
const cloneBaseDir = config?.cloneDir
|
|
42
|
+
? expandPath(config.cloneDir)
|
|
43
|
+
: join(homedir(), ".ao-clones");
|
|
44
|
+
return {
|
|
45
|
+
name: "clone",
|
|
46
|
+
async create(cfg) {
|
|
47
|
+
assertSafePathSegment(cfg.projectId, "projectId");
|
|
48
|
+
assertSafePathSegment(cfg.sessionId, "sessionId");
|
|
49
|
+
const repoPath = expandPath(cfg.project.path);
|
|
50
|
+
const projectCloneDir = join(cloneBaseDir, cfg.projectId);
|
|
51
|
+
const clonePath = join(projectCloneDir, cfg.sessionId);
|
|
52
|
+
mkdirSync(projectCloneDir, { recursive: true });
|
|
53
|
+
// Get the remote URL from the source repo
|
|
54
|
+
let remoteUrl;
|
|
55
|
+
try {
|
|
56
|
+
remoteUrl = await git(repoPath, "remote", "get-url", "origin");
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Fallback: use the local path as source
|
|
60
|
+
remoteUrl = repoPath;
|
|
61
|
+
}
|
|
62
|
+
// Fail early if destination already exists — avoid deleting a pre-existing
|
|
63
|
+
// workspace in the error handler below
|
|
64
|
+
if (existsSync(clonePath)) {
|
|
65
|
+
throw new Error(`Workspace path "${clonePath}" already exists for session "${cfg.sessionId}" — destroy it before re-creating`);
|
|
66
|
+
}
|
|
67
|
+
// Clone using --reference for faster clone with shared objects
|
|
68
|
+
try {
|
|
69
|
+
await execFileAsync("git", [
|
|
70
|
+
"clone",
|
|
71
|
+
"--reference",
|
|
72
|
+
repoPath,
|
|
73
|
+
"--branch",
|
|
74
|
+
cfg.project.defaultBranch,
|
|
75
|
+
remoteUrl,
|
|
76
|
+
clonePath,
|
|
77
|
+
]);
|
|
78
|
+
}
|
|
79
|
+
catch (cloneErr) {
|
|
80
|
+
// Clone failed — clean up any partial directory left on disk
|
|
81
|
+
if (existsSync(clonePath)) {
|
|
82
|
+
rmSync(clonePath, { recursive: true, force: true });
|
|
83
|
+
}
|
|
84
|
+
const msg = cloneErr instanceof Error ? cloneErr.message : String(cloneErr);
|
|
85
|
+
throw new Error(`Failed to clone repo for session "${cfg.sessionId}": ${msg}`, {
|
|
86
|
+
cause: cloneErr,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// Create and checkout the feature branch
|
|
90
|
+
try {
|
|
91
|
+
await git(clonePath, "checkout", "-b", cfg.branch);
|
|
92
|
+
}
|
|
93
|
+
catch (branchErr) {
|
|
94
|
+
// Branch may exist on remote — try plain checkout, but only label it
|
|
95
|
+
// as a branch_collision when git reported the distinct collision shape.
|
|
96
|
+
if (isBranchAlreadyExistsError(branchErr)) {
|
|
97
|
+
recordActivityEvent({
|
|
98
|
+
projectId: cfg.projectId,
|
|
99
|
+
sessionId: cfg.sessionId,
|
|
100
|
+
source: "workspace",
|
|
101
|
+
kind: "workspace.branch_collision",
|
|
102
|
+
level: "warn",
|
|
103
|
+
summary: `branch "${cfg.branch}" already exists; falling back to checkout`,
|
|
104
|
+
data: {
|
|
105
|
+
plugin: "workspace-clone",
|
|
106
|
+
branch: cfg.branch,
|
|
107
|
+
errorMessage: getErrorMessage(branchErr),
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
await git(clonePath, "checkout", cfg.branch);
|
|
113
|
+
}
|
|
114
|
+
catch (checkoutErr) {
|
|
115
|
+
// Both checkout attempts failed — clean up the orphaned clone
|
|
116
|
+
rmSync(clonePath, { recursive: true, force: true });
|
|
117
|
+
const msg = checkoutErr instanceof Error ? checkoutErr.message : String(checkoutErr);
|
|
118
|
+
throw new Error(`Failed to checkout branch "${cfg.branch}" in clone: ${msg}`, {
|
|
119
|
+
cause: checkoutErr,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
path: clonePath,
|
|
125
|
+
branch: cfg.branch,
|
|
126
|
+
sessionId: cfg.sessionId,
|
|
127
|
+
projectId: cfg.projectId,
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
async destroy(workspacePath) {
|
|
131
|
+
if (existsSync(workspacePath)) {
|
|
132
|
+
rmSync(workspacePath, { recursive: true, force: true });
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
async list(projectId) {
|
|
136
|
+
assertSafePathSegment(projectId, "projectId");
|
|
137
|
+
const projectCloneDir = join(cloneBaseDir, projectId);
|
|
138
|
+
if (!existsSync(projectCloneDir))
|
|
139
|
+
return [];
|
|
140
|
+
const entries = readdirSync(projectCloneDir, { withFileTypes: true });
|
|
141
|
+
const infos = [];
|
|
142
|
+
for (const entry of entries) {
|
|
143
|
+
if (!entry.isDirectory())
|
|
144
|
+
continue;
|
|
145
|
+
const clonePath = join(projectCloneDir, entry.name);
|
|
146
|
+
let branch;
|
|
147
|
+
try {
|
|
148
|
+
branch = await git(clonePath, "branch", "--show-current");
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
// Warn about corrupted clones instead of silently skipping.
|
|
152
|
+
// RCA: "session shows up on disk but isn't returned by list()".
|
|
153
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
154
|
+
// eslint-disable-next-line no-console -- expected diagnostic for corrupted clones
|
|
155
|
+
console.warn(`[workspace-clone] Skipping "${entry.name}": not a valid git repo (${msg})`);
|
|
156
|
+
if (!emittedCorruptClonePaths.has(clonePath)) {
|
|
157
|
+
emittedCorruptClonePaths.add(clonePath);
|
|
158
|
+
recordActivityEvent({
|
|
159
|
+
projectId,
|
|
160
|
+
sessionId: entry.name,
|
|
161
|
+
source: "workspace",
|
|
162
|
+
kind: "workspace.corrupt_clone_skipped",
|
|
163
|
+
level: "warn",
|
|
164
|
+
summary: `skipped corrupt clone "${entry.name}"`,
|
|
165
|
+
data: {
|
|
166
|
+
plugin: "workspace-clone",
|
|
167
|
+
clonePath,
|
|
168
|
+
errorMessage: msg,
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
infos.push({
|
|
175
|
+
path: clonePath,
|
|
176
|
+
branch,
|
|
177
|
+
sessionId: entry.name,
|
|
178
|
+
projectId,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return infos;
|
|
182
|
+
},
|
|
183
|
+
async exists(workspacePath) {
|
|
184
|
+
if (!existsSync(workspacePath))
|
|
185
|
+
return false;
|
|
186
|
+
try {
|
|
187
|
+
await execFileAsync("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
188
|
+
cwd: workspacePath,
|
|
189
|
+
timeout: 30_000,
|
|
190
|
+
});
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
async restore(cfg, workspacePath) {
|
|
198
|
+
const repoPath = expandPath(cfg.project.path);
|
|
199
|
+
// Get remote URL
|
|
200
|
+
let remoteUrl;
|
|
201
|
+
try {
|
|
202
|
+
remoteUrl = await git(repoPath, "remote", "get-url", "origin");
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
remoteUrl = repoPath;
|
|
206
|
+
}
|
|
207
|
+
// Clone fresh — clean up partial directory on failure
|
|
208
|
+
try {
|
|
209
|
+
await execFileAsync("git", [
|
|
210
|
+
"clone",
|
|
211
|
+
"--reference",
|
|
212
|
+
repoPath,
|
|
213
|
+
"--branch",
|
|
214
|
+
cfg.project.defaultBranch,
|
|
215
|
+
remoteUrl,
|
|
216
|
+
workspacePath,
|
|
217
|
+
]);
|
|
218
|
+
}
|
|
219
|
+
catch (cloneErr) {
|
|
220
|
+
rmSync(workspacePath, { recursive: true, force: true });
|
|
221
|
+
const msg = cloneErr instanceof Error ? cloneErr.message : String(cloneErr);
|
|
222
|
+
throw new Error(`Clone failed during restore: ${msg}`, { cause: cloneErr });
|
|
223
|
+
}
|
|
224
|
+
// Try to checkout the branch
|
|
225
|
+
try {
|
|
226
|
+
await git(workspacePath, "checkout", cfg.branch);
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
try {
|
|
230
|
+
await git(workspacePath, "checkout", "-b", cfg.branch);
|
|
231
|
+
}
|
|
232
|
+
catch (checkoutErr) {
|
|
233
|
+
rmSync(workspacePath, { recursive: true, force: true });
|
|
234
|
+
const msg = checkoutErr instanceof Error ? checkoutErr.message : String(checkoutErr);
|
|
235
|
+
throw new Error(`Failed to checkout branch "${cfg.branch}" during restore: ${msg}`, {
|
|
236
|
+
cause: checkoutErr,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
path: workspacePath,
|
|
242
|
+
branch: cfg.branch,
|
|
243
|
+
sessionId: cfg.sessionId,
|
|
244
|
+
projectId: cfg.projectId,
|
|
245
|
+
};
|
|
246
|
+
},
|
|
247
|
+
async postCreate(info, project) {
|
|
248
|
+
// Run postCreate hooks
|
|
249
|
+
// NOTE: commands run with full shell privileges — they come from trusted YAML config
|
|
250
|
+
if (project.postCreate) {
|
|
251
|
+
const shell = getShell();
|
|
252
|
+
for (const command of project.postCreate) {
|
|
253
|
+
await execFileAsync(shell.cmd, shell.args(command), { cwd: info.path });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
export default { manifest, create };
|
|
260
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@made-by-moonlight/athene-plugin-workspace-clone",
|
|
3
|
+
"version": "0.9.1",
|
|
4
|
+
"description": "Workspace plugin: git clone",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/slievr/Athene.git",
|
|
21
|
+
"directory": "packages/plugins/workspace-clone"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/slievr/Athene",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/slievr/Athene/issues"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20.0.0"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@made-by-moonlight/athene-core": "0.9.1"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^25.2.3",
|
|
35
|
+
"typescript": "^5.7.0",
|
|
36
|
+
"vitest": "^3.0.0"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsc",
|
|
43
|
+
"typecheck": "tsc --noEmit",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"test:watch": "vitest",
|
|
46
|
+
"clean": "rm -rf dist"
|
|
47
|
+
}
|
|
48
|
+
}
|