diff --git a/.yamllint.yml b/.yamllint.yml index 6a47615b..7a8fc3ca 100644 --- a/.yamllint.yml +++ b/.yamllint.yml @@ -11,6 +11,7 @@ ignore: | # https://github.com/sbaudoin/yamllint/issues/16 /helm/templates /python_sdk + /infrahub rules: new-lines: disable diff --git a/README.md b/README.md index f5e0fb86..bbc8003c 100644 --- a/README.md +++ b/README.md @@ -54,53 +54,258 @@ This command generates static content into the `build` directory and can be serv npm run serve ``` -## How to Setup a New Repo +## How to Setup Documentation for a New Repository -- Create a branch in the to have it's docs built locally -- Copy over these files & directories from another repo, like `emma` repo: +This guide walks through setting up documentation syncing from a new OpsMill repository to this aggregation site. -```console -docs/ -.vale -.vale.ini -.markdownlint.yml -.yamllint.yml -tasks.py -.github/ - build-docs.sh - file-filters.yml - labeler.yml - labels.yml - workflows/ - ci.yml ## only adding part - sync-docs.yml +### Automated Setup (Recommended) + +Use the setup script to automate most of the configuration: + +```bash +# Setup both source repo and infrahub-docs at once +python scripts/setup-docs-repo.py \ + --source-repo /path/to/my-new-project \ + --infrahub-docs-repo /path/to/infrahub-docs \ + --display-name "My New Project" + +# Setup source repo only +python scripts/setup-docs-repo.py \ + --source-repo /path/to/my-new-project \ + --display-name "My New Project" + +# Setup infrahub-docs only +python scripts/setup-docs-repo.py \ + --infrahub-docs-repo /path/to/infrahub-docs \ + --project-name my-new-project \ + --display-name "My New Project" ``` -- Modify the following: +The script will: -```console -docs/ - docusaurus.config.ts - sidebars.ts - docs/ ## Put docs here +- Copy all required template files to the source repository +- Update configuration files with your project name +- Create placeholder files in infrahub-docs +- Generate the plugin configuration snippets to add + +Run `python scripts/setup-docs-repo.py --help` for all options. + +### Manual Setup + +If you prefer to set things up manually, follow the steps below. + +### Prerequisites + +- A new OpsMill GitHub repository that needs documentation +- Access to create GitHub Actions secrets (for `PAT_TOKEN`) +- Cloudflare Pages access (for the integration) + +### Part 1: Configure the Source Repository + +These steps are performed in your new repository (e.g., `opsmill/my-new-project`). + +#### Step 1: Copy required files from an existing repo + +Use `infrahub-bundle-dc` as a template. Copy these files and directories: + +**Documentation framework:** + +```text +docs/ # Docusaurus site +├── .docusaurus/ # (auto-generated, gitignored) +├── .gitignore +├── babel.config.js +├── docs/ # Your documentation content goes here +│ └── readme.mdx # At minimum, create an index page +├── docusaurus.config.ts # Site configuration (modify this) +├── package.json +├── sidebars.ts # Sidebar navigation (modify this) +├── src/ +│ └── css/ +│ └── custom.css +├── static/ +│ └── img/ +│ ├── favicon.ico +│ ├── infrahub-hori.svg +│ └── infrahub-hori-dark.svg +└── tsconfig.json +``` + +**Linting configuration (copy to repo root):** + +```text +.vale/ # Vale style rules +.vale.ini # Vale configuration +.markdownlint.yaml # Markdown linting rules +.yamllint.yml # YAML linting rules (optional) +tasks.py # Invoke tasks (optional, for local builds) +``` + +**GitHub Actions (copy to `.github/`):** + +```text .github/ - workflows/ - ci.yml ## only adding part - sync-docs.yml ## paths +├── build-docs.sh # Build script for CI +├── file-filters.yml # Path filters for CI jobs +├── labeler.yml # PR labeling rules +├── labels.yml # Label definitions +└── workflows/ + ├── ci.yml # CI pipeline (add docs job) + └── sync-docs.yml # Syncs docs to infrahub-docs ``` -- `chmod 755 .github/build-docs.sh` +#### Step 2: Make the build script executable -### In `infrahub-docs` modify the following +```bash +chmod 755 .github/build-docs.sh +``` -```console +#### Step 3: Customize the documentation configuration + +**Edit `docs/docusaurus.config.ts`:** + +- Update `title` and `tagline` for your project +- Update `projectName` to match your repository name +- Update `editUrl` to point to your repository +- Update the `sidebarId` in navbar items to match your sidebar name +- Update the GitHub link `href` to your repository + +**Edit `docs/sidebars.ts`:** + +- Rename the sidebar (e.g., `MyProjectSidebar`) +- Define your documentation structure + +**Edit `docs/package.json`:** + +- Update the `name` field to match your project + +#### Step 4: Configure the sync workflow + +**Edit `.github/workflows/sync-docs.yml`:** + +Update the paths to match your project name: + +```yaml +- name: Sync folders + run: | + rm -rf target-repo/docs/docs-/* + rm -f target-repo/docs/sidebars-.ts + cp -r source-repo/docs/docs/* target-repo/docs/docs-/ + cp source-repo/docs/sidebars.ts target-repo/docs/sidebars-.ts + cd target-repo + git config user.name github-actions + git config user.email github-actions@github.com + git add . + if ! (git diff --quiet && git diff --staged --quiet); then git commit -m "Sync docs from repo" && git push; fi +``` + +#### Step 5: Add documentation CI job (if not present) + +Ensure your `.github/workflows/ci.yml` includes: + +1. **File change detection** for documentation files (in `file-filters.yml`): + + ```yaml + doc_files: &doc_files + - "docs/**" + - package.json + - package-lock.json + + documentation_all: + - *doc_files + - *markdown_all + ``` + +2. **Documentation build job** to verify docs build successfully before merge + +3. **Vale style validation job** (optional but recommended) + +#### Step 6: Add the PAT_TOKEN secret + +The sync workflow requires a `PAT_TOKEN` secret with write access to `opsmill/infrahub-docs`. Request this from a repository admin. + +### Part 2: Configure the Aggregation Repository (infrahub-docs) + +These steps are performed in this repository (`opsmill/infrahub-docs`). + +#### Step 1: Create placeholder files + +```bash +# Create the docs directory for synced content +mkdir -p docs/docs- + +# Create a placeholder file (will be overwritten by sync) +touch docs/docs-/readme.mdx + +# Create the sidebar configuration file (will be overwritten by sync) touch docs/sidebars-.ts -mkdir docs/docs- -touch docs/docs-/readme.mdx ### Placeholder -vi docs/docusaurus.config.ts ### Add a plugin and navbar entry ``` -### Remaining tasks +#### Step 2: Add plugin configuration + +Edit `docs/docusaurus.config.ts` to add your new documentation section. + +**Add the sidebar import** at the top of the file: + +```typescript +import sidebars from "./sidebars-"; +``` + +**Add a new plugin** in the `plugins` array: + +```typescript +[ + "@docusaurus/plugin-content-docs", + { + id: "", + path: "docs-", + routeBasePath: "", + sidebarPath: "./sidebars-.ts", + editUrl: "https://github.com/opsmill//tree/main/docs", + }, +], +``` + +**Add a navbar entry** in `themeConfig.navbar.items`: + +```typescript +{ + type: "docSidebar", + sidebarId: "Sidebar", // Must match sidebar name in source repo + docsPluginId: "", + position: "left", + label: "", +}, +``` + +Or add to an existing dropdown menu if appropriate. + +### Part 3: Final Setup + +#### Step 1: Set up Cloudflare Pages integration + +Configure Cloudflare Pages to build and deploy the documentation site. Contact the SRE team for access or help. + +#### Step 2: Create pull requests and test + +1. Create a PR in your source repository with the documentation setup +2. Create a PR in `infrahub-docs` with the plugin configuration +3. Merge the `infrahub-docs` PR first +4. Merge the source repository PR +5. Verify the sync workflow runs and documentation appears on the site + +### Troubleshooting + +**Sync workflow fails with permission errors:** + +- Verify the `PAT_TOKEN` secret is configured and has write access to `infrahub-docs` + +**Documentation build fails in CI:** + +- Run `cd docs && npm install && npm run build` locally to debug +- Check for broken links or invalid MDX syntax + +**Content not appearing after sync:** -- Setup Cloudflare Pages Integration -- Create PRs and test +- Verify the plugin ID and sidebar configuration match +- Check that the sync workflow completed successfully diff --git a/pyproject.toml b/pyproject.toml index 282cb3ca..31103146 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,3 +21,4 @@ build-backend = "poetry.core.masonry.api" [tool.ruff] line-length = 120 +exclude = ["infrahub/"] diff --git a/scripts/setup-docs-repo.py b/scripts/setup-docs-repo.py new file mode 100755 index 00000000..d677b5a8 --- /dev/null +++ b/scripts/setup-docs-repo.py @@ -0,0 +1,498 @@ +#!/usr/bin/env python3 +"""Setup documentation syncing for a new OpsMill repository. + +This script automates the setup of documentation infrastructure for a new +repository that will sync its docs to the infrahub-docs aggregation site. + +It can: +1. Set up the source repository with all required files for documentation +2. Set up the infrahub-docs repository with the plugin configuration +3. Do both at once + +Usage: + # Setup source repo only + python setup-docs-repo.py --source-repo /path/to/my-new-project + + # Setup infrahub-docs only + python setup-docs-repo.py --infrahub-docs-repo /path/to/infrahub-docs --project-name my-project + + # Setup both + python setup-docs-repo.py --source-repo /path/to/my-new-project --infrahub-docs-repo /path/to/infrahub-docs + + # With display name + python setup-docs-repo.py --source-repo /path/to/my-new-project --display-name "My Project" +""" + +import argparse +import re +import shutil +import stat +import sys +from pathlib import Path + +# Get the templates directory (relative to this script) +SCRIPT_DIR = Path(__file__).resolve().parent +TEMPLATES_DIR = SCRIPT_DIR / "templates" + +# Files to copy from templates (relative paths) +# Files with {{PLACEHOLDER}} patterns will have substitutions applied +TEMPLATE_FILES = [ + # Documentation framework + "docs/.gitignore", + "docs/babel.config.js", + "docs/docusaurus.config.ts", + "docs/package.json", + "docs/sidebars.ts", + "docs/tsconfig.json", + "docs/src/css/custom.css", + "docs/static/img/favicon.ico", + "docs/static/img/infrahub-hori.svg", + "docs/static/img/infrahub-hori-dark.svg", + "docs/docs/readme.mdx", + # Linting configuration + ".vale/", + ".vale.ini", + ".markdownlint.yaml", + ".yamllint.yml", + # GitHub Actions + ".github/build-docs.sh", + ".github/file-filters.yml", + ".github/labeler.yml", + ".github/labels.yml", + ".github/workflows/sync-docs.yml", +] + +# CI workflow jobs that can be added to an existing ci.yml +CI_WORKFLOW_JOBS = """ + documentation: + defaults: + run: + working-directory: ./docs + if: | + always() && !cancelled() && + !contains(needs.*.result, 'failure') && + !contains(needs.*.result, 'cancelled') && + needs.files-changed.outputs.documentation == 'true' + needs: ["files-changed", "yaml-lint", "python-lint", "markdown-lint"] + runs-on: "ubuntu-22.04" + timeout-minutes: 5 + steps: + - uses: actions/checkout@v5 + with: + submodules: true + - uses: actions/setup-node@v6 + with: + node-version: 20 + cache: 'npm' + cache-dependency-path: docs/package-lock.json + - name: "Install dependencies" + working-directory: docs + run: npm install + - name: "Build docs website" + run: npm run build + + validate-documentation-style: + if: | + always() && !cancelled() && + !contains(needs.*.result, 'failure') && + !contains(needs.*.result, 'cancelled') + needs: ["files-changed", "yaml-lint", "python-lint", "markdown-lint"] + runs-on: "ubuntu-22.04" + timeout-minutes: 5 + env: + VALE_VERSION: "3.13.0" + steps: + - uses: "actions/checkout@v5" + with: + submodules: true + - name: Download Vale + run: | + curl -sL "https://github.com/errata-ai/vale/releases/download/v${VALE_VERSION}/vale_${VALE_VERSION}_Linux_64-bit.tar.gz" -o vale.tar.gz + tar -xzf vale.tar.gz + - name: "Validate documentation style" + run: ./vale $(find ./docs/docs -type f \\( -name "*.mdx" -o -name "*.md" \\) ) +""" + + +def get_project_name(repo_path: Path) -> str: + """Extract project name from repository path.""" + return repo_path.name + + +def get_project_name_pascal(name: str) -> str: + """Convert project name to PascalCase for sidebar names. + + Args: + name: Project name (e.g., 'my-new-project'). + + Returns: + PascalCase version (e.g., 'MyNewProject'). + """ + return "".join(word.capitalize() for word in re.split(r"[-_]", name)) + + +def copy_and_substitute( + src: Path, + dst: Path, + substitutions: dict[str, str], +) -> None: + """Copy a file or directory, applying substitutions to text files. + + Args: + src: Source path. + dst: Destination path. + substitutions: Dictionary of placeholder -> replacement pairs. + """ + dst.parent.mkdir(parents=True, exist_ok=True) + + if src.is_dir(): + if dst.exists(): + shutil.rmtree(dst) + shutil.copytree(src, dst) + # Apply substitutions to all text files in the directory + for file_path in dst.rglob("*"): + if file_path.is_file(): + apply_substitutions_to_file(file_path, substitutions) + else: + shutil.copy2(src, dst) + apply_substitutions_to_file(dst, substitutions) + + +def apply_substitutions_to_file(file_path: Path, substitutions: dict[str, str]) -> None: + """Apply placeholder substitutions to a file if it's a text file. + + Args: + file_path: Path to the file. + substitutions: Dictionary of placeholder -> replacement pairs. + """ + # Skip binary files + binary_extensions = {".ico", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".woff", ".woff2"} + if file_path.suffix.lower() in binary_extensions: + return + + try: + content = file_path.read_text() + original_content = content + + for placeholder, replacement in substitutions.items(): + content = content.replace(placeholder, replacement) + + # Only write if content changed + if content != original_content: + file_path.write_text(content) + except UnicodeDecodeError: + # Skip binary files that weren't caught by extension check + pass + + +def setup_source_repo( + source_repo: Path, + project_name: str, + display_name: str, +) -> None: + """Set up documentation infrastructure in the source repository. + + Args: + source_repo: Path to the source repository to set up. + project_name: Name of the project (used for paths and URLs). + display_name: Human-readable display name for the project. + """ + print(f"\n{'=' * 60}") + print(f"Setting up source repository: {source_repo}") + print(f"Project name: {project_name}") + print(f"Display name: {display_name}") + print("=" * 60) + + if not TEMPLATES_DIR.exists(): + print(f"ERROR: Templates directory not found: {TEMPLATES_DIR}") + sys.exit(1) + + project_pascal = get_project_name_pascal(project_name) + + # Define substitutions + substitutions = { + "{{PROJECT_NAME}}": project_name, + "{{PROJECT_PASCAL}}": project_pascal, + "{{PROJECT_DISPLAY_NAME}}": display_name, + } + + # Copy template files + print("\nCopying template files...") + for rel_path in TEMPLATE_FILES: + src = TEMPLATES_DIR / rel_path + dst = source_repo / rel_path + + if not src.exists(): + print(f" WARNING: Template file not found: {rel_path}") + continue + + print(f" {rel_path}") + copy_and_substitute(src, dst, substitutions) + + # Make build script executable + build_script = source_repo / ".github" / "build-docs.sh" + if build_script.exists(): + build_script.chmod(build_script.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + print(" Made .github/build-docs.sh executable") + + print("\n" + "=" * 60) + print("Source repository setup complete!") + print("=" * 60) + print("\nNext steps for source repository:") + print("1. Review and customize docs/docusaurus.config.ts") + print("2. Update docs/sidebars.ts with your documentation structure") + print("3. Add your documentation content to docs/docs/") + print("4. Add PAT_TOKEN secret to GitHub repository settings") + print("5. Test locally: cd docs && npm install && npm start") + print("\nOptional: Add CI workflow jobs for documentation building") + print("See the CI_WORKFLOW_JOBS constant in this script for examples") + + +def setup_infrahub_docs( + infrahub_docs_repo: Path, + project_name: str, + source_repo_name: str | None = None, + display_name: str | None = None, + navbar_dropdown: str | None = None, +) -> None: + """Set up the infrahub-docs repository for a new documentation section. + + Args: + infrahub_docs_repo: Path to the infrahub-docs repository. + project_name: Name used for paths (e.g., 'my-project' -> docs-my-project). + source_repo_name: GitHub repo name if different from project_name. + display_name: Display name for navbar (defaults to project_name). + navbar_dropdown: Which dropdown to add to ('tools', 'integrations', 'demos'). + """ + print(f"\n{'=' * 60}") + print(f"Setting up infrahub-docs repository: {infrahub_docs_repo}") + print(f"Project name: {project_name}") + print("=" * 60) + + if not infrahub_docs_repo.exists(): + print(f"ERROR: infrahub-docs repository not found: {infrahub_docs_repo}") + sys.exit(1) + + docs_dir = infrahub_docs_repo / "docs" + if not docs_dir.exists(): + print(f"ERROR: docs directory not found in infrahub-docs: {docs_dir}") + sys.exit(1) + + project_pascal = get_project_name_pascal(project_name) + source_repo = source_repo_name or project_name + display = display_name or project_name + + # Create placeholder directory and files + print("\nCreating placeholder files...") + docs_project_dir = docs_dir / f"docs-{project_name}" + docs_project_dir.mkdir(parents=True, exist_ok=True) + + placeholder = docs_project_dir / "readme.mdx" + if not placeholder.exists(): + placeholder.write_text(f"""--- +title: {display} +--- + +# {display} + +This documentation will be synced from the source repository. +""") + print(f" Created docs/docs-{project_name}/readme.mdx") + + # Create sidebar file + sidebar_file = docs_dir / f"sidebars-{project_name}.ts" + if not sidebar_file.exists(): + sidebar_file.write_text(f"""import type {{SidebarsConfig}} from '@docusaurus/plugin-content-docs'; + +const sidebars: SidebarsConfig = {{ + {project_pascal}Sidebar: [ + {{ + type: 'autogenerated', + dirName: '.', + }}, + ] +}}; + +export default sidebars; +""") + print(f" Created docs/sidebars-{project_name}.ts") + + # Generate the plugin configuration snippet + plugin_config = f""" [ + '@docusaurus/plugin-content-docs', + {{ + id: 'docs-{project_name}', + path: 'docs-{project_name}', + routeBasePath: '{project_name}', + sidebarCollapsed: false, + sidebarPath: './sidebars-{project_name}.ts', + editUrl: 'https://github.com/opsmill/{source_repo}/tree/main/docs', + }}, + ],""" + + # Generate navbar entry + navbar_entry = f""" {{ + type: "docSidebar", + sidebarId: "{project_pascal}Sidebar", + label: "{display}", + docsPluginId: "docs-{project_name}", + }},""" + + print("\n" + "=" * 60) + print("Placeholder files created!") + print("=" * 60) + print("\nYou need to manually add the following to docs/docusaurus.config.ts:") + print("\n1. Add this plugin configuration in the 'plugins' array:") + print("-" * 40) + print(plugin_config) + print("-" * 40) + print("\n2. Add this navbar entry (in appropriate dropdown or top-level):") + print("-" * 40) + print(navbar_entry) + print("-" * 40) + + # Optionally try to auto-insert (with user confirmation) + config_file = docs_dir / "docusaurus.config.ts" + if config_file.exists(): + print("\nWould you like to attempt automatic insertion? (y/n): ", end="") + try: + response = input().strip().lower() + if response == "y": + insert_plugin_config(config_file, plugin_config, navbar_entry, navbar_dropdown) + except EOFError: + print("\nSkipping automatic insertion (non-interactive mode)") + + +def insert_plugin_config( + config_file: Path, + plugin_config: str, + navbar_entry: str, + navbar_dropdown: str | None, +) -> None: + """Attempt to automatically insert plugin configuration. + + Args: + config_file: Path to docusaurus.config.ts. + plugin_config: Plugin configuration snippet to insert. + navbar_entry: Navbar entry snippet to insert. + navbar_dropdown: Which dropdown to add to, or None for manual. + """ + content = config_file.read_text() + + # Find a good insertion point for the plugin (before google-tag-manager plugin) + plugin_marker = "@docusaurus/plugin-google-tag-manager" + if plugin_marker in content: + # Find the line with the marker and insert before it + lines = content.split("\n") + new_lines = [] + inserted = False + for line in lines: + if plugin_marker in line and not inserted: + # Insert our plugin before this one + new_lines.append(plugin_config) + inserted = True + new_lines.append(line) + + if inserted: + content = "\n".join(new_lines) + config_file.write_text(content) + print(" Inserted plugin configuration") + else: + print(" WARNING: Could not find insertion point for plugin") + else: + print(" WARNING: Could not find plugin insertion marker") + + print("\n NOTE: Navbar entry must be added manually to the appropriate location") + print(f" Suggested dropdown: {navbar_dropdown or 'top-level or new dropdown'}") + + +def main() -> None: + """Main entry point for the script.""" + parser = argparse.ArgumentParser( + description="Setup documentation syncing for a new OpsMill repository", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__doc__, + ) + + parser.add_argument( + "--source-repo", + type=Path, + help="Path to the source repository to set up with documentation infrastructure", + ) + parser.add_argument( + "--infrahub-docs-repo", + type=Path, + help="Path to the infrahub-docs repository", + ) + parser.add_argument( + "--project-name", + type=str, + help="Project name (default: derived from source repo directory name)", + ) + parser.add_argument( + "--display-name", + type=str, + help="Display name for navbar (default: project name)", + ) + parser.add_argument( + "--source-repo-name", + type=str, + help="GitHub repository name if different from project name", + ) + parser.add_argument( + "--navbar-dropdown", + type=str, + choices=["tools", "integrations", "demos"], + help="Which navbar dropdown to add the entry to", + ) + + args = parser.parse_args() + + if not args.source_repo and not args.infrahub_docs_repo: + parser.print_help() + print("\nERROR: Must specify at least one of --source-repo or --infrahub-docs-repo") + sys.exit(1) + + # Determine project name + if args.project_name: + project_name = args.project_name + elif args.source_repo: + project_name = get_project_name(args.source_repo.resolve()) + else: + print("ERROR: --project-name is required when only setting up infrahub-docs") + sys.exit(1) + + # Determine display name + display_name = args.display_name or project_name + + # Setup source repo if requested + if args.source_repo: + setup_source_repo( + args.source_repo.resolve(), + project_name, + display_name, + ) + + # Setup infrahub-docs if requested + if args.infrahub_docs_repo: + setup_infrahub_docs( + args.infrahub_docs_repo.resolve(), + project_name, + source_repo_name=args.source_repo_name, + display_name=display_name, + navbar_dropdown=args.navbar_dropdown, + ) + + print("\n" + "=" * 60) + print("Setup complete!") + print("=" * 60) + print("\nRemember to:") + print("1. Review all generated/modified files") + print("2. Add PAT_TOKEN secret to the source repository") + print("3. Create PRs in both repositories") + print("4. Merge infrahub-docs PR first, then source repo PR") + print("5. Set up Cloudflare Pages integration if needed") + + +if __name__ == "__main__": + main() diff --git a/scripts/templates/.github/build-docs.sh b/scripts/templates/.github/build-docs.sh new file mode 100755 index 00000000..bc55137a --- /dev/null +++ b/scripts/templates/.github/build-docs.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +cd docs && npm install && npm run build diff --git a/scripts/templates/.github/file-filters.yml b/scripts/templates/.github/file-filters.yml new file mode 100644 index 00000000..0c04075b --- /dev/null +++ b/scripts/templates/.github/file-filters.yml @@ -0,0 +1,25 @@ +--- +ci_config: &ci_config + - ".github/workflows/ci.yml" + - ".github/file-filters.yml" + +github_workflows: &github_workflows + - ".github/workflows/*.yml" + +doc_files: &doc_files + - "docs/**" + - package.json + - package-lock.json + +python_all: &python_all + - "**/*.py" + +yaml_all: &yaml_all + - "**/*.{yml,yaml}" + +markdown_all: &markdown_all + - "**/*.{md,mdx}" + +documentation_all: + - *doc_files + - *markdown_all diff --git a/scripts/templates/.github/labeler.yml b/scripts/templates/.github/labeler.yml new file mode 100644 index 00000000..581966f3 --- /dev/null +++ b/scripts/templates/.github/labeler.yml @@ -0,0 +1,8 @@ +--- +"group/ci": + - changed-files: + - any-glob-to-any-file: [".github/**"] + +"type/documentation": + - changed-files: + - any-glob-to-any-file: ["docs/**"] diff --git a/scripts/templates/.github/labels.yml b/scripts/templates/.github/labels.yml new file mode 100644 index 00000000..9dc5baa1 --- /dev/null +++ b/scripts/templates/.github/labels.yml @@ -0,0 +1,184 @@ +--- +# Labels names are important as they are used by Release Drafter to decide +# regarding where to record them in changelog or if to skip them. +# +# The repository labels will be automatically configured using this file and +# the GitHub Action https://github.com/marketplace/actions/github-labeler. + +# ---------------------------------- +# GROUP +# ---------------------------------- +- name: "group/backend" + description: "Issue related to the backend (API Server, Git Agent)" + color: "b60205" + +- name: "group/frontend" + description: "Issue related to the frontend (React)" + color: "353755" + +- name: "group/ux-design" + description: "Issue related to design or UX" + color: "3380ff" + +- name: "group/sync-engine" + description: "Issue related to the Synchronization engine" + color: "05b259" + +- name: "group/ci" + description: "Issue related to the CI pipeline" + color: "f25009" + +- name: "group/helm" + description: "Issue related to the Helm chart" + color: "326ce5" + +- name: "group/schema" + description: "Issue related to some schemas" + color: "fbceb1" + +# ---------------------------------- +# TYPE INFRAHUB (default) +# ---------------------------------- +- name: "type/feature" + description: "New feature or request" + color: "a2eeef" + +- name: "type/bug" + description: "Something isn't working as expected" + color: "d73a4a" + +- name: "type/housekeeping" + description: "Maintenance task" + color: "fef2c0" + +- name: "type/tech-debt" + description: "Item we know we need to improve way it is implemented" + color: "73FA20" + +# ---------------------------------- +# TYPE ALL +# ---------------------------------- +- name: "type/question" + description: "Question or discussion" + color: "d876e3" + +- name: "type/documentation" + description: "Improvements or additions to documentation" + color: "0075ca" + +- name: "type/duplicate" + description: "This issue or pull request already exists" + color: "cfd3d7" + +- name: "type/epic" + description: "Large body of work composed of multiple tasks and/or features" + color: "fbd39c" + +- name: "type/task" + description: "Body of work related to an epic" + color: "0b8e92" + +- name: "type/ecosystem" + description: "Effort related to the community, usually not part of the core project" + color: "0b5aa9" + +- name: "type/brainstorming" + description: "Topic for brainstorming, discussions" + color: "e19e0f" + +- name: "type/user-centric" + description: "Issue that would improve the overall experience of our users" + color: "ff8c00" + +- name: "type/newcomers" + description: "Good for newcomers" + color: "309c56" + +# ---------------------------------- +# EFFORT +# ---------------------------------- +- name: "effort/high" + description: "This issue should be completed in more than a day" + color: "b60205" + +- name: "effort/medium" + description: "This issue should be completed in a less than a day" + color: "ff9f1c" + +- name: "effort/low" + description: "This issue should be completed in a couple of hours" + color: "ffcc00" + +# ---------------------------------- +# SEVERITY +# ---------------------------------- +- name: "priority/1" + description: "This issue must be fixed/implemented ASAP, it's a blocker for a release" + color: "b60205" + +- name: "priority/2" + description: "This issue stalls work on the project or its dependents, it's a blocker for a release" + color: "ff9f1c" + +- name: "priority/3" + description: "Not blocking but should be fixed soon" + color: "ffcc00" + +- name: "priority/4" + description: "Low priority and doesn't need to be rushed" + color: "cfda2c" + +# ---------------------------------- +# STATUS +# ---------------------------------- +- name: "state/blocked" + description: "The issue is currently blocked and cannot be resolved." + color: "e34918" + +- name: "state/draft" + description: "The redaction of the issue is still a work in progress" + color: "dcb518" + +- name: "state/need-triage" + description: "This issue needs to be triaged" + color: "333333" + +- name: "state/need-more-info" + description: "This issue needs more information" + color: "cccccc" + +- name: "state/need-testing" + description: "This issue is ready and needs to be tested." + color: "cccccc" + +- name: "state/backlog" + description: "This issue is part of the backlog" + color: "eeeeee" + +- name: "state/planned" + description: "This issue is planned to be worked on in an upcoming release." + color: "eeeeee" + +- name: "state/not-reproducible" + description: "Unable to reproduce this issue." + color: "eeeeee" + +- name: "state/wont-fix" + description: "Issue has been closed and won't be fix/implemented." + color: "eeeeee" + +# DEPRECATED +- name: "state/ref" + description: "This issue is referenced in our internal tooling" + color: "c9510c" + +# ---------------------------------- +# CI +# ---------------------------------- +- name: "ci/skip-changelog" + description: "Don't include this PR in the changelog" + color: "c30664" + +- name: "cd/preview" + description: "Deploy preview branch" + color: "d77e96" diff --git a/scripts/templates/.github/workflows/sync-docs.yml b/scripts/templates/.github/workflows/sync-docs.yml new file mode 100644 index 00000000..86e7ff7c --- /dev/null +++ b/scripts/templates/.github/workflows/sync-docs.yml @@ -0,0 +1,39 @@ +--- +name: Sync Folders + +"on": + push: + branches: + - main + paths: + - 'docs/docs/**' + - 'docs/sidebars.ts' + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: Checkout source repository + uses: actions/checkout@v5 + with: + path: source-repo + + - name: Checkout target repository + uses: actions/checkout@v5 + with: + repository: opsmill/infrahub-docs + token: ${{ secrets.PAT_TOKEN }} + path: target-repo + + - name: Sync folders + run: | + rm -rf target-repo/docs/docs-{{PROJECT_NAME}}/* + rm -f target-repo/docs/sidebars-{{PROJECT_NAME}}.ts + cp -r source-repo/docs/docs/* target-repo/docs/docs-{{PROJECT_NAME}}/ + cp source-repo/docs/sidebars.ts target-repo/docs/sidebars-{{PROJECT_NAME}}.ts + cd target-repo + git config user.name github-actions + git config user.email github-actions@github.com + git add . + git diff --quiet && git diff --staged --quiet || \ + git commit -m "Sync docs from {{PROJECT_NAME}} repo" && git push diff --git a/scripts/templates/.markdownlint.yaml b/scripts/templates/.markdownlint.yaml new file mode 100644 index 00000000..9b33d226 --- /dev/null +++ b/scripts/templates/.markdownlint.yaml @@ -0,0 +1,13 @@ +--- +default: true +MD013: false # disables max line-length +MD014: false # dollar signs used before commands +MD024: false # disables 'no duplicate headings', which we use in tabs for instructions +MD025: + front_matter_title: "" # prevent collisions with h1s and frontmatter titles +MD029: false # allows manually creating ordered lists +MD033: false # allows inline html to override markdown styles +MD034: false # no-bare-urls +MD041: false # allow 1st line to not be a top-level heading (required for Towncrier) +MD045: false # no alt text around images +MD047: false # single trailing newline diff --git a/scripts/templates/.vale.ini b/scripts/templates/.vale.ini new file mode 100644 index 00000000..700cbe87 --- /dev/null +++ b/scripts/templates/.vale.ini @@ -0,0 +1,15 @@ +StylesPath = .vale/styles + +MinAlertLevel = warning + +[formats] +mdx = md + +[docs/**/*.md] +BasedOnStyles = Infrahub +;(import.*?\n) to ignore import statement in .mdx +;(```.*?```\n) to ignore code block in .mdx +BlockIgnores = (?s) *((import.*?\n)|(```.*?```\n)) + +[*] +BasedOnStyles = Infrahub diff --git a/scripts/templates/.vale/styles/Infrahub/branded-terms-case-swap.yml b/scripts/templates/.vale/styles/Infrahub/branded-terms-case-swap.yml new file mode 100644 index 00000000..82d87577 --- /dev/null +++ b/scripts/templates/.vale/styles/Infrahub/branded-terms-case-swap.yml @@ -0,0 +1,14 @@ +--- +extends: substitution +message: Use '%s' instead of '%s' +level: error +ignorecase: false +action: + name: replace +swap: + '(?i:(?/ pathname under which your site is served + baseUrl: '/', + + organizationName: 'opsmill', + projectName: '{{PROJECT_NAME}}', + + onBrokenLinks: 'throw', + onDuplicateRoutes: "throw", + markdown: { + mermaid: true, + hooks: { + onBrokenMarkdownLinks: 'warn', + }, + }, + themes: ['@docusaurus/theme-mermaid'], + + i18n: { + defaultLocale: 'en', + locales: ['en'], + }, + + presets: [ + [ + "classic", + { + docs: { + editUrl: "https://github.com/opsmill/{{PROJECT_NAME}}/tree/main/docs", + routeBasePath: "/", + sidebarCollapsed: true, + sidebarPath: "./sidebars.ts", + }, + blog: false, + theme: { + customCss: "./src/css/custom.css", + }, + } satisfies Preset.Options, + ], + ], + + themeConfig: { + navbar: { + logo: { + alt: "Infrahub", + src: "img/infrahub-hori.svg", + srcDark: "img/infrahub-hori-dark.svg", + }, + items: [ + { + type: "docSidebar", + sidebarId: "{{PROJECT_PASCAL}}Sidebar", + position: "left", + label: "{{PROJECT_DISPLAY_NAME}}", + }, + { + href: "https://github.com/opsmill/{{PROJECT_NAME}}", + position: "right", + className: "header-github-link", + "aria-label": "GitHub repository", + }, + ], + }, + footer: { + copyright: `Copyright © ${new Date().getFullYear()} - Infrahub by OpsMill.`, + }, + prism: { + theme: prismThemes.oneDark, + additionalLanguages: ["bash", "python", "markup-templating", "django", "json", "toml", "yaml"], + }, + } satisfies Preset.ThemeConfig, +}; + +export default config; diff --git a/scripts/templates/docs/package.json b/scripts/templates/docs/package.json new file mode 100644 index 00000000..fe139cb3 --- /dev/null +++ b/scripts/templates/docs/package.json @@ -0,0 +1,46 @@ +{ + "name": "{{PROJECT_NAME}}-docs", + "version": "0.0.0", + "private": true, + "scripts": { + "docusaurus": "docusaurus", + "start": "docusaurus start", + "build": "docusaurus build", + "swizzle": "docusaurus swizzle", + "deploy": "docusaurus deploy", + "clear": "docusaurus clear", + "serve": "docusaurus serve", + "write-translations": "docusaurus write-translations", + "write-heading-ids": "docusaurus write-heading-ids" + }, + "dependencies": { + "@docusaurus/core": "3.6.3", + "@docusaurus/preset-classic": "3.6.3", + "@docusaurus/theme-mermaid": "^3.6.3", + "@mdx-js/react": "^3.0.0", + "clsx": "^2.0.0", + "prism-react-renderer": "^2.3.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "devDependencies": { + "@docusaurus/module-type-aliases": "3.6.3", + "@docusaurus/types": "3.6.3", + "typescript": "~5.6.2" + }, + "browserslist": { + "production": [ + ">0.5%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 3 chrome version", + "last 3 firefox version", + "last 5 safari version" + ] + }, + "engines": { + "node": ">=18.0" + } +} diff --git a/scripts/templates/docs/sidebars.ts b/scripts/templates/docs/sidebars.ts new file mode 100644 index 00000000..afe50149 --- /dev/null +++ b/scripts/templates/docs/sidebars.ts @@ -0,0 +1,12 @@ +import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; + +const sidebars: SidebarsConfig = { + {{PROJECT_PASCAL}}Sidebar: [ + { + type: 'autogenerated', + dirName: '.', + }, + ] +}; + +export default sidebars; diff --git a/scripts/templates/docs/src/css/custom.css b/scripts/templates/docs/src/css/custom.css new file mode 100644 index 00000000..6f5b865e --- /dev/null +++ b/scripts/templates/docs/src/css/custom.css @@ -0,0 +1,146 @@ +/** + * Any CSS included here will be global. The classic template + * bundles Infima by default. Infima is a CSS framework designed to + * work well for content-centric websites. + */ + +/* You can override the default Infima variables here. */ +:root { + --ifm-color-primary: #08738f; + --ifm-color-primary-dark: #076781; + --ifm-color-primary-darker: #07627a; + --ifm-color-primary-darkest: #065064; + --ifm-color-primary-light: #097e9d; + --ifm-color-primary-lighter: #0984a4; + --ifm-color-primary-lightest: #0a95ba; + --ifm-code-font-size: 95%; + --docusaurus-highlighted-code-line-bg: rgb(24 65 100 / 0.85); + --ifm-hover-overlay: rgb(242, 252, 255); + --main-bg-color: #f9f9f9; + --layout-bg-color: #fff; + --border-color: #e5e7eb; + + --navbar-bg-opacity: 1; + --ifm-navbar-background-color: rgb(255 255 255 / var(--navbar-bg-opacity)); + --ifm-code-padding-horizontal: 0.3rem; +} + +/* For readability concerns, you should choose a lighter palette in dark mode. */ +[data-theme='dark'] { + --ifm-color-primary: #0a95ba; + --ifm-color-primary-dark: #0986a7; + --ifm-color-primary-darker: #087f9e; + --ifm-color-primary-darkest: #076882; + --ifm-color-primary-light: #0ba4cd; + --ifm-color-primary-lighter: #0babd6; + --ifm-color-primary-lightest: #0dc2f2; + --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); + --main-bg-color: #1e2327; + --layout-bg-color: #151a1c; + --border-color: rgb(71 71 71 / 0.5); + + --ifm-navbar-background-color: rgb(18 20 24 / var(--navbar-bg-opacity)); +} + +.header-github-link { + width: 32px; + height: 32px; + padding: 6px; + margin-right: 4px; + border-radius: 50%; + transition: background var(--ifm-transition-fast); +} + +.header-github-link:hover { + background: var(--ifm-color-emphasis-200); +} + +.header-github-link:before { + content: ''; + height: 100%; + display: block; + background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") no-repeat; +} + +html[data-theme='dark'] .header-github-link:before { + background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='white' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") no-repeat; +} + +.flex { + display: flex !important; +} + +.justify-between { + justify-content: space-between; +} + +.navbar { + box-shadow: none; + border-bottom: 1px solid var(--border-color); +} + +@media (min-width: 997px) { + :root { + --navbar-bg-opacity: 0.2; + } + + .navbar { + backdrop-filter: blur(10px); + } + + .menu { + font-weight: initial; + font-size: 0.875rem; + margin: 0.5rem; + border: 1px solid var(--border-color); + border-radius: 0.75rem; + background: var(--layout-bg-color); + padding: 0.75rem !important; + } +} + +.navbar__item { + color: var(--ifm-color-primary-darkest); +} + +.navbar__search-input { + border-radius: 0.375rem; + box-shadow: 0px 1px 0px #7b7b7b73, inset 0px 2px 8px 0px #c4c4c496; + border: 1px solid #aaaaaa; +} + +.main-wrapper { + background: var(--main-bg-color); +} + +body { + background: var(--layout-bg-color); +} + +.theme-doc-sidebar-container { + border: none !important; +} + +.theme-admonition { + border: 1px solid var(--border-color); + box-shadow: none; +} + +.tabs-container { + border: 1px solid var(--border-color); + padding: 0 1rem 1rem; + border-radius: 0.5rem; +} + +.footer { + border: 1px solid var(--border-color); + background-color: var(--layout-bg-color) +} + +.table-of-contents__link--active { + color: var(--ifm-breadcrumb-color-active); + background: var(--ifm-breadcrumb-item-background-active); + padding: 0.375rem 0.5rem; + border-radius: 0 var(--ifm-breadcrumb-border-radius) var(--ifm-breadcrumb-border-radius) 0; + border-left: 1px solid var(--ifm-breadcrumb-color-active); +} diff --git a/scripts/templates/docs/static/img/favicon.ico b/scripts/templates/docs/static/img/favicon.ico new file mode 100644 index 00000000..e8f21f7b Binary files /dev/null and b/scripts/templates/docs/static/img/favicon.ico differ diff --git a/scripts/templates/docs/static/img/infrahub-hori-dark.svg b/scripts/templates/docs/static/img/infrahub-hori-dark.svg new file mode 100644 index 00000000..2b3ec966 --- /dev/null +++ b/scripts/templates/docs/static/img/infrahub-hori-dark.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/templates/docs/static/img/infrahub-hori.svg b/scripts/templates/docs/static/img/infrahub-hori.svg new file mode 100644 index 00000000..8c4ade36 --- /dev/null +++ b/scripts/templates/docs/static/img/infrahub-hori.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/templates/docs/tsconfig.json b/scripts/templates/docs/tsconfig.json new file mode 100644 index 00000000..314eab8a --- /dev/null +++ b/scripts/templates/docs/tsconfig.json @@ -0,0 +1,7 @@ +{ + // This file is not used in compilation. It is here just for a nice editor experience. + "extends": "@docusaurus/tsconfig", + "compilerOptions": { + "baseUrl": "." + } +}