Skip to content

Commit

Permalink
Rewrite task
Browse files Browse the repository at this point in the history
  • Loading branch information
JamieMagee committed Oct 23, 2024
1 parent faca2ee commit 1d42f73
Show file tree
Hide file tree
Showing 11 changed files with 243 additions and 62 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Build

on:
push:
branches:
- main
pull_request:
branches:
- main

permissions: read-all

jobs:
build:
permissions:
attestations: write
contents: read
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
with:
persist-credentials: false

- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
node-version: '20'

- run: npm install

- run: npm run lint

- run: npm run build
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules/
dist/
*.vsix
*.vsix
*.tar.gz
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
save-exact = true
Binary file added assets/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion esbuild.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { build } from 'esbuild';
import { copy } from 'esbuild-plugin-copy';

await build({
entryPoints: ['src/index.ts'],
entryPoints: ['src/index.mts'],
bundle: true,
platform: 'node',
format: 'esm',
outfile: 'dist/index.mjs',
minify: true,
sourcemap: true,
plugins: [
copy({
assets: {
Expand Down
8 changes: 8 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"private": true,
"scripts": {
"build": "node esbuild.mjs",
"lint": "biome lint",
"lint": "biome check --write ./src",
"format": "biome format --write ./src",
"package": "tfx extension create --rev-version",
"publish": "tfx extension publish"
},
Expand All @@ -19,6 +20,7 @@
"devDependencies": {
"@biomejs/biome": "1.9.3",
"@tsconfig/node20": "20.1.4",
"@tsconfig/strictest": "2.0.5",
"@types/node": "20.16.11",
"esbuild": "0.24.0",
"esbuild-plugin-copy": "^2.1.1",
Expand Down
181 changes: 181 additions & 0 deletions src/index.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import console from "node:console";
import crypto from "node:crypto";
import { spawn } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import process from "node:process";
import { Readable } from "node:stream";
import { finished } from "node:stream/promises";
import * as tar from "tar";

/**
* Get the latest version of Scorecard from GitHub.
* @async
* @returns {Promise<string>} The latest version tag.
* @throws {Error} If the fetch fails.
*/
async function getLatestVersion(): Promise<string> {
const response = await fetch(
"https://api.github.com/repos/ossf/scorecard/releases/latest",
);
if (!response.ok) {
throw new Error(`Error fetching latest version: ${response.statusText}`);
}
const data = await response.json() as { tag_name: string };
return data.tag_name;
}

/**
* Get the current operating system.
* @returns { "darwin" | "linux" | "windows" } The current operating system.
* @throws {Error} If the operating system is unsupported.
*/
function getOs(): "darwin" | "linux" | "windows" {
switch (os.platform()) {
case "darwin":
return "darwin";
case "linux":
return "linux";
case "win32":
return "windows";
default:
throw new Error(`Unsupported OS: ${os.platform()}`);
}
}

/**
* Get the current architecture.
* @returns { "amd64" | "arm64" } The current architecture.
* @throws {Error} If the architecture is unsupported.
*/
function getArch(): "amd64" | "arm64" {
switch (os.arch()) {
case "x64":
return "amd64";
case "arm64":
return "arm64";
default:
throw new Error(`Unsupported architecture: ${os.arch()}`);
}
}

/**
* Get the download URL for the Scorecard binary.
* @async
* @returns {Promise<string>} The download URL for the Scorecard binary.
*/
async function getDownloadUrl(): Promise<string> {
const version = await getLatestVersion();
const os = getOs();
const arch = getArch();
return `https://github.com/ossf/scorecard/releases/download/${version}/scorecard_${version.substring(1)}_${os}_${arch}.tar.gz`;
}

/**
* Download a file from a URL to a destination path.
* @async
* @param url The URL to download from.
* @returns {Promise<void>} A promise that resolves when the download is complete.
* @throws {Error} If the fetch fails.
*/
async function downloadFile(url: string): Promise<void> {
const filename = path.basename(url);
const { body } = await fetch(url);
if (!body) {
throw new Error(`Failed to fetch ${url}`);
}
const fileStream = fs.createWriteStream(filename);
await finished(Readable.fromWeb(body).pipe(fileStream));
}

/**
* Verify the checksum of the downloaded Scorecard binary.
* @async
* @param downloadUrl The URL to the Scorecard download.
* @returns {Promise<void>} A promise that resolves when the checksum is verified.
* @throws {Error} If the checksum verification fails.
*/
async function verifyChecksum(downloadUrl: string): Promise<void> {
const checksumUrl = `${path.dirname(downloadUrl)}/scorecard_checksums.txt`;
const response = await fetch(checksumUrl);
if (!response.ok) {
throw new Error(`Error fetching checksum file: ${response.statusText}`);
}
const checksumData = await response.text();

const checksumLines = checksumData.split("\n");
const filename = path.basename(downloadUrl);
const expectedChecksum = checksumLines
.find((line) => line.includes(filename))
?.split(" ")[0];

if (!expectedChecksum) {
throw new Error(`Checksum not found for ${downloadUrl}`);
}

const fileBuffer = fs.readFileSync(filename);
const hash = crypto.createHash("sha256").update(fileBuffer).digest("hex");
if (hash !== expectedChecksum) {
throw new Error(`Checksum verification failed for ${filename}`);
}
}

/**
* Extract a .tar.gz file to a destination directory.
* @async
* @param filePath The path to the .tar.gz file.
* @returns {Promise<string>} The path to the extracted Scorecard binary.
*/
async function extractTarGz(filePath: string): Promise<string> {
const dest = path.join(os.tmpdir(), "scorecard");
fs.mkdirSync(dest, { recursive: true });
await tar.x({
file: filePath,
cwd: dest,
gzip: true,
filter: (file) => file.startsWith("scorecard"),
});
const suffix = getOs() === "windows" ? ".exe" : "";
const oldName = path.join(dest, `scorecard-${getOs()}-${getArch()}`, suffix);
const newName = path.join(dest, "scorecard", suffix);
fs.renameSync(oldName, newName);
return newName;
}

/**
* Run the Scorecard binary.
* @async
* @param binary The path to the Scorecard binary.
* @returns {Promise<void>} A promise that resolves when the command is executed.
*/
async function runScorecard(binary: string): Promise<void> {
const child = spawn(binary, ["--repo", "https://github.com/ossf/scorecard"], {
env: { SCORECARD_EXPERIMENTAL: "true" },
});
child.stdout.on("data", (data) => {
console.log(data.toString());
});
child.stderr.on("data", (data) => {
console.error(data.toString());
});
}

/**
* The main entrypoint of the task.
* @async
* @returns {Promise<void>} A promise that resolves when the task is complete.
*/
async function run(): Promise<void> {
const downloadUrl = await getDownloadUrl();
await downloadFile(downloadUrl);
await verifyChecksum(downloadUrl);
const binary = await extractTarGz(path.basename(downloadUrl));
await runScorecard(binary);
}

// Run the main function
run().catch((error) => {
console.error(error);
process.exit();
});
56 changes: 0 additions & 56 deletions src/index.ts

This file was deleted.

11 changes: 9 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
{
"extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": {}
"extends": [
"@tsconfig/strictest/tsconfig.json",
"@tsconfig/node20/tsconfig.json"
],
"compilerOptions": {
"module": "Preserve",
"moduleResolution": "Bundler",
"noEmit": true
}
}
5 changes: 4 additions & 1 deletion vss-extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"manifestVersion": 1,
"id": "scorecard",
"name": "OSSF Scorecard",
"version": "0.0.10",
"version": "0.0.13",
"public": false,
"publisher": "JamieMagee",
"targets": [
Expand All @@ -26,6 +26,9 @@
}
},
"tags": ["scorecard", "security", "ossf"],
"icons": {
"default": "assets/icon.png"
},
"links": {
"home": {
"uri": "https://scorecard.dev/"
Expand Down

0 comments on commit 1d42f73

Please sign in to comment.