diff --git a/.github/workflows/assembler-preview.yml b/.github/workflows/assembler-preview.yml new file mode 100644 index 000000000..cc626e8da --- /dev/null +++ b/.github/workflows/assembler-preview.yml @@ -0,0 +1,154 @@ +name: assembler-preview + +on: + pull_request: + types: + - opened + - synchronize + - reopened + - labeled + workflow_dispatch: + inputs: + pr_number: + description: 'Pull Request number to build the assembler preview for' + required: true + type: string + +permissions: + contents: read + deployments: write + id-token: write + pull-requests: read + +concurrency: + group: assembler-preview-${{ github.event.pull_request.number || inputs.pr_number }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + env: + PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }} + steps: + - name: Get PR details + if: github.event_name == 'workflow_dispatch' + id: pr-details + uses: actions/github-script@v8 + env: + PR_NUMBER: ${{ inputs.pr_number }} + with: + result-encoding: json + script: | + const { owner, repo } = context.repo; + const prNumber = parseInt(process.env.PR_NUMBER, 10); + + if (isNaN(prNumber) || prNumber <= 0) { + core.setFailed(`Invalid PR number: ${process.env.PR_NUMBER}`); + return; + } + + try { + const { data: pr } = await github.rest.pulls.get({ + owner, + repo, + pull_number: prNumber, + }); + + return { + sha: pr.head.sha, + ref: pr.head.ref, + base_ref: pr.base.ref, + }; + } catch (error) { + core.setFailed(`Failed to get PR #${prNumber}: ${error.message}`); + } + + - name: Set PR SHA (workflow_dispatch) + id: pr-sha-dispatch + if: github.event_name == 'workflow_dispatch' + run: echo "sha=${{ fromJSON(steps.pr-details.outputs.result).sha }}" >> $GITHUB_OUTPUT + + - name: Set PR SHA (pull_request) + id: pr-sha-pr + if: github.event_name == 'pull_request' + run: echo "sha=${{ github.event.pull_request.head.sha }}" >> $GITHUB_OUTPUT + + - name: Resolve PR SHA + id: pr-sha + run: echo "sha=${{ steps.pr-sha-dispatch.outputs.sha || steps.pr-sha-pr.outputs.sha }}" >> $GITHUB_OUTPUT + + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ steps.pr-sha.outputs.sha }} + persist-credentials: false + + - name: Create Deployment + uses: actions/github-script@v8 + id: deployment + env: + PR_SHA: ${{ steps.pr-sha.outputs.sha }} + with: + result-encoding: string + script: | + const { owner, repo } = context.repo; + const prNumber = process.env.PR_NUMBER; + const environment = 'assembler-preview'; + const task = `assembler-preview-${prNumber}`; + const deployment = await github.rest.repos.createDeployment({ + owner, + repo, + environment, + task, + ref: process.env.PR_SHA, + auto_merge: false, + transient_environment: true, + required_contexts: [], + }) + await github.rest.repos.createDeploymentStatus({ + deployment_id: deployment.data.id, + owner, + repo, + state: "in_progress", + log_url: `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`, + }) + return deployment.data.id + + - name: Bootstrap Action Workspace + uses: elastic/docs-builder/.github/actions/bootstrap@main + + - name: Build assembled documentation + id: assembler-build + env: + ASSEMBLER_PREVIEW_PATH_PREFIX: ${{ github.repository }}/docs/${{ env.PR_NUMBER }} + run: | + echo "ASSEMBLER_PREVIEW_PATH_PREFIX=${ASSEMBLER_PREVIEW_PATH_PREFIX}" >> $GITHUB_ENV + yq -i ".environments.preview.path_prefix = \"${ASSEMBLER_PREVIEW_PATH_PREFIX}\"" config/assembler.yml + dotnet run --project src/tooling/docs-builder -- assemble --skip-private-repositories --environment preview + + - uses: elastic/docs-builder/.github/actions/aws-auth@main + + - name: Upload assembled docs to S3 + id: s3-upload + env: + AWS_RETRY_MODE: standard + AWS_MAX_ATTEMPTS: 6 + run: | + aws s3 sync .artifacts/assembly/${ASSEMBLER_PREVIEW_PATH_PREFIX} "s3://elastic-docs-v3-website-preview/${ASSEMBLER_PREVIEW_PATH_PREFIX}" --delete --no-follow-symlinks + aws cloudfront create-invalidation \ + --distribution-id EKT7LT5PM8RKS \ + --paths "/${ASSEMBLER_PREVIEW_PATH_PREFIX}" "/${ASSEMBLER_PREVIEW_PATH_PREFIX}/*" + + - name: Update Deployment Status + if: always() && steps.deployment.outputs.result + uses: actions/github-script@v8 + with: + script: | + await github.rest.repos.createDeploymentStatus({ + owner: context.repo.owner, + repo: context.repo.repo, + deployment_id: ${{ steps.deployment.outputs.result }}, + state: "${{ steps.s3-upload.outcome == 'success' && 'success' || 'failure' }}", + environment_url: `https://docs-v3-preview.elastic.dev/${process.env.ASSEMBLER_PREVIEW_PATH_PREFIX}`, + log_url: `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`, + }) diff --git a/config/assembler.yml b/config/assembler.yml index 40b2c7311..5c9106742 100644 --- a/config/assembler.yml +++ b/config/assembler.yml @@ -36,6 +36,14 @@ environments: path_prefix: docs feature_flags: SEARCH_OR_ASK_AI: true + preview: + uri: https://docs-v3-preview.elastic.dev + path_prefix: ${ASSEMBLER_PREVIEW_PATH_PREFIX} + content_source: current + google_tag_manager: + enabled: false + feature_flags: + SEARCH_OR_ASK_AI: true shared_configuration: stack: &stack diff --git a/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs b/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs index 2ac6eab21..09e78ddad 100644 --- a/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs +++ b/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs @@ -48,7 +48,7 @@ public class LlmMarkdownExporter : IMarkdownExporter public async ValueTask FinishExportAsync(IDirectoryInfo outputFolder, Cancel ctx) { - var outputDirectory = Path.Combine(outputFolder.FullName, "docs"); + var outputDirectory = outputFolder.FullName; var zipPath = Path.Combine(outputDirectory, "llm.zip"); // Create the llms.txt file with boilerplate content diff --git a/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuildService.cs b/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuildService.cs index 5e72221a7..e9e3524ce 100644 --- a/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuildService.cs +++ b/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuildService.cs @@ -120,7 +120,11 @@ Cancel ctx if (exporters.Contains(Exporter.Html)) { - var sitemapBuilder = new SitemapBuilder(navigation.NavigationItems, assembleContext.WriteFileSystem, assembleContext.OutputDirectory); + var pathPrefix = assembleContext.Environment.PathPrefix; + var outputWithPrefix = string.IsNullOrEmpty(pathPrefix) + ? assembleContext.OutputDirectory + : assembleContext.WriteFileSystem.DirectoryInfo.New(assembleContext.WriteFileSystem.Path.Combine(assembleContext.OutputDirectory.FullName, pathPrefix)); + var sitemapBuilder = new SitemapBuilder(navigation.NavigationItems, assembleContext.WriteFileSystem, outputWithPrefix); sitemapBuilder.Generate(); } @@ -140,9 +144,10 @@ Cancel ctx private static async Task EnhanceLlmsTxtFile(AssembleContext context, SiteNavigation navigation, LlmsNavigationEnhancer enhancer, Cancel ctx) { - var llmsTxtPath = Path.Combine(context.OutputDirectory.FullName, "docs", "llms.txt"); - var readFs = context.ReadFileSystem; + var pathPrefix = context.Environment.PathPrefix ?? "docs"; + var llmsTxtPath = readFs.Path.Combine(context.OutputDirectory.FullName, pathPrefix, "llms.txt"); + if (!readFs.File.Exists(llmsTxtPath)) return; // No llms.txt file to enhance diff --git a/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuilder.cs b/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuilder.cs index a6410ffbf..de2f94d81 100644 --- a/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuilder.cs +++ b/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuilder.cs @@ -75,7 +75,11 @@ public async Task BuildAllAsync(PublishEnvironment environment, FrozenDictionary foreach (var exporter in markdownExporters) { _logger.LogInformation("Calling FinishExportAsync on {ExporterName}", exporter.GetType().Name); - _ = await exporter.FinishExportAsync(context.OutputDirectory, ctx); + var pathPrefix = context.Environment.PathPrefix; + var outputWithPrefix = string.IsNullOrEmpty(pathPrefix) + ? context.OutputDirectory + : context.ReadFileSystem.DirectoryInfo.New(Path.Join(context.OutputDirectory.FullName, pathPrefix)); + _ = await exporter.FinishExportAsync(outputWithPrefix, ctx); } if (exportOptions.Contains(Exporter.Redirects)) diff --git a/src/services/Elastic.Documentation.Assembler/Building/SitemapBuilder.cs b/src/services/Elastic.Documentation.Assembler/Building/SitemapBuilder.cs index 2059be9d9..a45a2ec51 100644 --- a/src/services/Elastic.Documentation.Assembler/Building/SitemapBuilder.cs +++ b/src/services/Elastic.Documentation.Assembler/Building/SitemapBuilder.cs @@ -54,7 +54,7 @@ public void Generate() doc.Add(root); - using var fileStream = fileSystem.File.Create(Path.Combine(outputFolder.ToString() ?? string.Empty, "docs", "sitemap.xml")); + using var fileStream = fileSystem.File.Create(fileSystem.Path.Combine(outputFolder.FullName, "sitemap.xml")); doc.Save(fileStream); } diff --git a/src/services/Elastic.Documentation.Assembler/Sourcing/RepositorySourcesFetcher.cs b/src/services/Elastic.Documentation.Assembler/Sourcing/RepositorySourcesFetcher.cs index 5f7fa220e..90bb4066b 100644 --- a/src/services/Elastic.Documentation.Assembler/Sourcing/RepositorySourcesFetcher.cs +++ b/src/services/Elastic.Documentation.Assembler/Sourcing/RepositorySourcesFetcher.cs @@ -117,11 +117,15 @@ await context.WriteFileSystem.File.WriteAllTextAsync( }; } - public async Task WriteLinkRegistrySnapshot(LinkRegistry linkRegistrySnapshot, Cancel ctx = default) => await context.WriteFileSystem.File.WriteAllTextAsync( - Path.Combine(context.OutputDirectory.FullName, "docs", CheckoutResult.LinkRegistrySnapshotFileName), + public async Task WriteLinkRegistrySnapshot(LinkRegistry linkRegistrySnapshot, Cancel ctx = default) + { + var pathPrefix = context.Environment.PathPrefix ?? "docs"; + await context.WriteFileSystem.File.WriteAllTextAsync( + context.WriteFileSystem.Path.Combine(context.OutputDirectory.FullName, pathPrefix, CheckoutResult.LinkRegistrySnapshotFileName), LinkRegistry.Serialize(linkRegistrySnapshot), ctx ); + } }