From cc73b3d09982348cd7d369ea8f77a1d0fe66d669 Mon Sep 17 00:00:00 2001 From: Allen Zhang Date: Fri, 10 Mar 2023 13:56:46 -0800 Subject: [PATCH] Restructuring Typespec migrate and add TypeSpec renaming, version update. (#1694) * Adding back Cadl to all compiler public artifacts got renamed with TypeSpec * initial restructuring * Green compile with re-structuring mostly done. * 0.41.0 file rename and package version update added * TypeSpec replacement done * tidy up the code with comments and output * finishing up yargs * update package.json to avoid rebase conflict * adding test scenario * adding cli help * Fixing PR comments * fixing PR checks * tspconfig migration code working * Update change log for migrate * Remove eslint warning by setting no-console to false as this is a CLI tool --- .vscode/launch.json | 10 + .../typespecMigrate_2023-03-09-06-50.json | 10 + common/config/rush/pnpm-lock.yaml | 106 +++++-- packages/migrate/.eslintrc.cjs | 3 + packages/migrate/package.json | 16 +- packages/migrate/src/cli.ts | 131 ++++++++- packages/migrate/src/migrate.ts | 119 -------- packages/migrate/src/migration-config.ts | 35 +++ packages/migrate/src/migration-impl.ts | 227 +++++++++++++++ packages/migrate/src/migration-types.ts | 161 +++++++++++ packages/migrate/src/migrations/migration.ts | 67 ----- .../src/migrations/v0.38/model-to-scalars.ts | 20 +- .../src/migrations/v0.41/typespec-rename.ts | 273 ++++++++++++++++++ packages/migrate/src/utils.ts | 10 +- .../migrate/test/model-to-scalars.test.ts | 2 +- .../test/scenarios/0.37/cadl-project.yaml | 3 + .../migrate/test/scenarios/0.37/main.cadl | 95 ++++++ .../migrate/test/scenarios/0.37/package.json | 21 ++ packages/migrate/tsconfig.json | 3 +- 19 files changed, 1077 insertions(+), 235 deletions(-) create mode 100644 common/changes/@typespec/migrate/typespecMigrate_2023-03-09-06-50.json delete mode 100644 packages/migrate/src/migrate.ts create mode 100644 packages/migrate/src/migration-config.ts create mode 100644 packages/migrate/src/migration-impl.ts create mode 100644 packages/migrate/src/migration-types.ts delete mode 100644 packages/migrate/src/migrations/migration.ts create mode 100644 packages/migrate/src/migrations/v0.41/typespec-rename.ts create mode 100644 packages/migrate/test/scenarios/0.37/cadl-project.yaml create mode 100644 packages/migrate/test/scenarios/0.37/main.cadl create mode 100644 packages/migrate/test/scenarios/0.37/package.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 43bad44fc..33f4167f0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -135,6 +135,16 @@ "presentation": { "hidden": true } + }, + { + "type": "node", + "request": "launch", + "name": "Debug TypeSpec Migrate", + "program": "${workspaceFolder}/packages/migrate/dist/src/cli.js", + "smartStep": true, + "sourceMaps": true, + "skipFiles": ["/**/*.js"], + "cwd": "C:/Github/Sandbox/playground/cadl/migrate/0.37" } ], "compounds": [ diff --git a/common/changes/@typespec/migrate/typespecMigrate_2023-03-09-06-50.json b/common/changes/@typespec/migrate/typespecMigrate_2023-03-09-06-50.json new file mode 100644 index 000000000..e6484c8fc --- /dev/null +++ b/common/changes/@typespec/migrate/typespecMigrate_2023-03-09-06-50.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@typespec/migrate", + "comment": "Updating @typespec/migrate with better layering and 0.41 TypeSpec rename migration.", + "type": "none" + } + ], + "packageName": "@typespec/migrate" +} \ No newline at end of file diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 71f3081b8..d55d5d9c6 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -56,6 +56,7 @@ specifiers: '@types/prompts': ~2.4.1 '@types/react': ~18.0.5 '@types/react-dom': ~18.0.1 + '@types/semver': ^7.3.13 '@types/sinon': ~10.0.13 '@types/swagger-ui': ~3.52.0 '@types/swagger-ui-react': ^4.11.0 @@ -65,6 +66,8 @@ specifiers: '@typescript-eslint/parser': ^5.30.7 '@typescript-eslint/utils': ~5.26.0 '@typespec/compiler-v0.37': npm:@cadl-lang/compiler@0.37.0 + '@typespec/compiler-v0.38': npm:@cadl-lang/compiler@0.38.0 + '@typespec/compiler-v0.40': npm:@cadl-lang/compiler@0.40.0 '@vitejs/plugin-react': ~2.2.0 '@vscode/vsce': ~2.15.0 ajv: ~8.11.2 @@ -112,6 +115,7 @@ specifiers: rimraf: ~3.0.2 rollup: ~3.4.0 rollup-plugin-visualizer: ~5.8.0 + semver: ^7.3.8 sinon: ~15.0.1 source-map-support: ~0.5.19 strip-json-comments: ~4.0.0 @@ -184,6 +188,7 @@ dependencies: '@types/prompts': 2.4.2 '@types/react': 18.0.27 '@types/react-dom': 18.0.10 + '@types/semver': 7.3.13 '@types/sinon': 10.0.13 '@types/swagger-ui': 3.52.0 '@types/swagger-ui-react': 4.11.0 @@ -193,6 +198,8 @@ dependencies: '@typescript-eslint/parser': 5.49.0_eslint@8.33.0+typescript@4.9.5 '@typescript-eslint/utils': 5.26.0_eslint@8.33.0+typescript@4.9.5 '@typespec/compiler-v0.37': /@cadl-lang/compiler/0.37.0 + '@typespec/compiler-v0.38': /@cadl-lang/compiler/0.38.0 + '@typespec/compiler-v0.40': /@cadl-lang/compiler/0.40.0 '@vitejs/plugin-react': 2.2.0_vite@3.2.5 '@vscode/vsce': 2.15.0 ajv: 8.11.2 @@ -240,6 +247,7 @@ dependencies: rimraf: 3.0.2 rollup: 3.4.0 rollup-plugin-visualizer: 5.8.3_rollup@3.4.0 + semver: 7.3.8 sinon: 15.0.1 source-map-support: 0.5.21 strip-json-comments: 4.0.0 @@ -1796,6 +1804,50 @@ packages: yargs: 17.3.1 dev: false + /@cadl-lang/compiler/0.38.0: + resolution: {integrity: sha512-SvKEbPdlNzkK7DWd/yM2+IiIVn593rv2bntzU+JY15LzbKmTDH6rrkArkr0nR7lMzcBXpBEEvU3vfRdeRrOTyg==} + engines: {node: '>=16.0.0'} + hasBin: true + dependencies: + '@babel/code-frame': 7.18.6 + ajv: 8.11.2 + change-case: 4.1.2 + globby: 13.1.3 + js-yaml: 4.1.0 + mkdirp: 1.0.4 + mustache: 4.2.0 + node-fetch: 3.2.8 + node-watch: 0.7.3 + picocolors: 1.0.0 + prettier: 2.7.1 + prompts: 2.4.2 + vscode-languageserver: 8.0.2 + vscode-languageserver-textdocument: 1.0.8 + yargs: 17.6.2 + dev: false + + /@cadl-lang/compiler/0.40.0: + resolution: {integrity: sha512-4u/Dnm39Ma+8wH0SDu7ya1+2oBRDiNByiRoijwyScHRec26UWLyWvHMvvU89ISU6O8Vwtq0bpmbD7FeJTnlbHw==} + engines: {node: '>=16.0.0'} + hasBin: true + dependencies: + '@babel/code-frame': 7.18.6 + ajv: 8.11.2 + change-case: 4.1.2 + globby: 13.1.3 + js-yaml: 4.1.0 + mkdirp: 1.0.4 + mustache: 4.2.0 + node-fetch: 3.2.8 + node-watch: 0.7.3 + picocolors: 1.0.0 + prettier: 2.8.3 + prompts: 2.4.2 + vscode-languageserver: 8.0.2 + vscode-languageserver-textdocument: 1.0.8 + yargs: 17.6.2 + dev: false + /@colors/colors/1.5.0: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -15283,7 +15335,7 @@ packages: dev: false file:projects/bundler.tgz: - resolution: {integrity: sha512-2QTp6cW0nHCRYpyhBuY0LJxfQaXujdjaApLqkJHOJZFu5X++cSMFIDH7E8yTgCBBOTCes/my2erb7GMPuoUQHg==, tarball: file:projects/bundler.tgz} + resolution: {integrity: sha512-3uJCkLkMa2sL0PL0VvTn3leCWM2k0YLtC1PaojgDs7UM3SFbg976ZFmFRZqKOM8NXPBM8IeIIRRqDk+/1uaZkg==, tarball: file:projects/bundler.tgz} name: '@rush-temp/bundler' version: 0.0.0 dependencies: @@ -15313,7 +15365,7 @@ packages: dev: false file:projects/compiler.tgz: - resolution: {integrity: sha512-E/67YdnjZc8zQyM9W57MRhwNhI+iZ9Tl2LnmsrY5Dz+AKJ+BqWgEOsOAoLrxOuiXrYhVbtFzjwJf0Jzi8LdySA==, tarball: file:projects/compiler.tgz} + resolution: {integrity: sha512-prwL+thcCHa8PuzSmzmSEhczD5W04oZdBBrluKtIYtH6NYUhLKcWZjMsTivpmP2c/7eenK9y1Suhr2l9yuBYVA==, tarball: file:projects/compiler.tgz} name: '@rush-temp/compiler' version: 0.0.0 dependencies: @@ -15382,7 +15434,7 @@ packages: dev: false file:projects/eslint-plugin.tgz: - resolution: {integrity: sha512-IutMRWMzDh8ekd+G0zQIEVUaYm9eq57oxWdIc9gUWrUODcjS+GTnTkTSqPkuHjVp23r4rPuisOagH4cUzARaPw==, tarball: file:projects/eslint-plugin.tgz} + resolution: {integrity: sha512-w4ufHmt3pf2RAe/JR+HroERTWVNA9sbGOmHWCRuo5CSiNyq93shzp6lOD3SDPynnvZ3yeT4ndWX5AwhGtcJndw==, tarball: file:projects/eslint-plugin.tgz} name: '@rush-temp/eslint-plugin' version: 0.0.0 dependencies: @@ -15402,7 +15454,7 @@ packages: dev: false file:projects/html-program-viewer.tgz: - resolution: {integrity: sha512-ErwYPLrbB7F4e+bqtexPP2lFJluP/ifs0uIrxHV/Z85oQjwum8bdcQRKNEY2YSGFpYy+op1ZLVukL1lEGJRN2g==, tarball: file:projects/html-program-viewer.tgz} + resolution: {integrity: sha512-4GhQ6FfR0kirc+AYmo5isMgknSaYLdWgObBc2IXz7wWuPeK5UkT0NGakggQ8i+8TNSgS+agLLRh6J+FxBuSRAA==, tarball: file:projects/html-program-viewer.tgz} name: '@rush-temp/html-program-viewer' version: 0.0.0 dependencies: @@ -15429,7 +15481,7 @@ packages: dev: false file:projects/http.tgz: - resolution: {integrity: sha512-+8QdG+qvoJ0GOqYSy/IrdPG79DMrtyDx0IE1486I1TaUfvnj7e7LhXdAkVfEA6NxFZeEkwKGZ7Rxt8JZgTRuUg==, tarball: file:projects/http.tgz} + resolution: {integrity: sha512-PQK1qg+HlfW/sJGbJJtsZ4cn0xU1E2Ce10LydTYlelq/MQRY7kR3OyFgPGV5zBJ13+Ol0SqsxtIYfLsPeuFZ2w==, tarball: file:projects/http.tgz} name: '@rush-temp/http' version: 0.0.0 dependencies: @@ -15447,7 +15499,7 @@ packages: dev: false file:projects/internal-build-utils.tgz: - resolution: {integrity: sha512-JN9avh7DaGqtRw3kxRvvpDFdDrKokHIYNe52qpSZU8CBE1n6TQTQgumtjaTC6kxdaG4OpxnMaWAbbnZeu3QyQw==, tarball: file:projects/internal-build-utils.tgz} + resolution: {integrity: sha512-uR5KWtoOTVIW4qfy2MrV08bLfV0tTiQXa9pCI6DW3EogZb6h+rsP3GRxBxR/G6n90fxITxoXHAt+1qXCSVFF1Q==, tarball: file:projects/internal-build-utils.tgz} name: '@rush-temp/internal-build-utils' version: 0.0.0 dependencies: @@ -15471,7 +15523,7 @@ packages: dev: false file:projects/library-linter.tgz: - resolution: {integrity: sha512-YxB1A4KBqtBOahnkriSZuPENCoaqNEI5e5WkLVa5Rzdd1inZoKi3EXXyX+nu1WmbgI3MgS7hoaMVfaIpYWtLxQ==, tarball: file:projects/library-linter.tgz} + resolution: {integrity: sha512-RBc+g7VbuJd8BJH2tDXnA3Asq/6Tw8nF7PpVooB//7bk1iQTlL5SWCZLB0Z1uoRFQWV0mQAqBNG5D0aNpHI9VA==, tarball: file:projects/library-linter.tgz} name: '@rush-temp/library-linter' version: 0.0.0 dependencies: @@ -15489,7 +15541,7 @@ packages: dev: false file:projects/lint.tgz: - resolution: {integrity: sha512-FGk655BBWZ30EdulGe5jklyl8JH0DT0vKqckZj8cLqUUtqYVuHIKpgUimH27vuk5wekRR3d1clMkK9Gqjr96uw==, tarball: file:projects/lint.tgz} + resolution: {integrity: sha512-S8xoi5EgbtraHI6dFZif3L6ajGwuV9ZUPT79eaRgwHoC6Ib3y97LneXA60w1KUB0Uw3Si9lX3xhzSbTbZcFabg==, tarball: file:projects/lint.tgz} name: '@rush-temp/lint' version: 0.0.0 dependencies: @@ -15507,27 +15559,37 @@ packages: dev: false file:projects/migrate.tgz: - resolution: {integrity: sha512-6q4jXfgG3kBpX8Hg/tj9rsgSEO8Qfl8mJpe7XKrRx9/21MAdhSkocOnBA/IS6CBF5kQMVyumJxAnmH78yeLCsA==, tarball: file:projects/migrate.tgz} + resolution: {integrity: sha512-0boLgSrZkcTsGaDluiAiGIPN+A5qXHoaQf10eYIuSua7/qrX5frkO77G2z9VIW6xzZuFbvUbsPP1kNN4503rEA==, tarball: file:projects/migrate.tgz} name: '@rush-temp/migrate' version: 0.0.0 dependencies: + '@types/js-yaml': 4.0.5 '@types/mocha': 10.0.1 '@types/node': 18.11.18 + '@types/prettier': 2.6.0 + '@types/semver': 7.3.13 + '@types/yargs': 17.0.21 '@typespec/compiler-v0.37': /@cadl-lang/compiler/0.37.0 + '@typespec/compiler-v0.38': /@cadl-lang/compiler/0.38.0 + '@typespec/compiler-v0.40': /@cadl-lang/compiler/0.40.0 c8: 7.12.0 eslint: 8.33.0 globby: 13.1.3 + js-yaml: 4.1.0 mocha: 10.1.0 mocha-junit-reporter: 2.2.0_mocha@10.1.0 mocha-multi-reporters: 1.5.1_mocha@10.1.0 + prettier: 2.8.3 rimraf: 3.0.2 + semver: 7.3.8 typescript: 4.9.5 + yargs: 17.6.2 transitivePeerDependencies: - supports-color dev: false file:projects/openapi.tgz: - resolution: {integrity: sha512-tqUvUFEZb3kr+MR/bSdTrTEj3HGtgY1jMHiUE8JCQcr09FzxOOHRVvplEu1reHm5y+JISLuXqzvU+1NRfOEIvQ==, tarball: file:projects/openapi.tgz} + resolution: {integrity: sha512-f89N0ova9mFbnHCagBsbJCfxzBwDHRKNHzSy3BkRaynCDqG4sWBk1wsUI/FccTpyVt0oMi4fG3sa1O1if/ZM2A==, tarball: file:projects/openapi.tgz} name: '@rush-temp/openapi' version: 0.0.0 dependencies: @@ -15545,7 +15607,7 @@ packages: dev: false file:projects/openapi3.tgz: - resolution: {integrity: sha512-+PHWnKVDGJixPRMlC+6itwq88+fx5YAS4LXV0kZt8Ioa6zXxR51u2A8X8r/Mu/b6+CG6M551odTjvxeVKJ8MoQ==, tarball: file:projects/openapi3.tgz} + resolution: {integrity: sha512-WweF+bBNr3tTrJxa4YKswxzppt7j3sNkr2xexhd/rhb0tvuZqTOA7goMbJfXF6V32f0Vt9F8DOj31hi+S/9KOw==, tarball: file:projects/openapi3.tgz} name: '@rush-temp/openapi3' version: 0.0.0 dependencies: @@ -15565,7 +15627,7 @@ packages: dev: false file:projects/playground.tgz_rollup@3.4.0: - resolution: {integrity: sha512-c/ILHfQHEWFiRnAzJoFaJmNVbfHxQ2wc/T1a64rMX3VxMHlU+8mxlqAIacoqhRABlUfJWw4itIvU70x3tA/NWQ==, tarball: file:projects/playground.tgz} + resolution: {integrity: sha512-j5/jnB6lpzAxxq1SIQ7HrVQZ5Pc4WrI4Rm0rZg8BeYniDTvrWRbbKeCHS35ni/hg0H/9ifPl74yLYB6lWhQYfQ==, tarball: file:projects/playground.tgz} id: file:projects/playground.tgz name: '@rush-temp/playground' version: 0.0.0 @@ -15621,7 +15683,7 @@ packages: dev: false file:projects/prettier-plugin-typespec.tgz: - resolution: {integrity: sha512-8LdJ1gW/+DKqFWba1QEPiADV9i2YYno4NxWQUwt4lAhOiZG9g0m8urceuGcA4uMZ1zujSq3W9XUhjcKi3jHdhA==, tarball: file:projects/prettier-plugin-typespec.tgz} + resolution: {integrity: sha512-Zmdd/2jlSxrmiotCMceO58hihxNOVz+HpFNTyjQQjDTaTfT183DEMPMJhqDVUwELKrSh1vHIEf77aB8A+F7f5A==, tarball: file:projects/prettier-plugin-typespec.tgz} name: '@rush-temp/prettier-plugin-typespec' version: 0.0.0 dependencies: @@ -15639,7 +15701,7 @@ packages: dev: false file:projects/ref-doc.tgz: - resolution: {integrity: sha512-F05bWju+vc/BFJ4CslZBKji0W2zzeUYXYy9m301kHclKjj3a5yxZzgxlvaGLUo1HIrsVterjimr7qfoG1qNcyw==, tarball: file:projects/ref-doc.tgz} + resolution: {integrity: sha512-G5rvHzQFxLZFKHFTNgLf+yEnV972SGx2vq6vTJRrlrG8mOGjMXwwrGAxoYB1YWvwwu+g/ZNeP9BapiTSAO8NxQ==, tarball: file:projects/ref-doc.tgz} name: '@rush-temp/ref-doc' version: 0.0.0 dependencies: @@ -15661,7 +15723,7 @@ packages: dev: false file:projects/rest.tgz: - resolution: {integrity: sha512-ptBSo46OFKkgaUZZb0kjmBBuZg/egYcGFIRaYqiS7oaoigs06co1hjT+ji4FE+7NfZAD1jNA38Kd6chFH8SVkw==, tarball: file:projects/rest.tgz} + resolution: {integrity: sha512-jyovPCwq3SPwu04TBokC9pAGvJ7bmxHv2FmorbQk5M+LTE0asW7gGr/uzBnGUn1NvdMov5nJeQ3A2GQpWAb0bA==, tarball: file:projects/rest.tgz} name: '@rush-temp/rest' version: 0.0.0 dependencies: @@ -15679,7 +15741,7 @@ packages: dev: false file:projects/samples.tgz: - resolution: {integrity: sha512-lnDPU+AYTd0YwkPNpnzismxBohIgIhxeZPFzLYsFQPm73JIt6nqdECwuu4B8G3kG/KhA0ds9qqy+suF6WI1uTw==, tarball: file:projects/samples.tgz} + resolution: {integrity: sha512-2DI/CQYaa89lmy4nL5qWUUwcM8985A8tSobbxoUrxj7AssjgwIGZIHAYaLd50ksTWufDsHEUJa8VyaEIER7+rw==, tarball: file:projects/samples.tgz} name: '@rush-temp/samples' version: 0.0.0 dependencies: @@ -15691,7 +15753,7 @@ packages: dev: false file:projects/spec.tgz: - resolution: {integrity: sha512-pj6X+JJQIRzYDmPmyx9tcoRvPndJn1RrI/AJApESrfXWUNF7kOnAunmGd7VvESJfpOhFvLl50fAdg9r5cuhNVw==, tarball: file:projects/spec.tgz} + resolution: {integrity: sha512-vrai9fKUuwUNksrn4RBWCpxjoxcXclJsa+bpdGVphztqaR9agH5SF9GeQjiSUqJbaedmoI9Mhf2/TxHKYTRRKA==, tarball: file:projects/spec.tgz} name: '@rush-temp/spec' version: 0.0.0 dependencies: @@ -15706,7 +15768,7 @@ packages: dev: false file:projects/tmlanguage-generator.tgz: - resolution: {integrity: sha512-vr/oeq6/6mo79rEz2Et39cROBjxYr7rwX7NvJkt/cLPvq3CzK22kgixQkyPhScyCZVPW4WRmv2iurKbH8s7G+Q==, tarball: file:projects/tmlanguage-generator.tgz} + resolution: {integrity: sha512-ZVMOy0RcZZp6MTJEwqmNHdtRLncamntvo6qsBM39FSVCXZMQTGWliddOlhUMxNYgZ8bTopCUkjvbOH8daq1EAw==, tarball: file:projects/tmlanguage-generator.tgz} name: '@rush-temp/tmlanguage-generator' version: 0.0.0 dependencies: @@ -15722,13 +15784,13 @@ packages: dev: false file:projects/typespec-vs.tgz: - resolution: {integrity: sha512-S8FACAX4yQTj2TnM1wafi55Z9SFJnTvW4MaDOlIkO2x48DpnbHZaBzSNIn43i4iLLvYcpAnazuFNKk2dwtEiIg==, tarball: file:projects/typespec-vs.tgz} + resolution: {integrity: sha512-athxR1f8AFsdNoI/vQtis+QxyR7zOj2naftG32eQFJNEF+1yoqnS1sJp8x1uFw5NbHSS5F11qbWCH9hukoAx7Q==, tarball: file:projects/typespec-vs.tgz} name: '@rush-temp/typespec-vs' version: 0.0.0 dev: false file:projects/typespec-vscode.tgz: - resolution: {integrity: sha512-cKCr5r+q3U04L4+WvAh3YLMHGQW0jP26/eD4YoQzW0qF5TfRg1qTTVAnCONSggiHGR9GGhGVl2kWefA6OHpt3w==, tarball: file:projects/typespec-vscode.tgz} + resolution: {integrity: sha512-HAVnHFKRFTgbhP6xGQn72iEoAGXWGhVwAp31yAPLP04YBVDvmBQ6fCREbxS+ktIj8Qyt6XBdfuOoKMsDayl8QA==, tarball: file:projects/typespec-vscode.tgz} name: '@rush-temp/typespec-vscode' version: 0.0.0 dependencies: @@ -15754,7 +15816,7 @@ packages: dev: false file:projects/versioning.tgz: - resolution: {integrity: sha512-kZZ3sFk/i3U9Sl0TxlLaxRHEMSmvAVy3EKpFIwhH+7wEQN+cpwZvuBpuCWj263JQK7yIE1js973eKXajZIGPcg==, tarball: file:projects/versioning.tgz} + resolution: {integrity: sha512-BHQ5YRNk+yL5yYQlpnNNUaM73kstHRnuTxTviUlrU+m1Yvnf2tA/KGoMOytNrWfTBM1zNgSjNhW3Gr7hTt8X/Q==, tarball: file:projects/versioning.tgz} name: '@rush-temp/versioning' version: 0.0.0 dependencies: @@ -15772,7 +15834,7 @@ packages: dev: false file:projects/website.tgz_@types+react@18.0.27: - resolution: {integrity: sha512-DP2BOM3gpf89GPfCB2zPKEjHbsnbIovcSyTyh86cAk7hQviHDQvStyVLE4PmTZDM/FNF60uwaKGt4GM48BrYNQ==, tarball: file:projects/website.tgz} + resolution: {integrity: sha512-msvMDz9MVQ3A1NnfEzpImCAHAKqRK81K+K1C2QhmYDSeyJuqMPqc6fECZNAiqmCLVaxHqqH6kmoLXoEOB7twpQ==, tarball: file:projects/website.tgz} id: file:projects/website.tgz name: '@rush-temp/website' version: 0.0.0 diff --git a/packages/migrate/.eslintrc.cjs b/packages/migrate/.eslintrc.cjs index c0b2a9d1a..237b3aa0c 100644 --- a/packages/migrate/.eslintrc.cjs +++ b/packages/migrate/.eslintrc.cjs @@ -4,4 +4,7 @@ module.exports = { plugins: ["@typespec/eslint-plugin"], extends: ["@typespec/eslint-config-typespec", "plugin:@typespec/eslint-plugin/recommended"], parserOptions: { tsconfigRootDir: __dirname }, + rules: { + "no-console": "off", + }, }; diff --git a/packages/migrate/package.json b/packages/migrate/package.json index 85da441e4..bc4facc31 100644 --- a/packages/migrate/package.json +++ b/packages/migrate/package.json @@ -38,21 +38,31 @@ "!dist/test/**" ], "dependencies": { - "globby": "~13.1.1", "@typespec/compiler": "~0.41.0", - "@typespec/compiler-v0.37": "npm:@cadl-lang/compiler@0.37.0" + "@typespec/compiler-v0.37": "npm:@cadl-lang/compiler@0.37.0", + "@typespec/compiler-v0.38": "npm:@cadl-lang/compiler@0.38.0", + "@typespec/compiler-v0.40": "npm:@cadl-lang/compiler@0.40.0", + "globby": "~13.1.1", + "prettier": "~2.8.1", + "semver": "^7.3.8", + "yargs": "~17.6.2", + "js-yaml": "~4.1.0" }, "devDependencies": { "@types/mocha": "~10.0.0", "@types/node": "~18.11.9", + "@types/js-yaml": "~4.0.1", + "@types/prettier": "2.6.0", + "@types/semver": "^7.3.13", + "@types/yargs": "~17.0.2", "@typespec/compiler": "~0.41.0", "@typespec/eslint-config-typespec": "~0.6.0", "@typespec/eslint-plugin": "~0.41.0", + "c8": "~7.12.0", "eslint": "^8.12.0", "mocha": "~10.1.0", "mocha-junit-reporter": "~2.2.0", "mocha-multi-reporters": "~1.5.1", - "c8": "~7.12.0", "rimraf": "~3.0.2", "typescript": "~4.9.3" } diff --git a/packages/migrate/src/cli.ts b/packages/migrate/src/cli.ts index 844b2b888..bbdd47f12 100644 --- a/packages/migrate/src/cli.ts +++ b/packages/migrate/src/cli.ts @@ -1,22 +1,129 @@ #!/usr/bin/env node /* eslint-disable no-console */ -import { migrateTypeSpecFiles } from "./migrate.js"; -import { migrateModelToScalar } from "./migrations/v0.38/model-to-scalars.js"; +import { MANIFEST, NodePackage, resolvePath } from "@typespec/compiler"; +import * as fs from "fs"; +import { readFile } from "fs/promises"; +import * as semver from "semver"; +import yargs from "yargs"; +import { migrationConfigurations } from "./migration-config.js"; +import { + migrateFileRename, + migratePackageVersion, + migrateTextFiles, + migrateTypeSpecFiles, +} from "./migration-impl.js"; +import { MigrationKind } from "./migration-types.js"; import { findTypeSpecFiles } from "./utils.js"; -async function main() { - const files = await findTypeSpecFiles(process.cwd()); - const result = await migrateTypeSpecFiles(files, migrateModelToScalar); +interface Options { + path: string; + tspVersion: string; +} - if (result.fileChanged.length === 0) { - console.log("No typespec files migrated, no change detected."); - } else { - console.log(`Updated ${result.fileChanged.length} typespec files:`); - for (const file of result.fileChanged) { - console.log(` - ${file}`); +async function main() { + console.log(`TypeSpec migration tool v${MANIFEST.version}\n`); + + const cliOptions: Options = await yargs(process.argv.slice(2)) + .option("path", { + alias: "p", + describe: "Path to the input directory. Defaults to the current directory.", + type: "string", + default: process.cwd(), + }) + .option("tspVersion", { + alias: "t", + describe: + "Specifies the TypeSpec compiler version used by the input. Defaults to the version of the compiler package in package.json.", + type: "string", + default: "", + }) + .help().argv; + + const PackageJsonFile = "package.json"; + if (cliOptions.tspVersion.length === 0) { + // Locate current package.json + const pkgFile = resolvePath(cliOptions.path, PackageJsonFile); + const packageJson: NodePackage = JSON.parse(await readFile(pkgFile, "utf-8")); + + // Locate current compiler version + const CadlCompiler = "@cadl-lang/compiler"; + const TypeSpecCompiler = "@typespec/compiler"; + if ( + packageJson?.devDependencies !== undefined && + packageJson?.devDependencies[CadlCompiler] !== undefined + ) { + cliOptions.tspVersion = packageJson.devDependencies[CadlCompiler]; + } else if ( + packageJson?.devDependencies !== undefined && + packageJson?.devDependencies[TypeSpecCompiler] !== undefined + ) { + cliOptions.tspVersion = packageJson.devDependencies[TypeSpecCompiler]; + } else { + console.error("Unable to find TypeSpec compiler version in package.json."); + return; } - console.log("This is a best effort migration, double check everything was migrated correctly."); + } + + if (!fs.existsSync(cliOptions.path)) { + console.error(`Path not found. ${cliOptions.path}`); + return; + } + + let changesMade = false; + + // Iterate thru migration configuration and invoke migration functions + console.log(`Current Typespec version ${cliOptions.tspVersion}.`); + const stepKeys = Object.keys(migrationConfigurations); + for (const key of stepKeys) { + if (semver.gt(key, cliOptions.tspVersion)) { + console.log( + `Migration step found to upgrade from ${cliOptions.tspVersion} to ${key}. Migrating...` + ); + + for (const migrationStep of migrationConfigurations[key]) { + const files = await findTypeSpecFiles(cliOptions.path); + switch (migrationStep.kind) { + case MigrationKind.AstContentMigration: + const result = await migrateTypeSpecFiles(files, migrationStep); + // If migration has been performed log status + if (result.filesChanged.length > 0) { + changesMade = true; + console.log(`Updated ${result.filesChanged.length} TypeSpec files:`); + for (const file of result.filesChanged) { + console.log(` - ${file}`); + } + } + break; + case MigrationKind.FileContentMigration: + changesMade = await migrateTextFiles(files, migrationStep); + break; + case MigrationKind.FileRename: + changesMade = await migrateFileRename(files, migrationStep); + break; + case MigrationKind.PackageVersionUpdate: + const pkgFile = resolvePath(cliOptions.path, PackageJsonFile); + changesMade = await migratePackageVersion(pkgFile, migrationStep); + break; + default: + console.log(`Unexpected error: unknown migration kind: ${migrationStep} `); + } + } + + cliOptions.tspVersion = key; + } else { + console.log( + `${cliOptions.tspVersion} is already greater than or equal to ${key}. Migration step skipped...` + ); + } + } + + if (changesMade) { + console.log( + "\nThis is a best effort migration, double check that everything was migrated correctly." + ); + } else { + console.log("\nNo typespec files have been migrated."); } } diff --git a/packages/migrate/src/migrate.ts b/packages/migrate/src/migrate.ts deleted file mode 100644 index 7111c57d0..000000000 --- a/packages/migrate/src/migrate.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { TextRange } from "@typespec/compiler"; -import { readFile, writeFile } from "fs/promises"; -import { - Migration, - TypeSpecCompiler, - TypeSpecCompilers, - TypeSpecCompilerVersion, -} from "./migrations/migration.js"; -export interface MigrationResult { - fileChanged: string[]; -} - -export async function migrateTypeSpecFiles(files: string[], migration: Migration) { - const fromCompiler = await loadCompiler(migration.from); - const toCompiler = await loadCompiler(migration.to); - return migrateTypeSpecFilesInternal(fromCompiler, toCompiler, files, migration); -} - -export async function migrateTypeSpecContent(content: string, migration: Migration) { - const fromCompiler = await loadCompiler(migration.from); - const toCompiler = await loadCompiler(migration.to); - return migrateTypeSpecContentInternal(fromCompiler, toCompiler, content, migration); -} - -async function loadCompiler( - version: V -): Promise { - try { - return await import(`@typespec/compiler-v${version}`); - } catch { - return (await import("@typespec/compiler")) as any; - } -} - -async function migrateTypeSpecFilesInternal( - fromCompiler: TypeSpecCompiler, - toCompiler: TypeSpecCompiler, - files: string[], - migration: Migration -): Promise { - const result: MigrationResult = { - fileChanged: [], - }; - for (const file of files) { - if (await migrateTypeSpecFile(fromCompiler, toCompiler, file, migration)) { - result.fileChanged.push(file); - } - } - return result; -} - -async function migrateTypeSpecFile( - fromCompiler: TypeSpecCompiler, - toCompiler: TypeSpecCompiler, - filename: string, - migration: Migration -): Promise { - const buffer = await readFile(filename); - const content = buffer.toString(); - const [newContent, changed] = migrateTypeSpecContentInternal( - fromCompiler, - toCompiler, - content, - migration - ); - - await writeFile(filename, newContent); - return changed; -} - -function migrateTypeSpecContentInternal( - fromCompiler: TypeSpecCompiler, - toCompiler: TypeSpecCompiler, - content: string, - migration: Migration -): [string, boolean] { - const parsed = fromCompiler.parse(content); - const actions = migration - .migrate(createMigrationContext(parsed), fromCompiler, parsed as any) - .sort((a, b) => a.target.pos - b.target.pos); - - if (actions.length === 0) { - return [content, false]; - } - const segments = []; - let last = 0; - for (const action of actions) { - segments.push(content.slice(last, action.target.pos)); - segments.push(action.content); - last = action.target.end; - } - segments.push(content.slice(last, -1)); - - const newContent = segments.join(""); - - try { - return [(toCompiler as any).formatTypeSpec(newContent), true]; - } catch (e) { - // eslint-disable-next-line no-console - console.error("Failed to format new code", e); - return [newContent, true]; - } -} - -function createMigrationContext(root: any) { - function printNode(node: TextRange) { - return root.file.text.slice(node.pos, node.end); - } - function printNodes(nodes: readonly TextRange[]): string { - if (nodes.length === 0) { - return ""; - } - const first = nodes[0]; - const last = nodes[nodes.length - 1]; - return root.file.text.slice(first.pos, last.end); - } - - return { printNode, printNodes }; -} diff --git a/packages/migrate/src/migration-config.ts b/packages/migrate/src/migration-config.ts new file mode 100644 index 000000000..e44985582 --- /dev/null +++ b/packages/migrate/src/migration-config.ts @@ -0,0 +1,35 @@ +import { MigrationStepsDictionary } from "./migration-types.js"; +import { migrateModelToScalar } from "./migrations/v0.38/model-to-scalars.js"; +import { + migrateCadlNameToTypeSpec, + migrateTspConfigFile, + renameCadlFileNames, + updatePackageVersion, +} from "./migrations/v0.41/typespec-rename.js"; + +// Update here before release. +export type TypeSpecCompilerCurrent = typeof import("@typespec/compiler"); +export type TypeSpecCompilerV0_37 = typeof import("@typespec/compiler-v0.37"); +export type TypeSpecCompilerV0_38 = typeof import("@typespec/compiler-v0.38"); +export type TypeSpecCompilerV0_40 = typeof import("@typespec/compiler-v0.40"); +export type TypeSpecCompilerV0_41 = TypeSpecCompilerCurrent; + +/** Defines the list of compiler versions will be used */ +export type TypeSpecCompilers = { + "0.37.0": TypeSpecCompilerV0_37; + "0.38.0": TypeSpecCompilerV0_38; + "0.40.0": TypeSpecCompilerV0_40; + "0.41.0": TypeSpecCompilerV0_41; +}; + +/** Please define the list of migration steps for each version. + * Step sequence is respected */ +export const migrationConfigurations: MigrationStepsDictionary = { + "0.38.0": [migrateModelToScalar], + "0.41.0": [ + migrateCadlNameToTypeSpec, + renameCadlFileNames, + updatePackageVersion, + migrateTspConfigFile, + ], +}; diff --git a/packages/migrate/src/migration-impl.ts b/packages/migrate/src/migration-impl.ts new file mode 100644 index 000000000..b88f2f8ab --- /dev/null +++ b/packages/migrate/src/migration-impl.ts @@ -0,0 +1,227 @@ +import { NodePackage, TextRange } from "@typespec/compiler"; +import * as fs from "fs"; +import { readFile, writeFile } from "fs/promises"; +import prettier from "prettier"; +import { TypeSpecCompilers } from "./migration-config.js"; +import { + AstContentMigrateAction, + AstContentMigration, + FileContentMigration, + FileRenameMigration, + MigrationKind, + PackageVersionUpdateMigration, + TypeSpecCompiler, + TypeSpecCompilerVersion, +} from "./migration-types.js"; + +export interface MigrationResult { + filesChanged: string[]; +} + +/** Main function for migrating text file content */ +export async function migrateTextFiles(files: string[], migration: FileContentMigration) { + const actions = await migration.migrate(files); + + if (actions.length > 0) { + console.log(`Updating text content of ${actions.length} file(s):`); + for (const action of actions) { + console.log(` - ${action.fileName}`); + await writeFile(action.fileName, action.newContent); + } + } + return actions.length > 0 ? true : false; +} + +/** Main function for migrating typespec file content */ +export async function migrateTypeSpecFiles(files: string[], migration: AstContentMigration) { + const fromCompiler = await loadCompiler(migration.from); + const toCompiler = await loadCompiler(migration.to); + return migrateTypeSpecFilesInternal(fromCompiler, toCompiler, files, migration); +} + +/** Main function for rename files */ +export async function migrateFileRename( + files: string[], + migration: FileRenameMigration +): Promise { + const renameActions = migration.migrate(files); + let changesMade = false; + if (renameActions.length === 0) return changesMade; + + console.log(`Renaming ${renameActions.length} file(s):`); + for (const action of renameActions) { + changesMade = true; + console.log(` - ${action.sourceFileName} -> ${action.targetFileName}`); + fs.rename(action.sourceFileName, action.targetFileName, (err) => { + if (err) { + console.error( + `Error renaming file from ${action.sourceFileName} to ${action.targetFileName}`, + err + ); + } + }); + } + return changesMade; +} + +/** Main function for migrating package versions in the package.json */ +export async function migratePackageVersion( + pkgFile: string, + migration: PackageVersionUpdateMigration +): Promise { + const packageJson: NodePackage = JSON.parse(await readFile(pkgFile, "utf-8")); + const actions = migration.migrate(packageJson); + let changeMade = false; + + if (actions.length === 0) return changeMade; + + console.log(`Updating ${actions.length} package(s):`); + + for (const action of actions) { + if ( + packageJson.dependencies !== undefined && + packageJson.dependencies[action.packageName] !== undefined + ) { + if (action.renamePackageName !== undefined) { + delete packageJson.dependencies[action.packageName]; + packageJson.dependencies[action.renamePackageName] = action.toVersion; + } else packageJson.dependencies[action.packageName] = action.toVersion; + + console.log(` - dependencies: ${action.renamePackageName} -> ${action.toVersion}.`); + changeMade = true; + } + if ( + packageJson.devDependencies !== undefined && + packageJson.devDependencies[action.packageName] !== undefined + ) { + if (action.renamePackageName !== undefined) { + delete packageJson.devDependencies[action.packageName]; + packageJson.devDependencies[action.renamePackageName] = action.toVersion; + } else packageJson.devDependencies[action.packageName] = action.toVersion; + + console.log(` - devDependencies: ${action.renamePackageName} -> ${action.toVersion}.`); + changeMade = true; + } + } + if (changeMade) { + const prettyJsonString = prettier.format(JSON.stringify(packageJson), { parser: "json" }); + fs.writeFileSync(pkgFile, prettyJsonString); + } + + return changeMade; +} + +/** This is used by test code to migrate single file content */ +export async function migrateTypeSpecContent(content: string, migration: AstContentMigration) { + const fromCompiler = await loadCompiler(migration.from); + const toCompiler = await loadCompiler(migration.to); + return migrateTypeSpecContentInternal(fromCompiler, toCompiler, content, migration); +} + +async function loadCompiler( + version: V +): Promise { + try { + return await import(`@typespec/compiler-v${version}`); + } catch { + return (await import("@typespec/compiler")) as any; + } +} + +async function migrateTypeSpecFilesInternal( + fromCompiler: TypeSpecCompiler, + toCompiler: TypeSpecCompiler, + files: string[], + migration: AstContentMigration +): Promise { + const result: MigrationResult = { + filesChanged: [], + }; + for (const file of files) { + if (await migrateTypeSpecFile(fromCompiler, toCompiler, file, migration)) { + result.filesChanged.push(file); + } + } + return result; +} + +async function migrateTypeSpecFile( + fromCompiler: TypeSpecCompiler, + toCompiler: TypeSpecCompiler, + filename: string, + migration: AstContentMigration +): Promise { + const buffer = await readFile(filename); + const content = buffer.toString(); + const [newContent, changed] = migrateTypeSpecContentInternal( + fromCompiler, + toCompiler, + content, + migration + ); + + await writeFile(filename, newContent); + return changed; +} + +function migrateTypeSpecContentInternal( + fromCompiler: TypeSpecCompiler, + toCompiler: TypeSpecCompiler, + content: string, + migration: AstContentMigration +): [string, boolean] { + const parsed = fromCompiler.parse(content); + const actions = migration + .migrate(createMigrationContext(parsed), fromCompiler, parsed as any) + .filter( + (action): action is AstContentMigrateAction => + action.kind === MigrationKind.AstContentMigration + ) + .sort((a, b) => a.target.pos - b.target.pos); + + return ContentMigration(toCompiler, content, actions); +} + +function ContentMigration( + toCompiler: TypeSpecCompiler, + content: string, + actions: AstContentMigrateAction[] +): [string, boolean] { + if (actions.length === 0) { + return [content, false]; + } + const segments = []; + let last = 0; + for (const action of actions) { + segments.push(content.slice(last, action.target.pos)); + segments.push(action.content); + last = action.target.end; + } + segments.push(content.slice(last, -1)); + + const newContent = segments.join(""); + + try { + return [(toCompiler as any).formatTypeSpec(newContent), true]; + } catch (e) { + // eslint-disable-next-line no-console + console.error("Failed to format new code", e); + return [newContent, true]; + } +} + +function createMigrationContext(root: any) { + function printNode(node: TextRange) { + return root.file.text.slice(node.pos, node.end); + } + function printNodes(nodes: readonly TextRange[]): string { + if (nodes.length === 0) { + return ""; + } + const first = nodes[0]; + const last = nodes[nodes.length - 1]; + return root.file.text.slice(first.pos, last.end); + } + + return { printNode, printNodes }; +} diff --git a/packages/migrate/src/migration-types.ts b/packages/migrate/src/migration-types.ts new file mode 100644 index 000000000..a18cb91a7 --- /dev/null +++ b/packages/migrate/src/migration-types.ts @@ -0,0 +1,161 @@ +import { NodePackage, TextRange } from "@typespec/compiler"; +import { TypeSpecCompilers } from "./migration-config.js"; + +/** Defines the configuration dictionary */ +export interface MigrationStepsDictionary { + [key: string]: Migration[]; +} + +/** This is the list of supported migration steps */ +export enum MigrationKind { + AstContentMigration, + FileContentMigration, + FileRename, + PackageVersionUpdate, +} + +/** Defines all migration actions */ +export type MigrateAction = + | AstContentMigrateAction + | FileContentMigrationAction + | FileRenameAction + | PackageVersionUpdateAction; + +/** Defines all migration functions that can be implemented by version specific migration functions. + * These functions should return corresponding array migration actions to be performed.*/ +export type Migration = + | AstContentMigration + | FileContentMigration + | FileRenameMigration + | PackageVersionUpdateMigration; + +/** Type of imported versions of tsp compilers defined in migration-config.ts. */ +export type TypeSpecCompiler = TypeSpecCompilers[keyof TypeSpecCompilers]; + +/** Key type of all compiler versions defined in migration-config.ts. */ +export type TypeSpecCompilerVersion = keyof TypeSpecCompilers; + +/** Migration Context type that contains some helper functions */ +export interface MigrationContext { + /** + * Print the text range as it is. + */ + printNode(node: TextRange): string; + + /** + * Print the entire text range from teh first node to the last.(Including anything in between the nodes.) + */ + printNodes(node: readonly TextRange[]): string; +} + +export interface MigrationBase { + name: string; + kind: MigrationKind; +} + +/** ContentMigration interface definition. */ +export interface AstContentMigration extends MigrationBase { + kind: MigrationKind.AstContentMigration; + + /** + * Compiler version. + */ + from: TFrom; + + /** + * Target version + */ + to: TypeSpecCompilerVersion; + + /** + * Migrate logic. + * @param compilerInstance Instance of the compiler at the `from` version. + * @param script TypeSpec Script source node. + */ + migrate( + context: MigrationContext, + compilerInstance: TypeSpecCompilers[TFrom], + script: unknown + ): MigrateAction[]; +} + +export interface FileContentMigration extends MigrationBase { + kind: MigrationKind.FileContentMigration; + + migrate(fileNames: string[]): Promise; +} + +/** File Rename migration interface definition. */ +export interface FileRenameMigration extends MigrationBase { + kind: MigrationKind.FileRename; + + migrate(fileNames: string[]): FileRenameAction[]; +} + +/** Package version update migration interface definition. */ +export interface PackageVersionUpdateMigration extends MigrationBase { + kind: MigrationKind.PackageVersionUpdate; + + migrate(pkg: NodePackage): PackageVersionUpdateAction[]; +} + +/** Base class for migration actions */ +export interface MigrateActionBase { + kind: MigrationKind; +} + +/** Migration action that modifies contents */ +export interface AstContentMigrateAction extends MigrateActionBase { + kind: MigrationKind.AstContentMigration; + + target: TextRange; // TypeSpec compiler node + /** + * Replaced content + */ + content: string; +} + +/** Migration action that renames a file */ +export interface FileRenameAction extends MigrateActionBase { + kind: MigrationKind.FileRename; + sourceFileName: string; + targetFileName: string; +} + +export interface FileContentMigrationAction extends MigrateActionBase { + kind: MigrationKind.FileContentMigration; + fileName: string; + newContent: string; +} + +/** Migration action that updates a package version */ +export interface PackageVersionUpdateAction extends MigrateActionBase { + kind: MigrationKind.PackageVersionUpdate; + packageName: string; + renamePackageName?: string; + toVersion: string; +} + +/** Helper functions to define a custom migration function */ +export function createContentMigration( + migration: AstContentMigration +): AstContentMigration { + return migration; +} + +/** Helper functions to define a custom migration function */ +export function createFileRenameMigration(migration: FileRenameMigration): FileRenameMigration { + return migration; +} + +/** Helper functions to define a custom migration function */ +export function createFileContentMigration(migration: FileContentMigration): FileContentMigration { + return migration; +} + +/** Helper functions to define a custom migration function */ +export function createPackageVersionMigration( + migration: PackageVersionUpdateMigration +): PackageVersionUpdateMigration { + return migration; +} diff --git a/packages/migrate/src/migrations/migration.ts b/packages/migrate/src/migrations/migration.ts deleted file mode 100644 index 1087e01dd..000000000 --- a/packages/migrate/src/migrations/migration.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { TextRange } from "@typespec/compiler"; - -export type MigrationKind = "Syntax"; - -// Update here before release. -export type TypeSpecCompilerCurrent = typeof import("@typespec/compiler"); -export type TypeSpecCompilerV0_38 = TypeSpecCompilerCurrent; -export type TypeSpecCompilerV0_37 = typeof import("@typespec/compiler-v0.37"); - -export type TypeSpecCompilers = { - "0.37": TypeSpecCompilerV0_37; - "0.38": TypeSpecCompilerV0_38; -}; - -export type TypeSpecCompiler = TypeSpecCompilers[keyof TypeSpecCompilers]; -export type TypeSpecCompilerVersion = keyof TypeSpecCompilers; - -export interface MigrationContext { - /** - * Print the text range as it is. - */ - printNode(node: TextRange): string; - - /** - * Print the entire text range from teh first node to the last.(Including anything in between the nodes.) - */ - printNodes(node: readonly TextRange[]): string; -} - -export interface Migration { - name: string; - kind: "Syntax"; - /** - * Compiler version. - */ - from: TFrom; - - /** - * Target version - */ - to: TypeSpecCompilerVersion; - - /** - * Migrate logic. - * @param compilerInstance Instance of the compiler at the `from` version. - * @param script TypeSpec Script source node. - */ - migrate( - context: MigrationContext, - compilerInstance: TypeSpecCompilers[TFrom], - script: unknown - ): MigrateAction[]; -} - -export interface MigrateAction { - target: TextRange; // TypeSpec compiler node - /** - * Replaced content - */ - content: string; -} - -export function createMigration( - migration: Migration -): Migration { - return migration; -} diff --git a/packages/migrate/src/migrations/v0.38/model-to-scalars.ts b/packages/migrate/src/migrations/v0.38/model-to-scalars.ts index 30fa33b53..637e3b22d 100644 --- a/packages/migrate/src/migrations/v0.38/model-to-scalars.ts +++ b/packages/migrate/src/migrations/v0.38/model-to-scalars.ts @@ -3,18 +3,19 @@ import type { Node, TemplateParameterDeclarationNode, } from "@typespec/compiler-v0.37"; +import { TypeSpecCompilerV0_37 } from "../../migration-config.js"; import { - createMigration, - MigrateAction, + AstContentMigrateAction, + createContentMigration, MigrationContext, - TypeSpecCompilerV0_37, -} from "../migration.js"; + MigrationKind, +} from "../../migration-types.js"; -export const migrateModelToScalar = createMigration({ +export const migrateModelToScalar = createContentMigration({ name: "Migrate Model To scalar", - kind: "Syntax", - from: "0.37", - to: "0.38", + kind: MigrationKind.AstContentMigration, + from: "0.37.0", + to: "0.38.0", migrate: ( { printNode, printNodes }: MigrationContext, compilerV37: TypeSpecCompilerV0_37, @@ -27,7 +28,7 @@ export const migrateModelToScalar = createMigration({ return `<${parameters.map((x) => printNode(x)).join(", ")}>`; } - const actions: MigrateAction[] = []; + const actions: AstContentMigrateAction[] = []; visitRecursive(compilerV37, root, (node) => { if ( node.kind === compilerV37.SyntaxKind.ModelStatement && @@ -39,6 +40,7 @@ export const migrateModelToScalar = createMigration({ ) { const decorators = printNodes(node.decorators); actions.push({ + kind: MigrationKind.AstContentMigration, target: node, content: `${decorators ? decorators + " " : ""}scalar ${ node.id.sv diff --git a/packages/migrate/src/migrations/v0.41/typespec-rename.ts b/packages/migrate/src/migrations/v0.41/typespec-rename.ts new file mode 100644 index 000000000..2482acc83 --- /dev/null +++ b/packages/migrate/src/migrations/v0.41/typespec-rename.ts @@ -0,0 +1,273 @@ +import type { Node, TypeSpecScriptNode } from "@typespec/compiler"; +import { getAnyExtensionFromPath, NodePackage } from "@typespec/compiler"; +import { readFile } from "fs/promises"; +import * as yaml from "js-yaml"; +import * as path from "path"; +import type { TypeSpecCompilerV0_40 } from "../../migration-config.js"; +import { + AstContentMigrateAction, + createContentMigration, + createFileContentMigration, + createFileRenameMigration, + createPackageVersionMigration, + FileContentMigrationAction, + FileRenameAction, + MigrationContext, + MigrationKind, + PackageVersionUpdateAction, +} from "../../migration-types.js"; + +export const updatePackageVersion = createPackageVersionMigration({ + name: "Update package version", + kind: MigrationKind.PackageVersionUpdate, + migrate: (pkg: NodePackage) => { + const actions: Array = []; + + actions.push({ + kind: MigrationKind.PackageVersionUpdate, + packageName: "@cadl-lang/compiler", + renamePackageName: "@typespec/compiler", + toVersion: "0.41.0", + }); + actions.push({ + kind: MigrationKind.PackageVersionUpdate, + packageName: "@cadl-lang/openapi", + renamePackageName: "@typespec/openapi", + toVersion: "0.41.0", + }); + actions.push({ + kind: MigrationKind.PackageVersionUpdate, + packageName: "@cadl-lang/openapi3", + renamePackageName: "@typespec/openapi3", + toVersion: "0.41.0", + }); + actions.push({ + kind: MigrationKind.PackageVersionUpdate, + packageName: "@cadl-lang/http", + renamePackageName: "@typespec/http", + toVersion: "0.41.0", + }); + actions.push({ + kind: MigrationKind.PackageVersionUpdate, + packageName: "@cadl-lang/versioning", + renamePackageName: "@typespec/versioning", + toVersion: "0.41.0", + }); + actions.push({ + kind: MigrationKind.PackageVersionUpdate, + packageName: "@cadl-lang/rest", + renamePackageName: "@typespec/rest", + toVersion: "0.41.0", + }); + actions.push({ + kind: MigrationKind.PackageVersionUpdate, + packageName: "@cadl-lang/lint", + renamePackageName: "@typespec/lint", + toVersion: "0.41.0", + }); + actions.push({ + kind: MigrationKind.PackageVersionUpdate, + packageName: "@azure-tools/cadl-autorest", + renamePackageName: "@azure-tools/typespec-autorest", + toVersion: "0.27.0", + }); + actions.push({ + kind: MigrationKind.PackageVersionUpdate, + packageName: "@azure-tools/cadl-azure-core", + renamePackageName: "@azure-tools/typespec-azure-core", + toVersion: "0.27.0", + }); + actions.push({ + kind: MigrationKind.PackageVersionUpdate, + packageName: "@azure-tools/cadl-azure-resource-manager", + renamePackageName: "@azure-tools/typespec-azure-resource-manager", + toVersion: "0.27.0", + }); + actions.push({ + kind: MigrationKind.PackageVersionUpdate, + packageName: "@azure-tools/cadl-dpg", + renamePackageName: "@azure-tools/typespec-client-generator-core", + toVersion: "0.27.0", + }); + actions.push({ + kind: MigrationKind.PackageVersionUpdate, + packageName: "@azure-tools/cadl-providerhub", + renamePackageName: "@azure-tools/typespec-providerhub", + toVersion: "0.27.0", + }); + actions.push({ + kind: MigrationKind.PackageVersionUpdate, + packageName: "@azure-tools/cadl-providerhub-controller", + renamePackageName: "@azure-tools/typespec-providerhub-controller", + toVersion: "0.27.0", + }); + + return actions; + }, +}); + +export const migrateCadlNameToTypeSpec = createContentMigration({ + name: "Migrate Model To scalar", + kind: MigrationKind.AstContentMigration, + from: "0.40.0", + to: "0.41.0", + migrate: ( + { printNode, printNodes }: MigrationContext, + compilerV40: TypeSpecCompilerV0_40, + root: TypeSpecScriptNode + ) => { + const actions: AstContentMigrateAction[] = []; + visitRecursive(compilerV40, root, (node) => { + if (node.kind === compilerV40.SyntaxKind.ImportStatement && node.path.value.length > 0) { + let newContent = ""; + + if (node.path.value.includes("/cadl-dpg")) { + newContent = node.path.value.replace("/cadl-dpg", "/typespec-client-generator-core"); + } else if (node.path.value.includes("@cadl-lang")) { + newContent = node.path.value.replace("@cadl-lang", "@typespec"); + } else if (node.path.value.includes("@azure-tools/cadl")) { + newContent = node.path.value.replace("@azure-tools/cadl", "@azure-tools/typespec"); + } + + if (newContent.length > 0) { + actions.push({ + kind: MigrationKind.AstContentMigration, + target: node, + content: `import "${newContent}";`, + }); + } + } else if ( + node.kind === compilerV40.SyntaxKind.UsingStatement && + node.name !== undefined && + node.name.kind === compilerV40.SyntaxKind.MemberExpression + ) { + if (node.name.id.sv === "DPG") { + actions.push({ + kind: MigrationKind.AstContentMigration, + target: node.name, + content: `Azure.ClientGenerator.Core`, + }); + } + + if ( + node.name.base !== undefined && + node.name.base.kind === compilerV40.SyntaxKind.Identifier && + node.name.base.sv === "Cadl" + ) { + actions.push({ + kind: MigrationKind.AstContentMigration, + target: node.name.base, + content: `TypeSpec`, + }); + } + } + }); + return actions; + }, +}); + +export const renameCadlFileNames = createFileRenameMigration({ + name: "Rename cadl file names", + kind: MigrationKind.FileRename, + migrate: (fileNames: string[]) => { + const actions: Array = []; + for (let i = 0; i < fileNames.length; i++) { + let toName: string | undefined = undefined; + const pathOnly = path.dirname(fileNames[i]); + const fileName = path.basename(fileNames[i]); + + if (fileName === "cadl-project.yaml") { + toName = "tspconfig.yaml"; + } + if (getAnyExtensionFromPath(fileName) === ".cadl") { + toName = fileName.slice(0, fileName.lastIndexOf(".")) + ".tsp"; + } + + if (toName !== undefined) { + actions.push({ + kind: MigrationKind.FileRename, + sourceFileName: fileNames[i], + targetFileName: path.join(pathOnly, toName), + }); + } + } + return actions; + }, +}); + +export const migrateTspConfigFile = createFileContentMigration({ + name: "Migrate cadl-project.yaml and tspConfig.yaml", + kind: MigrationKind.FileContentMigration, + migrate: async (fileNames: string[]) => { + // Old cadl-project.yaml file would have been migrated already. + // So we only need to deal with new config file name. + const TspConfigFileName = "tspconfig.yaml"; + const actions: Array = []; + + for (let i = 0; i < fileNames.length; i++) { + const fileName = path.basename(fileNames[i]); + + if (fileName === TspConfigFileName) { + // loading content + const buffer = await readFile(fileName); + let content = buffer.toString(); + + // replacing cadl with typespec + const replaceKeys = Object.keys(CadlToTypeSpecReplacement); + for (const key of replaceKeys) { + content = content.replace(key, CadlToTypeSpecReplacement[key]); + } + + // load data & convert to new format if needed to + let tspConfig: any; + try { + tspConfig = yaml.load(content); + // if config has older deprecated emitters format, convert to new format + if (tspConfig?.emitters !== undefined) { + (tspConfig as { emit: Array }).emit = []; + (tspConfig as { options: Record }).options = {}; + + // convert each emitters to new emit format + for (const key in tspConfig.emitters) { + tspConfig.emit.push(key); + if (typeof tspConfig.emitters[key] !== "boolean") { + tspConfig.options[key] = tspConfig.emitters[key]; + } + } + + // clean up config object for minimal output + tspConfig.emitters = undefined; + if (tspConfig.options.length === 0) tspConfig.options = undefined; + + content = yaml.dump(tspConfig); + } + } catch (err) { + console.warn( + `Failed to load ${fileNames[i]}. File may not have been migrated correctly. Error details: ${err}` + ); + } + + // Create replacement action + actions.push({ + kind: MigrationKind.FileContentMigration, + fileName: fileNames[i], + newContent: content, + }); + } + } + return actions; + }, +}); + +function visitRecursive(compiler: any, root: Node, callback: (node: Node) => void) { + const visit = (node: Node) => { + callback(node); + compiler.visitChildren(node, visit); + }; + visit(root); +} + +const CadlToTypeSpecReplacement: { [key: string]: string } = { + "@cadl-lang/": "@typespec/", + "@azure-tools/cadl-": "@azure-tools/typespec-", +}; diff --git a/packages/migrate/src/utils.ts b/packages/migrate/src/utils.ts index 6c75206d1..31a4c2e70 100644 --- a/packages/migrate/src/utils.ts +++ b/packages/migrate/src/utils.ts @@ -2,7 +2,15 @@ import { globby } from "globby"; import { join } from "path"; export async function findTypeSpecFiles(root: string, ignore: string[] = []) { - return findFiles([normalizePath(join(root, "**/*.tsp"))], ignore); + return findFiles( + [ + normalizePath(join(root, "**/*.tsp")), + normalizePath(join(root, "**/*.cadl")), + normalizePath(join(root, "**/cadl-project.yaml")), + normalizePath(join(root, "**/tspconfig.yaml")), + ], + ignore + ); } export async function findFiles(include: string[], ignore: string[] = []): Promise { diff --git a/packages/migrate/test/model-to-scalars.test.ts b/packages/migrate/test/model-to-scalars.test.ts index 2e2687464..5bb55316c 100644 --- a/packages/migrate/test/model-to-scalars.test.ts +++ b/packages/migrate/test/model-to-scalars.test.ts @@ -1,5 +1,5 @@ import { strictEqual } from "assert"; -import { migrateTypeSpecContent } from "../src/migrate.js"; +import { migrateTypeSpecContent } from "../src/migration-impl.js"; import { migrateModelToScalar } from "../src/migrations/v0.38/model-to-scalars.js"; describe("migration: model to scalars", () => { diff --git a/packages/migrate/test/scenarios/0.37/cadl-project.yaml b/packages/migrate/test/scenarios/0.37/cadl-project.yaml new file mode 100644 index 000000000..4d45a7e3e --- /dev/null +++ b/packages/migrate/test/scenarios/0.37/cadl-project.yaml @@ -0,0 +1,3 @@ +emitters: + "@azure-tools/cadl-autorest": + output-file: test.json diff --git a/packages/migrate/test/scenarios/0.37/main.cadl b/packages/migrate/test/scenarios/0.37/main.cadl new file mode 100644 index 000000000..b2fda09c2 --- /dev/null +++ b/packages/migrate/test/scenarios/0.37/main.cadl @@ -0,0 +1,95 @@ +import "@cadl-lang/rest"; +import "@cadl-lang/openapi"; +import "@azure-tools/cadl-autorest"; +import "@azure-tools/cadl-azure-core"; +import "@azure-tools/cadl-azure-resource-manager"; +import "@azure-tools/cadl-dpg"; + +// See here for more information: https://aka.ms/cadl/learn +using Autorest; +using Azure.ResourceManager; +using Cadl.Http; +using Cadl.Rest; +using Cadl.Versioning; +using Azure.DPG; +using OpenAPI; + +@armProviderNamespace +@service({ + title: "IndexManagementClient", + version: "2022-06-01-preview", +}) +@doc("Microsoft.Search resource management API.") +@versionedDependency(Azure.ResourceManager.Versions.v1_0_Preview_1) +namespace Microsoft.Search; + +interface Operations extends Azure.ResourceManager.Operations {} + +@doc("An index resource.") +model Index is TrackedResource { + @path + @key("name") + @segment("searchIndexes") + @visibility("read") + @doc("The name of the index resource.") + name: string; +} + +@armResourceOperations +interface IndexOperations extends ResourceOperations {} + +@doc("The properties of the index.") +model IndexProperties { + @visibility("read") + @doc("The current provisioning state of the index.") + provisioningState?: ProvisioningState; + + @visibility("read") + @doc("The endpoint at which the index can be accessed.") + endpoint?: string; + + @doc("The capacity allocated to the index for querying.") + queryCapacity: Capacity; + + @doc("The capacity allocated to the index for indexing documents.") + indexingCapacity: Capacity; +} + +@doc("The current provisioning state of the index.") +enum ProvisioningState { + Accepted, + Provisioning, + Succeeded, + Failed, + Canceled, + Deleting, +} + +@doc("The capacity will autoscale between the minimum and maximum number of vCores based on the usage of your index.") +model Capacity { + @doc("The minimum number of vCores that the index will consume. Represented with discrete values: 0.2, 0.5. 1, 2, 3, … to 16.") + minVCores: float32; + + @doc("The maximum number of vCores that the index can consume. Represented with discrete values: 0.2, 0.5. 1, 2, 3, … to 16.") + maxVCores: float32; + autoPause: Pause; +} + +@doc("Index pause strategy") +@discriminator("type") +model Pause {} + +@doc("When the pause strategy is set to 'Delay', the index will enter a paused state after not being used for a fixed amount of time.") +model DelayPause extends Pause { + @doc("Specifies the type of pausing strategy as 'Delay'.") + type: "Delay"; + + @doc("The interval after which an index is paused if not in use.") + duration: duration; +} + +@doc("When the pause strategy is set to 'None', the index will remain always active.") +model NonePause extends Pause { + @doc("Specifies the type of pausing strategy as 'None'.") + type: "None"; +} diff --git a/packages/migrate/test/scenarios/0.37/package.json b/packages/migrate/test/scenarios/0.37/package.json new file mode 100644 index 000000000..2c804a429 --- /dev/null +++ b/packages/migrate/test/scenarios/0.37/package.json @@ -0,0 +1,21 @@ +{ + "name": "Microsoft.Search", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "echo \"Nothing to do for template.\"" + }, + "dependencies": { + "@azure-tools/cadl-autorest": "0.22.0", + "@azure-tools/cadl-azure-core": "0.9.0", + "@azure-tools/cadl-azure-resource-manager": "0.12.0", + "@azure-tools/cadl-dpg": "0.3.0", + "@cadl-lang/openapi": "0.14.0", + "@cadl-lang/rest": "0.19.0", + "@cadl-lang/versioning": "0.10.0" + }, + "devDependencies": { + "@cadl-lang/compiler": "0.37.0" + } +} diff --git a/packages/migrate/tsconfig.json b/packages/migrate/tsconfig.json index 8250e7819..c1c1c9c9a 100644 --- a/packages/migrate/tsconfig.json +++ b/packages/migrate/tsconfig.json @@ -8,7 +8,8 @@ "outDir": "dist", "rootDir": ".", "tsBuildInfoFile": "temp/tsconfig.tsbuildinfo", - "types": ["node", "mocha"] + "types": ["node", "mocha"], + "resolveJsonModule": true }, "include": ["src/**/*.ts", "test/**/*.ts"] }