Skip to content
Merged
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
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.12/schema.json",
"$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
Expand Down
210 changes: 98 additions & 112 deletions bun.lock

Large diffs are not rendered by default.

23 changes: 12 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@
"packages/*"
],
"catalog": {
"@astrojs/cloudflare": "13.0.0-beta.1",
"@astrojs/solid-js": "6.0.0-beta.2",
"@better-auth/cli": "^1.4.17",
"@better-auth/passkey": "^1.4.17",
"@astrojs/cloudflare": "^13.0.0-beta.6",
"@astrojs/solid-js": "^6.0.0-beta.2",
"@better-auth/cli": "^1.4.18",
"@better-auth/passkey": "^1.4.18",
"@tailwindcss/vite": "^4.1.18",
"astro": "6.0.0-beta.3",
"better-auth": "^1.4.17",
"astro": "^6.0.0-beta.10",
"better-auth": "^1.4.18",
"drizzle-orm": "^0.45.1",
"elysia": "^1.4.22",
"rolldown": "1.0.0-rc.1",
"elysia": "^1.4.24",
"rolldown": "1.0.0-rc.3",
"solid-js": "^1.9.11",
"tailwindcss": "^4.1.18",
"tsdown": "^0.20.1",
"wrangler": "^4.59.2",
"tsdown": "^0.20.3",
"wrangler": "^4.64.0",
"zod": "^4.3.6"
}
},
Expand All @@ -29,10 +29,11 @@
"format": "biome check --write --linter-enabled=false",
"build:app": "bun run --cwd ./apps/registry build",
"build:cli": "bun run --cwd ./packages/usts build",
"build": "bun run build:cli",
"test": "bun run --cwd ./packages/usts test"
},
"devDependencies": {
"@biomejs/biome": "2.3.12"
"@biomejs/biome": "2.3.14"
},
"overrides": {
"esbuild": "^0.27.2"
Expand Down
4 changes: 2 additions & 2 deletions packages/usts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
"usts": "./dist/bin/cli.mjs"
},
"engines": {
"node": ">=22.0.0",
"bun": ">=1.2.20"
"node": ">=24.0.0",
"bun": ">=1.3.0"
},
"dependencies": {
"rolldown": "catalog:",
Expand Down
4 changes: 2 additions & 2 deletions packages/usts/src/bin/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ async function main(): Promise<void> {
if (bunVersion) {
try {
const { semver } = await import("bun");
const supportedBunVersion = ">=1.2.20";
const supportedBunVersion = ">=1.3.0";
if (!semver.satisfies(bunVersion, supportedBunVersion)) {
throw new Error("Unsupported Bun version. Please upgrade Bun.");
}
Expand All @@ -16,7 +16,7 @@ async function main(): Promise<void> {
}
} else {
const version = parseInt(process.versions.node, 10) || 0;
const minSupportedNodeVersion = 22;
const minSupportedNodeVersion = 24;
if (version < minSupportedNodeVersion) {
throw new Error("Unsupported Node version. Please upgrade Node.\n");
}
Expand Down
11 changes: 9 additions & 2 deletions packages/usts/src/cli/build/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { resolveConfig } from "~/config/resolve";
import { buildUserscript } from "~/core/build";
import { watchUserscript } from "~/core/build/watch";

async function build(): Promise<void> {
async function build(options: { watch?: boolean }): Promise<void> {
const { userscriptConfig, root } = await resolveConfig();

const outDir = userscriptConfig.outDir;
Expand All @@ -12,7 +13,13 @@ async function build(): Promise<void> {
);
}

await buildUserscript(userscriptConfig, { write: true });
if (!options.watch) {
await buildUserscript(userscriptConfig, { write: true });
}

if (options.watch) {
await watchUserscript(userscriptConfig);
}
}

export { build };
31 changes: 22 additions & 9 deletions packages/usts/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,25 @@ import { parseArgs } from "node:util";

type CLICommand = "help" | "build";

type Flags = {} & Record<string, never>;
type Flags = { watch: boolean };

interface Args {
values: Flags;
positionals: string[];
}

async function printHelp() {
console.log("Run `usts build` to build a userscript");
console.log(`
usts build - build a userscript
usts build --watch - build userscript in watch mode
`);
}

function isSupportedCommand<T extends CLICommand>(
supportedCommands: T[],
cmd: string,
): cmd is T {
return new Set<string>(supportedCommands).has(cmd);
}

function resolveCommand(parsedArgs: Args): CLICommand {
Expand All @@ -20,30 +30,33 @@ function resolveCommand(parsedArgs: Args): CLICommand {
return "help";
}

const supportedCommands = new Set(["build"]);
if (supportedCommands.has(cmd)) {
return cmd as CLICommand;
if (isSupportedCommand(["build"], cmd)) {
return cmd;
}

return "help";
}

async function runCommand(cmd: CLICommand) {
async function runCommand(cmd: CLICommand, flags: Flags) {
switch (cmd) {
case "help": {
await printHelp();
return;
}
case "build": {
const { build } = await import("./build/index.js");
await build();
await build({ watch: flags.watch });
return;
}
}
}

export async function cli(argv: string[]): Promise<void> {
const parsedArgs = parseArgs({ args: argv, allowPositionals: true });
const parsedArgs = parseArgs({
args: argv,
allowPositionals: true,
options: { watch: { type: "boolean", default: false } },
});
const cmd = resolveCommand(parsedArgs);
await runCommand(cmd);
await runCommand(cmd, parsedArgs.values);
}
27 changes: 4 additions & 23 deletions packages/usts/src/core/build/index.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,15 @@
import * as path from "node:path";

import * as rolldown from "rolldown";

import type { ResolvedUserscriptConfig } from "~/config/schema";

import { serializeMetaHeader } from "./meta-header";
import { resolveOptions } from "./options";

async function buildUserscript(
config: ResolvedUserscriptConfig,
options?: { write?: boolean; watch?: boolean },
options?: { write?: boolean },
): Promise<string> {
const header = serializeMetaHeader(config.header);

const USERSCRIPT_OUTPUT_FILE_NAME = "index.user.js";
const outFile = path.join(config.outDir, USERSCRIPT_OUTPUT_FILE_NAME);

const result = await rolldown.build({
input: config.entryPoint,
tsconfig: true,
plugins: [config.plugins],
output: {
format: "iife",
sourcemap: false,
minify: "dce-only",
postBanner: `${header}\n`,
cleanDir: config.clean,
file: outFile,
},
write: options?.write ?? false,
});
const buildOptions = resolveOptions(config, options);
const result = await rolldown.build(buildOptions);

if (result.output.length !== 1) {
throw new Error(`❌ Unexpected userscript build output`);
Expand Down
39 changes: 39 additions & 0 deletions packages/usts/src/core/build/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as path from "node:path";
import type { InputOptions, OutputOptions } from "rolldown";
import type { ResolvedUserscriptConfig } from "~/config/schema";
import { serializeMetaHeader } from "./meta-header";

interface ResolvedOutputOptions extends OutputOptions {
readonly file: string;
}

interface ResolvedOptions extends InputOptions {
/** @default false */
readonly write: boolean;
readonly output: ResolvedOutputOptions;
}

export function resolveOptions(
config: ResolvedUserscriptConfig,
options?: { write?: boolean },
): ResolvedOptions {
const header = serializeMetaHeader(config.header);

const USERSCRIPT_OUTPUT_FILE_NAME = "index.user.js";
const outFile = path.join(config.outDir, USERSCRIPT_OUTPUT_FILE_NAME);

return {
input: config.entryPoint,
tsconfig: true,
plugins: [config.plugins],
output: {
format: "iife",
sourcemap: false,
minify: "dce-only",
postBanner: `${header}\n`,
cleanDir: config.clean,
file: outFile,
},
write: options?.write ?? false,
};
}
33 changes: 33 additions & 0 deletions packages/usts/src/core/build/serve.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { readFile } from "node:fs/promises";
import { createServer } from "node:http";

export async function serve(options: {
port: number;
outDir: string;
fileName: string;
}): Promise<void> {
return new Promise<void>(() => {
const server = createServer(async (req, res) => {
if (req.url !== `/${options.fileName}`) {
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("Not found");
return;
}

const bundle = await readFile(`${options.outDir}/${options.fileName}`);

res.writeHead(200, {
"Content-Type": "application/javascript",
"Cache-Control": "no-cache, no-store, must-revalidate",
Pragma: "no-cache",
Expires: "0",
});

res.end(bundle);
});

server.listen(options.port, () => {
console.log(`http://localhost:${options.port}/${options.fileName}`);
});
});
}
22 changes: 22 additions & 0 deletions packages/usts/src/core/build/watch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as rolldown from "rolldown";

import type { ResolvedUserscriptConfig } from "~/config/schema";

import { resolveOptions } from "./options";
import { serve } from "./serve";

async function watchUserscript(
config: ResolvedUserscriptConfig,
options?: { port?: number },
): Promise<void> {
const USERSCRIPT_OUTPUT_FILE_NAME = "index.user.js";
const watchOptions = resolveOptions(config);
rolldown.watch(watchOptions);
await serve({
port: options?.port ?? 3000,
outDir: config.outDir,
fileName: USERSCRIPT_OUTPUT_FILE_NAME,
});
}

export { watchUserscript };