diff --git a/.github/workflows/sync-react-b2e-sample.yml b/.github/workflows/sync-react-b2e-sample.yml new file mode 100644 index 0000000..2139eef --- /dev/null +++ b/.github/workflows/sync-react-b2e-sample.yml @@ -0,0 +1,88 @@ +# Syncs @salesforce/webapp-template-app-react-sample-b2e-experimental into +# samples/webapp-template-app-react-sample-b2e-experimental. Opens a PR only when +# the npm package version has changed. Same steps as running locally: +# npm install && npm run sync-react-b2e-sample +# +# Uses IDEE_GH_TOKEN (bot) for checkout and PR creation; same pattern as +# salesforcedx-vscode-einstein-gpt/.github/workflows/build.yml + +name: Sync React B2E sample from npm + +on: + workflow_dispatch: + schedule: + # Nightly at 02:00 UTC + - cron: "0 2 * * *" + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.IDEE_GH_TOKEN }} + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + cache: npm + + - name: Configure Git for private repo access + run: | + git config --global --add url."https://${{ secrets.IDEE_GH_TOKEN }}@github.com/".insteadOf "https://github.com/" + git config --global --add url."https://${{ secrets.IDEE_GH_TOKEN }}@github.com/".insteadOf "git@github.com:" + git config --global --add url."https://${{ secrets.IDEE_GH_TOKEN }}@github.com/".insteadOf "ssh://git@github.com/" + + - name: Get latest npm version + id: npm-version + run: | + LATEST=$(npm view @salesforce/webapp-template-app-react-sample-b2e-experimental version) + echo "latest=$LATEST" >> $GITHUB_OUTPUT + + - name: Read current synced version + id: current-version + run: | + VERSION_FILE="samples/webapp-template-app-react-sample-b2e-experimental/.version" + if [ -f "$VERSION_FILE" ]; then + CURRENT=$(cat "$VERSION_FILE" | tr -d '[:space:]') + echo "current=$CURRENT" >> $GITHUB_OUTPUT + else + echo "current=" >> $GITHUB_OUTPUT + fi + + - name: Skip if version unchanged + id: skip + run: | + if [ "${{ steps.current-version.outputs.current }}" = "${{ steps.npm-version.outputs.latest }}" ]; then + echo "skip=true" >> $GITHUB_OUTPUT + echo "No version change. Current=${{ steps.current-version.outputs.current }}, Latest=${{ steps.npm-version.outputs.latest }}" + else + echo "skip=false" >> $GITHUB_OUTPUT + echo "Version change. Current=${{ steps.current-version.outputs.current }}, Latest=${{ steps.npm-version.outputs.latest }}" + fi + + - name: Install dependencies and run sync (same as local) + if: steps.skip.outputs.skip != 'true' + run: | + npm install + npm run sync-react-b2e-sample + + - name: Create PR on version change + if: steps.skip.outputs.skip != 'true' + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.IDEE_GH_TOKEN }} + branch: chore/sync-react-b2e-sample-${{ steps.npm-version.outputs.latest }} + base: main + title: "chore: sync React B2E sample from npm @ ${{ steps.npm-version.outputs.latest }}" + body: | + Synced `@salesforce/webapp-template-app-react-sample-b2e-experimental` to `samples/webapp-template-app-react-sample-b2e-experimental`. + - **Previous version:** ${{ steps.current-version.outputs.current || 'none' }} + - **New version:** ${{ steps.npm-version.outputs.latest }} + Same flow as running locally: `npm install` then `npm run sync-react-b2e-sample`. + commit-message: "chore: sync react-b2e-sample from npm @ ${{ steps.npm-version.outputs.latest }}" + committer: svc-idee-bot + author: svc-idee-bot diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..12419e9 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v20.18.1 \ No newline at end of file diff --git a/README.md b/README.md index 5f31a8e..5e5b1c0 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ afv-library/ │ ├── metadata-deployments/ │ ├── testing-automation/ │ └── ... +├── samples/ # Synced sample apps (e.g. from npm) +│ └── webapp-template-app-react-sample-b2e-experimental/ └── README.md ``` @@ -48,6 +50,17 @@ afv-library/ Browse the repository and copy/paste any prompt, rule, or skill directly into Agentforce Vibes or your preferred AI tool. +### Samples + +The `samples/` folder contains synced sample apps. For example, `samples/webapp-template-app-react-sample-b2e-experimental/` is kept in sync with the npm package `@salesforce/webapp-template-app-react-sample-b2e-experimental` (nightly and on manual trigger via GitHub Actions). To run the same sync locally from the repo root: + +```bash +npm install +npm run sync-react-b2e-sample +``` + +The GitHub Action runs these same commands and opens a PR only when the npm package version has changed. See [samples/README.md](samples/README.md) for details. + ### Connecting Team or Personal Libraries You can register additional repos with the extension as long as they mirror this structure: diff --git a/package.json b/package.json new file mode 100644 index 0000000..2df2c8b --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +˝{ + "name": "afv-library", + "description": "AI prompts and rules library for Agentforce Vibes development", + "private": true, + "devDependencies": { + "@salesforce/webapp-template-app-react-sample-b2e-experimental": "*" + }, + "scripts": { + "sync-react-b2e-sample": "node scripts/sync-react-b2e-sample.js" + } +} diff --git a/samples/README.md b/samples/README.md new file mode 100644 index 0000000..d9d860a --- /dev/null +++ b/samples/README.md @@ -0,0 +1,23 @@ +# Samples + +Sample apps and templates synced into this repo for reference and reuse. + +## webapp-template-app-react-sample-b2e-experimental + +Source is synced from the npm package [@salesforce/webapp-template-app-react-sample-b2e-experimental](https://www.npmjs.com/package/@salesforce/webapp-template-app-react-sample-b2e-experimental). Only the package’s source (no `node_modules`) is copied into `samples/webapp-template-app-react-sample-b2e-experimental/`. + +### How it’s updated + +- **GitHub Action**: Runs nightly and can be triggered manually from the **Actions** tab (“Sync React B2E sample from npm”). The workflow runs the same steps as below and opens a PR against `main` only when the npm package version has changed. +- **Local**: From the **repo root** you can run the same sync anytime: + + ```bash + npm install + npm run sync-react-b2e-sample + ``` + + This installs the package into root `node_modules` and copies its source into `samples/webapp-template-app-react-sample-b2e-experimental/`, and updates `.version` in that folder. + +### Version tracking + +The file `samples/webapp-template-app-react-sample-b2e-experimental/.version` stores the last-synced npm version. The Action compares it to the latest on npm and only creates a PR when they differ. diff --git a/scripts/sync-react-b2e-sample.js b/scripts/sync-react-b2e-sample.js new file mode 100644 index 0000000..b5119ba --- /dev/null +++ b/scripts/sync-react-b2e-sample.js @@ -0,0 +1,74 @@ +#!/usr/bin/env node +/** + * Syncs source from @salesforce/webapp-template-app-react-sample-b2e-experimental + * into samples/webapp-template-app-react-sample-b2e-experimental/. + * Run from repo root after npm install. Same flow used locally and in CI. + */ + +const fs = require('fs'); +const path = require('path'); + +const PACKAGE_NAME = '@salesforce/webapp-template-app-react-sample-b2e-experimental'; +const SAMPLE_DIR = path.join(process.cwd(), 'samples', 'webapp-template-app-react-sample-b2e-experimental'); + +const pkgRoot = path.join(process.cwd(), 'node_modules', PACKAGE_NAME.replace('/', path.sep)); + +if (!fs.existsSync(pkgRoot)) { + console.error(`Package not found at ${pkgRoot}. Run "npm install" from repo root first.`); + process.exit(1); +} + +// Read version from the package we're syncing from +const pkgJsonPath = path.join(pkgRoot, 'package.json'); +const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')); +const version = pkgJson.version; + +function copyRecursive(src, dest, excludeDirs = new Set(['node_modules', '.git'])) { + const stat = fs.statSync(src); + const basename = path.basename(src); + if (excludeDirs.has(basename)) return; + + if (stat.isDirectory()) { + if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true }); + for (const name of fs.readdirSync(src)) { + copyRecursive(path.join(src, name), path.join(dest, name), excludeDirs); + } + } else { + fs.mkdirSync(path.dirname(dest), { recursive: true }); + fs.copyFileSync(src, dest); + } +} + +// Ensure sample dir exists and clear it (we replace with fresh copy from dist) +if (fs.existsSync(SAMPLE_DIR)) { + for (const name of fs.readdirSync(SAMPLE_DIR)) { + if (name === '.version') continue; // keep .version until we overwrite it + const itemPath = path.join(SAMPLE_DIR, name); + if (fs.statSync(itemPath).isDirectory()) { + fs.rmSync(itemPath, { recursive: true }); + } else { + fs.unlinkSync(itemPath); + } + } +} else { + fs.mkdirSync(SAMPLE_DIR, { recursive: true }); +} + +// Copy dist/ contents into sample dir (package ships source in dist/) +const distPath = path.join(pkgRoot, 'dist'); +if (fs.existsSync(distPath)) { + for (const name of fs.readdirSync(distPath)) { + copyRecursive(path.join(distPath, name), path.join(SAMPLE_DIR, name)); + } +} + +// Copy LICENSE from package root +const licensePath = path.join(pkgRoot, 'LICENSE.txt'); +if (fs.existsSync(licensePath)) { + fs.copyFileSync(licensePath, path.join(SAMPLE_DIR, 'LICENSE.txt')); +} + +// Write .version for version tracking (used by workflow and local) +fs.writeFileSync(path.join(SAMPLE_DIR, '.version'), version + '\n', 'utf8'); + +console.log(`Synced ${PACKAGE_NAME}@${version} to ${SAMPLE_DIR}`);