{"id":30325711,"url":"https://github.com/stacksjs/pickier","last_synced_at":"2025-08-17T23:07:12.161Z","repository":{"id":309011451,"uuid":"1015478535","full_name":"stacksjs/pickier","owner":"stacksjs","description":"Performant Linter \u0026 Formatter.","archived":false,"fork":false,"pushed_at":"2025-08-16T10:10:04.000Z","size":591,"stargazers_count":4,"open_issues_count":6,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-16T10:11:49.940Z","etag":null,"topics":["css","formatter","html","javascript","json","jsonc","linter","md","performant","stx","typescript","yaml","yml"],"latest_commit_sha":null,"homepage":"https://pickier.netlify.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stacksjs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["stacksjs","chrisbbreuer"],"open_collective":"stacksjs"}},"created_at":"2025-07-07T15:02:43.000Z","updated_at":"2025-08-14T18:55:48.000Z","dependencies_parsed_at":"2025-08-09T09:13:08.183Z","dependency_job_id":"4a2d3a10-234b-49a1-9c66-a9b87b2740f4","html_url":"https://github.com/stacksjs/pickier","commit_stats":null,"previous_names":["stacksjs/pickier"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/stacksjs/pickier","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacksjs%2Fpickier","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacksjs%2Fpickier/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacksjs%2Fpickier/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacksjs%2Fpickier/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stacksjs","download_url":"https://codeload.github.com/stacksjs/pickier/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacksjs%2Fpickier/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270918404,"owners_count":24667679,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-17T02:00:09.016Z","response_time":129,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["css","formatter","html","javascript","json","jsonc","linter","md","performant","stx","typescript","yaml","yml"],"created_at":"2025-08-17T23:07:11.620Z","updated_at":"2025-08-17T23:07:12.148Z","avatar_url":"https://github.com/stacksjs.png","language":"TypeScript","funding_links":["https://github.com/sponsors/stacksjs","https://github.com/sponsors/chrisbbreuer","https://opencollective.com/stacksjs"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\u003cimg src=\".github/art/cover.jpg\" alt=\"Social Card of this repo\"\u003e\u003c/p\u003e\n\n[![npm version][npm-version-src]][npm-version-href]\n[![GitHub Actions][github-actions-src]][github-actions-href]\n[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)\n\u003c!-- [![npm downloads][npm-downloads-src]][npm-downloads-href] --\u003e\n\u003c!-- [![Codecov][codecov-src]][codecov-href] --\u003e\n\n# Pickier\n\n\u003e Fast linting and formatting. Minimal defaults. Extensible. Built for speed.\n\n## Features\n\n- Fast CLI with instant feedback\n- Lint and format in one tool\n- Zero-config defaults; simple, typed `pickier.config.ts` when you need it\n- Import organization: splits type/value imports, sorts modules/specifiers, removes unused named imports\n- JSON and config sorting for common files _(e.g. `package.json`, `tsconfig.json`)_\n- Flexible formatting: `indent`, `indentStyle` _(tabs or spaces)_, `quotes`, `semi`, `trimTrailingWhitespace`, `maxConsecutiveBlankLines`, `finalNewline`\n- Smart whitespace cleanup\n- ESLint-style plugin system for lint rules _(load plugins, enable/disable rules, WIP labeling)_\n- CI-friendly reporters _(stylish, compact, JSON)_ and strict `--max-warnings` control\n- Programmatic API for custom tooling and editor integrations\n\n## Install\n\n```bash\n# as a dev dependency\nbun add -D pickier\n# or\nnpm i -D pickier\n# or\npnpm add -D pickier\n# or\nyarn add -D pickier\n```\n\nYou can also run it directly via npx without installing:\n\n```bash\nnpx pickier --help\n# or\nbunx pickier --help\n```\n\n## Quick start\n\n```bash\n# Lint everything, pretty output\npickier lint .\n\n# Auto-fix issues (safe fixes only)\npickier lint . --fix\n\n# Preview fixes without writing\npickier lint . --fix --dry-run --verbose\n\n# Format and write changes\npickier format . --write\n\n# Check formatting without writing (CI-friendly)\npickier format . --check\n```\n\n## CLI\n\n- `pickier lint [...globs]`\n  - `--fix`: apply safe fixes (e.g. remove `debugger` statements)\n  - `--dry-run`: simulate fixes without writing\n  - `--max-warnings \u003cn\u003e`: fail if warnings exceed n (default: -1)\n  - `--reporter \u003cstylish|json|compact\u003e`: output format (default: stylish)\n  - `--ext \u003c.ts,.tsx,.js,...\u003e`: comma-separated extensions (overrides config)\n  - `--ignore-path \u003cfile\u003e`: optional ignore file (e.g. .gitignore)\n  - `--cache`: reserved (no-op currently)\n  - `--verbose`\n  - Examples:\n    - `pickier lint . --dry-run`\n    - `pickier lint src --fix`\n    - `pickier lint \"src/**/*.{ts,tsx}\" --reporter json`\n\n- `pickier format [...globs]`\n  - `--write`: write formatted files\n  - `--check`: only check, non-zero exit on differences\n  - `--ext \u003c.ts,.tsx,.js,.json,...\u003e`\n  - `--ignore-path \u003cfile\u003e`\n  - `--verbose`\n  - Examples:\n    - `pickier format . --check`\n    - `pickier format src --write`\n    - `pickier format \"**/*.{ts,tsx,js}\" --write`\n\n## Configuration\n\nPickier works out-of-the-box. To customize, create `pickier.config.ts` in your project root. All fields are optional.\n\n```ts\n// pickier.config.ts\nimport type { PickierConfig } from 'pickier'\n\nconst config: PickierConfig = {\n  verbose: false,\n  ignores: ['**/node_modules/**', '**/dist/**', '**/build/**'],\n\n  lint: {\n    // which extensions to lint ('.ts' or 'ts' both supported)\n    extensions: ['ts', 'js'],\n    // stylish | json | compact\n    reporter: 'stylish',\n    // reserved (not used yet)\n    cache: false,\n    // -1 disables, otherwise fail when warnings \u003e maxWarnings\n    maxWarnings: -1,\n  },\n\n  format: {\n    // which extensions to format\n    extensions: ['ts', 'js', 'json', 'md', 'yaml', 'yml'],\n    trimTrailingWhitespace: true,\n    maxConsecutiveBlankLines: 1,\n    // one | two | none\n    finalNewline: 'one',\n    // 2-space indentation (code files)\n    indent: 2,\n    // preferred string quotes in code files: 'single' | 'double'\n    quotes: 'single',\n    // when true, safely remove stylistic semicolons\n    // (never touches for(;;) headers; removes duplicate/empty semicolon statements)\n    semi: false,\n  },\n\n  rules: {\n    // 'off' | 'warn' | 'error'\n    noDebugger: 'error',\n    noConsole: 'warn',\n  },\n}\n\nexport default config\n```\n\n### Plugin system (rules)\n\nPickier supports an ESLint-style plugin system for lint rules. You can load plugins, configure their rules per severity, and mark experimental rules as WIP to surface errors early.\n\nConcepts:\n- Plugin: `{ name: string, rules: Record\u003cstring, RuleModule\u003e }`\n- RuleModule: `{ meta?: { docs?: string; recommended?: boolean; wip?: boolean }, check(content, context) =\u003e LintIssue[] }`\n- Configure rules via `pluginRules: { 'pluginName/ruleId': 'off' | 'warn' | 'error' | ['warn', options] }`\n\nDefine a plugin (example):\n\n```ts\n// sample-plugin.ts\nimport type { PickierPlugin, RuleContext } from 'pickier'\n\nexport const samplePlugin: PickierPlugin = {\n  name: 'sample',\n  rules: {\n    'no-todo': {\n      meta: { docs: 'disallow TODO comments', recommended: true },\n      check(content: string, ctx: RuleContext) {\n        const issues = []\n        const lines = content.split(/\\r?\\n/)\n        for (let i = 0; i \u003c lines.length; i++) {\n          const col = lines[i].indexOf('TODO')\n          if (col !== -1) {\n            issues.push({\n              filePath: ctx.filePath,\n              line: i + 1,\n              column: col + 1,\n              ruleId: 'sample/no-todo',\n              message: 'Unexpected TODO comment.',\n              severity: 'warning',\n            })\n          }\n        }\n        return issues\n      },\n    },\n    'experimental-check': {\n      meta: { wip: true },\n      check() {\n        // not implemented yet\n        throw new Error('WIP rule')\n      },\n    },\n  },\n}\n```\n\nUse the plugin in config:\n\n```ts\n// pickier.config.ts\nimport type { PickierConfig } from 'pickier'\nimport { samplePlugin } from './sample-plugin'\n\nconst config: PickierConfig = {\n  verbose: false,\n  ignores: ['**/node_modules/**'],\n  lint: { extensions: ['ts', 'js'], reporter: 'stylish', cache: false, maxWarnings: -1 },\n  format: { extensions: ['ts', 'js', 'json'], trimTrailingWhitespace: true, maxConsecutiveBlankLines: 1, finalNewline: 'one', indent: 2, quotes: 'single', semi: false },\n  rules: { noDebugger: 'error', noConsole: 'warn' },\n  // Register plugins (currently supports in-memory objects)\n  plugins: [samplePlugin],\n  // Enable/disable rules and pass options\n  pluginRules: {\n    'sample/no-todo': 'warn',\n    // WIP rules that throw will surface as errors with a :wip-error suffix\n    'sample/experimental-check': 'error',\n  },\n}\n\nexport default config\n```\n\nCLI example:\n\n```bash\npickier lint src --reporter json\n# If a WIP rule throws, you will see an error like:\n# {\n#   \"ruleId\": \"sample/experimental-check:wip-error\",\n#   \"message\": \"Rule sample/experimental-check is marked as WIP and threw: ...\",\n#   ...\n# }\n```\n\n### Formatting details\n\n- Semicolons\n  - Controlled by `format.semi` (default `false`). When `true`, Pickier removes only stylistic semicolons safely:\n    - preserves `for (init; test; update)` headers\n    - removes duplicate trailing semicolons (e.g. `foo();;` → `foo();`)\n    - removes lines that are just empty statements (`;`)\n    - keeps normal end-of-line semicolons otherwise (non-destructive)\n\n- Imports (TypeScript/JavaScript)\n  - Groups and rewrites the top import block:\n    - Splits type-only specifiers into `import type { ... } from 'x'`\n    - Keeps default and namespace imports\n    - Removes unused named specifiers only when they have no alias\n    - Merges multiple imports from the same module\n  - Sorting\n    - Order by kind: type imports, side-effect imports, value imports\n    - For modules: external before relative\n    - For specifiers: A→Z by identifier; minor normalization for consistent ordering\n  - Spacing/newlines\n    - Ensures a single blank line between the import block and the rest of the file\n    - Respects `format.finalNewline` at EOF\n\nNotes:\n\n- `noDebugger` removes lines that are debugger statements when `--fix` is used.\n- `noConsole` controls severity (turn off for libraries that allow console logs).\n\n## Development\n\nThis repository contains Pickier’s source under `packages/pickier`.\n\nCommon tasks:\n\n```bash\n# install deps\nbun i\n\n# run tests (with coverage)\nbun test --coverage\n\n# build JS and type declarations\nbun run -C packages/pickier build\n\n# compile native binary for your platform\nbun run -C packages/pickier compile\n\n# compile all platform binaries\nbun run -C packages/pickier compile:all\n```\n\nTry the CLI locally without publishing:\n\n```bash\n# run the TS entry directly\nbun packages/pickier/bin/cli.ts --help\n\n# run the built dist CLI\nbun packages/pickier/dist/bin/cli.js lint .\n\n# or the compiled native binary (after compile)\n./packages/pickier/bin/pickier-\u003cyour-platform\u003e --help\n```\n\n## Programmatic usage\n\nYou can also call Pickier from code (Bun/Node). Useful for custom tooling, editors, or pipelines.\n\n```ts\nimport type { FormatOptions, LintOptions } from 'pickier'\n// example.ts\nimport { pickierConfig, runFormat, runLint } from 'pickier'\n\n// Lint some directories\nconst lintOptions: LintOptions = {\n  fix: true, // apply safe fixes\n  dryRun: false, // set true to simulate fixes\n  reporter: 'json', // 'stylish' | 'json' | 'compact'\n  maxWarnings: 0, // fail on any warning\n}\n\nconst lintCode = await runLint(['src', 'tests'], lintOptions)\nconsole.log('lint exit code:', lintCode)\n\n// Format some globs\nconst formatOptions: FormatOptions = {\n  write: true, // write changes\n}\n\nconst fmtCode = await runFormat(['src/**/*.ts'], formatOptions)\nconsole.log('format exit code:', fmtCode)\n\n// Access loaded config (from pickier.config.ts or defaults)\nconsole.log('loaded config:', pickierConfig)\n```\n\nRun it with Bun:\n\n```bash\nbun example.ts\n```\n\n## Testing\n\n```bash\nbun test\n```\n\n## Changelog\n\nPlease see our [releases](https://github.com/stacksjs/stacks/releases) page for more information on what has changed recently.\n\n## Contributing\n\nPlease review the [Contributing Guide](https://github.com/stacksjs/contributing) for details.\n\n## Community\n\nFor help, discussion about best practices, or any other conversation that would benefit from being searchable:\n\n[Discussions on GitHub](https://github.com/stacksjs/stacks/discussions)\n\nFor casual chit-chat with others using this package:\n\n[Join the Stacks Discord Server](https://discord.gg/stacksjs)\n\n## Postcardware\n\n“Software that is free, but hopes for a postcard.” We love receiving postcards from around the world showing where `pickier` is being used! We showcase them on our website too.\n\nOur address: Stacks.js, 12665 Village Ln #2306, Playa Vista, CA 90094, United States 🌎\n\n## Sponsors\n\nWe would like to extend our thanks to the following sponsors for funding Stacks development. If you are interested in becoming a sponsor, please reach out to us.\n\n- [JetBrains](https://www.jetbrains.com/)\n- [The Solana Foundation](https://solana.com/)\n\n## Credits\n\n- [Chris Breuer](https://github.com/chrisbbreuer)\n- [All Contributors](../../contributors)\n\n## License\n\nThe MIT License (MIT). Please see [LICENSE](https://github.com/stacksjs/pickier/tree/main/LICENSE.md) for more information.\n\nMade with 💙\n\n\u003c!-- Badges --\u003e\n[npm-version-src]: https://img.shields.io/npm/v/pickier?style=flat-square\n[npm-version-href]: https://npmjs.com/package/pickier\n[github-actions-src]: https://img.shields.io/github/actions/workflow/status/stacksjs/pickier/ci.yml?style=flat-square\u0026branch=main\n[github-actions-href]: https://github.com/stacksjs/pickier/actions?query=workflow%3Aci\n\n\u003c!-- [codecov-src]: https://img.shields.io/codecov/c/gh/stacksjs/pickier/main?style=flat-square\n[codecov-href]: https://codecov.io/gh/stacksjs/pickier --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstacksjs%2Fpickier","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstacksjs%2Fpickier","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstacksjs%2Fpickier/lists"}