Skip to content

feat: add sofie-code-preset-setup CLI command#47

Open
rjmunro wants to merge 15 commits into
Sofie-Automation:mainfrom
rjmunro:rjmunro/setup-script
Open

feat: add sofie-code-preset-setup CLI command#47
rjmunro wants to merge 15 commits into
Sofie-Automation:mainfrom
rjmunro:rjmunro/setup-script

Conversation

@rjmunro
Copy link
Copy Markdown
Contributor

@rjmunro rjmunro commented May 6, 2026

About the Contributor

This pull request is posted on behalf of SuperFly.tv.

Type of Contribution

This is a Feature

Current Behavior

Setting up a new project to use @sofie-automation/code-standard-preset requires manually editing package.json (prettier config, lint scripts, lint-staged), creating eslint.config.mjs, copying .editorconfig, and creating the husky pre-commit hook — all described in the README.

New Behavior

A new sofie-code-preset-setup CLI command automates the setup (and can be re-run to update an existing project). It:

  • Errors if the project does not use yarn
  • Sets the prettier config key in package.json to @sofie-automation/code-standard-preset/prettier.config.mjs
  • Adds/updates lint, lint:fix, lint:eslint, lint:prettier, and license-validate scripts in package.json
  • Adds a prepare script for husky if not already present
  • Sets up lint-staged config in package.json
  • Creates eslint.config.mjs if it does not already exist
  • Copies .editorconfig from the preset (also now included in the published package files)
  • Creates .husky/pre-commit if it does not already exist

Testing Instructions

In a fresh yarn project directory (with a package.json and packageManager: yarn@...):

yarn add --dev @sofie-automation/code-standard-preset
npx sofie-code-preset-setup

Verify the expected files are created/updated. Re-run and verify it is idempotent (no duplicate entries, no overwrites of eslint.config.mjs).

Also test that running in an npm project (with package-lock.json) exits with an error.

Other Information

.editorconfig has been added to the files array in package.json so it is included in the published package.

Also fixed an issue where projects without a files field in package.json get an n/no-unpublished-import error on line 1 of their generated eslint.config.mjs, since config files always import devDependencies by design. This PR supressed n/no-unpublished-import for eslint.config.* files (alongside the existing n/no-extraneous-import suppression).

Status

  • PR is ready to be reviewed.
  • The functionality has been tested by the author.
  • Relevant unit tests has been added / updated.
  • Relevant documentation (code comments, system documentation) has been added / updated.

@rjmunro rjmunro requested a review from Julusian May 6, 2026 14:54
@rjmunro rjmunro force-pushed the rjmunro/setup-script branch from aaf194e to 31b2f4b Compare May 6, 2026 15:44
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

Review Change Stack

Warning

Rate limit exceeded

@rjmunro has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 16 minutes and 10 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: efebb26e-5a44-4f2d-8197-a59fcb53846a

📥 Commits

Reviewing files that changed from the base of the PR and between 84cdfc3 and 3987872.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (4)
  • README.md
  • bin/setup.mjs
  • eslint/main.mjs
  • package.json

Walkthrough

This PR introduces a complete automated setup CLI tool for the Sofie code standard preset. The setup script configures projects with ESLint flat config, Prettier, Husky pre-commit hooks, lint-staged, and optional monorepo cleanup. The CLI is exported via package.json, documented in updated README instructions, and supported by a minor ESLint config adjustment for handling generated eslint.config.mjs files.

Changes

Setup Automation Feature

