mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-11 09:51:40 +00:00
* fix(cli): allow esbuild builds in Vite templates * fix(cli): extend pnpm 11 build-script allowlists across app templates - Add packages: [] to single-app pnpm-workspace.yaml so pnpm 9 does not reject the file with "packages field missing or empty". - Add astro-app, react-router-app, start-app, next-app workspace yamls with the build-script allowlist each template needs (esbuild, sharp, unrs-resolver as applicable). - Set msw: false across all app allowlists so the registry component install runs cleanly under pnpm 11 without executing msw's service-worker postinstall. - Add a scaffold test pinning the packages:[] + allowBuilds shape so the parser keeps treating it as single-app. * chore: changeset * fix(templates): allow monorepo pnpm builds * ci(templates): validate app workspace conversion --------- Co-authored-by: shadcn <m@shadcn.com>
306 lines
9.9 KiB
YAML
306 lines
9.9 KiB
YAML
name: Templates
|
|
|
|
on:
|
|
pull_request:
|
|
branches: ["*"]
|
|
paths:
|
|
- ".github/workflows/templates.yml"
|
|
- "apps/v4/registry/**"
|
|
- "package.json"
|
|
- "packages/shadcn/src/commands/add.ts"
|
|
- "packages/shadcn/src/commands/init.ts"
|
|
- "packages/shadcn/src/templates/**"
|
|
- "packages/shadcn/src/utils/create-project.ts"
|
|
- "packages/shadcn/src/utils/get-monorepo-info.ts"
|
|
- "packages/shadcn/src/utils/get-package-manager.ts"
|
|
- "pnpm-lock.yaml"
|
|
- "templates/**"
|
|
|
|
jobs:
|
|
validate:
|
|
runs-on: ubuntu-latest
|
|
name: ${{ matrix.package-manager }} ${{ matrix.template }}
|
|
permissions:
|
|
contents: read
|
|
timeout-minutes: 45
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
template: [next, vite, astro, start, react-router]
|
|
package-manager: [pnpm, bun, npm, yarn]
|
|
env:
|
|
NEXT_PUBLIC_APP_URL: http://localhost:4000
|
|
NEXT_PUBLIC_V0_URL: https://v0.dev
|
|
REGISTRY_URL: http://localhost:4000/r
|
|
TEMPLATE_PNPM_VERSION: 10.33.4
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
|
|
- name: Install Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
|
|
- uses: pnpm/action-setup@v4
|
|
name: Install pnpm
|
|
id: pnpm-install
|
|
with:
|
|
version: 10.33.4
|
|
run_install: false
|
|
|
|
- name: Install Bun
|
|
uses: oven-sh/setup-bun@v2
|
|
|
|
- name: Install Yarn
|
|
if: matrix.package-manager == 'yarn'
|
|
run: |
|
|
corepack enable
|
|
COREPACK_ENABLE_PROJECT_SPEC=0 corepack prepare yarn@4.12.0 --activate
|
|
|
|
- name: Get pnpm store directory
|
|
id: pnpm-cache
|
|
run: |
|
|
echo "pnpm_cache_dir=$(pnpm store path)" >> "$GITHUB_OUTPUT"
|
|
|
|
- uses: actions/cache@v4
|
|
name: Setup pnpm cache
|
|
with:
|
|
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-pnpm-store-
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install
|
|
|
|
- name: Build packages
|
|
run: |
|
|
pnpm --filter=shadcn build
|
|
pnpm --filter=v4 registry:build
|
|
|
|
- name: Validate templates
|
|
env:
|
|
TEMPLATE: ${{ matrix.template }}
|
|
TEMPLATE_PACKAGE_MANAGER: ${{ matrix.package-manager }}
|
|
SHADCN_TEMPLATE_DIR: ${{ github.workspace }}/templates
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
root_pnpm="$(command -v pnpm)"
|
|
validation_script="$RUNNER_TEMP/validate-templates.sh"
|
|
|
|
cat > "$validation_script" <<'BASH'
|
|
set -euo pipefail
|
|
|
|
bin_dir="$RUNNER_TEMP/template-pnpm-bin"
|
|
mkdir -p "$bin_dir"
|
|
|
|
cat > "$bin_dir/pnpm" <<'PNPM'
|
|
#!/usr/bin/env bash
|
|
exec npx -y "pnpm@${TEMPLATE_PNPM_VERSION}" "$@"
|
|
PNPM
|
|
chmod +x "$bin_dir/pnpm"
|
|
export PATH="$bin_dir:$PATH"
|
|
|
|
echo "Using template pnpm $(pnpm --version)"
|
|
|
|
cli="$GITHUB_WORKSPACE/packages/shadcn/dist/index.js"
|
|
template_root="$RUNNER_TEMP/generated-template-${TEMPLATE_PACKAGE_MANAGER}-${TEMPLATE}"
|
|
rm -rf "$template_root"
|
|
mkdir -p "$template_root"
|
|
|
|
modes=(app monorepo)
|
|
|
|
has_script() {
|
|
node -e "const pkg = require('./package.json'); process.exit(pkg.scripts && pkg.scripts[process.argv[1]] ? 0 : 1)" "$1"
|
|
}
|
|
|
|
run_script_if_present() {
|
|
local script="$1"
|
|
if has_script "$script"; then
|
|
pnpm run "$script"
|
|
else
|
|
echo "No $script script found; skipping."
|
|
fi
|
|
}
|
|
|
|
validate_non_pnpm_project() {
|
|
local package_manager="$1"
|
|
local project_path="$2"
|
|
local check_workspace_protocol="$3"
|
|
local is_monorepo="$4"
|
|
|
|
cd "$project_path"
|
|
test ! -f pnpm-workspace.yaml
|
|
test ! -f pnpm-lock.yaml
|
|
|
|
EXPECTED_PACKAGE_MANAGER="$package_manager" \
|
|
CHECK_WORKSPACE_PROTOCOL="$check_workspace_protocol" \
|
|
IS_MONOREPO="$is_monorepo" \
|
|
node <<'NODE'
|
|
const fs = require("node:fs")
|
|
const path = require("node:path")
|
|
|
|
const expectedPackageManager = process.env.EXPECTED_PACKAGE_MANAGER
|
|
const checkWorkspaceProtocol =
|
|
process.env.CHECK_WORKSPACE_PROTOCOL === "true"
|
|
const isMonorepo = process.env.IS_MONOREPO === "true"
|
|
const pkg = JSON.parse(fs.readFileSync("package.json", "utf8"))
|
|
|
|
if (isMonorepo) {
|
|
const workspaces = pkg.workspaces ?? []
|
|
|
|
if (!Array.isArray(workspaces)) {
|
|
throw new Error("Expected package.json workspaces to be an array.")
|
|
}
|
|
|
|
if (workspaces.length === 0) {
|
|
throw new Error("Expected package.json workspaces to have entries.")
|
|
}
|
|
|
|
for (const workspace of ["sharp", "unrs-resolver", "esbuild"]) {
|
|
if (workspaces.includes(workspace)) {
|
|
throw new Error(`Unexpected workspace entry: ${workspace}`)
|
|
}
|
|
}
|
|
|
|
if (!pkg.packageManager?.startsWith(`${expectedPackageManager}@`)) {
|
|
throw new Error(
|
|
`Expected packageManager to use ${expectedPackageManager}, got ${pkg.packageManager}`
|
|
)
|
|
}
|
|
} else {
|
|
if (pkg.workspaces !== undefined) {
|
|
throw new Error("Did not expect package.json workspaces for app template.")
|
|
}
|
|
|
|
if (pkg.packageManager !== undefined) {
|
|
throw new Error(
|
|
`Did not expect packageManager for app template, got ${pkg.packageManager}`
|
|
)
|
|
}
|
|
}
|
|
|
|
if (checkWorkspaceProtocol) {
|
|
const packageJsonFiles = []
|
|
function collectPackageJsonFiles(dir) {
|
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
if (entry.name === "node_modules") {
|
|
continue
|
|
}
|
|
|
|
const fullPath = path.join(dir, entry.name)
|
|
if (entry.isDirectory()) {
|
|
collectPackageJsonFiles(fullPath)
|
|
} else if (entry.name === "package.json") {
|
|
packageJsonFiles.push(fullPath)
|
|
}
|
|
}
|
|
}
|
|
|
|
collectPackageJsonFiles(process.cwd())
|
|
|
|
for (const file of packageJsonFiles) {
|
|
const json = fs.readFileSync(file, "utf8")
|
|
if (json.includes("workspace:")) {
|
|
throw new Error(`Unexpected workspace: protocol in ${file}`)
|
|
}
|
|
}
|
|
}
|
|
NODE
|
|
}
|
|
|
|
for mode in "${modes[@]}"; do
|
|
project="test-${TEMPLATE}-${mode}-${TEMPLATE_PACKAGE_MANAGER}"
|
|
project_path="$template_root/$project"
|
|
|
|
echo "::group::${TEMPLATE} ${mode} ${TEMPLATE_PACKAGE_MANAGER}"
|
|
args=(
|
|
init
|
|
--defaults
|
|
--name "$project"
|
|
--template "$TEMPLATE"
|
|
--cwd "$template_root"
|
|
--silent
|
|
)
|
|
|
|
if [ "$mode" = "monorepo" ]; then
|
|
args+=(--monorepo)
|
|
is_monorepo="true"
|
|
else
|
|
args+=(--no-monorepo)
|
|
is_monorepo="false"
|
|
fi
|
|
|
|
case "$TEMPLATE_PACKAGE_MANAGER" in
|
|
pnpm)
|
|
SHADCN_TEMPLATE_DIR="$SHADCN_TEMPLATE_DIR" \
|
|
REGISTRY_URL="$REGISTRY_URL" \
|
|
npm_config_user_agent="pnpm/${TEMPLATE_PNPM_VERSION}" \
|
|
node "$cli" "${args[@]}"
|
|
|
|
cd "$project_path"
|
|
pnpm install --frozen-lockfile
|
|
run_script_if_present typecheck
|
|
run_script_if_present build
|
|
;;
|
|
bun)
|
|
(
|
|
cd "$template_root"
|
|
SHADCN_TEMPLATE_DIR="$SHADCN_TEMPLATE_DIR" \
|
|
REGISTRY_URL="$REGISTRY_URL" \
|
|
npm_config_user_agent="bun/$(bun --version)" \
|
|
bunx --bun --package "$GITHUB_WORKSPACE/packages/shadcn" \
|
|
shadcn "${args[@]}"
|
|
)
|
|
validate_non_pnpm_project \
|
|
"bun" \
|
|
"$project_path" \
|
|
"false" \
|
|
"$is_monorepo"
|
|
;;
|
|
npm)
|
|
(
|
|
cd "$template_root"
|
|
SHADCN_TEMPLATE_DIR="$SHADCN_TEMPLATE_DIR" \
|
|
REGISTRY_URL="$REGISTRY_URL" \
|
|
npm_config_user_agent="npm/$(npm --version)" \
|
|
npx --yes --package "$GITHUB_WORKSPACE/packages/shadcn" \
|
|
shadcn "${args[@]}"
|
|
)
|
|
validate_non_pnpm_project \
|
|
"npm" \
|
|
"$project_path" \
|
|
"true" \
|
|
"$is_monorepo"
|
|
;;
|
|
yarn)
|
|
(
|
|
cd "$template_root"
|
|
SHADCN_TEMPLATE_DIR="$SHADCN_TEMPLATE_DIR" \
|
|
REGISTRY_URL="$REGISTRY_URL" \
|
|
COREPACK_ENABLE_PROJECT_SPEC=0 \
|
|
npm_config_user_agent="yarn/$(COREPACK_ENABLE_PROJECT_SPEC=0 yarn --version)" \
|
|
yarn dlx --package "$GITHUB_WORKSPACE/packages/shadcn" \
|
|
shadcn "${args[@]}"
|
|
)
|
|
validate_non_pnpm_project \
|
|
"yarn" \
|
|
"$project_path" \
|
|
"false" \
|
|
"$is_monorepo"
|
|
;;
|
|
esac
|
|
|
|
echo "::endgroup::"
|
|
done
|
|
BASH
|
|
|
|
"$root_pnpm" exec start-server-and-test \
|
|
"$root_pnpm v4:dev" \
|
|
http://localhost:4000 \
|
|
"bash $validation_script"
|