@rsuvarna/pkgsort 0.1.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ Once releases begin, this file is generated from
9
+ [Changesets](https://github.com/changesets/changesets); do not edit released
10
+ sections by hand.
11
+
12
+ ## [Unreleased]
13
+
14
+ ### Added
15
+
16
+ - Repository foundation: documentation (PRD, Architecture, Roadmap, Config
17
+ reference), README, and community health files (Contributing, Code of
18
+ Conduct, License).
19
+ - Tooling baseline: TypeScript (strict), ESLint (typed) + Prettier, Vitest with
20
+ coverage thresholds.
21
+ - Continuous integration: lint/typecheck/format gates and a test matrix across
22
+ Node 22 and 24 on Linux, macOS, and Windows, plus package-contents
23
+ verification.
24
+
25
+ _No formatter functionality has shipped yet. See the
26
+ [Roadmap](./docs/ROADMAP.md) for the plan from `0.1` to `1.0`._
27
+
28
+ [Unreleased]: https://github.com/pkgsort/pkgsort/commits/main
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pkgsort contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,166 @@
1
+ <div align="center">
2
+
3
+ # pkgsort
4
+
5
+ **The opinionated, deterministic `package.json` formatter for professional JavaScript & TypeScript teams.**
6
+
7
+ [![CI](https://github.com/pkgsort/pkgsort/actions/workflows/ci.yml/badge.svg)](https://github.com/pkgsort/pkgsort/actions/workflows/ci.yml)
8
+ [![npm version](https://img.shields.io/npm/v/pkgsort.svg)](https://www.npmjs.com/package/pkgsort)
9
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
10
+ [![Node](https://img.shields.io/node/v/pkgsort.svg)](https://nodejs.org)
11
+
12
+ </div>
13
+
14
+ > [!NOTE]
15
+ > **Status: pre-release.** The repository foundation is in place; the formatter
16
+ > itself is under active development. See the [Roadmap](./docs/ROADMAP.md).
17
+
18
+ ---
19
+
20
+ ## Why pkgsort?
21
+
22
+ Every JavaScript project has a `package.json`, and every team eventually argues
23
+ about it: what order should the keys go in? Should `dependencies` be sorted?
24
+ Why does this field have a trailing comma and that one doesn't? Why did the diff
25
+ touch 40 lines when someone ran `npm install`?
26
+
27
+ [`sort-package-json`](https://github.com/keithamus/sort-package-json) solved
28
+ **key ordering** and did it well. pkgsort is not trying to replace it.
29
+
30
+ **pkgsort solves a different problem: treating `package.json` as a first-class,
31
+ CI-enforced source artifact.** It reads, sorts, and writes with guarantees a
32
+ casual sorter does not make — byte-level determinism, monorepo-wide
33
+ enforcement, and CI-grade reporting — the way Prettier makes source formatting
34
+ boring and conflict-free.
35
+
36
+ | | `sort-package-json` | **pkgsort** |
37
+ | -------------------------------------- | ------------------- | -------------------------------------------- |
38
+ | Sort top-level keys | ✅ | ✅ |
39
+ | Sort dependency maps | ✅ | ✅ |
40
+ | Deterministic, idempotent output | Partial | ✅ (guaranteed & tested) |
41
+ | `--check` mode for CI | ✅ | ✅ (with actionable diffs) |
42
+ | Configurable sort behaviour | Limited | ✅ (opinionated defaults, fully overridable) |
43
+ | Monorepo / workspace awareness | ❌ | ✅ |
44
+ | Structured (JSON) reporter for tooling | ❌ | ✅ |
45
+
46
+ If all you need is key ordering, `sort-package-json` is a fine, lightweight
47
+ choice. If you manage many packages across many repos and want `package.json`
48
+ to be as boring and diff-free as your Prettier-formatted source, pkgsort is
49
+ built for you.
50
+
51
+ ## Product philosophy
52
+
53
+ pkgsort is guided by seven principles. Every feature decision is measured
54
+ against them:
55
+
56
+ 1. **Opinionated by default.** Install it, run it, get a good result with zero
57
+ configuration.
58
+ 2. **Configurable when needed.** Every default can be overridden through a
59
+ documented, validated config file.
60
+ 3. **Deterministic.** The same input always produces byte-identical output on
61
+ every machine, OS, and Node version.
62
+ 4. **Safe.** pkgsort never reorders semantically significant data or drops
63
+ fields it doesn't recognize. Unknown keys are preserved.
64
+ 5. **Fast.** Formatting a `package.json` is instant; formatting a 500-package
65
+ monorepo is measured in milliseconds per file.
66
+ 6. **CI friendly.** A first-class `--check` mode, meaningful exit codes, and a
67
+ machine-readable reporter.
68
+ 7. **Great DX.** Clear diffs, clear errors, clear docs.
69
+
70
+ ## Installation
71
+
72
+ ```sh
73
+ # npm
74
+ npm install --save-dev pkgsort
75
+
76
+ # Yarn
77
+ yarn add --dev pkgsort
78
+
79
+ # pnpm
80
+ pnpm add -D pkgsort
81
+
82
+ # Bun
83
+ bun add --dev pkgsort
84
+ ```
85
+
86
+ Or run it once without installing:
87
+
88
+ ```sh
89
+ npx pkgsort --check
90
+ ```
91
+
92
+ > Requires **Node.js 22 LTS** or newer. pkgsort is package-manager agnostic and
93
+ > works the same under npm, Yarn, pnpm, and Bun.
94
+
95
+ ## Usage
96
+
97
+ ### CLI
98
+
99
+ ```sh
100
+ # Format package.json in the current directory (writes in place).
101
+ pkgsort
102
+
103
+ # Verify formatting without writing — the command CI should run.
104
+ # Exits non-zero if any file would change.
105
+ pkgsort --check
106
+
107
+ # Format an explicit set of files or globs.
108
+ pkgsort "packages/**/package.json"
109
+
110
+ # Preview changes without touching disk.
111
+ pkgsort --check --diff
112
+ ```
113
+
114
+ **Exit codes** (stable contract, safe to script against):
115
+
116
+ | Code | Meaning |
117
+ | ---- | -------------------------------------------------------- |
118
+ | `0` | Success — all files already formatted (or were written). |
119
+ | `1` | In `--check` mode, one or more files are not formatted. |
120
+ | `2` | Usage error (bad flags, no files matched). |
121
+ | `3` | A file could not be parsed or is invalid. |
122
+
123
+ > pkgsort `0.1` ships a **CLI only** — that is the entire public interface. A
124
+ > programmatic/TypeScript API is intentionally not offered yet and may be
125
+ > considered later based on demand (see the [Roadmap](./docs/ROADMAP.md)).
126
+
127
+ ## Configuration
128
+
129
+ pkgsort works with **zero configuration**. When you need to adjust its
130
+ behaviour, add a `pkgsort.config.json` (JSON), `pkgsort.config.jsonc` (JSON with
131
+ comments), or `pkgsort.config.js` (JavaScript) to your project root — or a
132
+ `"pkgsort"` key in your `package.json`:
133
+
134
+ ```json
135
+ {
136
+ "indent": 2,
137
+ "finalNewline": true,
138
+ "sort": {
139
+ "dependencies": "alphabetical"
140
+ }
141
+ }
142
+ ```
143
+
144
+ The complete, documented configuration reference lives in
145
+ [`docs/CONFIG.md`](./docs/CONFIG.md).
146
+
147
+ ## Documentation
148
+
149
+ | Document | Purpose |
150
+ | -------------------------------------- | ----------------------------------------- |
151
+ | [Product Requirements](./docs/PRD.md) | What we are building and for whom. |
152
+ | [Architecture](./docs/ARCHITECTURE.md) | How the pipeline is designed. |
153
+ | [Configuration](./docs/CONFIG.md) | Every option, its default, and rationale. |
154
+ | [Roadmap](./docs/ROADMAP.md) | Milestones from `0.x` to `1.0`. |
155
+ | [Contributing](./CONTRIBUTING.md) | How to set up, test, and submit changes. |
156
+ | [Changelog](./CHANGELOG.md) | Notable changes, per release. |
157
+
158
+ ## Contributing
159
+
160
+ We welcome issues and pull requests. Please read the
161
+ [Contributing Guide](./CONTRIBUTING.md) and our
162
+ [Code of Conduct](./CODE_OF_CONDUCT.md) before you start.
163
+
164
+ ## License
165
+
166
+ [MIT](./LICENSE) © pkgsort contributors
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { ParseError } from './core/parse.js';
5
+ import { formatFile } from './format-file.js';
6
+ /** Exit codes, per `docs/ARCHITECTURE.md` §7. */
7
+ const EXIT_OK = 0;
8
+ const EXIT_USAGE = 2;
9
+ const EXIT_PARSE = 3;
10
+ const HELP_TEXT = `Usage: pkgsort <path-to-package.json>
11
+
12
+ Sort the top-level keys of a package.json into a canonical order, in place.
13
+
14
+ Options:
15
+ -h, --help Print this help and exit.
16
+ -v, --version Print the version number and exit.`;
17
+ /** Read this package's version from its own package.json at runtime. */
18
+ function readVersion() {
19
+ const pkgPath = fileURLToPath(new URL('../package.json', import.meta.url));
20
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
21
+ return pkg.version;
22
+ }
23
+ /**
24
+ * The CLI shell: parse argv, format the target file, and translate the outcome
25
+ * into a process exit code. All formatting logic lives in the pure core; this
26
+ * layer only handles process concerns.
27
+ */
28
+ function main(argv) {
29
+ const arg = argv[2];
30
+ if (arg === '-h' || arg === '--help') {
31
+ process.stdout.write(`${HELP_TEXT}\n`);
32
+ return EXIT_OK;
33
+ }
34
+ if (arg === '-v' || arg === '--version') {
35
+ process.stdout.write(`${readVersion()}\n`);
36
+ return EXIT_OK;
37
+ }
38
+ if (arg === undefined || arg === '') {
39
+ process.stderr.write(`${HELP_TEXT}\n`);
40
+ return EXIT_USAGE;
41
+ }
42
+ const filePath = arg;
43
+ try {
44
+ const { changed } = formatFile(filePath);
45
+ process.stdout.write(changed ? `Sorted ${filePath}\n` : `${filePath} is already sorted\n`);
46
+ return EXIT_OK;
47
+ }
48
+ catch (error) {
49
+ if (error instanceof ParseError) {
50
+ process.stderr.write(`${error.message}\n`);
51
+ return EXIT_PARSE;
52
+ }
53
+ const err = error;
54
+ if (err.code === 'ENOENT') {
55
+ process.stderr.write(`File not found: ${filePath}\n`);
56
+ return EXIT_USAGE;
57
+ }
58
+ process.stderr.write(`pkgsort: ${err.message}\n`);
59
+ return EXIT_USAGE;
60
+ }
61
+ }
62
+ process.exit(main(process.argv));
@@ -0,0 +1,21 @@
1
+ import type { Style } from '../types.js';
2
+ /**
3
+ * Thrown when input cannot be parsed as a `package.json` object. The CLI shell
4
+ * maps this to exit code `3` (see `docs/ARCHITECTURE.md` §7).
5
+ */
6
+ export declare class ParseError extends Error {
7
+ constructor(message: string, options?: {
8
+ cause?: unknown;
9
+ });
10
+ }
11
+ /**
12
+ * Parse `input` into a plain object plus its detected whitespace {@link Style}.
13
+ *
14
+ * This is the "read" stage of the pipeline: it is pure and never touches the
15
+ * filesystem. On malformed JSON — or valid JSON that is not a top-level object
16
+ * — it throws a {@link ParseError}.
17
+ */
18
+ export declare function parse(input: string): {
19
+ data: Record<string, unknown>;
20
+ style: Style;
21
+ };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Thrown when input cannot be parsed as a `package.json` object. The CLI shell
3
+ * maps this to exit code `3` (see `docs/ARCHITECTURE.md` §7).
4
+ */
5
+ export class ParseError extends Error {
6
+ constructor(message, options) {
7
+ super(message, options);
8
+ this.name = 'ParseError';
9
+ }
10
+ }
11
+ /**
12
+ * Parse `input` into a plain object plus its detected whitespace {@link Style}.
13
+ *
14
+ * This is the "read" stage of the pipeline: it is pure and never touches the
15
+ * filesystem. On malformed JSON — or valid JSON that is not a top-level object
16
+ * — it throws a {@link ParseError}.
17
+ */
18
+ export function parse(input) {
19
+ let data;
20
+ try {
21
+ data = JSON.parse(input);
22
+ }
23
+ catch (error) {
24
+ const detail = error instanceof Error ? error.message : String(error);
25
+ throw new ParseError(`Invalid JSON: ${detail}`, { cause: error });
26
+ }
27
+ if (data === null || typeof data !== 'object' || Array.isArray(data)) {
28
+ throw new ParseError('Expected a JSON object at the top level of package.json');
29
+ }
30
+ return {
31
+ data: data,
32
+ style: {
33
+ indent: detectIndent(input),
34
+ trailingNewline: input.endsWith('\n'),
35
+ },
36
+ };
37
+ }
38
+ /**
39
+ * Detect the indentation of one level from the raw text by inspecting the
40
+ * leading whitespace of the first indented line. Falls back to two spaces —
41
+ * the ecosystem default — when the document has no indented lines (e.g. it is
42
+ * written on a single line).
43
+ */
44
+ function detectIndent(input) {
45
+ const match = /\r?\n([ \t]+)\S/.exec(input);
46
+ return match?.[1] ?? ' ';
47
+ }
@@ -0,0 +1,9 @@
1
+ import type { FormatResult } from '../types.js';
2
+ /**
3
+ * The pure formatting pipeline: **parse → sort → serialize**.
4
+ *
5
+ * Given the text of a `package.json`, return the formatted text and whether it
6
+ * differs from the input. Throws {@link ParseError} on invalid input. This
7
+ * function performs no I/O and is fully deterministic.
8
+ */
9
+ export declare function format(input: string): FormatResult;
@@ -0,0 +1,16 @@
1
+ import { sortTopLevelKeys } from '../sorters/top-level-keys.js';
2
+ import { parse } from './parse.js';
3
+ import { serialize } from './serialize.js';
4
+ /**
5
+ * The pure formatting pipeline: **parse → sort → serialize**.
6
+ *
7
+ * Given the text of a `package.json`, return the formatted text and whether it
8
+ * differs from the input. Throws {@link ParseError} on invalid input. This
9
+ * function performs no I/O and is fully deterministic.
10
+ */
11
+ export function format(input) {
12
+ const { data, style } = parse(input);
13
+ const sorted = sortTopLevelKeys(data);
14
+ const output = serialize(sorted, style);
15
+ return { output, changed: output !== input };
16
+ }
@@ -0,0 +1,9 @@
1
+ import type { Style } from '../types.js';
2
+ /**
3
+ * Serialize `data` to JSON text using the given whitespace {@link Style}.
4
+ *
5
+ * This is the "write" stage and the *only* place whitespace decisions are made,
6
+ * so output is a pure function of the object plus its style. Line endings are
7
+ * always `\n`; the trailing newline is reproduced from the detected style.
8
+ */
9
+ export declare function serialize(data: Record<string, unknown>, style: Style): string;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Serialize `data` to JSON text using the given whitespace {@link Style}.
3
+ *
4
+ * This is the "write" stage and the *only* place whitespace decisions are made,
5
+ * so output is a pure function of the object plus its style. Line endings are
6
+ * always `\n`; the trailing newline is reproduced from the detected style.
7
+ */
8
+ export function serialize(data, style) {
9
+ const json = JSON.stringify(data, null, style.indent);
10
+ return style.trailingNewline ? `${json}\n` : json;
11
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Read a `package.json` from disk, format it, and write it back in place when
3
+ * it changed.
4
+ *
5
+ * This is the thin effectful shell around the pure {@link format} core: it owns
6
+ * the file I/O so the core stays deterministic and testable. Errors from
7
+ * reading (e.g. a missing file) and parsing ({@link ParseError}) propagate to
8
+ * the caller, which maps them to exit codes.
9
+ */
10
+ export declare function formatFile(filePath: string): {
11
+ changed: boolean;
12
+ };
@@ -0,0 +1,19 @@
1
+ import { readFileSync, writeFileSync } from 'node:fs';
2
+ import { format } from './core/pipeline.js';
3
+ /**
4
+ * Read a `package.json` from disk, format it, and write it back in place when
5
+ * it changed.
6
+ *
7
+ * This is the thin effectful shell around the pure {@link format} core: it owns
8
+ * the file I/O so the core stays deterministic and testable. Errors from
9
+ * reading (e.g. a missing file) and parsing ({@link ParseError}) propagate to
10
+ * the caller, which maps them to exit codes.
11
+ */
12
+ export function formatFile(filePath) {
13
+ const input = readFileSync(filePath, 'utf8');
14
+ const { output, changed } = format(input);
15
+ if (changed) {
16
+ writeFileSync(filePath, output, 'utf8');
17
+ }
18
+ return { changed };
19
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The canonical order for top-level `package.json` keys.
3
+ *
4
+ * This list mirrors the default field order of the widely-used
5
+ * [`sort-package-json`](https://github.com/keithamus/sort-package-json) project
6
+ * (as of 2026-07-04), so pkgsort produces the ordering the ecosystem already
7
+ * expects. Keys are grouped by purpose: identity, descriptive metadata, module
8
+ * type & entry points, files, scripts & tool config, dependency maps,
9
+ * environment constraints, and publishing.
10
+ *
11
+ * **Intentional divergence:** pkgsort deliberately does *not* copy
12
+ * `sort-package-json`'s handling of keys that are absent from this list.
13
+ * `sort-package-json` sorts unknown keys alphabetically; pkgsort preserves
14
+ * their original relative order and appends them after all known keys (see
15
+ * {@link sortTopLevelKeys} and `docs/CONFIG.md` §3.2, `unknownKeys: "end"`).
16
+ */
17
+ export declare const DEFAULT_KEY_ORDER: readonly string[];
18
+ /**
19
+ * Return a new object with the same entries as `data`, its top-level keys
20
+ * reordered into {@link DEFAULT_KEY_ORDER}.
21
+ *
22
+ * Only the arrangement of top-level keys changes: values (including nested
23
+ * objects) are carried over untouched, and unknown keys keep their original
24
+ * relative order at the end. The comparison is purely index-based, so the
25
+ * result is deterministic and independent of locale.
26
+ */
27
+ export declare function sortTopLevelKeys(data: Record<string, unknown>): Record<string, unknown>;
@@ -0,0 +1,157 @@
1
+ /**
2
+ * The canonical order for top-level `package.json` keys.
3
+ *
4
+ * This list mirrors the default field order of the widely-used
5
+ * [`sort-package-json`](https://github.com/keithamus/sort-package-json) project
6
+ * (as of 2026-07-04), so pkgsort produces the ordering the ecosystem already
7
+ * expects. Keys are grouped by purpose: identity, descriptive metadata, module
8
+ * type & entry points, files, scripts & tool config, dependency maps,
9
+ * environment constraints, and publishing.
10
+ *
11
+ * **Intentional divergence:** pkgsort deliberately does *not* copy
12
+ * `sort-package-json`'s handling of keys that are absent from this list.
13
+ * `sort-package-json` sorts unknown keys alphabetically; pkgsort preserves
14
+ * their original relative order and appends them after all known keys (see
15
+ * {@link sortTopLevelKeys} and `docs/CONFIG.md` §3.2, `unknownKeys: "end"`).
16
+ */
17
+ export const DEFAULT_KEY_ORDER = [
18
+ // Identity
19
+ '$schema',
20
+ 'name',
21
+ 'displayName',
22
+ 'version',
23
+ 'stableVersion',
24
+ 'private',
25
+ // Descriptive metadata
26
+ 'description',
27
+ 'categories',
28
+ 'keywords',
29
+ 'homepage',
30
+ 'bugs',
31
+ 'repository',
32
+ 'funding',
33
+ 'license',
34
+ 'qna',
35
+ 'author',
36
+ 'maintainers',
37
+ 'contributors',
38
+ 'publisher',
39
+ // Module type & entry points
40
+ 'sideEffects',
41
+ 'type',
42
+ 'imports',
43
+ 'exports',
44
+ 'main',
45
+ 'svelte',
46
+ 'umd:main',
47
+ 'jsdelivr',
48
+ 'unpkg',
49
+ 'module',
50
+ 'source',
51
+ 'jsnext:main',
52
+ 'browser',
53
+ 'react-native',
54
+ 'types',
55
+ 'typesVersions',
56
+ 'typings',
57
+ 'style',
58
+ 'example',
59
+ 'examplestyle',
60
+ 'assets',
61
+ 'bin',
62
+ 'man',
63
+ 'directories',
64
+ 'files',
65
+ 'workspaces',
66
+ 'binary',
67
+ // Scripts & tool configuration
68
+ 'scripts',
69
+ 'betterScripts',
70
+ 'wireit',
71
+ 'l10n',
72
+ 'contributes',
73
+ 'activationEvents',
74
+ 'husky',
75
+ 'simple-git-hooks',
76
+ 'pre-commit',
77
+ 'commitlint',
78
+ 'lint-staged',
79
+ 'nano-staged',
80
+ 'config',
81
+ 'nodemonConfig',
82
+ 'browserify',
83
+ 'babel',
84
+ 'browserslist',
85
+ 'xo',
86
+ 'prettier',
87
+ 'eslintConfig',
88
+ 'eslintIgnore',
89
+ 'npmpkgjsonlint',
90
+ 'npmPackageJsonLintConfig',
91
+ 'npmpackagejsonlint',
92
+ 'release',
93
+ 'remarkConfig',
94
+ 'stylelint',
95
+ 'ava',
96
+ 'jest',
97
+ 'jest-junit',
98
+ 'jest-stare',
99
+ 'mocha',
100
+ 'nyc',
101
+ 'c8',
102
+ 'tap',
103
+ 'oclif',
104
+ // Dependency maps
105
+ 'resolutions',
106
+ 'overrides',
107
+ 'dependencies',
108
+ 'devDependencies',
109
+ 'dependenciesMeta',
110
+ 'peerDependencies',
111
+ 'peerDependenciesMeta',
112
+ 'optionalDependencies',
113
+ 'bundledDependencies',
114
+ 'bundleDependencies',
115
+ 'extensionPack',
116
+ 'extensionDependencies',
117
+ 'flat',
118
+ // Environment constraints
119
+ 'packageManager',
120
+ 'engines',
121
+ 'engineStrict',
122
+ 'devEngines',
123
+ 'volta',
124
+ 'languageName',
125
+ 'os',
126
+ 'cpu',
127
+ 'preferGlobal',
128
+ // Publishing & presentation
129
+ 'publishConfig',
130
+ 'icon',
131
+ 'badges',
132
+ 'galleryBanner',
133
+ 'preview',
134
+ 'markdown',
135
+ 'pnpm',
136
+ ];
137
+ const KEY_RANK = new Map(DEFAULT_KEY_ORDER.map((key, index) => [key, index]));
138
+ /**
139
+ * Return a new object with the same entries as `data`, its top-level keys
140
+ * reordered into {@link DEFAULT_KEY_ORDER}.
141
+ *
142
+ * Only the arrangement of top-level keys changes: values (including nested
143
+ * objects) are carried over untouched, and unknown keys keep their original
144
+ * relative order at the end. The comparison is purely index-based, so the
145
+ * result is deterministic and independent of locale.
146
+ */
147
+ export function sortTopLevelKeys(data) {
148
+ const rank = (key) => KEY_RANK.get(key) ?? Number.MAX_SAFE_INTEGER;
149
+ // Array.prototype.sort is stable, so two unknown keys (equal rank) retain
150
+ // their original order.
151
+ const orderedKeys = Object.keys(data).sort((a, b) => rank(a) - rank(b));
152
+ const result = {};
153
+ for (const key of orderedKeys) {
154
+ result[key] = data[key];
155
+ }
156
+ return result;
157
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared internal types.
3
+ *
4
+ * These describe the data that flows through the pure core; they are not a
5
+ * published, stable API (see `docs/ARCHITECTURE.md` §8).
6
+ */
7
+ /**
8
+ * The whitespace style detected from — and re-applied to — a `package.json`.
9
+ * We record the original detail so the serializer can reproduce it and so we
10
+ * can report `changed` accurately.
11
+ */
12
+ export interface Style {
13
+ /** The literal indentation of one level, e.g. `" "`, `" "`, or `"\t"`. */
14
+ indent: string;
15
+ /** Whether the original file ended with a trailing newline. */
16
+ trailingNewline: boolean;
17
+ }
18
+ /** The result of formatting a single `package.json` document. */
19
+ export interface FormatResult {
20
+ /** The formatted text. */
21
+ output: string;
22
+ /** `true` when {@link FormatResult.output} differs from the input. */
23
+ changed: boolean;
24
+ }
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Shared internal types.
3
+ *
4
+ * These describe the data that flows through the pure core; they are not a
5
+ * published, stable API (see `docs/ARCHITECTURE.md` §8).
6
+ */
7
+ export {};
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@rsuvarna/pkgsort",
3
+ "version": "0.1.0",
4
+ "description": "The opinionated, deterministic package.json formatter for professional JavaScript and TypeScript teams.",
5
+ "keywords": [
6
+ "package.json",
7
+ "formatter",
8
+ "linter",
9
+ "sort",
10
+ "monorepo",
11
+ "workspaces",
12
+ "prettier",
13
+ "eslint",
14
+ "ci",
15
+ "deterministic"
16
+ ],
17
+ "homepage": "https://github.com/rahulsuvarna/pkgsort#readme",
18
+ "bugs": {
19
+ "url": "https://github.com/rahulsuvarna/pkgsort/issues"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/rahulsuvarna/pkgsort.git"
24
+ },
25
+ "license": "MIT",
26
+ "author": "pkgsort contributors",
27
+ "type": "module",
28
+ "engines": {
29
+ "node": ">=22.0.0"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public",
33
+ "provenance": false
34
+ },
35
+ "sideEffects": false,
36
+ "bin": {
37
+ "pkgsort": "./dist/cli.js"
38
+ },
39
+ "exports": {
40
+ "./package.json": "./package.json"
41
+ },
42
+ "files": [
43
+ "dist",
44
+ "README.md",
45
+ "LICENSE",
46
+ "CHANGELOG.md"
47
+ ],
48
+ "scripts": {
49
+ "build": "tsc --project tsconfig.build.json",
50
+ "dev": "tsc --project tsconfig.build.json --watch",
51
+ "clean": "rimraf dist coverage",
52
+ "typecheck": "tsc --noEmit",
53
+ "lint": "eslint .",
54
+ "lint:fix": "eslint . --fix",
55
+ "format": "prettier --write .",
56
+ "format:check": "prettier --check .",
57
+ "test": "vitest run",
58
+ "test:watch": "vitest",
59
+ "test:coverage": "vitest run --coverage",
60
+ "check": "npm run typecheck && npm run lint && npm run format:check && npm run test",
61
+ "prepublishOnly": "npm run clean && npm run check && npm run build",
62
+ "changeset": "changeset",
63
+ "release": "changeset publish"
64
+ },
65
+ "devDependencies": {
66
+ "@eslint/js": "^10.0.1",
67
+ "@types/node": "^22.20.0",
68
+ "@vitest/coverage-v8": "^4.1.9",
69
+ "eslint": "^10.6.0",
70
+ "eslint-config-prettier": "^10.1.8",
71
+ "prettier": "^3.9.4",
72
+ "rimraf": "^6.1.3",
73
+ "typescript": "^6.0.3",
74
+ "typescript-eslint": "^8.62.1",
75
+ "vitest": "^4.1.9"
76
+ }
77
+ }