Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
name: Benchmark
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: install Rust stable
uses: dtolnay/rust-toolchain@stable
- name: Run benchmark
Expand All @@ -28,7 +28,7 @@ jobs:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: install Rust stable
uses: dtolnay/rust-toolchain@stable
- name: Build
Expand All @@ -40,14 +40,28 @@ jobs:
name: Integration Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: install Rust stable
uses: dtolnay/rust-toolchain@stable
- name: Setup Bun
uses: oven-sh/setup-bun@v1
uses: oven-sh/setup-bun@v2
- name: Run integration tests
run: make integration_test

vscode_package:
name: VS Code Package
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun install --frozen-lockfile
working-directory: editors/vscode
- name: Package extension
run: bun run package
working-directory: editors/vscode

build:
name: Build ${{ matrix.platform.project }} - ${{ matrix.platform.release_for }}
if: github.event.pull_request.draft == false
Expand Down Expand Up @@ -115,9 +129,9 @@ jobs:
echo "CARGO_HOME=D:\cargo_home" >> $env:GITHUB_ENV
Write-Host "Set CARGO_HOME to D:\cargo_home"
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Build binary
uses: houseabsolute/actions-rust-cross@v1
uses: houseabsolute/actions-rust-cross@v1.0.7
with:
command: ${{ matrix.platform.command }}
target: ${{ matrix.platform.target }}
Expand All @@ -133,7 +147,7 @@ jobs:
fi
cd -
- name: Upload Artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.platform.bin }}-${{ matrix.platform.target }}
path: ${{ matrix.platform.name }}
Expand All @@ -148,11 +162,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
pattern: codebook-lsp*
- name: Make release
uses: softprops/action-gh-release@37fd9d0351a2df198244c8ef9f56d02d1f921e20
uses: softprops/action-gh-release@v3
with:
files: codebook-lsp*/*
prerelease: true
Expand Down
1 change: 1 addition & 0 deletions editors/vscode/.vscodeignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
.vscode/
.vscode-test/
src/
scripts/
tsconfig.json
bun.lock
node_modules/
Expand Down
2 changes: 1 addition & 1 deletion editors/vscode/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Codebook combines [Tree Sitter](https://tree-sitter.github.io) and [Spellbook](h

## Requirements

- **VS Code** `1.80.0` or later
- A recent version of **VS Code**
- **OS:** macOS (x86_64, aarch64), Linux (x86_64, aarch64), Windows (x86_64, arm64)
- No other dependencies. The `codebook-lsp` binary is downloaded and managed automatically on first activation.

Expand Down
118 changes: 39 additions & 79 deletions editors/vscode/bun.lock

Large diffs are not rendered by default.

17 changes: 6 additions & 11 deletions editors/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
},
"homepage": "https://github.com/blopker/codebook",
"engines": {
"vscode": "^1.80.0"
"vscode": "^1.109.0"
},
"categories": [
"Linters",
Expand All @@ -38,6 +38,7 @@
"build": "esbuild src/extension.ts --bundle --outdir=dist --external:vscode --format=cjs --platform=node --sourcemap --minify",
"watch": "esbuild src/extension.ts --bundle --outdir=dist --external:vscode --format=cjs --platform=node --sourcemap --watch",
"typecheck": "tsc -p . --noEmit",
"sync-vscode-engine": "bun run scripts/sync-vscode-engine.ts",
"package": "bun run build && vsce package --no-dependencies",
"publish": "bun run build && vsce publish --no-dependencies"
},
Expand Down Expand Up @@ -76,19 +77,13 @@
}
},
"dependencies": {
"adm-zip": "^0.5.17",
"tar": "^7.5.13",
"vscode-languageclient": "^9.0.1",
"which": "^4.0.0"
"vscode-languageclient": "^9.0.1"
},
"devDependencies": {
"@types/adm-zip": "^0.5.8",
"@types/node": "^20.19.39",
"@types/tar": "^6.1.13",
"@types/vscode": "^1.118.0",
"@types/which": "^3.0.4",
"@types/vscode": "1.109.0",
"@vscode/vsce": "^3.9.1",
"esbuild": "^0.27.7",
"typescript": "^5.9.3"
"esbuild": "0.28.0",
"typescript": "6.0.3"
}
}
65 changes: 65 additions & 0 deletions editors/vscode/scripts/sync-vscode-engine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import fs from "node:fs";
import path from "node:path";
import { $ } from "bun";

const packagePath = path.join(import.meta.dir, "..", "package.json");
const monthsBack = Number.parseInt(process.argv[2] ?? "3", 10);

if (!Number.isInteger(monthsBack) || monthsBack < 1) {
throw new Error("Usage: bun run sync-vscode-engine [months-back]");
}

type RegistryResponse = {
time: Record<string, string>;
};

function compareVersions(a: string, b: string): number {
const aParts = a.split(".").map((part) => Number.parseInt(part, 10));
const bParts = b.split(".").map((part) => Number.parseInt(part, 10));

for (let i = 0; i < Math.max(aParts.length, bParts.length); i += 1) {
const difference = (aParts[i] ?? 0) - (bParts[i] ?? 0);
if (difference !== 0) return difference;
}

return 0;
}

function cutoffDate(months: number): Date {
const cutoff = new Date();
cutoff.setMonth(cutoff.getMonth() - months);
return cutoff;
}

const response = await fetch("https://registry.npmjs.org/@types%2Fvscode");
if (!response.ok) {
throw new Error(`Failed to fetch @types/vscode metadata: ${response.status}`);
}

const registry = (await response.json()) as RegistryResponse;
const cutoff = cutoffDate(monthsBack);
const version = Object.entries(registry.time)
.filter(([candidate]) => /^\d+\.\d+\.\d+$/.test(candidate))
.filter(([, publishedAt]) => new Date(publishedAt) <= cutoff)
.map(([candidate]) => candidate)
.sort(compareVersions)
.at(-1);

if (!version) {
throw new Error(`No @types/vscode version found before ${cutoff.toISOString()}`);
}

const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
const previousEngine = packageJson.engines.vscode;
const previousTypes = packageJson.devDependencies["@types/vscode"];

packageJson.engines.vscode = `^${version}`;
packageJson.devDependencies["@types/vscode"] = version;

fs.writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`);

await $`bun install`;

console.log(`Synced VS Code engine to ${version}`);
console.log(`engines.vscode: ${previousEngine} -> ^${version}`);
console.log(`@types/vscode: ${previousTypes} -> ${version}`);
67 changes: 47 additions & 20 deletions editors/vscode/src/binary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import * as path from "node:path";
import * as os from "node:os";
import * as https from "node:https";
import type { IncomingMessage } from "node:http";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { pipeline } from "node:stream/promises";
import AdmZip from "adm-zip";
import * as tar from "tar";
import which from "which";

const BINARY_BASENAME = "codebook-lsp";
const BINARY_FILENAME =
process.platform === "win32" ? `${BINARY_BASENAME}.exe` : BINARY_BASENAME;
const VERSION_FILENAME = "codebook.version";
const USER_AGENT = "codebook-vscode-extension";
const MAX_REDIRECTS = 5;
const execFileAsync = promisify(execFile);

interface GithubAsset {
name: string;
Expand All @@ -30,8 +30,6 @@ interface GithubRelease {
assets: GithubAsset[];
}

type ArchiveType = "tar-gz" | "zip";

export class CodebookBinaryManager {
private binaryPath?: string;

Expand Down Expand Up @@ -186,12 +184,7 @@ export class CodebookBinaryManager {
);

progress.report({ message: "Extracting archive" });
if (assetInfo.type === "zip") {
const zip = new AdmZip(archivePath);
zip.extractAllTo(versionDir, true);
} else {
await tar.x({ cwd: versionDir, file: archivePath });
}
await extractArchive(archivePath, versionDir);
},
);
} finally {
Expand Down Expand Up @@ -257,11 +250,33 @@ async function ensureExecutable(filePath: string): Promise<void> {
}

async function findOnPath(): Promise<string | undefined> {
const pathEntries = (process.env.PATH ?? "").split(path.delimiter);
const extensions =
process.platform === "win32"
? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";")
: [""];

for (const entry of pathEntries) {
for (const extension of extensions) {
const candidate = path.join(entry, `${BINARY_BASENAME}${extension}`);
if (await isExecutable(candidate)) {
return candidate;
}
}
}

return undefined;
}

async function isExecutable(filePath: string): Promise<boolean> {
try {
const result = await which(BINARY_BASENAME, { nothrow: true });
return result ?? undefined;
await fsp.access(
filePath,
process.platform === "win32" ? fs.constants.F_OK : fs.constants.X_OK,
);
return true;
} catch {
return undefined;
return false;
}
}

Expand All @@ -277,7 +292,6 @@ async function fileExists(filePath: string): Promise<boolean> {
function resolveAssetName(): {
filename: string;
descriptor: string;
type: ArchiveType;
} {
let archPart: string;
if (process.arch === "x64") {
Expand All @@ -290,23 +304,19 @@ function resolveAssetName(): {

let osPart: string;
let extension: string;
let type: ArchiveType;

switch (process.platform) {
case "darwin":
osPart = "apple-darwin";
extension = "tar.gz";
type = "tar-gz";
break;
case "linux":
osPart = "unknown-linux-musl";
extension = "tar.gz";
type = "tar-gz";
break;
case "win32":
osPart = "pc-windows-msvc";
extension = "zip";
type = "zip";
break;
default:
throw new Error(`Unsupported operating system: ${process.platform}`);
Expand All @@ -315,10 +325,27 @@ function resolveAssetName(): {
return {
filename: `codebook-lsp-${archPart}-${osPart}.${extension}`,
descriptor: `${archPart}-${osPart}`,
type,
};
}

async function extractArchive(archivePath: string, destination: string) {
try {
await execFileAsync("tar", ["-xf", archivePath, "-C", destination]);
} catch (error) {
if (process.platform !== "win32") {
throw error;
}

await execFileAsync("powershell", [
"-NoProfile",
"-Command",
"Expand-Archive -LiteralPath $args[0] -DestinationPath $args[1] -Force",
archivePath,
destination,
]);
}
}

function extractReleaseVersion(release: GithubRelease): string {
return release.tag_name ?? release.name ?? String(release.id);
}
Expand Down
Loading