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
5 changes: 2 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#!/usr/bin/env node
import fs from "node:fs/promises"
import path from "node:path"
import { spawnSync } from "node:child_process"

import { COMBINED_PATH, VERSION } from "./constants"
import { findProgram } from "./program"
import { downloadBinary, findRelease } from "./release"

async function main() {
Expand All @@ -24,8 +24,7 @@ async function main() {
main().catch(console.error)

async function execute() {
const [name] = await fs.readdir(path.join(COMBINED_PATH, "bin"))
const program = path.join(COMBINED_PATH, "bin", name)
const program = await findProgram(COMBINED_PATH, process.platform)
await fs.chmod(program, 0o755)
const { status } = spawnSync(program, process.argv.slice(2), {
stdio: "inherit",
Expand Down
63 changes: 63 additions & 0 deletions src/program.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import fs from "node:fs/promises"
import path from "node:path"
import { describe, it, beforeEach, afterEach } from "node:test"
import assert from "node:assert/strict"

import tmp from "tmp-promise"
import { findProgram } from "./program"

describe("findProgram", () => {
let directory: tmp.DirectoryResult

beforeEach(async () => {
directory = await tmp.dir({ unsafeCleanup: true })
})

afterEach(async () => {
await directory.cleanup()
})

it("should find the binary at the archive root", async () => {
// Arrange - Given
const input = { directory: directory.path, platform: "linux" }
const program = path.join(input.directory, "editorconfig-checker")
await fs.writeFile(program, "")

// Act - When
const output = await findProgram(input.directory, input.platform)

// Assert - Then
const expected = program
assert.equal(output, expected)
})

it("should find the .exe binary at the archive root on windows", async () => {
// Arrange - Given
const input = { directory: directory.path, platform: "win32" }
const program = path.join(input.directory, "editorconfig-checker.exe")
await fs.writeFile(program, "")

// Act - When
const output = await findProgram(input.directory, input.platform)

// Assert - Then
const expected = program
assert.equal(output, expected)
})

it("should fall back to the legacy bin/ directory", async () => {
// Arrange - Given
const input = { directory: directory.path, platform: "linux" }
const binPath = path.join(input.directory, "bin")
await fs.mkdir(binPath)
const program = path.join(binPath, "ec-linux-amd64")
await fs.writeFile(program, "")

// Act - When
const output = await findProgram(input.directory, input.platform)

// Assert - Then
const expected = program
assert.equal(output, expected)
})
})
24 changes: 24 additions & 0 deletions src/program.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import fs from "node:fs/promises"
import path from "node:path"

import { NAME } from "./constants"

export async function findProgram(directory: string, platform: string) {
const executableName = platform === "win32" ? `${NAME}.exe` : NAME
const currentProgram = path.join(directory, executableName)
if (await exists(currentProgram)) {
return currentProgram
}
const legacyBinPath = path.join(directory, "bin")
const [legacyName] = await fs.readdir(legacyBinPath)
return path.join(legacyBinPath, legacyName)
}

async function exists(filePath: string) {
try {
await fs.stat(filePath)
return true
} catch {
return false
}
}
125 changes: 124 additions & 1 deletion src/release.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import { describe, it, beforeEach, afterEach } from "node:test"
import assert from "node:assert/strict"

import { ProxyServer, createProxy } from "proxy"
import { proxiedFetch } from "./release"
import {
findFirstMatchingAsset,
getAssetPrefixes,
proxiedFetch,
} from "./release"

const oldEnv = process.env

Expand Down Expand Up @@ -84,3 +88,122 @@ describe("proxiedFetch", () => {
assert.equal(proxyConnectionEstablished, true)
})
})

describe("getAssetPrefixes", () => {
it("should prefer the current asset name and fall back to the legacy one", () => {
// Arrange - Given
const input = { platform: "linux", arch: "x64" }

// Act - When
const output = getAssetPrefixes(input.platform, input.arch)

// Assert - Then
const expected = ["editorconfig-checker-linux-amd64", "ec-linux-amd64"]
assert.deepEqual(output, expected)
})

it("should map win32 and x32 to the release naming", () => {
// Arrange - Given
const input = { platform: "win32", arch: "x32" }

// Act - When
const output = getAssetPrefixes(input.platform, input.arch)

// Assert - Then
const expected = ["editorconfig-checker-windows-386", "ec-windows-386"]
assert.deepEqual(output, expected)
})

it("should accept the universal darwin binary", () => {
// Arrange - Given
const input = { platform: "darwin", arch: "arm64" }

// Act - When
const output = getAssetPrefixes(input.platform, input.arch)

// Assert - Then
const expected = [
"editorconfig-checker-darwin-arm64",
"editorconfig-checker-darwin-all",
"ec-darwin-arm64",
]
assert.deepEqual(output, expected)
})
})

describe("findFirstMatchingAsset", () => {
const legacyAssets = [
{ name: "checksums.txt" },
{ name: "ec-linux-amd64.tar.gz" },
{ name: "ec-linux-amd64.tar.gz.sbom.json" },
]
const currentAssets = [
{ name: "editorconfig-checker-darwin-all.tar.gz" },
{ name: "editorconfig-checker-linux-amd64.tar.gz" },
{ name: "editorconfig-checker-windows-amd64.tar.gz" },
{ name: "editorconfig-checker-windows-amd64.zip" },
]

it("should pick the current asset when both are published", () => {
// Arrange - Given
const input = {
assets: [...legacyAssets, ...currentAssets],
assetPrefixes: getAssetPrefixes("linux", "x64"),
}

// Act - When
const output = findFirstMatchingAsset(input.assets, input.assetPrefixes)

// Assert - Then
const expected = { name: "editorconfig-checker-linux-amd64.tar.gz" }
assert.deepEqual(output, expected)
})

it("should pick the legacy asset for releases before the rename", () => {
// Arrange - Given
const input = {
assets: legacyAssets,
assetPrefixes: getAssetPrefixes("linux", "x64"),
}

// Act - When
const output = findFirstMatchingAsset(input.assets, input.assetPrefixes)

// Assert - Then
const expected = { name: "ec-linux-amd64.tar.gz" }
assert.deepEqual(output, expected)
})

it("should pick the universal binary on darwin", () => {
// Arrange - Given
const input = {
assets: currentAssets,
assetPrefixes: getAssetPrefixes("darwin", "arm64"),
}

// Act - When
const output = findFirstMatchingAsset(input.assets, input.assetPrefixes)

// Assert - Then
const expected = { name: "editorconfig-checker-darwin-all.tar.gz" }
assert.deepEqual(output, expected)
})

it("should ignore sbom and checksum files", () => {
// Arrange - Given
const input = {
assets: [
{ name: "checksums.txt" },
{ name: "ec-linux-amd64.tar.gz.sbom.json" },
],
assetPrefixes: getAssetPrefixes("linux", "x64"),
}

// Act - When
const output = findFirstMatchingAsset(input.assets, input.assetPrefixes)

// Assert - Then
const expected = undefined
assert.equal(output, expected)
})
})
45 changes: 32 additions & 13 deletions src/release.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@ const octokit = new Octokit({

export async function findRelease(version: string) {
const release = await getRelease(version)
const releasePrefix = getAssetPrefix()
const matchedAsset = release.data.assets.find(({ name }) => {
return (
name.startsWith(releasePrefix) &&
(name.endsWith(".tar.gz") || name.endsWith(".zip"))
)
})
const assetPrefixes = getAssetPrefixes(os.platform(), os.arch())
const matchedAsset = findFirstMatchingAsset(
release.data.assets,
assetPrefixes,
)
if (!matchedAsset) {
throw new Error(`The binary '${releasePrefix}*' not found`)
throw new Error(`The binary '${assetPrefixes.join("*' or '")}*' not found`)
}
return [
release.data.name,
Expand Down Expand Up @@ -54,7 +52,7 @@ export async function downloadBinary(assetId: number, assetFiletype: string) {
const outputFile = createWriteStream(tmpfile.path)
await pipeline(assetStream, outputFile)

if (assetFiletype === ".zip") {
if (assetFiletype === "zip") {
const zip = new admzip(tmpfile.path)
zip.extractAllTo(COMBINED_PATH, true)
} else {
Expand Down Expand Up @@ -87,16 +85,37 @@ function getRelease(version: string) {
return getReleaseByTag({ owner: NAME, repo: NAME, tag: version })
}

function getAssetPrefix() {
let platform: string = os.platform()
export function findFirstMatchingAsset<Asset extends { name: string }>(
assets: ReadonlyArray<Asset>,
assetPrefixes: string[],
) {
for (const assetPrefix of assetPrefixes) {
const matchedAsset = assets.find(({ name }) => {
return (
name.startsWith(assetPrefix) &&
(name.endsWith(".tar.gz") || name.endsWith(".zip"))
)
})
if (matchedAsset) {
return matchedAsset
}
}
return undefined
}

export function getAssetPrefixes(platform: string, arch: string) {
if (platform === "win32") {
platform = "windows"
}
let arch: string = os.arch()
if (arch === "x32") {
arch = "386"
} else if (arch === "x64") {
arch = "amd64"
}
return `ec-${platform}-${arch}`
const currentAssetPrefixes = [`${NAME}-${platform}-${arch}`]
if (platform === "darwin") {
currentAssetPrefixes.push(`${NAME}-darwin-all`)
}
const legacyAssetPrefix = `ec-${platform}-${arch}`
return [...currentAssetPrefixes, legacyAssetPrefix]
}
Loading