feat: support bootstrap mode (#522)
Some checks are pending
Check dist/ / Check dist/ (push) Waiting to run
Continuous Integration / TypeScript Tests (push) Waiting to run
CodeQL / Analyze (push) Waiting to run
release-plz / release-plz (push) Waiting to run
Test Redacted Environment Variables / test-redacted-env (push) Waiting to run
build-test / fetch_from_github (push) Waiting to run
build-test / final (push) Blocked by required conditions
build-test / build (push) Waiting to run
build-test / alpine (push) Waiting to run
build-test / macos (push) Waiting to run
build-test / ubuntu (push) Waiting to run
build-test / windows (push) Waiting to run
build-test / specific_version (push) Waiting to run
build-test / checksum_failure (push) Waiting to run
build-test / custom_cache_key (push) Waiting to run

This commit is contained in:
jdx 2026-06-16 22:43:53 -04:00 committed by GitHub
parent 03d53910f9
commit 5f61b63aff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 128 additions and 16 deletions

View file

@ -19,6 +19,9 @@ jobs:
version: 2026.3.10 # [default: latest] mise version to install
install: true # [default: true] run `mise install`
install_args: "bun" # [default: ""] additional arguments to `mise install`
bootstrap: false # [default: false] run `mise bootstrap` instead of `mise install`
bootstrap_skip: "tools,task" # [default: ""] comma-separated parts to skip when bootstrapping
bootstrap_args: "--yes" # [default: ""] additional arguments to `mise bootstrap`
cache: true # [default: true] cache mise using GitHub's cache
experimental: true # [default: false] enable experimental features
log_level: debug # [default: info] log level
@ -72,6 +75,7 @@ Available template variables:
- `{{file_hash}}` - Hash of all mise configuration files
- `{{mise_env}}` - The MISE_ENV environment variable value
- `{{install_args_hash}}` - SHA256 hash of the sorted tools from install args
- `{{bootstrap_hash}}` - SHA256 hash of bootstrap mode, skip list, and args
- `{{default}}` - The processed default cache key (useful for extending)
Conditional logic is also supported using Handlebars syntax like `{{#if version}}...{{/if}}`.
@ -123,6 +127,21 @@ This auto-detection is intended for repo-managed config files. If you provide
`mise_toml` or `tool_versions` inputs, the action does not automatically force
locked mode.
## Bootstrap
Set `bootstrap: true` to run `mise bootstrap` instead of `mise install`:
```yaml
- uses: jdx/mise-action@v4
with:
bootstrap: true
```
When a repo mise lock file is present, the action automatically runs
`mise --locked bootstrap`. `install_args` cannot be combined with
`bootstrap: true`; use `bootstrap_skip` and `bootstrap_args` for bootstrap
customization.
## Alternative Installation
Alternatively, mise is easy to use in GitHub Actions even without this:

View file

@ -26,10 +26,20 @@ inputs:
install:
required: false
default: "true"
description: if false, will not run `mise install`
description: if false, will not run `mise install` or `mise bootstrap`
install_args:
required: false
description: Arguments to pass to `mise install` such as "bun" to only install bun. When a repo mise lock file is present, the action automatically adds `--locked` unless you already provided it.
bootstrap:
required: false
default: "false"
description: if true, will run `mise bootstrap` instead of `mise install`
bootstrap_skip:
required: false
description: Comma-separated bootstrap parts to skip, such as "tools,task". Passed as `mise bootstrap --skip`.
bootstrap_args:
required: false
description: Additional arguments to pass to `mise bootstrap`.
install_dir:
required: false
description: deprecated
@ -49,8 +59,8 @@ inputs:
required: false
description: |
Override the complete cache key (ignores all other cache key options).
Supports template variables: {{version}}, {{cache_key_prefix}}, {{platform}}, {{file_hash}},
{{mise_env}}, {{install_args_hash}}, {{default}}, {{env.VAR_NAME}} for environment variables,
Supports template variables: {{version}}, {{cache_key_prefix}}, {{platform}}, {{file_hash}},
{{mise_env}}, {{install_args_hash}}, {{bootstrap_hash}}, {{default}}, {{env.VAR_NAME}} for environment variables,
and conditional logic like {{#if version}}...{{/if}}
experimental:
required: false

49
dist/index.js generated vendored
View file

@ -89242,7 +89242,7 @@ const MISE_CONFIG_FILE_PATTERNS = [
`**/.tool-versions`
];
// Default cache key template
const DEFAULT_CACHE_KEY_TEMPLATE = '{{cache_key_prefix}}-{{platform}}{{#if version}}-{{version}}{{/if}}{{#if mise_env}}-{{mise_env}}{{/if}}{{#if install_args_hash}}-{{install_args_hash}}{{/if}}-{{#if file_hash}}{{file_hash}}{{else}}no-config{{/if}}';
const DEFAULT_CACHE_KEY_TEMPLATE = '{{cache_key_prefix}}-{{platform}}{{#if version}}-{{version}}{{/if}}{{#if mise_env}}-{{mise_env}}{{/if}}{{#if install_args_hash}}-{{install_args_hash}}{{/if}}{{#if bootstrap_hash}}-{{bootstrap_hash}}{{/if}}-{{#if file_hash}}{{file_hash}}{{else}}no-config{{/if}}';
const ROOT_MISE_LOCK_FILE_PATTERNS = [/^\.?mise(?:\.[^.]+)?\.lock$/];
const CONFIG_DIR_MISE_LOCK_FILE_PATTERNS = [/^mise(?:\.[^.]+)?\.lock$/];
const CONFIG_MISE_LOCK_FILE_PATTERNS = [/^config(?:\.[^.]+)?\.lock$/];
@ -89284,10 +89284,14 @@ async function run() {
}
await testMise();
if (getBooleanInput('install')) {
await miseInstall();
if (cacheKey && getBooleanInput('cache_save')) {
await saveCache(cacheKey);
if (getBooleanInput('bootstrap')) {
await miseBootstrap();
}
else {
await miseInstall();
}
if (cacheKey && getBooleanInput('cache_save'))
await saveCache(cacheKey);
}
await miseLs();
const loadEnv = getBooleanInput('env');
@ -89422,8 +89426,10 @@ async function setEnvVars() {
exportVariable(k, v);
}
};
if (getBooleanInput('experimental'))
if (getBooleanInput('experimental') ||
getBooleanInput('bootstrap')) {
set('MISE_EXPERIMENTAL', '1');
}
const logLevel = getInput('log_level');
if (logLevel)
set('MISE_LOG_LEVEL', logLevel);
@ -89633,6 +89639,26 @@ const miseInstall = async () => {
}
return mise([command]);
};
const miseBootstrap = async () => {
const installArgs = getInput('install_args').trim();
if (installArgs) {
throw new Error('`install_args` cannot be used when `bootstrap` is true because `mise bootstrap` does not support partial tool install args.');
}
const bootstrapSkip = getInput('bootstrap_skip').trim();
const bootstrapArgs = getInput('bootstrap_args').trim();
const useLocked = (await shouldUseLockedInstall()) &&
!/(^|\s)--locked(?:\s|$)/.test(bootstrapArgs);
const command = [
...(useLocked ? ['--locked'] : []),
'bootstrap',
...(bootstrapSkip ? ['--skip', bootstrapSkip] : []),
...(bootstrapArgs ? [bootstrapArgs] : [])
].join(' ');
if (useLocked) {
info('Detected a mise lock file, running `mise --locked bootstrap`');
}
return mise([command]);
};
const miseLs = async () => mise([`ls`]);
const miseReshim = async () => mise([`reshim`, `-f`]);
const mise = async (args) => await group(`Running mise ${args.join(' ')}`, async () => {
@ -89775,6 +89801,9 @@ async function processCacheKeyTemplate(template) {
// Get all available variables
const version = getInput('version');
const installArgs = getInput('install_args');
const bootstrap = getBooleanInput('bootstrap');
const bootstrapSkip = getInput('bootstrap_skip');
const bootstrapArgs = getInput('bootstrap_args');
const cacheKeyPrefix = getInput('cache_key_prefix') || 'mise-v1';
const miseEnv = process.env.MISE_ENV?.replace(/,/g, '-');
const platform = `${await getTarget()}-${getRunnerImageId()}`;
@ -89792,6 +89821,13 @@ async function processCacheKeyTemplate(template) {
installArgsHash = crypto$1.createHash('sha256').update(tools).digest('hex');
}
}
let bootstrapHash = '';
if (bootstrap) {
bootstrapHash = crypto$1
.createHash('sha256')
.update([String(bootstrap), bootstrapSkip, bootstrapArgs].join('\0'))
.digest('hex');
}
// Prepare base template data
const baseTemplateData = {
version,
@ -89799,7 +89835,8 @@ async function processCacheKeyTemplate(template) {
platform,
file_hash: fileHash,
mise_env: miseEnv,
install_args_hash: installArgsHash
install_args_hash: installArgsHash,
bootstrap_hash: bootstrapHash
};
// Calculate the default cache key by processing the default template
const defaultTemplate = libExports.compile(DEFAULT_CACHE_KEY_TEMPLATE);

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

View file

@ -40,7 +40,7 @@ const MISE_CONFIG_FILE_PATTERNS = [
// Default cache key template
const DEFAULT_CACHE_KEY_TEMPLATE =
'{{cache_key_prefix}}-{{platform}}{{#if version}}-{{version}}{{/if}}{{#if mise_env}}-{{mise_env}}{{/if}}{{#if install_args_hash}}-{{install_args_hash}}{{/if}}-{{#if file_hash}}{{file_hash}}{{else}}no-config{{/if}}'
'{{cache_key_prefix}}-{{platform}}{{#if version}}-{{version}}{{/if}}{{#if mise_env}}-{{mise_env}}{{/if}}{{#if install_args_hash}}-{{install_args_hash}}{{/if}}{{#if bootstrap_hash}}-{{bootstrap_hash}}{{/if}}-{{#if file_hash}}{{file_hash}}{{else}}no-config{{/if}}'
const ROOT_MISE_LOCK_FILE_PATTERNS = [/^\.?mise(?:\.[^.]+)?\.lock$/]
const CONFIG_DIR_MISE_LOCK_FILE_PATTERNS = [/^mise(?:\.[^.]+)?\.lock$/]
@ -86,10 +86,13 @@ async function run(): Promise<void> {
}
await testMise()
if (core.getBooleanInput('install')) {
await miseInstall()
if (cacheKey && core.getBooleanInput('cache_save')) {
await saveCache(cacheKey)
if (core.getBooleanInput('bootstrap')) {
await miseBootstrap()
} else {
await miseInstall()
}
if (cacheKey && core.getBooleanInput('cache_save'))
await saveCache(cacheKey)
}
await miseLs()
const loadEnv = core.getBooleanInput('env')
@ -243,7 +246,12 @@ async function setEnvVars(): Promise<void> {
core.exportVariable(k, v)
}
}
if (core.getBooleanInput('experimental')) set('MISE_EXPERIMENTAL', '1')
if (
core.getBooleanInput('experimental') ||
core.getBooleanInput('bootstrap')
) {
set('MISE_EXPERIMENTAL', '1')
}
const logLevel = core.getInput('log_level')
if (logLevel) set('MISE_LOG_LEVEL', logLevel)
@ -513,6 +521,32 @@ const miseInstall = async (): Promise<number> => {
return mise([command])
}
const miseBootstrap = async (): Promise<number> => {
const installArgs = core.getInput('install_args').trim()
if (installArgs) {
throw new Error(
'`install_args` cannot be used when `bootstrap` is true because `mise bootstrap` does not support partial tool install args.'
)
}
const bootstrapSkip = core.getInput('bootstrap_skip').trim()
const bootstrapArgs = core.getInput('bootstrap_args').trim()
const useLocked =
(await shouldUseLockedInstall()) &&
!/(^|\s)--locked(?:\s|$)/.test(bootstrapArgs)
const command = [
...(useLocked ? ['--locked'] : []),
'bootstrap',
...(bootstrapSkip ? ['--skip', bootstrapSkip] : []),
...(bootstrapArgs ? [bootstrapArgs] : [])
].join(' ')
if (useLocked) {
core.info('Detected a mise lock file, running `mise --locked bootstrap`')
}
return mise([command])
}
const miseLs = async (): Promise<number> => mise([`ls`])
const miseReshim = async (): Promise<number> => mise([`reshim`, `-f`])
const mise = async (args: string[]): Promise<number> =>
@ -689,6 +723,9 @@ async function processCacheKeyTemplate(template: string): Promise<string> {
// Get all available variables
const version = core.getInput('version')
const installArgs = core.getInput('install_args')
const bootstrap = core.getBooleanInput('bootstrap')
const bootstrapSkip = core.getInput('bootstrap_skip')
const bootstrapArgs = core.getInput('bootstrap_args')
const cacheKeyPrefix = core.getInput('cache_key_prefix') || 'mise-v1'
const miseEnv = process.env.MISE_ENV?.replace(/,/g, '-')
const platform = `${await getTarget()}-${getRunnerImageId()}`
@ -709,6 +746,14 @@ async function processCacheKeyTemplate(template: string): Promise<string> {
}
}
let bootstrapHash = ''
if (bootstrap) {
bootstrapHash = crypto
.createHash('sha256')
.update([String(bootstrap), bootstrapSkip, bootstrapArgs].join('\0'))
.digest('hex')
}
// Prepare base template data
const baseTemplateData = {
version,
@ -716,7 +761,8 @@ async function processCacheKeyTemplate(template: string): Promise<string> {
platform,
file_hash: fileHash,
mise_env: miseEnv,
install_args_hash: installArgsHash
install_args_hash: installArgsHash,
bootstrap_hash: bootstrapHash
}
// Calculate the default cache key by processing the default template