@rtorcato/repo-tooling 4.6.0 → 4.7.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.
@@ -15,7 +15,7 @@ import path from 'node:path';
15
15
  import chalk from 'chalk';
16
16
  import fs from 'fs-extra';
17
17
  import { installAgentRules, installAiSetup } from '../cli/generators/agent-rules.js';
18
- import { addReadmeBanner, generateBrand, renderBrand, resolveBrandMeta, } from '../cli/generators/brand.js';
18
+ import { addReadmeBanner, generateBrand, renderBrand, resolveBrandMeta, syncBrandToDocs, } from '../cli/generators/brand.js';
19
19
  import { generateCommunityHealth } from '../cli/generators/community-health.js';
20
20
  import { generateCommitlintConfig } from '../cli/generators/git.js';
21
21
  import { generateCodeowners, generateEditorConfig } from '../cli/generators/misc.js';
@@ -274,6 +274,7 @@ export const BASE_FIXERS = [
274
274
  const tagline = lock?.rules?.brand?.tagline;
275
275
  const filesWritten = await generateBrand(pkg, targetDir, tagline);
276
276
  filesWritten.push(...((await renderBrand(targetDir)) ?? []));
277
+ filesWritten.push(...(await syncBrandToDocs(targetDir)));
277
278
  const { name } = await resolveBrandMeta(pkg, targetDir, tagline);
278
279
  const readme = await addReadmeBanner(targetDir, name);
279
280
  if (readme && !filesWritten.includes(readme))
@@ -2,7 +2,7 @@ import path from 'node:path';
2
2
  import chalk from 'chalk';
3
3
  import fs from 'fs-extra';
4
4
  import { renderGitHubWorkflow } from '../../base/ci.js';
5
- import { githubJobs, scriptsOf } from '../../languages/js/ci.js';
5
+ import { githubJobs, hasBin, scriptsOf } from '../../languages/js/ci.js';
6
6
  import { inferProjectConfig } from '../../languages/js/fixers.js';
7
7
  import { checkNpmTrustedPublisher, checkPublishJob, findNpmPublishJob, NPM_OIDC_CHECK, } from '../../languages/js/npm-trust.js';
8
8
  import { PERL_GIT_HOOKS, runPerlChecks } from '../../languages/perl/checks.js';
@@ -391,7 +391,7 @@ export async function runDoctor(dir) {
391
391
  },
392
392
  // Same `scripts` gating the fixer applies, or the workflow doctor
393
393
  // compares against would reference steps the fixer never writes (#364).
394
- presetWorkflow: renderGitHubWorkflow(githubJobs(inferProjectConfig(pkg), { scripts: scriptsOf(pkg) })),
394
+ presetWorkflow: renderGitHubWorkflow(githubJobs(inferProjectConfig(pkg), { scripts: scriptsOf(pkg), bin: hasBin(pkg) })),
395
395
  language,
396
396
  codeqlLanguages: languageModule.codeqlLanguages,
397
397
  })));
@@ -402,6 +402,29 @@ export async function renderBrand(targetDir) {
402
402
  }
403
403
  return written;
404
404
  }
405
+ /** brand/ file → docs-site static/img file. The ico and card PNG exist only once rendered. */
406
+ const DOCS_ASSETS = ['favicon.svg', 'favicon.ico', 'social-card.png'];
407
+ /**
408
+ * Copy the brand favicon and social card into the docs site's `static/img`
409
+ * (#680). Copy-if-missing, and a no-op without `apps/docs`, so `fix brand` and
410
+ * `fix docs-site` reach the same tree in either order — each calls it.
411
+ */
412
+ export async function syncBrandToDocs(targetDir) {
413
+ if (!(await fs.pathExists(path.join(targetDir, 'apps', 'docs'))))
414
+ return [];
415
+ const img = path.join('apps', 'docs', 'static', 'img');
416
+ const written = [];
417
+ for (const name of DOCS_ASSETS) {
418
+ const src = path.join(targetDir, 'brand', name);
419
+ const dest = path.join(targetDir, img, name);
420
+ if (!(await fs.pathExists(src)) || (await fs.pathExists(dest)))
421
+ continue;
422
+ await fs.ensureDir(path.dirname(dest));
423
+ await fs.copyFile(src, dest);
424
+ written.push(path.join(img, name));
425
+ }
426
+ return written;
427
+ }
405
428
  export const BANNER_START = '<!-- js-tooling:banner:start -->';
