Skip to content

Commit 37eb338

Browse files
committed
fix(scripts): prevent incorrect run coditions
1 parent ea6c184 commit 37eb338

File tree

7 files changed

+376
-308
lines changed

7 files changed

+376
-308
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"scripts": {
1414
"build:eslint": "yarn workspace eslint-plugin-automation-custom build && yarn install",
1515
"clean": "rm -rf **/dist **/build **/node_modules **/.gradle **/vendor || true",
16-
"cli": "yarn workspace scripts ts-node --transpile-only ./index.ts",
16+
"cli": "yarn workspace scripts ts-node --transpile-only ./cli/index.ts",
1717
"docker": "docker exec -it dev yarn cli $*",
1818
"docker:build": "./scripts/docker/build.sh",
1919
"docker:clean": "docker stop dev; docker rm -f dev; docker image rm -f api-clients-automation",

scripts/buildClients.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,11 @@ async function buildAllClients(
6161

6262
export async function buildClients(
6363
generators: Generator[],
64-
verbose: boolean
64+
{ verbose, skipUtils }: { verbose: boolean; skipUtils: boolean }
6565
): Promise<void> {
6666
const langs = [...new Set(generators.map((gen) => gen.language))];
6767

68-
if (!CI && langs.includes('javascript')) {
68+
if (!CI && !skipUtils && langs.includes('javascript')) {
6969
const spinner = createSpinner(
7070
"building 'JavaScript' utils",
7171
verbose

scripts/cli/index.ts

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
import { Argument, program } from 'commander';
2+
3+
import { buildClients } from '../buildClients';
4+
import { buildSpecs } from '../buildSpecs';
5+
import { CI, DOCKER, LANGUAGES } from '../common';
6+
import { ctsGenerateMany } from '../cts/generate';
7+
import { runCts } from '../cts/runCts';
8+
import { formatter } from '../formatter';
9+
import { generate } from '../generate';
10+
import { playground } from '../playground';
11+
12+
import type { Job, LangArg } from './utils';
13+
import {
14+
PROMPT_ALL,
15+
getClientChoices,
16+
generatorList,
17+
prompt,
18+
PROMPT_CLIENTS,
19+
PROMPT_LANGUAGES,
20+
} from './utils';
21+
22+
if (!CI && !DOCKER) {
23+
// eslint-disable-next-line no-console
24+
console.log('You should run scripts via the docker container, see README.md');
25+
// eslint-disable-next-line no-process-exit
26+
process.exit(1);
27+
}
28+
29+
const args = {
30+
language: new Argument('[language]', 'The language').choices(
31+
PROMPT_LANGUAGES
32+
),
33+
clients: (job: Job): Argument =>
34+
new Argument('[client...]', 'The client').choices(getClientChoices(job)),
35+
client: new Argument('[client]', 'The client').choices(PROMPT_CLIENTS),
36+
};
37+
38+
const flags = {
39+
verbose: {
40+
flag: '-v, --verbose',
41+
description: 'make the generation verbose',
42+
},
43+
interactive: {
44+
flag: '-i, --interactive',
45+
description: 'open prompt to query parameters',
46+
},
47+
skipCache: {
48+
flag: '-s, --skip-cache',
49+
description: 'skip cache checking to force building specs',
50+
},
51+
skipUtils: {
52+
flag: '-su, --skip-utils',
53+
description: 'skip utils build when building a JavaScript client',
54+
},
55+
outputType: {
56+
flag: '-json, --output-json',
57+
description: 'outputs the spec in JSON instead of yml',
58+
},
59+
};
60+
61+
program.name('cli');
62+
63+
program
64+
.command('generate')
65+
.description('Generate a specified client')
66+
.addArgument(args.language)
67+
.addArgument(args.clients('generate'))
68+
.option(flags.verbose.flag, flags.verbose.description)
69+
.option(flags.interactive.flag, flags.interactive.description)
70+
.action(
71+
async (langArg: LangArg, clientArg: string[], { verbose, interactive }) => {
72+
const { language, client, clientList } = await prompt({
73+
langArg,
74+
clientArg,
75+
job: 'generate',
76+
interactive,
77+
});
78+
79+
await generate(
80+
generatorList({ language, client, clientList }),
81+
Boolean(verbose)
82+
);
83+
}
84+
);
85+
86+
const buildCommand = program.command('build');
87+
88+
buildCommand
89+
.command('clients')
90+
.description('Build a specified client')
91+
.addArgument(args.language)
92+
.addArgument(args.clients('build'))
93+
.option(flags.verbose.flag, flags.verbose.description)
94+
.option(flags.interactive.flag, flags.interactive.description)
95+
.option(flags.skipUtils.flag, flags.skipUtils.description)
96+
.action(
97+
async (
98+
langArg: LangArg,
99+
clientArg: string[],
100+
{ verbose, interactive, skipUtils }
101+
) => {
102+
const { language, client, clientList } = await prompt({
103+
langArg,
104+
clientArg,
105+
job: 'build',
106+
interactive,
107+
});
108+
109+
await buildClients(generatorList({ language, client, clientList }), {
110+
verbose: Boolean(verbose),
111+
skipUtils: Boolean(skipUtils),
112+
});
113+
}
114+
);
115+
116+
buildCommand
117+
.command('specs')
118+
.description('Build a specified spec')
119+
.addArgument(args.clients('specs'))
120+
.option(flags.verbose.flag, flags.verbose.description)
121+
.option(flags.interactive.flag, flags.interactive.description)
122+
.option(flags.skipCache.flag, flags.skipCache.description)
123+
.option(flags.outputType.flag, flags.outputType.description)
124+
.action(
125+
async (
126+
clientArg: string[],
127+
{ verbose, interactive, skipCache, outputJson }
128+
) => {
129+
const { client, clientList } = await prompt({
130+
langArg: 'all',
131+
clientArg,
132+
job: 'specs',
133+
interactive,
134+
});
135+
136+
const outputFormat = outputJson ? 'json' : 'yml';
137+
138+
// ignore cache when building from cli
139+
await buildSpecs(
140+
client[0] === PROMPT_ALL ? clientList : client,
141+
outputFormat,
142+
Boolean(verbose),
143+
!skipCache
144+
);
145+
}
146+
);
147+
148+
const ctsCommand = program.command('cts');
149+
150+
ctsCommand
151+
.command('generate')
152+
.description('Generate the CTS tests')
153+
.addArgument(args.language)
154+
.addArgument(args.clients('generate'))
155+
.option(flags.verbose.flag, flags.verbose.description)
156+
.option(flags.interactive.flag, flags.interactive.description)
157+
.action(
158+
async (langArg: LangArg, clientArg: string[], { verbose, interactive }) => {
159+
const { language, client, clientList } = await prompt({
160+
langArg,
161+
clientArg,
162+
job: 'generate',
163+
interactive,
164+
});
165+
166+
await ctsGenerateMany(
167+
generatorList({ language, client, clientList }),
168+
Boolean(verbose)
169+
);
170+
}
171+
);
172+
173+
ctsCommand
174+
.command('run')
175+
.description('Run the tests for the CTS')
176+
.addArgument(args.language)
177+
.option(flags.verbose.flag, flags.verbose.description)
178+
.option(flags.interactive.flag, flags.interactive.description)
179+
.action(async (langArg: LangArg, { verbose, interactive }) => {
180+
const { language } = await prompt({
181+
langArg,
182+
clientArg: [PROMPT_ALL],
183+
job: 'generate',
184+
interactive,
185+
});
186+
187+
await runCts(
188+
language === PROMPT_ALL ? LANGUAGES : [language],
189+
Boolean(verbose)
190+
);
191+
});
192+
193+
program
194+
.command('playground')
195+
.description('Run the playground')
196+
.addArgument(args.language)
197+
.addArgument(args.client)
198+
.option(flags.interactive.flag, flags.interactive.description)
199+
.action(async (langArg: LangArg, cliClient: string, { interactive }) => {
200+
const { language, client } = await prompt({
201+
langArg,
202+
clientArg: [cliClient],
203+
job: 'build',
204+
interactive,
205+
});
206+
207+
await playground({
208+
language,
209+
client: client[0],
210+
});
211+
});
212+
213+
program
214+
.command('format')
215+
.description('Format the specified folder for a specific language')
216+
.addArgument(args.language)
217+
.argument('folder', 'The folder to format')
218+
.option(flags.verbose.flag, flags.verbose.description)
219+
.action(async (language: string, folder: string, { verbose }) => {
220+
await formatter(language, folder, verbose);
221+
});
222+
223+
program.parse();

scripts/cli/utils.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import inquirer from 'inquirer';
2+
3+
import { CLIENTS, GENERATORS, LANGUAGES } from '../common';
4+
import type { Generator, Language } from '../types';
5+
6+
export const PROMPT_ALL = 'all';
7+
export const PROMPT_LANGUAGES = [PROMPT_ALL, ...LANGUAGES];
8+
export const PROMPT_CLIENTS = [PROMPT_ALL, ...CLIENTS];
9+
10+
export type LangArg = Language | 'all' | undefined;
11+
12+
export type PromptDecision = {
13+
language: Language | 'all';
14+
client: string[];
15+
clientList: string[];
16+
};
17+
18+
export type Job = 'build' | 'generate' | 'specs';
19+
20+
type Prompt = {
21+
langArg: LangArg;
22+
clientArg: string[];
23+
job: Job;
24+
interactive: boolean;
25+
};
26+
27+
export function getClientChoices(
28+
job: Job,
29+
language?: Language | 'all'
30+
): string[] {
31+
const withoutAlgoliaSearch = PROMPT_CLIENTS.filter(
32+
(client) => client !== 'algoliasearch'
33+
);
34+
35+
if (!language) {
36+
return job === 'specs' ? withoutAlgoliaSearch : PROMPT_CLIENTS;
37+
}
38+
39+
const isJavaScript = language === PROMPT_ALL || language === 'javascript';
40+
41+
switch (job) {
42+
// We don't need to build `lite` client as it's a subset of the `algoliasearch` one
43+
case 'build':
44+
// Only `JavaScript` provide a lite client, others can build anything but it.
45+
if (isJavaScript) {
46+
return PROMPT_CLIENTS.filter((client) => client !== 'lite');
47+
}
48+
49+
return withoutAlgoliaSearch.filter((client) => client !== 'lite');
50+
// `algoliasearch` is not built from specs, it's an aggregation of clients
51+
case 'specs':
52+
return withoutAlgoliaSearch;
53+
case 'generate':
54+
// Only `JavaScript` provide a lite client, others can build anything but it.
55+
if (isJavaScript) {
56+
return withoutAlgoliaSearch;
57+
}
58+
59+
return withoutAlgoliaSearch.filter((client) => client !== 'lite');
60+
default:
61+
return PROMPT_CLIENTS;
62+
}
63+
}
64+
65+
export function generatorList({
66+
language,
67+
client,
68+
clientList,
69+
}: {
70+
language: Language | 'all';
71+
client: string[];
72+
clientList: string[];
73+
}): Generator[] {
74+
const langsTodo = language === PROMPT_ALL ? LANGUAGES : [language];
75+
const clientsTodo = client[0] === PROMPT_ALL ? clientList : client;
76+
77+
return langsTodo
78+
.flatMap((lang) => clientsTodo.map((cli) => GENERATORS[`${lang}-${cli}`]))
79+
.filter(Boolean);
80+
}
81+
82+
export async function prompt({
83+
langArg,
84+
clientArg,
85+
job,
86+
interactive,
87+
}: Prompt): Promise<PromptDecision> {
88+
const decision: PromptDecision = {
89+
client: [PROMPT_ALL],
90+
language: PROMPT_ALL,
91+
clientList: [],
92+
};
93+
94+
if (!langArg) {
95+
if (interactive) {
96+
const { language } = await inquirer.prompt<PromptDecision>([
97+
{
98+
type: 'list',
99+
name: 'language',
100+
message: 'Select a language',
101+
default: PROMPT_ALL,
102+
choices: LANGUAGES,
103+
},
104+
]);
105+
106+
decision.language = language;
107+
}
108+
} else {
109+
decision.language = langArg;
110+
}
111+
112+
decision.clientList = getClientChoices(job, decision.language);
113+
114+
if (!clientArg || !clientArg.length) {
115+
if (interactive) {
116+
const { client } = await inquirer.prompt<{ client: string }>([
117+
{
118+
type: 'list',
119+
name: 'client',
120+
message: 'Select a client',
121+
default: PROMPT_ALL,
122+
choices: decision.clientList,
123+
},
124+
]);
125+
126+
decision.client = [client];
127+
}
128+
} else {
129+
clientArg.forEach((client) => {
130+
if (!decision.clientList.includes(client)) {
131+
throw new Error(
132+
`The '${clientArg}' client can't run with the given job: '${job}'.\n\nAllowed choices are: ${decision.clientList.join(
133+
', '
134+
)}`
135+
);
136+
}
137+
});
138+
139+
decision.client = clientArg;
140+
}
141+
142+
return decision;
143+
}

0 commit comments

Comments
 (0)