diff --git a/.github/workflows/build.yml b/.github/workflows/build.yaml similarity index 100% rename from .github/workflows/build.yml rename to .github/workflows/build.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index b38945c..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: CI - -on: - pull_request: - branches: ["**"] - -jobs: - build-and-check: - name: Build and checks - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Use Node.js 20 - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: npm - - - name: Install dependencies - run: npm ci - - - name: TypeScript type check - run: npm run typecheck - - - name: Build (development env) - run: npm run build - - - name: Build (test env) - run: npm run build:test - - - name: Build (production env) - run: npm run build:prod - - - name: Archive production build - if: always() - uses: actions/upload-artifact@v4 - with: - name: dist - path: dist/** - if-no-files-found: ignore - - diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b12f2ee..7d29aff 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,39 +1,20 @@ name: Release on: - push: - branches: - - '**' pull_request: types: [closed] - branches: - - master - - main + branches: [master] + +permissions: + contents: write jobs: release: - if: | - ( - github.event_name == 'pull_request' && - github.event.pull_request.merged && - !contains(github.event.pull_request.title, '[release]') && - !contains(github.event.pull_request.title, '[no-release]') - ) || - ( - github.event_name == 'push' && - contains(join(github.event.commits.*.message), '[release]') && - !contains(join(github.event.commits.*.message), '[no-release]') - ) + if: github.event.pull_request.merged == true runs-on: ubuntu-latest - permissions: - contents: write steps: - - name: Checkout code + - name: Checkout uses: actions/checkout@v4 - with: - # On pull_request merge, checks out the merge commit. - # On push, this is empty and it checks out the triggering commit. - ref: ${{ github.event.pull_request.merge_commit_sha }} - name: Use Node.js 20 uses: actions/setup-node@v4 @@ -45,89 +26,44 @@ jobs: run: npm ci - name: Build (production) - run: npm run build:prod + run: npm run build - name: Make package script executable run: chmod +x scripts/package.sh - name: Package extension ZIP - run: ./scripts/package.sh production - - - name: Locate ZIP file - id: artifact + id: pkg run: | - ZIP_PATH=$(ls -1 release/*.zip | head -n1) - echo "zip_path=$ZIP_PATH" >> "$GITHUB_OUTPUT" - echo "zip_name=$(basename "$ZIP_PATH")" >> "$GITHUB_OUTPUT" + ./scripts/package.sh production + echo "zip=$(ls -1 release/*.zip | head -n1)" >> "$GITHUB_OUTPUT" - - name: Upload build artifact + - name: Read version from manifest + id: ver + run: echo "version=$(jq -r .version manifest.json)" >> "$GITHUB_OUTPUT" + + - name: Upload ZIP artifact uses: actions/upload-artifact@v4 with: - name: ${{ steps.artifact.outputs.zip_name }} - path: ${{ steps.artifact.outputs.zip_path }} - - - name: Generate Release Info - id: release_info - run: | - if [ "${{ github.event_name }}" == "pull_request" ]; then - TAG="pr-${{ github.event.pull_request.number }}-merge-${{ github.run_number }}" - RELEASE_NAME="Auto release for PR #${{ github.event.pull_request.number }}" - RELEASE_BODY="Automatic release for PR #${{ github.event.pull_request.number }} merged into ${{ github.event.pull_request.base.ref }}.\n- Commit: ${{ github.event.pull_request.merge_commit_sha }}" - else - VERSION=$(node -e "console.log(require('./package.json').version)") - COMMIT_SHA_SHORT=$(echo "${{ github.sha }}" | cut -c1-7) - TAG="v${VERSION}-${COMMIT_SHA_SHORT}-${{ github.run_number }}" - RELEASE_NAME="Release $TAG" - RELEASE_BODY="Manual release triggered from commit [\`${COMMIT_SHA_SHORT}\`](${{ github.event.repository.html_url }}/commit/${{ github.sha }}).\n\n**Commit message:**\n${{ github.event.head_commit.message }}" - fi - echo "TAG=$TAG" >> "$GITHUB_OUTPUT" - echo "RELEASE_NAME=$RELEASE_NAME" >> "$GITHUB_OUTPUT" - # Using heredoc for multiline body - { - echo "RELEASE_BODY<> "$GITHUB_OUTPUT" - - - name: Create and push tag - run: | - git config user.name "${{ github.actor }}" - git config user.email "${{ github.actor }}@users.noreply.github.com" - git tag -a "${{ steps.release_info.outputs.TAG }}" -m "${{ steps.release_info.outputs.RELEASE_NAME }}" - git push origin "${{ steps.release_info.outputs.TAG }}" + name: extension-zip + path: ${{ steps.pkg.outputs.zip }} + if-no-files-found: error + compression-level: 0 - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - tag_name: ${{ steps.release_info.outputs.TAG }} - name: ${{ steps.release_info.outputs.RELEASE_NAME }} - body: ${{ steps.release_info.outputs.RELEASE_BODY }} - files: ${{ steps.artifact.outputs.zip_path }} + tag_name: v${{ steps.ver.outputs.version }}-${{ github.sha }} + name: v${{ steps.ver.outputs.version }} (PR #${{ github.event.pull_request.number }}) + generate_release_notes: true + files: ${{ steps.pkg.outputs.zip }} - - name: Upload & publish to Chrome Web Store - env: - ZIP_PATH: ${{ steps.artifact.outputs.zip_path }} - EXT_ID: ${{ secrets.CWS_EXTENSION_ID }} - CLIENT_ID: ${{ secrets.CWS_CLIENT_ID }} - CLIENT_SECRET: ${{ secrets.CWS_CLIENT_SECRET }} - REFRESH_TOKEN: ${{ secrets.CWS_REFRESH_TOKEN }} - run: | - set -euo pipefail - ACCESS_TOKEN=$(curl -sS -X POST https://oauth2.googleapis.com/token \ - -d client_id="$CLIENT_ID" \ - -d client_secret="$CLIENT_SECRET" \ - -d refresh_token="$REFRESH_TOKEN" \ - -d grant_type=refresh_token | jq -r .access_token) - - curl -sS -X PUT \ - -H "Authorization: Bearer $ACCESS_TOKEN" \ - -H "x-goog-api-version: 2" \ - -H "Content-Type: application/zip" \ - --data-binary @"$ZIP_PATH" \ - "https://www.googleapis.com/upload/chromewebstore/v1.1/items/$EXT_ID" - - curl -sS -X POST \ - -H "Authorization: Bearer $ACCESS_TOKEN" \ - -H "x-goog-api-version: 2" \ - -H "Content-Length: 0" \ - "https://www.googleapis.com/chromewebstore/v1.1/items/$EXT_ID/publish?publishTarget=default" + - name: Upload to Chrome Web Store (optional) + if: ${{ secrets.CHROME_CLIENT_ID != '' && secrets.CHROME_CLIENT_SECRET != '' && secrets.CHROME_REFRESH_TOKEN != '' && secrets.CHROME_EXTENSION_ID != '' }} + uses: Klemensas/chrome-extension-deploy@v2 + with: + refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }} + client-id: ${{ secrets.CHROME_CLIENT_ID }} + client-secret: ${{ secrets.CHROME_CLIENT_SECRET }} + file-name: ${{ steps.pkg.outputs.zip }} + app-id: ${{ secrets.CHROME_EXTENSION_ID }} + publish: true diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml deleted file mode 100644 index e082e82..0000000 --- a/.github/workflows/version-bump.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Auto-bump manifest.json version in PR - -on: - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - -permissions: - contents: write # needed to push back to the PR branch - -jobs: - bump: - # Only try to push if the PR branch is in this repo (not a fork) - if: github.event.pull_request.head.repo.full_name == github.repository - runs-on: ubuntu-latest - - steps: - - name: Checkout PR head branch (not merge commit) - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.ref }} - persist-credentials: false - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - # Optional: if your repo requires "Verified" commits, import a GPG key - # Put your armored private key in repo/org secret GPG_PRIVATE_KEY and passphrase in GPG_PASSPHRASE - - name: Import GPG key (optional, for Verified commits) - if: ${{ secrets.GPG_PRIVATE_KEY && secrets.GPG_PASSPHRASE }} - uses: crazy-max/ghaction-import-gpg@v6 - with: - gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} - passphrase: ${{ secrets.GPG_PASSPHRASE }} - git_user_signingkey: true - git_commit_gpgsign: true - - - name: Bump manifest.json (patch) - id: bump - run: | - test -f manifest.json || { echo "manifest.json not found"; exit 1; } - node - <<'NODE' - const fs = require('fs'); - const p = 'manifest.json'; - const m = JSON.parse(fs.readFileSync(p, 'utf8')); - const parts = (m.version || '0.0.0').split('.').map(n => parseInt(n,10)||0); - while (parts.length < 3) parts.push(0); - parts[2] += 1; // patch++ - m.version = parts.join('.'); - fs.writeFileSync(p, JSON.stringify(m, null, 2) + '\n'); - console.log('NEW_VERSION=' + m.version); - NODE - NEW_VERSION=$(node -e "console.log(JSON.parse(require('fs').readFileSync('manifest.json','utf8')).version)") - echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT" - - - name: Commit change - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add manifest.json - git commit -m "chore: bump manifest version to ${{ steps.bump.outputs.new_version }} [skip ci]" || echo "No changes to commit" - - - name: Push back to PR branch - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - # push to the contributor's branch in *this* repo - git push "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" \ - HEAD:${{ github.event.pull_request.head.ref }} diff --git a/.husky/prepare-commit-msg b/.husky/prepare-commit-msg new file mode 100755 index 0000000..5111c10 --- /dev/null +++ b/.husky/prepare-commit-msg @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +node scripts/version-bump.js $1 diff --git a/manifest.json b/manifest.json index ae00220..985b342 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "listr", - "version": "1.0.1", + "version": "1.0.2", "description": "Displays a popup with bookmarks and more.", "icons": { "16": "assets/icons/icon-16.png", @@ -12,8 +12,14 @@ "action": { "default_popup": "popup.html" }, - "permissions": ["tabs", "storage", "downloads"], - "host_permissions": [""], + "permissions": [ + "tabs", + "storage", + "downloads" + ], + "host_permissions": [ + "" + ], "content_scripts": [ { "matches": [ @@ -22,7 +28,9 @@ "https://*.youtube.com/*", "https://*.pinterest.com/*" ], - "js": ["dist/content.js"], + "js": [ + "dist/content.js" + ], "run_at": "document_end" } ] diff --git a/package-lock.json b/package-lock.json index e07dcfd..e61d01a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "tiktok-downloader-chrome-extension", + "name": "listr", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "tiktok-downloader-chrome-extension", + "name": "listr", "version": "1.0.0", "dependencies": { "lucide-react": "^0.477.0", @@ -19,6 +19,7 @@ "@types/webextension-polyfill": "^0.12.3", "autoprefixer": "^10.4.20", "css-loader": "^7.1.2", + "husky": "^9.1.7", "postcss": "^8.4.49", "postcss-loader": "^8.1.1", "style-loader": "^4.0.0", @@ -1417,6 +1418,22 @@ "node": ">= 0.4" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/icss-utils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", diff --git a/package.json b/package.json index ed60a91..0f57552 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "listr", - "version": "1.0.0", + "version": "1.0.2", "description": "Get bookmarks and favorites from your favorite social media platforms.", "scripts": { "build": "webpack --mode production", @@ -8,7 +8,8 @@ "build:prod": "webpack --mode production", "start": "webpack --mode development --watch", "typecheck": "tsc --noEmit", - "package": "./scripts/package.sh" + "package": "./scripts/package.sh", + "prepare": "husky" }, "dependencies": { "lucide-react": "^0.477.0", @@ -22,6 +23,7 @@ "@types/webextension-polyfill": "^0.12.3", "autoprefixer": "^10.4.20", "css-loader": "^7.1.2", + "husky": "^9.1.7", "postcss": "^8.4.49", "postcss-loader": "^8.1.1", "style-loader": "^4.0.0", diff --git a/scripts/package.sh b/scripts/package.sh index 5e2a3c5..580ee0e 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -1,46 +1,62 @@ -#!/bin/bash - +#!/usr/bin/env bash set -euo pipefail -ENVIRONMENT=${1:-production} -ROOT_DIR=$(cd "$(dirname "$0")/.." && pwd) -RELEASE_DIR="$ROOT_DIR/release" -DIST_DIR="$ROOT_DIR/dist" - -echo "Packaging environment: $ENVIRONMENT" - -cd "$ROOT_DIR" - -echo "Cleaning dist and release directories..." -rm -rf "$DIST_DIR" "$RELEASE_DIR" -mkdir -p "$RELEASE_DIR" - -case "$ENVIRONMENT" in - development|test|production) ;; - *) echo "Unknown environment: $ENVIRONMENT (use development|test|production)"; exit 1;; -esac - -echo "Building extension..." -npm run build --silent +MODE="${1:-production}" -NAME=$(node -e "console.log(require('./package.json').name)") -VERSION=$(node -e "console.log(require('./package.json').version)") -ZIP_NAME="$NAME-$VERSION-$ENVIRONMENT.zip" +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" -echo "Creating zip: $ZIP_NAME" -TEMP_DIR=$(mktemp -d) -trap 'rm -rf "$TEMP_DIR"' EXIT +if ! command -v jq >/dev/null 2>&1; then + echo "jq is required (to read manifest.json)." >&2 + exit 1 +fi -# Copy required files into temp staging directory -mkdir -p "$TEMP_DIR" -cp -a manifest.json popup.html icons assets "$TEMP_DIR/" -cp -a dist "$TEMP_DIR/" +VERSION="$(jq -r .version manifest.json)" +NAME_RAW="$(jq -r .name manifest.json)" +NAME="$(echo "$NAME_RAW" | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9-_' | sed -E 's/_+/-/g' | sed -E 's/-+/-/g')" +[ -n "$NAME" ] || NAME="extension" -# Optional: include README and LICENSE -if [ -f README.md ]; then cp README.md "$TEMP_DIR/"; fi -if [ -f LICENSE ]; then cp LICENSE "$TEMP_DIR/"; fi +DIST="$ROOT/dist" +RELEASE_DIR="$ROOT/release" +ZIP_PATH="$RELEASE_DIR/${NAME}-${VERSION}-${MODE}.zip" +echo "Packaging environment: $MODE" mkdir -p "$RELEASE_DIR" -(cd "$TEMP_DIR" && zip -q -r "$RELEASE_DIR/$ZIP_NAME" .) -echo "Done: $RELEASE_DIR/$ZIP_NAME" +if [ ! -d "$DIST" ]; then + echo "dist/ not found; running build once…" + npm run build +fi + +echo "Preparing package content…" +cp -f "$ROOT/manifest.json" "$DIST/" + +if [ -d "$ROOT/icons" ]; then + echo "Copying icons/ → dist/icons/" + mkdir -p "$DIST/icons" + cp -R "$ROOT/icons/." "$DIST/icons/" +else + echo "No icons/ directory; skipping." +fi + +if [ -d "$ROOT/assets" ]; then + echo "Copying assets/ → dist/assets/" + mkdir -p "$DIST/assets" + cp -R "$ROOT/assets/." "$DIST/assets/" +fi + +if [ -d "$ROOT/public" ]; then + echo "Copying public/ → dist/" + cp -R "$ROOT/public/." "$DIST/" +fi + +for f in popup.html options.html background.html; do + if [ -f "$ROOT/$f" ]; then + cp -f "$ROOT/$f" "$DIST/" + fi +done + +echo "Creating zip: $(basename "$ZIP_PATH")" +cd "$DIST" +zip -qr9 "$ZIP_PATH" . +echo "✅ Created: $ZIP_PATH" diff --git a/scripts/version-bump.js b/scripts/version-bump.js index 83ab818..2e487dc 100644 --- a/scripts/version-bump.js +++ b/scripts/version-bump.js @@ -1,26 +1,56 @@ const fs = require('fs'); +const { execSync } = require('child_process'); const manifestPath = './manifest.json'; const packageJsonPath = './package.json'; +const commitMsgPath = process.argv[2]; -const branchName = process.env.BRANCH_NAME; -const commitMessages = process.env.COMMIT_MESSAGES; +if (!commitMsgPath) { + console.log('No commit message path provided. Skipping version bump.'); + process.exit(0); +} -// Read manifest.json to get the current version -const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); -const currentVersion = manifest.version; +// Function to get file content from a specific branch +function getFileFromBranch(branch, path) { + try { + return execSync(`git show ${branch}:${path}`).toString(); + } catch (error) { + console.error(`Error getting ${path} from branch ${branch}:`, error); + return null; + } +} -console.log(`Current version: ${currentVersion}`); +// 1. Get versions and compare +const masterManifestContent = getFileFromBranch('master', manifestPath) || getFileFromBranch('main', manifestPath); +if (!masterManifestContent) { + console.log('Could not retrieve manifest from master/main branch. Skipping version bump.'); + process.exit(0); +} -let [major, minor, patch] = currentVersion.split('.').map(Number); +const masterVersion = JSON.parse(masterManifestContent).version; +const currentManifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); +const currentVersion = currentManifest.version; + +console.log(`Version on master/main: ${masterVersion}`); +console.log(`Version on current branch: ${currentVersion}`); -let bumpType = 'patch'; // Default bump type +if (masterVersion !== currentVersion) { + console.log('Version already bumped in this branch. Skipping.'); + process.exit(0); +} + +// 2. Bump version based on commit message and branch name +const commitMessage = fs.readFileSync(commitMsgPath, 'utf8'); +const branchName = execSync('git rev-parse --abbrev-ref HEAD').toString().trim(); -if (branchName && branchName.startsWith('feature/')) { +let [major, minor, patch] = currentVersion.split('.').map(Number); +let bumpType = 'patch'; // Default + +if (branchName.startsWith('feature/')) { bumpType = 'minor'; } -if (commitMessages && commitMessages.includes('[major]')) { +if (commitMessage.includes('[major]')) { bumpType = 'major'; } @@ -38,18 +68,17 @@ if (bumpType === 'major') { } const newVersion = `${major}.${minor}.${patch}`; -console.log(`New version: ${newVersion}`); +console.log(`Bumping version to: ${newVersion}`); -// Update manifest.json -manifest.version = newVersion; -fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n'); -console.log(`Updated ${manifestPath} to version ${newVersion}`); +// 3. Update manifest.json and package.json +currentManifest.version = newVersion; +fs.writeFileSync(manifestPath, JSON.stringify(currentManifest, null, 2) + '\n'); -// Update package.json const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); packageJson.version = newVersion; fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n'); -console.log(`Updated ${packageJsonPath} to version ${newVersion}`); -// Set output for the GitHub Action -console.log(`::set-output name=new_version::${newVersion}`); +// 4. Add updated files to the commit +execSync(`git add ${manifestPath} ${packageJsonPath}`); + +console.log('Version bumped and files staged successfully.');