diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c750e94..aed7b8f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,8 @@ jobs: uses: ./.github/actions/setup-node-pnpm - name: Install Chromium - run: pnpm --filter @lvbt/analytics exec playwright install --with-deps chromium + run: + pnpm --filter @lasvegasfortransit/analytics exec playwright install --with-deps chromium - name: Check run: pnpm check diff --git a/.github/workflows/deploy-collector.yml b/.github/workflows/deploy-collector.yml index 605fa83..83295c8 100644 --- a/.github/workflows/deploy-collector.yml +++ b/.github/workflows/deploy-collector.yml @@ -33,13 +33,14 @@ jobs: uses: ./.github/actions/setup-node-pnpm - name: Install Chromium - run: pnpm --filter @lvbt/analytics exec playwright install --with-deps chromium + run: + pnpm --filter @lasvegasfortransit/analytics exec playwright install --with-deps chromium - name: Validate run: pnpm check - name: Deploy - run: pnpm --filter @lvbt/analytics-collector exec wrangler deploy + run: pnpm --filter @lasvegasfortransit/analytics-collector exec wrangler deploy env: CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5390adb..3a1a21f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,17 +3,17 @@ name: Publish package on: release: types: [published] + workflow_dispatch: permissions: contents: read - id-token: write + packages: write jobs: publish: name: Publish package runs-on: ubuntu-latest timeout-minutes: 15 - environment: npm steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -24,16 +24,17 @@ jobs: uses: ./.github/actions/setup-node-pnpm - name: Install Chromium - run: pnpm --filter @lvbt/analytics exec playwright install --with-deps chromium + run: + pnpm --filter @lasvegasfortransit/analytics exec playwright install --with-deps chromium - name: Validate run: pnpm check - - name: Configure npm registry + - name: Configure GitHub Packages uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version-file: package.json - registry-url: https://registry.npmjs.org + registry-url: https://npm.pkg.github.com - name: Confirm release version run: | @@ -42,4 +43,6 @@ jobs: - name: Publish with provenance working-directory: packages/analytics - run: pnpm publish --access public --no-git-checks + run: pnpm publish --access restricted --no-git-checks + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/weekly.yml b/.github/workflows/weekly.yml index b11922c..7f6ca81 100644 --- a/.github/workflows/weekly.yml +++ b/.github/workflows/weekly.yml @@ -28,7 +28,8 @@ jobs: run: pnpm build - name: Install Chromium - run: pnpm --filter @lvbt/analytics exec playwright install --with-deps chromium + run: + pnpm --filter @lasvegasfortransit/analytics exec playwright install --with-deps chromium - name: Verify public sites run: | diff --git a/README.md b/README.md index dd6885c..a66db41 100644 --- a/README.md +++ b/README.md @@ -10,19 +10,19 @@ Analytics stays off on localhost, preview deployments, retired archives, and fra ## Choose an integration -| Project | Entry point | Setup | -| -------------------- | ----------------------- | ---------------------------------------------------------- | -| Astro | `@lvbt/analytics/astro` | Add `lvbtAnalytics({ site })` to `astro.config.ts` | -| Vite or React | `@lvbt/analytics` | Call `init({ site, token })` before rendering | -| React component tree | `@lvbt/analytics/react` | Render `` and call `useTrack()` | -| Plain HTML | generated client | Run `lvbt-analytics client --out public/lvbt-analytics.js` | +| Project | Entry point | Setup | +| -------------------- | ------------------------------------- | ---------------------------------------------------------- | +| Astro | `@lasvegasfortransit/analytics/astro` | Add `lvbtAnalytics({ site })` to `astro.config.ts` | +| Vite or React | `@lasvegasfortransit/analytics` | Call `init({ site, token })` before rendering | +| React component tree | `@lasvegasfortransit/analytics/react` | Render `` and call `useTrack()` | +| Plain HTML | generated client | Run `lvbt-analytics client --out public/lvbt-analytics.js` | Production builds receive `PUBLIC_LVBT_CWA_TOKEN` and set `LVBT_REQUIRE_ANALYTICS=1`. Preview and local builds receive neither value, so the client is absent rather than merely pointed at a test property. ```ts -import lvbtAnalytics from '@lvbt/analytics/astro'; +import lvbtAnalytics from '@lasvegasfortransit/analytics/astro'; export default defineConfig({ integrations: [lvbtAnalytics({ site: 'labs.lasvegasfortransit.org' })], @@ -30,7 +30,7 @@ export default defineConfig({ ``` ```ts -import { init } from '@lvbt/analytics'; +import { init } from '@lasvegasfortransit/analytics'; init({ site: 'map.lasvegasfortransit.org', diff --git a/apps/collector/package.json b/apps/collector/package.json index cbed159..758af95 100644 --- a/apps/collector/package.json +++ b/apps/collector/package.json @@ -1,5 +1,5 @@ { - "name": "@lvbt/analytics-collector", + "name": "@lasvegasfortransit/analytics-collector", "version": "0.0.0", "private": true, "type": "module", @@ -13,7 +13,7 @@ "validate": "wrangler deploy --dry-run --outdir dist" }, "dependencies": { - "@lvbt/analytics": "workspace:*", + "@lasvegasfortransit/analytics": "workspace:*", "zod": "catalog:" }, "devDependencies": { diff --git a/apps/collector/src/columns.ts b/apps/collector/src/columns.ts index 3acbfca..fad7991 100644 --- a/apps/collector/src/columns.ts +++ b/apps/collector/src/columns.ts @@ -1,4 +1,4 @@ -import { EVENTS, type AnalyticsEvent } from '@lvbt/analytics'; +import { EVENTS, type AnalyticsEvent } from '@lasvegasfortransit/analytics'; export function dataPoint( event: AnalyticsEvent, diff --git a/apps/collector/src/cors.ts b/apps/collector/src/cors.ts index c3c845b..2db40f3 100644 --- a/apps/collector/src/cors.ts +++ b/apps/collector/src/cors.ts @@ -1,4 +1,4 @@ -import { EVENTS } from '@lvbt/analytics'; +import { EVENTS } from '@lasvegasfortransit/analytics'; const sites = new Set(Object.values(EVENTS).flatMap((event) => event.sites)); diff --git a/apps/collector/src/validate.ts b/apps/collector/src/validate.ts index 3e683de..84f76ea 100644 --- a/apps/collector/src/validate.ts +++ b/apps/collector/src/validate.ts @@ -1,4 +1,4 @@ -import { eventPayload, EVENTS, type AnalyticsEvent } from '@lvbt/analytics'; +import { eventPayload, EVENTS, type AnalyticsEvent } from '@lasvegasfortransit/analytics'; import { z } from 'zod'; export type CollectedEvent = AnalyticsEvent & { country?: string }; diff --git a/docs/development/explanation/architecture.md b/docs/development/explanation/architecture.md index 20da1bb..20245b3 100644 --- a/docs/development/explanation/architecture.md +++ b/docs/development/explanation/architecture.md @@ -6,9 +6,10 @@ event schema under LVBT control. ## Data paths -Every production site initializes `@lvbt/analytics` with its hostname and the shared Web Analytics -token. The client checks privacy signals, environment, path exclusions, and framing before it does -anything. An enabled client loads Cloudflare's beacon and exposes one typed `track` function. +Every production site initializes `@lasvegasfortransit/analytics` with its hostname and the shared +Web Analytics token. The client checks privacy signals, environment, path exclusions, and framing +before it does anything. An enabled client loads Cloudflare's beacon and exposes one typed `track` +function. Pageviews, referrers, UTM attribution, and Core Web Vitals go directly to Cloudflare Web Analytics. Allowlisted conversion events go to `events.lasvegasfortransit.org/e` as small `text/plain` JSON diff --git a/docs/development/reference/api.md b/docs/development/reference/api.md index 69d1055..6045605 100644 --- a/docs/development/reference/api.md +++ b/docs/development/reference/api.md @@ -1,7 +1,7 @@ # Client API -`@lvbt/analytics` has no runtime dependencies. The package exposes a browser client, framework -adapters, browser-test helpers, and a Node helper for static headers. +`@lasvegasfortransit/analytics` has no runtime dependencies. The package exposes a browser client, +framework adapters, browser-test helpers, and a Node helper for static headers. ## `init(options)` @@ -65,23 +65,25 @@ by direct `track` calls. ## Framework entry points -`@lvbt/analytics/astro` exports `lvbtAnalytics(options)`. It reads the standard environment, injects -an external page module only when a token exists, and disables JavaScript asset inlining so the site -CSP remains enforceable. +`@lasvegasfortransit/analytics/astro` exports `lvbtAnalytics(options)`. It reads the standard +environment, injects an external page module only when a token exists, and disables JavaScript asset +inlining so the site CSP remains enforceable. -`@lvbt/analytics/react` exports `Analytics`, `useAnalytics`, and `useTrack`. The provider -initializes the shared client after mount. Hooks return the disabled no-op handle during server -rendering and the first client render. +`@lasvegasfortransit/analytics/react` exports `Analytics`, `useAnalytics`, and `useTrack`. The +provider initializes the shared client after mount. Hooks return the disabled no-op handle during +server rendering and the first client render. -`@lvbt/analytics/client` exports `initFromScript`. It reads `data-lvbt-site`, `data-lvbt-token`, -`data-lvbt-collector`, `data-lvbt-spa`, and `data-lvbt-clicks` from a script element. +`@lasvegasfortransit/analytics/client` exports `initFromScript`. It reads `data-lvbt-site`, +`data-lvbt-token`, `data-lvbt-collector`, `data-lvbt-spa`, and `data-lvbt-clicks` from a script +element. ## Test and Node entry points -`@lvbt/analytics/testing` exports `captureBeacon(page)`, `captureEvents(page, collector)`, and -`serveAsProduction(localOrigin, site)`. The capture helpers install Playwright-compatible routes and -return arrays populated as requests occur. `serveAsProduction` returns the production URL and -Chromium host-resolver argument needed to exercise the hostname gate against a loopback server. +`@lasvegasfortransit/analytics/testing` exports `captureBeacon(page)`, +`captureEvents(page, collector)`, and `serveAsProduction(localOrigin, site)`. The capture helpers +install Playwright-compatible routes and return arrays populated as requests occur. +`serveAsProduction` returns the production URL and Chromium host-resolver argument needed to +exercise the hostname gate against a loopback server. -`@lvbt/analytics/node` exports `checkHeadersFile(path, collector)`. It returns every missing CSP -origin from every `Content-Security-Policy` line in a Cloudflare `_headers` file. +`@lasvegasfortransit/analytics/node` exports `checkHeadersFile(path, collector)`. It returns every +missing CSP origin from every `Content-Security-Policy` line in a Cloudflare `_headers` file. diff --git a/docs/development/tutorials/add-analytics-to-a-new-site.md b/docs/development/tutorials/add-analytics-to-a-new-site.md index 5e3fd85..60fe24b 100644 --- a/docs/development/tutorials/add-analytics-to-a-new-site.md +++ b/docs/development/tutorials/add-analytics-to-a-new-site.md @@ -9,7 +9,7 @@ hostname. Install the package at the organization-approved version: ```bash -pnpm add @lvbt/analytics +pnpm add @lasvegasfortransit/analytics ``` Use the production hostname as `site`. Do not invent a product ID or include `https://`. @@ -17,7 +17,7 @@ Use the production hostname as `site`. Do not invent a product ID or include `ht For Astro, add the integration to `astro.config.ts`: ```ts -import lvbtAnalytics from '@lvbt/analytics/astro'; +import lvbtAnalytics from '@lasvegasfortransit/analytics/astro'; export default defineConfig({ integrations: [lvbtAnalytics({ site: 'example.lasvegasfortransit.org' })], @@ -33,7 +33,7 @@ export default defineConfig({ envPrefix: ['VITE_', 'PUBLIC_'] }); ```ts // src/main.tsx -import { init } from '@lvbt/analytics'; +import { init } from '@lasvegasfortransit/analytics'; init({ site: 'example.lasvegasfortransit.org', diff --git a/docs/development/tutorials/start-here.md b/docs/development/tutorials/start-here.md index 8d7068a..f049d6c 100644 --- a/docs/development/tutorials/start-here.md +++ b/docs/development/tutorials/start-here.md @@ -28,9 +28,9 @@ published exports, and performs a Wrangler dry run. Use the focused commands while changing one boundary: ```bash -pnpm --filter @lvbt/analytics test -pnpm --filter @lvbt/analytics-collector test -pnpm --filter @lvbt/analytics-report test +pnpm --filter @lasvegasfortransit/analytics test +pnpm --filter @lasvegasfortransit/analytics-collector test +pnpm --filter @lasvegasfortransit/analytics-report test ``` `pnpm check:fix` applies formatting and safe lint fixes. Run `pnpm check` again after it finishes. diff --git a/docs/superpowers/specs/2026-09-01-analytics-standard-design.md b/docs/superpowers/specs/2026-09-01-analytics-standard-design.md index cc4ebb5..3725bdb 100644 --- a/docs/superpowers/specs/2026-09-01-analytics-standard-design.md +++ b/docs/superpowers/specs/2026-09-01-analytics-standard-design.md @@ -23,8 +23,8 @@ Decisions the user made on 2026-09-01: - Cloudflare Web Analytics is the standard backend. CWA cannot record custom events, so events go to one tiny first-party Worker writing to Workers Analytics Engine. This is the only way to honor both choices; it adds one deployable, kept deliberately minimal. -- Distribute the shared client as a published public npm package `@lvbt/analytics` from a new repo - `LasVegasForTransit/analytics`. +- Distribute the shared client as a published public npm package `@lasvegasfortransit/analytics` + from a new repo `LasVegasForTransit/analytics`. - Wire the standalone `transit-funding` repo now; the Labs copy is canonical going forward and inherits through the Labs shared hook. - Heavy emphasis on developer ease of use, turnkey maintenance, and foolproof documentation for a @@ -40,7 +40,7 @@ only, never free text, URLs, coordinates, or share ids; no IP or user-agent rete ``` ┌──────────────────────────────────────────────────┐ - four sites ──────► │ @lvbt/analytics (npm, ~1 KB, zero runtime deps) │ + four sites ──────► │ @lasvegasfortransit/analytics (npm, ~1 KB, zero runtime deps) │ init({site,token}) │ gate → inject CWA beacon → track() → clicks │ └───────┬──────────────────────────┬───────────────┘ │ pageviews, CWV, UTM │ allowlisted events @@ -69,7 +69,7 @@ Verified facts the design relies on (Cloudflare docs, 2026-09-01): ``` analytics/ -├── packages/analytics/ # published as @lvbt/analytics +├── packages/analytics/ # published as @lasvegasfortransit/analytics │ ├── src/index.ts # init, track, shouldEnable, EVENTS, csp, VERSION, DEFAULT_COLLECTOR │ ├── src/gate.ts # pure shouldEnable() │ ├── src/events.ts # THE allowlist (shared with the collector via workspace import) @@ -97,7 +97,7 @@ commits are already the org grammar; no per-PR changeset files for volunteers to runtime dependencies is enforced by a unit test. Size budget: `client.js` ≤ 1024 B gzip, `index.js` ≤ 1536 B gzip (website JS budget is 12 KB with ~10.9 KB used). -### Public API of `@lvbt/analytics` +### Public API of `@lasvegasfortransit/analytics` ```ts // site is the production hostname. It is both the identifier and the gate rule. @@ -284,31 +284,31 @@ Each numbered step is one PR in one repo. Order matters: the package must publis - `/privacy`: `src/pages/privacy.astro` rendering a vendored copy of `analytics/docs/public/privacy.md` with a "canonical lives in analytics repo" comment; footer link; sitemap. Fix `docs/reference/newsletter-ops.md:64-68` (Beehiiv, not Ghost). -- `tests/analytics.spec.ts` in the `ui-contracts` project using `@lvbt/analytics/testing`: served as - production → one beacon request, join click → one `join_click` POST; served from localhost → zero - requests. +- `tests/analytics.spec.ts` in the `ui-contracts` project using + `@lasvegasfortransit/analytics/testing`: served as production → one beacon request, join click → + one `join_click` POST; served from localhost → zero requests. - Docs: `docs/reference/analytics.md`; shorten the RUM section of `docs/standards/performance-monitoring.md` to point there; glossary and index entries. - Measure `pnpm check:baseline`; bump `perf-budgets.json` `jsGzipKb` to 13 only if needed. ### Step 3. Labs -- `pnpm-workspace.yaml` catalog: `'@lvbt/analytics': `; `turbo.json` +- `pnpm-workspace.yaml` catalog: `'@lasvegasfortransit/analytics': `; `turbo.json` `globalEnv: ["PUBLIC_LVBT_CWA_TOKEN", "LVBT_REQUIRE_ANALYTICS"]` (prevents a cached token-less build being replayed). - `packages/brand`: first JS exports `./analytics` (`LABS_SITE`, `initLabsAnalytics()` which - dynamically imports `@lvbt/analytics` only when the token is set) and `./analytics/astro` - (`labsAnalytics()` wrapping the integration); `dependencies: { '@lvbt/analytics': 'catalog:' }`; - `env.d.ts`; `tests/analytics.test.ts`. This honors - `docs/development/reference/brand-and-ui.md:55`. + dynamically imports `@lasvegasfortransit/analytics` only when the token is set) and + `./analytics/astro` (`labsAnalytics()` wrapping the integration); + `dependencies: { '@lasvegasfortransit/analytics': 'catalog:' }`; `env.d.ts`; + `tests/analytics.test.ts`. This honors `docs/development/reference/brand-and-ui.md:55`. - `apps/home/astro.config.ts`: `integrations: [sitemap(), labsAnalytics()]`. `apps/transit-funding/vite.config.ts`: `envPrefix: ['VITE_', 'PUBLIC_']`; `src/main.tsx`: `void initLabsAnalytics()` before `createRoot`. - Archive builds: both `build:archive` scripts run with `PUBLIC_LVBT_CWA_TOKEN=` (empty); new `tooling/src/check-archive.ts` fails `pnpm check` if any file under `apps/*/dist-archive` contains `cloudflareinsights` or the collector host; `.env.example` per app. -- `eslint.config.ts`: `no-restricted-imports` for `apps/**` forbidding direct `@lvbt/analytics` - (message: use `@lvbt/brand/analytics`). +- `eslint.config.ts`: `no-restricted-imports` for `apps/**` forbidding direct + `@lasvegasfortransit/analytics` (message: use `@lvbt/brand/analytics`). - `public/_headers` per app with the standard CSP block (labs docs already require one; separable into its own PR if it causes churn). The JSON-LD `