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.
Files changed (3) hide show
  1. package/README.md +103 -0
  2. package/bin/anomalift.js +166 -0
  3. 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
+ ```
@@ -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
+ }