From 9adeb721b38fb079e95ae6f232e97ed59251f696 Mon Sep 17 00:00:00 2001 From: Patrick Lu Date: Thu, 8 Jan 2026 18:42:45 -0800 Subject: [PATCH 1/4] meta tag synchronization --- README.md | 18 ++++++++++-------- codepress-swc-plugin/src/lib.rs | 6 +++++- src/index.ts | 7 +++++-- src/types.ts | 6 ++++++ 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 1880641..6770ea6 100644 --- a/README.md +++ b/README.md @@ -75,10 +75,11 @@ The SWC plugin auto-detects your repository and branch from `git` and common CI Optional options for `createSWCPlugin` (all are optional; omit to use auto-detection): -| Option | Type | Default | Purpose | -| ------------- | ------ | ------------------------------- | ---------------------------------------- | -| `repo_name` | string | auto-detected from `git remote` | Force repository id in `owner/repo` form | -| `branch_name` | string | auto-detected from env/`git` | Force branch name | +| Option | Type | Default | Purpose | +| ----------------- | ------ | ------------------------------- | -------------------------------------------------- | +| `repo_name` | string | auto-detected from `git remote` | Force repository id in `owner/repo` form | +| `branch_name` | string | auto-detected from env/`git` | Force branch name | +| `organization_id` | string | none | Your CodePress organization ID (from app settings) | --- @@ -95,10 +96,11 @@ Each JSX element receives a `codepress-data-fp` attribute whose value encodes th Optional options for the Babel plugin (all are optional; omit to use auto-detection): -| Option | Type | Default | Purpose | -| ------------- | ------ | ------------------------------- | ---------------------------------------- | -| `repo_name` | string | auto-detected from `git remote` | Force repository id in `owner/repo` form | -| `branch_name` | string | auto-detected from env/`git` | Force branch name | +| Option | Type | Default | Purpose | +| ----------------- | ------ | ------------------------------- | -------------------------------------------------- | +| `repo_name` | string | auto-detected from `git remote` | Force repository id in `owner/repo` form | +| `branch_name` | string | auto-detected from env/`git` | Force branch name | +| `organization_id` | string | none | Your CodePress organization ID (from app settings) | Entry points exposed by the package: diff --git a/codepress-swc-plugin/src/lib.rs b/codepress-swc-plugin/src/lib.rs index e9ccdfb..7ba9137 100644 --- a/codepress-swc-plugin/src/lib.rs +++ b/codepress-swc-plugin/src/lib.rs @@ -2126,8 +2126,12 @@ impl CodePressTransform { // for content script detection (content scripts run in isolated JS context) let escaped_repo = repo.replace('\\', "\\\\").replace('"', "\\\""); let escaped_branch = branch.replace('\\', "\\\\").replace('"', "\\\""); + // Use setTimeout(0) to defer meta tag injection - this avoids conflicts with React's + // Portal components (like @react-oauth/google) which also manipulate document.head. + // Synchronous DOM manipulation during module init can cause "removeChild" errors when + // React tries to clean up Portals and finds nodes it didn't create. let js = format!( - "try{{if(typeof window!=='undefined'){{window.__CODEPRESS_CONFIG__=Object.assign(window.__CODEPRESS_CONFIG__||{{}},{{repo:\"{}\",branch:\"{}\"}});}}if(typeof document!=='undefined'&&document.head&&!document.querySelector('meta[name=\"codepress-repo\"]')){{var m=document.createElement('meta');m.name='codepress-repo';m.content='{}';document.head.appendChild(m);var b=document.createElement('meta');b.name='codepress-branch';b.content='{}';document.head.appendChild(b);}}}}catch(_){{}}", + "try{{if(typeof window!=='undefined'){{window.__CODEPRESS_CONFIG__=Object.assign(window.__CODEPRESS_CONFIG__||{{}},{{repo:\"{}\",branch:\"{}\"}});}}setTimeout(function(){{try{{if(typeof document!=='undefined'&&document.head&&!document.querySelector('meta[name=\"codepress-repo\"]')){{var m=document.createElement('meta');m.name='codepress-repo';m.content='{}';document.head.appendChild(m);var b=document.createElement('meta');b.name='codepress-branch';b.content='{}';document.head.appendChild(b);}}}}catch(_){{}}}},0);}}catch(_){{}}", escaped_repo, escaped_branch, escaped_repo, escaped_branch ); diff --git a/src/index.ts b/src/index.ts index 61241d1..4980caa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -163,6 +163,7 @@ export default function codePressPlugin( const repoName = options.repo_name || currentRepoName; const branch = options.branch_name || currentBranch; + const organizationId = options.organization_id; // Skip component configuration (like SWC plugin) const skipComponents = new Set( @@ -284,16 +285,18 @@ try { } } - // Inject repo/branch config into window.__CODEPRESS_CONFIG__ (cleaner than DOM attributes) + // Inject repo/branch/org config into window.__CODEPRESS_CONFIG__ (cleaner than DOM attributes) if (!globalAttributesAdded && repoName && state.file.encodedPath) { const escapedRepo = repoName.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); const escapedBranch = (branch || 'main').replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + const escapedOrgId = organizationId ? organizationId.replace(/\\/g, '\\\\').replace(/"/g, '\\"') : ''; + const orgIdLine = escapedOrgId ? `\n organizationId: "${escapedOrgId}",` : ''; const scriptCode = ` try { if (typeof window !== 'undefined') { window.__CODEPRESS_CONFIG__ = Object.assign(window.__CODEPRESS_CONFIG__ || {}, { repo: "${escapedRepo}", - branch: "${escapedBranch}" + branch: "${escapedBranch}"${orgIdLine} }); } } catch (_) {} diff --git a/src/types.ts b/src/types.ts index 4405db6..a55a3c5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,12 @@ export interface CodePressPluginOptions { repo_name?: string; branch_name?: string; + /** + * Your CodePress organization ID (UUID). + * Found in your organization settings at app.codepress.dev. + * Required for multi-org setups. + */ + organization_id?: string; skip_components?: string[]; skip_member_roots?: string[]; /** From 4a44be5896912dda1bea438c22ed3b41d9a1bb8f Mon Sep 17 00:00:00 2001 From: Patrick Lu Date: Thu, 8 Jan 2026 18:45:12 -0800 Subject: [PATCH 2/4] remove org id changes --- README.md | 18 ++++++++---------- src/index.ts | 7 ++----- src/types.ts | 6 ------ 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 6770ea6..1880641 100644 --- a/README.md +++ b/README.md @@ -75,11 +75,10 @@ The SWC plugin auto-detects your repository and branch from `git` and common CI Optional options for `createSWCPlugin` (all are optional; omit to use auto-detection): -| Option | Type | Default | Purpose | -| ----------------- | ------ | ------------------------------- | -------------------------------------------------- | -| `repo_name` | string | auto-detected from `git remote` | Force repository id in `owner/repo` form | -| `branch_name` | string | auto-detected from env/`git` | Force branch name | -| `organization_id` | string | none | Your CodePress organization ID (from app settings) | +| Option | Type | Default | Purpose | +| ------------- | ------ | ------------------------------- | ---------------------------------------- | +| `repo_name` | string | auto-detected from `git remote` | Force repository id in `owner/repo` form | +| `branch_name` | string | auto-detected from env/`git` | Force branch name | --- @@ -96,11 +95,10 @@ Each JSX element receives a `codepress-data-fp` attribute whose value encodes th Optional options for the Babel plugin (all are optional; omit to use auto-detection): -| Option | Type | Default | Purpose | -| ----------------- | ------ | ------------------------------- | -------------------------------------------------- | -| `repo_name` | string | auto-detected from `git remote` | Force repository id in `owner/repo` form | -| `branch_name` | string | auto-detected from env/`git` | Force branch name | -| `organization_id` | string | none | Your CodePress organization ID (from app settings) | +| Option | Type | Default | Purpose | +| ------------- | ------ | ------------------------------- | ---------------------------------------- | +| `repo_name` | string | auto-detected from `git remote` | Force repository id in `owner/repo` form | +| `branch_name` | string | auto-detected from env/`git` | Force branch name | Entry points exposed by the package: diff --git a/src/index.ts b/src/index.ts index 4980caa..61241d1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -163,7 +163,6 @@ export default function codePressPlugin( const repoName = options.repo_name || currentRepoName; const branch = options.branch_name || currentBranch; - const organizationId = options.organization_id; // Skip component configuration (like SWC plugin) const skipComponents = new Set( @@ -285,18 +284,16 @@ try { } } - // Inject repo/branch/org config into window.__CODEPRESS_CONFIG__ (cleaner than DOM attributes) + // Inject repo/branch config into window.__CODEPRESS_CONFIG__ (cleaner than DOM attributes) if (!globalAttributesAdded && repoName && state.file.encodedPath) { const escapedRepo = repoName.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); const escapedBranch = (branch || 'main').replace(/\\/g, '\\\\').replace(/"/g, '\\"'); - const escapedOrgId = organizationId ? organizationId.replace(/\\/g, '\\\\').replace(/"/g, '\\"') : ''; - const orgIdLine = escapedOrgId ? `\n organizationId: "${escapedOrgId}",` : ''; const scriptCode = ` try { if (typeof window !== 'undefined') { window.__CODEPRESS_CONFIG__ = Object.assign(window.__CODEPRESS_CONFIG__ || {}, { repo: "${escapedRepo}", - branch: "${escapedBranch}"${orgIdLine} + branch: "${escapedBranch}" }); } } catch (_) {} diff --git a/src/types.ts b/src/types.ts index a55a3c5..4405db6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,12 +1,6 @@ export interface CodePressPluginOptions { repo_name?: string; branch_name?: string; - /** - * Your CodePress organization ID (UUID). - * Found in your organization settings at app.codepress.dev. - * Required for multi-org setups. - */ - organization_id?: string; skip_components?: string[]; skip_member_roots?: string[]; /** From f41737f019efc739d11c5d22b7a10025fe43664c Mon Sep 17 00:00:00 2001 From: Patrick Lu Date: Thu, 8 Jan 2026 19:21:11 -0800 Subject: [PATCH 3/4] meta sync --- codepress-swc-plugin/src/lib.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/codepress-swc-plugin/src/lib.rs b/codepress-swc-plugin/src/lib.rs index 7ba9137..d79cd1d 100644 --- a/codepress-swc-plugin/src/lib.rs +++ b/codepress-swc-plugin/src/lib.rs @@ -2122,16 +2122,15 @@ impl CodePressTransform { // Build the config object: window.__CODEPRESS_CONFIG__ = { repo: "...", branch: "..." } // Uses Object.assign to avoid overwriting if somehow multiple modules try to set it - // Also injects and tags - // for content script detection (content scripts run in isolated JS context) + // Also injects a hidden div with data attributes for content script detection + // (content scripts run in isolated JS context and can't access window.__CODEPRESS_CONFIG__) let escaped_repo = repo.replace('\\', "\\\\").replace('"', "\\\""); let escaped_branch = branch.replace('\\', "\\\\").replace('"', "\\\""); - // Use setTimeout(0) to defer meta tag injection - this avoids conflicts with React's - // Portal components (like @react-oauth/google) which also manipulate document.head. - // Synchronous DOM manipulation during module init can cause "removeChild" errors when - // React tries to clean up Portals and finds nodes it didn't create. + // Inject into document.body (not head) to avoid conflicts with React Portal-based libraries + // (like @react-oauth/google) which manipulate head and cause "removeChild" errors. + // Using setTimeout(0) to defer DOM manipulation until after React's render cycle. let js = format!( - "try{{if(typeof window!=='undefined'){{window.__CODEPRESS_CONFIG__=Object.assign(window.__CODEPRESS_CONFIG__||{{}},{{repo:\"{}\",branch:\"{}\"}});}}setTimeout(function(){{try{{if(typeof document!=='undefined'&&document.head&&!document.querySelector('meta[name=\"codepress-repo\"]')){{var m=document.createElement('meta');m.name='codepress-repo';m.content='{}';document.head.appendChild(m);var b=document.createElement('meta');b.name='codepress-branch';b.content='{}';document.head.appendChild(b);}}}}catch(_){{}}}},0);}}catch(_){{}}", + "try{{if(typeof window!=='undefined'){{window.__CODEPRESS_CONFIG__=Object.assign(window.__CODEPRESS_CONFIG__||{{}},{{repo:\"{}\",branch:\"{}\"}});}}setTimeout(function(){{try{{if(typeof document!=='undefined'&&document.body&&!document.getElementById('__codepress_config')){{var d=document.createElement('div');d.id='__codepress_config';d.style.display='none';d.setAttribute('data-codepress-repo','{}');d.setAttribute('data-codepress-branch','{}');document.body.appendChild(d);}}}}catch(_){{}}}},0);}}catch(_){{}}", escaped_repo, escaped_branch, escaped_repo, escaped_branch ); From b18cf8b3b9913977ae40d90d3483140efa0aa111 Mon Sep 17 00:00:00 2001 From: Patrick Lu Date: Thu, 8 Jan 2026 21:23:24 -0800 Subject: [PATCH 4/4] no react fragment --- src/index.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/index.ts b/src/index.ts index 61241d1..6006462 100644 --- a/src/index.ts +++ b/src/index.ts @@ -197,6 +197,25 @@ export default function codePressPlugin( return t.isJSXMemberExpression(name) || t.isJSXNamespacedName(name); } + function isReactFragment(name: Babel.types.JSXIdentifier | Babel.types.JSXMemberExpression | Babel.types.JSXNamespacedName): boolean { + // Check for + if (t.isJSXIdentifier(name) && name.name === "Fragment") { + return true; + } + // Check for + if (t.isJSXMemberExpression(name)) { + if ( + t.isJSXIdentifier(name.object) && + name.object.name === "React" && + t.isJSXIdentifier(name.property) && + name.property.name === "Fragment" + ) { + return true; + } + } + return false; + } + return { name: "babel-plugin-codepress-html", visitor: { @@ -322,6 +341,11 @@ try { } const { node } = nodePath; + + // Skip React.Fragment - it can only accept key and children props + if (isReactFragment(node.name)) { + return; + } const startLine = node.loc?.start.line ?? 0; const parentLoc = nodePath.parent.loc; const endLine = parentLoc?.end.line ?? startLine;