dslinter 0.0.6 → 0.0.7
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 +16 -0
- package/README.md +30 -10
- package/bin/dslinter.mjs +18 -8
- package/package.json +4 -2
- package/scripts/ensure-dslint.mjs +96 -0
- package/scripts/resolve-dslint-binary.mjs +25 -0
- package/src/resolve-dslint-binary.test.ts +48 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.0.7
|
|
4
|
+
|
|
5
|
+
[compare changes](https://github.com/jrmybtlr/DSLint/compare/v0.0.6...v0.0.7)
|
|
6
|
+
|
|
7
|
+
### 🚀 Enhancements
|
|
8
|
+
|
|
9
|
+
- Implement postinstall script to download prebuilt dslint binaries ([dae3072](https://github.com/jrmybtlr/DSLint/commit/dae3072))
|
|
10
|
+
|
|
11
|
+
### ❤️ Contributors
|
|
12
|
+
|
|
13
|
+
- Jeremy Butler <jeremy.butler@laravel.com>
|
|
14
|
+
|
|
3
15
|
## v0.0.6
|
|
4
16
|
|
|
5
17
|
[compare changes](https://github.com/jrmybtlr/DSLint/compare/v0.0.5...v0.0.6)
|
|
@@ -11,6 +23,10 @@
|
|
|
11
23
|
- **npm:** package renamed from `@dslinter/dashboard` to **`dslinter`**. Replace the dependency, imports (`dslinter`), and theme import (`dslinter/theme.css`).
|
|
12
24
|
- **CLI:** add **`dslinter`** binary that forwards arguments to **`dslint`** on `PATH` (Rust scanner not bundled).
|
|
13
25
|
|
|
26
|
+
### Enhancements
|
|
27
|
+
|
|
28
|
+
- **`postinstall`** attempts to download a **prebuilt `dslint`** for the current platform from **GitHub Releases** (tag `v` + package version). CI: `.github/workflows/release-dslint-binaries.yml`. Opt out with `DSLINT_SKIP_DOWNLOAD=1`.
|
|
29
|
+
|
|
14
30
|
## v0.0.5
|
|
15
31
|
|
|
16
32
|
[compare changes](https://github.com/jrmybtlr/DSLint/compare/v0.0.4...v0.0.5)
|
package/README.md
CHANGED
|
@@ -19,28 +19,48 @@ This package is **source-first**: entry points resolve to TypeScript/TSX under `
|
|
|
19
19
|
|
|
20
20
|
## CLI (`npx dslinter`)
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
The **`dslinter` binary** runs the **`dslint`** scanner with the same flags as the Rust CLI (`--json`, `-o`, `--serve`, etc.).
|
|
23
|
+
|
|
24
|
+
### Without installing Rust
|
|
25
|
+
|
|
26
|
+
On **`npm install dslinter`**, a **`postinstall`** script tries to download a **prebuilt `dslint`** for your OS/arch from this repo’s **GitHub Releases**, using the **same tag as the npm version** (for example npm `dslinter@0.0.6` → release **`v0.0.6`** and assets like `dslint-x86_64-unknown-linux-gnu`). The binary is stored under `node_modules/dslinter/vendor/` and `dslinter` / `npx dslinter` prefer it over `PATH`.
|
|
27
|
+
|
|
28
|
+
**Release workflow:** push git tag `v*` (after bumping the npm version) so [.github/workflows/release-dslint-binaries.yml](https://github.com/jrmybtlr/DSLint/blob/main/.github/workflows/release-dslint-binaries.yml) uploads the platform binaries, **then** publish `dslinter` to npm (or publish after the workflow finishes so installs resolve the assets).
|
|
29
|
+
|
|
30
|
+
Environment variables:
|
|
31
|
+
|
|
32
|
+
| Variable | Purpose |
|
|
33
|
+
|----------|---------|
|
|
34
|
+
| `DSLINT_SKIP_DOWNLOAD=1` | Skip postinstall download (air-gapped / you only use `PATH`). |
|
|
35
|
+
| `DSLINT_RELEASE_TAG` | Override release tag (default `v` + `dslinter` version from `package.json`). |
|
|
36
|
+
| `DSLINT_GITHUB_REPO` | Override `owner/repo` for downloads (default `jrmybtlr/DSLint`). |
|
|
37
|
+
|
|
38
|
+
### How this differs from `oxlint`
|
|
39
|
+
|
|
40
|
+
**`oxlint`** on npm ships **Node native addons** as **`optionalDependencies`** (`@oxlint/binding-darwin-arm64`, …) built with **napi-rs** — each package is a small prebuilt library loaded by Node.
|
|
41
|
+
|
|
42
|
+
**`dslint`** is a **standalone executable**. The practical pattern here is **download on install** from **GitHub Releases** (similar in spirit to tools that pull a platform binary once), instead of publishing dozens of `@dslinter/binding-*` packages.
|
|
43
|
+
|
|
44
|
+
### If there is no matching release asset yet
|
|
45
|
+
|
|
46
|
+
You’ll see a **warning** during install (install still succeeds) and **`dslinter`** falls back to **`dslint` on your PATH**, or prints install hints if missing:
|
|
23
47
|
|
|
24
48
|
```bash
|
|
25
49
|
npx dslinter /path/to/repo --json -o dslint-report.json
|
|
26
50
|
```
|
|
27
51
|
|
|
28
|
-
If `dslint` is not installed, you’ll get a short error with install hints (`cargo install dslint` once published, or build from this repo).
|
|
29
|
-
|
|
30
52
|
| Distribution | How users get `dslint` |
|
|
31
53
|
|--------------|-------------------------|
|
|
32
|
-
| **GitHub Releases** |
|
|
33
|
-
| **
|
|
34
|
-
| **
|
|
35
|
-
|
|
36
|
-
**Suggested path for this repo:** ship official binaries via **GitHub Releases** (CI builds with `cargo build --release` on `ubuntu`, `macos`, `windows`).
|
|
54
|
+
| **npm + GitHub Releases** | Default: postinstall download when release `vX.Y.Z` includes your platform asset. |
|
|
55
|
+
| **GitHub Releases** | Manual download of `dslint-*` from the release; add to `PATH`. |
|
|
56
|
+
| **crates.io** | `cargo install dslint` once published. |
|
|
37
57
|
|
|
38
|
-
Typical usage
|
|
58
|
+
Typical usage:
|
|
39
59
|
|
|
40
60
|
```bash
|
|
41
61
|
dslint /path/to/repo --json -o dslint-report.json
|
|
42
62
|
# or --serve for live reload while developing a dashboard
|
|
43
|
-
# or equivalently:
|
|
63
|
+
# or equivalently (vendored or PATH):
|
|
44
64
|
dslinter /path/to/repo --json -o dslint-report.json
|
|
45
65
|
```
|
|
46
66
|
|
package/bin/dslinter.mjs
CHANGED
|
@@ -1,26 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* primarily the React dashboard; report generation stays in `dslint`.
|
|
3
|
+
* Runs the `dslint` scanner: prefers a vendored binary from postinstall, else `dslint` on PATH.
|
|
5
4
|
*/
|
|
6
5
|
import { spawnSync } from "node:child_process";
|
|
7
|
-
import
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { vendorBinaryPath } from "../scripts/resolve-dslint-binary.mjs";
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const packageRoot = join(__dirname, "..");
|
|
8
13
|
|
|
9
14
|
const args = process.argv.slice(2);
|
|
10
|
-
|
|
15
|
+
|
|
16
|
+
const vendored = vendorBinaryPath(packageRoot);
|
|
17
|
+
const cmd = vendored && existsSync(vendored) ? vendored : "dslint";
|
|
18
|
+
const child = spawnSync(cmd, args, { stdio: "inherit" });
|
|
11
19
|
|
|
12
20
|
if (child.error && "code" in child.error && child.error.code === "ENOENT") {
|
|
13
21
|
process.stderr.write(`dslint: command not found.
|
|
14
22
|
|
|
15
|
-
The dslinter package
|
|
23
|
+
The dslinter package can download a prebuilt dslint on npm install when a matching
|
|
24
|
+
GitHub release exists (same tag as this package version, e.g. v0.0.6). See:
|
|
25
|
+
https://github.com/jrmybtlr/DSLint/releases
|
|
16
26
|
|
|
17
|
-
|
|
27
|
+
Otherwise install dslint on PATH:
|
|
18
28
|
|
|
19
29
|
cargo install dslint
|
|
20
|
-
# or
|
|
30
|
+
# or from this repo:
|
|
21
31
|
cargo install --path .
|
|
22
32
|
|
|
23
|
-
|
|
33
|
+
Skip auto-download (air-gapped): DSLINT_SKIP_DOWNLOAD=1
|
|
24
34
|
`);
|
|
25
35
|
process.exit(127);
|
|
26
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dslinter",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "DSLinter dashboard UI: playground shell, token wall, and governance panels (consumes dslint-report.json).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"types": "./src/index.ts",
|
|
47
47
|
"files": [
|
|
48
48
|
"bin",
|
|
49
|
+
"scripts",
|
|
49
50
|
"src",
|
|
50
51
|
"components.json",
|
|
51
52
|
"README.md",
|
|
@@ -85,6 +86,7 @@
|
|
|
85
86
|
"vitest": "^3.2.4"
|
|
86
87
|
},
|
|
87
88
|
"scripts": {
|
|
88
|
-
"test": "vitest run"
|
|
89
|
+
"test": "vitest run",
|
|
90
|
+
"postinstall": "node ./scripts/ensure-dslint.mjs"
|
|
89
91
|
}
|
|
90
92
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort download of the prebuilt `dslint` CLI for this platform.
|
|
3
|
+
* Runs on `npm install dslinter` (postinstall). Exits 0 even when the binary
|
|
4
|
+
* is missing (no release yet / offline) so installs never fail.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { chmod, mkdir, stat } from "node:fs/promises";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { releaseAssetBaseName, vendorBinaryPath } from "./resolve-dslint-binary.mjs";
|
|
11
|
+
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const packageRoot = join(__dirname, "..");
|
|
14
|
+
|
|
15
|
+
async function pathExists(p) {
|
|
16
|
+
try {
|
|
17
|
+
await stat(p);
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readPackageVersion() {
|
|
25
|
+
const pkg = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
|
|
26
|
+
return pkg.version;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function releaseTag(version) {
|
|
30
|
+
const o = process.env.DSLINT_RELEASE_TAG?.trim();
|
|
31
|
+
if (o) return o.startsWith("v") ? o : `v${o}`;
|
|
32
|
+
return `v${version}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function main() {
|
|
36
|
+
if (process.env.DSLINT_SKIP_DOWNLOAD === "1") return;
|
|
37
|
+
|
|
38
|
+
const dest = vendorBinaryPath(packageRoot);
|
|
39
|
+
if (await pathExists(dest)) return;
|
|
40
|
+
|
|
41
|
+
const asset = releaseAssetBaseName();
|
|
42
|
+
if (!asset) {
|
|
43
|
+
console.warn(
|
|
44
|
+
`[dslinter] No prebuilt dslint for ${process.platform}-${process.arch}. Install Rust and put dslint on PATH, or set DSLINT_SKIP_DOWNLOAD=1.`,
|
|
45
|
+
);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const version = readPackageVersion();
|
|
50
|
+
const tag = releaseTag(version);
|
|
51
|
+
const repo = process.env.DSLINT_GITHUB_REPO?.trim() || "jrmybtlr/DSLint";
|
|
52
|
+
const url = `https://github.com/${repo}/releases/download/${tag}/${asset}`;
|
|
53
|
+
|
|
54
|
+
const vendorDir = join(packageRoot, "vendor");
|
|
55
|
+
await mkdir(vendorDir, { recursive: true });
|
|
56
|
+
|
|
57
|
+
const tmp = `${dest}.part`;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const res = await fetch(url, { redirect: "follow" });
|
|
61
|
+
if (res.status === 404) {
|
|
62
|
+
console.warn(
|
|
63
|
+
`[dslinter] No GitHub release asset at ${url}\n` +
|
|
64
|
+
` Create release ${tag} with ${asset} (see .github/workflows/release-dslint-binaries.yml), or install dslint via cargo / PATH.`,
|
|
65
|
+
);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!res.ok) {
|
|
69
|
+
console.warn(`[dslinter] Download failed (${res.status}): ${url}`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
74
|
+
writeFileSync(tmp, buf);
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
if (await pathExists(dest)) unlinkSync(dest);
|
|
78
|
+
} catch {
|
|
79
|
+
/* ignore */
|
|
80
|
+
}
|
|
81
|
+
renameSync(tmp, dest);
|
|
82
|
+
|
|
83
|
+
if (process.platform !== "win32") {
|
|
84
|
+
await chmod(dest, 0o755);
|
|
85
|
+
}
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.warn(`[dslinter] Could not download dslint: ${err instanceof Error ? err.message : err}`);
|
|
88
|
+
try {
|
|
89
|
+
unlinkSync(tmp);
|
|
90
|
+
} catch {
|
|
91
|
+
/* ignore */
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
await main();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Maps the current OS/arch to the GitHub release asset basename (must match CI upload names).
|
|
5
|
+
* @param {NodeJS.Process} [proc]
|
|
6
|
+
* @returns {string | null}
|
|
7
|
+
*/
|
|
8
|
+
export function releaseAssetBaseName(proc = process) {
|
|
9
|
+
const { platform, arch } = proc;
|
|
10
|
+
if (platform === "darwin" && arch === "arm64") return "dslint-aarch64-apple-darwin";
|
|
11
|
+
if (platform === "darwin" && arch === "x64") return "dslint-x86_64-apple-darwin";
|
|
12
|
+
if (platform === "linux" && arch === "x64") return "dslint-x86_64-unknown-linux-gnu";
|
|
13
|
+
if (platform === "linux" && arch === "arm64") return "dslint-aarch64-unknown-linux-gnu";
|
|
14
|
+
if (platform === "win32" && arch === "x64") return "dslint-x86_64-pc-windows-msvc.exe";
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} packageRoot — directory containing package.json
|
|
20
|
+
* @param {NodeJS.Process} [proc]
|
|
21
|
+
*/
|
|
22
|
+
export function vendorBinaryPath(packageRoot, proc = process) {
|
|
23
|
+
const name = proc.platform === "win32" ? "dslint.exe" : "dslint";
|
|
24
|
+
return join(packageRoot, "vendor", name);
|
|
25
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
releaseAssetBaseName,
|
|
5
|
+
vendorBinaryPath,
|
|
6
|
+
} from "../scripts/resolve-dslint-binary.mjs";
|
|
7
|
+
|
|
8
|
+
function proc(platform: string, arch: string): NodeJS.Process {
|
|
9
|
+
return { platform, arch } as NodeJS.Process;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe("releaseAssetBaseName", () => {
|
|
13
|
+
it("maps darwin arm64", () => {
|
|
14
|
+
expect(releaseAssetBaseName(proc("darwin", "arm64"))).toBe(
|
|
15
|
+
"dslint-aarch64-apple-darwin",
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("maps linux x64", () => {
|
|
20
|
+
expect(releaseAssetBaseName(proc("linux", "x64"))).toBe(
|
|
21
|
+
"dslint-x86_64-unknown-linux-gnu",
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("maps win32 x64", () => {
|
|
26
|
+
expect(releaseAssetBaseName(proc("win32", "x64"))).toBe(
|
|
27
|
+
"dslint-x86_64-pc-windows-msvc.exe",
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("returns null for unknown", () => {
|
|
32
|
+
expect(releaseAssetBaseName(proc("freebsd", "x64"))).toBeNull();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("vendorBinaryPath", () => {
|
|
37
|
+
it("uses dslint.exe on Windows", () => {
|
|
38
|
+
expect(vendorBinaryPath(join("/", "pkg"), proc("win32", "x64"))).toBe(
|
|
39
|
+
join("/", "pkg", "vendor", "dslint.exe"),
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("uses dslint on Unix", () => {
|
|
44
|
+
expect(vendorBinaryPath(join("/", "pkg"), proc("linux", "x64"))).toBe(
|
|
45
|
+
join("/", "pkg", "vendor", "dslint"),
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
});
|