Skip to content

Commit e825c59

Browse files
[dev-tool] Enable sample folders to use nested directory structures. (Azure#15110)
* [dev-tool] Added support for nested sample files. * Tweak regexp for removing empty lines at beginning of JSDoc comments * Fix table generation to allow nested filenames for link tags.
1 parent 18d78f2 commit e825c59

File tree

5 files changed

+87
-44
lines changed

5 files changed

+87
-44
lines changed

common/tools/dev-tool/src/commands/samples/publish.ts

Lines changed: 59 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ import instantiateSampleReadme from "../../templates/sampleReadme.md";
3737
import { convert } from "./tsToJs";
3838

3939
import devToolPackageJson from "../../../package.json";
40+
import { findMatchingFiles } from "../../util/findMatchingFiles";
41+
import { EOL } from "os";
4042

4143
const log = createPrinter("publish");
4244

@@ -194,23 +196,26 @@ async function processSources(
194196
const tags = ts.getJSDocTags(node);
195197

196198
// Look for the @summary jsdoc tag block as well
197-
if (summary === undefined && tags.some(({ tagName: { text } }) => text === "summary")) {
199+
if (summary === undefined) {
198200
for (const tag of tags) {
201+
log.debug(`File ${relativeSourcePath} has tag ${tag.tagName.text}`);
199202
if (tag.tagName.text === "summary") {
200203
log.debug("Found summary tag on node:", node.getText(sourceFile));
201204
// Replace is required due to multi-line splitting messing with table formatting
202205
summary = tag.comment?.replace(/\s*\r?\n\s*/g, " ");
203-
} else if (
204-
tag.tagName.text.startsWith(`${AZSDK_META_TAG_PREFIX}`) &&
205-
tag.comment !== undefined
206-
) {
206+
} else if (tag.tagName.text.startsWith(`${AZSDK_META_TAG_PREFIX}`)) {
207207
// We ran into an `azsdk` directive in the metadata
208208
const metaTag = tag.tagName.text.replace(
209209
new RegExp(`^${AZSDK_META_TAG_PREFIX}`),
210210
""
211211
) as keyof AzSdkMetaTags;
212+
log.debug(`File ${relativeSourcePath} has azsdk tag ${tag.tagName.text}`);
212213
if (VALID_AZSDK_META_TAGS.includes(metaTag)) {
213-
azSdkTags[metaTag as keyof AzSdkMetaTags] = JSON.parse(tag.comment);
214+
const comment = tag.comment?.trim();
215+
// If there was _no_ comment, then we can assume it is a boolean tag
216+
// and so being specified at all is an indication that we should use
217+
// `true`
218+
azSdkTags[metaTag as keyof AzSdkMetaTags] = comment ? JSON.parse(comment) : true;
214219
} else {
215220
log.warn(
216221
`Invalid azsdk tag ${metaTag}. Valid tags include ${VALID_AZSDK_META_TAGS}`
@@ -228,17 +233,26 @@ async function processSources(
228233
return sourceFile;
229234
};
230235

236+
const jsModuleText = convert(sourceText, {
237+
fileName: source,
238+
transformers: {
239+
before: [sourceProcessor]
240+
}
241+
});
242+
243+
if (summary === undefined && azSdkTags.util !== true) {
244+
log.debug(azSdkTags.util, summary);
245+
fail(
246+
`${relativeSourcePath} does not include an @summary tag and is not marked as a util (using @azsdk-util true).`
247+
);
248+
}
249+
231250
return {
232251
filePath: source,
233252
relativeSourcePath,
234253
text: sourceText,
235-
jsModuleText: convert(sourceText, {
236-
fileName: source,
237-
transformers: {
238-
before: [sourceProcessor]
239-
}
240-
}),
241-
summary: summary ?? fail(`${relativeSourcePath} does not include an @summary tag.`),
254+
jsModuleText,
255+
summary,
242256
importedModules: importedModules.filter(isDependency),
243257
usedEnvironmentVariables,
244258
azSdkTags
@@ -248,6 +262,16 @@ async function processSources(
248262
return Promise.all(jobs);
249263
}
250264

265+
async function collect<T>(i: AsyncIterableIterator<T>): Promise<T[]> {
266+
const out = [];
267+
268+
for await (const v of i) {
269+
out.push(v);
270+
}
271+
272+
return out;
273+
}
274+
251275
/**
252276
* Extracts the sample generation metainformation from the sample sources and
253277
* configuration in package.json.
@@ -261,9 +285,10 @@ async function makeSampleGenerationInfo(
261285
onError: () => void
262286
): Promise<SampleGenerationInfo> {
263287
const sampleSourcesPath = path.join(projectInfo.path, DEV_SAMPLES_BASE);
264-
const sampleSources = (await fs.readdir(sampleSourcesPath))
265-
.filter((name) => name.endsWith(".ts"))
266-
.map((name) => path.join(sampleSourcesPath, name));
288+
289+
const sampleSources = await collect(
290+
findMatchingFiles(sampleSourcesPath, (name) => name.endsWith(".ts"))
291+
);
267292

268293
const sampleConfiguration = getSampleConfiguration(projectInfo.packageJson);
269294

@@ -398,7 +423,8 @@ function createReadme(outputKind: OutputKind, info: SampleGenerationInfo): strin
398423
},
399424
publicationDirectory: PUBLIC_SAMPLES_BASE + "/" + info.topLevelDirectory,
400425
useTypeScript: outputKind === OutputKind.TypeScript,
401-
...info
426+
...info,
427+
moduleInfos: info.moduleInfos.filter((mod) => mod.summary !== undefined)
402428
});
403429
}
404430

@@ -440,7 +466,18 @@ async function makeSamplesFactory(
440466
*/
441467
function postProcess(moduleText: string | Buffer): string {
442468
const content = Buffer.isBuffer(moduleText) ? moduleText.toString("utf8") : moduleText;
443-
return content.replace(new RegExp(`^\\s*\\*\\s*@${AZSDK_META_TAG_PREFIX}.*\n`, "gm"), "");
469+
return (
470+
content
471+
.replace(new RegExp(`^\\s*\\*\\s*@${AZSDK_META_TAG_PREFIX}.*\n`, "gm"), "")
472+
// We also need to clean up extra blank lines that might be left behind by
473+
// removing azsdk tags. These regular expressions are extremely frustrating
474+
// because they deal almost exclusively in the literal "/" and "*" characters.
475+
.replace(/(\s+\*)+\//s, EOL + " */")
476+
// Clean up blank lines at the beginning
477+
.replace(/\/\*\*(\s+\*)*/s, `/**${EOL} *`)
478+
// Finally remove empty doc comments.
479+
.replace(/\s*\/\*\*(\s+\*)*\/\s*/s, EOL + EOL)
480+
);
444481
}
445482

446483
// We use a tempdir at the outer layer to avoid creating dirty trees
@@ -455,8 +492,8 @@ async function makeSamplesFactory(
455492
// We copy the samples sources in to the `src` folder on the typescript side
456493
dir(
457494
"src",
458-
info.moduleInfos.map(({ filePath }) =>
459-
file(path.basename(filePath), () => postProcess(fs.readFileSync(filePath)))
495+
info.moduleInfos.map(({ relativeSourcePath, filePath }) =>
496+
file(relativeSourcePath, () => postProcess(fs.readFileSync(filePath)))
460497
)
461498
)
462499
]),
@@ -465,8 +502,8 @@ async function makeSamplesFactory(
465502
file("package.json", () => jsonify(createPackageJson(info, OutputKind.JavaScript))),
466503
copy("sample.env", path.join(projectInfo.path, "sample.env")),
467504
// Extract the JS Module Text from the module info structures
468-
...info.moduleInfos.map(({ filePath, jsModuleText }) =>
469-
file(path.basename(filePath).replace(/\.ts$/, ".js"), () => postProcess(jsModuleText))
505+
...info.moduleInfos.map(({ relativeSourcePath, jsModuleText }) =>
506+
file(relativeSourcePath.replace(/\.ts$/, ".js"), () => postProcess(jsModuleText))
470507
)
471508
]),
472509
// Copy extraFiles by reducing all configured destinations for each input file

common/tools/dev-tool/src/config/rollup.base.config.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,25 +39,22 @@ export function openTelemetryCommonJs(): Record<string, string[]> {
3939
];
4040
}
4141

42-
const releasedOpenTelemetryVersions = [
43-
"0.10.2",
44-
"1.0.0-rc.0"
45-
];
42+
const releasedOpenTelemetryVersions = ["0.10.2", "1.0.0-rc.0"];
4643

4744
for (const version of releasedOpenTelemetryVersions) {
4845
namedExports[
4946
// working around a limitation in the rollup common.js plugin - it's not able to resolve these modules so the named exports listed above will not get applied. We have to drill down to the actual path.
5047
`../../../common/temp/node_modules/.pnpm/@opentelemetry/api@${version}/node_modules/@opentelemetry/api/build/src/index.js`
5148
] = [
52-
"SpanKind",
53-
"TraceFlags",
54-
"getSpan",
55-
"setSpan",
56-
"StatusCode",
57-
"CanonicalCode",
58-
"getSpanContext",
59-
"setSpanContext"
60-
];
49+
"SpanKind",
50+
"TraceFlags",
51+
"getSpan",
52+
"setSpan",
53+
"StatusCode",
54+
"CanonicalCode",
55+
"getSpanContext",
56+
"setSpanContext"
57+
];
6158
}
6259

6360
return namedExports;

common/tools/dev-tool/src/templates/sampleReadme.md.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ function fence(language: string, ...contents: string[]): string {
3737
* Ex. recognizePii.ts -> recognizepii
3838
*/
3939
function sampleLinkTag(filePath: string): string {
40-
return path
41-
.basename(filePath)
40+
return filePath
41+
.split(path.sep)
42+
.join("_")
4243
.replace(/\.[a-z]*$/, "")
43-
.replace(/\//, "_")
4444
.toLowerCase();
4545
}
4646

@@ -55,12 +55,12 @@ function fileLinks(info: SampleReadmeConfiguration) {
5555
].join("/");
5656

5757
return filterModules(info)
58-
.map(({ filePath, relativeSourcePath }) => {
58+
.map(({ relativeSourcePath }) => {
5959
const sourcePath = info.useTypeScript
6060
? relativeSourcePath
6161
: relativeSourcePath.replace(/\.ts$/, ".js");
6262
return `[${sampleLinkTag(
63-
filePath
63+
relativeSourcePath
6464
)}]: https://github.com/Azure/azure-sdk-for-js/blob/master/${packageSamplesPathFragment}/${sourcePath}`;
6565
})
6666
.join("\n");
@@ -115,11 +115,11 @@ function filterModules(info: SampleReadmeConfiguration): SampleReadmeConfigurati
115115
* Renders the sample file table.
116116
*/
117117
function table(info: SampleReadmeConfiguration) {
118-
const contents = filterModules(info).map(({ filePath, summary, relativeSourcePath }) => {
118+
const contents = filterModules(info).map(({ summary, relativeSourcePath }) => {
119119
const fileName = info.useTypeScript
120120
? relativeSourcePath
121121
: relativeSourcePath.replace(/\.ts$/, ".js");
122-
return `| [${fileName}][${sampleLinkTag(filePath)}] | ${summary} |`;
122+
return `| [${fileName}][${sampleLinkTag(relativeSourcePath)}] | ${summary} |`;
123123
});
124124

125125
return [

common/tools/dev-tool/src/util/fileTree.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,5 +104,9 @@ export function file(name: string, contents: FileContents): FileTreeFactory {
104104
: Buffer.from(immediateContents, "utf8");
105105
};
106106

107-
return async (basePath) => fs.writeFile(path.join(basePath, name), getContentsAsBuffer());
107+
return async (basePath) => {
108+
const dirName = path.resolve(basePath, path.dirname(name));
109+
await fs.ensureDir(dirName);
110+
return fs.writeFile(path.join(basePath, name), getContentsAsBuffer());
111+
};
108112
}

common/tools/dev-tool/src/util/sampleGenerationInfo.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export interface ModuleInfo {
121121
/**
122122
* The description provided by the first doc comment.
123123
*/
124-
summary: string;
124+
summary?: string;
125125
/**
126126
* A list of module specifiers that are imported by this
127127
* source file.
@@ -186,6 +186,10 @@ export interface AzSdkMetaTags {
186186
* Causes the sample file to be ignored entirely (will skip publication).
187187
*/
188188
ignore?: boolean;
189+
/**
190+
* Causes the file to be omitted from the generated sample index (README).
191+
*/
192+
util?: boolean;
189193
/**
190194
* Causes the sample file to skip JavaScript output.
191195
*/
@@ -206,6 +210,7 @@ export const AZSDK_META_TAG_PREFIX = "azsdk-";
206210
export const VALID_AZSDK_META_TAGS: Array<keyof AzSdkMetaTags> = [
207211
"weight",
208212
"ignore",
213+
"util",
209214
"skip-javascript"
210215
];
211216

0 commit comments

Comments
 (0)