Skip to content

Commit d8ac445

Browse files
committed
chore: help should be done
1 parent c03ff70 commit d8ac445

15 files changed

Lines changed: 514 additions & 84 deletions

File tree

.editorconfig

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
root = true
22

33
[*]
4-
indent_style = tab
4+
indent_style = space
55
indent_size = 4
66
charset = utf-8
77
trim_trailing_whitespace = true
@@ -10,6 +10,14 @@ end_of_line = lf
1010
# editorconfig-tools is unable to ignore longs strings or urls
1111
max_line_length = null
1212

13+
[*.ts]
14+
indent_style = tab
15+
indent_size = 4
16+
17+
[*.js]
18+
indent_style = tab
19+
indent_size = 4
20+
1321
[*.md]
1422
indent_size = 2
1523

src/commands/actor/get-input.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { outputInputFromDefaultStore } from '../../lib/actor.js';
22
import { ApifyCommand } from '../../lib/command-framework/apify-command.js';
33

44
export class ActorGetInputCommand extends ApifyCommand<typeof ActorGetInputCommand> {
5+
static override name = 'get-input';
6+
57
static override description =
68
'Gets the Actor input value from the default key-value store associated with the Actor run.';
79

src/entrypoints/_shared.ts

Lines changed: 181 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import process from 'node:process';
22

3+
import chalk from 'chalk';
34
import yargonaut from 'yargonaut';
45
import yargs from 'yargs/yargs';
56

7+
import { camelCaseToKebabCase, commandRegistry, kebabCaseString } from '../lib/command-framework/apify-command.js';
8+
import type { FlagTag, TaggedFlagBuilder } from '../lib/command-framework/flags.js';
9+
import { renderMainHelpMenu, selectiveRenderHelpForCommand } from '../lib/command-framework/help.js';
610
import { readStdin } from '../lib/commands/read-stdin.js';
7-
import { version } from '../lib/consts.js';
11+
import { cliVersion } from '../lib/consts.js';
12+
import { error } from '../lib/outputs.js';
813

914
yargonaut //
1015
.style('blue')
@@ -13,10 +18,8 @@ yargonaut //
1318
.errorsStyle('red');
1419

1520
export const cli = yargs()
16-
.scriptName('apify')
1721
.version(false)
1822
.help(false)
19-
// .wrap(Math.max(80, process.stdout.columns || 80))
2023
.parserConfiguration({
2124
// Disables the automatic conversion of `--foo-bar` to `fooBar` (we handle it manually)
2225
'camel-case-expansion': false,
@@ -25,14 +28,186 @@ export const cli = yargs()
2528
// We parse numbers manually
2629
'parse-numbers': false,
2730
'parse-positional-numbers': false,
31+
'short-option-groups': false,
32+
})
33+
.strict()
34+
.locale('en')
35+
.updateStrings({
36+
// Keys come from https://github.com/yargs/yargs/blob/main/locales/en.json
37+
'Not enough arguments following: %s': 'MISSING_ARGUMENT_INPUT %s',
38+
// @ts-expect-error @types/yargs is outdated -.-
39+
'Unknown argument: %s': {
40+
one: 'UNKNOWN_ARGUMENT_INPUT %s',
41+
other: 'UNKNOWN_ARGUMENTS_INPUT %s',
42+
},
43+
// @ts-expect-error @types/yargs is outdated -.-
44+
'Not enough non-option arguments: got %s, need at least %s': {
45+
one: 'NOT_ENOUGH_NON_OPTION_ARGUMENTS_INPUT {"got":%s,"need":%s}',
46+
other: 'NOT_ENOUGH_NON_OPTION_ARGUMENTS_INPUT {"got":%s,"need":%s}',
47+
},
48+
'Arguments %s and %s are mutually exclusive': 'ARGUMENTS_ARE_MUTUALLY_EXCLUSIVE_INPUT ["%s","%s"]',
49+
})
50+
.option('help', {
51+
boolean: true,
52+
describe: 'Shows this help message.',
53+
alias: 'h',
2854
});
2955

3056
// @ts-expect-error @types/yargs is outdated -.-
3157
cli.usageConfiguration({ 'hide-types': true });
3258

33-
export function printCLIVersionAndExit(): never {
34-
console.log(version);
35-
process.exit(0);
59+
export function printCLIVersionAndExitIfFlagUsed(parsed: Awaited<ReturnType<typeof cli.parse>>) {
60+
if (parsed.v === true || parsed.version === true) {
61+
console.log(cliVersion);
62+
process.exit(0);
63+
}
64+
}
65+
66+
export function printHelpAndExitIfFlagUsed(parsed: Awaited<ReturnType<typeof cli.parse>>, entrypoint: string) {
67+
if (parsed.help === true || parsed.h === true || parsed._.length === 0) {
68+
console.log(renderMainHelpMenu(entrypoint));
69+
process.exit(0);
70+
}
71+
}
72+
73+
export async function runCLI(entrypoint: string) {
74+
await cli.parse(process.argv.slice(2), {}, (rawError, parsed) => {
75+
if (rawError) {
76+
if (process.env.CLI_DEBUG) {
77+
console.error({ type: 'parsed', error: rawError?.message, parsed });
78+
}
79+
80+
const errorMessageSplit = rawError.message.split(' ');
81+
82+
const possibleCommands = [
83+
//
84+
`${parsed._[0]} ${parsed._[1]}`,
85+
`${parsed._[0]}`,
86+
];
87+
88+
const command = commandRegistry.get(possibleCommands.find((cmd) => commandRegistry.has(cmd)) ?? '');
89+
90+
if (!command) {
91+
error({
92+
message: `Command ${parsed._[0]} not found`,
93+
});
94+
95+
return;
96+
}
97+
98+
const commandFlags = Object.entries(command.flags ?? {})
99+
.filter(([, flag]) => typeof flag !== 'string')
100+
.map(([flagName, flag]) => {
101+
const castedFlag = flag as TaggedFlagBuilder<FlagTag, unknown, unknown, unknown>;
102+
103+
const flagKey = kebabCaseString(camelCaseToKebabCase(flagName)).toLowerCase();
104+
105+
return {
106+
flagKey,
107+
char: castedFlag.char,
108+
aliases: castedFlag.aliases?.map((alias) =>
109+
kebabCaseString(camelCaseToKebabCase(alias)).toLowerCase(),
110+
),
111+
matches(otherFlagKey: string) {
112+
return (
113+
this.flagKey === otherFlagKey ||
114+
this.char === otherFlagKey ||
115+
this.aliases?.some((aliasedFlag) => aliasedFlag === otherFlagKey)
116+
);
117+
},
118+
};
119+
});
120+
121+
switch (errorMessageSplit[0]) {
122+
case 'MISSING_ARGUMENT_INPUT': {
123+
for (const flag of commandFlags) {
124+
if (flag.matches(errorMessageSplit[1])) {
125+
error({
126+
message: `Flag --${flag.flagKey} expects a value`,
127+
});
128+
129+
return;
130+
}
131+
}
132+
133+
break;
134+
}
135+
136+
case 'ARGUMENTS_ARE_MUTUALLY_EXCLUSIVE_INPUT': {
137+
const args = JSON.parse(errorMessageSplit[1]) as string[];
138+
139+
error({
140+
message: [
141+
`The following errors occurred:`,
142+
...args
143+
.sort((a, b) => a.localeCompare(b))
144+
.map((arg) => {
145+
const value = parsed[arg];
146+
147+
const isBoolean = typeof value === 'boolean';
148+
149+
const argRepresentation = isBoolean ? `--${arg}` : `--${arg}=${value}`;
150+
151+
return ` ${chalk.red('>')} ${chalk.gray(
152+
`${argRepresentation} cannot also be provided when using ${args
153+
.filter((a) => a !== arg)
154+
.map((a) => `--${a}`)
155+
.join(', ')}`,
156+
)}`;
157+
}),
158+
` ${chalk.red('>')} See more help with --help`,
159+
].join('\n'),
160+
});
161+
162+
break;
163+
}
164+
165+
case 'UNKNOWN_ARGUMENT_INPUT':
166+
case 'UNKNOWN_ARGUMENTS_INPUT': {
167+
const nonexistentType = commandFlags.length ? 'flag' : 'subcommand';
168+
const nonexistentRepresentation = (() => {
169+
// Rudimentary as heck, we cannot infer if the flag is provided as `-f` or `-ff` or `--flag`, etc.
170+
if (nonexistentType === 'flag') {
171+
return errorMessageSplit[1].length === 1
172+
? `-${errorMessageSplit[1]}`
173+
: `--${errorMessageSplit[1]}`;
174+
}
175+
176+
return errorMessageSplit[1];
177+
})();
178+
179+
error({
180+
message: [
181+
`Nonexistent ${nonexistentType}: ${nonexistentRepresentation}`,
182+
` ${chalk.red('>')} See more help with --help`,
183+
'',
184+
selectiveRenderHelpForCommand(command, {
185+
showUsageString: true,
186+
showSubcommands: true,
187+
}),
188+
].join('\n'),
189+
});
190+
191+
break;
192+
}
193+
194+
default: {
195+
console.error({ type: 'unhandled', error: rawError.message, parsed });
196+
}
197+
}
198+
} else {
199+
handleParseResults(parsed, entrypoint);
200+
}
201+
});
202+
}
203+
204+
export function handleParseResults(parsed: Awaited<ReturnType<typeof cli.parse>>, entrypoint: string) {
205+
if (parsed._.length === 0) {
206+
printCLIVersionAndExitIfFlagUsed(parsed);
207+
printHelpAndExitIfFlagUsed(parsed, entrypoint);
208+
}
209+
210+
// console.log({ unhandledArgs: parsed });
36211
}
37212

38213
export const cachedStdinInput = await readStdin();

src/entrypoints/actor.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { satisfies } from 'semver';
22

3-
import { cli, printCLIVersionAndExit } from './_shared.js';
3+
import { cli, runCLI } from './_shared.js';
44
import { actorCommands } from '../commands/_register.js';
55
import { SUPPORTED_NODEJS_VERSION } from '../lib/consts.js';
66
import { error } from '../lib/outputs.js';
@@ -20,14 +20,4 @@ for (const CommandClass of actorCommands) {
2020
CommandClass.registerCommand('actor', cli);
2121
}
2222

23-
const parsed = await cli.parse(process.argv.slice(2));
24-
25-
if (parsed._.length === 0) {
26-
if (parsed.v === true) {
27-
printCLIVersionAndExit();
28-
}
29-
30-
// TODO: print help
31-
// console.error('Unknown command, oh my');
32-
// cli.showHelp();
33-
}
23+
await runCLI('actor');

src/entrypoints/apify.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { satisfies } from 'semver';
22

3-
import { cli, printCLIVersionAndExit } from './_shared.js';
3+
import { cli, runCLI } from './_shared.js';
44
import { apifyCommands } from '../commands/_register.js';
55
import { CheckVersionCommand } from '../commands/check-version.js';
66
import { SUPPORTED_NODEJS_VERSION } from '../lib/consts.js';
@@ -36,15 +36,4 @@ for (const CommandClass of apifyCommands) {
3636
CommandClass.registerCommand('apify', cli);
3737
}
3838

39-
const parsed = await cli.parse(process.argv.slice(2));
40-
41-
if (parsed._.length === 0) {
42-
if (parsed.v === true || parsed.version === true) {
43-
printCLIVersionAndExit();
44-
}
45-
46-
console.log({ parsed });
47-
// TODO: print help
48-
// console.error('Unknown command, oh my');
49-
// cli.showHelp();
50-
}
39+
await runCLI('apify');

0 commit comments

Comments
 (0)