Skip to content
Open
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: 5 additions & 0 deletions packages/tooling/vis/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ When aube is the installer, `vis doctor` surfaces aube's effective hardening pos
| `vis generate [name]` | | Scaffold files from an in-repo template (native TS or moon-format) |
| `vis init` | | Initialize vis.config.ts with security defaults |
| `vis run <target>` | | Run a target across workspace projects with caching |
| `vis dev` | | Start the dev server: the package dev script, or the inferred Vite, Rsbuild, Rslib, or Rspack server |
| `vis build` | | Run the build: the package build script, or the inferred Vite, Rsbuild, Rslib, or Rspack build |
| `vis preview` | | Serve the production build: the package preview script, or the inferred Vite, Rsbuild, or Rspack preview |
| `vis pack` | | Build a library for publishing: the package pack script, or the inferred Rslib or packem build |
| `vis affected <target>` | | Run tasks only on projects affected by git changes |
| `vis ci <targets>` | | One-shot CI entry point: toolchain, install, then affected runs for each comma-separated target |
| `vis ignore <project>` | | CI build gating for Vercel / Netlify "Ignored Build Step" |
Expand All @@ -229,6 +233,7 @@ When aube is the installer, `vis doctor` surfaces aube's effective hardening pos
| `vis outdated [packages]` | | Check for outdated dependencies in workspace catalogs |
| `vis update [packages]` | `up` | Update packages, GitHub Actions, Dockerfiles, and GitLab CI refs |
| `vis install` | `i` | Install dependencies via the detected package manager |
| `vis rebuild` | | Rebuild native dependencies through the detected package manager |
| `vis info <package>` | `view` | Show npm registry metadata for a package (wraps `npm view` et al.) |
| `vis dlx <package>` | | Execute a remote package without permanent installation |
| `vis audit` | | Audit dependencies for security vulnerabilities |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { beforeEach, describe, expect, it, vi } from "vitest";

const runExecute = vi.hoisted(() => vi.fn());

vi.mock(import("../../../src/commands/run/handler"), () => { return { default: runExecute }; });

const { runDev } = await import("../../../src/commands/dev/handler");
const { runBuild } = await import("../../../src/commands/build/handler");

// The target is already defined, so these tests exercise forwarding, not the offer.
const DEFINED = { projects: async () => [{ name: "app", root: "/tmp/app", targets: { build: {}, dev: {} } }] };

const toolbox = (argument: string[] | undefined, rawUnknown: string[] | undefined, options: Record<string, unknown> = {}) =>
({ argument, logger: {}, options, rawUnknown }) as never;

