@vibe-agent-toolkit/utils 0.2.0-rc.2 → 0.2.0-rc.4
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/README.md +26 -15
- package/dist/compare-code-units.d.ts +28 -0
- package/dist/compare-code-units.d.ts.map +1 -0
- package/dist/compare-code-units.js +33 -0
- package/dist/compare-code-units.js.map +1 -0
- package/dist/crawl-timing.d.ts +657 -0
- package/dist/crawl-timing.d.ts.map +1 -0
- package/dist/crawl-timing.js +765 -0
- package/dist/crawl-timing.js.map +1 -0
- package/dist/crawl.d.ts +2 -4
- package/dist/crawl.d.ts.map +1 -1
- package/dist/crawl.js +2 -4
- package/dist/crawl.js.map +1 -1
- package/dist/file-crawler.d.ts +22 -0
- package/dist/file-crawler.d.ts.map +1 -1
- package/dist/file-crawler.js +31 -7
- package/dist/file-crawler.js.map +1 -1
- package/dist/fs-utils.d.ts +17 -0
- package/dist/fs-utils.d.ts.map +1 -1
- package/dist/fs-utils.js +69 -0
- package/dist/fs-utils.js.map +1 -1
- package/dist/fs.d.ts +3 -1
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +5 -1
- package/dist/fs.js.map +1 -1
- package/dist/git-run.d.ts +167 -0
- package/dist/git-run.d.ts.map +1 -0
- package/dist/git-run.js +154 -0
- package/dist/git-run.js.map +1 -0
- package/dist/git-snapshot.d.ts +181 -0
- package/dist/git-snapshot.d.ts.map +1 -0
- package/dist/git-snapshot.js +232 -0
- package/dist/git-snapshot.js.map +1 -0
- package/dist/git-tracker.d.ts +94 -1
- package/dist/git-tracker.d.ts.map +1 -1
- package/dist/git-tracker.js +135 -3
- package/dist/git-tracker.js.map +1 -1
- package/dist/git-utils.d.ts +38 -0
- package/dist/git-utils.d.ts.map +1 -1
- package/dist/git-utils.js +103 -80
- package/dist/git-utils.js.map +1 -1
- package/dist/git.d.ts +12 -4
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +12 -4
- package/dist/git.js.map +1 -1
- package/dist/gitignore-checker.d.ts.map +1 -1
- package/dist/gitignore-checker.js +6 -3
- package/dist/gitignore-checker.js.map +1 -1
- package/dist/index.d.ts +24 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +49 -30
- package/dist/index.js.map +1 -1
- package/dist/path-core.d.ts +1 -1
- package/dist/path-core.js +1 -1
- package/dist/project-utils.d.ts.map +1 -1
- package/dist/project-utils.js +6 -3
- package/dist/project-utils.js.map +1 -1
- package/dist/project.d.ts +7 -5
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +7 -5
- package/dist/project.js.map +1 -1
- package/dist/safe-exec.d.ts +38 -10
- package/dist/safe-exec.d.ts.map +1 -1
- package/dist/safe-exec.js +88 -19
- package/dist/safe-exec.js.map +1 -1
- package/dist/skill-test/index.d.ts +1 -1
- package/dist/skill-test/index.d.ts.map +1 -1
- package/dist/skill-test/index.js +1 -1
- package/dist/skill-test/index.js.map +1 -1
- package/dist/skill-test/spawn-claude.d.ts +28 -0
- package/dist/skill-test/spawn-claude.d.ts.map +1 -1
- package/dist/skill-test/spawn-claude.js +51 -2
- package/dist/skill-test/spawn-claude.js.map +1 -1
- package/dist/skill-test/transcript.d.ts +41 -2
- package/dist/skill-test/transcript.d.ts.map +1 -1
- package/dist/skill-test/transcript.js +47 -3
- package/dist/skill-test/transcript.js.map +1 -1
- package/dist/test-helpers.d.ts +185 -13
- package/dist/test-helpers.d.ts.map +1 -1
- package/dist/test-helpers.js +274 -27
- package/dist/test-helpers.js.map +1 -1
- package/dist/testing.d.ts +73 -3
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +93 -3
- package/dist/testing.js.map +1 -1
- package/dist/text-content.d.ts +197 -0
- package/dist/text-content.d.ts.map +1 -0
- package/dist/text-content.js +348 -0
- package/dist/text-content.js.map +1 -0
- package/dist/text-file.d.ts +45 -0
- package/dist/text-file.d.ts.map +1 -0
- package/dist/text-file.js +53 -0
- package/dist/text-file.js.map +1 -0
- package/dist/text.d.ts +16 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +16 -0
- package/dist/text.js.map +1 -0
- package/dist/timing-dump.d.ts +136 -0
- package/dist/timing-dump.d.ts.map +1 -0
- package/dist/timing-dump.js +230 -0
- package/dist/timing-dump.js.map +1 -0
- package/eslint/README.md +105 -3
- package/eslint/index.cjs +50 -0
- package/eslint/index.d.cts +14 -4
- package/eslint/rules/no-bare-symlink-in-tests.cjs +200 -0
- package/eslint/rules/no-process-exit-in-phase.cjs +117 -0
- package/eslint/rules/no-raw-text-decode.cjs +296 -0
- package/eslint/rules/no-self-package-import.cjs +157 -0
- package/package.json +13 -8
- package/dist/link-auth/build-headers.d.ts +0 -34
- package/dist/link-auth/build-headers.d.ts.map +0 -1
- package/dist/link-auth/build-headers.js +0 -58
- package/dist/link-auth/build-headers.js.map +0 -1
- package/dist/link-auth/expand-macro.d.ts +0 -38
- package/dist/link-auth/expand-macro.d.ts.map +0 -1
- package/dist/link-auth/expand-macro.js +0 -133
- package/dist/link-auth/expand-macro.js.map +0 -1
- package/dist/link-auth/macros.yaml +0 -50
- package/dist/link-auth/resolve-token.d.ts +0 -83
- package/dist/link-auth/resolve-token.d.ts.map +0 -1
- package/dist/link-auth/resolve-token.js +0 -101
- package/dist/link-auth/resolve-token.js.map +0 -1
- package/dist/link-auth/resolve.d.ts +0 -102
- package/dist/link-auth/resolve.d.ts.map +0 -1
- package/dist/link-auth/resolve.js +0 -66
- package/dist/link-auth/resolve.js.map +0 -1
- package/dist/link-auth/rewrite.d.ts +0 -52
- package/dist/link-auth/rewrite.d.ts.map +0 -1
- package/dist/link-auth/rewrite.js +0 -102
- package/dist/link-auth/rewrite.js.map +0 -1
- package/dist/link-auth/select-provider.d.ts +0 -30
- package/dist/link-auth/select-provider.d.ts.map +0 -1
- package/dist/link-auth/select-provider.js +0 -55
- package/dist/link-auth/select-provider.js.map +0 -1
- package/dist/link-auth/template.d.ts +0 -40
- package/dist/link-auth/template.d.ts.map +0 -1
- package/dist/link-auth/template.js +0 -89
- package/dist/link-auth/template.js.map +0 -1
- package/dist/link-auth/transforms.d.ts +0 -46
- package/dist/link-auth/transforms.d.ts.map +0 -1
- package/dist/link-auth/transforms.js +0 -52
- package/dist/link-auth/transforms.js.map +0 -1
- package/dist/template-entry.d.ts +0 -10
- package/dist/template-entry.d.ts.map +0 -1
- package/dist/template-entry.js +0 -10
- package/dist/template-entry.js.map +0 -1
- package/dist/template.d.ts +0 -7
- package/dist/template.d.ts.map +0 -1
- package/dist/template.js +0 -18
- package/dist/template.js.map +0 -1
package/README.md
CHANGED
|
@@ -16,38 +16,47 @@ bun add @vibe-agent-toolkit/utils
|
|
|
16
16
|
|
|
17
17
|
Every area has its own subpath. Import the one you need.
|
|
18
18
|
|
|
19
|
-
The package sets `"sideEffects": false`, so a modern bundler will tree-shake unused code out of the `.` barrel — importing `safePath` from `.` and from `./path` produce near-identical bundles. **Subpaths are not primarily a size optimization.** What they control is what your build has to *resolve* and what your module graph *reaches
|
|
19
|
+
The package sets `"sideEffects": false`, so a modern bundler will tree-shake unused code out of the `.` barrel — importing `safePath` from `.` and from `./path` produce near-identical bundles. **Subpaths are not primarily a size optimization.** What they control is what your build has to *resolve* and what your module graph *reaches*. A narrow entry reaches only what it needs.
|
|
20
|
+
|
|
21
|
+
**The `.` barrel reaches no third-party package at all.** Every domain that carries a dependency — directly or transitively — is a subpath: `./crawl`, `./git`, `./process`, `./skill-test`, `./yaml`. That is asserted by equality in `test/subpath-purity.test.ts`, so the barrel cannot silently regain one. It still reaches `node:fs` and friends, so it is Node-only; what it does not do is make a consumer of `safePath` install a template engine.
|
|
20
22
|
|
|
21
23
|
The last two columns are the ones that matter when choosing. **"Resolves with zero deps installed?"** is the sharper of the two: it separates an entry that is merely *heavy* from one that is *unbuildable* in an environment where the package's third-party dependencies are absent or unresolvable.
|
|
22
24
|
|
|
23
25
|
| Subpath | Contents | Node builtins reached | Third-party | Resolves with zero deps installed? |
|
|
24
26
|
|---|---|---|---|---|
|
|
25
27
|
| `./path` | `safePath`, `toForwardSlash`, `toNfc`, `isAbsolutePath`, `isAbsoluteAnyPlatform`, `hasParentTraversalSegment`, `toAbsolutePath`, `getRelativePath`, `issueLocation` | `path` only | — | **yes** |
|
|
28
|
+
| `./text` | `decodeTextContent` — the one bytes-to-text seam: BOM-announced UTF-8/UTF-16LE/UTF-16BE/UTF-32LE/UTF-32BE, BOM stripped, UTF-8 assumed otherwise; reports the encoding, whether it was a BOM fact or an assumption, and how many U+FFFD the decode substituted | **none** | — | **yes** |
|
|
26
29
|
| `./zod` | `ZodTypeNames`, `getZodTypeName`, `isZodType`, `unwrapZodType`, `isZodOptional`, `isZodNullable` | **none** | — | **yes** |
|
|
27
30
|
| `./glob` | `isGlob`, static base extraction, magic remainder | `path` only | — | **yes** |
|
|
28
|
-
| `./fs` | `normalizePath`, `normalizedTmpdir`, `mkdirSyncReal`, `resolveFromImportMeta`, `dynamicImportPath`, `copyDirectory`, `fillSiblingNames`, `classifyFilenameCaseFrom`, `FsLookupCache` | `fs`, `fs/promises`, `os`, `path`, `url` | — | **yes** |
|
|
29
|
-
| `./testing` | `getTestOutputDir`, `getTestOutputBase`, `setupAsyncTempDirSuite`, `setupSyncTempDirSuite` | `crypto`, `fs`, `fs/promises`, `os`, `path`, `url` | — | **yes** |
|
|
31
|
+
| `./fs` | `normalizePath`, `normalizedTmpdir`, `mkdirSyncReal`, `resolveFromImportMeta`, `dynamicImportPath`, `copyDirectory`, `fillSiblingNames`, `classifyFilenameCaseFrom`, `FsLookupCache`, `readTextContent`, `readTextContentSync` | `fs`, `fs/promises`, `os`, `path`, `url`, `util` | — | **yes** |
|
|
32
|
+
| `./testing` | `getTestOutputDir`, `getTestOutputBase`, `setupAsyncTempDirSuite`, `setupSyncTempDirSuite`, `removeScratchDir`, `symlinkCapability`, `createSymlink`, `createSymlinkAsync` | `crypto`, `fs`, `fs/promises`, `os`, `path`, `url` | — | **yes** |
|
|
30
33
|
| `./asset` | `resolveAssetReference` — paths and npm bare specifiers | `fs`, `module`, `os`, `path`, `url` | — | **yes** |
|
|
31
34
|
| `./yaml` | `updateYamlIn`, `verifyConfinedYamlEdit` — byte-surgical YAML edits | **none** | `yaml` | no — needs `yaml` |
|
|
32
|
-
| `./
|
|
33
|
-
| `./
|
|
34
|
-
| `./
|
|
35
|
-
| `./
|
|
36
|
-
| `./project` | `findProjectRoot`, `findConfigFile`, `findNodeWorkspaceRoot`, `resetProjectRootCaches` | `fs`, `path` | — | **yes** |
|
|
37
|
-
| `./eslint` | the
|
|
38
|
-
| `.` |
|
|
35
|
+
| `./process` | `safeExecSync`, `safeExecResult`, `safeExecFromString`, `isToolAvailable`, `getToolVersion`, `hasShellSyntax`, `CommandExecutionError`, `spawnHardened`, `shouldUseShell`, `windowsShellQuote`, `buildWindowsShellLine`, `resolveShellCommandToken`, `isPathLike`, `makeStdioBlocking`, `describeStdioBlocking` | `child_process`, `path` | `@vibe-validate/git`, `which` | no — needs both |
|
|
36
|
+
| `./git` | `runGit`, `runGitOrThrow`, `gitFindRoot`, `gitLsFiles`, `gitLsOthers`, `isGitIgnored`, `loadGitignoreRules`, `GitTracker`, `gitTreeSnapshot`, `peekGitTreeSnapshot`, `withGitSnapshotCache`, `parseGitUrl`, `isGitUrl`, `nonInteractiveGitOverrides` | `async_hooks`, `fs`, `fs/promises`, `os`, `path`, `url` | `@vibe-validate/git`, `ignore` | no — needs both |
|
|
37
|
+
| `./crawl` | `crawlDirectory`, `crawlDirectorySync`, `crawlPathFilter`, `NEVER_CRAWL_GLOBS`, `BUILD_OUTPUT_GLOBS` | `fs`, `os`, `path`, `url` | `@vibe-validate/git`, `picomatch` | no — needs both |
|
|
38
|
+
| `./skill-test` | `spawnHeadlessClaude`, `assembleClaudeArgs`, `killAllActiveClaudeChildren`, `resolveAuth`, `probeAuthStatus`, `AuthPreflightError`, `applyDeclaredEnv`, `buildForwardedEnv`, `formatForwardedEnvLine`, `isProtectedName`, `protectedEnvNames`, `parseStreamJsonTranscript`, `detectInvocationFromTranscript` | `child_process`, `path`, `stream` | `@vibe-validate/git`, `which` | no — needs both |
|
|
39
|
+
| `./project` | `findProjectRoot`, `findConfigFile`, `findNodeWorkspaceRoot`, `resetProjectRootCaches` | `fs`, `fs/promises`, `os`, `path`, `url` | — | **yes** |
|
|
40
|
+
| `./eslint` | the 22 ESLint rules that enforce everything above — see [ESLint rules](#eslint-rules--vibe-agent-toolkitutilseslint) | **none** | — | **yes** |
|
|
41
|
+
| `.` | the dependency-free entries above — path, text, fs, asset, project, testing, zod, glob, plus the crawl-timing seam | `async_hooks`, `crypto`, `fs`, `fs/promises`, `module`, `os`, `path`, `url`, `util` | — | **yes** |
|
|
39
42
|
| `./package.json` | the manifest itself, for version reporting and resolution assertions | — | — | **yes** |
|
|
40
43
|
|
|
41
44
|
Note `./zod` reaches nothing at all: it detects Zod types by duck-typing `_def.typeName` rather than importing Zod, which is exactly why it works across Zod v3 and v4.
|
|
42
45
|
|
|
43
|
-
`./crawl` is the only
|
|
46
|
+
`./crawl` is the only entry that reaches `picomatch`, and it is deliberately *not* folded into `./glob` — `./glob` is guarded as portable (`node:path`, no third-party), and directory crawling would break both halves of that guarantee.
|
|
47
|
+
|
|
48
|
+
`./skill-test` declares no dependency of its own; it is a separate entry because of what it *reaches*. Spawning a headless agent goes through `./process`, which costs `which` and — because `safeExecSync` refuses the `git` binary and delegates — `@vibe-validate/git`. Reachability is the criterion, not the import a module happens to write.
|
|
49
|
+
|
|
50
|
+
`./git` is the only published route to `runGit`. `safeExecSync` and `safeExecResult` on `./process` refuse `git` outright and point here, so a caller that wants git gets the scrubbed environment by construction rather than by remembering to ask.
|
|
44
51
|
|
|
45
52
|
```typescript
|
|
46
53
|
// Reaches node:path and nothing else
|
|
47
54
|
import { safePath, toForwardSlash } from '@vibe-agent-toolkit/utils/path';
|
|
55
|
+
|
|
56
|
+
// Reaches `which` and @vibe-validate/git — a real install cost, so it is its own entry
|
|
48
57
|
import { safeExecSync, spawnHardened } from '@vibe-agent-toolkit/utils/process';
|
|
49
58
|
|
|
50
|
-
// Reaches
|
|
59
|
+
// Reaches node builtins only — no third-party package, whatever you destructure
|
|
51
60
|
import { safePath } from '@vibe-agent-toolkit/utils';
|
|
52
61
|
```
|
|
53
62
|
|
|
@@ -61,8 +70,8 @@ This package targets **Node >= 22** and is not published for browsers. Most entr
|
|
|
61
70
|
|
|
62
71
|
A guard test in `test/subpath-purity.test.ts` walks each entry's transitive source graph and enforces **both** of the table's last two columns:
|
|
63
72
|
|
|
64
|
-
- **Third-party reach** — the "Resolves with zero deps installed?" column. Every entry's expected third-party set is asserted exactly, so every **yes** row above is a tested claim rather than a documented intention, and adding a dependency to any entry is a deliberate, reviewed edit.
|
|
65
|
-
- **Builtin reach** — five entries are held to a stricter contract still: `./zod`, `./yaml`, `./
|
|
73
|
+
- **Third-party reach** — the "Resolves with zero deps installed?" column. Every entry's expected third-party set is asserted exactly, so every **yes** row above is a tested claim rather than a documented intention, and adding a dependency to any entry is a deliberate, reviewed edit. The `.` row asserts `[]`, which is the load-bearing one: a dependency arriving on the barrel — through any module it exports, at any depth — reddens that assertion, and the fix is a new subpath rather than a longer expected list.
|
|
74
|
+
- **Builtin reach** — five entries are held to a stricter contract still: `./zod`, `./yaml`, `./text` reach **no Node builtin at all**, and `./path`, `./glob` reach **`node:path` and nothing else** — the one builtin every bundler shims.
|
|
66
75
|
|
|
67
76
|
That is an enforced invariant, not a browser-support commitment: there are no browser export conditions and no browser test lane. The guard exists so the property can't regress silently — it fails loudly if it cannot resolve a module, so it can't pass vacuously; `test/fixtures/dangling-import/` exercises that failure so the guarantee is demonstrated, not just claimed. If you add a new entry, add it to that test or nothing protects it.
|
|
68
77
|
|
|
@@ -183,6 +192,8 @@ These return **OS-native** separators, because they resolve real filesystem iden
|
|
|
183
192
|
|
|
184
193
|
- `setupAsyncTempDirSuite()` / `setupSyncTempDirSuite()` - per-suite temp directories with cleanup
|
|
185
194
|
- `getTestOutputDir()` / `getTestOutputBase()` - isolated test output paths
|
|
195
|
+
- `symlinkCapability()` - probes once (memoized per process) whether this host can create symlinks (Windows needs Developer Mode or `SeCreateSymbolicLinkPrivilege`), returning a `SymlinkCapability` token or `null`
|
|
196
|
+
- `createSymlink()` / `createSymlinkAsync()` - the sanctioned way to create a symlink in test code; both require a `SymlinkCapability` from `symlinkCapability()`, so a test cannot reach the raw syscall without first proving the host supports it (or explicitly skipping via vitest's `skip()`)
|
|
186
197
|
|
|
187
198
|
### Project roots — `@vibe-agent-toolkit/utils` (barrel only)
|
|
188
199
|
|
|
@@ -194,7 +205,7 @@ These are CLI-boundary functions: inner libraries should take a root as a parame
|
|
|
194
205
|
|
|
195
206
|
**These four are VAT-shaped — read this before reaching for them.** `findProjectRoot()` looks for `vibe-agent-toolkit.config.yaml` and then `.git/`; if your repo's notion of "root" is a `pnpm-workspace.yaml`, a `turbo.json`, or a lockfile, that ladder is not your ladder — and for a *published* package, keying anything on `.git/` is a bug, since it will not be there at install time. `findNodeWorkspaceRoot()` is narrower still: it needs a `package.json` carrying a `"workspaces"` key, which pnpm and Bun workspaces do not have. `findConfigFile()` hardcodes VAT's config filename. If you want a git root, take `gitFindRoot()` from [`./git`](#git--vibe-agent-toolkitutilsgit); if you want your own marker, a six-line walk-up is more honest than a helper whose ladder you have to work around.
|
|
196
207
|
|
|
197
|
-
They are nonetheless on their own [`./project`](#import-narrowly) entry rather than the barrel alone. The entry was briefly withdrawn on the grounds that the functions fit few repos — which is true, and is what the paragraph above says — but that answered the wrong question. What decides whether an *entry* exists is how heavy the only remaining door is
|
|
208
|
+
They are nonetheless on their own [`./project`](#import-narrowly) entry rather than the barrel alone. The entry was briefly withdrawn on the grounds that the functions fit few repos — which is true, and is what the paragraph above says — but that answered the wrong question. What decides whether an *entry* exists is how heavy the only remaining door is, and barrel-only these four once cost five third-party packages to reach while their own code imports nothing but `node:fs` and `node:path`. Publishing the entry is not a claim that the ladder fits you — only that finding out shouldn't cost a dependency graph. The barrel is dependency-free now, which is that same rule applied everywhere rather than a reason to fold `./project` back in.
|
|
198
209
|
|
|
199
210
|
### Directory crawling — `@vibe-agent-toolkit/utils/crawl`
|
|
200
211
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-independent string ordering, for sorts whose output is hashed or compared
|
|
3
|
+
* across machines.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this is not `localeCompare`
|
|
6
|
+
*
|
|
7
|
+
* `sonarjs/no-alphabetical-sort` fires on a bare `strings.sort()` and its suggested fix is a
|
|
8
|
+
* `localeCompare` comparator. **That fix is wrong for anything whose order feeds a digest, a
|
|
9
|
+
* content key, a serialized document or a golden file.** Collation is locale-dependent, so two
|
|
10
|
+
* machines running the same code over the same corpus can order the same strings differently —
|
|
11
|
+
* destroying exactly the cross-run comparability those artifacts exist to provide. The rule only
|
|
12
|
+
* requires *a* comparator; this is the one to give it.
|
|
13
|
+
*
|
|
14
|
+
* Code-unit order is total, stable and machine-independent. It is not alphabetical in any human
|
|
15
|
+
* sense (uppercase sorts before lowercase, `10` before `2`), which is fine — callers using it are
|
|
16
|
+
* ordering bytes for a machine, not a list for a reader. Sort numbers numerically instead.
|
|
17
|
+
*
|
|
18
|
+
* This lives in `utils` because it had independently grown three identical private copies (in
|
|
19
|
+
* `resources`' projection digest and blob population, and in `claude-marketplace`' plugin extent)
|
|
20
|
+
* and a fourth was nearly written. Three copies of a "never use `localeCompare`" rule is three
|
|
21
|
+
* chances for someone to helpfully replace one of them.
|
|
22
|
+
*
|
|
23
|
+
* @param left - First string
|
|
24
|
+
* @param right - Second string
|
|
25
|
+
* @returns Negative, zero or positive per the `Array.prototype.sort` contract
|
|
26
|
+
*/
|
|
27
|
+
export declare function compareCodeUnits(left: string, right: string): number;
|
|
28
|
+
//# sourceMappingURL=compare-code-units.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare-code-units.d.ts","sourceRoot":"","sources":["../src/compare-code-units.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAKpE"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-independent string ordering, for sorts whose output is hashed or compared
|
|
3
|
+
* across machines.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this is not `localeCompare`
|
|
6
|
+
*
|
|
7
|
+
* `sonarjs/no-alphabetical-sort` fires on a bare `strings.sort()` and its suggested fix is a
|
|
8
|
+
* `localeCompare` comparator. **That fix is wrong for anything whose order feeds a digest, a
|
|
9
|
+
* content key, a serialized document or a golden file.** Collation is locale-dependent, so two
|
|
10
|
+
* machines running the same code over the same corpus can order the same strings differently —
|
|
11
|
+
* destroying exactly the cross-run comparability those artifacts exist to provide. The rule only
|
|
12
|
+
* requires *a* comparator; this is the one to give it.
|
|
13
|
+
*
|
|
14
|
+
* Code-unit order is total, stable and machine-independent. It is not alphabetical in any human
|
|
15
|
+
* sense (uppercase sorts before lowercase, `10` before `2`), which is fine — callers using it are
|
|
16
|
+
* ordering bytes for a machine, not a list for a reader. Sort numbers numerically instead.
|
|
17
|
+
*
|
|
18
|
+
* This lives in `utils` because it had independently grown three identical private copies (in
|
|
19
|
+
* `resources`' projection digest and blob population, and in `claude-marketplace`' plugin extent)
|
|
20
|
+
* and a fourth was nearly written. Three copies of a "never use `localeCompare`" rule is three
|
|
21
|
+
* chances for someone to helpfully replace one of them.
|
|
22
|
+
*
|
|
23
|
+
* @param left - First string
|
|
24
|
+
* @param right - Second string
|
|
25
|
+
* @returns Negative, zero or positive per the `Array.prototype.sort` contract
|
|
26
|
+
*/
|
|
27
|
+
export function compareCodeUnits(left, right) {
|
|
28
|
+
if (left === right) {
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
return left < right ? -1 : 1;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=compare-code-units.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare-code-units.js","sourceRoot":"","sources":["../src/compare-code-units.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,KAAa;IAC1D,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACnB,OAAO,CAAC,CAAC;IACX,CAAC;IACD,OAAO,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/B,CAAC"}
|