406
429
  export const BANNER_END = '<!-- js-tooling:banner:end -->';
407
430
  /** The README `<picture>` banner, mobile variant under 640px, as a delimited block. */
@@ -6,6 +6,7 @@ import { coverageUploadWorkflow } from '../../base/checks.js';
6
6
  import { jsBadgeAudience } from '../../languages/js/checks.js';
7
7
  import { copyPreset, PRESETS } from '../utils/copy-preset.js';
8
8
  import { buildBadgeRow, parseRepository } from './badges.js';
9
+ import { syncBrandToDocs } from './brand.js';
9
10
  import { DOCS_SITE_BUILDS, mergeAllowBuilds } from './pnpm-workspace.js';
10
11
  import { inferSubpathsFromExports } from './treeshake.js';
11
12
  /**
@@ -176,6 +177,7 @@ ${typedocPlugins}\t\t[
176
177
  \t],
177
178
 
178
179
  \tthemeConfig: {
180
+ \t\timage: 'img/social-card.png',
179
181
  \t\tcolorMode: {
180
182
  \t\t\tdefaultMode: 'dark',
181
183
  \t\t\trespectPrefersColorScheme: true,
@@ -427,6 +429,10 @@ export async function generateDocsSite(pkg, targetDir, options = {}) {
427
429
  if (w)
428
430
  written.push(w);
429
431
  }
432
+ // static/img always exists (the config points at img/favicon.ico); the brand
433
+ // assets go in when brand/ has them (#680).
434
+ await fs.ensureDir(path.join(targetDir, DOCS_APP, 'static', 'img'));
435
+ written.push(...(await syncBrandToDocs(targetDir)));
430
436
  const ws = await ensureWorkspace(targetDir);
431
437
  if (ws)
432
438
  written.push(ws);
@@ -31,7 +31,7 @@ export const CI_WORKFLOW = '.github/workflows/ci.yml';
31
31
  * scripts itself as part of the same scaffold.
32
32
  * @returns the files actually written, relative to targetDir.
33
33
  */
