@@ -51,11 +60,25 @@ const loop = [...adopters, ...adopters];
+
diff --git a/website/.vitepress/components/homeExample.data.ts b/website/.vitepress/components/homeExample.data.ts
new file mode 100644
index 0000000000..03b14713c3
--- /dev/null
+++ b/website/.vitepress/components/homeExample.data.ts
@@ -0,0 +1,67 @@
+import fs from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { defineLoader } from 'vitepress';
+import { codeToHtml } from 'shiki';
+
+// Same themes VitePress uses for fenced code blocks, so the homepage sample
+// picks up the site's own light/dark code colours through `.vp-code`.
+const THEMES = { light: 'github-light', dark: 'github-dark' } as const;
+
+const taskfilePath = fileURLToPath(
+ new URL('../snippets/homepage-taskfile.yml', import.meta.url)
+);
+const terminalPath = fileURLToPath(
+ new URL('../snippets/homepage-terminal.txt', import.meta.url)
+);
+
+export interface HomeExample {
+ taskfile: string;
+ terminal: string;
+}
+
+declare const data: HomeExample;
+export { data };
+
+export default defineLoader({
+ watch: [
+ '../snippets/homepage-taskfile.yml',
+ '../snippets/homepage-terminal.txt'
+ ],
+ async load(): Promise {
+ const taskfile = fs.readFileSync(taskfilePath, 'utf-8').trimEnd();
+ return {
+ taskfile: await codeToHtml(taskfile, {
+ lang: 'yaml',
+ themes: THEMES,
+ defaultColor: false,
+ transformers: [
+ {
+ pre(node) {
+ this.addClassToHast(node, 'vp-code');
+ }
+ }
+ ]
+ }),
+ terminal: renderTerminal(fs.readFileSync(terminalPath, 'utf-8'))
+ };
+ }
+});
+
+function renderTerminal(source: string): string {
+ return source
+ .trimEnd()
+ .split('\n')
+ .map((line) =>
+ line.startsWith('$ ')
+ ? `$${escapeHtml(line.slice(1))}`
+ : escapeHtml(line)
+ )
+ .join('\n');
+}
+
+function escapeHtml(value: string): string {
+ return value
+ .replace(/&/g, '&')
+ .replace(//g, '>');
+}
diff --git a/website/.vitepress/githubStars.data.ts b/website/.vitepress/githubStars.data.ts
new file mode 100644
index 0000000000..ac567da4cd
--- /dev/null
+++ b/website/.vitepress/githubStars.data.ts
@@ -0,0 +1,64 @@
+import { defineLoader } from 'vitepress';
+
+const REPO = 'go-task/task';
+
+// Deploy previews build from shared, unauthenticated Netlify IPs, so a 403 from
+// GitHub's 60 requests/hour limit is routine. Falling back keeps the build green
+// with a number that is only ever too low.
+const FALLBACK_STARS = 16000;
+
+export interface GithubStars {
+ count: number;
+ label: string;
+}
+
+declare const data: GithubStars;
+export { data };
+
+export default defineLoader({
+ async load(): Promise {
+ const count = await fetchStarCount();
+ return { count, label: formatCount(count) };
+ }
+});
+
+async function fetchStarCount(): Promise {
+ const headers: Record = {
+ Accept: 'application/vnd.github+json'
+ };
+ if (process.env.GITHUB_TOKEN) {
+ headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
+ }
+
+ try {
+ const response = await fetch(`https://api.github.com/repos/${REPO}`, {
+ headers,
+ signal: AbortSignal.timeout(5000)
+ });
+ if (!response.ok) {
+ throw new Error(`GitHub answered ${response.status}`);
+ }
+
+ const { stargazers_count } = await response.json();
+ if (typeof stargazers_count !== 'number') {
+ throw new Error('no stargazers_count in the response');
+ }
+ return stargazers_count;
+ } catch (error) {
+ console.warn(
+ `Could not read the star count for ${REPO} (${error}), falling back to ${FALLBACK_STARS}.`
+ );
+ return FALLBACK_STARS;
+ }
+}
+
+// Rounding is deliberate: a count that never shows its last digits cannot look
+// stale between two deployments.
+function formatCount(count: number): string {
+ return new Intl.NumberFormat('en', {
+ notation: 'compact',
+ maximumFractionDigits: 1
+ })
+ .format(count)
+ .replace('K', 'k');
+}
diff --git a/website/.vitepress/snippets/homepage-taskfile.yml b/website/.vitepress/snippets/homepage-taskfile.yml
new file mode 100644
index 0000000000..2e3c9a7e4d
--- /dev/null
+++ b/website/.vitepress/snippets/homepage-taskfile.yml
@@ -0,0 +1,21 @@
+version: '3'
+
+vars:
+ GREETING: Hello
+
+tasks:
+ test:
+ desc: Run the test suite
+ sources: ['**/*.go']
+ cmds:
+ - go test ./...
+
+ build:
+ desc: Build the application
+ deps: [test]
+ sources: ['**/*.go']
+ generates: [bin/app]
+ cmds:
+ - mkdir -p bin
+ - echo "{{.GREETING}} from Task"
+ - go build -o bin/app ./cmd/app
diff --git a/website/.vitepress/snippets/homepage-terminal.txt b/website/.vitepress/snippets/homepage-terminal.txt
new file mode 100644
index 0000000000..7aec0c004a
--- /dev/null
+++ b/website/.vitepress/snippets/homepage-terminal.txt
@@ -0,0 +1,16 @@
+$ task --list
+task: Available tasks for this project:
+* build: Build the application
+* test: Run the test suite
+
+$ task build
+task: [test] go test ./...
+ok example/app 0.2s
+task: [build] mkdir -p bin
+task: [build] echo "Hello from Task"
+Hello from Task
+task: [build] go build -o bin/app ./cmd/app
+
+$ task build
+task: Task "test" is up to date
+task: Task "build" is up to date
diff --git a/website/.vitepress/theme/custom.css b/website/.vitepress/theme/custom.css
index 0c7456e0f9..795907739b 100644
--- a/website/.vitepress/theme/custom.css
+++ b/website/.vitepress/theme/custom.css
@@ -141,9 +141,8 @@ img[src*='custom-icon-badges.demolab.com'] {
}
}
-
.VPTeamPage > .VPTeamPageTitle {
- padding-top: 0
+ padding-top: 0;
}
/* VitePress greys out sponsor logos by default. Show them in their brand
@@ -169,3 +168,39 @@ html:not(.dark) .vp-sponsor-grid-item .logo-dark {
.dark .vp-sponsor-grid-item.has-dark-logo:hover .logo-light {
display: block;
}
+
+@media (max-width: 639px) {
+ .VPHomeHero {
+ overflow: hidden;
+ }
+
+ .VPHomeHero .main,
+ .VPHomeHero .heading,
+ .VPHomeHero .text,
+ .VPHomeHero .tagline {
+ min-width: 0;
+ max-width: 100%;
+ }
+
+ .VPHomeHero .text {
+ width: 100%;
+ overflow-wrap: anywhere;
+ font-size: 28px;
+ line-height: 36px;
+ }
+
+ .VPHomeHero .actions {
+ align-items: stretch;
+ margin-right: 0;
+ margin-left: 0;
+ }
+
+ .VPHomeHero .action {
+ min-width: min(100%, 15rem);
+ }
+
+ .VPHomeHero .VPButton {
+ justify-content: center;
+ width: 100%;
+ }
+}
diff --git a/website/.vitepress/theme/index.ts b/website/.vitepress/theme/index.ts
index 495bf7dc67..91c9d08592 100644
--- a/website/.vitepress/theme/index.ts
+++ b/website/.vitepress/theme/index.ts
@@ -23,7 +23,10 @@ export default {
app.component('BlogPost', BlogPost);
app.component('Version', Version);
app.component('Adopters', Adopters);
- app.component('CopyOrDownloadAsMarkdownButtons', CopyOrDownloadAsMarkdownButtons);
+ app.component(
+ 'CopyOrDownloadAsMarkdownButtons',
+ CopyOrDownloadAsMarkdownButtons
+ );
enhanceAppWithTabs(app);
}
} satisfies Theme;
diff --git a/website/package.json b/website/package.json
index a8be0fbe9f..51ff6f18de 100644
--- a/website/package.json
+++ b/website/package.json
@@ -18,10 +18,11 @@
"gray-matter": "^4.0.3",
"netlify-cli": "^27.0.0",
"prettier": "^3.6.2",
+ "shiki": "^2.5.0",
"vitepress": "^1.6.3",
"vitepress-plugin-group-icons": "^1.6.1",
- "vitepress-plugin-tabs": "^0.9.0",
"vitepress-plugin-llms": "^1.9.1",
+ "vitepress-plugin-tabs": "^0.9.0",
"vue": "^3.5.18"
},
"packageManager": "pnpm@11.22.0+sha512.1ff870c4c6133dfd88fb2afc46dd13d47f09c9794b438c6fdb47ca98caf3bc16381ee0be93a091b8e3824cf01f889f46d7d9e20910fb0be1ab0fb5baa80dd621"
diff --git a/website/pnpm-lock.yaml b/website/pnpm-lock.yaml
index 94599d49b0..5a86df4004 100644
--- a/website/pnpm-lock.yaml
+++ b/website/pnpm-lock.yaml
@@ -23,6 +23,9 @@ importers:
prettier:
specifier: ^3.6.2
version: 3.9.6
+ shiki:
+ specifier: ^2.5.0
+ version: 2.5.0
vitepress:
specifier: ^1.6.3
version: 1.6.4(@algolia/client-search@5.35.0)(@types/node@24.13.3)(jwt-decode@4.0.0)(postcss@8.5.26)(search-insights@2.17.3)(typescript@5.9.3)
diff --git a/website/src/index.md b/website/src/index.md
index e051f286d1..7ec1f85648 100644
--- a/website/src/index.md
+++ b/website/src/index.md
@@ -4,6 +4,7 @@ description:
Task is a fast, cross-platform task runner and build tool that uses a simple
YAML Taskfile.
layout: home
+titleTemplate: false
hero:
name: Task
text: The Modern Task Runner
@@ -15,38 +16,33 @@ hero:
alt: Task logo
actions:
- theme: brand
- text: Install
- link: /docs/installation
- - theme: alt
text: Get Started
link: /docs/getting-started
- theme: alt
- text: Guide
- link: /docs/guide
+ text: Browse the Reference
+ link: /docs/reference/schema
features:
- - title: 30-Second Setup
+ - title: Readable by design
details:
- Single binary download, zero dependencies. Works with Homebrew, Snapcraft,
- Scoop and more.
- icon: 🚀
+ Describe commands, dependencies, and inputs in YAML that the whole team
+ can understand.
+ icon: 📖
- - title: Truly cross-platform
+ - title: One workflow everywhere
icon: 🖥️
- details:
- Run the same Taskfile on Linux, macOS and Windows. No extra setup. Task
- handles platform quirks so you don’t have to.
+ details: Run the same Taskfile on Linux, macOS, Windows, locally, and in CI.
- - title: Smart Caching
+ - title: Only run what changed
icon: 🎯
details:
- Skip unnecessary rebuilds by tracking file changes (timestamp or
- content-based).
+ Track sources and generated files to skip work that is already up to date.
- - title: Ideal for code generation & scaffolding
- icon: ⚡
+ - title: One Taskfile, every repo
+ icon: 🔗
details:
- Use Task to wire up codegen tools, formatters, linters, or anything
- repetitive. Chain commands, set dependencies, and keep your workflow
- clean.
+ Include Taskfiles straight from a URL or a Git repo, so shared workflows
+ live in one place instead of being copy-pasted.
+ link: /docs/remote-taskfiles
+ linkText: Explore Remote Taskfiles
---