Repository: keithamus/sort-package-json (js)

Issue:
Sort pre/post scripts with colon together
I often use `pre` and `post` scripts such as `prebuild`, `build` and `postbuild`. Sometimes I split them into multiple scripts such as `prebuild:foo` and `prebuild:bar` with `prebuild` running something like [npm-run-all](https://www.npmjs.com/package/npm-run-all2) `run-s`.

The `prebuild` and `postbuild` is neatly sorted as expected:

```json
{
  "scripts": {
    "prebuild": "..",
    "build": "..",
    "postbuild": ".."
  }
}
```

However when using `:` the sorting isn't as neat any longer, a longer example of this is:

```json
{
  "scripts": {
    "prebuild": "run-s prebuild:*",
    "build": "run-s build:*",
    "postbuild": "run-s prebuild:*",
    "build:bar": "node bar.js",
    "build:baz": "node baz.js",
    "build:foo": "node foo.js",
    "d-unrelated": "..",
    "e-unrelated": "..",
    "f-unrelated": "..",
    "postbuild:1": "node prebuild.js 1",
    "postbuild:2": "node prebuild.js 2",
    "postbuild:3": "node prebuild.js 3",
    "prebuild:1": "node prebuild.js 1",
    "prebuild:2": "node prebuild.js 2",
    "prebuild:3": "node prebuild.js 3"
  }
}
```

The expected order would be:

```json
{
  "name": "scripts-with-colon",
  "version": "1.2.3",
  "scripts": {
    "prebuild": "run-s prebuild:*",
    "prebuild:1": "node prebuild.js 1",
    "prebuild:2": "node prebuild.js 2",
    "prebuild:3": "node prebuild.js 3",
    "build": "run-s build:*",
    "build:bar": "node bar.js",
    "build:baz": "node baz.js",
    "build:foo": "node foo.js",
    "postbuild": "run-s prebuild:*",
    "postbuild:1": "node prebuild.js 1",
    "postbuild:2": "node prebuild.js 2",
    "postbuild:3": "node prebuild.js 3",
    "d-unrelated": "..",
    "e-unrelated": "..",
    "f-unrelated": "..",
  }
}
```

If you think this is reasonable I could try to create a PR to implement this change.

Pull request description:
feat: sort pre/post scripts with colon together
# Issue

close #307 

# Test

![image](https://github.com/user-attachments/assets/e394b27c-97c8-4e37-9034-96358a6b60d9)


Patch:
diff --git a/index.js b/index.js
index d5da5e4..10a5ff3 100755
--- a/index.js
+++ b/index.js
@@ -248,9 +248,24 @@ const sortScripts = onObject((scripts, packageJson) => {
     keys.sort()
   }
 
-  const order = keys.flatMap((key) =>
-    prefixable.has(key) ? [`pre${key}`, key, `post${key}`] : [key],
-  )
+  const scriptsKeyMap = new Map()
+
+  keys
+    .flatMap((key) =>
+      prefixable.has(key) ? [`pre${key}`, key, `post${key}`] : [key],
+    )
+    .forEach((key) => {
+      const [prefix] = key.split(':')
+      const keySet = scriptsKeyMap.has(prefix)
+        ? scriptsKeyMap.get(prefix)
+        : new Set()
+      scriptsKeyMap.set(prefix, keySet.add(key))
+    })
+
+  const order = [...scriptsKeyMap.values()].flat().reduce((keys, keySet) => {
+    keys.push(...keySet)
+    return keys
+  }, [])
 
   return sortObjectKeys(scripts, order)
 })


Test patch:
diff --git a/tests/scripts.js b/tests/scripts.js
index 8a417c6..9f71086 100644
--- a/tests/scripts.js
+++ b/tests/scripts.js
@@ -85,3 +85,46 @@ for (const field of ['scripts', 'betterScripts']) {
     },
   })
 }
+
+for (const field of ['scripts', 'betterScripts']) {
+  test(`${field} sort pre/post scripts with colon together`, macro.sortObject, {
+    value: {
+      [field]: {
+        prebuild: 'run-s prebuild:*',
+        build: 'run-s build:*',
+        postbuild: 'run-s prebuild:*',
+        'build:bar': 'node bar.js',
+        'build:baz': 'node baz.js',
+        'build:foo': 'node foo.js',
+        'd-unrelated': '..',
+        'e-unrelated': '..',
+        'f-unrelated': '..',
+        'postbuild:1': 'node prebuild.js 1',
+        'postbuild:2': 'node prebuild.js 2',
+        'postbuild:3': 'node prebuild.js 3',
+        'prebuild:1': 'node prebuild.js 1',
+        'prebuild:2': 'node prebuild.js 2',
+        'prebuild:3': 'node prebuild.js 3',
+      },
+    },
+    expect: {
+      [field]: {
+        prebuild: 'run-s prebuild:*',
+        'prebuild:1': 'node prebuild.js 1',
+        'prebuild:2': 'node prebuild.js 2',
+        'prebuild:3': 'node prebuild.js 3',
+        build: 'run-s build:*',
+        'build:bar': 'node bar.js',
+        'build:baz': 'node baz.js',
+        'build:foo': 'node foo.js',
+        postbuild: 'run-s prebuild:*',
+        'postbuild:1': 'node prebuild.js 1',
+        'postbuild:2': 'node prebuild.js 2',
+        'postbuild:3': 'node prebuild.js 3',
+        'd-unrelated': '..',
+        'e-unrelated': '..',
+        'f-unrelated': '..',
+      },
+    },
+  })
+}