Skip to content

Commit

Permalink
[tsp-client] Support alternate entrypoint files (#9494)
Browse files Browse the repository at this point in the history
* support specific entry point

* release prep

* add docs

* update changelog entry

* add entrypoint test with files

* update version

* changelog

---------

Co-authored-by: Catalina Peralta <caperal@microsoft.com>
  • Loading branch information
catalinaperalta and cperaltah authored Jan 7, 2025
1 parent a23627a commit 9b9981e
Show file tree
Hide file tree
Showing 33 changed files with 1,484 additions and 15 deletions.
5 changes: 5 additions & 0 deletions tools/tsp-client/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Release

## 2025-01-07 - 0.15.0

- Support specifying an entrypoint file in tsp-location.yaml.
- Ensure client.tsp selection over main.tsp in the entrypoint file search.

## 2024-12-20 - 0.14.3

- Bumped `@autorest/openapi-to-typespec` version to `0.10.5`.
Expand Down
1 change: 1 addition & 0 deletions tools/tsp-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ The file has the following properties:
| <a id="additionalDirectories-anchor"></a> additionalDirectories | Sometimes a typespec file will use a relative import that might not be under the main directory. In this case a single `directory` will not be enough to pull down all necessary files. To support this you can specify additional directories as a list to sync so that all needed files are synced. | false: default = null |
| <a id="commit-anchor"></a> commit | The commit sha for the version of the typespec files you want to generate off of. This allows us to have idempotence on generation until we opt into pointing at a later version. | true |
| <a id="repo-anchor"></a> repo | The repo this spec lives in. This should be either `Azure/azure-rest-api-specs` or `Azure/azure-rest-api-specs-pr`. Note that pr will work locally but not in CI until we add another change to handle token based auth. | true |
| <a id="repo-anchor"></a> entrypointFile | A specific entrypoint file used to compile the TypeSpec project. NOTE: This option should only be used with a non-standard entrypoint file name. DO NOT use this option with standard entrypoints: `client.tsp` or `main.tsp`. | false |

Example:

Expand Down
4 changes: 2 additions & 2 deletions tools/tsp-client/package-lock.json

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

2 changes: 1 addition & 1 deletion tools/tsp-client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@azure-tools/typespec-client-generator-cli",
"version": "0.14.3",
"version": "0.15.0",
"description": "A tool to generate Azure SDKs from TypeSpec",
"main": "dist/index.js",
"homepage": "https://github.com/Azure/azure-sdk-tools/tree/main/tools/tsp-client#readme",
Expand Down
9 changes: 7 additions & 2 deletions tools/tsp-client/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ import {
} from "./fs.js";
import { cp, mkdir, readFile, stat, unlink, writeFile } from "fs/promises";
import { npmCommand, nodeCommand } from "./npm.js";
import { compileTsp, discoverMainFile, resolveTspConfigUrl, TspLocation } from "./typespec.js";
import {
compileTsp,
discoverEntrypointFile,
resolveTspConfigUrl,
TspLocation,
} from "./typespec.js";
import {
writeTspLocationYaml,
getAdditionalDirectoryName,
Expand Down Expand Up @@ -257,7 +262,7 @@ export async function generateCommand(argv: any) {
if (!emitter) {
throw new Error("emitter is undefined");
}
const mainFilePath = await discoverMainFile(srcDir);
const mainFilePath = await discoverEntrypointFile(srcDir, tspLocation.entrypointFile);
const resolvedMainFilePath = joinPaths(srcDir, mainFilePath);
Logger.info("Installing dependencies from npm...");
const args: string[] = [];
Expand Down
31 changes: 23 additions & 8 deletions tools/tsp-client/src/typespec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface TspLocation {
commit: string;
repo: string;
additionalDirectories?: string[];
entrypointFile?: string;
}

export function resolveTspConfigUrl(configUrl: string): {
Expand Down Expand Up @@ -42,18 +43,32 @@ export function resolveTspConfigUrl(configUrl: string): {
}
}

export async function discoverMainFile(srcDir: string): Promise<string> {
export async function discoverEntrypointFile(
srcDir: string,
specifiedEntrypointFile?: string,
): Promise<string> {
Logger.debug(`Discovering entry file in ${srcDir}`);
let entryTsp = "";
let entryTsp: string | undefined = undefined;
const files = await readdir(srcDir, { recursive: true });
for (const file of files) {
if (file.includes("client.tsp") || file.includes("main.tsp")) {
entryTsp = file;
Logger.debug(`Found entry file: ${entryTsp}`);
return entryTsp;

function findEntrypoint(name: string): string | undefined {
return files.find((file) => file.endsWith(name)) ?? undefined;
}
if (specifiedEntrypointFile) {
entryTsp = findEntrypoint(specifiedEntrypointFile);
if (!entryTsp) {
throw new Error(
`Couldn't find the entrypoint file specified in tsp-location.yaml: "${specifiedEntrypointFile}". Please verify that the entrypoint file name is correct.`,
);
}
} else {
entryTsp = findEntrypoint("client.tsp") ?? findEntrypoint("main.tsp");
if (!entryTsp) {
throw new Error(`No main.tsp or client.tsp found`);
}
}
throw new Error(`No main.tsp or client.tsp found`);
Logger.debug(`Found entry file: ${entryTsp}`);
return entryTsp;
}

export async function compileTsp({
Expand Down
15 changes: 15 additions & 0 deletions tools/tsp-client/test/commands.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,21 @@ describe.sequential("Verify commands", () => {
assert.isTrue(dir.isFile());
});

it("Generate with alternate entrypoint", async () => {
try {
const args = {
"output-dir": joinPaths(cwd(), "./test/examples/sdk/alternate-entrypoint"),
"local-spec-repo":
"./test/examples/specification/contosowidgetmanager/Contoso.WidgetManager",
};
await updateCommand(args);
} catch (error) {
assert.fail(`Failed to generate. Error: ${error}`);
}
const tspLocation = await readTspLocation("./test/examples/sdk/alternate-entrypoint");
assert.equal(tspLocation.entrypointFile, "foo.tsp");
});

it("Update example sdk", async () => {
try {
const args = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"plugins": ["@azure/azure-sdk"],
"extends": ["plugin:@azure/azure-sdk/azure-sdk-base"],
"rules": {
"@azure/azure-sdk/ts-modules-only-named": "warn",
"@azure/azure-sdk/ts-apiextractor-json-types": "warn",
"@azure/azure-sdk/ts-package-json-types": "warn",
"@azure/azure-sdk/ts-package-json-engine-is-present": "warn",
"tsdoc/syntax": "warn",
"@azure/azure-sdk/ts-package-json-module": "off",
"@azure/azure-sdk/ts-package-json-files-required": "off",
"@azure/azure-sdk/ts-package-json-main-is-cjs": "off"
}
}
57 changes: 57 additions & 0 deletions tools/tsp-client/test/examples/sdk/alternate-entrypoint/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Azure WidgetManager REST client library for JavaScript



**Please rely heavily on our [REST client docs](https://github.com/Azure/azure-sdk-for-js/blob/main/documentation/rest-clients.md) to use this library**

Key links:

- [Package (NPM)](https://www.npmjs.com/package/@azure-rest/contoso-widgetmanager-rest)
- [API reference documentation](https://docs.microsoft.com/javascript/api/@azure-rest/contoso-widgetmanager-rest?view=azure-node-preview)

## Getting started

### Currently supported environments

- LTS versions of Node.js

### Prerequisites

- You must have an [Azure subscription](https://azure.microsoft.com/free/) to use this package.

### Install the `@azure-rest/contoso-widgetmanager-rest` package

Install the Azure WidgetManager REST client REST client library for JavaScript with `npm`:

```bash
npm install @azure-rest/contoso-widgetmanager-rest
```

### Create and authenticate a `WidgetManagerClient`

To use an [Azure Active Directory (AAD) token credential](https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/identity/identity/samples/AzureIdentityExamples.md#authenticating-with-a-pre-fetched-access-token),
provide an instance of the desired credential type obtained from the
[@azure/identity](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/identity/identity#credentials) library.

To authenticate with AAD, you must first `npm` install [`@azure/identity`](https://www.npmjs.com/package/@azure/identity)

After setup, you can choose which type of [credential](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/identity/identity#credentials) from `@azure/identity` to use.
As an example, [DefaultAzureCredential](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/identity/identity#defaultazurecredential)
can be used to authenticate the client.

Set the values of the client ID, tenant ID, and client secret of the AAD application as environment variables:
AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET

## Troubleshooting

### Logging

Enabling logging may help uncover useful information about failures. In order to see a log of HTTP requests and responses, set the `AZURE_LOG_LEVEL` environment variable to `info`. Alternatively, logging can be enabled at runtime by calling `setLogLevel` in the `@azure/logger`:

```javascript
const { setLogLevel } = require("@azure/logger");

setLogLevel("info");
```

For more detailed instructions on how to enable logs, you can look at the [@azure/logger package docs](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/logger).
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"mainEntryPointFilePath": "./dist/esm/index.d.ts",
"docModel": { "enabled": true },
"apiReport": { "enabled": true, "reportFolder": "./review" },
"dtsRollup": {
"enabled": true,
"untrimmedFilePath": "",
"publicTrimmedFilePath": "./types/contoso-widgetmanager-rest.d.ts"
},
"messages": {
"tsdocMessageReporting": { "default": { "logLevel": "none" } },
"extractorMessageReporting": {
"ae-missing-release-tag": { "logLevel": "none" },
"ae-unresolved-link": { "logLevel": "none" }
}
}
}
133 changes: 133 additions & 0 deletions tools/tsp-client/test/examples/sdk/alternate-entrypoint/karma.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

// https://github.com/karma-runner/karma-chrome-launcher
process.env.CHROME_BIN = require("puppeteer").executablePath();
require("dotenv").config();
const { relativeRecordingsPath } = require("@azure-tools/test-recorder");
process.env.RECORDINGS_RELATIVE_PATH = relativeRecordingsPath();

module.exports = function (config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: "./",

// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ["source-map-support", "mocha"],

plugins: [
"karma-mocha",
"karma-mocha-reporter",
"karma-chrome-launcher",
"karma-firefox-launcher",
"karma-env-preprocessor",
"karma-coverage",
"karma-sourcemap-loader",
"karma-junit-reporter",
"karma-source-map-support",
],

// list of files / patterns to load in the browser
files: [
"dist-test/index.browser.js",
{
pattern: "dist-test/index.browser.js.map",
type: "html",
included: false,
served: true,
},
],

// list of files / patterns to exclude
exclude: [],

// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
"**/*.js": ["sourcemap", "env"],
// IMPORTANT: COMMENT following line if you want to debug in your browsers!!
// Preprocess source file to calculate code coverage, however this will make source file unreadable
// "dist-test/index.js": ["coverage"]
},

envPreprocessor: [
"TEST_MODE",
"ENDPOINT",
"AZURE_CLIENT_SECRET",
"AZURE_CLIENT_ID",
"AZURE_TENANT_ID",
"SUBSCRIPTION_ID",
"RECORDINGS_RELATIVE_PATH",
],

// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ["mocha", "coverage", "junit"],

coverageReporter: {
// specify a common output directory
dir: "coverage-browser/",
reporters: [
{ type: "json", subdir: ".", file: "coverage.json" },
{ type: "lcovonly", subdir: ".", file: "lcov.info" },
{ type: "html", subdir: "html" },
{ type: "cobertura", subdir: ".", file: "cobertura-coverage.xml" },
],
},

junitReporter: {
outputDir: "", // results will be saved as $outputDir/$browserName.xml
outputFile: "test-results.browser.xml", // if included, results will be saved as $outputDir/$browserName/$outputFile
suite: "", // suite will become the package name attribute in xml testsuite element
useBrowserName: false, // add browser name to report and classes names
nameFormatter: undefined, // function (browser, result) to customize the name attribute in xml testcase element
classNameFormatter: undefined, // function (browser, result) to customize the classname attribute in xml testcase element
properties: {}, // key value pair of properties to add to the <properties> section of the report
},

// web server port
port: 9876,

// enable / disable colors in the output (reporters and logs)
colors: true,

// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,

// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,

// --no-sandbox allows our tests to run in Linux without having to change the system.
// --disable-web-security allows us to authenticate from the browser without having to write tests using interactive auth, which would be far more complex.
browsers: ["ChromeHeadlessNoSandbox"],
customLaunchers: {
ChromeHeadlessNoSandbox: {
base: "ChromeHeadless",
flags: ["--no-sandbox", "--disable-web-security"],
},
},

// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: false,

// Concurrency level
// how many browser should be started simultaneous
concurrency: 1,

browserNoActivityTimeout: 60000000,
browserDisconnectTimeout: 10000,
browserDisconnectTolerance: 3,

client: {
mocha: {
// change Karma's debug.html to the mocha web reporter
reporter: "html",
timeout: "600000",
},
},
});
};
Loading

0 comments on commit 9b9981e

Please sign in to comment.