openspec-playwright 0.1.37 → 0.1.39
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/.claude/commands/opsx/e2e-body.md +39 -0
- package/dist/commands/editors.d.ts +35 -0
- package/dist/commands/editors.js +159 -0
- package/dist/commands/editors.js.map +1 -0
- package/dist/commands/init.js +35 -26
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.js +36 -33
- package/dist/commands/update.js.map +1 -1
- package/openspec-playwright-0.1.39.tgz +0 -0
- package/package.json +1 -1
- package/release-notes.md +2 -2
- package/src/commands/editors.ts +199 -0
- package/src/commands/init.ts +34 -29
- package/src/commands/update.ts +38 -43
- package/openspec-playwright-0.1.37.tgz +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Run Playwright E2E verification for an OpenSpec change.
|
|
2
|
+
|
|
3
|
+
## Workflow
|
|
4
|
+
|
|
5
|
+
1. **Validate environment**: Run the seed test to confirm your app is reachable.
|
|
6
|
+
```bash
|
|
7
|
+
npx playwright test tests/playwright/seed.spec.ts --project=chromium
|
|
8
|
+
```
|
|
9
|
+
If it fails, fix your BASE_URL or start the dev server first.
|
|
10
|
+
|
|
11
|
+
2. **Select the change**: If no change name is provided, run `openspec list --json` and pick one. Then announce: "Using change: `<name>`".
|
|
12
|
+
|
|
13
|
+
3. **Read specs**: Read all files from `openspec/changes/<name>/specs/*.md`.
|
|
14
|
+
|
|
15
|
+
4. **Detect auth**: Check if specs mention login, protected routes, or session handling. See `tests/playwright/auth.setup.ts` for auth setup.
|
|
16
|
+
|
|
17
|
+
5. **Generate test plan**: Create `openspec/changes/<name>/specs/playwright/test-plan.md` listing each test case with `@auth(required|none)` and `@role(...)` tags. Skip if already exists.
|
|
18
|
+
|
|
19
|
+
6. **Generate tests**: Write `tests/playwright/<name>.spec.ts` from the test plan. Follow the patterns in `seed.spec.ts`:
|
|
20
|
+
- Prefer `data-testid`, fallback to `getByRole`, `getByLabel`, `getByText`
|
|
21
|
+
- Include happy path AND error/edge cases
|
|
22
|
+
- Never use conditional `if (isVisible())` — always use `expect().toBeVisible()` (false-pass anti-pattern)
|
|
23
|
+
- Use `browser.newContext()` for auth guard tests (fresh session, no cookies)
|
|
24
|
+
|
|
25
|
+
7. **Run tests**:
|
|
26
|
+
```bash
|
|
27
|
+
openspec-pw run <change-name>
|
|
28
|
+
```
|
|
29
|
+
Or with role filtering:
|
|
30
|
+
```bash
|
|
31
|
+
npx playwright test tests/playwright/<name>.spec.ts --grep "@<role>"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
8. **Fix failures**: If tests fail, analyze the error:
|
|
35
|
+
- Network/backend error → `test.skip()` + report
|
|
36
|
+
- Selector changed → use Playwright MCP tools to find equivalent selectors, fix, re-run
|
|
37
|
+
- Auto-heal up to 3 attempts
|
|
38
|
+
|
|
39
|
+
9. **Report**: Results are saved to `openspec/reports/playwright-e2e-<name>-<timestamp>.md`. Present the summary table to the user.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Shared YAML escape — matches OpenSpec's escape logic */
|
|
2
|
+
export declare function escapeYamlValue(value: string): string;
|
|
3
|
+
/** Format tags as YAML inline array */
|
|
4
|
+
export declare function formatTagsArray(tags: string[]): string;
|
|
5
|
+
/** Command metadata shared across editors */
|
|
6
|
+
export interface CommandMeta {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
category: string;
|
|
11
|
+
tags: string[];
|
|
12
|
+
body: string;
|
|
13
|
+
}
|
|
14
|
+
/** Editor adapter — Strategy Pattern */
|
|
15
|
+
export interface EditorAdapter {
|
|
16
|
+
/** Tool identifier */
|
|
17
|
+
toolId: string;
|
|
18
|
+
/** Whether this editor supports SKILL.md */
|
|
19
|
+
hasSkill: boolean;
|
|
20
|
+
/** Get the command file path relative to project root */
|
|
21
|
+
getCommandPath(commandId: string): string;
|
|
22
|
+
/** Format the complete file content */
|
|
23
|
+
formatCommand(meta: CommandMeta): string;
|
|
24
|
+
}
|
|
25
|
+
/** Claude Code: .claude/commands/opsx/<id>.md + SKILL.md */
|
|
26
|
+
declare const claudeAdapter: EditorAdapter;
|
|
27
|
+
/** Detect which editors are installed by checking their config directories */
|
|
28
|
+
export declare function detectEditors(projectRoot: string): EditorAdapter[];
|
|
29
|
+
/** Build the shared command metadata */
|
|
30
|
+
export declare function buildCommandMeta(body: string): CommandMeta;
|
|
31
|
+
/** Install command files for all detected editors */
|
|
32
|
+
export declare function installForAllEditors(body: string, adapters: EditorAdapter[], projectRoot: string): void;
|
|
33
|
+
/** Install SKILL.md only for Claude Code */
|
|
34
|
+
export declare function installSkill(projectRoot: string, skillContent: string): void;
|
|
35
|
+
export { claudeAdapter };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
/** Shared YAML escape — matches OpenSpec's escape logic */
|
|
5
|
+
export function escapeYamlValue(value) {
|
|
6
|
+
const needsQuoting = /[:\n\r#{}[\],&*!|>'"%@`]|^\s|\s$/.test(value);
|
|
7
|
+
if (needsQuoting) {
|
|
8
|
+
const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
9
|
+
return `"${escaped}"`;
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
/** Format tags as YAML inline array */
|
|
14
|
+
export function formatTagsArray(tags) {
|
|
15
|
+
return `[${tags.map(t => escapeYamlValue(t)).join(', ')}]`;
|
|
16
|
+
}
|
|
17
|
+
/** Claude Code: .claude/commands/opsx/<id>.md + SKILL.md */
|
|
18
|
+
const claudeAdapter = {
|
|
19
|
+
toolId: 'claude',
|
|
20
|
+
hasSkill: true,
|
|
21
|
+
getCommandPath(id) {
|
|
22
|
+
return join('.claude', 'commands', 'opsx', `${id}.md`);
|
|
23
|
+
},
|
|
24
|
+
formatCommand(meta) {
|
|
25
|
+
return `---
|
|
26
|
+
name: ${escapeYamlValue(meta.name)}
|
|
27
|
+
description: ${escapeYamlValue(meta.description)}
|
|
28
|
+
category: ${escapeYamlValue(meta.category)}
|
|
29
|
+
tags: ${formatTagsArray(meta.tags)}
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
${meta.body}
|
|
33
|
+
`;
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
/** Cursor: .cursor/commands/opsx-<id>.md */
|
|
37
|
+
const cursorAdapter = {
|
|
38
|
+
toolId: 'cursor',
|
|
39
|
+
hasSkill: false,
|
|
40
|
+
getCommandPath(id) {
|
|
41
|
+
return join('.cursor', 'commands', `opsx-${id}.md`);
|
|
42
|
+
},
|
|
43
|
+
formatCommand(meta) {
|
|
44
|
+
return `---
|
|
45
|
+
name: /opsx-${meta.id}
|
|
46
|
+
id: opsx-${meta.id}
|
|
47
|
+
category: ${escapeYamlValue(meta.category)}
|
|
48
|
+
description: ${escapeYamlValue(meta.description)}
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
${meta.body}
|
|
52
|
+
`;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
/** Windsurf: .windsurf/workflows/opsx-<id>.md */
|
|
56
|
+
const windsurfAdapter = {
|
|
57
|
+
toolId: 'windsurf',
|
|
58
|
+
hasSkill: false,
|
|
59
|
+
getCommandPath(id) {
|
|
60
|
+
return join('.windsurf', 'workflows', `opsx-${id}.md`);
|
|
61
|
+
},
|
|
62
|
+
formatCommand(meta) {
|
|
63
|
+
return `---
|
|
64
|
+
name: ${escapeYamlValue(meta.name)}
|
|
65
|
+
description: ${escapeYamlValue(meta.description)}
|
|
66
|
+
category: ${escapeYamlValue(meta.category)}
|
|
67
|
+
tags: ${formatTagsArray(meta.tags)}
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
${meta.body}
|
|
71
|
+
`;
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
/** Cline: .clinerules/workflows/opsx-<id>.md — markdown header only */
|
|
75
|
+
const clineAdapter = {
|
|
76
|
+
toolId: 'cline',
|
|
77
|
+
hasSkill: false,
|
|
78
|
+
getCommandPath(id) {
|
|
79
|
+
return join('.clinerules', 'workflows', `opsx-${id}.md`);
|
|
80
|
+
},
|
|
81
|
+
formatCommand(meta) {
|
|
82
|
+
return `# ${meta.name}
|
|
83
|
+
|
|
84
|
+
${meta.description}
|
|
85
|
+
|
|
86
|
+
${meta.body}
|
|
87
|
+
`;
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
/** Continue: .continue/prompts/opsx-<id>.prompt */
|
|
91
|
+
const continueAdapter = {
|
|
92
|
+
toolId: 'continue',
|
|
93
|
+
hasSkill: false,
|
|
94
|
+
getCommandPath(id) {
|
|
95
|
+
return join('.continue', 'prompts', `opsx-${id}.prompt`);
|
|
96
|
+
},
|
|
97
|
+
formatCommand(meta) {
|
|
98
|
+
return `---
|
|
99
|
+
name: opsx-${meta.id}
|
|
100
|
+
description: ${meta.description}
|
|
101
|
+
invokable: true
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
${meta.body}
|
|
105
|
+
`;
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
/** All supported adapters */
|
|
109
|
+
const ALL_ADAPTERS = [
|
|
110
|
+
claudeAdapter,
|
|
111
|
+
cursorAdapter,
|
|
112
|
+
windsurfAdapter,
|
|
113
|
+
clineAdapter,
|
|
114
|
+
continueAdapter,
|
|
115
|
+
];
|
|
116
|
+
/** Detect which editors are installed by checking their config directories */
|
|
117
|
+
export function detectEditors(projectRoot) {
|
|
118
|
+
const checks = [
|
|
119
|
+
['.claude', claudeAdapter],
|
|
120
|
+
['.cursor', cursorAdapter],
|
|
121
|
+
['.windsurf', windsurfAdapter],
|
|
122
|
+
['.clinerules', clineAdapter],
|
|
123
|
+
['.continue', continueAdapter],
|
|
124
|
+
];
|
|
125
|
+
return checks
|
|
126
|
+
.filter(([dir]) => existsSync(join(projectRoot, dir)))
|
|
127
|
+
.map(([, adapter]) => adapter);
|
|
128
|
+
}
|
|
129
|
+
/** Build the shared command metadata */
|
|
130
|
+
export function buildCommandMeta(body) {
|
|
131
|
+
return {
|
|
132
|
+
id: 'e2e',
|
|
133
|
+
name: 'OPSX: E2E',
|
|
134
|
+
description: 'Run Playwright E2E verification for an OpenSpec change',
|
|
135
|
+
category: 'OpenSpec',
|
|
136
|
+
tags: ['openspec', 'playwright', 'e2e', 'testing'],
|
|
137
|
+
body,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/** Install command files for all detected editors */
|
|
141
|
+
export function installForAllEditors(body, adapters, projectRoot) {
|
|
142
|
+
const meta = buildCommandMeta(body);
|
|
143
|
+
for (const adapter of adapters) {
|
|
144
|
+
const relPath = adapter.getCommandPath(meta.id);
|
|
145
|
+
const absPath = join(projectRoot, relPath);
|
|
146
|
+
mkdirSync(dirname(absPath), { recursive: true });
|
|
147
|
+
writeFileSync(absPath, adapter.formatCommand(meta));
|
|
148
|
+
console.log(chalk.green(` ✓ ${adapter.toolId}: ${relPath}`));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** Install SKILL.md only for Claude Code */
|
|
152
|
+
export function installSkill(projectRoot, skillContent) {
|
|
153
|
+
const skillDir = join(projectRoot, '.claude', 'skills', 'openspec-e2e');
|
|
154
|
+
mkdirSync(skillDir, { recursive: true });
|
|
155
|
+
writeFileSync(join(skillDir, 'SKILL.md'), skillContent);
|
|
156
|
+
console.log(chalk.green(` ✓ claude: .claude/skills/openspec-e2e/SKILL.md`));
|
|
157
|
+
}
|
|
158
|
+
export { claudeAdapter };
|
|
159
|
+
//# sourceMappingURL=editors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editors.js","sourceRoot":"","sources":["../../src/commands/editors.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/init.js
CHANGED
|
@@ -6,10 +6,11 @@ import { fileURLToPath } from 'url';
|
|
|
6
6
|
import chalk from 'chalk';
|
|
7
7
|
import { readFile } from 'fs/promises';
|
|
8
8
|
import { syncMcpTools } from './mcpSync.js';
|
|
9
|
+
import { detectEditors, installForAllEditors, installSkill, claudeAdapter } from './editors.js';
|
|
9
10
|
const TEMPLATE_DIR = fileURLToPath(new URL('../../templates', import.meta.url));
|
|
10
11
|
const SCHEMA_DIR = fileURLToPath(new URL('../../schemas', import.meta.url));
|
|
11
|
-
const SKILL_SRC = fileURLToPath(new URL('../../.claude/skills/openspec-e2e', import.meta.url));
|
|
12
|
-
const
|
|
12
|
+
const SKILL_SRC = fileURLToPath(new URL('../../.claude/skills/openspec-e2e/SKILL.md', import.meta.url));
|
|
13
|
+
const CMD_BODY_SRC = fileURLToPath(new URL('../../.claude/commands/opsx/e2e-body.md', import.meta.url));
|
|
13
14
|
export async function init(options) {
|
|
14
15
|
console.log(chalk.blue('\n🔧 OpenSpec + Playwright E2E Setup\n'));
|
|
15
16
|
const projectRoot = process.cwd();
|
|
@@ -61,13 +62,32 @@ export async function init(options) {
|
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
|
-
// 4.
|
|
65
|
-
console.log(chalk.blue('\n─── Installing
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
// 4. Install E2E commands for detected editors
|
|
66
|
+
console.log(chalk.blue('\n─── Installing E2E Commands ───'));
|
|
67
|
+
const detected = detectEditors(projectRoot);
|
|
68
|
+
if (detected.length > 0) {
|
|
69
|
+
const body = await readFile(CMD_BODY_SRC, 'utf-8');
|
|
70
|
+
installForAllEditors(body, detected, projectRoot);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
const body = await readFile(CMD_BODY_SRC, 'utf-8');
|
|
74
|
+
installForAllEditors(body, [claudeAdapter], projectRoot);
|
|
75
|
+
}
|
|
76
|
+
// Claude Code also gets the SKILL.md
|
|
77
|
+
if (existsSync(join(projectRoot, '.claude'))) {
|
|
78
|
+
const skillContent = await readFile(SKILL_SRC, 'utf-8');
|
|
79
|
+
installSkill(projectRoot, skillContent);
|
|
80
|
+
}
|
|
81
|
+
// 5. Sync Healer tools with latest @playwright/mcp (Claude Code only)
|
|
82
|
+
if (existsSync(join(projectRoot, '.claude'))) {
|
|
83
|
+
console.log(chalk.blue('\n─── Syncing Healer Tools ───'));
|
|
84
|
+
const skillDest = join(projectRoot, '.claude', 'skills', 'openspec-e2e', 'SKILL.md');
|
|
85
|
+
await syncMcpTools(skillDest, true);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
console.log(chalk.blue('\n─── Syncing Healer Tools ───'));
|
|
89
|
+
console.log(chalk.gray(' - Claude Code not detected, skipping MCP sync'));
|
|
90
|
+
}
|
|
71
91
|
// 6. Install OpenSpec schema
|
|
72
92
|
console.log(chalk.blue('\n─── Installing OpenSpec Schema ───'));
|
|
73
93
|
await installSchema(projectRoot);
|
|
@@ -84,28 +104,17 @@ export async function init(options) {
|
|
|
84
104
|
console.log(chalk.gray(' 2. Customize tests/playwright/credentials.yaml with your test user'));
|
|
85
105
|
console.log(chalk.gray(' 3. Set credentials: export E2E_USERNAME=xxx E2E_PASSWORD=yyy'));
|
|
86
106
|
console.log(chalk.gray(' 4. Run auth setup: npx playwright test --project=setup'));
|
|
87
|
-
|
|
88
|
-
|
|
107
|
+
const hasClaude = existsSync(join(projectRoot, '.claude'));
|
|
108
|
+
if (hasClaude) {
|
|
109
|
+
console.log(chalk.gray(' 5. In Claude Code, run: /opsx:e2e <change-name>'));
|
|
110
|
+
}
|
|
111
|
+
console.log(chalk.gray(` ${hasClaude ? '6.' : '5.'} Or: openspec-pw run <change-name>`));
|
|
112
|
+
console.log(chalk.gray(` ${hasClaude ? '7.' : '6.'} Or: openspec-pw doctor to verify setup\n`));
|
|
89
113
|
console.log(chalk.bold('How it works:'));
|
|
90
114
|
console.log(chalk.gray(' /opsx:e2e reads your OpenSpec specs and runs Playwright'));
|
|
91
115
|
console.log(chalk.gray(' E2E tests through a three-agent pipeline:'));
|
|
92
116
|
console.log(chalk.gray(' Planner → Generator → Healer\n'));
|
|
93
117
|
}
|
|
94
|
-
async function installSkill(projectRoot) {
|
|
95
|
-
const skillsDir = join(projectRoot, '.claude', 'skills');
|
|
96
|
-
const skillDir = join(skillsDir, 'openspec-e2e');
|
|
97
|
-
const cmdDir = join(projectRoot, '.claude', 'commands');
|
|
98
|
-
// Copy skill
|
|
99
|
-
mkdirSync(skillDir, { recursive: true });
|
|
100
|
-
const skillContent = await readFile(SKILL_SRC + '/SKILL.md', 'utf-8');
|
|
101
|
-
writeFileSync(join(skillDir, 'SKILL.md'), skillContent);
|
|
102
|
-
console.log(chalk.green(` ✓ Skill installed: /openspec-e2e`));
|
|
103
|
-
// Copy command
|
|
104
|
-
mkdirSync(join(cmdDir, 'opsx'), { recursive: true });
|
|
105
|
-
const cmdContent = await readFile(CMD_SRC, 'utf-8');
|
|
106
|
-
writeFileSync(join(cmdDir, 'opsx', 'e2e.md'), cmdContent);
|
|
107
|
-
console.log(chalk.green(` ✓ Command installed: /opsx:e2e`));
|
|
108
|
-
}
|
|
109
118
|
async function generateSeedTest(projectRoot) {
|
|
110
119
|
const testsDir = join(projectRoot, 'tests', 'playwright');
|
|
111
120
|
mkdirSync(testsDir, { recursive: true });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EACL,UAAU,EACV,YAAY,EACZ,aAAa,EACb,SAAS,GACV,MAAM,IAAI,CAAC;AACZ,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/update.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { execSync } from 'child_process';
|
|
1
|
+
import { execSync, exec } from 'child_process';
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync, readdirSync, statSync, } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { tmpdir } from 'os';
|
|
5
5
|
import { promisify } from 'util';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
|
-
import { exec } from 'child_process';
|
|
8
7
|
import chalk from 'chalk';
|
|
9
8
|
import * as tar from 'tar';
|
|
10
9
|
import { syncMcpTools } from './mcpSync.js';
|
|
11
|
-
|
|
12
|
-
const
|
|
10
|
+
import { detectEditors, installForAllEditors, installSkill } from './editors.js';
|
|
11
|
+
const CMD_BODY_SRC = fileURLToPath(new URL('../../.claude/commands/opsx/e2e-body.md', import.meta.url));
|
|
13
12
|
const SCHEMA_DIR = fileURLToPath(new URL('../../schemas', import.meta.url));
|
|
14
13
|
export async function update(options) {
|
|
15
14
|
console.log(chalk.blue('\n🔄 Updating OpenSpec + Playwright E2E\n'));
|
|
@@ -34,9 +33,9 @@ export async function update(options) {
|
|
|
34
33
|
console.log(chalk.gray(' Run manually: npm install -g openspec-playwright'));
|
|
35
34
|
}
|
|
36
35
|
}
|
|
37
|
-
// 2. Update
|
|
36
|
+
// 2. Update commands for all detected editors + schema
|
|
38
37
|
if (options.skill !== false) {
|
|
39
|
-
console.log(chalk.blue('\n─── Updating
|
|
38
|
+
console.log(chalk.blue('\n─── Updating Commands & Schema ───'));
|
|
40
39
|
try {
|
|
41
40
|
const tmpDir = join(tmpdir(), 'openspec-e2e-update');
|
|
42
41
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
@@ -53,15 +52,27 @@ export async function update(options) {
|
|
|
53
52
|
const tarballPath = join(tmpDir, tgzFiles[0].name);
|
|
54
53
|
// Extract tarball
|
|
55
54
|
await tar.extract({ file: tarballPath, cwd: tmpDir, strip: 1 });
|
|
56
|
-
const
|
|
57
|
-
const cmdSrc = join(tmpDir, '.claude', 'commands', 'opsx', 'e2e.md');
|
|
55
|
+
const bodySrc = join(tmpDir, '.claude', 'commands', 'opsx', 'e2e-body.md');
|
|
58
56
|
const schemaSrc = join(tmpDir, 'schemas', 'playwright-e2e');
|
|
59
|
-
|
|
57
|
+
// Install commands for all detected editors
|
|
58
|
+
const adapters = detectEditors(projectRoot);
|
|
59
|
+
if (adapters.length > 0 && existsSync(bodySrc)) {
|
|
60
|
+
const body = readFileSync(bodySrc, 'utf-8');
|
|
61
|
+
installForAllEditors(body, adapters, projectRoot);
|
|
62
|
+
}
|
|
63
|
+
// Install SKILL.md for Claude Code
|
|
64
|
+
const skillSrc = join(tmpDir, '.claude', 'skills', 'openspec-e2e', 'SKILL.md');
|
|
65
|
+
if (existsSync(join(projectRoot, '.claude')) && existsSync(skillSrc)) {
|
|
66
|
+
const skillContent = readFileSync(skillSrc, 'utf-8');
|
|
67
|
+
installSkill(projectRoot, skillContent);
|
|
68
|
+
}
|
|
69
|
+
// Install schema
|
|
70
|
+
installSchemaFrom(schemaSrc, projectRoot);
|
|
60
71
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
61
|
-
console.log(chalk.green(' ✓
|
|
72
|
+
console.log(chalk.green(' ✓ Commands & schema updated to latest'));
|
|
62
73
|
}
|
|
63
74
|
catch {
|
|
64
|
-
console.log(chalk.yellow(' ⚠ Failed to update
|
|
75
|
+
console.log(chalk.yellow(' ⚠ Failed to update from npm'));
|
|
65
76
|
console.log(chalk.gray(' Trying npm install to pull latest version...'));
|
|
66
77
|
try {
|
|
67
78
|
execSync('npm install -g openspec-playwright', { stdio: 'inherit', cwd: projectRoot });
|
|
@@ -73,31 +84,23 @@ export async function update(options) {
|
|
|
73
84
|
}
|
|
74
85
|
}
|
|
75
86
|
}
|
|
76
|
-
// 3. Sync Healer tools
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
87
|
+
// 3. Sync Healer tools (Claude Code only)
|
|
88
|
+
if (existsSync(join(projectRoot, '.claude', 'skills', 'openspec-e2e', 'SKILL.md'))) {
|
|
89
|
+
console.log(chalk.blue('\n─── Syncing Healer Tools ───'));
|
|
90
|
+
const skillDest = join(projectRoot, '.claude', 'skills', 'openspec-e2e', 'SKILL.md');
|
|
91
|
+
await syncMcpTools(skillDest, true);
|
|
92
|
+
}
|
|
80
93
|
// Summary
|
|
81
94
|
console.log(chalk.blue('\n─── Summary ───'));
|
|
82
95
|
console.log(chalk.green(' ✓ Update complete!\n'));
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const cmdDir = join(projectRoot, '.claude', 'commands');
|
|
92
|
-
mkdirSync(skillDir, { recursive: true });
|
|
93
|
-
const skillContent = readFileSync(skillSrc, 'utf-8');
|
|
94
|
-
writeFileSync(join(skillDir, 'SKILL.md'), skillContent);
|
|
95
|
-
console.log(chalk.green(` ✓ Skill updated: /openspec-e2e`));
|
|
96
|
-
mkdirSync(join(cmdDir, 'opsx'), { recursive: true });
|
|
97
|
-
const cmdContent = readFileSync(cmdSrc, 'utf-8');
|
|
98
|
-
writeFileSync(join(cmdDir, 'opsx', 'e2e.md'), cmdContent);
|
|
99
|
-
console.log(chalk.green(` ✓ Command updated: /opsx:e2e`));
|
|
100
|
-
installSchemaFrom(schemaSrc, projectRoot);
|
|
96
|
+
if (existsSync(join(projectRoot, '.claude'))) {
|
|
97
|
+
console.log(chalk.bold('Restart Claude Code to use the updated skill.'));
|
|
98
|
+
console.log(chalk.gray(' Then run /opsx:e2e <change-name> to verify.\n'));
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
console.log(chalk.bold('Restart your AI coding assistant to use the updated commands.'));
|
|
102
|
+
console.log(chalk.gray(' Then run openspec-pw run <change-name> to verify.\n'));
|
|
103
|
+
}
|
|
101
104
|
}
|
|
102
105
|
function installSchemaFrom(schemaSrc, projectRoot) {
|
|
103
106
|
const schemaDest = join(projectRoot, 'openspec', 'schemas', 'playwright-e2e');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update.js","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"update.js","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"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"}
|
|
Binary file
|
package/package.json
CHANGED
package/release-notes.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
## What's Changed
|
|
2
2
|
|
|
3
|
-
-
|
|
3
|
+
- feat: support Top 5 editors via adapter pattern
|
|
4
4
|
|
|
5
|
-
**Full Changelog**: https://github.com/wxhou/openspec-playwright/releases/tag/v0.1.
|
|
5
|
+
**Full Changelog**: https://github.com/wxhou/openspec-playwright/releases/tag/v0.1.39
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
|
|
5
|
+
/** Shared YAML escape — matches OpenSpec's escape logic */
|
|
6
|
+
export function escapeYamlValue(value: string): string {
|
|
7
|
+
const needsQuoting = /[:\n\r#{}[\],&*!|>'"%@`]|^\s|\s$/.test(value);
|
|
8
|
+
if (needsQuoting) {
|
|
9
|
+
const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
10
|
+
return `"${escaped}"`;
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Format tags as YAML inline array */
|
|
16
|
+
export function formatTagsArray(tags: string[]): string {
|
|
17
|
+
return `[${tags.map(t => escapeYamlValue(t)).join(', ')}]`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Command metadata shared across editors */
|
|
21
|
+
export interface CommandMeta {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
description: string;
|
|
25
|
+
category: string;
|
|
26
|
+
tags: string[];
|
|
27
|
+
body: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Editor adapter — Strategy Pattern */
|
|
31
|
+
export interface EditorAdapter {
|
|
32
|
+
/** Tool identifier */
|
|
33
|
+
toolId: string;
|
|
34
|
+
/** Whether this editor supports SKILL.md */
|
|
35
|
+
hasSkill: boolean;
|
|
36
|
+
/** Get the command file path relative to project root */
|
|
37
|
+
getCommandPath(commandId: string): string;
|
|
38
|
+
/** Format the complete file content */
|
|
39
|
+
formatCommand(meta: CommandMeta): string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Claude Code: .claude/commands/opsx/<id>.md + SKILL.md */
|
|
43
|
+
const claudeAdapter: EditorAdapter = {
|
|
44
|
+
toolId: 'claude',
|
|
45
|
+
hasSkill: true,
|
|
46
|
+
getCommandPath(id: string) {
|
|
47
|
+
return join('.claude', 'commands', 'opsx', `${id}.md`);
|
|
48
|
+
},
|
|
49
|
+
formatCommand(meta) {
|
|
50
|
+
return `---
|
|
51
|
+
name: ${escapeYamlValue(meta.name)}
|
|
52
|
+
description: ${escapeYamlValue(meta.description)}
|
|
53
|
+
category: ${escapeYamlValue(meta.category)}
|
|
54
|
+
tags: ${formatTagsArray(meta.tags)}
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
${meta.body}
|
|
58
|
+
`;
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** Cursor: .cursor/commands/opsx-<id>.md */
|
|
63
|
+
const cursorAdapter: EditorAdapter = {
|
|
64
|
+
toolId: 'cursor',
|
|
65
|
+
hasSkill: false,
|
|
66
|
+
getCommandPath(id: string) {
|
|
67
|
+
return join('.cursor', 'commands', `opsx-${id}.md`);
|
|
68
|
+
},
|
|
69
|
+
formatCommand(meta) {
|
|
70
|
+
return `---
|
|
71
|
+
name: /opsx-${meta.id}
|
|
72
|
+
id: opsx-${meta.id}
|
|
73
|
+
category: ${escapeYamlValue(meta.category)}
|
|
74
|
+
description: ${escapeYamlValue(meta.description)}
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
${meta.body}
|
|
78
|
+
`;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/** Windsurf: .windsurf/workflows/opsx-<id>.md */
|
|
83
|
+
const windsurfAdapter: EditorAdapter = {
|
|
84
|
+
toolId: 'windsurf',
|
|
85
|
+
hasSkill: false,
|
|
86
|
+
getCommandPath(id: string) {
|
|
87
|
+
return join('.windsurf', 'workflows', `opsx-${id}.md`);
|
|
88
|
+
},
|
|
89
|
+
formatCommand(meta) {
|
|
90
|
+
return `---
|
|
91
|
+
name: ${escapeYamlValue(meta.name)}
|
|
92
|
+
description: ${escapeYamlValue(meta.description)}
|
|
93
|
+
category: ${escapeYamlValue(meta.category)}
|
|
94
|
+
tags: ${formatTagsArray(meta.tags)}
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
${meta.body}
|
|
98
|
+
`;
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** Cline: .clinerules/workflows/opsx-<id>.md — markdown header only */
|
|
103
|
+
const clineAdapter: EditorAdapter = {
|
|
104
|
+
toolId: 'cline',
|
|
105
|
+
hasSkill: false,
|
|
106
|
+
getCommandPath(id: string) {
|
|
107
|
+
return join('.clinerules', 'workflows', `opsx-${id}.md`);
|
|
108
|
+
},
|
|
109
|
+
formatCommand(meta) {
|
|
110
|
+
return `# ${meta.name}
|
|
111
|
+
|
|
112
|
+
${meta.description}
|
|
113
|
+
|
|
114
|
+
${meta.body}
|
|
115
|
+
`;
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/** Continue: .continue/prompts/opsx-<id>.prompt */
|
|
120
|
+
const continueAdapter: EditorAdapter = {
|
|
121
|
+
toolId: 'continue',
|
|
122
|
+
hasSkill: false,
|
|
123
|
+
getCommandPath(id: string) {
|
|
124
|
+
return join('.continue', 'prompts', `opsx-${id}.prompt`);
|
|
125
|
+
},
|
|
126
|
+
formatCommand(meta) {
|
|
127
|
+
return `---
|
|
128
|
+
name: opsx-${meta.id}
|
|
129
|
+
description: ${meta.description}
|
|
130
|
+
invokable: true
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
${meta.body}
|
|
134
|
+
`;
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/** All supported adapters */
|
|
139
|
+
const ALL_ADAPTERS: EditorAdapter[] = [
|
|
140
|
+
claudeAdapter,
|
|
141
|
+
cursorAdapter,
|
|
142
|
+
windsurfAdapter,
|
|
143
|
+
clineAdapter,
|
|
144
|
+
continueAdapter,
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
/** Detect which editors are installed by checking their config directories */
|
|
148
|
+
export function detectEditors(projectRoot: string): EditorAdapter[] {
|
|
149
|
+
const checks: Array<[string, EditorAdapter]> = [
|
|
150
|
+
['.claude', claudeAdapter],
|
|
151
|
+
['.cursor', cursorAdapter],
|
|
152
|
+
['.windsurf', windsurfAdapter],
|
|
153
|
+
['.clinerules', clineAdapter],
|
|
154
|
+
['.continue', continueAdapter],
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
return checks
|
|
158
|
+
.filter(([dir]) => existsSync(join(projectRoot, dir)))
|
|
159
|
+
.map(([, adapter]) => adapter);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Build the shared command metadata */
|
|
163
|
+
export function buildCommandMeta(body: string): CommandMeta {
|
|
164
|
+
return {
|
|
165
|
+
id: 'e2e',
|
|
166
|
+
name: 'OPSX: E2E',
|
|
167
|
+
description: 'Run Playwright E2E verification for an OpenSpec change',
|
|
168
|
+
category: 'OpenSpec',
|
|
169
|
+
tags: ['openspec', 'playwright', 'e2e', 'testing'],
|
|
170
|
+
body,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Install command files for all detected editors */
|
|
175
|
+
export function installForAllEditors(
|
|
176
|
+
body: string,
|
|
177
|
+
adapters: EditorAdapter[],
|
|
178
|
+
projectRoot: string
|
|
179
|
+
): void {
|
|
180
|
+
const meta = buildCommandMeta(body);
|
|
181
|
+
|
|
182
|
+
for (const adapter of adapters) {
|
|
183
|
+
const relPath = adapter.getCommandPath(meta.id);
|
|
184
|
+
const absPath = join(projectRoot, relPath);
|
|
185
|
+
mkdirSync(dirname(absPath), { recursive: true });
|
|
186
|
+
writeFileSync(absPath, adapter.formatCommand(meta));
|
|
187
|
+
console.log(chalk.green(` ✓ ${adapter.toolId}: ${relPath}`));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Install SKILL.md only for Claude Code */
|
|
192
|
+
export function installSkill(projectRoot: string, skillContent: string): void {
|
|
193
|
+
const skillDir = join(projectRoot, '.claude', 'skills', 'openspec-e2e');
|
|
194
|
+
mkdirSync(skillDir, { recursive: true });
|
|
195
|
+
writeFileSync(join(skillDir, 'SKILL.md'), skillContent);
|
|
196
|
+
console.log(chalk.green(` ✓ claude: .claude/skills/openspec-e2e/SKILL.md`));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export { claudeAdapter };
|
package/src/commands/init.ts
CHANGED
|
@@ -11,11 +11,12 @@ import { fileURLToPath } from 'url';
|
|
|
11
11
|
import chalk from 'chalk';
|
|
12
12
|
import { readFile } from 'fs/promises';
|
|
13
13
|
import { syncMcpTools } from './mcpSync.js';
|
|
14
|
+
import { detectEditors, installForAllEditors, installSkill, claudeAdapter } from './editors.js';
|
|
14
15
|
|
|
15
16
|
const TEMPLATE_DIR = fileURLToPath(new URL('../../templates', import.meta.url));
|
|
16
17
|
const SCHEMA_DIR = fileURLToPath(new URL('../../schemas', import.meta.url));
|
|
17
|
-
const SKILL_SRC = fileURLToPath(new URL('../../.claude/skills/openspec-e2e', import.meta.url));
|
|
18
|
-
const
|
|
18
|
+
const SKILL_SRC = fileURLToPath(new URL('../../.claude/skills/openspec-e2e/SKILL.md', import.meta.url));
|
|
19
|
+
const CMD_BODY_SRC = fileURLToPath(new URL('../../.claude/commands/opsx/e2e-body.md', import.meta.url));
|
|
19
20
|
|
|
20
21
|
export interface InitOptions {
|
|
21
22
|
change?: string;
|
|
@@ -81,14 +82,32 @@ export async function init(options: InitOptions) {
|
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
// 4.
|
|
85
|
-
console.log(chalk.blue('\n─── Installing
|
|
86
|
-
|
|
85
|
+
// 4. Install E2E commands for detected editors
|
|
86
|
+
console.log(chalk.blue('\n─── Installing E2E Commands ───'));
|
|
87
|
+
const detected = detectEditors(projectRoot);
|
|
88
|
+
if (detected.length > 0) {
|
|
89
|
+
const body = await readFile(CMD_BODY_SRC, 'utf-8');
|
|
90
|
+
installForAllEditors(body, detected, projectRoot);
|
|
91
|
+
} else {
|
|
92
|
+
const body = await readFile(CMD_BODY_SRC, 'utf-8');
|
|
93
|
+
installForAllEditors(body, [claudeAdapter], projectRoot);
|
|
94
|
+
}
|
|
87
95
|
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
// Claude Code also gets the SKILL.md
|
|
97
|
+
if (existsSync(join(projectRoot, '.claude'))) {
|
|
98
|
+
const skillContent = await readFile(SKILL_SRC, 'utf-8');
|
|
99
|
+
installSkill(projectRoot, skillContent);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 5. Sync Healer tools with latest @playwright/mcp (Claude Code only)
|
|
103
|
+
if (existsSync(join(projectRoot, '.claude'))) {
|
|
104
|
+
console.log(chalk.blue('\n─── Syncing Healer Tools ───'));
|
|
105
|
+
const skillDest = join(projectRoot, '.claude', 'skills', 'openspec-e2e', 'SKILL.md');
|
|
106
|
+
await syncMcpTools(skillDest, true);
|
|
107
|
+
} else {
|
|
108
|
+
console.log(chalk.blue('\n─── Syncing Healer Tools ───'));
|
|
109
|
+
console.log(chalk.gray(' - Claude Code not detected, skipping MCP sync'));
|
|
110
|
+
}
|
|
92
111
|
|
|
93
112
|
// 6. Install OpenSpec schema
|
|
94
113
|
console.log(chalk.blue('\n─── Installing OpenSpec Schema ───'));
|
|
@@ -109,8 +128,12 @@ export async function init(options: InitOptions) {
|
|
|
109
128
|
console.log(chalk.gray(' 2. Customize tests/playwright/credentials.yaml with your test user'));
|
|
110
129
|
console.log(chalk.gray(' 3. Set credentials: export E2E_USERNAME=xxx E2E_PASSWORD=yyy'));
|
|
111
130
|
console.log(chalk.gray(' 4. Run auth setup: npx playwright test --project=setup'));
|
|
112
|
-
|
|
113
|
-
|
|
131
|
+
const hasClaude = existsSync(join(projectRoot, '.claude'));
|
|
132
|
+
if (hasClaude) {
|
|
133
|
+
console.log(chalk.gray(' 5. In Claude Code, run: /opsx:e2e <change-name>'));
|
|
134
|
+
}
|
|
135
|
+
console.log(chalk.gray(` ${hasClaude ? '6.' : '5.'} Or: openspec-pw run <change-name>`));
|
|
136
|
+
console.log(chalk.gray(` ${hasClaude ? '7.' : '6.'} Or: openspec-pw doctor to verify setup\n`));
|
|
114
137
|
|
|
115
138
|
console.log(chalk.bold('How it works:'));
|
|
116
139
|
console.log(chalk.gray(' /opsx:e2e reads your OpenSpec specs and runs Playwright'));
|
|
@@ -118,24 +141,6 @@ export async function init(options: InitOptions) {
|
|
|
118
141
|
console.log(chalk.gray(' Planner → Generator → Healer\n'));
|
|
119
142
|
}
|
|
120
143
|
|
|
121
|
-
async function installSkill(projectRoot: string) {
|
|
122
|
-
const skillsDir = join(projectRoot, '.claude', 'skills');
|
|
123
|
-
const skillDir = join(skillsDir, 'openspec-e2e');
|
|
124
|
-
const cmdDir = join(projectRoot, '.claude', 'commands');
|
|
125
|
-
|
|
126
|
-
// Copy skill
|
|
127
|
-
mkdirSync(skillDir, { recursive: true });
|
|
128
|
-
const skillContent = await readFile(SKILL_SRC + '/SKILL.md', 'utf-8');
|
|
129
|
-
writeFileSync(join(skillDir, 'SKILL.md'), skillContent);
|
|
130
|
-
console.log(chalk.green(` ✓ Skill installed: /openspec-e2e`));
|
|
131
|
-
|
|
132
|
-
// Copy command
|
|
133
|
-
mkdirSync(join(cmdDir, 'opsx'), { recursive: true });
|
|
134
|
-
const cmdContent = await readFile(CMD_SRC, 'utf-8');
|
|
135
|
-
writeFileSync(join(cmdDir, 'opsx', 'e2e.md'), cmdContent);
|
|
136
|
-
console.log(chalk.green(` ✓ Command installed: /opsx:e2e`));
|
|
137
|
-
}
|
|
138
|
-
|
|
139
144
|
async function generateSeedTest(projectRoot: string) {
|
|
140
145
|
const testsDir = join(projectRoot, 'tests', 'playwright');
|
|
141
146
|
mkdirSync(testsDir, { recursive: true });
|
package/src/commands/update.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execSync } from 'child_process';
|
|
1
|
+
import { execSync, exec } from 'child_process';
|
|
2
2
|
import { existsSync,
|
|
3
3
|
readFileSync,
|
|
4
4
|
writeFileSync,
|
|
@@ -11,13 +11,12 @@ import { join } from 'path';
|
|
|
11
11
|
import { tmpdir } from 'os';
|
|
12
12
|
import { promisify } from 'util';
|
|
13
13
|
import { fileURLToPath } from 'url';
|
|
14
|
-
import { exec } from 'child_process';
|
|
15
14
|
import chalk from 'chalk';
|
|
16
15
|
import * as tar from 'tar';
|
|
17
16
|
import { syncMcpTools } from './mcpSync.js';
|
|
17
|
+
import { detectEditors, installForAllEditors, installSkill } from './editors.js';
|
|
18
18
|
|
|
19
|
-
const
|
|
20
|
-
const CMD_SRC = fileURLToPath(new URL('../../.claude/commands/opsx', import.meta.url));
|
|
19
|
+
const CMD_BODY_SRC = fileURLToPath(new URL('../../.claude/commands/opsx/e2e-body.md', import.meta.url));
|
|
21
20
|
const SCHEMA_DIR = fileURLToPath(new URL('../../schemas', import.meta.url));
|
|
22
21
|
|
|
23
22
|
export interface UpdateOptions {
|
|
@@ -56,9 +55,9 @@ export async function update(options: UpdateOptions) {
|
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
// 2. Update
|
|
58
|
+
// 2. Update commands for all detected editors + schema
|
|
60
59
|
if (options.skill !== false) {
|
|
61
|
-
console.log(chalk.blue('\n─── Updating
|
|
60
|
+
console.log(chalk.blue('\n─── Updating Commands & Schema ───'));
|
|
62
61
|
try {
|
|
63
62
|
const tmpDir = join(tmpdir(), 'openspec-e2e-update');
|
|
64
63
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
@@ -81,15 +80,30 @@ export async function update(options: UpdateOptions) {
|
|
|
81
80
|
// Extract tarball
|
|
82
81
|
await tar.extract({ file: tarballPath, cwd: tmpDir, strip: 1 });
|
|
83
82
|
|
|
84
|
-
const
|
|
85
|
-
const cmdSrc = join(tmpDir, '.claude', 'commands', 'opsx', 'e2e.md');
|
|
83
|
+
const bodySrc = join(tmpDir, '.claude', 'commands', 'opsx', 'e2e-body.md');
|
|
86
84
|
const schemaSrc = join(tmpDir, 'schemas', 'playwright-e2e');
|
|
87
85
|
|
|
88
|
-
|
|
86
|
+
// Install commands for all detected editors
|
|
87
|
+
const adapters = detectEditors(projectRoot);
|
|
88
|
+
if (adapters.length > 0 && existsSync(bodySrc)) {
|
|
89
|
+
const body = readFileSync(bodySrc, 'utf-8');
|
|
90
|
+
installForAllEditors(body, adapters, projectRoot);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Install SKILL.md for Claude Code
|
|
94
|
+
const skillSrc = join(tmpDir, '.claude', 'skills', 'openspec-e2e', 'SKILL.md');
|
|
95
|
+
if (existsSync(join(projectRoot, '.claude')) && existsSync(skillSrc)) {
|
|
96
|
+
const skillContent = readFileSync(skillSrc, 'utf-8');
|
|
97
|
+
installSkill(projectRoot, skillContent);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Install schema
|
|
101
|
+
installSchemaFrom(schemaSrc, projectRoot);
|
|
102
|
+
|
|
89
103
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
90
|
-
console.log(chalk.green(' ✓
|
|
104
|
+
console.log(chalk.green(' ✓ Commands & schema updated to latest'));
|
|
91
105
|
} catch {
|
|
92
|
-
console.log(chalk.yellow(' ⚠ Failed to update
|
|
106
|
+
console.log(chalk.yellow(' ⚠ Failed to update from npm'));
|
|
93
107
|
console.log(chalk.gray(' Trying npm install to pull latest version...'));
|
|
94
108
|
try {
|
|
95
109
|
execSync('npm install -g openspec-playwright', { stdio: 'inherit', cwd: projectRoot });
|
|
@@ -101,43 +115,24 @@ export async function update(options: UpdateOptions) {
|
|
|
101
115
|
}
|
|
102
116
|
}
|
|
103
117
|
|
|
104
|
-
// 3. Sync Healer tools
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
118
|
+
// 3. Sync Healer tools (Claude Code only)
|
|
119
|
+
if (existsSync(join(projectRoot, '.claude', 'skills', 'openspec-e2e', 'SKILL.md'))) {
|
|
120
|
+
console.log(chalk.blue('\n─── Syncing Healer Tools ───'));
|
|
121
|
+
const skillDest = join(projectRoot, '.claude', 'skills', 'openspec-e2e', 'SKILL.md');
|
|
122
|
+
await syncMcpTools(skillDest, true);
|
|
123
|
+
}
|
|
108
124
|
|
|
109
125
|
// Summary
|
|
110
126
|
console.log(chalk.blue('\n─── Summary ───'));
|
|
111
127
|
console.log(chalk.green(' ✓ Update complete!\n'));
|
|
112
128
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
join(CMD_SRC, 'e2e.md'),
|
|
121
|
-
join(SCHEMA_DIR, 'playwright-e2e'),
|
|
122
|
-
projectRoot
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function installSkillFrom(skillSrc: string, cmdSrc: string, schemaSrc: string, projectRoot: string) {
|
|
127
|
-
const skillDir = join(projectRoot, '.claude', 'skills', 'openspec-e2e');
|
|
128
|
-
const cmdDir = join(projectRoot, '.claude', 'commands');
|
|
129
|
-
|
|
130
|
-
mkdirSync(skillDir, { recursive: true });
|
|
131
|
-
const skillContent = readFileSync(skillSrc, 'utf-8');
|
|
132
|
-
writeFileSync(join(skillDir, 'SKILL.md'), skillContent);
|
|
133
|
-
console.log(chalk.green(` ✓ Skill updated: /openspec-e2e`));
|
|
134
|
-
|
|
135
|
-
mkdirSync(join(cmdDir, 'opsx'), { recursive: true });
|
|
136
|
-
const cmdContent = readFileSync(cmdSrc, 'utf-8');
|
|
137
|
-
writeFileSync(join(cmdDir, 'opsx', 'e2e.md'), cmdContent);
|
|
138
|
-
console.log(chalk.green(` ✓ Command updated: /opsx:e2e`));
|
|
139
|
-
|
|
140
|
-
installSchemaFrom(schemaSrc, projectRoot);
|
|
129
|
+
if (existsSync(join(projectRoot, '.claude'))) {
|
|
130
|
+
console.log(chalk.bold('Restart Claude Code to use the updated skill.'));
|
|
131
|
+
console.log(chalk.gray(' Then run /opsx:e2e <change-name> to verify.\n'));
|
|
132
|
+
} else {
|
|
133
|
+
console.log(chalk.bold('Restart your AI coding assistant to use the updated commands.'));
|
|
134
|
+
console.log(chalk.gray(' Then run openspec-pw run <change-name> to verify.\n'));
|
|
135
|
+
}
|
|
141
136
|
}
|
|
142
137
|
|
|
143
138
|
function installSchemaFrom(schemaSrc: string, projectRoot: string) {
|
|
Binary file
|