diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 37e778c..b07fcd1 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,25 +5,25 @@ "features": { "ghcr.io/devcontainers/features/sshd:1": { "version": "latest" + }, + + "ghcr.io/devcontainers/features/docker-in-docker:4": { + "moby": "false" } }, "runArgs": ["--network=host"], "customizations": { "vscode": { "extensions": [ - "dbaeumer.vscode-eslint", - "esbenp.prettier-vscode", + "oxc.oxc-vscode", "oven.bun-vscode", "ms-azuretools.vscode-docker", "coderabbit.coderabbit-vscode", - "DeepScan.vscode-deepscan", "WakaTime.vscode-wakatime" ] } }, - "mounts": [ - "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" - ], + "remoteUser": "root", "postCreateCommand": "bun upd && bun bs --default" -} \ No newline at end of file +} diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index caf684c..0000000 --- a/.dockerignore +++ /dev/null @@ -1,62 +0,0 @@ -# Version control -.git -.gitignore -.github/ -.gitlab/ -.gitlab-ci.yml - -# Documentation -README.md -CHANGELOG.md -*.md -docs/ - -# Node.js -node_modules/ -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.npm -.yarn/ -bun.lockb - -# Build artifacts -dist/ -build/ -*.log - -# IDE and editor files -.vscode/ -.idea/ -*.swp -*.swo -*~ - -# OS generated files -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db - -# Temporary files -tmp/ -temp/ -*.tmp -*.temp - -# Scripts and tools (not needed in containers) -scripts/ -examples/ - -# CI/CD files -.github/ -.gitlab/ -*.yml -*.yaml - -# Package files (keep only what's needed) -package-lock.json -yarn.lock diff --git a/.github/actions/cleanup-images/action.yml b/.github/actions/cleanup-images/action.yml deleted file mode 100644 index 6d8ae9c..0000000 --- a/.github/actions/cleanup-images/action.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: "Cleanup Untagged Images" -description: "Cleans up untagged container images from registries" - -inputs: - affected-containers: - description: "Comma-separated list of affected containers" - required: true - github-token: - description: "GitHub token for API access" - required: true - repository-name: - description: "Repository name" - required: true - -runs: - using: "composite" - steps: - - name: Cleanup untagged images - shell: bash - env: - GITHUB_TOKEN: ${{ inputs.github-token }} - AFFECTED_CONTAINERS: ${{ inputs.affected-containers }} - REPOSITORY_NAME: ${{ inputs.repository-name }} - run: | - echo "๐Ÿงน Cleaning up untagged images..." - - chmod +x scripts/cleanup-untagged-images.sh - ./scripts/cleanup-untagged-images.sh || echo "โš ๏ธ Image cleanup failed, but continuing..." diff --git a/.github/actions/git-operations/action.yml b/.github/actions/git-operations/action.yml deleted file mode 100644 index 9240ab3..0000000 --- a/.github/actions/git-operations/action.yml +++ /dev/null @@ -1,128 +0,0 @@ -name: "Git Operations" -description: "Comprehensive git operations: configure, stage, commit, and push changes" - -inputs: - github-token: - description: "GitHub token for pushing changes" - required: false - files-to-stage: - description: "Space or newline-separated list of files to stage (e.g., 'README.md CHANGELOG.md')" - required: false - default: "" - commit-message: - description: "Primary commit message" - required: false - default: "" - commit-body: - description: "Additional commit message body (multi-line supported)" - required: false - default: "" - version-map: - description: "JSON map of container versions for auto-generating commit message" - required: false - default: "{}" - skip-if-no-changes: - description: "Skip commit if no changes detected" - required: false - default: "true" - auto-pull-before-push: - description: "Pull remote changes before pushing" - required: false - default: "true" - branch: - description: "Branch to push to" - required: false - default: "main" - config-only: - description: "Only configure git, skip stage/commit/push operations" - required: false - default: "false" - -runs: - using: "composite" - steps: - - name: Configure Git user - shell: bash - run: | - git config --global user.email "${{ github.repository_owner_id }}+${{ github.repository_owner }}@users.noreply.github.com" - git config --global user.name "${{ github.actor }}" - - - name: Stage files - if: inputs.config-only != 'true' && inputs.files-to-stage != '' - shell: bash - run: | - echo "๐Ÿ“‹ Staging files..." - for file in ${{ inputs.files-to-stage }}; do - git add "$file" 2>/dev/null || true - done - - - name: Commit and push changes - if: inputs.config-only != 'true' && inputs.commit-message != '' - shell: bash - env: - GITHUB_TOKEN: ${{ inputs.github-token }} - VERSION_MAP: ${{ inputs.version-map }} - COMMIT_MSG: ${{ inputs.commit-message }} - COMMIT_BODY: ${{ inputs.commit-body }} - SKIP_IF_NO_CHANGES: ${{ inputs.skip-if-no-changes }} - AUTO_PULL: ${{ inputs.auto-pull-before-push }} - BRANCH: ${{ inputs.branch }} - run: | - echo "๐Ÿ“ Processing git commit..." - - # Check if there are any changes to commit - if git diff --quiet && git diff --staged --quiet; then - echo "โ„น๏ธ No changes detected" - if [ "$SKIP_IF_NO_CHANGES" = "true" ]; then - echo "โญ๏ธ Skipping commit (skip-if-no-changes=true)" - exit 0 - fi - fi - - # Prepare commit message - FINAL_COMMIT_MSG="$COMMIT_MSG" - - # If commit message is empty but version-map is provided, generate message - if [ -z "$FINAL_COMMIT_MSG" ] && [ "$VERSION_MAP" != "{}" ]; then - HIGHEST_VERSION=$(echo "$VERSION_MAP" | jq -r 'to_entries | max_by(.value | split(".") | map(tonumber)) | .value' 2>/dev/null || echo "") - if [ -n "$HIGHEST_VERSION" ]; then - FINAL_COMMIT_MSG="docs: update documentation for release v$HIGHEST_VERSION [skip ci]" - else - FINAL_COMMIT_MSG="docs: update documentation [skip ci]" - fi - fi - - # Commit changes - if [ -n "$COMMIT_BODY" ]; then - git commit -m "$FINAL_COMMIT_MSG" -m "$COMMIT_BODY" - else - git commit -m "$FINAL_COMMIT_MSG" - fi - - echo "โœ… Changes committed: $FINAL_COMMIT_MSG" - - # Pull before push if enabled - if [ "$AUTO_PULL" = "true" ]; then - echo "๐Ÿ”„ Pulling remote changes to avoid conflicts..." - - # Stage any remaining changes before pulling - git add -A - if ! git diff --staged --quiet; then - echo "๐Ÿ“ Amending commit with additional changes..." - git commit --amend --no-edit - fi - - # Try rebase first, fallback to merge if it fails - if ! git pull --rebase origin "$BRANCH"; then - echo "โš ๏ธ Pull with rebase failed. Trying merge strategy..." - # Check if rebase is in progress before aborting - if git status | grep -q "rebase in progress"; then - git rebase --abort - fi - git pull origin "$BRANCH" --no-edit - fi - fi - - # Push changes - git push origin "$BRANCH" - echo "โœ… Changes pushed to $BRANCH branch" diff --git a/.github/actions/notes/action.yml b/.github/actions/notes/action.yml new file mode 100644 index 0000000..5035cde --- /dev/null +++ b/.github/actions/notes/action.yml @@ -0,0 +1,100 @@ +name: 'Finalize Release Notes' +description: 'Downloads image metadata, updates the README sizes, prepends the CHANGELOG, and creates a GitHub Release.' + +inputs: + tag_name: + description: 'The tag for this release (e.g., v1.0.0)' + required: true + +runs: + using: 'composite' + steps: + - name: Download Metadata Artifacts + uses: actions/download-artifact@v4.3.4 + with: + pattern: metadata-* + merge-multiple: true + path: metadata/ + + - name: Build Notes, Update README & CHANGELOG + shell: bash + env: + NEW_TAG: ${{ inputs.tag_name }} + REPO: ${{ github.repository }} + OWNER: ${{ github.repository_owner }} + run: | + echo "## [$NEW_TAG] - $(date +'%Y-%m-%d')" > release_notes.md + echo "" >> release_notes.md + echo "All notable changes to this project are documented below. This release contains updates for the specific environments listed." >> release_notes.md + echo "" >> release_notes.md + + echo "### Released Environments" >> release_notes.md + echo "" >> release_notes.md + echo "| Container | Version | Date | Registry Links |" >> release_notes.md + echo "|---|---|---|---|" >> release_notes.md + + # 1. Update README sizes and build changelog table rows + for size_file in metadata/*.size; do + if [ -f "$size_file" ]; then + IMG_NAME=$(basename "$size_file" .size) + SIZE_VAL=$(cat "$size_file") + + # Add row to release notes + echo "| $IMG_NAME | $NEW_TAG | $(date +'%Y-%m-%d') | [GHCR](https://ghcr.io/$REPO/$IMG_NAME:latest) ยท [Docker Hub](https://hub.docker.com/r/vikshan/$IMG_NAME) |" >> release_notes.md + + # Smart replace the size in the README table inline + sed -i -E "s/\|[[:space:]]*\*\*$IMG_NAME\*\*[[:space:]]*\|[[:space:]]*[^|]+[[:space:]]*\|/\| **$IMG_NAME** \| $SIZE_VAL \|/" README.md + fi + done + + echo "" >> release_notes.md + echo "### Environment Tool Versions" >> release_notes.md + echo "" >> release_notes.md + + # 2. Append Ubuntu Tool Versions (Read directly from Skopeo-extracted OCI labels) + echo "#### Ubuntu-Based Environments" >> release_notes.md + echo '```properties' >> release_notes.md + UBUNTU_FALLBACK=$(find metadata -name "ubuntu*.versions" | head -n 1) + if [ -f "metadata/ubuntu-bun-node.versions" ]; then + cat metadata/ubuntu-bun-node.versions >> release_notes.md + elif [ -n "$UBUNTU_FALLBACK" ]; then + cat "$UBUNTU_FALLBACK" >> release_notes.md + else + echo "# No Ubuntu tools updated." >> release_notes.md + fi + echo '```' >> release_notes.md + echo "" >> release_notes.md + + # 3. Append Alpine Tool Versions (strictly targeting files without 'ubuntu' in the name) + echo "#### Alpine-Based Environments" >> release_notes.md + echo '```properties' >> release_notes.md + ALPINE_FALLBACK=$(find metadata -name "*.versions" ! -name "*ubuntu*" | head -n 1) + if [ -f "metadata/bun-node.versions" ]; then + cat metadata/bun-node.versions >> release_notes.md + elif [ -n "$ALPINE_FALLBACK" ]; then + cat "$ALPINE_FALLBACK" >> release_notes.md + else + echo "# No Alpine tools updated." >> release_notes.md + fi + echo '```' >> release_notes.md + echo "" >> release_notes.md + + # 4. Prepend to CHANGELOG.md safely + cat release_notes.md CHANGELOG.md > temp_changelog.md && mv temp_changelog.md CHANGELOG.md + + # 5. Update variants and setup documentation + python3 .github/actions/notes/update_docs.py + + - name: Commit CHANGELOG, README & Docs + id: auto_commit + uses: stefanzweifel/git-auto-commit-action@v7.1.0 + with: + commit_message: 'docs: update CHANGELOG, README, and variants documentation for ${{ inputs.tag_name }} [skip ci]' + file_pattern: 'CHANGELOG.md README.md docs/IMAGE_VARIANTS.md docs/SETUP.md' + + - name: Publish GitHub Release + uses: softprops/action-gh-release@v2.0.8 + with: + tag_name: ${{ inputs.tag_name }} + name: Release ${{ inputs.tag_name }} + body_path: release_notes.md diff --git a/.github/actions/notes/update_docs.py b/.github/actions/notes/update_docs.py new file mode 100644 index 0000000..60f0efe --- /dev/null +++ b/.github/actions/notes/update_docs.py @@ -0,0 +1,137 @@ +import os +import re + +def update_docs(): + sizes = {} + versions = {} + if not os.path.exists('metadata'): + print("No metadata directory found.") + return + + # 1. Read sizes and versions from metadata + for f in os.listdir('metadata'): + if f.endswith('.size'): + name = f.split('.')[0] + with open(os.path.join('metadata', f), 'r') as fh: + sizes[name] = fh.read().strip() + elif f.endswith('.versions'): + name = f.split('.')[0] + versions[name] = {} + with open(os.path.join('metadata', f), 'r') as fh: + for line in fh: + if '=' in line: + k, v = line.strip().split('=', 1) + versions[name][k] = v + + print(f"Loaded image sizes: {sizes}") + print(f"Loaded image versions: {versions}") + + # 2. Update docs/SETUP.md + if os.path.exists('docs/SETUP.md'): + print("Updating docs/SETUP.md...") + with open('docs/SETUP.md', 'r') as fh: + content = fh.read() + + for name, size in sizes.items(): + pattern = rf"(\|[ \t]*\*\*{name}\*\*[ \t]*\|[^|]+\|[^|]+\|)[ \t]*~?[^|]+[ \t]*\|" + content = re.sub(pattern, rf"\g<1> ~{size} |", content) + + with open('docs/SETUP.md', 'w') as fh: + fh.write(content) + + # 3. Update docs/IMAGE_VARIANTS.md + if os.path.exists('docs/IMAGE_VARIANTS.md'): + print("Updating docs/IMAGE_VARIANTS.md...") + with open('docs/IMAGE_VARIANTS.md', 'r') as fh: + content = fh.read() + + # Update Comparison Table Rows + header_match = re.search(r"\|[ \t]*Feature[ \t]*\|([ \t]*[a-zA-Z0-9_-]+[ \t]*\|)+", content) + if header_match: + headers = [h.strip() for h in header_match.group(0).split('|')[2:-1]] + + # A. Update Size Row + existing_sizes = {} + size_match = re.search(r"\|[ \t]*\*\*Size\*\*[ \t]*\|(.*)", content) + if size_match: + parts = size_match.group(1).split('|') + for idx, h in enumerate(headers): + if idx < len(parts): + existing_sizes[h] = parts[idx].strip() + + size_row = "| **Size** " + for h in headers: + if h in sizes: + size_row += f"| ~{sizes[h]} " + elif h in existing_sizes: + size_row += f"| {existing_sizes[h]} " + else: + size_row += "| - " + size_row += "|" + size_pattern = r"\|[ \t]*\*\*Size\*\*[ \t]*\|" + "".join(r"[^|]+\|" for _ in range(len(headers))) + content = re.sub(size_pattern, size_row, content) + + # B. Update Bun Version Row + existing_buns = {} + bun_match = re.search(r"\|[ \t]*\*\*Bun Version\*\*[ \t]*\|(.*)", content) + if bun_match: + parts = bun_match.group(1).split('|') + for idx, h in enumerate(headers): + if idx < len(parts): + existing_buns[h] = parts[idx].strip() + + bun_row = "| **Bun Version** " + for h in headers: + if h in versions and 'bun' in versions[h]: + bun_row += f"| {versions[h]['bun']} " + elif h in existing_buns: + bun_row += f"| {existing_buns[h]} " + else: + bun_row += "| โŒ " + bun_row += "|" + bun_pattern = r"\|[ \t]*\*\*Bun Version\*\*[ \t]*\|" + "".join(r"[^|]+\|" for _ in range(len(headers))) + content = re.sub(bun_pattern, bun_row, content) + + # C. Update Node.js Row + existing_nodes = {} + node_match = re.search(r"\|[ \t]*\*\*Node\.js\*\*[ \t]*\|(.*)", content) + if node_match: + parts = node_match.group(1).split('|') + for idx, h in enumerate(headers): + if idx < len(parts): + existing_nodes[h] = parts[idx].strip() + + node_row = "| **Node.js** " + for h in headers: + if h in versions and 'node' in versions[h]: + node_row += f"| โœ… v{versions[h]['node']} " + elif h in existing_nodes: + node_row += f"| {existing_nodes[h]} " + else: + node_row += "| โŒ " + node_row += "|" + node_pattern = r"\|[ \t]*\*\*Node\.js\*\*[ \t]*\|" + "".join(r"[^|]+\|" for _ in range(len(headers))) + content = re.sub(node_pattern, node_row, content) + + # Update other occurrences + for name, size in sizes.items(): + # Match: ### X. name (~size) + content = re.sub(rf"(###[ \t]+[0-9]+\.[ \t]+{name}[ \t]+\()[^)]+(\))", rf"\g<1>~{size}\g<2>", content) + # Match: **name** (~size) or **name** (size) + content = re.sub(rf"(\*\*{name}\*\*[ \t]+\()[^)]+(\))", rf"\g<1>~{size}\g<2>", content) + # Match: `name` (size) or `name` (~size) + content = re.sub(rf"(`{name}`[ \t]+\()[^)]+(\))", rf"\g<1>{size}\g<2>", content) + + # Specific lists / descriptions + if name == 'ubuntu-bun': + content = re.sub(r"Smallest size - Only [0-9]+ MB!", f"Smallest size - Only {size}!", content) + elif name == 'ubuntu-bun-node': + content = re.sub(r"Balanced size - Feature-rich at only [0-9]+ MB", f"Balanced size - Feature-rich at only {size}", content) + + with open('docs/IMAGE_VARIANTS.md', 'w') as fh: + fh.write(content) + + print("Documentation updated successfully.") + +if __name__ == '__main__': + update_docs() diff --git a/.github/actions/update-docs/action.yml b/.github/actions/update-docs/action.yml deleted file mode 100644 index 4f93012..0000000 --- a/.github/actions/update-docs/action.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: "Update Documentation" -description: "Updates README files with image sizes, commits, and pushes changes" - -inputs: - gitlab-token: - description: "GitLab token" - required: false - github-token: - description: "GitHub token" - required: true - dockerhub-token: - description: "DockerHub token" - required: false - version-map: - description: "JSON map of container versions for commit message" - required: false - default: "{}" - skip-commit: - description: "Skip commit and push steps (for testing)" - required: false - default: "false" - -runs: - using: "composite" - steps: - - name: Update image sizes - shell: bash - env: - GITLAB_TOKEN: ${{ inputs.gitlab-token }} - GH_TOKEN: ${{ inputs.github-token }} - DOCKERHUB_TOKEN: ${{ inputs.dockerhub-token }} - run: | - bun run s && bun run f - - - name: Commit and push documentation changes - if: inputs.skip-commit != 'true' - uses: ./.github/actions/git-operations - with: - github-token: ${{ inputs.github-token }} - files-to-stage: "container-versions.json README.md CHANGELOG.md docs/IMAGE_VARIANTS.md" - commit-message: "chore: update versions and documentation [skip ci]" - version-map: ${{ inputs.version-map }} - commit-body: |- - - Updated container-versions.json with tool versions cache - - Updated CHANGELOG.md with container versions and tool information - - Updated README files with latest image sizes - skip-if-no-changes: "true" - auto-pull-before-push: "true" - branch: "main" diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..a9b2e6a --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,70 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended", ":dependencyDashboard", ":semanticCommits"], + "reviewers": ["iamvikshan"], + "assignees": ["iamvikshan"], + "minimumReleaseAge": "3 days", + "semanticCommitType": "chore", + "baseBranchPatterns": ["main"], + "prConcurrentLimit": 10, + "prHourlyLimit": 5, + "labels": ["dependencies"], + "schedule": ["before 4am every weekday"], + "timezone": "Africa/Nairobi", + "dependencyDashboardApproval": false, + "rebaseWhen": "conflicted", + "rangeStrategy": "bump", + "separateMinorPatch": true, + "separateMajorMinor": true, + "pinDigests": false, + + "customManagers": [ + { + "description": "Update tool versions defined as ARGs in Dockerfiles", + "customType": "regex", + "fileMatch": ["(^|/)Dockerfile$"], + "matchStrings": [ + "#\\s*renovate:\\s*datasource=(?[a-zA-Z0-9.-]+)\\s*depName=(?[^\\s]+)(?:\\s+versioning=(?[a-zA-Z0-9.-]+))?\\n(?:ENV|ARG)\\s+[A-Z0-9_]+_VERSION=\"(?[^\"]+)\"" + ] + } + ], + + "packageRules": [ + { + "description": "Major version updates require manual review (Overridden by Docker rule below)", + "matchUpdateTypes": ["major"], + "automerge": false, + "labels": ["major"] + }, + { + "description": "Oxfmt configuration (replaces Prettier)", + "matchPackagePatterns": ["^oxfmt", "^@oxfmt/", "^eslint-plugin-oxfmt$"], + "groupName": "oxfmt", + "labels": ["formatter", "oxfmt"] + }, + { + "description": "Group and automerge all Docker bases and Container tool updates", + "matchFileNames": ["**/Dockerfile", "images/**/Dockerfile"], + "groupName": "docker-environments", + "labels": ["docker", "environments", "automerge"], + "automerge": true, + "minimumReleaseAge": "0 days" + }, + { + "description": "Ensure GitHub Actions use tags instead of SHA digests", + "matchManagers": ["github-actions"], + "pinDigests": false + } + ], + + "vulnerabilityAlerts": { + "enabled": true, + "labels": ["security"] + }, + "lockFileMaintenance": { + "enabled": true, + "minimumReleaseAge": "0 days", + "schedule": ["before 4am on monday"] + }, + "osvVulnerabilityAlerts": true +} diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 2abd63b..0e8ef34 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -10,4 +10,4 @@ jobs: run-cla: uses: iamvikshan/.github/.github/workflows/cla.yml@main secrets: - token: ${{ secrets.GH_TOKEN }} \ No newline at end of file + token: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index a4081d6..cf64b7c 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -2,136 +2,129 @@ name: Release DevContainers on: schedule: - # Run daily at 5 AM UTC to check for base image and tool updates - - cron: "0 5 * * *" + - cron: '0 0 * * 0' # Weekly forced release (Sunday at midnight) workflow_dispatch: inputs: - trigger_reason: - description: "Reason for manual trigger (optional)" + tag_override: + description: 'Override auto-tag (e.g., v1.0.5) - leave empty for auto-increment' required: false - default: "" - type: string - version: - description: "Override version (e.g., 0.0.5) - leave empty for auto-increment" - required: false - default: "" + default: '' type: string push: branches: [main] + paths: + - 'images/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false env: - GITHUB_REGISTRY: ghcr.io - GITLAB_REGISTRY: registry.gitlab.com GL_USERNAME: vikshan + # Hardcoded repository string based on your README to ensure registry paths are perfect + GHCR_BASE: ghcr.io/${{ github.repository }} jobs: # ============================================ - # Job 1: Analyze what needs to be released + # Job 1: Auto-Tagging & Matrix Generation # ============================================ - analyze: - name: Analyze Release + prepare: + name: Prepare Release runs-on: ubuntu-latest permissions: contents: write outputs: - should_release: ${{ steps.release_analysis.outputs.should_release }} - release_type: ${{ steps.release_analysis.outputs.release_type }} - affected_containers: ${{ steps.release_analysis.outputs.affected_containers }} - version_map: ${{ steps.release_analysis.outputs.version_map }} - matrix: ${{ steps.set_matrix.outputs.matrix }} - + new_tag: ${{ steps.tag.outputs.new_tag }} + bases: ${{ steps.matrix.outputs.bases }} + derivatives: ${{ steps.matrix.outputs.derivatives }} steps: - name: Checkout - uses: actions/checkout@v6.0.3 - with: - fetch-depth: 0 - token: ${{ secrets.GH_TOKEN }} + uses: actions/checkout@v4.1.7 - - name: Setup Bun and install dependencies - uses: iamvikshan/.github/.github/actions/bun@main + - name: Auto-bump Tag + id: auto_tag + if: github.event.inputs.tag_override == '' + uses: mathieudutour/github-tag-action@v6.2 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + default_bump: patch + dry_run: true - - name: Release analysis - id: release_analysis + - name: Set Final Tag + id: tag env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} - GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }} - DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} + TAG_OVERRIDE: ${{ github.event.inputs.tag_override }} + AUTO_TAG: ${{ steps.auto_tag.outputs.new_tag }} run: | - echo "๐Ÿ” Analyzing changes for release..." - - # Determine trigger type - TRIGGER="push" - VERSION_OVERRIDE="" - if [ "${{ github.event_name }}" = "schedule" ]; then - TRIGGER="schedule" - elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - if [[ "${{ github.event.inputs.trigger_reason }}" == *"Base image"* ]] || [[ "${{ github.event.inputs.trigger_reason }}" == *"base image"* ]]; then - TRIGGER="base-image-update" - echo "๐Ÿ”„ Detected base image update trigger" + if [ -n "$TAG_OVERRIDE" ]; then + if echo "$TAG_OVERRIDE" | grep -Eq '^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$'; then + echo "new_tag=$TAG_OVERRIDE" >> $GITHUB_OUTPUT else - TRIGGER="manual" - fi - if [ -n "${{ github.event.inputs.version }}" ]; then - VERSION_OVERRIDE="${{ github.event.inputs.version }}" - echo "๏ฟฝ๏ฟฝ Version override: $VERSION_OVERRIDE" + echo "Error: Invalid tag override format '$TAG_OVERRIDE'" >&2 + exit 1 fi + else + echo "new_tag=$AUTO_TAG" >> $GITHUB_OUTPUT fi - # Build command with optional version override - CMD="bun scripts/releaseOrchestrator.ts --trigger=$TRIGGER --workflow" - if [ -n "$VERSION_OVERRIDE" ]; then - CMD="$CMD --version=$VERSION_OVERRIDE" - fi + - name: Detect Changes + id: filter + uses: dorny/paths-filter@v3.0.2 + with: + filters: | + bun: "images/bun/**" + ubuntu_tools: "images/ubuntu-tools/**" + bun_node: "images/bun-node/**" + ubuntu_bun: "images/ubuntu-bun/**" + ubuntu_bun_node: "images/ubuntu-bun-node/**" - # Run release orchestrator in workflow mode - RESULT=$($CMD) - echo "๐Ÿ“‹ Release analysis result: $RESULT" - - # Parse the JSON result - SUCCESS=$(echo "$RESULT" | jq -r '.success') - SHOULD_RELEASE=$(echo "$RESULT" | jq -r '.outputs.should_release') - RELEASE_TYPE=$(echo "$RESULT" | jq -r '.outputs.release_type') - AFFECTED_CONTAINERS=$(echo "$RESULT" | jq -r '.outputs.affected_containers') - VERSION_MAP=$(echo "$RESULT" | jq -r '.outputs.version_map') - - echo "should_release=$SHOULD_RELEASE" >> $GITHUB_OUTPUT - echo "release_type=$RELEASE_TYPE" >> $GITHUB_OUTPUT - echo "affected_containers=$AFFECTED_CONTAINERS" >> $GITHUB_OUTPUT - echo "version_map=$VERSION_MAP" >> $GITHUB_OUTPUT - - if [ "$SUCCESS" = "true" ] && [ "$SHOULD_RELEASE" = "true" ]; then - echo "๐Ÿš€ Release needed: $RELEASE_TYPE" - echo "๐Ÿ“ฆ Affected containers: $AFFECTED_CONTAINERS" + - name: Construct DAG Matrices + id: matrix + run: | + BASES=() + DERIVATIVES=() + + # Force build ALL if scheduled or manually dispatched + if [ "${{ github.event_name }}" == "schedule" ] || [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + BASES=("bun" "ubuntu-tools") + DERIVATIVES=("bun-node" "ubuntu-bun" "ubuntu-bun-node") else - if [ "$SUCCESS" = "false" ]; then - ERROR=$(echo "$RESULT" | jq -r '.error // "Unknown error"') - echo "โŒ Release analysis failed: $ERROR" - else - echo "โ„น๏ธ No release needed" + if [ "${{ steps.filter.outputs.bun }}" == "true" ]; then + BASES+=("bun") + fi + if [ "${{ steps.filter.outputs.ubuntu_tools }}" == "true" ]; then + BASES+=("ubuntu-tools") fi - fi - - name: Set build matrix - id: set_matrix - if: steps.release_analysis.outputs.should_release == 'true' - run: | - # Convert comma-separated containers to JSON array for matrix - CONTAINERS="${{ steps.release_analysis.outputs.affected_containers }}" - if [ -n "$CONTAINERS" ]; then - MATRIX=$(echo "$CONTAINERS" | jq -R 'split(",") | map(gsub("^\\s+|\\s+$"; ""))' -c) - echo "matrix=$MATRIX" >> $GITHUB_OUTPUT - echo "๐Ÿ“ฆ Build matrix: $MATRIX" - else - echo "matrix=[]" >> $GITHUB_OUTPUT + # Enqueue derivatives if their bases or they themselves changed + if [ "${{ steps.filter.outputs.bun }}" == "true" ] || [ "${{ steps.filter.outputs.bun_node }}" == "true" ]; then + DERIVATIVES+=("bun-node") + fi + if [ "${{ steps.filter.outputs.ubuntu_tools }}" == "true" ] || [ "${{ steps.filter.outputs.ubuntu_bun }}" == "true" ]; then + DERIVATIVES+=("ubuntu-bun") + fi + if [ "${{ steps.filter.outputs.ubuntu_tools }}" == "true" ] || [ "${{ steps.filter.outputs.ubuntu_bun_node }}" == "true" ]; then + DERIVATIVES+=("ubuntu-bun-node") + fi fi + # Convert bash arrays to JSON strings for GitHub Actions + BASES_JSON=$(printf '%s\n' "${BASES[@]}" | jq -R . | jq -cs .) + [ "${#BASES[@]}" -eq 0 ] && BASES_JSON="[]" + + DERIVATIVES_JSON=$(printf '%s\n' "${DERIVATIVES[@]}" | jq -R . | jq -cs .) + [ "${#DERIVATIVES[@]}" -eq 0 ] && DERIVATIVES_JSON="[]" + + echo "bases=$BASES_JSON" >> $GITHUB_OUTPUT + echo "derivatives=$DERIVATIVES_JSON" >> $GITHUB_OUTPUT + # ============================================ - # Job 2: Build and push images (matrix) + # Job 2: Build Base Images First # ============================================ - build: - name: Build ${{ matrix.image }} - needs: analyze - if: needs.analyze.outputs.should_release == 'true' + build-bases: + name: Base -> ${{ matrix.image }} + needs: prepare + if: ${{ needs.prepare.outputs.bases != '[]' && needs.prepare.outputs.bases != '' }} runs-on: ubuntu-latest permissions: contents: read @@ -139,132 +132,235 @@ jobs: strategy: fail-fast: false matrix: - image: ${{ fromJson(needs.analyze.outputs.matrix) }} + image: ${{ fromJSON(needs.prepare.outputs.bases) }} steps: - name: Checkout - uses: actions/checkout@v6.0.3 - - - name: Get version for this image - id: version - run: | - VERSION=$(echo '${{ needs.analyze.outputs.version_map }}' | jq -r '.["${{ matrix.image }}"] // "latest"') - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "๐Ÿ“ฆ Building ${{ matrix.image }}:v$VERSION" - - - name: Set up QEMU - uses: docker/setup-qemu-action@v4.1.0 + uses: actions/checkout@v4.1.7 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4.1.0 + - name: Set up QEMU & Buildx + uses: docker/setup-qemu-action@v3.2.0 + - uses: docker/setup-buildx-action@v3.6.1 - # Login to all 3 registries - - name: Log in to GitHub Container Registry - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + - name: Log in to Registries + uses: docker/login-action@v3.3.0 with: registry: ghcr.io username: ${{ github.actor }} - password: ${{ secrets.GH_TOKEN }} - - - name: Log in to GitLab Container Registry - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + password: ${{ secrets.GITHUB_TOKEN }} + - uses: docker/login-action@v3.3.0 with: registry: registry.gitlab.com username: oauth2 password: ${{ secrets.GITLAB_TOKEN }} - - - name: Log in to Docker Hub - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + - uses: docker/login-action@v3.3.0 with: username: ${{ env.GL_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Extract metadata id: meta - uses: docker/metadata-action@v6.1.0 + uses: docker/metadata-action@v5.5.1 with: images: | - ghcr.io/${{ github.repository }}/${{ matrix.image }} + ${{ env.GHCR_BASE }}/${{ matrix.image }} registry.gitlab.com/${{ env.GL_USERNAME }}/devcontainers/${{ matrix.image }} ${{ env.GL_USERNAME }}/${{ matrix.image }} tags: | - type=raw,value=v${{ steps.version.outputs.version }} + type=raw,value=${{ needs.prepare.outputs.new_tag }} type=raw,value=latest - - name: Build and push - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + - name: Build and Push + uses: docker/build-push-action@v6.5.0 with: context: images/${{ matrix.image }} platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - provenance: false + provenance: mode=min + + - name: Extract Size & Tool Versions via Skopeo + run: | + mkdir -p metadata + # Wait for image to propagate to GHCR to avoid race conditions + sleep 5 + + # 1. Extract Size from remote manifest + skopeo inspect docker://${{ env.GHCR_BASE }}/${{ matrix.image }}:${{ needs.prepare.outputs.new_tag }} | \ + jq -r '[.LayersData[] | .Size] | add / 1048576 | floor | tostring + " MB"' > metadata/${{ matrix.image }}.size + + # 2. Extract specific devcontainer.tool.* OCI Labels + skopeo inspect docker://${{ env.GHCR_BASE }}/${{ matrix.image }}:${{ needs.prepare.outputs.new_tag }} | \ + jq -r '.Labels | to_entries[] | select(.key | startswith("devcontainer.tool.")) | "\(.key | sub("devcontainer\\.tool\\."; ""))=\(.value)"' > metadata/${{ matrix.image }}.versions + + - name: Upload Metadata + uses: actions/upload-artifact@v4.3.4 + with: + name: metadata-${{ matrix.image }} + path: metadata/ + retention-days: 1 # ============================================ - # Job 3: Finalize (changelog, docs, cleanup) + # Job 3: Build Derivative Images # ============================================ - finalize: - name: Finalize Release - needs: [analyze, build] - if: needs.analyze.outputs.should_release == 'true' + build-derivatives: + name: Deriv -> ${{ matrix.image }} + needs: [prepare, build-bases] + if: ${{ !failure() && !cancelled() && needs.prepare.outputs.derivatives != '[]' && needs.prepare.outputs.derivatives != '' }} runs-on: ubuntu-latest permissions: - contents: write + contents: read packages: write + strategy: + fail-fast: false + matrix: + image: ${{ fromJSON(needs.prepare.outputs.derivatives) }} steps: + - name: Wait for base images to propagate + run: sleep 10 + - name: Checkout - uses: actions/checkout@v6.0.3 + uses: actions/checkout@v4.1.7 + + - name: Set up QEMU & Buildx + uses: docker/setup-qemu-action@v3.2.0 + - uses: docker/setup-buildx-action@v3.6.1 + + - name: Log in to Registries + uses: docker/login-action@v3.3.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: docker/login-action@v3.3.0 with: - fetch-depth: 0 - token: ${{ secrets.GH_TOKEN }} + registry: registry.gitlab.com + username: oauth2 + password: ${{ secrets.GITLAB_TOKEN }} + - uses: docker/login-action@v3.3.0 + with: + username: ${{ env.GL_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Setup Bun and install dependencies - uses: iamvikshan/.github/.github/actions/bun@main + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5.5.1 + with: + images: | + ${{ env.GHCR_BASE }}/${{ matrix.image }} + registry.gitlab.com/${{ env.GL_USERNAME }}/devcontainers/${{ matrix.image }} + ${{ env.GL_USERNAME }}/${{ matrix.image }} + tags: | + type=raw,value=${{ needs.prepare.outputs.new_tag }} + type=raw,value=latest - - name: Cache tool versions - run: | - echo "๐Ÿ’พ Caching tool versions to container-versions.json..." - AFFECTED="${{ needs.analyze.outputs.affected_containers }}" - bun scripts/toolVersionExtractor.ts --local --containers="$AFFECTED" --save-to-cache || echo "โš ๏ธ Failed to cache tool versions, continuing..." + - name: Build and Push + uses: docker/build-push-action@v6.5.0 + with: + context: images/${{ matrix.image }} + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + cache-from: type=gha + cache-to: type=gha,mode=max + provenance: mode=min - - name: Update CHANGELOG.md - env: - VERSION_MAP: ${{ needs.analyze.outputs.version_map }} + - name: Extract Size & Tool Versions via Skopeo run: | - echo "๐Ÿ“ Updating CHANGELOG.md with new release versions..." - bun scripts/changelogManager.ts --update-table --version-map="$VERSION_MAP" || echo "โš ๏ธ Failed to update CHANGELOG.md, continuing..." + mkdir -p metadata + # Wait for image to propagate to GHCR to avoid race conditions + sleep 5 - - name: Update documentation - continue-on-error: true - uses: ./.github/actions/update-docs + # 1. Extract Size from remote manifest + skopeo inspect docker://${{ env.GHCR_BASE }}/${{ matrix.image }}:${{ needs.prepare.outputs.new_tag }} | \ + jq -r '[.LayersData[] | .Size] | add / 1048576 | floor | tostring + " MB"' > metadata/${{ matrix.image }}.size + + # 2. Extract specific devcontainer.tool.* OCI Labels + skopeo inspect docker://${{ env.GHCR_BASE }}/${{ matrix.image }}:${{ needs.prepare.outputs.new_tag }} | \ + jq -r '.Labels | to_entries[] | select(.key | startswith("devcontainer.tool.")) | "\(.key | sub("devcontainer\\.tool\\."; ""))=\(.value)"' > metadata/${{ matrix.image }}.versions + + - name: Upload Metadata + uses: actions/upload-artifact@v4.3.4 with: - gitlab-token: ${{ secrets.GITLAB_TOKEN }} - github-token: ${{ secrets.GH_TOKEN }} - dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} - version-map: ${{ needs.analyze.outputs.version_map }} - - - name: Cleanup untagged images - continue-on-error: true - uses: ./.github/actions/cleanup-images + name: metadata-${{ matrix.image }} + path: metadata/ + retention-days: 1 + + # ============================================ + # Job 4: Finalize Changelog & Documentation + # ============================================ + finalize: + name: Finalize Release Notes + needs: [prepare, build-bases, build-derivatives] + if: ${{ !failure() && !cancelled() && (needs.prepare.outputs.bases != '[]' || needs.prepare.outputs.derivatives != '[]') }} + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v4.1.7 + + # Uses our custom script to handle sizes, tools, README, CHANGELOG, and GH Release + - name: Execute Notes Action + uses: ./.github/actions/notes with: - affected-containers: ${{ needs.analyze.outputs.affected_containers }} - github-token: ${{ secrets.GH_TOKEN }} - repository-name: ${{ github.event.repository.name }} + tag_name: ${{ needs.prepare.outputs.new_tag }} + + - name: Upload updated README + uses: actions/upload-artifact@v4.3.4 + with: + name: updated-readme + path: README.md # ============================================ - # Job 4: No release needed (info only) + # Job 5: Sync DockerHub READMEs # ============================================ - no-release: - name: No Release Needed - needs: analyze - if: needs.analyze.outputs.should_release != 'true' + dockerhub-sync: + name: Sync DockerHub -> ${{ matrix.image }} + needs: finalize runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + image: [bun, bun-node, ubuntu-tools, ubuntu-bun, ubuntu-bun-node] steps: - - name: Summary - run: | - echo "โ„น๏ธ No Docker image release needed - all images are up to date" - echo "โœ… Workflow completed successfully" + - name: Download updated README + uses: actions/download-artifact@v4.3.4 + with: + name: updated-readme + path: . + + - name: Push README to DockerHub + uses: peter-evans/dockerhub-description@v4.0.0 + with: + username: ${{ env.GL_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + repository: ${{ env.GL_USERNAME }}/${{ matrix.image }} + readme-filepath: ./README.md + + # ============================================ + # Job 6: Standard GHCR Cleanup + # ============================================ + cleanup: + name: Prune Untagged GHCR Images -> ${{ matrix.image }} + needs: [build-bases, build-derivatives] + if: ${{ always() }} + runs-on: ubuntu-latest + permissions: + packages: write + strategy: + fail-fast: false + matrix: + image: [bun, bun-node, ubuntu-tools, ubuntu-bun, ubuntu-bun-node] + steps: + - name: Clean GHCR + uses: vlaurin/action-ghcr-prune@v0.6.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + organization: ${{ github.repository_owner }} + container: devcontainers/${{ matrix.image }} + keep-younger-than: 7 + prune-untagged: true diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index beeb6b3..c382b15 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -9,7 +9,7 @@ on: release: types: [published] schedule: - - cron: "0 */6 * * *" + - cron: '0 */6 * * *' workflow_dispatch: jobs: diff --git a/.gitignore b/.gitignore index 928e444..b68a13b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ node_modules .env *ignore -.vscode/ plans/ \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit index 45b4949..ac309d6 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,68 +1,5 @@ -# Skip Husky hooks in CI or when HUSKY is disabled -if [ "$CI" = "true" ] || [ -n "$GITHUB_ACTIONS" ] || [ "$HUSKY" = "0" ]; then - echo "Husky hook skipped in CI or HUSKY=0" - exit 0 -fi +#!/usr/bin/env sh +set -e -# Codespaces git identity check -if [ -n "$CODESPACES" ]; then - # Source GIT_USER and GIT_EMAIL from the single source of truth - SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" - if [ -f "$SCRIPT_DIR/scripts/author.sh" ]; then - # Extract variables using sed to handle single/double/no quotes robustly - # Pattern: export VAR="value" or export VAR='value' or export VAR=value - GIT_USER=$(grep -m1 '^export GIT_USER=' "$SCRIPT_DIR/scripts/author.sh" | sed "s/^export GIT_USER=[\"']\{0,1\}\([^\"']*\)[\"']\{0,1\}$/\1/") - GIT_EMAIL=$(grep -m1 '^export GIT_EMAIL=' "$SCRIPT_DIR/scripts/author.sh" | sed "s/^export GIT_EMAIL=[\"']\{0,1\}\([^\"']*\)[\"']\{0,1\}$/\1/") - else - echo "Error: scripts/author.sh not found. Cannot determine git identity." - exit 1 - fi - - # Validate that both variables were extracted successfully - if [ -z "$GIT_USER" ] || [ -z "$GIT_EMAIL" ]; then - echo "Error: Failed to extract GIT_USER or GIT_EMAIL from scripts/author.sh" - echo " GIT_USER='$GIT_USER' GIT_EMAIL='$GIT_EMAIL'" - echo " Please ensure the script contains 'export GIT_USER=...' and 'export GIT_EMAIL=...'" - exit 1 - fi - - CURRENT_USER=$(git config --global user.name 2> /dev/null || echo "") - CURRENT_EMAIL=$(git config --global user.email 2> /dev/null || echo "") - - # Also check GIT_AUTHOR_* env vars which override git config at commit time - EFFECTIVE_USER="${GIT_AUTHOR_NAME:-$CURRENT_USER}" - EFFECTIVE_EMAIL="${GIT_AUTHOR_EMAIL:-$CURRENT_EMAIL}" - - if [ "$EFFECTIVE_USER" != "$GIT_USER" ] || [ "$EFFECTIVE_EMAIL" != "$GIT_EMAIL" ]; then - echo "Git identity mismatch detected in Codespaces!" - echo " Expected: $GIT_USER <$GIT_EMAIL>" - echo " Effective: $EFFECTIVE_USER <$EFFECTIVE_EMAIL>" - if [ -n "$GIT_AUTHOR_NAME" ] || [ -n "$GIT_AUTHOR_EMAIL" ]; then - echo " (GIT_AUTHOR_NAME/EMAIL env vars are overriding git config)" - fi - echo "" - echo "Running author.sh to fix attribution..." - - if [ ! -x "$SCRIPT_DIR/scripts/author.sh" ]; then - echo "" - echo "Error: $SCRIPT_DIR/scripts/author.sh is not executable or does not exist." - echo " Run: chmod +x scripts/author.sh" - exit 1 - fi - - if "$SCRIPT_DIR/scripts/author.sh"; then - echo "" - echo "Git config updated. Please run 'git commit' again." - exit 1 - else - echo "" - echo "Error: author.sh failed to update git config." - echo " Please run '$SCRIPT_DIR/scripts/author.sh' manually to debug." - exit 1 - fi - fi -fi - -# Run project checks (type checking, linting, and formatting) -echo "Running pre-commit checks..." -bun check && bun f:check +# 1. Enforce global identity (instant execution) +# ./.husky/_/identity-guard.sh diff --git a/.prettierrc b/.oxfmtrc.json similarity index 52% rename from .prettierrc rename to .oxfmtrc.json index 720d1b8..6a13d49 100644 --- a/.prettierrc +++ b/.oxfmtrc.json @@ -1,42 +1,31 @@ { + "$schema": "./node_modules/oxfmt/configuration_schema.json", "endOfLine": "lf", "arrowParens": "avoid", "bracketSpacing": true, "htmlWhitespaceSensitivity": "css", - "insertPragma": false, "jsxSingleQuote": true, "printWidth": 80, - "proseWrap": "always", + "proseWrap": "preserve", "quoteProps": "as-needed", - "requirePragma": false, "semi": false, "singleQuote": true, "tabWidth": 2, "trailingComma": "none", "useTabs": false, - "plugins": ["prettier-plugin-sh"], + "sortPackageJson": false, + "ignorePatterns": ["node_modules/", "*ignore*", "*lock*", "metadata/"], "overrides": [ - { - "files": ["*.yml", "*.yaml", "*.json"], - "options": { - "singleQuote": false, - "printWidth": 130 - } - }, { "files": ["*.md"], "options": { - "proseWrap": "always", - "printWidth": 100 + "printWidth": 120 } }, { - "files": ["*.sh"], + "files": ["*.json", "*.yml", "*.yaml"], "options": { - "parser": "sh", - "printWidth": 80, - "tabWidth": 2, - "useTabs": false + "printWidth": 100 } } ] diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index f767e87..0000000 --- a/.prettierignore +++ /dev/null @@ -1,10 +0,0 @@ -node_modules/ -*ignore* -*.nix -*lock* - -# Exclude setup.zsh files with complex shell arithmetic that prettier-plugin-sh can't parse -images/**/setup.zsh - -# Exclude Husky shim internals generated under .husky/_ -.husky/_/* diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..f3dbf42 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,11 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Build DevContainer", + "type": "shell", + "command": "docker buildx build --pull --rm --output type=docker -f \"images/bun/Dockerfile\" -t workspace:latest \"images/bun\"", + "problemMatcher": [] + } + ] +} diff --git a/AGENTS.md b/AGENTS.md index 02cd39e..fc41fbf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,42 +1,31 @@ # AGENTS.md -## Project overview - -- Repository purpose: reusable DevContainer image definitions, manifests, and release automation. -- Primary implementation areas: - - `images/*` for image Dockerfiles and shell bootstrap scripts - - `.devcontainer/` and `images/*/devcontainer.json` for VS Code container manifests - - `scripts/*.ts` for release/version automation - - `docs/*.md` and `README.md` for user-facing documentation - -## Tooling - -- Package manager: `bun` (`bun.lock` present). -- Install deps: `bun install`. -- Typecheck + lint: `bun run check`. -- Format changed files: `bun run f`. - -## Code conventions - -- TypeScript uses ESM syntax, single quotes, no semicolons, 2-space indentation. -- ESLint targets `scripts/**/*.ts` with `typescript-eslint` recommended config. -- Prettier is authoritative for JSON, Markdown, and shell formatting. -- Prettier defaults: `printWidth: 80`, `singleQuote: true`, `semi: false`, `tabWidth: 2`. -- JSON/YAML overrides use double quotes and `printWidth: 130`. -- Markdown overrides use `printWidth: 100`. -- Shell files use 2-space indentation and LF line endings. - -## Container-image conventions - -- Each image Dockerfile writes `/usr/local/share/tool-versions.txt` during build. -- When tool availability changes in an image, keep the tool version extraction block accurate. -- Preserve non-root user handling (`USERNAME`) and passwordless sudo behavior where already - implemented. -- Keep image changes small and avoid reformatting unrelated Dockerfile sections. - -## Planning notes for Atlas - -- Store Atlas plans under `.atlas/plans/`. -- Existing historical plans under `plans/` are reference material only. -- Before changing image behavior, search for related references such as setup script filenames, - shell startup files, and devcontainer customizations. +## Project Overview + +- **Repository Purpose**: Reusable DevContainer image definitions, manifests, and release automation. +- **Primary Implementation Areas**: + - `images/*` for image Dockerfiles and shell bootstrap scripts. + - `.devcontainer/` and `images/*/devcontainer.json` for VS Code container manifests. + - `.github/actions/notes/update_docs.py` and GitHub Actions workflows for release/version automation. + - `docs/*.md` and `README.md` for user-facing documentation. + +## Tooling & Runtime Environment + +- **Runtime**: `bun` is the primary runtime for scripts and formatting. Node.js is not directly available; use `bun` for package management and script execution. +- **Dependency Management**: Uses Bun (`bun.lock` is present). Dependencies are installed with `bun install`. +- **Linting & Formatting**: + - `bun run f` to write formatting using `oxfmt --write`. + - `bun run f:check` to check formatting using `oxfmt --check`. + +## Code Conventions + +- **TypeScript**: ESM syntax, single quotes, no semicolons, 2-space indentation. +- **Formatting Defaults**: Formatted with `oxfmt` (using `.oxfmtrc.json` as the source of truth). Double quotes are used for JSON/YAML. + +## Container-Image Conventions + +- **OCI Labels**: Each Dockerfile exposes tool metadata via `devcontainer.tool.*` labels. +- **User Creation & Permissions**: + - Non-root `USERNAME` must be validated with regex: `echo "${USERNAME}" | grep -Eq '^[a-z_][a-z0-9_-]*[$]?$'`. + - Sudo configuration must be written to isolated file `/etc/sudoers.d/${USERNAME}` with `chmod 0440`. + - Sudo configurations must be verified using `visudo -cf "/etc/sudoers.d/${USERNAME}"` during build. diff --git a/CHANGELOG.md b/CHANGELOG.md index 0747234..ef0cb09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog -All notable changes to this project will be documented in this file. +All notable changes to this project and devcontainer images will be documented in this file. + +--- ## [0.0.4] - 2025-12-02 @@ -34,8 +36,8 @@ All notable changes to this project will be documented in this file. ## Other Changes -- release: 0.0.1 - roll back and implement manual release override functionality and update - changelog format +- release: 0.0.1 - roll back and implement manual release override functionality + and update changelog format ([5d82204](https://github.com/iamvikshan/devcontainers/commit/5d82204a9158b2e21b7215be4ddd9fe05b400ccb)) --- @@ -53,10 +55,11 @@ All notable changes to this project will be documented in this file. - fix: consolidate version tracking and optimize release workflow ([fd3d8bc](https://github.com/iamvikshan/devcontainers/commit/fd3d8bce896e733879d0852f72d06494b76fd7c8)) -- fix: setup script for improved readability and consistency; update package.json dependencies; - enhance build-all-images script with better error handling and logging; add GitHub Actions for - building, cleaning up, and tagging container images; implement scripts for extracting tool - versions and cleaning untagged images; ensure Docker setup verification is robust and +- fix: setup script for improved readability and consistency; update + package.json dependencies; enhance build-all-images script with better error + handling and logging; add GitHub Actions for building, cleaning up, and + tagging container images; implement scripts for extracting tool versions and + cleaning untagged images; ensure Docker setup verification is robust and user-friendly. ([700d7a6](https://github.com/iamvikshan/devcontainers/commit/700d7a6c933475adbb5de52aa1b26a73eb294e91)) - fix: resolve untagged images and missing Gitpod containers in documentation @@ -64,8 +67,8 @@ All notable changes to this project will be documented in this file. ## Other Changes -- release: 0.0.1 - roll back and implement manual release override functionality and update - changelog format +- release: 0.0.1 - roll back and implement manual release override functionality + and update changelog format ([5d82204](https://github.com/iamvikshan/devcontainers/commit/5d82204a9158b2e21b7215be4ddd9fe05b400ccb)) ## [0.0.1] - 2025-10-18 @@ -83,21 +86,26 @@ All notable changes to this project will be documented in this file. ([9aa6709](https://github.com/iamvikshan/devcontainers/commit/9aa67092335a6a9f7e0e8a96b87985d4cdd82979)) - fix: consolidate version tracking and optimize release workflow ([fd3d8bc](https://github.com/iamvikshan/devcontainers/commit/fd3d8bce896e733879d0852f72d06494b76fd7c8)) -- fix: setup script for improved readability and consistency; update package.json dependencies; - enhance build-all-images script with better error handling and logging; add GitHub Actions for - building, cleaning up, and tagging container images; implement scripts for extracting tool - versions and cleaning untagged images; ensure Docker setup verification is robust and +- fix: setup script for improved readability and consistency; update + package.json dependencies; enhance build-all-images script with better error + handling and logging; add GitHub Actions for building, cleaning up, and + tagging container images; implement scripts for extracting tool versions and + cleaning untagged images; ensure Docker setup verification is robust and user-friendly. ([700d7a6](https://github.com/iamvikshan/devcontainers/commit/700d7a6c933475adbb5de52aa1b26a73eb294e91)) -- fix: update base images for ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node +- fix: update base images for + ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node ([798074f](https://github.com/iamvikshan/devcontainers/commit/798074fda7df081ad6e7259498bbc344e620561e)) - fix: update base images for bun,bun-node,gitpod-bun,gitpod-bun-node ([e28d899](https://github.com/iamvikshan/devcontainers/commit/e28d89945aad9557d1ad1a56746289433c1a5d74)) -- fix: update base images for ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node +- fix: update base images for + ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node ([52a2747](https://github.com/iamvikshan/devcontainers/commit/52a2747fdde5cfe5b1502ec50ab7ef22b6531314)) -- fix: update base images for ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node +- fix: update base images for + ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node ([f1c0de4](https://github.com/iamvikshan/devcontainers/commit/f1c0de447ffe3d3131eaf1f970e20a43fe9a8e3d)) -- fix: update base images for ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node +- fix: update base images for + ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node ([bd9078a](https://github.com/iamvikshan/devcontainers/commit/bd9078a30b74a21604d7e3fe5325f6e3f51b23e0)) - fix: update base images for bun,bun-node,gitpod-bun,gitpod-bun-node ([e127627](https://github.com/iamvikshan/devcontainers/commit/e127627471cc091180e0608abb3c5b6b9f7358bb)) @@ -106,8 +114,8 @@ All notable changes to this project will be documented in this file. ## Other Changes -- release: 0.0.1 - roll back and implement manual release override functionality and update - changelog format +- release: 0.0.1 - roll back and implement manual release override functionality + and update changelog format ([5d82204](https://github.com/iamvikshan/devcontainers/commit/5d82204a9158b2e21b7215be4ddd9fe05b400ccb)) - docs: update documentation for release v2.0.0 ([c098a72](https://github.com/iamvikshan/devcontainers/commit/c098a723e995a56c321f416a057d328c3d303a62)) @@ -127,8 +135,8 @@ All notable changes to this project will be documented in this file. | ubuntu-bun | v0.0.1 (latest) | 2025-10-18 | [GitHub](https://ghcr.io/iamvikshan/devcontainers/ubuntu-bun:latest) ยท [GitLab](https://registry.gitlab.com/vikshan/devcontainers/ubuntu-bun:latest) ยท [Docker Hub](https://hub.docker.com/r/vikshan/ubuntu-bun) | | ubuntu-bun-node | v0.0.1 (latest) | 2025-10-18 | [GitHub](https://ghcr.io/iamvikshan/devcontainers/ubuntu-bun-node:latest) ยท [GitLab](https://registry.gitlab.com/vikshan/devcontainers/ubuntu-bun-node:latest) ยท [Docker Hub](https://hub.docker.com/r/vikshan/ubuntu-bun-node) | -> **Note:** The "(latest)" marker indicates the version currently tagged as `:latest` in all -> registries. +> **Note:** The "(latest)" marker indicates the version currently tagged as +> `:latest` in all registries. --- @@ -157,7 +165,8 @@ All notable changes to this project will be documented in this file. - Security patches and bug fixes from upstream - Improved compatibility and performance -**Impact:** Patch release - DevContainers will be rebuilt with updated base images +**Impact:** Patch release - DevContainers will be rebuilt with updated base +images --- @@ -178,66 +187,82 @@ All notable changes to this project will be documented in this file. - feat: Add Gitpod-specific DevContainer images for Bun and Node.js (3a7c300) - feat: Implement release orchestration and version management scripts (41016db) -- feat!: Implement centralized registry client for Docker Hub, GHCR, and GitLab (0b833d5) -- feat: let's start over, shall we? update release workflows to prevent redundant triggers and - enhance sync operations (5aba3ef) +- feat!: Implement centralized registry client for Docker Hub, GHCR, and GitLab + (0b833d5) +- feat: let's start over, shall we? update release workflows to prevent + redundant triggers and enhance sync operations (5aba3ef) ## Bug Fixes - fix: consolidate version tracking and optimize release workflow (fd3d8bc) -- fix: setup script for improved readability and consistency; update package.json dependencies; - enhance build-all-images script with better error handling and logging; add GitHub Actions for - building, cleaning up, and tagging container images; implement scripts for extracting tool - versions and cleaning untagged images; ensure Docker setup verification is robust and +- fix: setup script for improved readability and consistency; update + package.json dependencies; enhance build-all-images script with better error + handling and logging; add GitHub Actions for building, cleaning up, and + tagging container images; implement scripts for extracting tool versions and + cleaning untagged images; ensure Docker setup verification is robust and user-friendly. (700d7a6) -- fix: update base images for ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node - (798074f) +- fix: update base images for + ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node (798074f) - fix: update base images for bun,bun-node,gitpod-bun,gitpod-bun-node (e28d899) -- fix: update base images for ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node - (52a2747) -- fix: update base images for ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node - (f1c0de4) -- fix: update base images for ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node - (bd9078a) +- fix: update base images for + ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node (52a2747) +- fix: update base images for + ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node (f1c0de4) +- fix: update base images for + ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node (bd9078a) - fix: update base images for bun,bun-node,gitpod-bun,gitpod-bun-node (e127627) -- fix: resolve untagged images and missing Gitpod containers in documentation (e9fe715) -- fix: update advanced-git-sync action version and improve SSH startup script in gitpod Dockerfiles - (6b33e3f) -- fix: update Dockerfiles to include SSH server and configure settings for improved access (ea95c30) -- fix: update base images for ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node - (9296340) -- fix: add remote pull before pushing documentation changes and tags to avoid conflicts (b0e3d4c) -- fix: update base images for ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node - (a717021) +- fix: resolve untagged images and missing Gitpod containers in documentation + (e9fe715) +- fix: update advanced-git-sync action version and improve SSH startup script in + gitpod Dockerfiles (6b33e3f) +- fix: update Dockerfiles to include SSH server and configure settings for + improved access (ea95c30) +- fix: update base images for + ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node (9296340) +- fix: add remote pull before pushing documentation changes and tags to avoid + conflicts (b0e3d4c) +- fix: update base images for + ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node (a717021) - fix: update base images for bun,bun-node,gitpod-bun,gitpod-bun-node (65d85bb) - fix: Enhance base image check and release workflows (02baa52) - fix: update base images for bun, bun-node (f19798f) - fix: update base images for bun, bun-node (55537b6) - fix: update base images for ubuntu-bun, ubuntu-bun-node (6cfd75e) -- fix: update base images for bun, bun-node, ubuntu-bun, ubuntu-bun-node (ab3155f) -- fix: update base images for bun, bun-node, ubuntu-bun, ubuntu-bun-node (5bb948e) -- fix: update base images for bun, bun-node, ubuntu-bun, ubuntu-bun-node (0b5bffa) -- fix: update base images for bun, bun-node, ubuntu-bun, ubuntu-bun-node (2bf890e) -- fix: update base images for bun, bun-node, ubuntu-bun, ubuntu-bun-node (e375dd4) +- fix: update base images for bun, bun-node, ubuntu-bun, ubuntu-bun-node + (ab3155f) +- fix: update base images for bun, bun-node, ubuntu-bun, ubuntu-bun-node + (5bb948e) +- fix: update base images for bun, bun-node, ubuntu-bun, ubuntu-bun-node + (0b5bffa) +- fix: update base images for bun, bun-node, ubuntu-bun, ubuntu-bun-node + (2bf890e) +- fix: update base images for bun, bun-node, ubuntu-bun, ubuntu-bun-node + (e375dd4) - fix: update base images for bun, bun-node (abe2d81) -- fix: update image sizes in documentation and streamline action configurations (3f29d11) -- fix: update documentation commit message and streamline push process for versioning (5a90d62) -- fix: update README and action configurations to include Docker Hub support and streamline - container registry authentication (3163fbb) -- fix: streamline Docker image build process by combining build and push steps for GitHub and GitLab - registries (0b8c065) -- fix: update GitHub Actions for documentation updates and remove GitLab integration (ba02651) -- fix: update README files with new image sizes and enhance GitHub Actions for documentation updates - (f47e47b) +- fix: update image sizes in documentation and streamline action configurations + (3f29d11) +- fix: update documentation commit message and streamline push process for + versioning (5a90d62) +- fix: update README and action configurations to include Docker Hub support and + streamline container registry authentication (3163fbb) +- fix: streamline Docker image build process by combining build and push steps + for GitHub and GitLab registries (0b8c065) +- fix: update GitHub Actions for documentation updates and remove GitLab + integration (ba02651) +- fix: update README files with new image sizes and enhance GitHub Actions for + documentation updates (f47e47b) - fix: update sync configuration and enhance release workflow steps (687909c) -- fix: implement image size updater script and update README files with new sizes (e6c2811) -- fix: update advanced-git-sync action version to v1.1.5 in sync workflow (18dda27) -- fix: update GitHub Actions workflow and dependencies; upgrade checkout action and sync action - version. Uprade bun to v1.1.38 (10bc7cd) +- fix: implement image size updater script and update README files with new + sizes (e6c2811) +- fix: update advanced-git-sync action version to v1.1.5 in sync workflow + (18dda27) +- fix: update GitHub Actions workflow and dependencies; upgrade checkout action + and sync action version. Upgrade bun to v1.1.38 (10bc7cd) ## Other Changes -- chore: bump OpenSaucedHub/advanced-git-sync in the github-actions group (#36) (07af88f) +- chore: bump OpenSaucedHub/advanced-git-sync in the github-actions group (#36) + (07af88f) - chore: streamline tool version extraction script in release workflow (df3ef63) - chore: Refactor version management to changelog management (f874a69) - docs: update documentation for release v1.1.5 (3e9edd4) @@ -250,20 +275,24 @@ All notable changes to this project will be documented in this file. - chore(release): 1.1.4 [skip ci] (fc9ab4d) - chore(release): 1.1.3 [skip ci] (efd4322) - chore: enhance logging functionality and add silent mode support (18fefed) -- chore: bump OpenSaucedHub/advanced-git-sync in the github-actions group (#32) (b2e4933) +- chore: bump OpenSaucedHub/advanced-git-sync in the github-actions group (#32) + (b2e4933) - chore(release): 1.1.2 [skip ci] (14dcf85) - chore(release): 1.1.2 [skip ci] (d9ab672) - chore: update advanced-git-sync action to version 1.4.2 (8908b95) -- chore: update setup actions to use Bun and upgrade advanced-git-sync version (67dee93) -- chore: improve pre-release check and version prediction scripts for better workflow integration - (44920b5) -- chore: enhance release workflow with pre-release checks and version prediction (6217251) +- chore: update setup actions to use Bun and upgrade advanced-git-sync version + (67dee93) +- chore: improve pre-release check and version prediction scripts for better + workflow integration (44920b5) +- chore: enhance release workflow with pre-release checks and version prediction + (6217251) - Release 1.1.2 [skip ci] (fd9ceca) - Release 1.1.2 [skip ci] (58779bc) - Release 1.1.2 [skip ci] (6454123) - Release 1.1.2 [skip ci] (4e439b1) - Release 1.1.2 [skip ci] (10bfd3c) -- chore: enable token persistence for checkout and improve git push handling (e39c2f9) +- chore: enable token persistence for checkout and improve git push handling + (e39c2f9) - Release 1.1.2 [skip ci] (d9ef26f) - Release 1.1.2 [skip ci] (ebd0db8) - chore: bump actions/checkout (#28) (3dcb12d) @@ -293,19 +322,24 @@ All notable changes to this project will be documented in this file. - chore: update versions and sizes for release 1.1.0 [skip-sync] (f9a043a) - chore: update versions and sizes for release 1.1.0 [skip-sync] (8efdb63) - chore: update versions and sizes for release 1.1.0 [skip-sync] (cf479f3) -- chore: migrate from versions.json to VERSIONS.md for version management (1767e05) -- chore: update workflows to trigger release with reason input and log trigger details (1d29a02) -- chore: update sync configuration and GitHub Actions workflow for improved syncing and permissions - (7d621a6) -- chore: update Docker update schedule to daily and upgrade sync action version (445ac4d) +- chore: migrate from versions.json to VERSIONS.md for version management + (1767e05) +- chore: update workflows to trigger release with reason input and log trigger + details (1d29a02) +- chore: update sync configuration and GitHub Actions workflow for improved + syncing and permissions (7d621a6) +- chore: update Docker update schedule to daily and upgrade sync action version + (445ac4d) - chore: update versions and sizes for release 1.1.0 [skip-sync] (e52540f) - chore: update versions and sizes for release 1.1.0 [skip-sync] (a3822ca) - Release 1.1.0 [skip ci] (98bc36f) -- โœจ feat: fix GitHub Actions token issues and optimize version management (a2be3a8) +- โœจ feat: fix GitHub Actions token issues and optimize version management + (a2be3a8) - Release 1.0.4 [skip ci] (f5cd167) - chore(deps-dev): bump @types/node from 22.16.5 to 24.1.0 (#22) (9609880) - Merge pull request #18 from - iamvikshan/dependabot/npm_and_yarn/conventional-changelog-conventionalcommits-9.1.0 (8570805) + iamvikshan/dependabot/npm_and_yarn/conventional-changelog-conventionalcommits-9.1.0 + (8570805) - Release 1.0.3 [skip ci] (f700fbe) - Release 1.0.3 [skip ci] (2df4456) - Release 1.0.2 [skip ci] (6325b60) @@ -314,15 +348,15 @@ All notable changes to this project will be documented in this file. - Release 1.0.0 [skip ci] (56ecffd) - Update and rename gl-sync.yml to sync.yml (4c40196) - Update gl-sync.yml (8b8267b) -- chore: update GitHub Actions workflows and sync configuration; enhance release process and improve - README badges (8ad8372) +- chore: update GitHub Actions workflows and sync configuration; enhance release + process and improve README badges (8ad8372) - Release 1.0.0 [skip ci] (b1d0dc1) -- chore: update CLA workflow to use consistent GitHub token; refine release workflow triggers and - update changelog (e4ab6ad) +- chore: update CLA workflow to use consistent GitHub token; refine release + workflow triggers and update changelog (e4ab6ad) - Release 1.0.0 [skip ci] (89fcea2) - Release 1.0.0 [skip ci] (4b88988) -- chore: update container images and README with accurate sizes; adjust GitHub Actions for token - consistency (e078146) +- chore: update container images and README with accurate sizes; adjust GitHub + Actions for token consistency (e078146) - Release 1.0.0 [skip ci] (7166661) ## [1.1.5] - 2025-09-24 @@ -338,9 +372,10 @@ All notable changes to this project will be documented in this file. ## Bug Fixes -- fix: update advanced-git-sync action version and improve SSH startup script in gitpod Dockerfiles - (6b33e3f) -- fix: update Dockerfiles to include SSH server and configure settings for improved access (ea95c30) +- fix: update advanced-git-sync action version and improve SSH startup script in + gitpod Dockerfiles (6b33e3f) +- fix: update Dockerfiles to include SSH server and configure settings for + improved access (ea95c30) ## [1.1.4] - 2025-09-17 @@ -357,8 +392,8 @@ All notable changes to this project will be documented in this file. ## Bug Fixes -- fix: update base images for ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node - (9296340) +- fix: update base images for + ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node (9296340) ## [1.1.3] - 2025-09-16 @@ -375,7 +410,8 @@ All notable changes to this project will be documented in this file. ## Bug Fixes -- fix: add remote pull before pushing documentation changes and tags to avoid conflicts (b0e3d4c) +- fix: add remote pull before pushing documentation changes and tags to avoid + conflicts (b0e3d4c) ## [1.1.2] - 2025-09-16 @@ -392,8 +428,8 @@ All notable changes to this project will be documented in this file. ## Bug Fixes -- fix: update base images for ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node - (a717021) +- fix: update base images for + ubuntu-bun,ubuntu-bun-node,gitpod-ubuntu-bun,gitpod-ubuntu-bun-node (a717021) ## [1.1.1] - 2025-09-15 @@ -437,9 +473,10 @@ All notable changes to this project will be documented in this file. ## Features - feat: Implement release orchestration and version management scripts (41016db) -- feat!: Implement centralized registry client for Docker Hub, GHCR, and GitLab (0b833d5) -- feat: let's start over, shall we? update release workflows to prevent redundant triggers and - enhance sync operations (5aba3ef) +- feat!: Implement centralized registry client for Docker Hub, GHCR, and GitLab + (0b833d5) +- feat: let's start over, shall we? update release workflows to prevent + redundant triggers and enhance sync operations (5aba3ef) ## Bug Fixes @@ -447,46 +484,60 @@ All notable changes to this project will be documented in this file. - fix: update base images for bun, bun-node (f19798f) - fix: update base images for bun, bun-node (55537b6) - fix: update base images for ubuntu-bun, ubuntu-bun-node (6cfd75e) -- fix: update base images for bun, bun-node, ubuntu-bun, ubuntu-bun-node (ab3155f) -- fix: update base images for bun, bun-node, ubuntu-bun, ubuntu-bun-node (5bb948e) -- fix: update base images for bun, bun-node, ubuntu-bun, ubuntu-bun-node (0b5bffa) -- fix: update base images for bun, bun-node, ubuntu-bun, ubuntu-bun-node (2bf890e) -- fix: update base images for bun, bun-node, ubuntu-bun, ubuntu-bun-node (e375dd4) +- fix: update base images for bun, bun-node, ubuntu-bun, ubuntu-bun-node + (ab3155f) +- fix: update base images for bun, bun-node, ubuntu-bun, ubuntu-bun-node + (5bb948e) +- fix: update base images for bun, bun-node, ubuntu-bun, ubuntu-bun-node + (0b5bffa) +- fix: update base images for bun, bun-node, ubuntu-bun, ubuntu-bun-node + (2bf890e) +- fix: update base images for bun, bun-node, ubuntu-bun, ubuntu-bun-node + (e375dd4) - fix: update base images for bun, bun-node (abe2d81) -- fix: update image sizes in documentation and streamline action configurations (3f29d11) -- fix: update documentation commit message and streamline push process for versioning (5a90d62) -- fix: update README and action configurations to include Docker Hub support and streamline - container registry authentication (3163fbb) -- fix: streamline Docker image build process by combining build and push steps for GitHub and GitLab - registries (0b8c065) -- fix: update GitHub Actions for documentation updates and remove GitLab integration (ba02651) -- fix: update README files with new image sizes and enhance GitHub Actions for documentation updates - (f47e47b) +- fix: update image sizes in documentation and streamline action configurations + (3f29d11) +- fix: update documentation commit message and streamline push process for + versioning (5a90d62) +- fix: update README and action configurations to include Docker Hub support and + streamline container registry authentication (3163fbb) +- fix: streamline Docker image build process by combining build and push steps + for GitHub and GitLab registries (0b8c065) +- fix: update GitHub Actions for documentation updates and remove GitLab + integration (ba02651) +- fix: update README files with new image sizes and enhance GitHub Actions for + documentation updates (f47e47b) - fix: update sync configuration and enhance release workflow steps (687909c) -- fix: implement image size updater script and update README files with new sizes (e6c2811) -- fix: update advanced-git-sync action version to v1.1.5 in sync workflow (18dda27) -- fix: update GitHub Actions workflow and dependencies; upgrade checkout action and sync action - version. Uprade bun to v1.1.38 (10bc7cd) +- fix: implement image size updater script and update README files with new + sizes (e6c2811) +- fix: update advanced-git-sync action version to v1.1.5 in sync workflow + (18dda27) +- fix: update GitHub Actions workflow and dependencies; upgrade checkout action + and sync action version. Upgrade bun to v1.1.38 (10bc7cd) ## Other Changes - chore(release): 1.1.4 [skip ci] (fc9ab4d) - chore(release): 1.1.3 [skip ci] (efd4322) - chore: enhance logging functionality and add silent mode support (18fefed) -- chore: bump OpenSaucedHub/advanced-git-sync in the github-actions group (#32) (b2e4933) +- chore: bump OpenSaucedHub/advanced-git-sync in the github-actions group (#32) + (b2e4933) - chore(release): 1.1.2 [skip ci] (14dcf85) - chore(release): 1.1.2 [skip ci] (d9ab672) - chore: update advanced-git-sync action to version 1.4.2 (8908b95) -- chore: update setup actions to use Bun and upgrade advanced-git-sync version (67dee93) -- chore: improve pre-release check and version prediction scripts for better workflow integration - (44920b5) -- chore: enhance release workflow with pre-release checks and version prediction (6217251) +- chore: update setup actions to use Bun and upgrade advanced-git-sync version + (67dee93) +- chore: improve pre-release check and version prediction scripts for better + workflow integration (44920b5) +- chore: enhance release workflow with pre-release checks and version prediction + (6217251) - Release 1.1.2 [skip ci] (fd9ceca) - Release 1.1.2 [skip ci] (58779bc) - Release 1.1.2 [skip ci] (6454123) - Release 1.1.2 [skip ci] (4e439b1) - Release 1.1.2 [skip ci] (10bfd3c) -- chore: enable token persistence for checkout and improve git push handling (e39c2f9) +- chore: enable token persistence for checkout and improve git push handling + (e39c2f9) - Release 1.1.2 [skip ci] (d9ef26f) - Release 1.1.2 [skip ci] (ebd0db8) - chore: bump actions/checkout (#28) (3dcb12d) @@ -516,19 +567,24 @@ All notable changes to this project will be documented in this file. - chore: update versions and sizes for release 1.1.0 [skip-sync] (f9a043a) - chore: update versions and sizes for release 1.1.0 [skip-sync] (8efdb63) - chore: update versions and sizes for release 1.1.0 [skip-sync] (cf479f3) -- chore: migrate from versions.json to VERSIONS.md for version management (1767e05) -- chore: update workflows to trigger release with reason input and log trigger details (1d29a02) -- chore: update sync configuration and GitHub Actions workflow for improved syncing and permissions - (7d621a6) -- chore: update Docker update schedule to daily and upgrade sync action version (445ac4d) +- chore: migrate from versions.json to VERSIONS.md for version management + (1767e05) +- chore: update workflows to trigger release with reason input and log trigger + details (1d29a02) +- chore: update sync configuration and GitHub Actions workflow for improved + syncing and permissions (7d621a6) +- chore: update Docker update schedule to daily and upgrade sync action version + (445ac4d) - chore: update versions and sizes for release 1.1.0 [skip-sync] (e52540f) - chore: update versions and sizes for release 1.1.0 [skip-sync] (a3822ca) - Release 1.1.0 [skip ci] (98bc36f) -- โœจ feat: fix GitHub Actions token issues and optimize version management (a2be3a8) +- โœจ feat: fix GitHub Actions token issues and optimize version management + (a2be3a8) - Release 1.0.4 [skip ci] (f5cd167) - chore(deps-dev): bump @types/node from 22.16.5 to 24.1.0 (#22) (9609880) - Merge pull request #18 from - iamvikshan/dependabot/npm_and_yarn/conventional-changelog-conventionalcommits-9.1.0 (8570805) + iamvikshan/dependabot/npm_and_yarn/conventional-changelog-conventionalcommits-9.1.0 + (8570805) - Release 1.0.3 [skip ci] (f700fbe) - Release 1.0.3 [skip ci] (2df4456) - Release 1.0.2 [skip ci] (6325b60) @@ -537,15 +593,15 @@ All notable changes to this project will be documented in this file. - Release 1.0.0 [skip ci] (56ecffd) - Update and rename gl-sync.yml to sync.yml (4c40196) - Update gl-sync.yml (8b8267b) -- chore: update GitHub Actions workflows and sync configuration; enhance release process and improve - README badges (8ad8372) +- chore: update GitHub Actions workflows and sync configuration; enhance release + process and improve README badges (8ad8372) - Release 1.0.0 [skip ci] (b1d0dc1) -- chore: update CLA workflow to use consistent GitHub token; refine release workflow triggers and - update changelog (e4ab6ad) +- chore: update CLA workflow to use consistent GitHub token; refine release + workflow triggers and update changelog (e4ab6ad) - Release 1.0.0 [skip ci] (89fcea2) - Release 1.0.0 [skip ci] (4b88988) -- chore: update container images and README with accurate sizes; adjust GitHub Actions for token - consistency (e078146) +- chore: update container images and README with accurate sizes; adjust GitHub + Actions for token consistency (e078146) - Release 1.0.0 [skip ci] (7166661) ## [1.1.4](https://github.com/iamvikshan/devcontainers/compare/v1.1.3...v1.1.4) (2025-08-27) @@ -718,8 +774,8 @@ All notable changes to this project will be documented in this file. ## โœจ New Features -- [`a2be3a8`](https://github.com/iamvikshan/devcontainers/commit/a2be3a8) feat: fix GitHub Actions - token issues and optimize version management +- [`a2be3a8`](https://github.com/iamvikshan/devcontainers/commit/a2be3a8) feat: + fix GitHub Actions token issues and optimize version management ## [1.1.0](https://github.com/iamvikshan/devcontainers/compare/v1.0.4...v1.1.0) (2025-08-01) @@ -747,13 +803,13 @@ All notable changes to this project will be documented in this file. ### Bug Fixes -- streamline Docker image build process by combining build and push steps for GitHub and GitLab - registries +- streamline Docker image build process by combining build and push steps for + GitHub and GitLab registries ([0b8c065](https://github.com/iamvikshan/devcontainers/commit/0b8c065ecb196182bd4ae218118bce00ba0b3795)) - update documentation commit message and streamline push process for versioning ([5a90d62](https://github.com/iamvikshan/devcontainers/commit/5a90d6274c75a7669a0d26155e9fa24172960a08)) -- update README and action configurations to include Docker Hub support and streamline container - registry authentication +- update README and action configurations to include Docker Hub support and + streamline container registry authentication ([3163fbb](https://github.com/iamvikshan/devcontainers/commit/3163fbb4edb1d5820e82c9979789a253bc234fe8)) # [v1.0.3](https://github.com/iamvikshan/devcontainers/compare/v1.0.2...v1.0.3) (2024-12-08) @@ -762,8 +818,8 @@ All notable changes to this project will be documented in this file. ### Bug Fixes -- streamline Docker image build process by combining build and push steps for GitHub and GitLab - registries +- streamline Docker image build process by combining build and push steps for + GitHub and GitLab registries ([0b8c065](https://github.com/iamvikshan/devcontainers/commit/0b8c065ecb196182bd4ae218118bce00ba0b3795)) # [v1.0.2](https://github.com/iamvikshan/devcontainers/compare/v1.0.1...v1.0.2) (2024-12-08) @@ -781,7 +837,8 @@ All notable changes to this project will be documented in this file. ### Bug Fixes -- update README files with new image sizes and enhance GitHub Actions for documentation updates +- update README files with new image sizes and enhance GitHub Actions for + documentation updates ([f47e47b](https://github.com/iamvikshan/devcontainers/commit/f47e47b31b5059a4a3015675e876a7010eeec0ac)) # v1.0.0 (2024-12-07) @@ -790,17 +847,18 @@ All notable changes to this project will be documented in this file. ### Features -- let's start over, shall we? update release workflows to prevent redundant triggers and enhance - sync operations +- let's start over, shall we? update release workflows to prevent redundant + triggers and enhance sync operations ([5aba3ef](https://github.com/iamvikshan/devcontainers/commit/5aba3ef22af4f11d7767f4c6de4876ad3c50d147)) -* DevContainer configurations for Bun and Node.js, including package management and CI workflows +* DevContainer configurations for Bun and Node.js, including package management + and CI workflows * sync operations * Remove default Gitpod bun, all now use oven/bun image. Added setup.sh. * Remove unnecessary release scopes from semantic-release configuration * Update configuration files and add Prettier support -* Update semantic-release configuration and add new plugins for improved versioning and release - notes +* Update semantic-release configuration and add new plugins for improved + versioning and release notes ### Bug Fixes @@ -808,32 +866,44 @@ All notable changes to this project will be documented in this file. ([e6c2811](https://github.com/iamvikshan/devcontainers/commit/e6c2811ff805e7677f67cd99144e5ff6d1a81238)) - update advanced-git-sync action version to v1.1.5 in sync workflow ([18dda27](https://github.com/iamvikshan/devcontainers/commit/18dda275ce6dcc8d42ecfef0e62510ad056c0fc5)) -- update GitHub Actions workflow and dependencies; upgrade checkout action and sync action version. - Uprade bun to v1.1.38 +- update GitHub Actions workflow and dependencies; upgrade checkout action and + sync action version. Upgrade bun to v1.1.38 ([10bc7cd](https://github.com/iamvikshan/devcontainers/commit/10bc7cda116360c530b2d9a5a20d74a5d5983120)) - update sync configuration and enhance release workflow steps ([687909c](https://github.com/iamvikshan/devcontainers/commit/687909cbbfc24578ee1f5c75ba3e0eab8427e764)) -* Add GitLab plugin to semantic-release configuration and streamline CI environment handling +* Add GitLab plugin to semantic-release configuration and streamline CI + environment handling * Add Husky pre-commit hook and update README with image sizes * Change default username to root in Dockerfiles for bun and ubuntu environments -* enhance branch syncing by checking for updates before pushing to GitLab and GitHub to avoid +* enhance branch syncing by checking for updates before pushing to GitLab and + GitHub to avoid * infinite loop -* Enhance GitLab release configuration with buildx setup and improved error handling -* enhance GitLab sync and release workflows with tag existence check and README updates -* Enhance semantic-release configuration and improve error handling in GitLab release process +* Enhance GitLab release configuration with buildx setup and improved error + handling +* enhance GitLab sync and release workflows with tag existence check and README + updates +* Enhance semantic-release configuration and improve error handling in GitLab + release process * preinstall curl and open-ssh -* Refactor GitLab release configuration for improved versioning and error handling -* remove GH_TOKEN from environment setup and clean up unused GHCR visibility functions +* Refactor GitLab release configuration for improved versioning and error + handling +* remove GH_TOKEN from environment setup and clean up unused GHCR visibility + functions * Remove Husky pre-commit hook and update image size handling in README * Remove outdated GitLab sync workflows and update GitHub sync configuration * Remove package-lock.json from .gitignore -* Replace git-sync.yml with gitlab-sync.yml for improved synchronization with GitLab -* Update DevContainer Dockerfile to use Gitpod base image and streamline environment setup -* Update Dockerfiles to install OpenSSH client and curl, and configure SSH server for TCP forwarding +* Replace git-sync.yml with gitlab-sync.yml for improved synchronization with + GitLab +* Update DevContainer Dockerfile to use Gitpod base image and streamline + environment setup +* Update Dockerfiles to install OpenSSH client and curl, and configure SSH + server for TCP forwarding * Update GitHub sync configuration and enhance semantic-release setup -* update GitHub/Gitlab sync workflow and update image size handling in release workflows +* update GitHub/Gitlab sync workflow and update image size handling in release + workflows * update GitLab API endpoint and correct image sizes in README files * Update GitLab release configuration for buildx setup and disable TLS -* update GitLab sync workflow to include GITLAB_TOKEN and enhance size retrieval for images +* update GitLab sync workflow to include GITLAB_TOKEN and enhance size retrieval + for images * update username and split long action workflow diff --git a/README.md b/README.md index fff856d..b68509d 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,9 @@ # DevContainer Configurations -This repository contains reusable DevContainer configurations for different development -environments. All images now default to a customized Zsh + Oh My Zsh interactive shell experience. +This repository contains reusable DevContainer configurations for different +development environments. All images now default to a customized Zsh + Oh My Zsh +interactive shell experience. ## Available Images @@ -47,8 +48,8 @@ docker pull ghcr.io/iamvikshan/devcontainers/ubuntu-tools:latest ``` > **Alternative Sources:** All images are also available on -> [GitLab Container Registry](https://gitlab.com/vikshan/devcontainers/container_registry) and -> [Docker Hub](https://hub.docker.com/u/vikshan) +> [GitLab Container Registry](https://gitlab.com/vikshan/devcontainers/container_registry) +> and [Docker Hub](https://hub.docker.com/u/vikshan) ## Usage @@ -108,9 +109,12 @@ services: For comprehensive setup instructions and detailed information: - **[Setup Guide](docs/SETUP.md)** - Complete setup instructions for all images -- **[Image Variants](docs/IMAGE_VARIANTS.md)** - Detailed comparison and use cases -- **[Build Commands](docs/BUILD_COMMANDS.md)** - Building and testing images locally -- **[Current Versions](CHANGELOG.md#released-versions)** - Latest versions, sizes, and changelogs +- **[Image Variants](docs/IMAGE_VARIANTS.md)** - Detailed comparison and use + cases +- **[Build Commands](docs/BUILD_COMMANDS.md)** - Building and testing images + locally +- **[Current Versions](CHANGELOG.md#released-versions)** - Latest versions, + sizes, and changelogs ## Image Details @@ -123,19 +127,20 @@ All images include: - **curl** - HTTP requests - **btop** - System resource monitor - **Basic development utilities** +- **oxlint** - Fast JavaScript linter (absent from _ubuntu-tools_ img) +- **oxfmt** - Fast JavaScript/TypeScript formatter (absent from _ubuntu-tools_ img) #### Alpine-based Images (`bun`, `bun-node`) -- **Bun** 1.3.3 - Fast JavaScript runtime -- **Node.js** v22.11.0 _(bun-node only)_ -- **npm** 10.9.0 _(bun-node only)_ -- **ESLint** _(bun-node only)_ +- **Bun** - Fast TypeScript and JavaScript runtime +- **Node.js** _(bun-node only)_ +- **npm** _(bun-node only)_ #### Ubuntu-based Images (`ubuntu-bun`, `ubuntu-bun-node`) -- **Bun** 1.3.3 - Installed via script -- **Node.js** v25.2.0 _(ubuntu-bun-node only)_ -- **npm** 11.6.2 _(ubuntu-bun-node only)_ +- **Bun** - Installed via script +- **Node.js** _(ubuntu-bun-node only)_ +- **npm** _(ubuntu-bun-node only)_ - **sudo** - Administrative access - **Ubuntu package manager** (apt) @@ -143,14 +148,14 @@ All images include: - **Python 3** - General scripting runtime - **jq** - JSON processing utility -- **No Bun, Node.js, npm, or ESLint** - Purpose-built tools-only variant +- **No Bun, Node.js, npm, or oxlint/oxfmt** - Purpose-built tools-only variant - **sudo** - Administrative access - **Ubuntu package manager** (apt) ### Building Locally -Want to build or customize these images? See [`docs/BUILD_COMMANDS.md`](docs/BUILD_COMMANDS.md) for -complete instructions. +Want to build or customize these images? See +[`docs/BUILD_COMMANDS.md`](docs/BUILD_COMMANDS.md) for complete instructions. ## Automated Updates @@ -158,10 +163,13 @@ This repository includes automated systems to keep the devcontainers up to date: ### ๐Ÿ”„ Smart Release System -- **Independent container versioning** - each container has its own semantic version -- **Semantic commit analysis** - automatic version bumping based on conventional commits +- **Independent container versioning** - each container has its own semantic + version +- **Semantic commit analysis** - automatic version bumping based on conventional + commits - **Push-triggered releases** - releases created when changes are pushed to main -- **Weekly scheduled releases** - every Sunday at 2 AM UTC for base image updates +- **Weekly scheduled releases** - every Sunday at 2 AM UTC for base image + updates ### ๐Ÿ” Base Image Monitoring @@ -196,6 +204,7 @@ bun run sync-sizes 1. [Fork](https://gitlab.com/vikshan/devcontainers/-/forks/new) the repository 2. Create a feature branch -3. Submit a [pull request](https://gitlab.com/vikshan/devcontainers/-/merge_requests/new) +3. Submit a + [pull request](https://gitlab.com/vikshan/devcontainers/-/merge_requests/new) ![Alt](https://repobeats.axiom.co/api/embed/8d282a5449c103e135703ea0472d24444b60d064.svg 'Repobeats analytics image') diff --git a/bun.lock b/bun.lock index 8e1831b..715a3e4 100644 --- a/bun.lock +++ b/bun.lock @@ -4,275 +4,51 @@ "workspaces": { "": { "devDependencies": { - "@eslint/js": "^10.0.1", - "@types/eslint__js": "^9.14.0", - "@types/node": "^25.9.3", - "axios": "^1.18.0", - "eslint": "^10.5.0", - "husky": "^9.1.7", - "node-fetch": "^3.3.2", - "prettier": "^3.8.4", - "prettier-plugin-sh": "^0.18.1", - "typescript": "^6.0.3", - "typescript-eslint": "^8.61.1", + "oxfmt": "^0.56.0", }, }, }, "packages": { - "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], + "@oxfmt/binding-android-arm-eabi": ["@oxfmt/binding-android-arm-eabi@0.56.0", "", { "os": "android", "cpu": "arm" }, "sha512-CSCxi7ovYojgfdPOdUb9T508HKeAdDIKeRGg7x8IZwVJrWz9gVgX7MbUnFqtQAE4QvoNo07mj2JlwnOzJw4qqA=="], - "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + "@oxfmt/binding-android-arm64": ["@oxfmt/binding-android-arm64@0.56.0", "", { "os": "android", "cpu": "arm64" }, "sha512-HYJFnd+PkDwf6S9ZPGzXXtjNqvRWFnnhdbWaouh4mi/SxU8wmDuzlMn3xo/wDTGnr4Q1VA7ZzOaE/D4biW0W6A=="], - "@eslint/config-array": ["@eslint/config-array@0.23.5", "", { "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="], + "@oxfmt/binding-darwin-arm64": ["@oxfmt/binding-darwin-arm64@0.56.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-sftR/bEOr+t1gs+evwsHi/Xbq2FAPA2uU3VMr8n6ZU9PoK/IMSfnfu7+OEe/uy1+knhrFl4Wvy7Vkm3uo9mJ7g=="], - "@eslint/config-helpers": ["@eslint/config-helpers@0.6.0", "", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA=="], + "@oxfmt/binding-darwin-x64": ["@oxfmt/binding-darwin-x64@0.56.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-z66SdjLqa3MUPKvTp3Mbb5nSjKSbnYxJGeB+Wx987s8T5hPcIRiBMfnJ6zcPgYtQn3x5xjvdzNVkXrSeYH6ZFg=="], - "@eslint/core": ["@eslint/core@1.2.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ=="], + "@oxfmt/binding-freebsd-x64": ["@oxfmt/binding-freebsd-x64@0.56.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-t2tkrV1vtZyaItSQ71dTi2ZVKZEI39b/LqLT12V5KMfIeXK6N32TUC1jhOXKVQmhECq9j2ZXMQV3JeT1kh9Vmg=="], - "@eslint/js": ["@eslint/js@10.0.1", "", { "peerDependencies": { "eslint": "^10.0.0" }, "optionalPeers": ["eslint"] }, "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA=="], + "@oxfmt/binding-linux-arm-gnueabihf": ["@oxfmt/binding-linux-arm-gnueabihf@0.56.0", "", { "os": "linux", "cpu": "arm" }, "sha512-+gCy+Tp3RHeXQ9y/QrS76lXIpZkbziTyp6hIgjB2MssCwfMph3vG/GEfkhO34Rai1vhYIaUkvv8UT1BcDorJPw=="], - "@eslint/object-schema": ["@eslint/object-schema@3.0.5", "", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="], + "@oxfmt/binding-linux-arm-musleabihf": ["@oxfmt/binding-linux-arm-musleabihf@0.56.0", "", { "os": "linux", "cpu": "arm" }, "sha512-0kKkVvQ2I+FJ2sxQyUu1zJ0yWP5kcWse/yVFnGQSFCXMwSSkfEaUGu0dW774O7nyy3jrcBGap7OSc8dZmU/CdA=="], - "@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.2", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A=="], + "@oxfmt/binding-linux-arm64-gnu": ["@oxfmt/binding-linux-arm64-gnu@0.56.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-npkA2siMbyWRh+wEhi1aTAx4RirukGcGNt8V4Ch86pG+xU9aurqS1MZOnKYMu03ISwat3rB6zkQx51SsB9obNw=="], - "@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], + "@oxfmt/binding-linux-arm64-musl": ["@oxfmt/binding-linux-arm64-musl@0.56.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UekqOjGkV4/MkqreCV9SPIB2jlR3/HbXrmhV1rVXJZ9wfDXMyCMriLtq3tHqLY4PkbVWNtfcm1kMojJ26KLSJw=="], - "@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="], + "@oxfmt/binding-linux-ppc64-gnu": ["@oxfmt/binding-linux-ppc64-gnu@0.56.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-XSzveSpeZMD5XJpew5lRFVtNnT04xd3rJxENXmk7wkZzN9oWzv2aFJyoNDhOtoz69BYaS/fg4SYl+CfEZRpB0Q=="], - "@humanfs/types": ["@humanfs/types@0.15.0", "", {}, "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q=="], + "@oxfmt/binding-linux-riscv64-gnu": ["@oxfmt/binding-linux-riscv64-gnu@0.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-EkQ0nJa7k7HDDIVuPF7WY+k4k+bzdclLYtyIXNt7/OqVghfNiMym6YGppFBgx1XRIHW6QylxBz5OogumPjPJbQ=="], - "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + "@oxfmt/binding-linux-riscv64-musl": ["@oxfmt/binding-linux-riscv64-musl@0.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-dyjAGW8jKRge0ik6U/dgvQG0nVpA3iBlRskQTz5qJLvQWIrySxX5jpqzPetLBNIIZ231KA82fDdi1nLTk8ENCw=="], - "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + "@oxfmt/binding-linux-s390x-gnu": ["@oxfmt/binding-linux-s390x-gnu@0.56.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-60ZGH3LtfqlW8X6vcLdSFY4lvCQYINurttYBKaALnHCDVAUCYJ1LsUgS6p1XOzVlzEDx3yNUZvDF1Lvt59zoZw=="], - "@reteps/dockerfmt": ["@reteps/dockerfmt@0.5.2", "", {}, "sha512-Hbr7yen4fP5TxGM54ucXa4o5NwWXatJ6Bd9I8gp0PValYbI4Rug2Gu+rVv7K7o/efQc3F5ctqWJz47rYaa8zBw=="], + "@oxfmt/binding-linux-x64-gnu": ["@oxfmt/binding-linux-x64-gnu@0.56.0", "", { "os": "linux", "cpu": "x64" }, "sha512-u1suj1tgJHK4ZqB7buCtdbNef2n8+d0lXTPJwLHNmtyK6p+DTpsaoDvmqhQrA56fgKYv4LuRxNtL8YooebKOew=="], - "@types/eslint__js": ["@types/eslint__js@9.14.0", "", { "dependencies": { "@eslint/js": "*" } }, "sha512-s0jepCjOJWB/GKcuba4jISaVpBudw3ClXJ3fUK4tugChUMQsp6kSwuA8Dcx6wFd/JsJqcY8n4rEpa5RTHs5ypA=="], + "@oxfmt/binding-linux-x64-musl": ["@oxfmt/binding-linux-x64-musl@0.56.0", "", { "os": "linux", "cpu": "x64" }, "sha512-aYGLvlQHt80y+qKEtfJY/Nm27G0125Lv+qyh9SJ4Cjc6lXnXjD+ndfhqQnbV24POpMi7rNRi0jvx/0d70FRpCQ=="], - "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], + "@oxfmt/binding-openharmony-arm64": ["@oxfmt/binding-openharmony-arm64@0.56.0", "", { "os": "none", "cpu": "arm64" }, "sha512-H/re/gO+7ysVc+kywHNuzY3C33EN9sQcZhg0kp1ZwOZl7y998ZE5mhnBiuGR/nYI0pqLL5xQfrHVUOJ/cIJsCA=="], - "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + "@oxfmt/binding-win32-arm64-msvc": ["@oxfmt/binding-win32-arm64-msvc@0.56.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-6qLNXfXmtAs8jXDvYMkxk6Wec5SUJoew+ZX1uOZmqaR7ks0EJFbAohuOCELDyJMWyVlxotVG8Xf8m74Bfq0O2w=="], - "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + "@oxfmt/binding-win32-ia32-msvc": ["@oxfmt/binding-win32-ia32-msvc@0.56.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-UXEXuKphAe15bsob4AswNMArCw38XSmUIs3wk1s6e6MX9OWGW/IRWU95s1hZDiVg09STy1jHgyN2qkqbu1FT0w=="], - "@types/node": ["@types/node@25.9.3", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg=="], + "@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.56.0", "", { "os": "win32", "cpu": "x64" }, "sha512-HPyNDjky+NIOuaMvHZflR+kst3YWdUOH2JUQYkf99grqZ5mEBTQM6h9iGy501Z8Xt5xMScrwHOuVCOlqDrktRw=="], - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.61.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.61.1", "@typescript-eslint/type-utils": "8.61.1", "@typescript-eslint/utils": "8.61.1", "@typescript-eslint/visitor-keys": "8.61.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.61.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ=="], + "oxfmt": ["oxfmt@0.56.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.56.0", "@oxfmt/binding-android-arm64": "0.56.0", "@oxfmt/binding-darwin-arm64": "0.56.0", "@oxfmt/binding-darwin-x64": "0.56.0", "@oxfmt/binding-freebsd-x64": "0.56.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.56.0", "@oxfmt/binding-linux-arm-musleabihf": "0.56.0", "@oxfmt/binding-linux-arm64-gnu": "0.56.0", "@oxfmt/binding-linux-arm64-musl": "0.56.0", "@oxfmt/binding-linux-ppc64-gnu": "0.56.0", "@oxfmt/binding-linux-riscv64-gnu": "0.56.0", "@oxfmt/binding-linux-riscv64-musl": "0.56.0", "@oxfmt/binding-linux-s390x-gnu": "0.56.0", "@oxfmt/binding-linux-x64-gnu": "0.56.0", "@oxfmt/binding-linux-x64-musl": "0.56.0", "@oxfmt/binding-openharmony-arm64": "0.56.0", "@oxfmt/binding-win32-arm64-msvc": "0.56.0", "@oxfmt/binding-win32-ia32-msvc": "0.56.0", "@oxfmt/binding-win32-x64-msvc": "0.56.0" }, "peerDependencies": { "svelte": "^5.0.0", "vite-plus": "*" }, "optionalPeers": ["svelte", "vite-plus"], "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-9Dv0wV3zKiyvhjD7bRKaInKmHQ1sPx3RGOjQkGFJbbdQ16576yf8qhMSO9Q9cvHcs+1NpBsRTkuDDYFFPTJ6gw=="], - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.61.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.61.1", "@typescript-eslint/types": "8.61.1", "@typescript-eslint/typescript-estree": "8.61.1", "@typescript-eslint/visitor-keys": "8.61.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg=="], - - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.61.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.61.1", "@typescript-eslint/types": "^8.61.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA=="], - - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.61.1", "", { "dependencies": { "@typescript-eslint/types": "8.61.1", "@typescript-eslint/visitor-keys": "8.61.1" } }, "sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w=="], - - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.61.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg=="], - - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.61.1", "", { "dependencies": { "@typescript-eslint/types": "8.61.1", "@typescript-eslint/typescript-estree": "8.61.1", "@typescript-eslint/utils": "8.61.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw=="], - - "@typescript-eslint/types": ["@typescript-eslint/types@8.61.1", "", {}, "sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA=="], - - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.61.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.61.1", "@typescript-eslint/tsconfig-utils": "8.61.1", "@typescript-eslint/types": "8.61.1", "@typescript-eslint/visitor-keys": "8.61.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg=="], - - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.61.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.61.1", "@typescript-eslint/types": "8.61.1", "@typescript-eslint/typescript-estree": "8.61.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA=="], - - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.61.1", "", { "dependencies": { "@typescript-eslint/types": "8.61.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w=="], - - "acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="], - - "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], - - "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], - - "ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], - - "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], - - "axios": ["axios@1.18.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw=="], - - "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - - "brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], - - "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], - - "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], - - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - - "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], - - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - - "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], - - "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], - - "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - - "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], - - "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - - "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="], - - "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - - "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - - "eslint": ["eslint@10.5.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.6.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ=="], - - "eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="], - - "eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], - - "espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="], - - "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], - - "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], - - "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], - - "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - - "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], - - "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - - "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - - "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], - - "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], - - "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], - - "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], - - "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], - - "follow-redirects": ["follow-redirects@1.16.0", "", { "peerDependencies": { "debug": "*" }, "optionalPeers": ["debug"] }, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], - - "form-data": ["form-data@4.0.6", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.4", "mime-types": "^2.1.35" } }, "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ=="], - - "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], - - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - - "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], - - "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - - "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], - - "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], - - "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], - - "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - - "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], - - "https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], - - "husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="], - - "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], - - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - - "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], - - "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - - "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], - - "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], - - "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], - - "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - - "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], - - "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - - "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - - "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - - "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], - - "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], - - "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], - - "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], - - "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], - - "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], - - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - - "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], - - "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - - "prettier": ["prettier@3.8.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q=="], - - "prettier-plugin-sh": ["prettier-plugin-sh@0.18.1", "", { "dependencies": { "@reteps/dockerfmt": "^0.5.1", "sh-syntax": "^0.5.8" }, "peerDependencies": { "prettier": "^3.6.0" } }, "sha512-uZmU22wBMevjh3rmCatNQqiEer2+5KLa0xYCBX6zQQUQkcNzVL+s6FbPKK6ZSUNUbQk6jMAcQHrYPvuL2W6ihQ=="], - - "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], - - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - - "semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="], - - "sh-syntax": ["sh-syntax@0.5.8", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-JfVoxf4FxQI5qpsPbkHhZo+n6N9YMJobyl4oGEUBb/31oQYlgTjkXQD8PBiafS2UbWoxrTO0Z5PJUBXEPAG1Zw=="], - - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], - - "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - - "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], - - "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], - - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - - "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], - - "typescript-eslint": ["typescript-eslint@8.61.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.61.1", "@typescript-eslint/parser": "8.61.1", "@typescript-eslint/typescript-estree": "8.61.1", "@typescript-eslint/utils": "8.61.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-V7PayAfJokV3pEHgN7/v03D1SpujhRfQtYLbLIiBfDDncdg4PAiRBfoS4cnCANK4jmAPncczi59QO3afiXUlNw=="], - - "undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], - - "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - - "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], - - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - - "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - - "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - - "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - - "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + "tinypool": ["tinypool@2.1.0", "", {}, "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw=="], } } diff --git a/container-versions.json b/container-versions.json deleted file mode 100644 index dd54707..0000000 --- a/container-versions.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "bun": { - "name": "bun", - "version": "0.0.4", - "lastUpdated": "2025-12-02T13:18:54.789Z", - "baseImage": "oven/bun", - "toolVersions": { - "build_date": "2025-12-02T00:00:00Z", - "base_image": "oven/bun:alpine", - "bun_version": "1.3.3", - "alpine_version": "3.21", - "git_version": "2.47.1", - "curl_version": "8.11.1" - } - }, - "bun-node": { - "name": "bun-node", - "version": "0.0.4", - "lastUpdated": "2025-12-02T13:18:54.789Z", - "baseImage": "oven/bun", - "toolVersions": { - "build_date": "2025-12-02T00:00:00Z", - "base_image": "oven/bun:alpine", - "bun_version": "1.3.3", - "node_version": "v22.11.0", - "npm_version": "10.9.0", - "eslint_version": "v9.16.0", - "alpine_version": "3.21", - "git_version": "2.47.1", - "curl_version": "8.11.1" - } - }, - "ubuntu-bun": { - "name": "ubuntu-bun", - "version": "0.0.4", - "lastUpdated": "2025-12-02T13:18:54.789Z", - "baseImage": "library/ubuntu", - "toolVersions": { - "build_date": "2025-11-22T05:11:23Z", - "base_image": "ubuntu:", - "ubuntu_version": "24.04", - "bun_version": "1.3.3", - "git_version": "2.43.0", - "curl_version": "8.5.0" - } - }, - "ubuntu-bun-node": { - "name": "ubuntu-bun-node", - "version": "0.0.4", - "lastUpdated": "2025-12-02T13:18:54.789Z", - "baseImage": "library/ubuntu", - "toolVersions": { - "build_date": "2025-11-22T05:12:22Z", - "base_image": "ubuntu:", - "ubuntu_version": "24.04", - "bun_version": "1.3.3", - "node_version": "v25.2.0", - "npm_version": "11.6.2", - "git_version": "2.43.0", - "curl_version": "8.5.0" - } - }, - "ubuntu-tools": { - "name": "ubuntu-tools", - "version": "0.0.4", - "lastUpdated": "2026-03-13T08:08:01Z", - "baseImage": "library/ubuntu", - "toolVersions": { - "build_date": "2026-03-13T08:08:01Z", - "base_image": "ubuntu:24.04", - "ubuntu_version": "24.04", - "git_version": "2.43.0", - "curl_version": "8.5.0", - "jq_version": "1.7", - "python_version": "3.12.3", - "btop_version": "btop version: 1.3.0" - } - } -} diff --git a/docs/BUILD_COMMANDS.md b/docs/BUILD_COMMANDS.md index 386a30d..481ce1d 100644 --- a/docs/BUILD_COMMANDS.md +++ b/docs/BUILD_COMMANDS.md @@ -2,7 +2,8 @@ ## ๐Ÿณ Docker Setup -The devcontainer includes Docker-in-Docker for building and testing images locally. +The devcontainer includes Docker-in-Docker for building and testing images +locally. ### Verify Docker Setup @@ -245,7 +246,7 @@ docker build --no-cache -t devcontainers/bun:test images/bun ```bash # Check current sizes across local test images docker images --filter=reference='devcontainers/*' --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" - + # Review Docker disk usage docker system df -v ``` @@ -255,5 +256,6 @@ docker build --no-cache -t devcontainers/bun:test images/bun - All test images are tagged with `:test` to avoid conflicts - The examples tag images with `:test` to keep cleanup straightforward - Use `docker logs ` to debug container issues -- Check Dockerfile changes with `docker build --progress=plain` to inspect the full output +- Check Dockerfile changes with `docker build --progress=plain` to inspect the + full output - Images are automatically updated weekly and when base images change diff --git a/docs/IMAGE_VARIANTS.md b/docs/IMAGE_VARIANTS.md index 622ec2b..ba48a7d 100644 --- a/docs/IMAGE_VARIANTS.md +++ b/docs/IMAGE_VARIANTS.md @@ -1,6 +1,7 @@ # DevContainer Image Variants -This document provides detailed information about all 5 available devcontainer images. +This document provides detailed information about all 5 available devcontainer +images. ## ๐Ÿ“Š Images Comparison @@ -9,8 +10,8 @@ This document provides detailed information about all 5 available devcontainer i | **Base Image** | oven/bun (Alpine) | oven/bun (Alpine) | ubuntu:latest | ubuntu:latest | ubuntu:latest | | **Size** | ~133 MB | ~227 MB | ~94 MB | ~166 MB | ~80 MB | | **Bun Version** | 1.3.3 | 1.3.3 | 1.3.3 | 1.3.3 | โŒ | -| **Node.js** | โŒ | โœ… v22.11.0 | โŒ | โœ… v25.2.0 | โŒ | -| **npm** | โŒ | โœ… 10.9.0 | โŒ | โœ… 11.6.2 | โŒ | +| **Node.js** | โŒ | โœ… v22.11.0 | โŒ | โœ… v22.11.0 | โŒ | +| **npm** | โŒ | โœ… 10.9.0 | โŒ | โœ… 10.9.0 | โŒ | | **Package Mgr** | Alpine (apk) | Alpine (apk) | Ubuntu (apt) | Ubuntu (apt) | Ubuntu (apt) | | **Best For** | Pure Bun projects | Full-stack development | Ubuntu workflows | Ubuntu full-stack | Tools-only automation | @@ -31,8 +32,9 @@ This document provides detailed information about all 5 available devcontainer i **Primary Image:** `ghcr.io/iamvikshan/devcontainers/bun:latest` -**Description:** Lightweight Bun development environment based on the official Alpine-based Bun -image. Perfect for pure Bun projects that don't require Node.js compatibility. +**Description:** Lightweight Bun development environment based on the official +Alpine-based Bun image. Perfect for pure Bun projects that don't require Node.js +compatibility. **Key Features:** @@ -65,7 +67,7 @@ image. Perfect for pure Bun projects that don't require Node.js compatibility. "image": "ghcr.io/iamvikshan/devcontainers/bun:latest", "customizations": { "vscode": { - "extensions": ["oven.bun-vscode", "esbenp.prettier-vscode"] + "extensions": ["oven.bun-vscode", "oxc.oxc-vscode"] } }, "postCreateCommand": "bun install" @@ -76,22 +78,23 @@ image. Perfect for pure Bun projects that don't require Node.js compatibility. **Primary Image:** `ghcr.io/iamvikshan/devcontainers/bun-node:latest` -**Description:** Full-featured development environment with both Bun and Node.js. Ideal for projects -that need Bun's performance with Node.js ecosystem compatibility. +**Description:** Full-featured development environment with both Bun and +Node.js. Ideal for projects that need Bun's performance with Node.js ecosystem +compatibility. **Key Features:** - ๐Ÿš€ **Best of both worlds** - Bun speed + Node.js compatibility - ๐Ÿ“ฆ **Full npm ecosystem** - Access to all npm packages -- ๐Ÿ”ง **Latest runtimes** - Bun 1.3.3 + Node.js v22.11.0 -- ๐Ÿ› ๏ธ **Development tools** - ESLint pre-installed +- ๐Ÿ”ง **Latest runtimes** - Bun and Node.js +- ๐Ÿ› ๏ธ **Development tools** - oxlint/oxfmt pre-installed **Included Tools:** - Bun 1.3.3 -- Node.js v22.11.0 -- npm 10.9.0 -- ESLint (global) +- Node.js +- npm +- oxlint/oxfmt (linting and formatting) - Git, SSH client, curl - btop (system resource monitor) - Alpine package manager (apk) @@ -112,7 +115,7 @@ that need Bun's performance with Node.js ecosystem compatibility. "image": "ghcr.io/iamvikshan/devcontainers/bun-node:latest", "customizations": { "vscode": { - "extensions": ["oven.bun-vscode", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] + "extensions": ["oven.bun-vscode", "oxc.oxc-vscode"] } }, "postCreateCommand": "bun install" @@ -123,8 +126,8 @@ that need Bun's performance with Node.js ecosystem compatibility. **Primary Image:** `ghcr.io/iamvikshan/devcontainers/ubuntu-bun:latest` -**Description:** Ubuntu-based Bun environment for developers who prefer Ubuntu's package ecosystem -and tooling. The smallest image in our collection! +**Description:** Ubuntu-based Bun environment for developers who prefer Ubuntu's +package ecosystem and tooling. The smallest image in our collection! **Key Features:** @@ -158,7 +161,7 @@ and tooling. The smallest image in our collection! "image": "ghcr.io/iamvikshan/devcontainers/ubuntu-bun:latest", "customizations": { "vscode": { - "extensions": ["oven.bun-vscode", "esbenp.prettier-vscode"] + "extensions": ["oven.bun-vscode", "oxc.oxc-vscode"] } }, "postCreateCommand": "bun install" @@ -169,8 +172,9 @@ and tooling. The smallest image in our collection! **Primary Image:** `ghcr.io/iamvikshan/devcontainers/ubuntu-bun-node:latest` -**Description:** Complete Ubuntu-based development environment with Bun, Node.js, and npm. Best of -both worlds with Ubuntu's flexibility and modern JavaScript runtimes. +**Description:** Complete Ubuntu-based development environment with Bun, +Node.js, and npm. Best of both worlds with Ubuntu's flexibility and modern +JavaScript runtimes. **Key Features:** @@ -182,9 +186,9 @@ both worlds with Ubuntu's flexibility and modern JavaScript runtimes. **Included Tools:** - Bun 1.3.3 (installed via script) -- Node.js v24.5.0 -- npm 11.5.1 -- ESLint (global) +- Node.js v22.11.0 +- npm 10.9.0 +- oxlint/oxfmt (linting and formatting) - Git, SSH client, curl, unzip - btop (system resource monitor) - sudo (administrative access) @@ -206,7 +210,7 @@ both worlds with Ubuntu's flexibility and modern JavaScript runtimes. "image": "ghcr.io/iamvikshan/devcontainers/ubuntu-bun-node:latest", "customizations": { "vscode": { - "extensions": ["oven.bun-vscode", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] + "extensions": ["oven.bun-vscode", "oxc.oxc-vscode"] } }, "postCreateCommand": "bun install" @@ -217,15 +221,15 @@ both worlds with Ubuntu's flexibility and modern JavaScript runtimes. **Primary Image:** `ghcr.io/iamvikshan/devcontainers/ubuntu-tools:latest` -**Description:** Ubuntu-based tools-only environment for automation, scripting, and utility-heavy -workflows that do not require Bun or Node.js runtimes. +**Description:** Ubuntu-based tools-only environment for automation, scripting, +and utility-heavy workflows that do not require Bun or Node.js runtimes. **Key Features:** - ๐Ÿงฐ **Tools-focused image** - Includes Python, jq, Git, curl, and btop - ๐Ÿง **Ubuntu base** - Standard Ubuntu package ecosystem with apt - ๐Ÿ” **sudo access** - Administrative privileges for setup tasks -- ๐Ÿšซ **No JS runtime bundle** - No Bun, Node.js, npm, or ESLint preinstalled +- ๐Ÿšซ **No JS runtime bundle** - No Bun, Node.js, npm, or oxlint/oxfmt preinstalled **Included Tools:** @@ -251,7 +255,7 @@ workflows that do not require Bun or Node.js runtimes. "image": "ghcr.io/iamvikshan/devcontainers/ubuntu-tools:latest", "customizations": { "vscode": { - "extensions": ["ms-python.python", "esbenp.prettier-vscode"] + "extensions": ["ms-python.python", "oxc.oxc-vscode"] } } } @@ -296,8 +300,8 @@ Do you need Bun runtime? ## ๐Ÿ“ฆ Alternative Sources -While we recommend using GitHub Container Registry as the primary source, all images are available -from multiple registries: +While we recommend using GitHub Container Registry as the primary source, all +images are available from multiple registries: ### GitHub Container Registry (Primary) @@ -317,8 +321,8 @@ registry.gitlab.com/vikshan/devcontainers/[image]:latest docker.io/vikshan/[image]:latest ``` -> **Note:** All registries contain identical images. Choose based on your preference or -> organizational requirements. +> **Note:** All registries contain identical images. Choose based on your +> preference or organizational requirements. ## ๐Ÿ”„ Update Schedule @@ -330,7 +334,8 @@ All images are automatically updated: ## ๐Ÿ“ˆ Version History -See [CHANGELOG.md](../CHANGELOG.md#released-versions) for detailed version history and changelogs. +See [CHANGELOG.md](../CHANGELOG.md#released-versions) for detailed version +history and changelogs. ## ๐Ÿค Contributing @@ -343,7 +348,9 @@ To contribute improvements to any image: ## ๐Ÿ“ž Support -- **Issues**: [GitHub Issues](https://github.com/iamvikshan/devcontainers/issues) -- **Discussions**: [GitHub Discussions](https://github.com/iamvikshan/devcontainers/discussions) +- **Issues**: + [GitHub Issues](https://github.com/iamvikshan/devcontainers/issues) +- **Discussions**: + [GitHub Discussions](https://github.com/iamvikshan/devcontainers/discussions) - **Setup Guide**: [SETUP.md](SETUP.md) - **Build Guide**: [BUILD_COMMANDS.md](BUILD_COMMANDS.md) diff --git a/docs/SETUP.md b/docs/SETUP.md index 8486463..b6faf83 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -1,7 +1,8 @@ # DevContainer Setup Guide -This guide provides comprehensive setup instructions for all devcontainer images in this repository. -Interactive shells default to a customized Zsh + Oh My Zsh setup, so the examples below use `zsh`. +This guide provides comprehensive setup instructions for all devcontainer images +in this repository. Interactive shells default to a customized Zsh + Oh My Zsh +setup, so the examples below use `zsh`. ## ๐Ÿš€ Quick Start @@ -176,7 +177,8 @@ bun run build bun run script.ts ``` -> `ubuntu-tools` does not include Bun by default. Use this section only for Bun-based images. +> `ubuntu-tools` does not include Bun by default. Use this section only for +> Bun-based images. ### npm Commands (Node images only) @@ -255,7 +257,8 @@ Images are automatically updated: - **Base image updates** trigger new builds - **Security patches** applied automatically -Check the [CHANGELOG](../CHANGELOG.md#released-versions) for current versions and release history. +Check the [CHANGELOG](../CHANGELOG.md#released-versions) for current versions +and release history. ## ๐Ÿ“š Additional Resources diff --git a/images/bun/devcontainer.json b/docs/devcontainer.json similarity index 81% rename from images/bun/devcontainer.json rename to docs/devcontainer.json index 3540364..094cad7 100644 --- a/images/bun/devcontainer.json +++ b/docs/devcontainer.json @@ -1,12 +1,11 @@ { - "name": "Alpine + Bun", + "name": "Alpine Bun", "build": { "dockerfile": "Dockerfile" }, "features": { // uncomment the following lines to install common-utils // "ghcr.io/devcontainers/features/common-utils:latest": { - // "installZsh": "true", // "username": "root", // "upgradePackages": "true" // }, @@ -24,7 +23,7 @@ }, "terminal.integrated.defaultProfile.linux": "zsh" }, - "extensions": ["dbaeumer.vscode-eslint", "GitHub.copilot", "esbenp.prettier-vscode", "eamodio.gitlens", "oven.bun-vscode"] + "extensions": ["GitHub.copilot", "eamodio.gitlens", "oven.bun-vscode", "oxc.oxc-vscode"] } }, diff --git a/eslint.config.js b/eslint.config.js deleted file mode 100644 index a76450e..0000000 --- a/eslint.config.js +++ /dev/null @@ -1,26 +0,0 @@ -import eslint from '@eslint/js' -import tseslint from 'typescript-eslint' - -export default tseslint.config( - eslint.configs.recommended, - ...tseslint.configs.recommended, - { - files: ['scripts/**/*.ts'], - languageOptions: { - parserOptions: { - projectService: true, - tsconfigRootDir: import.meta.dirname - } - }, - rules: { - '@typescript-eslint/no-explicit-any': 'warn', - '@typescript-eslint/no-unused-vars': [ - 'warn', - { argsIgnorePattern: '^_', varsIgnorePattern: '^_' } - ] - } - }, - { - ignores: ['node_modules/', 'dist/', 'plans/', 'examples/'] - } -) diff --git a/examples/bun-node-example/.devcontainer.json b/examples/bun-node-example/.devcontainer.json deleted file mode 100644 index 9c06d0d..0000000 --- a/examples/bun-node-example/.devcontainer.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "Project with Bun+Node", - "image": "ghcr.io/iamvikshan/devcontainers/bun-node:latest", - - // Optional: Override or add settings - "customizations": { - "vscode": { - "settings": { - "terminal.integrated.profiles.linux": { - "zsh": { - "path": "zsh" - } - }, - "terminal.integrated.defaultProfile.linux": "zsh" - }, - "extensions": [ - // Add project-specific extensions - ] - } - }, - - "postCreateCommand": "bun install", - "remoteUser": "root" -} diff --git a/examples/bun/.devcontainer.json b/examples/bun/.devcontainer.json deleted file mode 100644 index ba02498..0000000 --- a/examples/bun/.devcontainer.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "Project with Bun", - "image": "ghcr.io/iamvikshan/devcontainers/bun:latest", - - // Optional: Override or add settings - "customizations": { - "vscode": { - "settings": { - "terminal.integrated.profiles.linux": { - "zsh": { - "path": "zsh" - } - }, - "terminal.integrated.defaultProfile.linux": "zsh" - }, - "extensions": [ - // Add project-specific extensions - ] - } - }, - - "postCreateCommand": "bun install", - "remoteUser": "root" -} diff --git a/images/bun-node/Dockerfile b/images/bun-node/Dockerfile index ad1c004..a50b6c1 100644 --- a/images/bun-node/Dockerfile +++ b/images/bun-node/Dockerfile @@ -1,57 +1,35 @@ # syntax=docker/dockerfile:1 -ARG VARIANT=alpine -FROM oven/bun:${VARIANT} -ARG VARIANT +# 1. Base Image Tracking +# renovate: datasource=docker depName=ghcr.io/iamvikshan/devcontainers/bun +ARG BASE_TAG="latest" + +FROM ghcr.io/iamvikshan/devcontainers/bun:${BASE_TAG} + +ARG BASE_TAG + +# 2. Re-declare inherited ARGs purely for Renovate tracking and Label injection +# renovate: datasource=docker depName=oven/bun +ARG BUN_VERSION="1.1.17" +# renovate: datasource=npm depName=oxlint +ARG OXLINT_VERSION="0.7.1" +# renovate: datasource=npm depName=oxfmt +ARG OXFMT_VERSION="0.56.0" + ARG USERNAME=root -# 1. Install packages, global NPM tools, generate versions, and install Antigravity +# 4. Elevate to root, install Node/NPM, and drop privileges +USER root RUN --mount=type=cache,target=/var/cache/apk \ - --mount=type=cache,target=/root/.npm \ - apk add --no-cache \ - zsh git sudo curl jq python3 btop tree tmux bash nodejs npm \ - && npm install -g eslint \ - && mkdir -p /usr/local/share \ - && { \ - echo "# DevContainer Tool Versions"; \ - echo "build_date=$(date -u +%Y-%m-%dT%H:%M:%SZ)"; \ - echo "base_image=oven/bun:${VARIANT}"; \ - echo "bun_version=$(bun --version)"; \ - echo "zsh_version=$(zsh --version | cut -d' ' -f2)"; \ - echo "node_version=$(node --version)"; \ - echo "npm_version=$(npm --version)"; \ - echo "eslint_version=$(eslint --version)"; \ - echo "alpine_version=$(cat /etc/alpine-release)"; \ - echo "git_version=$(git --version | cut -d' ' -f3)"; \ - echo "curl_version=$(curl --version | head -n1 | cut -d' ' -f2)"; \ - echo "jq_version=$(jq --version | cut -d'-' -f2)"; \ - echo "python_version=$(python3 --version | cut -d' ' -f2)"; \ - echo "btop_version=$(btop --version | head -n1)"; \ - echo "tree_version=$(tree --version | cut -d' ' -f2)"; \ - echo "tmux_version=$(tmux -V | cut -d' ' -f2)"; \ - } > /usr/local/share/tool-versions.txt \ - && curl -fsSL https://antigravity.google/cli/install.sh | bash \ - && mv /root/.local/bin/agy /usr/local/bin/agy \ - && rm -rf /root/.local /root/.cache - -# 2. Consolidated User, Remote ZSH configuration, and Tmux setup -RUN if [ "${USERNAME}" != "root" ]; then \ - adduser -D -s /bin/zsh "${USERNAME}" && \ - addgroup "${USERNAME}" wheel && \ - echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \ - HOME_DIR="/home/${USERNAME}"; \ - else \ - HOME_DIR="/root"; \ - fi && \ - mkdir -p "${HOME_DIR}" && \ - git clone --depth=1 https://github.com/ohmyzsh/ohmyzsh.git "${HOME_DIR}/.oh-my-zsh" && \ - git clone --depth=1 https://github.com/zsh-users/zsh-autosuggestions "${HOME_DIR}/.oh-my-zsh/custom/plugins/zsh-autosuggestions" && \ - git clone --depth=1 https://github.com/zsh-users/zsh-syntax-highlighting "${HOME_DIR}/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting" && \ - curl -fsSL https://raw.githubusercontent.com/iamvikshan/.github/refs/heads/main/scripts/universal.zshrc -o "${HOME_DIR}/.zshrc" && \ - echo 'set-option -g default-shell /bin/zsh' > "${HOME_DIR}/.tmux.conf" && \ - if [ "${USERNAME}" != "root" ]; then \ - chown -R "${USERNAME}:${USERNAME}" "${HOME_DIR}"; \ - fi + apk add --no-cache nodejs npm USER ${USERNAME} + +# 5. Inject Metadata for Skopeo Extraction +LABEL org.opencontainers.image.title="bun-node" \ + org.opencontainers.image.base.name="ghcr.io/iamvikshan/devcontainers/bun:${BASE_TAG}" \ + devcontainer.tool.bun="${BUN_VERSION}" \ + devcontainer.tool.oxlint="${OXLINT_VERSION}" \ + devcontainer.tool.oxfmt="${OXFMT_VERSION}" + CMD ["sleep", "infinity"] \ No newline at end of file diff --git a/images/bun-node/devcontainer.json b/images/bun-node/devcontainer.json deleted file mode 100644 index fdbd396..0000000 --- a/images/bun-node/devcontainer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "Alpine + Bun + Node.js", - "build": { - "dockerfile": "Dockerfile" - }, - "features": { - // uncomment the following lines to install common-utils - // "ghcr.io/devcontainers/features/common-utils:latest": { - // "installZsh": "true", - // "username": "root", - // "upgradePackages": "true" - // }, - // git is preinstalled in the bun-node image - }, - - "customizations": { - "vscode": { - "settings": { - "terminal.integrated.profiles.linux": { - "zsh": { - "path": "zsh" - } - }, - "terminal.integrated.defaultProfile.linux": "zsh" - }, - "extensions": ["dbaeumer.vscode-eslint", "GitHub.copilot", "esbenp.prettier-vscode", "eamodio.gitlens", "oven.bun-vscode"] - } - }, - - "postCreateCommand": "bun install", - "remoteUser": "root" -} diff --git a/images/bun/Dockerfile b/images/bun/Dockerfile index a8e7b7f..ee1fe8a 100644 --- a/images/bun/Dockerfile +++ b/images/bun/Dockerfile @@ -1,40 +1,38 @@ # syntax=docker/dockerfile:1 -ARG VARIANT=alpine -FROM oven/bun:${VARIANT} -ARG VARIANT +# 1. Define the version ARGs for Renovate to track +# renovate: datasource=docker depName=oven/bun +ARG BUN_VERSION="1.1.17" +# renovate: datasource=npm depName=oxlint +ARG OXLINT_VERSION="0.7.1" +# renovate: datasource=npm depName=oxfmt +ARG OXFMT_VERSION="0.56.0" + +# Inject the tracked Bun version into the base image +FROM oven/bun:${BUN_VERSION}-alpine + +# Re-declare ARGs after FROM so they are available in the build environment +ARG BUN_VERSION +ARG OXLINT_VERSION +ARG OXFMT_VERSION ARG USERNAME=root -# 1. Install packages, generate versions tracker, and install Antigravity in ONE layer -# Added bash (required for antigravity script), tree, and tmux. +# 2. Install unpinned OS packages & pinned global tools (using Bun) +# We completely removed the tool-versions.txt generation block RUN --mount=type=cache,target=/var/cache/apk \ apk add --no-cache \ zsh git sudo curl jq python3 btop tree tmux bash \ - && mkdir -p /usr/local/share \ - && { \ - echo "# DevContainer Tool Versions"; \ - echo "build_date=$(date -u +%Y-%m-%dT%H:%M:%SZ)"; \ - echo "base_image=oven/bun:${VARIANT}"; \ - echo "bun_version=$(bun --version)"; \ - echo "zsh_version=$(zsh --version | cut -d' ' -f2)"; \ - echo "alpine_version=$(cat /etc/alpine-release)"; \ - echo "git_version=$(git --version | cut -d' ' -f3)"; \ - echo "curl_version=$(curl --version | head -n1 | cut -d' ' -f2)"; \ - echo "jq_version=$(jq --version | cut -d'-' -f2)"; \ - echo "python_version=$(python3 --version | cut -d' ' -f2)"; \ - echo "btop_version=$(btop --version | head -n1)"; \ - echo "tree_version=$(tree --version | cut -d' ' -f2)"; \ - echo "tmux_version=$(tmux -V | cut -d' ' -f2)"; \ - } > /usr/local/share/tool-versions.txt \ - && curl -fsSL https://antigravity.google/cli/install.sh | bash \ - && mv /root/.local/bin/agy /usr/local/bin/agy \ - && rm -rf /root/.local /root/.cache - -# 2. Consolidated User, Remote ZSH configuration, and Tmux setup + && bun install -g oxlint@${OXLINT_VERSION} oxfmt@${OXFMT_VERSION} + +# 3. Consolidated User, Remote ZSH configuration, and Tmux setup RUN if [ "${USERNAME}" != "root" ]; then \ + echo "${USERNAME}" | grep -Eq '^[a-z_][a-z0-9_-]*[$]?$' || { echo "Invalid USERNAME: ${USERNAME}" >&2; exit 1; } && \ adduser -D -s /bin/zsh "${USERNAME}" && \ addgroup "${USERNAME}" wheel && \ - echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \ + mkdir -p /etc/sudoers.d && \ + echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" > "/etc/sudoers.d/${USERNAME}" && \ + chmod 0440 "/etc/sudoers.d/${USERNAME}" && \ + visudo -cf "/etc/sudoers.d/${USERNAME}" && \ HOME_DIR="/home/${USERNAME}"; \ else \ HOME_DIR="/root"; \ @@ -50,4 +48,12 @@ RUN if [ "${USERNAME}" != "root" ]; then \ fi USER ${USERNAME} + +# 4. Inject Metadata for Skopeo Extraction +LABEL org.opencontainers.image.title="bun" \ + org.opencontainers.image.base.name="oven/bun:${BUN_VERSION}-alpine" \ + devcontainer.tool.bun="${BUN_VERSION}" \ + devcontainer.tool.oxlint="${OXLINT_VERSION}" \ + devcontainer.tool.oxfmt="${OXFMT_VERSION}" + CMD ["sleep", "infinity"] \ No newline at end of file diff --git a/images/ubuntu-bun-node/Dockerfile b/images/ubuntu-bun-node/Dockerfile index d5037b3..2ecdb85 100644 --- a/images/ubuntu-bun-node/Dockerfile +++ b/images/ubuntu-bun-node/Dockerfile @@ -1,74 +1,56 @@ # syntax=docker/dockerfile:1 -ARG VARIANT=latest -FROM ubuntu:${VARIANT} -ARG VARIANT -ARG USERNAME=root -ARG NPM_GLOBAL=/usr/local/share/npm-global +# 1. Define Bun Source for Multi-Stage Copy +# renovate: datasource=docker depName=oven/bun +ARG BUN_VERSION="1.1.17" +FROM oven/bun:${BUN_VERSION}-alpine AS bun_source -# Add NPM global to PATH and set noninteractive frontend -ENV PATH=${NPM_GLOBAL}/bin:/usr/local/bun/bin:${PATH} -ENV DEBIAN_FRONTEND=noninteractive +# 2. Base Image Tracking +# renovate: datasource=docker depName=ghcr.io/iamvikshan/devcontainers/ubuntu-tools +ARG BASE_TAG="latest" +FROM ghcr.io/iamvikshan/devcontainers/ubuntu-tools:${BASE_TAG} -# 1. Consolidated User Creation & Permissions -RUN if [ "${USERNAME}" != "root" ] && ! id -u ${USERNAME} > /dev/null 2>&1; then \ - useradd -m -s /bin/zsh ${USERNAME} \ - && usermod -aG sudo ${USERNAME} \ - && echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers; \ - fi +# 3. Re-declare ARGs for Renovate tracking and Label injection +ARG BUN_VERSION +ARG BASE_TAG -# 2. Multi-stage Bun install (Instant copy, no curl/unzip) -COPY --from=oven/bun:latest /usr/local/bin/bun /usr/local/bin/bun -RUN ln -s /usr/local/bin/bun /usr/local/bin/bunx +# renovate: datasource=node-version depName=node +ARG NODE_VERSION="22.11.0" +# renovate: datasource=npm depName=oxlint +ARG OXLINT_VERSION="0.4.3" +# renovate: datasource=npm depName=oxfmt +ARG OXFMT_VERSION="0.56.0" + +ARG USERNAME=root -# 3. Packages, Node.js, Global NPM, Antigravity, and Version Tracking -RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked \ - --mount=type=cache,target=/root/.npm \ - apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates zsh git sudo curl unzip jq python3 btop tree tmux bash \ - && curl -fsSL https://deb.nodesource.com/setup_current.x | bash - \ - && apt-get install -y nodejs \ - && mkdir -p ${NPM_GLOBAL} \ - && npm config set prefix ${NPM_GLOBAL} \ - && npm install -g eslint \ - && curl -fsSL https://antigravity.google/cli/install.sh | bash \ - && mv /root/.local/bin/agy /usr/local/bin/agy \ - && rm -rf /root/.local \ - && mkdir -p /usr/local/share \ - && { \ - echo "# DevContainer Tool Versions"; \ - echo "build_date=$(date -u +%Y-%m-%dT%H:%M:%SZ)"; \ - echo "base_image=ubuntu:${VARIANT}"; \ - echo "ubuntu_version=$(cat /etc/os-release | grep VERSION_ID | cut -d'=' -f2 | tr -d '\"')"; \ - echo "bun_version=$(bun --version)"; \ - echo "node_version=$(node --version)"; \ - echo "npm_version=$(npm --version)"; \ - echo "eslint_version=$(eslint --version)"; \ - echo "zsh_version=$(zsh --version | cut -d' ' -f2)"; \ - echo "git_version=$(git --version | cut -d' ' -f3)"; \ - echo "curl_version=$(curl --version | head -n1 | cut -d' ' -f2)"; \ - echo "jq_version=$(jq --version | cut -d'-' -f2)"; \ - echo "python_version=$(python3 --version | cut -d' ' -f2)"; \ - echo "btop_version=$(btop --version | head -n1)"; \ - echo "tree_version=$(tree --version | cut -d' ' -f2)"; \ - echo "tmux_version=$(tmux -V | cut -d' ' -f2)"; \ - } > /usr/local/share/tool-versions.txt \ - && grep -Eq '^base_image=ubuntu:[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$' /usr/local/share/tool-versions.txt \ - && grep -Eq '^bun_version=.+$' /usr/local/share/tool-versions.txt \ - && grep -Eq '^node_version=.+$' /usr/local/share/tool-versions.txt \ - && if [ "${USERNAME}" != "root" ]; then chown -R ${USERNAME}:${USERNAME} ${NPM_GLOBAL}; fi \ - && rm -rf /var/lib/apt/lists/* +# 4. Elevate to root to copy binaries and install Node + global tools +USER root -# 4. Remote ZSH Configuration & Tmux Injection -RUN HOME_DIR=$(if [ "${USERNAME}" = "root" ]; then echo "/root"; else echo "/home/${USERNAME}"; fi) \ - && mkdir -p "${HOME_DIR}" \ - && git clone --depth=1 https://github.com/ohmyzsh/ohmyzsh.git "${HOME_DIR}/.oh-my-zsh" \ - && git clone --depth=1 https://github.com/zsh-users/zsh-autosuggestions "${HOME_DIR}/.oh-my-zsh/custom/plugins/zsh-autosuggestions" \ - && git clone --depth=1 https://github.com/zsh-users/zsh-syntax-highlighting "${HOME_DIR}/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting" \ - && curl -fsSL https://raw.githubusercontent.com/iamvikshan/.github/refs/heads/main/scripts/universal.zshrc -o "${HOME_DIR}/.zshrc" \ - && echo 'set-option -g default-shell /bin/zsh' > "${HOME_DIR}/.tmux.conf" \ - && if [ "${USERNAME}" != "root" ]; then chown -R "${USERNAME}:${USERNAME}" "${HOME_DIR}"; fi +# Copy Bun binary and symlink bunx +COPY --from=bun_source /usr/local/bin/bun /usr/local/bin/bun +RUN ln -s /usr/local/bin/bun /usr/local/bin/bunx + +# Install Node.js deterministically for the correct architecture, then install formatters +ARG TARGETARCH +RUN ARCH="$(case "$TARGETARCH" in amd64) echo x64 ;; arm64) echo arm64 ;; *) echo "Unsupported TARGETARCH: $TARGETARCH" >&2; exit 1 ;; esac)" \ + && NODE_TARBALL="node-v${NODE_VERSION}-linux-${ARCH}.tar.xz" \ + && curl -fsSLO "https://nodejs.org/dist/v${NODE_VERSION}/${NODE_TARBALL}" \ + && curl -fsSLO "https://nodejs.org/dist/v${NODE_VERSION}/SHASUMS256.txt" \ + && grep " ${NODE_TARBALL}$" SHASUMS256.txt > node_checksum.txt \ + && sha256sum -c node_checksum.txt \ + && tar -xJf "${NODE_TARBALL}" -C /usr/local --strip-components=1 \ + && rm -f "${NODE_TARBALL}" SHASUMS256.txt node_checksum.txt \ + && bun install -g oxlint@${OXLINT_VERSION} oxfmt@${OXFMT_VERSION} +# 5. Drop privileges USER ${USERNAME} + +# 6. Inject Metadata for Skopeo Extraction +LABEL org.opencontainers.image.title="ubuntu-bun-node" \ + org.opencontainers.image.base.name="ghcr.io/iamvikshan/devcontainers/ubuntu-tools:${BASE_TAG}" \ + devcontainer.tool.bun="${BUN_VERSION}" \ + devcontainer.tool.node="${NODE_VERSION}" \ + devcontainer.tool.oxlint="${OXLINT_VERSION}" \ + devcontainer.tool.oxfmt="${OXFMT_VERSION}" + CMD ["sleep", "infinity"] \ No newline at end of file diff --git a/images/ubuntu-bun/Dockerfile b/images/ubuntu-bun/Dockerfile index d90628c..2239ad8 100644 --- a/images/ubuntu-bun/Dockerfile +++ b/images/ubuntu-bun/Dockerfile @@ -1,66 +1,41 @@ # syntax=docker/dockerfile:1 -ARG VARIANT=latest -FROM ubuntu:${VARIANT} -ARG VARIANT -ARG USERNAME=root -ENV DEBIAN_FRONTEND=noninteractive +# 1. Define Bun Source for Multi-Stage Copy +# renovate: datasource=docker depName=oven/bun +ARG BUN_VERSION="1.1.17" +FROM oven/bun:${BUN_VERSION}-alpine AS bun_source -# 1. Consolidated User Creation & Permissions -# Merged 4 separate RUN blocks into a single layer to prevent snapshot bloat -RUN if [ "${USERNAME}" != "root" ] && ! id -u ${USERNAME} > /dev/null 2>&1; then \ - useradd -m -s /bin/zsh ${USERNAME} \ - && usermod -aG sudo ${USERNAME} \ - && echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers; \ - fi +# 2. Base Image Tracking +# renovate: datasource=docker depName=ghcr.io/iamvikshan/devcontainers/ubuntu-tools +ARG BASE_TAG="latest" +FROM ghcr.io/iamvikshan/devcontainers/ubuntu-tools:${BASE_TAG} -# 2. Install Packages (with BuildKit Cache) -# Added tree and tmux to the baseline requirements -RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked \ - apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates zsh git sudo curl unzip jq python3 btop tree tmux \ - && rm -rf /var/lib/apt/lists/* +# 3. Re-declare ARGs for Renovate tracking and Label injection +ARG BUN_VERSION +ARG BASE_TAG -# 3. Multi-stage Bun install & The Node Checkmate -# Bypasses curl/unzip entirely and ensures strict Bun enforcement -COPY --from=oven/bun:latest /usr/local/bin/bun /usr/local/bin/bun -RUN ln -s /usr/local/bin/bun /usr/local/bin/bunx \ - && ln -s /usr/local/bin/bun /usr/local/bin/node +# renovate: datasource=npm depName=oxlint +ARG OXLINT_VERSION="0.4.3" +# renovate: datasource=npm depName=oxfmt +ARG OXFMT_VERSION="0.56.0" -# 4. Antigravity CLI & Tool Versions Tracker -# Replaced subshells with direct echoing for speed, and dropped the lsb_release -# dependency since /etc/os-release is universally available in Ubuntu images. -RUN curl -fsSL https://antigravity.google/cli/install.sh | bash \ - && mv /root/.local/bin/agy /usr/local/bin/agy \ - && rm -rf /root/.local /root/.cache \ - && mkdir -p /usr/local/share \ - && { \ - echo "# DevContainer Tool Versions"; \ - echo "build_date=$(date -u +%Y-%m-%dT%H:%M:%SZ)"; \ - echo "base_image=ubuntu:${VARIANT}"; \ - echo "ubuntu_version=$(cat /etc/os-release | grep VERSION_ID | cut -d'=' -f2 | tr -d '\"')"; \ - echo "bun_version=$(bun --version)"; \ - echo "zsh_version=$(zsh --version | cut -d' ' -f2)"; \ - echo "git_version=$(git --version | cut -d' ' -f3)"; \ - echo "curl_version=$(curl --version | head -n1 | cut -d' ' -f2)"; \ - echo "jq_version=$(jq --version | cut -d'-' -f2)"; \ - echo "python_version=$(python3 --version | cut -d' ' -f2)"; \ - echo "btop_version=$(btop --version | head -n1)"; \ - echo "tree_version=$(tree --version | cut -d' ' -f2)"; \ - echo "tmux_version=$(tmux -V | cut -d' ' -f2)"; \ - } > /usr/local/share/tool-versions.txt \ - && grep -Eq '^base_image=ubuntu:[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$' /usr/local/share/tool-versions.txt +ARG USERNAME=root -# 5. Remote ZSH Configuration & Tmux Injection -RUN HOME_DIR=$(if [ "${USERNAME}" = "root" ]; then echo "/root"; else echo "/home/${USERNAME}"; fi) \ - && mkdir -p "${HOME_DIR}" \ - && git clone --depth=1 https://github.com/ohmyzsh/ohmyzsh.git "${HOME_DIR}/.oh-my-zsh" \ - && git clone --depth=1 https://github.com/zsh-users/zsh-autosuggestions "${HOME_DIR}/.oh-my-zsh/custom/plugins/zsh-autosuggestions" \ - && git clone --depth=1 https://github.com/zsh-users/zsh-syntax-highlighting "${HOME_DIR}/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting" \ - && curl -fsSL https://raw.githubusercontent.com/iamvikshan/.github/refs/heads/main/scripts/universal.zshrc -o "${HOME_DIR}/.zshrc" \ - && echo 'set-option -g default-shell /bin/zsh' > "${HOME_DIR}/.tmux.conf" \ - && if [ "${USERNAME}" != "root" ]; then chown -R "${USERNAME}:${USERNAME}" "${HOME_DIR}"; fi +# 4. Elevate to root to copy binaries and install global tools +USER root +COPY --from=bun_source /usr/local/bin/bun /usr/local/bin/bun +RUN ln -s /usr/local/bin/bun /usr/local/bin/bunx \ + && bun install -g oxlint@${OXLINT_VERSION} oxfmt@${OXFMT_VERSION} + +# 5. Drop privileges USER ${USERNAME} + +# 6. Inject Metadata for Skopeo Extraction +LABEL org.opencontainers.image.title="ubuntu-bun" \ + org.opencontainers.image.base.name="ghcr.io/iamvikshan/devcontainers/ubuntu-tools:${BASE_TAG}" \ + devcontainer.tool.bun="${BUN_VERSION}" \ + devcontainer.tool.oxlint="${OXLINT_VERSION}" \ + devcontainer.tool.oxfmt="${OXFMT_VERSION}" + CMD ["sleep", "infinity"] \ No newline at end of file diff --git a/images/ubuntu-tools/Dockerfile b/images/ubuntu-tools/Dockerfile index 88119f5..5a7d9c6 100644 --- a/images/ubuntu-tools/Dockerfile +++ b/images/ubuntu-tools/Dockerfile @@ -1,52 +1,38 @@ # syntax=docker/dockerfile:1 -ARG VARIANT=latest -FROM ubuntu:${VARIANT} -ARG VARIANT +# renovate: datasource=docker depName=ubuntu +ARG UBUNTU_VERSION="26.04" + +FROM ubuntu:${UBUNTU_VERSION} + +# Re-declare ARGs after FROM +ARG UBUNTU_VERSION ARG USERNAME=root ENV DEBIAN_FRONTEND=noninteractive -# 1. Consolidated User Creation & Security Validation -# Merges regex validation, user creation, and safe sudoers.d configuration +# 2. Consolidated User Creation & Permissions RUN if [ "${USERNAME}" != "root" ]; then \ - echo "${USERNAME}" | grep -Eq '^[a-z_][a-z0-9_-]*[$]?$' || { echo "Invalid USERNAME build arg: ${USERNAME}" >&2; exit 1; }; \ - if ! id -u "${USERNAME}" > /dev/null 2>&1; then \ - useradd -m -s /bin/zsh "${USERNAME}"; \ + echo "${USERNAME}" | grep -Eq '^[a-z_][a-z0-9_-]*[$]?$' || { echo "Invalid USERNAME: ${USERNAME}" >&2; exit 1; }; \ + if ! id -u ${USERNAME} > /dev/null 2>&1; then \ + useradd -m -s /bin/zsh ${USERNAME}; \ fi \ - && usermod -aG sudo "${USERNAME}" \ - && mkdir -p /etc/sudoers.d \ - && echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" > "/etc/sudoers.d/${USERNAME}" \ - && chmod 0440 "/etc/sudoers.d/${USERNAME}" \ - && visudo -cf "/etc/sudoers.d/${USERNAME}"; \ + && usermod -aG sudo ${USERNAME}; \ fi -# 2. Install Packages, Antigravity CLI, and Clean Up (with BuildKit Caching) +# 3. OS Packages & Antigravity CLI (BuildKit Cached) RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ apt-get update && apt-get install -y --no-install-recommends \ ca-certificates zsh git sudo curl unzip jq python3 btop tree tmux bash \ + && if [ "${USERNAME}" != "root" ]; then \ + mkdir -p /etc/sudoers.d \ + && echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" > "/etc/sudoers.d/${USERNAME}" \ + && chmod 0440 "/etc/sudoers.d/${USERNAME}" \ + && visudo -cf "/etc/sudoers.d/${USERNAME}"; \ + fi \ && curl -fsSL https://antigravity.google/cli/install.sh | bash \ && mv /root/.local/bin/agy /usr/local/bin/agy \ - && rm -rf /root/.local /root/.cache \ - && rm -rf /var/lib/apt/lists/* - -# 3. Tool Versions Tracker -RUN mkdir -p /usr/local/share \ - && { \ - echo "# DevContainer Tool Versions"; \ - echo "build_date=$(date -u +%Y-%m-%dT%H:%M:%SZ)"; \ - echo "base_image=ubuntu:${VARIANT}"; \ - echo "ubuntu_version=$(grep VERSION_ID /etc/os-release | cut -d'=' -f2 | tr -d '\"')"; \ - echo "zsh_version=$(zsh --version | cut -d' ' -f2)"; \ - echo "git_version=$(git --version | cut -d' ' -f3)"; \ - echo "curl_version=$(curl --version | head -n1 | cut -d' ' -f2)"; \ - echo "jq_version=$(jq --version | cut -d'-' -f2)"; \ - echo "python_version=$(python3 --version | cut -d' ' -f2)"; \ - echo "btop_version=$(btop --version | head -n1)"; \ - echo "tree_version=$(tree --version | cut -d' ' -f2)"; \ - echo "tmux_version=$(tmux -V | cut -d' ' -f2)"; \ - } > /usr/local/share/tool-versions.txt \ - && grep -Eq '^base_image=ubuntu:[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$' /usr/local/share/tool-versions.txt + && rm -rf /root/.local /var/lib/apt/lists/* # 4. Remote ZSH Configuration & Tmux Injection RUN HOME_DIR=$(if [ "${USERNAME}" = "root" ]; then echo "/root"; else echo "/home/${USERNAME}"; fi) \ @@ -59,4 +45,10 @@ RUN HOME_DIR=$(if [ "${USERNAME}" = "root" ]; then echo "/root"; else echo "/hom && if [ "${USERNAME}" != "root" ]; then chown -R "${USERNAME}:${USERNAME}" "${HOME_DIR}"; fi USER ${USERNAME} + +# 5. Inject Metadata for Skopeo Extraction +LABEL org.opencontainers.image.title="ubuntu-tools" \ + org.opencontainers.image.base.name="ubuntu:${UBUNTU_VERSION}" \ + devcontainer.tool.ubuntu="${UBUNTU_VERSION}" + CMD ["sleep", "infinity"] \ No newline at end of file diff --git a/package.json b/package.json index c09af76..ed6f706 100644 --- a/package.json +++ b/package.json @@ -1,26 +1,12 @@ { "scripts": { - "s": "bun scripts/changelogManager.ts --sync-only", "prepare": "test -d .git && husky || true", - "f": "(git diff --name-only --diff-filter=ACMR HEAD && git ls-files --others --exclude-standard) | xargs bunx prettier --write --ignore-unknown", - "f:all": "prettier --write .", - "f:check": "prettier --check .", - "lint:fix": "eslint . --fix", - "check": "eslint . && tsgo --noEmit", + "f": "oxfmt --write .", + "f:check": "oxfmt --check .", "bs": "mkdir -p scripts && curl -sL https://raw.githubusercontent.com/iamvikshan/.github/main/scripts/bootstrap.sh > scripts/bootstrap.sh && chmod +x scripts/bootstrap.sh && scripts/bootstrap.sh", "upd": "sudo apt update && sudo apt upgrade -y && bun upgrade && bun update --latest && bun i && bun f" }, "devDependencies": { - "@eslint/js": "^10.0.1", - "@types/eslint__js": "^9.14.0", - "@types/node": "^25.9.3", - "axios": "^1.18.0", - "eslint": "^10.5.0", - "husky": "^9.1.7", - "node-fetch": "^3.3.2", - "prettier": "^3.8.4", - "prettier-plugin-sh": "^0.18.1", - "typescript": "^6.0.3", - "typescript-eslint": "^8.61.1" + "oxfmt": "^0.56.0" } } diff --git a/plans/add-btop-plan.md b/plans/add-btop-plan.md deleted file mode 100644 index db0f612..0000000 --- a/plans/add-btop-plan.md +++ /dev/null @@ -1,35 +0,0 @@ -## Plan: Add btop to DevContainer Images - -Add `btop` (a modern resource monitor) to all 4 Docker images and update documentation to list it -alongside the other included tools. - -**Phase Count Rationale:** - -- Single contained change: adding one package across 4 Dockerfiles + docs -- No architectural risk, no migrations, no unknowns -- All changes are independent and low-risk - -**Phases: 1** - -1. **โœ… Phase 1: Add btop to all images and documentation** - - **Objective:** Install btop in all 4 Dockerfiles, add version extraction, and update all - documentation - - **Files/Functions to Modify/Create:** - - `images/bun/Dockerfile` โ€” add `btop` to `apk add` + version extraction - - `images/bun-node/Dockerfile` โ€” add `btop` to `apk add` + version extraction - - `images/ubuntu-bun/Dockerfile` โ€” add `btop` to `apt-get install` + version extraction - - `images/ubuntu-bun-node/Dockerfile` โ€” add `btop` to `apt-get install` + version extraction - - `scripts/toolVersionExtractor.ts` โ€” add btop to manual fallback extraction commands - - `docs/IMAGE_VARIANTS.md` โ€” add btop to "Included Tools" for all 4 images - - `README.md` โ€” add btop to "What's Included" section - - **Tests to Write:** N/A (infrastructure/Dockerfiles, no unit tests applicable) - - **Steps:** - 1. Add `btop` package to all 4 Dockerfiles' install commands - 2. Add `btop_version` extraction to tool-versions.txt blocks in each Dockerfile - 3. Update `toolVersionExtractor.ts` manual fallback to include btop - 4. Update `IMAGE_VARIANTS.md` included tools lists for all 4 image sections - 5. Update `README.md` "What's Included" section to mention btop - -**Open Questions:** - -1. btop version output format โ€” will use `btop --version | head -n1` for safe extraction diff --git a/plans/eslint-setup-plan.md b/plans/eslint-setup-plan.md deleted file mode 100644 index 83bb2f5..0000000 --- a/plans/eslint-setup-plan.md +++ /dev/null @@ -1,44 +0,0 @@ -## Plan: Set Up ESLint & Fix Type Errors - -Set up ESLint v10 flat config and tsconfig.json for the project, then fix all existing TypeScript -type errors (primarily unsafe `catch` blocks) so `bun check` passes cleanly. - -**Phase Count Rationale:** - -- Phase 1 creates configs so we can see real errors from `tsc --noEmit` and `eslint` -- Phase 2 fixes all discovered type errors -- Two phases needed because actual error list depends on config strictness - -**Phases: 2** - -1. **โœ… Phase 1: Create tsconfig.json and eslint.config.js** - - **Objective:** Add both config files + install deps so `bun check` can run - - **Files/Functions to Modify/Create:** - - `tsconfig.json` โ€” strict, ESNext/NodeNext, include scripts/\*_/_.ts - - `eslint.config.js` โ€” ESLint v10 flat config with typescript-eslint - - **Tests to Write:** N/A (config files) - - **Steps:** - 1. Install typescript-eslint and @eslint/js - 2. Create tsconfig.json - 3. Create eslint.config.js - 4. Run `bun check` to capture all errors for Phase 2 - -2. **โœ… Phase 2: Fix all TypeScript type errors** - - **Objective:** Fix all catch blocks and type errors so `bun check` passes cleanly - - **Files/Functions to Modify:** - - scripts/issueManager.ts โ€” 6 unsafe catch blocks - - scripts/registryClient.ts โ€” 5 unsafe catch blocks + 1 empty catch - - scripts/toolVersionExtractor.ts โ€” 3 unsafe catch blocks - - scripts/versionManager.ts โ€” 1 empty catch + 3 catch (error: any) blocks - - scripts/changelogManager.ts โ€” 2 catch (error: any) blocks - - scripts/imageOperations.ts โ€” 6 catch blocks (may need narrowing) - - scripts/releaseOrchestrator.ts โ€” 4 catch blocks - - scripts/changeDetector.ts โ€” 1 catch block - - **Tests to Write:** N/A (infrastructure scripts) - - **Steps:** - 1. Fix all unsafe error.message accesses with proper type narrowing - 2. Replace catch (error: any) with catch (error: unknown) + narrowing - 3. Fix any other tsc/eslint errors discovered - 4. Run `bun check` to verify 0 errors - -**Open Questions:** None โ€” proceeding with implementation. diff --git a/renovate.json b/renovate.json deleted file mode 100644 index 147c2b6..0000000 --- a/renovate.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:recommended", - ":dependencyDashboard", - ":semanticCommits" - ], - "reviewers": ["iamvikshan"], - "assignees": ["iamvikshan"], - "minimumReleaseAge": "3 days", - "semanticCommitType": "chore", - "baseBranchPatterns": ["main"], - "automerge": true, - "prConcurrentLimit": 10, - "prHourlyLimit": 5, - "labels": ["dependencies"], - "schedule": ["before 4am every weekday"], - "timezone": "Africa/Nairobi", - "dependencyDashboardApproval": false, - "rebaseWhen": "conflicted", - "rangeStrategy": "bump", - "separateMinorPatch": true, - "separateMajorMinor": true, - "pinDigests": false, - - "packageRules": [ - { - "description": "Label all npm package updates", - "matchManagers": ["npm"], - "labels": ["dependencies", "npm"] - }, - { - "description": "TypeScript and type definitions", - "matchPackagePatterns": ["^@types/", "^typescript$"], - "excludePackageNames": ["@types/react", "@types/react-dom"], - "groupName": "typescript", - "labels": ["dependencies", "npm", "typescript"] - }, - { - "description": "Prettier and formatting", - "matchPackagePatterns": ["^prettier"], - "groupName": "prettier", - "labels": ["dependencies", "npm", "formatting"] - }, - { - "description": "Major version updates require manual review", - "matchUpdateTypes": ["major"], - "automerge": false, - "labels": ["dependencies", "major"] - } - ], - - "vulnerabilityAlerts": { - "enabled": true, - "labels": ["security"] - }, - "lockFileMaintenance": { - "enabled": true, - "minimumReleaseAge": "0 days", - "schedule": ["before 4am on monday"] - }, - "osvVulnerabilityAlerts": true -} diff --git a/scripts/author.sh b/scripts/author.sh deleted file mode 100755 index e73d8e7..0000000 --- a/scripts/author.sh +++ /dev/null @@ -1,895 +0,0 @@ -#!/bin/bash -# Complete setup script to configure Git and GitHub CLI for iamvikshan -# -# This script sets up: -# 1. Git global config (user.name and user.email) -# 2. GitHub CLI authentication as iamvikshan -# 3. SSH signing keys for commit verification -# 4. Updates ~/.bashrc to clear GITHUB_TOKEN and add verification function -# 5. Ensures all commits and pushes are attributed to iamvikshan -# -# Run this once to set up your development environment permanently. -# Safe to run multiple times (idempotent). -# -# Usage: -# ./author.sh [--repo ] [--force|--yes] -# -# Options: -# --repo Override the target repository URL (default: https://github.com/iamvikshan/devcontainers) -# --force, --yes Force changes without prompting (required in non-interactive mode for remote URL changes) -# -# Environment variables: -# TARGET_REPO Set this to override the default target repository URL -# -# Examples: -# ./author.sh -# ./author.sh --repo https://github.com/myorg/myrepo.git -# TARGET_REPO=https://github.com/myorg/myrepo.git ./author.sh - -set -euo pipefail - -# Script name for help/error messages -SCRIPT_NAME=$(basename "$0") - -# Detect non-interactive/non-TTY environment -# In non-interactive mode, skip interactive prompts and use defaults -IS_INTERACTIVE=true -if [[ ! -t 0 || ! -t 1 ]]; then - IS_INTERACTIVE=false - echo "โš ๏ธ Running in non-interactive mode (no TTY detected)" - echo " Authentication steps requiring user input will be skipped." - echo " Set environment variables or run interactively for full setup." - echo "" -fi - -# Read timeout in seconds for interactive prompts -READ_TIMEOUT=60 - -# Git identity configuration - single source of truth -# These are exported so other scripts (e.g., .husky/pre-commit) can source this file -export GIT_USER="iamvikshan" -export GIT_EMAIL="103361575+iamvikshan@users.noreply.github.com" -BASHRC_FILE="$HOME/.bashrc" -MARKER_START="# iamvikshan development setup" -MARKER_END="# End iamvikshan development setup" - -# Default target repository (can be overridden by TARGET_REPO env var or --repo argument) -DEFAULT_TARGET_REPO="https://github.com/iamvikshan/devcontainers" -TARGET_REPO="${TARGET_REPO:-$DEFAULT_TARGET_REPO}" - -# Flag to force remote URL changes without prompting (for non-interactive use) -FORCE_REMOTE_UPDATE=false - -# Flag to track SSH signing key prune failure (deferred failure: final status will be incomplete) -PRUNE_FAILED=false - -# Initialize optional variables with defaults to satisfy 'set -u' -: "${GITHUB_TOKEN:=}" - -# Parse command line arguments -while [[ $# -gt 0 ]]; do - case $1 in - --repo) - if [[ $# -lt 2 || -z "${2-}" || "${2-}" == -* ]]; then - echo "Error: --repo requires a repository URL argument." - echo "Run '$SCRIPT_NAME --help' for usage information." - exit 1 - fi - TARGET_REPO="${2-}" - shift 2 - ;; - --force | --yes | -f | -y) - FORCE_REMOTE_UPDATE=true - shift - ;; - --help | -h) - echo "Usage: $SCRIPT_NAME [--repo ] [--force|--yes]" - echo "" - echo "Options:" - echo " --repo Override the target repository URL" - echo " (default: $DEFAULT_TARGET_REPO)" - echo " --force, --yes, -f, -y" - echo " Force remote URL changes without prompting" - echo " (required in non-interactive mode if remote differs)" - echo "" - echo "Environment variables:" - echo " TARGET_REPO Set this to override the default target repository URL" - exit 0 - ;; - *) - echo "Unknown option: $1" - echo "Run '$SCRIPT_NAME --help' for usage information." - exit 1 - ;; - esac -done - -# Validate TARGET_REPO format -if [[ ! "$TARGET_REPO" =~ ^(https://|git@) ]]; then - echo "Error: TARGET_REPO must start with 'https://' or 'git@'" - echo " Provided: $TARGET_REPO" - exit 1 -fi - -# Function to prune SSH signing keys to enforce maximum of 10 -# Deletes oldest keys first (by created_at timestamp) -# Uses gh built-in jq query output as TSV (no external jq dependency) -# Returns 0 on success, 1 on failure -prune_ssh_signing_keys() { - local max_keys=10 - - # Fetch all signing keys with pagination as TSV rows: created_atid - # NOTE: Defensive '|| FETCH_EXIT=$?' pattern prevents 'set -e' from killing the script - local FETCH_EXIT=0 - local keys_tsv="" - keys_tsv=$(gh api /user/ssh_signing_keys --paginate --jq '.[] | [.created_at, .id] | @tsv') || FETCH_EXIT=$? - - if [[ $FETCH_EXIT -ne 0 ]]; then - echo "Failed to fetch SSH signing keys" - if [[ -n "$keys_tsv" ]]; then - echo "$keys_tsv" - fi - return 1 - fi - - # Create temporary files for validated key rows and delete list - local temp_keys - local temp_sorted - local temp_delete - temp_keys=$(mktemp) - temp_sorted=$(mktemp) - temp_delete=$(mktemp) - trap 'rm -f "$temp_keys" "$temp_sorted" "$temp_delete"' RETURN - - # Persist fetched TSV output (empty output is valid when no keys exist) - if [[ -n "$keys_tsv" ]]; then - printf '%s\n' "$keys_tsv" > "$temp_keys" || { - echo "Failed to store SSH signing key list" - return 1 - } - else - : > "$temp_keys" || { - echo "Failed to initialize SSH signing key list" - return 1 - } - fi - - # Validate and normalize rows as created_atid - local PARSE_EXIT=0 - awk -F '\t' ' - NF == 0 { next } - NF != 2 || $1 == "" || $2 == "" { bad = 1; next } - $2 !~ /^[0-9]+$/ { bad = 1; next } - { print $1 "\t" $2; count++ } - END { - if (bad) exit 1 - } - ' "$temp_keys" > "$temp_sorted" || PARSE_EXIT=$? - - if [[ $PARSE_EXIT -ne 0 ]]; then - echo "Failed to parse SSH signing keys" - return 1 - fi - - local key_count=0 - key_count=$(awk -F '\t' 'NF == 2 {count++} END {print count+0}' "$temp_sorted") - - if [[ $key_count -le $max_keys ]]; then - echo "SSH signing keys: $key_count key(s) found (within max of $max_keys)" - return 0 - fi - - local delete_needed=0 - delete_needed=$((key_count - max_keys)) - - echo "Found $key_count SSH signing keys (exceeds max of $max_keys)" - echo " Deleting $delete_needed oldest key(s)..." - - # Sort by date ascending (oldest first), then delete exactly the oldest excess entries - local BUILD_DELETE_LIST_EXIT=0 - sort "$temp_sorted" | head -n "$delete_needed" | awk -F '\t' '{print $2}' > "$temp_delete" || BUILD_DELETE_LIST_EXIT=$? - - if [[ $BUILD_DELETE_LIST_EXIT -ne 0 ]]; then - echo "Failed to build delete list for old SSH signing keys" - return 1 - fi - - local delete_list_count=0 - delete_list_count=$(awk 'NF > 0 {count++} END {print count+0}' "$temp_delete") - if [[ $delete_list_count -ne $delete_needed ]]; then - echo "Failed to identify old SSH signing keys to delete" - return 1 - fi - - # Delete each old key - local delete_count=0 - local delete_fail_count=0 - while IFS= read -r key_id; do - if [[ -z "$key_id" ]]; then - continue - fi - - # NOTE: Defensive '|| DELETE_EXIT=$?' pattern prevents 'set -e' from killing the script - local DELETE_EXIT=0 - local delete_output="" - delete_output=$(gh api -X DELETE /user/ssh_signing_keys/"$key_id" 2>&1) || DELETE_EXIT=$? - - if [[ $DELETE_EXIT -eq 0 ]]; then - echo " Deleted signing key ID $key_id" - delete_count=$((delete_count + 1)) - else - echo " Failed to delete signing key ID $key_id: $delete_output" - delete_fail_count=$((delete_fail_count + 1)) - fi - done < "$temp_delete" - - if [[ $delete_fail_count -gt 0 ]]; then - echo "Failed to delete $delete_fail_count old signing key(s) - setup marked incomplete" - return 1 - fi - - echo "Pruned $delete_count old signing key(s), now have max $max_keys keys" - return 0 -} - -# Define the canonical check_dev_setup function body using a here-doc -# This ensures both code paths (insert after setup.sh and fallback append) use identical content -read -r -d '' FUNCTION_DEF << 'FUNCTION_EOF' || true -# Clear GITHUB_TOKEN to use stored gh CLI credentials (GIT_USER_PLACEHOLDER) instead of existing GITHUB_TOKEN -# This ensures all Git operations and GitHub CLI commands use GIT_USER_PLACEHOLDER credentials -# Must be after setup.sh is sourced, as Codespace may set GITHUB_TOKEN -# Setting to empty string works better than unset for some environments -export GITHUB_TOKEN="" - -# GIT_USER_PLACEHOLDER development setup verification -check_dev_setup() { - local git_user=$(git config --global user.name 2>/dev/null) - local git_email=$(git config --global user.email 2>/dev/null) - local gh_user="" - - if command -v gh &> /dev/null; then - gh_user=$(gh api user --jq .login 2>/dev/null || echo "") - fi - - if [[ "$git_user" != "GIT_USER_PLACEHOLDER" || "$git_email" != "GIT_EMAIL_PLACEHOLDER" ]]; then - echo "โš ๏ธ Git is not configured for GIT_USER_PLACEHOLDER" - echo " Run: git config --global user.name 'GIT_USER_PLACEHOLDER'" - echo " Run: git config --global user.email 'GIT_EMAIL_PLACEHOLDER'" - return 1 - fi - - if [[ -z "$gh_user" || "$gh_user" != "GIT_USER_PLACEHOLDER" ]]; then - if [[ -n "$GITHUB_TOKEN" ]]; then - echo "โš ๏ธ GitHub CLI is using existing GITHUB_TOKEN, not GIT_USER_PLACEHOLDER" - echo " Run: ./author.sh to authenticate as GIT_USER_PLACEHOLDER" - else - echo "โš ๏ธ GitHub CLI is not authenticated as GIT_USER_PLACEHOLDER" - echo " Run: ./author.sh to authenticate" - fi - return 1 - fi - - echo "โœ“ Development setup verified: working as GIT_USER_PLACEHOLDER" - return 0 -} - -# Uncomment the line below to auto-check on shell startup -# check_dev_setup -FUNCTION_EOF - -# Replace placeholders with actual values -FUNCTION_DEF="${FUNCTION_DEF//GIT_USER_PLACEHOLDER/$GIT_USER}" -FUNCTION_DEF="${FUNCTION_DEF//GIT_EMAIL_PLACEHOLDER/$GIT_EMAIL}" - -# Check for required dependencies -if ! command -v ssh-keygen &> /dev/null; then - echo "Checking dependencies..." - echo "โš ๏ธ ssh-keygen command not found." - if command -v apt-get &> /dev/null; then - echo " Installing openssh-client..." - if [[ "$EUID" -ne 0 ]] && command -v sudo &> /dev/null; then - sudo apt-get update && sudo apt-get install -y openssh-client - else - apt-get update && apt-get install -y openssh-client - fi - echo "โœ“ openssh-client installed" - else - echo "โŒ Error: ssh-keygen is required but cannot be installed automatically." - echo " Please install openssh-client manually." - exit 1 - fi - echo "" -fi - -echo "==========================================" -echo "Complete Setup for $GIT_USER" -echo "==========================================" -echo "" - -# Step 1: Configure Git -echo "Step 1: Configuring Git..." -git config --global user.name "$GIT_USER" -git config --global user.email "$GIT_EMAIL" -echo "โœ“ Git user configured as $GIT_USER" -echo " Name: $(git config --global user.name)" -echo " Email: $(git config --global user.email)" -echo "" - -# Step 2: Check GitHub CLI authentication -echo "Step 2: Checking GitHub CLI authentication..." -# Clear GITHUB_TOKEN to check actual authenticated user (not Codespace token) -export GITHUB_TOKEN="" -CURRENT_USER=$(gh api user --jq .login 2> /dev/null || echo "") - -if [[ "$CURRENT_USER" = "$GIT_USER" ]]; then - echo "โœ“ GitHub CLI already authenticated as $GIT_USER" - NEEDS_AUTH=false -else - echo "โš ๏ธ GitHub CLI needs to be authenticated as $GIT_USER" - echo " Current: ${CURRENT_USER:-Not authenticated}" - NEEDS_AUTH=true -fi -echo "" - -# Step 3: Authenticate GitHub CLI if needed -if [[ "$NEEDS_AUTH" = "true" ]]; then - echo "Step 3: Authenticating GitHub CLI..." - echo "The Codespace's existing GITHUB_TOKEN will be temporarily disabled" - echo "" - - # Check how many accounts are authenticated and who they are - # Only clear auth if: wrong user is authenticated OR multiple accounts exist - AUTH_STATUS=$(gh auth status --hostname github.com 2>&1 || true) - ACCOUNT_COUNT=$(echo "$AUTH_STATUS" | grep -c "Logged in to github.com" || echo "0") - - if [[ "$ACCOUNT_COUNT" -gt 0 ]]; then - echo "Clearing existing GitHub CLI authentication for github.com..." - echo " Found $ACCOUNT_COUNT existing account(s), current user: ${CURRENT_USER:-none}" - # Loop to remove all accounts (gh auth logout only removes one at a time) - while gh auth status --hostname github.com &> /dev/null; do - gh auth logout --hostname github.com 2> /dev/null || break - done - echo "โœ“ Existing github.com auth cleared" - else - echo "No existing github.com authentication to clear" - fi - echo "" - - # Handle non-interactive mode: skip authentication - if [[ "$IS_INTERACTIVE" != "true" ]]; then - echo "โš ๏ธ Skipping GitHub CLI authentication (non-interactive mode)" - echo " To authenticate, either:" - echo " - Run this script in an interactive terminal" - echo " - Pre-authenticate with 'gh auth login' before running" - echo "" - choice="s" - else - echo "You have two options:" - echo "" - echo "Option 1: Interactive web login (recommended)" - echo " This will open a browser window for you to authenticate" - echo "" - echo "Option 2: Use a Personal Access Token" - echo " If you have a PAT for $GIT_USER, you can paste it here" - echo "" - - # Use timed read to prevent hanging; default to skip on timeout - choice="" - if ! read -t "$READ_TIMEOUT" -p "Choose option (1 or 2, or 's' to skip) [timeout=${READ_TIMEOUT}s -> skip]: " choice; then - echo "" - echo "โš ๏ธ Input timed out after ${READ_TIMEOUT}s. Skipping authentication." - choice="s" - fi - # Handle empty input (user just pressed Enter) - choice="${choice:-s}" - fi - - case $choice in - 1) - echo "Starting web-based authentication..." - echo " Requesting scopes: repo, workflow, write:packages, read:packages, write:ssh_signing_key" - export GITHUB_TOKEN="" - gh auth login --hostname github.com --web --git-protocol https --scopes "repo,workflow,write:packages,read:packages,write:ssh_signing_key" - - # Verify the authenticated user is the intended user - AUTHED_USER=$(gh api user --jq .login 2> /dev/null || echo "") - if [[ "$AUTHED_USER" = "$GIT_USER" ]]; then - echo "โœ“ Authentication complete - logged in as $GIT_USER" - elif [[ -n "$AUTHED_USER" ]]; then - echo "โš ๏ธ Warning: Authenticated as '$AUTHED_USER' but expected '$GIT_USER'" - echo " You may have logged into the wrong account." - echo " Run 'gh auth logout' and try again with the correct account." - else - echo "โš ๏ธ Authentication may have failed - could not verify user" - fi - ;; - 2) - echo "Please provide your Personal Access Token for $GIT_USER" - echo "You can create one at: https://github.com/settings/tokens" - echo "Required scopes: repo, workflow, write:packages, read:packages, write:ssh_signing_key" - - # Use timed read for token input; skip on timeout - token="" - if ! read -t "$READ_TIMEOUT" -sp "Enter token [timeout=${READ_TIMEOUT}s -> skip]: " token; then - echo "" - echo "โš ๏ธ Token input timed out. Skipping authentication." - elif [[ -n "$token" ]]; then - echo "" - export GITHUB_TOKEN="" - # Authenticate with the token using here-string to avoid exposing token in process list - # (echo "$token" | gh ... would show token in ps output via echo process) - gh auth login --with-token <<< "$token" - - # Verify the authenticated user is the intended user - AUTHED_USER=$(gh api user --jq .login 2> /dev/null || echo "") - if [[ "$AUTHED_USER" = "$GIT_USER" ]]; then - echo "โœ“ Authentication complete - logged in as $GIT_USER" - elif [[ -n "$AUTHED_USER" ]]; then - echo "โš ๏ธ Warning: Authenticated as '$AUTHED_USER' but expected '$GIT_USER'" - echo " The token may belong to a different account." - echo " Run 'gh auth logout' and try again with a token for $GIT_USER." - else - echo "โš ๏ธ Authentication may have failed - could not verify user" - fi - else - echo "" - echo "โš ๏ธ Empty token provided. Skipping authentication." - fi - - # SECURITY: Clear the token from memory immediately after use - # Overwrite with fixed-length string before unsetting to reduce exposure - token="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" - unset token - ;; - s | S) - echo "โš ๏ธ Skipping GitHub CLI authentication" - echo " You may need to authenticate manually later" - ;; - *) - echo "โš ๏ธ Invalid choice. Skipping authentication." - ;; - esac - echo "" -else - echo "Step 3: GitHub CLI authentication - skipped (already configured)" - echo "" -fi - -# Step 3.1: Setup SSH Signing Keys (mandatory for commit verification) -echo "Step 3.1: Setting up SSH signing keys for commit verification..." -export GITHUB_TOKEN="" - -# Check if write:ssh_signing_key scope is available -# We need WRITE access to add signing keys; GET /user/ssh_signing_keys only proves READ access. -# Use X-OAuth-Scopes header to verify the token actually has write:ssh_signing_key scope. -# NOTE: Defensive '|| SCOPE_CHECK_EXIT=$?' pattern prevents 'set -e' from killing the script -# before we can handle the error. -HAS_SIGNING_SCOPE=true -SCOPE_CHECK_EXIT=0 -SCOPE_CHECK_OUTPUT=$(gh api -i /user/ssh_signing_keys 2>&1) || SCOPE_CHECK_EXIT=$? - -# Extract HTTP status code from response headers (first line: "HTTP/2 200" or "HTTP/1.1 403") -HTTP_STATUS=$(echo "$SCOPE_CHECK_OUTPUT" | head -n1 | awk '{print $2}' || true) - -if [[ $SCOPE_CHECK_EXIT -ne 0 ]]; then - # Classify error based on HTTP status code (preferred) or exit code (fallback) - if [[ "$HTTP_STATUS" = "401" ]]; then - # 401 Unauthorized - not authenticated - echo "โŒ Error: GitHub CLI is not authenticated" - echo " HTTP Status: 401 Unauthorized" - echo " Please run 'gh auth login' first" - exit 1 - elif [[ "$HTTP_STATUS" = "403" ]]; then - # 403 Forbidden - authenticated but missing required scope - HAS_SIGNING_SCOPE=false - elif [[ -z "$HTTP_STATUS" ]]; then - # No HTTP status found - likely network/connection issue or non-HTTP failure - echo "โš ๏ธ Warning: Could not verify SSH signing scope due to network/API issue" - echo " Exit code: $SCOPE_CHECK_EXIT" - echo " Output: $SCOPE_CHECK_OUTPUT" - echo " Assuming scope is available; if commit signing fails, run: gh auth refresh -h github.com -s write:ssh_signing_key" - else - # Other non-success HTTP status codes (4xx/5xx) - assume scope missing to be safe - HAS_SIGNING_SCOPE=false - fi -else - # GET succeeded (HTTP 200), but this only proves READ access. - # Check X-OAuth-Scopes header to verify the token also has WRITE access. - # write:ssh_signing_key scope is required for adding signing keys. - OAUTH_SCOPES=$(echo "$SCOPE_CHECK_OUTPUT" | grep -i '^X-OAuth-Scopes:' | sed 's/^[^:]*:[[:space:]]*//' | tr -d '\r' || true) - if [[ -n "$OAUTH_SCOPES" ]]; then - # Classic OAuth token / PAT - scopes header is present, check for required scope - if ! echo "$OAUTH_SCOPES" | grep -qi 'write:ssh_signing_key\|admin:ssh_signing_key'; then - HAS_SIGNING_SCOPE=false - echo "โš ๏ธ Token can read signing keys but lacks write permission" - echo " Current scopes: $OAUTH_SCOPES" - echo " Required scope: write:ssh_signing_key" - fi - fi - # If X-OAuth-Scopes header is empty/missing, it may be a fine-grained token (no - # traditional scopes). We'll attempt the key upload later and handle failure there. -fi - -if [[ "$HAS_SIGNING_SCOPE" != "true" ]]; then - echo "โš ๏ธ Need 'write:ssh_signing_key' scope for commit signing" - - if [[ "$IS_INTERACTIVE" != "true" ]]; then - echo " Skipping scope refresh (non-interactive mode)" - echo " Run interactively or pre-authorize with: gh auth refresh -h github.com -s write:ssh_signing_key" - else - echo " This will open a browser for authorization..." - confirm="" - if ! read -t "$READ_TIMEOUT" -p "Press Enter to continue (or wait ${READ_TIMEOUT}s to skip): " confirm; then - echo "" - echo " Timed out. Skipping scope refresh." - else - if gh auth refresh -h github.com -s write:ssh_signing_key; then - HAS_SIGNING_SCOPE=true - echo "โœ“ Scope granted" - else - echo "โš ๏ธ Failed to refresh scope. SSH signing key upload will be skipped." - HAS_SIGNING_SCOPE=false - fi - fi - fi -fi - -# Use consistent key name for reuse across environments -SIGNING_KEY_PATH="$HOME/.ssh/devcontainers-id_ed25519_signing" -SIGNING_KEY_PUB="$SIGNING_KEY_PATH.pub" - -remote_signing_key_exists() { - local normalized_key="$1" - local remote_keys="" - - if ! remote_keys=$(gh api /user/ssh_signing_keys --paginate --jq '.[] | .key' 2> /dev/null); then - return 1 - fi - - if printf '%s\n' "$remote_keys" | grep -qFx "$normalized_key"; then - return 0 - fi - - return 1 -} - -# Check if key already exists locally -if [[ -f "$SIGNING_KEY_PATH" && -f "$SIGNING_KEY_PUB" ]]; then - echo "โœ“ Found existing SSH signing key: $SIGNING_KEY_PATH" -else - echo "Generating new SSH signing key..." - mkdir -p "$HOME/.ssh" - - # SECURITY NOTE: Empty passphrase (-N "") is used intentionally here. - # Reason: This key is for automated commit signing in CI/dev environments - # where interactive passphrase entry is not practical. - # Implications: - # - The private key is protected only by filesystem permissions - # - Anyone with read access to ~/.ssh/devcontainers-id_ed25519_signing can use it - # For production/high-security environments: - # - Consider using a passphrase and ssh-agent for key caching - # - Or use hardware security keys (e.g., YubiKey) - # - Set SSH_SIGNING_PASSPHRASE env var and modify this script to use it - echo " Note: Generating key with empty passphrase for automated signing." - echo " For production use, consider adding a passphrase manually." - - ssh-keygen -t ed25519 -C "$GIT_EMAIL" -f "$SIGNING_KEY_PATH" -N "" -q - echo "โœ“ SSH signing key generated: $SIGNING_KEY_PATH" -fi - -# Normalize the public key to the canonical "type base64" form used by GitHub's -# SSH signing key API. Public key files often include a trailing comment. -NORMALIZED_SIGNING_KEY=$(awk '{print $1 " " $2}' "$SIGNING_KEY_PUB" 2> /dev/null || echo "") - -# Check if this key is already on GitHub and add if needed -# Skip GitHub upload if we know the token lacks write scope (it will fail anyway) -if [[ "$HAS_SIGNING_SCOPE" != "true" ]]; then - echo "โš ๏ธ Skipping GitHub key upload (missing write:ssh_signing_key scope)" - echo " The local signing key is configured, but GitHub won't show commits as 'Verified'" - echo " To fix: gh auth refresh -h github.com -s write:ssh_signing_key" - echo " Then re-run this script to upload the key" -else - echo "Ensuring SSH signing key is added to GitHub..." - if [[ -z "$NORMALIZED_SIGNING_KEY" ]]; then - echo "โš ๏ธ Failed to normalize the SSH signing key" - echo " Public key location: $SIGNING_KEY_PUB" - echo " If the key file is corrupted, remove it and rerun this script to regenerate it" - else - if remote_signing_key_exists "$NORMALIZED_SIGNING_KEY"; then - echo "โœ“ SSH signing key already exists on GitHub" - else - # Try to create the key through the documented REST API. - # NOTE: Defensive '|| ADD_EXIT_CODE=$?' pattern prevents 'set -e' from killing the script. - ADD_EXIT_CODE=0 - ADD_OUTPUT=$(gh api -X POST /user/ssh_signing_keys -f key="$NORMALIZED_SIGNING_KEY" -f title="devcontainers-$GIT_USER signing key" 2>&1) || ADD_EXIT_CODE=$? - - if [[ $ADD_EXIT_CODE -eq 0 ]]; then - echo "โœ“ SSH signing key added to GitHub" - elif remote_signing_key_exists "$NORMALIZED_SIGNING_KEY"; then - echo "โœ“ SSH signing key already exists on GitHub" - else - echo "โš ๏ธ Failed to add SSH signing key to GitHub" - echo " Exit code: $ADD_EXIT_CODE" - echo " Output: $ADD_OUTPUT" - echo " If the token is missing write access, refresh it with: gh auth refresh -h github.com -s write:ssh_signing_key" - echo " If the key itself is stale or broken, remove it and rerun this script to regenerate it:" - echo " rm -f '$SIGNING_KEY_PATH' '$SIGNING_KEY_PUB'" - fi - fi - fi -fi - -# Prune remote SSH signing keys (attempt oldest-first deletion; failures are recorded for final status) -if [[ "$HAS_SIGNING_SCOPE" = "true" ]]; then - echo "Pruning SSH signing keys (target max 10; failures are recorded for final status)..." - PRUNE_EXIT_CODE=0 - PRUNE_OUTPUT=$(prune_ssh_signing_keys) || PRUNE_EXIT_CODE=$? - - if [[ $PRUNE_EXIT_CODE -eq 0 ]]; then - echo "$PRUNE_OUTPUT" - else - echo "โŒ SSH signing key pruning failed" - echo " Exit code: $PRUNE_EXIT_CODE" - echo " Output: $PRUNE_OUTPUT" - echo " Continuing execution; final status will be incomplete if prune failed" - PRUNE_FAILED=true - fi -else - echo "โš ๏ธ Skipping SSH signing key pruning (missing write:ssh_signing_key scope)" -fi - -# Configure Git for SSH signing -git config --global gpg.format ssh -git config --global user.signingkey "$SIGNING_KEY_PUB" -git config --global commit.gpgsign true -echo "โœ“ Git configured for SSH signing" -echo "" - -# Step 3.2: Configure Git Remote and Credentials -echo "Step 3.2: Configuring Git Remote and Credentials..." - -# Configure git to use GitHub CLI as credential helper -echo "Configuring git credential helper to use gh CLI..." -gh auth setup-git - -# Ensure remote is set correctly -echo "Ensuring git remote 'origin' is configured..." -# Check if we are in a git repo -if git rev-parse --is-inside-work-tree > /dev/null 2>&1; then - # Set remote to HTTPS URL (TARGET_REPO configured at script start) - # We use HTTPS because we just set up the credential helper - - if git remote | grep -q "^origin$"; then - CURRENT_URL=$(git remote get-url origin) - # Normalize URLs for comparison: strip trailing .git and trailing slash - # Both https://github.com/user/repo and https://github.com/user/repo.git are equivalent - NORMALIZED_CURRENT="${CURRENT_URL%.git}" - NORMALIZED_CURRENT="${NORMALIZED_CURRENT%/}" - NORMALIZED_TARGET="${TARGET_REPO%.git}" - NORMALIZED_TARGET="${NORMALIZED_TARGET%/}" - if [ "$NORMALIZED_CURRENT" != "$NORMALIZED_TARGET" ]; then - echo "" - echo "โš ๏ธ Remote 'origin' URL differs from target:" - echo " Current URL: $CURRENT_URL" - echo " Target URL: $TARGET_REPO" - echo "" - - PROCEED_WITH_UPDATE=false - - # Check if running in interactive mode (using canonical IS_INTERACTIVE flag) - if [[ "$IS_INTERACTIVE" = "true" ]]; then - # Interactive mode: prompt user for confirmation - confirm="" - read -t "$READ_TIMEOUT" -p "Update remote URL to target? [y/N]: " confirm || true - case "$confirm" in - [Yy] | [Yy][Ee][Ss]) - PROCEED_WITH_UPDATE=true - ;; - *) - echo " Skipping remote URL update (user declined)" - ;; - esac - else - # Non-interactive mode: require --force or --yes flag - if [[ "$FORCE_REMOTE_UPDATE" = "true" ]]; then - PROCEED_WITH_UPDATE=true - else - echo "โŒ ERROR: Remote URL change requires confirmation in non-interactive mode." - echo "" - echo " To proceed, re-run with --force or --yes flag:" - echo " $SCRIPT_NAME --force" - echo " $SCRIPT_NAME --yes" - echo "" - echo " Or run interactively to be prompted for confirmation." - echo "" - # Don't exit - just skip this step - fi - fi - - if [[ "$PROCEED_WITH_UPDATE" = "true" ]]; then - echo "Updating 'origin' remote to $TARGET_REPO..." - git remote set-url origin "$TARGET_REPO" - echo "โœ“ Remote 'origin' updated" - fi - else - echo "Remote 'origin' is already set to $CURRENT_URL" - fi - else - echo "Adding 'origin' remote..." - git remote add origin "$TARGET_REPO" - fi - echo "โœ“ Remote 'origin' configured" -else - echo "โš ๏ธ Not inside a git repository. Skipping remote configuration." -fi -echo "" - -# Step 4: Update ~/.bashrc -echo "Step 4: Updating ~/.bashrc..." - -# Atomic update of ~/.bashrc: -# 1. Read the original file -# 2. Build complete new content in a temp file (skip old marker block, insert new block) -# 3. Only after temp file is fully written, atomically replace original via mv -# This prevents data loss if the script is interrupted mid-write. - -BASHRC_TMP="${BASHRC_FILE}.tmp.$$" - -# Ensure temp file is cleaned up on exit/error -trap 'rm -f "$BASHRC_TMP"' EXIT - -# Find the line number where setup.sh is sourced (to insert after it) -# Use '|| true' to handle case where file doesn't exist or pattern not found -SETUP_LINE="" -if [[ -f "$BASHRC_FILE" ]]; then - SETUP_LINE=$(grep -n "source /usr/local/bin/setup.sh" "$BASHRC_FILE" 2> /dev/null | tail -1 | cut -d: -f1 || true) -fi - -# Build the complete new ~/.bashrc content atomically -{ - if [[ -f "$BASHRC_FILE" ]]; then - # Read original file, skipping any existing marker block - # Track line numbers to insert the new block at the right position - line_num=0 - in_old_block=false - block_inserted=false - - while IFS= read -r line || [ -n "$line" ]; do - line_num=$((line_num + 1)) - - # Check for start of old block - if [[ "$line" = "$MARKER_START" ]]; then - in_old_block=true - continue - fi - - # Check for end of old block - if [[ "$line" = "$MARKER_END" ]]; then - in_old_block=false - continue - fi - - # Skip lines inside the old block - if [[ "$in_old_block" = "true" ]]; then - continue - fi - - # Output the current line - printf '%s\n' "$line" - - # Insert new block after the setup.sh line if applicable - if [[ -n "$SETUP_LINE" && "$line_num" = "$SETUP_LINE" && "$block_inserted" = "false" ]]; then - echo "" - echo "$MARKER_START" - echo "$FUNCTION_DEF" - echo "$MARKER_END" - block_inserted=true - fi - done < "$BASHRC_FILE" - - # If no SETUP_LINE or block wasn't inserted yet, append at EOF - if [[ "$block_inserted" = "false" ]]; then - echo "" - echo "$MARKER_START" - echo "$FUNCTION_DEF" - echo "$MARKER_END" - fi - else - # No existing ~/.bashrc, create fresh with just the block - echo "$MARKER_START" - echo "$FUNCTION_DEF" - echo "$MARKER_END" - fi -} > "$BASHRC_TMP" - -# Atomically replace the original file -mv "$BASHRC_TMP" "$BASHRC_FILE" - -# Clear the trap since we successfully moved the file -trap - EXIT - -if [[ -n "$SETUP_LINE" ]]; then - echo "โœ“ Updated ~/.bashrc with GITHUB_TOKEN clearing and verification function" -else - echo "โœ“ Appended setup to ~/.bashrc" -fi -echo "" - -# Step 5: Verify final setup -echo "Step 5: Verifying final setup..." -echo "" - -# Source bashrc to test the new configuration -export GITHUB_TOKEN="" -FINAL_GIT_USER=$(git config --global user.name) -FINAL_GIT_EMAIL=$(git config --global user.email) -FINAL_GH_USER=$(gh api user --jq .login 2> /dev/null || echo "") -FINAL_SIGNING_KEY=$(git config --global user.signingkey || echo "") -FINAL_GPGSIGN=$(git config --global commit.gpgsign || echo "false") - -echo "Current configuration:" -echo " โœ“ Git user.name: $FINAL_GIT_USER" -echo " โœ“ Git user.email: $FINAL_GIT_EMAIL" -if [[ -n "$FINAL_GH_USER" ]]; then - echo " โœ“ GitHub CLI user: $FINAL_GH_USER" -else - echo " โš ๏ธ GitHub CLI: Not authenticated" -fi -if [[ -n "$FINAL_SIGNING_KEY" && "$FINAL_GPGSIGN" = "true" ]]; then - echo " โœ“ Commit signing: Enabled ($FINAL_SIGNING_KEY)" -else - echo " โš ๏ธ Commit signing: Not configured" -fi -echo "" - -# Final status -SETUP_COMPLETE=true -if [[ "$FINAL_GIT_USER" != "$GIT_USER" || "$FINAL_GIT_EMAIL" != "$GIT_EMAIL" ]]; then - SETUP_COMPLETE=false -fi -if [[ "$FINAL_GH_USER" != "$GIT_USER" ]]; then - SETUP_COMPLETE=false -fi -if [[ -z "$FINAL_SIGNING_KEY" || "$FINAL_GPGSIGN" != "true" ]]; then - SETUP_COMPLETE=false -fi -if [[ "$PRUNE_FAILED" = "true" ]]; then - SETUP_COMPLETE=false -fi - -if [[ "$SETUP_COMPLETE" = "true" ]]; then - echo "==========================================" - echo "โœ“ Setup Complete!" - echo "==========================================" - echo "" - echo "All operations will be attributed to $GIT_USER" - echo "All commits will be signed with SSH key: $FINAL_SIGNING_KEY" - echo "" - echo "To verify your setup in a new shell, run:" - echo " source ~/.bashrc" - echo " check_dev_setup" - echo "" - exit 0 -else - echo "==========================================" - echo "โš ๏ธ Setup Incomplete" - echo "==========================================" - echo "" - if [[ "$FINAL_GIT_USER" != "$GIT_USER" || "$FINAL_GIT_EMAIL" != "$GIT_EMAIL" ]]; then - echo "Git configuration needs attention" - fi - if [[ "$FINAL_GH_USER" != "$GIT_USER" ]]; then - echo "GitHub CLI authentication needs attention" - echo "Run this script again and choose option 1 or 2 for authentication" - fi - if [[ -z "$FINAL_SIGNING_KEY" || "$FINAL_GPGSIGN" != "true" ]]; then - echo "SSH signing key setup needs attention" - echo "Run this script again to complete SSH signing setup" - fi - if [[ "$PRUNE_FAILED" = "true" ]]; then - echo "SSH signing key pruning failed" - echo "Ensure you have sufficient scopes and re-run: $SCRIPT_NAME" - fi - echo "" - exit 1 -fi diff --git a/scripts/changeDetector.ts b/scripts/changeDetector.ts deleted file mode 100644 index 653b956..0000000 --- a/scripts/changeDetector.ts +++ /dev/null @@ -1,365 +0,0 @@ -import { execSync } from 'child_process' -import { versionManager } from './versionManager' -import { ReleaseContext } from './types' -import { imageOperations } from './imageOperations' -import { IMAGE_DEFINITIONS } from './registryClient' - -export class ChangeDetector { - private silent = false - private versionOverride: string | undefined - - setSilent(silent: boolean): void { - this.silent = silent - // Propagate silent mode to imageOperations - imageOperations.setSilent(silent) - } - - setVersionOverride(version: string): void { - this.versionOverride = version - } - - private log(message: string): void { - if (!this.silent) { - console.log(message) - } - } - - // Analyze changes and determine what needs to be released - async analyzeChanges( - trigger: ReleaseContext['trigger'] = 'push' - ): Promise { - this.log('๐Ÿ” Analyzing changes for release...') - - // For manual trigger, release ALL containers - if (trigger === 'manual') { - this.log('๐ŸŽฏ Manual trigger detected - releasing ALL containers') - return this.createManualReleaseContext() - } - - const rawCommits = versionManager.getCommitsSinceLastRelease() - // Filter out commits that don't affect any containers - const commits = rawCommits.filter( - c => c.affectedContainers && c.affectedContainers.length > 0 - ) - const versionBumps = versionManager.processCommits(commits) - - // Check for manual release override - const manualReleaseCommit = commits.find( - c => c.type === 'release' && c.manualVersion - ) - let manualOverride: ReleaseContext['manualOverride'] = undefined - - if (manualReleaseCommit && manualReleaseCommit.manualVersion) { - manualOverride = { - version: manualReleaseCommit.manualVersion, - commitHash: manualReleaseCommit.hash - } - this.log( - `๐ŸŽฏ Manual release override detected: v${manualReleaseCommit.manualVersion}` - ) - } - - // Get unique affected containers - const affectedContainers = Array.from( - new Set(commits.flatMap(c => c.affectedContainers)) - ) - - // Check for base image updates and tool updates if this is a scheduled run - let baseImageUpdates: any[] = [] - if (trigger === 'schedule' || trigger === 'base-image-update') { - try { - // Check base image updates - const updates = await imageOperations.checkBaseImageUpdates() - baseImageUpdates = updates.filter(u => u.hasUpdate) - - if (baseImageUpdates.length > 0) { - this.log(`๐Ÿ“ฆ Found ${baseImageUpdates.length} base image updates`) - - // Add base image update version bumps - baseImageUpdates.forEach(update => { - const containerName = update.containerName - if (!versionBumps.find(v => v.container === containerName)) { - const versions = versionManager.loadVersions() - const currentVersion = versions[containerName]?.version || '1.0.0' - const { newVersion } = versionManager.calculateVersionBump( - currentVersion, - 'fix', - false - ) - - versionBumps.push({ - container: containerName, - currentVersion, - newVersion, - bumpType: 'patch', - reason: 'base image update' - }) - - if (!affectedContainers.includes(containerName)) { - affectedContainers.push(containerName) - } - } - }) - } - - // Check tool version updates - const toolUpdates = await imageOperations.checkToolVersionUpdates() - if ( - toolUpdates.hasUpdates && - toolUpdates.affectedContainers.length > 0 - ) { - this.log( - `๐Ÿ”ง Found tool updates for ${toolUpdates.affectedContainers.length} containers` - ) - - // Add tool update version bumps - toolUpdates.affectedContainers.forEach(containerName => { - if (!versionBumps.find(v => v.container === containerName)) { - const versions = versionManager.loadVersions() - const currentVersion = versions[containerName]?.version || '1.0.0' - const { newVersion } = versionManager.calculateVersionBump( - currentVersion, - 'fix', - false - ) - - versionBumps.push({ - container: containerName, - currentVersion, - newVersion, - bumpType: 'patch', - reason: 'tool version update' - }) - - if (!affectedContainers.includes(containerName)) { - affectedContainers.push(containerName) - } - } - }) - } - } catch (error) { - this.log( - `โš ๏ธ Error checking external updates: ${error instanceof Error ? error.message : String(error)}` - ) - } - } - - return { - trigger, - affectedContainers, - versionBumps, - commits, - baseImageUpdates, - manualOverride - } - } - - // Create release context for manual trigger (all containers) - private createManualReleaseContext(): ReleaseContext { - const versions = versionManager.loadVersions() - - // Get commits since last release for release notes (even in manual mode) - const rawCommits = versionManager.getCommitsSinceLastRelease() - const commits = rawCommits.filter( - c => c.affectedContainers && c.affectedContainers.length > 0 - ) - - const versionBumps = IMAGE_DEFINITIONS.names.map(container => { - const currentVersion = versions[container]?.version || '1.0.0' - - // Use version override if provided, otherwise auto-increment - let newVersion: string - let bumpType: 'major' | 'minor' | 'patch' - - if (this.versionOverride) { - newVersion = this.versionOverride - // Determine bump type based on version difference - const [curMajor, curMinor] = currentVersion.split('.').map(Number) - const [newMajor, newMinor] = newVersion.split('.').map(Number) - if (newMajor > curMajor) bumpType = 'major' - else if (newMinor > curMinor) bumpType = 'minor' - else bumpType = 'patch' - } else { - const result = versionManager.calculateVersionBump( - currentVersion, - 'fix', - false - ) - newVersion = result.newVersion - bumpType = result.bumpType - } - - return { - container, - currentVersion, - newVersion, - bumpType, - reason: 'manual release' - } - }) - - return { - trigger: 'manual', - affectedContainers: [...IMAGE_DEFINITIONS.names], - versionBumps, - commits, // Include commits for release notes - baseImageUpdates: [] - } - } - - // Check if any containers need to be released - shouldRelease(context: ReleaseContext): boolean { - // Manual trigger always releases - if (context.trigger === 'manual') return true - return ( - context.versionBumps.length > 0 || - (context.baseImageUpdates?.length ?? 0) > 0 - ) - } - - // Get containers that need building based on changes - getContainersToRebuild(context: ReleaseContext): string[] { - return context.affectedContainers - } - - // Get the highest version bump type across all containers - getOverallReleaseType( - context: ReleaseContext - ): 'major' | 'minor' | 'patch' | 'none' { - // Manual override always returns major (since it can be any version) - if (context.manualOverride) return 'major' - - if (context.versionBumps.length === 0) return 'none' - - const priorities = { major: 3, minor: 2, patch: 1 } - let highestPriority = 0 - let releaseType: 'major' | 'minor' | 'patch' = 'patch' - - context.versionBumps.forEach(bump => { - const priority = priorities[bump.bumpType] - if (priority > highestPriority) { - highestPriority = priority - releaseType = bump.bumpType - } - }) - - return releaseType - } - - // Generate release notes for the changes - generateReleaseNotes(context: ReleaseContext): string[] { - const notes: string[] = [] - - // Removed Container Updates section - now shown in Released Versions table - - if (context.baseImageUpdates && context.baseImageUpdates.length > 0) { - notes.push('## Base Image Updates') - context.baseImageUpdates.forEach(update => { - notes.push(`- **${update.containerName}**: Updated ${update.baseImage}`) - }) - } - - if (context.commits.length > 0) { - const features = context.commits.filter(c => c.type === 'feat') - const fixes = context.commits.filter(c => c.type === 'fix') - const others = context.commits.filter( - c => !['feat', 'fix'].includes(c.type) - ) - - // GitHub repository info (adjust if needed) - const repoUrl = 'https://github.com/iamvikshan/devcontainers' - - if (features.length > 0) { - notes.push('## Features') - features.forEach(commit => { - const shortHash = commit.hash.substring(0, 7) - notes.push( - `- ${commit.message} ([${shortHash}](${repoUrl}/commit/${commit.hash}))` - ) - }) - } - - if (fixes.length > 0) { - notes.push('## Bug Fixes') - fixes.forEach(commit => { - const shortHash = commit.hash.substring(0, 7) - notes.push( - `- ${commit.message} ([${shortHash}](${repoUrl}/commit/${commit.hash}))` - ) - }) - } - - if (others.length > 0) { - notes.push('## Other Changes') - others.forEach(commit => { - const shortHash = commit.hash.substring(0, 7) - notes.push( - `- ${commit.message} ([${shortHash}](${repoUrl}/commit/${commit.hash}))` - ) - }) - } - } - - return notes - } - - // Create a commit message for base image updates - createBaseImageCommitMessage(baseImageUpdates: any[]): { - message: string - body: string[] - } { - const updatedImages = baseImageUpdates.map(u => u.containerName).join(', ') - - const message = `fix: update base images for ${updatedImages}` - const body = [ - '- Updated base images to latest versions', - '- Security patches and bug fixes from upstream', - '- Improved compatibility and performance', - '', - 'This commit triggers an automated patch release to rebuild DevContainers with updated base images.' - ] - - return { message, body } - } - - // Check if there are any changes since last release - hasChangesSinceLastRelease(): boolean { - try { - const commits = versionManager.getCommitsSinceLastRelease() - return commits.length > 0 - } catch (error) { - this.log( - `โš ๏ธ Error checking for changes: ${error instanceof Error ? error.message : String(error)}` - ) - return false - } - } - - // Get changed files since last release - getChangedFilesSinceLastRelease(): string[] { - try { - let gitCommand = 'git diff --name-only' - - try { - const lastTag = execSync('git describe --tags --abbrev=0', { - encoding: 'utf-8' - }).trim() - gitCommand += ` ${lastTag}..HEAD` - } catch { - // No tags found, get all files - gitCommand = 'git ls-files' - } - - const output = execSync(gitCommand, { encoding: 'utf-8' }).trim() - return output ? output.split('\n').filter(Boolean) : [] - } catch (error) { - this.log( - `โš ๏ธ Error getting changed files: ${error instanceof Error ? error.message : String(error)}` - ) - return [] - } - } -} - -// Export singleton instance -export const changeDetector = new ChangeDetector() diff --git a/scripts/changelogManager.ts b/scripts/changelogManager.ts deleted file mode 100644 index cf4809e..0000000 --- a/scripts/changelogManager.ts +++ /dev/null @@ -1,484 +0,0 @@ -import { readFileSync, writeFileSync, existsSync } from 'fs' -import { join } from 'path' -import { registryClient, IMAGE_DEFINITIONS } from './registryClient' -import { imageOperations } from './imageOperations' - -export class ChangelogManager { - private changelogPath: string - private silent: boolean = false - - constructor() { - this.changelogPath = join(process.cwd(), 'CHANGELOG.md') - } - - setSilent(silent: boolean): void { - this.silent = silent - } - - private log(message: string): void { - // In workflow/silent mode we route informational logs to stderr so - // the caller can safely capture stdout for machine-readable output. - if (this.silent) { - console.error(message) - } else { - console.log(message) - } - } - - // Update CHANGELOG.md with new release information - async updateChangelogFile( - versionMap?: Record, - releaseNotes?: string[] - ): Promise { - this.log( - '๐Ÿ“ Updating CHANGELOG.md with release and container information...' - ) - - try { - const currentDate = new Date().toISOString().split('T')[0] - - // Read existing CHANGELOG.md - let content = '' - if (existsSync(this.changelogPath)) { - content = readFileSync(this.changelogPath, 'utf-8') - } else { - content = this.createInitialChangelogContent() - } - - // If we have a version map, create a new release entry - if (versionMap && Object.keys(versionMap).length > 0) { - const overallVersion = this.getHighestVersion(versionMap) - content = this.addReleaseEntry( - content, - overallVersion, - currentDate, - versionMap, - releaseNotes || [] - ) - } - - // Write updated content - writeFileSync(this.changelogPath, content) - - this.log('โœ… CHANGELOG.md updated successfully') - - if (versionMap) { - const overallVersion = this.getHighestVersion(versionMap) - this.log(`๐Ÿš€ New version: ${overallVersion}`) - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error) - console.error('โŒ Error updating CHANGELOG.md:', message) - throw error - } - } - - // Sync sizes only (for --sync-only flag) - async syncAllSizes(): Promise { - this.log('๐Ÿ”„ Syncing sizes in CHANGELOG.md and README files...') - - try { - // Get real-time sizes - const sizes = await imageOperations.getAllImageSizes() - - // Update README files - await imageOperations.updateReadmeFiles(sizes) - - // Update CHANGELOG.md with new sizes - await this.updateChangelogFile() - - this.log('โœ… All sizes synced successfully!') - } catch (error) { - const message = error instanceof Error ? error.message : String(error) - console.error('โŒ Error syncing sizes:', message) - throw error - } - } - - private createInitialChangelogContent(): string { - return `# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -### Added -- Initial release of DevContainer configurations - ---- - -*This file is automatically updated by our CI/CD workflows.*` - } - - private getHighestVersion(versionMap: Record): string { - const versions = Object.values(versionMap) - return versions.sort((a, b) => { - const [aMajor, aMinor, aPatch] = a.split('.').map(Number) - const [bMajor, bMinor, bPatch] = b.split('.').map(Number) - if (aMajor !== bMajor) return bMajor - aMajor - if (aMinor !== bMinor) return bMinor - aMinor - return bPatch - aPatch - })[0] - } - - private addReleaseEntry( - content: string, - version: string, - date: string, - versionMap: Record, - releaseNotes: string[] - ): string { - // Generate Released Versions table for this release - const releasedVersionsTable = this.generateReleasedVersionsTable( - versionMap, - date - ) - - // Create the release entry (without Container Images section) - const releaseEntry = `## [${version}] - ${date} - -${releasedVersionsTable} - -${releaseNotes.join('\n')} - ---- - -` - - // Insert the new release entry right after the file header - // Find the end of the header (after "All notable changes..." line) - const headerEndMatch = content.match( - /All notable changes to this project will be documented in this file\.\s*\n\s*\n/ - ) - - if (headerEndMatch) { - const insertPosition = - content.indexOf(headerEndMatch[0]) + headerEndMatch[0].length - content = - content.slice(0, insertPosition) + - releaseEntry + - content.slice(insertPosition) - } else { - // Fallback: insert after the first blank line following the title - const firstHeadingIndex = content.indexOf('# Changelog') - if (firstHeadingIndex !== -1) { - const afterTitle = content.indexOf('\n\n', firstHeadingIndex) - if (afterTitle !== -1) { - content = - content.slice(0, afterTitle + 2) + - releaseEntry + - content.slice(afterTitle + 2) - } else { - content += '\n' + releaseEntry - } - } else { - content += '\n' + releaseEntry - } - } - - return content - } - - private generateReleasedVersionsTable( - versionMap: Record, - date: string - ): string { - const rows = Object.entries(versionMap).map(([container, version]) => { - // Generate registry links - const ghcrLink = `[GHCR](https://ghcr.io/iamvikshan/devcontainers/${container}:${version})` - const dockerLink = `[Docker Hub](https://hub.docker.com/r/vikshan/${container})` - const gitlabLink = `[GitLab](https://registry.gitlab.com/vikshan/devcontainers/${container}:${version})` - const registryLinks = `${ghcrLink} ยท ${dockerLink} ยท ${gitlabLink}` - - return `| ${container} | v${version} | ${date} | ${registryLinks} |` - }) - - return [ - '### Released Versions', - '', - '| Container | Version | Date | Registry Links |', - '| --------- | ------- | ---- | -------------- |', - ...rows, - '' - ].join('\n') - } - - private generateContainerUpdatesSection( - versionMap: Record - ): string { - let section = '' - for (const [container, version] of Object.entries(versionMap)) { - section += `- **${container}**: Updated to v${version}\n` - } - return section - } - - private generateContainerInfoSection( - versionMap: Record, - sizes: any, - baseImageDigests: Record, - toolVersions: any[] - ): string { - let section = '' - - for (const imageName of IMAGE_DEFINITIONS.names) { - if (!versionMap[imageName]) continue // Only include containers that were updated - - const imageSize = sizes[imageName]?.ghcr?.size_mb || 0 - const baseImage = - (IMAGE_DEFINITIONS.baseImages as any)[imageName] || 'unknown' - const digest = baseImageDigests[imageName] || 'unknown' - const version = versionMap[imageName] - - // Get tool versions for this container - const containerToolVersions = toolVersions.find( - tv => tv.container === imageName - ) - const tools = containerToolVersions?.versions || {} - - // Generate tools description - const toolsDesc = this.generateToolsDescription(imageName, tools) - - // Generate emoji based on container type - const emoji = this.getContainerEmoji(imageName) - - section += `### ${emoji} ${imageName} - -- **Version:** v${version} -- **Base Image:** \`${baseImage}\` -- **Base Image Digest:** \`${digest}\` -- **Tools:** ${toolsDesc} -- **Size:** ~${imageSize.toFixed(2)} MB -- **Registries:** - - GitHub: \`ghcr.io/iamvikshan/devcontainers/${imageName}:v${version}\` - - GitLab: \`registry.gitlab.com/vikshan/devcontainers/${imageName}:v${version}\` - - Docker Hub: \`docker.io/vikshan/${imageName}:v${version}\` - -` - } - - return section - } - - private generateToolsDescription(imageName: string, tools: any): string { - const toolsList = [] - - if (tools.bun_version) { - toolsList.push(`Bun ${tools.bun_version}`) - } - - if (tools.node_version) { - toolsList.push(`Node.js ${tools.node_version}`) - } - - if (tools.npm_version) { - toolsList.push(`npm ${tools.npm_version}`) - } - - if (tools.eslint_version) { - toolsList.push(`ESLint ${tools.eslint_version}`) - } else if (imageName.includes('node')) { - toolsList.push('ESLint (global)') - } - - if (tools.git_version) { - toolsList.push(`Git ${tools.git_version}`) - } - - if (tools.curl_version) { - toolsList.push(`curl ${tools.curl_version}`) - } - - // Add special notes for certain containers - if (imageName.includes('ubuntu') && imageName.includes('node')) { - toolsList.push('ESLint (non-root)') - } - - return toolsList.length > 0 - ? toolsList.join(', ') - : 'Basic development tools' - } - - private getContainerEmoji(imageName: string): string { - if (imageName.includes('ubuntu')) { - return '๐Ÿง' // Ubuntu penguin - } else if (imageName.startsWith('bun')) { - return '๐Ÿ”๏ธ' // Alpine mountain - } else { - return '๐Ÿš€' // Default rocket - } - } - - private loadToolVersions(): any[] { - // Tool versions should now be loaded from container-versions.json - return [] - } - - // Get base image digests - async getBaseImageDigests(): Promise> { - this.log('๐Ÿ” Getting base image digests...') - - const digests: Record = {} - - for (const [imageName, baseImage] of Object.entries( - IMAGE_DEFINITIONS.baseImages - )) { - try { - const tags = await registryClient.getDockerHubTags(baseImage) - const latestTag = tags.find(t => t.name === 'latest') - - if (latestTag?.digest) { - digests[imageName] = latestTag.digest - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error) - console.error(`Error getting digest for ${baseImage}:`, message) - digests[imageName] = 'sha256:unknown' - } - } - - return digests - } - - /** - * Update only the Released Versions table in CHANGELOG.md - * This is used by the workflow to quickly update version numbers without full changelog generation - */ - async updateVersionsTable(versionMap: Record): Promise { - this.log('๐Ÿ“ Updating Released Versions table in CHANGELOG.md...') - - if (!existsSync(this.changelogPath)) { - throw new Error('CHANGELOG.md not found') - } - - const content = readFileSync(this.changelogPath, 'utf-8') - const currentDate = new Date().toISOString().split('T')[0] - - // Find the Released Versions table - const tableRegex = /## Released Versions\s+([\s\S]*?)(?=\n---)/ - const tableMatch = content.match(tableRegex) - - if (!tableMatch) { - throw new Error('Released Versions table not found in CHANGELOG.md') - } - - const tableContent = tableMatch[1] - const lines = tableContent.split('\n') - - // Update the table rows - const updatedLines = lines.map(line => { - if ( - !line.includes('|') || - line.includes('Container') || - line.includes('---') - ) { - return line - } - - const cells = line - .split('|') - .map(c => c.trim()) - .filter(Boolean) - - if (cells.length < 3) return line - - const containerName = cells[0] - - // Check if this container was updated - if (versionMap[containerName]) { - const newVersion = versionMap[containerName] - cells[1] = `v${newVersion}` - cells[2] = currentDate - return `| ${cells.join(' | ')} |` - } - - return line - }) - - // Replace the table in the content - const updatedTable = updatedLines.join('\n') - const newContent = content.replace( - tableRegex, - `## Released Versions\n${updatedTable}\n` - ) - - writeFileSync(this.changelogPath, newContent) - - this.log('โœ… Released Versions table updated successfully') - this.log(`๐Ÿ“ฆ Updated versions: ${Object.keys(versionMap).join(', ')}`) - } -} - -// Export singleton instance -export const changelogManager = new ChangelogManager() - -// CLI functionality -async function main() { - const args = process.argv.slice(2) - const workflowMode = args.includes('--workflow') - // Respect workflow mode by routing informational logs to stderr - changelogManager.setSilent(workflowMode) - const newVersion = args - .find(arg => arg.startsWith('--version=')) - ?.split('=')[1] - const versionMapArg = args - .find(arg => arg.startsWith('--version-map=')) - ?.split('=')[1] - const releaseNotesArg = args - .find(arg => arg.startsWith('--notes=')) - ?.split('=')[1] - const releaseNotes = releaseNotesArg ? releaseNotesArg.split(',') : undefined - const syncOnly = args.includes('--sync-only') - const updateTableOnly = args.includes('--update-table') - - if (workflowMode) { - console.error('๐Ÿ”„ Changelog Manager Starting...\n') - } else { - console.log('๐Ÿ”„ Changelog Manager Starting...\n') - } - - try { - if (syncOnly) { - // Just sync sizes between README and CHANGELOG.md - if (workflowMode) console.error('๐Ÿ“Š Syncing sizes only...') - else console.log('๐Ÿ“Š Syncing sizes only...') - await changelogManager.syncAllSizes() - } else if (updateTableOnly && versionMapArg) { - // Just update the Released Versions table - if (workflowMode) - console.error('๐Ÿ“ Updating Released Versions table only...') - else console.log('๐Ÿ“ Updating Released Versions table only...') - const versionMap = JSON.parse(versionMapArg) - await changelogManager.updateVersionsTable(versionMap) - } else if (versionMapArg) { - // Update with version map from new release system - if (workflowMode) - console.error('๐Ÿ“ Updating CHANGELOG.md with version map...') - else console.log('๐Ÿ“ Updating CHANGELOG.md with version map...') - const versionMap = JSON.parse(versionMapArg) - - await changelogManager.updateChangelogFile(versionMap, releaseNotes) - } else { - // Full version update with real-time data - if (workflowMode) - console.error('๐Ÿ“ Updating CHANGELOG.md with real-time data...') - else console.log('๐Ÿ“ Updating CHANGELOG.md with real-time data...') - await changelogManager.updateChangelogFile() - } - - if (workflowMode) console.error('\n๐ŸŽ‰ Changelog management complete!') - else console.log('\n๐ŸŽ‰ Changelog management complete!') - } catch (error) { - const message = error instanceof Error ? error.message : String(error) - console.error('โŒ Changelog management failed:', message) - process.exit(1) - } -} - -// Run CLI if called directly -if (require.main === module) { - main().catch(console.error) -} diff --git a/scripts/cleanup-untagged-images.sh b/scripts/cleanup-untagged-images.sh deleted file mode 100755 index d04685f..0000000 --- a/scripts/cleanup-untagged-images.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash -set -e - -# Script to clean up untagged container images from GitHub Container Registry -# Usage: ./cleanup-untagged-images.sh - -GITHUB_TOKEN="${GITHUB_TOKEN}" -AFFECTED_CONTAINERS="${AFFECTED_CONTAINERS}" -REPOSITORY_NAME="${REPOSITORY_NAME}" - -if [ -z "$AFFECTED_CONTAINERS" ]; then - echo "โ„น๏ธ No containers to clean up" - exit 0 -fi - -echo "๐Ÿงน Cleaning up any untagged images..." - -IFS=',' read -ra CONTAINERS <<< "$AFFECTED_CONTAINERS" - -for container in "${CONTAINERS[@]}"; do - container=$(echo "$container" | xargs) # trim whitespace - - echo "๐Ÿ” Checking for untagged images for $container..." - - # Clean up untagged images in GitHub Container Registry - # Note: This uses GitHub API to delete untagged package versions - curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github.v3+json" \ - "https://api.github.com/user/packages/container/${REPOSITORY_NAME}%2F${container}/versions" \ - | jq -r '.[] | select(.metadata.container.tags | length == 0) | .id' \ - | while read version_id; do - if [ -n "$version_id" ]; then - echo "๐Ÿ—‘๏ธ Deleting untagged version $version_id for $container" - curl -s -X DELETE \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github.v3+json" \ - "https://api.github.com/user/packages/container/${REPOSITORY_NAME}%2F${container}/versions/$version_id" || true - fi - done -done - -echo "โœ… Cleanup completed" diff --git a/scripts/dvcntnr.sh b/scripts/dvcntnr.sh deleted file mode 100644 index 8098e81..0000000 --- a/scripts/dvcntnr.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/bash - -# Exit immediately if a pipeline returns a non-zero status -set -euo pipefail - -echo "Starting environment setup..." - -# 1. Update and upgrade system packages -echo "Updating system packages..." -sudo apt update && sudo apt upgrade -y - -# 2. Make author.sh executable if it exists -if [ -f scripts/author.sh ]; then - echo "Making scripts/author.sh executable..." - chmod +x scripts/author.sh -fi - -# 3. Set the timezone -echo "Setting timezone to Africa/Nairobi..." -sudo ln -sf /usr/share/zoneinfo/Africa/Nairobi /etc/localtime - -# 4. Install CodeRabbit CLI -echo "Installing CodeRabbit CLI..." -curl -fsSL https://cli.coderabbit.ai/install.sh | sh - -# 5. Fetch hooks from the iamvikshan/atlas repository -echo "Fetching hooks from GitHub..." - -TMP_DIR=$(mktemp -d) -trap 'rm -rf "$TMP_DIR"' INT TERM EXIT -git clone --depth 1 --filter=blob:none --sparse https://github.com/iamvikshan/atlas.git "$TMP_DIR" -git -C "$TMP_DIR" sparse-checkout set scripts/hooks - -mkdir -p scripts/hooks -SRC="$TMP_DIR/scripts/hooks" - -if [ ! -d "$SRC" ]; then - echo "ERROR: hooks directory not found in fetched repository." >&2 - exit 1 -fi - -copied=0 - -for f in "$SRC"/*; do - [ -e "$f" ] || continue - cp -R "$f" scripts/hooks/ - copied=$((copied + 1)) -done - -for f in "$SRC"/.*; do - name="${f##*/}" - if [ "$name" = "." ] || [ "$name" = ".." ]; then continue; fi - [ -e "$f" ] || continue - cp -R "$f" scripts/hooks/ - copied=$((copied + 1)) -done - -if [ "$copied" -eq 0 ]; then - echo "ERROR: source hooks directory is empty; nothing copied." >&2 - exit 1 -else - echo "Hooks successfully fetched and copied!" -fi - -echo "Setup complete!" diff --git a/scripts/hooks/atlas-hook.ps1 b/scripts/hooks/atlas-hook.ps1 deleted file mode 100644 index 9c52f9a..0000000 --- a/scripts/hooks/atlas-hook.ps1 +++ /dev/null @@ -1,17 +0,0 @@ -param([Parameter(Mandatory)][string]$Hook) -Set-StrictMode -Version Latest -$ErrorActionPreference = 'Stop' - -if ($Hook -notmatch '^[a-zA-Z0-9_-]+$') { - [Console]::Error.WriteLine("atlas: invalid hook name '$Hook'") - exit 1 -} - -$script = Join-Path (Split-Path -Parent $PSCommandPath) "$Hook.ps1" -if (-not (Test-Path $script)) { - [Console]::Error.WriteLine("atlas: $Hook hook not found") - exit 1 -} - -& powershell.exe -NonInteractive -NoProfile -File $script @args -exit $LASTEXITCODE diff --git a/scripts/hooks/comment-checker.ps1 b/scripts/hooks/comment-checker.ps1 deleted file mode 100644 index 8ff9a86..0000000 --- a/scripts/hooks/comment-checker.ps1 +++ /dev/null @@ -1,113 +0,0 @@ -# PostToolUse hook: Flags files with excessive comment density (>30%). -Set-StrictMode -Version Latest -$ErrorActionPreference = 'Stop' - -$raw = [Console]::In.ReadToEnd() -if (-not $raw.Trim()) { exit 0 } -try { $data = $raw | ConvertFrom-Json } catch { exit 0 } - -$toolName = if ($data.PSObject.Properties['tool_name']) { [string]$data.tool_name } else { '' } -$writingTools = @('editFiles','create_file','replace_string_in_file','multi_replace_string_in_file') -if ($toolName -notin $writingTools) { exit 0 } - -$filePath = $env:TOOL_INPUT_FILE_PATH -if (-not $filePath -and $data.PSObject.Properties['tool_input']) { - $ti = $data.tool_input - if ($ti.PSObject.Properties['filePath']) { $filePath = $ti.filePath } -} -if (-not $filePath -or -not (Test-Path $filePath -PathType Leaf)) { exit 0 } - -$ext = [System.IO.Path]::GetExtension($filePath).TrimStart('.') -$supportedExts = @('js','ts','tsx','jsx','go','java','c','cpp','rs','swift','kt','cs','py','sh','bash','zsh','yml','yaml','toml','rb','html','xml','svg','vue','ps1') -if ($ext -notin $supportedExts) { exit 0 } - -$totalLines = 0 -$commentLines = 0 -$inJsdoc = $false -$inBlock = $false -$inHtmlBlock = $false - -foreach ($line in [System.IO.File]::ReadLines($filePath)) { - $trimmed = $line.Trim() - if (-not $trimmed) { continue } - $totalLines++ - - if ($inJsdoc) { - if ($trimmed.EndsWith('*/')) { $inJsdoc = $false } - continue - } - if ($inBlock) { - $commentLines++ - if ($trimmed.EndsWith('*/') -or $trimmed.EndsWith('#>')) { $inBlock = $false } - continue - } - if ($inHtmlBlock) { - $commentLines++ - if ($trimmed.Contains('-->')) { $inHtmlBlock = $false } - continue - } - - if ($trimmed.StartsWith('/**')) { - if (-not $trimmed.EndsWith('*/')) { $inJsdoc = $true } - continue - } - if ($trimmed.StartsWith('/*')) { - $commentLines++ - if (-not $trimmed.EndsWith('*/')) { $inBlock = $true } - continue - } - - switch -Wildcard ($ext) { - { $_ -in @('js','ts','tsx','jsx','go','java','c','cpp','rs','swift','kt','cs') } { - if ($trimmed.StartsWith('//')) { - if ($trimmed -notmatch '^//\s*(eslint-disable|@ts-|prettier-ignore|noinspection|NOLINT|nosec|nolint|istanbul)') { - $commentLines++ - } - } - } - 'py' { - if ($trimmed.StartsWith('#')) { - if (-not $trimmed.StartsWith('#!') -and $trimmed -notmatch '^#\s*(type:|noqa|pylint:|fmt:|isort:|pragma:)') { - $commentLines++ - } - } - } - { $_ -in @('sh','bash','zsh','yml','yaml','toml','rb') } { - if ($trimmed.StartsWith('#')) { - if (-not $trimmed.StartsWith('#!') -and $trimmed -notmatch '^#\s*(shellcheck|rubocop)') { - $commentLines++ - } - } - } - { $_ -in @('html','xml','svg','vue') } { - if ($trimmed.StartsWith('')) { $inHtmlBlock = $true } - } - } - 'ps1' { - if ($trimmed.StartsWith('<#')) { - $commentLines++ - if (-not $trimmed.EndsWith('#>')) { $inBlock = $true } - } elseif ($trimmed.StartsWith('#')) { - if ($trimmed -notmatch '^#\s*(Requires|region|endregion)') { - $commentLines++ - } - } - } - } -} - -if ($totalLines -lt 10) { exit 0 } - -$ratio = [int]($commentLines * 100 / $totalLines) -if ($ratio -le 30) { exit 0 } - -$baseName = [System.IO.Path]::GetFileName($filePath) -[PSCustomObject]@{ - hookSpecificOutput = [PSCustomObject]@{ - hookEventName = 'PostToolUse' - additionalContext = "WARNING: $baseName has ${ratio}% comment density (${commentLines}/${totalLines} non-blank lines). Comments exceeding 30% often indicate AI slop -- restating what code obviously does. Remove comments that add no value beyond what the code communicates. JSDoc/docstrings for public APIs and directive comments are already excluded from this count." - } -} | ConvertTo-Json -Compress -Depth 5 -exit 0 diff --git a/scripts/hooks/comment-checker.sh b/scripts/hooks/comment-checker.sh deleted file mode 100644 index 8cf9a0c..0000000 --- a/scripts/hooks/comment-checker.sh +++ /dev/null @@ -1,139 +0,0 @@ -#!/bin/bash -# PostToolUse hook: Flags files with excessive comment density (>30%). -# High comment ratios often indicate AI slop -- comments that restate what -# code obviously does. JSDoc/docstrings, directive comments, and shebangs -# are excluded from the count. - -set -euo pipefail - -# jq is required for JSON parsing -- degrade silently if missing -if ! command -v jq &> /dev/null; then - cat > /dev/null - exit 0 -fi - -INPUT=$(cat) - -TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty') - -case "$TOOL_NAME" in - editFiles | create_file | replace_string_in_file | multi_replace_string_in_file) ;; - *) exit 0 ;; -esac - -FILE_PATH="${TOOL_INPUT_FILE_PATH:-}" -if [[ -z "$FILE_PATH" || ! -f "$FILE_PATH" ]]; then - exit 0 -fi - -EXT="${FILE_PATH##*.}" - -TOTAL_LINES=0 -COMMENT_LINES=0 -IN_JSDOC=false -IN_BLOCK_COMMENT=false - -while IFS= read -r line || [[ -n "$line" ]]; do - trimmed="${line#"${line%%[![:space:]]*}"}" - trimmed="${trimmed%"${trimmed##*[![:space:]]}"}" - [[ -z "$trimmed" ]] && continue - - TOTAL_LINES=$((TOTAL_LINES + 1)) - - # Inside a JSDoc block -- skip entirely (public API docs are exempt) - if $IN_JSDOC; then - if [[ "$trimmed" == *"*/" ]]; then - IN_JSDOC=false - fi - continue - fi - - # Inside a regular block comment -- count - if $IN_BLOCK_COMMENT; then - COMMENT_LINES=$((COMMENT_LINES + 1)) - if [[ "$trimmed" == *"*/" ]]; then - IN_BLOCK_COMMENT=false - elif [[ "$EXT" == "ps1" && "$trimmed" == *"#>" ]]; then - IN_BLOCK_COMMENT=false - fi - continue - fi - - # JSDoc / docstring start (/** ... ) - if [[ "$trimmed" == "/**"* ]]; then - [[ "$trimmed" != *"*/" ]] && IN_JSDOC=true - continue - fi - - # Regular block comment start (/* ... ) - if [[ "$trimmed" == "/*"* ]]; then - COMMENT_LINES=$((COMMENT_LINES + 1)) - [[ "$trimmed" != *"*/" ]] && IN_BLOCK_COMMENT=true - continue - fi - - # Single-line comments by language family - case "$EXT" in - js | ts | tsx | jsx | go | java | c | cpp | rs | swift | kt | cs) - if [[ "$trimmed" == "//"* ]]; then - # Directive comments are exempt - if [[ "$trimmed" =~ ^//[[:space:]]*(eslint-disable|@ts-|prettier-ignore|noinspection|NOLINT|nosec|nolint|istanbul) ]]; then - continue - fi - COMMENT_LINES=$((COMMENT_LINES + 1)) - fi - ;; - py) - if [[ "$trimmed" == "#"* ]]; then - [[ "$trimmed" == "#!/"* ]] && continue - if [[ "$trimmed" =~ ^#[[:space:]]*(type:|noqa|pylint:|fmt:|isort:|pragma:) ]]; then - continue - fi - COMMENT_LINES=$((COMMENT_LINES + 1)) - fi - ;; - sh | bash | zsh | yml | yaml | toml | rb) - if [[ "$trimmed" == "#"* ]]; then - [[ "$trimmed" == "#!/"* ]] && continue - if [[ "$trimmed" =~ ^#[[:space:]]*(shellcheck|rubocop) ]]; then - continue - fi - COMMENT_LINES=$((COMMENT_LINES + 1)) - fi - ;; - html | xml | svg | vue) - if [[ "$trimmed" == "