···11+---
22+"@bomb.sh/tools": minor
33+---
44+55+Updatess `oxfmt` config:
66+77+- Sets `"singleQuote"` option to `true`
88+- Adds `"*.json", "*.md", "*.yml", "*.jsonc"` to `"ignorePatterns"` option
99+1010+Updates `format` command to include all files by default instead of the `./src` directory
1111+1212+Extracts `publint` from the `lint` command into a separate command
···11-import { existsSync } from "node:fs";
22-import { fileURLToPath } from "node:url";
33-import { x } from "tinyexec";
44-import type { CommandContext } from "../context.ts";
55-import { local } from "../utils.ts";
11+import { existsSync } from 'node:fs';
22+import { fileURLToPath } from 'node:url';
33+import { x } from 'tinyexec';
44+import type { CommandContext } from '../context.ts';
55+import { local } from '../utils.ts';
6677function resolveConfig(): string {
88 // Built output (.mjs) or source (.ts)
99- for (const ext of [".mjs", ".ts"]) {
99+ for (const ext of ['.mjs', '.ts']) {
1010 const url = new URL(`../test-utils/vitest.config${ext}`, import.meta.url);
1111 const path = fileURLToPath(url);
1212 if (existsSync(path)) return path;
1313 }
1414- throw new Error("Could not resolve vitest.config file");
1414+ throw new Error('Could not resolve vitest.config file');
1515}
16161717export async function test(ctx: CommandContext) {
1818- const stdio = x(local("vitest"), ["run", "--config", resolveConfig(), ...ctx.args]);
1818+ const stdio = x(local('vitest'), ['run', '--config', resolveConfig(), ...ctx.args]);
19192020 for await (const line of stdio) {
2121 console.log(line);
···11-import { mkdtemp, symlink as fsSymlink } from "node:fs/promises";
22-import { tmpdir } from "node:os";
33-import { sep } from "node:path";
44-import { fileURLToPath, pathToFileURL } from "node:url";
55-import { NodeHfs } from "@humanfs/node";
66-import type { HfsImpl } from "@humanfs/types";
77-import { expect, onTestFinished } from "vitest";
11+import { mkdtemp, symlink as fsSymlink } from 'node:fs/promises';
22+import { tmpdir } from 'node:os';
33+import { sep } from 'node:path';
44+import { fileURLToPath, pathToFileURL } from 'node:url';
55+import { NodeHfs } from '@humanfs/node';
66+import type { HfsImpl } from '@humanfs/types';
77+import { expect, onTestFinished } from 'vitest';
8899interface ScopedHfsImpl extends Required<HfsImpl> {
1010 text(file: string | URL): Promise<string | undefined>;
···5454 symlink: (target: string) => SymlinkMarker;
5555}
56565757-const SYMLINK = Symbol("symlink");
5757+const SYMLINK = Symbol('symlink');
58585959/** Opaque marker returned by `ctx.symlink()`. */
6060export interface SymlinkMarker {
···8787}
88888989function isSymlinkMarker(value: unknown): value is SymlinkMarker {
9090- return typeof value === "object" && value !== null && SYMLINK in value;
9090+ return typeof value === 'object' && value !== null && SYMLINK in value;
9191}
92929393function isFileTree(value: unknown): value is FileTree {
9494 return (
9595- typeof value === "object" &&
9595+ typeof value === 'object' &&
9696 value !== null &&
9797 !Buffer.isBuffer(value) &&
9898 !Array.isArray(value) &&
···148148 * ```
149149 */
150150export async function createFixture(files: FileTree): Promise<Fixture> {
151151- const raw = expect.getState().currentTestName ?? "bsh";
151151+ const raw = expect.getState().currentTestName ?? 'bsh';
152152 const prefix = raw
153153 .toLowerCase()
154154- .replace(/[^a-z0-9]+/g, "-")
155155- .replace(/^-|-$/g, "");
154154+ .replace(/[^a-z0-9]+/g, '-')
155155+ .replace(/^-|-$/g, '');
156156 const root = new URL(`${prefix}-`, `file://${tmpdir()}/`);
157157 const path = await mkdtemp(fileURLToPath(root));
158158 const base = pathToFileURL(path + sep);
159159160160 const inner = new NodeHfs();
161161 const scoped = scopeHfs(inner, base);
162162- const resolve = (...segments: string[]) => new URL(`./${segments.join("/")}`, base);
162162+ const resolve = (...segments: string[]) => new URL(`./${segments.join('/')}`, base);
163163164164 const ctx: FileContext = {
165165 importMeta: {
···177177178178 // Nested directory object (not a plain value)
179179 if (
180180- typeof raw !== "function" &&
180180+ typeof raw !== 'function' &&
181181 !Buffer.isBuffer(raw) &&
182182 !Array.isArray(raw) &&
183183 isFileTree(raw) &&
184184- !name.includes(".")
184184+ !name.includes('.')
185185 ) {
186186 await inner.createDirectory(url);
187187 // Trailing slash so nested entries resolve relative to the dir
···190190 }
191191192192 // Ensure parent directory exists
193193- const parent = new URL("./", url);
193193+ const parent = new URL('./', url);
194194 await inner.createDirectory(parent);
195195196196 // Resolve functions
197197- const content = typeof raw === "function" ? raw(ctx) : raw;
197197+ const content = typeof raw === 'function' ? raw(ctx) : raw;
198198199199 // Symlink
200200 if (isSymlinkMarker(content)) {
···209209 }
210210211211 // JSON auto-serialization for .json files with non-string content
212212- if (name.endsWith(".json") && typeof content !== "string") {
212212+ if (name.endsWith('.json') && typeof content !== 'string') {
213213 await inner.write(url, JSON.stringify(content, null, 2));
214214 continue;
215215 }
+2-2
src/test-utils/index.ts
···11-export { createFixture } from "./fixture.ts";
22-export { createMocks, type Mocks } from "./mock.ts";
11+export { createFixture } from './fixture.ts';
22+export { createMocks, type Mocks } from './mock.ts';
+13-13
src/test-utils/mock.test.ts
···11-import { describe, it, expect } from "vitest";
22-import { createMocks } from "./mock.ts";
33-import { MockReadable, MockWritable } from "./stdio.ts";
11+import { describe, it, expect } from 'vitest';
22+import { createMocks } from './mock.ts';
33+import { MockReadable, MockWritable } from './stdio.ts';
4455-describe("createMocks", () => {
66- it("returns undefined streams when not requested", () => {
55+describe('createMocks', () => {
66+ it('returns undefined streams when not requested', () => {
77 const mocks = createMocks();
88 expect(mocks.input).toBeUndefined();
99 expect(mocks.output).toBeUndefined();
1010 });
11111212- it("creates input stream with `true`", () => {
1212+ it('creates input stream with `true`', () => {
1313 const mocks = createMocks({ input: true });
1414 expect(mocks.input).toBeInstanceOf(MockReadable);
1515 });
16161717- it("creates output stream with `true`", () => {
1717+ it('creates output stream with `true`', () => {
1818 const mocks = createMocks({ output: true });
1919 expect(mocks.output).toBeInstanceOf(MockWritable);
2020 });
21212222- it("passes config to output stream", () => {
2222+ it('passes config to output stream', () => {
2323 const mocks = createMocks({ output: { columns: 120, rows: 40, isTTY: true } });
2424 expect(mocks.output.columns).toBe(120);
2525 expect(mocks.output.rows).toBe(40);
2626 expect(mocks.output.isTTY).toBe(true);
2727 });
28282929- it("passes config to input stream", () => {
2929+ it('passes config to input stream', () => {
3030 const mocks = createMocks({ input: { isTTY: true } });
3131 expect(mocks.input.isTTY).toBe(true);
3232 });
33333434- it("stubs env vars for duration of test", () => {
3535- createMocks({ env: { TEST_MOCK_VAR: "hello" } });
3636- expect(process.env.TEST_MOCK_VAR).toBe("hello");
3434+ it('stubs env vars for duration of test', () => {
3535+ createMocks({ env: { TEST_MOCK_VAR: 'hello' } });
3636+ expect(process.env.TEST_MOCK_VAR).toBe('hello');
3737 });
38383939- it("restores env vars after test finishes", async () => {
3939+ it('restores env vars after test finishes', async () => {
4040 // Previous test's onTestFinished should have cleaned up
4141 expect(process.env.TEST_MOCK_VAR).toBeUndefined();
4242 });