@azure-tools/typespec-ts 0.56.0-dev.3 → 0.56.0-dev.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -6
- package/dist/src/interfaces.d.ts +0 -1
- package/dist/src/interfaces.d.ts.map +1 -1
- package/dist/src/interfaces.js.map +1 -1
- package/dist/src/lib.d.ts +0 -1
- package/dist/src/lib.d.ts.map +1 -1
- package/dist/src/lib.js +0 -5
- package/dist/src/lib.js.map +1 -1
- package/dist/src/metadata/build-readme-file.d.ts.map +1 -1
- package/dist/src/metadata/build-readme-file.js +14 -67
- package/dist/src/metadata/build-readme-file.js.map +1 -1
- package/dist/src/metadata/render-template.d.ts +24 -0
- package/dist/src/metadata/render-template.d.ts.map +1 -0
- package/dist/src/metadata/render-template.js +148 -0
- package/dist/src/metadata/render-template.js.map +1 -0
- package/dist/src/metadata/test/build-recorded-client.d.ts.map +1 -1
- package/dist/src/metadata/test/build-recorded-client.js +1 -7
- package/dist/src/metadata/test/build-recorded-client.js.map +1 -1
- package/dist/src/metadata/test/build-sample-test.d.ts.map +1 -1
- package/dist/src/metadata/test/build-sample-test.js +1 -4
- package/dist/src/metadata/test/build-sample-test.js.map +1 -1
- package/dist/src/metadata/test/build-snippets.d.ts.map +1 -1
- package/dist/src/metadata/test/build-snippets.js +2 -4
- package/dist/src/metadata/test/build-snippets.js.map +1 -1
- package/dist/src/metadata/test/template.d.ts +0 -1
- package/dist/src/metadata/test/template.d.ts.map +1 -1
- package/dist/src/metadata/test/template.js +0 -127
- package/dist/src/metadata/test/template.js.map +1 -1
- package/dist/src/transform/transform-client-options.d.ts.map +1 -1
- package/dist/src/transform/transform-client-options.js +0 -2
- package/dist/src/transform/transform-client-options.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -9
- package/src/interfaces.ts +0 -1
- package/src/lib.ts +0 -6
- package/src/metadata/build-readme-file.ts +17 -91
- package/src/metadata/render-template.ts +202 -0
- package/src/metadata/test/build-recorded-client.ts +1 -7
- package/src/metadata/test/build-sample-test.ts +1 -4
- package/src/metadata/test/build-snippets.ts +2 -4
- package/src/metadata/test/template.ts +0 -128
- package/src/transform/transform-client-options.ts +0 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@azure-tools/typespec-ts",
|
|
3
|
-
"version": "0.56.0-dev.
|
|
3
|
+
"version": "0.56.0-dev.5",
|
|
4
4
|
"description": "A TypeSpec emitter for TypeScript",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -42,9 +42,7 @@
|
|
|
42
42
|
"@typespec/xml": "^0.83.0 || >=0.84.0-dev <0.84.0",
|
|
43
43
|
"@typespec/ts-http-runtime": "0.3.6",
|
|
44
44
|
"cross-env": "^10.1.0",
|
|
45
|
-
"
|
|
46
|
-
"npm-run-all": "~4.1.5",
|
|
47
|
-
"prettier": "^3.8.1",
|
|
45
|
+
"concurrently": "^10.0.0",
|
|
48
46
|
"vitest": "^4.1.3",
|
|
49
47
|
"vite": "^8.0.8",
|
|
50
48
|
"@vitest/coverage-v8": "^4.1.3",
|
|
@@ -62,7 +60,6 @@
|
|
|
62
60
|
},
|
|
63
61
|
"dependencies": {
|
|
64
62
|
"fast-xml-parser": "^5.7.0",
|
|
65
|
-
"handlebars": "^4.7.7",
|
|
66
63
|
"prettier": "^3.8.1",
|
|
67
64
|
"ts-morph": "^23.0.0",
|
|
68
65
|
"tslib": "^2.3.1"
|
|
@@ -92,11 +89,11 @@
|
|
|
92
89
|
"lint": "eslint . --ext .ts --max-warnings=0",
|
|
93
90
|
"lint:fix": "eslint . --fix --ext .ts",
|
|
94
91
|
"check:tree": "node ./test/commands/check-clean-tree.ts",
|
|
95
|
-
"integration-test-ci": "npm
|
|
96
|
-
"spector-test": "
|
|
97
|
-
"start-test-server": "
|
|
92
|
+
"integration-test-ci": "npm run copy:typespec && npm run spector-test",
|
|
93
|
+
"spector-test": "concurrently --kill-others --success first \"npm:start-test-server\" \"npm:generate-and-run\"",
|
|
94
|
+
"start-test-server": "node ./test/commands/start-test-server.js",
|
|
98
95
|
"copy:typespec": "node ./test/commands/copy-typespec.ts",
|
|
99
|
-
"generate-and-run": "npm run generate-tsp-only:client &&
|
|
96
|
+
"generate-and-run": "npm run generate-tsp-only:client && concurrently --kill-others-on-fail \"npm:integration-test:alone\" \"npm:generate-tsp-only:declarations\" && npm run stop-test-server -- -p 3002",
|
|
100
97
|
"generate-tsp-only": "node ./test/commands/gen-spector.js",
|
|
101
98
|
"generate-tsp-only:client": "node ./test/commands/gen-spector.js --phase=client",
|
|
102
99
|
"generate-tsp-only:declarations": "node ./test/commands/gen-spector.js --phase=declarations",
|
package/src/interfaces.ts
CHANGED
package/src/lib.ts
CHANGED
|
@@ -39,7 +39,6 @@ export interface EmitterOptions {
|
|
|
39
39
|
*/
|
|
40
40
|
"generate-test"?: boolean;
|
|
41
41
|
"generate-sample"?: boolean;
|
|
42
|
-
"is-typespec-test"?: boolean;
|
|
43
42
|
"azure-arm"?: boolean;
|
|
44
43
|
"enable-operation-group"?: boolean;
|
|
45
44
|
"enable-model-namespace"?: boolean;
|
|
@@ -170,11 +169,6 @@ export const EmitterOptionsSchema: JSONSchemaType<EmitterOptions> = {
|
|
|
170
169
|
description:
|
|
171
170
|
"Whether to generate sample files, for basic samples of your generated sdks. Defaults to `undefined`. Management packages' default to `true`.",
|
|
172
171
|
},
|
|
173
|
-
"is-typespec-test": {
|
|
174
|
-
type: "boolean",
|
|
175
|
-
nullable: true,
|
|
176
|
-
description: "Internal option for test",
|
|
177
|
-
},
|
|
178
172
|
"azure-arm": {
|
|
179
173
|
type: "boolean",
|
|
180
174
|
nullable: true,
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
3
|
|
|
4
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
5
|
-
// @ts-ignore: to fix the handlebars issue
|
|
6
|
-
import hbs from "handlebars";
|
|
7
4
|
import { ClientModel } from "../interfaces.js";
|
|
8
5
|
import { getClientName } from "../utils/name-constructors.js";
|
|
9
6
|
import { NameType, normalizeName } from "../utils/name-utils.js";
|
|
7
|
+
import { renderTemplate } from "./render-template.js";
|
|
10
8
|
|
|
11
9
|
const azureReadmeModularTemplate = `# {{ clientDescriptiveName }} library for JavaScript
|
|
12
10
|
|
|
@@ -38,13 +36,10 @@ Key links:
|
|
|
38
36
|
|
|
39
37
|
See our [support policy](https://github.com/Azure/azure-sdk-for-js/blob/main/SUPPORT.md) for more details.
|
|
40
38
|
|
|
41
|
-
{{#if azure}}
|
|
42
39
|
### Prerequisites
|
|
43
40
|
|
|
44
41
|
- An [Azure subscription][azure_sub].
|
|
45
|
-
{{/if}}
|
|
46
42
|
|
|
47
|
-
{{#if isReleasablePackage}}
|
|
48
43
|
### Install the \`{{ clientPackageName }}\` package
|
|
49
44
|
|
|
50
45
|
Install the {{ clientDescriptiveName }} library for JavaScript with \`npm\`:
|
|
@@ -52,9 +47,7 @@ Install the {{ clientDescriptiveName }} library for JavaScript with \`npm\`:
|
|
|
52
47
|
\`\`\`bash
|
|
53
48
|
npm install {{ clientPackageName }}
|
|
54
49
|
\`\`\`
|
|
55
|
-
{{/if}}
|
|
56
50
|
|
|
57
|
-
{{#if azure}}
|
|
58
51
|
{{#if addCredentials}}
|
|
59
52
|
### Create and authenticate a \`{{ clientClassName}}\`
|
|
60
53
|
|
|
@@ -129,7 +122,8 @@ const credential = new InteractiveBrowserCredential({
|
|
|
129
122
|
const client = new {{ clientClassName }}("<endpoint>", credential);
|
|
130
123
|
\`\`\`
|
|
131
124
|
{{/if}}
|
|
132
|
-
{{/if}}
|
|
125
|
+
{{/if}}
|
|
126
|
+
|
|
133
127
|
|
|
134
128
|
### JavaScript Bundle
|
|
135
129
|
To use this client library in the browser, first you need to use a bundler. For details on how to do this, please refer to our [bundling documentation](https://aka.ms/AzureSDKBundling).
|
|
@@ -140,7 +134,6 @@ To use this client library in the browser, first you need to use a bundler. For
|
|
|
140
134
|
|
|
141
135
|
\`{{ clientClassName }}\` is the primary interface for developers using the {{ clientDescriptiveName }} library. Explore the methods on this client object to understand the different features of the {{ serviceName }} service that you can access.
|
|
142
136
|
|
|
143
|
-
{{#if azure}}
|
|
144
137
|
## Troubleshooting
|
|
145
138
|
|
|
146
139
|
### Logging
|
|
@@ -153,7 +146,7 @@ import { setLogLevel } from "@azure/logger";
|
|
|
153
146
|
setLogLevel("info");
|
|
154
147
|
\`\`\`
|
|
155
148
|
|
|
156
|
-
For more detailed instructions on how to enable logs, you can look at the [@azure/logger package docs](
|
|
149
|
+
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).
|
|
157
150
|
|
|
158
151
|
{{#if samplesURL}}
|
|
159
152
|
## Next steps
|
|
@@ -163,54 +156,16 @@ Please take a look at the [samples]({{ samplesURL }}) directory for detailed exa
|
|
|
163
156
|
|
|
164
157
|
## Contributing
|
|
165
158
|
|
|
166
|
-
If you'd like to contribute to this library, please read the [contributing guide](
|
|
159
|
+
If you'd like to contribute to this library, please read the [contributing guide](https://github.com/Azure/azure-sdk-for-js/blob/main/CONTRIBUTING.md) to learn more about how to build and test the code.
|
|
167
160
|
|
|
168
161
|
## Related projects
|
|
169
162
|
|
|
170
|
-
- [
|
|
163
|
+
- [Microsoft Azure SDK for JavaScript](https://github.com/Azure/azure-sdk-for-js)
|
|
171
164
|
|
|
172
165
|
[azure_sub]: https://azure.microsoft.com/free/
|
|
173
166
|
[azure_portal]: https://portal.azure.com
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
{{/if}}
|
|
177
|
-
`;
|
|
178
|
-
|
|
179
|
-
const nonBrandedReadmeTemplate = `# {{ clientDescriptiveName }} library for JavaScript
|
|
180
|
-
|
|
181
|
-
{{ description }}
|
|
182
|
-
|
|
183
|
-
Key links:
|
|
184
|
-
|
|
185
|
-
{{#if packageSourceURL}}
|
|
186
|
-
- [Source code]({{ packageSourceURL }})
|
|
187
|
-
{{/if}}
|
|
188
|
-
{{#if packageNPMURL}}
|
|
189
|
-
- [Package (NPM)]({{ packageNPMURL }})
|
|
190
|
-
{{/if}}
|
|
191
|
-
{{#if apiRefURL}}
|
|
192
|
-
- [API reference documentation]({{ apiRefURL }})
|
|
193
|
-
{{/if}}
|
|
194
|
-
{{#if serviceDocURL}}
|
|
195
|
-
- [Product documentation]({{ serviceDocURL }})
|
|
196
|
-
{{/if}}
|
|
197
|
-
{{#if samplesURL}}
|
|
198
|
-
- [Samples]({{ samplesURL }})
|
|
199
|
-
{{/if}}
|
|
200
|
-
|
|
201
|
-
## Getting started
|
|
202
|
-
|
|
203
|
-
### Currently supported environments
|
|
204
|
-
|
|
205
|
-
- LTS versions of Node.js
|
|
206
|
-
|
|
207
|
-
### Install the \`{{ clientPackageName }}\` package
|
|
208
|
-
|
|
209
|
-
Install the {{ clientDescriptiveName }} library for JavaScript with \`npm\`:
|
|
210
|
-
|
|
211
|
-
\`\`\`bash
|
|
212
|
-
npm install {{ clientPackageName }}
|
|
213
|
-
\`\`\`
|
|
167
|
+
[azure_identity]: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/identity/identity
|
|
168
|
+
[defaultazurecredential]: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/identity/identity#defaultazurecredential
|
|
214
169
|
`;
|
|
215
170
|
|
|
216
171
|
const apiReferenceTemplate = `{{#if apiRefURL}}
|
|
@@ -228,8 +183,6 @@ interface Metadata {
|
|
|
228
183
|
clientPackageName: string;
|
|
229
184
|
/** The name of the client class */
|
|
230
185
|
clientClassName: string;
|
|
231
|
-
/** The URL of the repository the package lives in */
|
|
232
|
-
repoURL?: string;
|
|
233
186
|
/** The URL to the package directory in the repository */
|
|
234
187
|
packageSourceURL?: string;
|
|
235
188
|
/** The URL to the package's samples */
|
|
@@ -240,29 +193,12 @@ interface Metadata {
|
|
|
240
193
|
description?: string;
|
|
241
194
|
/** The URL to the package on npmjs.org */
|
|
242
195
|
packageNPMURL?: string;
|
|
243
|
-
/** The name of the project that lives in the repository */
|
|
244
|
-
projectName?: string;
|
|
245
196
|
/** whether the client accepts standard credentials */
|
|
246
197
|
addCredentials?: boolean;
|
|
247
|
-
/** The link to the identity package in the repository */
|
|
248
|
-
identityPackageURL?: string;
|
|
249
|
-
/** The URL for the service document */
|
|
250
|
-
serviceDocURL?: string;
|
|
251
|
-
/** The dependency info for this service */
|
|
252
|
-
dependencyDescription?: string;
|
|
253
|
-
dependencyLink?: string;
|
|
254
|
-
/** Indicates if the package is a multi-client */
|
|
255
|
-
hasMultiClients?: boolean;
|
|
256
198
|
/** The URL to the API reference */
|
|
257
199
|
apiRefURL?: string;
|
|
258
200
|
/** Check if the rp is management plane */
|
|
259
201
|
azureArm?: boolean;
|
|
260
|
-
/** Whether the package being generated is for an Azure service */
|
|
261
|
-
azure: boolean;
|
|
262
|
-
/** Indicates if the package is a test/releasable package. */
|
|
263
|
-
isReleasablePackage?: boolean;
|
|
264
|
-
/** The link to the contributing guide in the repository */
|
|
265
|
-
contributingGuideURL?: string;
|
|
266
202
|
/** Indicates if the package need generate test files */
|
|
267
203
|
generateTest?: boolean;
|
|
268
204
|
/** Indicates if the package need SubscriptionId as the client parameter */
|
|
@@ -271,13 +207,10 @@ interface Metadata {
|
|
|
271
207
|
|
|
272
208
|
export function buildReadmeFile(model: ClientModel) {
|
|
273
209
|
const metadata = createMetadata(model) ?? {};
|
|
274
|
-
const
|
|
275
|
-
model.options ? azureReadmeModularTemplate : nonBrandedReadmeTemplate,
|
|
276
|
-
{ noEscape: true },
|
|
277
|
-
);
|
|
210
|
+
const content = renderTemplate(azureReadmeModularTemplate, metadata as Record<string, unknown>);
|
|
278
211
|
return {
|
|
279
212
|
path: "README.md",
|
|
280
|
-
content
|
|
213
|
+
content,
|
|
281
214
|
};
|
|
282
215
|
}
|
|
283
216
|
|
|
@@ -301,7 +234,10 @@ export function updateReadmeFile(
|
|
|
301
234
|
try {
|
|
302
235
|
const metadata = createMetadata(model) ?? {};
|
|
303
236
|
|
|
304
|
-
const newApiRefLink =
|
|
237
|
+
const newApiRefLink = renderTemplate(
|
|
238
|
+
apiReferenceTemplate,
|
|
239
|
+
metadata as Record<string, unknown>,
|
|
240
|
+
).trim();
|
|
305
241
|
|
|
306
242
|
if (!newApiRefLink) {
|
|
307
243
|
return { path: "README.md", content: existingReadmeContent };
|
|
@@ -329,14 +265,12 @@ function createMetadata(model: ClientModel): Metadata | undefined {
|
|
|
329
265
|
return;
|
|
330
266
|
}
|
|
331
267
|
// const packageDetails = model.options.packageDetails;
|
|
332
|
-
const { packageDetails, azureOutputDirectory, serviceInfo
|
|
268
|
+
const { packageDetails, azureOutputDirectory, serviceInfo } = model.options;
|
|
333
269
|
|
|
334
|
-
const azureHuh =
|
|
335
|
-
packageDetails?.scopeName === "azure" || packageDetails?.scopeName === "azure-rest";
|
|
336
270
|
const repoURL = "https://github.com/Azure/azure-sdk-for-js";
|
|
337
271
|
const relativePackageSourcePath = azureOutputDirectory;
|
|
338
272
|
const packageSourceURL =
|
|
339
|
-
relativePackageSourcePath &&
|
|
273
|
+
relativePackageSourcePath && `${repoURL}/tree/main/${relativePackageSourcePath}`;
|
|
340
274
|
|
|
341
275
|
const clientPackageName = packageDetails?.name;
|
|
342
276
|
const clientClassName = getClientName(model);
|
|
@@ -363,17 +297,9 @@ function createMetadata(model: ClientModel): Metadata | undefined {
|
|
|
363
297
|
packageNPMURL: `https://www.npmjs.com/package/${clientPackageName}`,
|
|
364
298
|
samplesURL:
|
|
365
299
|
model.options.generateSample && packageSourceURL ? `${packageSourceURL}/samples` : undefined,
|
|
366
|
-
apiRefURL:
|
|
367
|
-
? `https://learn.microsoft.com/javascript/api/${clientPackageName}${apiRefUrlQueryParameter}`
|
|
368
|
-
: undefined,
|
|
300
|
+
apiRefURL: `https://learn.microsoft.com/javascript/api/${clientPackageName}${apiRefUrlQueryParameter}`,
|
|
369
301
|
azureArm: Boolean(model.options.azureArm),
|
|
370
|
-
azure: azureHuh,
|
|
371
|
-
isReleasablePackage: !isTypeSpecTest,
|
|
372
|
-
repoURL: repoURL,
|
|
373
|
-
projectName: azureHuh ? "Microsoft Azure SDK for JavaScript" : undefined,
|
|
374
|
-
identityPackageURL: repoURL && `${repoURL}/tree/main/sdk/identity/identity`,
|
|
375
302
|
addCredentials: model.options.addCredentials,
|
|
376
|
-
contributingGuideURL: repoURL && `${repoURL}/blob/main/CONTRIBUTING.md`,
|
|
377
303
|
generateTest: model.options.generateTest,
|
|
378
304
|
hasSubscriptionId: model.options.hasSubscriptionId,
|
|
379
305
|
};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A tiny, dependency-free renderer for the small subset of Handlebars/Mustache
|
|
6
|
+
* syntax used by the emitter's metadata templates. It intentionally supports only
|
|
7
|
+
* what those templates need:
|
|
8
|
+
*
|
|
9
|
+
* - `{{ name }}` interpolation (always "unescaped", matching the previous
|
|
10
|
+
* `{ noEscape: true }` Handlebars option).
|
|
11
|
+
* - `{{#if name}} ... {{else}} ... {{/if}}` conditional blocks (nesting allowed).
|
|
12
|
+
*
|
|
13
|
+
* It reproduces Handlebars' "standalone" whitespace handling so that a block tag
|
|
14
|
+
* occupying its own line does not leave a blank line behind, keeping the rendered
|
|
15
|
+
* output byte-for-byte identical to the previous Handlebars-based implementation.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
type TemplateData = Record<string, unknown>;
|
|
19
|
+
|
|
20
|
+
interface TextToken {
|
|
21
|
+
kind: "text";
|
|
22
|
+
value: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface TagToken {
|
|
26
|
+
kind: "open" | "else" | "close" | "var";
|
|
27
|
+
/** Condition (for `open`) or variable name (for `var`). */
|
|
28
|
+
name: string;
|
|
29
|
+
/** Whether this block tag stands alone on its line (Handlebars semantics). */
|
|
30
|
+
standalone: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type Token = TextToken | TagToken;
|
|
34
|
+
|
|
35
|
+
const MUSTACHE = /\{\{([^}]*)\}\}/g;
|
|
36
|
+
|
|
37
|
+
function tokenize(template: string): Token[] {
|
|
38
|
+
const tokens: Token[] = [];
|
|
39
|
+
let lastIndex = 0;
|
|
40
|
+
let match: RegExpExecArray | null;
|
|
41
|
+
MUSTACHE.lastIndex = 0;
|
|
42
|
+
while ((match = MUSTACHE.exec(template)) !== null) {
|
|
43
|
+
tokens.push({ kind: "text", value: template.slice(lastIndex, match.index) });
|
|
44
|
+
const inner = match[1]!.trim();
|
|
45
|
+
if (inner.startsWith("#if ")) {
|
|
46
|
+
tokens.push({ kind: "open", name: inner.slice(4).trim(), standalone: false });
|
|
47
|
+
} else if (inner === "else") {
|
|
48
|
+
tokens.push({ kind: "else", name: "", standalone: false });
|
|
49
|
+
} else if (inner === "/if") {
|
|
50
|
+
tokens.push({ kind: "close", name: "", standalone: false });
|
|
51
|
+
} else {
|
|
52
|
+
tokens.push({ kind: "var", name: inner, standalone: false });
|
|
53
|
+
}
|
|
54
|
+
lastIndex = match.index + match[0].length;
|
|
55
|
+
}
|
|
56
|
+
tokens.push({ kind: "text", value: template.slice(lastIndex) });
|
|
57
|
+
return tokens;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Applies Handlebars "standalone" whitespace stripping. A block tag (`#if`,
|
|
62
|
+
* `else`, `/if`) that is the only non-whitespace content on its line has the
|
|
63
|
+
* surrounding indentation and trailing newline removed.
|
|
64
|
+
*/
|
|
65
|
+
function stripStandaloneWhitespace(tokens: Token[]): void {
|
|
66
|
+
const isBlock = (t: Token): t is TagToken =>
|
|
67
|
+
t.kind === "open" || t.kind === "else" || t.kind === "close";
|
|
68
|
+
|
|
69
|
+
// First pass: detect standalone tags from the original text tokens.
|
|
70
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
71
|
+
const token = tokens[i]!;
|
|
72
|
+
if (!isBlock(token)) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const prev = tokens[i - 1] as TextToken | undefined;
|
|
76
|
+
const next = tokens[i + 1] as TextToken | undefined;
|
|
77
|
+
const prevIsFirst = i - 1 === 0;
|
|
78
|
+
const nextIsLast = i + 1 === tokens.length - 1;
|
|
79
|
+
|
|
80
|
+
const prevValue = prev?.value ?? "";
|
|
81
|
+
const nextValue = next?.value ?? "";
|
|
82
|
+
|
|
83
|
+
const prevStandalone =
|
|
84
|
+
/\n[ \t]*$/.test(prevValue) || (prevIsFirst && /^[ \t]*$/.test(prevValue));
|
|
85
|
+
const nextStandalone =
|
|
86
|
+
/^[ \t]*\n/.test(nextValue) || (nextIsLast && /^[ \t]*$/.test(nextValue));
|
|
87
|
+
|
|
88
|
+
token.standalone = prevStandalone && nextStandalone;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Second pass: strip whitespace around standalone tags.
|
|
92
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
93
|
+
const token = tokens[i]!;
|
|
94
|
+
if (!isBlock(token) || !token.standalone) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const prev = tokens[i - 1] as TextToken | undefined;
|
|
98
|
+
const next = tokens[i + 1] as TextToken | undefined;
|
|
99
|
+
if (prev) {
|
|
100
|
+
// Drop the indentation preceding the tag, keeping the previous newline.
|
|
101
|
+
prev.value = prev.value.replace(/[ \t]*$/, "");
|
|
102
|
+
}
|
|
103
|
+
if (next) {
|
|
104
|
+
// Drop the rest of the tag's line, including its trailing newline.
|
|
105
|
+
next.value = /^[ \t]*\n/.test(next.value)
|
|
106
|
+
? next.value.replace(/^[ \t]*\n/, "")
|
|
107
|
+
: next.value.replace(/^[ \t]*$/, "");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
type Node = TextNode | VarNode | IfNode;
|
|
113
|
+
interface TextNode {
|
|
114
|
+
kind: "text";
|
|
115
|
+
value: string;
|
|
116
|
+
}
|
|
117
|
+
interface VarNode {
|
|
118
|
+
kind: "var";
|
|
119
|
+
name: string;
|
|
120
|
+
}
|
|
121
|
+
interface IfNode {
|
|
122
|
+
kind: "if";
|
|
123
|
+
condition: string;
|
|
124
|
+
whenTrue: Node[];
|
|
125
|
+
whenFalse: Node[];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function parse(tokens: Token[], start: number, stopKinds: Token["kind"][]): [Node[], number] {
|
|
129
|
+
const nodes: Node[] = [];
|
|
130
|
+
let i = start;
|
|
131
|
+
while (i < tokens.length) {
|
|
132
|
+
const token = tokens[i]!;
|
|
133
|
+
if (token.kind === "text") {
|
|
134
|
+
if (token.value) {
|
|
135
|
+
nodes.push({ kind: "text", value: token.value });
|
|
136
|
+
}
|
|
137
|
+
i++;
|
|
138
|
+
} else if (token.kind === "var") {
|
|
139
|
+
nodes.push({ kind: "var", name: token.name });
|
|
140
|
+
i++;
|
|
141
|
+
} else if (token.kind === "open") {
|
|
142
|
+
const [whenTrue, afterTrue] = parse(tokens, i + 1, ["else", "close"]);
|
|
143
|
+
let whenFalse: Node[] = [];
|
|
144
|
+
let next = afterTrue;
|
|
145
|
+
if (tokens[next]?.kind === "else") {
|
|
146
|
+
const [elseNodes, afterElse] = parse(tokens, next + 1, ["close"]);
|
|
147
|
+
whenFalse = elseNodes;
|
|
148
|
+
next = afterElse;
|
|
149
|
+
}
|
|
150
|
+
// `next` now points at the matching `close` token.
|
|
151
|
+
nodes.push({ kind: "if", condition: token.name, whenTrue, whenFalse });
|
|
152
|
+
i = next + 1;
|
|
153
|
+
} else if (stopKinds.includes(token.kind)) {
|
|
154
|
+
return [nodes, i];
|
|
155
|
+
} else {
|
|
156
|
+
// Unmatched `else`/`close`; skip defensively.
|
|
157
|
+
i++;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return [nodes, i];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function isTruthy(value: unknown): boolean {
|
|
164
|
+
if (Array.isArray(value)) {
|
|
165
|
+
return value.length > 0;
|
|
166
|
+
}
|
|
167
|
+
return Boolean(value);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function renderNodes(nodes: Node[], data: TemplateData): string {
|
|
171
|
+
let out = "";
|
|
172
|
+
for (const node of nodes) {
|
|
173
|
+
switch (node.kind) {
|
|
174
|
+
case "text":
|
|
175
|
+
out += node.value;
|
|
176
|
+
break;
|
|
177
|
+
case "var": {
|
|
178
|
+
const value = data[node.name];
|
|
179
|
+
out += value === undefined || value === null ? "" : String(value);
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
case "if":
|
|
183
|
+
out += renderNodes(isTruthy(data[node.condition]) ? node.whenTrue : node.whenFalse, data);
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return out;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Renders a metadata template against the provided data.
|
|
192
|
+
*
|
|
193
|
+
* @param template - Template using the supported `{{var}}` / `{{#if}}` subset.
|
|
194
|
+
* @param data - Values referenced by the template.
|
|
195
|
+
* @returns The rendered string.
|
|
196
|
+
*/
|
|
197
|
+
export function renderTemplate(template: string, data: TemplateData): string {
|
|
198
|
+
const tokens = tokenize(template);
|
|
199
|
+
stripStandaloneWhitespace(tokens);
|
|
200
|
+
const [nodes] = parse(tokens, 0, []);
|
|
201
|
+
return renderNodes(nodes, data);
|
|
202
|
+
}
|
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2
|
-
// @ts-ignore: to fix the handlebars issue
|
|
3
|
-
import hbs from "handlebars";
|
|
4
1
|
import { ClientModel } from "../../interfaces.js";
|
|
5
2
|
import { recordedClientContent } from "./template.js";
|
|
6
3
|
|
|
7
4
|
export function buildRecordedClientFile(_model: ClientModel) {
|
|
8
|
-
const recordedClientFileContents = hbs.compile(recordedClientContent, {
|
|
9
|
-
noEscape: true,
|
|
10
|
-
});
|
|
11
5
|
return {
|
|
12
6
|
path: "test/public/utils/recordedClient.ts",
|
|
13
|
-
content:
|
|
7
|
+
content: recordedClientContent,
|
|
14
8
|
};
|
|
15
9
|
}
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2
|
-
// @ts-ignore: to fix the handlebars issue
|
|
3
|
-
import hbs from "handlebars";
|
|
4
1
|
import { ClientModel } from "../../interfaces.js";
|
|
5
2
|
import { sampleTestContent } from "./template.js";
|
|
6
3
|
|
|
7
4
|
export function buildSampleTest(_model: ClientModel) {
|
|
8
5
|
return {
|
|
9
6
|
path: "test/public/sampleTest.spec.ts",
|
|
10
|
-
content:
|
|
7
|
+
content: sampleTestContent,
|
|
11
8
|
};
|
|
12
9
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2
|
-
// @ts-ignore: to fix the handlebars issue
|
|
3
|
-
import hbs from "handlebars";
|
|
4
1
|
import { ClientModel } from "../../interfaces.js";
|
|
5
2
|
import { getClientName } from "../../utils/name-constructors.js";
|
|
3
|
+
import { renderTemplate } from "../render-template.js";
|
|
6
4
|
import { snippetsContent } from "./template.js";
|
|
7
5
|
|
|
8
6
|
export function buildSnippets(model: ClientModel, clientName?: string) {
|
|
@@ -14,7 +12,7 @@ export function buildSnippets(model: ClientModel, clientName?: string) {
|
|
|
14
12
|
) {
|
|
15
13
|
return {
|
|
16
14
|
path: "test/snippets.spec.ts",
|
|
17
|
-
content:
|
|
15
|
+
content: renderTemplate(snippetsContent, {
|
|
18
16
|
clientClassName: clientName ? clientName : getClientName(model),
|
|
19
17
|
azureArm: model.options?.azureArm,
|
|
20
18
|
hasSubscriptionId: model.options.hasSubscriptionId,
|
|
@@ -1,131 +1,3 @@
|
|
|
1
|
-
export const karmaConfig = `
|
|
2
|
-
// https://github.com/karma-runner/karma-chrome-launcher
|
|
3
|
-
process.env.CHROME_BIN = require("puppeteer").executablePath();
|
|
4
|
-
require("dotenv").config();
|
|
5
|
-
const { relativeRecordingsPath } = require("@azure-tools/test-recorder");
|
|
6
|
-
process.env.RECORDINGS_RELATIVE_PATH = relativeRecordingsPath();
|
|
7
|
-
|
|
8
|
-
module.exports = function (config) {
|
|
9
|
-
config.set({
|
|
10
|
-
// base path that will be used to resolve all patterns (eg. files, exclude)
|
|
11
|
-
basePath: "./",
|
|
12
|
-
|
|
13
|
-
// frameworks to use
|
|
14
|
-
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
|
15
|
-
frameworks: ["source-map-support", "mocha"],
|
|
16
|
-
|
|
17
|
-
plugins: [
|
|
18
|
-
"karma-mocha",
|
|
19
|
-
"karma-mocha-reporter",
|
|
20
|
-
"karma-chrome-launcher",
|
|
21
|
-
"karma-firefox-launcher",
|
|
22
|
-
"karma-env-preprocessor",
|
|
23
|
-
"karma-coverage",
|
|
24
|
-
"karma-sourcemap-loader",
|
|
25
|
-
"karma-junit-reporter",
|
|
26
|
-
"karma-source-map-support",
|
|
27
|
-
],
|
|
28
|
-
|
|
29
|
-
// list of files / patterns to load in the browser
|
|
30
|
-
files: [
|
|
31
|
-
"dist-test/index.browser.js",
|
|
32
|
-
{ pattern: "dist-test/index.browser.js.map", type: "html", included: false, served: true },
|
|
33
|
-
],
|
|
34
|
-
|
|
35
|
-
// list of files / patterns to exclude
|
|
36
|
-
exclude: [],
|
|
37
|
-
|
|
38
|
-
// preprocess matching files before serving them to the browser
|
|
39
|
-
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
|
40
|
-
preprocessors: {
|
|
41
|
-
"**/*.js": ["sourcemap", "env"],
|
|
42
|
-
// IMPORTANT: COMMENT following line if you want to debug in your browsers!!
|
|
43
|
-
// Preprocess source file to calculate code coverage, however this will make source file unreadable
|
|
44
|
-
// "dist-test/index.js": ["coverage"]
|
|
45
|
-
},
|
|
46
|
-
|
|
47
|
-
envPreprocessor: [
|
|
48
|
-
"TEST_MODE",
|
|
49
|
-
"ENDPOINT",
|
|
50
|
-
"AZURE_CLIENT_SECRET",
|
|
51
|
-
"AZURE_CLIENT_ID",
|
|
52
|
-
"AZURE_TENANT_ID",
|
|
53
|
-
"SUBSCRIPTION_ID",
|
|
54
|
-
"RECORDINGS_RELATIVE_PATH",
|
|
55
|
-
],
|
|
56
|
-
|
|
57
|
-
// test results reporter to use
|
|
58
|
-
// possible values: 'dots', 'progress'
|
|
59
|
-
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
|
60
|
-
reporters: ["mocha", "coverage", "junit"],
|
|
61
|
-
|
|
62
|
-
coverageReporter: {
|
|
63
|
-
// specify a common output directory
|
|
64
|
-
dir: "coverage-browser/",
|
|
65
|
-
reporters: [
|
|
66
|
-
{ type: "json", subdir: ".", file: "coverage.json" },
|
|
67
|
-
{ type: "lcovonly", subdir: ".", file: "lcov.info" },
|
|
68
|
-
{ type: "html", subdir: "html" },
|
|
69
|
-
{ type: "cobertura", subdir: ".", file: "cobertura-coverage.xml" },
|
|
70
|
-
],
|
|
71
|
-
},
|
|
72
|
-
|
|
73
|
-
junitReporter: {
|
|
74
|
-
outputDir: "", // results will be saved as $outputDir/$browserName.xml
|
|
75
|
-
outputFile: "test-results.browser.xml", // if included, results will be saved as $outputDir/$browserName/$outputFile
|
|
76
|
-
suite: "", // suite will become the package name attribute in xml testsuite element
|
|
77
|
-
useBrowserName: false, // add browser name to report and classes names
|
|
78
|
-
nameFormatter: undefined, // function (browser, result) to customize the name attribute in xml testcase element
|
|
79
|
-
classNameFormatter: undefined, // function (browser, result) to customize the classname attribute in xml testcase element
|
|
80
|
-
properties: {}, // key value pair of properties to add to the <properties> section of the report
|
|
81
|
-
},
|
|
82
|
-
|
|
83
|
-
// web server port
|
|
84
|
-
port: 9876,
|
|
85
|
-
|
|
86
|
-
// enable / disable colors in the output (reporters and logs)
|
|
87
|
-
colors: true,
|
|
88
|
-
|
|
89
|
-
// level of logging
|
|
90
|
-
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
|
91
|
-
logLevel: config.LOG_INFO,
|
|
92
|
-
|
|
93
|
-
// enable / disable watching file and executing tests whenever any file changes
|
|
94
|
-
autoWatch: false,
|
|
95
|
-
|
|
96
|
-
// --no-sandbox allows our tests to run in Linux without having to change the system.
|
|
97
|
-
// --disable-web-security allows us to authenticate from the browser without having to write tests using interactive auth, which would be far more complex.
|
|
98
|
-
browsers: ["ChromeHeadlessNoSandbox"],
|
|
99
|
-
customLaunchers: {
|
|
100
|
-
ChromeHeadlessNoSandbox: {
|
|
101
|
-
base: "ChromeHeadless",
|
|
102
|
-
flags: ["--no-sandbox", "--disable-web-security"],
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
|
|
106
|
-
// Continuous Integration mode
|
|
107
|
-
// if true, Karma captures browsers, runs the tests and exits
|
|
108
|
-
singleRun: false,
|
|
109
|
-
|
|
110
|
-
// Concurrency level
|
|
111
|
-
// how many browser should be started simultaneous
|
|
112
|
-
concurrency: 1,
|
|
113
|
-
|
|
114
|
-
browserNoActivityTimeout: 60000000,
|
|
115
|
-
browserDisconnectTimeout: 10000,
|
|
116
|
-
browserDisconnectTolerance: 3,
|
|
117
|
-
|
|
118
|
-
client: {
|
|
119
|
-
mocha: {
|
|
120
|
-
// change Karma's debug.html to the mocha web reporter
|
|
121
|
-
reporter: "html",
|
|
122
|
-
timeout: "600000",
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
});
|
|
126
|
-
};
|
|
127
|
-
`;
|
|
128
|
-
|
|
129
1
|
export const recordedClientContent = `
|
|
130
2
|
|
|
131
3
|
import {
|