diff --git a/.gitignore b/.gitignore
index c31fcb3ea6..6fcfe4198b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@ node_modules
out
.tmp
.env*.local
+.env
.vercel
_temp-migrations/
src/data/_redirects.generated.json
diff --git a/.npmrc b/.npmrc
index b6f27f1359..a32230ed05 100644
--- a/.npmrc
+++ b/.npmrc
@@ -1 +1,2 @@
engine-strict=true
+@hashicorp:registry=https://registry.npmjs.org/
diff --git a/config/base.json b/config/base.json
index 7c0e5392de..5037fea747 100644
--- a/config/base.json
+++ b/config/base.json
@@ -28,7 +28,10 @@
"clientToken": "pubd5cf774aa1cbbd001accd50cb463925b",
"service": "non-prod.developer.hashicorp.com"
},
- "product_slugs_with_integrations": ["vault", "nomad", "packer"]
+ "product_slugs_with_integrations": ["vault", "nomad", "packer"],
+ "sandbox": {
+ "instruqt_base_url": "https://play.instruqt.com/embed"
+ }
},
"learn": {
"max_static_paths": 10
diff --git a/config/development.json b/config/development.json
index a424fa0f83..be9acacde7 100644
--- a/config/development.json
+++ b/config/development.json
@@ -4,7 +4,10 @@
"max_static_paths": 1
},
"dev_dot": {
- "max_static_paths": 1
+ "max_static_paths": 1,
+ "sandbox": {
+ "instruqt_base_url": "https://play.instruqt.com/embed"
+ }
},
"learn": {
"max_static_paths": 1
diff --git a/package-lock.json b/package-lock.json
index 39177c5389..ebf3b02a1a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -149,8 +149,7 @@
"@types/google-spreadsheet": "^3.2.2",
"@types/js-cookie": "^3.0.6",
"@types/jsonwebtoken": "^8.5.9",
- "@types/node": "^18.18.9",
- "@types/react": "^18.2.6",
+ "@types/node": "^20.19.0",
"@types/react-dom": "^18.2.4",
"@types/semver": "^7.3.9",
"@types/shortid": "^0.0.29",
@@ -168,7 +167,7 @@
"next-remote-watch": "^2.0.0",
"nextjs-bundle-analysis": "^0.5.0-canary.1",
"nock": "^14.0.0-beta.5",
- "postcss": "^8.4.5",
+ "postcss": "^8.5.6",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-normalize": "^10.0.1",
"postcss-preset-env": "^6.7.0",
@@ -176,6 +175,7 @@
"rimraf": "^5.0.5",
"sass": "^1.75.0",
"simple-git-hooks": "^2.7.0",
+ "source-map": "^0.6.1",
"stylelint": "^13.8.0",
"typescript": "^5.4.5",
"typescript-eslint": "^8.25.0",
@@ -2718,16 +2718,6 @@
"util-deprecate": "~1.0.1"
}
},
- "node_modules/@hashicorp/next-optimized-images/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "peer": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/@hashicorp/next-optimized-images/node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -4246,16 +4236,6 @@
"node": ">=8.0.0"
}
},
- "node_modules/@hashicorp/platform-cli/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "peer": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/@hashicorp/platform-cli/node_modules/ts-jest": {
"version": "26.5.6",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.6.tgz",
@@ -9851,6 +9831,15 @@
"semver": "bin/semver"
}
},
+ "node_modules/@mdx-js/mdx/node_modules/source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/@mdx-js/mdx/node_modules/unist-util-is": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz",
@@ -15974,14 +15963,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/@types/cssnano/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/@types/debug": {
"version": "4.1.7",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
@@ -16125,11 +16106,12 @@
"integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
},
"node_modules/@types/node": {
- "version": "18.18.9",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.9.tgz",
- "integrity": "sha512-0f5klcuImLnG4Qreu9hPj/rEfFq6YRc5n2mAjSsH+ec/mJL+3voBH0+8T7o8RpFjH7ovc+TRsL/c7OYIQsPTfQ==",
+ "version": "20.19.13",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.13.tgz",
+ "integrity": "sha512-yCAeZl7a0DxgNVteXFHt9+uyFbqXGy/ShC4BlcHkoE0AfGXYv/BUiplV72DjMYXHDBXFjhvr6DD1NiRVfB4j8g==",
+ "license": "MIT",
"dependencies": {
- "undici-types": "~5.26.4"
+ "undici-types": "~6.21.0"
}
},
"node_modules/@types/node-fetch": {
@@ -18102,15 +18084,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/autoprefixer/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/autoprefixer/node_modules/supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
@@ -21443,15 +21416,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/css-blank-pseudo/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/css-color-names": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz",
@@ -21536,15 +21500,6 @@
"node": ">=4"
}
},
- "node_modules/css-has-pseudo/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/css-prefers-color-scheme": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz",
@@ -21583,15 +21538,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/css-prefers-color-scheme/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/css-select": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz",
@@ -21620,14 +21566,6 @@
"node": ">=8.0.0"
}
},
- "node_modules/css-tree/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/css-what": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz",
@@ -21787,14 +21725,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/cssnano-util-raw-cache/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/cssnano-util-same-parent": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz",
@@ -21842,14 +21772,6 @@
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
"integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow=="
},
- "node_modules/csso/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/cssom": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
@@ -23257,18 +23179,6 @@
"source-map": "~0.6.1"
}
},
- "node_modules/escodegen/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "optional": true,
- "peer": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/eslint": {
"version": "8.57.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
@@ -28855,16 +28765,6 @@
"node": ">=10"
}
},
- "node_modules/istanbul-lib-source-maps/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "peer": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/istanbul-reports": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
@@ -30203,16 +30103,6 @@
"node": ">=8"
}
},
- "node_modules/jest-jasmine2/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "peer": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/jest-jasmine2/node_modules/type-fest": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
@@ -35760,15 +35650,16 @@
}
},
"node_modules/nanoid": {
- "version": "3.3.7",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
- "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@@ -36065,6 +35956,34 @@
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.1.tgz",
"integrity": "sha512-qsHJle3GU3CmVx7pUoXcghX4sRN+vINkbLdH611T8ZlsP//grzqVW87BSUgOZeSAD4q7ZdZicdwNe/20U2janA=="
},
+ "node_modules/next/node_modules/postcss": {
+ "version": "8.4.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.6",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
"node_modules/nextjs-bundle-analysis": {
"version": "0.5.0-canary.1",
"resolved": "https://registry.npmjs.org/nextjs-bundle-analysis/-/nextjs-bundle-analysis-0.5.0-canary.1.tgz",
@@ -38404,9 +38323,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.31",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
- "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"funding": [
{
"type": "opencollective",
@@ -38421,10 +38340,11 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
- "nanoid": "^3.3.6",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.0.2"
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -38463,15 +38383,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-attribute-case-insensitive/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-browser-comments": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-4.0.0.tgz",
@@ -38516,14 +38427,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-calc/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-clamp": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz",
@@ -38690,15 +38593,6 @@
"node": ">=6.14.4"
}
},
- "node_modules/postcss-color-functional-notation/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-color-gray": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz",
@@ -38750,15 +38644,6 @@
"node": ">=6.14.4"
}
},
- "node_modules/postcss-color-gray/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-color-hex-alpha": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz",
@@ -38809,15 +38694,6 @@
"node": ">=6.14.4"
}
},
- "node_modules/postcss-color-hex-alpha/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-color-mod-function": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz",
@@ -38869,15 +38745,6 @@
"node": ">=6.14.4"
}
},
- "node_modules/postcss-color-mod-function/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-color-rebeccapurple": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz",
@@ -38928,15 +38795,6 @@
"node": ">=6.14.4"
}
},
- "node_modules/postcss-color-rebeccapurple/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-colormin": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.2.tgz",
@@ -39004,15 +38862,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-custom-media/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-custom-properties": {
"version": "8.0.11",
"resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz",
@@ -39063,15 +38912,6 @@
"node": ">=6.14.4"
}
},
- "node_modules/postcss-custom-properties/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-custom-selectors": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz",
@@ -39134,15 +38974,6 @@
"node": ">=4"
}
},
- "node_modules/postcss-custom-selectors/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-dir-pseudo-class": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz",
@@ -39205,15 +39036,6 @@
"node": ">=4"
}
},
- "node_modules/postcss-dir-pseudo-class/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-discard-comments": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.3.tgz",
@@ -39311,15 +39133,6 @@
"node": ">=6.14.4"
}
},
- "node_modules/postcss-double-position-gradients/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-env-function": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-2.0.2.tgz",
@@ -39370,15 +39183,6 @@
"node": ">=6.14.4"
}
},
- "node_modules/postcss-env-function/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-flexbugs-fixes": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz",
@@ -39423,15 +39227,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-focus-visible/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-focus-within": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz",
@@ -39467,15 +39262,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-focus-within/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-font-variant": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-4.0.1.tgz",
@@ -39508,15 +39294,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-font-variant/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-gap-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz",
@@ -39552,15 +39329,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-gap-properties/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-html": {
"version": "0.36.0",
"resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.36.0.tgz",
@@ -39624,15 +39392,6 @@
"node": ">=6.14.4"
}
},
- "node_modules/postcss-image-set-function/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-import": {
"version": "16.1.0",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-16.1.0.tgz",
@@ -39681,15 +39440,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-initial/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-lab-function": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz",
@@ -39741,15 +39491,6 @@
"node": ">=6.14.4"
}
},
- "node_modules/postcss-lab-function/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-less": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-3.1.4.tgz",
@@ -39785,15 +39526,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-less/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-load-config": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-5.1.0.tgz",
@@ -39878,15 +39610,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-logical/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-media-minmax": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz",
@@ -39922,15 +39645,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-media-minmax/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-media-query-parser": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz",
@@ -40120,15 +39834,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-nesting/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-normalize": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/postcss-normalize/-/postcss-normalize-10.0.1.tgz",
@@ -40343,15 +40048,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-overflow-shorthand/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-page-break": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-2.0.0.tgz",
@@ -40384,15 +40080,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-page-break/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-place": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-4.0.1.tgz",
@@ -40443,15 +40130,6 @@
"node": ">=6.14.4"
}
},
- "node_modules/postcss-place/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-preset-env": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz",
@@ -40523,15 +40201,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-preset-env/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-pseudo-class-any-link": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz",
@@ -40594,15 +40263,6 @@
"node": ">=4"
}
},
- "node_modules/postcss-pseudo-class-any-link/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-reduce-initial": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.2.tgz",
@@ -40664,15 +40324,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-replace-overflow-wrap/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-reporter": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.1.0.tgz",
@@ -40739,15 +40390,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-safe-parser/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-sass": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.4.4.tgz",
@@ -40781,15 +40423,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-sass/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-scss": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-2.1.1.tgz",
@@ -40825,15 +40458,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-scss/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-selector-matches": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz",
@@ -40867,15 +40491,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-selector-matches/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-selector-not": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.1.tgz",
@@ -40909,15 +40524,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-selector-not/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-selector-parser": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
@@ -40966,15 +40572,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-sorting/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/postcss-svgo": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.0.1.tgz",
@@ -41226,15 +40823,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/postcss-values-parser/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/posthog-js": {
"version": "1.249.1",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.249.1.tgz",
@@ -43579,6 +43167,15 @@
"semver": "bin/semver"
}
},
+ "node_modules/remark-mdx/node_modules/source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/remark-parse": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz",
@@ -46498,6 +46095,16 @@
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
},
+ "node_modules/snapdragon/node_modules/source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/sort-keys": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
@@ -46530,17 +46137,19 @@
"peer": true
},
"node_modules/source-map": {
- "version": "0.5.7",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
- "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-js": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
- "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
@@ -46568,14 +46177,6 @@
"source-map": "^0.6.0"
}
},
- "node_modules/source-map-support/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/source-map-url": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz",
@@ -47859,15 +47460,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/stylelint-order/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/stylelint-use-nesting": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/stylelint-use-nesting/-/stylelint-use-nesting-3.0.0.tgz",
@@ -48165,15 +47757,6 @@
"node": ">=8"
}
},
- "node_modules/stylelint/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/stylelint/node_modules/strip-indent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
@@ -48239,15 +47822,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/sugarss/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -49997,9 +49571,10 @@
}
},
"node_modules/undici-types": {
- "version": "5.26.5",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
- "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "license": "MIT"
},
"node_modules/unfetch": {
"version": "4.2.0",
@@ -51155,34 +50730,6 @@
"@esbuild/win32-x64": "0.19.12"
}
},
- "node_modules/vite/node_modules/postcss": {
- "version": "8.4.35",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
- "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "dependencies": {
- "nanoid": "^3.3.7",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.0.2"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
"node_modules/vitest": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-1.4.0.tgz",
diff --git a/package.json b/package.json
index 87faddd658..9d28d7c00d 100644
--- a/package.json
+++ b/package.json
@@ -61,8 +61,7 @@
"@types/google-spreadsheet": "^3.2.2",
"@types/js-cookie": "^3.0.6",
"@types/jsonwebtoken": "^8.5.9",
- "@types/node": "^18.18.9",
- "@types/react": "^18.2.6",
+ "@types/node": "^20.19.0",
"@types/react-dom": "^18.2.4",
"@types/semver": "^7.3.9",
"@types/shortid": "^0.0.29",
@@ -80,7 +79,7 @@
"next-remote-watch": "^2.0.0",
"nextjs-bundle-analysis": "^0.5.0-canary.1",
"nock": "^14.0.0-beta.5",
- "postcss": "^8.4.5",
+ "postcss": "^8.5.6",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-normalize": "^10.0.1",
"postcss-preset-env": "^6.7.0",
@@ -88,6 +87,7 @@
"rimraf": "^5.0.5",
"sass": "^1.75.0",
"simple-git-hooks": "^2.7.0",
+ "source-map": "^0.6.1",
"stylelint": "^13.8.0",
"typescript": "^5.4.5",
"typescript-eslint": "^8.25.0",
diff --git a/public/img/sandbox/hashicorp.png b/public/img/sandbox/hashicorp.png
new file mode 100644
index 0000000000..7c7104bb6a
Binary files /dev/null and b/public/img/sandbox/hashicorp.png differ
diff --git a/src/components/dev-dot-content/mdx-components/mdx-alert/index.test.tsx b/src/components/dev-dot-content/mdx-components/mdx-alert/index.test.tsx
index cb19b9b715..5a2eb704c3 100644
--- a/src/components/dev-dot-content/mdx-components/mdx-alert/index.test.tsx
+++ b/src/components/dev-dot-content/mdx-components/mdx-alert/index.test.tsx
@@ -38,11 +38,8 @@ describe('`MdxInlineAlert` Component', () => {
const { queryByText, queryByTestId } = render(
{data.body}
)
- // icon renders
expect(queryByTestId('icon')).toBeInTheDocument()
- // default title renders
expect(queryByText(data.title)).toBeInTheDocument()
- // body text renders
expect(queryByText(data.body)).toBeInTheDocument()
})
@@ -51,11 +48,8 @@ describe('`MdxInlineAlert` Component', () => {
const { queryByText, queryByTestId } = render(
{data.body}
)
- // icon renders
expect(queryByTestId('icon')).toBeInTheDocument()
- // default title renders
expect(queryByText(data.title)).toBeInTheDocument()
- // body text renders
expect(queryByText(data.body)).toBeInTheDocument()
})
@@ -64,11 +58,8 @@ describe('`MdxInlineAlert` Component', () => {
const { queryByText, queryByTestId } = render(
{data.body}
)
- // icon renders
expect(queryByTestId('icon')).toBeInTheDocument()
- // default title renders
expect(queryByText(data.title)).toBeInTheDocument()
- // body text renders
expect(queryByText(data.body)).toBeInTheDocument()
})
@@ -77,11 +68,8 @@ describe('`MdxInlineAlert` Component', () => {
const { queryByText, queryByTestId } = render(
{data.body}
)
- // icon renders
expect(queryByTestId('icon')).toBeInTheDocument()
- // default title renders
expect(queryByText(data.title)).toBeInTheDocument()
- // body text renders
expect(queryByText(data.body)).toBeInTheDocument()
})
@@ -94,32 +82,42 @@ describe('`MdxInlineAlert` Component', () => {
expect(queryByText(TEST_DATA.customTitle)).toBeInTheDocument()
})
- it('throws when children are not passed', () => {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- expect(() => render()).toThrowError(
- TEST_DATA.errors.noChildren
- )
+ it('renders error fallback when children are empty', () => {
+ const { getByText } = render({''})
+
+ // Should render the error fallback UI instead of throwing
+ expect(getByText('Alert Error')).toBeInTheDocument()
+ expect(
+ getByText(
+ 'There was an error rendering this alert. Please check the alert configuration.'
+ )
+ ).toBeInTheDocument()
})
- it('throws when type is invalid', () => {
- expect(() =>
- render(
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- I am an MdxInlineAlert
+ it('renders error fallback when type is invalid', () => {
+ const invalidType = 'doughnut' as 'tip' | 'highlight' | 'note' | 'warning'
+ const { getByText } = render(
+ I am an MdxInlineAlert
+ )
+
+ // Should render the error fallback UI instead of throwing
+ expect(getByText('Alert Error')).toBeInTheDocument()
+ expect(
+ getByText(
+ 'There was an error rendering this alert. Please check the alert configuration.'
)
- ).toThrowError(TEST_DATA.errors.invalidType)
+ ).toBeInTheDocument()
})
it('renders multiple children', () => {
- expect(() =>
- render(
-
- Liquorice cake marzipan danish brownie
- Lollipop gingerbread bear claw muffin croissant
-
- )
- ).not.toThrowError()
+ const { getByText } = render(
+
+ This may render multiple children.
+ This should get multiple children.
+
+ )
+
+ expect(getByText('This may render multiple children.')).toBeInTheDocument()
+ expect(getByText('This should get multiple children.')).toBeInTheDocument()
})
})
diff --git a/src/components/dev-dot-content/mdx-components/mdx-alert/index.tsx b/src/components/dev-dot-content/mdx-components/mdx-alert/index.tsx
index 7e4fc9a4ff..a3c79a37cf 100644
--- a/src/components/dev-dot-content/mdx-components/mdx-alert/index.tsx
+++ b/src/components/dev-dot-content/mdx-components/mdx-alert/index.tsx
@@ -4,6 +4,7 @@
*/
import InlineAlert from 'components/inline-alert'
+import { withErrorBoundary } from 'components/error-boundary'
import { IconInfo24 } from '@hashicorp/flight-icons/svg-react/info-24'
import { IconAlertTriangle24 } from '@hashicorp/flight-icons/svg-react/alert-triangle-24'
import { IconAlertDiamond24 } from '@hashicorp/flight-icons/svg-react/alert-diamond-24'
@@ -22,7 +23,7 @@ const ALERT_DATA: MdxInlineAlertData = {
},
}
-export function MdxInlineAlert({
+function MdxInlineAlertBase({
children,
title,
type = 'tip',
@@ -55,4 +56,38 @@ export function MdxInlineAlert({
)
}
+// Create a fallback UI for when the alert component fails
+const AlertErrorFallback = (
+
+ }
+ title="Alert Error"
+ description="There was an error rendering this alert. Please check the alert configuration."
+ color="critical"
+ className={s.typographyOverride}
+ />
+
+)
+
+export const MdxInlineAlert = withErrorBoundary(
+ MdxInlineAlertBase,
+ AlertErrorFallback,
+ (error, errorInfo) => {
+ if (typeof window !== 'undefined' && window.posthog?.capture) {
+ window.posthog.capture('mdx_component_error', {
+ component_name: 'MdxInlineAlert',
+ error_message: error.message,
+ error_stack: error.stack,
+ component_stack: errorInfo?.componentStack,
+ timestamp: new Date().toISOString(),
+ page_url: window.location.href,
+ })
+ }
+
+ if (process.env.NODE_ENV === 'development') {
+ console.warn('MdxInlineAlert validation error:', error.message, errorInfo)
+ }
+ }
+)
+
export { MdxHighlight, MdxTip, MdxNote, MdxWarning }
diff --git a/src/components/error-boundary/__tests__/error-boundary.test.tsx b/src/components/error-boundary/__tests__/error-boundary.test.tsx
new file mode 100644
index 0000000000..1b72108e1a
--- /dev/null
+++ b/src/components/error-boundary/__tests__/error-boundary.test.tsx
@@ -0,0 +1,126 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import React from 'react'
+import { render, screen } from '@testing-library/react'
+import { vi } from 'vitest'
+import { ErrorBoundary, withErrorBoundary } from '../index'
+
+// Test component that throws an error
+const ThrowError = ({ shouldThrow = false }: { shouldThrow?: boolean }) => {
+ if (shouldThrow) {
+ throw new Error('Test error')
+ }
+ return No error
+}
+
+describe('ErrorBoundary', () => {
+ // Suppress console.error during tests to avoid noise
+ const originalError = console.error
+ beforeAll(() => {
+ console.error = vi.fn()
+ })
+ afterAll(() => {
+ console.error = originalError
+ })
+
+ it('renders children when there is no error', () => {
+ render(
+
+
+
+ )
+
+ expect(screen.getByText('No error')).toBeInTheDocument()
+ })
+
+ it('renders default error message when child throws error', () => {
+ render(
+
+
+
+ )
+
+ expect(screen.getByText('Something went wrong.')).toBeInTheDocument()
+ })
+
+ it('renders custom fallback when provided', () => {
+ const customFallback = Custom error message
+
+ render(
+
+
+
+ )
+
+ expect(screen.getByText('Custom error message')).toBeInTheDocument()
+ expect(screen.queryByText('Something went wrong.')).not.toBeInTheDocument()
+ })
+
+ it('calls onError callback when error occurs', () => {
+ const onError = vi.fn()
+
+ render(
+
+
+
+ )
+
+ expect(onError).toHaveBeenCalledWith(
+ expect.any(Error),
+ expect.objectContaining({
+ componentStack: expect.any(String)
+ })
+ )
+ })
+
+ it('shows error details in development mode', () => {
+ // Mock NODE_ENV for this test
+ vi.stubEnv('NODE_ENV', 'development')
+
+ render(
+
+
+
+ )
+
+ expect(screen.getByText('Error details (development only)')).toBeInTheDocument()
+
+ // Restore
+ vi.unstubAllEnvs()
+ })
+})
+
+describe('withErrorBoundary HOC', () => {
+ const originalError = console.error
+ beforeAll(() => {
+ console.error = vi.fn()
+ })
+ afterAll(() => {
+ console.error = originalError
+ })
+
+ it('wraps component with error boundary', () => {
+ const WrappedComponent = withErrorBoundary(ThrowError)
+
+ render()
+ expect(screen.getByText('No error')).toBeInTheDocument()
+ })
+
+ it('catches errors in wrapped component', () => {
+ const WrappedComponent = withErrorBoundary(ThrowError)
+
+ render()
+ expect(screen.getByText('Something went wrong.')).toBeInTheDocument()
+ })
+
+ it('uses custom fallback with HOC', () => {
+ const customFallback = HOC custom error
+ const WrappedComponent = withErrorBoundary(ThrowError, customFallback)
+
+ render()
+ expect(screen.getByText('HOC custom error')).toBeInTheDocument()
+ })
+})
diff --git a/src/components/error-boundary/index.tsx b/src/components/error-boundary/index.tsx
new file mode 100644
index 0000000000..c89937a9ed
--- /dev/null
+++ b/src/components/error-boundary/index.tsx
@@ -0,0 +1,216 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import React, {
+ Component,
+ ErrorInfo,
+ ReactNode,
+ useCallback,
+ useState,
+ useRef,
+ createContext,
+ useContext,
+} from 'react'
+
+interface ErrorBoundaryProps {
+ children: ReactNode
+ fallback?: ReactNode | ((error: Error, errorInfo: ErrorInfo) => ReactNode)
+ onError?: (error: Error, errorInfo: ErrorInfo) => void
+ resetOnPropsChange?: boolean
+ resetKeys?: Array
+}
+
+interface ErrorBoundaryState {
+ hasError: boolean
+ error?: Error
+ errorInfo?: ErrorInfo
+}
+
+interface ErrorBoundaryContextType {
+ resetErrorBoundary: () => void
+ captureError: (error: Error) => void
+}
+
+const ErrorBoundaryContext = createContext(
+ null
+)
+
+/**
+ * Error boundary component that catches JavaScript errors anywhere in the child component tree,
+ * logs those errors, and displays a fallback UI.
+ */
+export class ErrorBoundary extends Component<
+ ErrorBoundaryProps,
+ ErrorBoundaryState
+> {
+ constructor(props: ErrorBoundaryProps) {
+ super(props)
+ this.state = { hasError: false }
+ }
+
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState {
+ return { hasError: true, error }
+ }
+
+ componentDidCatch(error: Error, errorInfo: ErrorInfo) {
+ console.error('ErrorBoundary caught an error:', error, errorInfo)
+ this.setState({ errorInfo })
+
+ if (this.props.onError) {
+ this.props.onError(error, errorInfo)
+ }
+ }
+
+ componentDidUpdate(prevProps: ErrorBoundaryProps) {
+ if (this.props.resetKeys && prevProps.resetKeys) {
+ const hasResetKeyChanged = this.props.resetKeys.some(
+ (key, index) => key !== prevProps.resetKeys![index]
+ )
+
+ if (hasResetKeyChanged && this.state.hasError) {
+ this.setState({
+ hasError: false,
+ error: undefined,
+ errorInfo: undefined,
+ })
+ }
+ }
+ }
+
+ render(): ReactNode {
+ if (this.state.hasError && this.state.error) {
+ if (typeof this.props.fallback === 'function') {
+ return this.props.fallback(this.state.error, this.state.errorInfo!)
+ }
+
+ return (
+ this.props.fallback || (
+
+
Something went wrong.
+ {process.env.NODE_ENV === 'development' && this.state.error && (
+
+ Error details (development only)
+
+ {this.state.error.message}
+ {this.state.errorInfo && (
+ <>
+
+
+ Component Stack:
+ {this.state.errorInfo.componentStack}
+ >
+ )}
+
+
+ )}
+
+ )
+ )
+ }
+
+ return this.props.children
+ }
+}
+
+export function useErrorBoundary() {
+ const [resetKey, setResetKey] = useState(0)
+ const errorRef = useRef(null)
+
+ const resetErrorBoundary = useCallback(() => {
+ // Increment reset key to trigger error boundary reset
+ setResetKey((prev) => prev + 1)
+ errorRef.current = null
+ }, [])
+
+ const captureError = useCallback((error: Error) => {
+ errorRef.current = error
+ throw error
+ }, [])
+
+ const context = useContext(ErrorBoundaryContext)
+
+ if (context) {
+ return context
+ }
+
+ return {
+ resetErrorBoundary,
+ captureError,
+ resetKey,
+ }
+}
+
+interface ErrorBoundaryWrapperProps
+ extends Omit {
+ children: ReactNode
+}
+
+export const ErrorBoundaryWrapper: React.FC = ({
+ children,
+ ...props
+}) => {
+ const [resetKey, setResetKey] = useState(0)
+
+ const resetErrorBoundary = useCallback(() => {
+ setResetKey((prev) => prev + 1)
+ }, [])
+
+ const captureError = useCallback((error: Error) => {
+ throw error
+ }, [])
+
+ const contextValue: ErrorBoundaryContextType = {
+ resetErrorBoundary,
+ captureError,
+ }
+
+ return (
+
+
+ {children}
+
+
+ )
+}
+
+export function useErrorBoundaryContext() {
+ const context = useContext(ErrorBoundaryContext)
+ if (!context) {
+ throw new Error(
+ 'useErrorBoundaryContext must be used within an ErrorBoundaryWrapper'
+ )
+ }
+ return context
+}
+
+export const withErrorBoundary = (
+ Component: React.ComponentType,
+ fallback?: ReactNode | ((error: Error, errorInfo: ErrorInfo) => ReactNode),
+ onError?: (error: Error, errorInfo: ErrorInfo) => void
+) => {
+ const WrappedComponent = (props: T) => (
+
+
+
+ )
+
+ WrappedComponent.displayName = `withErrorBoundary(${
+ Component.displayName || Component.name
+ })`
+
+ return WrappedComponent
+}
+
+export default ErrorBoundaryWrapper
+
+export { ErrorBoundary as ErrorBoundaryClass }
diff --git a/src/components/lab-embed/embed-element/__tests__/embed-element.test.tsx b/src/components/lab-embed/embed-element/__tests__/embed-element.test.tsx
new file mode 100644
index 0000000000..760f5519de
--- /dev/null
+++ b/src/components/lab-embed/embed-element/__tests__/embed-element.test.tsx
@@ -0,0 +1,270 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
+import { render, screen, fireEvent, act } from '@testing-library/react'
+import EmbedElement from '../index'
+
+import { useInstruqtEmbed } from 'contexts/instruqt-lab'
+import { useRouter } from 'next/router'
+import { trackSandboxEvent } from 'lib/posthog-events'
+
+vi.mock('contexts/instruqt-lab', () => ({
+ useInstruqtEmbed: vi.fn(),
+}))
+
+vi.mock('next/router', () => ({
+ useRouter: vi.fn(),
+}))
+
+vi.mock('lib/posthog-events', () => ({
+ trackSandboxEvent: vi.fn(),
+ SANDBOX_EVENT: {
+ SANDBOX_LOADED: 'sandbox_loaded',
+ SANDBOX_ERROR: 'sandbox_error',
+ SANDBOX_RETRY: 'sandbox_retry',
+ },
+}))
+
+const mockUseInstruqtEmbed = vi.mocked(useInstruqtEmbed)
+const mockUseRouter = vi.mocked(useRouter)
+const mockTrackSandboxEvent = vi.mocked(trackSandboxEvent)
+
+describe('EmbedElement', () => {
+ const createMockRouter = () => ({
+ asPath: '/test-path',
+ route: '/test-path',
+ pathname: '/test-path',
+ query: {},
+ basePath: '',
+ isLocaleDomain: false,
+ push: vi.fn().mockResolvedValue(true),
+ replace: vi.fn().mockResolvedValue(true),
+ reload: vi.fn(),
+ back: vi.fn(),
+ forward: vi.fn(),
+ prefetch: vi.fn().mockResolvedValue(undefined),
+ beforePopState: vi.fn(),
+ events: {
+ on: vi.fn(),
+ off: vi.fn(),
+ emit: vi.fn(),
+ },
+ isFallback: false,
+ isReady: true,
+ isPreview: false,
+ locale: undefined,
+ locales: undefined,
+ defaultLocale: undefined,
+ domainLocales: undefined,
+ })
+
+ const createMockInstruqtContext = (overrides = {}) => ({
+ labId: 'test-lab-id',
+ active: true,
+ setActive: vi.fn(),
+ openLab: vi.fn(),
+ closeLab: vi.fn(),
+ hasConfigError: false,
+ configErrors: [],
+ ...overrides,
+ })
+
+ beforeEach(() => {
+ vi.clearAllMocks()
+ vi.useFakeTimers()
+
+ mockUseRouter.mockReturnValue(createMockRouter())
+ mockUseInstruqtEmbed.mockReturnValue(createMockInstruqtContext())
+ })
+
+ afterEach(() => {
+ vi.useRealTimers()
+ vi.clearAllTimers()
+ })
+
+ describe('rendering and props', () => {
+ it('renders an iframe with the correct props', () => {
+ render()
+
+ const iframe = screen.getByTitle('Instruqt Lab Environment: test-lab-id')
+
+ expect(iframe).toBeInTheDocument()
+ expect(iframe.tagName).toBe('IFRAME')
+ expect(iframe).toHaveAttribute(
+ 'src',
+ 'https://play.instruqt.com/embed/test-lab-id'
+ )
+ expect(iframe).toHaveAttribute(
+ 'sandbox',
+ 'allow-same-origin allow-scripts allow-popups allow-forms allow-modals'
+ )
+ expect(iframe).toHaveAttribute(
+ 'aria-label',
+ 'Interactive lab environment for test-lab-id'
+ )
+ expect(iframe).toHaveAttribute('allow', 'fullscreen')
+ })
+
+ it('shows loading state initially', () => {
+ render()
+
+ expect(screen.getByText('Loading your sandbox...')).toBeInTheDocument()
+ expect(
+ screen.getByText('This may take a few moments')
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('status', { name: /loading sandbox/i })
+ ).toBeInTheDocument()
+ })
+ })
+
+ describe('state transitions', () => {
+ it('hides loading state when iframe loads', () => {
+ render()
+
+ const iframe = screen.getByTitle('Instruqt Lab Environment: test-lab-id')
+
+ fireEvent.load(iframe)
+
+ expect(
+ screen.queryByText('Loading your sandbox...')
+ ).not.toBeInTheDocument()
+
+ expect(mockTrackSandboxEvent).toHaveBeenCalledWith('sandbox_loaded', {
+ labId: 'test-lab-id',
+ page: '/test-path',
+ })
+ })
+
+ it('shows error state when timeout occurs', () => {
+ render()
+
+ act(() => {
+ vi.advanceTimersByTime(30000)
+ })
+
+ expect(screen.getByText('Unable to Load Sandbox')).toBeInTheDocument()
+ expect(
+ screen.getAllByText(/Failed to load sandbox/).length
+ ).toBeGreaterThan(0)
+ expect(screen.getByRole('alert')).toBeInTheDocument()
+ })
+
+ it('allows retry when error occurs', () => {
+ render()
+
+ act(() => {
+ vi.advanceTimersByTime(30000)
+ })
+
+ const retryButton = screen.getByRole('button', { name: /try again/i })
+ expect(retryButton).toBeInTheDocument()
+
+ act(() => {
+ fireEvent.click(retryButton)
+ })
+
+ expect(screen.getByText('Loading your sandbox...')).toBeInTheDocument()
+
+ expect(mockTrackSandboxEvent).toHaveBeenCalledWith(
+ 'sandbox_retry',
+ expect.objectContaining({
+ labId: 'test-lab-id',
+ page: '/test-path',
+ })
+ )
+ })
+ })
+
+ describe('edge cases and error handling', () => {
+ it('shows no lab selected state when labId is null', () => {
+ mockUseInstruqtEmbed.mockReturnValue(
+ createMockInstruqtContext({
+ active: true,
+ labId: null,
+ })
+ )
+
+ render()
+
+ expect(screen.getByText('No Lab Selected')).toBeInTheDocument()
+ expect(
+ screen.getByText(/Please select a lab from the sandbox menu/)
+ ).toBeInTheDocument()
+ expect(screen.getByRole('status')).toBeInTheDocument()
+ })
+
+ it('has the correct styles when not active', () => {
+ mockUseInstruqtEmbed.mockReturnValue(
+ createMockInstruqtContext({
+ active: false,
+ labId: 'test-lab-id',
+ })
+ )
+
+ render()
+
+ const container = screen
+ .getByTitle('Instruqt Lab Environment: test-lab-id')
+ .closest('div')
+ expect(container).toHaveClass('_hide_4118c4')
+ })
+
+ it('shows Try Again button when error occurs', () => {
+ render()
+
+ // Trigger timeout to get error state
+ act(() => {
+ vi.advanceTimersByTime(30000)
+ })
+
+ // Verify retry button is present
+ expect(
+ screen.getByRole('button', { name: /try again/i })
+ ).toBeInTheDocument()
+ })
+ })
+
+ describe('accessibility', () => {
+ it('includes proper accessibility attributes', () => {
+ render()
+
+ const iframe = screen.getByTitle('Instruqt Lab Environment: test-lab-id')
+
+ // Verify accessibility attributes
+ expect(iframe).toHaveAttribute(
+ 'aria-label',
+ 'Interactive lab environment for test-lab-id'
+ )
+ expect(iframe).toHaveAttribute('aria-live', 'polite')
+ expect(iframe).toHaveAttribute('aria-busy', 'true') // Initially loading
+
+ // Verify screen reader announcements
+ expect(
+ screen.getByText('Loading sandbox environment')
+ ).toBeInTheDocument()
+
+ // Verify loading spinner is hidden from screen readers
+ const spinner = document.querySelector('[aria-hidden="true"]')
+ expect(spinner).toBeInTheDocument()
+ })
+
+ it('provides proper ARIA roles for different states', () => {
+ render()
+
+ // Loading state
+ expect(
+ screen.getByRole('status', { name: /loading sandbox/i })
+ ).toBeInTheDocument()
+
+ act(() => {
+ vi.advanceTimersByTime(30000)
+ })
+
+ expect(screen.getByRole('alert')).toBeInTheDocument()
+ })
+ })
+})
diff --git a/src/components/lab-embed/embed-element/embed-element.module.css b/src/components/lab-embed/embed-element/embed-element.module.css
index daafb8ae03..50a100af34 100644
--- a/src/components/lab-embed/embed-element/embed-element.module.css
+++ b/src/components/lab-embed/embed-element/embed-element.module.css
@@ -11,6 +11,141 @@
}
}
+.embedContainer {
+ width: 100%;
+ height: 100%;
+ position: relative;
+}
+
.hide {
display: none;
}
+
+.loadingContainer {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ background-color: var(--token-color-surface-primary);
+ border: 1px solid var(--token-color-border-primary);
+ border-radius: 8px;
+ padding: 2rem;
+ z-index: 1;
+}
+
+.loadingSpinner {
+ width: 40px;
+ height: 40px;
+ border: 3px solid var(--token-color-border-primary);
+ border-top: 3px solid var(--token-color-palette-blue-200);
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ margin-bottom: 1rem;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+.loadingText {
+ text-align: center;
+ color: var(--token-color-foreground-primary);
+
+ & h3 {
+ margin: 0 0 0.5rem 0;
+ font-size: 1.125rem;
+ font-weight: 500;
+ }
+
+ & p {
+ margin: 0;
+ color: var(--token-color-foreground-faint);
+ font-size: 0.875rem;
+ }
+}
+
+.errorContainer {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: var(--token-color-surface-primary);
+ border: 1px solid var(--token-color-border-critical);
+ border-radius: 8px;
+ padding: 2rem;
+ z-index: 1;
+
+ &.fullHeight {
+ position: relative;
+ height: 100%;
+ }
+}
+
+.errorContent {
+ text-align: center;
+ max-width: 400px;
+
+ & h3 {
+ margin: 0 0 1rem 0;
+ color: var(--token-color-foreground-critical);
+ font-size: 1.125rem;
+ font-weight: 500;
+ }
+
+ & p {
+ margin: 0 0 1.5rem 0;
+ color: var(--token-color-foreground-primary);
+ font-size: 0.875rem;
+ line-height: 1.5;
+ }
+}
+
+.retryButton {
+ background-color: var(--token-color-palette-blue-200);
+ color: white;
+ border: none;
+ border-radius: 4px;
+ padding: 0.75rem 1.5rem;
+ font-size: 0.875rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: background-color 0.2s ease;
+
+ &:hover {
+ background-color: var(--token-color-palette-blue-300);
+ }
+
+ &:focus {
+ outline: 2px solid var(--token-color-focus-action-internal);
+ outline-offset: 2px;
+ }
+
+ &:disabled {
+ background-color: var(--token-color-surface-faint);
+ color: var(--token-color-foreground-disabled);
+ cursor: not-allowed;
+ }
+}
+
+/* Screen reader only content */
+.sr-only {
+ position: absolute !important;
+ width: 1px !important;
+ height: 1px !important;
+ padding: 0 !important;
+ margin: -1px !important;
+ overflow: hidden !important;
+ clip: rect(0, 0, 0, 0) !important;
+ white-space: nowrap !important;
+ border: 0 !important;
+}
diff --git a/src/components/lab-embed/embed-element/index.tsx b/src/components/lab-embed/embed-element/index.tsx
index c22716a85d..6d08d61f8b 100644
--- a/src/components/lab-embed/embed-element/index.tsx
+++ b/src/components/lab-embed/embed-element/index.tsx
@@ -3,35 +3,265 @@
* SPDX-License-Identifier: MPL-2.0
*/
-import { MutableRefObject, useRef, useEffect } from 'react'
+import { useRef, useEffect, useState, useCallback, memo } from 'react'
import classNames from 'classnames'
+import { useRouter } from 'next/router'
import { useInstruqtEmbed } from 'contexts/instruqt-lab'
+import { trackSandboxEvent, SANDBOX_EVENT } from 'lib/posthog-events'
import s from './embed-element.module.css'
-export default function EmbedElement(): JSX.Element {
- const ref: MutableRefObject = useRef()
+interface EmbedState {
+ isLoading: boolean
+ hasError: boolean
+ errorMessage: string
+ retryCount: number
+}
+
+/**
+ * EmbedElement component for displaying Instruqt lab environments
+ *
+ */
+const EmbedElement = memo(function EmbedElement(): JSX.Element {
+ const ref = useRef(null)
+ const { active, labId } = useInstruqtEmbed()
+ const router = useRouter()
+
+ const [embedState, setEmbedState] = useState({
+ isLoading: true,
+ hasError: false,
+ errorMessage: '',
+ retryCount: 0,
+ })
+
+ const resetEmbedState = useCallback(() => {
+ setEmbedState({
+ isLoading: true,
+ hasError: false,
+ errorMessage: '',
+ retryCount: 0,
+ })
+ }, [])
+
+ const handleIframeLoad = useCallback(() => {
+ setEmbedState((prev) => ({
+ ...prev,
+ isLoading: false,
+ hasError: false,
+ }))
+
+ if (labId) {
+ trackSandboxEvent(SANDBOX_EVENT.SANDBOX_LOADED, {
+ labId,
+ page: router.asPath,
+ })
+ }
+ }, [labId, router.asPath])
+
+ const handleIframeError = useCallback(() => {
+ setEmbedState((prev) => ({
+ ...prev,
+ isLoading: false,
+ hasError: true,
+ errorMessage:
+ 'Failed to load sandbox. Please check your internet connection and try again.',
+ }))
+
+ // Track error
+ if (labId) {
+ trackSandboxEvent(SANDBOX_EVENT.SANDBOX_ERROR, {
+ labId,
+ page: router.asPath,
+ error: 'iframe_load_error',
+ retryCount: embedState.retryCount,
+ })
+ }
+ }, [labId, router.asPath, embedState.retryCount])
+
+ const handleRetry = useCallback(() => {
+ if (embedState.retryCount < 3) {
+ setEmbedState((prev) => ({
+ ...prev,
+ isLoading: true,
+ hasError: false,
+ errorMessage: '',
+ retryCount: prev.retryCount + 1,
+ }))
+
+ if (labId) {
+ trackSandboxEvent(SANDBOX_EVENT.SANDBOX_RETRY, {
+ labId,
+ page: router.asPath,
+ retryCount: embedState.retryCount + 1,
+ })
+ }
+
+ const iframe = ref.current
+ if (iframe) {
+ const expectedSrc = `https://play.instruqt.com/embed/${labId}`
+ iframe.src = ''
+ const timeoutId = setTimeout(() => {
+ if (ref.current) {
+ ref.current.src = expectedSrc
+ }
+ }, 100)
+
+ return () => clearTimeout(timeoutId)
+ }
+ } else {
+ setEmbedState((prev) => ({
+ ...prev,
+ errorMessage:
+ 'Maximum retry attempts reached. Please refresh the page or try again later.',
+ }))
+ }
+ }, [embedState.retryCount, labId, router.asPath])
+
+ // Reset state when labId changes
+ useEffect(() => {
+ if (labId) {
+ resetEmbedState()
+ }
+ }, [labId, resetEmbedState])
useEffect(() => {
- if (!ref.current) {
+ if (!labId || !ref.current) {
return
}
- // ensures that focus properly shifts when the lab component is mounted
- ref.current.focus()
- }, [])
+ const iframe = ref.current
+ const expectedSrc = `https://play.instruqt.com/embed/${labId}`
- const { active, labId } = useInstruqtEmbed()
+ if (iframe.src !== expectedSrc) {
+ iframe.src = expectedSrc
+ }
+ }, [labId])
+
+ useEffect(() => {
+ if (!embedState.isLoading || !labId) {
+ return
+ }
+
+ const timeoutId = setTimeout(() => {
+ setEmbedState((current) => {
+ if (current.isLoading) {
+ return {
+ ...current,
+ isLoading: false,
+ hasError: true,
+ errorMessage:
+ 'Failed to load sandbox. Please check your internet connection and try again.',
+ }
+ }
+ return current
+ })
+ }, 30000)
+
+ return () => clearTimeout(timeoutId)
+ }, [embedState.isLoading, labId])
+
+ useEffect(() => {
+ const iframe = ref.current
+ if (!iframe || !active) {
+ return
+ }
+
+ iframe.focus()
+ }, [active])
+
+ if (!labId) {
+ return (
+
+
+
No Lab Selected
+
Please select a lab from the sandbox menu to get started.
+
+
+ )
+ }
return (
-
+
+
+ {embedState.isLoading && 'Loading sandbox environment'}
+ {embedState.hasError &&
+ `Error loading sandbox: ${embedState.errorMessage}`}
+ {!embedState.isLoading &&
+ !embedState.hasError &&
+ labId &&
+ 'Sandbox environment loaded successfully'}
+
+
+ {embedState.isLoading && (
+
+
+
+
Loading your sandbox...
+
This may take a few moments
+
+
+ )}
+
+ {embedState.hasError && (
+
+
+
Unable to Load Sandbox
+
{embedState.errorMessage}
+ {embedState.retryCount < 3 && (
+
+ )}
+
+ Retry loading the sandbox environment
+
+
+
+ )}
+
+ {labId && (
+
+ )}
+
)
-}
+})
+
+export default EmbedElement
diff --git a/src/components/lab-embed/resizable/__tests__/resizable.test.tsx b/src/components/lab-embed/resizable/__tests__/resizable.test.tsx
new file mode 100644
index 0000000000..fac6b7997b
--- /dev/null
+++ b/src/components/lab-embed/resizable/__tests__/resizable.test.tsx
@@ -0,0 +1,162 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import { render, screen, fireEvent } from '@testing-library/react'
+import Resizable from '../index'
+import s from '../resizable.module.css'
+
+// Mock the useInstruqtEmbed hook
+const mockUseInstruqtEmbed = vi.fn()
+vi.mock('contexts/instruqt-lab', () => ({
+ useInstruqtEmbed: () => mockUseInstruqtEmbed(),
+}))
+
+describe('Resizable', () => {
+ const mockSetPanelActive = vi.fn()
+ const mockCloseLab = vi.fn()
+
+ beforeEach(() => {
+ vi.clearAllMocks()
+ mockUseInstruqtEmbed.mockImplementation(() => ({
+ closeLab: mockCloseLab,
+ }))
+
+ Object.defineProperty(window, 'innerWidth', {
+ writable: true,
+ configurable: true,
+ value: 1200,
+ })
+
+ global.dispatchEvent = vi.fn()
+ })
+
+ it('renders children when panel is active', () => {
+ render(
+
+ Child content
+
+ )
+
+ expect(screen.getByTestId('test-child')).toBeInTheDocument()
+ })
+
+ it('does not render children when panel is not active', () => {
+ render(
+
+ Child content
+
+ )
+
+ // Check that the main resizable container has the 'hide' class
+ const resizableContainer = screen
+ .getByTestId('test-child')
+ .closest('div._resizable_17d1f1')
+ expect(resizableContainer).toHaveClass(s.hide)
+
+ expect(screen.queryByTestId('test-child')).toBeInTheDocument()
+ })
+
+ it('has the correct initial height', () => {
+ render(
+
+ Child content
+
+ )
+
+ const resizableDiv = screen
+ .getByTestId('test-child')
+ .closest('div[data-resizing]')
+ expect(resizableDiv).toHaveStyle('height: 500px')
+ })
+
+ it('calls closeLab when close button is clicked', () => {
+ render(
+
+ Child content
+
+ )
+
+ const closeButton = screen.getByRole('button', { name: /close/i })
+ fireEvent.click(closeButton)
+
+ expect(mockCloseLab).toHaveBeenCalled()
+ })
+
+ it('starts resizing when resizer is clicked', () => {
+ render(
+
+ Child content
+
+ )
+
+ const resizer = screen.getByRole('button', { name: /resize/i })
+ fireEvent.mouseDown(resizer, { screenY: 100 })
+
+ const resizableDiv = screen
+ .getByTestId('test-child')
+ .closest('div[data-resizing]')
+ expect(resizableDiv).toHaveAttribute('data-resizing', 'true')
+ })
+
+ it('changes height during resizing', async () => {
+ render(
+
+ Child content
+
+ )
+
+ const resizer = screen.getByRole('button', { name: /resize/i })
+
+ fireEvent.mouseDown(resizer, { screenY: 500 })
+
+ // Verify resizing started
+ let resizableDiv = screen
+ .getByTestId('test-child')
+ .closest('div[data-resizing]')
+ expect(resizableDiv).toHaveAttribute('data-resizing', 'true')
+
+ // Verify the initial height is maintained
+ expect(resizableDiv).toHaveStyle('height: 500px')
+
+ fireEvent.mouseUp(document)
+
+ await new Promise((resolve) => setTimeout(resolve, 10))
+
+ resizableDiv = screen
+ .getByTestId('test-child')
+ .closest('div[data-resizing]')
+ expect(resizableDiv).toHaveAttribute('data-resizing', 'false')
+ })
+})
diff --git a/src/components/lab-embed/resizable/components/resizer.module.css b/src/components/lab-embed/resizable/components/resizer.module.css
index af07e86f3c..300d683ec9 100644
--- a/src/components/lab-embed/resizable/components/resizer.module.css
+++ b/src/components/lab-embed/resizable/components/resizer.module.css
@@ -23,6 +23,10 @@
height: 100%;
display: flex;
justify-content: center;
+
+ &:hover {
+ background-color: var(--token-color-palette-neutral-100);
+ }
}
.resizeBar {
diff --git a/src/components/lab-embed/resizable/components/resizer.tsx b/src/components/lab-embed/resizable/components/resizer.tsx
index 595d4b2047..6925afb912 100644
--- a/src/components/lab-embed/resizable/components/resizer.tsx
+++ b/src/components/lab-embed/resizable/components/resizer.tsx
@@ -6,8 +6,9 @@
import { MouseEventHandler } from 'react'
import CSS from 'csstype'
import { IconX24 } from '@hashicorp/flight-icons/svg-react/x-24'
-import InlineSvg from '@hashicorp/react-inline-svg'
-import ResizeBar from './img/resize_bar.svg?include'
+// Removing the InlineSvg import which is causing issues in tests
+// import InlineSvg from '@hashicorp/react-inline-svg'
+// import ResizeBar from './img/resize_bar.svg?include'
import s from './resizer.module.css'
interface ResizerProps {
@@ -31,10 +32,57 @@ export default function Resizer({
className={s.resizer}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
+ onKeyDown={(e: React.KeyboardEvent) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault()
+ // For keyboard activation, trigger mouse down to enable resizing
+ if (onMouseDown) {
+ const keyboardMouseEvent = {
+ preventDefault: () => {},
+ stopPropagation: () => {},
+ screenY: 0,
+ clientY: 0,
+ button: 0,
+ buttons: 1,
+ currentTarget: e.currentTarget,
+ target: e.target,
+ } as React.MouseEvent
+ onMouseDown(keyboardMouseEvent)
+ }
+ }
+ }}
+ role="button"
+ aria-label="Resize panel"
+ tabIndex={0}
>
-
+ {/* Replace InlineSvg with direct SVG for testing */}
+
-