@symbiosis-lab/moss-plugin-github 1.5.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/CHANGELOG.md +30 -0
- package/README.md +18 -0
- package/assets/icon.svg +3 -0
- package/assets/manifest.json +19 -0
- package/e2e/deploy-api.test.ts +1129 -0
- package/e2e/moss-cli.test.ts +478 -0
- package/features/auth/device-flow.feature +41 -0
- package/features/deploy/validation.feature +50 -0
- package/features/steps/auth.steps.ts +285 -0
- package/features/steps/deploy.steps.ts +354 -0
- package/package.json +51 -0
- package/src/__tests__/auth-flow.integration.test.ts +738 -0
- package/src/__tests__/auth.test.ts +147 -0
- package/src/__tests__/configure-domain.test.ts +263 -0
- package/src/__tests__/deploy.integration.test.ts +798 -0
- package/src/__tests__/git.test.ts +190 -0
- package/src/__tests__/github-api.test.ts +761 -0
- package/src/__tests__/github-deploy.test.ts +2411 -0
- package/src/__tests__/progress-timeout.test.ts +209 -0
- package/src/__tests__/repo-setup-progress.test.ts +367 -0
- package/src/__tests__/repo-setup.test.ts +370 -0
- package/src/__tests__/token.test.ts +152 -0
- package/src/__tests__/utils.test.ts +129 -0
- package/src/__tests__/workflow.test.ts +146 -0
- package/src/auth.ts +588 -0
- package/src/constants.ts +7 -0
- package/src/git.ts +60 -0
- package/src/github-api.ts +601 -0
- package/src/github-deploy.ts +593 -0
- package/src/main.ts +646 -0
- package/src/repo-setup.ts +685 -0
- package/src/token.ts +202 -0
- package/src/types.ts +91 -0
- package/src/utils.ts +108 -0
- package/src/workflow.ts +79 -0
- package/test-helpers/mock-github-api.ts +217 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +50 -0
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E Tests for GitHub Deployer Plugin using moss CLI
|
|
3
|
+
*
|
|
4
|
+
* These tests verify the plugin works correctly when invoked through
|
|
5
|
+
* the moss CLI, testing real-world scenarios.
|
|
6
|
+
*
|
|
7
|
+
* Requirements:
|
|
8
|
+
* - moss binary built and available (set MOSS_BINARY env var or build locally)
|
|
9
|
+
* - Plugin built (npm run build)
|
|
10
|
+
* - Tests create temporary directories for fixtures
|
|
11
|
+
* - Display server available (xvfb-run on Linux CI)
|
|
12
|
+
*
|
|
13
|
+
* Plugin Execution:
|
|
14
|
+
* - Use --wait-plugins flag to wait for plugin hooks to complete
|
|
15
|
+
* - Plugin JavaScript runs in Tauri webview (requires display)
|
|
16
|
+
* - Tests verify plugin validation messages and error handling
|
|
17
|
+
*
|
|
18
|
+
* CI Setup:
|
|
19
|
+
* - The workflow downloads moss binary from releases before running tests
|
|
20
|
+
* - Set MOSS_BINARY environment variable to the binary path
|
|
21
|
+
* - Linux CI uses xvfb-run for display server
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { describe, it, expect, beforeAll, afterAll, beforeEach } from "vitest";
|
|
25
|
+
import { execSync, spawn, ChildProcess } from "child_process";
|
|
26
|
+
import * as fs from "fs";
|
|
27
|
+
import * as path from "path";
|
|
28
|
+
import * as os from "os";
|
|
29
|
+
|
|
30
|
+
// Path to moss binary - check env var first, then fallback to local dev path
|
|
31
|
+
const MOSS_BINARY = process.env.MOSS_BINARY || path.join(
|
|
32
|
+
__dirname,
|
|
33
|
+
"../../../../moss/develop/src-tauri/target/debug/moss"
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
// Check if we're using a release binary (from CI) vs local dev build
|
|
37
|
+
const IS_CI_BINARY = !!process.env.MOSS_BINARY;
|
|
38
|
+
|
|
39
|
+
// Check if --wait-plugins is supported (v0.3.1+)
|
|
40
|
+
let HAS_WAIT_PLUGINS = false;
|
|
41
|
+
|
|
42
|
+
// Path to plugin dist
|
|
43
|
+
const PLUGIN_DIST = path.join(__dirname, "../dist");
|
|
44
|
+
|
|
45
|
+
// Test fixture directory
|
|
46
|
+
let testDir: string;
|
|
47
|
+
let fixtureCounter = 0;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Create a test fixture directory with optional git initialization
|
|
51
|
+
*/
|
|
52
|
+
function createFixture(options: {
|
|
53
|
+
withGit?: boolean;
|
|
54
|
+
withRemote?: string;
|
|
55
|
+
withPlugin?: boolean;
|
|
56
|
+
content?: Record<string, string>;
|
|
57
|
+
}): string {
|
|
58
|
+
const fixtureName = `moss-e2e-${Date.now()}-${fixtureCounter++}`;
|
|
59
|
+
const fixturePath = path.join(testDir, fixtureName);
|
|
60
|
+
fs.mkdirSync(fixturePath, { recursive: true });
|
|
61
|
+
|
|
62
|
+
// Create content files
|
|
63
|
+
const defaultContent = {
|
|
64
|
+
"index.md": "# Hello World\n\nThis is a test site.",
|
|
65
|
+
"about.md": "# About\n\nAbout page content.",
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const content = options.content || defaultContent;
|
|
69
|
+
for (const [filename, fileContent] of Object.entries(content)) {
|
|
70
|
+
const filePath = path.join(fixturePath, filename);
|
|
71
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
72
|
+
fs.writeFileSync(filePath, fileContent);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Initialize git if requested
|
|
76
|
+
if (options.withGit) {
|
|
77
|
+
execSync("git init", { cwd: fixturePath, stdio: "pipe" });
|
|
78
|
+
execSync("git config user.email 'test@example.com'", {
|
|
79
|
+
cwd: fixturePath,
|
|
80
|
+
stdio: "pipe",
|
|
81
|
+
});
|
|
82
|
+
execSync("git config user.name 'Test User'", {
|
|
83
|
+
cwd: fixturePath,
|
|
84
|
+
stdio: "pipe",
|
|
85
|
+
});
|
|
86
|
+
execSync("git add .", { cwd: fixturePath, stdio: "pipe" });
|
|
87
|
+
execSync('git commit -m "Initial commit"', {
|
|
88
|
+
cwd: fixturePath,
|
|
89
|
+
stdio: "pipe",
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Add remote if requested
|
|
93
|
+
if (options.withRemote) {
|
|
94
|
+
execSync(`git remote add origin ${options.withRemote}`, {
|
|
95
|
+
cwd: fixturePath,
|
|
96
|
+
stdio: "pipe",
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Link plugin if requested
|
|
102
|
+
if (options.withPlugin) {
|
|
103
|
+
const mossDir = path.join(fixturePath, ".moss");
|
|
104
|
+
const pluginsDir = path.join(mossDir, "plugins");
|
|
105
|
+
const githubPluginDir = path.join(pluginsDir, "github");
|
|
106
|
+
|
|
107
|
+
fs.mkdirSync(pluginsDir, { recursive: true });
|
|
108
|
+
|
|
109
|
+
// Copy plugin files (symlink might not work on all systems)
|
|
110
|
+
const pluginFiles = ["main.bundle.js", "manifest.json", "icon.svg"];
|
|
111
|
+
fs.mkdirSync(githubPluginDir, { recursive: true });
|
|
112
|
+
|
|
113
|
+
for (const file of pluginFiles) {
|
|
114
|
+
const src = path.join(PLUGIN_DIST, file);
|
|
115
|
+
const dest = path.join(githubPluginDir, file);
|
|
116
|
+
if (fs.existsSync(src)) {
|
|
117
|
+
fs.copyFileSync(src, dest);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return fixturePath;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Run moss CLI and return stdout/stderr
|
|
127
|
+
*/
|
|
128
|
+
function runMoss(
|
|
129
|
+
args: string[],
|
|
130
|
+
options?: { cwd?: string; timeout?: number }
|
|
131
|
+
): Promise<{ stdout: string; stderr: string; code: number }> {
|
|
132
|
+
return new Promise((resolve) => {
|
|
133
|
+
const proc = spawn(MOSS_BINARY, args, {
|
|
134
|
+
cwd: options?.cwd || testDir,
|
|
135
|
+
timeout: options?.timeout || 30000,
|
|
136
|
+
env: {
|
|
137
|
+
...process.env,
|
|
138
|
+
// Disable interactive prompts
|
|
139
|
+
CI: "true",
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
let stdout = "";
|
|
144
|
+
let stderr = "";
|
|
145
|
+
|
|
146
|
+
proc.stdout?.on("data", (data) => {
|
|
147
|
+
stdout += data.toString();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
proc.stderr?.on("data", (data) => {
|
|
151
|
+
stderr += data.toString();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
proc.on("close", (code) => {
|
|
155
|
+
resolve({ stdout, stderr, code: code ?? 1 });
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
proc.on("error", (err) => {
|
|
159
|
+
stderr += err.message;
|
|
160
|
+
resolve({ stdout, stderr, code: 1 });
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
describe("moss CLI E2E Tests", () => {
|
|
166
|
+
beforeAll(async () => {
|
|
167
|
+
// Verify moss binary exists - fail if not available
|
|
168
|
+
if (!fs.existsSync(MOSS_BINARY)) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`moss binary not found at ${MOSS_BINARY}. ` +
|
|
171
|
+
`Either build moss locally, or set MOSS_BINARY environment variable. ` +
|
|
172
|
+
`In CI, the binary should be downloaded from releases.`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Verify plugin dist exists - fail if not built
|
|
177
|
+
if (!fs.existsSync(PLUGIN_DIST)) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`Plugin dist not found at ${PLUGIN_DIST}. Please build the plugin first with 'npm run build'.`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Create temp directory for test fixtures
|
|
184
|
+
testDir = fs.mkdtempSync(path.join(os.tmpdir(), "moss-e2e-"));
|
|
185
|
+
|
|
186
|
+
// Check if --wait-plugins is supported
|
|
187
|
+
const { stdout } = await runMoss(["--help"]);
|
|
188
|
+
HAS_WAIT_PLUGINS = stdout.includes("--wait-plugins");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
afterAll(() => {
|
|
192
|
+
// Cleanup test directory
|
|
193
|
+
if (testDir && fs.existsSync(testDir)) {
|
|
194
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe("moss --help", () => {
|
|
199
|
+
it("shows help message with build command", async () => {
|
|
200
|
+
const { stdout, code } = await runMoss(["--help"]);
|
|
201
|
+
|
|
202
|
+
expect(code).toBe(0);
|
|
203
|
+
expect(stdout).toContain("moss");
|
|
204
|
+
expect(stdout).toContain("build");
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("shows help message with deploy command", async () => {
|
|
208
|
+
const { stdout, code } = await runMoss(["--help"]);
|
|
209
|
+
|
|
210
|
+
expect(code).toBe(0);
|
|
211
|
+
expect(stdout).toContain("deploy");
|
|
212
|
+
expect(stdout).toContain("GitHub Pages");
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe("Basic compilation (no plugins)", () => {
|
|
217
|
+
it("builds a folder with markdown files", async () => {
|
|
218
|
+
const fixture = createFixture({ content: { "index.md": "# Hello" } });
|
|
219
|
+
|
|
220
|
+
const { stdout, stderr, code } = await runMoss([
|
|
221
|
+
"build",
|
|
222
|
+
fixture,
|
|
223
|
+
"--no-plugins",
|
|
224
|
+
]);
|
|
225
|
+
|
|
226
|
+
// Should succeed
|
|
227
|
+
expect(code).toBe(0);
|
|
228
|
+
expect(stdout + stderr).toContain("Compiling");
|
|
229
|
+
|
|
230
|
+
// Should create .moss/site directory
|
|
231
|
+
const siteDir = path.join(fixture, ".moss", "site");
|
|
232
|
+
expect(fs.existsSync(siteDir)).toBe(true);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe("Deploy command", () => {
|
|
237
|
+
// This test verifies exit code 1 when no deploy plugin is installed.
|
|
238
|
+
// The behavior was fixed in moss v0.3.0 (Bug 5).
|
|
239
|
+
// Only runs in CI with release binary - local dev builds may have different behavior.
|
|
240
|
+
it.skipIf(!IS_CI_BINARY)("shows 'no plugin' message when no deploy plugin installed", async () => {
|
|
241
|
+
const fixture = createFixture({
|
|
242
|
+
withGit: true,
|
|
243
|
+
withRemote: "git@github.com:user/test-repo.git",
|
|
244
|
+
withPlugin: false, // No plugin installed
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const { stdout, stderr, code } = await runMoss(["deploy", fixture]);
|
|
248
|
+
|
|
249
|
+
// Should exit with error (no plugin)
|
|
250
|
+
expect(code).toBe(1);
|
|
251
|
+
const output = stdout + stderr;
|
|
252
|
+
expect(output).toMatch(/no.*plugin|install.*plugin/i);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("builds before deploying", async () => {
|
|
256
|
+
const fixture = createFixture({
|
|
257
|
+
withGit: true,
|
|
258
|
+
withRemote: "git@github.com:user/test-repo.git",
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const { stdout, stderr } = await runMoss(["deploy", fixture]);
|
|
262
|
+
|
|
263
|
+
// Should show compilation step
|
|
264
|
+
const output = stdout + stderr;
|
|
265
|
+
expect(output).toContain("Compiling");
|
|
266
|
+
|
|
267
|
+
// Should create .moss/site directory
|
|
268
|
+
const siteDir = path.join(fixture, ".moss", "site");
|
|
269
|
+
expect(fs.existsSync(siteDir)).toBe(true);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("shows progress messages", async () => {
|
|
273
|
+
const fixture = createFixture({
|
|
274
|
+
withGit: true,
|
|
275
|
+
withRemote: "git@github.com:user/test-repo.git",
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const { stdout, stderr } = await runMoss(["deploy", fixture]);
|
|
279
|
+
const output = stdout + stderr;
|
|
280
|
+
|
|
281
|
+
// Should show deploy step
|
|
282
|
+
expect(output).toContain("Deploying");
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
describe("Plugin discovery", () => {
|
|
287
|
+
it("creates plugin directory structure correctly", async () => {
|
|
288
|
+
const fixture = createFixture({
|
|
289
|
+
withPlugin: true,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Verify plugin files were copied
|
|
293
|
+
const pluginsDir = path.join(fixture, ".moss", "plugins", "github");
|
|
294
|
+
expect(fs.existsSync(pluginsDir)).toBe(true);
|
|
295
|
+
expect(fs.existsSync(path.join(pluginsDir, "manifest.json"))).toBe(true);
|
|
296
|
+
expect(fs.existsSync(path.join(pluginsDir, "main.bundle.js"))).toBe(true);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("manifest.json has correct structure", async () => {
|
|
300
|
+
const fixture = createFixture({
|
|
301
|
+
withPlugin: true,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const manifestPath = path.join(fixture, ".moss", "plugins", "github", "manifest.json");
|
|
305
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
306
|
+
|
|
307
|
+
expect(manifest.name).toBe("github");
|
|
308
|
+
expect(manifest.capabilities).toContain("deploy");
|
|
309
|
+
expect(manifest.entry).toBe("main.bundle.js");
|
|
310
|
+
expect(manifest.global_name).toBe("GithubPlugin");
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Plugin Execution Tests (with --wait-plugins)
|
|
316
|
+
*
|
|
317
|
+
* These tests use --wait-plugins to wait for plugin hooks to complete.
|
|
318
|
+
* Requires:
|
|
319
|
+
* - moss v0.3.1+ with --wait-plugins support
|
|
320
|
+
* - Display server (xvfb-run on Linux CI)
|
|
321
|
+
*
|
|
322
|
+
* The plugin validation logic runs in the webview and reports errors
|
|
323
|
+
* via Tauri IPC commands which are captured in the CLI output.
|
|
324
|
+
*/
|
|
325
|
+
describe("Plugin execution (with --wait-plugins)", () => {
|
|
326
|
+
it.skipIf(!HAS_WAIT_PLUGINS)("reports validation errors for non-git repos", async () => {
|
|
327
|
+
// Create fixture WITHOUT git initialization - plugin should detect this
|
|
328
|
+
const fixture = createFixture({
|
|
329
|
+
withGit: false, // Not a git repo
|
|
330
|
+
withPlugin: true,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// First build the site (creates .moss/site)
|
|
334
|
+
await runMoss(["build", fixture, "--no-plugins"]);
|
|
335
|
+
|
|
336
|
+
// Then deploy - plugin should report "not a git repository" error
|
|
337
|
+
const { stdout, stderr, code } = await runMoss(
|
|
338
|
+
["deploy", fixture, "--wait-plugins"],
|
|
339
|
+
{ timeout: 60000 }
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
const output = stdout + stderr;
|
|
343
|
+
|
|
344
|
+
// Plugin should have executed and reported validation error
|
|
345
|
+
// The exact error message depends on the plugin implementation
|
|
346
|
+
expect(output).toMatch(/not.*git.*repository|git.*init|Repository setup/i);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it.skipIf(!HAS_WAIT_PLUGINS)("reports errors for missing remote", async () => {
|
|
350
|
+
// Create git repo WITHOUT remote
|
|
351
|
+
const fixture = createFixture({
|
|
352
|
+
withGit: true,
|
|
353
|
+
withRemote: undefined, // No remote configured
|
|
354
|
+
withPlugin: true,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// First build the site
|
|
358
|
+
await runMoss(["build", fixture, "--no-plugins"]);
|
|
359
|
+
|
|
360
|
+
// Then deploy - plugin should report "no remote" error
|
|
361
|
+
const { stdout, stderr, code } = await runMoss(
|
|
362
|
+
["deploy", fixture, "--wait-plugins"],
|
|
363
|
+
{ timeout: 60000 }
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
const output = stdout + stderr;
|
|
367
|
+
|
|
368
|
+
// Plugin should report missing remote or show repo setup UI
|
|
369
|
+
expect(output).toMatch(/no.*remote|Repository setup|cancelled/i);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it.skipIf(!HAS_WAIT_PLUGINS)("reports errors for non-GitHub remote", async () => {
|
|
373
|
+
// Create git repo with GitLab remote (not GitHub)
|
|
374
|
+
const fixture = createFixture({
|
|
375
|
+
withGit: true,
|
|
376
|
+
withRemote: "git@gitlab.com:user/repo.git", // GitLab, not GitHub
|
|
377
|
+
withPlugin: true,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// First build the site
|
|
381
|
+
await runMoss(["build", fixture, "--no-plugins"]);
|
|
382
|
+
|
|
383
|
+
// Then deploy - plugin should report "not a GitHub URL" error
|
|
384
|
+
const { stdout, stderr, code } = await runMoss(
|
|
385
|
+
["deploy", fixture, "--wait-plugins"],
|
|
386
|
+
{ timeout: 60000 }
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
const output = stdout + stderr;
|
|
390
|
+
|
|
391
|
+
// Plugin should report non-GitHub remote error
|
|
392
|
+
expect(output).toMatch(/not.*GitHub|GitHub.*only|github\.com/i);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it.skipIf(!HAS_WAIT_PLUGINS)("validates site is built before deploy", async () => {
|
|
396
|
+
// Create fixture with valid GitHub setup but NO built site
|
|
397
|
+
const fixture = createFixture({
|
|
398
|
+
withGit: true,
|
|
399
|
+
withRemote: "git@github.com:testuser/testrepo.git",
|
|
400
|
+
withPlugin: true,
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Do NOT build - go straight to deploy
|
|
404
|
+
// Plugin should report "no site files" error
|
|
405
|
+
const { stdout, stderr, code } = await runMoss(
|
|
406
|
+
["deploy", fixture, "--wait-plugins"],
|
|
407
|
+
{ timeout: 60000 }
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
const output = stdout + stderr;
|
|
411
|
+
|
|
412
|
+
// Plugin should report empty site or compilation needed
|
|
413
|
+
expect(output).toMatch(/site.*empty|build.*first|no.*files/i);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it.skipIf(!HAS_WAIT_PLUGINS)("shows deploy progress messages", async () => {
|
|
417
|
+
// Create fixture with valid GitHub setup
|
|
418
|
+
const fixture = createFixture({
|
|
419
|
+
withGit: true,
|
|
420
|
+
withRemote: "git@github.com:testuser/testrepo.git",
|
|
421
|
+
withPlugin: true,
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Build first
|
|
425
|
+
await runMoss(["build", fixture, "--no-plugins"]);
|
|
426
|
+
|
|
427
|
+
// Deploy with --wait-plugins to see full output
|
|
428
|
+
const { stdout, stderr, code } = await runMoss(
|
|
429
|
+
["deploy", fixture, "--wait-plugins"],
|
|
430
|
+
{ timeout: 120000 } // Longer timeout for full deploy
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
const output = stdout + stderr;
|
|
434
|
+
|
|
435
|
+
// Should show deploy-related messages
|
|
436
|
+
// Note: actual deployment will fail without real git push,
|
|
437
|
+
// but we should see the validation passing and deploy starting
|
|
438
|
+
expect(output).toMatch(/deploy|GitHub|validat/i);
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Build with plugins tests
|
|
444
|
+
*
|
|
445
|
+
* Tests compilation with plugins enabled (no --no-plugins flag).
|
|
446
|
+
* The GitHub plugin has deploy capability, not process/enhance,
|
|
447
|
+
* so it won't affect compilation, but this verifies plugin loading works.
|
|
448
|
+
*/
|
|
449
|
+
describe("Build with plugins", () => {
|
|
450
|
+
it.skipIf(!HAS_WAIT_PLUGINS)("builds successfully with plugin installed", async () => {
|
|
451
|
+
const fixture = createFixture({
|
|
452
|
+
withGit: true,
|
|
453
|
+
withRemote: "git@github.com:testuser/testrepo.git",
|
|
454
|
+
withPlugin: true,
|
|
455
|
+
content: {
|
|
456
|
+
"index.md": "# Test Site\n\nHello world!",
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
// Build WITH plugins (default behavior)
|
|
461
|
+
const { stdout, stderr, code } = await runMoss(
|
|
462
|
+
["build", fixture, "--wait-plugins"],
|
|
463
|
+
{ timeout: 60000 }
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
// Should succeed
|
|
467
|
+
expect(code).toBe(0);
|
|
468
|
+
|
|
469
|
+
// Should create site directory
|
|
470
|
+
const siteDir = path.join(fixture, ".moss", "site");
|
|
471
|
+
expect(fs.existsSync(siteDir)).toBe(true);
|
|
472
|
+
|
|
473
|
+
// Should have generated HTML
|
|
474
|
+
const indexHtml = path.join(siteDir, "index.html");
|
|
475
|
+
expect(fs.existsSync(indexHtml)).toBe(true);
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Feature: GitHub OAuth Device Flow Authentication
|
|
2
|
+
As a user deploying to GitHub Pages
|
|
3
|
+
I want to authenticate via browser
|
|
4
|
+
So I can push without configuring git credentials manually
|
|
5
|
+
|
|
6
|
+
Scenario: Request device code from GitHub
|
|
7
|
+
Given no existing GitHub credentials
|
|
8
|
+
When I initiate the device flow authentication
|
|
9
|
+
Then I should receive a device code response
|
|
10
|
+
And the response should include user_code, verification_uri, and interval
|
|
11
|
+
|
|
12
|
+
Scenario: Poll for access token after authorization
|
|
13
|
+
Given a valid device code
|
|
14
|
+
And the user has authorized the application
|
|
15
|
+
When I poll for the access token
|
|
16
|
+
Then I should receive an access token
|
|
17
|
+
And the token should have repo and workflow scopes
|
|
18
|
+
|
|
19
|
+
Scenario: Handle authorization pending state
|
|
20
|
+
Given a valid device code
|
|
21
|
+
And the user has not yet authorized
|
|
22
|
+
When I poll for the access token
|
|
23
|
+
Then I should receive authorization_pending error
|
|
24
|
+
And I should continue polling
|
|
25
|
+
|
|
26
|
+
Scenario: Store token in git credential helper
|
|
27
|
+
Given a valid access token
|
|
28
|
+
When I store the token
|
|
29
|
+
Then the token should be stored successfully
|
|
30
|
+
And I should be able to retrieve the token
|
|
31
|
+
|
|
32
|
+
Scenario: Handle expired device code
|
|
33
|
+
Given a device code that has expired
|
|
34
|
+
When I poll for the access token
|
|
35
|
+
Then I should receive an expired_token error
|
|
36
|
+
|
|
37
|
+
Scenario: Validate token with GitHub API
|
|
38
|
+
Given a valid access token
|
|
39
|
+
When I validate the token
|
|
40
|
+
Then I should receive user information
|
|
41
|
+
And the scopes should include repo and workflow
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Feature: GitHub Deployer Validation
|
|
2
|
+
As a user deploying to GitHub Pages
|
|
3
|
+
I want clear error messages when configuration is incorrect
|
|
4
|
+
So I can fix issues and deploy successfully
|
|
5
|
+
|
|
6
|
+
Scenario: Deploy from non-git directory
|
|
7
|
+
Given the directory is not a git repository
|
|
8
|
+
When I attempt to deploy
|
|
9
|
+
Then the deployment should fail
|
|
10
|
+
And the error should indicate setup was cancelled
|
|
11
|
+
|
|
12
|
+
Scenario: Deploy without any git remote
|
|
13
|
+
Given the directory is a git repository
|
|
14
|
+
And no git remote is configured
|
|
15
|
+
When I attempt to deploy
|
|
16
|
+
Then the deployment should fail
|
|
17
|
+
And the error should mention "No git remote configured"
|
|
18
|
+
And the error should include instructions to add a GitHub remote
|
|
19
|
+
|
|
20
|
+
Scenario: Deploy with non-GitHub remote triggers setup
|
|
21
|
+
Given the directory is a git repository
|
|
22
|
+
And the git remote is not a GitHub URL
|
|
23
|
+
When I attempt to deploy
|
|
24
|
+
Then the deployment should fail
|
|
25
|
+
And the error should indicate setup was cancelled
|
|
26
|
+
|
|
27
|
+
Scenario: Deploy with empty site directory
|
|
28
|
+
Given the directory is a git repository
|
|
29
|
+
And the site directory is empty
|
|
30
|
+
When I attempt to deploy
|
|
31
|
+
Then the deployment should fail
|
|
32
|
+
And the error should mention that the site needs to be built
|
|
33
|
+
|
|
34
|
+
Scenario: Successful deployment with SSH remote
|
|
35
|
+
Given the directory is a git repository
|
|
36
|
+
And the git remote is "git@github.com:testuser/testrepo.git"
|
|
37
|
+
And the site is built with files in ".moss/build/site/"
|
|
38
|
+
And the GitHub Actions workflow already exists
|
|
39
|
+
When I attempt to deploy
|
|
40
|
+
Then the deployment should succeed
|
|
41
|
+
And the deployment URL should be "https://testuser.github.io/testrepo"
|
|
42
|
+
|
|
43
|
+
Scenario: First-time deployment creates workflow
|
|
44
|
+
Given the directory is a git repository
|
|
45
|
+
And the git remote is "git@github.com:user/repo.git"
|
|
46
|
+
And the site is built with files in ".moss/build/site/"
|
|
47
|
+
And the GitHub Actions workflow does not exist
|
|
48
|
+
When I attempt to deploy
|
|
49
|
+
Then the deployment should succeed
|
|
50
|
+
And the result should indicate first-time setup
|