Layer / File(s) Summary
CLI tool export and installation guide
package.json, README.md
Registers sofie-code-standard-preset-setup as a CLI bin entry, includes .editorconfig in published files, declares husky@^9 and lint-staged@^17 as peer dependencies. README is updated to replace the old one-liner installation with a yarn add --dev step and a new "Automated setup" section documenting the CLI and what it configures.
Setup script entry point and validation
bin/setup.mjs
Implements the CLI entrypoint with --force, --fix-subpackages, and --help flag parsing, locates and parses the root package.json, and enforces yarn by checking packageManager field and detecting npm/pnpm lockfiles.
Root project configuration
bin/setup.mjs
Mutates package.json (preserving original indentation) to configure Prettier, add/update lint and license scripts, and set up lint-staged. Creates eslint.config.mjs via generateEslintConfig, copies the preset .editorconfig, and creates .husky/pre-commit hook with executable permissions when missing.
Dependency installation
bin/setup.mjs
Derives version-pinned devDependency specifications from the preset's peerDependencies, installs them via yarn add --dev, and provides fallback manual install instructions on failure.
Monorepo sub-package cleanup
bin/setup.mjs
When --fix-subpackages is enabled, scans first-level subdirectories and removes conflicting Prettier keys, preset-referenced Prettier config files, and legacy .eslintrc* files; otherwise accumulates and prints warnings. Always logs notes for detected eslint.config.mjs in sub-packages and prints a final completion message.
ESLint config support for eslint.config. files*
eslint/main.mjs
Extends the generated ESLint flat config to suppress both n/no-extraneous-import and n/no-unpublished-import for eslint.config.* files, with inline comments documenting that these files are expected to import ESLint plugins and presets as devDependencies.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • Julusian
  • nytamin
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding a new CLI command for automating setup of the code-standard-preset.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing current behavior, new behavior, testing instructions, and implementation notes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rjmunro rjmunro force-pushed the rjmunro/setup-script branch 3 times, most recently from 232a96d to 84cdfc3 Compare May 13, 2026 10:46
@rjmunro rjmunro marked this pull request as ready for review May 13, 2026 10:54
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bin/setup.mjs`:
- Line 18: Update the help/usage text string that currently reads "Usage:
sofie-code-preset-setup [--force] [--help]" to the published CLI name
"sofie-code-standard-preset-setup" so the usage shows "Usage:
sofie-code-standard-preset-setup [--force] [--help]"; search for the exact
string literal (the usage line in bin/setup.mjs) and replace it, and also scan
for any other occurrences of "sofie-code-preset-setup" in the same module to
keep help text consistent.
- Around line 75-84: The current check inside the pmField falsy branch allows
projects with no lockfile to slip through; update the logic in bin/setup.mjs
(within the pmField conditional that references pmField, projectDir, path.join
and existsSync) to require explicit Yarn evidence: verify either a yarn.lock
exists (existsSync(path.join(projectDir, 'yarn.lock'))) or pmField indicates
yarn (pmField startsWith 'yarn@'); if neither is true, log an error and exit
(use the existing console.error and process.exit(1)) so non-Yarn projects fail
fast instead of breaking later during install.
- Around line 213-216: The current code unconditionally copies srcEditorconfig
to destEditorconfig (using copyFile), clobbering any existing .editorconfig;
change it to first check for the destination file (e.g., with fs.existsSync or
stat) and skip the copy and success log when destEditorconfig already exists
unless the user explicitly requested overwrite (the script's existing
force/--force flag or equivalent variable), and only call copyFile and
console.log('  ✓ Copied .editorconfig') when the file is absent or the force
flag is true.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 07e861cb-c432-46bc-abda-14ef973d3462

📥 Commits

Reviewing files that changed from the base of the PR and between 85e75b5 and 84cdfc3.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (4)
  • README.md
  • bin/setup.mjs
  • eslint/main.mjs
  • package.json

Comment thread bin/setup.mjs

if (help) {
console.log(`
Usage: sofie-code-preset-setup [--force] [--help]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix CLI name in help usage text.

Help prints sofie-code-preset-setup, but the published bin is sofie-code-standard-preset-setup. This will mislead users copying the command.

