Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion tools/js-sdk-release-tools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"code-gen-pipeline": "./dist/autoGenerateInPipeline.js",
"rlc-code-gen": "./dist/rlcCodegenCli.js",
"update-changelog": "./dist/generateChangelogCli.js",
"update-version": "./dist/updateBumpVersionCli.js"
"update-version": "./dist/updateBumpVersionCli.js",
"generate-ci-yaml": "./dist/generateCiYamlCli.js"
},
"author": "Microsoft Corporation",
"license": "MIT",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# NOTE: Please refer to https://aka.ms/azsdk/engsys/ci-yaml before editing this file.

trigger:
branches:
include:
- main
- release/*
- hotfix/*
paths:
include: null

pr:
branches:
include:
- main
- feature/*
- release/*
- hotfix/*
exclude:
- feature/v4
paths:
include: null

extends:
template: /eng/pipelines/templates/stages/archetype-sdk-client.yml
parameters:
ServiceDirectory: null
Artifacts:
- name: null
safeName: null
66 changes: 61 additions & 5 deletions tools/js-sdk-release-tools/src/common/ciYamlUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,17 +112,73 @@ async function createManagementPlaneCiYaml(
await writeCiYaml(ciMgmtPath, parsed);
}

async function writeCiYaml(ciMgmtPath: string, config: any) {
async function writeCiYaml(ciPath: string, config: any) {
const content = comment + stringify(config);
await writeFile(ciMgmtPath, content, { encoding: 'utf-8', flush: true });
logger.info(`Created Management CI file '${posix.resolve(ciMgmtPath)}' with content: \n${content}`);
await writeFile(ciPath, content, { encoding: 'utf-8', flush: true });
logger.info(`Created or updated CI file '${posix.resolve(ciPath)}' with content: \n${content}`);
}

async function createOrUpdateDataPlaneCiYaml(
async function updateDataPlaneCiYaml(
generatedPackageDirectory: string,
ciPath: string,
npmPackageInfo: NpmPackageInfo
): Promise<void> {
const content = await readFile(ciPath, { encoding: 'utf-8' });
let parsed = parse(content.toString());

makeSureArrayAvailableInCiYaml(parsed, ['trigger', 'branches', 'exclude']);
makeSureArrayAvailableInCiYaml(parsed, ['pr', 'branches', 'exclude']);
makeSureArrayAvailableInCiYaml(parsed, ['trigger', 'paths', 'include']);
makeSureArrayAvailableInCiYaml(parsed, ['pr', 'paths', 'include']);
makeSureArrayAvailableInCiYaml(parsed, ['extends', 'parameters', 'Artifacts']);

const artifact: ArtifactInfo = getArtifact(npmPackageInfo);
const artifactInclude = (array: ArtifactInfo[], item: ArtifactInfo) => array.map((a) => a.name).includes(item.name);

let needUpdate = false;
needUpdate = tryAddItemInArray(parsed.trigger.branches.exclude, 'feature/v4') || needUpdate;
needUpdate = tryAddItemInArray(parsed.pr.branches.exclude, 'feature/v4') || needUpdate;
needUpdate = tryAddItemInArray(parsed.trigger.paths.include, generatedPackageDirectory) || needUpdate;
needUpdate = tryAddItemInArray(parsed.trigger.paths.include, ciPath) || needUpdate;
needUpdate = tryAddItemInArray(parsed.pr.paths.include, generatedPackageDirectory) || needUpdate;
needUpdate = tryAddItemInArray(parsed.pr.paths.include, ciPath) || needUpdate;
needUpdate = tryAddItemInArray(parsed.extends.parameters.Artifacts, artifact, artifactInclude) || needUpdate;

await writeCiYaml(ciPath, parsed);
}

async function createDataPlaneCiYaml(
packageDirToSdkRoot: string,
ciPath: string,
serviceDirToSdkRoot: string,
npmPackageInfo: NpmPackageInfo
): Promise<void> {
const artifact = getArtifact(npmPackageInfo);
const __dirname = import.meta.dirname || dirname(fileURLToPath(import.meta.url));
const templatePath = posix.join(__dirname, 'ciYamlTemplates/ci.template.yml');
const template = await readFile(templatePath, { encoding: 'utf-8' });
const parsed = parse(template.toString());

parsed.trigger.paths.include = [packageDirToSdkRoot, ciPath];
parsed.pr.paths.include = [packageDirToSdkRoot, ciPath];
parsed.extends.parameters.ServiceDirectory = serviceDirToSdkRoot.split('/')[1];
parsed.extends.parameters.Artifacts = [artifact];

await writeCiYaml(ciPath, parsed);
}

async function createOrUpdateDataPlaneCiYaml(
packageDirToSdkRoot: string,
npmPackageInfo: NpmPackageInfo
): Promise<string> {
throw new Error('Not implemented function');
const serviceDirToSDKDir = posix.join(packageDirToSdkRoot, '..');
const ciPath = posix.join(serviceDirToSDKDir, 'ci.yml');

if (!(await existsAsync(ciPath))) {
await createDataPlaneCiYaml(packageDirToSdkRoot, ciPath, serviceDirToSDKDir, npmPackageInfo);
}
await updateDataPlaneCiYaml(packageDirToSdkRoot, ciPath, npmPackageInfo);
return ciPath;
}

export async function createOrUpdateCiYaml(
Expand Down
75 changes: 75 additions & 0 deletions tools/js-sdk-release-tools/src/generateCiYamlCli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/usr/bin/env node

import commandLineArgs from "command-line-args";
import { createOrUpdateCiYaml } from "./common/ciYamlUtils.js";
import { getNpmPackageInfo, getNpmPackageName } from "./common/npmUtils.js";
import { VersionPolicyName } from "./common/types.js";
import { logger } from "./utils/logger.js";
import path from "path";

const generateCiYamlCli = async (
sdkRepoPath: string | undefined,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will this CLI be used directly in inner loop? Need to confirm whether we had the agreement about the input/output of this too. Please check with Ray for more clarification.

packageFolderPath: string | undefined
) => {
if (!sdkRepoPath || !packageFolderPath) {
logger.error(`SdkRepoPath and PackagePath are required.`);
logger.error(
`Usage: generate-ci-yaml --sdkRepoPath <SdkRepoPath> --packagePath <PackagePath>`
);
process.exit(1);
}

// Calculate relative path from sdkRepoPath to packagePath
const normalizedSdkRepoPath = path.resolve(sdkRepoPath);
const absolutePackagePath = path.resolve(packageFolderPath);
const relativePackagePath = path.relative(normalizedSdkRepoPath, absolutePackagePath);

// Validate that the package path is safely inside the SDK repo path.
// Reject obvious path traversal attempts such as absolute paths outside the repo
// or relative paths that start with "..".
const normalizedSdkRepoPathWithSep = normalizedSdkRepoPath.endsWith(path.sep)
? normalizedSdkRepoPath
: normalizedSdkRepoPath + path.sep;

const isRelativeTraversal =
!path.isAbsolute(packageFolderPath) &&
(packageFolderPath === ".." || packageFolderPath.startsWith(".." + path.sep));

const isOutsideRepoByPrefix = !absolutePackagePath.startsWith(normalizedSdkRepoPathWithSep);

const isOutsideRepoByRelative =
relativePackagePath === ".." || relativePackagePath.startsWith(".." + path.sep);

if (isRelativeTraversal || isOutsideRepoByPrefix || isOutsideRepoByRelative) {
logger.error(
`The provided packagePath ("${packageFolderPath}") resolves outside the sdkRepoPath ("${sdkRepoPath}"). ` +
`Please provide a package path that is within the SDK repository root.`
);
process.exit(1);
}
logger.info(`SDK Repo Path: ${normalizedSdkRepoPath}`);
logger.info(`Package Path (absolute): ${absolutePackagePath}`);
logger.info(`Package Path (relative): ${relativePackagePath}`);

const npmPackageInfo = await getNpmPackageInfo(absolutePackagePath);
const packageName = getNpmPackageName(npmPackageInfo);
const versionPolicyName: VersionPolicyName = packageName.includes("arm-") ? "management" : "client";
logger.info(`Detected versionPolicyName: ${versionPolicyName} for package: ${packageName}`);

const ciPath = await createOrUpdateCiYaml(
relativePackagePath.replace(/\\/g, "/"),
versionPolicyName,
npmPackageInfo
);

logger.info(`CI yaml file created/updated at: ${ciPath}`);
};

const optionDefinitions = [
{ name: "sdkRepoPath", type: String },
{ name: "packagePath", type: String },
];

const options = commandLineArgs(optionDefinitions);

generateCiYamlCli(options["sdkRepoPath"], options["packagePath"]);
Loading