diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index ddd1861e66db4..7114c33cea0fd 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -966,7 +966,7 @@ jobs:
uses: actions/dependency-review-action@v4.3.2
with:
allow-licenses: Apache-2.0, 0BSD, BSD-2-Clause, BSD-3-Clause, CC0-1.0, ISC, MIT, MIT-0, MPL-2.0
- allow-dependencies-licenses: "pkg:golang/github.com/coder/wgtunnel@0.1.13-0.20240522110300-ade90dfb2da0, pkg:npm/pako@1.0.11, pkg:npm/caniuse-lite@1.0.30001639"
+ allow-dependencies-licenses: "pkg:golang/github.com/coder/wgtunnel@0.1.13-0.20240522110300-ade90dfb2da0, pkg:npm/pako@1.0.11, pkg:npm/caniuse-lite@1.0.30001639, pkg:githubactions/alwaysmeticulous/report-diffs-action/cloud-compute"
license-check: true
vulnerability-check: false
- name: "Report"
@@ -992,3 +992,23 @@ jobs:
fi
done
echo "No incompatible licenses detected"
+ meticulous:
+ runs-on: ubuntu-latest
+ steps:
+ - name: "Checkout Repository"
+ uses: actions/checkout@v4
+ - name: Setup Node
+ uses: ./.github/actions/setup-node
+ - name: Build
+ working-directory: ./site
+ run: pnpm build
+ - name: Serve
+ working-directory: ./site
+ run: |
+ pnpm vite preview &
+ sleep 5
+ - name: Run Meticulous tests
+ uses: alwaysmeticulous/report-diffs-action/cloud-compute@v1
+ with:
+ api-token: ${{ secrets.METICULOUS_API_TOKEN }}
+ app-url: "http://127.0.0.1:4173/"
diff --git a/coderd/httpmw/csp.go b/coderd/httpmw/csp.go
index 0862a0cd7cb2a..99d22acf6df6c 100644
--- a/coderd/httpmw/csp.go
+++ b/coderd/httpmw/csp.go
@@ -59,7 +59,7 @@ func CSPHeaders(telemetry bool, websocketHosts func() []string) func(next http.H
cspDirectiveConnectSrc: {"'self'"},
cspDirectiveChildSrc: {"'self'"},
// https://github.com/suren-atoyan/monaco-react/issues/168
- cspDirectiveScriptSrc: {"'self'"},
+ cspDirectiveScriptSrc: {"'self' "},
cspDirectiveStyleSrc: {"'self' 'unsafe-inline'"},
// data: is used by monaco editor on FE for Syntax Highlight
cspDirectiveFontSrc: {"'self' data:"},
@@ -88,6 +88,11 @@ func CSPHeaders(telemetry bool, websocketHosts func() []string) func(next http.H
if telemetry {
// If telemetry is enabled, we report to coder.com.
cspSrcs.Append(cspDirectiveConnectSrc, "https://coder.com")
+ // These are necessary to allow meticulous to collect sampling to
+ // improve our testing. Only remove these if we're no longer using
+ // their services.
+ cspSrcs.Append(cspDirectiveConnectSrc, meticulousConnectSrc...)
+ cspSrcs.Append(cspDirectiveScriptSrc, meticulousScriptSrc...)
}
// This extra connect-src addition is required to support old webkit
@@ -131,3 +136,8 @@ func CSPHeaders(telemetry bool, websocketHosts func() []string) func(next http.H
})
}
}
+
+var (
+ meticulousConnectSrc = []string{"https://cognito-identity.us-west-2.amazonaws.com", "https://user-events-v3.s3-accelerate.amazonaws.com", "*.sentry.io"}
+ meticulousScriptSrc = []string{"https://snippet.meticulous.ai", "https://browser.sentry-cdn.com"}
+)
diff --git a/site/package.json b/site/package.json
index b0f3b2254e712..4704ebb0a7adb 100644
--- a/site/package.json
+++ b/site/package.json
@@ -30,6 +30,7 @@
"deadcode": "ts-prune | grep -v \".stories\\|.config\\|e2e\\|__mocks__\\|used in module\\|testHelpers\\|typesGenerated\" || echo \"No deadcode found.\""
},
"dependencies": {
+ "@alwaysmeticulous/recorder-loader": "2.137.0",
"@emoji-mart/data": "1.2.1",
"@emoji-mart/react": "1.1.1",
"@emotion/css": "11.11.2",
diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml
index fbdee02e28702..2b7e0cf81d9a7 100644
--- a/site/pnpm-lock.yaml
+++ b/site/pnpm-lock.yaml
@@ -9,6 +9,9 @@ overrides:
semver: 7.6.2
dependencies:
+ '@alwaysmeticulous/recorder-loader':
+ specifier: 2.137.0
+ version: 2.137.0
'@emoji-mart/data':
specifier: 1.2.1
version: 1.2.1
@@ -456,6 +459,10 @@ packages:
resolution: {integrity: sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==}
dev: true
+ /@alwaysmeticulous/recorder-loader@2.137.0:
+ resolution: {integrity: sha512-ux/xGYCNsOe8BzquEg7k7YSNJiw/0Sg2Pd/7fppYiVr5xEefpPeIhh3qwuupZgx6sB2t5KpKQdodNWVmGeyh/w==}
+ dev: false
+
/@ampproject/remapping@2.3.0:
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
@@ -1936,7 +1943,7 @@ packages:
resolution: {integrity: sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==}
engines: {node: '>=6.9.0'}
dependencies:
- regenerator-runtime: 0.14.1
+ regenerator-runtime: 0.14.0
dev: true
/@babel/runtime@7.24.7:
@@ -5289,7 +5296,7 @@ packages:
engines: {node: '>=14'}
dependencies:
'@babel/code-frame': 7.24.7
- '@babel/runtime': 7.24.7
+ '@babel/runtime': 7.23.2
'@types/aria-query': 5.0.3
aria-query: 5.1.3
chalk: 4.1.2
@@ -11972,7 +11979,7 @@ packages:
peerDependencies:
react: '>=16.13.1'
dependencies:
- '@babel/runtime': 7.24.7
+ '@babel/runtime': 7.22.6
react: 18.3.1
dev: true
@@ -12288,6 +12295,10 @@ packages:
/regenerator-runtime@0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
+ /regenerator-runtime@0.14.0:
+ resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==}
+ dev: true
+
/regenerator-runtime@0.14.1:
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
diff --git a/site/src/index.tsx b/site/src/index.tsx
index 8604ff268655d..489d747a3c0a6 100644
--- a/site/src/index.tsx
+++ b/site/src/index.tsx
@@ -1,3 +1,4 @@
+import { tryLoadAndStartRecorder } from "@alwaysmeticulous/recorder-loader";
import { createRoot } from "react-dom/client";
import { App } from "./App";
@@ -12,5 +13,30 @@ const element = document.getElementById("root");
if (element === null) {
throw new Error("root element is null");
}
+
const root = createRoot(element);
-root.render();
+async function startApp() {
+ // Record all sessions on localhost, staging stacks and preview URLs
+ if (isInternal()) {
+ // Start the Meticulous recorder before you initialise your app.
+ // Note: all errors are caught and logged, so no need to surround with try/catch
+ await tryLoadAndStartRecorder({
+ projectId: "Y4uHy1qs0B660xxUdrkLPkazUMPr6OuTqYEnShaR",
+ isProduction: false,
+ });
+ }
+
+ root.render();
+}
+
+function isInternal() {
+ return (
+ window.location.hostname.indexOf("dev.coder.com") > -1 ||
+ window.location.hostname.indexOf("localhost") > -1 ||
+ window.location.hostname.indexOf("127.0.0.1") > -1
+ );
+}
+
+startApp().catch((error) => {
+ console.error(error);
+});