Skip to content
Draft
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
8 changes: 4 additions & 4 deletions .github/workflows/cli.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ jobs:
run: bun run build --filter @cartesi/cli

- name: Test
run: bun run test --filter @cartesi/cli
run: bun test --coverage --coverage-reporter=lcov --coverage-dir=coverage apps/cli/

- name: "Report Coverage"
- name: Coverage Report
uses: 70-10/bun-coverage-report-action@6173866ce2a31456a726ff3f4c91f230bd94a9e9 # v1.0.3
if: always()
uses: davelosert/vitest-coverage-report-action@5a78cb16e761204097ad8a39369ea5d0ff7c8a5d # v2.8.0
with:
working-directory: ./apps/cli
lcov-path: coverage/lcov.info
5 changes: 2 additions & 3 deletions apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@
"rimraf": "^6.0.1",
"ts-node": "^10.9.2",
"tslib": "^2.8.1",
"typescript": "^5.9.2",
"vitest": "^4.0.17"
"typescript": "^5.9.2"
},
"scripts": {
"build": "run-s clean codegen compile",
Expand All @@ -71,7 +70,7 @@
"compile": "bun build.ts",
"lint": "biome lint",
"posttest": "bun lint",
"test": "vitest"
"test": "bun test"
},
"engines": {
"node": ">=20.0.0"
Expand Down
47 changes: 18 additions & 29 deletions apps/cli/src/template.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
import { ensureDir } from "fs-extra";
import { unpackTar } from "modern-tar/fs";
import path from "node:path";
import { pipeline } from "node:stream/promises";
import { createGunzip } from "node:zlib";

export interface DownloadTemplateResult {
dir: string;
source: string;
Expand All @@ -17,37 +11,32 @@ export const download = async (
const repoUrl = "https://github.com/cartesi/application-templates";
const tarballUrl = `https://codeload.github.com/cartesi/application-templates/tar.gz/refs/heads/${branch}`;

// Ensure output directory exists
await ensureDir(out);

// Download the tarball
const response = await fetch(tarballUrl);
if (!response.ok) {
throw new Error(`Failed to download template: ${response.statusText}`);
}

// Stream download → gunzip → extract with filtering
// Create archive from downloaded tarball
// GitHub tarball structure: application-templates-{branch}/{template}/...
// We need to extract only files within the template subdirectory
const extractStream = unpackTar(out, {
filter: (header) => {
// remove first path segment
const pathname = header.name.split("/").splice(1).join("/");
return pathname.startsWith(template);
},
map: (header) => {
// remove first path segment
const pathname = header.name.split("/").splice(1).join("/");
header.name = path.relative(template, pathname);
return header;
},
});
const archive = new Bun.Archive(await response.blob());

await pipeline(
response.body as ReadableStream,
createGunzip(),
extractStream,
);
// Get all files matching the template subdirectory pattern
// The glob pattern matches: application-templates-{branch}/{template}/**
const pattern = `*/${template}/**`;
const files = await archive.files(pattern);

// Write extracted files, stripping the prefix from paths
for (const [archivePath, file] of files) {
// Remove "application-templates-{branch}/{template}/" prefix
const segments = archivePath.split("/");
const relativePath = segments.slice(2).join("/");

if (relativePath) {
const targetPath = `${out}/${relativePath}`;
await Bun.write(targetPath, file);
}
}

return {
dir: out,
Expand Down
Empty file.
44 changes: 26 additions & 18 deletions apps/cli/tests/integration/builder/directory.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "bun:test";
import fs from "fs-extra";
import path from "node:path";
import { describe, expect } from "vitest";
import { build } from "../../../src/builder/directory.js";
import type { DirectoryDriveConfig } from "../../../src/config.js";
import { TEST_SDK } from "../config.js";
import { tmpdirTest } from "./tmpdirTest.js";
import { setupIntegrationTests, TEST_SDK } from "../config.js";
import { cleanupTempDir, createTempDir } from "../tmpdirTest.js";

beforeAll(async () => {
await setupIntegrationTests();
}, { timeout: 60000 });

describe("when building with the directory builder", () => {
const image = TEST_SDK;
let destination: string;

beforeEach(async () => {
destination = await createTempDir();
});

afterEach(async () => {
await cleanupTempDir(destination);
});

tmpdirTest(
it(
"should fail when the directory doesn't exists",
async ({ tmpdir }) => {
const destination = tmpdir;
async () => {
const drive: DirectoryDriveConfig = {
builder: "directory",
directory: path.join(__dirname, "data", "invalid"),
Expand All @@ -25,10 +37,9 @@ describe("when building with the directory builder", () => {
},
);

tmpdirTest(
it(
"should fail when the directory is empty",
async ({ tmpdir }) => {
const destination = tmpdir;
async () => {
const directory = path.join(__dirname, "data", "empty");
fs.ensureDirSync(directory);
const drive: DirectoryDriveConfig = {
Expand All @@ -43,10 +54,9 @@ describe("when building with the directory builder", () => {
},
);

tmpdirTest(
it(
"should pass when the directory is empty but extra size is defined",
async ({ tmpdir }) => {
const destination = tmpdir;
async () => {
const directory = path.join(__dirname, "data", "empty");
fs.ensureDirSync(directory);
const drive: DirectoryDriveConfig = {
Expand All @@ -62,10 +72,9 @@ describe("when building with the directory builder", () => {
},
);

tmpdirTest(
it(
"should pass for a populated directory (ext2)",
async ({ tmpdir }) => {
const destination = tmpdir;
async () => {
const drive: DirectoryDriveConfig = {
builder: "directory",
directory: path.join(__dirname, "fixtures", "sample1"),
Expand All @@ -79,10 +88,9 @@ describe("when building with the directory builder", () => {
},
);

tmpdirTest(
it(
"should pass for a populated directory (sqfs)",
async ({ tmpdir }) => {
const destination = tmpdir;
async () => {
const drive: DirectoryDriveConfig = {
builder: "directory",
directory: path.join(__dirname, "fixtures", "sample1"),
Expand Down
37 changes: 20 additions & 17 deletions apps/cli/tests/integration/builder/docker.test.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "bun:test";
import fs from "fs-extra";
import path from "node:path";
import { beforeEach } from "node:test";
import { describe, expect } from "vitest";
import { build } from "../../../src/builder/docker.js";
import type { DockerDriveConfig } from "../../../src/config.js";
import { TEST_SDK } from "../config.js";
import { tmpdirTest } from "./tmpdirTest.js";
import { setupIntegrationTests, TEST_SDK } from "../config.js";
import { cleanupTempDir, createTempDir } from "./tmpdirTest.js";

beforeAll(async () => {
await setupIntegrationTests();
}, { timeout: 60000 });

describe("when building with the docker builder", () => {
const image = TEST_SDK;
let destination: string;

beforeEach(async () => {
destination = await createTempDir();
});

beforeEach(({ name }) => {
fs.mkdirpSync(path.join(__dirname, "output", name));
afterEach(async () => {
await cleanupTempDir(destination);
});

tmpdirTest("should fail without correct context", async ({ tmpdir }) => {
const destination = tmpdir;
it("should fail without correct context", async () => {
const drive: DockerDriveConfig = {
buildArgs: [],
builder: "docker",
Expand All @@ -32,8 +39,7 @@ describe("when building with the docker builder", () => {
).rejects.toThrow("exit code 1");
});

tmpdirTest("should fail a non-riscv image", async ({ tmpdir }) => {
const destination = tmpdir;
it("should fail a non-riscv image", async () => {
const drive: DockerDriveConfig = {
buildArgs: [],
builder: "docker",
Expand All @@ -50,10 +56,9 @@ describe("when building with the docker builder", () => {
).rejects.toThrow(/no match for platform in manifest/);
});

tmpdirTest(
it(
"should build an ext2 drive with a target definition",
async ({ tmpdir }) => {
const destination = tmpdir;
async () => {
const drive: DockerDriveConfig = {
buildArgs: [],
builder: "docker",
Expand All @@ -72,8 +77,7 @@ describe("when building with the docker builder", () => {
},
);

tmpdirTest("should build an ext2 drive", async ({ tmpdir }) => {
const destination = tmpdir;
it("should build an ext2 drive", async () => {
const drive: DockerDriveConfig = {
buildArgs: [],
builder: "docker",
Expand All @@ -91,8 +95,7 @@ describe("when building with the docker builder", () => {
expect(stat.size).toEqual(93716480);
});

tmpdirTest.skip("should build a sqfs drive", async ({ tmpdir }) => {
const destination = tmpdir;
it.skip("should build a sqfs drive", async () => {
const drive: DockerDriveConfig = {
buildArgs: [],
builder: "docker",
Expand Down
28 changes: 19 additions & 9 deletions apps/cli/tests/integration/builder/empty.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "bun:test";
import fs from "fs-extra";
import path from "node:path";
import { describe, expect } from "vitest";
import { build } from "../../../src/builder/empty.js";
import type { EmptyDriveConfig } from "../../../src/config.js";
import { TEST_SDK } from "../config.js";
import { tmpdirTest } from "./tmpdirTest.js";
import { setupIntegrationTests, TEST_SDK } from "../config.js";
import { cleanupTempDir, createTempDir } from "./tmpdirTest.js";

beforeAll(async () => {
await setupIntegrationTests();
}, { timeout: 60000 });

describe("when building with the empty builder", () => {
const image = TEST_SDK;
let destination: string;

beforeEach(async () => {
destination = await createTempDir();
});

afterEach(async () => {
await cleanupTempDir(destination);
});

tmpdirTest("should fail with an invalid size", async ({ tmpdir }) => {
const destination = tmpdir;
it("should fail with an invalid size", async () => {
const drive: EmptyDriveConfig = {
builder: "empty",
format: "ext2",
Expand All @@ -21,8 +33,7 @@ describe("when building with the empty builder", () => {
);
});

tmpdirTest("should pass and create ext2 drive", async ({ tmpdir }) => {
const destination = tmpdir;
it("should pass and create ext2 drive", async () => {
const driveName = "root.ext2";
const drive: EmptyDriveConfig = {
builder: "empty",
Expand All @@ -38,8 +49,7 @@ describe("when building with the empty builder", () => {
expect(stat.size).toEqual(1 * 1024 * 1024);
});

tmpdirTest("should pass and create raw drive", async ({ tmpdir }) => {
const destination = tmpdir;
it("should pass and create raw drive", async () => {
const driveName = "root.raw";
const drive: EmptyDriveConfig = {
builder: "empty",
Expand Down
25 changes: 19 additions & 6 deletions apps/cli/tests/integration/builder/none.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "bun:test";
import fs from "fs-extra";
import path from "node:path";
import { describe, expect } from "vitest";
import { build } from "../../../src/builder/none.js";
import type { ExistingDriveConfig } from "../../../src/config.js";
import { tmpdirTest } from "./tmpdirTest.js";
import { setupIntegrationTests } from "../config.js";
import { cleanupTempDir, createTempDir } from "./tmpdirTest.js";

beforeAll(async () => {
await setupIntegrationTests();
}, { timeout: 60000 });

describe("when building with the none builder", () => {
tmpdirTest("should not build a missing file", async ({ tmpdir }) => {
const destination = tmpdir;
let destination: string;

beforeEach(async () => {
destination = await createTempDir();
});

afterEach(async () => {
await cleanupTempDir(destination);
});

it("should not build a missing file", async () => {
const drive: ExistingDriveConfig = {
builder: "none",
filename: path.join(__dirname, "data", "missing.ext2"),
Expand All @@ -18,8 +32,7 @@ describe("when building with the none builder", () => {
);
});

tmpdirTest("should just copy an existing drive", async ({ tmpdir }) => {
const destination = tmpdir;
it("should just copy an existing drive", async () => {
const filename = path.join(__dirname, "fixtures", "data.ext2");
const drive: ExistingDriveConfig = {
builder: "none",
Expand Down
Loading
Loading