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 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
- This package exposes a **`dslinter` binary** that **forwards every argument to `dslint` on your PATH** (same flags as the Rust CLI: `--json`, `-o`, `--serve`, etc.). It does **not** bundle the Rust scanner.
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** | Attach `dslint` (or `dslint.exe`) per OS/arch to each release; document download + `PATH`. |
33
- | **crates.io** | Publish the crate as `dslint`; users run `cargo install dslint` (Rust toolchain required). |
34
- | **npm** | `npx dslinter …` after `dslint` is on `PATH`, or a future dedicated wrapper with prebuilds. |
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 after the CLI is on `PATH`:
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
- * Forwards to the `dslint` Rust CLI on PATH. The published `dslinter` package is
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 process from "node:process";
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
- const child = spawnSync("dslint", args, { stdio: "inherit" });
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 ships the dashboard UI. JSON reports and scans are produced by the dslint CLI (Rust).
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
- Install dslint, then run dslinter again (this command forwards to dslint):
27
+ Otherwise install dslint on PATH:
18
28
 
19
29
  cargo install dslint
20
- # or build from the DSLint repo:
30
+ # or from this repo:
21
31
  cargo install --path .
22
32
 
23
- More: https://github.com/jrmybtlr/DSLint
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.6",
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
+ });