@jpcw/configs
v2.8.1
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 every repo on the same toolchain, and run a quieter tsc --watch.
In honor and memory of Forrest Jones. C# > TS.
Every TypeScript repo carries the same half-dozen config files, and they drift the moment they're copy-pasted. Here, your repo takes one dev dependency plus a handful of one-line files that extend a preset. Update the package, run fleet --fix, and every repo follows.
Contents: Quick start · Presets · Scaffolded files · CLI: init / doctor / fleet / upgrade / format / watch / setup-watch / NetSuite commands · Migrating an existing repo · Versioning
Quick start
mkdir my-project && cd my-project
git init
npx -p @jpcw/configs jpcw-configs init suitescript --pm yarn # or react | react-native
yarn install # match your --pmThat's a complete setup: configs, VS Code workspace, CI, pre-commit hook, Claude Code notes, watch task. init is idempotent — re-running only adds what's missing.
After scaffolding:
- suitescript — replace
PROJECT_NAMEandREPLACE_WITH_SDF_AUTH_ID_*inpackage.json. For full strict mode, point tsconfigextendsat@jpcw/configs/suitescript/tsconfig.strict.json. - react-native — assumes Expo + Expo Router; on bare RN, drop
expo-routerand adjustmain. - VS Code asks once to allow automatic tasks (the watch task) — accept it.
Presets
| Preset | Target | Notes |
|---|---|---|
| suitescript | NetSuite SuiteScript 2.x | AMD/ES2021 emit, CRLF, NetSuite-specific lints (below) |
| react | React web | Vite, ES2022, JSX, LF, jsx-a11y |
| react-native | React Native / Expo | no DOM lib, __DEV__ global, jsx: react-native |
The suitescript preset lints for the actual NetSuite runtime, catching deploy-time failures at lint time:
- Node.js built-in imports are errors — they don't exist in the NetSuite runtime.
- Files under
SuiteScripts/require the@NApiVersion/@NScriptTypeJSDoc tags; miscased tags (@NAPIVersion) break NetSuite's loader and are auto-corrected byeslint --fix. - Entry-point files (
.ue.ts,.mr.ts,.sch.ts, …) must declare the@NScriptTypematching their suffix and export that type's entry-point function(s). - A separate
tsconfig.spa.jsoncovers React SPAs bundled into a Suitelet (src/spa/).
Scaffolded files
| File | Purpose |
|---|---|
| tsconfig.json | Extends the preset; project paths/include filled in |
| eslint.config.mjs, prettier.config.mjs | One-line re-exports of the preset |
| package.json | Pinned deps; lint, format (compiled output excluded), typecheck, watch, knip scripts (+ sb/prod/ns:* for suitescript); lint-staged + hook-wiring postinstall |
| .editorconfig, .gitattributes | Tabs + line endings pinned (CRLF for suitescript, LF otherwise), independent of each machine's autocrlf |
| .gitignore, .prettierignore | Per-preset ignores |
| .vscode/settings.json | Format-on-save, ESLint flat-config mode, search/watcher excludes for build output (editor-only — no effect on git or uploads) |
| .vscode/extensions.json | Recommended extensions: ESLint, Prettier, EditorConfig, Claude Code, per-preset extras |
| .vscode/tasks.json | The watch task, auto-started on folder open |
| knip.json | Dead-code audit (yarn knip) with per-preset entry points — for suitescript the report reads as "code nothing deployed can reach" |
| .githooks/pre-commit | lint-staged on staged files only |
| .github/workflows/ci.yml | Thin caller of this repo's central reusable-ci.yml (lint + typecheck + format on push/PR) |
| CLAUDE.md, .claude/settings.json | Per-preset Claude Code notes + permission allowlist |
| tsconfig.spa.json | suitescript only — Suitelet-served React SPA build |
| .yarnrc.yml | Yarn only — nodeLinker: node-modules (SDF tooling needs it) + npmPreapprovedPackages for @jpcw/* |
CLI
| Command | What it does |
|---|---|
| init <preset> | Scaffold a project (idempotent) |
| doctor [preset] | Audit one repo for drift; --fix repairs additively |
| fleet | doctor across every repo under a directory |
| upgrade <preset> | Bump the managed toolchain to current versions |
| format | Prettier, minus compiled output |
| watch | Incremental tsc --watch with delta-only output |
| setup-watch | Add the watch script + VS Code task to a repo |
| write-project-json | Switch the target NetSuite account |
| ns-whereami | Print which NetSuite account is active |
| ns-upload | Upload File Cabinet files (multi-select capable) |
jpcw-configs init <preset>
Scaffolds a project with the preset's config files (full list). Idempotent — re-running only adds what's missing.
jpcw-configs init suitescript --pm yarn| Flag | Default | Behavior |
|---|---|---|
| --pm <yarn\|npm\|bun> | yarn | Pins packageManager and drops in PM-specific files (e.g. .yarnrc.yml) |
| --force | off | Overwrite an existing .gitignore (other existing files are always skipped) |
| --latest | off | Follow up with upgrade <preset> so deps install at current versions, not the template pins |
jpcw-configs doctor [preset]
Audits an existing repo against its preset: missing scaffold files, package.json gaps, legacy .eslintrc*/.prettierrc* shadowing the flat config, a TS 7 install that breaks the toolchain, an @jpcw/configs pin a major behind. Preset is auto-detected; pass it only if detection fails.
jpcw-configs doctor # report
jpcw-configs doctor --fix # apply the additive fixes--fix is additive only — copies missing files, fills package.json gaps, never overwrites or deletes (legacy configs are reported for manual cleanup). Exits non-zero while warnings/errors remain, so it works as a CI gate.
| Flag | Default | Behavior |
|---|---|---|
| --fix | off | Apply fixable findings |
| --json | off | JSON findings instead of human output |
| --pm <yarn\|npm\|bun> | auto | Package manager used when filling packageManager |
jpcw-configs fleet
doctor, across every repo at once: scans below cwd (or --root) for directories whose package.json declares @jpcw/configs, audits each, and prints one status line per repo with findings indented. Discovery recurses a few levels (grouping folders like ~/projects/<client>/<repo> work) but never descends into a matched repo, node_modules, or hidden directories. A preset improvement lands in all client repos with one command.
cd ~/projects && jpcw-configs fleet # who's drifted?
cd ~/projects && jpcw-configs fleet --fix # repair everywhere (doctor's additive-only guarantee)| Flag | Default | Behavior |
|---|---|---|
| --fix | off | Apply doctor's fixable findings in each repo |
| --root <dir> | cwd | Directory whose children are scanned |
jpcw-configs upgrade <preset>
The template pins are reproducible but go stale. upgrade resolves the managed dep set (read from the preset template, so it tracks automatically) to whatever is current the moment you run it, via your package manager.
jpcw-configs upgrade suitescript
jpcw-configs upgrade react-native --include-frameworks --dry-run- Toolchain (eslint, prettier, typescript,
@types/*, vite, …) — always taken at@latest. Exception: typescript is capped at 6.x — TS 7's native compiler drops the JS compiler API (watch, type-aware linting) and AMD emit, so that move is a deliberate migration. - Framework / native (react, expo, react-native, …) — skipped unless
--include-frameworks. Expo deps go throughexpo installso they stay SDK-correct, never npm-latest.
| Flag | Default | Behavior |
|---|---|---|
| --pm <yarn\|npm\|bun> | auto | Detected from packageManager / lockfile |
| --include-frameworks | off | Also upgrade framework/native deps |
| --dry-run | off | Print the commands without running them |
jpcw-configs format
Prettier over the project, skipping compiled output. Backs the format /
format:write scripts and the lint-staged entry in the suitescript preset.
The suitescript tsconfig has no outDir — tsc emits foo.js next to foo.ts
under src/FileCabinet, and both halves are committed because SDF deploys the
.js. Letting Prettier reformat that output means every build rewrites it back
in tsc's style, so a plain tsc dirties the whole File Cabinet with diffs that
change nothing. Branches look like they have work in them when all that
happened was a rebuild.
A blanket **/*.js exclusion would be wrong: SuiteScript 1.0 is JS-only, and
plenty of early 2.x scripts predate the TypeScript move. Those are real sources
and must keep getting formatted. What separates them is a sibling .ts — the
same rule ns-upload uses to upload both
halves of a pair — so the exclusion is computed per file, not globbed.
jpcw-configs format # check (CI does this)
jpcw-configs format --write # rewrite
jpcw-configs format --list # which files get skipped, without running prettierThe suitescript ESLint preset applies the same rule to its ignores, so
compiled output isn't linted or --fix'd either. The editor is the one place
this rule can't reach — the Prettier extension reads only .prettierignore —
so the scaffolded .vscode/settings.json turns format-on-save off for .js
and leaves it to the commit hook. doctor flags repos that haven't done that.
Adopting this on an existing repo produces one settling commit: the files
that were being reformatted revert to tsc's output the next time you build.
After that the File Cabinet only shows up in git status when a script
genuinely changed.
Across a fleet, doctor finds the repos still on whole-tree Prettier and
fleet --fix migrates them all at once:
cd ~/projects && jpcw-configs fleet # who's still churning?
cd ~/projects && jpcw-configs fleet --fix # migrate every repoThen, per repo: rebuild (yarn watch once, or tsc) and commit the revert.
jpcw-configs watch
Incremental TypeScript watch (type-check + emit, same compiler as tsc --watch) that reports the diff instead of re-printing every error each pass:
── 9:02:16 AM ── recompiling… ──────────────────────────
a.ts — fixed (was 1) ✔
b.ts — 1 error
src/b.ts:3:7 TS2322: Type 'number' is not assignable to type 'string'.
Watching… ✖ 1 error in 1 file (▼1)- Last line always carries the current state:
✖ 13 errors in 5 files (▼1)(▲in red when the count rose). - Full diagnostics only for files whose error set changed — including ripple effects, tagged
[affected by your change]. Errors print aspath:line:colfor IDE hyperlinking. - Uses the repo's own
typescriptinstall, so it always agrees withyarn typecheck.
| Flag | Default | Behavior |
|---|---|---|
| -p, --project <path> | nearest tsconfig.json | tsconfig to use |
| --no-color | off | Disable ANSI colors (also honors NO_COLOR) |
jpcw-configs setup-watch
Adds the watch setup to an existing repo (new repos get it from init): the "watch": "jpcw-configs watch" script and the .vscode/tasks.json task that auto-starts on folder open. Idempotent; an existing tasks.json is left alone with instructions printed.
One manual step per repo/machine: allow automatic tasks when VS Code prompts (or Tasks: Manage Automatic Tasks → Allow).
NetSuite commands
jpcw-configs write-project-json --auth <id>
Writes a SuiteCloud project.json, making the account switch a one-line package.json script (the scaffolded sb / prod). Prints the account banner after writing, so a switch to PRODUCTION is never silent.
| Flag | Default | Meaning |
|---|---|---|
| --auth | required | SDF auth ID for the target account |
| --asv | ERROR | accountSpecificValues (ERROR or WARNING) |
| --out | project.json | Output path |
jpcw-configs ns-whereami
Prints the NetSuite account the nearest project.json targets, with PRODUCTION flagged loudly (an auth ID counts as non-prod when it contains sb / sand / tstdrv / dev). Run it before a batch of IDE uploads; scaffolded as yarn ns:whereami.
jpcw-configs ns-upload [files...]
Uploads File Cabinet files to the active account via suitecloud file:upload — a multi-file selection in one keystroke, where the IDE plugins handle one file at a time. Selecting either half of a .ts/.js pair uploads both. Files must live under src/FileCabinet. The banner (and --notify toast) names the target account, PRODUCTION flagged.
jpcw-configs ns-upload src/FileCabinet/SuiteScripts/jpcw/lib/a.lib.ts
jpcw-configs ns-upload --from-clipboard # VS Code multi-select hook| Flag | Default | Behavior |
|---|---|---|
| --from-clipboard | off | Read newline-separated paths from the clipboard (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) on finish/fail |
VS Code multi-select in one keystroke — a user-level task (Tasks: Open User Tasks) plus keybinding works in every SDF repo with no per-repo files:
// User Tasks — reveal: silent keeps focus in the editor; --notify toasts on finish
{ "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" }Migrating an existing repo
init is safe on a non-empty repo — it only adds what's missing.
yarn add -D @jpcw/configs && npx jpcw-configs init suitescript(orreact/react-native). Older repo? Follow withupgrade, or useinit --latest.initskipped anytsconfig.json/eslint.config.mjs/prettier.config.mjsyou already had. Delete yours and re-run, or pointextendsat the preset (e.g."@jpcw/configs/suitescript/tsconfig.json").- Delete legacy
.eslintrc*/.prettierrc*files — ESLint and Prettier pick those up before the preset otherwise (doctorflags them). - Verify:
npx jpcw-configs doctor && yarn typecheck && yarn lint && yarn format. - Suitescript repos:
doctor --fixmovesformatoff whole-tree Prettier (seeformat). Rebuild and commit the one-time revert of the compiled output.
Versioning
Semver. Breaking preset changes (rule removals, severity bumps) are major; new rules, lints, and CLI flags are minor. Per-version notes in CHANGELOG.md; maintainer notes in MAINTAINING.md.