Suggested patch
-Usage: sofie-code-preset-setup [--force] [--help]
+Usage: sofie-code-standard-preset-setup [--force] [--help]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Usage: sofie-code-preset-setup [--force] [--help]
Usage: sofie-code-standard-preset-setup [--force] [--help]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/setup.mjs` at line 18, Update the help/usage text string that currently
reads "Usage: sofie-code-preset-setup [--force] [--help]" to the published CLI
name "sofie-code-standard-preset-setup" so the usage shows "Usage:
sofie-code-standard-preset-setup [--force] [--help]"; search for the exact
string literal (the usage line in bin/setup.mjs) and replace it, and also scan
for any other occurrences of "sofie-code-preset-setup" in the same module to
keep help text consistent.

Comment thread bin/setup.mjs
Comment on lines +75 to +84
if (!pmField) {
if (existsSync(path.join(projectDir, 'package-lock.json'))) {
console.error('Error: Found a package-lock.json. This tool requires yarn.')
process.exit(1)
}
if (existsSync(path.join(projectDir, 'pnpm-lock.yaml'))) {
console.error('Error: Found a pnpm-lock.yaml. This tool requires yarn.')
process.exit(1)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Yarn enforcement has a false-negative path.

If packageManager is absent and no npm/pnpm lockfile exists, non-yarn projects pass validation and fail later during install. Require explicit yarn evidence (yarn.lock or packageManager: yarn@...) before continuing.

Suggested patch
 if (!pmField) {
+	if (!existsSync(path.join(projectDir, 'yarn.lock'))) {
+		console.error('Error: Could not verify yarn usage (missing packageManager and yarn.lock). This tool requires yarn.')
+		process.exit(1)
+	}
 	if (existsSync(path.join(projectDir, 'package-lock.json'))) {
 		console.error('Error: Found a package-lock.json. This tool requires yarn.')
 		process.exit(1)
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!pmField) {
if (existsSync(path.join(projectDir, 'package-lock.json'))) {
console.error('Error: Found a package-lock.json. This tool requires yarn.')
process.exit(1)
}
if (existsSync(path.join(projectDir, 'pnpm-lock.yaml'))) {
console.error('Error: Found a pnpm-lock.yaml. This tool requires yarn.')
process.exit(1)
}
}
if (!pmField) {
if (!existsSync(path.join(projectDir, 'yarn.lock'))) {
console.error('Error: Could not verify yarn usage (missing packageManager and yarn.lock). This tool requires yarn.')
process.exit(1)
}
if (existsSync(path.join(projectDir, 'package-lock.json'))) {
console.error('Error: Found a package-lock.json. This tool requires yarn.')
process.exit(1)
}
if (existsSync(path.join(projectDir, 'pnpm-lock.yaml'))) {
console.error('Error: Found a pnpm-lock.yaml. This tool requires yarn.')
process.exit(1)
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/setup.mjs` around lines 75 - 84, The current check inside the pmField
falsy branch allows projects with no lockfile to slip through; update the logic
in bin/setup.mjs (within the pmField conditional that references pmField,
projectDir, path.join and existsSync) to require explicit Yarn evidence: verify
either a yarn.lock exists (existsSync(path.join(projectDir, 'yarn.lock'))) or
pmField indicates yarn (pmField startsWith 'yarn@'); if neither is true, log an
error and exit (use the existing console.error and process.exit(1)) so non-Yarn
projects fail fast instead of breaking later during install.

Comment thread bin/setup.mjs
Comment on lines +213 to +216
const srcEditorconfig = path.join(scriptDir, '..', '.editorconfig')
const destEditorconfig = path.join(projectDir, '.editorconfig')
await copyFile(srcEditorconfig, destEditorconfig)
console.log(' \u2714 Copied .editorconfig')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid unconditional overwrite of .editorconfig.

This always clobbers existing .editorconfig, even without --force. Align with the script’s non-destructive default behavior by skipping when present unless forced.

Suggested patch
 const srcEditorconfig = path.join(scriptDir, '..', '.editorconfig')
 const destEditorconfig = path.join(projectDir, '.editorconfig')
