dslinter 0.0.10 → 0.0.12
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 +24 -0
- package/README.md +6 -4
- package/bin/dslinter.mjs +1 -1
- package/package.json +4 -4
- package/scripts/ensure-dslint.mjs +63 -32
- package/scripts/github-release.mjs +109 -0
- package/scripts/print-missing-scanner.mjs +4 -2
- package/scripts/resolve-dslint-binary.mjs +38 -0
- package/src/resolve-dslint-binary.test.ts +52 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.0.12
|
|
4
|
+
|
|
5
|
+
[compare changes](https://github.com/jrmybtlr/DSLinter/compare/v0.0.11...v0.0.12)
|
|
6
|
+
|
|
7
|
+
### 🚀 Enhancements
|
|
8
|
+
|
|
9
|
+
- Enhance GitHub release asset handling and logging ([4a62b9f](https://github.com/jrmybtlr/DSLinter/commit/4a62b9f))
|
|
10
|
+
|
|
11
|
+
### ❤️ Contributors
|
|
12
|
+
|
|
13
|
+
- Jeremy Butler <jeremy.butler@laravel.com>
|
|
14
|
+
|
|
15
|
+
## v0.0.11
|
|
16
|
+
|
|
17
|
+
[compare changes](https://github.com/jrmybtlr/DSLinter/compare/v0.0.10...v0.0.11)
|
|
18
|
+
|
|
19
|
+
### 🩹 Fixes
|
|
20
|
+
|
|
21
|
+
- Update repository references from DSLint to DSLinter ([2dbae8d](https://github.com/jrmybtlr/DSLinter/commit/2dbae8d))
|
|
22
|
+
|
|
23
|
+
### ❤️ Contributors
|
|
24
|
+
|
|
25
|
+
- Jeremy Butler <jeremy.butler@laravel.com>
|
|
26
|
+
|
|
3
27
|
## v0.0.10
|
|
4
28
|
|
|
5
29
|
[compare changes](https://github.com/jrmybtlr/DSLint/compare/v0.0.9...v0.0.10)
|
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ The **`dslinter` binary** runs the **`dslint`** scanner with the same flags as t
|
|
|
25
25
|
|
|
26
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
27
|
|
|
28
|
-
**Release workflow:** push git tag `v*` (after bumping the npm version) so [.github/workflows/release-dslint-binaries.yml](https://github.com/jrmybtlr/
|
|
28
|
+
**Release workflow:** push git tag `v*` (after bumping the npm version) so [.github/workflows/release-dslint-binaries.yml](https://github.com/jrmybtlr/DSLinter/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
29
|
|
|
30
30
|
Environment variables:
|
|
31
31
|
|
|
@@ -33,7 +33,9 @@ Environment variables:
|
|
|
33
33
|
|----------|---------|
|
|
34
34
|
| `DSLINT_SKIP_DOWNLOAD=1` | Skip postinstall download (air-gapped / you only use `PATH`). |
|
|
35
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/
|
|
36
|
+
| `DSLINT_GITHUB_REPO` | Override `owner/repo` for downloads (default from `package.json` → `jrmybtlr/DSLinter`). |
|
|
37
|
+
| `DSLINT_VERBOSE=1` | Log which GitHub releases/assets were tried when downloading. |
|
|
38
|
+
| `GITHUB_TOKEN` / `GH_TOKEN` | Optional token for private repos or higher API rate limits. |
|
|
37
39
|
|
|
38
40
|
### How this differs from `oxlint`
|
|
39
41
|
|
|
@@ -45,7 +47,7 @@ Environment variables:
|
|
|
45
47
|
|
|
46
48
|
The crates.io crate **`dslint`** (v0.0.x) is a **different project** (design-file linting). It is **not** this design-system scanner. Installing it will break `npx dslinter` if it ends up on your `PATH`.
|
|
47
49
|
|
|
48
|
-
Use **`cargo install --git https://github.com/jrmybtlr/
|
|
50
|
+
Use **`cargo install --git https://github.com/jrmybtlr/DSLinter dslinter --locked`** or set **`DSLINT_BIN`** to a local `target/release/dslinter` build.
|
|
49
51
|
|
|
50
52
|
### If there is no matching release asset yet
|
|
51
53
|
|
|
@@ -59,7 +61,7 @@ npx dslinter /path/to/repo --json -o dslint-report.json
|
|
|
59
61
|
|--------------|-------------------------|
|
|
60
62
|
| **npm + GitHub Releases** | Default: download when release `vX.Y.Z` includes your platform asset. |
|
|
61
63
|
| **GitHub Releases** | Manual download of `dslinter-*` from the release; run directly or set `DSLINT_BIN`. |
|
|
62
|
-
| **From source** | `cargo install --git https://github.com/jrmybtlr/
|
|
64
|
+
| **From source** | `cargo install --git https://github.com/jrmybtlr/DSLinter dslinter --locked` (not `cargo install dslint`). |
|
|
63
65
|
|
|
64
66
|
Typical usage:
|
|
65
67
|
|
package/bin/dslinter.mjs
CHANGED
|
@@ -43,7 +43,7 @@ async function resolveCommand() {
|
|
|
43
43
|
|
|
44
44
|
const vendored = vendorBinaryPath(packageRoot);
|
|
45
45
|
if (!existsSync(vendored)) {
|
|
46
|
-
await ensureDslintBinary(packageRoot, { quiet:
|
|
46
|
+
await ensureDslintBinary(packageRoot, { quiet: false });
|
|
47
47
|
}
|
|
48
48
|
if (existsSync(vendored)) {
|
|
49
49
|
return vendored;
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dslinter",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
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",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/jrmybtlr/
|
|
9
|
+
"url": "https://github.com/jrmybtlr/DSLinter.git",
|
|
10
10
|
"directory": "packages/dashboard"
|
|
11
11
|
},
|
|
12
12
|
"bugs": {
|
|
13
|
-
"url": "https://github.com/jrmybtlr/
|
|
13
|
+
"url": "https://github.com/jrmybtlr/DSLinter/issues"
|
|
14
14
|
},
|
|
15
|
-
"homepage": "https://github.com/jrmybtlr/
|
|
15
|
+
"homepage": "https://github.com/jrmybtlr/DSLinter#readme",
|
|
16
16
|
"keywords": [
|
|
17
17
|
"dslinter",
|
|
18
18
|
"design-system",
|
|
@@ -7,7 +7,12 @@ import { chmod, mkdir, stat } from "node:fs/promises";
|
|
|
7
7
|
import { dirname, join } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import {
|
|
10
|
-
|
|
10
|
+
fetchReleasesForVersion,
|
|
11
|
+
pickReleaseAsset,
|
|
12
|
+
} from "./github-release.mjs";
|
|
13
|
+
import {
|
|
14
|
+
githubRepoFromPackage,
|
|
15
|
+
releaseAssetCandidateNames,
|
|
11
16
|
vendorBinaryPath,
|
|
12
17
|
} from "./resolve-dslint-binary.mjs";
|
|
13
18
|
|
|
@@ -30,18 +35,10 @@ function readPackageVersion(packageRoot) {
|
|
|
30
35
|
return pkg.version;
|
|
31
36
|
}
|
|
32
37
|
|
|
33
|
-
function
|
|
34
|
-
const
|
|
35
|
-
if (
|
|
36
|
-
return
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function releaseRepo() {
|
|
40
|
-
return process.env.DSLINT_GITHUB_REPO?.trim() || "jrmybtlr/DSLint";
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function assetUrl(tag, asset) {
|
|
44
|
-
return `https://github.com/${releaseRepo()}/releases/download/${tag}/${asset}`;
|
|
38
|
+
function releaseRepo(packageRoot) {
|
|
39
|
+
const override = process.env.DSLINT_GITHUB_REPO?.trim();
|
|
40
|
+
if (override) return override;
|
|
41
|
+
return githubRepoFromPackage(packageRoot);
|
|
45
42
|
}
|
|
46
43
|
|
|
47
44
|
/**
|
|
@@ -52,6 +49,7 @@ function assetUrl(tag, asset) {
|
|
|
52
49
|
export async function ensureDslintBinary(packageRoot = defaultPackageRoot, opts = {}) {
|
|
53
50
|
const { quiet = false } = opts;
|
|
54
51
|
const log = quiet ? () => {} : console.warn.bind(console);
|
|
52
|
+
const verbose = process.env.DSLINT_VERBOSE === "1";
|
|
55
53
|
|
|
56
54
|
if (process.env.DSLINT_SKIP_DOWNLOAD === "1") {
|
|
57
55
|
return pathExists(vendorBinaryPath(packageRoot));
|
|
@@ -60,8 +58,8 @@ export async function ensureDslintBinary(packageRoot = defaultPackageRoot, opts
|
|
|
60
58
|
const dest = vendorBinaryPath(packageRoot);
|
|
61
59
|
if (await pathExists(dest)) return true;
|
|
62
60
|
|
|
63
|
-
const
|
|
64
|
-
if (
|
|
61
|
+
const candidates = releaseAssetCandidateNames();
|
|
62
|
+
if (candidates.length === 0) {
|
|
65
63
|
log(
|
|
66
64
|
`[dslinter] No prebuilt scanner for ${process.platform}-${process.arch}.`,
|
|
67
65
|
);
|
|
@@ -69,22 +67,55 @@ export async function ensureDslintBinary(packageRoot = defaultPackageRoot, opts
|
|
|
69
67
|
}
|
|
70
68
|
|
|
71
69
|
const version = readPackageVersion(packageRoot);
|
|
72
|
-
const
|
|
73
|
-
if (process.env.DSLINT_USE_LATEST_RELEASE !== "0") {
|
|
74
|
-
tagsToTry.push("latest");
|
|
75
|
-
}
|
|
76
|
-
|
|
70
|
+
const repo = releaseRepo(packageRoot);
|
|
77
71
|
const vendorDir = join(packageRoot, "vendor");
|
|
78
72
|
await mkdir(vendorDir, { recursive: true });
|
|
79
73
|
const tmp = `${dest}.part`;
|
|
80
74
|
|
|
81
|
-
|
|
82
|
-
|
|
75
|
+
let releases;
|
|
76
|
+
try {
|
|
77
|
+
releases = await fetchReleasesForVersion(repo, version);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
log(
|
|
80
|
+
`[dslinter] Could not query GitHub releases for ${repo}: ${err instanceof Error ? err.message : err}`,
|
|
81
|
+
);
|
|
82
|
+
if (process.env.GITHUB_TOKEN || process.env.GH_TOKEN) {
|
|
83
|
+
log(" (token was set but request still failed — check repo access)");
|
|
84
|
+
} else {
|
|
85
|
+
log(
|
|
86
|
+
" For private repos, set GITHUB_TOKEN or GH_TOKEN with read access to releases.",
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (releases.length === 0) {
|
|
93
|
+
log(
|
|
94
|
+
`[dslinter] No GitHub release found on ${repo} for v${version}.\n` +
|
|
95
|
+
` Publish tag v${version} with workflow release-dslint-binaries.yml (assets: ${candidates.join(" or ")}).`,
|
|
96
|
+
);
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
for (const release of releases) {
|
|
101
|
+
const asset = pickReleaseAsset(release, candidates);
|
|
102
|
+
if (!asset) {
|
|
103
|
+
if (verbose) {
|
|
104
|
+
log(
|
|
105
|
+
`[dslinter] Release ${release.tag_name} has no asset in ${candidates.join(", ")} (has: ${release.assets.map((a) => a.name).join(", ") || "none"})`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (verbose) {
|
|
112
|
+
log(`[dslinter] Downloading ${asset.name} from ${release.tag_name}…`);
|
|
113
|
+
}
|
|
114
|
+
|
|
83
115
|
try {
|
|
84
|
-
const res = await fetch(
|
|
85
|
-
if (res.status === 404) continue;
|
|
116
|
+
const res = await fetch(asset.browser_download_url, { redirect: "follow" });
|
|
86
117
|
if (!res.ok) {
|
|
87
|
-
log(`[dslinter] Download failed (${res.status}): ${
|
|
118
|
+
log(`[dslinter] Download failed (${res.status}): ${asset.browser_download_url}`);
|
|
88
119
|
continue;
|
|
89
120
|
}
|
|
90
121
|
|
|
@@ -99,15 +130,15 @@ export async function ensureDslintBinary(packageRoot = defaultPackageRoot, opts
|
|
|
99
130
|
if (process.platform !== "win32") {
|
|
100
131
|
await chmod(dest, 0o755);
|
|
101
132
|
}
|
|
102
|
-
if (
|
|
133
|
+
if (release.tag_name !== `v${version}` && !quiet) {
|
|
103
134
|
log(
|
|
104
|
-
`[dslinter] Installed scanner from
|
|
135
|
+
`[dslinter] Installed scanner from release ${release.tag_name} (npm v${version}).`,
|
|
105
136
|
);
|
|
106
137
|
}
|
|
107
138
|
return true;
|
|
108
139
|
} catch (err) {
|
|
109
140
|
log(
|
|
110
|
-
`[dslinter] Could not download: ${err instanceof Error ? err.message : err}`,
|
|
141
|
+
`[dslinter] Could not download ${asset.name}: ${err instanceof Error ? err.message : err}`,
|
|
111
142
|
);
|
|
112
143
|
try {
|
|
113
144
|
unlinkSync(tmp);
|
|
@@ -118,8 +149,8 @@ export async function ensureDslintBinary(packageRoot = defaultPackageRoot, opts
|
|
|
118
149
|
}
|
|
119
150
|
|
|
120
151
|
log(
|
|
121
|
-
`[dslinter]
|
|
122
|
-
`
|
|
152
|
+
`[dslinter] Found releases on ${repo} but none include ${candidates.join(" or ")} for this platform.\n` +
|
|
153
|
+
` Upload platform binaries via .github/workflows/release-dslint-binaries.yml, or set DSLINT_BIN.`,
|
|
123
154
|
);
|
|
124
155
|
return false;
|
|
125
156
|
}
|
|
@@ -129,6 +160,6 @@ const isMain =
|
|
|
129
160
|
fileURLToPath(import.meta.url) === process.argv[1];
|
|
130
161
|
|
|
131
162
|
if (isMain) {
|
|
132
|
-
|
|
133
|
-
process.exit(
|
|
163
|
+
await ensureDslintBinary();
|
|
164
|
+
process.exit(0);
|
|
134
165
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve GitHub release assets via the REST API (works with exact names + legacy names).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const API = "https://api.github.com";
|
|
6
|
+
|
|
7
|
+
function apiHeaders() {
|
|
8
|
+
const headers = {
|
|
9
|
+
Accept: "application/vnd.github+json",
|
|
10
|
+
"User-Agent": "dslinter-npm",
|
|
11
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
12
|
+
};
|
|
13
|
+
const token =
|
|
14
|
+
process.env.GITHUB_TOKEN?.trim() || process.env.GH_TOKEN?.trim();
|
|
15
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
16
|
+
return headers;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} repo `owner/name`
|
|
21
|
+
* @param {string} path
|
|
22
|
+
*/
|
|
23
|
+
async function githubGet(repo, path) {
|
|
24
|
+
const res = await fetch(`${API}/repos/${repo}${path}`, {
|
|
25
|
+
headers: apiHeaders(),
|
|
26
|
+
redirect: "follow",
|
|
27
|
+
});
|
|
28
|
+
if (res.status === 404) return null;
|
|
29
|
+
if (!res.ok) {
|
|
30
|
+
const body = await res.text().catch(() => "");
|
|
31
|
+
throw new Error(`GitHub API ${res.status} for ${path}: ${body.slice(0, 200)}`);
|
|
32
|
+
}
|
|
33
|
+
return res.json();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} repo
|
|
38
|
+
* @param {string} tag e.g. `v0.0.11` or `latest`
|
|
39
|
+
*/
|
|
40
|
+
export async function fetchRelease(repo, tag) {
|
|
41
|
+
if (tag === "latest") {
|
|
42
|
+
return githubGet(repo, "/releases/latest");
|
|
43
|
+
}
|
|
44
|
+
const byTag = await githubGet(repo, `/releases/tags/${encodeURIComponent(tag)}`);
|
|
45
|
+
if (byTag) return byTag;
|
|
46
|
+
// Some releases use tag without `v` prefix.
|
|
47
|
+
if (tag.startsWith("v")) {
|
|
48
|
+
return githubGet(repo, `/releases/tags/${encodeURIComponent(tag.slice(1))}`);
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {string} repo
|
|
55
|
+
* @param {string} npmVersion e.g. `0.0.11`
|
|
56
|
+
*/
|
|
57
|
+
export async function fetchReleasesForVersion(repo, npmVersion) {
|
|
58
|
+
const out = [];
|
|
59
|
+
const seen = new Set();
|
|
60
|
+
|
|
61
|
+
const push = (release) => {
|
|
62
|
+
if (!release?.assets?.length) return;
|
|
63
|
+
const key = release.id ?? release.tag_name;
|
|
64
|
+
if (seen.has(key)) return;
|
|
65
|
+
seen.add(key);
|
|
66
|
+
out.push(release);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
push(await fetchRelease(repo, `v${npmVersion}`));
|
|
70
|
+
push(await fetchRelease(repo, npmVersion));
|
|
71
|
+
push(await fetchRelease(repo, "latest"));
|
|
72
|
+
|
|
73
|
+
if (out.length > 0) return out;
|
|
74
|
+
|
|
75
|
+
const list = await githubGet(repo, "/releases?per_page=30");
|
|
76
|
+
if (!Array.isArray(list)) return out;
|
|
77
|
+
|
|
78
|
+
for (const release of list) {
|
|
79
|
+
const tag = String(release.tag_name ?? "");
|
|
80
|
+
if (tag === `v${npmVersion}` || tag === npmVersion) {
|
|
81
|
+
push(release);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (out.length > 0) return out;
|
|
85
|
+
|
|
86
|
+
// Newest release that has any scanner-looking asset.
|
|
87
|
+
for (const release of list) {
|
|
88
|
+
const names = release.assets?.map((a) => a.name) ?? [];
|
|
89
|
+
if (names.some((n) => n.startsWith("dslinter-") || n.startsWith("dslint-"))) {
|
|
90
|
+
push(release);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @param {string[]} candidateNames
|
|
100
|
+
* @param {{ assets: { name: string; browser_download_url: string }[] }} release
|
|
101
|
+
* @returns {{ name: string; browser_download_url: string } | null}
|
|
102
|
+
*/
|
|
103
|
+
export function pickReleaseAsset(release, candidateNames) {
|
|
104
|
+
for (const name of candidateNames) {
|
|
105
|
+
const asset = release.assets.find((a) => a.name === name);
|
|
106
|
+
if (asset?.browser_download_url) return asset;
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
@@ -16,10 +16,12 @@ To run the design-system scanner:
|
|
|
16
16
|
|
|
17
17
|
1. Re-run after a GitHub release exists for v${version} (prebuilt download), or
|
|
18
18
|
2. Build from this repo and point at it:
|
|
19
|
-
cargo install --git https://github.com/jrmybtlr/
|
|
19
|
+
cargo install --git https://github.com/jrmybtlr/DSLinter dslinter --locked
|
|
20
20
|
export DSLINT_BIN="$(command -v dslinter)"
|
|
21
21
|
npx dslinter ...
|
|
22
22
|
3. Or set DSLINT_BIN to your local target/release/dslinter
|
|
23
23
|
|
|
24
|
-
Releases: https://github.com/jrmybtlr/
|
|
24
|
+
Releases: https://github.com/jrmybtlr/DSLinter/releases
|
|
25
|
+
|
|
26
|
+
Tip: re-run with DSLINT_VERBOSE=1 to see which GitHub releases/assets were tried.
|
|
25
27
|
`);
|
|
@@ -1,8 +1,38 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
1
2
|
import { join } from "node:path";
|
|
2
3
|
|
|
3
4
|
/** CLI binary name (avoids collision with unrelated `dslint` on crates.io). */
|
|
4
5
|
export const CLI_BINARY_NAME = "dslinter";
|
|
5
6
|
|
|
7
|
+
/** Fallback when package.json has no parseable `repository` field. */
|
|
8
|
+
export const DEFAULT_GITHUB_REPO = "jrmybtlr/DSLinter";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {string | { type?: string; url?: string } | undefined} repository
|
|
12
|
+
* @returns {string | null} `owner/repo`
|
|
13
|
+
*/
|
|
14
|
+
export function parseGitHubRepo(repository) {
|
|
15
|
+
if (!repository) return null;
|
|
16
|
+
const url = typeof repository === "string" ? repository : repository.url;
|
|
17
|
+
if (!url) return null;
|
|
18
|
+
const m = String(url).match(/github\.com[/:]([^/]+)\/([^/.]+?)(?:\.git)?\/?$/i);
|
|
19
|
+
return m ? `${m[1]}/${m[2]}` : null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} packageRoot
|
|
24
|
+
*/
|
|
25
|
+
export function githubRepoFromPackage(packageRoot) {
|
|
26
|
+
try {
|
|
27
|
+
const pkg = JSON.parse(
|
|
28
|
+
readFileSync(join(packageRoot, "package.json"), "utf8"),
|
|
29
|
+
);
|
|
30
|
+
return parseGitHubRepo(pkg.repository) ?? DEFAULT_GITHUB_REPO;
|
|
31
|
+
} catch {
|
|
32
|
+
return DEFAULT_GITHUB_REPO;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
6
36
|
/**
|
|
7
37
|
* Maps the current OS/arch to the GitHub release asset basename (must match CI upload names).
|
|
8
38
|
* @param {NodeJS.Process} [proc]
|
|
@@ -28,6 +58,14 @@ export function releaseAssetBaseName(proc = process) {
|
|
|
28
58
|
return null;
|
|
29
59
|
}
|
|
30
60
|
|
|
61
|
+
/** Primary GitHub asset name plus legacy `dslint-*` names from older releases. */
|
|
62
|
+
export function releaseAssetCandidateNames(proc = process) {
|
|
63
|
+
const primary = releaseAssetBaseName(proc);
|
|
64
|
+
if (!primary) return [];
|
|
65
|
+
const legacy = primary.replace(/^dslinter/, "dslint");
|
|
66
|
+
return legacy === primary ? [primary] : [primary, legacy];
|
|
67
|
+
}
|
|
68
|
+
|
|
31
69
|
/**
|
|
32
70
|
* @param {string} packageRoot — directory containing package.json
|
|
33
71
|
* @param {NodeJS.Process} [proc]
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
pickReleaseAsset,
|
|
5
|
+
} from "../scripts/github-release.mjs";
|
|
3
6
|
import {
|
|
4
7
|
CLI_BINARY_NAME,
|
|
8
|
+
DEFAULT_GITHUB_REPO,
|
|
9
|
+
parseGitHubRepo,
|
|
5
10
|
releaseAssetBaseName,
|
|
11
|
+
releaseAssetCandidateNames,
|
|
6
12
|
vendorBinaryPath,
|
|
7
13
|
} from "../scripts/resolve-dslint-binary.mjs";
|
|
8
14
|
|
|
@@ -10,6 +16,52 @@ function proc(platform: string, arch: string): NodeJS.Process {
|
|
|
10
16
|
return { platform, arch } as NodeJS.Process;
|
|
11
17
|
}
|
|
12
18
|
|
|
19
|
+
describe("parseGitHubRepo", () => {
|
|
20
|
+
it("parses https repository url", () => {
|
|
21
|
+
expect(
|
|
22
|
+
parseGitHubRepo("https://github.com/jrmybtlr/DSLinter.git"),
|
|
23
|
+
).toBe("jrmybtlr/DSLinter");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("parses repository object", () => {
|
|
27
|
+
expect(
|
|
28
|
+
parseGitHubRepo({
|
|
29
|
+
type: "git",
|
|
30
|
+
url: "git+https://github.com/jrmybtlr/DSLinter.git",
|
|
31
|
+
}),
|
|
32
|
+
).toBe("jrmybtlr/DSLinter");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("defaults constant points at DSLinter", () => {
|
|
36
|
+
expect(DEFAULT_GITHUB_REPO).toBe("jrmybtlr/DSLinter");
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("releaseAssetCandidateNames", () => {
|
|
41
|
+
it("includes legacy dslint asset name", () => {
|
|
42
|
+
expect(releaseAssetCandidateNames(proc("darwin", "arm64"))).toEqual([
|
|
43
|
+
"dslinter-aarch64-apple-darwin",
|
|
44
|
+
"dslint-aarch64-apple-darwin",
|
|
45
|
+
]);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("pickReleaseAsset", () => {
|
|
50
|
+
it("prefers primary name then legacy", () => {
|
|
51
|
+
const release = {
|
|
52
|
+
assets: [
|
|
53
|
+
{
|
|
54
|
+
name: "dslint-aarch64-apple-darwin",
|
|
55
|
+
browser_download_url: "https://example.com/legacy",
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
expect(
|
|
60
|
+
pickReleaseAsset(release, releaseAssetCandidateNames(proc("darwin", "arm64"))),
|
|
61
|
+
).toMatchObject({ name: "dslint-aarch64-apple-darwin" });
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
13
65
|
describe("releaseAssetBaseName", () => {
|
|
14
66
|
it("maps darwin arm64", () => {
|
|
15
67
|
expect(releaseAssetBaseName(proc("darwin", "arm64"))).toBe(
|