',
+ backgroundColor: "#00000000",
isCloseable: false,
},
algolia: {
@@ -224,14 +223,6 @@ const config: Config = {
},
footer: {
links: [
- {
- label: "Manifesto",
- href: "/manifesto",
- },
- {
- label: "Supporters",
- href: "/supporters",
- },
{
label: "FAQs",
href: "/faq",
@@ -249,16 +240,6 @@ const config: Config = {
navbar: {
hideOnScroll: true,
items: [
- {
- to: "/manifesto",
- label: "Manifesto",
- position: "left",
- },
- {
- to: "/supporters",
- label: "Supporters",
- position: "left",
- },
{
to: "/faq",
label: "FAQs",
@@ -269,6 +250,11 @@ const config: Config = {
label: "Blog",
position: "left",
},
+ {
+ label: "Registry",
+ href: "https://search.opentofu.org",
+ position: "left",
+ },
{
label: "Roadmap",
href: "https://github.com/opentofu/opentofu/milestones",
@@ -282,27 +268,26 @@ const config: Config = {
items: [
{
label: "v1.9.x (current)",
- href: "/docs/"
+ href: "/docs/",
},
{
label: "v1.8.x",
- href: "/docs/v1.8/"
+ href: "/docs/v1.8/",
},
{
label: "v1.7.x",
- href: "/docs/v1.7/"
+ href: "/docs/v1.7/",
},
{
label: "v1.6.x",
- href: "/docs/v1.6/"
+ href: "/docs/v1.6/",
},
{
label: "Development",
- href: "/docs/main/"
+ href: "/docs/main/",
},
],
},
-
// TODO: This link is important but there's no design for it yet
// {
// type: "dropdown",
diff --git a/faq.mdx b/faq.mdx
index 526530f..31d0520 100644
--- a/faq.mdx
+++ b/faq.mdx
@@ -1,26 +1,12 @@
-
+
-OpenTofu is an infrastructure as code tool that lets you define both cloud and on-prem resources in human-readable configuration files that you can version, reuse, and share. You can then use a consistent workflow to provision and manage all of your infrastructure throughout its lifecycle. OpenTofu can manage low-level components like compute, storage, and networking resources, as well as high-level components like DNS entries and SaaS features.
-
-
-
-
-
-OpenTofu is a Terraform fork, created as an initiative of Gruntwork, Spacelift, Harness, Env0, Scalr, and others, in response to HashiCorp’s switch from an open-source license to the BUSL. The initiative has many supporters, all of whom are listed [here](/supporters).
+OpenTofu is a Terraform fork, created as an initiative of Gruntwork, Spacelift, Harness, Env0, Scalr, and others, in response to HashiCorp's switch from an open-source license to the BUSL. The initiative has many supporters, all of whom are listed [here](/supporters).
The BUSL and the additional use grant outlined by the HashiCorp team are ambiguous, which makes it challenging for companies, vendors, and developers using Terraform to decide whether their actions could be interpreted as being outside the permitted scope of use.
-Hashicorp’s FAQs give some peace of mind to end users and system integrators for now, but the licensing terms’ implications for future usage are unclear. The possibility that the company’s definition of _“competitive”_ or _“embedding”_ could change or the license could be further modified to make it closed source prompts uncertainty for Terraform users.
+HashiCorp's FAQs give some peace of mind to end users and system integrators for now, but the licensing terms' implications for future usage are unclear. The possibility that the company's definition of _"competitive"_ or _"embedding"_ could change or the license could be further modified to make it closed source prompts uncertainty for Terraform users.
-We firmly believe that Terraform should remain open-source because it is a project many companies use, and many contributors have made Terraform what it is today. Terraform’s success would not have been possible without the community’s work to build many supporting projects around it.
-
-
-
-
-
-On the technical level, OpenTofu 1.6.x is very similar feature-wise to Terraform 1.6.x. In the future, the projects feature sets will diverge.
-
-The other main difference is that OpenTofu is open-source, and its goal is to be driven in a collaborative way with no single company being able to dictate the roadmap.
+We firmly believe that Terraform should remain open-source because it is a project many companies use, and many contributors have made Terraform what it is today. Terraform's success would not have been possible without the community's work to build many supporting projects around it.
@@ -32,11 +18,11 @@ Initial impressions suggest you could use either OpenTofu or Terraform for perso
#### Consultants
-A consultant should offer their clients the best possible solution that aligns with their budget. OpenTofu will be on par with Terraform, and one of the project’s central objectives is to listen to the community’s issues, so it makes sense to recommend a project that will always stay open-source. Anyone who has used Terraform in the last eight years has probably come across issues that took some time to be resolved. The large community involved in developing OpenTofu means this will no longer be the case.
+A consultant should offer their clients the best possible solution that aligns with their budget. OpenTofu will be on par with Terraform, and one of the project's central objectives is to listen to the community's issues, so it makes sense to recommend a project that will always stay open-source. Anyone who has used Terraform in the last eight years has probably come across issues that took some time to be resolved. The large community involved in developing OpenTofu means this will no longer be the case.
#### Companies
-Companies will encounter more difficulties with the situation. Switching to a new project carries risks, but staying with a project that changes its license without warning is far riskier. This risk is minimized by giving OpenTofu to the Linux Foundation, and OpenTofu’s aim of maintaining feature parity with Terraform for future releases reduces the technical risks.
+Companies will encounter more difficulties with the situation. Switching to a new project carries risks, but staying with a project that changes its license without warning is far riskier. This risk is minimized by giving OpenTofu to the Linux Foundation, and OpenTofu's aim of maintaining feature parity with Terraform for future releases reduces the technical risks.
@@ -48,22 +34,20 @@ If you're missing a feature in OpenTofu that's available in Terraform, feel free
-
-
-Right now, OpenTofu is a drop-in replacement for Terraform, as it's compatible with Terraform versions 1.5.x and most of 1.6.x. You don’t need to make any changes to your code to ensure compatibility.
-
-OpenTofu is suitable for production use cases without any exception.
-
-Please see [our migration guide](/docs/intro/migration) for more information.
-
-
-
OpenTofu will work with existing state files up to those created with Terraform versions 1.5.x.
+
+
+The OpenTofu Registry hosts thousands of providers and modules that are compatible with OpenTofu. These include all the popular cloud providers, third-party services, and community-maintained resources you might need for your infrastructure.
+
+You can search for providers, modules, and their documentation at [search.opentofu.org](https://search.opentofu.org). The registry provides seamless access to the same ecosystem of providers and modules that you're familiar with, ensuring you have all the tools needed to build and manage your infrastructure.
+
+
+
OpenTofu will not have its own providers. Terraform providers have not altered their licenses, and the potential for such a change is virtually zero. OpenTofu works with the current Terraform providers, but it uses a separate registry.
diff --git a/package-lock.json b/package-lock.json
index 2f06f29..5977e63 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -26,6 +26,7 @@
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
+ "framer-motion": "^12.7.4",
"postcss": "^8.4.33",
"prettier": "^3.1.1",
"prism-react-renderer": "^2.3.1",
@@ -33,6 +34,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-github-btn": "^1.4.0",
+ "swiper": "^11.2.6",
"tailwindcss": "^3.4.1",
"typescript": "~5.3.3"
},
@@ -7643,6 +7645,32 @@
"url": "https://github.com/sponsors/rawify"
}
},
+ "node_modules/framer-motion": {
+ "version": "12.7.4",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.7.4.tgz",
+ "integrity": "sha512-jX0bPsTmU0oPZTYz/dVyD0dmOyEOEJvdn0TaZBE5I8g2GvVnnQnW9f65cJnoVfUkY3WZWNXGXnPbVA9YnaIfVA==",
+ "dependencies": {
+ "motion-dom": "^12.7.4",
+ "motion-utils": "^12.7.2",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@@ -12221,6 +12249,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/motion-dom": {
+ "version": "12.7.4",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.7.4.tgz",
+ "integrity": "sha512-1ZUHAoSUMMxP6jPqyxlk9XUfb6NxMsnWPnH2YGhrOhTURLcXWbETi6eemoKb60Pe32NVJYduL4B62VQSO5Jq8Q==",
+ "dependencies": {
+ "motion-utils": "^12.7.2"
+ }
+ },
+ "node_modules/motion-utils": {
+ "version": "12.7.2",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.7.2.tgz",
+ "integrity": "sha512-XhZwqctxyJs89oX00zn3OGCuIIpVevbTa+u82usWBC6pSHUd2AoNWiYa7Du8tJxJy9TFbZ82pcn5t7NOm1PHAw=="
+ },
"node_modules/mrmime": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz",
@@ -15900,6 +15941,24 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
+ "node_modules/swiper": {
+ "version": "11.2.6",
+ "resolved": "https://registry.npmjs.org/swiper/-/swiper-11.2.6.tgz",
+ "integrity": "sha512-8aXpYKtjy3DjcbzZfz+/OX/GhcU5h+looA6PbAzHMZT6ESSycSp9nAjPCenczgJyslV+rUGse64LMGpWE3PX9Q==",
+ "funding": [
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/swiperjs"
+ },
+ {
+ "type": "open_collective",
+ "url": "http://opencollective.com/swiper"
+ }
+ ],
+ "engines": {
+ "node": ">= 4.7.0"
+ }
+ },
"node_modules/tailwindcss": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz",
diff --git a/package.json b/package.json
index 4b34a14..efdedeb 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
"clear": "docusaurus clear",
"typecheck": "tsc",
"sync-supporters": "node ./sync-supporters.js",
+ "sync-contributor-stats": "node ./scripts/fetch-contributor-stats.js",
"prepare": "husky install",
"lint": "eslint --cache \"**/*.{js,jsx,ts,tsx}\""
},
@@ -31,6 +32,7 @@
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
+ "framer-motion": "^12.7.4",
"postcss": "^8.4.33",
"prettier": "^3.1.1",
"prism-react-renderer": "^2.3.1",
@@ -38,6 +40,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-github-btn": "^1.4.0",
+ "swiper": "^11.2.6",
"tailwindcss": "^3.4.1",
"typescript": "~5.3.3"
},
diff --git a/scripts/fetch-contributor-stats.js b/scripts/fetch-contributor-stats.js
new file mode 100755
index 0000000..6932531
--- /dev/null
+++ b/scripts/fetch-contributor-stats.js
@@ -0,0 +1,212 @@
+#!/usr/bin/env node
+
+/**
+ * Script to fetch OpenTofu contributor statistics
+ *
+ * This script fetches contributor data from OpenTofu GitHub repositories
+ * and generates a JSON file containing contributor counts.
+ *
+ * Usage:
+ * node scripts/fetch-contributor-stats.js
+ *
+ * Note: For more accurate results, you can set a GitHub token:
+ * GITHUB_TOKEN=your_token node scripts/fetch-contributor-stats.js
+ */
+
+const https = require("https");
+const fs = require("fs");
+const path = require("path");
+
+// Configuration
+const FORK_DATE = "2023-08-25"; // OpenTofu was officially announced on this date
+const OUTPUT_FILE = path.join(
+ __dirname,
+ "..",
+ "src",
+ "data",
+ "contributor-stats.json",
+);
+const GITHUB_TOKEN = process.env.GITHUB_TOKEN || ""; // Optional GitHub token
+
+// For fallback purposes, let's hardcode the contributor count we found earlier
+// If GitHub API access is fixed later, this will be replaced with the actual count
+const FALLBACK_CONTRIBUTOR_COUNT = 180;
+
+// Create the data directory if it doesn't exist
+const dataDir = path.dirname(OUTPUT_FILE);
+if (!fs.existsSync(dataDir)) {
+ fs.mkdirSync(dataDir, { recursive: true });
+}
+
+// Main repositories to check (excluding manifesto, roadmap, .github, legal-documents)
+const REPOSITORIES = [
+ "opentofu",
+ "opentofu.org",
+ "brand-artifacts",
+ "equivalence-testing",
+ "scripts",
+ "registry-alpha",
+ "registry-address",
+ "setup-opentofu",
+ "tofu-exec",
+ "get.opentofu.org",
+ "tfenv",
+];
+
+// Helper function to make GitHub API requests
+function makeRequest(url) {
+ return new Promise((resolve, reject) => {
+ const options = {
+ headers: {
+ "User-Agent": "OpenTofu-Stats-Collector",
+ ...(GITHUB_TOKEN && { Authorization: `token ${GITHUB_TOKEN}` }),
+ },
+ };
+
+ https
+ .get(url, options, (response) => {
+ if (response.statusCode === 403) {
+ console.warn("Rate limit exceeded. Consider using a GitHub token.");
+ return resolve([]);
+ }
+
+ if (response.statusCode !== 200) {
+ return reject(
+ new Error(`Request failed with status code ${response.statusCode}`),
+ );
+ }
+
+ let data = "";
+
+ response.on("data", (chunk) => {
+ data += chunk;
+ });
+
+ response.on("end", () => {
+ try {
+ resolve(JSON.parse(data));
+ } catch (error) {
+ reject(
+ new Error(
+ `Failed to parse response from ${url}: ${error.message}`,
+ ),
+ );
+ }
+ });
+ })
+ .on("error", (error) => {
+ reject(new Error(`Request to ${url} failed: ${error.message}`));
+ });
+ });
+}
+
+// Fetch contributors for a repository after the fork date
+async function fetchContributors(repo) {
+ console.log(`Fetching contributors for ${repo}...`);
+ const contributors = new Set();
+ let page = 1;
+ let hasMore = true;
+
+ while (hasMore) {
+ const url = `https://api.github.com/repos/opentofu/${repo}/commits?since=${FORK_DATE}T00:00:00Z&per_page=100&page=${page}`;
+
+ try {
+ const commits = await makeRequest(url);
+
+ if (commits.length === 0 || !Array.isArray(commits)) {
+ hasMore = false;
+ continue;
+ }
+
+ // Extract unique contributor logins
+ commits.forEach((commit) => {
+ if (commit.author && commit.author.login) {
+ contributors.add(commit.author.login);
+ }
+ });
+
+ // If we got fewer than 100 commits, we've reached the last page
+ if (commits.length < 100) {
+ hasMore = false;
+ } else {
+ page++;
+ // Add a small delay to avoid rate limits
+ await new Promise((resolve) => setTimeout(resolve, 500));
+ }
+ } catch (error) {
+ console.error(
+ `Error fetching commits for ${repo} (page ${page}):`,
+ error.message,
+ );
+ hasMore = false;
+ }
+ }
+
+ return Array.from(contributors);
+}
+
+// Main function
+async function main() {
+ console.log(`Gathering contributor statistics since ${FORK_DATE}...`);
+
+ const allContributors = new Set();
+ let apiSuccessful = false;
+
+ // Process each repository
+ for (const repo of REPOSITORIES) {
+ try {
+ const contributors = await fetchContributors(repo);
+ console.log(`Found ${contributors.length} contributors in ${repo}`);
+
+ contributors.forEach((contributor) => allContributors.add(contributor));
+
+ if (contributors.length > 0) {
+ apiSuccessful = true;
+ }
+ } catch (error) {
+ console.error(`Failed to process repository ${repo}:`, error.message);
+ }
+ }
+
+ // If API requests didn't yield results, use fallback count
+ const contributorCount = apiSuccessful
+ ? allContributors.size
+ : FALLBACK_CONTRIBUTOR_COUNT;
+
+ // Generate stats
+ const stats = {
+ timestamp: new Date().toISOString(),
+ stats: {
+ contributors: {
+ total: contributorCount,
+ as_of_date: FORK_DATE,
+ },
+ },
+ };
+
+ // Save to JSON file
+ fs.writeFileSync(OUTPUT_FILE, JSON.stringify(stats, null, 2));
+
+ console.log(`\nContributor statistics:`);
+ console.log(`Total unique contributors: ${contributorCount}`);
+ console.log(`\nSaved stats to ${OUTPUT_FILE}`);
+
+ if (!apiSuccessful) {
+ console.warn(
+ "\nWarning: GitHub API requests did not return contributor data.",
+ );
+ console.warn(
+ "Using fallback contributor count of",
+ FALLBACK_CONTRIBUTOR_COUNT,
+ );
+ console.warn("For more accurate results, set a GitHub token:");
+ console.warn(
+ " GITHUB_TOKEN=your_token node scripts/fetch-contributor-stats.js",
+ );
+ }
+}
+
+main().catch((error) => {
+ console.error("Error executing script:", error);
+ process.exit(1);
+});
diff --git a/src/components/Accordion/Item.tsx b/src/components/Accordion/Item.tsx
index 9ef06ba..7f1996d 100644
--- a/src/components/Accordion/Item.tsx
+++ b/src/components/Accordion/Item.tsx
@@ -23,8 +23,8 @@ const classNames = [
"max-w-none",
"text-gray-900",
"dark:text-gray-100",
- "pb-6",
- "px-6",
+ "pb-4 sm:pb-6",
+ "px-3 sm:px-6",
"leading-6",
"mt-2",
"text-base",
@@ -105,7 +105,7 @@ const AccordionItem = ({
>
{summary}