Skip to content
Merged
Show file tree
Hide file tree
Changes from 114 commits
Commits
Show all changes
116 commits
Select commit Hold shift + click to select a range
13dc6c0
Add package.json
danieljurek May 30, 2025
bb7d024
Enable eng/tools in JS
danieljurek May 30, 2025
7c8ebe1
First cut at direct port
danieljurek May 30, 2025
f9089e7
Wire up command, move some functions into changed-files.js
danieljurek May 30, 2025
909e216
Move, pipeline, refactor
danieljurek Jun 5, 2025
abb8b30
Trivial test change to a spec file
danieljurek Jun 5, 2025
fc265e2
Usage, logging, add pathExists to changed-files.js, types in doc-prev…
danieljurek Jun 9, 2025
f1485ea
Prettier
danieljurek Jun 9, 2025
3cad9ae
SkipCheckoutNone
danieljurek Jun 12, 2025
ef1e1bd
Git token auth
danieljurek Jun 12, 2025
ebb9b99
--
danieljurek Jun 12, 2025
2a627e8
../
danieljurek Jun 18, 2025
99387fd
Pool
danieljurek Jun 18, 2025
8dae139
fetchDepth: 2
danieljurek Jun 18, 2025
8a31cc7
Get-ChildItem
danieljurek Jun 18, 2025
7fd80bd
More logging
danieljurek Jun 18, 2025
3b1fc64
Directory
danieljurek Jun 18, 2025
87b0480
BaseRepoOwner
danieljurek Jun 18, 2025
36a8d4a
Cleanup
danieljurek Jul 3, 2025
2e2cae3
02
danieljurek Jul 3, 2025
97933ba
Comment
danieljurek Jul 3, 2025
91f2706
Test a change that should break the docs build
danieljurek Jul 3, 2025
f164da2
Queue docs build
danieljurek Jul 3, 2025
8b8e43f
02
danieljurek Jul 3, 2025
32bbcf7
Tab
danieljurek Jul 3, 2025
8fcf1a0
Add wait and result output
danieljurek Jul 7, 2025
98300ed
Log files
danieljurek Jul 7, 2025
43e07c8
Direct invocation with valid path
danieljurek Jul 7, 2025
b1667ff
Output buildstart.json
danieljurek Jul 7, 2025
044323a
Fixes
danieljurek Jul 7, 2025
541a4b0
Remove BOM
danieljurek Jul 7, 2025
05ce4e2
PR Number
danieljurek Jul 7, 2025
eb65d1c
Test changes that won't break the build
danieljurek Jul 7, 2025
e52370e
encodeURIComponent
danieljurek Jul 7, 2025
f23fef2
-pwsh
danieljurek Jul 7, 2025
d6b28c5
Revert BatchService.json testing
danieljurek Jul 8, 2025
04aeecc
Test from another PR
danieljurek Jul 8, 2025
12968c4
Test timeouts
danieljurek Jul 8, 2025
0e8dd88
Test timeout in script
danieljurek Jul 8, 2025
8f13d13
>
danieljurek Jul 8, 2025
8bf334e
Remove test exception
danieljurek Jul 8, 2025
974c8c1
Show token scope
danieljurek Jul 8, 2025
47da581
Set status on PR
danieljurek Jul 9, 2025
5d84748
git rev-parse HEAD
danieljurek Jul 9, 2025
f1b510c
SourceCommitId
danieljurek Jul 9, 2025
74d76c8
-Azure/
danieljurek Jul 9, 2025
485e748
Set check state in DevOps pipeline
danieljurek Jul 9, 2025
64636e7
set-pr-check.yml
danieljurek Jul 9, 2025
e4ef673
-description
danieljurek Jul 9, 2025
050bccf
Can't enforce values for macro (defined at runtime) syntax at templat…
danieljurek Jul 9, 2025
11a717f
.mjs -> .js
danieljurek Jul 9, 2025
2948b84
.js, other documentation
danieljurek Jul 9, 2025
0bfe5e5
vso
danieljurek Jul 9, 2025
9a7f7da
displayName
danieljurek Jul 9, 2025
ae4adba
Add package.json to eng/scripts, wire up to github-test.yaml (might n…
danieljurek Jul 9, 2025
682a510
sparse checkout eng/scripts
danieljurek Jul 9, 2025
422b94e
eng/scripts/package-lock.json
danieljurek Jul 9, 2025
f5d640b
package.json
danieljurek Jul 10, 2025
a512962
npm i
danieljurek Jul 10, 2025
656639b
prettier -> format
danieljurek Jul 10, 2025
2b0930a
package-lock.json
danieljurek Jul 10, 2025
bcaa4c4
--output
danieljurek Jul 10, 2025
1c350d9
Path
danieljurek Jul 10, 2025
73b1ffa
Revert test changes in specification/
danieljurek Jul 10, 2025
429dc7b
Revert "Test from another PR"
danieljurek Jul 10, 2025
7fd4c3b
Architecture feedback
danieljurek Jul 10, 2025
1a1b5cc
Paths
danieljurek Jul 10, 2025
c1d356e
Docs preview
danieljurek Jul 10, 2025
e911f00
pscore
danieljurek Jul 11, 2025
c01a4db
`
danieljurek Jul 11, 2025
6ec290d
copy
danieljurek Jul 11, 2025
224d360
$buildStart
danieljurek Jul 11, 2025
b040cfe
Test: timeout
danieljurek Jul 11, 2025
76f4d29
(
danieljurek Jul 11, 2025
9141932
Remove test timeout
danieljurek Jul 11, 2025
c4de2fd
Test orchestration build failure
danieljurek Jul 11, 2025
234ecf3
Revert contrived orchestration failure
danieljurek Jul 11, 2025
0523e95
Contrive a docs build failure
danieljurek Jul 11, 2025
2470642
Test success case
danieljurek Jul 11, 2025
25cc59c
Revert spec change
danieljurek Jul 11, 2025
1e1847a
Add support for filtering quickstart templates
danieljurek Jul 11, 2025
8b58e31
Use aka.ms link for docs support teams channel
danieljurek Jul 11, 2025
4f1a085
Sparse checkout might work but that can be investigated elsewhere
danieljurek Jul 11, 2025
f0e7864
Improve param validation
danieljurek Jul 11, 2025
929d33c
Test api-doc-preview param validation in practice
danieljurek Jul 11, 2025
670515b
Revert github-test.yaml
danieljurek Jul 11, 2025
100f4cf
Revert .gitignore
danieljurek Jul 11, 2025
dc24f8d
Remove unnecessary files
danieljurek Jul 11, 2025
c5c72e7
More status update messages
danieljurek Jul 11, 2025
2ed2bb3
Revert "Test api-doc-preview param validation in practice"
danieljurek Jul 11, 2025
55c560a
Fix package.json/package-lock.json
danieljurek Jul 11, 2025
dd2d433
package-lock.json
danieljurek Jul 11, 2025
1f86abe
Merge branch 'main' into djurek/migrate-apidocpreview
danieljurek Jul 11, 2025
a7811ca
npm i
danieljurek Jul 11, 2025
4919a94
Format
danieljurek Jul 11, 2025
3bdb0b5
Apply suggestions from code review
danieljurek Jul 15, 2025
6c8ee1a
inline, fix ts-check errors
danieljurek Jul 15, 2025
99f1430
Use getChangedFilesStatuses to exclude deleted files instead of query…
danieljurek Jul 15, 2025
dde8c41
Comments
danieljurek Jul 15, 2025
6fd04c0
Named typedefs, more exceptions, expand "key" to a couple variables
danieljurek Jul 15, 2025
1872b4a
Adjust PR triggers
danieljurek Jul 15, 2025
d5af561
Remove fs/promises/access
danieljurek Jul 15, 2025
2a45ee2
Add WorkingDirectory to npm-install.yml, use shebang invocation
danieljurek Jul 15, 2025
be52827
Remove infinite loop condition
danieljurek Jul 15, 2025
f6aafa0
pathExists
danieljurek Jul 15, 2025
0833e32
.js
danieljurek Jul 15, 2025
48145c2
(
danieljurek Jul 15, 2025
32f69d2
Only run docs build if there are changes to push
danieljurek Jul 15, 2025
77de6d1
DisplayName
danieljurek Jul 16, 2025
894bd87
Final TODOs
danieljurek Jul 16, 2025
4d93c98
Merge branch 'main' into djurek/migrate-apidocpreview
danieljurek Jul 16, 2025
34b3c0c
Merge branch 'main' into djurek/migrate-apidocpreview
danieljurek Jul 23, 2025
fac26bc
Format
danieljurek Jul 24, 2025
2172995
Merge branch 'main' into djurek/migrate-apidocpreview
danieljurek Jul 24, 2025
8311106
Update eng/pipelines/swagger-api-doc-preview.yml
danieljurek Jul 24, 2025
7dcbba5
Merge branch 'main' into djurek/migrate-apidocpreview
danieljurek Jul 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions .github/shared/cmd/api-doc-preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#!/usr/bin/env node
// @ts-check

import { mkdir, writeFile } from "fs/promises";
import { dirname, join, resolve } from "path";
import { fileURLToPath } from "url";
import { parseArgs } from "util";

import { getChangedFilesStatuses, swagger } from "../src/changed-files.js";

import {
getSwaggersToProcess,
indexMd,
mappingJSONTemplate,
repoJSONTemplate,
} from "../src/doc-preview.js";

const __dirname = dirname(fileURLToPath(import.meta.url));

function usage() {
console.log(`Usage:
npx api-doc-preview --output <output-dir>

parameters:
--output <output-dir> Directory to write documentation artifacts to.
--build-id <build-id> Build ID, used in the documentation index. Defaults to BUILD_BUILDID environment variable.
--spec-repo-name <name> Name of the repository containing the swagger files of the form <org>/<repo-name>. Defaults to BUILD_REPOSITORY_NAME environment variable.
--spec-repo-pr-number <pr-number> PR number of the repository containing the swagger files. Defaults to SYSTEM_PULLREQUEST_PULLREQUESTNUMBER environment variable.
--spec-repo-root <path> Root path of the repository containing the swagger files. Defaults to the root of the repository containing this script.`);
}

const {
values: {
output: outputDir,
"build-id": buildId,
"spec-repo-name": specRepoName,
"spec-repo-pr-number": specRepoPrNumber,
"spec-repo-root": specRepoRoot,
},
} = parseArgs({
options: {
output: { type: "string", default: "" },
"build-id": {
type: "string",
default: process.env.BUILD_BUILDID || "",
},
"spec-repo-name": {
type: "string",
default: process.env.BUILD_REPOSITORY_NAME || "",
},
"spec-repo-pr-number": {
type: "string",
default: process.env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER || "",
},
"spec-repo-root": {
type: "string",
default: resolve(__dirname, "../../../"),
},
},
allowPositionals: false,
});

let validArgs = true;

if (!outputDir) {
console.log(`Missing required parameter --output. Value given: ${outputDir || "<empty>"}`);
validArgs = false;
}

if (!specRepoName) {
console.log(
`Missing required parameter --spec-repo-name. Value given: ${specRepoName || "<empty>"}`,
);
validArgs = false;
}

if (!specRepoPrNumber) {
console.log(
`Missing required parameter --spec-repo-pr-number. Value given: ${specRepoPrNumber || "<empty>"}`,
);
validArgs = false;
}

if (!specRepoRoot) {
console.log(`Invalid parameter --spec-repo-root. Value given: ${specRepoRoot || "<empty>"}`);
validArgs = false;
}

if (!validArgs) {
usage();
process.exit(1);
}

// Get selected version and swaggers to process

const changedFileStatuses = await getChangedFilesStatuses({
cwd: specRepoRoot,
paths: ["specification"],
});

// Exclude deleted files as they are not relevant for generating documentation.
const changedFiles = [
...changedFileStatuses.additions,
...changedFileStatuses.modifications,
// Current names of renamed files are interesting, previous names are not.
...changedFileStatuses.renames.map((r) => r.to),
];
console.log(`Found ${changedFiles.length} relevant changed files in ${specRepoRoot}`);
console.log("Changed files:");
changedFiles.forEach((file) => console.log(` - ${file}`));
const swaggerPaths = changedFiles.filter(swagger);

if (swaggerPaths.length === 0) {
console.log("No eligible swagger files found. No documentation artifacts will be written.");
process.exit(0);
}

const { selectedVersion, swaggersToProcess } = getSwaggersToProcess(swaggerPaths);

const repoName = specRepoName;
const prNumber = specRepoPrNumber;

await mkdir(outputDir, { recursive: true });
await writeFile(
join(outputDir, "repo.json"),
JSON.stringify(repoJSONTemplate(repoName, prNumber), null, 2),
);
await writeFile(
join(outputDir, "mapping.json"),
JSON.stringify(mappingJSONTemplate(swaggersToProcess), null, 2),
);
await writeFile(join(outputDir, "index.md"), indexMd(buildId, repoName, prNumber));
console.log(`Documentation preview artifacts written to ${outputDir}`);

console.log(`Doc preview for API version ${selectedVersion} includes:`);
swaggersToProcess.forEach((swagger) => console.log(` - ${swagger}`));
console.log(`Artifacts written to: ${outputDir}`);
1 change: 1 addition & 0 deletions .github/shared/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion .github/shared/package.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"name": "@azure-tools/specs-shared",
"private": "true",
Expand All @@ -23,7 +23,8 @@
"./test/examples": "./test/examples.js"
},
"bin": {
"spec-model": "./cmd/spec-model.js"
"spec-model": "./cmd/spec-model.js",
"api-doc-preview": "./cmd/api-doc-preview.js"
},
"_comments": {
"dependencies": "Runtime dependencies must be kept to an absolute minimum for performance, ideally with no transitive dependencies",
Expand Down
16 changes: 15 additions & 1 deletion .github/shared/src/changed-files.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// @ts-check

import debug from "debug";
Expand All @@ -8,6 +8,8 @@
debug.enable("simple-git");

/**
* Get a list of changed files in a git repository
*
* @param {Object} [options]
* @param {string} [options.baseCommitish] Default: "HEAD^".
* @param {string} [options.cwd] Current working directory. Default: process.cwd().
Expand All @@ -32,7 +34,6 @@
const result = await simpleGit(cwd).diff(["--name-only", baseCommitish, headCommitish, ...paths]);

const files = result.trim().split("\n");

logger?.info("Changed Files:");
for (const file of files) {
logger?.info(` ${file}`);
Expand All @@ -43,6 +44,10 @@
}

/**
* Get a list of changed files in a git repository with statuses for additions,
* modifications, deletions, and renames. Warning: rename behavior can vary
* based on the git client's configuration of diff.renames.
*
* @param {Object} [options]
* @param {string} [options.baseCommitish] Default: "HEAD^".
* @param {string} [options.cwd] Current working directory. Default: process.cwd().
Expand Down Expand Up @@ -210,6 +215,14 @@
);
}

/**
* @param {string} [file]
* @returns {boolean}
*/
export function quickstartTemplate(file) {
return typeof file === "string" && json(file) && file.includes("/quickstart-templates/");
}

/**
* @param {string} [file]
* @returns {boolean}
Expand All @@ -220,6 +233,7 @@
json(file) &&
(dataPlane(file) || resourceManager(file)) &&
!example(file) &&
!quickstartTemplate(file) &&
!scenario(file)
);
}
Expand Down
167 changes: 167 additions & 0 deletions .github/shared/src/doc-preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// @ts-check
const DOCS_NAMESPACE = "_swagger_specs";
const SPEC_FILE_REGEX =
"(specification/)+(.*)/(resourcemanager|resource-manager|dataplane|data-plane|control-plane)/(.*)/(preview|stable|privatepreview)/(.*?)/(example)?(.*)";

/**
* @typedef {Object} SwaggerFileMetadata
* @property {string} path
* @property {string} serviceName
* @property {string} serviceType
* @property {string} resourceProvider
* @property {string} releaseState
* @property {string} apiVersion
* @property {string} fileName
*/

/**
* @typedef {Object} RepoJSONTemplate
* @property {Object[]} repo
* @property {string} repo[].url
* @property {string} repo[].prNumber
* @property {string} repo[].name
*/

/**
* @typedef {Object} MappingJSONStructure
* @property {string} target_api_root_dir
* @property {boolean} enable_markdown_fragment
* @property {string} markdown_fragment_folder
* @property {boolean} use_yaml_toc
* @property {boolean} formalize_url
* @property {string[]} version_list
* @property {Object[]} organizations
* @property {string} organizations[].index
* @property {string} organizations[].default_toc_title
* @property {string} organizations[].version
* @property {Object[]} organizations[].services
* @property {string} organizations[].services[].toc_title
* @property {string} organizations[].services[].url_group
* @property {Object[]} organizations[].services[].swagger_files
* @property {string} organizations[].services[].swagger_files[].source
*/

/**
* Extract swagger file metadata from path.
* @param {string} specPath
* @returns {SwaggerFileMetadata}
*/
export function parseSwaggerFilePath(specPath) {
const m = specPath.match(SPEC_FILE_REGEX);
if (!m) {
throw new Error(`Path "${specPath}" does not match expected swagger file pattern.`);
}
const [path, , serviceName, serviceType, resourceProvider, releaseState, apiVersion, , fileName] =
m;
return {
path,
serviceName,
serviceType,
resourceProvider,
releaseState,
apiVersion,
fileName,
};
}

/**
* @param {string} repoName
* @param {string} prNumber
* @returns {object}
*/
export function repoJSONTemplate(repoName, prNumber) {
return {
repo: [
{
url: `https://github.com/${repoName}`,
prNumber: prNumber,
name: DOCS_NAMESPACE,
},
],
};
}

/**
* @param {string[]} files
* @returns {MappingJSONStructure}
*/
export function mappingJSONTemplate(files) {
return {
target_api_root_dir: "structured",
enable_markdown_fragment: true,
markdown_fragment_folder: "authored",
use_yaml_toc: true,
formalize_url: true,
version_list: ["default"],
organizations: [
{
index: "index.md",
default_toc_title: "Getting Started",
version: "default",
services: [
{
toc_title: "Documentation Preview",
url_group: "documentation-preview",
swagger_files: files.map((source) => ({
source: `${DOCS_NAMESPACE}/${source}`,
})),
},
],
},
],
};
}

/**
* @param {string} buildId
* @param {string} repoName
* @param {string} prNumber
* @returns {string}
*/
export function indexMd(buildId, repoName, prNumber) {
return `# Documentation Preview for swagger pipeline build #${buildId}

Welcome to documentation preview for ${repoName}/pull/${prNumber}
created via the swagger pipeline.

Your documentation may be viewed in the menu on the left hand side.

If you have issues around documentation generation, please feel free to contact
us in the [Docs Support Teams Channel](https://aka.ms/ci-fix/api-docs-help)`;
}

/**
* Given a list of changed swagger files, select an API version and a list of
* swagger files in that API version to process.
* @param {string[]} swaggerFiles
**/
export function getSwaggersToProcess(swaggerFiles) {
const swaggerFileObjs = swaggerFiles.map(parseSwaggerFilePath);

const versions = swaggerFileObjs.map((obj) => obj.apiVersion).filter(Boolean);
if (versions.length === 0) {
throw new Error("No new API versions found in eligible swagger files.");
}
const uniqueVersions = Array.from(new Set(versions));

let selectedVersion;
if (uniqueVersions.length === 1) {
selectedVersion = uniqueVersions[0];
console.log(`Single API version found: ${selectedVersion}`);
} else {
// This sorting logic is ported from the original code which sorts only the
// strings and doesn't attempt to parse versions for more semantically-aware
// sorting.
const sortedVersions = [...uniqueVersions].sort();
selectedVersion = sortedVersions[sortedVersions.length - 1];
console.log(
`Multiple API versions found: ${JSON.stringify(sortedVersions)}. Selected version: ${selectedVersion}`,
);
}

const swaggersToProcess = swaggerFileObjs
.filter((obj) => obj.apiVersion === selectedVersion)
.map((obj) => obj.path);

return { selectedVersion, swaggersToProcess };
}
Loading