@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.
Files changed (136) hide show
  1. package/AGENTS.md +113 -0
  2. package/LICENSE +21 -0
  3. package/README.md +201 -0
  4. package/dist/cli/commands/doctor.js +336 -0
  5. package/dist/cli/commands/fix-targets.js +192 -0
  6. package/dist/cli/commands/fix.js +440 -0
  7. package/dist/cli/commands/setup-presets.js +281 -0
  8. package/dist/cli/commands/setup.js +501 -0
  9. package/dist/cli/generators/agent-rules.js +103 -0
  10. package/dist/cli/generators/badges.js +88 -0
  11. package/dist/cli/generators/build.js +216 -0
  12. package/dist/cli/generators/bun.js +25 -0
  13. package/dist/cli/generators/community-health.js +145 -0
  14. package/dist/cli/generators/docs-site.js +436 -0
  15. package/dist/cli/generators/git.js +164 -0
  16. package/dist/cli/generators/github-actions.js +35 -0
  17. package/dist/cli/generators/github-workflows.js +24 -0
  18. package/dist/cli/generators/gitlab-ci.js +10 -0
  19. package/dist/cli/generators/index.js +123 -0
  20. package/dist/cli/generators/linting.js +54 -0
  21. package/dist/cli/generators/misc.js +298 -0
  22. package/dist/cli/generators/nx.js +16 -0
  23. package/dist/cli/generators/package-json.js +317 -0
  24. package/dist/cli/generators/pnpm-workspace.js +126 -0
  25. package/dist/cli/generators/postcss.js +24 -0
  26. package/dist/cli/generators/readme.js +268 -0
  27. package/dist/cli/generators/security.js +156 -0
  28. package/dist/cli/generators/skills-install.js +70 -0
  29. package/dist/cli/generators/tailwind.js +34 -0
  30. package/dist/cli/generators/testing.js +88 -0
  31. package/dist/cli/generators/treeshake.js +148 -0
  32. package/dist/cli/generators/tsconfig.js +27 -0
  33. package/dist/cli/generators/turborepo.js +35 -0
  34. package/dist/cli/generators/typedoc.js +40 -0
  35. package/dist/cli/index.js +332 -0
  36. package/dist/cli/utils/copy-preset.js +98 -0
  37. package/dist/cli/utils/detect-language.js +22 -0
  38. package/dist/cli/utils/format.js +32 -0
  39. package/dist/cli/utils/install.js +28 -0
  40. package/dist/cli/utils/lockfile.js +84 -0
  41. package/dist/languages/js/checks.js +949 -0
  42. package/dist/languages/js/ci.js +251 -0
  43. package/dist/languages/js/fixers.js +735 -0
  44. package/dist/languages/registry.js +37 -0
  45. package/dist/languages/swift/checks.js +127 -0
  46. package/dist/languages/swift/ci.js +165 -0
  47. package/dist/languages/swift/fixers.js +102 -0
  48. package/dist/languages/swift/git-hooks.js +63 -0
  49. package/dist/languages/swift/gitignore.js +43 -0
  50. package/dist/languages/swift/scaffold.js +244 -0
  51. package/package.json +461 -0
  52. package/tooling/biome/README.md +90 -0
  53. package/tooling/biome/biome.json +63 -0
  54. package/tooling/bun/bunfig.toml +14 -0
  55. package/tooling/changesets/README.md +35 -0
  56. package/tooling/changesets/config.json +11 -0
  57. package/tooling/claude/repo-tooling.md +87 -0
  58. package/tooling/commitlint/commitlint.d.mts +4 -0
  59. package/tooling/commitlint/commitlint.mjs +40 -0
  60. package/tooling/cypress/cypress.config.d.mts +4 -0
  61. package/tooling/cypress/cypress.config.mjs +11 -0
  62. package/tooling/docusaurus/index.d.mts +17 -0
  63. package/tooling/docusaurus/index.mjs +38 -0
  64. package/tooling/docusaurus/sync-changelog.mjs +42 -0
  65. package/tooling/docusaurus/theme-tokens.css +79 -0
  66. package/tooling/docusaurus/theme.css +378 -0
  67. package/tooling/esbuild/index.d.mts +6 -0
  68. package/tooling/esbuild/index.mjs +102 -0
  69. package/tooling/eslint/base.d.mts +6 -0
  70. package/tooling/eslint/base.mjs +122 -0
  71. package/tooling/eslint/nextjs.d.mts +4 -0
  72. package/tooling/eslint/nextjs.mjs +22 -0
  73. package/tooling/eslint/types.d.ts +58 -0
  74. package/tooling/github-actions/workflows/cloudflare-pages.yml +42 -0
  75. package/tooling/github-actions/workflows/docker-publish.yml +44 -0
  76. package/tooling/github-actions/workflows/preview-deployments.yml +54 -0
  77. package/tooling/github-actions/workflows/vercel-deploy.yml +40 -0
  78. package/tooling/jest-presets/browser/jest-preset.d.mts +4 -0
  79. package/tooling/jest-presets/browser/jest-preset.mjs +14 -0
  80. package/tooling/jest-presets/node/jest-preset.d.mts +4 -0
  81. package/tooling/jest-presets/node/jest-preset.mjs +13 -0
  82. package/tooling/mcp/mcp.json.example +27 -0
  83. package/tooling/nx/nx.json +24 -0
  84. package/tooling/oxlint/README.md +25 -0
  85. package/tooling/oxlint/oxlintrc.json +28 -0
  86. package/tooling/playwright/playwright.config.d.mts +4 -0
  87. package/tooling/playwright/playwright.config.mjs +19 -0
  88. package/tooling/prettier/index.d.mts +4 -0
  89. package/tooling/prettier/index.mjs +36 -0
  90. package/tooling/release-please/.release-please-manifest.json +3 -0
  91. package/tooling/release-please/release-please-config.json +9 -0
  92. package/tooling/rolldown/rolldown.config.d.mts +18 -0
  93. package/tooling/rolldown/rolldown.config.mjs +59 -0
  94. package/tooling/rollup/rollup.config.d.mts +20 -0
  95. package/tooling/rollup/rollup.config.mjs +70 -0
  96. package/tooling/semantic-release/docker.d.mts +4 -0
  97. package/tooling/semantic-release/docker.mjs +59 -0
  98. package/tooling/semantic-release/github.d.mts +4 -0
  99. package/tooling/semantic-release/github.mjs +79 -0
  100. package/tooling/semantic-release/index.d.mts +4 -0
  101. package/tooling/semantic-release/index.mjs +80 -0
  102. package/tooling/swift/periphery.yml +4 -0
  103. package/tooling/swift/swiftlint.yml +23 -0
  104. package/tooling/tests/exports-resolution.d.mts +14 -0
  105. package/tooling/tests/exports-resolution.mjs +68 -0
  106. package/tooling/tests/ssr-safety.d.mts +14 -0
  107. package/tooling/tests/ssr-safety.mjs +53 -0
  108. package/tooling/tsup/index.d.mts +8 -0
  109. package/tooling/tsup/index.mjs +33 -0
  110. package/tooling/typedoc/typedoc.json +7 -0
  111. package/tooling/typescript/README.md +49 -0
  112. package/tooling/typescript/reset.d.ts +9 -0
  113. package/tooling/typescript/tsconfig.base.json +84 -0
  114. package/tooling/typescript/tsconfig.build.json +11 -0
  115. package/tooling/typescript/tsconfig.bun.json +9 -0
  116. package/tooling/typescript/tsconfig.express.json +9 -0
  117. package/tooling/typescript/tsconfig.next.json +20 -0
  118. package/tooling/typescript/tsconfig.node.json +9 -0
  119. package/tooling/typescript/tsconfig.react.json +15 -0
  120. package/tooling/typescript/tsconfig.test.json +8 -0
  121. package/tooling/typescript/v1/tsconfig.base.json +81 -0
  122. package/tooling/typescript/v1/tsconfig.express.json +9 -0
  123. package/tooling/typescript/v1/tsconfig.next.json +19 -0
  124. package/tooling/typescript/v1/tsconfig.node.json +9 -0
  125. package/tooling/typescript/v1/tsconfig.react.json +14 -0
  126. package/tooling/typescript/v1/tsconfig.test.json +8 -0
  127. package/tooling/vite/vite.config.d.mts +4 -0
  128. package/tooling/vite/vite.config.mjs +18 -0
  129. package/tooling/vitest/jsdom-shims.d.mts +1 -0
  130. package/tooling/vitest/jsdom-shims.mjs +58 -0
  131. package/tooling/vitest/vitest.config.d.mts +4 -0
  132. package/tooling/vitest/vitest.config.mjs +25 -0
  133. package/tooling/vitest/vitest.config.react.d.mts +4 -0
  134. package/tooling/vitest/vitest.config.react.mjs +27 -0
  135. package/tooling/vitest/vitest.setup.d.mts +1 -0
  136. package/tooling/vitest/vitest.setup.mjs +3 -0
