Skip to content

Commit 0662eaa

Browse files
authored
fix(scripts): prevent incorrect run conditions (#731)
1 parent 9ed5eee commit 0662eaa

File tree

8 files changed

+373
-310
lines changed

8 files changed

+373
-310
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: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
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+
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] === 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: [ALL],
183+
job: 'generate',
184+
interactive,
185+
});
186+
187+
await runCts(language === ALL ? LANGUAGES : [language], Boolean(verbose));
188+
});
189+
190+
program
191+
.command('playground')
192+
.description('Run the playground')
193+
.addArgument(args.language)
194+
.addArgument(args.client)
195+
.option(flags.interactive.flag, flags.interactive.description)
196+
.action(async (langArg: LangArg, cliClient: string, { interactive }) => {
197+
const { language, client } = await prompt({
198+
langArg,
199+
clientArg: [cliClient],
200+
job: 'build',
201+
interactive,
202+
});
203+
204+
await playground({
205+
language,
206+
client: client[0],
207+
});
208+
});
209+
210+
program
211+
.command('format')
212+
.description('Format the specified folder for a specific language')
213+
.addArgument(args.language)
214+
.argument('folder', 'The folder to format')
215+
.option(flags.verbose.flag, flags.verbose.description)
216+
.action(async (language: string, folder: string, { verbose }) => {
217+
await formatter(language, folder, verbose);
218+
});
219+
220+
program.parse();

scripts/cli/utils.ts

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

0 commit comments

Comments
 (0)