@rtorcato/repo-tooling 2.59.0
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/AGENTS.md +113 -0
- package/LICENSE +21 -0
- package/README.md +201 -0
- package/dist/cli/commands/doctor.js +336 -0
- package/dist/cli/commands/fix-targets.js +192 -0
- package/dist/cli/commands/fix.js +440 -0
- package/dist/cli/commands/setup-presets.js +281 -0
- package/dist/cli/commands/setup.js +501 -0
- package/dist/cli/generators/agent-rules.js +103 -0
- package/dist/cli/generators/badges.js +88 -0
- package/dist/cli/generators/build.js +216 -0
- package/dist/cli/generators/bun.js +25 -0
- package/dist/cli/generators/community-health.js +145 -0
- package/dist/cli/generators/docs-site.js +436 -0
- package/dist/cli/generators/git.js +164 -0
- package/dist/cli/generators/github-actions.js +35 -0
- package/dist/cli/generators/github-workflows.js +24 -0
- package/dist/cli/generators/gitlab-ci.js +10 -0
- package/dist/cli/generators/index.js +123 -0
- package/dist/cli/generators/linting.js +54 -0
- package/dist/cli/generators/misc.js +298 -0
- package/dist/cli/generators/nx.js +16 -0
- package/dist/cli/generators/package-json.js +317 -0
- package/dist/cli/generators/pnpm-workspace.js +126 -0
- package/dist/cli/generators/postcss.js +24 -0
- package/dist/cli/generators/readme.js +268 -0
- package/dist/cli/generators/security.js +156 -0
- package/dist/cli/generators/skills-install.js +70 -0
- package/dist/cli/generators/tailwind.js +34 -0
- package/dist/cli/generators/testing.js +88 -0
- package/dist/cli/generators/treeshake.js +148 -0
- package/dist/cli/generators/tsconfig.js +27 -0
- package/dist/cli/generators/turborepo.js +35 -0
- package/dist/cli/generators/typedoc.js +40 -0
- package/dist/cli/index.js +332 -0
- package/dist/cli/utils/copy-preset.js +98 -0
- package/dist/cli/utils/detect-language.js +22 -0
- package/dist/cli/utils/format.js +32 -0
- package/dist/cli/utils/install.js +28 -0
- package/dist/cli/utils/lockfile.js +84 -0
- package/dist/languages/js/checks.js +949 -0
- package/dist/languages/js/ci.js +251 -0
- package/dist/languages/js/fixers.js +735 -0
- package/dist/languages/registry.js +37 -0
- package/dist/languages/swift/checks.js +127 -0
- package/dist/languages/swift/ci.js +165 -0
- package/dist/languages/swift/fixers.js +102 -0
- package/dist/languages/swift/git-hooks.js +63 -0
- package/dist/languages/swift/gitignore.js +43 -0
- package/dist/languages/swift/scaffold.js +244 -0
- package/package.json +461 -0
- package/tooling/biome/README.md +90 -0
- package/tooling/biome/biome.json +63 -0
- package/tooling/bun/bunfig.toml +14 -0
- package/tooling/changesets/README.md +35 -0
- package/tooling/changesets/config.json +11 -0
- package/tooling/claude/repo-tooling.md +87 -0
- package/tooling/commitlint/commitlint.d.mts +4 -0
- package/tooling/commitlint/commitlint.mjs +40 -0
- package/tooling/cypress/cypress.config.d.mts +4 -0
- package/tooling/cypress/cypress.config.mjs +11 -0
- package/tooling/docusaurus/index.d.mts +17 -0
- package/tooling/docusaurus/index.mjs +38 -0
- package/tooling/docusaurus/sync-changelog.mjs +42 -0
- package/tooling/docusaurus/theme-tokens.css +79 -0
- package/tooling/docusaurus/theme.css +378 -0
- package/tooling/esbuild/index.d.mts +6 -0
- package/tooling/esbuild/index.mjs +102 -0
- package/tooling/eslint/base.d.mts +6 -0
- package/tooling/eslint/base.mjs +122 -0
- package/tooling/eslint/nextjs.d.mts +4 -0
- package/tooling/eslint/nextjs.mjs +22 -0
- package/tooling/eslint/types.d.ts +58 -0
- package/tooling/github-actions/workflows/cloudflare-pages.yml +42 -0
- package/tooling/github-actions/workflows/docker-publish.yml +44 -0
- package/tooling/github-actions/workflows/preview-deployments.yml +54 -0
- package/tooling/github-actions/workflows/vercel-deploy.yml +40 -0
- package/tooling/jest-presets/browser/jest-preset.d.mts +4 -0
- package/tooling/jest-presets/browser/jest-preset.mjs +14 -0
- package/tooling/jest-presets/node/jest-preset.d.mts +4 -0
- package/tooling/jest-presets/node/jest-preset.mjs +13 -0
- package/tooling/mcp/mcp.json.example +27 -0
- package/tooling/nx/nx.json +24 -0
- package/tooling/oxlint/README.md +25 -0
- package/tooling/oxlint/oxlintrc.json +28 -0
- package/tooling/playwright/playwright.config.d.mts +4 -0
- package/tooling/playwright/playwright.config.mjs +19 -0
- package/tooling/prettier/index.d.mts +4 -0
- package/tooling/prettier/index.mjs +36 -0
- package/tooling/release-please/.release-please-manifest.json +3 -0
- package/tooling/release-please/release-please-config.json +9 -0
- package/tooling/rolldown/rolldown.config.d.mts +18 -0
- package/tooling/rolldown/rolldown.config.mjs +59 -0
- package/tooling/rollup/rollup.config.d.mts +20 -0
- package/tooling/rollup/rollup.config.mjs +70 -0
- package/tooling/semantic-release/docker.d.mts +4 -0
- package/tooling/semantic-release/docker.mjs +59 -0
- package/tooling/semantic-release/github.d.mts +4 -0
- package/tooling/semantic-release/github.mjs +79 -0
- package/tooling/semantic-release/index.d.mts +4 -0
- package/tooling/semantic-release/index.mjs +80 -0
- package/tooling/swift/periphery.yml +4 -0
- package/tooling/swift/swiftlint.yml +23 -0
- package/tooling/tests/exports-resolution.d.mts +14 -0
- package/tooling/tests/exports-resolution.mjs +68 -0
- package/tooling/tests/ssr-safety.d.mts +14 -0
- package/tooling/tests/ssr-safety.mjs +53 -0
- package/tooling/tsup/index.d.mts +8 -0
- package/tooling/tsup/index.mjs +33 -0
- package/tooling/typedoc/typedoc.json +7 -0
- package/tooling/typescript/README.md +49 -0
- package/tooling/typescript/reset.d.ts +9 -0
- package/tooling/typescript/tsconfig.base.json +84 -0
- package/tooling/typescript/tsconfig.build.json +11 -0
- package/tooling/typescript/tsconfig.bun.json +9 -0
- package/tooling/typescript/tsconfig.express.json +9 -0
- package/tooling/typescript/tsconfig.next.json +20 -0
- package/tooling/typescript/tsconfig.node.json +9 -0
- package/tooling/typescript/tsconfig.react.json +15 -0
- package/tooling/typescript/tsconfig.test.json +8 -0
- package/tooling/typescript/v1/tsconfig.base.json +81 -0
- package/tooling/typescript/v1/tsconfig.express.json +9 -0
- package/tooling/typescript/v1/tsconfig.next.json +19 -0
- package/tooling/typescript/v1/tsconfig.node.json +9 -0
- package/tooling/typescript/v1/tsconfig.react.json +14 -0
- package/tooling/typescript/v1/tsconfig.test.json +8 -0
- package/tooling/vite/vite.config.d.mts +4 -0
- package/tooling/vite/vite.config.mjs +18 -0
- package/tooling/vitest/jsdom-shims.d.mts +1 -0
- package/tooling/vitest/jsdom-shims.mjs +58 -0
- package/tooling/vitest/vitest.config.d.mts +4 -0
- package/tooling/vitest/vitest.config.mjs +25 -0
- package/tooling/vitest/vitest.config.react.d.mts +4 -0
- package/tooling/vitest/vitest.config.react.mjs +27 -0
- package/tooling/vitest/vitest.setup.d.mts +1 -0
- package/tooling/vitest/vitest.setup.mjs +3 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { buildBadgeBlock, parseRepository } from './badges.js';
|
|
4
|
+
export async function generateReadme(config, targetDir) {
|
|
5
|
+
const readmePath = path.join(targetDir, 'README.md');
|
|
6
|
+
const badges = config.badges ? await buildReadmeBadgeBlock(config, targetDir) : '';
|
|
7
|
+
const readme = `# ${config.projectName}
|
|
8
|
+
${badges ? `\n${badges}\n` : ''}
|
|
9
|
+
> Generated with [@rtorcato/repo-tooling](https://www.npmjs.com/package/@rtorcato/repo-tooling)
|
|
10
|
+
|
|
11
|
+
## Description
|
|
12
|
+
|
|
13
|
+
Your project description here.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
\`\`\`bash
|
|
18
|
+
# Using pnpm (recommended)
|
|
19
|
+
pnpm install
|
|
20
|
+
|
|
21
|
+
# Using npm
|
|
22
|
+
npm install
|
|
23
|
+
|
|
24
|
+
# Using yarn
|
|
25
|
+
yarn install
|
|
26
|
+
\`\`\`
|
|
27
|
+
|
|
28
|
+
## Development
|
|
29
|
+
|
|
30
|
+
${generateDevelopmentSection(config)}
|
|
31
|
+
|
|
32
|
+
## Testing
|
|
33
|
+
|
|
34
|
+
${generateTestingSection(config)}
|
|
35
|
+
|
|
36
|
+
## Building
|
|
37
|
+
|
|
38
|
+
${generateBuildingSection(config)}
|
|
39
|
+
|
|
40
|
+
## Scripts
|
|
41
|
+
|
|
42
|
+
${generateScriptsSection(config)}
|
|
43
|
+
|
|
44
|
+
## Project Structure
|
|
45
|
+
|
|
46
|
+
\`\`\`
|
|
47
|
+
${config.projectName}/
|
|
48
|
+
├── src/ # Source code
|
|
49
|
+
${config.typescript.enabled ? '├── reset.d.ts # TypeScript reset types' : ''}
|
|
50
|
+
${config.testing.framework !== 'none' ? '├── tests/ # Test files' : ''}
|
|
51
|
+
├── dist/ # Build output${config.projectType === 'library' ? ' (library)' : ''}
|
|
52
|
+
├── package.json
|
|
53
|
+
${config.typescript.enabled ? '├── tsconfig.json # TypeScript configuration' : ''}
|
|
54
|
+
${config.linting.tool === 'biome' || config.linting.tool === 'both' ? '├── biome.jsonc # Biome configuration' : ''}
|
|
55
|
+
${config.linting.tool === 'eslint' || config.linting.tool === 'both' ? '├── eslint.config.mjs # ESLint configuration' : ''}
|
|
56
|
+
${config.linting.tool === 'eslint' ? '├── prettier.config.mjs # Prettier configuration' : ''}
|
|
57
|
+
${config.testing.framework === 'vitest' ? '├── vitest.config.ts # Vitest configuration' : ''}
|
|
58
|
+
${config.testing.framework === 'jest' ? '├── jest.config.mjs # Jest configuration' : ''}
|
|
59
|
+
${config.testing.framework === 'playwright' ? '├── playwright.config.ts # Playwright configuration' : ''}
|
|
60
|
+
${config.testing.framework === 'cypress' ? '├── cypress.config.ts # Cypress configuration' : ''}
|
|
61
|
+
${config.bundler === 'tsup' ? '├── tsup.config.ts # tsup configuration' : ''}
|
|
62
|
+
${config.bundler === 'esbuild' ? '├── build.mjs # esbuild configuration' : ''}
|
|
63
|
+
${config.bundler === 'rollup' ? '├── rollup.config.mjs # Rollup configuration' : ''}
|
|
64
|
+
${config.bundler === 'rolldown' ? '├── rolldown.config.mjs # Rolldown configuration' : ''}
|
|
65
|
+
${config.bundler === 'vite' ? '├── vite.config.ts # Vite configuration' : ''}
|
|
66
|
+
${config.commitLint ? '├── commitlint.config.mjs # Commitlint configuration' : ''}
|
|
67
|
+
${config.gitHooks ? '├── .husky/ # Git hooks' : ''}
|
|
68
|
+
└── README.md
|
|
69
|
+
\`\`\`
|
|
70
|
+
|
|
71
|
+
## Tooling
|
|
72
|
+
|
|
73
|
+
This project uses:
|
|
74
|
+
|
|
75
|
+
${generateToolingList(config)}
|
|
76
|
+
|
|
77
|
+
## Contributing
|
|
78
|
+
|
|
79
|
+
1. Fork the repository
|
|
80
|
+
2. Create your feature branch (\`git checkout -b feature/amazing-feature\`)
|
|
81
|
+
3. Commit your changes (\`git commit -m 'feat: add amazing feature'\`)
|
|
82
|
+
4. Push to the branch (\`git push origin feature/amazing-feature\`)
|
|
83
|
+
5. Open a Pull Request
|
|
84
|
+
|
|
85
|
+
${config.commitLint
|
|
86
|
+
? `
|
|
87
|
+
### Commit Convention
|
|
88
|
+
|
|
89
|
+
This project follows [Conventional Commits](https://conventionalcommits.org/):
|
|
90
|
+
|
|
91
|
+
- \`feat:\` new features
|
|
92
|
+
- \`fix:\` bug fixes
|
|
93
|
+
- \`docs:\` documentation changes
|
|
94
|
+
- \`style:\` formatting changes
|
|
95
|
+
- \`refactor:\` code refactoring
|
|
96
|
+
- \`test:\` adding or updating tests
|
|
97
|
+
- \`chore:\` maintenance tasks
|
|
98
|
+
|
|
99
|
+
Run \`pnpm commit\` for a guided prompt (commitizen) that builds a compliant
|
|
100
|
+
message; commitlint enforces the same rules on every commit.
|
|
101
|
+
`
|
|
102
|
+
: ''}
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
[Add your license here]
|
|
107
|
+
`;
|
|
108
|
+
await fs.writeFile(readmePath, readme);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Build the badge block for a generated README. URLs are derived from the
|
|
112
|
+
* target's package.json (name + repository); visibility falls back to the
|
|
113
|
+
* project type (libraries are public, apps private) when `private` is unset.
|
|
114
|
+
*/
|
|
115
|
+
export async function buildReadmeBadgeBlock(config, targetDir) {
|
|
116
|
+
let repository;
|
|
117
|
+
let isPrivate = config.projectType !== 'library';
|
|
118
|
+
const pkgPath = path.join(targetDir, 'package.json');
|
|
119
|
+
if (await fs.pathExists(pkgPath)) {
|
|
120
|
+
const pkg = (await fs.readJson(pkgPath));
|
|
121
|
+
repository = pkg.repository;
|
|
122
|
+
if (typeof pkg.private === 'boolean')
|
|
123
|
+
isPrivate = pkg.private;
|
|
124
|
+
}
|
|
125
|
+
const parsed = parseRepository(repository);
|
|
126
|
+
return buildBadgeBlock({
|
|
127
|
+
name: config.projectName,
|
|
128
|
+
owner: parsed?.owner,
|
|
129
|
+
repo: parsed?.repo,
|
|
130
|
+
isPrivate,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function generateDevelopmentSection(config) {
|
|
134
|
+
const sections = [];
|
|
135
|
+
if (config.bundler === 'vite') {
|
|
136
|
+
sections.push('```bash\npnpm dev\n```');
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
sections.push('Start development by running your preferred development server.');
|
|
140
|
+
}
|
|
141
|
+
if (config.linting.tool !== 'none') {
|
|
142
|
+
const lintCommand = config.linting.tool === 'biome' || config.linting.tool === 'both'
|
|
143
|
+
? 'pnpm check:fix'
|
|
144
|
+
: 'pnpm lint:fix';
|
|
145
|
+
sections.push(`\n### Linting & Formatting\n\n\`\`\`bash\n${lintCommand}\n\`\`\``);
|
|
146
|
+
}
|
|
147
|
+
if (config.typescript.enabled) {
|
|
148
|
+
sections.push('\n### Type Checking\n\n```bash\npnpm typecheck\n```');
|
|
149
|
+
}
|
|
150
|
+
return sections.length > 0 ? sections.join('\n') : 'No specific development commands configured.';
|
|
151
|
+
}
|
|
152
|
+
function generateTestingSection(config) {
|
|
153
|
+
if (config.testing.framework === 'none') {
|
|
154
|
+
return 'No testing framework configured.';
|
|
155
|
+
}
|
|
156
|
+
const commands = [];
|
|
157
|
+
if (config.testing.framework === 'vitest') {
|
|
158
|
+
commands.push('pnpm test # Run tests');
|
|
159
|
+
commands.push('pnpm test:watch # Run tests in watch mode');
|
|
160
|
+
commands.push('pnpm test:ui # Run tests with UI');
|
|
161
|
+
commands.push('pnpm coverage # Generate coverage report');
|
|
162
|
+
}
|
|
163
|
+
else if (config.testing.framework === 'jest') {
|
|
164
|
+
commands.push('pnpm test # Run tests');
|
|
165
|
+
commands.push('pnpm test:watch # Run tests in watch mode');
|
|
166
|
+
commands.push('pnpm coverage # Generate coverage report');
|
|
167
|
+
}
|
|
168
|
+
else if (config.testing.framework === 'playwright') {
|
|
169
|
+
commands.push('pnpm test:e2e # Run E2E tests');
|
|
170
|
+
commands.push('pnpm test:e2e:ui # Run E2E tests with UI');
|
|
171
|
+
}
|
|
172
|
+
else if (config.testing.framework === 'cypress') {
|
|
173
|
+
commands.push('pnpm test:e2e # Run E2E tests (headless)');
|
|
174
|
+
commands.push('pnpm test:e2e:ui # Open the Cypress runner');
|
|
175
|
+
}
|
|
176
|
+
return `\`\`\`bash\n${commands.join('\n')}\n\`\`\``;
|
|
177
|
+
}
|
|
178
|
+
function generateBuildingSection(config) {
|
|
179
|
+
if (config.bundler === 'none') {
|
|
180
|
+
return 'No build configuration set up.';
|
|
181
|
+
}
|
|
182
|
+
return '```bash\npnpm build\n```';
|
|
183
|
+
}
|
|
184
|
+
function generateScriptsSection(config) {
|
|
185
|
+
const scripts = [];
|
|
186
|
+
scripts.push('- `pnpm install` - Install dependencies');
|
|
187
|
+
if (config.typescript.enabled) {
|
|
188
|
+
scripts.push('- `pnpm typecheck` - Type check TypeScript');
|
|
189
|
+
}
|
|
190
|
+
if (config.linting.tool !== 'none') {
|
|
191
|
+
if (config.linting.tool === 'biome' || config.linting.tool === 'both') {
|
|
192
|
+
scripts.push('- `pnpm lint` - Lint code with Biome');
|
|
193
|
+
scripts.push('- `pnpm format` - Format code with Biome');
|
|
194
|
+
scripts.push('- `pnpm check:fix` - Lint and format with Biome');
|
|
195
|
+
}
|
|
196
|
+
if (config.linting.tool === 'eslint' || config.linting.tool === 'both') {
|
|
197
|
+
scripts.push('- `pnpm lint` - Lint code with ESLint');
|
|
198
|
+
scripts.push('- `pnpm lint:fix` - Fix ESLint issues');
|
|
199
|
+
scripts.push('- `pnpm format` - Format code with Prettier');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (config.testing.framework !== 'none') {
|
|
203
|
+
scripts.push('- `pnpm test` - Run tests');
|
|
204
|
+
if (config.testing.framework === 'vitest') {
|
|
205
|
+
scripts.push('- `pnpm test:watch` - Run tests in watch mode');
|
|
206
|
+
scripts.push('- `pnpm coverage` - Generate test coverage');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (config.bundler !== 'none') {
|
|
210
|
+
scripts.push('- `pnpm build` - Build for production');
|
|
211
|
+
}
|
|
212
|
+
if (config.commitLint) {
|
|
213
|
+
scripts.push('- `pnpm commit` - Write a conventional commit via a guided prompt');
|
|
214
|
+
}
|
|
215
|
+
return scripts.join('\n');
|
|
216
|
+
}
|
|
217
|
+
function generateToolingList(config) {
|
|
218
|
+
const tools = [];
|
|
219
|
+
if (config.typescript.enabled) {
|
|
220
|
+
tools.push('- **TypeScript** - Type-safe JavaScript');
|
|
221
|
+
}
|
|
222
|
+
if (config.linting.tool === 'biome') {
|
|
223
|
+
tools.push('- **Biome** - Fast formatter and linter');
|
|
224
|
+
}
|
|
225
|
+
else if (config.linting.tool === 'eslint') {
|
|
226
|
+
tools.push('- **ESLint** - Linting utility');
|
|
227
|
+
tools.push('- **Prettier** - Code formatter');
|
|
228
|
+
}
|
|
229
|
+
else if (config.linting.tool === 'both') {
|
|
230
|
+
tools.push('- **Biome** - Fast formatter and linter');
|
|
231
|
+
tools.push('- **ESLint** - Additional linting rules');
|
|
232
|
+
}
|
|
233
|
+
if (config.testing.framework === 'vitest') {
|
|
234
|
+
tools.push('- **Vitest** - Fast testing framework');
|
|
235
|
+
}
|
|
236
|
+
else if (config.testing.framework === 'jest') {
|
|
237
|
+
tools.push('- **Jest** - Testing framework');
|
|
238
|
+
}
|
|
239
|
+
else if (config.testing.framework === 'playwright') {
|
|
240
|
+
tools.push('- **Playwright** - End-to-end testing');
|
|
241
|
+
}
|
|
242
|
+
else if (config.testing.framework === 'cypress') {
|
|
243
|
+
tools.push('- **Cypress** - End-to-end testing');
|
|
244
|
+
}
|
|
245
|
+
if (config.bundler === 'tsup') {
|
|
246
|
+
tools.push('- **tsup** - TypeScript bundler');
|
|
247
|
+
}
|
|
248
|
+
else if (config.bundler === 'esbuild') {
|
|
249
|
+
tools.push('- **esbuild** - Fast JavaScript bundler');
|
|
250
|
+
}
|
|
251
|
+
else if (config.bundler === 'vite') {
|
|
252
|
+
tools.push('- **Vite** - Fast build tool');
|
|
253
|
+
}
|
|
254
|
+
if (config.gitHooks) {
|
|
255
|
+
tools.push('- **Husky** - Git hooks');
|
|
256
|
+
tools.push('- **lint-staged** - Run linters on staged files');
|
|
257
|
+
}
|
|
258
|
+
if (config.commitLint) {
|
|
259
|
+
tools.push('- **Commitlint** - Conventional commit linting');
|
|
260
|
+
}
|
|
261
|
+
if (config.semanticRelease) {
|
|
262
|
+
tools.push('- **Semantic Release** - Automated versioning and publishing');
|
|
263
|
+
}
|
|
264
|
+
if (config.releasePlease) {
|
|
265
|
+
tools.push('- **Release Please** - Automated release PRs and changelogs');
|
|
266
|
+
}
|
|
267
|
+
return tools.join('\n');
|
|
268
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import { renderCodeQLWorkflow } from '../../base/ci.js';
|
|
4
|
+
import { LANGUAGES } from '../../languages/registry.js';
|
|
5
|
+
/**
|
|
6
|
+
* The canonical dependency-update standard shared by every @rtorcato repo.
|
|
7
|
+
*
|
|
8
|
+
* `ecosystem` is the language's Dependabot `package-ecosystem` (#303) — null
|
|
9
|
+
* for a language Dependabot doesn't support, which drops the manifest block and
|
|
10
|
+
* leaves only the github-actions one.
|
|
11
|
+
*/
|
|
12
|
+
export function dependabotConfig(ecosystem) {
|
|
13
|
+
const manifest = ecosystem === null
|
|
14
|
+
? ''
|
|
15
|
+
: ` - package-ecosystem: ${ecosystem}
|
|
16
|
+
directory: /
|
|
17
|
+
schedule:
|
|
18
|
+
interval: monthly
|
|
19
|
+
time: "06:00"
|
|
20
|
+
timezone: Etc/UTC
|
|
21
|
+
# Let brand-new releases settle before opening a PR — keeps grouped bumps
|
|
22
|
+
# from tripping pnpm's minimumReleaseAge supply-chain check (a same-day
|
|
23
|
+
# release fails the frozen-lockfile install in CI).
|
|
24
|
+
cooldown:
|
|
25
|
+
default-days: 7
|
|
26
|
+
open-pull-requests-limit: 5
|
|
27
|
+
versioning-strategy: increase
|
|
28
|
+
commit-message:
|
|
29
|
+
prefix: chore
|
|
30
|
+
include: scope
|
|
31
|
+
groups:
|
|
32
|
+
# Safe tier: runtime + dev minor/patch auto-merge on green (see the
|
|
33
|
+
# dependabot-automerge workflow). Grouped so react/react-dom move together.
|
|
34
|
+
production-minor:
|
|
35
|
+
dependency-type: production
|
|
36
|
+
update-types:
|
|
37
|
+
- minor
|
|
38
|
+
- patch
|
|
39
|
+
dev-minor:
|
|
40
|
+
dependency-type: development
|
|
41
|
+
update-types:
|
|
42
|
+
- minor
|
|
43
|
+
- patch
|
|
44
|
+
# Majors batch into one PR per ecosystem — breaking by definition, so
|
|
45
|
+
# never auto-merged; triaged on the monthly cadence.
|
|
46
|
+
major-updates:
|
|
47
|
+
update-types:
|
|
48
|
+
- major
|
|
49
|
+
|
|
50
|
+
`;
|
|
51
|
+
return `version: 2
|
|
52
|
+
updates:
|
|
53
|
+
${manifest} - package-ecosystem: github-actions
|
|
54
|
+
directory: /
|
|
55
|
+
schedule:
|
|
56
|
+
interval: monthly
|
|
57
|
+
commit-message:
|
|
58
|
+
prefix: ci
|
|
59
|
+
include: scope
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
62
|
+
/** The JS flavour — the historical default, kept for the generators that don't
|
|
63
|
+
* resolve a language module. */
|
|
64
|
+
export const DEPENDABOT_CONFIG = dependabotConfig('npm');
|
|
65
|
+
/**
|
|
66
|
+
* Auto-merges patch + minor Dependabot PRs once CI is green. Requires branch
|
|
67
|
+
* protection with required status checks on the target branch — without it,
|
|
68
|
+
* \`gh pr merge --auto\` never fires. Majors are excluded (they land in the
|
|
69
|
+
* major-updates group for manual triage).
|
|
70
|
+
*/
|
|
71
|
+
export const DEPENDABOT_AUTOMERGE_WORKFLOW = `name: Dependabot auto-merge
|
|
72
|
+
|
|
73
|
+
on: pull_request
|
|
74
|
+
|
|
75
|
+
permissions:
|
|
76
|
+
contents: write
|
|
77
|
+
pull-requests: write
|
|
78
|
+
|
|
79
|
+
jobs:
|
|
80
|
+
auto-merge:
|
|
81
|
+
runs-on: ubuntu-latest
|
|
82
|
+
if: github.actor == 'dependabot[bot]'
|
|
83
|
+
steps:
|
|
84
|
+
- name: Fetch Dependabot metadata
|
|
85
|
+
id: metadata
|
|
86
|
+
uses: dependabot/fetch-metadata@v3
|
|
87
|
+
with:
|
|
88
|
+
github-token: \${{ secrets.GITHUB_TOKEN }}
|
|
89
|
+
|
|
90
|
+
- name: Auto-merge patch and minor updates
|
|
91
|
+
if: |
|
|
92
|
+
steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
|
|
93
|
+
steps.metadata.outputs.update-type == 'version-update:semver-minor'
|
|
94
|
+
run: gh pr merge --auto --squash "$PR_URL"
|
|
95
|
+
env:
|
|
96
|
+
PR_URL: \${{ github.event.pull_request.html_url }}
|
|
97
|
+
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
98
|
+
`;
|
|
99
|
+
/** Relative paths this generator owns, in a stable order for \`filesWritten\`. */
|
|
100
|
+
export const DEPENDABOT_FILES = [
|
|
101
|
+
'.github/dependabot.yml',
|
|
102
|
+
'.github/workflows/dependabot-automerge.yml',
|
|
103
|
+
];
|
|
104
|
+
/**
|
|
105
|
+
* Scaffold the canonical Dependabot setup: the grouped \`dependabot.yml\` **and**
|
|
106
|
+
* the auto-merge workflow. They're a paired unit — the config batches updates
|
|
107
|
+
* into a safe tier and a major tier, and the workflow merges the safe tier on
|
|
108
|
+
* green. See \`apps/docs/docs/guides/dependabot-strategy.md\`.
|
|
109
|
+
*/
|
|
110
|
+
export async function generateDependabotConfig(targetDir, ecosystem = 'npm') {
|
|
111
|
+
await fs.ensureDir(path.join(targetDir, '.github', 'workflows'));
|
|
112
|
+
await fs.writeFile(path.join(targetDir, '.github', 'dependabot.yml'), dependabotConfig(ecosystem));
|
|
113
|
+
await fs.writeFile(path.join(targetDir, '.github', 'workflows', 'dependabot-automerge.yml'), DEPENDABOT_AUTOMERGE_WORKFLOW);
|
|
114
|
+
return [...DEPENDABOT_FILES];
|
|
115
|
+
}
|
|
116
|
+
export async function generateRenovateConfig(targetDir) {
|
|
117
|
+
const filepath = path.join(targetDir, 'renovate.json');
|
|
118
|
+
const content = `${JSON.stringify({
|
|
119
|
+
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
|
|
120
|
+
extends: ['config:recommended', ':semanticCommits', ':semanticCommitTypeAll(chore)'],
|
|
121
|
+
schedule: ['before 4am on Monday'],
|
|
122
|
+
prConcurrentLimit: 10,
|
|
123
|
+
prHourlyLimit: 0,
|
|
124
|
+
rangeStrategy: 'bump',
|
|
125
|
+
packageRules: [
|
|
126
|
+
{
|
|
127
|
+
matchManagers: ['github-actions'],
|
|
128
|
+
commitMessagePrefix: 'chore(ci):',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
matchManagers: ['npm'],
|
|
132
|
+
commitMessagePrefix: 'chore(deps):',
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
}, null, 2)}\n`;
|
|
136
|
+
await fs.writeFile(filepath, content);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Scaffold `.github/workflows/codeql.yml` for the given CodeQL languages,
|
|
140
|
+
* defaulting to the JS module's matrix (#283).
|
|
141
|
+
*
|
|
142
|
+
* Returns the files written — empty when the language has no CodeQL support at
|
|
143
|
+
* all (Perl), since a workflow with an empty matrix would fail every run.
|
|
144
|
+
*/
|
|
145
|
+
export async function generateCodeQLWorkflow(targetDir, languages = LANGUAGES.js.codeqlLanguages) {
|
|
146
|
+
if (languages.length === 0)
|
|
147
|
+
return [];
|
|
148
|
+
await fs.ensureDir(path.join(targetDir, '.github', 'workflows'));
|
|
149
|
+
const filepath = path.join(targetDir, '.github', 'workflows', 'codeql.yml');
|
|
150
|
+
await fs.writeFile(filepath, renderCodeQLWorkflow(languages));
|
|
151
|
+
return ['.github/workflows/codeql.yml'];
|
|
152
|
+
}
|
|
153
|
+
export async function generateSecurityConfigs(targetDir) {
|
|
154
|
+
await generateDependabotConfig(targetDir);
|
|
155
|
+
await generateCodeQLWorkflow(targetDir);
|
|
156
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Documents the one-command `npx skills add` install for every agent skill a
|
|
2
|
+
// repo ships under `skills/<name>/SKILL.md`. All values are derived from the
|
|
3
|
+
// repo (its skills dir + package.json `repository`) so it works for any consumer.
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
import { upsertBlock } from './agent-rules.js';
|
|
7
|
+
import { parseRepository } from './badges.js';
|
|
8
|
+
export const SKILLS_DOCS_START = '<!-- js-tooling:skills:start -->';
|
|
9
|
+
export const SKILLS_DOCS_END = '<!-- js-tooling:skills:end -->';
|
|
10
|
+
/** Names of `skills/<name>/` directories that actually contain a SKILL.md. */
|
|
11
|
+
export async function findSkills(targetDir) {
|
|
12
|
+
const skillsDir = path.join(targetDir, 'skills');
|
|
13
|
+
if (!(await fs.pathExists(skillsDir)))
|
|
14
|
+
return [];
|
|
15
|
+
const entries = await fs.readdir(skillsDir, { withFileTypes: true });
|
|
16
|
+
const names = [];
|
|
17
|
+
for (const entry of entries) {
|
|
18
|
+
if (!entry.isDirectory())
|
|
19
|
+
continue;
|
|
20
|
+
if (await fs.pathExists(path.join(skillsDir, entry.name, 'SKILL.md')))
|
|
21
|
+
names.push(entry.name);
|
|
22
|
+
}
|
|
23
|
+
return names.sort();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Build the README section body (inner content, no delimiters) listing one
|
|
27
|
+
* `npx skills add` command per skill. Returns '' when there are no skills.
|
|
28
|
+
*/
|
|
29
|
+
export function buildSkillsInstallBody(owner, repo, skills) {
|
|
30
|
+
if (skills.length === 0)
|
|
31
|
+
return '';
|
|
32
|
+
const commands = skills
|
|
33
|
+
.map((s) => `npx skills add https://github.com/${owner}/${repo} --skill ${s}`)
|
|
34
|
+
.join('\n');
|
|
35
|
+
const plural = skills.length > 1 ? 's' : '';
|
|
36
|
+
return [
|
|
37
|
+
`## Install the skill${plural} (\`npx skills\`)`,
|
|
38
|
+
'',
|
|
39
|
+
`Any agent that supports the [\`skills\`](https://www.npmjs.com/package/skills) CLI can install this repo's skill${plural} straight from GitHub — no clone, no package install:`,
|
|
40
|
+
'',
|
|
41
|
+
'```bash',
|
|
42
|
+
commands,
|
|
43
|
+
'```',
|
|
44
|
+
].join('\n');
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Add or refresh a "Install the skill" block in README.md. No-op (returns null)
|
|
48
|
+
* when the repo ships no skills or package.json has no GitHub `repository` to
|
|
49
|
+
* build the URL from. Merge-safe: only the delimited block is touched.
|
|
50
|
+
*/
|
|
51
|
+
export async function installSkillsInstallDocs(targetDir) {
|
|
52
|
+
const skills = await findSkills(targetDir);
|
|
53
|
+
if (skills.length === 0)
|
|
54
|
+
return null;
|
|
55
|
+
const pkgPath = path.join(targetDir, 'package.json');
|
|
56
|
+
const pkg = (await fs.pathExists(pkgPath))
|
|
57
|
+
? (await fs.readJson(pkgPath).catch(() => null))
|
|
58
|
+
: null;
|
|
59
|
+
const parsed = parseRepository(pkg?.repository);
|
|
60
|
+
if (!parsed)
|
|
61
|
+
return null;
|
|
62
|
+
const body = buildSkillsInstallBody(parsed.owner, parsed.repo, skills);
|
|
63
|
+
if (!body)
|
|
64
|
+
return null;
|
|
65
|
+
await upsertBlock(path.join(targetDir, 'README.md'), body, {
|
|
66
|
+
start: SKILLS_DOCS_START,
|
|
67
|
+
end: SKILLS_DOCS_END,
|
|
68
|
+
});
|
|
69
|
+
return 'README.md';
|
|
70
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
/**
|
|
4
|
+
* Scaffold Tailwind CSS v4. v4 is CSS-first — there is no `tailwind.config.js`
|
|
5
|
+
* and content is auto-detected, so the whole setup is a PostCSS plugin plus a
|
|
6
|
+
* CSS entry that imports Tailwind. Works uniformly for Vite, Next.js, and any
|
|
7
|
+
* PostCSS-based pipeline. Both files are safe-add — an existing file is never
|
|
8
|
+
* clobbered — so this can also repair a half-wired setup.
|
|
9
|
+
*/
|
|
10
|
+
export async function generateTailwind(targetDir) {
|
|
11
|
+
const written = [];
|
|
12
|
+
const postcss = path.join(targetDir, 'postcss.config.mjs');
|
|
13
|
+
if (!(await fs.pathExists(postcss))) {
|
|
14
|
+
await fs.writeFile(postcss, POSTCSS_CONFIG);
|
|
15
|
+
written.push('postcss.config.mjs');
|
|
16
|
+
}
|
|
17
|
+
const cssRel = path.join('src', 'styles', 'globals.css');
|
|
18
|
+
const css = path.join(targetDir, cssRel);
|
|
19
|
+
if (!(await fs.pathExists(css))) {
|
|
20
|
+
await fs.ensureDir(path.dirname(css));
|
|
21
|
+
await fs.writeFile(css, GLOBALS_CSS);
|
|
22
|
+
written.push(cssRel);
|
|
23
|
+
}
|
|
24
|
+
return written;
|
|
25
|
+
}
|
|
26
|
+
const POSTCSS_CONFIG = `export default {
|
|
27
|
+
plugins: {
|
|
28
|
+
'@tailwindcss/postcss': {},
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
32
|
+
// v4 config lives in CSS. Import `src/styles/globals.css` from your app entry.
|
|
33
|
+
const GLOBALS_CSS = `@import "tailwindcss";
|
|
34
|
+
`;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export async function generateTestingConfigs(config, targetDir) {
|
|
4
|
+
if (config.testing.framework === 'vitest') {
|
|
5
|
+
await generateVitestConfig(config, targetDir);
|
|
6
|
+
}
|
|
7
|
+
else if (config.testing.framework === 'jest') {
|
|
8
|
+
await generateJestConfig(config, targetDir);
|
|
9
|
+
}
|
|
10
|
+
else if (config.testing.framework === 'playwright') {
|
|
11
|
+
await generatePlaywrightConfig(targetDir);
|
|
12
|
+
}
|
|
13
|
+
else if (config.testing.framework === 'cypress') {
|
|
14
|
+
await generateCypressConfig(targetDir);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function generateVitestConfig(config, targetDir) {
|
|
18
|
+
const vitestConfigPath = path.join(targetDir, 'vitest.config.ts');
|
|
19
|
+
const vitestConfig = `import { defineConfig } from 'vitest/config'
|
|
20
|
+
|
|
21
|
+
export default defineConfig({
|
|
22
|
+
test: {
|
|
23
|
+
globals: true,
|
|
24
|
+
environment: '${config.testing.environment === 'browser' ? 'jsdom' : 'node'}',
|
|
25
|
+
setupFiles: ['./vitest.setup.ts'],
|
|
26
|
+
coverage: {
|
|
27
|
+
provider: 'v8',
|
|
28
|
+
// lcov feeds Codecov; text is the local console summary.
|
|
29
|
+
reporter: ['text', 'lcov']
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
`;
|
|
34
|
+
await fs.writeFile(vitestConfigPath, vitestConfig);
|
|
35
|
+
// Create setup file
|
|
36
|
+
const setupPath = path.join(targetDir, 'vitest.setup.ts');
|
|
37
|
+
const setupContent = `// Vitest setup file
|
|
38
|
+
// Add global test setup here
|
|
39
|
+
`;
|
|
40
|
+
await fs.writeFile(setupPath, setupContent);
|
|
41
|
+
}
|
|
42
|
+
async function generateJestConfig(config, targetDir) {
|
|
43
|
+
const jestConfigPath = path.join(targetDir, 'jest.config.mjs');
|
|
44
|
+
const environment = config.testing.environment === 'browser' ? 'browser' : 'node';
|
|
45
|
+
const jestConfig = `export { default } from '@rtorcato/repo-tooling/jest-presets/${environment}/jest-preset'
|
|
46
|
+
`;
|
|
47
|
+
await fs.writeFile(jestConfigPath, jestConfig);
|
|
48
|
+
}
|
|
49
|
+
export async function generatePlaywrightConfig(targetDir) {
|
|
50
|
+
const playwrightConfigPath = path.join(targetDir, 'playwright.config.ts');
|
|
51
|
+
const playwrightConfig = `export { default } from '@rtorcato/repo-tooling/playwright'
|
|
52
|
+
`;
|
|
53
|
+
await fs.writeFile(playwrightConfigPath, playwrightConfig);
|
|
54
|
+
}
|
|
55
|
+
const CYPRESS_EXAMPLE_SPEC = `describe('example', () => {
|
|
56
|
+
it('loads the app', () => {
|
|
57
|
+
cy.visit('/')
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
`;
|
|
61
|
+
/**
|
|
62
|
+
* Scaffolds cypress.config.ts (a re-export of the shipped preset) plus the
|
|
63
|
+
* tests/e2e + cypress/support boilerplate. The config re-export is regenerated
|
|
64
|
+
* every run (idempotent); boilerplate/specs are only created when absent, so
|
|
65
|
+
* real tests are never clobbered. Returns the relative paths written.
|
|
66
|
+
*/
|
|
67
|
+
export async function generateCypressConfig(targetDir) {
|
|
68
|
+
const written = [];
|
|
69
|
+
await fs.writeFile(path.join(targetDir, 'cypress.config.ts'), `export { default } from '@rtorcato/repo-tooling/cypress'\n`);
|
|
70
|
+
written.push('cypress.config.ts');
|
|
71
|
+
const boilerplate = [
|
|
72
|
+
['cypress/support/e2e.ts', `import './commands'\n`],
|
|
73
|
+
[
|
|
74
|
+
'cypress/support/commands.ts',
|
|
75
|
+
`// Add custom commands via Cypress.Commands.add(...).\nexport {}\n`,
|
|
76
|
+
],
|
|
77
|
+
['tests/e2e/example.cy.ts', CYPRESS_EXAMPLE_SPEC],
|
|
78
|
+
];
|
|
79
|
+
for (const [rel, content] of boilerplate) {
|
|
80
|
+
const filePath = path.join(targetDir, rel);
|
|
81
|
+
if (await fs.pathExists(filePath))
|
|
82
|
+
continue;
|
|
83
|
+
await fs.ensureDir(path.dirname(filePath));
|
|
84
|
+
await fs.writeFile(filePath, content);
|
|
85
|
+
written.push(rel);
|
|
86
|
+
}
|
|
87
|
+
return written;
|
|
88
|
+
}
|