From f56d51ed0d522328156ad99f295174e9716ef5b5 Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 18 Jan 2026 15:51:18 +0000 Subject: [PATCH] Refactor + add tests --- .github/workflows/ci.yml | 3 ++ package.json | 3 +- packages/usts/package.json | 3 +- packages/usts/src/config/schema.ts | 6 ++-- packages/usts/src/core/build/build.ts | 13 +++++-- packages/usts/src/core/build/index.ts | 2 +- packages/usts/src/core/build/meta-header.ts | 34 +++++++++---------- .../tests/__snapshots__/basic.test.ts.snap | 16 +++++++++ packages/usts/tests/basic.test.ts | 12 +++++++ packages/usts/tests/fixtures/basic/index.ts | 1 + packages/usts/tests/helpers/resolve.ts | 23 +++++++++++++ packages/usts/tsconfig.json | 4 +-- 12 files changed, 90 insertions(+), 30 deletions(-) create mode 100644 packages/usts/tests/__snapshots__/basic.test.ts.snap create mode 100644 packages/usts/tests/basic.test.ts create mode 100644 packages/usts/tests/fixtures/basic/index.ts create mode 100644 packages/usts/tests/helpers/resolve.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc527a1..5ca39bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,3 +28,6 @@ jobs: - name: Build CLI (test) run: bun run build:cli + + - name: Run tests + run: bun run test diff --git a/package.json b/package.json index 92bb2a6..2cd50a2 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,8 @@ "lint": "biome lint", "format": "biome check --write --linter-enabled=false", "build:app": "bun run -F './apps/registry' build", - "build:cli": "bun run -F './packages/usts' build" + "build:cli": "bun run -F './packages/usts' build", + "test": "bun run -F './packages/usts' test" }, "devDependencies": { "@biomejs/biome": "2.3.11", diff --git a/packages/usts/package.json b/packages/usts/package.json index d8207a8..003ecd4 100644 --- a/packages/usts/package.json +++ b/packages/usts/package.json @@ -6,7 +6,8 @@ "license": "MIT", "scripts": { "build": "bunx --bun tsdown", - "prepublishOnly": "bun run build" + "prepublishOnly": "bun run build", + "test": "bun test" }, "repository": { "type": "git", diff --git a/packages/usts/src/config/schema.ts b/packages/usts/src/config/schema.ts index 11be27f..cf3f3da 100644 --- a/packages/usts/src/config/schema.ts +++ b/packages/usts/src/config/schema.ts @@ -61,9 +61,7 @@ const UserscriptMetaHeaderConfigSchema: z.ZodObject<{ > >; require: z.ZodOptional>; - resource: z.ZodOptional< - z.ZodUnion]> - >; + resource: z.ZodOptional>; grant: z.ZodOptional>; downloadURL: z.ZodOptional; updateURL: z.ZodOptional; @@ -88,7 +86,7 @@ const UserscriptMetaHeaderConfigSchema: z.ZodObject<{ ), require: z.optional(z.array(z.string())), - resource: z.optional(z.union([z.string(), z.array(z.string())])), + resource: z.optional(z.record(z.string(), z.string())), grant: z.optional(z.array(z.string())), diff --git a/packages/usts/src/core/build/build.ts b/packages/usts/src/core/build/build.ts index 0b08840..419502c 100644 --- a/packages/usts/src/core/build/build.ts +++ b/packages/usts/src/core/build/build.ts @@ -11,10 +11,11 @@ const USERSCRIPT_OUTPUT_FILE_NAME = "index.user.js"; async function buildUserscript( config: ResolvedUserscriptConfig, -): Promise { + options?: { write?: boolean }, +): Promise { console.log("\n⚒️ Building userscript"); - const header = serializeMetaHeader(config.header).serializedHeader; + const header = serializeMetaHeader(config.header); const bundle = await rolldown({ input: config.entryPoint, tsconfig: true }); const result = await bundle.generate({ @@ -28,7 +29,11 @@ async function buildUserscript( } const bundledCode = result.output[0].code; - const fullCode = `${header}\n\n${bundledCode}` as const; + const fullCode = `${header}\n\n${bundledCode}`; + + if (!options?.write) { + return fullCode; + } const outDir = config.outDir; if (config.clean) { @@ -39,6 +44,8 @@ async function buildUserscript( const outFile = path.join(outDir, USERSCRIPT_OUTPUT_FILE_NAME); await fs.writeFile(outFile, fullCode, "utf-8"); console.log("\n🎉 Build process complete!"); + + return fullCode; } export { buildUserscript }; diff --git a/packages/usts/src/core/build/index.ts b/packages/usts/src/core/build/index.ts index 4e29dcb..739c550 100644 --- a/packages/usts/src/core/build/index.ts +++ b/packages/usts/src/core/build/index.ts @@ -12,5 +12,5 @@ export default async function build(): Promise { ); } - await buildUserscript(userscriptConfig); + await buildUserscript(userscriptConfig, { write: true }); } diff --git a/packages/usts/src/core/build/meta-header.ts b/packages/usts/src/core/build/meta-header.ts index 4cc8f42..88f4563 100644 --- a/packages/usts/src/core/build/meta-header.ts +++ b/packages/usts/src/core/build/meta-header.ts @@ -3,41 +3,39 @@ import type { UserscriptMetaHeaderConfig } from "~/config/schema"; const headerStart = "// ==UserScript==" as const; const headerEnd = "// ==/UserScript==" as const; -type HeaderLine = `// @${string}`; - -function getHeaderLine(key: string, val: string | boolean): HeaderLine { +function getHeaderLine( + key: string, + val: string | boolean | readonly [string, string], +) { if (typeof val === "boolean") return `// @${key}`; const paddedKey = key.padEnd(16); - return `// @${paddedKey} ${val}`; + if (typeof val === "string") return `// @${paddedKey} ${val}`; + const paddedSubKey = val[0].padEnd(8); + return `// @${paddedKey} ${paddedSubKey} ${val[1]}`; } function getHeaderLines( key: string, - val: string | string[] | boolean, -): HeaderLine[] { + val: string | string[] | boolean | Record, +) { if (Array.isArray(val)) return val.map((v) => getHeaderLine(key, v)); if (typeof val === "string") return [getHeaderLine(key, val)]; if (typeof val === "boolean") return [getHeaderLine(key, val)]; + if (typeof val === "object") { + return Object.entries(val).map((v) => getHeaderLine(key, v)); + } throw new Error(`Unknown header value type: ${typeof val}`); } -type SerializeMetaHeader = `// ==UserScript== -${string} -// ==/UserScript==`; - -interface SerializeMetaHeaderResult { - serializedHeader: SerializeMetaHeader; -} - export function serializeMetaHeader( headerConfig: UserscriptMetaHeaderConfig, -): SerializeMetaHeaderResult { +): string { const headerConfigEntries = Object.entries(headerConfig); - const headerLines = headerConfigEntries.flatMap(([key, val]) => - getHeaderLines(key, val), + const headerLines = headerConfigEntries.flatMap(([kwy, val]) => + getHeaderLines(kwy, val), ); const serializedHeaderLines = headerLines.join("\n"); const serializedHeader = `${headerStart}\n${serializedHeaderLines}\n${headerEnd}` as const; - return { serializedHeader }; + return serializedHeader; } diff --git a/packages/usts/tests/__snapshots__/basic.test.ts.snap b/packages/usts/tests/__snapshots__/basic.test.ts.snap new file mode 100644 index 0000000..d0a432c --- /dev/null +++ b/packages/usts/tests/__snapshots__/basic.test.ts.snap @@ -0,0 +1,16 @@ +// Bun Snapshot v1, https://bun.sh/docs/test/snapshots + +exports[`Basic userscript Correctly bundles a basic userscript 1`] = ` +"// ==UserScript== +// @name Userscript name +// @namespace fixtures +// @match https://example.com/* +// @version 1.0.0 +// @description Userscript description +// ==/UserScript== + +(function() { + console.log("Hello world!"); +})(); +" +`; diff --git a/packages/usts/tests/basic.test.ts b/packages/usts/tests/basic.test.ts new file mode 100644 index 0000000..d2ea150 --- /dev/null +++ b/packages/usts/tests/basic.test.ts @@ -0,0 +1,12 @@ +import { describe, expect, it } from "bun:test"; + +import { buildUserscript } from "../src/core/build/build"; +import { resolveFixture } from "./helpers/resolve"; + +describe("Basic userscript", () => { + it("Correctly bundles a basic userscript", async () => { + const fixture = resolveFixture("fixtures/basic/index.ts"); + const result = await buildUserscript(fixture); + expect(result).toMatchSnapshot(); + }); +}); diff --git a/packages/usts/tests/fixtures/basic/index.ts b/packages/usts/tests/fixtures/basic/index.ts new file mode 100644 index 0000000..940a3ff --- /dev/null +++ b/packages/usts/tests/fixtures/basic/index.ts @@ -0,0 +1 @@ +console.log("Hello world!"); diff --git a/packages/usts/tests/helpers/resolve.ts b/packages/usts/tests/helpers/resolve.ts new file mode 100644 index 0000000..377a62a --- /dev/null +++ b/packages/usts/tests/helpers/resolve.ts @@ -0,0 +1,23 @@ +import { fileURLToPath } from "bun"; +import { + type ResolvedUserscriptConfig, + UserscriptConfigSchema, + type UserscriptMetaHeaderConfig, +} from "../../src/config/schema"; + +export function resolveFixture( + entryPoint: string, + header?: Partial, +): ResolvedUserscriptConfig { + return UserscriptConfigSchema.decode({ + entryPoint: fileURLToPath(new URL(`../${entryPoint}`, import.meta.url)), + header: { + name: "Userscript name", + namespace: "fixtures", + description: "Userscript description", + match: "https://example.com/*", + version: "1.0.0", + ...header, + }, + }); +} diff --git a/packages/usts/tsconfig.json b/packages/usts/tsconfig.json index a5e9c9b..c0b9575 100644 --- a/packages/usts/tsconfig.json +++ b/packages/usts/tsconfig.json @@ -8,7 +8,7 @@ "moduleResolution": "bundler", "moduleDetection": "force", - "rootDir": "./src", + "rootDir": "./", "paths": { "~/*": ["./src/*"] }, @@ -27,5 +27,5 @@ "forceConsistentCasingInFileNames": true, "verbatimModuleSyntax": true }, - "include": ["src/**/*"] + "include": ["src", "tests"] }