34
- export async function generateGitHubActions(config, targetDir, { overwrite = false, scripts } = {}) {
34
+ export async function generateGitHubActions(config, targetDir, { overwrite = false, scripts, bin, } = {}) {
35
35
  const workflowsDir = path.join(targetDir, '.github', 'workflows');
36
36
  await fs.ensureDir(workflowsDir);
37
37
  // This is the JS path specifically. Swift (#287) renders its own workflow
@@ -40,7 +40,7 @@ export async function generateGitHubActions(config, targetDir, { overwrite = fal
40
40
  // shared entry point would mean inventing a fake config to pass in. Both
41
41
  // paths meet at renderGitHubWorkflow() in src/base/ci.ts, which is the seam
42
42
  // that actually matters.
43
- const workflow = renderGitHubWorkflow(githubJobs(config, { scripts }));
43
+ const workflow = renderGitHubWorkflow(githubJobs(config, { scripts, bin }));
44
44
  const ciPath = path.join(workflowsDir, 'ci.yml');
45
45
  const existing = (await fs.pathExists(ciPath)) ? await fs.readFile(ciPath, 'utf-8') : null;
46
46
  const filesWritten = [];
@@ -73,6 +73,10 @@ const DEPENDENCIES_JOB = {
73
73
  - name: 📦 Install dependencies
74
74
  run: pnpm install --frozen-lockfile`,
75
75
  };
76
+ /** `JobOptions.bin` for a real repo. */
77
+ export function hasBin(pkg) {
78
+ return Boolean(pkg?.bin);
79
+ }
76
80
  /**
77
81
  * `JobOptions.scripts` for a real repo. A package.json with no `scripts` block
78
82
  * yields `{}` — "this repo has no scripts", which gates every step off —
@@ -160,6 +164,21 @@ export function githubJobs(config, opts = {}) {
160
164
  const publint = config.publint
161
165
  ? '\n - name: 🔍 Validate package with publint\n run: pnpm exec publint --strict\n'
162
166
  : '';
167
+ // npm's own `bin[…] invalid and removed` warning and a bin pnpm never
168
+ // links both hide until the tarball is installed somewhere else (#693).
169
+ const packSmoke = opts.bin
170
+ ? `
171
+ - name: 📦 Smoke-test the packed tarball
172
+ run: |
173
+ set -e
174
+ BIN=$(node -p "const p=require('./package.json');typeof p.bin==='string'?p.name.split('/').pop():Object.keys(p.bin)[0]")
175
+ TARBALL="$PWD/$(npm pack --silent | tail -n1)"
176
+ tar -tzf "$TARBALL" | grep -q . || { echo "::error::npm pack produced an empty tarball"; exit 1; }
177
+ SMOKE=$(mktemp -d)
178
+ cd "$SMOKE" && npm init -y >/dev/null && npm install "$TARBALL"
179
+ ./node_modules/.bin/"$BIN" --version
180
+ `
181
+ : '';
163
182
  jobs.push({
164
183
  id: 'build',
165
184
  needs: ['dependencies'],
@@ -167,7 +186,7 @@ export function githubJobs(config, opts = {}) {
167
186
 
168
187
  - name: 🏗️ Build project
169
188
  run: pnpm build
170
- ${attw}${publint}
189
+ ${attw}${publint}${packSmoke}
171
190
  - name: 📦 Upload build artifacts
172
191
  uses: actions/upload-artifact@v7
173
192
  with:
@@ -9,7 +9,7 @@ import { generateGitLabCI } from '../../cli/generators/gitlab-ci.js';
9
9
  import { GH_WORKFLOWS, generateGhWorkflow } from '../../cli/generators/github-workflows.js';
10
10
  import { BIOME_CONFIG, generateBiomeConfig, generateESLintConfig, generatePrettierConfig, } from '../../cli/generators/linting.js';
11
11
  import { alignNodeVersion, ensureEnginesNode, ensurePackageManager, generateKnipConfig, generateNvmrc, generateSizeLimitConfig, generateVscodeExtensions, } from '../../cli/generators/misc.js';
12
- import { scriptsOf } from './ci.js';
12
+ import { hasBin, scriptsOf } from './ci.js';
13
13
  import { NPM_OIDC_CHECK, NPM_TRUST_FIXER } from './npm-trust.js';
14
14
  import { usesPnpm } from './checks.js';
15
15
  import { SIZE_LIMIT_SCRIPTS, SIZE_LIMIT_VERSION, composeVerifyScriptFromPkg, ensureScripts, } from '../../cli/generators/package-json.js';
@@ -451,6 +451,7 @@ export const FIXERS = [
451
451
  overwrite: result.check === 'GitHub Actions',
452
452
  // Only reference scripts this repo actually has (#364).
453
453
  scripts: scriptsOf(pkg),
454
+ bin: hasBin(pkg),
454
455
  });
455
456
  // `pnpm/action-setup` is emitted without a `version:` input, so without
456
457
  // this every job dies at setup with "No pnpm version is specified".
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rtorcato/repo-tooling",
3
- "version": "4.6.0",
3
+ "version": "4.7.0",
4
4
  "description": "One CLI to scaffold, audit and fix your repo's whole toolchain — linting, tests, commits, releases & CI.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -248,7 +248,7 @@
248
248
  "@next/eslint-plugin-next": "^16.3.2",
249
249
  "@playwright/test": "^1.62.1",
250
250
  "@rollup/plugin-typescript": "^12.3.0",
251
- "@rtorcato/repo-ai": "^3.1.1",
251
+ "@rtorcato/repo-ai": "^3.1.2",
252
252
  "@semantic-release/commit-analyzer": "^13.0.1",
253
253
  "@semantic-release/exec": "^7.1.0",
254
254
  "@semantic-release/github": "^12.0.9",