@@ -0,0 +1,88 @@
1
+ // Shared badge-block builder. All URLs are derived from package.json (name +
2
+ // repository) — no hardcoded owner/repo — so it works for any consumer.
3
+ export const BADGE_START = '<!-- js-tooling:badges:start -->';
4
+ export const BADGE_END = '<!-- js-tooling:badges:end -->';
5
+ /**
6
+ * Parse a package.json `repository` field into `{ owner, repo }`.
7
+ * Handles the shorthand (`owner/repo`, `github:owner/repo`) and full git URLs
8
+ * (`git+https://github.com/owner/repo.git`, `git@github.com:owner/repo.git`).
9
+ * Returns null for non-GitHub or unparseable values.
10
+ */
11
+ export function parseRepository(repository) {
12
+ const url = typeof repository === 'string'
13
+ ? repository
14
+ : repository && typeof repository === 'object'
15
+ ? repository.url
16
+ : undefined;
17
+ if (typeof url !== 'string' || url.length === 0)
18
+ return null;
19
+ // `owner/repo` or `github:owner/repo` shorthand (no scheme, no host).
20
+ if (!url.includes('://') && !url.includes('@')) {
21
+ const short = url.replace(/^github:/, '').match(/^([\w.-]+)\/([\w.-]+?)(?:\.git)?$/);
22
+ if (short?.[1] && short[2])
23
+ return { owner: short[1], repo: short[2] };
24
+ }
25
+ const m = url.match(/github\.com[/:]([\w.-]+)\/([\w.-]+?)(?:\.git)?(?:$|[/#?])/);
26
+ return m?.[1] && m[2] ? { owner: m[1], repo: m[2] } : null;
27
+ }
28
+ /**
29
+ * Build the badge markdown row (no delimiter comments). Returns '' when only
30
+ * the always-on license badge would render — i.e. nothing package- or
31
+ * repo-specific is derivable, so a badge row isn't worth adding.
32
+ */
33
+ export function buildBadgeRow(inputs) {
34
+ const { name, owner, repo, branch = 'main', isPrivate = false } = inputs;
35
+ const slug = owner && repo ? `${owner}/${repo}` : null;
36
+ const badges = [];
37
+ let specific = false;
38
+ if (slug) {
39
+ specific = true;
40
+ badges.push(`[![CI](https://github.com/${slug}/actions/workflows/ci.yml/badge.svg)](https://github.com/${slug}/actions/workflows/ci.yml)`);
41
+ }
42
+ if (!isPrivate && name) {
43
+ specific = true;
44
+ badges.push(`[![npm version](https://img.shields.io/npm/v/${name})](https://www.npmjs.com/package/${name})`);
45
+ badges.push(`[![npm downloads](https://img.shields.io/npm/dm/${name})](https://www.npmjs.com/package/${name})`);
46
+ badges.push(`[![Bundle size](https://img.shields.io/bundlephobia/minzip/${name})](https://bundlephobia.com/package/${name})`);
47
+ }
48
+ if (!isPrivate && slug) {
49
+ badges.push(`[![Coverage](https://codecov.io/gh/${slug}/branch/${branch}/graph/badge.svg)](https://codecov.io/gh/${slug})`);
50
+ }
51
+ badges.push('[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)');
52
+ return specific ? badges.join('\n') : '';
53
+ }
54
+ /** Build the full delimited badge block, or '' when there's nothing worth showing. */
55
+ export function buildBadgeBlock(inputs) {
56
+ const row = buildBadgeRow(inputs);
57
+ return row ? `${BADGE_START}\n${row}\n${BADGE_END}` : '';
58
+ }
59
+ /** True when the text carries npm/bundlephobia/codecov badges (which 404 on private/unpublished). */
60
+ export function hasPublicOnlyBadges(text) {
61
+ return /img\.shields\.io\/npm\/|bundlephobia|codecov\.io/.test(text);
62
+ }
63
+ /**
64
+ * Insert or refresh the badge block in a README. Replaces an existing delimited
65
+ * block in place; otherwise inserts right after the first H1 (or prepends when
66
+ * there is none). Passing an empty block strips any existing one. Idempotent.
67
+ */
68
+ export function upsertBadges(readme, block) {
69
+ const start = readme.indexOf(BADGE_START);
70
+ const end = readme.indexOf(BADGE_END);
71
+ const hasBlock = start !== -1 && end !== -1 && end > start;
72
+ if (!block) {
73
+ if (!hasBlock)
74
+ return readme;
75
+ // Strip the block plus the blank line that follows it, if any.
76
+ const after = readme.slice(end + BADGE_END.length).replace(/^\n\n/, '\n');
77
+ return (readme.slice(0, start).replace(/\n\n$/, '\n') + after).replace(/^\n+/, '');
78
+ }
79
+ if (hasBlock) {
80
+ return readme.slice(0, start) + block + readme.slice(end + BADGE_END.length);
81
+ }
82
+ const lines = readme.split('\n');
83
+ const h1 = lines.findIndex((l) => /^#\s+/.test(l));
84
+ if (h1 === -1)
85
+ return `${block}\n\n${readme}`;
86
+ lines.splice(h1 + 1, 0, '', block);
87
+ return lines.join('\n');
88
+ }
@@ -0,0 +1,216 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'node:path';
3
+ // tsup, esbuild, and vite all pull in esbuild, whose install-time build script
4
+ // pnpm 11 refuses to run until it's approved — otherwise `pnpm install` fails
5
+ // with ERR_PNPM_IGNORED_BUILDS.
6
+ export function bundlerNeedsEsbuild(config) {
7
+ return config.bundler === 'tsup' || config.bundler === 'esbuild' || config.bundler === 'vite';
8
+ }
9
+ // pnpm 11 reads build-script approvals from the `allowBuilds` map (package →
10
+ // boolean), NOT the older `onlyBuiltDependencies` list — verified against the
11
+ // pinned pnpm@11.1.3, which ignores the list form and errors with
12
+ // ERR_PNPM_IGNORED_BUILDS.
13
+ const SINGLE_PACKAGE_BUILD_APPROVALS = `allowBuilds:
14
+ esbuild: true
15
+ `;
16
+ /**
17
+ * pnpm 11 no longer reads build-script approvals from package.json's `pnpm`
18
+ * field — they live in pnpm-workspace.yaml. For a single-package esbuild-backed
19
+ * build, write a minimal pnpm-workspace.yaml approving esbuild. Never clobbers
20
+ * an existing file (the treeshake-check path writes a richer one that already
21
+ * lists esbuild). Must run after that path so its file wins. Returns the
22
+ * relative path if written, else null.
23
+ */
24
+ export async function ensureBuildApprovals(config, targetDir) {
25
+ if (!bundlerNeedsEsbuild(config))
26
+ return null;
27
+ const wsPath = path.join(targetDir, 'pnpm-workspace.yaml');
28
+ if (await fs.pathExists(wsPath))
29
+ return null;
30
+ await fs.writeFile(wsPath, SINGLE_PACKAGE_BUILD_APPROVALS);
31
+ return 'pnpm-workspace.yaml';
32
+ }
33
+ export async function generateBuildConfigs(config, targetDir) {
34
+ if (config.bundler === 'tsup') {
35
+ await generateTsupConfig(targetDir);
36
+ }
37
+ else if (config.bundler === 'esbuild') {
38
+ await generateEsbuildConfig(targetDir);
39
+ }
40
+ else if (config.bundler === 'rollup') {
41
+ await generateRollupConfig(targetDir);
42
+ }
43
+ else if (config.bundler === 'rolldown') {
44
+ await generateRolldownConfig(targetDir);
45
+ }
46
+ else if (config.bundler === 'vite') {
47
+ await generateViteConfig(config, targetDir);
48
+ }
49
+ // Generate semantic-release config for GitHub
50
+ if (config.semanticRelease) {
51
+ await generateSemanticReleaseConfig(targetDir);
52
+ }
53
+ // Generate Changesets config (alternative to semantic-release)
54
+ if (config.changesets) {
55
+ await generateChangesetsConfig(targetDir);
56
+ }
57
+ // Generate Release Please config (alternative to semantic-release)
58
+ if (config.releasePlease) {
59
+ await generateReleasePleaseConfig(targetDir);
60
+ }
61
+ }
62
+ async function generateTsupConfig(targetDir) {
63
+ const tsupConfigPath = path.join(targetDir, 'tsup.config.ts');
64
+ const tsupConfig = `import { getConfig } from '@rtorcato/repo-tooling/tsup'
65
+
66
+ export default getConfig({
67
+ entry: ['src/index.ts'],
68
+ format: ['cjs', 'esm'],
69
+ dts: true,
70
+ clean: true,
71
+ splitting: false,
72
+ sourcemap: true,
73
+ }, process.env.NODE_ENV || 'development')
74
+ `;
75
+ await fs.writeFile(tsupConfigPath, tsupConfig);
76
+ }
77
+ async function generateEsbuildConfig(targetDir) {
78
+ const esbuildConfigPath = path.join(targetDir, 'build.mjs');
79
+ const esbuildConfig = `import { build } from 'esbuild'
80
+ import { nodeExternalsPlugin } from 'esbuild-node-externals'
81
+
82
+ const isProduction = process.env.NODE_ENV === 'production'
83
+
84
+ await build({
85
+ entryPoints: ['src/index.ts'],
86
+ bundle: true,
87
+ outdir: 'dist',
88
+ format: 'esm',
89
+ target: 'node18',
90
+ platform: 'node',
91
+ minify: isProduction,
92
+ sourcemap: !isProduction,
93
+ plugins: [nodeExternalsPlugin()],
94
+ })
95
+
96
+ console.log('Build completed!')
97
+ `;
98
+ await fs.writeFile(esbuildConfigPath, esbuildConfig);
99
+ }
100
+ export async function generateRollupConfig(targetDir) {
101
+ const rollupConfigPath = path.join(targetDir, 'rollup.config.mjs');
102
+ const rollupConfig = `export { default } from '@rtorcato/repo-tooling/rollup'
103
+ `;
104
+ await fs.writeFile(rollupConfigPath, rollupConfig);
105
+ }
106
+ export async function generateRolldownConfig(targetDir) {
107
+ const rolldownConfigPath = path.join(targetDir, 'rolldown.config.mjs');
108
+ const rolldownConfig = `export { default } from '@rtorcato/repo-tooling/rolldown'
109
+ `;
110
+ await fs.writeFile(rolldownConfigPath, rolldownConfig);
111
+ }
112
+ export async function generateViteConfig(config, targetDir) {
113
+ const viteConfigPath = path.join(targetDir, 'vite.config.ts');
114
+ // React apps need the plugin; we layer it on top of the shipped preset.
115
+ const viteConfig = config.projectType === 'react-app'
116
+ ? `import preset from '@rtorcato/repo-tooling/vite'
117
+ import react from '@vitejs/plugin-react'
118
+ import { defineConfig, mergeConfig } from 'vite'
119
+
120
+ export default mergeConfig(preset, defineConfig({ plugins: [react()] }))
121
+ `
122
+ : `export { default } from '@rtorcato/repo-tooling/vite'
123
+ `;
124
+ await fs.writeFile(viteConfigPath, viteConfig);
125
+ }
126
+ // Plugins the github/gitlab preset activates that semantic-release core does
127
+ // NOT bundle (core bundles only commit-analyzer, release-notes-generator, npm,
128
+ // github). Without these in the consumer's deps, `semantic-release` crashes
129
+ // with "Cannot find module '@semantic-release/changelog'" on first run.
130
+ const RELEASE_PLUGIN_DEPS = {
131
+ '@semantic-release/changelog': '^6.0.0',
132
+ '@semantic-release/git': '^10.0.0',
133
+ };
134
+ export async function generateSemanticReleaseConfig(targetDir) {
135
+ const releaseConfigPath = path.join(targetDir, 'release.config.mjs');
136
+ const releaseConfig = `export { default } from '@rtorcato/repo-tooling/semantic-release/github'
137
+ `;
138
+ await fs.writeFile(releaseConfigPath, releaseConfig);
139
+ const written = ['release.config.mjs'];
140
+ // Ensure the preset's non-bundled plugins are installed; otherwise the
141
+ // scaffolded release.config.mjs references modules the consumer lacks.
142
+ const pkgPath = path.join(targetDir, 'package.json');
143
+ if (await fs.pathExists(pkgPath)) {
144
+ const pkg = (await fs.readJson(pkgPath));
145
+ const devDeps = (pkg.devDependencies ?? {});
146
+ const deps = (pkg.dependencies ?? {});
147
+ let changed = false;
148
+ for (const [name, version] of Object.entries(RELEASE_PLUGIN_DEPS)) {
149
+ if (!devDeps[name] && !deps[name]) {
150
+ devDeps[name] = version;
151
+ changed = true;
152
+ }
153
+ }
154
+ if (changed) {
155
+ pkg.devDependencies = devDeps;
156
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
157
+ written.push('package.json');
158
+ }
159
+ }
160
+ return written;
161
+ }
162
+ export async function generateChangesetsConfig(targetDir) {
163
+ // Drop the canonical Changesets config into .changeset/config.json. The user
164
+ // owns this file once it's in their repo; subsequent `pnpm changeset` runs
165
+ // create per-change markdown files alongside it.
166
+ const { copyPreset } = await import('../utils/copy-preset.js');
167
+ await copyPreset('changesets', targetDir);
168
+ }
169
+ // The release-please workflow — googleapis/release-please-action opens/maintains
170
+ // a release PR on every push to main and tags + creates the GitHub release when
171
+ // it merges. RELEASE_TOKEN (falling back to GITHUB_TOKEN) lets the release PR's
172
+ // checks run, mirroring the semantic-release setup.
173
+ const RELEASE_PLEASE_WORKFLOW = `name: release-please
174
+
175
+ on:
176
+ push:
177
+ branches: [main]
178
+
179
+ permissions:
180
+ contents: write
181
+ pull-requests: write
182
+
183
+ jobs:
184
+ release-please:
185
+ runs-on: ubuntu-latest
186
+ steps:
187
+ - uses: googleapis/release-please-action@v4
188
+ with:
189
+ token: \${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }}
190
+ config-file: release-please-config.json
191
+ manifest-file: .release-please-manifest.json
192
+ `;
193
+ /**
194
+ * Scaffolds Release Please: the config (from the shipped preset), a starting
195
+ * manifest, and the release workflow. The config is (re)written to realign with
196
+ * the preset, but the manifest (holds live versions) and the workflow (may be
197
+ * user-tuned) are only created when absent. Returns the relative paths touched.
198
+ */
199
+ export async function generateReleasePleaseConfig(targetDir) {
200
+ const { copyPreset, getPackageRoot } = await import('../utils/copy-preset.js');
201
+ const written = [];
202
+ const cfg = await copyPreset('release-please', targetDir);
203
+ written.push(cfg.target);
204
+ const manifestDest = path.join(targetDir, '.release-please-manifest.json');
205
+ if (!(await fs.pathExists(manifestDest))) {
206
+ await fs.copy(path.join(getPackageRoot(), 'tooling/release-please/.release-please-manifest.json'), manifestDest);
207
+ }
208
+ written.push('.release-please-manifest.json');
209
+ const workflowPath = path.join(targetDir, '.github', 'workflows', 'release-please.yml');
210
+ if (!(await fs.pathExists(workflowPath))) {
211
+ await fs.ensureDir(path.dirname(workflowPath));
212
+ await fs.writeFile(workflowPath, RELEASE_PLEASE_WORKFLOW);
213
+ }
214
+ written.push('.github/workflows/release-please.yml');
215
+ return written;
216
+ }
@@ -0,0 +1,25 @@
1
+ import path from 'node:path';
2
+ import fs from 'fs-extra';
3
+ /**
4
+ * Scaffold Bun toolchain files: a bunfig.toml and — only when no tsconfig.json
5
+ * exists yet — a tsconfig.json that extends the Bun-typed preset
6
+ * (`@rtorcato/repo-tooling/typescript/bun`, which adds `types: ['bun']`). Both are
7
+ * safe-add: an existing bunfig.toml or tsconfig.json is never clobbered (an
8
+ * existing project points its tsconfig at the bun preset by hand — see the docs).
9
+ * Returns the relative paths written.
10
+ */
11
+ export async function generateBun(targetDir) {
12
+ const written = [];
13
+ const bunfigPath = path.join(targetDir, 'bunfig.toml');
14
+ if (!(await fs.pathExists(bunfigPath))) {
15
+ const { copyPreset } = await import('../utils/copy-preset.js');
16
+ const result = await copyPreset('bun', targetDir);
17
+ written.push(result.target);
18
+ }
19
+ const tsconfigPath = path.join(targetDir, 'tsconfig.json');
20
+ if (!(await fs.pathExists(tsconfigPath))) {
21
+ await fs.writeJson(tsconfigPath, { extends: '@rtorcato/repo-tooling/typescript/bun' }, { spaces: 2 });
22
+ written.push('tsconfig.json');
23
+ }
24
+ return written;
25
+ }
@@ -0,0 +1,145 @@
1
+ import path from 'node:path';
2
+ import fs from 'fs-extra';
3
+ const CONTRIBUTING = `# Contributing
4
+
5
+ Thanks for your interest in contributing!
6
+
7
+ ## Local setup
8
+
9
+ \`\`\`bash
10
+ pnpm install
11
+ pnpm build
12
+ pnpm test
13
+ \`\`\`
14
+
15
+ ## Commit messages
16
+
17
+ This project uses [Conventional Commits](https://www.conventionalcommits.org/).
18
+ Format: \`type(scope): summary\` — e.g. \`fix(api): handle empty payload\`.
19
+ Common types: \`feat\`, \`fix\`, \`docs\`, \`refactor\`, \`test\`, \`chore\`.
20
+ If commitizen is set up, run \`pnpm commit\` to be guided through it.
21
+
22
+ ## Pull requests
23
+
24
+ - Keep PRs focused and small where possible.
25
+ - Make sure \`pnpm test\` and \`pnpm check\` (lint) pass.
26
+ - Add tests for new behaviour.
27
+ - Describe what changed and why in the PR description.
28
+ `;
29
+ // Relative advisory link works from a repo-root SECURITY.md on GitHub, so the
30
+ // template stays repo-agnostic (no hardcoded owner/name). Fill in the contact.
31
+ const SECURITY = `# Security Policy
32
+
33
+ ## Supported versions
34
+
35
+ Only the latest major version receives security fixes.
36
+
37
+ ## Reporting a vulnerability
38
+
39
+ Do **not** open a public GitHub issue for security vulnerabilities.
40
+
41
+ Instead, use [GitHub's private vulnerability reporting](../../security/advisories/new)
42
+ or email **<security-contact@example.com>** with:
43
+
44
+ - A description of the vulnerability
45
+ - Steps to reproduce
46
+ - Potential impact
47
+
48
+ You'll receive a response within 5 business days.
49
+ `;
50
+ const PULL_REQUEST_TEMPLATE = `## Summary
51
+
52
+ <!-- 1-3 bullet points describing what changed and why -->
53
+ -
54
+
55
+ ## Type of change
56
+
57
+ - [ ] Bug fix
58
+ - [ ] New feature
59
+ - [ ] Refactor / cleanup
60
+ - [ ] Documentation
61
+ - [ ] CI / tooling
62
+
63
+ ## Test plan
64
+
65
+ - [ ] Existing tests pass (\`pnpm test\`)
66
+ - [ ] New tests added for new behaviour
67
+
68
+ ## Checklist
69
+
70
+ - [ ] No debug logging left in production code
71
+ - [ ] No breaking changes (or BREAKING CHANGE footer added to commit)
72
+ - [ ] Lint passes (\`pnpm check\` / \`pnpm lint\`)
73
+ `;
74
+ const BUG_REPORT = `---
75
+ name: Bug report
76
+ about: Something isn't working as expected
77
+ labels: bug
78
+ ---
79
+
80
+ ## Describe the bug
81
+
82
+ <!-- A clear and concise description of what the bug is -->
83
+
84
+ ## Steps to reproduce
85
+
86
+ 1.
87
+ 2.
88
+ 3.
89
+
90
+ ## Expected behaviour
91
+
92
+ ## Actual behaviour
93
+
94
+ <!-- Paste any error output here -->
95
+
96
+ \`\`\`
97
+ \`\`\`
98
+
99
+ ## Environment
100
+
101
+ - Package version:
102
+ - Node version (\`node -v\`):
103
+ - Package manager + version:
104
+ - OS:
105
+ `;
106
+ const FEATURE_REQUEST = `---
107
+ name: Feature request
108
+ about: Suggest an idea or improvement
109
+ labels: enhancement
110
+ ---
111
+
112
+ ## Problem
113
+
114
+ <!-- What are you trying to do that you can't do today? -->
115
+
116
+ ## Proposed solution
117
+
118
+ ## Alternatives considered
119
+
120
+ ## Additional context
121
+ `;
122
+ const FILES = [
123
+ { rel: 'CONTRIBUTING.md', content: CONTRIBUTING },
124
+ { rel: 'SECURITY.md', content: SECURITY },
125
+ { rel: '.github/PULL_REQUEST_TEMPLATE.md', content: PULL_REQUEST_TEMPLATE },
126
+ { rel: '.github/ISSUE_TEMPLATE/bug_report.md', content: BUG_REPORT },
127
+ { rel: '.github/ISSUE_TEMPLATE/feature_request.md', content: FEATURE_REQUEST },
128
+ ];
129
+ /**
130
+ * Scaffold GitHub community-health files. Safe-add: existing files are left
131
+ * untouched (the user owns them once written). Returns the list of files
132
+ * actually created.
133
+ */
134
+ export async function generateCommunityHealth(targetDir) {
135
+ const written = [];
136
+ for (const { rel, content } of FILES) {
137
+ const filepath = path.join(targetDir, rel);
138
+ if (await fs.pathExists(filepath))
139
+ continue;
140
+ await fs.ensureDir(path.dirname(filepath));
141
+ await fs.writeFile(filepath, content);
142
+ written.push(rel);
143
+ }
144
+ return written;
145
+ }