Skip to content

Commit b46259a

Browse files
committed
chore: doc specs location and scripts cleanup
1 parent 15696dd commit b46259a

File tree

4 files changed

+56
-93
lines changed

4 files changed

+56
-93
lines changed

scripts/specs/format.ts

Lines changed: 17 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ import { HarRequest, HTTPSnippet } from 'httpsnippet';
55
import yaml from 'js-yaml';
66

77
import { Cache } from '../cache.js';
8-
import { exists, GENERATORS, run, toAbsolutePath } from '../common.js';
8+
import { GENERATORS, run, toAbsolutePath } from '../common.js';
99
import { createSpinner } from '../spinners.js';
1010
import type { Spec } from '../types.js';
1111

12-
import { getCodeSampleLabel, transformCodeSamplesToGuideMethods, transformSnippetsToCodeSamples } from './snippets.js';
13-
import type { SnippetSamples } from './types.js';
12+
import { bundleCodeSamplesForDoc, getCodeSampleLabel, transformGeneratedSnippetsToCodeSamples } from './snippets.js';
1413

1514
export async function lintCommon(useCache: boolean): Promise<void> {
1615
const spinner = createSpinner('linting common spec');
@@ -37,48 +36,26 @@ export async function lintCommon(useCache: boolean): Promise<void> {
3736
spinner.succeed();
3837
}
3938

40-
/*
41-
* This function will transform properties in the bundle depending on the context.
42-
* E.g:
43-
* - Check tags definition
44-
* - Add name of the client in tags
45-
* - Remove unecessary punctuation for documentation
46-
* - etc...
47-
*/
48-
export async function transformBundle({
49-
bundledPath,
50-
docs,
51-
clientName,
52-
alias,
53-
}: {
54-
bundledPath: string;
55-
docs: boolean;
56-
clientName: string;
57-
alias?: string;
58-
}): Promise<void> {
59-
if (!(await exists(bundledPath))) {
60-
throw new Error(`Bundled file not found ${bundledPath}.`);
61-
}
39+
export async function bundleSpecsForClient(bundledPath: string, clientName: string): Promise<void> {
40+
const bundledSpec = yaml.load(await fsp.readFile(bundledPath, 'utf8')) as Spec;
41+
42+
Object.values(bundledSpec.paths).forEach((pathMethods) => {
43+
Object.values(pathMethods).forEach((specMethod) => (specMethod.tags = [clientName]));
44+
});
6245

46+
await fsp.writeFile(bundledPath, yaml.dump(bundledSpec, { noRefs: true }));
47+
}
48+
49+
export async function bundleSpecsForDoc(bundledPath: string, clientName: string): Promise<void> {
6350
const bundledSpec = yaml.load(await fsp.readFile(bundledPath, 'utf8')) as Spec;
6451
const harRequests = await oas2har.oas2har(bundledSpec as any, { includeVendorExamples: true });
6552
const tagsDefinitions = bundledSpec.tags;
66-
const snippetSamples = docs ? await transformSnippetsToCodeSamples(clientName) : ({} as SnippetSamples);
53+
const codeSamples = await transformGeneratedSnippetsToCodeSamples(clientName);
6754

68-
if (docs) {
69-
const snippets = transformCodeSamplesToGuideMethods(JSON.parse(JSON.stringify(snippetSamples)));
70-
await fsp.writeFile(toAbsolutePath(`docs/bundled/${clientName}-snippets.json`), snippets);
71-
}
55+
await bundleCodeSamplesForDoc(JSON.parse(JSON.stringify(codeSamples)), clientName);
7256

7357
for (const [pathKey, pathMethods] of Object.entries(bundledSpec.paths)) {
7458
for (const [method, specMethod] of Object.entries(pathMethods)) {
75-
if (!docs) {
76-
// In the main bundle we need to have only the clientName
77-
// because open-api-generator will use this to determine the name of the client
78-
specMethod.tags = [clientName];
79-
continue;
80-
}
81-
8259
if (specMethod['x-helper']) {
8360
delete bundledSpec.paths[pathKey];
8461
break;
@@ -94,11 +71,11 @@ export async function transformBundle({
9471
specMethod['x-codeSamples'] = [];
9572
}
9673

97-
if (snippetSamples[gen.language][specMethod.operationId]) {
74+
if (codeSamples[gen.language][specMethod.operationId]) {
9875
specMethod['x-codeSamples'].push({
9976
lang: gen.language,
10077
label: getCodeSampleLabel(gen.language),
101-
source: Object.values(snippetSamples[gen.language][specMethod.operationId])[0],
78+
source: Object.values(codeSamples[gen.language][specMethod.operationId])[0],
10279
});
10380
}
10481
}
@@ -142,12 +119,6 @@ export async function transformBundle({
142119
);
143120
}
144121

145-
if (alias && tag === alias) {
146-
throw new Error(
147-
`Tag name "${tag} for operation ${specMethod.operationId} must be different from alias ${alias}`,
148-
);
149-
}
150-
151122
const tagExists = tagsDefinitions ? tagsDefinitions.find((t) => t.name === tag) : null;
152123
if (!tagExists) {
153124
throw new Error(
@@ -158,8 +129,5 @@ export async function transformBundle({
158129
}
159130
}
160131

161-
await fsp.writeFile(
162-
docs ? toAbsolutePath(`specs/bundled/${clientName}.doc.yml`) : bundledPath,
163-
yaml.dump(bundledSpec, { noRefs: true }),
164-
);
132+
await fsp.writeFile(toAbsolutePath(`specs/bundled/${clientName}.doc.yml`), yaml.dump(bundledSpec, { noRefs: true }));
165133
}

scripts/specs/index.ts

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import fsp from 'fs/promises';
33
import yaml from 'js-yaml';
44

55
import { Cache } from '../cache.js';
6-
import { run, toAbsolutePath } from '../common.js';
6+
import { exists, run, toAbsolutePath } from '../common.js';
77
import { createSpinner } from '../spinners.js';
88
import type { Spec } from '../types.js';
99

10-
import { lintCommon, transformBundle } from './format.js';
10+
import { bundleSpecsForClient, bundleSpecsForDoc, lintCommon } from './format.js';
1111
import type { BaseBuildSpecsOptions } from './types.js';
1212

1313
const ALGOLIASEARCH_LITE_OPERATIONS = ['search', 'customPost', 'getRecommendations'];
@@ -53,12 +53,7 @@ async function buildLiteSpec({
5353
// remove unused components for the outputted light spec
5454
await run(`yarn openapi bundle ${bundledPath} -o ${bundledPath} --ext yml --remove-unused-components`);
5555

56-
await transformBundle({
57-
bundledPath,
58-
clientName: spec,
59-
// Lite does not need documentation because it's just a subset
60-
docs: false,
61-
});
56+
await bundleSpecsForClient(bundledPath, spec);
6257
}
6358

6459
/**
@@ -70,15 +65,15 @@ async function buildSpec({
7065
docs,
7166
useCache,
7267
}: BaseBuildSpecsOptions & { spec: string }): Promise<void> {
73-
const isAlgoliasearch = spec === 'algoliasearch';
68+
const isLiteSpec = spec === 'algoliasearch';
7469

75-
if (docs && isAlgoliasearch) {
70+
if (docs && isLiteSpec) {
7671
return;
7772
}
7873

7974
// In case of lite we use a the `search` spec as a base because only its bundled form exists.
80-
const specBase = isAlgoliasearch ? 'search' : spec;
81-
const deps = isAlgoliasearch ? ['search', 'recommend'] : [spec];
75+
const specBase = isLiteSpec ? 'search' : spec;
76+
const deps = isLiteSpec ? ['search', 'recommend'] : [spec];
8277
const logSuffix = docs ? 'doc spec' : 'spec';
8378
const cache = new Cache({
8479
folder: toAbsolutePath('specs/'),
@@ -105,16 +100,16 @@ async function buildSpec({
105100
await run(`yarn specs:fix ${specBase}`);
106101

107102
// Then bundle the file
108-
const bundledPath = `specs/bundled/${spec}.${docs ? 'doc.' : ''}${outputFormat}`;
103+
const bundledPath = toAbsolutePath(`specs/bundled/${spec}.${docs ? 'doc.' : ''}${outputFormat}`);
109104
await run(`yarn openapi bundle specs/${specBase}/spec.yml -o ${bundledPath} --ext ${outputFormat}`);
110105

106+
if (!(await exists(bundledPath))) {
107+
throw new Error(`Bundled file not found ${bundledPath}.`);
108+
}
109+
111110
// Add the correct tags to be able to generate the proper client
112-
if (!isAlgoliasearch) {
113-
await transformBundle({
114-
bundledPath: toAbsolutePath(bundledPath),
115-
clientName: spec,
116-
docs,
117-
});
111+
if (!isLiteSpec) {
112+
docs ? await bundleSpecsForDoc(bundledPath, spec) : await bundleSpecsForClient(bundledPath, spec);
118113
} else {
119114
await buildLiteSpec({
120115
spec,

scripts/specs/snippets.ts

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import fsp from 'fs/promises';
33
import { GENERATORS, capitalize, createClientName, exists, toAbsolutePath } from '../common.js';
44
import type { Language } from '../types.js';
55

6-
import type { CodeSamples, SnippetForMethod, SnippetSamples } from './types.js';
6+
import type { CodeSamples, OpenAPICodeSample, SampleForOperation } from './types.js';
77

8-
export function getCodeSampleLabel(language: Language): CodeSamples['label'] {
8+
export function getCodeSampleLabel(language: Language): OpenAPICodeSample['label'] {
99
switch (language) {
1010
case 'csharp':
1111
return 'C#';
@@ -14,14 +14,14 @@ export function getCodeSampleLabel(language: Language): CodeSamples['label'] {
1414
case 'php':
1515
return 'PHP';
1616
default:
17-
return capitalize(language) as CodeSamples['label'];
17+
return capitalize(language) as OpenAPICodeSample['label'];
1818
}
1919
}
2020

21-
// Iterates over the snippet samples and sanitize the data to only keep the method part in order to use it in the guides.
22-
export function transformCodeSamplesToGuideMethods(snippetSamples: SnippetSamples): string {
23-
for (const [language, operationWithSample] of Object.entries(snippetSamples)) {
24-
for (const [operation, samples] of Object.entries(operationWithSample)) {
21+
// Iterates over the result of `transformSnippetsToCodeSamples` in order to generate a JSON file for the doc to consume.
22+
export async function bundleCodeSamplesForDoc(codeSamples: CodeSamples, clientName: string): Promise<void> {
23+
for (const [language, operationWithSamples] of Object.entries(codeSamples)) {
24+
for (const [operation, samples] of Object.entries(operationWithSamples)) {
2525
if (operation === 'import') {
2626
continue;
2727
}
@@ -37,25 +37,25 @@ export function transformCodeSamplesToGuideMethods(snippetSamples: SnippetSample
3737
const initLine = sampleMatch[1];
3838
const callLine = sampleMatch[3];
3939

40-
if (!('init' in snippetSamples[language])) {
41-
snippetSamples[language].init = {
40+
if (!('init' in codeSamples[language])) {
41+
codeSamples[language].init = {
4242
default: initLine.trim(),
4343
};
4444
}
4545

46-
snippetSamples[language][operation][sampleName] = callLine.trim();
46+
codeSamples[language][operation][sampleName] = callLine.trim();
4747
}
4848
}
4949
}
5050

51-
return JSON.stringify(snippetSamples, null, 2);
51+
await fsp.writeFile(toAbsolutePath(`docs/bundled/${clientName}-snippets.json`), JSON.stringify(codeSamples, null, 2));
5252
}
5353

54-
// For a given `clientName`, reads the matching snippet file for every available clients and builds an hashmap of snippets per operationId per language.
55-
export async function transformSnippetsToCodeSamples(clientName: string): Promise<SnippetSamples> {
56-
const snippetSamples = Object.values(GENERATORS).reduce(
54+
// Reads the generated `docs/snippets/` file for every languages of the given `clientName` and builds an hashmap of snippets per operationId per language.
55+
export async function transformGeneratedSnippetsToCodeSamples(clientName: string): Promise<CodeSamples> {
56+
const codeSamples = Object.values(GENERATORS).reduce<CodeSamples>(
5757
(prev, curr) => ({ ...prev, [curr.language]: {} }),
58-
{} as SnippetSamples,
58+
{} as CodeSamples,
5959
);
6060

6161
for (const gen of Object.values(GENERATORS)) {
@@ -76,7 +76,7 @@ export async function transformSnippetsToCodeSamples(clientName: string): Promis
7676

7777
const importMatch = snippetFileContent.match(/>IMPORT\n([\s\S]*?)\n.*IMPORT</);
7878
if (importMatch) {
79-
snippetSamples[gen.language].import = {
79+
codeSamples[gen.language].import = {
8080
default: importMatch[1].trim(),
8181
};
8282
}
@@ -91,24 +91,24 @@ export async function transformSnippetsToCodeSamples(clientName: string): Promis
9191
const operationId = match[1];
9292
const testName = match[2] || 'default';
9393

94-
if (!snippetSamples[gen.language][operationId]) {
95-
snippetSamples[gen.language][operationId] = {};
94+
if (!codeSamples[gen.language][operationId]) {
95+
codeSamples[gen.language][operationId] = {};
9696
}
9797

98-
const snippetForMethod: SnippetForMethod = snippetSamples[gen.language][operationId];
98+
const sampleForOperation: SampleForOperation = codeSamples[gen.language][operationId];
9999

100-
snippetForMethod[testName] = '';
100+
sampleForOperation[testName] = '';
101101

102102
const indent = lines[0].length - lines[0].trim().length;
103103
// skip first and last lines because they contain the SEPARATOR or operationId
104104
lines.forEach((line) => {
105105
// best effort to determine how far the snippet is indented so we
106106
// can have every snippets in the documentation on the far left
107107
// without impacting the formatting
108-
snippetForMethod[testName] += `${line.slice(indent).replaceAll(/\t/g, ' ')}\n`;
108+
sampleForOperation[testName] += `${line.slice(indent).replaceAll(/\t/g, ' ')}\n`;
109109
});
110110
}
111111
}
112112

113-
return snippetSamples;
113+
return codeSamples;
114114
}

scripts/specs/types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ export type BaseBuildSpecsOptions = {
66
useCache: boolean;
77
};
88

9-
export type SnippetForMethod = Record<string, string>;
10-
export type SnippetSamples = Record<Language, Record<string, SnippetForMethod>>;
9+
export type SampleForOperation = Record<string, string>;
10+
export type CodeSamples = Record<Language, Record<string, SampleForOperation>>;
1111

12-
export type CodeSamples = {
12+
export type OpenAPICodeSample = {
1313
lang:
1414
| 'c'
1515
| 'c++'

0 commit comments

Comments
 (0)