diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml index ae35e3e9f0..4ebb3905d1 100644 --- a/.github/workflows/deploy-site.yml +++ b/.github/workflows/deploy-site.yml @@ -18,6 +18,11 @@ on: description: 'Tag in github/copilot-sdk monorepo (e.g., java/v1.0.1-java.0)' type: string required: true + artifact_version: + description: 'Java Maven artifact version. Defaults to the site version for older callers.' + type: string + required: false + default: '' permissions: contents: write @@ -80,23 +85,29 @@ jobs: echo "Compiling Java source from tag ${{ inputs.monorepo_tag }}" mvn clean compile test -DskipTests=false -Dcheckstyle.skip=true -B - # 5. Update hardcoded version strings in site content - # Cookbook files and other markdown pages embed JBang //DEPS lines with - # hardcoded artifact versions for direct browsing on GitHub. Update them - # to the version being released. (${project.version} references are - # handled by Maven resource filtering during mvn site.) - - name: Update version strings in site content + # 5. Prepare Java artifact versions and snapshot repository instructions + - name: Prepare site artifact versions + env: + VERSION: ${{ inputs.version }} + ARTIFACT_VERSION_INPUT: ${{ inputs.artifact_version }} run: | - VERSION="${{ inputs.version }}" - echo "Updating copilot-sdk-java version references to ${VERSION}" - find src/site/markdown -name "*.md" -type f -exec \ - sed -i "s|copilot-sdk-java:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-[a-z][a-z0-9-]*\.[0-9][0-9]*\)*|copilot-sdk-java:${VERSION}|g" {} \; + ARTIFACT_VERSION="${ARTIFACT_VERSION_INPUT}" + if [[ -z "${ARTIFACT_VERSION}" ]]; then + if [[ "${VERSION}" == *-* ]]; then + echo "::error::artifact_version is required for preview documentation" + exit 1 + fi + ARTIFACT_VERSION="${VERSION}" + fi + node scripts/prepare-site-versions.mjs \ + "${VERSION}" "${ARTIFACT_VERSION}" src/site/markdown + echo "ARTIFACT_VERSION=${ARTIFACT_VERSION}" >> "$GITHUB_ENV" # 6. Build the Maven Site using THIS repo's pom.xml + src/site/ - name: Build documentation site run: | - echo "Building site for version ${{ inputs.version }}" - mvn site -Dsite.version=${{ inputs.version }} -B + echo "Building site for version ${{ inputs.version }} with Java artifacts ${ARTIFACT_VERSION}" + mvn site -Dsite.version=${{ inputs.version }} -Dartifact.version="${ARTIFACT_VERSION}" -B # 7. Prepare gh-pages deployment directory - name: Prepare gh-pages directory diff --git a/pom.xml b/pom.xml index e34688255d..cd234fd0c5 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,8 @@ UTF-8 0.0.0-SNAPSHOT + + ${site.version} ${project.basedir}/monorepo/java diff --git a/scripts/prepare-site-versions.mjs b/scripts/prepare-site-versions.mjs new file mode 100644 index 0000000000..7e78127055 --- /dev/null +++ b/scripts/prepare-site-versions.mjs @@ -0,0 +1,117 @@ +import assert from "node:assert/strict"; +import { readFileSync, readdirSync, statSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { pathToFileURL } from "node:url"; + +const VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/; +const SNAPSHOT_REPOSITORY = "https://central.sonatype.com/repository/maven-snapshots/"; + +function markdownFiles(directory) { + return readdirSync(directory).flatMap((name) => { + const path = join(directory, name); + return statSync(path).isDirectory() + ? markdownFiles(path) + : path.endsWith(".md") + ? [path] + : []; + }); +} + +export function prepareSiteVersions(siteVersion, artifactVersion, markdownDirectory) { + assert.match(siteVersion, VERSION_PATTERN, "Invalid site version"); + assert.match(artifactVersion, VERSION_PATTERN, "Invalid artifact version"); + const siteBase = /^\d+\.\d+\.\d+/.exec(siteVersion)?.[0]; + if (artifactVersion.endsWith("-SNAPSHOT")) { + assert.equal( + artifactVersion, + `${siteBase}-SNAPSHOT`, + "Snapshot artifact version must use the site version's base" + ); + } else { + assert.equal( + artifactVersion, + siteVersion, + "Non-snapshot artifact version must match the site version" + ); + } + + const snapshot = artifactVersion.endsWith("-SNAPSHOT"); + const replacements = new Map([ + [ + "", + snapshot + ? `Snapshot builds require this repository: + +\`\`\`xml + + + central-snapshots + ${SNAPSHOT_REPOSITORY} + false + true + + +\`\`\`` + : "", + ], + [ + "", + snapshot + ? `Snapshot builds require this repository: + +\`\`\`groovy +repositories { + mavenCentral() + maven { + url = uri('${SNAPSHOT_REPOSITORY}') + mavenContent { snapshotsOnly() } + } +} +\`\`\`` + : "", + ], + [ + "//ARTIFACT_REPOSITORY", + snapshot ? `//REPOS central,central-snapshots=${SNAPSHOT_REPOSITORY}` : "", + ], + ]); + + let hardcodedCoordinates = 0; + const markerCounts = new Map([...replacements.keys()].map((marker) => [marker, 0])); + for (const path of markdownFiles(markdownDirectory)) { + let content = readFileSync(path, "utf8"); + content = content.replace( + /copilot-sdk-java:\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?/g, + () => { + hardcodedCoordinates++; + return `copilot-sdk-java:${artifactVersion}`; + } + ); + if (snapshot) { + const dependency = `//DEPS com.github:copilot-sdk-java:${artifactVersion}`; + content = content.replaceAll( + dependency, + `//REPOS central,central-snapshots=${SNAPSHOT_REPOSITORY}\n${dependency}` + ); + } + for (const [marker, replacement] of replacements) { + const count = content.split(marker).length - 1; + markerCounts.set(marker, markerCounts.get(marker) + count); + content = content.replaceAll(marker, replacement); + } + writeFileSync(path, content); + } + assert(hardcodedCoordinates > 0, "Expected hardcoded Java coordinates in site content"); + for (const [marker, count] of markerCounts) { + assert(count > 0, `Expected site marker: ${marker}`); + } +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + assert.equal( + process.argv.length, + 5, + "Usage: prepare-site-versions.mjs " + ); + prepareSiteVersions(process.argv[2], process.argv[3], process.argv[4]); +} diff --git a/scripts/prepare-site-versions.test.mjs b/scripts/prepare-site-versions.test.mjs new file mode 100644 index 0000000000..5a4eb5c918 --- /dev/null +++ b/scripts/prepare-site-versions.test.mjs @@ -0,0 +1,74 @@ +import assert from "node:assert/strict"; +import { cpSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import test from "node:test"; +import { prepareSiteVersions } from "./prepare-site-versions.mjs"; + +function fixture(t) { + const root = mkdtempSync(join(tmpdir(), "copilot-java-site-")); + t.after(() => rmSync(root, { recursive: true, force: true })); + const markdown = join(root, "markdown"); + cpSync(resolve("src/site/markdown"), markdown, { recursive: true }); + return markdown; +} + +test("renders preview dependency coordinates and snapshot repositories", (t) => { + const markdown = fixture(t); + prepareSiteVersions("1.0.15-preview.2", "1.0.15-SNAPSHOT", markdown); + + const gettingStarted = readFileSync(join(markdown, "getting-started.md"), "utf8"); + assert.match(gettingStarted, /\$\{artifact\.version\}<\/version>/); + assert.match(gettingStarted, /implementation 'com\.github:copilot-sdk-java:\$\{artifact\.version\}'/); + assert.match(gettingStarted, /false<\/enabled><\/releases>/); + assert.match(gettingStarted, /mavenContent \{ snapshotsOnly\(\) \}/); + assert.match( + gettingStarted, + /\/\/REPOS central,central-snapshots=https:\/\/central\.sonatype\.com\/repository\/maven-snapshots\// + ); + const cookbook = readFileSync(join(markdown, "cookbook", "managing-local-files.md"), "utf8"); + assert.match(cookbook, /copilot-sdk-java:1\.0\.15-SNAPSHOT/); + for (const match of cookbook.matchAll(/\/\/DEPS com\.github:copilot-sdk-java:1\.0\.15-SNAPSHOT/g)) { + assert.equal( + cookbook.slice(0, match.index).trimEnd().split("\n").at(-1), + "//REPOS central,central-snapshots=https://central.sonatype.com/repository/maven-snapshots/" + ); + } + + const cookbookContent = readFileSync(join(markdown, "cookbook", "error-handling.md"), "utf8"); + assert.equal( + cookbookContent.match(/\/\/REPOS central,central-snapshots=/g)?.length, + cookbookContent.match(/\/\/DEPS com\.github:copilot-sdk-java:1\.0\.15-SNAPSHOT/g)?.length + ); +}); + +test("keeps stable dependency rendering free of snapshot configuration", (t) => { + const markdown = fixture(t); + prepareSiteVersions("1.0.15", "1.0.15", markdown); + + const gettingStarted = readFileSync(join(markdown, "getting-started.md"), "utf8"); + assert.doesNotMatch(gettingStarted, /maven-snapshots/); + assert.doesNotMatch(gettingStarted, /ARTIFACT_/); + const cookbook = readFileSync(join(markdown, "cookbook", "managing-local-files.md"), "utf8"); + assert.match(cookbook, /copilot-sdk-java:1\.0\.15/); +}); + +test("rejects an artifact version from another shared base", (t) => { + const markdown = fixture(t); + assert.throws( + () => prepareSiteVersions("1.0.15-preview.2", "1.0.16-SNAPSHOT", markdown), + /site version's base/ + ); +}); + +test("passes artifact inputs through the workflow environment and detects previews by version", () => { + const workflow = readFileSync(resolve(".github/workflows/deploy-site.yml"), "utf8"); + const prepareStep = workflow.slice( + workflow.indexOf("- name: Prepare site artifact versions"), + workflow.indexOf("# 6. Build the Maven Site") + ); + assert.match(prepareStep, /ARTIFACT_VERSION_INPUT: \$\{\{ inputs\.artifact_version \}\}/); + assert.doesNotMatch(prepareStep.match(/run: \|([\s\S]*)/)?.[1] ?? "", /inputs\.artifact_version/); + assert.match(prepareStep, /if \[\[ "\$\{VERSION\}" == \*-\* \]\]/); + assert.doesNotMatch(prepareStep, /inputs\.publish_as_latest/); +}); diff --git a/src/site/markdown/getting-started.md b/src/site/markdown/getting-started.md index 0bde2ef50d..6f9705e37e 100644 --- a/src/site/markdown/getting-started.md +++ b/src/site/markdown/getting-started.md @@ -29,17 +29,19 @@ copilot --version ## Step 1: Install the SDK -> If you want to test a _-SNAPSHOT_ version, you may have to clone the project's repository and install the library locally with _mvn install_. +Preview documentation can use a rolling `-SNAPSHOT` artifact version. Multiple previews of the same base SDK version update that coordinate, so an older preview's documentation directory does not guarantee an exact historical preview build. ### Apache Maven Add the dependency to your `pom.xml`: + + ```xml com.github copilot-sdk-java - ${project.version} + ${artifact.version} ``` @@ -47,8 +49,10 @@ Add the dependency to your `pom.xml`: Add the dependency to your `build.gradle`: + + ```groovy -implementation 'com.github:copilot-sdk-java:${project.version}' +implementation 'com.github:copilot-sdk-java:${artifact.version}' ``` ### JBang (Quick Start) @@ -56,7 +60,8 @@ implementation 'com.github:copilot-sdk-java:${project.version}' For the fastest way to try the SDK without setting up a project, use [JBang](https://www.jbang.dev/). Create a file named `HelloCopilot.java` with the following header, and then proceed to Step 2 by appending the proposed content into this same file. ```bash -//DEPS com.github:copilot-sdk-java:${project.version} +//ARTIFACT_REPOSITORY +//DEPS com.github:copilot-sdk-java:${artifact.version} ``` ## Step 2: Send Your First Message diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md index 7819c5f05e..45a4e33bf8 100644 --- a/src/site/markdown/index.md +++ b/src/site/markdown/index.md @@ -15,22 +15,26 @@ Welcome to the documentation for the **GitHub Copilot SDK for Java** — a Java Add the dependency to your project: -> **Note:** If this is a `-SNAPSHOT` version, clone the project repository and run `./mvnw install` locally first so the artifact is available in your local Maven cache. Otherwise, the version is available on Maven Central. +Preview documentation can use a rolling `-SNAPSHOT` artifact version. Multiple previews of the same base SDK version update that coordinate, so an older preview's documentation directory does not guarantee an exact historical preview build. **Maven:** + + ```xml com.github copilot-sdk-java - ${project.version} + ${artifact.version} ``` **Gradle:** + + ```groovy -implementation 'com.github:copilot-sdk-java:${project.version}' +implementation 'com.github:copilot-sdk-java:${artifact.version}' ``` ### Quick Example @@ -86,7 +90,8 @@ You can quickly try the SDK without setting up a full project using [JBang](http # Create a simple script cat > hello-copilot.java << 'EOF' -//DEPS com.github:copilot-sdk-java:${project.version} +//ARTIFACT_REPOSITORY +//DEPS com.github:copilot-sdk-java:${artifact.version} import com.github.copilot.CopilotClient; import com.github.copilot.generated.AssistantMessageEvent; import com.github.copilot.generated.SessionIdleEvent;