claude-autopm 1.18.0 → 1.20.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/README.md +159 -0
- package/autopm/.claude/agents/README.md +1 -1
- package/autopm/.claude/agents/core/mcp-manager.md +1 -1
- package/autopm/.claude/agents/decision-matrices/python-backend-selection.md +25 -25
- package/autopm/.claude/agents/decision-matrices/ui-framework-selection.md +43 -43
- package/autopm/.claude/agents/devops/github-operations-specialist.md +1 -1
- package/autopm/.claude/agents/frameworks/README.md +5 -5
- package/autopm/.claude/agents/frameworks/e2e-test-engineer.md +1 -1
- package/autopm/.claude/agents/frameworks/nats-messaging-expert.md +1 -1
- package/autopm/.claude/agents/frameworks/react-frontend-engineer.md +1 -1
- package/autopm/.claude/agents/frameworks/react-ui-expert.md +3 -3
- package/autopm/.claude/agents/frameworks/tailwindcss-expert.md +3 -3
- package/autopm/.claude/agents/frameworks/ux-design-expert.md +3 -3
- package/autopm/.claude/commands/infrastructure/traefik-setup.md +1 -1
- package/autopm/.claude/commands/playwright/test-scaffold.md +1 -1
- package/autopm/.claude/commands/pm/context.md +11 -0
- package/autopm/.claude/commands/pm/epic-decompose.md +25 -2
- package/autopm/.claude/commands/pm/epic-oneshot.md +13 -0
- package/autopm/.claude/commands/pm/epic-start.md +19 -0
- package/autopm/.claude/commands/pm/epic-sync-modular.md +10 -10
- package/autopm/.claude/commands/pm/epic-sync.md +14 -14
- package/autopm/.claude/commands/pm/issue-start.md +50 -5
- package/autopm/.claude/commands/pm/issue-sync.md +15 -15
- package/autopm/.claude/commands/pm/what-next.md +11 -0
- package/autopm/.claude/commands/ui/bootstrap-scaffold.md +6 -5
- package/autopm/.claude/commands/ui/tailwind-system.md +1 -1
- package/autopm/.claude/examples/mcp/playwright-mcp.md +2 -2
- package/autopm/.claude/examples/mcp-servers.example.json +2 -2
- package/autopm/.claude/hooks/docker-first-enforcement.sh +1 -1
- package/autopm/.claude/mcp/MCP-REGISTRY.md +1 -1
- package/autopm/.claude/mcp/playwright-mcp.md +2 -2
- package/autopm/.claude/rules/agent-coordination.md +26 -24
- package/autopm/.claude/rules/docker-first-development.md +1 -1
- package/autopm/.claude/rules/infrastructure-pipeline.md +1 -1
- package/autopm/.claude/rules/ui-development-standards.md +1 -1
- package/autopm/.claude/rules/visual-testing.md +3 -3
- package/autopm/.claude/scripts/azure/active-work.js +2 -2
- package/autopm/.claude/scripts/azure/blocked.js +13 -13
- package/autopm/.claude/scripts/azure/daily.js +1 -1
- package/autopm/.claude/scripts/azure/dashboard.js +1 -1
- package/autopm/.claude/scripts/azure/feature-list.js +2 -2
- package/autopm/.claude/scripts/azure/feature-status.js +1 -1
- package/autopm/.claude/scripts/azure/next-task.js +1 -1
- package/autopm/.claude/scripts/azure/search.js +1 -1
- package/autopm/.claude/scripts/azure/setup.js +15 -15
- package/autopm/.claude/scripts/azure/sprint-report.js +2 -2
- package/autopm/.claude/scripts/azure/sync.js +1 -1
- package/autopm/.claude/scripts/azure/us-list.js +1 -1
- package/autopm/.claude/scripts/azure/us-status.js +1 -1
- package/autopm/.claude/scripts/azure/validate.js +13 -13
- package/autopm/.claude/scripts/lib/frontmatter-utils.sh +42 -7
- package/autopm/.claude/scripts/lib/logging-utils.sh +20 -16
- package/autopm/.claude/scripts/lib/validation-utils.sh +1 -1
- package/autopm/.claude/scripts/pm/context.js +338 -0
- package/autopm/.claude/scripts/pm/issue-sync/format-comment.sh +3 -3
- package/autopm/.claude/scripts/pm/lib/README.md +85 -0
- package/autopm/.claude/scripts/pm/lib/logger.js +78 -0
- package/autopm/.claude/scripts/pm/next.js +25 -1
- package/autopm/.claude/scripts/pm/what-next.js +660 -0
- package/autopm/.claude/teams.json +3 -5
- package/autopm/.claude/templates/claude-templates/addons/devops-agents.md +2 -2
- package/autopm/.claude/templates/claude-templates/addons/docker-agents.md +4 -4
- package/autopm/.claude/templates/claude-templates/addons/minimal-agents.md +1 -1
- package/autopm/.claude/templates/issue-decomposition/api.yaml +2 -2
- package/autopm/.claude/templates/issue-decomposition/auth.yaml +4 -4
- package/autopm/.claude/templates/issue-decomposition/crud.yaml +3 -3
- package/autopm/.claude/templates/issue-decomposition/default.yaml +1 -1
- package/autopm/.claude/templates/issue-decomposition/ui-feature.yaml +2 -2
- package/bin/autopm.js +25 -0
- package/package.json +1 -2
- package/lib/agentExecutor.js.deprecated +0 -101
- package/lib/azure/cache.js +0 -80
- package/lib/azure/client.js +0 -77
- package/lib/azure/formatter.js +0 -177
- package/lib/commandHelpers.js +0 -177
- package/lib/context/manager.js +0 -290
- package/lib/documentation/manager.js +0 -528
- package/lib/github/workflow-manager.js +0 -546
- package/lib/helpers/azure-batch-api.js +0 -133
- package/lib/helpers/azure-cache-manager.js +0 -287
- package/lib/helpers/azure-parallel-processor.js +0 -158
- package/lib/helpers/azure-work-item-create.js +0 -278
- package/lib/helpers/gh-issue-create.js +0 -250
- package/lib/helpers/interactive-prompt.js +0 -336
- package/lib/helpers/output-manager.js +0 -335
- package/lib/helpers/progress-indicator.js +0 -258
- package/lib/performance/benchmarker.js +0 -429
- package/lib/pm/epic-decomposer.js +0 -273
- package/lib/pm/epic-syncer.js +0 -221
- package/lib/prdMetadata.js +0 -270
- package/lib/providers/azure/index.js +0 -234
- package/lib/providers/factory.js +0 -87
- package/lib/providers/github/index.js +0 -204
- package/lib/providers/interface.js +0 -73
- package/lib/python/scaffold-manager.js +0 -576
- package/lib/react/scaffold-manager.js +0 -745
- package/lib/regression/analyzer.js +0 -578
- package/lib/release/manager.js +0 -324
- package/lib/tailwind/manager.js +0 -486
- package/lib/traefik/manager.js +0 -484
- package/lib/utils/colors.js +0 -126
- package/lib/utils/config.js +0 -317
- package/lib/utils/filesystem.js +0 -316
- package/lib/utils/logger.js +0 -135
- package/lib/utils/prompts.js +0 -294
- package/lib/utils/shell.js +0 -237
- package/lib/validators/email-validator.js +0 -337
- package/lib/workflow/manager.js +0 -449
|
@@ -1,745 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* React Scaffold Manager
|
|
3
|
-
* Centralized React application scaffolding functionality
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const fs = require('fs').promises;
|
|
7
|
-
const path = require('path');
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Configuration
|
|
11
|
-
*/
|
|
12
|
-
const CONFIG = {
|
|
13
|
-
defaults: {
|
|
14
|
-
bundler: 'vite',
|
|
15
|
-
port: 3000,
|
|
16
|
-
testFramework: 'vitest'
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Templates
|
|
22
|
-
*/
|
|
23
|
-
const TEMPLATES = {
|
|
24
|
-
vitePackage: {
|
|
25
|
-
name: 'react-app',
|
|
26
|
-
private: true,
|
|
27
|
-
version: '0.0.0',
|
|
28
|
-
type: 'module',
|
|
29
|
-
scripts: {
|
|
30
|
-
dev: 'vite',
|
|
31
|
-
build: 'vite build',
|
|
32
|
-
lint: 'eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0',
|
|
33
|
-
preview: 'vite preview',
|
|
34
|
-
test: 'vitest'
|
|
35
|
-
},
|
|
36
|
-
dependencies: {
|
|
37
|
-
react: '^18.2.0',
|
|
38
|
-
'react-dom': '^18.2.0'
|
|
39
|
-
},
|
|
40
|
-
devDependencies: {
|
|
41
|
-
'@types/react': '^18.2.43',
|
|
42
|
-
'@types/react-dom': '^18.2.17',
|
|
43
|
-
'@vitejs/plugin-react': '^4.2.1',
|
|
44
|
-
eslint: '^8.55.0',
|
|
45
|
-
'eslint-plugin-react': '^7.33.2',
|
|
46
|
-
'eslint-plugin-react-hooks': '^4.6.0',
|
|
47
|
-
'eslint-plugin-react-refresh': '^0.4.5',
|
|
48
|
-
vite: '^5.0.8'
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
class ReactScaffoldManager {
|
|
54
|
-
constructor(projectRoot = process.cwd()) {
|
|
55
|
-
this.projectRoot = projectRoot;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Creates React app
|
|
60
|
-
*/
|
|
61
|
-
async createApp(name = 'app', options = {}) {
|
|
62
|
-
const bundler = options.bundler || CONFIG.defaults.bundler;
|
|
63
|
-
const typescript = options.typescript || false;
|
|
64
|
-
|
|
65
|
-
const appDir = path.join(this.projectRoot, name);
|
|
66
|
-
await fs.mkdir(appDir, { recursive: true });
|
|
67
|
-
await fs.mkdir(path.join(appDir, 'src'), { recursive: true });
|
|
68
|
-
await fs.mkdir(path.join(appDir, 'public'), { recursive: true });
|
|
69
|
-
|
|
70
|
-
// Create package.json
|
|
71
|
-
const packageJson = { ...TEMPLATES.vitePackage, name };
|
|
72
|
-
|
|
73
|
-
if (typescript) {
|
|
74
|
-
packageJson.devDependencies.typescript = '^5.2.2';
|
|
75
|
-
packageJson.devDependencies['@typescript-eslint/eslint-plugin'] = '^6.14.0';
|
|
76
|
-
packageJson.devDependencies['@typescript-eslint/parser'] = '^6.14.0';
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
await fs.writeFile(
|
|
80
|
-
path.join(appDir, 'package.json'),
|
|
81
|
-
JSON.stringify(packageJson, null, 2)
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
// Create vite.config.js
|
|
85
|
-
const viteConfig = `import { defineConfig } from 'vite'
|
|
86
|
-
import react from '@vitejs/plugin-react'
|
|
87
|
-
|
|
88
|
-
// https://vitejs.dev/config/
|
|
89
|
-
export default defineConfig({
|
|
90
|
-
plugins: [react()],
|
|
91
|
-
server: {
|
|
92
|
-
port: ${CONFIG.defaults.port}
|
|
93
|
-
}
|
|
94
|
-
})
|
|
95
|
-
`;
|
|
96
|
-
|
|
97
|
-
await fs.writeFile(path.join(appDir, 'vite.config.js'), viteConfig);
|
|
98
|
-
|
|
99
|
-
// Create index.html
|
|
100
|
-
const indexHtml = `<!doctype html>
|
|
101
|
-
<html lang="en">
|
|
102
|
-
<head>
|
|
103
|
-
<meta charset="UTF-8" />
|
|
104
|
-
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
105
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
106
|
-
<title>${name}</title>
|
|
107
|
-
</head>
|
|
108
|
-
<body>
|
|
109
|
-
<div id="root"></div>
|
|
110
|
-
<script type="module" src="/src/main.jsx"></script>
|
|
111
|
-
</body>
|
|
112
|
-
</html>
|
|
113
|
-
`;
|
|
114
|
-
|
|
115
|
-
await fs.writeFile(path.join(appDir, 'index.html'), indexHtml);
|
|
116
|
-
|
|
117
|
-
// Create main.jsx
|
|
118
|
-
const mainJsx = `import React from 'react'
|
|
119
|
-
import ReactDOM from 'react-dom/client'
|
|
120
|
-
import App from './App.jsx'
|
|
121
|
-
import './index.css'
|
|
122
|
-
|
|
123
|
-
ReactDOM.createRoot(document.getElementById('root')).render(
|
|
124
|
-
<React.StrictMode>
|
|
125
|
-
<App />
|
|
126
|
-
</React.StrictMode>,
|
|
127
|
-
)
|
|
128
|
-
`;
|
|
129
|
-
|
|
130
|
-
await fs.writeFile(path.join(appDir, 'src', 'main.jsx'), mainJsx);
|
|
131
|
-
|
|
132
|
-
// Create App.jsx
|
|
133
|
-
const appJsx = `import { useState } from 'react'
|
|
134
|
-
import './App.css'
|
|
135
|
-
|
|
136
|
-
function App() {
|
|
137
|
-
const [count, setCount] = useState(0)
|
|
138
|
-
|
|
139
|
-
return (
|
|
140
|
-
<>
|
|
141
|
-
<h1>React + Vite</h1>
|
|
142
|
-
<div className="card">
|
|
143
|
-
<button onClick={() => setCount((count) => count + 1)}>
|
|
144
|
-
count is {count}
|
|
145
|
-
</button>
|
|
146
|
-
</div>
|
|
147
|
-
</>
|
|
148
|
-
)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export default App
|
|
152
|
-
`;
|
|
153
|
-
|
|
154
|
-
await fs.writeFile(path.join(appDir, 'src', 'App.jsx'), appJsx);
|
|
155
|
-
|
|
156
|
-
// Create basic CSS files
|
|
157
|
-
await fs.writeFile(path.join(appDir, 'src', 'index.css'), '/* Global styles */\n');
|
|
158
|
-
await fs.writeFile(path.join(appDir, 'src', 'App.css'), '/* App styles */\n');
|
|
159
|
-
|
|
160
|
-
if (typescript) {
|
|
161
|
-
await this.setupTypeScript(appDir);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return {
|
|
165
|
-
name,
|
|
166
|
-
bundler,
|
|
167
|
-
typescript,
|
|
168
|
-
path: appDir
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Sets up TypeScript
|
|
174
|
-
*/
|
|
175
|
-
async setupTypeScript(appDir) {
|
|
176
|
-
const tsConfig = {
|
|
177
|
-
compilerOptions: {
|
|
178
|
-
target: 'ES2020',
|
|
179
|
-
useDefineForClassFields: true,
|
|
180
|
-
lib: ['ES2020', 'DOM', 'DOM.Iterable'],
|
|
181
|
-
module: 'ESNext',
|
|
182
|
-
skipLibCheck: true,
|
|
183
|
-
moduleResolution: 'bundler',
|
|
184
|
-
allowImportingTsExtensions: true,
|
|
185
|
-
resolveJsonModule: true,
|
|
186
|
-
isolatedModules: true,
|
|
187
|
-
noEmit: true,
|
|
188
|
-
jsx: 'react-jsx',
|
|
189
|
-
strict: true,
|
|
190
|
-
noUnusedLocals: true,
|
|
191
|
-
noUnusedParameters: true,
|
|
192
|
-
noFallthroughCasesInSwitch: true
|
|
193
|
-
},
|
|
194
|
-
include: ['src'],
|
|
195
|
-
references: [{ path: './tsconfig.node.json' }]
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
await fs.writeFile(
|
|
199
|
-
path.join(appDir, 'tsconfig.json'),
|
|
200
|
-
JSON.stringify(tsConfig, null, 2)
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
return tsConfig;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Creates app structure
|
|
208
|
-
*/
|
|
209
|
-
async createStructure() {
|
|
210
|
-
const dirs = [
|
|
211
|
-
'src',
|
|
212
|
-
'src/components',
|
|
213
|
-
'src/pages',
|
|
214
|
-
'src/hooks',
|
|
215
|
-
'src/utils',
|
|
216
|
-
'src/services',
|
|
217
|
-
'src/assets',
|
|
218
|
-
'src/assets/images',
|
|
219
|
-
'src/assets/styles',
|
|
220
|
-
'public'
|
|
221
|
-
];
|
|
222
|
-
|
|
223
|
-
for (const dir of dirs) {
|
|
224
|
-
await fs.mkdir(path.join(this.projectRoot, dir), { recursive: true });
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Create .gitignore
|
|
228
|
-
const gitignore = `# Dependencies
|
|
229
|
-
node_modules
|
|
230
|
-
.pnp
|
|
231
|
-
.pnp.js
|
|
232
|
-
|
|
233
|
-
# Testing
|
|
234
|
-
coverage
|
|
235
|
-
|
|
236
|
-
# Production
|
|
237
|
-
build
|
|
238
|
-
dist
|
|
239
|
-
|
|
240
|
-
# Misc
|
|
241
|
-
.DS_Store
|
|
242
|
-
.env.local
|
|
243
|
-
.env.development.local
|
|
244
|
-
.env.test.local
|
|
245
|
-
.env.production.local
|
|
246
|
-
|
|
247
|
-
npm-debug.log*
|
|
248
|
-
yarn-debug.log*
|
|
249
|
-
yarn-error.log*
|
|
250
|
-
pnpm-debug.log*
|
|
251
|
-
lerna-debug.log*
|
|
252
|
-
|
|
253
|
-
# Editor directories and files
|
|
254
|
-
.vscode/*
|
|
255
|
-
!.vscode/extensions.json
|
|
256
|
-
.idea
|
|
257
|
-
*.suo
|
|
258
|
-
*.ntvs*
|
|
259
|
-
*.njsproj
|
|
260
|
-
*.sln
|
|
261
|
-
*.sw?
|
|
262
|
-
`;
|
|
263
|
-
|
|
264
|
-
await fs.writeFile(path.join(this.projectRoot, '.gitignore'), gitignore);
|
|
265
|
-
|
|
266
|
-
return {
|
|
267
|
-
directories: dirs,
|
|
268
|
-
gitignore: true
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Creates component
|
|
274
|
-
*/
|
|
275
|
-
async createComponent(name = 'Component', options = {}) {
|
|
276
|
-
const type = options.type || 'functional';
|
|
277
|
-
const styled = options.styled || false;
|
|
278
|
-
const typescript = options.typescript || false;
|
|
279
|
-
const ext = typescript ? 'tsx' : 'jsx';
|
|
280
|
-
|
|
281
|
-
const componentsDir = path.join(this.projectRoot, 'src', 'components');
|
|
282
|
-
await fs.mkdir(componentsDir, { recursive: true });
|
|
283
|
-
|
|
284
|
-
let componentCode;
|
|
285
|
-
|
|
286
|
-
if (type === 'functional') {
|
|
287
|
-
componentCode = `import React from 'react';
|
|
288
|
-
${styled ? `import './${name}.css';` : ''}
|
|
289
|
-
|
|
290
|
-
const ${name} = (props) => {
|
|
291
|
-
return (
|
|
292
|
-
<div className="${name.toLowerCase()}">
|
|
293
|
-
<h2>${name}</h2>
|
|
294
|
-
{props.children}
|
|
295
|
-
</div>
|
|
296
|
-
);
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
export default ${name};
|
|
300
|
-
`;
|
|
301
|
-
} else {
|
|
302
|
-
componentCode = `import React, { Component } from 'react';
|
|
303
|
-
${styled ? `import './${name}.css';` : ''}
|
|
304
|
-
|
|
305
|
-
class ${name} extends Component {
|
|
306
|
-
render() {
|
|
307
|
-
return (
|
|
308
|
-
<div className="${name.toLowerCase()}">
|
|
309
|
-
<h2>${name}</h2>
|
|
310
|
-
{this.props.children}
|
|
311
|
-
</div>
|
|
312
|
-
);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
export default ${name};
|
|
317
|
-
`;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
await fs.writeFile(
|
|
321
|
-
path.join(componentsDir, `${name}.${ext}`),
|
|
322
|
-
componentCode
|
|
323
|
-
);
|
|
324
|
-
|
|
325
|
-
if (styled) {
|
|
326
|
-
const styleContent = `.${name.toLowerCase()} {
|
|
327
|
-
/* ${name} styles */
|
|
328
|
-
padding: 1rem;
|
|
329
|
-
margin: 1rem 0;
|
|
330
|
-
}
|
|
331
|
-
`;
|
|
332
|
-
await fs.writeFile(
|
|
333
|
-
path.join(componentsDir, `${name}.css`),
|
|
334
|
-
styleContent
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Create test file
|
|
339
|
-
const testContent = `import { render, screen } from '@testing-library/react';
|
|
340
|
-
import ${name} from './${name}';
|
|
341
|
-
|
|
342
|
-
describe('${name}', () => {
|
|
343
|
-
it('renders ${name} component', () => {
|
|
344
|
-
render(<${name} />);
|
|
345
|
-
const element = screen.getByText('${name}');
|
|
346
|
-
expect(element).toBeInTheDocument();
|
|
347
|
-
});
|
|
348
|
-
});
|
|
349
|
-
`;
|
|
350
|
-
|
|
351
|
-
await fs.writeFile(
|
|
352
|
-
path.join(componentsDir, `${name}.test.${ext}`),
|
|
353
|
-
testContent
|
|
354
|
-
);
|
|
355
|
-
|
|
356
|
-
return {
|
|
357
|
-
name,
|
|
358
|
-
type,
|
|
359
|
-
path: `src/components/${name}.${ext}`,
|
|
360
|
-
styled,
|
|
361
|
-
test: `src/components/${name}.test.${ext}`
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Sets up state management
|
|
367
|
-
*/
|
|
368
|
-
async setupStore(type = 'redux') {
|
|
369
|
-
const storeDir = path.join(this.projectRoot, 'src', 'store');
|
|
370
|
-
await fs.mkdir(storeDir, { recursive: true });
|
|
371
|
-
|
|
372
|
-
if (type === 'redux') {
|
|
373
|
-
return await this.setupReduxStore(storeDir);
|
|
374
|
-
} else if (type === 'zustand') {
|
|
375
|
-
return await this.setupZustandStore(storeDir);
|
|
376
|
-
} else {
|
|
377
|
-
throw new Error(`Unknown store type: ${type}`);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Sets up Redux store
|
|
383
|
-
*/
|
|
384
|
-
async setupReduxStore(storeDir) {
|
|
385
|
-
const storeConfig = `import { configureStore } from '@reduxjs/toolkit';
|
|
386
|
-
import rootReducer from './reducers';
|
|
387
|
-
|
|
388
|
-
export const store = configureStore({
|
|
389
|
-
reducer: rootReducer,
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
export type RootState = ReturnType<typeof store.getState>;
|
|
393
|
-
export type AppDispatch = typeof store.dispatch;
|
|
394
|
-
`;
|
|
395
|
-
|
|
396
|
-
await fs.writeFile(path.join(storeDir, 'index.js'), storeConfig);
|
|
397
|
-
|
|
398
|
-
// Create reducers directory
|
|
399
|
-
await fs.mkdir(path.join(storeDir, 'reducers'), { recursive: true });
|
|
400
|
-
|
|
401
|
-
const rootReducer = `import { combineReducers } from '@reduxjs/toolkit';
|
|
402
|
-
|
|
403
|
-
const rootReducer = combineReducers({
|
|
404
|
-
// Add your reducers here
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
export default rootReducer;
|
|
408
|
-
`;
|
|
409
|
-
|
|
410
|
-
await fs.writeFile(path.join(storeDir, 'reducers', 'index.js'), rootReducer);
|
|
411
|
-
|
|
412
|
-
// Create example slice
|
|
413
|
-
const exampleSlice = `import { createSlice } from '@reduxjs/toolkit';
|
|
414
|
-
|
|
415
|
-
const initialState = {
|
|
416
|
-
value: 0,
|
|
417
|
-
};
|
|
418
|
-
|
|
419
|
-
export const counterSlice = createSlice({
|
|
420
|
-
name: 'counter',
|
|
421
|
-
initialState,
|
|
422
|
-
reducers: {
|
|
423
|
-
increment: (state) => {
|
|
424
|
-
state.value += 1;
|
|
425
|
-
},
|
|
426
|
-
decrement: (state) => {
|
|
427
|
-
state.value -= 1;
|
|
428
|
-
},
|
|
429
|
-
incrementByAmount: (state, action) => {
|
|
430
|
-
state.value += action.payload;
|
|
431
|
-
},
|
|
432
|
-
},
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
|
|
436
|
-
export default counterSlice.reducer;
|
|
437
|
-
`;
|
|
438
|
-
|
|
439
|
-
await fs.writeFile(path.join(storeDir, 'counterSlice.js'), exampleSlice);
|
|
440
|
-
|
|
441
|
-
return {
|
|
442
|
-
type: 'redux',
|
|
443
|
-
store: 'src/store/index.js',
|
|
444
|
-
reducers: 'src/store/reducers/',
|
|
445
|
-
example: 'src/store/counterSlice.js'
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
* Sets up Zustand store
|
|
451
|
-
*/
|
|
452
|
-
async setupZustandStore(storeDir) {
|
|
453
|
-
const zustandStore = `import { create } from 'zustand';
|
|
454
|
-
import { devtools, persist } from 'zustand/middleware';
|
|
455
|
-
|
|
456
|
-
interface StoreState {
|
|
457
|
-
count: number;
|
|
458
|
-
increment: () => void;
|
|
459
|
-
decrement: () => void;
|
|
460
|
-
reset: () => void;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
const useStore = create<StoreState>()(
|
|
464
|
-
devtools(
|
|
465
|
-
persist(
|
|
466
|
-
(set) => ({
|
|
467
|
-
count: 0,
|
|
468
|
-
increment: () => set((state) => ({ count: state.count + 1 })),
|
|
469
|
-
decrement: () => set((state) => ({ count: state.count - 1 })),
|
|
470
|
-
reset: () => set({ count: 0 }),
|
|
471
|
-
}),
|
|
472
|
-
{
|
|
473
|
-
name: 'app-storage',
|
|
474
|
-
}
|
|
475
|
-
)
|
|
476
|
-
)
|
|
477
|
-
);
|
|
478
|
-
|
|
479
|
-
export default useStore;
|
|
480
|
-
`;
|
|
481
|
-
|
|
482
|
-
await fs.writeFile(path.join(storeDir, 'index.js'), zustandStore);
|
|
483
|
-
|
|
484
|
-
return {
|
|
485
|
-
type: 'zustand',
|
|
486
|
-
store: 'src/store/index.js'
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Sets up routing
|
|
492
|
-
*/
|
|
493
|
-
async setupRouting() {
|
|
494
|
-
const srcDir = path.join(this.projectRoot, 'src');
|
|
495
|
-
await fs.mkdir(srcDir, { recursive: true });
|
|
496
|
-
|
|
497
|
-
// Create router configuration
|
|
498
|
-
const routerConfig = `import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
|
499
|
-
import Layout from './components/Layout';
|
|
500
|
-
import HomePage from './pages/HomePage';
|
|
501
|
-
import AboutPage from './pages/AboutPage';
|
|
502
|
-
import NotFoundPage from './pages/NotFoundPage';
|
|
503
|
-
|
|
504
|
-
const router = createBrowserRouter([
|
|
505
|
-
{
|
|
506
|
-
path: '/',
|
|
507
|
-
element: <Layout />,
|
|
508
|
-
children: [
|
|
509
|
-
{
|
|
510
|
-
index: true,
|
|
511
|
-
element: <HomePage />,
|
|
512
|
-
},
|
|
513
|
-
{
|
|
514
|
-
path: 'about',
|
|
515
|
-
element: <AboutPage />,
|
|
516
|
-
},
|
|
517
|
-
{
|
|
518
|
-
path: '*',
|
|
519
|
-
element: <NotFoundPage />,
|
|
520
|
-
},
|
|
521
|
-
],
|
|
522
|
-
},
|
|
523
|
-
]);
|
|
524
|
-
|
|
525
|
-
function Router() {
|
|
526
|
-
return <RouterProvider router={router} />;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
export default Router;
|
|
530
|
-
`;
|
|
531
|
-
|
|
532
|
-
await fs.writeFile(path.join(srcDir, 'router.jsx'), routerConfig);
|
|
533
|
-
|
|
534
|
-
// Create pages
|
|
535
|
-
await this.createPages();
|
|
536
|
-
|
|
537
|
-
// Create Layout component
|
|
538
|
-
await this.createLayoutComponent();
|
|
539
|
-
|
|
540
|
-
return {
|
|
541
|
-
router: 'src/router.jsx',
|
|
542
|
-
pages: ['HomePage', 'AboutPage', 'NotFoundPage'],
|
|
543
|
-
layout: 'src/components/Layout.jsx'
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
/**
|
|
548
|
-
* Creates page components
|
|
549
|
-
*/
|
|
550
|
-
async createPages() {
|
|
551
|
-
const pagesDir = path.join(this.projectRoot, 'src', 'pages');
|
|
552
|
-
await fs.mkdir(pagesDir, { recursive: true });
|
|
553
|
-
|
|
554
|
-
const pages = {
|
|
555
|
-
HomePage: `import React from 'react';
|
|
556
|
-
|
|
557
|
-
const HomePage = () => {
|
|
558
|
-
return (
|
|
559
|
-
<div>
|
|
560
|
-
<h1>Home Page</h1>
|
|
561
|
-
<p>Welcome to the React app!</p>
|
|
562
|
-
</div>
|
|
563
|
-
);
|
|
564
|
-
};
|
|
565
|
-
|
|
566
|
-
export default HomePage;
|
|
567
|
-
`,
|
|
568
|
-
AboutPage: `import React from 'react';
|
|
569
|
-
|
|
570
|
-
const AboutPage = () => {
|
|
571
|
-
return (
|
|
572
|
-
<div>
|
|
573
|
-
<h1>About Page</h1>
|
|
574
|
-
<p>This is the about page.</p>
|
|
575
|
-
</div>
|
|
576
|
-
);
|
|
577
|
-
};
|
|
578
|
-
|
|
579
|
-
export default AboutPage;
|
|
580
|
-
`,
|
|
581
|
-
NotFoundPage: `import React from 'react';
|
|
582
|
-
|
|
583
|
-
const NotFoundPage = () => {
|
|
584
|
-
return (
|
|
585
|
-
<div>
|
|
586
|
-
<h1>404 - Page Not Found</h1>
|
|
587
|
-
<p>The page you are looking for does not exist.</p>
|
|
588
|
-
</div>
|
|
589
|
-
);
|
|
590
|
-
};
|
|
591
|
-
|
|
592
|
-
export default NotFoundPage;
|
|
593
|
-
`
|
|
594
|
-
};
|
|
595
|
-
|
|
596
|
-
for (const [name, content] of Object.entries(pages)) {
|
|
597
|
-
await fs.writeFile(path.join(pagesDir, `${name}.jsx`), content);
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
/**
|
|
602
|
-
* Creates Layout component
|
|
603
|
-
*/
|
|
604
|
-
async createLayoutComponent() {
|
|
605
|
-
const componentsDir = path.join(this.projectRoot, 'src', 'components');
|
|
606
|
-
await fs.mkdir(componentsDir, { recursive: true });
|
|
607
|
-
|
|
608
|
-
const layout = `import React from 'react';
|
|
609
|
-
import { Outlet, Link } from 'react-router-dom';
|
|
610
|
-
|
|
611
|
-
const Layout = () => {
|
|
612
|
-
return (
|
|
613
|
-
<div>
|
|
614
|
-
<nav>
|
|
615
|
-
<ul>
|
|
616
|
-
<li><Link to="/">Home</Link></li>
|
|
617
|
-
<li><Link to="/about">About</Link></li>
|
|
618
|
-
</ul>
|
|
619
|
-
</nav>
|
|
620
|
-
<main>
|
|
621
|
-
<Outlet />
|
|
622
|
-
</main>
|
|
623
|
-
</div>
|
|
624
|
-
);
|
|
625
|
-
};
|
|
626
|
-
|
|
627
|
-
export default Layout;
|
|
628
|
-
`;
|
|
629
|
-
|
|
630
|
-
await fs.writeFile(path.join(componentsDir, 'Layout.jsx'), layout);
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
/**
|
|
634
|
-
* Sets up testing
|
|
635
|
-
*/
|
|
636
|
-
async setupTesting(framework = CONFIG.defaults.testFramework) {
|
|
637
|
-
if (framework === 'vitest') {
|
|
638
|
-
return await this.setupVitest();
|
|
639
|
-
} else if (framework === 'jest') {
|
|
640
|
-
return await this.setupJest();
|
|
641
|
-
} else {
|
|
642
|
-
throw new Error(`Unknown test framework: ${framework}`);
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
/**
|
|
647
|
-
* Sets up Vitest
|
|
648
|
-
*/
|
|
649
|
-
async setupVitest() {
|
|
650
|
-
const vitestConfig = `/// <reference types="vitest" />
|
|
651
|
-
import { defineConfig } from 'vite';
|
|
652
|
-
import react from '@vitejs/plugin-react';
|
|
653
|
-
|
|
654
|
-
export default defineConfig({
|
|
655
|
-
plugins: [react()],
|
|
656
|
-
test: {
|
|
657
|
-
globals: true,
|
|
658
|
-
environment: 'jsdom',
|
|
659
|
-
setupFiles: './src/test/setup.js',
|
|
660
|
-
coverage: {
|
|
661
|
-
provider: 'v8',
|
|
662
|
-
reporter: ['text', 'json', 'html'],
|
|
663
|
-
},
|
|
664
|
-
},
|
|
665
|
-
});
|
|
666
|
-
`;
|
|
667
|
-
|
|
668
|
-
await fs.writeFile(
|
|
669
|
-
path.join(this.projectRoot, 'vitest.config.js'),
|
|
670
|
-
vitestConfig
|
|
671
|
-
);
|
|
672
|
-
|
|
673
|
-
// Create test setup
|
|
674
|
-
const testDir = path.join(this.projectRoot, 'src', 'test');
|
|
675
|
-
await fs.mkdir(testDir, { recursive: true });
|
|
676
|
-
|
|
677
|
-
const setupFile = `import '@testing-library/jest-dom';
|
|
678
|
-
import { cleanup } from '@testing-library/react';
|
|
679
|
-
import { afterEach } from 'vitest';
|
|
680
|
-
|
|
681
|
-
// Cleanup after each test
|
|
682
|
-
afterEach(() => {
|
|
683
|
-
cleanup();
|
|
684
|
-
});
|
|
685
|
-
`;
|
|
686
|
-
|
|
687
|
-
await fs.writeFile(path.join(testDir, 'setup.js'), setupFile);
|
|
688
|
-
|
|
689
|
-
// Create example test
|
|
690
|
-
const exampleTest = `import { describe, it, expect } from 'vitest';
|
|
691
|
-
import { render, screen } from '@testing-library/react';
|
|
692
|
-
import App from '../App';
|
|
693
|
-
|
|
694
|
-
describe('App', () => {
|
|
695
|
-
it('renders headline', () => {
|
|
696
|
-
render(<App />);
|
|
697
|
-
const headline = screen.getByText(/React/i);
|
|
698
|
-
expect(headline).toBeInTheDocument();
|
|
699
|
-
});
|
|
700
|
-
});
|
|
701
|
-
`;
|
|
702
|
-
|
|
703
|
-
await fs.writeFile(path.join(testDir, 'App.test.jsx'), exampleTest);
|
|
704
|
-
|
|
705
|
-
return {
|
|
706
|
-
framework: 'vitest',
|
|
707
|
-
config: 'vitest.config.js',
|
|
708
|
-
setup: 'src/test/setup.js',
|
|
709
|
-
example: 'src/test/App.test.jsx'
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
/**
|
|
714
|
-
* Sets up Jest
|
|
715
|
-
*/
|
|
716
|
-
async setupJest() {
|
|
717
|
-
const jestConfig = {
|
|
718
|
-
testEnvironment: 'jsdom',
|
|
719
|
-
setupFilesAfterEnv: ['<rootDir>/src/test/setup.js'],
|
|
720
|
-
moduleNameMapper: {
|
|
721
|
-
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
|
|
722
|
-
},
|
|
723
|
-
transform: {
|
|
724
|
-
'^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', {
|
|
725
|
-
presets: [
|
|
726
|
-
['@babel/preset-env', { targets: { node: 'current' } }],
|
|
727
|
-
'@babel/preset-react',
|
|
728
|
-
],
|
|
729
|
-
}],
|
|
730
|
-
},
|
|
731
|
-
};
|
|
732
|
-
|
|
733
|
-
await fs.writeFile(
|
|
734
|
-
path.join(this.projectRoot, 'jest.config.json'),
|
|
735
|
-
JSON.stringify(jestConfig, null, 2)
|
|
736
|
-
);
|
|
737
|
-
|
|
738
|
-
return {
|
|
739
|
-
framework: 'jest',
|
|
740
|
-
config: 'jest.config.json'
|
|
741
|
-
};
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
module.exports = ReactScaffoldManager;
|