Support RLC Changelog Generation Based on Inline Usage (#8751)
This commit is contained in:
Родитель
ee00555fb6
Коммит
a37817cc57
|
@ -0,0 +1,4 @@
|
|||
# JS SDK Release Tools
|
||||
!packages/*
|
||||
.tmp/*
|
||||
.tmp-detect/
|
|
@ -65,11 +65,6 @@ extends:
|
|||
npm run test
|
||||
displayName: 'npm run test'
|
||||
workingDirectory: $(System.DefaultWorkingDirectory)/tools/js-sdk-release-tools
|
||||
|
||||
- script: |
|
||||
npm install
|
||||
displayName: 'npm install'
|
||||
workingDirectory: $(System.DefaultWorkingDirectory)/tools/js-sdk-release-tools
|
||||
|
||||
- script: |
|
||||
npm pack
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -3,14 +3,13 @@
|
|||
"version": "2.7.11",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"dev": "nodemon src/changelogToolCli.ts",
|
||||
"dev": "tsx watch src/changelogToolCli.ts",
|
||||
"start": "node dist/changelogToolCli.js",
|
||||
"debug": "node --inspect-brk dist/changelogToolCli.js",
|
||||
"build": "rimraf dist && tsc -p .",
|
||||
"prepack": "npm run build",
|
||||
"test": "vitest --run",
|
||||
"test:watch": "vitest",
|
||||
"test:ci": "node dist/changelogToolCli.js src/test/testCases/ci/arm-networkanalytics"
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"bin": {
|
||||
"changelog-tool": "./dist/changelogToolCli.js",
|
||||
|
@ -28,23 +27,45 @@
|
|||
"command-line-args": "^5.1.1",
|
||||
"comment-json": "^4.1.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"glob": "^11.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"parse-ts-to-ast": "^0.1.1",
|
||||
"semver": "^7.3.5",
|
||||
"shelljs": "^0.8.4",
|
||||
"shx": "^0.3.4",
|
||||
"simple-git": "^3.5.0",
|
||||
"ts-morph": "^12.0.0",
|
||||
"ts-morph": "^23.0.0",
|
||||
"tslib": "^1.9.3",
|
||||
"typescript-codegen-breaking-change-detector": "0.4.11",
|
||||
"unixify": "^1.0.0",
|
||||
"winston": "^3.13.1",
|
||||
"yaml": "^1.10.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.12.12",
|
||||
"@types/shelljs": "^0.8.15",
|
||||
"nodemon": "^3.1.0",
|
||||
"@types/unixify": "^1.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.5",
|
||||
"vitest": "^1.6.0"
|
||||
"tsx": "^4.16.5",
|
||||
"typescript": "5.5.3",
|
||||
"vitest": "2.0.5"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.20.0",
|
||||
"@rollup/rollup-android-arm64": "4.20.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.20.0",
|
||||
"@rollup/rollup-darwin-x64": "4.20.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.20.0",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.20.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.20.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.20.0",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.20.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.20.0",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.20.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.20.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.20.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.20.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.20.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.20.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
build
|
||||
coverage
|
||||
src/tests/e2e/cases
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"prettier"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"prettier/prettier": 2
|
||||
}
|
||||
}
|
132
tools/js-sdk-release-tools/packages/typescript-codegen-breaking-change-detector/.gitignore
поставляемый
Normal file
132
tools/js-sdk-release-tools/packages/typescript-codegen-breaking-change-detector/.gitignore
поставляемый
Normal file
|
@ -0,0 +1,132 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
.tmp/
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": [
|
||||
"development"
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
registry=https://registry.npmjs.org/
|
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
build
|
||||
coverage
|
||||
src/tests/e2e/cases
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"arrowParens": "always",
|
||||
"semi": true,
|
||||
"endOfLine": "lf",
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true,
|
||||
"printWidth": 120
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
# typescript-codegen-breaking-change-detector
|
||||
|
||||
> 🏗 Work in progress...
|
||||
|
||||
Detect breaking changes to your TypeScript client generated by open api, typespec and more...
|
||||
|
||||
## Contribution
|
||||
|
||||
### sort-imports
|
||||
|
||||
Install `amatiasq.sort-imports` VS code extension to sort imports automatically.
|
|
@ -0,0 +1,20 @@
|
|||
# Azure REST Level Breaking Change Rules
|
||||
|
||||
## Special Rule Set
|
||||
|
||||
Special rule set for Azure REST level client: [rule set](../src/azure/rule-sets/rest-level-client-rule-sets.ts).
|
||||
|
||||
### Non-Breaking Changes
|
||||
|
||||
- Operation name changes: [rule](../src/azure/common/rules/ignore-operation-interface-name-changes.ts)
|
||||
- Request parameter model name changes: [rule](../src/azure/common/rules/ignore-request-parameter-model-name-changes.ts)
|
||||
- Response model name changes:
|
||||
-
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Path value changes
|
||||
|
||||
### TODO
|
||||
|
||||
- Request parameter type changes with content type
|
|
@ -0,0 +1,127 @@
|
|||
{
|
||||
"name": "@azure-rest/arm-servicefabric",
|
||||
"sdk-type": "client",
|
||||
"author": "Microsoft Corporation",
|
||||
"version": "1.0.0-beta.1",
|
||||
"description": "",
|
||||
"keywords": [
|
||||
"node",
|
||||
"azure",
|
||||
"cloud",
|
||||
"typescript",
|
||||
"browser",
|
||||
"isomorphic"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
"module": "./dist-esm/src/index.js",
|
||||
"types": "./types/arm-servicefabric.d.ts",
|
||||
"homepage": "https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/servicefabric/arm-servicefabric-rest/README.md",
|
||||
"repository": "github:Azure/azure-sdk-for-js",
|
||||
"bugs": {
|
||||
"url": "https://github.com/Azure/azure-sdk-for-js/issues"
|
||||
},
|
||||
"files": [
|
||||
"dist/",
|
||||
"dist-esm/src/",
|
||||
"types/arm-servicefabric.d.ts",
|
||||
"README.md",
|
||||
"LICENSE",
|
||||
"review/*"
|
||||
],
|
||||
"//metadata": {
|
||||
"constantPaths": [
|
||||
{
|
||||
"path": "swagger/README.md",
|
||||
"prefix": "package-version"
|
||||
}
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"audit": "node ../../../common/scripts/rush-audit.js && rimraf node_modules package-lock.json && npm i --package-lock-only 2>&1 && npm audit",
|
||||
"build:browser": "tsc -p . && cross-env ONLY_BROWSER=true rollup -c 2>&1",
|
||||
"build:node": "tsc -p . && cross-env ONLY_NODE=true rollup -c 2>&1",
|
||||
"build:samples": "echo skipped.",
|
||||
"build:test": "tsc -p . && dev-tool run bundle",
|
||||
"build:debug": "tsc -p . && dev-tool run bundle && api-extractor run --local",
|
||||
"check-format": "prettier --list-different --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"samples-dev/**/*.ts\" \"*.{js,json}\"",
|
||||
"clean": "rimraf dist dist-browser dist-esm test-dist temp types *.tgz *.log",
|
||||
"execute:samples": "dev-tool samples run samples-dev",
|
||||
"extract-api": "rimraf review && mkdirp ./review && api-extractor run --local",
|
||||
"format": "prettier --write --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"samples-dev/**/*.ts\" \"*.{js,json}\"",
|
||||
"generate:client": "autorest --typescript swagger/README.md && npm run format",
|
||||
"integration-test:browser": "dev-tool run test:browser",
|
||||
"integration-test:node": "dev-tool run test:node-js-input -- --timeout 5000000 'dist-esm/test/**/*.spec.js'",
|
||||
"integration-test": "npm run integration-test:node && npm run integration-test:browser",
|
||||
"lint:fix": "eslint package.json api-extractor.json src test --ext .ts --fix --fix-type [problem,suggestion]",
|
||||
"lint": "eslint package.json api-extractor.json src test --ext .ts",
|
||||
"pack": "npm pack 2>&1",
|
||||
"test:browser": "npm run clean && npm run build:test && npm run unit-test:browser",
|
||||
"test:node": "npm run clean && npm run build:test && npm run unit-test:node",
|
||||
"test": "npm run clean && npm run build:test && npm run unit-test",
|
||||
"unit-test": "npm run unit-test:node && npm run unit-test:browser",
|
||||
"unit-test:node": "dev-tool run test:node-ts-input -- --timeout 1200000 --exclude 'test/**/browser/*.spec.ts' 'test/**/*.spec.ts'",
|
||||
"unit-test:browser": "dev-tool run test:browser",
|
||||
"build": "npm run clean && tsc -p . && dev-tool run bundle && mkdirp ./review && api-extractor run --local"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"autoPublish": false,
|
||||
"dependencies": {
|
||||
"@azure/core-auth": "^1.3.0",
|
||||
"@azure-rest/core-client": "1.0.0-beta.10",
|
||||
"@azure/core-rest-pipeline": "^1.8.0",
|
||||
"@azure/logger": "^1.0.0",
|
||||
"tslib": "^2.2.0",
|
||||
"@azure/core-paging": "^1.2.0",
|
||||
"@azure/core-lro": "^2.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/api-extractor": "7.18.11",
|
||||
"autorest": "latest",
|
||||
"@types/node": "^12.0.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"eslint": "^7.15.0",
|
||||
"mkdirp": "^1.0.4",
|
||||
"prettier": "2.2.1",
|
||||
"rimraf": "^3.0.0",
|
||||
"source-map-support": "^0.5.9",
|
||||
"typescript": "~4.2.0",
|
||||
"@azure/dev-tool": "^1.0.0",
|
||||
"@azure/eslint-plugin-azure-sdk": "^3.0.0",
|
||||
"@azure-tools/test-credential": "^1.0.0",
|
||||
"@azure/identity": "^2.0.1",
|
||||
"@azure-tools/test-recorder": "^2.0.0",
|
||||
"mocha": "^7.1.1",
|
||||
"mocha-junit-reporter": "^1.18.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"@types/chai": "^4.2.8",
|
||||
"chai": "^4.2.0",
|
||||
"karma-chrome-launcher": "^3.0.0",
|
||||
"karma-coverage": "^2.0.0",
|
||||
"karma-edge-launcher": "^0.4.2",
|
||||
"karma-env-preprocessor": "^0.1.1",
|
||||
"karma-firefox-launcher": "^1.1.0",
|
||||
"karma-ie-launcher": "^1.0.0",
|
||||
"karma-junit-reporter": "^2.0.1",
|
||||
"karma-mocha-reporter": "^2.2.5",
|
||||
"karma-mocha": "^2.0.1",
|
||||
"karma-source-map-support": "~1.4.0",
|
||||
"karma-sourcemap-loader": "^0.3.8",
|
||||
"karma": "^6.2.0",
|
||||
"nyc": "^14.0.0"
|
||||
},
|
||||
"browser": {
|
||||
"./dist-esm/test/public/utils/env.js": "./dist-esm/test/public/utils/env.browser.js"
|
||||
},
|
||||
"//sampleConfiguration": {
|
||||
"productName": "",
|
||||
"productSlugs": [
|
||||
"azure"
|
||||
],
|
||||
"disableDocsMs": true,
|
||||
"apiRefLink": "https://docs.microsoft.com/javascript/api/@azure-rest/arm-servicefabric?view=azure-node-preview"
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,17 @@
|
|||
# Release History
|
||||
|
||||
## 1.0.0 (2024-08-16)
|
||||
|
||||
### Features Added
|
||||
|
||||
- Added operation group ClustersGet_DDD
|
||||
- Added Interface DiagnosticsStorageAccountConfigOutput_FFF
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Removed operation group ClustersGet
|
||||
- Operation ClusterVersionsGet.get has a new signature
|
||||
- Interface DiagnosticsStorageAccountConfig no longer has parameter protectedAccountKeyName
|
||||
- Interface DiagnosticsStorageAccountConfig has a new required parameter protectedAccountKeyName_GGG
|
||||
- Type of parameter diagnosticsStorageAccountConfig of interface ClusterPropertiesOutput is changed from DiagnosticsStorageAccountConfigOutput to DiagnosticsStorageAccountConfigOutput_FFF
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
{
|
||||
"name": "@azure-rest/arm-servicefabric",
|
||||
"sdk-type": "client",
|
||||
"author": "Microsoft Corporation",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"keywords": [
|
||||
"node",
|
||||
"azure",
|
||||
"cloud",
|
||||
"typescript",
|
||||
"browser",
|
||||
"isomorphic"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
"module": "./dist-esm/src/index.js",
|
||||
"types": "./types/arm-servicefabric.d.ts",
|
||||
"homepage": "https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/servicefabric/arm-servicefabric-rest/README.md",
|
||||
"repository": "github:Azure/azure-sdk-for-js",
|
||||
"bugs": {
|
||||
"url": "https://github.com/Azure/azure-sdk-for-js/issues"
|
||||
},
|
||||
"files": [
|
||||
"dist/",
|
||||
"dist-esm/src/",
|
||||
"types/arm-servicefabric.d.ts",
|
||||
"README.md",
|
||||
"LICENSE",
|
||||
"review/*"
|
||||
],
|
||||
"//metadata": {
|
||||
"constantPaths": [
|
||||
{
|
||||
"path": "swagger/README.md",
|
||||
"prefix": "package-version"
|
||||
}
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"audit": "node ../../../common/scripts/rush-audit.js && rimraf node_modules package-lock.json && npm i --package-lock-only 2>&1 && npm audit",
|
||||
"build:browser": "tsc -p . && dev-tool run bundle",
|
||||
"build:node": "tsc -p . && dev-tool run bundle --browser-test false",
|
||||
"build:samples": "echo skipped.",
|
||||
"build:test": "tsc -p . && dev-tool run bundle",
|
||||
"build:debug": "tsc -p . && dev-tool run bundle && dev-tool run extract-api",
|
||||
"check-format": "dev-tool run vendored prettier --list-different --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"samples-dev/**/*.ts\" \"*.{js,json}\"",
|
||||
"clean": "rimraf --glob dist dist-browser dist-esm test-dist temp types *.tgz *.log",
|
||||
"execute:samples": "dev-tool samples run samples-dev",
|
||||
"extract-api": "rimraf review && mkdirp ./review && dev-tool run extract-api",
|
||||
"format": "dev-tool run vendored prettier --write --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"samples-dev/**/*.ts\" \"*.{js,json}\"",
|
||||
"generate:client": "autorest --typescript swagger/README.md && npm run format",
|
||||
"integration-test:browser": "dev-tool run test:browser",
|
||||
"integration-test:node": "dev-tool run test:node-js-input -- --timeout 5000000 'dist-esm/test/**/*.spec.js'",
|
||||
"integration-test": "npm run integration-test:node && npm run integration-test:browser",
|
||||
"lint:fix": "eslint package.json api-extractor.json src test --ext .ts --fix --fix-type [problem,suggestion]",
|
||||
"lint": "eslint package.json api-extractor.json src test --ext .ts",
|
||||
"pack": "npm pack 2>&1",
|
||||
"test:browser": "npm run clean && npm run build:test && npm run unit-test:browser",
|
||||
"test:node": "npm run clean && npm run build:test && npm run unit-test:node",
|
||||
"test": "npm run clean && npm run build:test && npm run unit-test",
|
||||
"unit-test": "npm run unit-test:node && npm run unit-test:browser",
|
||||
"unit-test:node": "dev-tool run test:node-ts-input -- --timeout 1200000 --exclude 'test/**/browser/*.spec.ts' 'test/**/*.spec.ts'",
|
||||
"unit-test:browser": "dev-tool run test:browser",
|
||||
"build": "npm run clean && tsc -p . && dev-tool run bundle && mkdirp ./review && dev-tool run extract-api"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"autoPublish": false,
|
||||
"dependencies": {
|
||||
"@azure/core-auth": "^1.3.0",
|
||||
"@azure-rest/core-client": "^1.0.0",
|
||||
"@azure/core-rest-pipeline": "^1.8.0",
|
||||
"@azure/logger": "^1.0.0",
|
||||
"tslib": "^2.2.0",
|
||||
"@azure/core-paging": "^1.2.0",
|
||||
"@azure/core-lro": "^2.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/api-extractor": "^7.31.1",
|
||||
"autorest": "latest",
|
||||
"@types/node": "^18.0.0",
|
||||
"dotenv": "^16.0.0",
|
||||
"eslint": "^8.0.0",
|
||||
"mkdirp": "^3.0.1",
|
||||
"rimraf": "^5.0.5",
|
||||
"source-map-support": "^0.5.9",
|
||||
"typescript": "~5.5.3",
|
||||
"@azure/dev-tool": "^1.0.0",
|
||||
"@azure/eslint-plugin-azure-sdk": "^3.0.0",
|
||||
"@azure-tools/test-credential": "^1.0.0",
|
||||
"@azure/identity": "^4.0.1",
|
||||
"@azure-tools/test-recorder": "^3.0.0",
|
||||
"mocha": "^10.0.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"@types/chai": "^4.2.8",
|
||||
"chai": "^4.2.0",
|
||||
"karma-chrome-launcher": "^3.0.0",
|
||||
"karma-coverage": "^2.0.0",
|
||||
"karma-env-preprocessor": "^0.1.1",
|
||||
"karma-firefox-launcher": "^1.1.0",
|
||||
"karma-junit-reporter": "^2.0.1",
|
||||
"karma-mocha-reporter": "^2.2.5",
|
||||
"karma-mocha": "^2.0.1",
|
||||
"karma-source-map-support": "~1.4.0",
|
||||
"karma-sourcemap-loader": "^0.3.8",
|
||||
"karma": "^6.2.0",
|
||||
"nyc": "^17.0.0",
|
||||
"ts-node": "^10.0.0",
|
||||
"@types/mocha": "^10.0.0"
|
||||
},
|
||||
"browser": {
|
||||
"./dist-esm/test/public/utils/env.js": "./dist-esm/test/public/utils/env.browser.js"
|
||||
},
|
||||
"//sampleConfiguration": {
|
||||
"productName": "",
|
||||
"productSlugs": [
|
||||
"azure"
|
||||
],
|
||||
"disableDocsMs": true,
|
||||
"apiRefLink": "https://docs.microsoft.com/javascript/api/@azure-rest/arm-servicefabric?view=azure-node-preview"
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { ClientOptions, getClient } from "@azure-rest/core-client";
|
||||
import { TokenCredential } from "@azure/core-auth";
|
||||
import { ServiceFabricClient } from "./clientDefinitions";
|
||||
import { customizedApiVersionPolicy } from "./customizedApiVersionPolicy";
|
||||
|
||||
export default function createClient(
|
||||
credentials: TokenCredential,
|
||||
options: ClientOptions = {},
|
||||
): ServiceFabricClient {
|
||||
const baseUrl = options.baseUrl ?? "https://management.azure.com";
|
||||
options.apiVersion = options.apiVersion ?? "2021-06-01";
|
||||
options = {
|
||||
...options,
|
||||
credentials: {
|
||||
scopes: ["https://management.azure.com/.default"],
|
||||
},
|
||||
};
|
||||
|
||||
const userAgentInfo = `azsdk-js-arm-servicefabric-rest/1.0.0-beta.1`;
|
||||
const userAgentPrefix =
|
||||
options.userAgentOptions && options.userAgentOptions.userAgentPrefix
|
||||
? `${options.userAgentOptions.userAgentPrefix} ${userAgentInfo}`
|
||||
: `${userAgentInfo}`;
|
||||
options = {
|
||||
...options,
|
||||
userAgentOptions: {
|
||||
userAgentPrefix,
|
||||
},
|
||||
};
|
||||
|
||||
const client = getClient(baseUrl, credentials, options) as ServiceFabricClient;
|
||||
// Considering ApiVersionPolicy in core has bugs so we replace with our customized one
|
||||
client.pipeline.removePolicy({
|
||||
name: "ApiVersionPolicy",
|
||||
});
|
||||
client.pipeline.addPolicy(customizedApiVersionPolicy(options));
|
||||
|
||||
return client;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "../../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist-esm",
|
||||
"declarationDir": "./types",
|
||||
"paths": { "@azure-rest/arm-servicefabric": ["./src/index"] }
|
||||
},
|
||||
"include": ["src/**/*.ts", "./test/**/*.ts", "samples-dev/**/*.ts"]
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"name": "typescript-codegen-breaking-change-detector",
|
||||
"version": "0.4.11",
|
||||
"description": "Detect breaking changes to your TypeScript client generated by open api, typespec and more...",
|
||||
"main": "dist/src/index.js",
|
||||
"types": "./dist/src/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rimraf ./dist && tsc",
|
||||
"start:dev": "tsx watch src/cli.ts",
|
||||
"start": "npm run build && node dist/index.js",
|
||||
"test": "vitest --run",
|
||||
"test:watch": "vitest",
|
||||
"lint": "eslint . --ext .ts --max-warnings=0",
|
||||
"prettier-format": "run-script-os",
|
||||
"prettier-format:win32": "prettier --config .prettierrc \"./src/**/*.ts\" --write",
|
||||
"prettier-format:darwin:linux": "prettier --config .prettierrc 'src/**/*.ts' --write",
|
||||
"prettier-format:default": "prettier --config .prettierrc 'src/**/*.ts' --write",
|
||||
"prettier-watch": "run-script-os",
|
||||
"prettier-watch:win32": "onchange \"src/**/*.ts\" -- prettier --write {{changed}}",
|
||||
"prettier-watch:darwin:linux": "onchange 'src/**/*.ts' -- prettier --write {{changed}}",
|
||||
"prettier-watch:default": "onchange 'src/**/*.ts' -- prettier --write {{changed}}"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure-tools/openapi-tools-common": "^1.2.2",
|
||||
"@typescript-eslint/scope-manager": "^7.17.0",
|
||||
"@typescript-eslint/types": "^7.17.0",
|
||||
"@typescript-eslint/typescript-estree": "^7.17.0",
|
||||
"@typescript-eslint/utils": "^7.17.0",
|
||||
"@typescript-eslint/visitor-keys": "^7.17.0",
|
||||
"commander": "^12.1.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"glob": "^11.0.0",
|
||||
"marked": "^13.0.2",
|
||||
"pino": "^9.3.1",
|
||||
"pino-pretty": "^11.2.1",
|
||||
"ts-morph": "^23.0.0",
|
||||
"typescript": "5.5.3",
|
||||
"typescript-eslint": "^7.17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/eslint": "^8.56.10",
|
||||
"@types/eslint-scope": "^3.7.7",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@typescript-eslint/eslint-plugin": "^7.17.0",
|
||||
"@typescript-eslint/parser": "^7.17.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"husky": "^9.0.11",
|
||||
"onchange": "^7.1.0",
|
||||
"prettier": "3.3.2",
|
||||
"rimraf": "^5.0.7",
|
||||
"tsx": "^4.16.2",
|
||||
"vitest": "^2.0.4"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "npm run prettier-format && npm run lint"
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,63 @@
|
|||
import {
|
||||
CreateOperationRule,
|
||||
InlineDeclarationNameSetMessage,
|
||||
NodeContext,
|
||||
ParseForESLintResult,
|
||||
RuleMessageKind,
|
||||
} from '../types';
|
||||
import { RuleListener, getParserServices } from '@typescript-eslint/utils/eslint-utils';
|
||||
import {
|
||||
convertToMorphNode,
|
||||
findAllRenameAbleDeclarationsInNode,
|
||||
findDeclaration,
|
||||
getGlobalScope,
|
||||
isInterfaceDeclarationNode,
|
||||
isParseServiceWithTypeInfo,
|
||||
} from '../../../utils/ast-utils';
|
||||
|
||||
import { ParserServicesWithTypeInformation } from '@typescript-eslint/typescript-estree';
|
||||
import { RuleContext } from '@typescript-eslint/utils/ts-eslint';
|
||||
import { Scope } from '@typescript-eslint/scope-manager';
|
||||
import { createOperationRuleListener } from '../../utils/azure-rule-utils';
|
||||
import { getSettings } from '../../../utils/common-utils';
|
||||
import { ignoreInlineDeclarationsInOperationGroup } from '../../../common/models/rules/rule-ids';
|
||||
|
||||
function getInlineDeclarationNameSet(service: ParserServicesWithTypeInformation, scope: Scope) {
|
||||
const inlineDeclarationMap = new Map<string, NodeContext>();
|
||||
const routes = findDeclaration('Routes', scope, isInterfaceDeclarationNode);
|
||||
const moNode = convertToMorphNode(routes, service);
|
||||
const result = findAllRenameAbleDeclarationsInNode(moNode, scope, service);
|
||||
result.interfaces.forEach((i) => inlineDeclarationMap.set(i.getName(), { node: i, used: false }));
|
||||
result.typeAliases.forEach((t) => inlineDeclarationMap.set(t.getName(), { node: t, used: false }));
|
||||
result.enums.forEach((e) => inlineDeclarationMap.set(e.getName(), { node: e, used: false }));
|
||||
return inlineDeclarationMap;
|
||||
}
|
||||
|
||||
const rule: CreateOperationRule = (baselineParsedResult: ParseForESLintResult | undefined) => {
|
||||
if (!baselineParsedResult)
|
||||
throw new Error(`ParseForESLintResult is required in ${ignoreInlineDeclarationsInOperationGroup} rule`);
|
||||
const baselineService = baselineParsedResult.services;
|
||||
if (!isParseServiceWithTypeInfo(baselineService)) {
|
||||
throw new Error(`Failed to get ParserServicesWithTypeInformation. It indicates the parser configuration is wrong.`);
|
||||
}
|
||||
const baselineGlobalScope = getGlobalScope(baselineParsedResult.scopeManager);
|
||||
const baselineInlineDeclarationNameSet = getInlineDeclarationNameSet(baselineService, baselineGlobalScope);
|
||||
|
||||
return createOperationRuleListener(
|
||||
ignoreInlineDeclarationsInOperationGroup,
|
||||
(context: RuleContext<string, readonly unknown[]>): RuleListener => {
|
||||
const currentService = getParserServices(context);
|
||||
const currentGlobalScope = getGlobalScope(context.sourceCode.scopeManager);
|
||||
const currentInlineDeclarationMap = getInlineDeclarationNameSet(currentService, currentGlobalScope);
|
||||
const message: InlineDeclarationNameSetMessage = {
|
||||
id: ignoreInlineDeclarationsInOperationGroup,
|
||||
baseline: baselineInlineDeclarationNameSet,
|
||||
current: currentInlineDeclarationMap,
|
||||
kind: RuleMessageKind.InlineDeclarationNameSetMessage,
|
||||
};
|
||||
getSettings(context).reportInlineDeclarationNameSetMessage(message);
|
||||
return {};
|
||||
}
|
||||
);
|
||||
};
|
||||
export default rule;
|
|
@ -0,0 +1,52 @@
|
|||
import { RuleListener, RuleModule } from '@typescript-eslint/utils/eslint-utils';
|
||||
|
||||
import { ParserServices } from '@typescript-eslint/parser';
|
||||
import type { ScopeManager } from '@typescript-eslint/scope-manager';
|
||||
import { TSESTree } from '@typescript-eslint/utils';
|
||||
import type { VisitorKeys } from '@typescript-eslint/visitor-keys';
|
||||
import { EnumDeclaration, InterfaceDeclaration, Node, TypeAliasDeclaration } from 'ts-morph';
|
||||
|
||||
export interface ParseForESLintResult {
|
||||
ast: TSESTree.Program & {
|
||||
range?: [number, number];
|
||||
tokens?: TSESTree.Token[];
|
||||
comments?: TSESTree.Comment[];
|
||||
};
|
||||
services: ParserServices;
|
||||
visitorKeys: VisitorKeys;
|
||||
scopeManager: ScopeManager;
|
||||
}
|
||||
|
||||
export interface CreateOperationRule {
|
||||
(baselineParsedResult: ParseForESLintResult | undefined): RuleModule<'default', readonly unknown[], RuleListener>;
|
||||
}
|
||||
|
||||
export interface RuleMessage {
|
||||
id: string;
|
||||
kind: RuleMessageKind;
|
||||
}
|
||||
|
||||
export enum RuleMessageKind {
|
||||
InlineDeclarationNameSetMessage = 'InlineDeclarationNameSetMessage',
|
||||
}
|
||||
|
||||
export interface InlineDeclarationNameSetMessage extends RuleMessage {
|
||||
baseline: Map<string, NodeContext>;
|
||||
current: Map<string, NodeContext>;
|
||||
kind: RuleMessageKind.InlineDeclarationNameSetMessage;
|
||||
}
|
||||
|
||||
export interface LinterSettings {
|
||||
reportInlineDeclarationNameSetMessage(message: InlineDeclarationNameSetMessage): void;
|
||||
}
|
||||
|
||||
export interface NodeContext {
|
||||
node: InterfaceDeclaration | TypeAliasDeclaration | EnumDeclaration;
|
||||
used: boolean;
|
||||
}
|
||||
|
||||
export interface RenameAbleDeclarations {
|
||||
interfaces: InterfaceDeclaration[];
|
||||
typeAliases: TypeAliasDeclaration[];
|
||||
enums: EnumDeclaration[];
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
import * as parser from '@typescript-eslint/parser';
|
||||
import * as ruleIds from '../common/models/rules/rule-ids';
|
||||
|
||||
import {
|
||||
InlineDeclarationNameSetMessage,
|
||||
LinterSettings,
|
||||
ParseForESLintResult,
|
||||
RuleMessage,
|
||||
} from './common/types';
|
||||
import { Renderer, marked } from 'marked';
|
||||
import { basename, join, posix, relative } from 'node:path';
|
||||
import { devConsolelog, toPosixPath } from '../utils/common-utils';
|
||||
import { exists, outputFile, readFile, remove } from 'fs-extra';
|
||||
|
||||
import { TSESLint } from '@typescript-eslint/utils';
|
||||
import ignoreInlineDeclarationsInOperationGroup from './common/rules/ignore-inline-declarations-in-operation-group';
|
||||
import { glob } from 'glob';
|
||||
import { logger } from '../logging/logger';
|
||||
|
||||
const tsconfig = `
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "preserve",
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["es2015", "es2017", "esnext"],
|
||||
"experimentalDecorators": true,
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
],
|
||||
"exclude": ["**/node_modules/**/*.*"]
|
||||
}
|
||||
`;
|
||||
|
||||
interface SubProjectContext {
|
||||
code: string;
|
||||
relativeFilePath: string;
|
||||
}
|
||||
interface ProjectContext {
|
||||
root: string;
|
||||
baseline: SubProjectContext;
|
||||
current: SubProjectContext;
|
||||
}
|
||||
|
||||
async function loadCodeFromApiView(path: string) {
|
||||
const content = await readFile(path, { encoding: 'utf-8' });
|
||||
const markdown = content.toString();
|
||||
const codeBlocks: string[] = [];
|
||||
const renderer = new Renderer();
|
||||
renderer.code = ({ text }) => {
|
||||
codeBlocks.push(text);
|
||||
return '';
|
||||
};
|
||||
marked(markdown, { renderer });
|
||||
if (codeBlocks.length !== 1) throw new Error(`Expected 1 code block, got ${codeBlocks.length} in "${path}".`);
|
||||
|
||||
return codeBlocks[0];
|
||||
}
|
||||
|
||||
async function prepareProject(
|
||||
currentPackageFolder: string,
|
||||
baselinePackageFolder: string,
|
||||
tempFolder: string
|
||||
): Promise<ProjectContext> {
|
||||
const [currentCode, baselineCode] = await Promise.all([
|
||||
loadCodeFromApiView(currentPackageFolder),
|
||||
loadCodeFromApiView(baselinePackageFolder),
|
||||
]);
|
||||
|
||||
const relativeCurrentPath = join('current', 'review', 'index.ts');
|
||||
const relativeBaselinePath = join('baseline', 'review', 'index.ts');
|
||||
const currentPath = join(tempFolder, relativeCurrentPath);
|
||||
const baselinePath = join(tempFolder, relativeBaselinePath);
|
||||
const tsConfigPath = join(tempFolder, 'tsconfig.json');
|
||||
await Promise.all([
|
||||
outputFile(tsConfigPath, tsconfig, 'utf-8'),
|
||||
outputFile(currentPath, currentCode, 'utf-8'),
|
||||
outputFile(baselinePath, baselineCode, 'utf-8'),
|
||||
]);
|
||||
return {
|
||||
root: tempFolder,
|
||||
baseline: {
|
||||
code: baselineCode,
|
||||
relativeFilePath: relativeBaselinePath,
|
||||
},
|
||||
current: {
|
||||
code: currentCode,
|
||||
relativeFilePath: relativeCurrentPath,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function parseBaselinePackage(projectContext: ProjectContext): Promise<ParseForESLintResult> {
|
||||
const result = parser.parseForESLint(projectContext.baseline.code, {
|
||||
comment: true,
|
||||
tokens: true,
|
||||
range: true,
|
||||
loc: true,
|
||||
project: './tsconfig.json',
|
||||
tsconfigRootDir: projectContext.root,
|
||||
filePath: projectContext.baseline.relativeFilePath,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
async function detectBreakingChangesCore(projectContext: ProjectContext): Promise<RuleMessage[] | undefined> {
|
||||
try {
|
||||
const breakingChangeResults: RuleMessage[] = [];
|
||||
const baselineParsed = await parseBaselinePackage(projectContext);
|
||||
const linter = new TSESLint.Linter({ cwd: projectContext.root });
|
||||
// linter.defineRule(ruleIds.ignoreOperationGroupNameChanges, ignoreOperationGroupNameChangesRule(baselineParsed));
|
||||
linter.defineRule(
|
||||
ruleIds.ignoreInlineDeclarationsInOperationGroup,
|
||||
ignoreInlineDeclarationsInOperationGroup(baselineParsed)
|
||||
);
|
||||
linter.defineParser('@typescript-eslint/parser', parser);
|
||||
linter.verify(
|
||||
projectContext.current.code,
|
||||
{
|
||||
rules: {
|
||||
// [ruleIds.ignoreOperationGroupNameChanges]: [2],
|
||||
[ruleIds.ignoreInlineDeclarationsInOperationGroup]: [2],
|
||||
},
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
filePath: projectContext.current.relativeFilePath,
|
||||
comment: true,
|
||||
tokens: true,
|
||||
range: true,
|
||||
loc: true,
|
||||
project: './tsconfig.json',
|
||||
tsconfigRootDir: projectContext.root,
|
||||
},
|
||||
settings: (<LinterSettings>{
|
||||
reportInlineDeclarationNameSetMessage: (message: InlineDeclarationNameSetMessage) => {
|
||||
breakingChangeResults.push(message);
|
||||
},
|
||||
}) as any,
|
||||
},
|
||||
projectContext.current.relativeFilePath
|
||||
);
|
||||
return breakingChangeResults;
|
||||
} catch (err) {
|
||||
logger.error(`Failed to detect breaking changes due to ${(err as Error).stack ?? err}`);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function detectBreakingChangesBetweenPackages(
|
||||
baselinePackageFolder: string | undefined,
|
||||
currentPackageFolder: string | undefined,
|
||||
tempFolder: string | undefined,
|
||||
cleanUpAtTheEnd: boolean
|
||||
): Promise<Map<string, RuleMessage[] | undefined>> {
|
||||
if (!baselinePackageFolder) throw new Error(`Failed to use undefined or null baseline package folder`);
|
||||
|
||||
if (!currentPackageFolder) throw new Error(`Failed to use undefined or null current package folder`);
|
||||
|
||||
if (!tempFolder) throw new Error(`Failed to use undefined or null temp folder`);
|
||||
|
||||
try {
|
||||
baselinePackageFolder = toPosixPath(baselinePackageFolder);
|
||||
currentPackageFolder = toPosixPath(currentPackageFolder);
|
||||
tempFolder = toPosixPath(tempFolder);
|
||||
|
||||
const apiViewPathPattern = posix.join(baselinePackageFolder, 'review/*.api.md');
|
||||
const baselineApiViewPaths = await glob(apiViewPathPattern);
|
||||
const messsagesPromises = baselineApiViewPaths.map(async (baselineApiViewPath) => {
|
||||
const relativeApiViewPath = relative(baselinePackageFolder!, baselineApiViewPath);
|
||||
const apiViewBasename = basename(relativeApiViewPath);
|
||||
const currentApiViewPath = join(currentPackageFolder!, relativeApiViewPath);
|
||||
if (!(await exists(currentApiViewPath))) throw new Error(`Failed to find API view: ${currentApiViewPath}`);
|
||||
const projectContext = await prepareProject(currentApiViewPath, baselineApiViewPath, tempFolder!);
|
||||
const messages = await detectBreakingChangesCore(projectContext);
|
||||
return { name: apiViewBasename, messages };
|
||||
});
|
||||
const messagesMap = new Map<string, RuleMessage[] | undefined>();
|
||||
const promises = messsagesPromises.map(async (p) => {
|
||||
const result = await p;
|
||||
messagesMap.set(result.name, result.messages);
|
||||
});
|
||||
await Promise.all(promises);
|
||||
return messagesMap;
|
||||
} finally {
|
||||
if (cleanUpAtTheEnd) {
|
||||
if (await exists(tempFolder)) {
|
||||
await remove(tempFolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
// TODO
|
|
@ -0,0 +1,33 @@
|
|||
import { ESLintUtils } from '@typescript-eslint/utils';
|
||||
import { RuleContext } from '@typescript-eslint/utils/ts-eslint';
|
||||
import { RuleListener } from '@typescript-eslint/utils/eslint-utils';
|
||||
import { ruleDescriptions } from '../../common/models/rules/rule-descriptions';
|
||||
|
||||
// TODO: provide correct endpint
|
||||
const endpoint = 'https://a.b.c/rules';
|
||||
const createRule = ESLintUtils.RuleCreator((name) => `${endpoint}/${name}`);
|
||||
|
||||
export function createOperationRuleListener(
|
||||
id: string,
|
||||
createListener: (context: RuleContext<string, readonly unknown[]>) => RuleListener
|
||||
) {
|
||||
const messages = { default: '' };
|
||||
const defaultOptions: ReadonlyArray<unknown> = [];
|
||||
const rule = createRule({
|
||||
name: id,
|
||||
meta: {
|
||||
docs: {
|
||||
description: ruleDescriptions[id],
|
||||
},
|
||||
messages,
|
||||
schema: [],
|
||||
type: 'problem',
|
||||
},
|
||||
defaultOptions,
|
||||
create(context) {
|
||||
const listener = createListener(context);
|
||||
return listener;
|
||||
},
|
||||
});
|
||||
return rule;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import {
|
||||
ignoreInlineDeclarationsInOperationGroup
|
||||
} from './rule-ids';
|
||||
|
||||
export const ruleDescriptions: { [ruleId: string]: string } = {
|
||||
[ignoreInlineDeclarationsInOperationGroup]: 'Ignore inline types in routes',
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export const ignoreInlineDeclarationsInOperationGroup = 'ignore-inline-declarations-in-operation-group'
|
|
@ -0,0 +1,2 @@
|
|||
export * from './azure/detect-breaking-changes';
|
||||
export * from './azure/common/types';
|
|
@ -0,0 +1,23 @@
|
|||
import { pino } from 'pino';
|
||||
import pretty from 'pino-pretty';
|
||||
|
||||
const devStream = pretty({
|
||||
colorize: false,
|
||||
levelFirst: true,
|
||||
translateTime: 'yyyy-dd-mm, h:MM:ss TT',
|
||||
// destination: 'logs/app.log',
|
||||
append: false,
|
||||
});
|
||||
|
||||
const stream = pretty({
|
||||
colorize: false,
|
||||
levelFirst: true,
|
||||
translateTime: 'yyyy-dd-mm, h:MM:ss TT',
|
||||
// destination: 'logs/app.log',
|
||||
append: true,
|
||||
});
|
||||
|
||||
// TODO: make logger configurable
|
||||
export const devFileLogger = pino({}, devStream);
|
||||
|
||||
export const logger = pino({}, stream);
|
|
@ -0,0 +1,46 @@
|
|||
import { mkdirp, pathExists } from 'fs-extra';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { join } from 'node:path';
|
||||
import { detectBreakingChangesBetweenPackages } from '../azure/detect-breaking-changes';
|
||||
|
||||
function getFormattedDate(): string {
|
||||
const today = new Date();
|
||||
|
||||
const year = today.getFullYear();
|
||||
const month = String(today.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(today.getDate()).padStart(2, '0');
|
||||
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
async function createTempFolder(tempFolderPrefix: string): Promise<string> {
|
||||
const maxRetry = 1000;
|
||||
let tempFolder = '';
|
||||
for (let i = 0; i < maxRetry; i++) {
|
||||
tempFolder = `${tempFolderPrefix}-${Math.round(Math.random() * 1000)}`;
|
||||
if (await pathExists(tempFolder)) continue;
|
||||
|
||||
await mkdirp(tempFolder);
|
||||
return tempFolder;
|
||||
}
|
||||
throw new Error(`Failed to create temp folder at "${tempFolder}" for ${maxRetry} times`);
|
||||
}
|
||||
|
||||
describe('detect rest level client breaking changes', async () => {
|
||||
test('should ignore operation rename', async () => {
|
||||
const testCaseDir = '../../misc/test-cases/rest-level-client-to-rest-level-client/';
|
||||
const currentPackageFolder = join(__dirname, testCaseDir, 'current-package');
|
||||
const baselinePackageFolder = join(__dirname, testCaseDir, 'baseline-package');
|
||||
const date = getFormattedDate();
|
||||
const tempFolder = await createTempFolder(`.tmp/temp-${date}`);
|
||||
const messagesMap = await detectBreakingChangesBetweenPackages(
|
||||
baselinePackageFolder,
|
||||
currentPackageFolder,
|
||||
tempFolder,
|
||||
true
|
||||
);
|
||||
expect(messagesMap.size).toBe(1);
|
||||
// TODO: add more checks
|
||||
});
|
||||
});
|
|
@ -0,0 +1,159 @@
|
|||
import {
|
||||
EnumDeclaration,
|
||||
InterfaceDeclaration,
|
||||
Node,
|
||||
SyntaxKind,
|
||||
TypeAliasDeclaration,
|
||||
TypeReferenceNode,
|
||||
createWrappedNode,
|
||||
} from 'ts-morph';
|
||||
import { ParserServices, ParserServicesWithTypeInformation } from '@typescript-eslint/typescript-estree';
|
||||
import { Scope, ScopeManager } from '@typescript-eslint/scope-manager';
|
||||
|
||||
import { RenameAbleDeclarations } from '../azure/common/types';
|
||||
import { TSESTree } from '@typescript-eslint/types';
|
||||
import { findVariable } from '@typescript-eslint/utils/ast-utils';
|
||||
import { logger } from '../logging/logger';
|
||||
|
||||
function tryFindDeclaration<TNode extends TSESTree.Node>(
|
||||
name: string,
|
||||
scope: Scope,
|
||||
typeGuard: ((node: TSESTree.Node) => node is TNode) | undefined,
|
||||
shouldLog: boolean = true
|
||||
): TNode | undefined {
|
||||
const variable = findVariable(scope as Scope, name);
|
||||
const node = variable?.defs?.[0]?.node;
|
||||
if (!node) {
|
||||
if (shouldLog) logger.warn(`Failed to find ${name}'s declaration`);
|
||||
return undefined;
|
||||
}
|
||||
if (typeGuard && !typeGuard(node)) {
|
||||
if (shouldLog) logger.warn(`Found ${name}'s declaration but with another node type "${node.type}"`);
|
||||
return undefined;
|
||||
}
|
||||
return node as TNode;
|
||||
}
|
||||
|
||||
function getAllTypeReferencesInNode(node: Node, found: Set<string>) {
|
||||
const types: TypeReferenceNode[] = [];
|
||||
if (!node) return types;
|
||||
node.forEachChild((child) => {
|
||||
if (Node.isTypeReference(child) && !found.has(child.getText())) {
|
||||
types.push(child);
|
||||
}
|
||||
|
||||
const childTypes = getAllTypeReferencesInNode(child, found);
|
||||
childTypes.forEach((c) => {
|
||||
types.push(c);
|
||||
});
|
||||
});
|
||||
return types;
|
||||
}
|
||||
|
||||
function updateRenameAbleDeclarations(
|
||||
declaration: TypeAliasDeclaration | InterfaceDeclaration | EnumDeclaration,
|
||||
renameAbleDeclarations: RenameAbleDeclarations,
|
||||
found: Set<string>
|
||||
) {
|
||||
const foundDeclarations = new Set<string>(
|
||||
[...renameAbleDeclarations.interfaces, ...renameAbleDeclarations.typeAliases, ...renameAbleDeclarations.enums].map(
|
||||
(i) => i.getName()
|
||||
)
|
||||
);
|
||||
if (foundDeclarations.has(declaration.getName())) return;
|
||||
switch (declaration.getKind()) {
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
renameAbleDeclarations.interfaces.push(declaration as InterfaceDeclaration);
|
||||
break;
|
||||
case SyntaxKind.TypeAliasDeclaration:
|
||||
renameAbleDeclarations.typeAliases.push(declaration as TypeAliasDeclaration);
|
||||
break;
|
||||
case SyntaxKind.EnumDeclaration:
|
||||
renameAbleDeclarations.enums.push(declaration as EnumDeclaration);
|
||||
break;
|
||||
}
|
||||
found.add(declaration.getName());
|
||||
}
|
||||
|
||||
function findDeclarationOfTypeReference(
|
||||
reference: TypeReferenceNode,
|
||||
scope: Scope,
|
||||
service: ParserServicesWithTypeInformation
|
||||
): (TypeAliasDeclaration | InterfaceDeclaration | EnumDeclaration) | undefined {
|
||||
const esDeclaration = tryFindDeclaration(reference.getText(), scope, undefined, false);
|
||||
if (!esDeclaration) return;
|
||||
const msDeclaration = convertToMorphNode(esDeclaration, service);
|
||||
const msDeclarationKind = msDeclaration.getKind();
|
||||
if (
|
||||
msDeclarationKind !== SyntaxKind.InterfaceDeclaration &&
|
||||
msDeclarationKind !== SyntaxKind.TypeAliasDeclaration &&
|
||||
msDeclarationKind !== SyntaxKind.EnumDeclaration
|
||||
)
|
||||
return;
|
||||
const declaration = msDeclaration as TypeAliasDeclaration | InterfaceDeclaration | EnumDeclaration;
|
||||
return declaration;
|
||||
}
|
||||
|
||||
function findAllRenameAbleDeclarationsInNodeCore(
|
||||
node: Node,
|
||||
scope: Scope,
|
||||
service: ParserServicesWithTypeInformation,
|
||||
renameAbleDeclarations: RenameAbleDeclarations,
|
||||
found: Set<string>
|
||||
): void {
|
||||
if (!node) return;
|
||||
|
||||
const references = getAllTypeReferencesInNode(node, found);
|
||||
references.forEach((reference) => {
|
||||
const declaration = findDeclarationOfTypeReference(reference, scope, service);
|
||||
if (!declaration) return;
|
||||
updateRenameAbleDeclarations(declaration, renameAbleDeclarations, found);
|
||||
findAllRenameAbleDeclarationsInNodeCore(declaration, scope, service, renameAbleDeclarations, found);
|
||||
});
|
||||
}
|
||||
|
||||
export function getGlobalScope(scopeManager: ScopeManager | null): Scope {
|
||||
const globalScope = scopeManager?.globalScope;
|
||||
if (!globalScope) throw new Error(`Failed to find global scope`);
|
||||
return globalScope;
|
||||
}
|
||||
|
||||
export function findDeclaration<TNode extends TSESTree.Node>(
|
||||
name: string,
|
||||
scope: Scope,
|
||||
typeGuard: (node: TSESTree.Node) => node is TNode
|
||||
): TNode {
|
||||
const node = tryFindDeclaration(name, scope, typeGuard);
|
||||
if (!node) throw new Error(`Failed to find "${name}"`);
|
||||
return node;
|
||||
}
|
||||
|
||||
export function isParseServiceWithTypeInfo(service: ParserServices): service is ParserServicesWithTypeInformation {
|
||||
return service.program !== null;
|
||||
}
|
||||
|
||||
export function isInterfaceDeclarationNode(node: TSESTree.Node): node is TSESTree.TSInterfaceDeclaration {
|
||||
return node.type === TSESTree.AST_NODE_TYPES.TSInterfaceDeclaration;
|
||||
}
|
||||
|
||||
export function convertToMorphNode(node: TSESTree.Node, service: ParserServicesWithTypeInformation) {
|
||||
const tsNode = service.esTreeNodeToTSNodeMap.get(node);
|
||||
const typeChecker = service.program.getTypeChecker();
|
||||
const moNode = createWrappedNode(tsNode, { typeChecker });
|
||||
return moNode;
|
||||
}
|
||||
|
||||
export function findAllRenameAbleDeclarationsInNode(
|
||||
node: Node,
|
||||
scope: Scope,
|
||||
service: ParserServicesWithTypeInformation
|
||||
): { interfaces: InterfaceDeclaration[]; typeAliases: TypeAliasDeclaration[]; enums: EnumDeclaration[] } {
|
||||
const renameAbleDeclarations: RenameAbleDeclarations = {
|
||||
interfaces: [],
|
||||
typeAliases: [],
|
||||
enums: [],
|
||||
};
|
||||
const found = new Set<string>();
|
||||
findAllRenameAbleDeclarationsInNodeCore(node, scope, service, renameAbleDeclarations, found);
|
||||
return renameAbleDeclarations;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import { LinterSettings } from '../azure/common/types';
|
||||
import { RuleContext } from '@typescript-eslint/utils/ts-eslint';
|
||||
import path from 'path';
|
||||
import util from 'util';
|
||||
|
||||
// IMPORTANT: dev with chakrounanas.turbo-console-log vscode extension
|
||||
export function devConsolelog(title?: any, ...optionalParams: any[]): void {
|
||||
const start = '💎💎💎💎';
|
||||
const end = '📍📍📍📍';
|
||||
const body = util.inspect(optionalParams, { depth: null, colors: true });
|
||||
console.log(start, 'START:', title, start);
|
||||
console.log(body);
|
||||
console.log(end, 'END :', title, end);
|
||||
console.log();
|
||||
}
|
||||
|
||||
export function getSettings(context: RuleContext<string, readonly unknown[]>) {
|
||||
return (context.settings as any as LinterSettings)!;
|
||||
}
|
||||
|
||||
export function toPosixPath(winPath: string) {
|
||||
const posixPath = winPath.split(path.sep).join(path.posix.sep);
|
||||
return posixPath;
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
/* Language and Environment */
|
||||
"target": "ES2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
/* Modules */
|
||||
"module": "NodeNext", /* Specify what module code is generated. */
|
||||
"rootDir": ".", /* Specify the root folder within your source files. */
|
||||
"moduleResolution": "NodeNext", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
"resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
/* Emit */
|
||||
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
"declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "dist", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
|
||||
"typeRoots": ["./node_modules/@types", "./dist/src"],
|
||||
|
||||
},
|
||||
"exclude": ["**/tests/**/cases/**", "dist/**", "packages/**"],
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
exclude: ['misc/**', 'dist/**', 'node_modules/**'],
|
||||
},
|
||||
});
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,118 @@
|
|||
import { Changelog, ChangelogItem } from './changelogGenerator';
|
||||
import { InlineDeclarationNameSetMessage, NodeContext } from 'typescript-codegen-breaking-change-detector';
|
||||
|
||||
export class RestLevelClientChangelogPostProcessor {
|
||||
private changelog: Changelog;
|
||||
private message: InlineDeclarationNameSetMessage;
|
||||
constructor(changelog: Changelog, message: InlineDeclarationNameSetMessage) {
|
||||
this.changelog = changelog;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public run() {
|
||||
this.handleChangelogItems(this.changelog.addedOperationGroup);
|
||||
this.handleChangelogItems(this.changelog.removedOperationGroup);
|
||||
this.handleChangelogItems(this.changelog.addedOperation);
|
||||
this.handleChangelogItems(this.changelog.addedInterface);
|
||||
this.handleChangelogItems(this.changelog.addedClass);
|
||||
this.handleChangelogItems(this.changelog.addedTypeAlias);
|
||||
this.handleChangelogItems(this.changelog.interfaceAddOptionalParam);
|
||||
this.handleChangelogItems(this.changelog.interfaceParamTypeExtended);
|
||||
this.handleChangelogItems(this.changelog.typeAliasAddInherit);
|
||||
this.handleChangelogItems(this.changelog.typeAliasAddParam);
|
||||
this.handleChangelogItems(this.changelog.addedEnum);
|
||||
this.handleChangelogItems(this.changelog.addedEnumValue);
|
||||
this.handleChangelogItems(this.changelog.addedFunction);
|
||||
this.handleChangelogItems(this.changelog.removedOperation);
|
||||
this.handleChangelogItems(this.changelog.operationSignatureChange);
|
||||
this.handleChangelogItems(this.changelog.deletedClass);
|
||||
this.handleChangelogItems(this.changelog.classSignatureChange);
|
||||
this.handleChangelogItems(this.changelog.interfaceParamDelete);
|
||||
this.handleChangelogItems(this.changelog.interfaceParamAddRequired);
|
||||
this.handleChangelogItems(this.changelog.interfaceParamTypeChanged);
|
||||
this.handleChangelogItems(this.changelog.interfaceParamChangeRequired);
|
||||
this.handleChangelogItems(this.changelog.classParamDelete);
|
||||
this.handleChangelogItems(this.changelog.classParamChangeRequired);
|
||||
this.handleChangelogItems(this.changelog.typeAliasDeleteInherit);
|
||||
this.handleChangelogItems(this.changelog.typeAliasParamDelete);
|
||||
this.handleChangelogItems(this.changelog.typeAliasAddRequiredParam);
|
||||
this.handleChangelogItems(this.changelog.typeAliasParamChangeRequired);
|
||||
this.handleChangelogItems(this.changelog.removedEnum);
|
||||
this.handleChangelogItems(this.changelog.removedEnumValue);
|
||||
this.handleChangelogItems(this.changelog.removedFunction);
|
||||
}
|
||||
|
||||
private getCurrentNodeContext(name: string | undefined): NodeContext | undefined {
|
||||
if (!name) return undefined;
|
||||
return this.message.current.get(name);
|
||||
}
|
||||
|
||||
private getBaselineNodeContext(name: string | undefined): NodeContext | undefined {
|
||||
if (!name) return undefined;
|
||||
return this.message.baseline.get(name);
|
||||
}
|
||||
|
||||
private findCompatibleNodeContext(
|
||||
inputContext: NodeContext,
|
||||
contextMapToFind: Map<string, NodeContext>,
|
||||
): NodeContext | undefined {
|
||||
for (const [_, foundContext] of contextMapToFind) {
|
||||
const isCompatibleFromInputToFound = inputContext.node.getType().isAssignableTo(foundContext.node.getType())
|
||||
const isCompatibleFromFoundToInput = foundContext.node.getType().isAssignableTo(inputContext.node.getType())
|
||||
if (isCompatibleFromInputToFound && isCompatibleFromFoundToInput) return foundContext;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private tryIgnoreInlineTypeInChangelogItem(
|
||||
inputContext: NodeContext,
|
||||
nodeContextMapToFind: Map<string, NodeContext>,
|
||||
item: ChangelogItem
|
||||
) {
|
||||
if (!inputContext) return;
|
||||
const foundContext = this.findCompatibleNodeContext(
|
||||
inputContext,
|
||||
nodeContextMapToFind,
|
||||
);
|
||||
if (foundContext) {
|
||||
inputContext.used = true;
|
||||
foundContext.used = true;
|
||||
item.toDelete = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private handleChangelogItems(items: ChangelogItem[]) {
|
||||
items.forEach((item) => {
|
||||
if (!item.oldName && !item.newName) return;
|
||||
|
||||
if (item.newName && item.oldName) {
|
||||
const currentContext = this.getCurrentNodeContext(item.newName);
|
||||
if (!currentContext) return;
|
||||
const baselineContext = this.getBaselineNodeContext(item.oldName);
|
||||
if (!baselineContext) return;
|
||||
const currentType = currentContext.node.getType();
|
||||
const baselineType = baselineContext.node.getType();
|
||||
if (currentType.isAssignableTo(baselineType)) {
|
||||
item.toDelete = true;
|
||||
currentContext.used = true;
|
||||
baselineContext.used = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.newName) {
|
||||
const inputContext = this.getCurrentNodeContext(item.newName);
|
||||
if (!inputContext) return;
|
||||
this.tryIgnoreInlineTypeInChangelogItem(inputContext, this.message.baseline, item);
|
||||
return;
|
||||
}
|
||||
|
||||
// item.oldName exists
|
||||
const inputContext = this.getBaselineNodeContext(item.oldName);
|
||||
if (!inputContext) return;
|
||||
this.tryIgnoreInlineTypeInChangelogItem(inputContext, this.message.current, item);
|
||||
return;
|
||||
});
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,5 +1,5 @@
|
|||
import { ApiVersionType } from "./types";
|
||||
|
||||
export interface IApiVersionTypeExtractor {
|
||||
(packageRoot: string): ApiVersionType;
|
||||
(packageRoot: string): Promise<ApiVersionType>;
|
||||
}
|
|
@ -47,6 +47,10 @@ export function getClassicClientParametersPath(packageRoot: string): string {
|
|||
|
||||
export function getSDKType(packageRoot: string): SDKType {
|
||||
const paraPath = getClassicClientParametersPath(packageRoot);
|
||||
const packageName = getNpmPackageName(packageRoot);
|
||||
if (packageName.startsWith('@azure-rest/')) {
|
||||
return SDKType.RestLevelClient;
|
||||
}
|
||||
const exist = shell.test('-e', paraPath);
|
||||
const type = exist ? SDKType.HighLevelClient : SDKType.ModularClient;
|
||||
logger.info(`SDK type '${type}' is detected in '${packageRoot}'.`);
|
||||
|
|
|
@ -3,7 +3,7 @@ import { IApiVersionTypeExtractor } from "../../common/interfaces";
|
|||
import { getClassicClientParametersPath, getTsSourceFile } from "../../common/utils";
|
||||
|
||||
// TODO: add unit test
|
||||
export const getApiVersionType: IApiVersionTypeExtractor = (packageRoot: string): ApiVersionType => {
|
||||
export const getApiVersionType: IApiVersionTypeExtractor = async (packageRoot: string): Promise<ApiVersionType> => {
|
||||
const paraPath = getClassicClientParametersPath(packageRoot);
|
||||
const source = getTsSourceFile(paraPath);
|
||||
const variableDeclarations = source?.getVariableDeclarations();
|
||||
|
|
|
@ -20,14 +20,14 @@ import {
|
|||
} from "../../utils/version";
|
||||
import { execSync } from "child_process";
|
||||
import { getversionDate } from "../../utils/version";
|
||||
import { ApiVersionType } from "../../common/types"
|
||||
import { ApiVersionType, SDKType } from "../../common/types"
|
||||
import { getApiVersionType } from '../../xlc/apiVersion/apiVersionTypeExtractor'
|
||||
import { fixChangelogFormat, getApiReviewPath, getNpmPackageName, getSDKType, tryReadNpmPackageChangelog } from '../../common/utils';
|
||||
|
||||
export async function generateChangelogAndBumpVersion(packageFolderPath: string) {
|
||||
const jsSdkRepoPath = String(shell.pwd());
|
||||
packageFolderPath = path.join(jsSdkRepoPath, packageFolderPath);
|
||||
const ApiType = getApiVersionType(packageFolderPath);
|
||||
const ApiType = await getApiVersionType(packageFolderPath);
|
||||
const isStableRelease = ApiType != ApiVersionType.Preview;
|
||||
const packageName = getNpmPackageName(packageFolderPath);
|
||||
const npm = new NPMScope({ executionFolderPath: packageFolderPath });
|
||||
|
@ -57,7 +57,8 @@ export async function generateChangelogAndBumpVersion(packageFolderPath: string)
|
|||
|
||||
// only track2 sdk includes sdk-type with value mgmt
|
||||
const sdkType = JSON.parse(fs.readFileSync(path.join(packageFolderPath, 'changelog-temp', 'package', 'package.json'), {encoding: 'utf-8'}))['sdk-type'];
|
||||
if (sdkType && sdkType === 'mgmt') {
|
||||
const clientType = getSDKType(packageFolderPath);
|
||||
if (sdkType && sdkType === 'mgmt' || clientType === SDKType.RestLevelClient) {
|
||||
logger.info(`Package ${packageName} released before is track2 sdk.`);
|
||||
logger.info('Start to generate changelog by comparing api.md.');
|
||||
const npmPackageRoot = path.join(packageFolderPath, 'changelog-temp', 'package');
|
||||
|
@ -100,6 +101,7 @@ export async function generateChangelogAndBumpVersion(packageFolderPath: string)
|
|||
}
|
||||
makeChangesForPatchReleasingTrack2(packageFolderPath, newVersion);
|
||||
} else {
|
||||
await changelog.postProcess(npmPackageRoot, packageFolderPath, clientType)
|
||||
const newVersion = getNewVersion(stableVersion, usedVersions, changelog.hasBreakingChange, isStableRelease);
|
||||
makeChangesForReleasingTrack2(packageFolderPath, newVersion, changelog, originalChangeLogContent,stableVersion);
|
||||
logger.info('Generated changelogs and set version for track2 release successfully.');
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import { findParametersPath, getApiVersionTypeFromOperations, getApiVersionTypeFromRestClient, tryFindRestClientPath } from "../../xlc/apiVersion/utils";
|
||||
|
||||
import { ApiVersionType } from "../../common/types";
|
||||
import { IApiVersionTypeExtractor } from "../../common/interfaces";
|
||||
|
||||
export const getApiVersionType: IApiVersionTypeExtractor = async (
|
||||
packageRoot: string
|
||||
): Promise<ApiVersionType> => {
|
||||
let clientPattern = "src/*Context.ts";
|
||||
let typeFromClient = await getApiVersionTypeFromRestClient(packageRoot, clientPattern, tryFindRestClientPath);
|
||||
if (typeFromClient !== ApiVersionType.None) return typeFromClient;
|
||||
|
||||
clientPattern = "src/*Client.ts";
|
||||
typeFromClient = await getApiVersionTypeFromRestClient(packageRoot, clientPattern, tryFindRestClientPath);
|
||||
if (typeFromClient !== ApiVersionType.None) return typeFromClient;
|
||||
|
||||
const typeFromOperations = getApiVersionTypeFromOperations(packageRoot, clientPattern, findParametersPath);
|
||||
if (typeFromOperations !== ApiVersionType.None) return typeFromOperations;
|
||||
return ApiVersionType.Stable;
|
||||
};
|
|
@ -1,135 +1,19 @@
|
|||
import { SourceFile, SyntaxKind } from "ts-morph";
|
||||
import shell from "shelljs";
|
||||
import path from "path";
|
||||
import * as ts from "typescript";
|
||||
|
||||
import { ApiVersionType } from "../../common/types";
|
||||
import { IApiVersionTypeExtractor } from "../../common/interfaces";
|
||||
import { getTsSourceFile } from "../../common/utils";
|
||||
import { readFileSync } from "fs";
|
||||
import { findParametersPath, getApiVersionTypeFromOperations, getApiVersionTypeFromRestClient, tryFindRestClientPath } from "../../xlc/apiVersion/utils";
|
||||
|
||||
const findRestClientPath = (packageRoot: string): string => {
|
||||
const restPath = path.join(packageRoot, "src/rest/");
|
||||
const fileNames = shell.ls(restPath);
|
||||
const clientFiles = fileNames.filter((f) => f.endsWith("Client.ts"));
|
||||
if (clientFiles.length !== 1)
|
||||
throw new Error(`Single client is supported, but found "${clientFiles}" in ${restPath}`);
|
||||
|
||||
const clientPath = path.join(restPath, clientFiles[0]);
|
||||
return clientPath;
|
||||
};
|
||||
|
||||
const findApiVersionInRestClientV1 = (
|
||||
clientPath: string
|
||||
): string | undefined => {
|
||||
const sourceFile = getTsSourceFile(clientPath);
|
||||
const createClientFunction = sourceFile?.getFunction("createClient");
|
||||
if (!createClientFunction)
|
||||
throw new Error("Function 'createClient' not found.");
|
||||
|
||||
const apiVersionStatements = createClientFunction
|
||||
.getStatements()
|
||||
.filter((s) => s.getText().includes("options.apiVersion"));
|
||||
if (apiVersionStatements.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const text =
|
||||
apiVersionStatements[apiVersionStatements.length - 1].getText();
|
||||
return extractApiVersionFromText(text);
|
||||
};
|
||||
|
||||
const extractApiVersionFromText = (text: string): string | undefined => {
|
||||
const begin = text.indexOf('"');
|
||||
const end = text.lastIndexOf('"');
|
||||
return text.substring(begin + 1, end);
|
||||
};
|
||||
|
||||
// new ways in @autorest/typespec-ts emitter to set up api-version
|
||||
const findApiVersionInRestClientV2 = (clientPath: string): string | undefined => {
|
||||
const sourceCode= readFileSync(clientPath, {encoding: 'utf-8'})
|
||||
const sourceFile = ts.createSourceFile("example.ts", sourceCode, ts.ScriptTarget.Latest, true);
|
||||
const createClientFunction = sourceFile.statements.filter(s => (s as ts.FunctionDeclaration)?.name?.escapedText === 'createClient').map(s => (s as ts.FunctionDeclaration))[0];
|
||||
let apiVersion: string | undefined = undefined;
|
||||
createClientFunction.parameters.forEach(p => {
|
||||
const isBindingPattern = node => node && typeof node === "object" && "elements" in node && "parent" in node && "kind" in node;
|
||||
if (!isBindingPattern(p.name)) {
|
||||
return;
|
||||
}
|
||||
const binding = p.name as ts.ObjectBindingPattern;
|
||||
const apiVersionTexts = binding.elements?.filter(e => (e.name as ts.Identifier)?.escapedText === "apiVersion").map(e => e.initializer?.getText());
|
||||
// apiVersionTexts.length must be 0 or 1, otherwise the binding pattern contains the same keys, which causes a ts error
|
||||
if (apiVersionTexts.length === 1 && apiVersionTexts[0]) {
|
||||
apiVersion = extractApiVersionFromText(apiVersionTexts[0]);
|
||||
}
|
||||
});
|
||||
return apiVersion;
|
||||
};
|
||||
|
||||
// workaround for createClient function changes it's way to setup api-version
|
||||
export const findApiVersionInRestClient = (clientPath: string): string | undefined => {
|
||||
const version2 = findApiVersionInRestClientV2(clientPath);
|
||||
if (version2) {
|
||||
return version2;
|
||||
}
|
||||
const version1 = findApiVersionInRestClientV1(clientPath);
|
||||
return version1;
|
||||
};
|
||||
|
||||
const getApiVersionTypeFromRestClient: IApiVersionTypeExtractor = (
|
||||
export const getApiVersionType: IApiVersionTypeExtractor = async (
|
||||
packageRoot: string
|
||||
): ApiVersionType => {
|
||||
const clientPath = findRestClientPath(packageRoot);
|
||||
const apiVersion = findApiVersionInRestClient(clientPath);
|
||||
if (apiVersion && apiVersion.indexOf("-preview") >= 0)
|
||||
return ApiVersionType.Preview;
|
||||
if (apiVersion && apiVersion.indexOf("-preview") < 0)
|
||||
return ApiVersionType.Stable;
|
||||
return ApiVersionType.None;
|
||||
};
|
||||
|
||||
const findApiVersionsInOperations = (
|
||||
sourceFile: SourceFile | undefined
|
||||
): Array<string> | undefined => {
|
||||
const interfaces = sourceFile?.getInterfaces();
|
||||
const interfacesWithApiVersion = interfaces?.filter((itf) =>
|
||||
itf.getProperty('"api-version"')
|
||||
);
|
||||
const apiVersions = interfacesWithApiVersion?.map((itf) => {
|
||||
const property = itf.getMembers().filter((m) => {
|
||||
const defaultValue = m.getChildrenOfKind(
|
||||
SyntaxKind.StringLiteral
|
||||
)[0];
|
||||
return defaultValue && defaultValue.getText() === '"api-version"';
|
||||
})[0];
|
||||
const apiVersion = property
|
||||
.getChildrenOfKind(SyntaxKind.LiteralType)[0]
|
||||
.getText();
|
||||
return apiVersion;
|
||||
});
|
||||
return apiVersions;
|
||||
};
|
||||
|
||||
const getApiVersionTypeFromOperations: IApiVersionTypeExtractor = (
|
||||
packageRoot: string
|
||||
): ApiVersionType => {
|
||||
const paraPath = path.join(packageRoot, "src/rest/parameters.ts");
|
||||
const sourceFile = getTsSourceFile(paraPath);
|
||||
const apiVersions = findApiVersionsInOperations(sourceFile);
|
||||
if (!apiVersions) return ApiVersionType.None;
|
||||
const previewVersions = apiVersions.filter(
|
||||
(v) => v.indexOf("-preview") >= 0
|
||||
);
|
||||
return previewVersions.length > 0
|
||||
? ApiVersionType.Preview
|
||||
: ApiVersionType.Stable;
|
||||
};
|
||||
|
||||
export const getApiVersionType: IApiVersionTypeExtractor = (
|
||||
packageRoot: string
|
||||
): ApiVersionType => {
|
||||
const typeFromClient = getApiVersionTypeFromRestClient(packageRoot);
|
||||
): Promise<ApiVersionType> => {
|
||||
let clientPattern = "src/api/*Context.ts";
|
||||
let typeFromClient = await getApiVersionTypeFromRestClient(packageRoot, clientPattern, tryFindRestClientPath);
|
||||
if (typeFromClient !== ApiVersionType.None) return typeFromClient;
|
||||
const typeFromOperations = getApiVersionTypeFromOperations(packageRoot);
|
||||
|
||||
clientPattern = "src/rest/*Client.ts";
|
||||
typeFromClient = await getApiVersionTypeFromRestClient(packageRoot, clientPattern, tryFindRestClientPath);
|
||||
if (typeFromClient !== ApiVersionType.None) return typeFromClient;
|
||||
|
||||
const typeFromOperations = getApiVersionTypeFromOperations(packageRoot, clientPattern, findParametersPath);
|
||||
if (typeFromOperations !== ApiVersionType.None) return typeFromOperations;
|
||||
return ApiVersionType.Stable;
|
||||
};
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { expect, test } from "vitest";
|
||||
import { findApiVersionInRestClient, getApiVersionType } from "../../mlc/apiVersion/apiVersionTypeExtractor";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { getApiVersionType } from "../../mlc/apiVersion/apiVersionTypeExtractor";
|
||||
import { getApiVersionType as getApiVersionTypeInRLC } from "../../llc/apiVersion/apiVersionTypeExtractor";
|
||||
import { join } from "path";
|
||||
import { ApiVersionType } from "../../common/types";
|
||||
import { findApiVersionInRestClient } from "../../xlc/apiVersion/utils";
|
||||
|
||||
test("MLC api-version Extractor: new createClient function", async () => {
|
||||
const clientPath = join(__dirname, 'testCases/new/src/rest/newClient.ts');
|
||||
|
@ -11,7 +13,7 @@ test("MLC api-version Extractor: new createClient function", async () => {
|
|||
|
||||
test("MLC api-version Extractor: get api version type from new createClient function", async () => {
|
||||
const root = join(__dirname, 'testCases/new/');
|
||||
const version = getApiVersionType(root);
|
||||
const version = await getApiVersionType(root);
|
||||
expect(version).toBe(ApiVersionType.Preview);
|
||||
});
|
||||
|
||||
|
@ -23,7 +25,7 @@ test("MLC api-version Extractor: old createClient function 1", async () => {
|
|||
|
||||
test("MLC api-version Extractor: get api version type from old createClient function 1", async () => {
|
||||
const root = join(__dirname, 'testCases/old1/');
|
||||
const version = getApiVersionType(root);
|
||||
const version = await getApiVersionType(root);
|
||||
expect(version).toBe(ApiVersionType.Preview);
|
||||
});
|
||||
|
||||
|
@ -35,6 +37,36 @@ test("MLC api-version Extractor: old createClient function 2", async () => {
|
|||
|
||||
test("MLC api-version Extractor: get api version type from old createClient function 2", async () => {
|
||||
const root = join(__dirname, 'testCases/old2/');
|
||||
const version = getApiVersionType(root);
|
||||
const version = await getApiVersionType(root);
|
||||
expect(version).toBe(ApiVersionType.Stable);
|
||||
});
|
||||
|
||||
describe("Rest client file fallbacks", () => {
|
||||
// Modular
|
||||
{
|
||||
test("Modular: src/api/xxxContext.ts exists", async () => {
|
||||
const root = join(__dirname, 'testCases/new-context/');
|
||||
const version = await getApiVersionType(root);
|
||||
expect(version).toBe(ApiVersionType.Preview);
|
||||
});
|
||||
test("Modular: src/api/xxxContext.ts doesn't exists, fallback to src/rest/xxxClient.ts", async () => {
|
||||
const root = join(__dirname, 'testCases/new/');
|
||||
const version = await getApiVersionType(root);
|
||||
expect(version).toBe(ApiVersionType.Preview);
|
||||
});
|
||||
}
|
||||
// RLC
|
||||
{
|
||||
test("RLC: src/xxxContext.ts exists", async () => {
|
||||
const root = join(__dirname, 'testCases/rlc-context/');
|
||||
const version = await getApiVersionTypeInRLC(root);
|
||||
expect(version).toBe(ApiVersionType.Preview);
|
||||
|
||||
});
|
||||
test("RLC: src/xxxContext.ts doesn't exists, fallback to src/xxxClient.ts", async () => {
|
||||
const root = join(__dirname, 'testCases/rlc-client/');
|
||||
const version = await getApiVersionTypeInRLC(root);
|
||||
expect(version).toBe(ApiVersionType.Preview);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { getClient, ClientOptions } from "@azure-rest/core-client";
|
||||
import { logger } from "../logger.js";
|
||||
import { TokenCredential } from "@azure/core-auth";
|
||||
import { DocumentDBContext } from "./clientDefinitions.js";
|
||||
|
||||
/** The optional parameters for the client */
|
||||
export interface DocumentDBContextOptions extends ClientOptions {
|
||||
/** The api version option of the client */
|
||||
apiVersion?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new instance of `DocumentDBContext`
|
||||
* @param credentials - uniquely identify client credential
|
||||
* @param options - the parameter for all optional parameters
|
||||
*/
|
||||
export default function createClient(
|
||||
credentials: TokenCredential,
|
||||
{
|
||||
apiVersion = "2024-03-01-preview",
|
||||
...options
|
||||
}: DocumentDBContextOptions = {},
|
||||
): DocumentDBContext {
|
||||
const endpointUrl =
|
||||
options.endpoint ?? options.baseUrl ?? `https://management.azure.com`;
|
||||
const userAgentInfo = `azsdk-js-arm-mongocluster/1.0.0-beta.1`;
|
||||
const userAgentPrefix =
|
||||
options.userAgentOptions && options.userAgentOptions.userAgentPrefix
|
||||
? `${options.userAgentOptions.userAgentPrefix} ${userAgentInfo}`
|
||||
: `${userAgentInfo}`;
|
||||
options = {
|
||||
...options,
|
||||
userAgentOptions: {
|
||||
userAgentPrefix,
|
||||
},
|
||||
loggingOptions: {
|
||||
logger: options.loggingOptions?.logger ?? logger.info,
|
||||
},
|
||||
credentials: {
|
||||
scopes: options.credentials?.scopes ?? [`${endpointUrl}/.default`],
|
||||
},
|
||||
};
|
||||
const client = getClient(
|
||||
endpointUrl,
|
||||
credentials,
|
||||
options,
|
||||
) as DocumentDBContext;
|
||||
|
||||
client.pipeline.removePolicy({ name: "ApiVersionPolicy" });
|
||||
client.pipeline.addPolicy({
|
||||
name: "ClientApiVersionPolicy",
|
||||
sendRequest: (req, next) => {
|
||||
// Use the apiVersion defined in request url directly
|
||||
// Append one if there is no apiVersion and we have one at client options
|
||||
const url = new URL(req.url);
|
||||
if (!url.searchParams.get("api-version") && apiVersion) {
|
||||
req.url = `${req.url}${Array.from(url.searchParams.keys()).length > 0 ? "&" : "?"
|
||||
}api-version=${apiVersion}`;
|
||||
}
|
||||
|
||||
return next(req);
|
||||
},
|
||||
});
|
||||
return client;
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { getClient, ClientOptions } from "@azure-rest/core-client";
|
||||
import { logger } from "../logger.js";
|
||||
import { TokenCredential } from "@azure/core-auth";
|
||||
import { DocumentDBContext } from "./clientDefinitions.js";
|
||||
|
||||
/** The optional parameters for the client */
|
||||
export interface DocumentDBContextOptions extends ClientOptions {
|
||||
/** The api version option of the client */
|
||||
apiVersion?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new instance of `DocumentDBContext`
|
||||
* @param credentials - uniquely identify client credential
|
||||
* @param options - the parameter for all optional parameters
|
||||
*/
|
||||
export default function createClient(
|
||||
credentials: TokenCredential,
|
||||
{
|
||||
apiVersion = "2024-03-01-preview",
|
||||
...options
|
||||
}: DocumentDBContextOptions = {},
|
||||
): DocumentDBContext {
|
||||
const endpointUrl =
|
||||
options.endpoint ?? options.baseUrl ?? `https://management.azure.com`;
|
||||
const userAgentInfo = `azsdk-js-arm-mongocluster/1.0.0-beta.1`;
|
||||
const userAgentPrefix =
|
||||
options.userAgentOptions && options.userAgentOptions.userAgentPrefix
|
||||
? `${options.userAgentOptions.userAgentPrefix} ${userAgentInfo}`
|
||||
: `${userAgentInfo}`;
|
||||
options = {
|
||||
...options,
|
||||
userAgentOptions: {
|
||||
userAgentPrefix,
|
||||
},
|
||||
loggingOptions: {
|
||||
logger: options.loggingOptions?.logger ?? logger.info,
|
||||
},
|
||||
credentials: {
|
||||
scopes: options.credentials?.scopes ?? [`${endpointUrl}/.default`],
|
||||
},
|
||||
};
|
||||
const client = getClient(
|
||||
endpointUrl,
|
||||
credentials,
|
||||
options,
|
||||
) as DocumentDBContext;
|
||||
|
||||
client.pipeline.removePolicy({ name: "ApiVersionPolicy" });
|
||||
client.pipeline.addPolicy({
|
||||
name: "ClientApiVersionPolicy",
|
||||
sendRequest: (req, next) => {
|
||||
// Use the apiVersion defined in request url directly
|
||||
// Append one if there is no apiVersion and we have one at client options
|
||||
const url = new URL(req.url);
|
||||
if (!url.searchParams.get("api-version") && apiVersion) {
|
||||
req.url = `${req.url}${Array.from(url.searchParams.keys()).length > 0 ? "&" : "?"
|
||||
}api-version=${apiVersion}`;
|
||||
}
|
||||
|
||||
return next(req);
|
||||
},
|
||||
});
|
||||
return client;
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { getClient, ClientOptions } from "@azure-rest/core-client";
|
||||
import { logger } from "../logger.js";
|
||||
import { TokenCredential } from "@azure/core-auth";
|
||||
import { DocumentDBContext } from "./clientDefinitions.js";
|
||||
|
||||
/** The optional parameters for the client */
|
||||
export interface DocumentDBContextOptions extends ClientOptions {
|
||||
/** The api version option of the client */
|
||||
apiVersion?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new instance of `DocumentDBContext`
|
||||
* @param credentials - uniquely identify client credential
|
||||
* @param options - the parameter for all optional parameters
|
||||
*/
|
||||
export default function createClient(
|
||||
credentials: TokenCredential,
|
||||
{
|
||||
apiVersion = "2024-03-01-preview",
|
||||
...options
|
||||
}: DocumentDBContextOptions = {},
|
||||
): DocumentDBContext {
|
||||
const endpointUrl =
|
||||
options.endpoint ?? options.baseUrl ?? `https://management.azure.com`;
|
||||
const userAgentInfo = `azsdk-js-arm-mongocluster/1.0.0-beta.1`;
|
||||
const userAgentPrefix =
|
||||
options.userAgentOptions && options.userAgentOptions.userAgentPrefix
|
||||
? `${options.userAgentOptions.userAgentPrefix} ${userAgentInfo}`
|
||||
: `${userAgentInfo}`;
|
||||
options = {
|
||||
...options,
|
||||
userAgentOptions: {
|
||||
userAgentPrefix,
|
||||
},
|
||||
loggingOptions: {
|
||||
logger: options.loggingOptions?.logger ?? logger.info,
|
||||
},
|
||||
credentials: {
|
||||
scopes: options.credentials?.scopes ?? [`${endpointUrl}/.default`],
|
||||
},
|
||||
};
|
||||
const client = getClient(
|
||||
endpointUrl,
|
||||
credentials,
|
||||
options,
|
||||
) as DocumentDBContext;
|
||||
|
||||
client.pipeline.removePolicy({ name: "ApiVersionPolicy" });
|
||||
client.pipeline.addPolicy({
|
||||
name: "ClientApiVersionPolicy",
|
||||
sendRequest: (req, next) => {
|
||||
// Use the apiVersion defined in request url directly
|
||||
// Append one if there is no apiVersion and we have one at client options
|
||||
const url = new URL(req.url);
|
||||
if (!url.searchParams.get("api-version") && apiVersion) {
|
||||
req.url = `${req.url}${Array.from(url.searchParams.keys()).length > 0 ? "&" : "?"
|
||||
}api-version=${apiVersion}`;
|
||||
}
|
||||
|
||||
return next(req);
|
||||
},
|
||||
});
|
||||
return client;
|
||||
}
|
|
@ -3,9 +3,8 @@ import { extractExportAndGenerateChangelog } from "../../changelog/extractMetaDa
|
|||
import path, { join } from "path";
|
||||
import { SDKType } from "../../common/types";
|
||||
import { describe } from "node:test";
|
||||
import { mkdirSync } from "node:fs";
|
||||
import { tryReadNpmPackageChangelog } from "../../common/utils";
|
||||
import { rmdirSync, writeFileSync } from "fs";
|
||||
import { ensureDirSync, removeSync, outputFileSync } from "fs-extra";
|
||||
|
||||
function getRandomInt(max) {
|
||||
return Math.floor(Math.random() * max);
|
||||
|
@ -34,10 +33,10 @@ describe("Breaking change detection", () => {
|
|||
expect(changelog.addedOperation.length).toBe(1);
|
||||
expect(changelog.removedOperation.length).toBe(1);
|
||||
|
||||
expect(changelog.addedOperation[0]).toBe(
|
||||
expect(changelog.addedOperation[0].line).toBe(
|
||||
"Added operation DataProductsCatalogsOperations.listByResourceGroup_NEW"
|
||||
);
|
||||
expect(changelog.removedOperation[0]).toBe(
|
||||
expect(changelog.removedOperation[0].line).toBe(
|
||||
"Removed operation DataProductsCatalogs.listByResourceGroup"
|
||||
);
|
||||
});
|
||||
|
@ -64,10 +63,10 @@ describe("Breaking change detection", () => {
|
|||
expect(changelog.addedOperation.length).toBe(1);
|
||||
expect(changelog.removedOperation.length).toBe(1);
|
||||
|
||||
expect(changelog.addedOperation[0]).toBe(
|
||||
expect(changelog.addedOperation[0].line).toBe(
|
||||
"Added operation DataProductsCatalogs.get_NEW"
|
||||
);
|
||||
expect(changelog.removedOperation[0]).toBe(
|
||||
expect(changelog.removedOperation[0].line).toBe(
|
||||
"Removed operation DataProductsCatalogs.get"
|
||||
);
|
||||
});
|
||||
|
@ -94,10 +93,10 @@ describe("Breaking change detection", () => {
|
|||
expect(changelog.addedOperation.length).toBe(1);
|
||||
expect(changelog.removedOperation.length).toBe(1);
|
||||
|
||||
expect(changelog.addedOperation[0]).toBe(
|
||||
expect(changelog.addedOperation[0].line).toBe(
|
||||
"Added operation DataProductsCatalogsOperations.listByResourceGroup_NEW"
|
||||
);
|
||||
expect(changelog.removedOperation[0]).toBe(
|
||||
expect(changelog.removedOperation[0].line).toBe(
|
||||
"Removed operation DataProductsCatalogsOperations.listByResourceGroup"
|
||||
);
|
||||
});
|
||||
|
@ -126,33 +125,34 @@ describe("Breaking change detection", () => {
|
|||
|
||||
expect(changelog.operationSignatureChange.length).toBe(1);
|
||||
|
||||
expect(changelog.addedOperationGroup[0]).toBe(
|
||||
expect(changelog.addedOperationGroup[0].line).toBe(
|
||||
"Added operation group DataProductsCatalogs_add"
|
||||
);
|
||||
expect(changelog.removedOperationGroup[0]).toBe(
|
||||
expect(changelog.removedOperationGroup[0].line).toBe(
|
||||
"Removed operation group DataProductsCatalogs_remove"
|
||||
);
|
||||
expect(changelog.operationSignatureChange[0]).toBe(
|
||||
expect(changelog.operationSignatureChange[0].line).toBe(
|
||||
"Operation DataProductsCatalogs_sig_change.get has a new signature"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Changelog reading", () => {
|
||||
const tempPackageFolder = `./tmp/package-${getRandomInt(10000)}`;
|
||||
try {
|
||||
test("Read changelog that doesn't exist", () => {
|
||||
const content = tryReadNpmPackageChangelog(tempPackageFolder);
|
||||
const content = tryReadNpmPackageChangelog('./do/not/exist');
|
||||
expect(content).toBe("");
|
||||
});
|
||||
const changelogPath = join(tempPackageFolder, 'CHANGELOG.md')
|
||||
writeFileSync(changelogPath, 'aaa', 'utf-8');
|
||||
|
||||
test("Read changelog that exists", () => {
|
||||
const content = tryReadNpmPackageChangelog(tempPackageFolder);
|
||||
expect(content).toBe("aaa");
|
||||
test("Read changelog that exists", () => {
|
||||
const tempPackageFolder = path.join(__dirname, `tmp/package-${getRandomInt(10000)}`);
|
||||
try {
|
||||
ensureDirSync(tempPackageFolder);
|
||||
const changelogPath = path.join(tempPackageFolder, 'changelog-temp', 'package', 'CHANGELOG.md')
|
||||
outputFileSync(changelogPath, 'aaa', 'utf-8');
|
||||
const content = tryReadNpmPackageChangelog(tempPackageFolder);
|
||||
expect(content).toBe("aaa");
|
||||
} finally {
|
||||
removeSync(tempPackageFolder);
|
||||
}
|
||||
})
|
||||
} finally {
|
||||
rmdirSync(tempPackageFolder);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -14,7 +14,6 @@ const pipeline = winston.format((info, opts) => {
|
|||
export const logger = winston.createLogger({
|
||||
level: "info",
|
||||
format: winston.format.combine(winston.format.json(), pipeline()),
|
||||
defaultMeta: { service: "user-service" },
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
|
|
|
@ -3,15 +3,18 @@ import { ApiVersionType, SDKType } from "../../common/types";
|
|||
import { IApiVersionTypeExtractor } from "../../common/interfaces";
|
||||
import * as mlcApi from '../../mlc/apiVersion/apiVersionTypeExtractor'
|
||||
import * as hlcApi from '../../hlc/apiVersion/apiVersionTypeExtractor'
|
||||
import * as rlcApi from '../../llc/apiVersion/apiVersionTypeExtractor'
|
||||
|
||||
// TODO: move to x-level-client folder
|
||||
export const getApiVersionType: IApiVersionTypeExtractor = (packageRoot: string): ApiVersionType => {
|
||||
export const getApiVersionType: IApiVersionTypeExtractor = async (packageRoot: string): Promise<ApiVersionType> => {
|
||||
const sdkType = getSDKType(packageRoot);
|
||||
switch (sdkType) {
|
||||
case SDKType.ModularClient:
|
||||
return mlcApi.getApiVersionType(packageRoot);
|
||||
return await mlcApi.getApiVersionType(packageRoot);
|
||||
case SDKType.HighLevelClient:
|
||||
return hlcApi.getApiVersionType(packageRoot);
|
||||
return await hlcApi.getApiVersionType(packageRoot);
|
||||
case SDKType.RestLevelClient:
|
||||
return await rlcApi.getApiVersionType(packageRoot);
|
||||
default:
|
||||
console.warn(`Unsupported SDK type ${sdkType} to get detact api version`);
|
||||
return ApiVersionType.None;
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
import { readFileSync } from "node:fs";
|
||||
import { getTsSourceFile } from "../../common/utils";
|
||||
import { ApiVersionType, SDKType } from "../../common/types";
|
||||
import ts from "typescript";
|
||||
import path from "node:path";
|
||||
import shell from "shelljs";
|
||||
import { SourceFile, SyntaxKind } from "ts-morph";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { glob } from 'glob'
|
||||
|
||||
var unixify = require('unixify');
|
||||
|
||||
const findApiVersionInRestClientV1 = (
|
||||
clientPath: string
|
||||
): string | undefined => {
|
||||
const sourceFile = getTsSourceFile(clientPath);
|
||||
const createClientFunction = sourceFile?.getFunction("createClient");
|
||||
if (!createClientFunction)
|
||||
throw new Error("Function 'createClient' not found.");
|
||||
|
||||
const apiVersionStatements = createClientFunction
|
||||
.getStatements()
|
||||
.filter((s) => s.getText().includes("options.apiVersion"));
|
||||
if (apiVersionStatements.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const text =
|
||||
apiVersionStatements[apiVersionStatements.length - 1].getText();
|
||||
return extractApiVersionFromText(text);
|
||||
};
|
||||
|
||||
const extractApiVersionFromText = (text: string): string | undefined => {
|
||||
const begin = text.indexOf('"');
|
||||
const end = text.lastIndexOf('"');
|
||||
return text.substring(begin + 1, end);
|
||||
};
|
||||
|
||||
// new ways in @autorest/typespec-ts emitter to set up api-version
|
||||
const findApiVersionInRestClientV2 = (clientPath: string): string | undefined => {
|
||||
const sourceCode= readFileSync(clientPath, {encoding: 'utf-8'})
|
||||
const sourceFile = ts.createSourceFile("example.ts", sourceCode, ts.ScriptTarget.Latest, true);
|
||||
const createClientFunction = sourceFile.statements.filter(s => (s as ts.FunctionDeclaration)?.name?.escapedText === 'createClient').map(s => (s as ts.FunctionDeclaration))[0];
|
||||
let apiVersion: string | undefined = undefined;
|
||||
createClientFunction.parameters.forEach(p => {
|
||||
const isBindingPattern = node => node && typeof node === "object" && "elements" in node && "parent" in node && "kind" in node;
|
||||
if (!isBindingPattern(p.name)) {
|
||||
return;
|
||||
}
|
||||
const binding = p.name as ts.ObjectBindingPattern;
|
||||
const apiVersionTexts = binding.elements?.filter(e => (e.name as ts.Identifier)?.escapedText === "apiVersion").map(e => e.initializer?.getText());
|
||||
// apiVersionTexts.length must be 0 or 1, otherwise the binding pattern contains the same keys, which causes a ts error
|
||||
if (apiVersionTexts.length === 1 && apiVersionTexts[0]) {
|
||||
apiVersion = extractApiVersionFromText(apiVersionTexts[0]);
|
||||
}
|
||||
});
|
||||
return apiVersion;
|
||||
};
|
||||
|
||||
const findApiVersionsInOperations = (
|
||||
sourceFile: SourceFile | undefined
|
||||
): Array<string> | undefined => {
|
||||
const interfaces = sourceFile?.getInterfaces();
|
||||
const interfacesWithApiVersion = interfaces?.filter((itf) =>
|
||||
itf.getProperty('"api-version"')
|
||||
);
|
||||
const apiVersions = interfacesWithApiVersion?.map((itf) => {
|
||||
const property = itf.getMembers().filter((m) => {
|
||||
const defaultValue = m.getChildrenOfKind(
|
||||
SyntaxKind.StringLiteral
|
||||
)[0];
|
||||
return defaultValue && defaultValue.getText() === '"api-version"';
|
||||
})[0];
|
||||
const apiVersion = property
|
||||
.getChildrenOfKind(SyntaxKind.LiteralType)[0]
|
||||
.getText();
|
||||
return apiVersion;
|
||||
});
|
||||
return apiVersions;
|
||||
};
|
||||
|
||||
// workaround for createClient function changes it's way to setup api-version
|
||||
export const findApiVersionInRestClient = (clientPath: string): string | undefined => {
|
||||
const version2 = findApiVersionInRestClientV2(clientPath);
|
||||
if (version2) {
|
||||
return version2;
|
||||
}
|
||||
const version1 = findApiVersionInRestClientV1(clientPath);
|
||||
return version1;
|
||||
};
|
||||
|
||||
export const tryFindRestClientPath = async (packageRoot: string, clientPattern: string): Promise<string | undefined> => {
|
||||
const pattern = unixify(path.join(packageRoot, clientPattern));
|
||||
const clientFiles = await glob(pattern);
|
||||
if (clientFiles.length !== 1) {
|
||||
logger.warn(`Failed to find extactly one REST client in pattern '${pattern}', got '${clientFiles}'.`);
|
||||
return undefined;
|
||||
}
|
||||
return clientFiles[0];
|
||||
};
|
||||
|
||||
export const findParametersPath = (packageRoot: string, relativeParametersFolder: string): string => {
|
||||
const parametersPath = path.join(packageRoot, relativeParametersFolder);
|
||||
const fileNames = shell.ls(parametersPath);
|
||||
const clientFiles = fileNames.filter((f) => f === "parameters.ts");
|
||||
if (clientFiles.length !== 1)
|
||||
throw new Error(`Expected 1 'parameters.ts' file, but found '${clientFiles}' in '${parametersPath}'.`);
|
||||
|
||||
const clientPath = path.join(parametersPath, clientFiles[0]);
|
||||
return clientPath;
|
||||
};
|
||||
|
||||
export const getApiVersionTypeFromRestClient = async (
|
||||
packageRoot: string,
|
||||
clientPattern: string,
|
||||
findRestClientPath: (packageRoot: string, clientPattern: string) => Promise<string | undefined>
|
||||
): Promise<ApiVersionType> => {
|
||||
const clientPath = await findRestClientPath(packageRoot, clientPattern);
|
||||
if (!clientPath) return ApiVersionType.None;
|
||||
const apiVersion = findApiVersionInRestClient(clientPath);
|
||||
if (apiVersion && apiVersion.indexOf("-preview") >= 0)
|
||||
return ApiVersionType.Preview;
|
||||
if (apiVersion && apiVersion.indexOf("-preview") < 0)
|
||||
return ApiVersionType.Stable;
|
||||
return ApiVersionType.None;
|
||||
};
|
||||
|
||||
export const getApiVersionTypeFromOperations = (
|
||||
packageRoot: string,
|
||||
relativeParametersFolder: string,
|
||||
findPararametersPath: (packageRoot: string, relativeParametersFolder: string) => string
|
||||
): ApiVersionType => {
|
||||
const paraPath = findPararametersPath(packageRoot, relativeParametersFolder);
|
||||
const sourceFile = getTsSourceFile(paraPath);
|
||||
const apiVersions = findApiVersionsInOperations(sourceFile);
|
||||
if (!apiVersions) return ApiVersionType.None;
|
||||
const previewVersions = apiVersions.filter(
|
||||
(v) => v.indexOf("-preview") >= 0
|
||||
);
|
||||
return previewVersions.length > 0
|
||||
? ApiVersionType.Preview
|
||||
: ApiVersionType.Stable;
|
||||
};
|
|
@ -4,14 +4,13 @@
|
|||
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
|
||||
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
|
||||
"lib": ["ES6"] /* Specify library files to be included in the compilation. */,
|
||||
"target": "ES2022" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
|
||||
"module": "NodeNext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
||||
"declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
"declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
"sourceMap": true /* Generates corresponding '.map' file. */,
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "./dist" /* Redirect output structure to the directory. */,
|
||||
|
@ -43,7 +42,7 @@
|
|||
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
"moduleResolution": "NodeNext", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
|
@ -68,5 +67,6 @@
|
|||
"skipLibCheck": true /* Skip type checking of declaration files. */,
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
},
|
||||
"exclude": ["src/test/**/testCases/**", "vitest.config.ts"]
|
||||
"exclude": ["src/test/**/testCases/**", "vitest.config.ts"],
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
|
|
|
@ -2,6 +2,12 @@ import { defineConfig } from "vitest/config";
|
|||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
exclude: ["src/test/testCases/**", "dist/**", "node_modules/**"],
|
||||
exclude: [
|
||||
"src/test/testCases/**",
|
||||
"dist/**",
|
||||
"**/node_modules/**",
|
||||
"**/misc/**",
|
||||
"packages/**",
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче