diff --git a/.claude/skills/pnpm-postinstall-deps/SKILL.md b/.claude/skills/pnpm-postinstall-deps/SKILL.md new file mode 100644 index 000000000..7227b44d7 --- /dev/null +++ b/.claude/skills/pnpm-postinstall-deps/SKILL.md @@ -0,0 +1,61 @@ +--- +name: pnpm-postinstall-deps +description: Use when user adds package with postinstall script (supabase, sharp), gets "binary not found" errors in CI, or asks about onlyBuiltDependencies. Explains the two-step pattern for managing packages that need build/postinstall scripts in pnpm workspaces with frozen lockfiles. +--- + +# pnpm Postinstall Dependencies + +Manage packages that need postinstall scripts in pnpm workspaces. + +## The Problem + +`pnpm install --frozen-lockfile --prefer-offline` in CI restores from cache and skips postinstall scripts. Packages like `supabase` download binaries via postinstall - without it, the binary doesn't exist. + +## The Pattern + +**Two-step sync required:** + +1. **pnpm-workspace.yaml** - Declares which packages are allowed to run build scripts +2. **Setup action** - Explicitly rebuilds those packages after install + + +Both lists MUST stay in sync. If they diverge: +- Missing in yaml → Package can't run postinstall (security) +- Missing in action → Binary won't be available in CI + + +## Checklist + +When adding a package that needs postinstall: + +### 1. Add to pnpm-workspace.yaml + +```yaml +onlyBuiltDependencies: + - supabase + - sharp + - your-new-package # Add here +``` + +### 2. Update .github/actions/setup/action.yml + +```yaml +- name: Rebuild packages that need postinstall (onlyBuiltDependencies) + shell: bash + run: pnpm rebuild supabase sharp your-new-package # Add here +``` + +### 3. Verify + +Check both files have identical package lists. + +## Why Explicit Package Names? + +`pnpm rebuild --pending` is too implicit - unclear if it respects onlyBuiltDependencies. Explicit names guarantee behavior and make configuration visible. + +## Common Packages + +- `supabase` - Downloads CLI binary from GitHub releases +- `sharp` - Builds native image processing library +- `esbuild` - Downloads platform-specific binary +- `puppeteer` - Downloads Chromium binary diff --git a/apps/demo/cloudflare-worker-plausible-proxy.js b/apps/demo/cloudflare-worker-plausible-proxy.js index bda89bde8..9aeca83c4 100644 --- a/apps/demo/cloudflare-worker-plausible-proxy.js +++ b/apps/demo/cloudflare-worker-plausible-proxy.js @@ -1,15 +1,9 @@ /** * Cloudflare Worker for Plausible Analytics Proxy * - * IMPORTANT: You MUST update the ProxyScript variable below with your - * actual Plausible script URL from your dashboard before deploying! + * This worker proxies Plausible Analytics scripts and events to avoid ad-blocker detection */ -// CONFIGURATION - UPDATE THIS VALUE! -// Get this from your Plausible dashboard (Site Settings > Site Installation) -// It will look like: https://plausible.io/js/script.js (or pa-XXXXX.js for custom domains) -const ProxyScript = 'https://plausible.io/js/script.js'; // ← UPDATE THIS! - // Customize these paths to avoid ad-blocker detection const ScriptName = '/stats/script.js'; const Endpoint = '/stats/event'; @@ -18,34 +12,39 @@ const Endpoint = '/stats/event'; const ScriptWithoutExtension = ScriptName.replace('.js', ''); addEventListener('fetch', (event) => { - event.passThroughOnException(); - event.respondWith(handleRequest(event)); + event.passThroughOnException(); + event.respondWith(handleRequest(event)); }); async function handleRequest(event) { - const pathname = new URL(event.request.url).pathname; - const [baseUri] = pathname.split('.'); + const pathname = new URL(event.request.url).pathname; + const [baseUri, ...extensions] = pathname.split('.'); - if (baseUri.endsWith(ScriptWithoutExtension)) { - return getScript(event); - } else if (pathname.endsWith(Endpoint)) { - return postData(event); - } + if (baseUri.endsWith(ScriptWithoutExtension)) { + return getScript(event, extensions); + } else if (pathname.endsWith(Endpoint)) { + return postData(event); + } - return new Response(null, { status: 404 }); + return new Response(null, { status: 404 }); } -async function getScript(event) { - let response = await caches.default.match(event.request); - if (!response) { - response = await fetch(ProxyScript); - event.waitUntil(caches.default.put(event.request, response.clone())); - } - return response; +async function getScript(event, extensions) { + let response = await caches.default.match(event.request); + if (!response) { + // Fetch the standard Plausible script with any extensions + // Default to 'plausible.js' if no extensions are provided + const scriptPath = extensions.length > 0 + ? 'plausible.' + extensions.join('.') + : 'plausible.js'; + response = await fetch('https://plausible.io/js/' + scriptPath); + event.waitUntil(caches.default.put(event.request, response.clone())); + } + return response; } async function postData(event) { - const request = new Request(event.request); - request.headers.delete('cookie'); - return await fetch('https://plausible.io/api/event', request); -} + const request = new Request(event.request); + request.headers.delete('cookie'); + return await fetch('https://plausible.io/api/event', request); +} \ No newline at end of file diff --git a/apps/demo/src/lib/components/ExplanationPanel.svelte b/apps/demo/src/lib/components/ExplanationPanel.svelte index 6a4984e22..c68747614 100644 --- a/apps/demo/src/lib/components/ExplanationPanel.svelte +++ b/apps/demo/src/lib/components/ExplanationPanel.svelte @@ -50,17 +50,17 @@ explanation: 'Unique identifier for this flow. Must be ≤128 characters, containing only letters, numbers, and underscores.' }, - { - name: 'maxAttempts', - value: '2', - explanation: - 'Maximum total attempts allowed. With 2 attempts: 1 initial try + 1 retry on failure.' - }, { name: 'baseDelay', value: '1', explanation: 'Base delay for retries in seconds. With exponential backoff, first retry waits 1s (subsequent retries would wait 2s, 4s, etc).' + }, + { + name: 'maxAttempts', + value: '2', + explanation: + 'Maximum total attempts allowed. With 2 attempts: 1 initial try + 1 retry on failure.' } ], inputType: `{ diff --git a/apps/demo/src/lib/data/flow-code.ts b/apps/demo/src/lib/data/flow-code.ts index 22b3c12fe..2e0d04137 100644 --- a/apps/demo/src/lib/data/flow-code.ts +++ b/apps/demo/src/lib/data/flow-code.ts @@ -19,7 +19,7 @@ export const FLOW_SECTIONS: Record = { flow_config: { code: `new Flow<{ url: string }>({ slug: 'article_flow', - baseDelay:1, + baseDelay: 1, maxAttempts: 2 })` }, diff --git a/apps/demo/src/routes/+layout.svelte b/apps/demo/src/routes/+layout.svelte index 3fd4182a3..319107a2f 100644 --- a/apps/demo/src/routes/+layout.svelte +++ b/apps/demo/src/routes/+layout.svelte @@ -10,8 +10,8 @@ diff --git a/apps/demo/src/routes/+page.svelte b/apps/demo/src/routes/+page.svelte index fc3973cf2..289350756 100644 --- a/apps/demo/src/routes/+page.svelte +++ b/apps/demo/src/routes/+page.svelte @@ -571,6 +571,8 @@
track('Logo Clicked', { location: 'header' })} > @@ -580,6 +582,8 @@ | track('External Link Clicked', { destination: 'website', location: 'header' })} @@ -588,6 +592,8 @@ | track('External Link Clicked', { destination: 'github', location: 'header' })} diff --git a/apps/demo/tsconfig.json b/apps/demo/tsconfig.json index 3840a59d6..0dc4a6071 100644 --- a/apps/demo/tsconfig.json +++ b/apps/demo/tsconfig.json @@ -9,7 +9,8 @@ "skipLibCheck": true, "sourceMap": true, "strict": true, - "moduleResolution": "bundler" + "moduleResolution": "bundler", + "composite": true }, "references": [ { diff --git a/pkgs/client/vite.config.ts b/pkgs/client/vite.config.ts index 22b7eb088..6bb155d28 100644 --- a/pkgs/client/vite.config.ts +++ b/pkgs/client/vite.config.ts @@ -7,16 +7,17 @@ export default defineConfig({ root: __dirname, cacheDir: '../../node_modules/.vite/pkgs/client', plugins: [ - dts({ + dts({ include: ['src/**/*.ts'], outDir: 'dist', rollupTypes: false, // Don't bundle for now insertTypesEntry: true, - tsConfigFilePath: resolve(__dirname, 'tsconfig.lib.json'), - skipDiagnostics: true // Skip TypeScript diagnostics to avoid vite-plugin-dts errors with monorepo project references. - // The plugin tries to compile all imported files (including from other packages) + tsconfigPath: resolve(__dirname, 'tsconfig.lib.json'), + skipDiagnostics: true // Skip TypeScript diagnostics to avoid vite-plugin-dts errors with monorepo project references. + // The plugin tries to compile all imported files (including from other packages) // which breaks rootDir boundaries. Nx runs proper type checking separately. - }) + }) as any // Type cast to avoid Vite version mismatch between packages + ], build: { lib: { diff --git a/pkgs/dsl/__tests__/types/array-method.test-d.ts b/pkgs/dsl/__tests__/types/array-method.test-d.ts index 0d7fa7d74..8086cdaf3 100644 --- a/pkgs/dsl/__tests__/types/array-method.test-d.ts +++ b/pkgs/dsl/__tests__/types/array-method.test-d.ts @@ -178,7 +178,7 @@ describe('.array() method type constraints', () => { // ExtractFlowContext should include FlowContext & custom resources type FlowCtx = ExtractFlowContext; - // eslint-disable-next-line @typescript-eslint/no-explicit-any + expectTypeOf().toMatchTypeOf<{ env: Record; shutdownSignal: AbortSignal; diff --git a/pkgs/website/src/env.d.ts b/pkgs/website/src/env.d.ts index 5d34fb160..5a39abfe9 100644 --- a/pkgs/website/src/env.d.ts +++ b/pkgs/website/src/env.d.ts @@ -1,6 +1,6 @@ // eslint-disable-next-line @typescript-eslint/triple-slash-reference /// -// eslint-disable-next-line @typescript-eslint/triple-slash-reference + /// interface ImportMetaEnv { diff --git a/tsconfig.json b/tsconfig.json index 1015cbfe8..3729a7d1e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,6 +20,9 @@ }, { "path": "./pkgs/client" + }, + { + "path": "./apps/demo" } ] }