diff --git a/.changeset/clibuddy-tinyexec.md b/.changeset/clibuddy-tinyexec.md new file mode 100644 index 0000000..1ee1f0a --- /dev/null +++ b/.changeset/clibuddy-tinyexec.md @@ -0,0 +1,29 @@ +--- +"@vlandoss/clibuddy": minor +--- + +**Breaking:** Replace `zx` with `tinyexec` and redesign `ShellService` around array-based exec. + +The previous tagged-template API (`shell.$\`...\``) and its surrounding helpers (`quote`, `isRaw`, `defaultQuote`, `getPreferLocal`, `localBaseBinPath`, `mute()`, `quiet()`, `isProcessOutput`, `./test-helpers` export) are gone. They duplicated zx internals, introduced a quoting bug for whitespace strings, and surfaced inconsistent `node_modules/.bin` resolution. + +New surface: + +- `shell.run(cmd, args, opts?)` — streams stdio to the terminal and prints `$ ` in verbose mode. Throws `NonZeroExitError` on non-zero exit by default. +- `shell.runCaptured(cmd, args, opts?)` — silent, returns the captured `Output { stdout, stderr, exitCode }`. Same throw-by-default semantics. +- `shell.at(cwd)` / `shell.child(opts)` — child shells with merged options. +- `RunOptions`: `cwd`, `env`, `verbose`, `throwOnError`, `shell` (pass-through `shell: true` for `&&`/pipes), `stdin`, `display` (override the verbose-printed name without affecting what's spawned). +- `resolvePackageBin(pkg, { from, binName? })` — async resolver that returns the absolute path to an installed package's binary, tolerating restrictive `exports` maps (oxlint) and packages without `main`/`exports` at all (`@biomejs/biome`). Memoised per `(pkg, from, binName)`. +- `isNonZeroExitError(value)` — replaces `isProcessOutput`. + +`tinyexec` automatically prepends every parent `node_modules/.bin` to `PATH`, so `localBaseBinPath` / `getPreferLocal` are no longer needed. + +New dependencies: `tinyexec` (replaces `zx`), `memoize` (for `resolvePackageBin`). + +**Migration** + +- `await shell.$\`git init\`` → `await shell.run("git", ["init"])` +- `await shell.$\`git config\`.nothrow()` → `await shell.runCaptured("git", ["config", ...], { throwOnError: false })` +- `shell.mute()` → call `runCaptured` instead (silent by default). +- `createShellService({ localBaseBinPath: [dir] })` → drop the option; tinyexec walks up automatically. +- `isProcessOutput(err)` → `isNonZeroExitError(err)`. +- Tools wrapping a npm package (e.g. biome, tsdown) should resolve the bin path via `resolvePackageBin` and pass it as the `cmd` with `display: ""` to avoid `node_modules/.bin/` shim loops. diff --git a/.changeset/run-run-shell-migration.md b/.changeset/run-run-shell-migration.md new file mode 100644 index 0000000..3df1f0d --- /dev/null +++ b/.changeset/run-run-shell-migration.md @@ -0,0 +1,14 @@ +--- +"@vlandoss/run-run": patch +--- + +Internal migration to the new tinyexec-backed `ShellService` (see `@vlandoss/clibuddy`). + +- `ToolService.exec` now accepts only `string[]` (the `string` overload that silently word-split on spaces is gone). All tool services (`biome`, `oxlint`, `oxfmt`, `tsdown`, `tsc`) build their flags as arrays so each flag survives as its own argv entry. +- Bin resolution moves into the base `ToolService`: subclasses declare `{ pkg, bin?, ui }` in the constructor and the base resolves the absolute path via `resolvePackageBin` (memoised). The verbose `$ ` line is preserved via the `display` option so users still see `$ biome check ...` instead of an absolute resolved path. Resolving to the absolute path bypasses the `node_modules/.bin/` shims that run-run itself publishes (`tools/biome` etc.), which would otherwise loop back through `rr tools ` indefinitely. +- `tscheck` runs `pretsc` / `pretypecheck` package scripts through `shell: true` so they can use `&&`, pipes, and env-var substitution. +- Bump `tsdown` from `0.21.10` to `0.22.0`. `tsdown@0.21.x` depends on `unrun@^0.2.37`, which pnpm resolved to `0.2.38` — whose published tarball is missing `dist/`, producing `WARN Failed to create bin … unrun` on every install. `tsdown@0.22.0` dropped `unrun` from `dependencies` (now an optional peer), erradicating the warning. + +Tests reorganised into one e2e file per command (`cli`, `jsc`, `lint`, `format`, `tsc`, `build-lib`). Each spawns the real `rr` binary against a temp fixture (`makeFixture` helper) and asserts on observable output, so we no longer rely on a `clibuddy/test-helpers` mock. + +End-user CLI behaviour is unchanged. diff --git a/.changeset/vland-init-prompts-and-git-fix.md b/.changeset/vland-init-prompts-and-git-fix.md new file mode 100644 index 0000000..d3cccf1 --- /dev/null +++ b/.changeset/vland-init-prompts-and-git-fix.md @@ -0,0 +1,7 @@ +--- +"@vlandoss/vland": minor +--- + +`vland init` now prompts (default-yes) for installing dependencies and initialising a git repository when those flags aren't passed on the CLI. Use `--install` / `--no-install` and `--git` / `--no-git` to skip the prompts; in non-interactive contexts both default to `true`. + +Also fixes the git initialisation step: the commit message was being split on whitespace by the underlying shell layer, producing errors like `pathspec 'initial' did not match any file(s)` and leaving the repo half-initialised. The migration to `tinyexec` (via `@vlandoss/clibuddy`) makes each argv entry survive as a separate token, so the canonical first commit `chore: initial commit from vland` now lands cleanly. diff --git a/packages/clibuddy/package.json b/packages/clibuddy/package.json index a66b795..db64b79 100644 --- a/packages/clibuddy/package.json +++ b/packages/clibuddy/package.json @@ -15,8 +15,7 @@ "author": "rcrd ", "type": "module", "exports": { - ".": "./src/index.ts", - "./test-helpers": "./test-helpers/index.ts" + ".": "./src/index.ts" }, "files": [ "dist", @@ -33,10 +32,11 @@ "@pnpm/fs.find-packages": "1000.0.24", "@pnpm/types": "1001.3.0", "ansis": "4.2.0", + "memoize": "10.2.0", "pkg-types": "2.3.0", "std-env": "3.9.0", - "yaml": "2.8.4", - "zx": "8.8.5" + "tinyexec": "1.1.2", + "yaml": "2.8.4" }, "publishConfig": { "access": "public", diff --git a/packages/clibuddy/src/run.ts b/packages/clibuddy/src/run.ts index 6e7e61d..9ef666d 100644 --- a/packages/clibuddy/src/run.ts +++ b/packages/clibuddy/src/run.ts @@ -1,4 +1,4 @@ -import { isProcessOutput } from "./shell/utils.ts"; +import { isNonZeroExitError } from "./shell/utils.ts"; function hasMessage(error: unknown): error is { message: string } { return ( @@ -18,7 +18,8 @@ export async function run(fn: () => Promise, logger: { error: (...args: un try { await fn(); } catch (error) { - if (!isProcessOutput(error)) { + // The subprocess already streamed its own stderr; don't double-print. + if (!isNonZeroExitError(error)) { logger.error(formatError(error)); } process.exit(1); diff --git a/packages/clibuddy/src/shell/create.ts b/packages/clibuddy/src/shell/create.ts index b4334c5..e3596dd 100644 --- a/packages/clibuddy/src/shell/create.ts +++ b/packages/clibuddy/src/shell/create.ts @@ -1,50 +1,9 @@ import fs from "node:fs"; import { ShellService } from "./shell.ts"; -import type { CreateOptions } from "./types.ts"; -import { getPreferLocal } from "./utils.ts"; +import type { ShellOptions } from "./types.ts"; export const cwd = fs.realpathSync(process.cwd()); -// Inspired by https://dub.sh/6tiHVgn -export function quote(arg: string) { - if (/^[\w./:=@-]+$/i.test(arg) || arg === "") { - return arg; - } - - return arg - .replace(/\\/g, "\\\\") - .replace(/'/g, "\\'") - .replace(/\f/g, "\\f") - .replace(/\n/g, "\\n") - .replace(/\r/g, "\\r") - .replace(/\t/g, "\\t") - .replace(/\v/g, "\\v") - .replace(/\0/g, "\\0"); -} - -export const isRaw = (arg: unknown): arg is { stdout: string } => - typeof arg === "object" && arg !== null && "stdout" in arg && typeof arg.stdout === "string"; - -function defaultQuote(arg: unknown) { - if (typeof arg === "string") { - return quote(arg); - } - - if (isRaw(arg)) { - return arg.stdout; - } - - throw TypeError(`Unsupported argument type: ${typeof arg}`); -} - -export function createShellService(options: CreateOptions = {}) { - const preferLocal = getPreferLocal(options.localBaseBinPath); - - return new ShellService({ - verbose: true, - cwd, - preferLocal, - quote: defaultQuote, - ...options, - }); +export function createShellService(options: ShellOptions = {}): ShellService { + return new ShellService({ cwd, ...options }); } diff --git a/packages/clibuddy/src/shell/index.ts b/packages/clibuddy/src/shell/index.ts index d0263ca..2875250 100644 --- a/packages/clibuddy/src/shell/index.ts +++ b/packages/clibuddy/src/shell/index.ts @@ -1,4 +1,5 @@ export * from "./create.ts"; +export * from "./resolve-package-bin.ts"; export * from "./shell.ts"; export * from "./types.ts"; export * from "./utils.ts"; diff --git a/packages/clibuddy/src/shell/resolve-package-bin.ts b/packages/clibuddy/src/shell/resolve-package-bin.ts new file mode 100644 index 0000000..1d68cd7 --- /dev/null +++ b/packages/clibuddy/src/shell/resolve-package-bin.ts @@ -0,0 +1,26 @@ +import { createRequire } from "node:module"; +import path from "node:path"; +import memoize from "memoize"; +import { readPackageJSON, resolvePackageJSON } from "pkg-types"; + +type Options = { from: string; binName?: string }; + +// pkg-types covers any package whose `.` entry is in `exports` (including +// restrictive ones like oxlint). The fallback handles packages without +// `main`/`exports` at all (e.g. @biomejs/biome). +async function _resolvePackageBin(pkg: string, { from, binName }: Options): Promise { + let pkgJsonPath: string; + try { + pkgJsonPath = await resolvePackageJSON(pkg, { from }); + } catch { + pkgJsonPath = createRequire(from).resolve(`${pkg}/package.json`); + } + const { bin } = await readPackageJSON(pkgJsonPath); + const rel = typeof bin === "string" ? bin : bin?.[binName ?? pkg.replace(/^@[^/]+\//, "")]; + if (!rel) throw new Error(`No bin "${binName ?? pkg}" in ${pkg}`); + return path.join(path.dirname(pkgJsonPath), rel); +} + +export const resolvePackageBin = memoize(_resolvePackageBin, { + cacheKey: ([pkg, opts]) => `${pkg}|${opts.from}|${opts.binName ?? ""}`, +}); diff --git a/packages/clibuddy/src/shell/shell.ts b/packages/clibuddy/src/shell/shell.ts index ece6efb..b9d8827 100644 --- a/packages/clibuddy/src/shell/shell.ts +++ b/packages/clibuddy/src/shell/shell.ts @@ -1,65 +1,76 @@ -import { $ as make$ } from "zx"; -import type { Shell, ShellOptions } from "./types.ts"; -import { getPreferLocal } from "./utils.ts"; +import { type Output, x as tinyexec } from "tinyexec"; +import { palette } from "../colors.ts"; +import type { RunOptions, ShellOptions } from "./types.ts"; export class ShellService { - #shell: Shell; #options: ShellOptions; - constructor(options: ShellOptions) { - this.#options = Object.freeze(options); - this.#shell = make$(options); + constructor(options: ShellOptions = {}) { + this.#options = Object.freeze({ + cwd: options.cwd ?? process.cwd(), + env: options.env, + verbose: options.verbose ?? true, + }); } - get options() { + get options(): ShellOptions { return this.#options; } - get $() { - return this.#shell; + at(cwd: string): ShellService { + return this.child({ cwd }); } - child(options: ShellOptions) { + child(options: ShellOptions): ShellService { return new ShellService({ ...this.#options, ...options, + env: options.env ? { ...this.#options.env, ...options.env } : this.#options.env, }); } - quiet(options?: ShellOptions) { - return this.child({ - ...options, - verbose: false, - }); + quiet(): ShellService { + return this.child({ verbose: false }); } - mute(options?: ShellOptions) { - return this.quiet({ - ...options, - stdio: "pipe", + async run(cmd: string, args: string[] = [], opts: RunOptions = {}): Promise { + const merged = this.#mergeRunOpts(opts); + if (merged.verbose) printCmdLine(opts.display ?? cmd, args); + return tinyexec(cmd, args, { + throwOnError: opts.throwOnError ?? true, + ...(opts.stdin !== undefined && { stdin: opts.stdin }), + nodeOptions: { + cwd: merged.cwd, + ...(merged.env && { env: merged.env }), + stdio: "inherit", + ...(opts.shell && { shell: true }), + }, }); } - at(cwd: string, options?: ShellOptions) { - const getLocals = (locals: boolean | string | string[] | undefined) => - // NOTE: the boolean handling is done outside when determining preferLocal - typeof locals === "boolean" ? [] : typeof locals === "undefined" ? [] : Array.isArray(locals) ? locals : [locals]; - - const cwdPreferLocal = getPreferLocal(cwd); - - const preferLocal = - options?.preferLocal === false - ? false - : [ - ...getLocals(this.#options.preferLocal), - ...getLocals(options?.preferLocal), - ...(cwdPreferLocal ? cwdPreferLocal : []), - ]; - - return this.child({ - ...options, - cwd, - preferLocal, + async runCaptured(cmd: string, args: string[] = [], opts: RunOptions = {}): Promise { + const merged = this.#mergeRunOpts(opts); + return tinyexec(cmd, args, { + throwOnError: opts.throwOnError ?? true, + ...(opts.stdin !== undefined && { stdin: opts.stdin }), + nodeOptions: { + cwd: merged.cwd, + ...(merged.env && { env: merged.env }), + ...(opts.shell && { shell: true }), + }, }); } + + #mergeRunOpts(opts: RunOptions) { + return { + cwd: opts.cwd ?? this.#options.cwd ?? process.cwd(), + env: opts.env ? { ...this.#options.env, ...opts.env } : this.#options.env, + verbose: opts.verbose ?? this.#options.verbose ?? true, + }; + } +} + +function printCmdLine(cmd: string, args: string[]): void { + const tail = args.length === 0 ? "" : ` ${args.join(" ")}`; + process.stderr.write(`${palette.muted("$")} ${palette.highlight(cmd)}${tail}\n`); } diff --git a/packages/clibuddy/src/shell/types.ts b/packages/clibuddy/src/shell/types.ts index 7e32549..96d258b 100644 --- a/packages/clibuddy/src/shell/types.ts +++ b/packages/clibuddy/src/shell/types.ts @@ -1,9 +1,12 @@ -import type { Options as ZxOptions, Shell as ZxShell } from "zx"; - -export type Shell = ZxShell; - -export type ShellOptions = Partial; +export type ShellOptions = { + cwd?: string; + env?: NodeJS.ProcessEnv; + verbose?: boolean; +}; -export type CreateOptions = ShellOptions & { - localBaseBinPath?: string | Array; +export type RunOptions = ShellOptions & { + throwOnError?: boolean; + shell?: boolean; + stdin?: string; + display?: string; }; diff --git a/packages/clibuddy/src/shell/utils.ts b/packages/clibuddy/src/shell/utils.ts index df8eb8e..879ac05 100644 --- a/packages/clibuddy/src/shell/utils.ts +++ b/packages/clibuddy/src/shell/utils.ts @@ -1,16 +1,7 @@ -import path from "node:path"; -import { ProcessOutput } from "zx"; +import { NonZeroExitError } from "tinyexec"; -export function isProcessOutput(value: unknown): value is ProcessOutput { - return value instanceof ProcessOutput; -} - -const getLocalBinPath = (dirPath: string) => path.join(dirPath, "node_modules", ".bin"); +export { NonZeroExitError }; -export function getPreferLocal(localBaseBinPath: string | Array | undefined) { - return !localBaseBinPath - ? undefined - : Array.isArray(localBaseBinPath) - ? localBaseBinPath.map(getLocalBinPath) - : [localBaseBinPath].map(getLocalBinPath); +export function isNonZeroExitError(value: unknown): value is NonZeroExitError { + return value instanceof NonZeroExitError; } diff --git a/packages/clibuddy/test-helpers/index.ts b/packages/clibuddy/test-helpers/index.ts deleted file mode 100644 index 09093e8..0000000 --- a/packages/clibuddy/test-helpers/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { vi } from "vitest"; -import { isRaw } from "../src/index.ts"; - -vi.mock("zx", async (importOriginal) => { - const originalZx = await importOriginal(); - - const $$ = vi.fn(function fakeShell(strs: TemplateStringsArray, ...args: unknown[]) { - let output = ""; - let argsIndex = 0; - - const stringifyArg = (arg: unknown) => (isRaw(arg) ? (arg as { stdout: string }).stdout : arg); - - for (const str of strs) { - if (str === "") { - if (args[argsIndex]) { - output += stringifyArg(args[argsIndex]); - argsIndex += 1; - } - } else { - output += str; - } - } - - return output; - }); - - const $ = vi.fn(function make$() { - return $$; - }); - - return { - ...originalZx, - $, - }; -}); diff --git a/packages/run-run/README.md b/packages/run-run/README.md index 09337a8..030e4d8 100644 --- a/packages/run-run/README.md +++ b/packages/run-run/README.md @@ -4,7 +4,7 @@ CLI toolbox to fullstack common scripts in [Variable Land](https://variable.land ## Prerequisites -- Node.js >= 24.0.0 +- Node.js >= 20.0.0 ## Toolbox diff --git a/packages/run-run/package.json b/packages/run-run/package.json index 501742c..6e5bd60 100644 --- a/packages/run-run/package.json +++ b/packages/run-run/package.json @@ -76,7 +76,7 @@ "oxlint": "1.50.0", "oxlint-tsgolint": "0.15.0", "rimraf": "6.1.3", - "tsdown": "0.21.10", + "tsdown": "0.22.0", "typescript": "6.0.3" }, "devDependencies": { diff --git a/packages/run-run/src/program/commands/test-static.ts b/packages/run-run/src/program/commands/test-static.ts index 59fd13c..3778a4e 100644 --- a/packages/run-run/src/program/commands/test-static.ts +++ b/packages/run-run/src/program/commands/test-static.ts @@ -9,6 +9,6 @@ export function createTestStaticCommand(ctx: Context) { "Runs static tests, including linting, formatting checks, and TypeScript type checking, to ensure code quality and correctness without executing the code.", ) .action(async function testStaticAction() { - await ctx.shell.$`rr x jscheck tscheck`; + await ctx.shell.run("rr", ["x", "jscheck", "tscheck"]); }); } diff --git a/packages/run-run/src/program/commands/tscheck.ts b/packages/run-run/src/program/commands/tscheck.ts index ba7a054..f309320 100644 --- a/packages/run-run/src/program/commands/tscheck.ts +++ b/packages/run-run/src/program/commands/tscheck.ts @@ -34,7 +34,9 @@ async function typecheckAt({ dir, scripts, log, shell, run }: TypecheckAtOptions const preScript = getPreScript(scripts); if (preScript) { log.start(`Running pre-script: ${preScript}`); - await shellAt.$`${preScript}`; + // Pre-scripts come from package.json and may contain shell features + // (`&&`, pipes, env-var substitution) — run them through `/bin/sh -c`. + await shellAt.run(preScript, [], { shell: true }); log.success("Pre-script completed"); } @@ -71,11 +73,10 @@ export function createTsCheckCommand(ctx: Context) { const runTypecheck = async (shell: ShellService) => { if (config.future?.oxc) { const oxlint = new OxlintService(shell); - await oxlint.exec(`--type-aware --type-check --report-unused-disable-directives`); + await oxlint.exec(["--type-aware", "--type-check", "--report-unused-disable-directives"]); } else { const tsc = new TscService(shell); - await tsc.exec(`--noEmit`); - // await shell.$`tsc --noEmit`; + await tsc.exec(["--noEmit"]); } }; diff --git a/packages/run-run/src/program/commands/x.ts b/packages/run-run/src/program/commands/x.ts index 58ff54d..74d6f0d 100644 --- a/packages/run-run/src/program/commands/x.ts +++ b/packages/run-run/src/program/commands/x.ts @@ -7,8 +7,7 @@ export function createXCommand(ctx: Context) { .description("Run multiple rr subcommands concurrently (e.g. `rr x jsc tsc`).") .argument("", "rr subcommands to execute concurrently") .action(async function runXAction(cmds: string[]) { - const { $ } = ctx.shell; - const results = await Promise.allSettled(cmds.map((cmd) => $`rr ${cmd}`)); + const results = await Promise.allSettled(cmds.map((cmd) => ctx.shell.run("rr", [cmd]))); if (results.some((r) => r.status === "rejected")) { process.exitCode = 1; } diff --git a/packages/run-run/src/services/biome.ts b/packages/run-run/src/services/biome.ts index d220df4..357d533 100644 --- a/packages/run-run/src/services/biome.ts +++ b/packages/run-run/src/services/biome.ts @@ -1,48 +1,34 @@ -import { createRequire } from "node:module"; import { isCI, type ShellService } from "@vlandoss/clibuddy"; import { TOOL_LABELS } from "#src/program/ui.ts"; import type { FormatOptions, Formatter, Linter, LintOptions, StaticChecker, StaticCheckerOptions } from "#src/types/tool.ts"; import { ToolService } from "./tool.ts"; +const COMMON_FLAGS = ["--colors=force", "--no-errors-on-unmatched"]; + export class BiomeService extends ToolService implements Formatter, Linter, StaticChecker { constructor(shellService: ShellService) { - super({ bin: "biome", ui: TOOL_LABELS.BIOME, shellService }); - } - - override getBinDir() { - const require = createRequire(import.meta.url); - return require.resolve("@biomejs/biome/bin/biome"); + super({ pkg: "@biomejs/biome", bin: "biome", ui: TOOL_LABELS.BIOME, shellService }); } async format(options: FormatOptions) { - const commonOptions = "format --colors=force --no-errors-on-unmatched"; - - if (options.fix) { - await this.exec(`${commonOptions} --fix`); - } else { - await this.exec(`${commonOptions}`); - } + const args = ["format", ...COMMON_FLAGS]; + if (options.fix) args.push("--fix"); + await this.exec(args); } async lint(options: LintOptions) { - const commonOptions = "check --colors=force --no-errors-on-unmatched --formatter-enabled=false"; - - if (options.fix) { - await this.exec(`${commonOptions} --fix --unsafe`); - } else { - await this.exec(`${commonOptions}`); - } + const args = ["check", ...COMMON_FLAGS, "--formatter-enabled=false"]; + if (options.fix) args.push("--fix", "--unsafe"); + await this.exec(args); } async check(options: StaticCheckerOptions): Promise { - const commonOptions = (cmd = "check") => `${cmd} --colors=force --no-errors-on-unmatched`; - if (options.fix) { - await this.exec(`${commonOptions()} --fix`); + await this.exec(["check", ...COMMON_FLAGS, "--fix"]); } else if (options.fixStaged) { - await this.exec(`${commonOptions()} --fix --staged`); + await this.exec(["check", ...COMMON_FLAGS, "--fix", "--staged"]); } else { - await this.exec(`${commonOptions(isCI ? "ci" : "check")}`); + await this.exec([isCI ? "ci" : "check", ...COMMON_FLAGS]); } } } diff --git a/packages/run-run/src/services/ctx.ts b/packages/run-run/src/services/ctx.ts index 6d6663b..784b659 100644 --- a/packages/run-run/src/services/ctx.ts +++ b/packages/run-run/src/services/ctx.ts @@ -32,10 +32,7 @@ export async function createContext(binDir: string): Promise { debug("app pkg info: %O", appPkg.info()); debug("bin pkg info: %O", binPkg.info()); - const shell = createShellService({ - localBaseBinPath: [binDir], - stdio: "inherit", - }); + const shell = createShellService(); debug("shell service options: %O", shell.options); diff --git a/packages/run-run/src/services/oxfmt.ts b/packages/run-run/src/services/oxfmt.ts index d05b0df..fc598e6 100644 --- a/packages/run-run/src/services/oxfmt.ts +++ b/packages/run-run/src/services/oxfmt.ts @@ -5,20 +5,10 @@ import { ToolService } from "./tool.ts"; export class OxfmtService extends ToolService implements Formatter { constructor(shellService: ShellService) { - super({ bin: "oxfmt", ui: TOOL_LABELS.OXFMT, shellService }); - } - - override getBinDir() { - return require.resolve("oxfmt/bin/oxfmt"); + super({ pkg: "oxfmt", ui: TOOL_LABELS.OXFMT, shellService }); } async format(options: FormatOptions) { - const commonOptions = "--no-error-on-unmatched-pattern"; - - if (options.fix) { - await this.exec(`${commonOptions} --fix`); - } else { - await this.exec(`${commonOptions} --check`); - } + await this.exec(["--no-error-on-unmatched-pattern", options.fix ? "--fix" : "--check"]); } } diff --git a/packages/run-run/src/services/oxlint.ts b/packages/run-run/src/services/oxlint.ts index 3f48d0c..42ff58d 100644 --- a/packages/run-run/src/services/oxlint.ts +++ b/packages/run-run/src/services/oxlint.ts @@ -5,20 +5,10 @@ import { ToolService } from "./tool.ts"; export class OxlintService extends ToolService implements Linter { constructor(shellService: ShellService) { - super({ bin: "oxlint", ui: TOOL_LABELS.OXLINT, shellService }); - } - - override getBinDir() { - return require.resolve("oxlint/bin/oxlint"); + super({ pkg: "oxlint", ui: TOOL_LABELS.OXLINT, shellService }); } async lint(options: LintOptions) { - const commonOptions = "--report-unused-disable-directives"; - - if (options.fix) { - await this.exec(`${commonOptions} --fix`); - } else { - await this.exec(`${commonOptions} --check`); - } + await this.exec(["--report-unused-disable-directives", options.fix ? "--fix" : "--check"]); } } diff --git a/packages/run-run/src/services/tool.ts b/packages/run-run/src/services/tool.ts index d4ce216..41f58f3 100644 --- a/packages/run-run/src/services/tool.ts +++ b/packages/run-run/src/services/tool.ts @@ -1,78 +1,60 @@ -import fs from "node:fs"; -import path from "node:path"; -import type { ShellService } from "@vlandoss/clibuddy"; -import memoize from "memoize"; +import { resolvePackageBin, type ShellService } from "@vlandoss/clibuddy"; import type { DoctorResult } from "#src/types/tool.ts"; type CreateOptions = { - bin: string; - ui?: string; + pkg: string; + bin?: string; + ui: string; shellService: ShellService; }; -export abstract class ToolService { +export class ToolService { #shellService: ShellService; + #pkg: string; #bin: string; #ui: string; - constructor({ bin, ui, shellService }: CreateOptions) { - this.#bin = bin; - this.#ui = ui ?? bin; - this.#shellService = shellService; + get bin() { + return this.#bin; } - getBinDir?(): string; - - async exec(args?: string | string[]) { - const shell = this.#shell(); - return this.#run(shell, args); + get ui() { + return this.#ui; } - async doctor(): Promise { - const shell = this.#shell().mute(); - - const output = await this.#run(shell, "--help"); - const ok = output.exitCode === 0; - - return { ok, output }; + get pkg() { + return this.#pkg; } - #shell = memoize((cwd?: string) => { - const preferLocal = this.#getPreferLocal(); + constructor({ pkg, bin, ui, shellService }: CreateOptions) { + this.#pkg = pkg; + this.#bin = bin ?? pkg; + this.#ui = ui; + this.#shellService = shellService; + } - return this.#shellService.child({ - ...(cwd && { cwd }), - ...(preferLocal && { preferLocal }), + async getBinDir() { + return resolvePackageBin(this.#pkg, { + from: import.meta.url, + binName: this.#bin, }); - }); - - #run(shell: ShellService, args?: string | string[]) { - if (!args) { - return shell.$`${this.#bin}`; - } - - return shell.$`${this.#bin} ${typeof args === "string" ? args : args.join(" ")}`; } - #getPreferLocal() { - if (!this.getBinDir) { - return undefined; - } - - try { - const binPath = this.getBinDir(); - const isDir = fs.statSync(binPath).isDirectory(); - return isDir ? binPath : path.dirname(binPath); - } catch { - return undefined; - } + async exec(args: string[] = []) { + return this.#shellService.run(await this.getBinDir(), args, { display: this.#bin }); } - get bin() { - return this.#bin; - } + async doctor(): Promise { + const output = await this.#shellService.runCaptured(await this.getBinDir(), ["--help"], { throwOnError: false }); + const ok = output.exitCode === 0; - get ui() { - return this.#ui; + return { + ok, + output: { + stdout: output.stdout, + stderr: output.stderr, + exitCode: output.exitCode, + }, + }; } } diff --git a/packages/run-run/src/services/tsc.ts b/packages/run-run/src/services/tsc.ts index 172ff77..2cefd76 100644 --- a/packages/run-run/src/services/tsc.ts +++ b/packages/run-run/src/services/tsc.ts @@ -4,6 +4,6 @@ import { ToolService } from "./tool.ts"; export class TscService extends ToolService { constructor(shellService: ShellService) { - super({ bin: "tsc", ui: TOOL_LABELS.TSC, shellService }); + super({ pkg: "typescript", bin: "tsc", ui: TOOL_LABELS.TSC, shellService }); } } diff --git a/packages/run-run/src/services/tsdown.ts b/packages/run-run/src/services/tsdown.ts index dc85eff..f1a4958 100644 --- a/packages/run-run/src/services/tsdown.ts +++ b/packages/run-run/src/services/tsdown.ts @@ -4,7 +4,7 @@ import { ToolService } from "./tool.ts"; export class TsdownService extends ToolService { constructor(shellService: ShellService) { - super({ bin: "tsdown", ui: TOOL_LABELS.TSDOWN, shellService }); + super({ pkg: "tsdown", ui: TOOL_LABELS.TSDOWN, shellService }); } async buildLib() { diff --git a/packages/run-run/src/types/tool.ts b/packages/run-run/src/types/tool.ts index 17450a4..2ab8a9d 100644 --- a/packages/run-run/src/types/tool.ts +++ b/packages/run-run/src/types/tool.ts @@ -14,7 +14,7 @@ export type StaticCheckerOptions = { export type DoctorOutput = { stdout: string; stderr: string; - exitCode: number | null; + exitCode: number | undefined; }; export type DoctorResult = { diff --git a/packages/run-run/test/helpers.ts b/packages/run-run/test/helpers.ts index d4b115c..47e5989 100644 --- a/packages/run-run/test/helpers.ts +++ b/packages/run-run/test/helpers.ts @@ -1,17 +1,22 @@ import { spawnSync } from "node:child_process"; -import { resolve } from "node:path"; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path, { resolve } from "node:path"; import { dirnameOf } from "@vlandoss/clibuddy"; -export function createTestCli(mode: "dev" | "prod" = "prod") { - const bin = resolve(dirnameOf(import.meta), "../bin"); +const BIN = resolve(dirnameOf(import.meta), "../bin"); - // NODE_ENV=test and TEST=true (injected by vitest) cause consola to suppress - // all output. Override them so the subprocess behaves like a real invocation. - // NO_COLOR=1 keeps assertions stable across color-library detection differences - // — tests check content, not terminal escape sequences. - return function cli(cmd: string) { - return spawnSync(bin, cmd.split(" "), { +type CliMode = "dev" | "prod"; + +type CliOptions = { + cwd?: string; +}; + +export function createTestCli(mode: CliMode = "prod") { + return function cli(cmd: string, opts: CliOptions = {}) { + return spawnSync(BIN, cmd.split(" ").filter(Boolean), { encoding: "utf8", + cwd: opts.cwd, env: mode === "dev" ? process.env @@ -24,3 +29,28 @@ export function createTestCli(mode: "dev" | "prod" = "prod") { }); }; } + +type FixtureFiles = Record; + +export function makeFixture(name: string, files: FixtureFiles): { dir: string; cleanup: () => void } { + const dir = mkdtempSync(path.join(tmpdir(), `rr-${name}-`)); + for (const [rel, content] of Object.entries(files)) { + const abs = path.join(dir, rel); + mkdirSync(path.dirname(abs), { recursive: true }); + writeFileSync(abs, content); + } + return { + dir, + cleanup: () => { + rmSync(dir, { recursive: true, force: true }); + }, + }; +} + +export const fixtures = { + pkg: (name = "rr-test-fixture") => `${JSON.stringify({ name, version: "0.0.0", private: true }, null, 2)}\n`, + biomeNoop: () => + `${JSON.stringify({ formatter: { enabled: false }, linter: { enabled: false }, assist: { enabled: false } }, null, 2)}\n`, + tsconfig: () => + `${JSON.stringify({ compilerOptions: { target: "es2020", module: "esnext", moduleResolution: "bundler", strict: true, noEmit: true, skipLibCheck: true } }, null, 2)}\n`, +}; diff --git a/packages/run-run/test/integration/build-lib.test.ts b/packages/run-run/test/integration/build-lib.test.ts new file mode 100644 index 0000000..079ccd6 --- /dev/null +++ b/packages/run-run/test/integration/build-lib.test.ts @@ -0,0 +1,23 @@ +import { afterEach, beforeEach, describe, expect, test } from "vitest"; +import { createTestCli, fixtures, makeFixture } from "#test/helpers.ts"; + +const cli = createTestCli(); + +describe("rr build:lib", () => { + let fixture: { dir: string; cleanup: () => void }; + + beforeEach(() => { + fixture = makeFixture("build-lib", { + "package.json": fixtures.pkg(), + }); + }); + + afterEach(() => fixture.cleanup()); + + test("doctor: exits 0 and reports tsdown ok", () => { + const r = cli("build:lib doctor", { cwd: fixture.dir }); + expect(r.stderr).toBe(""); + expect(r.stdout).toContain("tsdown ok"); + expect(r.status).toBe(0); + }); +}); diff --git a/packages/run-run/test/integration/cli.test.ts b/packages/run-run/test/integration/cli.test.ts new file mode 100644 index 0000000..a0f90c1 --- /dev/null +++ b/packages/run-run/test/integration/cli.test.ts @@ -0,0 +1,23 @@ +import { describe, expect, test } from "vitest"; +import { createTestCli } from "#test/helpers.ts"; + +const cli = createTestCli(); + +describe("rr (cli surface)", () => { + test("--help prints usage and exits 0", () => { + const r = cli("--help"); + expect(r.status).toBe(0); + expect(r.stdout).toContain("Usage:"); + }); + + test("--version prints a semver-shaped string", () => { + const r = cli("--version"); + expect(r.status).toBe(0); + expect(r.stdout.trim()).toMatch(/^\d+\.\d+\.\d+/); + }); + + test("an unknown command exits non-zero", () => { + const r = cli("definitely-not-a-real-command"); + expect(r.status).not.toBe(0); + }); +}); diff --git a/packages/run-run/test/integration/doctor.test.ts b/packages/run-run/test/integration/doctor.test.ts deleted file mode 100644 index ebb3d49..0000000 --- a/packages/run-run/test/integration/doctor.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { expect, test } from "vitest"; -import { createTestCli } from "#test/helpers.ts"; - -const cli = createTestCli(); - -test("jsc doctor: exits 0 and reports biome ok", () => { - const result = cli("jsc doctor"); - - expect(result.stderr).toBe(""); - expect(result.stdout).toContain("biome ok"); - expect(result.status).toBe(0); -}); - -test("tsc doctor: exits 0 and reports tsc ok", () => { - const result = cli("tsc doctor"); - - expect(result.stderr).toBe(""); - expect(result.stdout).toContain("tsc ok"); - expect(result.status).toBe(0); -}); - -test("lint doctor: exits 0 and reports biome ok", () => { - const result = cli("lint doctor"); - - expect(result.stderr).toBe(""); - expect(result.stdout).toContain("biome ok"); - expect(result.status).toBe(0); -}); - -test("format doctor: exits 0 and reports biome ok", () => { - const result = cli("format doctor"); - - expect(result.stderr).toBe(""); - expect(result.stdout).toContain("biome ok"); - expect(result.status).toBe(0); -}); - -test("build:lib doctor: exits 0 and reports tsdown ok", () => { - const result = cli("build:lib doctor"); - - expect(result.stderr).toBe(""); - expect(result.stdout).toContain("tsdown ok"); - expect(result.status).toBe(0); -}); diff --git a/packages/run-run/test/integration/format.test.ts b/packages/run-run/test/integration/format.test.ts new file mode 100644 index 0000000..b4fe0a9 --- /dev/null +++ b/packages/run-run/test/integration/format.test.ts @@ -0,0 +1,32 @@ +import { afterEach, beforeEach, describe, expect, test } from "vitest"; +import { createTestCli, fixtures, makeFixture } from "#test/helpers.ts"; + +const cli = createTestCli(); + +describe("rr format", () => { + let fixture: { dir: string; cleanup: () => void }; + + beforeEach(() => { + fixture = makeFixture("format", { + "package.json": fixtures.pkg(), + "biome.json": fixtures.biomeNoop(), + "src/ok.ts": "export const ok = 1;\n", + }); + }); + + afterEach(() => fixture.cleanup()); + + test("doctor: exits 0 and reports biome ok", () => { + const r = cli("format doctor", { cwd: fixture.dir }); + expect(r.stderr).toBe(""); + expect(r.stdout).toContain("biome ok"); + expect(r.status).toBe(0); + }); + + test("runs biome format end-to-end", () => { + const r = cli("format", { cwd: fixture.dir }); + const combined = r.stdout + r.stderr; + expect(combined).toMatch(/\$ biome format/); + expect(r.status).toBe(0); + }); +}); diff --git a/packages/run-run/test/integration/jsc.test.ts b/packages/run-run/test/integration/jsc.test.ts new file mode 100644 index 0000000..49faa2c --- /dev/null +++ b/packages/run-run/test/integration/jsc.test.ts @@ -0,0 +1,41 @@ +import { afterEach, beforeEach, describe, expect, test } from "vitest"; +import { createTestCli, fixtures, makeFixture } from "#test/helpers.ts"; + +const cli = createTestCli(); + +describe("rr jsc", () => { + let fixture: { dir: string; cleanup: () => void }; + + beforeEach(() => { + fixture = makeFixture("jsc", { + "package.json": fixtures.pkg(), + "biome.json": fixtures.biomeNoop(), + "src/ok.ts": "export const ok = 1;\n", + }); + }); + + afterEach(() => fixture.cleanup()); + + test("doctor: exits 0 and reports biome ok", () => { + const r = cli("jsc doctor", { cwd: fixture.dir }); + expect(r.stderr).toBe(""); + expect(r.stdout).toContain("biome ok"); + expect(r.status).toBe(0); + }); + + test("runs biome end-to-end on a clean fixture", () => { + const r = cli("jsc", { cwd: fixture.dir }); + const combined = r.stdout + r.stderr; + // `check` locally, `ci` in CI — both are valid and exercise the same path. + expect(combined).toMatch(/\$ biome (check|ci)/); + expect(combined).not.toMatch(/expected `COMMAND/); + expect(r.status).toBe(0); + }); + + test("forwards each biome flag as its own argv entry", () => { + const r = cli("jsc", { cwd: fixture.dir }); + const combined = r.stdout + r.stderr; + expect(combined).toMatch(/\$ biome (check|ci) --colors=force --no-errors-on-unmatched/); + expect(r.status).toBe(0); + }); +}); diff --git a/packages/run-run/test/integration/lint.test.ts b/packages/run-run/test/integration/lint.test.ts new file mode 100644 index 0000000..209048d --- /dev/null +++ b/packages/run-run/test/integration/lint.test.ts @@ -0,0 +1,32 @@ +import { afterEach, beforeEach, describe, expect, test } from "vitest"; +import { createTestCli, fixtures, makeFixture } from "#test/helpers.ts"; + +const cli = createTestCli(); + +describe("rr lint", () => { + let fixture: { dir: string; cleanup: () => void }; + + beforeEach(() => { + fixture = makeFixture("lint", { + "package.json": fixtures.pkg(), + "biome.json": fixtures.biomeNoop(), + "src/ok.ts": "export const ok = 1;\n", + }); + }); + + afterEach(() => fixture.cleanup()); + + test("doctor: exits 0 and reports biome ok", () => { + const r = cli("lint doctor", { cwd: fixture.dir }); + expect(r.stderr).toBe(""); + expect(r.stdout).toContain("biome ok"); + expect(r.status).toBe(0); + }); + + test("runs biome check with formatter disabled end-to-end", () => { + const r = cli("lint", { cwd: fixture.dir }); + const combined = r.stdout + r.stderr; + expect(combined).toMatch(/\$ biome check .*--formatter-enabled=false/); + expect(r.status).toBe(0); + }); +}); diff --git a/packages/run-run/test/integration/node-compat.test.ts b/packages/run-run/test/integration/node-compat.test.ts deleted file mode 100644 index 4ee6eb5..0000000 --- a/packages/run-run/test/integration/node-compat.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { expect, test } from "vitest"; -import { createTestCli } from "#test/helpers.ts"; - -const cli = createTestCli(); - -test("node bin.mjs --help exits 0", () => { - const result = cli("--help"); - - expect(result.stderr).toBe(""); - expect(result.stdout).toContain("Usage:"); - expect(result.status).toBe(0); -}); diff --git a/packages/run-run/test/integration/tsc.test.ts b/packages/run-run/test/integration/tsc.test.ts new file mode 100644 index 0000000..051f9ff --- /dev/null +++ b/packages/run-run/test/integration/tsc.test.ts @@ -0,0 +1,59 @@ +import { afterEach, beforeEach, describe, expect, test } from "vitest"; +import { createTestCli, fixtures, makeFixture } from "#test/helpers.ts"; + +const cli = createTestCli(); + +describe("rr tsc", () => { + let fixture: { dir: string; cleanup: () => void }; + + afterEach(() => fixture?.cleanup()); + + describe("doctor", () => { + beforeEach(() => { + fixture = makeFixture("tsc-doctor", { + "package.json": fixtures.pkg(), + "tsconfig.json": fixtures.tsconfig(), + }); + }); + + test("exits 0 and reports tsc ok", () => { + const r = cli("tsc doctor", { cwd: fixture.dir }); + expect(r.stderr).toBe(""); + expect(r.stdout).toContain("tsc ok"); + expect(r.status).toBe(0); + }); + }); + + describe("happy path", () => { + beforeEach(() => { + fixture = makeFixture("tsc-ok", { + "package.json": fixtures.pkg(), + "tsconfig.json": fixtures.tsconfig(), + "src/ok.ts": "export const ok: number = 1;\n", + }); + }); + + test("exits 0 when types check cleanly", () => { + const r = cli("tsc", { cwd: fixture.dir }); + const combined = r.stdout + r.stderr; + expect(combined).toMatch(/\$ tsc --noEmit/); + expect(r.status).toBe(0); + }); + }); + + describe("type error path", () => { + beforeEach(() => { + fixture = makeFixture("tsc-bad", { + "package.json": fixtures.pkg(), + "tsconfig.json": fixtures.tsconfig(), + "src/bad.ts": 'export const bad: number = "not a number";\n', + }); + }); + + test("surfaces the type error and exits non-zero", () => { + const r = cli("tsc", { cwd: fixture.dir }); + expect(r.status).not.toBe(0); + expect(r.stdout + r.stderr).toMatch(/Type 'string' is not assignable to type 'number'/); + }); + }); +}); diff --git a/packages/run-run/test/setup.ts b/packages/run-run/test/setup.ts index 6f9223c..fe167b6 100644 --- a/packages/run-run/test/setup.ts +++ b/packages/run-run/test/setup.ts @@ -1,7 +1,5 @@ import { vi } from "vitest"; -// import "@vlandoss/clibuddy/test-helpers"; - // required to make the version command work independently of the package.json version process.env.VERSION = "0.0.0-test"; diff --git a/packages/tsdown-config/package.json b/packages/tsdown-config/package.json index fccf26c..131a882 100644 --- a/packages/tsdown-config/package.json +++ b/packages/tsdown-config/package.json @@ -29,6 +29,6 @@ "node": ">=20.0.0" }, "peerDependencies": { - "tsdown": "^0.21.10" + "tsdown": "^0.22.0" } } diff --git a/packages/vland/src/actions/init.ts b/packages/vland/src/actions/init.ts index 2e4831b..f5924ca 100644 --- a/packages/vland/src/actions/init.ts +++ b/packages/vland/src/actions/init.ts @@ -1,20 +1,20 @@ import { readdir } from "node:fs/promises"; import { isAbsolute, resolve } from "node:path"; -import { cancel, intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts"; +import { cancel, confirm, intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts"; import { hasTTY, palette } from "@vlandoss/clibuddy"; import { detectPackageManager, installDependencies } from "nypm"; import type { Context } from "#src/services/ctx.ts"; import { logger } from "#src/services/logger.ts"; import { replacePlaceholders, updateRootPackageName } from "./placeholders.ts"; -import { fetchTemplate, TEMPLATES, type TemplateName } from "./template.ts"; +import { fetchTemplate, TEMPLATE_META, TEMPLATES, type TemplateName } from "./template.ts"; export type InitOptions = { name?: string; template?: TemplateName; dir?: string; pm?: "npm" | "pnpm" | "yarn" | "bun"; - install: boolean; - git: boolean; + install?: boolean; + git?: boolean; force: boolean; }; @@ -46,8 +46,8 @@ async function isDirEmpty(dir: string): Promise { async function readGitAuthor(shell: Context["shell"]): Promise { try { const [name, email] = await Promise.all([ - shell.$`git config --get user.name`.nothrow(), - shell.$`git config --get user.email`.nothrow(), + shell.runCaptured("git", ["config", "--get", "user.name"], { throwOnError: false }), + shell.runCaptured("git", ["config", "--get", "user.email"], { throwOnError: false }), ]); const trimmedName = name.stdout.trim(); const trimmedEmail = email.stdout.trim(); @@ -67,8 +67,7 @@ export async function runInit(ctx: Context, options: InitOptions) { const debug = logger.subdebug("init"); debug("options: %O", options); - // Mute zx so the @clack/prompts UI stays clean while git output is captured. - const shell = ctx.shell.mute(); + const shell = ctx.shell; intro(`${palette.label(" vland init ")}`); @@ -93,7 +92,7 @@ export async function runInit(ctx: Context, options: InitOptions) { if (!hasTTY) abort("Project name is required in non-interactive environments. Pass it as the first argument."); const value = await text({ message: "Project name", - placeholder: "my-app", + placeholder: TEMPLATE_META[template].placeholder, validate: (input) => validateProjectName(input ?? ""), }); if (isCancel(value)) abort("Cancelled."); @@ -153,13 +152,17 @@ export async function runInit(ctx: Context, options: InitOptions) { await updateRootPackageName(dir, name); placeholderSpin.stop("Placeholders applied"); - // 7. Install deps - if (options.install) { + // 7. Resolve install / git decisions (prompt with default-yes when not set on CLI) + const shouldInstall = await resolveYesNo(options.install, "Install dependencies?"); + const shouldGit = await resolveYesNo(options.git, "Initialise a git repository?"); + + // 8. Install deps + if (shouldInstall) { const detected = options.pm ?? (await detectPackageManager(dir, { ignorePackageJSON: false }))?.name ?? "pnpm"; const installSpin = spinner(); installSpin.start(`Installing dependencies with ${palette.highlight(detected)}`); try { - await installDependencies({ cwd: dir, packageManager: { name: detected, command: detected } }); + await installDependencies({ cwd: dir, packageManager: { name: detected, command: detected }, silent: true }); installSpin.stop(`Installed with ${palette.highlight(detected)}`); } catch (error) { installSpin.stop("Failed to install dependencies", 1); @@ -167,44 +170,52 @@ export async function runInit(ctx: Context, options: InitOptions) { debug("install error: %O", error); } } else { - log.info(`Skipping ${palette.highlight("install")} (--no-install).`); + log.info(`Skipping ${palette.highlight("install")}.`); } - // 8. Git init - if (options.git) { + // 9. Git init + if (shouldGit) { const gitSpin = spinner(); gitSpin.start("Initialising git repository"); try { const gitShell = shell.at(dir).child({ env: { - ...process.env, GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME ?? "vland", GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL ?? "noreply@variable.land", GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME ?? "vland", GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL ?? "noreply@variable.land", }, }); - await gitShell.$`git init`; - await gitShell.$`git add -A`; - await gitShell.$`git commit -m ${"chore: initial commit from vland"}`; + await gitShell.runCaptured("git", ["init"]); + await gitShell.runCaptured("git", ["add", "-A"]); + await gitShell.runCaptured("git", ["commit", "-m", "chore: initial commit from vland"]); gitSpin.stop("Initialised git repository"); } catch (error) { gitSpin.stop("Failed to initialise git", 1); debug("git error: %O", error); } } else { - log.info(`Skipping ${palette.highlight("git init")} (--no-git).`); + log.info(`Skipping ${palette.highlight("git init")}.`); } - // 9. Outro with next steps + // 10. Outro with next steps const detectedPm = options.pm ?? (await detectPackageManager(dir, { ignorePackageJSON: false }))?.name ?? "pnpm"; + const runScript = TEMPLATE_META[template].runScript; outro( [ palette.success("Done!"), "", palette.muted("Next steps:"), ` cd ${name}`, - options.install ? ` ${detectedPm} dev` : ` ${detectedPm} install && ${detectedPm} dev`, + shouldInstall ? ` ${detectedPm} ${runScript}` : ` ${detectedPm} install && ${detectedPm} ${runScript}`, ].join("\n"), ); } + +async function resolveYesNo(explicit: boolean | undefined, message: string): Promise { + if (typeof explicit === "boolean") return explicit; + if (!hasTTY) return true; + const value = await confirm({ message, initialValue: true }); + if (isCancel(value)) abort("Cancelled."); + return value as boolean; +} diff --git a/packages/vland/src/actions/template.ts b/packages/vland/src/actions/template.ts index f90c29c..460b5b3 100644 --- a/packages/vland/src/actions/template.ts +++ b/packages/vland/src/actions/template.ts @@ -6,6 +6,12 @@ import { logger } from "#src/services/logger.ts"; export const TEMPLATES = ["library", "backend", "monorepo"] as const; export type TemplateName = (typeof TEMPLATES)[number]; +export const TEMPLATE_META: Record = { + library: { placeholder: "my-lib", runScript: "test" }, + backend: { placeholder: "my-api", runScript: "dev" }, + monorepo: { placeholder: "my-mono", runScript: "dev" }, +}; + const GITHUB_SOURCE = "github:variableland/dx"; const GITHUB_REF = "main"; diff --git a/packages/vland/src/program/commands/init.ts b/packages/vland/src/program/commands/init.ts index 8f6683c..6134252 100644 --- a/packages/vland/src/program/commands/init.ts +++ b/packages/vland/src/program/commands/init.ts @@ -21,14 +21,20 @@ export function createInitCommand(ctx: Context) { .addOption(new Option("-t, --template ", "template to use").choices([...TEMPLATES])) .addOption(new Option("-d, --dir ", "target directory (default: ./)")) .addOption(new Option("--pm ", "package manager to use").choices(["npm", "pnpm", "yarn", "bun"])) + .addOption(new Option("--install", "install dependencies (skip prompt)")) .addOption(new Option("--no-install", "skip dependency installation")) + .addOption(new Option("--git", "initialise git repository (skip prompt)")) .addOption(new Option("--no-git", "skip git init")) .addOption(new Option("-f, --force", "overwrite existing directory").default(false)) - .action(async (name: string | undefined, options: InitOptions) => { + .action(async function (this: import("commander").Command, name: string | undefined, options: InitOptions) { console.log(getBannerText(ctx.binPkg.version)); + const installSource = this.getOptionValueSource("install"); + const gitSource = this.getOptionValueSource("git"); await runInit(ctx, { name, ...options, + install: installSource === "cli" ? options.install : undefined, + git: gitSource === "cli" ? options.git : undefined, }); }); } diff --git a/packages/vland/src/services/ctx.ts b/packages/vland/src/services/ctx.ts index fa2972b..5946207 100644 --- a/packages/vland/src/services/ctx.ts +++ b/packages/vland/src/services/ctx.ts @@ -22,9 +22,7 @@ export async function createContext(binDir: string): Promise { debug("bin pkg info: %O", binPkg.info()); - const shell = createShellService({ - localBaseBinPath: [binDir], - }); + const shell = createShellService(); debug("shell service options: %O", shell.options); diff --git a/packages/vland/test/helpers.ts b/packages/vland/test/helpers.ts new file mode 100644 index 0000000..65fe2ce --- /dev/null +++ b/packages/vland/test/helpers.ts @@ -0,0 +1,49 @@ +import { spawnSync } from "node:child_process"; +import { existsSync, mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; + +export { existsSync as pathExists }; + +const REPO_ROOT = path.resolve(import.meta.dirname, "..", "..", ".."); +const TEMPLATES_DIR = path.join(REPO_ROOT, "templates"); +const VLAND_BIN = path.join(REPO_ROOT, "packages", "vland", "bin"); + +type CliMode = "dev" | "prod"; + +type CliOptions = { + cwd?: string; +}; + +export function createTestCli(mode: CliMode = "prod") { + return function cli(cmd: string, opts: CliOptions = {}) { + return spawnSync(VLAND_BIN, cmd.split(" ").filter(Boolean), { + encoding: "utf8", + cwd: opts.cwd, + env: + mode === "dev" + ? { ...process.env, VLAND_TEMPLATES_DIR: TEMPLATES_DIR } + : { + ...process.env, + NODE_ENV: "production", + TEST: undefined, + NO_COLOR: "1", + VLAND_TEMPLATES_DIR: TEMPLATES_DIR, + }, + }); + }; +} + +export function makeTmpDir(name: string): { dir: string; cleanup: () => void } { + const dir = mkdtempSync(path.join(tmpdir(), `vland-${name}-`)); + return { + dir, + cleanup: () => { + rmSync(dir, { recursive: true, force: true }); + }, + }; +} + +export function gitOutput(cwd: string, args: string[]): string { + return spawnSync("git", args, { cwd, encoding: "utf8" }).stdout.trim(); +} diff --git a/packages/vland/test/integration/init-git.test.ts b/packages/vland/test/integration/init-git.test.ts new file mode 100644 index 0000000..8a6a02f --- /dev/null +++ b/packages/vland/test/integration/init-git.test.ts @@ -0,0 +1,50 @@ +import path from "node:path"; +import { afterEach, beforeEach, describe, expect, test } from "vitest"; +import { createTestCli, gitOutput, makeTmpDir, pathExists } from "#test/helpers.ts"; + +const cli = createTestCli(); + +describe("vland init (git initialisation)", () => { + let fixture: { dir: string; cleanup: () => void }; + + beforeEach(() => { + fixture = makeTmpDir("init-git"); + }); + + afterEach(() => fixture.cleanup()); + + test("--git: creates a repo with the canonical first commit", async () => { + const name = "git-on"; + const r = cli(`init ${name} -t library --no-install --git`, { cwd: fixture.dir }); + expect(r.status).toBe(0); + const combined = r.stdout + r.stderr; + expect(combined).not.toMatch(/pathspec/i); + expect(combined).not.toMatch(/Failed to initialise git/); + + const projectDir = path.join(fixture.dir, name); + expect(pathExists(path.join(projectDir, ".git"))).toBe(true); + expect(gitOutput(projectDir, ["log", "-1", "--pretty=%s"])).toBe("chore: initial commit from vland"); + expect(gitOutput(projectDir, ["rev-list", "--count", "HEAD"])).toBe("1"); + }); + + test("defaults to git init in non-interactive runs when --no-git is omitted", async () => { + const name = "git-default"; + const r = cli(`init ${name} -t library --no-install`, { cwd: fixture.dir }); + expect(r.status).toBe(0); + expect(r.stdout + r.stderr).not.toMatch(/pathspec/i); + + const projectDir = path.join(fixture.dir, name); + expect(pathExists(path.join(projectDir, ".git"))).toBe(true); + expect(gitOutput(projectDir, ["log", "-1", "--pretty=%s"])).toBe("chore: initial commit from vland"); + }); + + test("--no-git: skips and prints a Skipping note", async () => { + const name = "git-off"; + const r = cli(`init ${name} -t library --no-install --no-git`, { cwd: fixture.dir }); + expect(r.status).toBe(0); + expect(r.stdout + r.stderr).toMatch(/Skipping.*git init/); + + const projectDir = path.join(fixture.dir, name); + expect(pathExists(path.join(projectDir, ".git"))).toBe(false); + }); +}); diff --git a/packages/vland/test/integration/init-install.test.ts b/packages/vland/test/integration/init-install.test.ts new file mode 100644 index 0000000..393495e --- /dev/null +++ b/packages/vland/test/integration/init-install.test.ts @@ -0,0 +1,28 @@ +import { afterEach, beforeEach, describe, expect, test } from "vitest"; +import { createTestCli, makeTmpDir } from "#test/helpers.ts"; + +const cli = createTestCli(); + +describe("vland init (install resolution)", () => { + let fixture: { dir: string; cleanup: () => void }; + + beforeEach(() => { + fixture = makeTmpDir("init-install"); + }); + + afterEach(() => fixture.cleanup()); + + test("library: --no-install recommends `install && test` (no dev script in libraries)", () => { + const r = cli("init install-off -t library --no-install --no-git", { cwd: fixture.dir }); + expect(r.status).toBe(0); + const out = r.stdout + r.stderr; + expect(out).toMatch(/Skipping.*install/); + expect(out).toMatch(/install && pnpm test/); + }); + + test("backend: --no-install recommends `install && dev`", () => { + const r = cli("init install-off -t backend --no-install --no-git", { cwd: fixture.dir }); + expect(r.status).toBe(0); + expect(r.stdout + r.stderr).toMatch(/install && pnpm dev/); + }); +}); diff --git a/packages/vland/test/integration/init-templates.test.ts b/packages/vland/test/integration/init-templates.test.ts new file mode 100644 index 0000000..2425223 --- /dev/null +++ b/packages/vland/test/integration/init-templates.test.ts @@ -0,0 +1,69 @@ +import { readFile } from "node:fs/promises"; +import path from "node:path"; +import { afterEach, beforeEach, describe, expect, test } from "vitest"; +import { createTestCli, makeTmpDir } from "#test/helpers.ts"; + +const cli = createTestCli(); + +describe("vland init (template scaffolding)", () => { + let fixture: { dir: string; cleanup: () => void }; + + beforeEach(() => { + fixture = makeTmpDir("init-templates"); + }); + + afterEach(() => fixture.cleanup()); + + test("library: replaces placeholders and sets the package name", async () => { + const name = "my-lib"; + const r = cli(`init ${name} -t library --no-install --no-git`, { cwd: fixture.dir }); + expect(r.status).toBe(0); + + const projectDir = path.join(fixture.dir, name); + const pkg = JSON.parse(await readFile(path.join(projectDir, "package.json"), "utf8")); + expect(pkg.name).toBe(name); + + const license = await readFile(path.join(projectDir, "LICENSE"), "utf8"); + expect(license).not.toContain("{{"); + + const readme = await readFile(path.join(projectDir, "README.md"), "utf8"); + expect(readme).toContain(name); + expect(readme).not.toContain("{{"); + }); + + test("backend: clears placeholders and wires the logger service name", async () => { + const name = "my-api"; + const r = cli(`init ${name} -t backend --no-install --no-git`, { cwd: fixture.dir }); + expect(r.status).toBe(0); + + const projectDir = path.join(fixture.dir, name); + const pkg = JSON.parse(await readFile(path.join(projectDir, "package.json"), "utf8")); + expect(pkg.name).toBe(name); + expect(pkg.dependencies).toMatchObject({ elysia: expect.any(String), evlog: expect.any(String) }); + + const logger = await readFile(path.join(projectDir, "src", "logger.ts"), "utf8"); + expect(logger).toContain(`service: "${name}"`); + }); + + test("monorepo: rewrites workspace package names", async () => { + const name = "my-mono"; + const r = cli(`init ${name} -t monorepo --no-install --no-git`, { cwd: fixture.dir }); + expect(r.status).toBe(0); + + const projectDir = path.join(fixture.dir, name); + const apiPkg = JSON.parse(await readFile(path.join(projectDir, "apps", "api", "package.json"), "utf8")); + expect(apiPkg.name).toBe(`@${name}/api`); + expect(apiPkg.dependencies).toMatchObject({ [`@${name}/types`]: "workspace:*" }); + + const typesPkg = JSON.parse(await readFile(path.join(projectDir, "packages", "types", "package.json"), "utf8")); + expect(typesPkg.name).toBe(`@${name}/types`); + }); + + test("refuses to overwrite a non-empty target dir without --force", () => { + const name = "dup-lib"; + cli(`init ${name} -t library --no-install --no-git`, { cwd: fixture.dir }); + const second = cli(`init ${name} -t library --no-install --no-git`, { cwd: fixture.dir }); + expect(second.status).not.toBe(0); + expect(second.stdout + second.stderr).toMatch(/--force/); + }); +}); diff --git a/packages/vland/test/integration/init.test.ts b/packages/vland/test/integration/init.test.ts deleted file mode 100644 index b04dd81..0000000 --- a/packages/vland/test/integration/init.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { spawnSync } from "node:child_process"; -import { mkdtemp, readFile, rm, stat } from "node:fs/promises"; -import { tmpdir } from "node:os"; -import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it } from "vitest"; - -const REPO_ROOT = path.resolve(import.meta.dirname, "..", "..", "..", ".."); -const TEMPLATES_DIR = path.join(REPO_ROOT, "templates"); -const VLAND_BIN = path.join(REPO_ROOT, "packages", "vland", "bin"); - -// Same shape as run-run/test/helpers.ts: spawnSync with NODE_ENV=production, -// TEST cleared, NO_COLOR=1 so assertions don't depend on terminal capability. -function runVland(cwd: string, args: string[]) { - return spawnSync(VLAND_BIN, args, { - encoding: "utf8", - cwd, - env: { - ...process.env, - NODE_ENV: "production", - TEST: undefined, - NO_COLOR: "1", - VLAND_TEMPLATES_DIR: TEMPLATES_DIR, - }, - }); -} - -describe("vland init (against local templates/)", () => { - let tmp: string; - - beforeEach(async () => { - tmp = await mkdtemp(path.join(tmpdir(), "vland-it-")); - }); - - afterEach(async () => { - if (tmp) await rm(tmp, { recursive: true, force: true }); - }); - - it("scaffolds a library with placeholders replaced and the package name set", async () => { - const projectName = "my-lib"; - const result = runVland(tmp, ["init", projectName, "-t", "library", "--no-install", "--no-git"]); - expect(result.status).toBe(0); - - const projectDir = path.join(tmp, projectName); - await expect(stat(projectDir)).resolves.toMatchObject({}); - - const pkg = JSON.parse(await readFile(path.join(projectDir, "package.json"), "utf8")); - expect(pkg.name).toBe(projectName); - - // Placeholders are replaced everywhere - const license = await readFile(path.join(projectDir, "LICENSE"), "utf8"); - expect(license).not.toContain("{{"); - - const readme = await readFile(path.join(projectDir, "README.md"), "utf8"); - expect(readme).toContain(projectName); - expect(readme).not.toContain("{{"); - }); - - it("fails clearly on a non-empty target dir without --force", () => { - const projectName = "dup-lib"; - runVland(tmp, ["init", projectName, "-t", "library", "--no-install", "--no-git"]); - const second = runVland(tmp, ["init", projectName, "-t", "library", "--no-install", "--no-git"]); - expect(second.status).not.toBe(0); - expect(second.stdout + second.stderr).toMatch(/--force/); - }); - - it("scaffolds a backend template with all placeholders cleared", async () => { - const projectName = "my-api"; - const result = runVland(tmp, ["init", projectName, "-t", "backend", "--no-install", "--no-git"]); - expect(result.status).toBe(0); - - const projectDir = path.join(tmp, projectName); - const pkg = JSON.parse(await readFile(path.join(projectDir, "package.json"), "utf8")); - expect(pkg.name).toBe(projectName); - expect(pkg.dependencies).toMatchObject({ elysia: expect.any(String), evlog: expect.any(String) }); - - const logger = await readFile(path.join(projectDir, "src", "logger.ts"), "utf8"); - expect(logger).toContain(`service: "${projectName}"`); - }); - - it("scaffolds a monorepo with workspace package names rewritten", async () => { - const projectName = "my-mono"; - const result = runVland(tmp, ["init", projectName, "-t", "monorepo", "--no-install", "--no-git"]); - expect(result.status).toBe(0); - - const projectDir = path.join(tmp, projectName); - const apiPkg = JSON.parse(await readFile(path.join(projectDir, "apps", "api", "package.json"), "utf8")); - expect(apiPkg.name).toBe(`@${projectName}/api`); - expect(apiPkg.dependencies).toMatchObject({ [`@${projectName}/types`]: "workspace:*" }); - - const typesPkg = JSON.parse(await readFile(path.join(projectDir, "packages", "types", "package.json"), "utf8")); - expect(typesPkg.name).toBe(`@${projectName}/types`); - }); -}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b804e4..2ff7196 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,18 +50,21 @@ importers: ansis: specifier: 4.2.0 version: 4.2.0 + memoize: + specifier: 10.2.0 + version: 10.2.0 pkg-types: specifier: 2.3.0 version: 2.3.0 std-env: specifier: 3.9.0 version: 3.9.0 + tinyexec: + specifier: 1.1.2 + version: 1.1.2 yaml: specifier: 2.8.4 version: 2.8.4 - zx: - specifier: 8.8.5 - version: 8.8.5 devDependencies: '@vlandoss/tsdown-config': specifier: workspace:^ @@ -134,8 +137,8 @@ importers: specifier: 6.1.3 version: 6.1.3 tsdown: - specifier: 0.21.10 - version: 0.21.10(typescript@6.0.3) + specifier: 0.22.0 + version: 0.22.0(typescript@6.0.3)(unrun@0.2.37) typescript: specifier: 6.0.3 version: 6.0.3 @@ -147,8 +150,8 @@ importers: packages/tsdown-config: dependencies: tsdown: - specifier: ^0.21.10 - version: 0.21.10(typescript@6.0.3) + specifier: ^0.22.0 + version: 0.22.0(typescript@6.0.3)(unrun@0.2.37) packages/vland: dependencies: @@ -188,10 +191,18 @@ packages: resolution: {integrity: sha512-em37/13/nR320G4jab/nIIHZgc2Wz2y/D39lxnTyxB4/D/omPQncl/lSdlnJY1OhQcRGugTSIF2l/69o31C9dA==} engines: {node: ^20.19.0 || >=22.12.0} + '@babel/generator@8.0.0-rc.4': + resolution: {integrity: sha512-YZ+FuIgkj7KrIb2a2X1XiY0QYgDxAbVbYP64SjwJzOK3euCsUerzenh2oqdsmKuPSlhzmFOOklnxzHAzXagvpw==} + engines: {node: ^20.19.0 || >=22.12.0} + '@babel/helper-string-parser@8.0.0-rc.3': resolution: {integrity: sha512-AmwWFx1m8G/a5cXkxLxTiWl+YEoWuoFLUCwqMlNuWO1tqAYITQAbCRPUkyBHv1VOFgfjVOqEj6L3u15J5ZCzTA==} engines: {node: ^20.19.0 || >=22.12.0} + '@babel/helper-string-parser@8.0.0-rc.5': + resolution: {integrity: sha512-sN7R8rBvDurfaziNfDEIjIntlazmlkCDGO4SNl2RJ3wRCn+QxspLV7hzYAE8WWVd2joVuT8sUxeePdLp2idI1A==} + engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-validator-identifier@7.28.5': resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} @@ -200,11 +211,24 @@ packages: resolution: {integrity: sha512-8AWCJ2VJJyDFlGBep5GpaaQ9AAaE/FjAcrqI7jyssYhtL7WGV0DOKpJsQqM037xDbpRLHXsY8TwU7zDma7coOw==} engines: {node: ^20.19.0 || >=22.12.0} + '@babel/helper-validator-identifier@8.0.0-rc.4': + resolution: {integrity: sha512-HTD3bskipk5MSm08twTW6832jzIXUhxMddy4NPPzIMuyMEsrs0ZgwAaMj5ubB5+6hMlUjDu17vNconEmwsmpYg==} + engines: {node: ^20.19.0 || >=22.12.0} + + '@babel/helper-validator-identifier@8.0.0-rc.5': + resolution: {integrity: sha512-ehJDxHvtbZ85RtX/L2fi0h9AGsBNqB5Euv1EB8RMAvGYvD+2X+QbpzzOpbklnNXO+WSZJNOaetw2BBj27xsWVg==} + engines: {node: ^22.18.0 || >=24.11.0} + '@babel/parser@8.0.0-rc.3': resolution: {integrity: sha512-B20dvP3MfNc/XS5KKCHy/oyWl5IA6Cn9YjXRdDlCjNmUFrjvLXMNUfQq/QUy9fnG2gYkKKcrto2YaF9B32ToOQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + '@babel/parser@8.0.0-rc.4': + resolution: {integrity: sha512-0S/1yefMa15N4i2v3t8Fw9pgMHhf2gF6Lc1UEXI96Ls6FNAjqvHHZouZ2ZS/deqLhbMFtmfVeFac6iTsvFbLwA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + '@babel/runtime@7.29.2': resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} engines: {node: '>=6.9.0'} @@ -213,6 +237,10 @@ packages: resolution: {integrity: sha512-mOm5ZrYmphGfqVWoH5YYMTITb3cDXsFgmvFlvkvWDMsR9X8RFnt7a0Wb6yNIdoFsiMO9WjYLq+U/FMtqIYAF8Q==} engines: {node: ^20.19.0 || >=22.12.0} + '@babel/types@8.0.0-rc.5': + resolution: {integrity: sha512-JeSVu/m8x/zpp4CLjYHVNXuhEyOkhPXuxM8YOXjh6L4LlvQNKuUNOTo5KdBuKAcTDHw8DquToTaEkhsBqPXOaA==} + engines: {node: ^22.18.0 || >=24.11.0} + '@bgotink/kdl@0.4.0': resolution: {integrity: sha512-F0uJCjo5FQvFdcGF5QbYVNfcGiRWlocuzyIdQxottZF2+gu6L2xjMGEu9PIpse2hifAca/19vIospgaETCKxIg==} @@ -554,6 +582,9 @@ packages: '@oxc-project/types@0.127.0': resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + '@oxc-project/types@0.129.0': + resolution: {integrity: sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==} + '@oxfmt/binding-android-arm-eabi@0.35.0': resolution: {integrity: sha512-BaRKlM3DyG81y/xWTsE6gZiv89F/3pHe2BqX2H4JbiB8HNVlWWtplzgATAE5IDSdwChdeuWLDTQzJ92Lglw3ZA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -873,95 +904,187 @@ packages: '@quansync/fs@1.0.0': resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} + '@rolldown/binding-android-arm64@1.0.0': + resolution: {integrity: sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@rolldown/binding-android-arm64@1.0.0-rc.17': resolution: {integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] + '@rolldown/binding-darwin-arm64@1.0.0': + resolution: {integrity: sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': resolution: {integrity: sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0': + resolution: {integrity: sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-rc.17': resolution: {integrity: sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] + '@rolldown/binding-freebsd-x64@1.0.0': + resolution: {integrity: sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': resolution: {integrity: sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0': + resolution: {integrity: sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': resolution: {integrity: sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@rolldown/binding-linux-arm64-gnu@1.0.0': + resolution: {integrity: sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': resolution: {integrity: sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + '@rolldown/binding-linux-arm64-musl@1.0.0': + resolution: {integrity: sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + '@rolldown/binding-linux-ppc64-gnu@1.0.0': + resolution: {integrity: sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + '@rolldown/binding-linux-s390x-gnu@1.0.0': + resolution: {integrity: sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + '@rolldown/binding-linux-x64-gnu@1.0.0': + resolution: {integrity: sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + '@rolldown/binding-linux-x64-musl@1.0.0': + resolution: {integrity: sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + '@rolldown/binding-openharmony-arm64@1.0.0': + resolution: {integrity: sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] + '@rolldown/binding-wasm32-wasi@1.0.0': + resolution: {integrity: sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': resolution: {integrity: sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] + '@rolldown/binding-win32-arm64-msvc@1.0.0': + resolution: {integrity: sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': resolution: {integrity: sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0': + resolution: {integrity: sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': resolution: {integrity: sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] + '@rolldown/pluginutils@1.0.0': + resolution: {integrity: sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==} + '@rolldown/pluginutils@1.0.0-rc.17': resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} @@ -1285,6 +1408,15 @@ packages: oxc-resolver: optional: true + dts-resolver@3.0.0: + resolution: {integrity: sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==} + engines: {node: ^22.18.0 || >=24.0.0} + peerDependencies: + oxc-resolver: '>=11.0.0' + peerDependenciesMeta: + oxc-resolver: + optional: true + empathic@2.0.0: resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} engines: {node: '>=14'} @@ -1371,6 +1503,10 @@ packages: get-tsconfig@4.14.0: resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + get-tsconfig@5.0.0-beta.5: + resolution: {integrity: sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==} + engines: {node: '>=20.20.0'} + giget@2.0.0: resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} hasBin: true @@ -1413,6 +1549,10 @@ packages: resolution: {integrity: sha512-bDxwDdF04gm550DfZHgffvlX+9kUlcz32UD0AeBTmVPFiWkrexF2XVmiuFFbDhiFuP8fQkrkvI2KdSNPYWAXkQ==} engines: {node: '>=20.19.0'} + import-without-cache@0.4.0: + resolution: {integrity: sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ==} + engines: {node: ^22.18.0 || >=24.0.0} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -1754,6 +1894,30 @@ packages: vue-tsc: optional: true + rolldown-plugin-dts@0.25.0: + resolution: {integrity: sha512-GE3uDZgUuA9l6g+1u928TRmadd5IVhaWiwpWast2kCyLv9tYJJCC6E5HHkV0HGmwC5ZL73xh12/PRZI+KZ2vdQ==} + engines: {node: ^22.18.0 || >=24.0.0} + peerDependencies: + '@ts-macro/tsc': ^0.3.6 + '@typescript/native-preview': '>=7.0.0-dev.20260325.1' + rolldown: ^1.0.0 + typescript: ^6.0.0 + vue-tsc: ~3.2.0 + peerDependenciesMeta: + '@ts-macro/tsc': + optional: true + '@typescript/native-preview': + optional: true + typescript: + optional: true + vue-tsc: + optional: true + + rolldown@1.0.0: + resolution: {integrity: sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rolldown@1.0.0-rc.17: resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1849,6 +2013,10 @@ packages: resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} engines: {node: '>=18'} + tinyexec@1.1.2: + resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==} + engines: {node: '>=18'} + tinyglobby@0.2.16: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} @@ -1900,6 +2068,40 @@ packages: unplugin-unused: optional: true + tsdown@0.22.0: + resolution: {integrity: sha512-FgW0hHb27nGQA/+F3d5+U9wKXkfilk9DVkc5+7x/ZqF03g+Hoz/eeApT32jqxATt9eRoR+1jxk7MUMON+O4CXw==} + engines: {node: ^22.18.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + '@tsdown/css': 0.22.0 + '@tsdown/exe': 0.22.0 + '@vitejs/devtools': '*' + publint: ^0.3.8 + tsx: '*' + typescript: ^5.0.0 || ^6.0.0 + unplugin-unused: ^0.5.0 + unrun: '*' + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + '@tsdown/css': + optional: true + '@tsdown/exe': + optional: true + '@vitejs/devtools': + optional: true + publint: + optional: true + tsx: + optional: true + typescript: + optional: true + unplugin-unused: + optional: true + unrun: + optional: true + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -2072,11 +2274,6 @@ packages: engines: {node: '>= 14.6'} hasBin: true - zx@8.8.5: - resolution: {integrity: sha512-SNgDF5L0gfN7FwVOdEFguY3orU5AkfFZm9B5YSHog/UDHv+lvmd82ZAsOenOkQixigwH2+yyH198AwNdKhj+RA==} - engines: {node: '>= 12.17.0'} - hasBin: true - snapshots: '@babel/code-frame@7.29.0': @@ -2094,16 +2291,35 @@ snapshots: '@types/jsesc': 2.5.1 jsesc: 3.1.0 + '@babel/generator@8.0.0-rc.4': + dependencies: + '@babel/parser': 8.0.0-rc.4 + '@babel/types': 8.0.0-rc.5 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@types/jsesc': 2.5.1 + jsesc: 3.1.0 + '@babel/helper-string-parser@8.0.0-rc.3': {} + '@babel/helper-string-parser@8.0.0-rc.5': {} + '@babel/helper-validator-identifier@7.28.5': {} '@babel/helper-validator-identifier@8.0.0-rc.3': {} + '@babel/helper-validator-identifier@8.0.0-rc.4': {} + + '@babel/helper-validator-identifier@8.0.0-rc.5': {} + '@babel/parser@8.0.0-rc.3': dependencies: '@babel/types': 8.0.0-rc.3 + '@babel/parser@8.0.0-rc.4': + dependencies: + '@babel/types': 8.0.0-rc.5 + '@babel/runtime@7.29.2': {} '@babel/types@8.0.0-rc.3': @@ -2111,6 +2327,11 @@ snapshots: '@babel/helper-string-parser': 8.0.0-rc.3 '@babel/helper-validator-identifier': 8.0.0-rc.3 + '@babel/types@8.0.0-rc.5': + dependencies: + '@babel/helper-string-parser': 8.0.0-rc.5 + '@babel/helper-validator-identifier': 8.0.0-rc.5 + '@bgotink/kdl@0.4.0': {} '@biomejs/biome@2.4.4': @@ -2473,6 +2694,8 @@ snapshots: '@oxc-project/types@0.127.0': {} + '@oxc-project/types@0.129.0': {} + '@oxfmt/binding-android-arm-eabi@0.35.0': optional: true @@ -2685,42 +2908,85 @@ snapshots: dependencies: quansync: 1.0.0 + '@rolldown/binding-android-arm64@1.0.0': + optional: true + '@rolldown/binding-android-arm64@1.0.0-rc.17': optional: true + '@rolldown/binding-darwin-arm64@1.0.0': + optional: true + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': optional: true + '@rolldown/binding-darwin-x64@1.0.0': + optional: true + '@rolldown/binding-darwin-x64@1.0.0-rc.17': optional: true + '@rolldown/binding-freebsd-x64@1.0.0': + optional: true + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0': + optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0': + optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0': + optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': optional: true + '@rolldown/binding-linux-ppc64-gnu@1.0.0': + optional: true + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': optional: true + '@rolldown/binding-linux-s390x-gnu@1.0.0': + optional: true + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0': + optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': optional: true + '@rolldown/binding-linux-x64-musl@1.0.0': + optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': optional: true + '@rolldown/binding-openharmony-arm64@1.0.0': + optional: true + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': optional: true + '@rolldown/binding-wasm32-wasi@1.0.0': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': dependencies: '@emnapi/core': 1.10.0 @@ -2728,12 +2994,20 @@ snapshots: '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0': + optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0': + optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': optional: true + '@rolldown/pluginutils@1.0.0': {} + '@rolldown/pluginutils@1.0.0-rc.17': {} '@rollup/rollup-android-arm-eabi@4.60.2': @@ -2984,6 +3258,8 @@ snapshots: dts-resolver@2.1.3: {} + dts-resolver@3.0.0: {} + empathic@2.0.0: {} encoding@0.1.13: @@ -3091,6 +3367,10 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + get-tsconfig@5.0.0-beta.5: + dependencies: + resolve-pkg-maps: 1.0.0 + giget@2.0.0: dependencies: citty: 0.1.6 @@ -3138,6 +3418,8 @@ snapshots: import-without-cache@0.3.3: {} + import-without-cache@0.4.0: {} + imurmurhash@0.1.4: {} individual@3.0.0: {} @@ -3452,6 +3734,43 @@ snapshots: transitivePeerDependencies: - oxc-resolver + rolldown-plugin-dts@0.25.0(rolldown@1.0.0)(typescript@6.0.3): + dependencies: + '@babel/generator': 8.0.0-rc.4 + '@babel/helper-validator-identifier': 8.0.0-rc.4 + '@babel/parser': 8.0.0-rc.4 + ast-kit: 3.0.0-beta.1 + birpc: 4.0.0 + dts-resolver: 3.0.0 + get-tsconfig: 5.0.0-beta.5 + obug: 2.1.1 + rolldown: 1.0.0 + optionalDependencies: + typescript: 6.0.3 + transitivePeerDependencies: + - oxc-resolver + + rolldown@1.0.0: + dependencies: + '@oxc-project/types': 0.129.0 + '@rolldown/pluginutils': 1.0.0 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0 + '@rolldown/binding-darwin-arm64': 1.0.0 + '@rolldown/binding-darwin-x64': 1.0.0 + '@rolldown/binding-freebsd-x64': 1.0.0 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0 + '@rolldown/binding-linux-arm64-gnu': 1.0.0 + '@rolldown/binding-linux-arm64-musl': 1.0.0 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0 + '@rolldown/binding-linux-s390x-gnu': 1.0.0 + '@rolldown/binding-linux-x64-gnu': 1.0.0 + '@rolldown/binding-linux-x64-musl': 1.0.0 + '@rolldown/binding-openharmony-arm64': 1.0.0 + '@rolldown/binding-wasm32-wasi': 1.0.0 + '@rolldown/binding-win32-arm64-msvc': 1.0.0 + '@rolldown/binding-win32-x64-msvc': 1.0.0 + rolldown@1.0.0-rc.17: dependencies: '@oxc-project/types': 0.127.0 @@ -3561,6 +3880,8 @@ snapshots: tinyexec@1.1.1: {} + tinyexec@1.1.2: {} + tinyglobby@0.2.16: dependencies: fdir: 6.5.0(picomatch@4.0.4) @@ -3605,6 +3926,32 @@ snapshots: - synckit - vue-tsc + tsdown@0.22.0(typescript@6.0.3)(unrun@0.2.37): + dependencies: + ansis: 4.2.0 + cac: 7.0.0 + defu: 6.1.7 + empathic: 2.0.0 + hookable: 6.1.1 + import-without-cache: 0.4.0 + obug: 2.1.1 + picomatch: 4.0.4 + rolldown: 1.0.0 + rolldown-plugin-dts: 0.25.0(rolldown@1.0.0)(typescript@6.0.3) + semver: 7.7.4 + tinyexec: 1.1.2 + tinyglobby: 0.2.16 + tree-kill: 1.2.2 + unconfig-core: 7.5.0 + optionalDependencies: + typescript: 6.0.3 + unrun: 0.2.37 + transitivePeerDependencies: + - '@ts-macro/tsc' + - '@typescript/native-preview' + - oxc-resolver + - vue-tsc + tslib@2.8.1: optional: true @@ -3717,5 +4064,3 @@ snapshots: write-file-atomic: 5.0.1 yaml@2.8.4: {} - - zx@8.8.5: {}