describe("vis dev and vis build", () => {
beforeEach(() => {
runExecute.mockReset();
});

it("runs the dev target through the run handler", async () => {
expect.assertions(1);

await runDev(toolbox(undefined, undefined), DEFINED);

expect(runExecute.mock.calls[0][0].argument).toStrictEqual(["dev"]);
});

it("forwards positional and flag arguments after the target", async () => {
expect.assertions(1);

await runDev(toolbox(["web"], ["--port", "3000"]), DEFINED);

expect(runExecute.mock.calls[0][0].argument).toStrictEqual(["dev", "web", "--port", "3000"]);
});

it("passes run's options through unchanged", async () => {
expect.assertions(1);

await runBuild(toolbox(undefined, undefined, { filter: ["@org/web"], preflight: undefined }), DEFINED);

expect(runExecute.mock.calls[0][0].options).toStrictEqual({ filter: ["@org/web"], preflight: undefined });
});

it("runs the build target", async () => {
expect.assertions(1);

await runBuild(toolbox(undefined, ["--mode", "production"]), DEFINED);

expect(runExecute.mock.calls[0][0].argument).toStrictEqual(["build", "--mode", "production"]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
import { access, readFile, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

const runExecute = vi.hoisted(() => vi.fn());

vi.mock(import("../../../src/commands/run/handler"), () => { return { default: runExecute }; });

const { runDev } = await import("../../../src/commands/dev/handler");
const { runBuild } = await import("../../../src/commands/build/handler");

describe("vis dev and vis build with no target", () => {
let root: string;

beforeEach(() => {
root = mkdtempSync(join(realpathSync(tmpdir()), "vis-dev-offer-"));
mkdirSync(join(root, "node_modules"), { recursive: true });
mkdirSync(join(root, "packages", "app"), { recursive: true });
writeFileSync(join(root, "pnpm-workspace.yaml"), "packages:\n - 'packages/*'\n");
writeFileSync(join(root, "package.json"), JSON.stringify({ name: "root", private: true }));
writeFileSync(join(root, "packages", "app", "package.json"), JSON.stringify({ name: "@fix/app", scripts: {} }));
runExecute.mockReset();
process.exitCode = undefined;
});

afterEach(() => {
rmSync(root, { force: true, recursive: true });
process.exitCode = undefined;
});

const toolbox = (workspaceRoot: string) =>
({
argument: [],
fs: { access, readFile, writeFile },
logger: { info: vi.fn() },
options: {},
rawUnknown: [],
visConfig: undefined,
workspaceRoot,
}) as never;

it("runs the dev target without prompting when a package already defines it", async () => {
expect.assertions(2);

await runDev(toolbox(root), { install: () => 0, isInteractive: () => false, projects: async () => [{ name: "@fix/app", root: join(root, "packages", "app"), targets: { dev: {} } }] });

expect(runExecute).toHaveBeenCalledTimes(1);
// The current directory is not the workspace root here, so the selector is scoped to the package.
expect(runExecute.mock.calls[0][0].argument).toStrictEqual(["~:dev"]);
});

it("errors for build in a non-interactive run, and does not run the target", async () => {
expect.assertions(2);

await expect(
runBuild(toolbox(root), { install: () => 0, isInteractive: () => false, projects: async () => [{ name: "@fix/app", root: join(root, "packages", "app"), targets: undefined }] }),
).rejects.toThrow("vis add -D @rslib/core");

expect(runExecute).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import aiCommands from "../../src/commands/ai";
import analyzeCommand from "../../src/commands/analyze";
import approveBuildsCommand from "../../src/commands/approve-builds";
import auditCommand from "../../src/commands/audit";
import buildCommand from "../../src/commands/build";
import cacheCommands from "../../src/commands/cache";
import checkCommand from "../../src/commands/check";
import ciCommand from "../../src/commands/ci";
import ciIgnoreCommand from "../../src/commands/ci-ignore";
import cleanCommand from "../../src/commands/clean";
import createCommand from "../../src/commands/create";
import dedupeCommand from "../../src/commands/dedupe";
import devCommand from "../../src/commands/dev";
import devcontainerCommand from "../../src/commands/devcontainer";
import dlxCommand from "../../src/commands/dlx";
import dockerCommands from "../../src/commands/docker";
Expand All @@ -33,7 +35,10 @@ import listCommand from "../../src/commands/list";
import migrateCommands from "../../src/commands/migrate";
import optimizeCommand from "../../src/commands/optimize";
import outdatedCommand from "../../src/commands/outdated";
import packCommand from "../../src/commands/pack";
import pmCommand from "../../src/commands/pm";
import previewCommand from "../../src/commands/preview";
import rebuildCommand from "../../src/commands/rebuild";
import removeCommand from "../../src/commands/remove";
import runCommand from "../../src/commands/run";
import sbomCommand from "../../src/commands/sbom";
Expand All @@ -56,11 +61,13 @@ const FLAT_COMMANDS: Command[] = [
analyzeCommand,
approveBuildsCommand,
auditCommand,
buildCommand,
checkCommand,
ciCommand,
cleanCommand,
createCommand,
dedupeCommand,
devCommand,
devcontainerCommand,
dlxCommand,
doctorCommand,
Expand All @@ -75,8 +82,11 @@ const FLAT_COMMANDS: Command[] = [
linkCommand,
listCommand,
optimizeCommand,
packCommand,
outdatedCommand,
pmCommand,
previewCommand,
rebuildCommand,
removeCommand,
runCommand,
sbomCommand,
Expand Down
77 changes: 77 additions & 0 deletions packages/tooling/vis/__tests__/commands/pack/pack-offer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";

import type { CerebroFs } from "@visulima/cerebro";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

const runExecute = vi.hoisted(() => vi.fn());

vi.mock(import("../../../src/commands/run/handler"), () => { return { default: runExecute }; });

const { runPack } = await import("../../../src/commands/pack/handler");

const fs = { access, mkdir, readFile, writeFile } as unknown as CerebroFs;

describe("vis pack", () => {
let root: string;
let app: string;

beforeEach(() => {
root = mkdtempSync(join(realpathSync(tmpdir()), "vis-pack-"));
app = join(root, "packages", "lib");
mkdirSync(join(root, "node_modules"), { recursive: true });
mkdirSync(app, { recursive: true });
writeFileSync(join(root, "pnpm-workspace.yaml"), "packages:\n - 'packages/*'\n");
writeFileSync(join(root, "package.json"), JSON.stringify({ name: "root", private: true }));
writeFileSync(join(app, "package.json"), JSON.stringify({ name: "@fix/lib", scripts: {} }, undefined, 2));
runExecute.mockReset();
process.exitCode = undefined;
});

afterEach(() => {
rmSync(root, { force: true, recursive: true });
process.exitCode = undefined;
});

const toolbox = (options: Record<string, unknown> = {}, argument: string[] = []) =>
({ argument, fs, logger: { info: vi.fn() }, options, rawUnknown: [], visConfig: undefined, workspaceRoot: root }) as never;

const noTarget = async () => [{ name: "@fix/lib", root: app, targets: undefined }];
const withPack = async () => [{ name: "@fix/lib", root: app, targets: { pack: {} } }];

it("runs the pack target without prompting when a project already defines it", async () => {
expect.assertions(2);

await runPack(toolbox({}, ["--sourcemap"]), { install: () => 0, isInteractive: () => false, projects: withPack });

// The current directory is not the workspace root here, so the selector is scoped to the package.
expect(runExecute.mock.calls[0][0].argument).toStrictEqual(["~:pack", "--sourcemap"]);
expect(process.exitCode).toBeUndefined();
});

it("errors in a non-interactive run, listing Rslib and packem with their install commands", async () => {
expect.assertions(3);

const install = vi.fn(() => 0);

await expect(runPack(toolbox(), { install, isInteractive: () => false, projects: noTarget })).rejects.toThrow("vis add -D @rslib/core");
await expect(runPack(toolbox(), { install, isInteractive: () => false, projects: noTarget })).rejects.toThrow("vis add -D @visulima/packem");
expect(runExecute).not.toHaveBeenCalled();
});

it("installs packem with its transformer and typescript, writes the config and entry, then runs pack", async () => {
expect.assertions(4);

const install = vi.fn(() => 0);

// Install is faked; the run is mocked. Only the offer's effects are checked here.
await runPack(toolbox(), { install, isInteractive: () => true, projects: noTarget, readline: async () => "2" });

expect(install.mock.calls.map(([name]) => name)).toStrictEqual(["@visulima/packem", "esbuild", "typescript"]);
expect(readFileSync(join(app, "packem.config.ts"), "utf8")).toContain("emitESM: true");
expect(readFileSync(join(app, "src", "index.ts"), "utf8")).toBe("export {};\n");
expect(runExecute.mock.calls[0][0].argument).toStrictEqual(["~:pack"]);
});
});
58 changes: 58 additions & 0 deletions packages/tooling/vis/__tests__/commands/rebuild/rebuild.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { beforeEach, describe, expect, it, vi } from "vitest";

const pm = vi.hoisted(() => {
return {
resolveInstaller: vi.fn(),
runPmSubcommand: vi.fn(),
};
});

vi.mock(import("../../../src/pm/pm-runner"), () => pm);

const { default: rebuildExecute } = await import("../../../src/commands/rebuild/handler");

const logger = { error: vi.fn(), info: vi.fn(), warn: vi.fn() };

describe("vis rebuild", () => {
beforeEach(() => {
pm.resolveInstaller.mockReset();
pm.runPmSubcommand.mockReset();
process.exitCode = undefined;
});

it("rejects yarn 1, which has no rebuild, instead of exiting 0", async () => {
expect.assertions(2);

pm.resolveInstaller.mockReturnValue({ name: "yarn", version: "1.22.19" });

await expect(rebuildExecute({ logger, options: {}, workspaceRoot: "/tmp/ws" } as never)).rejects.toThrow("yarn 1 has no `rebuild`");
expect(pm.runPmSubcommand).not.toHaveBeenCalled();
});

it("runs rebuild through the detected package manager", async () => {
expect.assertions(2);

const installer = { name: "pnpm", version: "10.0.0" };

pm.resolveInstaller.mockReturnValue(installer);
pm.runPmSubcommand.mockReturnValue(0);

await rebuildExecute({ logger, options: {}, workspaceRoot: "/tmp/ws" } as never);

expect(pm.runPmSubcommand).toHaveBeenCalledWith(installer, "rebuild", [], "/tmp/ws", logger);
expect(process.exitCode).toBeUndefined();
});

it("propagates a failing rebuild's exit code", async () => {
expect.assertions(1);

pm.resolveInstaller.mockReturnValue({ name: "npm", version: "10.0.0" });
pm.runPmSubcommand.mockReturnValue(2);

await rebuildExecute({ logger, options: {}, workspaceRoot: "/tmp/ws" } as never);

expect(process.exitCode).toBe(2);

process.exitCode = undefined;
});
});
7 changes: 3 additions & 4 deletions packages/tooling/vis/__tests__/inference/inference.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,9 @@ describe("inference", () => {
});

expect(result.targets["build"]?.command).toBe("vite build");
// packem still ran, but didn't contribute a unique target — its
// name should be absent from sources, since packem only had the
// (now-shadowed) `build` target on offer in v1.
expect(result.sources).toStrictEqual(["vite"]);
// packem's `build` is shadowed by vite, but packem still contributes its
// unique `pack` target, so it is a source. Its `build` is not used.
expect(result.sources).toStrictEqual(["vite", "packem"]);
});

it("should combine targets across detectors that don't collide", () => {
Expand Down
51 changes: 51 additions & 0 deletions packages/tooling/vis/__tests__/inference/pack-targets.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

import { afterEach, beforeEach, describe, expect, it } from "vitest";

import { discoverWorkspace } from "../../src/config/workspace";

const writeProject = (root: string, name: string, configFile: string): void => {
const directory = join(root, "packages", name);

mkdirSync(directory, { recursive: true });
writeFileSync(join(directory, "package.json"), JSON.stringify({ name: `@fix/${name}`, scripts: {} }, undefined, 2));
writeFileSync(join(directory, configFile), "export default {}");
};

describe("pack target inference", () => {
let scratch: string;

const targetsOf = (name: string): Record<string, { command?: string } | undefined> =>
discoverWorkspace(scratch, {}).workspace.projects[`@fix/${name}`]?.targets ?? {};

beforeEach(() => {
scratch = mkdtempSync(join(realpathSync(tmpdir()), "vis-pack-infer-"));
mkdirSync(join(scratch, "node_modules"), { recursive: true });
writeFileSync(join(scratch, "pnpm-workspace.yaml"), "packages:\n - 'packages/*'\n");
writeFileSync(join(scratch, "package.json"), JSON.stringify({ name: "fixture-root", private: true }, undefined, 2));
});

afterEach(() => {
rmSync(scratch, { force: true, recursive: true });
});

it("infers pack from rslib.config.ts, and keeps build", () => {
expect.assertions(2);

writeProject(scratch, "lib", "rslib.config.mts");

expect(targetsOf("lib")["pack"]?.command).toBe("rslib build");
expect(targetsOf("lib")["build"]?.command).toBe("rslib build");
});

it("infers pack from packem.config.ts, and keeps build", () => {
expect.assertions(2);

writeProject(scratch, "tool", "packem.config.ts");

expect(targetsOf("tool")["pack"]?.command).toBe("packem build");
expect(targetsOf("tool")["build"]?.command).toBe("packem build");
});
});
Loading
Loading