* feat(cli): add consistent get/list/new subcommands for all item types Make the CLI consistent so every item type (script, flow, app, resource, resource-type, variable, schedule, folder, trigger) supports get/list/new subcommands, enabling the CLI to be used as a full API client in bash scripts with jq piping. - Add --json flag to all list commands for machine-readable output - Register explicit "list" subcommand alongside default action - Add "get <path> [--json]" subcommand to fetch single items from API - Rename "bootstrap" to "new" for script/flow, keep "bootstrap" as alias - Add "new" subcommand for resource, resource-type, variable, schedule, folder, and trigger to create local template YAML files - Update cli-commands skill documentation for wmill init - Add integration tests for all new commands Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * all * feat: install wmill CLI in Docker images and use it for bash variable/resource access - Install windmill-cli via bun in all Dockerfiles that include bun - DockerfileCli: switch from node:slim to oven/bun:slim - CLI: auto-configure from WM_WORKSPACE/WM_TOKEN/BASE_INTERNAL_URL env vars as last-resort fallback when no workspace is configured - Frontend: replace curl-based bash snippets with wmill variable/resource get - Add backend integration tests for wmill CLI in bash scripts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ci): install windmill-cli in backend test workflow Ensures wmill is available on PATH for bash integration tests that use `wmill variable get` and `wmill resource get`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(cli): replace @std/* Deno dependencies with Node.js equivalents Replace @std/log with a lightweight custom logger (core/log.ts), @std/path with node:path, and @std/yaml with the yaml npm package. Also fix process hang on exit, add --node option to install_dev.sh, and add missing hasRequiredPermissions to NpmProvider. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * all * all * all * refactor(cli): replace @ayonli/jsext and @std/encoding with lightweight alternatives Replace @ayonli/jsext (8.4MB) with tar-stream (32kB) for tar creation, replace @std/encoding with Node.js Buffer.toString("hex"), and fix @windmill-labs/shared-utils to use direct npm instead of JSR mirror. Also resolve merge conflicts in sync.ts and fix pre-existing type errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(cli): use singleQuote YAML output and pass yamlOptions in gitsync pull The yaml library defaults to double quotes, but the codebase (and tests) expect single-quoted strings. Add singleQuote: true to yamlOptions and pass yamlOptions to gitsync-settings pull writeFile calls. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * all * all * fix(cli): address code review feedback - Install CLI from source in backend tests instead of npm - Fix script bootstrap catch block to re-throw "File already exists" - Add type-safe local variable after trigger kind validation - Use created_by instead of policy.on_behalf_of for app get output - Note --kind is recommended for faster trigger lookup in help text - Document node symlink purpose in Dockerfiles Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ci): use /usr/bin for wmill wrapper to ensure it's in PATH Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ci): install wmill to ~/.local/bin to avoid permission issues Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * ci(backend): switch to Blacksmith runner and add cargo caching - Switch from ubicloud-standard-16 to blacksmith-16vcpu-ubuntu-2404 for faster NVMe-backed builds - Add stickydisk for cargo target directory (persistent NVMe cache across runs) - Add cache for cargo registry and git dependencies - Upgrade DuckDB FFI cache from actions/cache@v3 to useblacksmith/cache@v1 - Enable CARGO_INCREMENTAL=1 to benefit from persistent target cache Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix ci --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
141 lines
4.3 KiB
TypeScript
141 lines
4.3 KiB
TypeScript
/**
|
|
* Unit tests for the tar creation utility.
|
|
* These tests require no backend — they test standalone tar logic.
|
|
*/
|
|
|
|
import { expect, test, describe } from "bun:test";
|
|
import { createTarBlob, type TarEntry } from "../src/utils/tar.ts";
|
|
import { extract, type Headers } from "tar-stream";
|
|
import { Readable } from "node:stream";
|
|
|
|
/** Extract all entries from a tarball Blob into a map of name -> content string */
|
|
async function extractTar(
|
|
blob: Blob
|
|
): Promise<Map<string, { content: string; header: Headers }>> {
|
|
const result = new Map<string, { content: string; header: Headers }>();
|
|
const ex = extract();
|
|
const buffer = Buffer.from(await blob.arrayBuffer());
|
|
|
|
return new Promise((resolve, reject) => {
|
|
ex.on("entry", (header, stream, next) => {
|
|
const chunks: Buffer[] = [];
|
|
stream.on("data", (chunk: Buffer) => chunks.push(chunk));
|
|
stream.on("end", () => {
|
|
result.set(header.name, {
|
|
content: Buffer.concat(chunks).toString("utf-8"),
|
|
header,
|
|
});
|
|
next();
|
|
});
|
|
stream.on("error", reject);
|
|
stream.resume();
|
|
});
|
|
ex.on("finish", () => resolve(result));
|
|
ex.on("error", reject);
|
|
|
|
Readable.from(buffer).pipe(ex);
|
|
});
|
|
}
|
|
|
|
describe("createTarBlob", () => {
|
|
test("single file tarball", async () => {
|
|
const entries: TarEntry[] = [
|
|
{ name: "main.js", content: 'console.log("hello");' },
|
|
];
|
|
|
|
const blob = await createTarBlob(entries);
|
|
const extracted = await extractTar(blob);
|
|
|
|
expect(extracted.size).toBe(1);
|
|
expect(extracted.has("main.js")).toBe(true);
|
|
expect(extracted.get("main.js")!.content).toBe('console.log("hello");');
|
|
});
|
|
|
|
test("multiple output files", async () => {
|
|
const entries: TarEntry[] = [
|
|
{ name: "main.js", content: 'import "./chunk-abc.js";' },
|
|
{ name: "chunk-abc.js", content: "export const x = 42;" },
|
|
{ name: "chunk-def.js", content: "export const y = 99;" },
|
|
];
|
|
|
|
const blob = await createTarBlob(entries);
|
|
const extracted = await extractTar(blob);
|
|
|
|
expect(extracted.size).toBe(3);
|
|
expect(extracted.get("main.js")!.content).toBe(
|
|
'import "./chunk-abc.js";'
|
|
);
|
|
expect(extracted.get("chunk-abc.js")!.content).toBe(
|
|
"export const x = 42;"
|
|
);
|
|
expect(extracted.get("chunk-def.js")!.content).toBe(
|
|
"export const y = 99;"
|
|
);
|
|
});
|
|
|
|
test("single file with assets", async () => {
|
|
const entries: TarEntry[] = [
|
|
{ name: "main.js", content: "const data = require('./data.json');" },
|
|
{ name: "data.json", content: '{"key":"value"}' },
|
|
];
|
|
|
|
const blob = await createTarBlob(entries);
|
|
const extracted = await extractTar(blob);
|
|
|
|
expect(extracted.size).toBe(2);
|
|
expect(extracted.has("main.js")).toBe(true);
|
|
expect(extracted.has("data.json")).toBe(true);
|
|
expect(extracted.get("data.json")!.content).toBe('{"key":"value"}');
|
|
});
|
|
|
|
test("produces a valid Blob", async () => {
|
|
const entries: TarEntry[] = [
|
|
{ name: "main.js", content: "module.exports = {};" },
|
|
];
|
|
|
|
const blob = await createTarBlob(entries);
|
|
|
|
expect(blob).toBeInstanceOf(Blob);
|
|
expect(blob.size).toBeGreaterThan(0);
|
|
// Tar blocks are 512-byte aligned
|
|
expect(blob.size % 512).toBe(0);
|
|
});
|
|
|
|
test("file naming — entries have exact names given", async () => {
|
|
const entries: TarEntry[] = [
|
|
{ name: "main.js", content: "entry point" },
|
|
{ name: "lib/utils.js", content: "utils" },
|
|
];
|
|
|
|
const blob = await createTarBlob(entries);
|
|
const extracted = await extractTar(blob);
|
|
|
|
// Names should be exactly as provided (no leading slash)
|
|
expect(extracted.has("main.js")).toBe(true);
|
|
expect(extracted.has("lib/utils.js")).toBe(true);
|
|
});
|
|
|
|
test("handles Buffer content", async () => {
|
|
const entries: TarEntry[] = [
|
|
{ name: "main.js", content: Buffer.from("buffer content") },
|
|
];
|
|
|
|
const blob = await createTarBlob(entries);
|
|
const extracted = await extractTar(blob);
|
|
|
|
expect(extracted.get("main.js")!.content).toBe("buffer content");
|
|
});
|
|
|
|
test("handles Uint8Array content", async () => {
|
|
const content = new TextEncoder().encode("uint8 content");
|
|
const entries: TarEntry[] = [
|
|
{ name: "main.js", content },
|
|
];
|
|
|
|
const blob = await createTarBlob(entries);
|
|
const extracted = await extractTar(blob);
|
|
|
|
expect(extracted.get("main.js")!.content).toBe("uint8 content");
|
|
});
|
|
});
|