diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ffe47c8226..b3fbeeba15 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -127,7 +127,7 @@ jobs: strategy: fail-fast: false matrix: - check: ['lint', 'format', 'commit-lint', 'dependencies'] + check: ['lint', 'format', 'commit-lint', 'dependencies', 'scripts'] steps: - uses: socketdev/action@v1 @@ -174,6 +174,10 @@ jobs: if: matrix.check == 'dependencies' run: yarn run check-deps + - name: Test Root Scripts + if: matrix.check == 'scripts' + run: yarn run test:scripts + # We conciously do not audit dependencies as a PR step since errors are typically # unrelated to the PR changes. This check is performed in `publish.yml`. diff --git a/CLAUDE.md b/CLAUDE.md index f30d5e8677..8cfdbd2683 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -75,6 +75,8 @@ For modules that need browser support (especially those using `@bitgo/wasm-utxo` ### Commits BitGoJS uses conventional commits. All commits MUST pass the spec described in `commitlint.config.js`. +Every commit needs a footer issue reference (`references-empty: never`). Web Experience side work without a Linear ticket may use `WEB-000` in the footer (junk-drawer reference, same pattern as `BTC-000` / `CSHLD-000`). Branches named `WEB-000-*` get `TICKET: WEB-000` appended automatically by `scripts/prepare-commit-msg.js`. + ### TypeScript Guidelines - **Avoid `any` type**: Use specific types, interfaces, or union types instead of `any`. If absolutely necessary, prefer `unknown` and use type guards for safety. diff --git a/commitlint.config.js b/commitlint.config.js index b1e893aae0..bedf927713 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -74,6 +74,7 @@ module.exports = { 'WAL-', 'WCN-', 'WCI-', + 'WEB-', 'COIN-', 'COINS-', 'COINFLP-', diff --git a/modules/web-demo/README.md b/modules/web-demo/README.md index d923bac251..1d5e81b3ad 100644 --- a/modules/web-demo/README.md +++ b/modules/web-demo/README.md @@ -1,13 +1,63 @@ # web-demo -This module serves as an example on how you can integrate the BitGo SDK into your own UI. +Reference UI for integrating the BitGo SDK in the browser. Routes mirror problems solved in production apps (**bitgo-retail**, **bitgo-ui**) using SDK packages directly — no private `@bitgo-private/*` dependencies. -## Usage +## Quick start -In order to start the server, run the following command after having already run `yarn install` in the root of the repo. +From the BitGoJS repo root (after `yarn install`): -``` +```bash +cd modules/web-demo yarn dev ``` -This will spin up a server serving the UI at [localhost:8080](localhost:8080). +Opens [localhost:8080](http://localhost:8080). + +## Consumer config matrix + +Production apps bundle the SDK differently. web-demo will support **consumer profiles** (via `CONSUMER_PROFILE` env) so each profile documents one real-world setup. Profiles are added incrementally; only `classic-webpack` is active today. + +| Profile | Status | Mirrors | Bundler | WASM | SDK auth | +| --- | --- | --- | --- | --- | --- | +| `classic-webpack` | **active** (default) | bitgo-ui classic app, default web-demo | Webpack + [`webpack/bitgojs.config.js`](../../webpack/bitgojs.config.js) | Partial ESM aliases in shared config | Access token or `/webcrypto-auth` | +| `retail-browser` | planned | bitgo-retail web (`MOBILE_MODE` off) | Webpack profile | Full `@bitgo/wasm-*` ESM paths, DKLS/cardano browser swaps | `WebCryptoHmacStrategy` + `requestIdPrefix` | +| `retail-mobile-stub` | planned | bitgo-retail mobile webview | Webpack profile | Empty WASM shims (no heavy signing WASM) | Bridge / token (documented only) | +| `direct-sdk` | planned | bitgo-retail mock server, `examples/ts` | Either | Per profile | `BitGoAPI` + `customRootURI`, no Express | +| `express-proxy` | planned | Self-hosted Express users | Either | Per profile | Local Express signing proxy | + +**Planned usage** (not wired yet): + +```bash +yarn dev # classic-webpack +CONSUMER_PROFILE=retail-browser yarn dev # future +CONSUMER_PROFILE=retail-mobile-stub yarn dev # future +``` + +### What each production app solved + +| Problem | bitgo-retail | bitgo-ui | web-demo route | +| --- | --- | --- | --- | +| Browser WASM loading | Vite ESM aliases for `@bitgo/wasm-utxo`, `wasm-ton`, `wasm-mps` | Webpack aliases (similar) | `/wasm-miniscript` | +| HMAC without raw token in storage | `WebCryptoHmacStrategy` + IndexedDB | Classic token flow | `/webcrypto-auth` | +| Passkey PRF wallet flows | `@bitgo-private/web-client` (retail); SDK: `@bitgo/passkey-crypto` | Partial | `/passkey-demo` | +| Keycard generation | `@bitgo/key-card` in wallet create / migration | Keycard flows | `/keycard` | +| Lazy coin registration | `retail-sdk-client/coinFactory` | `~/utils/coinFactory` | `/coins` | +| Token enable prebuild | React Query + `PrebuildTransactionResult` | `useBuildTokenEnablementMutation` | planned `/token-enable` | + +## Demo routes + +| Path | Description | +| --- | --- | +| `/` | Home | +| `/bitgo-js` | BitGo SDK object inspect | +| `/bitgo-api` | BitGoAPI usage | +| `/coins` | Coin factory / registration | +| `/keycard` | Keycard download fixtures | +| `/wasm-miniscript` | WASM miniscript smoke | +| `/ecdsachallenge` | ECDSA challenge generation | +| `/webcrypto-auth` | WebCrypto HMAC strategy + IndexedDB session | +| `/passkey-demo` | Passkey register / attach / wallet (uses `@bitgo/passkey-crypto`) | + +## Contributing + +Owned by **@BitGo/web-experience** (`CODEOWNERS`). Prefer small PRs: one profile, one route, or one README section at a time. See the local BitGoJS side-improvements plan for micro-PR sizing. diff --git a/package.json b/package.json index 309cf9cde4..dceb154a40 100644 --- a/package.json +++ b/package.json @@ -278,7 +278,8 @@ "precommit": "lint-staged", "lint-fix": "lerna run lint --parallel -- --fix", "prepare-commit-msg": "node ./scripts/prepare-commit-msg.js", - "test:prepare-release": "mocha --require tsx ./scripts/tests/prepareRelease/prepare-release-main.test.ts" + "test:prepare-release": "mocha --require tsx ./scripts/tests/prepareRelease/prepare-release-main.test.ts", + "test:scripts": "mocha './scripts/tests/**/*.test.js'" }, "dependencies": { "axios": "1.16.1", diff --git a/scripts/prepare-commit-msg.js b/scripts/prepare-commit-msg.js index 0972fc0131..05bffcd559 100755 --- a/scripts/prepare-commit-msg.js +++ b/scripts/prepare-commit-msg.js @@ -5,21 +5,35 @@ const childProcess = require('child_process'); const fs = require('fs'); const exec = promisify(childProcess.exec); -// ex WP-1234 -const branchRegex = /([A-Z]+-)(\d+)/; +// Build regex from commitlint's known issuePrefixes so arbitrary branch segments +// (e.g. "release-2-hotfix") don't produce bogus TICKET footers. +let issuePrefixes; +try { + issuePrefixes = require('../commitlint.config.js').parserPreset.parserOpts.issuePrefixes; +} catch (_) { + issuePrefixes = []; +} +const escapedPrefixes = issuePrefixes + .filter((p) => p !== '#') + .map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); +// Anchored at branch start; case-insensitive so "web-000-desc" matches "WEB-" +const branchRegex = new RegExp(`^(${escapedPrefixes.join('|')})(\\d+)`, 'i'); // ex TICKET: WP-1234 const commitRegex = /(ticket|issue):\s(\S+)/gim; +function extractTicket(branch) { + const found = branch.match(branchRegex); + return found ? found[0].toUpperCase() : null; +} + async function main() { const commitMsgFilepath = process.argv[2]; try { const branch = (await exec(`git branch --show-current`)).stdout.trim(); - const found = branch.match(branchRegex); - // Do not append message if branch name does not match regex - if (!found.length) { + const ticket = extractTicket(branch); + if (!ticket) { return; } - const ticket = found[0]; const data = fs.readFileSync(commitMsgFilepath, 'utf8'); // Exit if ticket is already in commit footer if (data.match(commitRegex)) { @@ -29,4 +43,5 @@ async function main() { } catch (e) {} } -main(); +module.exports = { extractTicket }; +if (require.main === module) main(); diff --git a/scripts/tests/prepare-commit-msg.test.js b/scripts/tests/prepare-commit-msg.test.js new file mode 100644 index 0000000000..9ea0b7d6d3 --- /dev/null +++ b/scripts/tests/prepare-commit-msg.test.js @@ -0,0 +1,26 @@ +const assert = require('assert'); +const { extractTicket } = require('../prepare-commit-msg'); + +describe('extractTicket', () => { + it('extracts uppercase ticket from uppercase branch', () => { + assert.strictEqual(extractTicket('WEB-000-my-feature'), 'WEB-000'); + }); + + it('extracts and uppercases ticket from lowercase branch', () => { + assert.strictEqual(extractTicket('web-000-my-feature'), 'WEB-000'); + }); + + it('extracts WP ticket', () => { + assert.strictEqual(extractTicket('WP-1234-some-work'), 'WP-1234'); + }); + + it('returns null for branches without a known prefix', () => { + assert.strictEqual(extractTicket('release-2-hotfix'), null); + assert.strictEqual(extractTicket('main'), null); + assert.strictEqual(extractTicket('feature-no-ticket'), null); + }); + + it('returns null for empty branch', () => { + assert.strictEqual(extractTicket(''), null); + }); +});