@jpcw/configs
v2.5.0
Published
Shared TypeScript / ESLint / Prettier configs and CLI helpers for JPCW projects (SuiteScript, React, and React Native presets).
Readme
@jpcw/configs
Opinionated TypeScript / ESLint / Prettier presets and a small CLI: scaffold a fully configured project in one command, keep the toolchain current without hand-editing pins, and run a quieter tsc --watch.
In honor and memory of Forrest Jones. C# > TS.
Why
Every TypeScript repo carries the same half-dozen config files, and they drift the moment they're copy-pasted. This package centralizes them: your repo takes one dev dependency and a handful of one-line files that extend / re-export a preset. Update the package, and every repo follows.
- Modern by default — ESLint 9+ flat config (no airbnb, no FlatCompat), fast type-aware linting via
projectService: true, Prettier owns all formatting. - NetSuite as a first-class target — generic presets get SuiteScript wrong; the
suitescriptpreset targets the actual runtime (AMD/ES2021 output, CRLF) and lints for its constraints. - A CLI for the boring parts —
initscaffolds everything,upgradere-resolves the toolchain to current versions,watchis an incrementaltsc --watchthat reports only what changed.
Presets
suitescript— NetSuite SuiteScript 2.x. TS targeting AMD/ES2021, CRLF, modern typescript-eslint. Lints Node.js built-in imports as errors (they don't exist in the NetSuite runtime), requires the@NApiVersion/@NScriptTypegovernance JSDoc on files underSuiteScripts/(miscased tags like@NAPIVersionbreak NetSuite's loader and are auto-corrected byeslint --fix), and enforces the entry-point contract:.ue.ts/.mr.ts/etc. files must declare the@NScriptTypematching their suffix and export that script type's entry-point function(s) — deploy-time failures caught at lint time. Ships a separatetsconfig.spa.jsonfor React SPAs bundled into a Suitelet.react— React for the web (Vite + ES2022 + JSX, LF, jsx-a11y).react-native— React Native / Expo (no DOM lib,__DEV__global,jsx: react-native).
Quick start
mkdir my-project && cd my-project
git init
npx -p @jpcw/configs jpcw-configs init suitescript --pm yarn # or react | react-native; --pm npm | bun
yarn install # match your --pminit scaffolds everything: .gitignore, .gitattributes, .editorconfig, tsconfig.json, eslint.config.mjs, prettier.config.mjs, .vscode/settings.json, a CI workflow, a pre-commit hook (lint-staged on staged files), CLAUDE.md + .claude/settings.json for Claude Code, and a starter package.json with deps already wired up. It's idempotent — re-running on an existing project only adds what's missing (doctor --fix does the same for repos scaffolded by older versions).
Preset notes:
- suitescript — replace the
PROJECT_NAMEandREPLACE_WITH_SDF_AUTH_ID_*placeholders inpackage.json, then install. Want full strict mode? Switch the tsconfigextendsto@jpcw/configs/suitescript/tsconfig.strict.json. - react-native — assumes Expo + Expo Router. On bare RN, drop
expo-routerfrompackage.jsonand adjustmain.
What gets written
| File | Purpose |
|---|---|
| .gitignore | Per-preset ignores (Node, build artifacts, IDE, secrets, plus SDF for suitescript / native + Expo for react-native) |
| .gitattributes | Line endings pinned at the git layer (CRLF working tree for suitescript, LF otherwise) — checkouts stop depending on each machine's autocrlf |
| .prettierignore | Per-preset Prettier ignores (build output, lockfiles; native dirs for react-native) |
| tsconfig.spa.json | suitescript only — bundler/react-jsx tsconfig for a React SPA served by a Suitelet (source under src/spa/) |
| .editorconfig | Tabs/EOL source of truth (CRLF for suitescript, LF for react/react-native) |
| .vscode/settings.json | Format-on-save, ESLint flat-config mode, Prettier as default formatter |
| tsconfig.json | Extends the preset; project-specific paths/include filled in |
| eslint.config.mjs | One-line re-export of the preset |
| prettier.config.mjs | One-line re-export of the preset |
| package.json | Pinned deps, lint/lint:fix/format/format:write/typecheck scripts, SDF auth scripts (suitescript), lint-staged config + a postinstall that wires the pre-commit hook |
| .githooks/pre-commit | Runs lint-staged (Prettier + eslint --fix on staged files only); wired via git config core.hooksPath .githooks in postinstall |
| .github/workflows/ci.yml | Thin caller of this repo's centrally maintained reusable-ci.yml — lint + typecheck + format check on push/PR, package-manager auto-detected |
| CLAUDE.md | Per-preset starter notes for Claude Code (commands, conventions — e.g. the "script records are created in the NetSuite UI" rule for suitescript) |
| .claude/settings.json | Claude Code permission allowlist for the standard scripts (fewer prompts in fresh repos) |
| .yarnrc.yml | Yarn-only: nodeLinker: node-modules (required for SDF tooling on suitescript) + npmPreapprovedPackages for @jpcw/* (Yarn 4's age gate otherwise blocks fresh releases) |
Migrate an existing project
init is safe to run on a non-empty repo — it only adds what's missing and skips files that already exist (--force overrides .gitignore).
Add the package and run
init:yarn add -D @jpcw/configs npx jpcw-configs init suitescript # or react | react-nativeinitadds deps at the template's pinned versions. Migrating an older repo? Follow withupgradeto bring the toolchain to current versions — or scaffold withinit suitescript --latestto do both at once.If you already had
tsconfig.json/eslint.config.mjs/prettier.config.mjs,initskipped them. Either delete yours and re-run, or point them at the preset by hand:- "extends": "./configs/tsconfig.base.json", + "extends": "@jpcw/configs/suitescript/tsconfig.json",Replace any legacy
.eslintrc*with the scaffoldedeslint.config.mjs(ESLint 9 flat config), and any.prettierrc*withprettier.config.mjs. Delete the old files — ESLint and Prettier will pick up the old ones first otherwise.Verify:
npx jpcw-configs doctor yarn typecheck && yarn lint && yarn formatdoctorre-run any time later catches drift (new scaffold files added by newer@jpcw/configsversions, lost files, package.json gaps) —--fixapplies the additive repairs.
CLI reference
jpcw-configs init <preset>
Scaffolds a project with the preset's config files. Idempotent — re-running only adds what's missing.
jpcw-configs init suitescript --pm yarn
jpcw-configs init react --pm bun
jpcw-configs init react-native --pm yarn| Flag | Default | Behavior |
|---|---|---|
| --pm <yarn\|npm\|bun> | yarn | Pins packageManager in package.json and drops in PM-specific files (e.g. .yarnrc.yml). |
| --force | off | Overwrites an existing .gitignore. Other config files are skipped if they already exist (delete first to re-scaffold). |
| --latest | off | After scaffolding, runs upgrade <preset> so the toolchain installs at current versions instead of the template pins. |
jpcw-configs doctor [preset]
The drift check init isn't: audits an existing repo against its preset and reports findings — missing scaffold files, package.json gaps (scripts, deps, packageManager, private, lint-staged), legacy .eslintrc*/.prettierrc* files shadowing the flat config, a TS 7 install that breaks the toolchain, or an @jpcw/configs pin a major behind. The preset is auto-detected from the repo's configs; pass it only if detection fails.
jpcw-configs doctor # report
jpcw-configs doctor --fix # apply the additive fixes
jpcw-configs doctor --json # machine-readable, for scripting across repos--fix is additive only — it copies missing files and fills package.json gaps, and never overwrites or deletes anything (legacy configs are reported for manual cleanup). Exits non-zero while warnings/errors remain, so it works as a CI or fleet-script gate. Typical fleet sweep: run doctor --fix in each repo after bumping @jpcw/configs.
| Flag | Default | Behavior |
|---|---|---|
| --fix | off | Apply fixable findings (copy missing files, merge package.json gaps) |
| --json | off | JSON findings instead of human output |
| --pm <yarn\|npm\|bun> | auto | Package manager used when filling packageManager |
jpcw-configs upgrade <preset>
The template package.json ships reproducible pinned versions — good for a deterministic scaffold, but they go stale. upgrade resolves the managed dependency set to whatever is current at the moment you run it, by delegating to your package manager. Run it the day you scaffold, or a year later on an old repo — it picks up whatever is newest then, with no template re-pinning required.
jpcw-configs upgrade suitescript # bump the toolchain to latest
jpcw-configs upgrade react --include-frameworks # also bump react/vite
jpcw-configs upgrade react-native --include-frameworks --dry-runThe managed set is read straight from the preset template, so it tracks the preset automatically. Deps are split:
- Toolchain (eslint, prettier, typescript,
@jpcw/configs,@types/*, vite, …) — always taken at@latest. The preset is built around current tooling. Exception:typescriptis capped at 6.x — TS 7 (the native compiler) drops the JS compiler API (watch, type-aware linting) and AMD emit (the suitescript output format), so moving to it is a deliberate migration, not an upgrade. - Framework / native (react, react-dom, expo, expo-router, react-native, …) — version-sensitive, so skipped unless
--include-frameworks. For thereact-nativepreset these never go to npm-latest (that breaks the Expo SDK); they're realigned vianpx expo install expo@latest && npx expo install --fix, which keeps them SDK-correct.
| Flag | Default | Behavior |
|---|---|---|
| --pm <yarn\|npm\|bun> | auto | Detected from packageManager / lockfile if omitted. |
| --include-frameworks | off | Also upgrade framework/native deps (Expo via expo install). |
| --dry-run | off | Print the package-manager commands without running them. |
jpcw-configs write-project-json --auth <id>
Writes a SuiteCloud SDK project.json (pretty-printed), so switching the target NetSuite account is a one-line package.json script instead of a hand-maintained helper file.
jpcw-configs write-project-json --auth MY_SDF_AUTH_ID| Flag | Default | Meaning |
|---|---|---|
| --auth | required | SDF auth ID for the target NetSuite account |
| --asv | ERROR | accountSpecificValues strategy (ERROR or WARNING) |
| --out | project.json | output path, relative to cwd |
jpcw-configs ns-upload [files...]
Uploads one or more File Cabinet files to the active NetSuite account (the project.json found at or above cwd) via suitecloud file:upload — so a multi-file selection uploads in one keystroke, where the IDE plugins only handle a single file. Selecting either half of a .ts/.js pair uploads both. Files must live under src/FileCabinet.
jpcw-configs ns-upload src/FileCabinet/SuiteScripts/jpcw/lib/a.lib.ts
jpcw-configs ns-upload --from-clipboard # VS Code multi-select hookUploads go to whatever account project.json targets. The banner (and the --notify toast) names the account, flagged PRODUCTION when the auth ID doesn't look like a dev/sandbox one (sb / sand / tstdrv / dev).
VS Code multi-select in one keystroke: add a user-level task (Tasks: Open User Tasks) and keybinding, and it works in every SDF repo with no per-repo files:
// User Tasks — reveal: silent keeps focus in the editor (terminal only pops
// on failure); --notify toasts when the upload finishes instead
{ "label": "NS: Upload selected", "type": "shell",
"command": "npx jpcw-configs ns-upload --from-clipboard --notify",
"presentation": { "reveal": "silent", "focus": false, "panel": "shared", "clear": true } }// keybindings.json — copies ALL selected explorer paths, then runs the task
{ "key": "shift+alt+y", "command": "runCommands",
"args": { "commands": ["copyFilePath",
{ "command": "workbench.action.tasks.runTask", "args": "NS: Upload selected" }] },
"when": "filesExplorerFocus" }| Flag | Default | Behavior |
|---|---|---|
| --from-clipboard | off | Read newline-separated paths from the clipboard (accepts WSL, relative, or \\wsl.localhost\... UNC paths) |
| --dry-run | off | Print the suitecloud command without running it |
| --notify | off | Desktop notification (Windows toast from WSL, else notify-send) when the upload finishes or fails |
jpcw-configs watch
Incremental TypeScript watch mode (type-check + emit, same as tsc --watch) with delta-aware reporting. tsc --watch already re-checks only changed files internally, but it re-prints every error in the project on every pass; watch drives the same incremental compiler via the API and reports the diff instead:
── 9:02:16 AM ── recompiling… ──────────────────────────
a.ts — fixed (was 1) ✔
src/a.ts
b.ts — 1 error
src/b.ts
src/b.ts:3:7 TS2322: Type 'number' is not assignable to type 'string'.
Watching… ✖ 1 error in 1 file (▼1)- Every pass ends with the current state on the last line:
Watching… ✖ 13 errors in 5 files (▼1)(▲in red when the count went up). Passes are separated by a timestamped divider. - Full diagnostics only for files whose error set changed — whether saved directly or broken/fixed as a ripple effect of a change elsewhere (tagged
[affected by your change]). Each error prints aspath:line:col, which IDE terminals hyperlink straight to the error. - Files that go clean get a one-time
fixed ✔line; files whose errors didn't change aren't listed at all (the summary line carries the totals). - On startup, per-file error counts only — details print as files change.
Uses the project's own typescript install, so it always agrees with the version the repo type-checks against.
| flag | default | |
| --- | --- | --- |
| -p, --project | nearest tsconfig.json | tsconfig to use |
| --no-color | | disable ANSI colors (also honors NO_COLOR) |
jpcw-configs setup-watch
Sets up incremental TypeScript watch-mode compilation for the project — a long-running jpcw-configs watch (see above) that only recompiles what changed, instead of a per-save IDE File Watcher spawning a full non-incremental compile every time. Run from the project root. Idempotent:
- Adds
"watch": "jpcw-configs watch"topackage.jsonscripts. Awatchscript that still holds the old default (tsc --watch) is upgraded in place; anything else is left alone with a warning. - Writes
.vscode/tasks.json— a watch task (type: npm,$tsc-watchproblem matcher) with"runOptions": { "runOn": "folderOpen" }, so the watcher starts when the project opens. Committed to git, so it's available to everyone who opens the project. If the file already exists, it's left alone and the task JSON is printed for you to add manually.
npx @jpcw/configs setup-watchOne manual step (per repo, per machine): VS Code won't auto-run workspace tasks until you allow it — run Tasks: Manage Automatic Tasks → Allow Automatic Tasks, or just accept the prompt the first time the folder opens.
Versioning
Semver. Breaking changes to any preset (rule removals, severity bumps) are major; new rules / new lints / new CLI flags are minor. Per-version notes live in CHANGELOG.md; maintainer notes in MAINTAINING.md.
