diff --git a/.babelrc.js b/.babelrc.js
new file mode 100644
index 0000000..5139969
--- /dev/null
+++ b/.babelrc.js
@@ -0,0 +1,12 @@
+module.exports = {
+ presets: [
+ [
+ '@babel/preset-env',
+ {
+ loose: true,
+ bugfixes: true,
+ modules: false
+ }
+ ]
+ ]
+};
diff --git a/.browserslistrc b/.browserslistrc
new file mode 100644
index 0000000..cddd230
--- /dev/null
+++ b/.browserslistrc
@@ -0,0 +1,12 @@
+# https://github.com/browserslist/browserslist#readme
+
+>= 0.5%
+last 2 major versions
+not dead
+Chrome >= 60
+Firefox >= 60
+Firefox ESR
+iOS >= 12
+Safari >= 12
+not Explorer <= 11
+not kaios <= 2.5 # fix floating label issues in Firefox (see https://github.com/postcss/autoprefixer/issues/1533)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..235ad54
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,45 @@
+# Ignore docs files
+/_site/
+
+# Numerous always-ignore extensions
+*.diff
+*.err
+*.log
+*.orig
+*.rej
+*.swo
+*.swp
+*.vi
+*.zip
+*~
+
+# OS or Editor folders
+._*
+.cache
+.DS_Store
+.idea
+.project
+.settings
+.tmproj
+*.esproj
+*.sublime-project
+*.sublime-workspace
+nbproject
+Thumbs.db
+# Local Netlify folder
+.netlify
+
+# Komodo
+.komodotools
+*.komodoproject
+
+# Folders to ignore
+/dist-sass/
+/js/coverage/
+/node_modules/
+
+# Site
+/site/dist
+/site/node_modules
+/site/.astro
+/site/public
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 28fd5e8..7562983 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
-* Demonstrating empathy and kindness toward other people
-* Being respectful of differing opinions, viewpoints, and experiences
-* Giving and gracefully accepting constructive feedback
-* Accepting responsibility and apologizing to those affected by our mistakes,
+- Demonstrating empathy and kindness toward other people
+- Being respectful of differing opinions, viewpoints, and experiences
+- Giving and gracefully accepting constructive feedback
+- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
-* Focusing on what is best not just for us as individuals, but for the overall
+- Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
-* The use of sexualized language or imagery, and sexual attention or advances of
+- The use of sexualized language or imagery, and sexual attention or advances of
any kind
-* Trolling, insulting or derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or email address,
+- Trolling, insulting or derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others’ private information, such as a physical or email address,
without their explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
+- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
diff --git a/LICENSE b/LICENSE
index dda75ca..fa7c00b 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,7 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2011-2022 Twitter, Inc.
-Copyright (c) 2011-2022 The Bootstrap Authors
+Copyright (c) 2011-2025 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index bd40192..e3618e6 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-
+
@@ -9,15 +9,13 @@
Sleek, intuitive, and powerful front-end framework for faster and easier web development.
- Explore Bootstrap docs »
+ Explore Bootstrap docs »
Report bug
·
Request feature
·
- Themes
- ·
Blog
@@ -31,7 +29,7 @@ Our default branch is for development of our Bootstrap 5 release. Head to the [`
- [Quick start](#quick-start)
- [Status](#status)
-- [What's included](#whats-included)
+- [What’s included](#whats-included)
- [Bugs and feature requests](#bugs-and-feature-requests)
- [Documentation](#documentation)
- [Contributing](#contributing)
@@ -46,37 +44,38 @@ Our default branch is for development of our Bootstrap 5 release. Head to the [`
Several quick start options are available:
-- [Download the latest release](https://github.com/twbs/bootstrap/archive/v5.2.3.zip)
+- [Download the latest release](https://github.com/twbs/bootstrap/archive/v5.3.8.zip)
- Clone the repo: `git clone https://github.com/twbs/bootstrap.git`
-- Install with [npm](https://www.npmjs.com/): `npm install bootstrap@v5.2.3`
-- Install with [yarn](https://yarnpkg.com/): `yarn add bootstrap@v5.2.3`
-- Install with [Composer](https://getcomposer.org/): `composer require twbs/bootstrap:5.2.3`
+- Install with [npm](https://www.npmjs.com/): `npm install bootstrap@v5.3.8`
+- Install with [yarn](https://yarnpkg.com/): `yarn add bootstrap@v5.3.8`
+- Install with [Bun](https://bun.sh/): `bun add bootstrap@v5.3.8`
+- Install with [Composer](https://getcomposer.org/): `composer require twbs/bootstrap:5.3.8`
- Install with [NuGet](https://www.nuget.org/): CSS: `Install-Package bootstrap` Sass: `Install-Package bootstrap.sass`
-Read the [Getting started page](https://getbootstrap.com/docs/5.2/getting-started/introduction/) for information on the framework contents, templates, examples, and more.
+Read the [Getting started page](https://getbootstrap.com/docs/5.3/getting-started/introduction/) for information on the framework contents, templates, examples, and more.
## Status
-[](https://github.com/twbs/bootstrap/actions?query=workflow%3AJS+Tests+branch%3Amain)
-[](https://www.npmjs.com/package/bootstrap)
-[](https://rubygems.org/gems/bootstrap)
-[](https://atmospherejs.com/twbs/bootstrap)
-[](https://packagist.org/packages/twbs/bootstrap)
-[](https://www.nuget.org/packages/bootstrap/absoluteLatest)
-[](https://coveralls.io/github/twbs/bootstrap?branch=main)
+[](https://github.com/twbs/bootstrap/actions/workflows/js.yml?query=workflow%3AJS+branch%3Amain)
+[](https://www.npmjs.com/package/bootstrap)
+[](https://rubygems.org/gems/bootstrap)
+[](https://atmospherejs.com/twbs/bootstrap)
+[](https://packagist.org/packages/twbs/bootstrap)
+[](https://www.nuget.org/packages/bootstrap/absoluteLatest)
+[](https://coveralls.io/github/twbs/bootstrap?branch=main)
[](https://github.com/twbs/bootstrap/blob/main/dist/css/bootstrap.min.css)
[](https://github.com/twbs/bootstrap/blob/main/dist/css/bootstrap.min.css)
[](https://github.com/twbs/bootstrap/blob/main/dist/js/bootstrap.min.js)
[](https://github.com/twbs/bootstrap/blob/main/dist/js/bootstrap.min.js)
-[](https://www.browserstack.com/automate/public-build/SkxZcStBeExEdVJqQ2hWYnlWckpkNmNEY213SFp6WHFETWk2bGFuY3pCbz0tLXhqbHJsVlZhQnRBdEpod3NLSDMzaHc9PQ==--3d0b75245708616eb93113221beece33e680b229)
-[](#backers)
-[](#sponsors)
+
+[](#backers)
+[](#sponsors)
-## What's included
+## What’s included
-Within the download you'll find the following directories and files, logically grouping common assets and providing both compiled and minified variations.
+Within the download you’ll find the following directories and files, logically grouping common assets and providing both compiled and minified variations.
Download contents
@@ -132,7 +131,7 @@ Within the download you'll find the following directories and files, logically g
```
-We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [Source maps](https://developers.google.com/web/tools/chrome-devtools/javascript/source-maps) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/).
+We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [Source maps](https://web.dev/articles/source-maps) (`bootstrap.*.map`) are available for use with certain browsers’ developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/docs/v2/).
## Bugs and feature requests
@@ -142,18 +141,18 @@ Have a bug or a feature request? Please first read the [issue guidelines](https:
## Documentation
-Bootstrap's documentation, included in this repo in the root directory, is built with [Hugo](https://gohugo.io/) and publicly hosted on GitHub Pages at . The docs may also be run locally.
+Bootstrap’s documentation, included in this repo in the root directory, is built with [Astro](https://astro.build/) and publicly hosted on GitHub Pages at . The docs may also be run locally.
-Documentation search is powered by [Algolia's DocSearch](https://docsearch.algolia.com/). Working on our search? Be sure to set `debug: true` in `site/assets/js/search.js`.
+Documentation search is powered by [Algolia's DocSearch](https://docsearch.algolia.com/).
### Running documentation locally
-1. Run `npm install` to install the Node.js dependencies, including Hugo (the site builder).
+1. Run `npm install` to install the Node.js dependencies, including Astro (the site builder).
2. Run `npm run test` (or a specific npm script) to rebuild distributed CSS and JavaScript files, as well as our docs assets.
3. From the root `/bootstrap` directory, run `npm run docs-serve` in the command line.
-4. Open `http://localhost:9001/` in your browser, and voilà.
+4. Open in your browser, and voilà.
-Learn more about using Hugo by reading its [documentation](https://gohugo.io/documentation/).
+Learn more about using Astro by reading its [documentation](https://docs.astro.build/en/getting-started/).
### Documentation for previous releases
@@ -173,11 +172,12 @@ Editor preferences are available in the [editor config](https://github.com/twbs/
## Community
-Get updates on Bootstrap's development and chat with the project maintainers and community members.
+Get updates on Bootstrap’s development and chat with the project maintainers and community members.
-- Follow [@getbootstrap on Twitter](https://twitter.com/getbootstrap).
+- Follow [@getbootstrap on X](https://x.com/getbootstrap).
- Read and subscribe to [The Official Bootstrap Blog](https://blog.getbootstrap.com/).
-- Ask and explore [our GitHub Discussions](https://github.com/twbs/bootstrap/discussions).
+- Ask questions and explore [our GitHub Discussions](https://github.com/twbs/bootstrap/discussions).
+- Discuss, ask questions, and more on [the community Discord](https://discord.gg/bZUvakRU3M) or [Bootstrap subreddit](https://www.reddit.com/r/bootstrap/).
- Chat with fellow Bootstrappers in IRC. On the `irc.libera.chat` server, in the `#bootstrap` channel.
- Implementation help may be found at Stack Overflow (tagged [`bootstrap-5`](https://stackoverflow.com/questions/tagged/bootstrap-5)).
- Developers should use the keyword `bootstrap` on packages which modify or add to the functionality of Bootstrap when distributing through [npm](https://www.npmjs.com/browse/keyword/bootstrap) or similar delivery mechanisms for maximum discoverability.
@@ -194,12 +194,12 @@ See [the Releases section of our GitHub project](https://github.com/twbs/bootstr
**Mark Otto**
--
+-
-
**Jacob Thornton**
--
+-
-
@@ -243,4 +243,4 @@ Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com
## Copyright and license
-Code and documentation copyright 2011–2022 the [Bootstrap Authors](https://github.com/twbs/bootstrap/graphs/contributors) and [Twitter, Inc.](https://twitter.com) Code released under the [MIT License](https://github.com/twbs/bootstrap/blob/main/LICENSE). Docs released under [Creative Commons](https://creativecommons.org/licenses/by/3.0/).
+Code and documentation copyright 2011-2025 the [Bootstrap Authors](https://github.com/twbs/bootstrap/graphs/contributors). Code released under the [MIT License](https://github.com/twbs/bootstrap/blob/main/LICENSE). Docs released under [Creative Commons](https://creativecommons.org/licenses/by/3.0/).
diff --git a/build/.eslintrc.json b/build/.eslintrc.json
deleted file mode 100644
index dec6323..0000000
--- a/build/.eslintrc.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "env": {
- "browser": false,
- "node": true
- },
- "parserOptions": {
- "sourceType": "script"
- },
- "extends": "../.eslintrc.json",
- "rules": {
- "no-console": "off",
- "strict": "error",
- "unicorn/prefer-top-level-await": "off"
- }
-}
diff --git a/build/banner.js b/build/banner.mjs
similarity index 50%
rename from build/banner.js
rename to build/banner.mjs
index df82ff3..3fea93c 100644
--- a/build/banner.js
+++ b/build/banner.mjs
@@ -1,6 +1,12 @@
-'use strict'
+import fs from 'node:fs/promises'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
+
+const pkgJson = path.join(__dirname, '../package.json')
+const pkg = JSON.parse(await fs.readFile(pkgJson, 'utf8'))
-const pkg = require('../package.json')
const year = new Date().getFullYear()
function getBanner(pluginFilename) {
@@ -11,4 +17,4 @@ function getBanner(pluginFilename) {
*/`
}
-module.exports = getBanner
+export default getBanner
diff --git a/build/build-plugins.js b/build/build-plugins.mjs
similarity index 82%
rename from build/build-plugins.js
rename to build/build-plugins.mjs
index a160209..f56cd21 100644
--- a/build/build-plugins.js
+++ b/build/build-plugins.mjs
@@ -2,21 +2,22 @@
/*!
* Script to build our plugins to use them separately.
- * Copyright 2020-2022 The Bootstrap Authors
- * Copyright 2020-2022 Twitter, Inc.
+ * Copyright 2020-2025 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
-'use strict'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+import { babel } from '@rollup/plugin-babel'
+import { globby } from 'globby'
+import { rollup } from 'rollup'
+import banner from './banner.mjs'
-const path = require('node:path')
-const rollup = require('rollup')
-const globby = require('globby')
-const { babel } = require('@rollup/plugin-babel')
-const banner = require('./banner.js')
+const __filename = fileURLToPath(import.meta.url)
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
const sourcePath = path.resolve(__dirname, '../js/src/').replace(/\\/g, '/')
-const jsFiles = globby.sync(sourcePath + '/**/*.js')
+const jsFiles = await globby(`${sourcePath}/**/*.js`)
// Array which holds the resolved plugins
const resolvedPlugins = []
@@ -27,7 +28,7 @@ const filenameToEntity = filename => filename.replace('.js', '')
for (const file of jsFiles) {
resolvedPlugins.push({
- src: file.replace('.js', ''),
+ src: file,
dist: file.replace('src', 'dist'),
fileName: path.basename(file),
className: filenameToEntity(path.basename(file))
@@ -36,9 +37,12 @@ for (const file of jsFiles) {
}
const build = async plugin => {
+ /**
+ * @type {import('rollup').GlobalsOption}
+ */
const globals = {}
- const bundle = await rollup.rollup({
+ const bundle = await rollup({
input: plugin.src,
plugins: [
babel({
diff --git a/build/change-version.js b/build/change-version.js
deleted file mode 100644
index 57c5fde..0000000
--- a/build/change-version.js
+++ /dev/null
@@ -1,81 +0,0 @@
-#!/usr/bin/env node
-
-/*!
- * Script to update version number references in the project.
- * Copyright 2017-2022 The Bootstrap Authors
- * Copyright 2017-2022 Twitter, Inc.
- * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
- */
-
-'use strict'
-
-const fs = require('node:fs').promises
-const path = require('node:path')
-const globby = require('globby')
-
-const VERBOSE = process.argv.includes('--verbose')
-const DRY_RUN = process.argv.includes('--dry') || process.argv.includes('--dry-run')
-
-// These are the filetypes we only care about replacing the version
-const GLOB = [
- '**/*.{css,html,js,json,md,scss,txt,yml}'
-]
-const GLOBBY_OPTIONS = {
- cwd: path.join(__dirname, '..'),
- gitignore: true
-}
-
-// Blame TC39... https://github.com/benjamingr/RegExp.escape/issues/37
-function regExpQuote(string) {
- return string.replace(/[$()*+-.?[\\\]^{|}]/g, '\\$&')
-}
-
-function regExpQuoteReplacement(string) {
- return string.replace(/\$/g, '$$')
-}
-
-async function replaceRecursively(file, oldVersion, newVersion) {
- const originalString = await fs.readFile(file, 'utf8')
- const newString = originalString.replace(
- new RegExp(regExpQuote(oldVersion), 'g'), regExpQuoteReplacement(newVersion)
- )
-
- // No need to move any further if the strings are identical
- if (originalString === newString) {
- return
- }
-
- if (VERBOSE) {
- console.log(`FILE: ${file}`)
- }
-
- if (DRY_RUN) {
- return
- }
-
- await fs.writeFile(file, newString, 'utf8')
-}
-
-async function main(args) {
- let [oldVersion, newVersion] = args
-
- if (!oldVersion || !newVersion) {
- console.error('USAGE: change-version old_version new_version [--verbose] [--dry[-run]]')
- console.error('Got arguments:', args)
- process.exit(1)
- }
-
- // Strip any leading `v` from arguments because otherwise we will end up with duplicate `v`s
- [oldVersion, newVersion] = [oldVersion, newVersion].map(arg => arg.startsWith('v') ? arg.slice(1) : arg)
-
- try {
- const files = await globby(GLOB, GLOBBY_OPTIONS)
-
- await Promise.all(files.map(file => replaceRecursively(file, oldVersion, newVersion)))
- } catch (error) {
- console.error(error)
- process.exit(1)
- }
-}
-
-main(process.argv.slice(2))
diff --git a/build/change-version.mjs b/build/change-version.mjs
new file mode 100644
index 0000000..1400274
--- /dev/null
+++ b/build/change-version.mjs
@@ -0,0 +1,113 @@
+#!/usr/bin/env node
+
+/*!
+ * Script to update version number references in the project.
+ * Copyright 2017-2025 The Bootstrap Authors
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ */
+
+import { execFile } from 'node:child_process'
+import fs from 'node:fs/promises'
+import process from 'node:process'
+
+const VERBOSE = process.argv.includes('--verbose')
+const DRY_RUN = process.argv.includes('--dry') || process.argv.includes('--dry-run')
+
+// These are the files we only care about replacing the version
+const FILES = [
+ 'README.md',
+ 'config.yml',
+ 'js/src/base-component.js',
+ 'package.js',
+ 'scss/mixins/_banner.scss',
+ 'site/data/docs-versions.yml'
+]
+
+// Blame TC39... https://github.com/benjamingr/RegExp.escape/issues/37
+function regExpQuote(string) {
+ return string.replace(/[$()*+-.?[\\\]^{|}]/g, '\\$&')
+}
+
+function regExpQuoteReplacement(string) {
+ return string.replace(/\$/g, '$$')
+}
+
+async function replaceRecursively(file, oldVersion, newVersion) {
+ const originalString = await fs.readFile(file, 'utf8')
+ const newString = originalString
+ .replace(
+ new RegExp(regExpQuote(oldVersion), 'g'),
+ regExpQuoteReplacement(newVersion)
+ )
+ // Also replace the version used by the rubygem,
+ // which is using periods (`.`) instead of hyphens (`-`)
+ .replace(
+ new RegExp(regExpQuote(oldVersion.replace(/-/g, '.')), 'g'),
+ regExpQuoteReplacement(newVersion.replace(/-/g, '.'))
+ )
+
+ // No need to move any further if the strings are identical
+ if (originalString === newString) {
+ return
+ }
+
+ if (VERBOSE) {
+ console.log(`Found ${oldVersion} in ${file}`)
+ }
+
+ if (DRY_RUN) {
+ return
+ }
+
+ await fs.writeFile(file, newString, 'utf8')
+}
+
+function bumpNpmVersion(newVersion) {
+ if (DRY_RUN) {
+ return
+ }
+
+ execFile('npm', ['version', newVersion, '--no-git-tag'], { shell: true }, error => {
+ if (error) {
+ console.error(error)
+ process.exit(1)
+ }
+ })
+}
+
+function showUsage(args) {
+ console.error('USAGE: change-version old_version new_version [--verbose] [--dry[-run]]')
+ console.error('Got arguments:', args)
+ process.exit(1)
+}
+
+async function main(args) {
+ let [oldVersion, newVersion] = args
+
+ if (!oldVersion || !newVersion) {
+ showUsage(args)
+ }
+
+ // Strip any leading `v` from arguments because
+ // otherwise we will end up with duplicate `v`s
+ [oldVersion, newVersion] = [oldVersion, newVersion].map(arg => {
+ return arg.startsWith('v') ? arg.slice(1) : arg
+ })
+
+ if (oldVersion === newVersion) {
+ showUsage(args)
+ }
+
+ bumpNpmVersion(newVersion)
+
+ try {
+ await Promise.all(
+ FILES.map(file => replaceRecursively(file, oldVersion, newVersion))
+ )
+ } catch (error) {
+ console.error(error)
+ process.exit(1)
+ }
+}
+
+main(process.argv.slice(2))
diff --git a/build/docs-prep.sh b/build/docs-prep.sh
new file mode 100755
index 0000000..357768a
--- /dev/null
+++ b/build/docs-prep.sh
@@ -0,0 +1,157 @@
+#!/bin/bash
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[0;33m'
+BLUE='\033[0;34m'
+NC='\033[0m' # No Color
+
+# Default branch suffix
+BRANCH_SUFFIX="release"
+
+# Check if a custom version parameter was provided
+if [ $# -eq 1 ]; then
+ BRANCH_SUFFIX="$1"
+fi
+
+# Branch name to create
+NEW_BRANCH="gh-pages-${BRANCH_SUFFIX}"
+
+# Get the current docs version from config
+DOCS_VERSION=$(node -p "require('js-yaml').load(require('fs').readFileSync('config.yml', 'utf8')).docs_version")
+
+# Function to print colored messages
+print_success() {
+ echo -e "${GREEN}✓ $1${NC}"
+}
+
+print_error() {
+ echo -e "${RED}✗ $1${NC}"
+ exit 1
+}
+
+print_info() {
+ echo -e "${BLUE}ℹ $1${NC}"
+}
+
+print_warning() {
+ echo -e "${YELLOW}⚠ $1${NC}"
+}
+
+# Function to execute command with error handling
+execute() {
+ print_info "Running: $1"
+ eval $1
+ if [ $? -ne 0 ]; then
+ print_error "Failed to execute: $1"
+ else
+ print_success "Successfully executed: $1"
+ fi
+}
+
+# Check if /tmp/_site directory exists from a previous run
+if [ -d "/tmp/_site" ]; then
+ print_warning "Found existing /tmp/_site directory. Removing it…"
+ rm -rf /tmp/_site
+fi
+
+# Main process
+print_info "Starting documentation deployment process…"
+
+# Step 1: Build documentation
+print_info "Building documentation with npm run docs…"
+npm run docs
+if [ $? -ne 0 ]; then
+ print_error "Documentation build failed!"
+fi
+print_success "Documentation built successfully"
+
+# Step 2: Move _site to /tmp/
+print_info "Moving _site to temporary location…"
+execute "mv _site /tmp/"
+
+# Step 3: Switch to gh-pages branch
+print_info "Checking out gh-pages branch…"
+git checkout gh-pages
+if [ $? -ne 0 ]; then
+ print_error "Failed to checkout gh-pages branch. Make sure it exists."
+fi
+print_success "Switched to gh-pages branch"
+
+git reset --hard origin/gh-pages
+if [ $? -ne 0 ]; then
+ print_error "Failed to reset to origin/gh-pages. Check your git configuration."
+fi
+print_success "Reset to origin/gh-pages"
+
+git pull origin gh-pages
+if [ $? -ne 0 ]; then
+ print_error "Failed to pull from origin/gh-pages. Check your network connection and git configuration."
+fi
+print_success "Pulled latest changes from origin/gh-pages"
+
+# Step 4: Create a new branch for the update
+print_info "Checking if branch ${NEW_BRANCH} exists and deleting it if it does…"
+if git show-ref --verify --quiet refs/heads/${NEW_BRANCH}; then
+ execute "git branch -D ${NEW_BRANCH}"
+else
+ print_info "Branch ${NEW_BRANCH} does not exist, proceeding with creation…"
+fi
+print_info "Creating new branch ${NEW_BRANCH}…"
+execute "git checkout -b ${NEW_BRANCH}"
+
+# Step 5: Move all root-level files from Astro build
+find /tmp/_site -maxdepth 1 -type f -exec mv {} . \;
+
+# Step 6: Move all top-level directories except 'docs' (which needs special handling)
+find /tmp/_site -maxdepth 1 -type d ! -name "_site" ! -name "docs" -exec sh -c 'dir=$(basename "$1"); rm -rf "$dir"; mv "$1" .' _ {} \;
+
+# Step 7: Handle docs directory specially
+if [ -d "/tmp/_site/docs" ]; then
+ # Replace only the current version's docs
+ if [ -d "docs/$DOCS_VERSION" ]; then
+ rm -rf "docs/$DOCS_VERSION"
+ fi
+ mv "/tmp/_site/docs/$DOCS_VERSION" "docs/"
+
+ # Handle docs root files
+ find /tmp/_site/docs -maxdepth 1 -type f -exec mv {} docs/ \;
+
+ # Handle special docs directories (getting-started, versions)
+ for special_dir in getting-started versions; do
+ if [ -d "/tmp/_site/docs/$special_dir" ]; then
+ rm -rf "docs/$special_dir"
+ mv "/tmp/_site/docs/$special_dir" "docs/"
+ fi
+ done
+fi
+
+# Clean up remaining files in /tmp/_site if any
+if [ -d "/tmp/_site" ]; then
+ remaining_files=$(find /tmp/_site -type f | wc -l)
+ remaining_dirs=$(find /tmp/_site -type d | wc -l)
+ if [ $remaining_files -gt 0 ] || [ $remaining_dirs -gt 1 ]; then
+ print_warning "There are still some files or directories in /tmp/_site that weren't moved."
+ print_warning "You may want to inspect /tmp/_site to see if anything important was missed."
+ else
+ print_info "Cleaning up temporary directory…"
+ rm -rf /tmp/_site
+ print_success "Temporary directory cleaned up"
+ fi
+fi
+
+# Step 10: Remove empty site directory if it exists
+if [ -d "site" ]; then
+ print_info "Removing empty site directory…"
+ execute "rm -rf site"
+fi
+
+print_success "Docs prep complete!"
+print_info "Review changes before committing and pushing."
+print_info "Next steps:"
+print_info " 1. Run a local server to review changes"
+print_info " 2. Check browser and web inspector for any errors"
+print_info " 3. git add ."
+print_info " 4. git commit -m \"Update documentation\""
+print_info " 5. git push origin ${NEW_BRANCH}"
diff --git a/build/generate-sri.js b/build/generate-sri.mjs
similarity index 60%
rename from build/generate-sri.js
rename to build/generate-sri.mjs
index ef1b39f..5622843 100644
--- a/build/generate-sri.js
+++ b/build/generate-sri.mjs
@@ -5,17 +5,17 @@
* Remember to use the same vendor files as the CDN ones,
* otherwise the hashes won't match!
*
- * Copyright 2017-2022 The Bootstrap Authors
- * Copyright 2017-2022 Twitter, Inc.
+ * Copyright 2017-2025 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
-'use strict'
+import crypto from 'node:crypto'
+import fs from 'node:fs'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+import sh from 'shelljs'
-const crypto = require('node:crypto')
-const fs = require('node:fs')
-const path = require('node:path')
-const sh = require('shelljs')
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
sh.config.fatal = true
@@ -47,18 +47,18 @@ const files = [
}
]
-for (const file of files) {
- fs.readFile(file.file, 'utf8', (error, data) => {
+for (const { file, configPropertyName } of files) {
+ fs.readFile(file, 'utf8', (error, data) => {
if (error) {
throw error
}
- const algo = 'sha384'
- const hash = crypto.createHash(algo).update(data, 'utf8').digest('base64')
- const integrity = `${algo}-${hash}`
+ const algorithm = 'sha384'
+ const hash = crypto.createHash(algorithm).update(data, 'utf8').digest('base64')
+ const integrity = `${algorithm}-${hash}`
- console.log(`${file.configPropertyName}: ${integrity}`)
+ console.log(`${configPropertyName}: ${integrity}`)
- sh.sed('-i', new RegExp(`^(\\s+${file.configPropertyName}:\\s+["'])\\S*(["'])`), `$1${integrity}$2`, configFile)
+ sh.sed('-i', new RegExp(`^(\\s+${configPropertyName}:\\s+["'])\\S*(["'])`), `$1${integrity}$2`, configFile)
})
}
diff --git a/build/postcss.config.js b/build/postcss.config.mjs
similarity index 86%
rename from build/postcss.config.js
rename to build/postcss.config.mjs
index 7f8186d..7717cfc 100644
--- a/build/postcss.config.js
+++ b/build/postcss.config.mjs
@@ -1,12 +1,10 @@
-'use strict'
-
const mapConfig = {
inline: false,
annotation: true,
sourcesContent: true
}
-module.exports = context => {
+export default context => {
return {
map: context.file.dirname.includes('examples') ? false : mapConfig,
plugins: {
diff --git a/build/rollup.config.js b/build/rollup.config.mjs
similarity index 62%
rename from build/rollup.config.js
rename to build/rollup.config.mjs
index 27f12ac..dd6c7d1 100644
--- a/build/rollup.config.js
+++ b/build/rollup.config.mjs
@@ -1,15 +1,17 @@
-'use strict'
+import path from 'node:path'
+import process from 'node:process'
+import { fileURLToPath } from 'node:url'
+import { babel } from '@rollup/plugin-babel'
+import { nodeResolve } from '@rollup/plugin-node-resolve'
+import replace from '@rollup/plugin-replace'
+import banner from './banner.mjs'
-const path = require('node:path')
-const { babel } = require('@rollup/plugin-babel')
-const { nodeResolve } = require('@rollup/plugin-node-resolve')
-const replace = require('@rollup/plugin-replace')
-const banner = require('./banner.js')
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
const BUNDLE = process.env.BUNDLE === 'true'
const ESM = process.env.ESM === 'true'
-let fileDestination = `bootstrap${ESM ? '.esm' : ''}`
+let destinationFile = `bootstrap${ESM ? '.esm' : ''}`
const external = ['@popperjs/core']
const plugins = [
babel({
@@ -24,7 +26,7 @@ const globals = {
}
if (BUNDLE) {
- fileDestination += '.bundle'
+ destinationFile += '.bundle'
// Remove last entry in external array to bundle Popper
external.pop()
delete globals['@popperjs/core']
@@ -40,8 +42,8 @@ if (BUNDLE) {
const rollupConfig = {
input: path.resolve(__dirname, `../js/index.${ESM ? 'esm' : 'umd'}.js`),
output: {
- banner,
- file: path.resolve(__dirname, `../dist/js/${fileDestination}.js`),
+ banner: banner(),
+ file: path.resolve(__dirname, `../dist/js/${destinationFile}.js`),
format: ESM ? 'esm' : 'umd',
globals,
generatedCode: 'es2015'
@@ -54,4 +56,4 @@ if (!ESM) {
rollupConfig.output.name = 'bootstrap'
}
-module.exports = rollupConfig
+export default rollupConfig
diff --git a/build/vnu-jar.js b/build/vnu-jar.mjs
similarity index 64%
rename from build/vnu-jar.js
rename to build/vnu-jar.mjs
index f29eeb7..4eedb1b 100644
--- a/build/vnu-jar.js
+++ b/build/vnu-jar.mjs
@@ -2,22 +2,22 @@
/*!
* Script to run vnu-jar if Java is available.
- * Copyright 2017-2022 The Bootstrap Authors
- * Copyright 2017-2022 Twitter, Inc.
+ * Copyright 2017-2025 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
-'use strict'
-
-const { execFile, spawn } = require('node:child_process')
-const vnu = require('vnu-jar')
+import { execFile, spawn } from 'node:child_process'
+import vnu from 'vnu-jar'
execFile('java', ['-version'], (error, stdout, stderr) => {
if (error) {
- console.error('Skipping vnu-jar test; Java is missing.')
+ console.error('Skipping vnu-jar test; Java is probably missing.')
+ console.error(error)
return
}
+ console.log('Running vnu-jar validation...')
+
const is32bitJava = !/64-Bit/.test(stderr)
// vnu-jar accepts multiple ignores joined with a `|`.
@@ -30,7 +30,14 @@ execFile('java', ['-version'], (error, stdout, stderr) => {
// Per https://www.w3.org/TR/html-aria/#docconformance having "aria-disabled" on a link is
// NOT RECOMMENDED, but it's still valid - we explain in the docs that it's not ideal,
// and offer more robust alternatives, but also need to show a less-than-ideal example
- 'An “aria-disabled” attribute whose value is “true” should not be specified on an “a” element that has an “href” attribute.'
+ 'An “aria-disabled” attribute whose value is “true” should not be specified on an “a” element that has an “href” attribute.',
+ // A `code` element with the `is:raw` attribute coming from remark-prismjs (Astro upstream possible bug)
+ 'Attribute “is:raw” is not serializable as XML 1.0.',
+ 'Attribute “is:raw” not allowed on element “code” at this point.',
+ // Astro's expecting trailing slashes on HTML tags such as
+ 'Trailing slash on void elements has no effect and interacts badly with unquoted attribute values.',
+ // Allow `switch` attribute.
+ 'Attribute “switch” not allowed on element “input” at this point.'
].join('|')
const args = [
@@ -49,6 +56,8 @@ execFile('java', ['-version'], (error, stdout, stderr) => {
args.splice(0, 0, '-Xss512k')
}
+ console.log(`command used: java ${args.join(' ')}`)
+
return spawn('java', args, {
shell: true,
stdio: 'inherit'
diff --git a/build/zip-examples.js b/build/zip-examples.mjs
similarity index 57%
rename from build/zip-examples.js
rename to build/zip-examples.mjs
index 077901e..6b2e1e0 100644
--- a/build/zip-examples.js
+++ b/build/zip-examples.mjs
@@ -3,16 +3,20 @@
/*!
* Script to create the built examples zip archive;
* requires the `zip` command to be present!
- * Copyright 2020-2022 The Bootstrap Authors
+ * Copyright 2020-2025 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
-'use strict'
+import fs from 'node:fs/promises'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+import sh from 'shelljs'
+import { format } from 'prettier'
-const path = require('node:path')
-const sh = require('shelljs')
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
-const pkg = require('../package.json')
+const pkgJson = path.join(__dirname, '../package.json')
+const pkg = JSON.parse(await fs.readFile(pkgJson, 'utf8'))
const versionShort = pkg.config.version_short
const distFolder = `bootstrap-${pkg.version}-examples`
@@ -34,6 +38,9 @@ const imgFiles = [
'bootstrap-logo.svg',
'bootstrap-logo-white.svg'
]
+const staticJsFiles = [
+ 'color-modes.js'
+]
sh.config.fatal = true
@@ -52,7 +59,8 @@ sh.mkdir('-p', [
distFolder,
`${distFolder}/assets/brand/`,
`${distFolder}/assets/dist/css/`,
- `${distFolder}/assets/dist/js/`
+ `${distFolder}/assets/dist/js/`,
+ `${distFolder}/assets/js/`
])
sh.cp('-Rf', `${docsDir}/examples/*`, distFolder)
@@ -69,22 +77,44 @@ for (const file of imgFiles) {
sh.cp('-f', `${docsDir}/assets/brand/${file}`, `${distFolder}/assets/brand/`)
}
+for (const file of staticJsFiles) {
+ sh.cp('-f', `${docsDir}/assets/js/${file}`, `${distFolder}/assets/js/`)
+}
+
sh.rm(`${distFolder}/index.html`)
// get all examples' HTML files
-for (const file of sh.find(`${distFolder}/**/*.html`)) {
+const htmlFiles = sh.find(`${distFolder}/**/*.html`)
+
+const formatPromises = htmlFiles.map(async file => {
const fileContents = sh.cat(file)
.toString()
.replace(new RegExp(`"/docs/${versionShort}/`, 'g'), '"../')
.replace(/"..\/dist\//g, '"../assets/dist/')
- .replace(/( /g, '$1>')
- .replace(/(')
- .replace(/( +)