Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions .claude/skills/pnpm-postinstall-deps/SKILL.md
Original file line number Diff line number Diff line change
@@ -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

<critical>
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
</critical>

## 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
55 changes: 27 additions & 28 deletions apps/demo/cloudflare-worker-plausible-proxy.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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);
}
12 changes: 6 additions & 6 deletions apps/demo/src/lib/components/ExplanationPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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: `{
Expand Down
2 changes: 1 addition & 1 deletion apps/demo/src/lib/data/flow-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const FLOW_SECTIONS: Record<string, CodeSection> = {
flow_config: {
code: `new Flow<{ url: string }>({
slug: 'article_flow',
baseDelay:1,
baseDelay: 1,
maxAttempts: 2
})`
},
Expand Down
4 changes: 2 additions & 2 deletions apps/demo/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
<script
defer
data-domain="demo.pgflow.dev"
data-api="https://pgflow-demo-edge.jumski.workers.dev/stats/api/event"
src="https://plausible.io/js/script.js"
data-api="https://pgflow-demo-edge.jumski.workers.dev/stats/event"
src="https://pgflow-demo-edge.jumski.workers.dev/stats/script.js"
></script>
</svelte:head>

Expand Down
6 changes: 6 additions & 0 deletions apps/demo/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,8 @@
<div class="flex items-center gap-2 md:gap-3">
<a
href="https://pgflow.dev"
target="_blank"
rel="noopener noreferrer"
class="flex items-center gap-1.5 md:gap-2"
onclick={() => track('Logo Clicked', { location: 'header' })}
>
Expand All @@ -580,6 +582,8 @@
<span class="text-muted-foreground text-xs">|</span>
<a
href="https://pgflow.dev"
target="_blank"
rel="noopener noreferrer"
class="text-xs text-muted-foreground hover:text-foreground"
onclick={() =>
track('External Link Clicked', { destination: 'website', location: 'header' })}
Expand All @@ -588,6 +592,8 @@
<span class="text-muted-foreground text-xs">|</span>
<a
href="https://github.com/pgflow-dev/pgflow"
target="_blank"
rel="noopener noreferrer"
class="text-xs text-muted-foreground hover:text-foreground"
onclick={() =>
track('External Link Clicked', { destination: 'github', location: 'header' })}
Expand Down
3 changes: 2 additions & 1 deletion apps/demo/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
"moduleResolution": "bundler",
"composite": true
Comment on lines +12 to +13
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding 'composite: true' requires additional configuration. Either remove this property or add required settings like 'rootDir' and 'outDir'. Composite projects have stricter requirements that may be causing TypeScript errors.

Spotted by Graphite Agent (based on CI logs)

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

},
"references": [
{
Expand Down
11 changes: 6 additions & 5 deletions pkgs/client/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +15 to +19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The property was renamed from 'tsConfigFilePath' to 'tsconfigPath' and a type cast was added. This might be causing compatibility issues. Verify the correct property name for the version of vite-plugin-dts being used and remove the type cast if possible.

Spotted by Graphite Agent (based on CI logs)

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.


],
build: {
lib: {
Expand Down
2 changes: 1 addition & 1 deletion pkgs/dsl/__tests__/types/array-method.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ describe('.array() method type constraints', () => {

// ExtractFlowContext should include FlowContext & custom resources
type FlowCtx = ExtractFlowContext<typeof flow>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An eslint-disable comment was removed. If this was intentional, ensure the code doesn't trigger the eslint rule that was being disabled. Otherwise, restore the comment.

Spotted by Graphite Agent (based on CI logs)

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

expectTypeOf<FlowCtx>().toMatchTypeOf<{
env: Record<string, string | undefined>;
shutdownSignal: AbortSignal;
Expand Down
2 changes: 1 addition & 1 deletion pkgs/website/src/env.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference path="../.astro/types.d.ts" />
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference types="astro/client" />

interface ImportMetaEnv {
Expand Down
3 changes: 3 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
},
{
"path": "./pkgs/client"
},
{
"path": "./apps/demo"
}
]
}
Loading