-await copyFile(srcEditorconfig, destEditorconfig)
-console.log('  \u2714 Copied .editorconfig')
+if (!existsSync(destEditorconfig) || force) {
+	await copyFile(srcEditorconfig, destEditorconfig)
+	console.log('  \u2714 Copied .editorconfig')
+} else {
+	console.log('  - .editorconfig already exists, skipping (use --force to overwrite)')
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const srcEditorconfig = path.join(scriptDir, '..', '.editorconfig')
const destEditorconfig = path.join(projectDir, '.editorconfig')
await copyFile(srcEditorconfig, destEditorconfig)
console.log(' \u2714 Copied .editorconfig')
const srcEditorconfig = path.join(scriptDir, '..', '.editorconfig')
const destEditorconfig = path.join(projectDir, '.editorconfig')
if (!existsSync(destEditorconfig) || force) {
await copyFile(srcEditorconfig, destEditorconfig)
console.log(' \u2714 Copied .editorconfig')
} else {
console.log(' - .editorconfig already exists, skipping (use --force to overwrite)')
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/setup.mjs` around lines 213 - 216, The current code unconditionally
copies srcEditorconfig to destEditorconfig (using copyFile), clobbering any
existing .editorconfig; change it to first check for the destination file (e.g.,
with fs.existsSync or stat) and skip the copy and success log when
destEditorconfig already exists unless the user explicitly requested overwrite
(the script's existing force/--force flag or equivalent variable), and only call
copyFile and console.log('  ✓ Copied .editorconfig') when the file is absent or
the force flag is true.

rjmunro added 15 commits May 13, 2026 16:57
Adds a new `sofie-code-preset-setup` CLI command that automates setting up
or updating a project to use this preset. It:

- Errors if the project does not use yarn
- Sets the `prettier` config key in package.json
- Adds/updates lint, lint:fix, lint:eslint, lint:prettier, and
  license-validate scripts
- Adds a prepare script for husky if not already present
- Sets up lint-staged config in package.json
- Creates eslint.config.mjs if it does not already exist
- Copies the .editorconfig from the preset
- Creates .husky/pre-commit if it does not already exist

Also adds .editorconfig to the published files list.
The setup script was running `yarn add --dev eslint` without a version
constraint, picking up whatever the latest major was (e.g. eslint@10)
instead of the version range declared in this preset's peerDependencies
(eslint@^9). This caused ERR_MODULE_NOT_FOUND for @eslint/js because
eslint@10 no longer bundles it.

Now the script reads its own peerDependencies and appends the range when
installing each peer dep (e.g. eslint@^9, typescript@~6.0, prettier@^3).
…cript

If the project dir contains a .prettierrc.json pointing to the old
.prettierrc.json path (which no longer exists in the preset), update it
to prettier.config.mjs. Prettier searches .prettierrc.json before the
package.json "prettier" key, so a stale .prettierrc.json silently
overrides the correct package.json config.
…figs

In a monorepo, prettier and ESLint config files in sub-packages shadow
the root config. Running setup with --fix-subpackages will:
- Remove the "prettier" key from sub-package package.json files
  (they inherit from the root via config walk-up)
- Delete sub-package .prettierrc* files that are just references to
  the preset (root config handles this)
- Delete legacy .eslintrc* files (replaced by flat config at root)
- Note flat eslint.config.mjs files for manual review

Without the flag, setup reports any issues found and suggests running
with --fix-subpackages.
Pre-commit hooks should fail and notify, not silently auto-fix.
Use 'prettier --check' and 'eslint' instead of '--write'/'--fix'.
These are installed by the setup script, so they should be declared
as peer dependencies with version constraints, matching what's in use
across other Sofie projects.
eslint.config.* files always import devDependencies (ESLint plugins/presets)
by design — they are never part of the published package. However, projects
without a "files" field in package.json cause npm to default to publishing
everything, making eslint.config.* appear to be a published file. In that
case the rule fires on line 1 of every project's eslint.config.mjs:

  "@sofie-automation/code-standard-preset" is not published  n/no-unpublished-import

We already suppress n/no-extraneous-import for the same files. Add the same
exemption for n/no-unpublished-import.
@rjmunro rjmunro force-pushed the rjmunro/setup-script branch from 84cdfc3 to 3987872 Compare May 13, 2026 15:59
@rjmunro rjmunro requested a review from nytamin May 13, 2026 16:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants