anomalift 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/README.md +103 -0
- package/bin/anomalift.js +166 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# anomalift (npm)
|
|
2
|
+
|
|
3
|
+
Find the mistakes your coding agent repeats, and stop them.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npx anomalift
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Or install it:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install -g anomalift
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## What this package actually is
|
|
16
|
+
|
|
17
|
+
A shim, and nothing more. It does not contain anomalift, and it cannot build
|
|
18
|
+
it. The real program is a single Rust binary; this package downloads nothing at
|
|
19
|
+
install time and runs no install scripts. What it does is declare one optional
|
|
20
|
+
dependency per platform, each containing one prebuilt binary:
|
|
21
|
+
|
|
22
|
+
| package | os | cpu | release asset |
|
|
23
|
+
| ------------------------ | -------- | ------- | ------------------------------ |
|
|
24
|
+
| `anomalift-darwin-arm64` | `darwin` | `arm64` | `anomalift-macos-arm64` |
|
|
25
|
+
| `anomalift-darwin-x64` | `darwin` | `x64` | `anomalift-macos-x86_64` |
|
|
26
|
+
| `anomalift-linux-x64` | `linux` | `x64` | `anomalift-linux-x86_64` |
|
|
27
|
+
| `anomalift-linux-arm64` | `linux` | `arm64` | `anomalift-linux-arm64` |
|
|
28
|
+
| `anomalift-windows-x64` | `win32` | `x64` | `anomalift-windows-x86_64.exe` |
|
|
29
|
+
|
|
30
|
+
The names are unscoped rather than `@anomalift/*` because a scoped package
|
|
31
|
+
needs the npm organisation to exist before the first publish, and publishes
|
|
32
|
+
private unless every publish remembers `--access public`. Getting either wrong
|
|
33
|
+
ships a wrapper whose dependencies nobody can install.
|
|
34
|
+
|
|
35
|
+
npm reads the `os` and `cpu` fields, installs the one package that matches the
|
|
36
|
+
machine, and silently skips the other four. `bin/anomalift.js` then resolves
|
|
37
|
+
that package and execs the binary inside it, forwarding stdio and propagating
|
|
38
|
+
the exit code. That last part matters: anomalift is meant to run in scripts and
|
|
39
|
+
pre-commit hooks, and a swallowed non-zero exit turns "this found problems"
|
|
40
|
+
into "this passed".
|
|
41
|
+
|
|
42
|
+
You do not need Rust, and nothing is compiled on your machine.
|
|
43
|
+
|
|
44
|
+
## If you would rather not involve npm
|
|
45
|
+
|
|
46
|
+
The binary is the product; npm is one of three ways to get it.
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
# macOS / Linux
|
|
50
|
+
curl -fsSL https://raw.githubusercontent.com/kailash16dev/Anomalift/main/install.sh | sh
|
|
51
|
+
|
|
52
|
+
# Windows (PowerShell)
|
|
53
|
+
irm https://raw.githubusercontent.com/kailash16dev/Anomalift/main/install.ps1 | iex
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Both verify the published SHA-256 before installing. So does nothing about npm,
|
|
57
|
+
which is worth knowing: `npm install` trusts the registry and its own lockfile
|
|
58
|
+
integrity hashes instead.
|
|
59
|
+
|
|
60
|
+
## Windows on ARM
|
|
61
|
+
|
|
62
|
+
There is no native arm64 Windows build, so `anomalift-windows-x64` is what a
|
|
63
|
+
Windows ARM machine gets. It runs under the OS's x64 emulation. `install.ps1`
|
|
64
|
+
does the same thing and says so when it does.
|
|
65
|
+
|
|
66
|
+
## Publishing a release
|
|
67
|
+
|
|
68
|
+
One command, and it refuses rather than guesses:
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
gh release download v0.1.0 --dir dist # the workflow's artifacts
|
|
72
|
+
scripts/npm-release.sh --assets ./dist --dry-run
|
|
73
|
+
scripts/npm-release.sh --assets ./dist
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The release workflow also runs this on a tag when an `NPM_TOKEN` secret exists,
|
|
77
|
+
so publishing by hand is the fallback rather than the routine.
|
|
78
|
+
|
|
79
|
+
What the script checks before anything leaves the machine, because
|
|
80
|
+
`npm publish` cannot be taken back — npm refuses to reuse a version number even
|
|
81
|
+
after `npm unpublish`:
|
|
82
|
+
|
|
83
|
+
- **every asset is present**, so a half-published release is impossible
|
|
84
|
+
- **all six manifests agree on the version.** The wrapper pins its optional
|
|
85
|
+
dependencies exactly; a mismatch means npm installs no platform package at
|
|
86
|
+
all and every user gets "platform package is not installed"
|
|
87
|
+
- **no binary carries local paths.** The compiler bakes absolute source paths
|
|
88
|
+
into panic locations, so a laptop build published as-is puts the
|
|
89
|
+
maintainer's home directory in front of everyone. The workflow remaps them
|
|
90
|
+
- **the executable bit is set.** npm preserves the mode from the tarball, and a
|
|
91
|
+
binary published without it fails with `EACCES` on every machine, after the
|
|
92
|
+
version is already public
|
|
93
|
+
- **each tarball contains exactly what it should** — the binary and the
|
|
94
|
+
manifest, nothing else
|
|
95
|
+
- **platform packages publish first, wrapper last.** The other order leaves a
|
|
96
|
+
window where `npm install anomalift` resolves to a version whose
|
|
97
|
+
dependencies do not exist yet
|
|
98
|
+
|
|
99
|
+
Then verify from an empty directory:
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
npx --yes anomalift@latest --version
|
|
103
|
+
```
|
package/bin/anomalift.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// Shim. Finds the prebuilt binary that npm installed for this machine, runs it,
|
|
5
|
+
// and gets out of the way.
|
|
6
|
+
//
|
|
7
|
+
// The whole point of shipping on npm is that `npx anomalift` works for someone
|
|
8
|
+
// who has Node and nothing else - no Rust, no cargo, no compiler. So this file
|
|
9
|
+
// must never try to build anything, and must never be more than a hop.
|
|
10
|
+
//
|
|
11
|
+
// CommonJS on purpose: a `bin` entry is resolved and run directly by npm, and
|
|
12
|
+
// ESM here would break on any consumer whose package.json says "type": "module"
|
|
13
|
+
// in the wrong direction. `require` always works.
|
|
14
|
+
|
|
15
|
+
const fs = require("fs");
|
|
16
|
+
const path = require("path");
|
|
17
|
+
const { spawnSync } = require("child_process");
|
|
18
|
+
|
|
19
|
+
// npm reads the `os` and `cpu` fields of each optional dependency and installs
|
|
20
|
+
// only the one that matches, silently skipping the others - that is the whole
|
|
21
|
+
// trick. It also means exactly one of these is ever present on disk, so we do
|
|
22
|
+
// not choose between them, we just ask for the one this machine needs.
|
|
23
|
+
//
|
|
24
|
+
// Unscoped names on purpose: a scoped package needs the npm organisation to
|
|
25
|
+
// exist before the first publish, and publishes as private unless every single
|
|
26
|
+
// publish remembers `--access public`. Getting that wrong ships a root package
|
|
27
|
+
// whose dependencies nobody can install.
|
|
28
|
+
const PLATFORM_PACKAGES = {
|
|
29
|
+
"darwin-arm64": "anomalift-darwin-arm64",
|
|
30
|
+
"darwin-x64": "anomalift-darwin-x64",
|
|
31
|
+
"linux-x64": "anomalift-linux-x64",
|
|
32
|
+
"linux-arm64": "anomalift-linux-arm64",
|
|
33
|
+
"win32-x64": "anomalift-windows-x64",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const REPO = "kailash16dev/Anomalift";
|
|
37
|
+
|
|
38
|
+
// process.arch reports the architecture of the *Node process*, not the CPU. An
|
|
39
|
+
// x64 Node under Rosetta on Apple Silicon says "x64", and will then get the
|
|
40
|
+
// x86_64 build - slower, but correct, and the alternative is running an arm64
|
|
41
|
+
// binary that machine's Node cannot even spawn.
|
|
42
|
+
const key = process.platform + "-" + process.arch;
|
|
43
|
+
const pkgName = PLATFORM_PACKAGES[key];
|
|
44
|
+
|
|
45
|
+
function fail(message) {
|
|
46
|
+
process.stderr.write("\nanomalift: " + message + "\n\n");
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function installHint() {
|
|
51
|
+
return (
|
|
52
|
+
"Install the binary directly instead - it does not involve npm at all:\n\n" +
|
|
53
|
+
" macOS / Linux curl -fsSL https://raw.githubusercontent.com/" +
|
|
54
|
+
REPO +
|
|
55
|
+
"/main/install.sh | sh\n" +
|
|
56
|
+
" Windows irm https://raw.githubusercontent.com/" +
|
|
57
|
+
REPO +
|
|
58
|
+
"/main/install.ps1 | iex"
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!pkgName) {
|
|
63
|
+
fail(
|
|
64
|
+
"no prebuilt binary exists for " +
|
|
65
|
+
key +
|
|
66
|
+
".\n\n" +
|
|
67
|
+
"This package only wraps binaries produced by the release workflow; it\n" +
|
|
68
|
+
"cannot build from source. Supported: " +
|
|
69
|
+
Object.keys(PLATFORM_PACKAGES).join(", ") +
|
|
70
|
+
".\n\n" +
|
|
71
|
+
"If you need " +
|
|
72
|
+
key +
|
|
73
|
+
", build it with `cargo build --release` from the repo."
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const exe = process.platform === "win32" ? "anomalift.exe" : "anomalift";
|
|
78
|
+
|
|
79
|
+
let pkgRoot;
|
|
80
|
+
try {
|
|
81
|
+
// Resolve the *manifest*, not the binary, and join the rest by hand.
|
|
82
|
+
// require.resolve throws the same MODULE_NOT_FOUND when the target file is
|
|
83
|
+
// absent, so resolving `<pkg>/bin/anomalift` directly cannot tell "npm never
|
|
84
|
+
// installed this package" apart from "this package was published without its
|
|
85
|
+
// binary in it" - and the second is a botched release needing a completely
|
|
86
|
+
// different fix. Resolving package.json, which is always there if the
|
|
87
|
+
// package is, separates the two.
|
|
88
|
+
//
|
|
89
|
+
// The platform packages deliberately have no "exports" field, which is what
|
|
90
|
+
// keeps this legal: an "exports" map seals off every subpath not listed in
|
|
91
|
+
// it, package.json included.
|
|
92
|
+
pkgRoot = path.dirname(require.resolve(pkgName + "/package.json"));
|
|
93
|
+
} catch (err) {
|
|
94
|
+
// A stack trace here helps nobody. The realistic causes are all install-time
|
|
95
|
+
// and all have the same two fixes.
|
|
96
|
+
fail(
|
|
97
|
+
"the platform package " +
|
|
98
|
+
pkgName +
|
|
99
|
+
" is not installed.\n\n" +
|
|
100
|
+
"npm skips optional dependencies when installed with --no-optional or\n" +
|
|
101
|
+
"--omit=optional, and some CI caches restore a lockfile that predates the\n" +
|
|
102
|
+
"platform packages. Try:\n\n" +
|
|
103
|
+
" npm install --force " +
|
|
104
|
+
pkgName +
|
|
105
|
+
"\n\n" +
|
|
106
|
+
installHint()
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const binPath = path.join(pkgRoot, "bin", exe);
|
|
111
|
+
|
|
112
|
+
if (!fs.existsSync(binPath)) {
|
|
113
|
+
fail(
|
|
114
|
+
"the platform package " +
|
|
115
|
+
pkgName +
|
|
116
|
+
" resolved but its binary is missing:\n " +
|
|
117
|
+
binPath +
|
|
118
|
+
"\n\n" +
|
|
119
|
+
"That package was published without the binary copied in, or the install\n" +
|
|
120
|
+
"was interrupted. " +
|
|
121
|
+
installHint()
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const result = spawnSync(binPath, process.argv.slice(2), {
|
|
126
|
+
stdio: "inherit",
|
|
127
|
+
// Do not use `shell: true`. Arguments here are file paths and globs from a
|
|
128
|
+
// user's command line, and a shell would re-interpret every quote and space
|
|
129
|
+
// in them - and would also be an injection hole in any script that builds an
|
|
130
|
+
// anomalift invocation from repository contents.
|
|
131
|
+
shell: false,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (result.error) {
|
|
135
|
+
if (result.error.code === "EACCES") {
|
|
136
|
+
fail(
|
|
137
|
+
"the binary is not executable:\n " +
|
|
138
|
+
binPath +
|
|
139
|
+
"\n\n" +
|
|
140
|
+
"npm normally preserves the executable bit from the published tarball.\n" +
|
|
141
|
+
"Fix it with `chmod +x " +
|
|
142
|
+
binPath +
|
|
143
|
+
"`, or reinstall."
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
fail("failed to run " + binPath + ": " + result.error.message);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Exit code propagation is the reason this file is not a one-liner. anomalift
|
|
150
|
+
// is meant to be used in scripts and pre-commit hooks, where a swallowed
|
|
151
|
+
// non-zero exit turns "this found problems" into "this passed".
|
|
152
|
+
if (result.signal) {
|
|
153
|
+
// A child killed by a signal exits with status === null, and reporting 0 for
|
|
154
|
+
// that would be a lie. Re-raise the same signal on ourselves so the parent
|
|
155
|
+
// shell sees a signalled death, which is what it would see without this shim
|
|
156
|
+
// in the middle. Windows has no real signals, so fall through to 1 if we are
|
|
157
|
+
// somehow still alive afterwards.
|
|
158
|
+
try {
|
|
159
|
+
process.kill(process.pid, result.signal);
|
|
160
|
+
} catch (err) {
|
|
161
|
+
/* not fatal - handled by the exit below */
|
|
162
|
+
}
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
process.exit(result.status === null ? 1 : result.status);
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "anomalift",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Find the mistakes your coding agent repeats, and stop them. Wraps a prebuilt binary; no Rust toolchain required.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/kailash16dev/Anomalift.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/kailash16dev/Anomalift#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/kailash16dev/Anomalift/issues"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"cli",
|
|
16
|
+
"code-review",
|
|
17
|
+
"static-analysis",
|
|
18
|
+
"coding-agent"
|
|
19
|
+
],
|
|
20
|
+
"bin": {
|
|
21
|
+
"anomalift": "bin/anomalift.js"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"bin/anomalift.js",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=16"
|
|
29
|
+
},
|
|
30
|
+
"optionalDependencies": {
|
|
31
|
+
"anomalift-darwin-arm64": "0.1.0",
|
|
32
|
+
"anomalift-darwin-x64": "0.1.0",
|
|
33
|
+
"anomalift-linux-x64": "0.1.0",
|
|
34
|
+
"anomalift-linux-arm64": "0.1.0",
|
|
35
|
+
"anomalift-windows-x64": "0.1.0"
|
|
36
|
+
}
|
|
37
|
+
}
|