From 936b90c625b1e8005eb555f2a0ea5f254ae9c9ff Mon Sep 17 00:00:00 2001 From: Valentin Maerten Date: Mon, 17 Aug 2026 14:15:03 +0200 Subject: [PATCH] feat(website): serve the released docs from a versioned copy (#2979) --- .github/workflows/ci.yml | 40 +- Taskfile.yml | 3 +- cmd/release/main.go | 50 +- website/.vitepress/blog.data.ts | 12 +- website/.vitepress/config.ts | 193 +- website/.vitepress/sidebar/latest.ts | 134 + website/.vitepress/sidebar/next.ts | 138 + website/Taskfile.yml | 40 +- .../src/{ => latest}/blog/any-variables.md | 0 .../blog/github-secure-open-source-program.md | 0 website/src/{ => latest}/blog/go-tool-task.md | 0 .../blog/if-and-variable-prompt.md | 0 website/src/{ => latest}/blog/index.md | 2 +- website/src/{ => latest}/blog/task-in-2023.md | 0 .../{ => latest}/blog/windows-core-utils.md | 0 website/src/{ => latest}/docs/changelog.md | 0 website/src/{ => latest}/docs/community.md | 0 website/src/{ => latest}/docs/contributing.md | 41 +- .../docs/deprecations/completion-scripts.md | 0 .../{ => latest}/docs/deprecations/index.md | 0 .../docs/deprecations/template-functions.md | 0 .../docs/deprecations/template.md | 0 .../docs/deprecations/version-2-schema.md | 0 .../docs/experiments/env-precedence.md | 0 .../docs/experiments/gentle-force.md | 0 website/src/latest/docs/experiments/index.md | 148 + .../docs/experiments/remote-taskfiles.md | 505 +++ .../{ => latest}/docs/experiments/template.md | 0 website/src/{ => latest}/docs/faq.md | 0 .../src/{ => latest}/docs/getting-started.md | 0 website/src/latest/docs/guide.md | 2888 +++++++++++++++++ website/src/latest/docs/installation.md | 448 +++ website/src/{ => latest}/docs/integrations.md | 4 +- website/src/latest/docs/reference/cli.md | 445 +++ website/src/latest/docs/reference/config.md | 197 ++ .../src/latest/docs/reference/environment.md | 162 + .../{ => latest}/docs/reference/package.md | 0 website/src/latest/docs/reference/schema.md | 1013 ++++++ .../src/latest/docs/reference/templating.md | 860 +++++ website/src/{ => latest}/docs/releasing.md | 20 +- .../docs/security/incident-response-plan.md | 0 .../src/{ => latest}/docs/security/index.md | 0 .../docs/security/threat-model.md | 0 website/src/{ => latest}/docs/styleguide.md | 0 .../{ => latest}/docs/taskfile-versions.md | 0 website/src/next/blog/any-variables.md | 155 + .../blog/github-secure-open-source-program.md | 58 + website/src/next/blog/go-tool-task.md | 56 + .../src/next/blog/if-and-variable-prompt.md | 137 + website/src/next/blog/index.md | 20 + website/src/next/blog/task-in-2023.md | 141 + website/src/next/blog/windows-core-utils.md | 141 + website/src/next/docs/changelog.md | 1575 +++++++++ website/src/next/docs/community.md | 41 + website/src/next/docs/contributing.md | 261 ++ .../docs/deprecations/completion-scripts.md | 25 + website/src/next/docs/deprecations/index.md | 22 + .../docs/deprecations/template-functions.md | 27 + .../src/next/docs/deprecations/template.md | 24 + .../docs/deprecations/version-2-schema.md | 33 + .../next/docs/experiments/env-precedence.md | 77 + .../src/next/docs/experiments/gentle-force.md | 48 + .../src/{ => next}/docs/experiments/index.md | 0 .../docs/experiments/remote-taskfiles.md | 0 website/src/next/docs/experiments/template.md | 36 + website/src/next/docs/faq.md | 135 + website/src/next/docs/getting-started.md | 135 + website/src/{ => next}/docs/guide.md | 0 website/src/{ => next}/docs/installation.md | 0 website/src/next/docs/integrations.md | 126 + website/src/{ => next}/docs/reference/cli.md | 0 .../src/{ => next}/docs/reference/config.md | 0 .../{ => next}/docs/reference/environment.md | 0 website/src/next/docs/reference/package.md | 181 ++ .../src/{ => next}/docs/reference/schema.md | 0 .../{ => next}/docs/reference/templating.md | 0 website/src/next/docs/releasing.md | 63 + .../src/{ => next}/docs/remote-taskfiles.md | 0 .../docs/security/incident-response-plan.md | 91 + website/src/next/docs/security/index.md | 22 + .../src/next/docs/security/threat-model.md | 174 + website/src/next/docs/styleguide.md | 230 ++ website/src/next/docs/taskfile-versions.md | 83 + website/src/public/next-schema-taskrc.json | 94 + website/src/public/next-schema.json | 920 ++++++ website/src/public/schema-taskrc.json | 4 + website/src/public/schema.json | 87 +- 87 files changed, 12318 insertions(+), 247 deletions(-) create mode 100644 website/.vitepress/sidebar/latest.ts create mode 100644 website/.vitepress/sidebar/next.ts rename website/src/{ => latest}/blog/any-variables.md (100%) rename website/src/{ => latest}/blog/github-secure-open-source-program.md (100%) rename website/src/{ => latest}/blog/go-tool-task.md (100%) rename website/src/{ => latest}/blog/if-and-variable-prompt.md (100%) rename website/src/{ => latest}/blog/index.md (83%) rename website/src/{ => latest}/blog/task-in-2023.md (100%) rename website/src/{ => latest}/blog/windows-core-utils.md (100%) rename website/src/{ => latest}/docs/changelog.md (100%) rename website/src/{ => latest}/docs/community.md (100%) rename website/src/{ => latest}/docs/contributing.md (85%) rename website/src/{ => latest}/docs/deprecations/completion-scripts.md (100%) rename website/src/{ => latest}/docs/deprecations/index.md (100%) rename website/src/{ => latest}/docs/deprecations/template-functions.md (100%) rename website/src/{ => latest}/docs/deprecations/template.md (100%) rename website/src/{ => latest}/docs/deprecations/version-2-schema.md (100%) rename website/src/{ => latest}/docs/experiments/env-precedence.md (100%) rename website/src/{ => latest}/docs/experiments/gentle-force.md (100%) create mode 100644 website/src/latest/docs/experiments/index.md create mode 100644 website/src/latest/docs/experiments/remote-taskfiles.md rename website/src/{ => latest}/docs/experiments/template.md (100%) rename website/src/{ => latest}/docs/faq.md (100%) rename website/src/{ => latest}/docs/getting-started.md (100%) create mode 100644 website/src/latest/docs/guide.md create mode 100644 website/src/latest/docs/installation.md rename website/src/{ => latest}/docs/integrations.md (96%) create mode 100644 website/src/latest/docs/reference/cli.md create mode 100644 website/src/latest/docs/reference/config.md create mode 100644 website/src/latest/docs/reference/environment.md rename website/src/{ => latest}/docs/reference/package.md (100%) create mode 100644 website/src/latest/docs/reference/schema.md create mode 100644 website/src/latest/docs/reference/templating.md rename website/src/{ => latest}/docs/releasing.md (69%) rename website/src/{ => latest}/docs/security/incident-response-plan.md (100%) rename website/src/{ => latest}/docs/security/index.md (100%) rename website/src/{ => latest}/docs/security/threat-model.md (100%) rename website/src/{ => latest}/docs/styleguide.md (100%) rename website/src/{ => latest}/docs/taskfile-versions.md (100%) create mode 100644 website/src/next/blog/any-variables.md create mode 100644 website/src/next/blog/github-secure-open-source-program.md create mode 100644 website/src/next/blog/go-tool-task.md create mode 100644 website/src/next/blog/if-and-variable-prompt.md create mode 100644 website/src/next/blog/index.md create mode 100644 website/src/next/blog/task-in-2023.md create mode 100644 website/src/next/blog/windows-core-utils.md create mode 100644 website/src/next/docs/changelog.md create mode 100644 website/src/next/docs/community.md create mode 100644 website/src/next/docs/contributing.md create mode 100644 website/src/next/docs/deprecations/completion-scripts.md create mode 100644 website/src/next/docs/deprecations/index.md create mode 100644 website/src/next/docs/deprecations/template-functions.md create mode 100644 website/src/next/docs/deprecations/template.md create mode 100644 website/src/next/docs/deprecations/version-2-schema.md create mode 100644 website/src/next/docs/experiments/env-precedence.md create mode 100644 website/src/next/docs/experiments/gentle-force.md rename website/src/{ => next}/docs/experiments/index.md (100%) rename website/src/{ => next}/docs/experiments/remote-taskfiles.md (100%) create mode 100644 website/src/next/docs/experiments/template.md create mode 100644 website/src/next/docs/faq.md create mode 100644 website/src/next/docs/getting-started.md rename website/src/{ => next}/docs/guide.md (100%) rename website/src/{ => next}/docs/installation.md (100%) create mode 100644 website/src/next/docs/integrations.md rename website/src/{ => next}/docs/reference/cli.md (100%) rename website/src/{ => next}/docs/reference/config.md (100%) rename website/src/{ => next}/docs/reference/environment.md (100%) create mode 100644 website/src/next/docs/reference/package.md rename website/src/{ => next}/docs/reference/schema.md (100%) rename website/src/{ => next}/docs/reference/templating.md (100%) create mode 100644 website/src/next/docs/releasing.md rename website/src/{ => next}/docs/remote-taskfiles.md (100%) create mode 100644 website/src/next/docs/security/incident-response-plan.md create mode 100644 website/src/next/docs/security/index.md create mode 100644 website/src/next/docs/security/threat-model.md create mode 100644 website/src/next/docs/styleguide.md create mode 100644 website/src/next/docs/taskfile-versions.md create mode 100644 website/src/public/next-schema-taskrc.json create mode 100644 website/src/public/next-schema.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35c46d12..0ff31d6d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,7 +95,45 @@ jobs: run: python -m pip install 'check-jsonschema==0.27.3' - name: 📋 Validate JSON Schema - run: check-jsonschema --check-metaschema website/src/public/schema.json + run: check-jsonschema --check-metaschema website/src/public/next-schema.json website/src/public/schema.json + + check-latest-content: + name: 📚 Check latest content + # Pull requests only: the release commit is pushed straight to main and is + # the one thing allowed to rewrite these files. + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + permissions: + pull-requests: read + steps: + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + // Everything cmd/release overwrites. Adding a file is fine -- that + // is how a blog post gets published early -- editing one is not. + const generated = (name) => + name.startsWith('website/src/latest/') || + name === 'website/src/public/schema.json' || + name === 'website/src/public/schema-taskrc.json' || + name === 'website/.vitepress/sidebar/latest.ts' + + const files = await github.paginate( + github.rest.pulls.listFiles, { + pull_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100, + } + ) + const edited = files.filter( + (f) => generated(f.filename) && f.status !== 'added' + ) + if (edited.length > 0) { + core.setFailed( + 'These files are generated by cmd/release and would be overwritten at the next release. Update their website/src counterpart instead:\n' + + edited.map((f) => f.filename).join('\n') + ) + } govulncheck: name: 🛡️ Vulnerabilities diff --git a/Taskfile.yml b/Taskfile.yml index 6b9b69a7..da8b2b44 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -197,8 +197,7 @@ tasks: This task will do the following: - Update the version and date in the CHANGELOG.md file - - Update the version in the package.json and package-lock.json files - - Copy the latest docs to the "current" version on the website + - Promote the docs, sidebar and JSON schemas to the released version - Commit the changes - Create a new tag - Push the commit/tag to the repository diff --git a/cmd/release/main.go b/cmd/release/main.go index 1406729c..9448f03b 100644 --- a/cmd/release/main.go +++ b/cmd/release/main.go @@ -15,10 +15,29 @@ import ( const ( changelogSource = "CHANGELOG.md" - changelogTarget = "website/src/docs/changelog.md" + changelogTarget = "website/src/next/docs/changelog.md" versionFile = "internal/version/version.txt" ) +type promotion struct{ source, target string } + +// Promoted at release time: the website builds `next` from the sources on the +// left and `latest` from the targets on the right, so that taskfile.dev only +// ever documents the version being released. The other half of the mechanism +// lives in website/.vitepress/config.ts, which picks a side at build time. +var ( + promotedDirs = []promotion{ + {"website/src/next/docs", "website/src/latest/docs"}, + {"website/src/next/blog", "website/src/latest/blog"}, + } + + promotedFiles = []promotion{ + {"website/.vitepress/sidebar/next.ts", "website/.vitepress/sidebar/latest.ts"}, + {"website/src/public/next-schema.json", "website/src/public/schema.json"}, + {"website/src/public/next-schema-taskrc.json", "website/src/public/schema-taskrc.json"}, + } +) + var changelogReleaseRegex = regexp.MustCompile(`## Unreleased`) // Flags @@ -61,6 +80,11 @@ func release() error { return err } + // After the changelog so that the promoted docs carry it. + if err := promote(); err != nil { + return err + } + if err := setVersionFile(versionFile, version); err != nil { return err } @@ -68,6 +92,30 @@ func release() error { return nil } +func promote() error { + for _, p := range promotedDirs { + // CopyFS refuses to overwrite, so the previous release has to go first. + if err := os.RemoveAll(p.target); err != nil { + return err + } + if err := os.CopyFS(p.target, os.DirFS(p.source)); err != nil { + return err + } + } + + for _, p := range promotedFiles { + b, err := os.ReadFile(p.source) + if err != nil { + return err + } + if err := os.WriteFile(p.target, b, 0o644); err != nil { //nolint:gosec + return err + } + } + + return nil +} + func getVersion(filename string) (*semver.Version, error) { b, err := os.ReadFile(filename) if err != nil { diff --git a/website/.vitepress/blog.data.ts b/website/.vitepress/blog.data.ts index f6db8fc0..b3b43cab 100644 --- a/website/.vitepress/blog.data.ts +++ b/website/.vitepress/blog.data.ts @@ -29,10 +29,20 @@ function extractExcerpt(html: string): string | undefined { .trim(); } -export default createContentLoader('blog/*.md', { +// Same channel as .vitepress/config.ts: the posts of the other one are not part +// of this build. +const channel = process.env.DOCS_CHANNEL === 'latest' ? 'latest' : 'next'; + +export default createContentLoader(`${channel}/blog/*.md`, { render: true, transform(raw) { return raw + .map((page) => ({ + ...page, + // Content loaders resolve URLs against `srcDir` and know nothing about + // `rewrites`, so the channel has to be stripped by hand. + url: page.url.replace(`/${channel}/`, '/') + })) .filter(({ url }) => url !== '/blog/') .map(({ frontmatter, html, url }) => { const date = new Date(frontmatter.date); diff --git a/website/.vitepress/config.ts b/website/.vitepress/config.ts index 16b1ac1b..c17d73ed 100644 --- a/website/.vitepress/config.ts +++ b/website/.vitepress/config.ts @@ -14,15 +14,28 @@ import { adopters } from './adopters.ts'; import { taskDescription, taskName, ogUrl, ogImage } from './meta.ts'; import { fileURLToPath, URL } from 'node:url'; import llmstxt from 'vitepress-plugin-llms'; +import { sidebar as nextSidebar } from './sidebar/next.ts'; +import { sidebar as latestSidebar } from './sidebar/latest.ts'; const version = readFileSync( resolve(__dirname, '../../internal/version/version.txt'), 'utf8' ).trim(); +// Which channel to build. `src/next` is written for the upcoming release and +// serves next.taskfile.dev; `src/latest` is its copy at the released version +// and serves taskfile.dev. Both mount at the same URLs, so taskfile.dev never +// documents or announces a feature that is not in the released binary. +// cmd/release owns the other half of this: it promotes one over the other. +const isLatest = process.env.DOCS_CHANNEL === 'latest'; +const channel = isLatest ? 'latest' : 'next'; +const other = isLatest ? 'next' : 'latest'; + +const docsSidebar = isLatest ? latestSidebar : nextSidebar; + // Builds the "/blog/" sidebar from each blog post's frontmatter. function buildBlogSidebar() { - const blogDir = resolve(__dirname, '../src/blog'); + const blogDir = resolve(__dirname, `../src/${channel}/blog`); const posts = readdirSync(blogDir) .filter((file) => file.endsWith('.md') && file !== 'index.md') .map((file) => { @@ -53,11 +66,15 @@ function buildBlogSidebar() { })); } +// Ports are the ones the dev tasks bind to; keep them in sync with +// website/Taskfile.yml. DOCS_LOCAL is set by those tasks alone, so a build can +// never end up shipping localhost URLs. +const localPorts = { latest: 3002, next: 3001 }; const urlVersion = - process.env.NODE_ENV === 'development' + process.env.DOCS_LOCAL === '1' ? { - current: 'https://taskfile.dev/', - next: 'http://localhost:3002/' + current: `http://localhost:${localPorts.latest}/`, + next: `http://localhost:${localPorts.next}/` } : { current: 'https://taskfile.dev/', @@ -252,6 +269,8 @@ export default defineConfig({ }, srcDir: 'src', cleanUrls: true, + srcExclude: [`${other}/**`], + rewrites: { [`${channel}/:path*`]: ':path*' }, markdown: { config: (md) => { md.use(githubLinksPlugin, { @@ -269,11 +288,12 @@ export default defineConfig({ 'index.md', 'team.md', 'donate.md', - 'docs/styleguide.md', - 'docs/contributing.md', - 'docs/releasing.md', - 'docs/changelog.md', - 'blog/*' + // Matched against source paths, which `rewrites` does not touch. + `${channel}/docs/styleguide.md`, + `${channel}/docs/contributing.md`, + `${channel}/docs/releasing.md`, + `${channel}/docs/changelog.md`, + `${channel}/blog/*` ] }), groupIconVitePlugin({ @@ -326,17 +346,24 @@ export default defineConfig({ { text: 'Donate', link: '/donate' }, { text: 'Team', link: '/team' }, { - text: process.env.NODE_ENV === 'development' ? 'Next' : `v${version}`, + text: isLatest ? `v${version}` : 'Next', items: [ { items: [ + // Absolute links, so VitePress would treat them as external and + // open them in a new tab. Switching channels is navigation, not a + // detour off the site. { text: `v${version}`, - link: urlVersion.current + link: urlVersion.current, + target: '_self', + noIcon: true }, { text: 'Next', - link: urlVersion.next + link: urlVersion.next, + target: '_self', + noIcon: true } ] } @@ -346,139 +373,7 @@ export default defineConfig({ sidebar: { '/blog/': buildBlogSidebar(), - '/': [ - { - text: 'Installation', - link: '/docs/installation' - }, - { - text: 'Getting Started', - link: '/docs/getting-started' - }, - { - text: 'Guide', - link: '/docs/guide' - }, - { - text: 'Remote Taskfiles', - link: '/docs/remote-taskfiles' - }, - { - text: 'Reference', - collapsed: true, - items: [ - { - text: 'Taskfile Schema', - link: '/docs/reference/schema' - }, - { - text: 'Environment', - link: '/docs/reference/environment' - }, - { - text: 'Configuration', - link: '/docs/reference/config' - }, - { - text: 'CLI', - link: '/docs/reference/cli' - }, - { - text: 'Templating', - link: '/docs/reference/templating' - }, - { - text: 'Package API', - link: '/docs/reference/package' - } - ] - }, - { - text: 'Experiments', - collapsed: true, - link: '/docs/experiments/', - items: [ - { - text: 'Env Precedence (#1038)', - link: '/docs/experiments/env-precedence' - }, - { - text: 'Gentle Force (#1200)', - link: '/docs/experiments/gentle-force' - }, - { - text: 'Remote Taskfiles (#1317)', - link: '/docs/experiments/remote-taskfiles' - } - ] - }, - { - text: 'Deprecations', - collapsed: true, - link: '/docs/deprecations/', - items: [ - { - text: 'Completion Scripts', - link: '/docs/deprecations/completion-scripts' - }, - { - text: 'Template Functions', - link: '/docs/deprecations/template-functions' - }, - { - text: 'Version 2 Schema (#1197)', - link: '/docs/deprecations/version-2-schema' - } - ] - }, - { - text: 'Taskfile Versions', - link: '/docs/taskfile-versions' - }, - { - text: 'Integrations', - link: '/docs/integrations' - }, - { - text: 'Community', - link: '/docs/community' - }, - { - text: 'Style Guide', - link: '/docs/styleguide' - }, - { - text: 'Contributing', - link: '/docs/contributing' - }, - { - text: 'Releasing', - link: '/docs/releasing' - }, - { - text: 'Security', - collapsed: true, - link: '/docs/security/', - items: [ - { - text: 'Incident Response Plan', - link: '/docs/security/incident-response-plan' - }, - { - text: 'Threat Model', - link: '/docs/security/threat-model' - } - ] - }, - { - text: 'Changelog', - link: '/docs/changelog' - }, - { - text: 'FAQ', - link: '/docs/faq' - } - ], + '/': docsSidebar, // Hacky to disable sidebar for these pages '/donate': [], '/team': [], @@ -495,7 +390,13 @@ export default defineConfig({ editLink: { text: 'Edit this page on GitHub', - pattern: 'https://github.com/go-task/task/edit/main/website/src/:path' + // Docs are always edited in `src/next`, even when the latest channel + // serves them from `src/latest/docs`. + // Serialized with toString() and evaluated in the browser, so it must not + // reference anything from this module. Both channels are edited in + // src/next, so strip whichever prefix the page was built from. + pattern: ({ filePath }) => + `https://github.com/go-task/task/edit/main/website/src/next/${filePath.replace(/^(next|latest)\//, '')}` }, footer: { diff --git a/website/.vitepress/sidebar/latest.ts b/website/.vitepress/sidebar/latest.ts new file mode 100644 index 00000000..34cd8c3b --- /dev/null +++ b/website/.vitepress/sidebar/latest.ts @@ -0,0 +1,134 @@ +import type { DefaultTheme } from 'vitepress'; + +// Navigation for the `/docs` section. next.ts is the source of both sidebars; +// cmd/release copies it over latest.ts alongside the content it describes. See +// the "Documentation channels" section of website/src/next/docs/contributing.md. +export const sidebar: DefaultTheme.SidebarItem[] = [ + { + text: 'Installation', + link: '/docs/installation' + }, + { + text: 'Getting Started', + link: '/docs/getting-started' + }, + { + text: 'Guide', + link: '/docs/guide' + }, + { + text: 'Reference', + collapsed: true, + items: [ + { + text: 'Taskfile Schema', + link: '/docs/reference/schema' + }, + { + text: 'Environment', + link: '/docs/reference/environment' + }, + { + text: 'Configuration', + link: '/docs/reference/config' + }, + { + text: 'CLI', + link: '/docs/reference/cli' + }, + { + text: 'Templating', + link: '/docs/reference/templating' + }, + { + text: 'Package API', + link: '/docs/reference/package' + } + ] + }, + { + text: 'Experiments', + collapsed: true, + link: '/docs/experiments/', + items: [ + { + text: 'Env Precedence (#1038)', + link: '/docs/experiments/env-precedence' + }, + { + text: 'Gentle Force (#1200)', + link: '/docs/experiments/gentle-force' + }, + { + text: 'Remote Taskfiles (#1317)', + link: '/docs/experiments/remote-taskfiles' + } + ] + }, + { + text: 'Deprecations', + collapsed: true, + link: '/docs/deprecations/', + items: [ + { + text: 'Completion Scripts', + link: '/docs/deprecations/completion-scripts' + }, + { + text: 'Template Functions', + link: '/docs/deprecations/template-functions' + }, + { + text: 'Version 2 Schema (#1197)', + link: '/docs/deprecations/version-2-schema' + } + ] + }, + { + text: 'Taskfile Versions', + link: '/docs/taskfile-versions' + }, + { + text: 'Integrations', + link: '/docs/integrations' + }, + { + text: 'Community', + link: '/docs/community' + }, + { + text: 'Style Guide', + link: '/docs/styleguide' + }, + { + text: 'Contributing', + link: '/docs/contributing' + }, + { + text: 'Releasing', + link: '/docs/releasing' + }, + { + text: 'Security', + collapsed: true, + link: '/docs/security/', + items: [ + { + text: 'Incident Response Plan', + link: '/docs/security/incident-response-plan' + }, + { + text: 'Threat Model', + link: '/docs/security/threat-model' + } + ] + }, + { + text: 'Changelog', + link: '/docs/changelog' + }, + { + text: 'FAQ', + link: '/docs/faq' + } +]; diff --git a/website/.vitepress/sidebar/next.ts b/website/.vitepress/sidebar/next.ts new file mode 100644 index 00000000..c7171547 --- /dev/null +++ b/website/.vitepress/sidebar/next.ts @@ -0,0 +1,138 @@ +import type { DefaultTheme } from 'vitepress'; + +// Navigation for the `/docs` section. next.ts is the source of both sidebars; +// cmd/release copies it over latest.ts alongside the content it describes. See +// the "Documentation channels" section of website/src/next/docs/contributing.md. +export const sidebar: DefaultTheme.SidebarItem[] = [ + { + text: 'Installation', + link: '/docs/installation' + }, + { + text: 'Getting Started', + link: '/docs/getting-started' + }, + { + text: 'Guide', + link: '/docs/guide' + }, + { + text: 'Remote Taskfiles', + link: '/docs/remote-taskfiles' + }, + { + text: 'Reference', + collapsed: true, + items: [ + { + text: 'Taskfile Schema', + link: '/docs/reference/schema' + }, + { + text: 'Environment', + link: '/docs/reference/environment' + }, + { + text: 'Configuration', + link: '/docs/reference/config' + }, + { + text: 'CLI', + link: '/docs/reference/cli' + }, + { + text: 'Templating', + link: '/docs/reference/templating' + }, + { + text: 'Package API', + link: '/docs/reference/package' + } + ] + }, + { + text: 'Experiments', + collapsed: true, + link: '/docs/experiments/', + items: [ + { + text: 'Env Precedence (#1038)', + link: '/docs/experiments/env-precedence' + }, + { + text: 'Gentle Force (#1200)', + link: '/docs/experiments/gentle-force' + }, + { + text: 'Remote Taskfiles (#1317)', + link: '/docs/experiments/remote-taskfiles' + } + ] + }, + { + text: 'Deprecations', + collapsed: true, + link: '/docs/deprecations/', + items: [ + { + text: 'Completion Scripts', + link: '/docs/deprecations/completion-scripts' + }, + { + text: 'Template Functions', + link: '/docs/deprecations/template-functions' + }, + { + text: 'Version 2 Schema (#1197)', + link: '/docs/deprecations/version-2-schema' + } + ] + }, + { + text: 'Taskfile Versions', + link: '/docs/taskfile-versions' + }, + { + text: 'Integrations', + link: '/docs/integrations' + }, + { + text: 'Community', + link: '/docs/community' + }, + { + text: 'Style Guide', + link: '/docs/styleguide' + }, + { + text: 'Contributing', + link: '/docs/contributing' + }, + { + text: 'Releasing', + link: '/docs/releasing' + }, + { + text: 'Security', + collapsed: true, + link: '/docs/security/', + items: [ + { + text: 'Incident Response Plan', + link: '/docs/security/incident-response-plan' + }, + { + text: 'Threat Model', + link: '/docs/security/threat-model' + } + ] + }, + { + text: 'Changelog', + link: '/docs/changelog' + }, + { + text: 'FAQ', + link: '/docs/faq' + } +]; diff --git a/website/Taskfile.yml b/website/Taskfile.yml index d2e5d973..cfaf794a 100644 --- a/website/Taskfile.yml +++ b/website/Taskfile.yml @@ -3,6 +3,9 @@ version: '3' tasks: install: desc: Setup VitePress locally + # start:all reaches this task through two parallel branches, and pnpm does + # not expect two installs at once. + run: once cmds: - pnpm install sources: @@ -12,13 +15,30 @@ tasks: default: desc: Start website deps: [install] - aliases: [s, start] + aliases: [s, start, start:next] vars: HOST: '{{default "0.0.0.0" .HOST}}' PORT: '{{default "3001" .PORT}}' + env: + DOCS_CHANNEL: '{{.CHANNEL | default "next"}}' + # Only the dev server sets this: it is what keeps the localhost URLs of + # the version selector out of a build. See .vitepress/config.ts. + DOCS_LOCAL: '1' cmds: - pnpm dev --host={{.HOST}} --port={{.PORT}} + # The port is half of a pair: config.ts links the two channels to each other + # on 3001 and 3002, so both dev servers can run side by side. + start:latest: + desc: Start website with the content of the released version + cmds: + - task: default + vars: { CHANNEL: latest, PORT: '{{default "3002" .PORT}}' } + + start:all: + desc: Start both channels side by side + deps: [default, start:latest] + lint: desc: Lint website deps: [install] @@ -27,10 +47,19 @@ tasks: build: desc: Build website + aliases: [build:next] deps: [install] + env: + DOCS_CHANNEL: '{{.CHANNEL | default "next"}}' cmds: - pnpm build + build:latest: + desc: Build website with the content of the released version + cmds: + - task: build + vars: { CHANNEL: latest } + preview: desc: Preview Website deps: [build] @@ -46,12 +75,17 @@ tasks: cmds: - rm -rf ./vitepress/dist + # --no-build is what makes the channel stick: the CLI builds by default, and + # that build would come from netlify.toml, which knows nothing about the + # channel these tasks just built. deploy:next: desc: Build and deploy next.taskfile.dev + deps: [build:next] cmds: - - pnpm netlify deploy --prod --site=4e13dfcf-fc0d-4bec-ad60-b918a8dc3942 + - pnpm netlify deploy --prod --no-build --site=4e13dfcf-fc0d-4bec-ad60-b918a8dc3942 deploy:prod: desc: Build and deploy taskfile.dev + deps: [build:latest] cmds: - - pnpm netlify deploy --prod --site=e625bc6a-1cd3-465d-ad30-7bbddaeb4f31 + - pnpm netlify deploy --prod --no-build --site=e625bc6a-1cd3-465d-ad30-7bbddaeb4f31 diff --git a/website/src/blog/any-variables.md b/website/src/latest/blog/any-variables.md similarity index 100% rename from website/src/blog/any-variables.md rename to website/src/latest/blog/any-variables.md diff --git a/website/src/blog/github-secure-open-source-program.md b/website/src/latest/blog/github-secure-open-source-program.md similarity index 100% rename from website/src/blog/github-secure-open-source-program.md rename to website/src/latest/blog/github-secure-open-source-program.md diff --git a/website/src/blog/go-tool-task.md b/website/src/latest/blog/go-tool-task.md similarity index 100% rename from website/src/blog/go-tool-task.md rename to website/src/latest/blog/go-tool-task.md diff --git a/website/src/blog/if-and-variable-prompt.md b/website/src/latest/blog/if-and-variable-prompt.md similarity index 100% rename from website/src/blog/if-and-variable-prompt.md rename to website/src/latest/blog/if-and-variable-prompt.md diff --git a/website/src/blog/index.md b/website/src/latest/blog/index.md similarity index 83% rename from website/src/blog/index.md rename to website/src/latest/blog/index.md index 540d7e88..d57ceb43 100644 --- a/website/src/blog/index.md +++ b/website/src/latest/blog/index.md @@ -5,7 +5,7 @@ editLink: false --- ` to the URL. +- You can also optionally specify a branch or tag to use by appending + `?ref=` to the end of the URL. If you omit a reference, the default + branch will be used. + +### Git over SSH + +`git@github.com/go-task/task.git//website/src/public/Taskfile.yml?ref=main` + +This type of node works by downloading the file from a Git repository over SSH. +The first part of the URL is the user and base URL of the Git repository. This +is the same URL that you would use to clone the repo over SSH. + +To use Git over SSH, you need to make sure that your SSH agent has your private +SSH keys added so that they can be used during authentication. + +- You can optionally add the path to the Taskfile in the repository by appending + `//` to the URL. +- You can also optionally specify a branch or tag to use by appending + `?ref=` to the end of the URL. If you omit a reference, the default + branch will be used. + +Task has an example remote Taskfile in our repository that you can use for +testing and that we will use throughout this document: + +```yaml +version: '3' + +tasks: + default: + cmds: + - task: hello + + hello: + cmds: + - echo "Hello Task!" +``` + +## Specifying a remote entrypoint + +By default, Task will look for one of the supported file names on your local +filesystem. If you want to use a remote file instead, you can pass its URI into +the `--taskfile`/`-t` flag just like you would to specify a different local +file. For example: + +::: code-group + +```shell [HTTP/HTTPS] +$ task --taskfile https://raw.githubusercontent.com/go-task/task/main/website/src/public/Taskfile.yml +task: [hello] echo "Hello Task!" +Hello Task! +``` + +```shell [Git over HTTP] +$ task --taskfile https://github.com/go-task/task.git//website/src/public/Taskfile.yml?ref=main +task: [hello] echo "Hello Task!" +Hello Task! +``` + +```shell [Git over SSH] +$ task --taskfile git@github.com/go-task/task.git//website/src/public/Taskfile.yml?ref=main +task: [hello] echo "Hello Task!" +Hello Task! +``` + +::: + +## Including remote Taskfiles + +Including a remote file works exactly the same way that including a local file +does. You just need to replace the local path with a remote URI. Any tasks in +the remote Taskfile will be available to run from your main Taskfile. + +::: code-group + +```yaml [HTTP/HTTPS] +version: '3' + +includes: + my-remote-namespace: https://raw.githubusercontent.com/go-task/task/main/website/src/public/Taskfile.yml +``` + +```yaml [Git over HTTP] +version: '3' + +includes: + my-remote-namespace: https://github.com/go-task/task.git//website/src/public/Taskfile.yml?ref=main +``` + +```yaml [Git over SSH] +version: '3' + +includes: + my-remote-namespace: git@github.com/go-task/task.git//website/src/public/Taskfile.yml?ref=main +``` + +::: + +```shell +$ task my-remote-namespace:hello +task: [hello] echo "Hello Task!" +Hello Task! +``` + +### Authenticating using environment variables + +The Taskfile location is processed by the templating system, so you can +reference environment variables in your URL if you need to add authentication. +For example: + +```yaml +version: '3' + +includes: + my-remote-namespace: https://{{.TOKEN}}@raw.githubusercontent.com/my-org/my-repo/main/Taskfile.yml +``` + +## Special Variables + +The file-path [special variables](../reference/templating.md#file-paths) behave +differently when a Taskfile is loaded from a remote source, because there is no +local file or directory that corresponds 1:1 to the Taskfile: + +| Variable | Value when loaded remotely | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `TASKFILE` / `ROOT_TASKFILE` | The original URL, unchanged | +| `TASKFILE_DIR` / `ROOT_DIR` | Empty string — a directory variable cannot point to a URL | +| `TASK_DIR` | Resolved against `USER_WORKING_DIR` (relative `dir:` → joined with `USER_WORKING_DIR`, empty `dir:` → `USER_WORKING_DIR`, absolute `dir:` → kept as-is) | + +If a remote Taskfile includes a local Taskfile (or vice-versa), each variable +reflects the source of the Taskfile it refers to. + +## Security + +### Automatic checksums + +Running commands from sources that you do not control is always a potential +security risk. For this reason, we have added some automatic checks when using +remote Taskfiles: + +1. When running a task from a remote Taskfile for the first time, Task will + print a warning to the console asking you to check that you are sure that you + trust the source of the Taskfile. If you do not accept the prompt, then Task + will exit with code `104` (not trusted) and nothing will run. If you accept + the prompt, the remote Taskfile will run and further calls to the remote + Taskfile will not prompt you again. +2. Whenever you run a remote Taskfile, Task will create and store a checksum of + the file that you are running. If the checksum changes, then Task will print + another warning to the console to inform you that the contents of the remote + file has changed. If you do not accept the prompt, then Task will exit with + code `104` (not trusted) and nothing will run. If you accept the prompt, the + checksum will be updated and the remote Taskfile will run. + +Sometimes you need to run Task in an environment that does not have an +interactive terminal, so you are not able to accept a prompt. In these cases you +are able to tell task to accept these prompts automatically by using the `--yes` +flag or the `--trusted-hosts` flag. The `--trusted-hosts` flag allows you to +specify trusted +hosts for remote Taskfiles, while `--yes` applies to all prompts in Task. You +can also configure trusted hosts in your [taskrc configuration](#trusted-hosts) using +`remote.trusted-hosts`. Before enabling automatic trust, you should: + +1. Be sure that you trust the source and contents of the remote Taskfile. +2. Consider using a pinned version of the remote Taskfile (e.g. A link + containing a commit hash) to prevent Task from automatically accepting a + prompt that says a remote Taskfile has changed. + +### Manual checksum pinning + +Alternatively, if you expect the contents of your remote files to be a constant +value, you can pin the checksum of the included file instead: + +```yaml +version: '3' + +includes: + included: + taskfile: https://taskfile.dev + checksum: c153e97e0b3a998a7ed2e61064c6ddaddd0de0c525feefd6bba8569827d8efe9 +``` + +This will disable the automatic checksum prompts discussed above. However, if +the checksums do not match, Task will exit immediately with an error. When +setting this up for the first time, you may not know the correct value of the +checksum. There are a couple of ways you can obtain this: + +1. Add the include normally without the `checksum` key. The first time you run + the included Taskfile, a `.task/remote` temporary directory is created. Find + the correct set of files for your included Taskfile and open the file that + ends with `.checksum`. You can copy the contents of this file and paste it + into the `checksum` key of your include. This method is safest as it allows + you to inspect the downloaded Taskfile before you pin it. +2. Alternatively, add the include with a temporary random value in the + `checksum` key. When you try to run the Taskfile, you will get an error that + will report the incorrect expected checksum and the actual checksum. You can + copy the actual checksum and replace your temporary random value. + +### TLS + +Task currently supports both `http` and `https` URLs. However, the `http` +requests will not execute by default unless you run the task with the +`--insecure` flag. This is to protect you from accidentally running a remote +Taskfile that is downloaded via an unencrypted connection. Sources that are not +protected by TLS are vulnerable to man-in-the-middle attacks and should be +avoided unless you know what you are doing. + +#### Custom Certificates + +If your remote Taskfiles are hosted on a server that uses a custom CA +certificate (e.g., a corporate internal server), you can specify the CA +certificate using the `--cacert` flag: + +```shell +task --taskfile https://internal.example.com/Taskfile.yml --cacert /path/to/ca.crt +``` + +For servers that require client certificate authentication (mTLS), you can +provide a client certificate and key: + +```shell +task --taskfile https://secure.example.com/Taskfile.yml \ + --cert /path/to/client.crt \ + --cert-key /path/to/client.key +``` + +::: warning + +Encrypted private keys are not currently supported. If your key is encrypted, +you must decrypt it first: + +```shell +openssl rsa -in encrypted.key -out decrypted.key +``` + +::: + +These options can also be configured in the [configuration file](#configuration). + +## Caching & Running Offline + +Whenever you run a remote Taskfile, the latest copy will be downloaded from the +internet and cached locally. This cached file will be used for all future +invocations of the Taskfile until the cache expires. Once it expires, Task will +download the latest copy of the file and update the cache. By default, the cache +is set to expire immediately. This means that Task will always fetch the latest +version. However, the cache expiry duration can be modified by setting the +`--expiry` flag. + +If for any reason you lose access to the internet or you are running Task in +offline mode (via the `--offline` flag or `TASK_OFFLINE` environment variable), +Task will run the any available cached files _even if they are expired_. This +means that you should never be stuck without the ability to run your tasks as +long as you have downloaded a remote Taskfile at least once. + +By default, Task will timeout requests to download remote files after 10 seconds +and look for a cached copy instead. This timeout can be configured by setting +the `--timeout` flag and specifying a duration. For example, `--timeout 5s` will +set the timeout to 5 seconds. + +By default, the cache is stored in the Task temp directory (`.task`). You can +override the location of the cache by using the `--remote-cache-dir` flag, the +`remote.cache-dir` option in your [configuration file](#cache-dir), or the +`TASK_REMOTE_DIR` environment variable. This way, you can share the cache +between different projects. + +You can force Task to ignore the cache and download the latest version by using +the `--download` flag. + +You can use the `--clear-cache` flag to clear all cached remote files. + +## Configuration + +This experiment adds a new `remote` section to the +[configuration file](../reference/config.md). + +- **Type**: `object` +- **Description**: Remote configuration settings for handling remote Taskfiles + +```yaml +remote: + insecure: false + offline: false + timeout: "30s" + cache-expiry: "24h" + cache-dir: ~/.task + trusted-hosts: + - github.com + - gitlab.com + cacert: "" + cert: "" + cert-key: "" +``` + +#### `insecure` + +- **Type**: `boolean` +- **Default**: `false` +- **Description**: Allow insecure connections when fetching remote Taskfiles +- **CLI equivalent**: `--insecure` +- **Environment variable**: `TASK_REMOTE_INSECURE` + +```yaml +remote: + insecure: true +``` + +#### `offline` + +- **Type**: `boolean` +- **Default**: `false` +- **Description**: Work in offline mode, preventing remote Taskfile fetching +- **CLI equivalent**: `--offline` +- **Environment variable**: `TASK_REMOTE_OFFLINE` + +```yaml +remote: + offline: true +``` + +#### `timeout` + +- **Type**: `string` +- **Default**: 10s +- **Pattern**: `^[0-9]+(ns|us|µs|ms|s|m|h)$` +- **Description**: Timeout duration for remote operations (e.g., '30s', '5m') +- **CLI equivalent**: `--timeout` +- **Environment variable**: `TASK_REMOTE_TIMEOUT` + +```yaml +remote: + timeout: "1m" +``` + +#### `cache-expiry` + +- **Type**: `string` +- **Default**: 0s (no cache) +- **Pattern**: `^[0-9]+(ns|us|µs|ms|s|m|h)$` +- **Description**: Cache expiry duration for remote Taskfiles (e.g., '1h', + '24h') +- **CLI equivalent**: `--expiry` +- **Environment variable**: `TASK_REMOTE_CACHE_EXPIRY` + +```yaml +remote: + cache-expiry: "6h" +``` + +#### `cache-dir` + +- **Type**: `string` +- **Default**: `.task` +- **Description**: Directory where remote Taskfiles are cached. Can be an + absolute path (e.g., `/var/cache/task`) or relative to the Taskfile directory. +- **CLI equivalent**: `--remote-cache-dir` +- **Environment variable**: `TASK_REMOTE_CACHE_DIR` + +```yaml +remote: + cache-dir: ~/.task +``` + +#### `trusted-hosts` + +- **Type**: `array of strings` +- **Default**: `[]` (empty list) +- **Description**: List of trusted hosts for remote Taskfiles. Hosts in this + list will not prompt for confirmation when downloading Taskfiles +- **CLI equivalent**: `--trusted-hosts` +- **Environment variable**: `TASK_REMOTE_TRUSTED_HOSTS` (comma-separated) + +```yaml +remote: + trusted-hosts: + - github.com + - gitlab.com + - raw.githubusercontent.com + - example.com:8080 +``` + +Hosts in the trusted hosts list will automatically be trusted without prompting for +confirmation when they are first downloaded or when their checksums change. The +host matching includes the port if specified in the URL. Use with caution and +only add hosts you fully trust. + +You can also specify trusted hosts via the command line: + +```shell +# Trust specific host for this execution +task --trusted-hosts github.com -t https://github.com/user/repo.git//Taskfile.yml + +# Trust multiple hosts (comma-separated) +task --trusted-hosts github.com,gitlab.com -t https://github.com/user/repo.git//Taskfile.yml + +# Trust a host with a specific port +task --trusted-hosts example.com:8080 -t https://example.com:8080/Taskfile.yml +``` + +#### `cacert` + +- **Type**: `string` +- **Default**: `""` +- **Description**: Path to a custom CA certificate file for TLS verification + +```yaml +remote: + cacert: "/path/to/ca.crt" +``` + +#### `cert` + +- **Type**: `string` +- **Default**: `""` +- **Description**: Path to a client certificate file for mTLS authentication + +```yaml +remote: + cert: "/path/to/client.crt" +``` + +#### `cert-key` + +- **Type**: `string` +- **Default**: `""` +- **Description**: Path to the client certificate private key file + +```yaml +remote: + cert-key: "/path/to/client.key" +``` diff --git a/website/src/docs/experiments/template.md b/website/src/latest/docs/experiments/template.md similarity index 100% rename from website/src/docs/experiments/template.md rename to website/src/latest/docs/experiments/template.md diff --git a/website/src/docs/faq.md b/website/src/latest/docs/faq.md similarity index 100% rename from website/src/docs/faq.md rename to website/src/latest/docs/faq.md diff --git a/website/src/docs/getting-started.md b/website/src/latest/docs/getting-started.md similarity index 100% rename from website/src/docs/getting-started.md rename to website/src/latest/docs/getting-started.md diff --git a/website/src/latest/docs/guide.md b/website/src/latest/docs/guide.md new file mode 100644 index 00000000..d5ca4622 --- /dev/null +++ b/website/src/latest/docs/guide.md @@ -0,0 +1,2888 @@ +--- +outline: deep +--- + +# Guide + +## Running Taskfiles + +Specific Taskfiles can be called by specifying the `--taskfile` flag. If you +don't specify a Taskfile, Task will automatically look for a file with one of +the [supported file names](#supported-file-names) in the current directory. If +you want to search in a different directory, you can use the `--dir` flag. + +### Supported file names + +Task looks for files with the following names, in order of priority: + +- `Taskfile.yml` +- `taskfile.yml` +- `Taskfile.yaml` +- `taskfile.yaml` +- `Taskfile.dist.yml` +- `taskfile.dist.yml` +- `Taskfile.dist.yaml` +- `taskfile.dist.yaml` + +The `.dist` variants allow projects to have one committed file (`.dist`) while +still allowing individual users to override the Taskfile by adding an additional +`Taskfile.yml` (which would be in your `.gitignore`). + +### Running a Taskfile from a subdirectory + +If a Taskfile cannot be found in the current working directory, it will walk up +the file tree until it finds one (similar to how `git` works). When running Task +from a subdirectory like this, it will behave as if you ran it from the +directory containing the Taskfile. + +You can use this functionality along with the special +`{{.USER_WORKING_DIR}}` variable to create some very useful +reusable tasks. For example, if you have a monorepo with directories for each +microservice, you can `cd` into a microservice directory and run a task command +to bring it up without having to create multiple tasks or Taskfiles with +identical content. For example: + +```yaml +version: '3' + +tasks: + up: + dir: '{{.USER_WORKING_DIR}}' + preconditions: + - test -f docker-compose.yml + cmds: + - docker-compose up -d +``` + +In this example, we can run `cd ` and `task up` and as long as the +`` directory contains a `docker-compose.yml`, the Docker composition +will be brought up. + +### Running a global Taskfile + +If you call Task with the `--global` (alias `-g`) flag, it will look for your +home directory instead of your working directory. In short, Task will look for a +Taskfile that matches `$HOME/{T,t}askfile.{yml,yaml}` . + +This is useful to have automation that you can run from anywhere in your system! + +::: info + +When running your global Taskfile with `-g`, tasks will run on `$HOME` by +default, and not on your working directory! + +As mentioned in the previous section, the +`{{.USER_WORKING_DIR}}` special variable can be very handy +here to run stuff on the directory you're calling `task -g` from. + +```yaml +version: '3' + +tasks: + from-home: + cmds: + - pwd + + from-working-directory: + dir: '{{.USER_WORKING_DIR}}' + cmds: + - pwd +``` + +::: + +### Reading a Taskfile from stdin + +Taskfile also supports reading from stdin. This is useful if you are generating +Taskfiles dynamically and don't want write them to disk. To tell task to read +from stdin, you must specify the `-t/--taskfile` flag with the special `-` +value. You may then pipe into Task as you would any other program: + +```shell +task -t - < ./Taskfile.yml +# OR +cat ./Taskfile.yml | task -t - +``` + +## Environment variables + +### Task + +You can use `env` to set custom environment variables for a specific task: + +```yaml +version: '3' + +tasks: + greet: + cmds: + - echo $GREETING + env: + GREETING: Hey, there! +``` + +Additionally, you can set global environment variables that will be available to +all tasks: + +```yaml +version: '3' + +env: + GREETING: Hey, there! + +tasks: + greet: + cmds: + - echo $GREETING +``` + +::: info + +`env` supports expansion and retrieving output from a shell command just like +variables, as you can see in the [Variables](#variables) section. + +::: + +### .env files + +You can also ask Task to include `.env` like files by using the `dotenv:` +setting: + +::: code-group + +```shell [.env] +KEYNAME=VALUE +``` + +```shell [testing/.env] +ENDPOINT=testing.com +``` + +::: + +```yaml +version: '3' + +env: + ENV: testing + +dotenv: ['.env', '{{.ENV}}/.env', '{{.HOME}}/.env'] + +tasks: + greet: + cmds: + - echo "Using $KEYNAME and endpoint $ENDPOINT" +``` + +When the same variable is defined in multiple dotenv files, the **first file in +the list takes precedence**. This allows you to set up override patterns by +placing higher-priority files first: + +```yaml +version: '3' + +dotenv: + - .env.local # Highest priority - local developer overrides + - .env.{{.ENV}} # Environment-specific settings + - .env # Base defaults (lowest priority) +``` + +Dotenv files can also be specified at the task level: + +```yaml +version: '3' + +env: + ENV: testing + +tasks: + greet: + dotenv: ['.env', '{{.ENV}}/.env', '{{.HOME}}/.env'] + cmds: + - echo "Using $KEYNAME and endpoint $ENDPOINT" +``` + +Environment variables specified explicitly at the task-level will override +variables defined in dotfiles: + +```yaml +version: '3' + +env: + ENV: testing + +tasks: + greet: + dotenv: ['.env', '{{.ENV}}/.env', '{{.HOME}}/.env'] + env: + KEYNAME: DIFFERENT_VALUE + cmds: + - echo "Using $KEYNAME and endpoint $ENDPOINT" +``` + +::: info + +Please note that you are not currently able to use the `dotenv` key inside +included Taskfiles. + +::: + +## Including other Taskfiles + +If you want to share tasks between different projects (Taskfiles), you can use +the importing mechanism to include other Taskfiles using the `includes` keyword: + +```yaml +version: '3' + +includes: + docs: ./documentation # will look for ./documentation/Taskfile.yml + docker: ./DockerTasks.yml +``` + +The tasks described in the given Taskfiles will be available with the informed +namespace. So, you'd call `task docs:serve` to run the `serve` task from +`documentation/Taskfile.yml` or `task docker:build` to run the `build` task from +the `DockerTasks.yml` file. + +Relative paths are resolved relative to the directory containing the including +Taskfile. + +### OS-specific Taskfiles + +You can include OS-specific Taskfiles by using a templating function: + +```yaml +version: '3' + +includes: + build: ./Taskfile_{{OS}}.yml +``` + +### Directory of included Taskfile + +By default, included Taskfile's tasks are run in the current directory, even if +the Taskfile is in another directory, but you can force its tasks to run in +another directory by using this alternative syntax: + +```yaml +version: '3' + +includes: + docs: + taskfile: ./docs/Taskfile.yml + dir: ./docs +``` + +::: info + +The included Taskfiles must be using the same schema version as the main +Taskfile uses. + +::: + +### Optional includes + +Includes marked as optional will allow Task to continue execution as normal if +the included file is missing. + +```yaml +version: '3' + +includes: + tests: + taskfile: ./tests/Taskfile.yml + optional: true + +tasks: + greet: + cmds: + - echo "This command can still be successfully executed if + ./tests/Taskfile.yml does not exist" +``` + +### Internal includes + +Includes marked as internal will set all the tasks of the included file to be +internal as well (see the [Internal tasks](#internal-tasks) section below). This +is useful when including utility tasks that are not intended to be used directly +by the user. + +```yaml +version: '3' + +includes: + tests: + taskfile: ./taskfiles/Utils.yml + internal: true +``` + +### Flatten includes + +You can flatten the included Taskfile tasks into the main Taskfile by using the +`flatten` option. It means that the included Taskfile tasks will be available +without the namespace. + +::: code-group + +```yaml [Taskfile.yml] +version: '3' + +includes: + lib: + taskfile: ./Included.yml + flatten: true + +tasks: + greet: + cmds: + - echo "Greet" + - task: foo +``` + +```yaml [Included.yml] +version: '3' + +tasks: + foo: + cmds: + - echo "Foo" +``` + +::: + +If you run `task -a` it will print : + +```sh +task: Available tasks for this project: +* greet: +* foo +``` + +You can run `task foo` directly without the namespace. + +You can also reference the task in other tasks without the namespace. So if you +run `task greet` it will run `greet` and `foo` tasks and the output will be : + +```text +Greet +Foo +``` + +If multiple tasks have the same name, an error will be thrown: + +::: code-group + +```yaml [Taskfile.yml] +version: '3' +includes: + lib: + taskfile: ./Included.yml + flatten: true + +tasks: + greet: + cmds: + - echo "Greet" + - task: foo +``` + +```yaml [Included.yml] +version: '3' + +tasks: + greet: + cmds: + - echo "Foo" +``` + +::: + +If you run `task -a` it will print: + +```text +task: Found multiple tasks (greet) included by "lib" +``` + +If the included Taskfile has a task with the same name as a task in the main +Taskfile, you may want to exclude it from the flattened tasks. + +You can do this by using the +[`excludes` option](#exclude-tasks-from-being-included). + +### Exclude tasks from being included + +You can exclude tasks from being included by using the `excludes` option. This +option takes the list of tasks to be excluded from this include. + +::: code-group + +```yaml [Taskfile.yml] +version: '3' + +includes: + included: + taskfile: ./Included.yml + excludes: [foo] +``` + +```yaml [Included.yml] +version: '3' + +tasks: + foo: echo "Foo" + bar: echo "Bar" +``` + +::: + +`task included:foo` will throw an error because the `foo` task is excluded but +`task included:bar` will work and display `Bar`. + +It's compatible with the `flatten` option. + +### Vars of included Taskfiles + +You can also specify variables when including a Taskfile. This may be useful for +having a reusable Taskfile that can be tweaked or even included more than once: + +```yaml +version: '3' + +includes: + backend: + taskfile: ./taskfiles/Docker.yml + vars: + DOCKER_IMAGE: backend_image + + frontend: + taskfile: ./taskfiles/Docker.yml + vars: + DOCKER_IMAGE: frontend_image +``` + +### Namespace aliases + +When including a Taskfile, you can give the namespace a list of `aliases`. This +works in the same way as [task aliases](#task-aliases) and can be used together +to create shorter and easier-to-type commands. + +```yaml +version: '3' + +includes: + generate: + taskfile: ./taskfiles/Generate.yml + aliases: [gen] +``` + +::: info + +Vars declared in the included Taskfile have preference over the variables in the +including Taskfile! If you want a variable in an included Taskfile to be +overridable, use the +[default function](https://sprig.taskfile.dev/defaults.html): +`MY_VAR: '{{.MY_VAR | default "my-default-value"}}'`. + +::: + +## Internal tasks + +Internal tasks are tasks that cannot be called directly by the user. They will +not appear in the output when running `task --list|--list-all`. Other tasks may +call internal tasks in the usual way. This is useful for creating reusable, +function-like tasks that have no useful purpose on the command line. + +```yaml +version: '3' + +tasks: + build-image-1: + cmds: + - task: build-image + vars: + DOCKER_IMAGE: image-1 + + build-image: + internal: true + cmds: + - docker build -t {{.DOCKER_IMAGE}} . +``` + +## Task directory + +By default, tasks will be executed in the directory where the Taskfile is +located. But you can easily make the task run in another folder, informing +`dir`: + +```yaml +version: '3' + +tasks: + serve: + dir: public/www + cmds: + # run http server + - caddy +``` + +If the directory does not exist, `task` creates it. + +## Task dependencies + +> Dependencies run in parallel, so dependencies of a task should not depend one +> another. If you want to force tasks to run serially, take a look at the +> [Calling Another Task](#calling-another-task) section below. + +You may have tasks that depend on others. Just pointing them on `deps` will make +them run automatically before running the parent task: + +```yaml +version: '3' + +tasks: + build: + deps: [assets] + cmds: + - go build -v -i main.go + + assets: + cmds: + - esbuild --bundle --minify css/index.css > public/bundle.css +``` + +In the above example, `assets` will always run right before `build` if you run +`task build`. + +A task can have only dependencies and no commands to group tasks together: + +```yaml +version: '3' + +tasks: + assets: + deps: [js, css] + + js: + cmds: + - esbuild --bundle --minify js/index.js > public/bundle.js + + css: + cmds: + - esbuild --bundle --minify css/index.css > public/bundle.css +``` + +If there is more than one dependency, they always run in parallel for better +performance. + +::: tip + +You can also make the tasks given by the command line run in parallel by using +the `--parallel` flag (alias `-p`). Example: `task --parallel js css`. + +::: + +If you want to pass information to dependencies, you can do that the same manner +as you would to [call another task](#calling-another-task): + +```yaml +version: '3' + +tasks: + default: + deps: + - task: echo_sth + vars: { TEXT: 'before 1' } + - task: echo_sth + vars: { TEXT: 'before 2' } + silent: true + cmds: + - echo "after" + + echo_sth: + cmds: + - echo {{.TEXT}} +``` + +### Fail-fast dependencies + +By default, Task waits for all dependencies to finish running before continuing. +If you want Task to stop executing further dependencies as soon as one fails, +you can set `failfast: true` on your [`.taskrc.yml`][config] or for a specific +task: + +```yaml +# .taskrc.yml +failfast: true # applies to all tasks +``` + +```yaml +# Taskfile.yml +version: '3' + +tasks: + default: + deps: [task1, task2, task3] + failfast: true # applies only to this task +``` + +Alternatively, you can use `--failfast`, which also work for `--parallel`. + +## Platform specific tasks and commands + +If you want to restrict the running of tasks to explicit platforms, this can be +achieved using the `platforms:` key. Tasks can be restricted to a specific OS, +architecture or a combination of both. On a mismatch, the task or command will +be skipped, and no error will be thrown. + +The values allowed as OS or Arch are valid `GOOS` and `GOARCH` values, as +defined by the Go language +[here](https://github.com/golang/go/blob/master/src/internal/syslist/syslist.go). + +The `build-windows` task below will run only on Windows, and on any +architecture: + +```yaml +version: '3' + +tasks: + build-windows: + platforms: [windows] + cmds: + - echo 'Running command on Windows' +``` + +This can be restricted to a specific architecture as follows: + +```yaml +version: '3' + +tasks: + build-windows-amd64: + platforms: [windows/amd64] + cmds: + - echo 'Running command on Windows (amd64)' +``` + +It is also possible to restrict the task to specific architectures: + +```yaml +version: '3' + +tasks: + build-amd64: + platforms: [amd64] + cmds: + - echo 'Running command on amd64' +``` + +Multiple platforms can be specified as follows: + +```yaml +version: '3' + +tasks: + build: + platforms: [windows/amd64, darwin] + cmds: + - echo 'Running command on Windows (amd64) and macOS' +``` + +Individual commands can also be restricted to specific platforms: + +```yaml +version: '3' + +tasks: + build: + cmds: + - cmd: echo 'Running command on Windows (amd64) and macOS' + platforms: [windows/amd64, darwin] + - cmd: echo 'Running on all platforms' +``` + +## Calling another task + +When a task has many dependencies, they are executed concurrently. This will +often result in a faster build pipeline. However, in some situations, you may +need to call other tasks serially. In this case, use the following syntax: + +```yaml +version: '3' + +tasks: + main-task: + cmds: + - task: task-to-be-called + - task: another-task + - echo "Both done" + + task-to-be-called: + cmds: + - echo "Task to be called" + + another-task: + cmds: + - echo "Another task" +``` + +Using the `vars` and `silent` attributes you can choose to pass variables and +toggle [silent mode](#silent-mode) on a call-by-call basis: + +```yaml +version: '3' + +tasks: + greet: + vars: + RECIPIENT: '{{default "World" .RECIPIENT}}' + cmds: + - echo "Hello, {{.RECIPIENT}}!" + + greet-pessimistically: + cmds: + - task: greet + vars: { RECIPIENT: 'Cruel World' } + silent: true +``` + +The above syntax is also supported in `deps`. + +::: tip + +NOTE: If you want to call a task declared in the root Taskfile from within an +[included Taskfile](#including-other-taskfiles), add a leading `:` like this: +`task: :task-name`. + +::: + +## Prevent unnecessary work + +### By fingerprinting locally generated files and their sources + +If a task generates something, you can inform Task the source and generated +files, so Task will prevent running them if not necessary. + +```yaml +version: '3' + +tasks: + build: + deps: [js, css] + cmds: + - go build -v -i main.go + + js: + cmds: + - esbuild --bundle --minify js/index.js > public/bundle.js + sources: + - src/js/**/*.js + generates: + - public/bundle.js + + css: + cmds: + - esbuild --bundle --minify css/index.css > public/bundle.css + sources: + - src/css/**/*.css + generates: + - public/bundle.css +``` + +`sources` and `generates` can be files or glob patterns. When given, Task will +compare the checksum of the source files to determine if it's necessary to run +the task. If not, it will just print a message like `Task "js" is up to date`. + +`exclude:` can also be used to exclude files from fingerprinting. Sources are +evaluated in order, so `exclude:` must come after the positive glob it is +negating. + +```yaml +version: '3' + +tasks: + css: + sources: + - mysources/**/*.css + - exclude: mysources/ignoreme.css + generates: + - public/bundle.css +``` + +If you prefer these check to be made by the modification timestamp of the files, +instead of its checksum (content), just set the `method` property to +`timestamp`. This can be done at two levels: + +At the task level for a specific task: + +```yaml +version: '3' + +tasks: + build: + cmds: + - go build . + sources: + - ./*.go + generates: + - app{{exeExt}} + method: timestamp +``` + +At the root level of the Taskfile to apply it globally to all tasks: + +```yaml +version: '3' + +method: timestamp # Will be the default for all tasks + +tasks: + build: + cmds: + - go build . + sources: + - ./*.go + generates: + - app{{exeExt}} +``` + +In situations where you need more flexibility the `status` keyword can be used. +You can even combine the two. See the documentation for +[status](#using-programmatic-checks-to-indicate-a-task-is-up-to-date) for an +example. + +::: info + +By default, task stores checksums on a local `.task` directory in the project's +directory. Most of the time, you'll want to have this directory on `.gitignore` +(or equivalent) so it isn't committed. (If you have a task for code generation +that is committed it may make sense to commit the checksum of that task as well, +though). + +If you want these files to be stored in another directory, you can set a +`TASK_TEMP_DIR` environment variable in your machine. It can contain a relative +path like `tmp/task` that will be interpreted as relative to the project +directory, or an absolute or home path like `/tmp/.task` or `~/.task` +(subdirectories will be created for each project). + +```shell +export TASK_TEMP_DIR='~/.task' +``` + +::: + +::: info + +Each task has only one checksum stored for its `sources`. If you want to +distinguish a task by any of its input variables, you can add those variables as +part of the task's label, and it will be considered a different task. + +This is useful if you want to run a task once for each distinct set of inputs +until the sources actually change. For example, if the sources depend on the +value of a variable, or you if you want the task to rerun if some arguments +change even if the source has not. + +::: + +::: tip + +The method `none` skips any validation and always runs the task. + +::: + +::: info + +For the `checksum` (default) or `timestamp` method to work, it is only necessary +to inform the source files. When the `timestamp` method is used, the last time +of the running the task is considered as a generate. + +::: + +::: tip + +If your globs match files that are ignored by Git (build artifacts, caches, +etc.), you can set `use_gitignore: true` at the root of your Taskfile to +exclude anything matched by `.gitignore` rules from `sources` and `generates` +resolution. The setting can also be enabled or disabled per task, which takes +precedence over the root value. + +::: + +### Using programmatic checks to indicate a task is up to date + +Alternatively, you can inform a sequence of tests as `status`. If no error is +returned (exit status 0), the task is considered up-to-date: + +```yaml +version: '3' + +tasks: + generate-files: + cmds: + - mkdir directory + - touch directory/file1.txt + - touch directory/file2.txt + # test existence of files + status: + - test -d directory + - test -f directory/file1.txt + - test -f directory/file2.txt +``` + +Normally, you would use `sources` in combination with `generates` - but for +tasks that generate remote artifacts (Docker images, deploys, CD releases) the +checksum source and timestamps require either access to the artifact or for an +out-of-band refresh of the `.checksum` fingerprint file. + +Two special variables `{{.CHECKSUM}}` and +`{{.TIMESTAMP}}` are available for interpolation within +`cmds` and `status` commands, depending on the method assigned to fingerprint +the sources. Only `source` globs are fingerprinted. + +Note that the `{{.TIMESTAMP}}` variable is a "live" Go +`time.Time` struct, and can be formatted using any of the methods that +`time.Time` responds to. + +See [the Go Time documentation](https://golang.org/pkg/time/) for more +information. + +You can use `--force` or `-f` if you want to force a task to run even when +up-to-date. + +Also, `task --status [tasks]...` will exit with a non-zero +[exit code](/docs/reference/cli#exit-codes) if any of the tasks are not up-to-date. + +`status` can be combined with the +[fingerprinting](#by-fingerprinting-locally-generated-files-and-their-sources) +to have a task run if either the source/generated artifacts changes, or the +programmatic check fails: + +```yaml +version: '3' + +tasks: + build:prod: + desc: Build for production usage. + cmds: + - composer install + # Run this task if source files changes. + sources: + - composer.json + - composer.lock + generates: + - ./vendor/composer/installed.json + - ./vendor/autoload.php + # But also run the task if the last build was not a production build. + status: + - grep -q '"dev"{{:}} false' ./vendor/composer/installed.json +``` + +### Using programmatic checks to cancel the execution of a task and its dependencies + +In addition to `status` checks, `preconditions` checks are the logical inverse +of `status` checks. That is, if you need a certain set of conditions to be +_true_ you can use the `preconditions` stanza. `preconditions` are similar to +`status` lines, except they support `sh` expansion, and they SHOULD all +return 0. + +```yaml +version: '3' + +tasks: + generate-files: + cmds: + - mkdir directory + - touch directory/file1.txt + - touch directory/file2.txt + # test existence of files + preconditions: + - test -f .env + - sh: '[ 1 = 0 ]' + msg: "One doesn't equal Zero, Halting" +``` + +Preconditions can set specific failure messages that can tell a user what steps +to take using the `msg` field. + +If a task has a dependency on a sub-task with a precondition, and that +precondition is not met - the calling task will fail. Note that a task executed +with a failing precondition will not run unless `--force` is given. + +Unlike `status`, which will skip a task if it is up to date and continue +executing tasks that depend on it, a `precondition` will fail a task, along with +any other tasks that depend on it. + +```yaml +version: '3' + +tasks: + task-will-fail: + preconditions: + - sh: 'exit 1' + + task-will-also-fail: + deps: + - task-will-fail + + task-will-still-fail: + cmds: + - task: task-will-fail + - echo "I will not run" +``` + +### Conditional execution with `if` + +The `if` attribute allows you to conditionally skip tasks or commands based on a +shell command's exit code. Unlike `preconditions` which fail and stop execution, +`if` simply skips the task or command when the condition is not met and continues +with the rest of the Taskfile. + +#### Task-level `if` + +When `if` is set on a task, the entire task is skipped if the condition fails: + +```yaml +version: '3' + +tasks: + deploy: + if: '[ "$CI" = "true" ]' + cmds: + - echo "Deploying..." + - ./deploy.sh +``` + +#### Command-level `if` + +When `if` is set on a command, only that specific command is skipped: + +```yaml +version: '3' + +tasks: + build: + cmds: + - cmd: echo "Building for production" + if: '[ "$ENV" = "production" ]' + - cmd: echo "Building for development" + if: '[ "$ENV" = "development" ]' + - go build ./... +``` + +#### Using templates in `if` conditions + +You can use Go template expressions in `if` conditions. Template expressions like +`{{eq .VAR "value"}}` evaluate to `true` or `false`, which are valid shell +commands (`true` exits with 0, `false` exits with 1): + +```yaml +version: '3' + +tasks: + conditional: + vars: + ENABLE_FEATURE: "true" + cmds: + - cmd: echo "Feature is enabled" + if: '{{eq .ENABLE_FEATURE "true"}}' + - cmd: echo "Feature is disabled" + if: '{{ne .ENABLE_FEATURE "true"}}' +``` + +#### Using `if` with `for` loops + +When used inside a `for` loop, the `if` condition is evaluated for each iteration: + +```yaml +version: '3' + +tasks: + process-items: + cmds: + - for: ['a', 'b', 'c'] + cmd: echo "processing {{.ITEM}}" + if: '[ "{{.ITEM}}" != "b" ]' +``` + +This will output: + +``` +processing a +processing c +``` + +#### `if` vs `preconditions` + +| Aspect | `if` | `preconditions` | +|--------|------|-----------------| +| On failure | Skips (continues) | Fails (stops) | +| Message | Only in verbose mode | Always shown | +| Use case | "Run if possible" | "Must be true" | + +Use `if` when you want optional conditional execution that shouldn't stop the +workflow. Use `preconditions` when the condition must be met for the task to +make sense. + +### Limiting when tasks run + +If a task executed by multiple `cmds` or multiple `deps` you can control when it +is executed using `run`. `run` can also be set at the root of the Taskfile to +change the behavior of all the tasks unless explicitly overridden. + +Supported values for `run`: + +- `always` (default) always attempt to invoke the task regardless of the number + of previous executions +- `once` only invoke this task once regardless of the number of references +- `when_changed` only invokes the task once for each unique set of variables + passed into the task + +```yaml +version: '3' + +tasks: + default: + cmds: + - task: generate-file + vars: { CONTENT: '1' } + - task: generate-file + vars: { CONTENT: '2' } + - task: generate-file + vars: { CONTENT: '2' } + + generate-file: + run: when_changed + deps: + - install-deps + cmds: + - echo {{.CONTENT}} + + install-deps: + run: once + cmds: + - sleep 5 # long operation like installing packages +``` + +### Ensuring required variables are set + +If you want to check that certain variables are set before running a task then +you can use `requires`. This is useful when might not be clear to users which +variables are needed, or if you want clear message about what is required. Also +some tasks could have dangerous side effects if run with un-set variables. + +Using `requires` you specify an array of strings in the `vars` sub-section under +`requires`, these strings are variable names which are checked prior to running +the task. If any variables are un-set then the task will error and not run. + +Environmental variables are also checked. + +Syntax: + +```yaml +requires: + vars: [] # Array of strings +``` + +::: info + +Variables set to empty zero length strings, will pass the `requires` check. + +::: + +Example of using `requires`: + +```yaml +version: '3' + +tasks: + docker-build: + cmds: + - 'docker build . -t {{.IMAGE_NAME}}:{{.IMAGE_TAG}}' + + # Make sure these variables are set before running + requires: + vars: [IMAGE_NAME, IMAGE_TAG] +``` + +### Ensuring required variables have allowed values + +If you want to ensure that a variable is set to one of a predefined set of valid +values before executing a task, you can use requires. This is particularly +useful when there are strict requirements for what values a variable can take, +and you want to provide clear feedback to the user when an invalid value is +detected. + +To use `requires`, you specify an array of allowed values in the vars +sub-section under requires. Task will check if the variable is set to one of the +allowed values. If the variable does not match any of these values, the task +will raise an error and stop execution. + +This check applies both to user-defined variables and environment variables. + +Example of using `requires`: + +```yaml +version: '3' + +tasks: + deploy: + cmds: + - echo "deploying to {{.ENV}}" + + requires: + vars: + - name: ENV + enum: [dev, beta, prod] +``` + +If `ENV` is not one of 'dev', 'beta' or 'prod' an error will be raised. + +::: info + +This is supported only for string variables. + +::: + +### Using variable references for enum values + +Instead of hardcoding enum values, you can reference a variable containing the +allowed values. This is useful when you want to define allowed values once and +reuse them, or when the values are computed dynamically. + +Use the `ref` key to reference a variable: + +```yaml +version: '3' + +vars: + ALLOWED_ENVS: [dev, staging, prod] + +tasks: + deploy: + requires: + vars: + - name: ENV + enum: + ref: .ALLOWED_ENVS + cmds: + - echo "Deploying to {{.ENV}}" +``` + +You can also use template expressions to transform the value: + +```yaml +version: '3' + +vars: + CONFIG: + sh: cat config.json + +tasks: + deploy: + requires: + vars: + - name: ENV + enum: + ref: ( .CONFIG | fromJson ).allowed_environments + cmds: + - echo "Deploying to {{.ENV}}" +``` + +Or generate values dynamically from a shell command: + +```yaml +version: '3' + +vars: + AVAILABLE_SERVICES: + sh: ls services/ + +tasks: + deploy: + requires: + vars: + - name: SERVICE + enum: + ref: .AVAILABLE_SERVICES | splitLines | compact + cmds: + - echo "Deploying {{.SERVICE}}" +``` + +### Prompting for missing variables interactively + +If you want Task to prompt users for missing required variables instead of +failing, you can enable interactive mode in your `.taskrc.yml`: + +```yaml +# ~/.taskrc.yml +interactive: true +``` + +When enabled, Task will display an interactive prompt for any missing required +variable. For variables with an `enum`, a selection menu is shown. For variables +without an enum, a text input is displayed. + +```yaml +# Taskfile.yml +version: '3' + +tasks: + deploy: + requires: + vars: + - name: ENVIRONMENT + enum: [dev, staging, prod] + - VERSION + cmds: + - echo "Deploying {{.VERSION}} to {{.ENVIRONMENT}}" +``` + +```shell +$ task deploy +? Select value for ENVIRONMENT: +❯ dev + staging + prod +? Enter value for VERSION: 1.0.0 +Deploying 1.0.0 to prod +``` + +If the variable is already set (via CLI, environment, or Taskfile), no prompt +is shown: + +```shell +$ task deploy ENVIRONMENT=prod VERSION=1.0.0 +Deploying 1.0.0 to prod +``` + +::: info + +Interactive prompts require a TTY (terminal). Task automatically detects +non-interactive environments like GitHub Actions, GitLab CI, and other CI +pipelines where stdin/stdout are not connected to a terminal. In these cases, +prompts are skipped and missing variables will cause an error as usual. + +You can enable prompts from the command line with `--interactive` or by setting +`interactive: true` in your `.taskrc.yml`. + +::: + +## Variables + +Task allows you to set variables using the `vars` keyword. The following +variable types are supported: + +- `string` +- `bool` +- `int` +- `float` +- `array` +- `map` + +::: info + +Defining a map requires that you use a special `map` subkey (see example below). + +::: + +```yaml +version: 3 + +tasks: + foo: + vars: + STRING: 'Hello, World!' + BOOL: true + INT: 42 + FLOAT: 3.14 + ARRAY: [1, 2, 3] + MAP: + map: { A: 1, B: 2, C: 3 } + cmds: + - 'echo {{.STRING}}' # Hello, World! + - 'echo {{.BOOL}}' # true + - 'echo {{.INT}}' # 42 + - 'echo {{.FLOAT}}' # 3.14 + - 'echo {{.ARRAY}}' # [1 2 3] + - 'echo {{index .ARRAY 0}}' # 1 + - 'echo {{.MAP}}' # map[A:1 B:2 C:3] + - 'echo {{.MAP.A}}' # 1 +``` + +Variables can be set in many places in a Taskfile. When executing +[templates][templating-reference], Task will look for variables in the order +listed below (most important first): + +- Variables declared in the task definition +- Variables given while calling a task from another (See + [Calling another task](#calling-another-task) above) +- Variables of the [included Taskfile](#including-other-taskfiles) (when the + task is included) +- Variables of the [inclusion of the Taskfile](#vars-of-included-taskfiles) + (when the task is included) +- Global variables (those declared in the `vars:` option in the Taskfile) +- Environment variables + +Example of sending parameters with environment variables: + +```shell +$ TASK_VARIABLE=a-value task do-something +``` + +::: tip + +A special variable `.TASK` is always available containing the task name. + +::: + +Since some shells do not support the above syntax to set environment variables +(Windows) tasks also accept a similar style when not at the beginning of the +command. + +```shell +$ task write-file FILE=file.txt "CONTENT=Hello, World!" print "MESSAGE=All done!" +``` + +Example of locally declared vars: + +```yaml +version: '3' + +tasks: + print-var: + cmds: + - echo "{{.VAR}}" + vars: + VAR: Hello! +``` + +Example of global vars in a `Taskfile.yml`: + +```yaml +version: '3' + +vars: + GREETING: Hello from Taskfile! + +tasks: + greet: + cmds: + - echo "{{.GREETING}}" +``` + +Example of a `default` value to be overridden from CLI: + +```yaml +version: '3' + +tasks: + greet_user: + desc: 'Greet the user with a name.' + vars: + USER_NAME: '{{.USER_NAME| default "DefaultUser"}}' + cmds: + - echo "Hello, {{.USER_NAME}}!" +``` + +```shell +$ task greet_user +task: [greet_user] echo "Hello, DefaultUser!" +Hello, DefaultUser! +$ task greet_user USER_NAME="Bob" +task: [greet_user] echo "Hello, Bob!" +Hello, Bob! +``` + +### Dynamic variables + +The below syntax (`sh:` prop in a variable) is considered a dynamic variable. +The value will be treated as a command and the output assigned. If there are one +or more trailing newlines, the last newline will be trimmed. + +```yaml +version: '3' + +tasks: + build: + cmds: + - go build -ldflags="-X main.Version={{.GIT_COMMIT}}" main.go + vars: + GIT_COMMIT: + sh: git log -n 1 --format=%h +``` + +This works for all types of variables. + +### Referencing other variables + +Templating is great for referencing string values if you want to pass a value +from one task to another. However, the templating engine is only able to output +strings. If you want to pass something other than a string to another task then +you will need to use a reference (`ref`) instead. + +::: code-group + +```yaml [Templating Engine] +version: 3 + +tasks: + foo: + vars: + FOO: [A, B, C] # <-- FOO is defined as an array + cmds: + - task: bar + vars: + FOO: '{{.FOO}}' # <-- FOO gets converted to a string when passed to bar + bar: + cmds: + - 'echo {{index .FOO 0}}' # <-- FOO is a string so the task outputs '91' which is the ASCII code for '[' instead of the expected 'A' +``` + +```yaml [Reference] +version: 3 + +tasks: + foo: + vars: + FOO: [A, B, C] # <-- FOO is defined as an array + cmds: + - task: bar + vars: + FOO: + ref: .FOO # <-- FOO gets passed by reference to bar and maintains its type + bar: + cmds: + - 'echo {{index .FOO 0}}' # <-- FOO is still a map so the task outputs 'A' as expected +``` + +::: + +This also works the same way when calling `deps` and when defining a variable +and can be used in any combination: + +```yaml +version: 3 + +tasks: + foo: + vars: + FOO: [A, B, C] # <-- FOO is defined as an array + BAR: + ref: .FOO # <-- BAR is defined as a reference to FOO + deps: + - task: bar + vars: + BAR: + ref: .BAR # <-- BAR gets passed by reference to bar and maintains its type + bar: + cmds: + - 'echo {{index .BAR 0}}' # <-- BAR still refers to FOO so the task outputs 'A' +``` + +All references use the same templating syntax as regular templates, so in +addition to calling `.FOO`, you can also pass subkeys (`.FOO.BAR`) or indexes +(`index .FOO 0`) and use functions (`len .FOO`) as described in the +[templating-reference][templating-reference]: + +```yaml +version: 3 + +tasks: + foo: + vars: + FOO: [A, B, C] # <-- FOO is defined as an array + cmds: + - task: bar + vars: + FOO: + ref: index .FOO 0 # <-- The element at index 0 is passed by reference to bar + bar: + cmds: + - 'echo {{.FOO}}' # <-- FOO is just the letter 'A' +``` + +### Parsing JSON/YAML into map variables + +If you have a raw JSON or YAML string that you want to process in Task, you can +use a combination of the `ref` keyword and the `fromJson` or `fromYaml` +templating functions to parse the string into a map variable. For example: + +```yaml +version: '3' + +tasks: + task-with-map: + vars: + JSON: '{"a": 1, "b": 2, "c": 3}' + FOO: + ref: 'fromJson .JSON' + cmds: + - echo {{.FOO}} +``` + +```txt +map[a:1 b:2 c:3] +``` + +### Secret variables + +Task supports marking variables as `secret` to prevent their values from being +displayed in command logs. When a variable is marked as secret, its value will +be replaced with `*****` in the task output logs. + +::: warning + +**Security Notice**: This feature helps prevent accidental exposure of secrets +in logs, but is **not a substitute** for proper secret management practices. + +**What this protects:** + +- ✅ Secret values in console/terminal logs +- ✅ Secret values in CI/CD logs +- ✅ Accidental copy-paste of logs containing secrets + +**What this does NOT protect:** + +- ❌ Secrets visible in process inspection (e.g., `ps aux`) +- ❌ Secrets in shell history +- ❌ Secrets in command output (stdout/stderr) +- ❌ Secret values copied into derived (non-secret) variables + +Always use proper secret management tools (HashiCorp Vault, AWS Secrets +Manager, etc.) for production environments. + +::: + +To mark a variable as secret, add `secret: true` to the variable definition: + +```yaml +version: '3' + +vars: + API_KEY: + value: 'sk-1234567890abcdef' + secret: true + +tasks: + deploy: + cmds: + - curl -H "Authorization: {{.API_KEY}}" api.example.com + # Logged as: task: [deploy] curl -H "Authorization: *****" api.example.com +``` + +Secret variables work with all variable types: + +::: code-group + +```yaml [Simple Value] +version: '3' + +vars: + PASSWORD: + value: 'my-secret-password' + secret: true + +tasks: + connect: + cmds: + - psql -U user -p {{.PASSWORD}} mydb + # Logged as: psql -U user -p ***** mydb +``` + +```yaml [Shell Command] +version: '3' + +vars: + DB_PASSWORD: + sh: vault read -field=password secret/db + secret: true + +tasks: + migrate: + cmds: + - psql -U admin -p {{.DB_PASSWORD}} mydb + # Password from vault is masked in logs +``` + +```yaml [Task-Level Secret] +version: '3' + +vars: + PUBLIC_URL: https://example.com + +tasks: + deploy: + vars: + DEPLOY_TOKEN: + value: 'secret-token-123' + secret: true + cmds: + - echo "Deploying to {{.PUBLIC_URL}} with token {{.DEPLOY_TOKEN}}" + # Logged as: echo "Deploying to https://example.com with token *****" +``` + +::: + +Multiple secrets in the same command are all masked: + +```yaml +version: '3' + +vars: + API_KEY: + value: 'api-key-123' + secret: true + PASSWORD: + value: 'password-456' + secret: true + +tasks: + setup: + cmds: + - ./setup.sh --api {{.API_KEY}} --pwd {{.PASSWORD}} + # Logged as: ./setup.sh --api ***** --pwd ***** +``` + +::: tip + +**Best practices for secret variables:** + +1. **Use shell commands to load secrets**, not hardcoded values: + + ```yaml + # ❌ BAD - Secret visible in Taskfile + vars: + API_KEY: + value: 'hardcoded-secret' + secret: true + + # ✅ GOOD - Secret loaded from external source + vars: + API_KEY: + sh: vault kv get -field=api_key secret/myapp + secret: true + ``` + +2. **Combine with environment variables:** + + ```yaml + vars: + API_KEY: + sh: echo $MY_API_KEY + secret: true + ``` + +3. **Use .gitignore for secret files:** + + If you use dotenv files, add them to `.gitignore`: + + ```yaml + dotenv: ['.env.local'] # Load from .env.local (in .gitignore) + ``` + +::: + +::: warning + +**Secrets are not propagated to derived variables.** The `secret` flag only +masks the variable it is set on. A non-secret variable that references a secret +will expose the resolved value in logs: + +```yaml +version: '3' + +vars: + API_KEY: + value: 'secret-api-key-123' + secret: true + HEADER: + value: 'Bearer {{.API_KEY}}' # ❌ not marked as secret + +tasks: + call: + cmds: + - curl -H "{{.HEADER}}" api.example.com + # Logged as: curl -H "Bearer secret-api-key-123" api.example.com (LEAK) +``` + +Mark every variable that carries a secret value as `secret: true`: + +```yaml +vars: + HEADER: + value: 'Bearer {{.API_KEY}}' + secret: true # ✅ masked +``` + +::: + +## Looping over values + +Task allows you to loop over certain values and execute a command for each. +There are a number of ways to do this depending on the type of value you want to +loop over. + +### Looping over a static list + +The simplest kind of loop is an explicit one. This is useful when you want to +loop over a set of values that are known ahead of time. + +```yaml +version: '3' + +tasks: + default: + cmds: + - for: ['foo.txt', 'bar.txt'] + cmd: cat {{ .ITEM }} +``` + +### Looping over a matrix + +If you need to loop over all permutations of multiple lists, you can use the +`matrix` property. This should be familiar to anyone who has used a matrix in a +CI/CD pipeline. + +```yaml +version: '3' + +tasks: + default: + silent: true + cmds: + - for: + matrix: + OS: ['windows', 'linux', 'darwin'] + ARCH: ['amd64', 'arm64'] + cmd: + echo "{{.ITEM.OS}}/{{.ITEM.ARCH}}" +``` + +This will output: + +```txt +windows/amd64 +windows/arm64 +linux/amd64 +linux/arm64 +darwin/amd64 +darwin/arm64 +``` + +You can also use references to other variables as long as they are also lists: + +```yaml +version: '3' + +vars: + OS_VAR: ['windows', 'linux', 'darwin'] + ARCH_VAR: ['amd64', 'arm64'] + +tasks: + default: + cmds: + - for: + matrix: + OS: + ref: .OS_VAR + ARCH: + ref: .ARCH_VAR + cmd: + echo "{{.ITEM.OS}}/{{.ITEM.ARCH}}" +``` + +### Looping over your task's sources or generated files + +You are also able to loop over the sources of your task or the files it +generates: + +::: code-group + +```yaml [Sources] +version: '3' + +tasks: + default: + sources: + - foo.txt + - bar.txt + cmds: + - for: sources + cmd: cat {{ .ITEM }} +``` + +```yaml [Generates] +version: '3' + +tasks: + default: + generates: + - foo.txt + - bar.txt + cmds: + - for: generates + cmd: cat {{ .ITEM }} +``` + +::: + +This will also work if you use globbing syntax in `sources` or `generates`. For +example, if you specify a source for `*.txt`, the loop will iterate over all +files that match that glob. + +Paths will always be returned as paths relative to the task directory. If you +need to convert this to an absolute path, you can use the built-in `joinPath` +function. There are some +[special variables](/docs/reference/templating#special-variables) that you may find +useful for this. + +::: code-group + +```yaml [Sources] +version: '3' + +tasks: + default: + vars: + MY_DIR: /path/to/dir + dir: '{{.MY_DIR}}' + sources: + - foo.txt + - bar.txt + cmds: + - for: sources + cmd: cat {{joinPath .MY_DIR .ITEM}} +``` + +```yaml [Generates] +version: '3' + +tasks: + default: + vars: + MY_DIR: /path/to/dir + dir: '{{.MY_DIR}}' + generates: + - foo.txt + - bar.txt + cmds: + - for: generates + cmd: cat {{joinPath .MY_DIR .ITEM}} +``` + +::: + +### Looping over variables + +To loop over the contents of a variable, use the `var` key followed by the name +of the variable you want to loop over. By default, string variables will be +split on any whitespace characters. + +```yaml +version: '3' + +tasks: + default: + vars: + MY_VAR: foo.txt bar.txt + cmds: + - for: { var: MY_VAR } + cmd: cat {{.ITEM}} +``` + +If you need to split a string on a different character, you can do this by +specifying the `split` property: + +```yaml +version: '3' + +tasks: + default: + vars: + MY_VAR: foo.txt,bar.txt + cmds: + - for: { var: MY_VAR, split: ',' } + cmd: cat {{.ITEM}} +``` + +You can also loop over arrays and maps directly: + +```yaml +version: 3 + +tasks: + foo: + vars: + LIST: [foo, bar, baz] + cmds: + - for: + var: LIST + cmd: echo {{.ITEM}} +``` + +When looping over a map we also make an additional `{{.KEY}}` +variable available that holds the string value of the map key. Remember that +maps are unordered, so the order in which the items are looped over is random. + +All of this also works with dynamic variables! + +```yaml +version: '3' + +tasks: + default: + vars: + MY_VAR: + sh: find -type f -name '*.txt' + cmds: + - for: { var: MY_VAR } + cmd: cat {{.ITEM}} +``` + +### Renaming variables + +If you want to rename the iterator variable to make it clearer what the value +contains, you can do so by specifying the `as` property: + +```yaml +version: '3' + +tasks: + default: + vars: + MY_VAR: foo.txt bar.txt + cmds: + - for: { var: MY_VAR, as: FILE } + cmd: cat {{.FILE}} +``` + +### Looping over tasks + +Because the `for` property is defined at the `cmds` level, you can also use it +alongside the `task` keyword to run tasks multiple times with different +variables. + +```yaml +version: '3' + +tasks: + default: + cmds: + - for: [foo, bar] + task: my-task + vars: + FILE: '{{.ITEM}}' + + my-task: + cmds: + - echo '{{.FILE}}' +``` + +Or if you want to run different tasks depending on the value of the loop: + +```yaml +version: '3' + +tasks: + default: + cmds: + - for: [foo, bar] + task: task-{{.ITEM}} + + task-foo: + cmds: + - echo 'foo' + + task-bar: + cmds: + - echo 'bar' +``` + +### Looping over dependencies + +All of the above looping techniques can also be applied to the `deps` property. +This allows you to combine loops with concurrency: + +```yaml +version: '3' + +tasks: + default: + deps: + - for: [foo, bar] + task: my-task + vars: + FILE: '{{.ITEM}}' + + my-task: + cmds: + - echo '{{.FILE}}' +``` + +It is important to note that as `deps` are run in parallel, the order in which +the iterations are run is not guaranteed and the output may vary. For example, +the output of the above example may be either: + +```shell +foo +bar +``` + +or + +```shell +bar +foo +``` + +## Forwarding CLI arguments to commands + +If `--` is given in the CLI, all following parameters are added to a special +`.CLI_ARGS` variable. This is useful to forward arguments to another command. + +The below example will run `yarn install`. + +```shell +$ task yarn -- install +``` + +```yaml +version: '3' + +tasks: + yarn: + cmds: + - yarn {{.CLI_ARGS}} +``` + +## Wildcard arguments + +Another way to parse arguments into a task is to use a wildcard in your task's +name. Wildcards are denoted by an asterisk (`*`) and can be used multiple times +in a task's name to pass in multiple arguments. + +Matching arguments will be captured and stored in the `.MATCH` variable and can +then be used in your task's commands like any other variable. This variable is +an array of strings and so will need to be indexed to access the individual +arguments. We suggest creating a named variable for each argument to make it +clear what they contain: + +```yaml +version: '3' + +tasks: + start:*:*: + vars: + SERVICE: '{{index .MATCH 0}}' + REPLICAS: '{{index .MATCH 1}}' + cmds: + - echo "Starting {{.SERVICE}} with {{.REPLICAS}} replicas" + + start:*: + vars: + SERVICE: '{{index .MATCH 0}}' + cmds: + - echo "Starting {{.SERVICE}}" +``` + +This call matches the `start:*` task and the string "foo" is captured by the +wildcard and stored in the `.MATCH` variable. We then index the `.MATCH` array +and store the result in the `.SERVICE` variable which is then echoed out in the +cmds: + +```shell +$ task start:foo +Starting foo +``` + +You can use whitespace in your arguments as long as you quote the task name: + +```shell +$ task "start:foo bar" +Starting foo bar +``` + +If multiple matching tasks are found, the first one listed in the Taskfile will +be used. If you are using included Taskfiles, tasks in parent files will be +considered first. + +```shell +$ task start:foo:3 +Starting foo with 3 replicas +``` + +Using wildcards with aliases +Wildcards also work with aliases. If a task has an alias, you can use the alias name with wildcards to capture arguments. For example: + +```yaml +version: '3' + +tasks: + start:*: + aliases: [run:*] + vars: + SERVICE: "{{index .MATCH 0}}" + cmds: + - echo "Running {{.SERVICE}}" +``` +In this example, you can call the task using the alias run:*: + +```shell +$ task run:foo +Running foo +``` + +## Doing task cleanup with `defer` + +With the `defer` keyword, it's possible to schedule cleanup to be run once the +task finishes. The difference with just putting it as the last command is that +this command will run even when the task fails. + +In the example below, `rm -rf tmpdir/` will run even if the third command fails: + +```yaml +version: '3' + +tasks: + default: + cmds: + - mkdir -p tmpdir/ + - defer: rm -rf tmpdir/ + - echo 'Do work on tmpdir/' +``` + +If you want to move the cleanup command into another task, that is possible as +well: + +```yaml +version: '3' + +tasks: + default: + cmds: + - mkdir -p tmpdir/ + - defer: { task: cleanup } + - echo 'Do work on tmpdir/' + + cleanup: rm -rf tmpdir/ +``` + +::: info + +Due to the nature of how the +[Go's own `defer` work](https://go.dev/tour/flowcontrol/13), the deferred +commands are executed in the reverse order if you schedule multiple of them. + +::: + +A special variable `.EXIT_CODE` is exposed when a command exited with a non-zero +[exit code](/docs/reference/cli#exit-codes). You can check its presence to know if +the task completed successfully or not: + +```yaml +version: '3' + +tasks: + default: + cmds: + - defer: + echo '{{if .EXIT_CODE}}Failed with {{.EXIT_CODE}}!{{else}}Success!{{end}}' + - exit 1 +``` + +## Help + +Running `task --list` (or `task -l`) lists all tasks with a description. The +following Taskfile: + +```yaml +version: '3' + +tasks: + build: + desc: Build the go binary. + cmds: + - go build -v -i main.go + + test: + desc: Run all the go tests. + cmds: + - go test -race ./... + + js: + cmds: + - esbuild --bundle --minify js/index.js > public/bundle.js + + css: + cmds: + - esbuild --bundle --minify css/index.css > public/bundle.css +``` + +would print the following output: + +```shell +* build: Build the go binary. +* test: Run all the go tests. +``` + +If you want to see all tasks, there's a `--list-all` (alias `-a`) flag as well. + +## Display summary of task + +Running `task --summary task-name` will show a summary of a task. The following +Taskfile: + +```yaml +version: '3' + +tasks: + release: + deps: [build] + summary: | + Release your project to github + + It will build your project before starting the release. + Please make sure that you have set GITHUB_TOKEN before starting. + cmds: + - your-release-tool + + build: + cmds: + - your-build-tool +``` + +with running `task --summary release` would print the following output: + +``` +task: release + +Release your project to github + +It will build your project before starting the release. +Please make sure that you have set GITHUB_TOKEN before starting. + +dependencies: + - build + +commands: + - your-release-tool +``` + +If a summary is missing, the description will be printed. If the task does not +have a summary or a description, a warning is printed. + +Please note: _showing the summary will not execute the command_. + +## Task aliases + +Aliases are alternative names for tasks. They can be used to make it easier and +quicker to run tasks with long or hard-to-type names. You can use them on the +command line, when [calling sub-tasks](#calling-another-task) in your Taskfile +and when [including tasks](#including-other-taskfiles) with aliases from another +Taskfile. They can also be used together with +[namespace aliases](#namespace-aliases). + +```yaml +version: '3' + +tasks: + generate: + aliases: [gen] + cmds: + - task: gen-mocks + + generate-mocks: + aliases: [gen-mocks] + cmds: + - echo "generating..." +``` + +## Overriding task name + +Sometimes you may want to override the task name printed on the summary, +up-to-date messages to STDOUT, etc. In this case, you can just set `label:`, +which can also be interpolated with variables: + +```yaml +version: '3' + +tasks: + default: + cmds: + - task: print + vars: + MESSAGE: hello + - task: print + vars: + MESSAGE: world + + print: + label: 'print-{{.MESSAGE}}' + cmds: + - echo "{{.MESSAGE}}" +``` + +## Warning Prompts + +Warning Prompts are used to prompt a user for confirmation before a task is +executed. + +Below is an example using `prompt` with a dangerous command, that is called +between two safe commands: + +```yaml +version: '3' + +tasks: + example: + cmds: + - task: not-dangerous + - task: dangerous + - task: another-not-dangerous + + not-dangerous: + cmds: + - echo 'not dangerous command' + + another-not-dangerous: + cmds: + - echo 'another not dangerous command' + + dangerous: + prompt: This is a dangerous command... Do you want to continue? + cmds: + - echo 'dangerous command' +``` + +```shell +❯ task dangerous +task: "This is a dangerous command... Do you want to continue?" [y/N] +``` + +Prompts can be a single value or a list of prompts, like below: + +```yaml +version: '3' + +tasks: + example: + cmds: + - task: dangerous + + dangerous: + prompt: + - This is a dangerous command... Do you want to continue? + - Are you sure? + cmds: + - echo 'dangerous command' +``` + +Warning prompts are called before executing a task. If a prompt is denied Task +will exit with [exit code](/docs/reference/cli#exit-codes) 205. If approved, Task +will continue as normal. + +```shell +❯ task example +not dangerous command +task: "This is a dangerous command. Do you want to continue?" [y/N] +y +dangerous command +another not dangerous command +``` + +To skip warning prompts automatically, you can use the `--yes` (alias `-y`) +option when calling the task. By including this option, all warnings, will be +automatically confirmed, and no prompts will be shown. + +::: warning + +Tasks with prompts always fail by default on non-terminal environments, like a +CI, where an `stdin` won't be available for the user to answer. In those cases, +use `--yes` (`-y`) to force all tasks with a prompt to run. + +::: + +## Silent mode + +Silent mode disables the echoing of commands before Task runs it. For the +following Taskfile: + +```yaml +version: '3' + +tasks: + echo: + cmds: + - echo "Print something" +``` + +Normally this will be printed: + +```shell +echo "Print something" +Print something +``` + +With silent mode on, the below will be printed instead: + +```shell +Print something +``` + +There are four ways to enable silent mode: + +- At command level: + +```yaml +version: '3' + +tasks: + echo: + cmds: + - cmd: echo "Print something" + silent: true +``` + +- At task level: + +```yaml +version: '3' + +tasks: + echo: + cmds: + - echo "Print something" + silent: true +``` + +- Globally at Taskfile level: + +```yaml +version: '3' + +silent: true + +tasks: + echo: + cmds: + - echo "Print something" +``` + +- Or globally with `--silent` or `-s` flag + +If you want to suppress STDOUT instead, just redirect a command to `/dev/null`: + +```yaml +version: '3' + +tasks: + echo: + cmds: + - echo "This will print nothing" > /dev/null +``` + +## Dry run mode + +Dry run mode (`--dry`) compiles and steps through each task, printing the +commands that would be run without executing them. This is useful for debugging +your Taskfiles. + +## Ignore errors + +You have the option to ignore errors during command execution. Given the +following Taskfile: + +```yaml +version: '3' + +tasks: + echo: + cmds: + - exit 1 + - echo "Hello World" +``` + +Task will abort the execution after running `exit 1` because the status code `1` +stands for `EXIT_FAILURE`. However, it is possible to continue with execution +using `ignore_error`: + +```yaml +version: '3' + +tasks: + echo: + cmds: + - cmd: exit 1 + ignore_error: true + - echo "Hello World" +``` + +`ignore_error` can also be set for a task, which means errors will be suppressed +for all commands. Nevertheless, keep in mind that this option will not propagate +to other tasks called either by `deps` or `cmds`! + +## Output syntax + +By default, Task just redirects the STDOUT and STDERR of the running commands to +the shell in real-time. This is good for having live feedback for logging +printed by commands, but the output can become messy if you have multiple +commands running simultaneously and printing lots of stuff. + +To make this more customizable, there are currently three different output +options you can choose: + +- `interleaved` (default) +- `group` +- `prefixed` + +To choose another one, just set it to root in the Taskfile: + +```yaml +version: '3' + +output: 'group' + +tasks: + # ... +``` + +The `group` output will print the entire output of a command once after it +finishes, so you will not have live feedback for commands that take a long time +to run. + +When using the `group` output, you can optionally provide a templated message to +print at the start and end of the group. This can be useful for instructing CI +systems to group all of the output for a given task, such as with +[GitHub Actions' `::group::` command](https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions#grouping-log-lines) +or +[Azure Pipelines](https://docs.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands?expand=1&view=azure-devops&tabs=bash#formatting-commands). + +```yaml +version: '3' + +output: + group: + begin: '::group::{{.TASK}}' + end: '::endgroup::' + +tasks: + default: + cmds: + - echo 'Hello, World!' + silent: true +``` + +```shell +$ task default +::group::default +Hello, World! +::endgroup:: +``` + +When using the `group` output, you may swallow the output of the executed +command on standard output and standard error if it does not fail (zero exit +code). + +```yaml +version: '3' + +silent: true + +output: + group: + error_only: true + +tasks: + passes: echo 'output-of-passes' + errors: echo 'output-of-errors' && exit 1 +``` + +```shell +$ task passes +$ task errors +output-of-errors +task: Failed to run task "errors": exit status 1 +``` + +The `prefix` output will prefix every line printed by a command with +`[task-name] ` as the prefix, but you can customize the prefix for a command +with the `prefix:` attribute: + +```yaml +version: '3' + +output: prefixed + +tasks: + default: + deps: + - task: print + vars: { TEXT: foo } + - task: print + vars: { TEXT: bar } + - task: print + vars: { TEXT: baz } + + print: + cmds: + - echo "{{.TEXT}}" + prefix: 'print-{{.TEXT}}' + silent: true +``` + +```shell +$ task default +[print-foo] foo +[print-bar] bar +[print-baz] baz +``` + +::: tip + +The `output` option can also be specified by the `--output` or `-o` flags. + +::: + +## CI Integration + +### Colored output + +Task automatically enables colored output when running in CI environments +(`CI=true`). Most CI providers set this variable automatically. + +You can also force colored output with `FORCE_COLOR=1` or disable it with +`NO_COLOR=1`. + +### Error annotations + +When running in GitHub Actions (`GITHUB_ACTIONS=true`), Task automatically emits +error annotations when a task fails. These annotations appear in the workflow +summary, making it easier to spot failures without scrolling through logs. + +```shell +::error title=Task 'build' failed::exit status 1 +``` + +This feature requires no configuration and works automatically. + +## Interactive CLI application + +When running interactive CLI applications inside Task they can sometimes behave +weirdly, especially when the [output mode](#output-syntax) is set to something +other than `interleaved` (the default), or when interactive apps are run in +parallel with other tasks. + +The `interactive: true` tells Task this is an interactive application and Task +will try to optimize for it: + +```yaml +version: '3' + +tasks: + default: + cmds: + - vim my-file.txt + interactive: true +``` + +If you still have problems running an interactive app through Task, please open +an issue about it. + +## Short task syntax + +Starting on Task v3, you can now write tasks with a shorter syntax if they have +the default settings (e.g. no custom `env:`, `vars:`, `desc:`, `silent:` , etc): + +```yaml +version: '3' + +tasks: + build: go build -v -o ./app{{exeExt}} . + + run: + - task: build + - ./app{{exeExt}} -h localhost -p 8080 +``` + +## `set` and `shopt` + +It's possible to specify options to the +[`set`](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html) +and +[`shopt`](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html) +builtins. This can be added at global, task or command level. + +```yaml +version: '3' + +set: [pipefail] +shopt: [globstar] + +tasks: + # `globstar` required for double star globs to work + default: echo **/*.go +``` + +::: info + +Keep in mind that not all options are available in the +[shell interpreter library](https://github.com/mvdan/sh) that Task uses. + +::: + +## Watch tasks + +With the flags `--watch` or `-w` task will watch for file changes and run the +task again. This requires the `sources` attribute to be given, so task knows +which files to watch. + +The default watch interval is 100 milliseconds, but it's possible to change it +by either setting `interval: '500ms'` in the root of the Taskfile or by passing +it as an argument like `--interval=500ms`. This interval is the time Task will +wait for duplicated events. It will only run the task again once, even if +multiple changes happen within the interval. + +Also, it's possible to set `watch: true` in a given task and it'll automatically +run in watch mode: + +```yaml +version: '3' + +interval: 500ms + +tasks: + build: + desc: Builds the Go application + watch: true + sources: + - '**/*.go' + cmds: + - go build # ... +``` + +::: info + +Note that when setting `watch: true` to a task, it'll only run in watch mode +when running from the CLI via `task my-watch-task`, but won't run in watch mode +if called by another task, either directly or as a dependency. + +::: + +::: warning + +The watcher can misbehave in certain scenarios, in particular for long-running +servers. There is a [known bug](https://github.com/go-task/task/issues/160) +where child processes of the running might not be killed appropriately. It's +advised to avoid running commands as `go run` and prefer `go build [...] && +./binary` instead. + +If you are having issues, you might want to try tools specifically designed for +live-reloading, like [Air](https://github.com/air-verse/air/). Also, be sure to +[report any issues](https://github.com/go-task/task/issues/new?template=bug_report.yml) +to us. + +::: + +[config]: /docs/reference/config +[gotemplate]: https://golang.org/pkg/text/template/ +[templating-reference]: /docs/reference/templating diff --git a/website/src/latest/docs/installation.md b/website/src/latest/docs/installation.md new file mode 100644 index 00000000..a983dc0e --- /dev/null +++ b/website/src/latest/docs/installation.md @@ -0,0 +1,448 @@ +--- +title: Installation +description: Installation methods for Task +outline: deep +--- + +# Installation + +Task offers many installation methods. Check out the available methods below. + +## Official Package Managers + +These installation methods are maintained by the Task team and are always +up-to-date. + +:::info Package Repository Hosting + +[![Hosted By: Cloudsmith](https://img.shields.io/badge/OSS%20hosting%20by-cloudsmith-blue?logo=cloudsmith&style=for-the-badge)](https://cloudsmith.com) + +Package repository hosting for deb/rpm/apk is graciously provided by [Cloudsmith](https://cloudsmith.com). +Cloudsmith is the only fully hosted, cloud-native, universal package management solution, that +enables your organization to create, store and share packages in any format, to any place, with total +confidence. + +::: + +### [dnf](https://docs.fedoraproject.org/en-US/quick-docs/dnf) ![Fedora](https://img.shields.io/badge/Fedora-51A2DA?logo=fedora&logoColor=fff) ![CentOS](https://img.shields.io/badge/CentOS-002260?logo=centos&logoColor=F0F0F0) ![Fedora](https://img.shields.io/badge/Red_Hat-EE0000?logo=redhat&logoColor=white) {#dnf} + +[[package](https://cloudsmith.io/~task/repos/task/packages/?sort=-format&q=format%3Arpm)] + +If you Set up the repository by running : + +```shell +curl -1sLf 'https://dl.cloudsmith.io/public/task/task/setup.rpm.sh' | sudo -E bash +``` + +Then you can install Task with: + +```shell +dnf install task +``` + +### [apt](https://doc.ubuntu-fr.org/apt) ![Ubuntu](https://img.shields.io/badge/Ubuntu-E95420?logo=Ubuntu&logoColor=white) ![Debian](https://img.shields.io/badge/debian-red?logo=debian&logoColor=orange&color=darkred) ![Linux Mint](https://img.shields.io/badge/Linux%20Mint-87CF3E?logo=linuxmint&logoColor=fff) {#apt} + +[[package](https://cloudsmith.io/~task/repos/task/packages/?sort=-format&q=format%3Adeb)] + +If you Set up the repository by running: + +```shell +curl -1sLf 'https://dl.cloudsmith.io/public/task/task/setup.deb.sh' | sudo -E bash +``` + +Then you can install Task with: + +```shell +apt install task +``` + +### [apk](https://wiki.alpinelinux.org/wiki/Alpine_Package_Keeper) ![Alpine Linux](https://img.shields.io/badge/Alpine_Linux-0D597F?logo=alpinelinux&logoColor=fff) {#apk} + +[[package](https://cloudsmith.io/~task/repos/task/packages/?sort=-format&q=format%3Aalpine)] + +Set up the repository by running: + +```shell +curl -1sLf 'https://dl.cloudsmith.io/public/task/task/setup.alpine.sh' | sudo -E bash +``` + +Then you can install Task with: + +```shell +apk add task +``` + +### [Homebrew](https://brew.sh) ![macOS](https://img.shields.io/badge/MacOS-000000?logo=apple&logoColor=F0F0F0) ![Linux](https://img.shields.io/badge/Linux-FCC624?logo=linux&logoColor=black) {#homebrew} + +Task is available via our official Homebrew tap +[[source](https://github.com/go-task/homebrew-tap/blob/main/Formula/go-task.rb)]: + +```shell +brew install go-task/tap/go-task +``` + +Alternatively it can be installed from the official Homebrew repository +[[package](https://formulae.brew.sh/formula/go-task)] +[[source](https://github.com/Homebrew/homebrew-core/blob/master/Formula/g/go-task.rb)] +by running: + +```shell +brew install go-task +``` + +### [Snap](https://snapcraft.io/task) ![macOS](https://img.shields.io/badge/MacOS-000000?logo=apple&logoColor=F0F0F0) ![Linux](https://img.shields.io/badge/Linux-FCC624?logo=linux&logoColor=black) {#snap} + +Task is available on [Snapcraft](https://snapcraft.io/task) +[[source](https://github.com/go-task/snap/blob/main/snap/snapcraft.yaml)], but +keep in mind that your Linux distribution should allow classic confinement for +Snaps to Task work correctly: + +```shell +sudo snap install task --classic +``` + +### [npm](https://www.npmjs.com) ![macOS](https://img.shields.io/badge/MacOS-000000?logo=apple&logoColor=F0F0F0) ![Linux](https://img.shields.io/badge/Linux-FCC624?logo=linux&logoColor=black) ![Windows](https://custom-icon-badges.demolab.com/badge/Windows-0078D6?logo=windows11&logoColor=white) {#npm} + +Npm can be used as cross-platform way to install Task globally or as a +dependency of your project +[[package](https://www.npmjs.com/package/@go-task/cli)] +[[source](https://github.com/go-task/task/blob/main/package.json)]: + +```shell +npm install -g @go-task/cli +``` + +### [WinGet](https://github.com/microsoft/winget-cli) ![Windows](https://custom-icon-badges.demolab.com/badge/Windows-0078D6?logo=windows11&logoColor=white) {#winget} + +Task is available via the +[community repository](https://github.com/microsoft/winget-pkgs) +[[source](https://github.com/microsoft/winget-pkgs/tree/master/manifests/t/Task/Task)]: + +```shell +winget install Task.Task +``` + +## Community-Maintained Package Managers + +::: warning Community Maintained + +These installation methods are maintained by the community and may not always be +up-to-date with the latest Task version. The Task team does not directly control +these packages. + +::: + +### [Mise](https://mise.jdx.dev/) ![macOS](https://img.shields.io/badge/MacOS-000000?logo=apple&logoColor=F0F0F0) ![Linux](https://img.shields.io/badge/Linux-FCC624?logo=linux&logoColor=black) ![Windows](https://custom-icon-badges.demolab.com/badge/Windows-0078D6?logo=windows11&logoColor=white) {#mise} + +Mise is a cross-platform package manager that acts as a "frontend" to a variety +of other package managers "backends" such as `asdf`, `aqua` and `ubi`. + +If using Mise, we recommend using the `aqua` or `ubi` backends to install Task +as these install directly from our GitHub releases. + +::: code-group + +```shell [aqua] +mise use -g aqua:go-task/task@latest +mise install +``` + +```shell [ubi] +mise use -g ubi:go-task/task +mise install +``` + +::: + +### [Macports](https://macports.org) ![macOS](https://img.shields.io/badge/MacOS-000000?logo=apple&logoColor=F0F0F0) {#macports} + +Task repository is tracked by Macports +[[package](https://ports.macports.org/port/go-task/details/)] +[[source](https://github.com/macports/macports-ports/blob/master/devel/go-task/Portfile)]: + +```shell +port install go-task +``` + +### [pip](https://pip.pypa.io) ![macOS](https://img.shields.io/badge/MacOS-000000?logo=apple&logoColor=F0F0F0) ![Linux](https://img.shields.io/badge/Linux-FCC624?logo=linux&logoColor=black) ![Windows](https://custom-icon-badges.demolab.com/badge/Windows-0078D6?logo=windows11&logoColor=white) {#pip} + +Like npm, pip can be used as a cross-platform way to install Task +[[package](https://pypi.org/project/go-task-bin)] +[[source](https://github.com/Bing-su/pip-binary-factory/tree/main/task)]: + +```shell +pip install go-task-bin +``` + +### [Chocolatey](https://chocolatey.org) ![Windows](https://custom-icon-badges.demolab.com/badge/Windows-0078D6?logo=windows11&logoColor=white) {#chocolatey} + +[[package](https://community.chocolatey.org/packages/go-task)] +[[source](https://github.com/Starz0r/ChocolateyPackagingScripts/blob/master/src/go-task_gh_build.py)] + +```shell +choco install go-task +``` + +### [Scoop](https://scoop.sh) ![Windows](https://custom-icon-badges.demolab.com/badge/Windows-0078D6?logo=windows11&logoColor=white) {#scoop} + +[[source](https://github.com/ScoopInstaller/Main/blob/master/bucket/task.json)] + +```shell +scoop install task +``` + +### Arch ([pacman](https://wiki.archlinux.org/title/Pacman)) ![Arch Linux](https://img.shields.io/badge/Arch%20Linux-1793D1?logo=arch-linux&logoColor=fff) {#arch} + +[[package](https://archlinux.org/packages/extra/x86_64/go-task/)] +[[source](https://gitlab.archlinux.org/archlinux/packaging/packages/go-task)] + +```shell +pacman -S go-task +``` + +### Fedora ([dnf](https://docs.fedoraproject.org/en-US/quick-docs/dnf)) ![Fedora](https://img.shields.io/badge/Fedora-51A2DA?logo=fedora&logoColor=fff) {#fedora-community} + +[[package](https://packages.fedoraproject.org/pkgs/golang-github-task/go-task/)] +[[source](https://src.fedoraproject.org/rpms/golang-github-task)] + +```shell +dnf install go-task +``` + +### FreeBSD ([Ports](https://ports.freebsd.org/cgi/ports.cgi)) ![FreeBSD](https://img.shields.io/badge/FreeBSD-990000?logo=freebsd&logoColor=fff) {#freebsd} + +[[package](https://cgit.freebsd.org/ports/tree/devel/task)] +[[source](https://cgit.freebsd.org/ports/tree/devel/task/Makefile)] + +```shell +pkg install task +``` + +### [Nix](https://nixos.org) ![Nix](https://img.shields.io/badge/Nix-5277C3?logo=nixos&logoColor=fff) ![NixOS](https://img.shields.io/badge/NixOS-5277C3?logo=nixos&logoColor=fff) ![Linux](https://img.shields.io/badge/Linux-FCC624?logo=linux&logoColor=black) ![macOS](https://img.shields.io/badge/MacOS-000000?logo=apple&logoColor=F0F0F0) {#nix} + +[[source](https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/go/go-task/package.nix)] + +```shell +nix-env -iA nixpkgs.go-task +``` + +### [pacstall](https://github.com/pacstall/pacstall) ![Debian](https://img.shields.io/badge/Debian-A81D33?logo=debian&logoColor=fff) ![Ubuntu](https://img.shields.io/badge/Ubuntu-E95420?logo=ubuntu&logoColor=fff) {#pacstall} + +[[package](https://pacstall.dev/packages/go-task-deb)] +[[source](https://github.com/pacstall/pacstall-programs/blob/master/packages/go-task-deb/go-task-deb.pacscript)] + +```shell +pacstall -I go-task-deb +``` + +### [pkgx](https://pkgx.sh) ![macOS](https://img.shields.io/badge/MacOS-000000?logo=apple&logoColor=F0F0F0) ![Linux](https://img.shields.io/badge/Linux-FCC624?logo=linux&logoColor=black) {#pkgx} + +[[package](https://pkgx.dev/pkgs/taskfile.dev)] +[[source](https://github.com/pkgxdev/pantry/blob/main/projects/taskfile.dev/package.yml)] + +```shell +pkgx task +``` + +or, if you have pkgx integration enabled: + +```shell +task +``` + +## Get The Binary + +### Binary + +You can download the binary from the +[releases page on GitHub](https://github.com/go-task/task/releases) and add to +your `$PATH`. + +DEB, RPM and APK packages are also available. + +The `task_checksums.txt` file contains the SHA-256 checksum for each file. + +### Install Script + +We also have an +[install script](https://github.com/go-task/task/blob/main/install-task.sh) +which is very useful in scenarios like CI. Many thanks to +[GoDownloader](https://github.com/goreleaser/godownloader) for enabling the easy +generation of this script. + +By default, it installs on the `./bin` directory relative to the working +directory: + +```shell +sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d +``` + +It is possible to override the installation directory with the `-b` parameter. +On Linux, common choices are `~/.local/bin` and `~/bin` to install for the +current user or `/usr/local/bin` to install for all users: + +```shell +sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin +``` + +::: warning + +On macOS and Windows, `~/.local/bin` and `~/bin` are not added to `$PATH` by +default. + +::: + +By default, it installs the latest version available. You can also specify a tag +(available in [releases](https://github.com/go-task/task/releases)) to install a +specific version: + +```shell +sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d v3.36.0 +``` + +Parameters are order specific, to set both installation directory and version: + +```shell +sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin v3.42.1 +``` + +### GitHub Actions + +We have an [official GitHub Action](https://github.com/go-task/setup-task) to +install Task in your GitHub workflows. This repository is forked from the +fantastic project by the Arduino team. Check out the repository for more +examples and configuration. + +```yaml +- name: Install Task + uses: go-task/setup-task@v1 +``` + +## Build From Source + +Ensure that you have a supported version of [Go](https://golang.org) properly +installed and setup. You can find the minimum required version of Go in the +[go.mod](https://github.com/go-task/task/blob/main/go.mod#L3) file. + +You can then install the latest release globally by running: + +```shell +go install github.com/go-task/task/v3/cmd/task@latest +``` + +Or you can install into another directory: + +```shell +env GOBIN=/bin go install github.com/go-task/task/v3/cmd/task@latest +``` + +::: tip + +For CI environments we recommend using the [install script](#install-script) +instead, which is faster and more stable, since it'll just download the latest +released binary. + +::: + +## Go Tool + +If you're working in a Go project, a nice possibility is using `go tool`. +`go tool` makes it easy to run Task without needing to install the binary +manually. This works well on CI. + +To do that, just run the following to add Task as a tool in your Go project. +Task will be added to your `go.mod`. + +```bash +go get -tool github.com/go-task/task/v3/cmd/task@latest +``` + +Then, prefix `go tool` when calling Task like below. Go will compile Task on +demand before calling it. + +```bash +go tool task {arguments...} +``` + +## Setup completions + +Some installation methods will automatically install completions too, but if +this isn't working for you or your chosen method doesn't include them, you can +run `task --completion ` to output a completion script for any supported +shell. There are a couple of ways these completions can be added to your shell +config: + +### Option 1. Load the completions in your shell's startup config (Recommended) + +This method loads the completion script from the currently installed version of +task every time you create a new shell. This ensures that your completions are +always up-to-date. +If your executable isn’t named task, set the `TASK_EXE` environment variable before running eval. + +::: code-group + +```shell [bash] +# ~/.bashrc + +# export TASK_EXE='go-task' if needed +eval "$(task --completion bash)" +``` + +```shell [zsh] +# ~/.zshrc + +# export TASK_EXE='go-task' if needed +eval "$(task --completion zsh)" +``` + +```shell [fish] +# ~/.config/fish/config.fish + +# export TASK_EXE='go-task' if needed +task --completion fish | source +``` + +```powershell [powershell] +# $PROFILE\Microsoft.PowerShell_profile.ps1 +Invoke-Expression (&task --completion powershell | Out-String) +``` + +::: + +### Option 2. Copy the script to your shell's completions directory + +This method requires you to manually update the completions whenever Task is +updated. However, it is useful if you want to modify the completions yourself. + +::: code-group + +```shell [bash] +task --completion bash > /etc/bash_completion.d/task +``` + +```shell [zsh] +task --completion zsh > /usr/local/share/zsh/site-functions/_task +``` + +```shell [fish] +task --completion fish > ~/.config/fish/completions/task.fish +``` + +::: + +### Zsh customization + +The Zsh completion supports the standard `verbose` zstyle to control whether task +descriptions are shown. By default, descriptions are displayed. To show only task +names without descriptions, add this to your `~/.zshrc` (after the completion is loaded): + +```shell +zstyle ':completion:*:*:task:*' verbose false +``` + +By default, task aliases are also offered as completions. To complete only the +canonical task names, add the `show-aliases` zstyle: + +```shell +zstyle ':completion:*:*:task:*' show-aliases false +``` diff --git a/website/src/docs/integrations.md b/website/src/latest/docs/integrations.md similarity index 96% rename from website/src/docs/integrations.md rename to website/src/latest/docs/integrations.md index 2bf5e34e..9d95151d 100644 --- a/website/src/docs/integrations.md +++ b/website/src/latest/docs/integrations.md @@ -36,12 +36,12 @@ In v1.0.0 of the extension, the configuration namespace was changed from `task` to `taskfile` in order to fix [an issue](https://github.com/go-task/vscode-task/issues/56). -![Configuration namespace change warning](../public/img/config-namespace-change.png) +![Configuration namespace change warning](/img/config-namespace-change.png) If you receive a warning like the one above, you will need to update your settings to use the new `taskfile` namespace instead: -![Configuration namespace diff](../public/img/config-namespace-diff.png) +![Configuration namespace diff](/img/config-namespace-diff.png) ## Schema diff --git a/website/src/latest/docs/reference/cli.md b/website/src/latest/docs/reference/cli.md new file mode 100644 index 00000000..47b5c7d1 --- /dev/null +++ b/website/src/latest/docs/reference/cli.md @@ -0,0 +1,445 @@ +--- +title: Command Line Interface Reference +description: Complete reference for Task CLI commands, flags, and exit codes +permalink: /reference/cli/ +outline: deep +--- + +# Command Line Interface Reference + +Task has multiple ways of being configured. These methods are parsed, in +sequence, in the following order with the highest priority last: + +- [Configuration files](./config.md) +- [Environment variables](./environment.md) +- _Command-line flags_ + +In this document, we will look at the last of the three options, command-line +flags. All CLI commands override their configuration file and environment +variable equivalents. + +## Format + +Task commands have the following syntax: + +```bash +task [options] [tasks...] [-- CLI_ARGS...] +``` + +::: tip + +If `--` is given, all remaining arguments will be assigned to a special +`CLI_ARGS` variable. + +::: + +## Commands + +### `task [tasks...]` + +Run one or more tasks defined in your Taskfile. + +```bash +task build +task test lint +task deploy --force +``` + +### `task --list` + +List all available tasks with their descriptions. + +```bash +task --list +task -l +``` + +### `task --list-all` + +List all tasks, including those without descriptions. + +```bash +task --list-all +task -a +``` + +### `task --init` + +Create a new Taskfile.yml in the current directory. + +```bash +task --init +task -i +``` + +::: tip + +Combine `--list` or `--list-all` with `--silent` (`-ls` or `-as` for shortants) +to list only the task names in each line. Useful for scripting with `grep` or +similar. + +::: + +## Options + +### General + +#### `-h, --help` + +Show help information. + +```bash +task --help +``` + +#### `--version` + +Show Task version. + +```bash +task --version +``` + +#### `-v, --verbose` + +Enable verbose mode for detailed output. + +- **Config equivalent**: [`verbose`](./config.md#verbose) +- **Environment variable**: [`TASK_VERBOSE`](./environment.md#task-verbose) + +```bash +task build --verbose +``` + +#### `-s, --silent` + +Disable command echoing. + +- **Config equivalent**: [`silent`](./config.md#silent) +- **Environment variable**: [`TASK_SILENT`](./environment.md#task-silent) + +```bash +task deploy --silent +``` + +#### `--disable-fuzzy` + +Disable fuzzy matching for task names. When enabled, Task will not suggest +similar task names when you mistype a task name. + +- **Config equivalent**: [`disable-fuzzy`](./config.md#disable-fuzzy) +- **Environment variable**: [`TASK_DISABLE_FUZZY`](./environment.md#task-disable-fuzzy) + +```bash +task buidl --disable-fuzzy +# Output: Task "buidl" does not exist +# (without "Did you mean 'build'?" suggestion) +``` + +### Execution Control + +#### `-F, --failfast` + +Stop executing dependencies as soon as one of them fails. + +- **Config equivalent**: [`failfast`](./config.md#failfast) +- **Environment variable**: [`TASK_FAILFAST`](./environment.md#task-failfast) + +```bash +task build --failfast +``` + +#### `-f, --force` + +Force execution even when the task is up-to-date. + +```bash +task build --force +``` + +#### `-n, --dry` + +Compile and print tasks without executing them. + +- **Environment variable**: [`TASK_DRY`](./environment.md#task-dry) + +```bash +task deploy --dry +``` + +#### `-p, --parallel` + +Execute multiple tasks in parallel. + +```bash +task test lint --parallel +``` + +#### `-C, --concurrency ` + +Limit the number of concurrent tasks. Zero means unlimited. + +- **Config equivalent**: [`concurrency`](./config.md#concurrency) +- **Environment variable**: [`TASK_CONCURRENCY`](./environment.md#task-concurrency) + +```bash +task test --concurrency 4 +``` + +#### `-x, --exit-code` + +Pass through the exit code of failed commands. + +```bash +task test --exit-code +``` + +### File and Directory + +#### `-d, --dir ` + +Set the directory where Task will run and look for Taskfiles. + +```bash +task build --dir ./backend +``` + +#### `-t, --taskfile ` + +Specify a custom Taskfile path. + +```bash +task build --taskfile ./custom/Taskfile.yml +``` + +#### `-g, --global` + +Run the global Taskfile from `$HOME/Taskfile.{yml,yaml}`. + +```bash +task backup --global +``` + +#### `--temp-dir ` + +Set the directory used to store Task temporary files, such as checksums. +Relative paths are relative to the root Taskfile. + +- **Config equivalent**: [`temp-dir`](./config.md#temp-dir) +- **Environment variable**: [`TASK_TEMP_DIR`](./environment.md#task-temp-dir) + +```bash +task build --temp-dir .task-cache +``` + +### Output Control + +#### `-o, --output ` + +Set output style. Available modes: `interleaved`, `group`, `prefixed`. + +- **Environment variable**: [`TASK_OUTPUT`](./environment.md#task-output) + +```bash +task test --output group +``` + +#### `--output-group-begin