feat(components): add @vue-tui/components + Spinner (first component) (#229)
* feat(components): scaffold @vue-tui/components with spinner preset data New private package (0.0.0) for high-level components composed from runtime primitives. Ships the dots/line preset data + a pure resolveSpinner() with edge-guards (empty frames / unknown type → dots; interval threads both modes), fully unit-tested incl. a width-safety guard (string-width === 1 per frame). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(components): add the Spinner component Spinner is a <Text> + useAnimation pure composition: `type` selects an inline preset (dots/line), `frames`/`interval` is the escape hatch, and it always animates (no interactivity gate — there is no public signal; matches Ink). Renders a visible glyph non-interactively. Typed props via ExtractPublicPropTypes. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(components): Spinner color + label `color` tints the glyph only (label stays default, matching ora/@inkjs/ui); `label` renders after the glyph with a separating space (interpolated so Vue whitespace-condense keeps it). Two <Text> spans share an outer <Text> context so they render inline on one line rather than stacking vertically. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(components): add @vue-tui/components to the CI task graph Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(components): record Spinner decisions Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(components): refresh package status and clarify ink-spinner parity note The design-principles status blockquote said the package was 'planned' with no code yet; this branch ships Spinner, so mark it active. Also reword the spinner Behavior note to name the third-party ink-spinner explicitly and soften it to an unverified, un-run-checked observation. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(readme): list @vue-tui/components + <Spinner> Add the new package to the hero line + Packages table, and <Spinner> to the Components table. Marked "New; early". Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(readme): give @vue-tui/components its own section Move <Spinner> out of the runtime Components table into a separate "High-level Components" section so the package's API surface stays decoupled from the runtime primitives. Add a ToC entry. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(readme): drop the "New; early" status tag for @vue-tui/components Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,8 +5,10 @@
|
||||
> issue, not here. It records how components in `@vue-tui/components` should be _shaped_ and
|
||||
> _styled_, and the bar for adding one in the first place.
|
||||
>
|
||||
> **Status:** planned — no `@vue-tui/components` code exists yet. This is design intent, not a
|
||||
> description of shipped code.
|
||||
> **Status:** active — the package now ships its first component, `Spinner` (see per-component
|
||||
> records below). The principles here are design intent for the package as a whole.
|
||||
>
|
||||
> **Per-component records:** [spinner](./components/spinner.md).
|
||||
|
||||
**The governing idea:** components in `@vue-tui/components` are **pure compositions of
|
||||
`@vue-tui/runtime` primitives**. The runtime owns the terminal-I/O and layout/commit boundary;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Spinner — decision record
|
||||
|
||||
> Decisions specific to `@vue-tui/components`' `Spinner`. Shared conventions live in
|
||||
> [components-design-principles.md](../components-design-principles.md). Tracking: #218.
|
||||
|
||||
A pure composition of `<Text>` + `useAnimation` — no new runtime export needed.
|
||||
|
||||
## Style selection & the escape hatch
|
||||
|
||||
- `type` selects an **inlined preset**; only **`dots`** (default) and **`line`** ship.
|
||||
- **Inclusion bar for a preset:** universal default + functional fallback only; every frame
|
||||
must be **width-safe (exactly 1 column, verified with `string-width`)**; non-novelty. `dots`
|
||||
is the universal default; `line` is the only pure-ASCII fallback (braille needs a braille font).
|
||||
Everything else — including the full `cli-spinners` set — is reachable via the escape hatch.
|
||||
- **Escape hatch:** `frames: string[]` + `interval?: number` override `type`. A `cli-spinners`
|
||||
entry (`{ interval, frames }`) can be spread in verbatim. Empty `frames` and an unknown `type`
|
||||
both fall back to `dots`; `interval` overrides in either mode.
|
||||
- **No `cli-spinners` dependency.** This is a _set-membership_ decision under the inclusion bar,
|
||||
recorded here — **not** an Ink divergence (`ink-spinner`/`cli-spinners` are third-party npm, not
|
||||
Ink-core v7.0.4, which has no Spinner; there is nothing to diverge from).
|
||||
|
||||
## Behavior
|
||||
|
||||
- **Always animates.** It does NOT gate on app interactivity — there is no public signal for
|
||||
`interactive` (it lives only on the internal `AppContext`), and reaching for it would break the
|
||||
pure-composition rule. This mirrors the third-party `ink-spinner`, which likewise just animates
|
||||
(unverified parity — not run-checked); the runtime governs
|
||||
non-interactive output one layer down. (If a static-when-non-interactive affordance is ever
|
||||
wanted, it needs the runtime to expose interactivity publicly first.)
|
||||
- Switching `type` changes the preset interval; `useAnimation` resets `frame` to 0 on a live
|
||||
interval change — acceptable for a spinner.
|
||||
|
||||
## API shape
|
||||
|
||||
- `color` tints the **glyph only**; the `label` stays default-colored (matches ora / @inkjs/ui).
|
||||
- `label` is a **`string` prop** (type-friendly + the common Vue idiom for simple text). If rich
|
||||
label content is ever needed, add a same-purpose **default slot** later — non-breaking.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- **succeed / fail / pending** terminal states (`✔`/`✖`) are NOT Spinner's job — they belong to a
|
||||
future `TaskList` / `StatusMessage` (render = f(state) in a separate component).
|
||||
- **Screen-reader** handling: deferred (niche; no surveyed spinner implements it).
|
||||
@@ -6,7 +6,7 @@ The Vue framework for terminal UIs.
|
||||
Build with components, develop with HMR, test with confidence.
|
||||
|
||||
<p align="center">
|
||||
<a href="https://npmx.dev/@vue-tui/runtime"><code>@vue-tui/runtime</code></a> · <a href="https://npmx.dev/@vue-tui/vite"><code>@vue-tui/vite</code></a> · <a href="https://npmx.dev/@vue-tui/testing"><code>@vue-tui/testing</code></a>
|
||||
<a href="https://npmx.dev/@vue-tui/runtime"><code>@vue-tui/runtime</code></a> · <a href="https://npmx.dev/@vue-tui/components"><code>@vue-tui/components</code></a> · <a href="https://npmx.dev/@vue-tui/vite"><code>@vue-tui/vite</code></a> · <a href="https://npmx.dev/@vue-tui/testing"><code>@vue-tui/testing</code></a>
|
||||
</p>
|
||||
|
||||
- **Vue SFC & JSX** — write terminal interfaces with `<template>`, TSX, or both
|
||||
@@ -82,6 +82,7 @@ For non-interactive output — snapshots, CI logs, piped commands — `renderToS
|
||||
- [Packages](#packages)
|
||||
- [Examples](#examples)
|
||||
- [Components](#components)
|
||||
- [High-level Components](#high-level-components)
|
||||
- [Composables (Hooks)](#composables-hooks)
|
||||
- [Testing](#testing)
|
||||
- [Development](#development)
|
||||
@@ -92,10 +93,11 @@ For non-interactive output — snapshots, CI logs, piped commands — `renderToS
|
||||
## Packages
|
||||
|
||||
| Package | Description |
|
||||
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [`@vue-tui/runtime`](https://www.npmjs.com/package/@vue-tui/runtime) | The core framework — Vue 3 renderer for the terminal with components (`Box`, `Text`, `Static`, etc.), composables (`useInput`, `useFocus`, `useApp`, etc.), and yoga-based flexbox layout. _API stabilizing._ |
|
||||
| [`@vue-tui/vite`](https://www.npmjs.com/package/@vue-tui/vite) | Vite plugin — add `vueTui()` to `vite.config.ts` for an in-process terminal dev server with HMR (`npm run dev`) plus a production build (`vite build`). _Experimental; may change._ |
|
||||
| [`@vue-tui/testing`](https://www.npmjs.com/package/@vue-tui/testing) | Test harness — render in an isolated fake terminal, simulate input, assert output frame by frame |
|
||||
| [`@vue-tui/components`](https://www.npmjs.com/package/@vue-tui/components) | High-level components built on the runtime primitives — currently `<Spinner>` (animated loading), with more to come. |
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -117,6 +119,14 @@ For non-interactive output — snapshots, CI logs, piped commands — `renderToS
|
||||
| [`<Static>`](./packages/runtime) | Renders a list of items once, above the redrawn region |
|
||||
| [`<Transform>`](./packages/runtime) | Applies a string transform function to each rendered line |
|
||||
|
||||
## High-level Components
|
||||
|
||||
The [`@vue-tui/components`](./packages/components) package adds higher-level components composed from the runtime primitives — published separately from the core.
|
||||
|
||||
| Component | Description |
|
||||
| ------------------------------------ | ------------------------------------------------------------------------------------------ |
|
||||
| [`<Spinner>`](./packages/components) | Animated loading spinner — built-in `dots`/`line` presets or custom frames, optional label |
|
||||
|
||||
## Composables (Hooks)
|
||||
|
||||
| Composable | Description |
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "@vue-tui/components",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"description": "High-level Vue components for vue-tui, composed from @vue-tui/runtime primitives.",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./dist/index.mjs",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vp pack",
|
||||
"dev": "vp pack --watch",
|
||||
"test": "vp test",
|
||||
"check:type": "vue-tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.6.2",
|
||||
"@vitejs/plugin-vue-jsx": "catalog:",
|
||||
"@vue-tui/runtime": "workspace:*",
|
||||
"@vue-tui/testing": "workspace:*",
|
||||
"chalk": "catalog:",
|
||||
"string-width": "catalog:",
|
||||
"strip-ansi": "catalog:",
|
||||
"typescript": "^6.0.3",
|
||||
"unplugin-vue": "catalog:",
|
||||
"vite-plus": "catalog:",
|
||||
"vue": "^3.4.0",
|
||||
"vue-tsc": "catalog:"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue-tui/runtime": "workspace:*",
|
||||
"vue": "^3.4.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as Spinner } from "./spinner.vue";
|
||||
export type { SpinnerProps } from "./spinner-props.ts";
|
||||
@@ -0,0 +1,13 @@
|
||||
import { type ExtractPublicPropTypes, type PropType } from "vue";
|
||||
import type { PresetName } from "./spinners.ts";
|
||||
|
||||
export const spinnerProps = {
|
||||
type: { type: String as PropType<PresetName>, default: "dots" as PresetName },
|
||||
frames: { type: Array as PropType<string[]> },
|
||||
interval: Number,
|
||||
color: String,
|
||||
label: String,
|
||||
};
|
||||
|
||||
/** Props accepted by `<Spinner>`. */
|
||||
export type SpinnerProps = ExtractPublicPropTypes<typeof spinnerProps>;
|
||||
@@ -0,0 +1,79 @@
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { render } from "@vue-tui/testing";
|
||||
import Spinner from "./spinner.vue";
|
||||
import { PRESETS } from "./spinners.ts";
|
||||
|
||||
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
|
||||
const dots = PRESETS.dots.frames;
|
||||
|
||||
describe("Spinner", () => {
|
||||
test("renders a dots glyph by default", async () => {
|
||||
const r = await render(Spinner, { interactive: false });
|
||||
await delay(50);
|
||||
const out = r.lastFrame() ?? "";
|
||||
expect(out.length).toBeGreaterThan(0);
|
||||
expect(dots.some((g) => out.includes(g))).toBe(true);
|
||||
r.unmount();
|
||||
});
|
||||
|
||||
test("animates through more than two distinct glyphs over time", async () => {
|
||||
const r = await render(Spinner);
|
||||
await delay(250);
|
||||
const distinct = new Set(r.frames.map((f) => f.trim()).filter(Boolean));
|
||||
expect(distinct.size).toBeGreaterThan(2);
|
||||
r.unmount();
|
||||
});
|
||||
|
||||
test("renders a visible (non-blank) glyph when non-interactive", async () => {
|
||||
const r = await render(Spinner, { interactive: false });
|
||||
await delay(50);
|
||||
expect((r.lastFrame() ?? "").trim().length).toBeGreaterThan(0);
|
||||
r.unmount();
|
||||
});
|
||||
|
||||
test("type='line' renders a line glyph", async () => {
|
||||
const r = await render(Spinner, { interactive: false, props: { type: "line" } });
|
||||
await delay(50);
|
||||
const out = r.lastFrame() ?? "";
|
||||
expect(PRESETS.line.frames.some((g) => out.includes(g))).toBe(true);
|
||||
r.unmount();
|
||||
});
|
||||
|
||||
test("custom frames override the preset", async () => {
|
||||
const r = await render(Spinner, { interactive: false, props: { frames: ["@"] } });
|
||||
await delay(50);
|
||||
expect((r.lastFrame() ?? "").includes("@")).toBe(true);
|
||||
r.unmount();
|
||||
});
|
||||
|
||||
test("color tints the glyph but not the label", async () => {
|
||||
const chalk = (await import("chalk")).default;
|
||||
const r = await render(Spinner, {
|
||||
interactive: false,
|
||||
props: { frames: ["⠋"], color: "green", label: "Loading" },
|
||||
});
|
||||
await delay(20);
|
||||
const out = r.lastFrame() ?? "";
|
||||
expect(out).toContain(chalk.green("⠋"));
|
||||
expect(out).toContain(" Loading");
|
||||
expect(out).not.toContain(chalk.green("Loading"));
|
||||
r.unmount();
|
||||
});
|
||||
|
||||
test("label renders after the glyph with a separating space", async () => {
|
||||
const r = await render(Spinner, {
|
||||
interactive: false,
|
||||
props: { frames: ["⠋"], label: "Done" },
|
||||
});
|
||||
await delay(20);
|
||||
expect(r.lastFrame() ?? "").toContain("⠋ Done");
|
||||
r.unmount();
|
||||
});
|
||||
|
||||
test("no label renders the glyph only", async () => {
|
||||
const r = await render(Spinner, { interactive: false, props: { frames: ["⠋"] } });
|
||||
await delay(20);
|
||||
expect((r.lastFrame() ?? "").trim()).toBe("⠋");
|
||||
r.unmount();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { useAnimation, Text } from "@vue-tui/runtime";
|
||||
import { spinnerProps } from "./spinner-props.ts";
|
||||
import { resolveSpinner } from "./spinners.ts";
|
||||
|
||||
defineOptions({ name: "Spinner" });
|
||||
const props = defineProps(spinnerProps);
|
||||
|
||||
const set = computed(() => resolveSpinner(props));
|
||||
const { frame } = useAnimation({ interval: () => set.value.interval });
|
||||
const glyph = computed(() => set.value.frames[frame.value % set.value.frames.length]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- The outer <Text> establishes a shared text context (runtime TextContextKey) so
|
||||
both inner spans render INLINE as <tui-virtual-text> (one line, `⠋ Loading`).
|
||||
Two bare sibling top-level <Text> would each be a block <tui-text> node and stack
|
||||
vertically under the root's column direction. The outer Text carries no color, so
|
||||
only the glyph span is tinted; the label span stays default. The separating space
|
||||
is an interpolation so Vue's whitespace:'condense' keeps it. -->
|
||||
<Text
|
||||
><Text :color="color">{{ glyph }}</Text
|
||||
><Text v-if="label">{{ " " + label }}</Text></Text
|
||||
>
|
||||
</template>
|
||||
@@ -0,0 +1,55 @@
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import stringWidth from "string-width";
|
||||
import { PRESETS, resolveSpinner } from "./spinners.ts";
|
||||
|
||||
describe("PRESETS", () => {
|
||||
test("ships exactly dots and line", () => {
|
||||
expect(Object.keys(PRESETS).sort()).toEqual(["dots", "line"]);
|
||||
});
|
||||
test("every preset frame is exactly one column wide", () => {
|
||||
for (const [name, set] of Object.entries(PRESETS)) {
|
||||
for (const frame of set.frames) {
|
||||
expect(stringWidth(frame), `${name} frame ${JSON.stringify(frame)}`).toBe(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveSpinner", () => {
|
||||
test("defaults to dots at 80ms", () => {
|
||||
expect(resolveSpinner({})).toEqual({ frames: PRESETS.dots.frames, interval: 80 });
|
||||
});
|
||||
test("selects a named preset with its own interval", () => {
|
||||
expect(resolveSpinner({ type: "line" })).toEqual({
|
||||
frames: PRESETS.line.frames,
|
||||
interval: 130,
|
||||
});
|
||||
});
|
||||
test("unknown type falls back to dots", () => {
|
||||
expect(resolveSpinner({ type: "bogus" })).toEqual({
|
||||
frames: PRESETS.dots.frames,
|
||||
interval: 80,
|
||||
});
|
||||
});
|
||||
test("custom frames override type", () => {
|
||||
expect(resolveSpinner({ type: "line", frames: ["a", "b"] })).toEqual({
|
||||
frames: ["a", "b"],
|
||||
interval: 80,
|
||||
});
|
||||
});
|
||||
test("empty frames fall back to dots", () => {
|
||||
expect(resolveSpinner({ frames: [] })).toEqual({ frames: PRESETS.dots.frames, interval: 80 });
|
||||
});
|
||||
test("interval applies in preset mode", () => {
|
||||
expect(resolveSpinner({ type: "dots", interval: 200 })).toEqual({
|
||||
frames: PRESETS.dots.frames,
|
||||
interval: 200,
|
||||
});
|
||||
});
|
||||
test("interval applies in frames mode", () => {
|
||||
expect(resolveSpinner({ frames: ["a"], interval: 50 })).toEqual({
|
||||
frames: ["a"],
|
||||
interval: 50,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
// Curated spinner presets. Inclusion bar (see .agents/docs/components/spinner.md):
|
||||
// universal default + functional fallback only, every frame width-safe (1 column).
|
||||
// Everything else is reachable via the `frames`/`interval` escape hatch.
|
||||
export const PRESETS = {
|
||||
dots: { interval: 80, frames: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] },
|
||||
line: { interval: 130, frames: ["-", "\\", "|", "/"] },
|
||||
} satisfies Record<string, { interval: number; frames: string[] }>;
|
||||
|
||||
export type PresetName = keyof typeof PRESETS;
|
||||
|
||||
/** Resolve effective `{ frames, interval }` from spinner props. Custom `frames` win;
|
||||
* an empty `frames` array and an unknown `type` both fall back to `dots`; `interval`
|
||||
* overrides in either mode. Pure — no rendering, no timers. */
|
||||
export function resolveSpinner(opts: {
|
||||
type?: string;
|
||||
frames?: readonly string[];
|
||||
interval?: number;
|
||||
}): { frames: string[]; interval: number } {
|
||||
if (opts.frames?.length) {
|
||||
return { frames: [...opts.frames], interval: opts.interval ?? 80 };
|
||||
}
|
||||
const p = PRESETS[opts.type as PresetName] ?? PRESETS.dots;
|
||||
return { frames: p.frames, interval: opts.interval ?? p.interval };
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"lib": ["es2023"],
|
||||
"moduleDetection": "force",
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
"resolveJsonModule": true,
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"declaration": true,
|
||||
"noEmit": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"skipLibCheck": true,
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "vue"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { defineConfig } from "vite-plus";
|
||||
import vueJsx from "@vitejs/plugin-vue-jsx";
|
||||
import Vue from "unplugin-vue/rolldown";
|
||||
import VueVite from "unplugin-vue/vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vueJsx(), VueVite()],
|
||||
pack: {
|
||||
plugins: [Vue({ isProduction: true })],
|
||||
dts: { vue: true },
|
||||
exports: true,
|
||||
},
|
||||
test: {
|
||||
env: { FORCE_COLOR: "3", CI: "false" },
|
||||
},
|
||||
});
|
||||
Generated
+39
@@ -152,6 +152,45 @@ importers:
|
||||
specifier: 8.1.0
|
||||
version: 8.1.0(@types/node@24.12.4)(esbuild@0.28.0)(tsx@4.22.3)
|
||||
|
||||
packages/components:
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^24.12.4
|
||||
version: 24.12.4
|
||||
'@vitejs/plugin-vue-jsx':
|
||||
specifier: 'catalog:'
|
||||
version: 5.1.5(vite@8.1.0(@types/node@24.12.4)(esbuild@0.28.0)(tsx@4.22.3))(vue@3.5.34(typescript@6.0.3))
|
||||
'@vue-tui/runtime':
|
||||
specifier: workspace:*
|
||||
version: link:../runtime
|
||||
'@vue-tui/testing':
|
||||
specifier: workspace:*
|
||||
version: link:../testing
|
||||
chalk:
|
||||
specifier: 'catalog:'
|
||||
version: 5.6.2
|
||||
string-width:
|
||||
specifier: 'catalog:'
|
||||
version: 8.2.1
|
||||
strip-ansi:
|
||||
specifier: 'catalog:'
|
||||
version: 7.2.0
|
||||
typescript:
|
||||
specifier: ^6.0.3
|
||||
version: 6.0.3
|
||||
unplugin-vue:
|
||||
specifier: 'catalog:'
|
||||
version: 7.2.0(@types/node@24.12.4)(esbuild@0.28.0)(tsx@4.22.3)(vue@3.5.34(typescript@6.0.3))
|
||||
vite-plus:
|
||||
specifier: 'catalog:'
|
||||
version: 0.1.22(@types/node@24.12.4)(esbuild@0.28.0)(tsx@4.22.3)(typescript@6.0.3)(vite@8.1.0(@types/node@24.12.4)(esbuild@0.28.0)(tsx@4.22.3))
|
||||
vue:
|
||||
specifier: ^3.4.0
|
||||
version: 3.5.34(typescript@6.0.3)
|
||||
vue-tsc:
|
||||
specifier: 'catalog:'
|
||||
version: 3.3.4(typescript@6.0.3)
|
||||
|
||||
packages/runtime:
|
||||
dependencies:
|
||||
'@alcalzone/ansi-tokenize':
|
||||
|
||||
@@ -74,6 +74,14 @@ export default defineConfig({
|
||||
command: "vp run @vue-tui/runtime-tests#test:examples",
|
||||
dependsOn: ["ci:build"],
|
||||
},
|
||||
// @vue-tui/components (high-level components composed from runtime
|
||||
// primitives) carries its own unit suite; run it on its own parallel
|
||||
// branch like the other packages. Depends on ci:build because its tests
|
||||
// resolve @vue-tui/runtime + @vue-tui/testing from their built dist.
|
||||
"ci:test:components": {
|
||||
command: "vp run @vue-tui/components#test",
|
||||
dependsOn: ["ci:build"],
|
||||
},
|
||||
ci: {
|
||||
command: "echo ci ok",
|
||||
dependsOn: [
|
||||
@@ -86,6 +94,7 @@ export default defineConfig({
|
||||
"ci:test:pty",
|
||||
"ci:test:vite-plugin",
|
||||
"ci:test:examples",
|
||||
"ci:test:components",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user