@avandar/acclimate 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { camelCase } from 'change-case';
2
2
  import { match } from 'ts-pattern';
3
+ import { stdout, stdin } from 'process';
4
+ import { createInterface } from 'readline/promises';
3
5
 
4
6
  // src/CLIError.ts
5
7
  var CLIError = class _CLIError extends Error {
@@ -172,14 +174,224 @@ var defaultCLIState = {
172
174
  positionalArgs: [],
173
175
  optionArgs: {},
174
176
  globalOptionArgs: {},
175
- action: () => {
176
- }
177
+ action: void 0
177
178
  };
178
179
 
179
180
  // src/AcclimateCLI/createCLI/createCLI.ts
180
181
  function createCLI(name) {
181
182
  return AcclimateCLI({ ...defaultCLIState, name });
182
183
  }
184
+
185
+ // src/generateTerminalMessage/generateTerminalMessage.ts
186
+ var COLOR_CODES = {
187
+ reset: "\x1B[0m",
188
+ black: "\x1B[30m",
189
+ red: "\x1B[31m",
190
+ green: "\x1B[32m",
191
+ yellow: "\x1B[33m",
192
+ blue: "\x1B[34m",
193
+ magenta: "\x1B[35m",
194
+ cyan: "\x1B[36m",
195
+ white: "\x1B[37m",
196
+ bright_black: "\x1B[90m",
197
+ gray: "\x1B[90m",
198
+ grey: "\x1B[90m",
199
+ bright_red: "\x1B[91m",
200
+ bright_green: "\x1B[92m",
201
+ bright_yellow: "\x1B[93m",
202
+ bright_blue: "\x1B[94m",
203
+ bright_magenta: "\x1B[95m",
204
+ bright_cyan: "\x1B[96m",
205
+ bright_white: "\x1B[97m"
206
+ };
207
+ var PARAM_TOKEN_REGEX = /\$([a-zA-Z0-9_]+)\$/g;
208
+ var COLOR_TOKEN_REGEX = /\|([a-zA-Z_]+)\|/g;
209
+ function interpolateParams(message, params) {
210
+ return message.replace(PARAM_TOKEN_REGEX, (match2, key) => {
211
+ const value = params[key];
212
+ if (value === void 0) {
213
+ return "";
214
+ }
215
+ return value;
216
+ });
217
+ }
218
+ function applyColors(message) {
219
+ return message.replace(COLOR_TOKEN_REGEX, (match2, colorName) => {
220
+ const code = COLOR_CODES[colorName.toLowerCase()];
221
+ return code ?? match2;
222
+ });
223
+ }
224
+ function generateTerminalMessage(message, params = {}) {
225
+ const hasColorToken = Boolean(message.match(COLOR_TOKEN_REGEX));
226
+ const endsWithReset = message.trimEnd().endsWith("|reset|");
227
+ const withReset = hasColorToken && !endsWithReset ? `${message}|reset|` : message;
228
+ const interpolated = interpolateParams(withReset, params);
229
+ return applyColors(interpolated);
230
+ }
231
+
232
+ // src/AcclimateCLI/generateHelpText/generateHelpText.ts
233
+ function formatSectionTitle(title) {
234
+ return `|bright_yellow|${title}|reset|`;
235
+ }
236
+ function formatNoneLine() {
237
+ return " |gray|None|reset|";
238
+ }
239
+ function formatDefaultValue(value) {
240
+ return `|gray|[default: ${JSON.stringify(value)}]|reset|`;
241
+ }
242
+ function formatParamLine(param) {
243
+ const description = param.description ?? "No description";
244
+ const requiredLabel = param.required ? "|red|required|reset|" : "|gray|optional|reset|";
245
+ const defaultLabel = param.defaultValue !== void 0 ? formatDefaultValue(param.defaultValue) : void 0;
246
+ const aliasList = param.aliases && param.aliases.length > 0 ? param.aliases.join(", ") : "";
247
+ const displayName = aliasList ? `${param.name}, ${aliasList}` : param.name;
248
+ const segments = [
249
+ ` |bright_white|${displayName}|reset|`,
250
+ `(${param.type})|reset| ${requiredLabel}`,
251
+ `- |gray|${description}|reset|`,
252
+ defaultLabel
253
+ ].filter(Boolean);
254
+ return segments.join(" ");
255
+ }
256
+ function formatSection(title, params) {
257
+ if (params.length === 0) {
258
+ return [formatSectionTitle(title), formatNoneLine()];
259
+ }
260
+ return [formatSectionTitle(title), ...params.map(formatParamLine)];
261
+ }
262
+ function formatCommandLine(options) {
263
+ const { name, description } = options;
264
+ const label = description ?? "No description";
265
+ return ` |bright_white|${name}|reset| - |gray|${label}|reset|`;
266
+ }
267
+ function formatAvailableCommandsLine(commands) {
268
+ const label = commands.length === 0 ? "None" : commands.map((cmd) => {
269
+ return cmd;
270
+ }).join(", ");
271
+ return `|bright_yellow|Available Commands:|reset| |gray|${label}|reset|`;
272
+ }
273
+ function _generateHelpTextHelper(cli, options) {
274
+ const name = cli.getName();
275
+ const description = cli.state.description;
276
+ const level = Number(options.level);
277
+ const positionalParams = cli.state.positionalArgs;
278
+ const optionParams = Object.values(
279
+ cli.state.optionArgs
280
+ );
281
+ const globalOptionParams = Object.values(
282
+ cli.state.globalOptionArgs
283
+ );
284
+ const sortedCommands = Object.entries(cli.state.commands).sort(([a], [b]) => {
285
+ return a.localeCompare(b);
286
+ });
287
+ const commandNames = sortedCommands.map(([commandName]) => {
288
+ return commandName;
289
+ });
290
+ const headerLines = [`|bright_cyan|${name}|reset|`];
291
+ if (description !== void 0) {
292
+ headerLines.push(` |gray|${description}|reset|`);
293
+ }
294
+ let commandSection;
295
+ if (level === 2) {
296
+ commandSection = [
297
+ formatSectionTitle("Commands"),
298
+ ` ${formatAvailableCommandsLine(commandNames)}`
299
+ ];
300
+ } else if (sortedCommands.length === 0) {
301
+ commandSection = [formatSectionTitle("Commands"), formatNoneLine()];
302
+ } else {
303
+ commandSection = [
304
+ formatSectionTitle("Commands"),
305
+ ...sortedCommands.map(([commandName, commandCLI]) => {
306
+ return formatCommandLine({
307
+ name: commandName,
308
+ description: commandCLI.state.description
309
+ });
310
+ })
311
+ ];
312
+ }
313
+ const sections = [
314
+ formatSection("Positional Arguments", positionalParams),
315
+ formatSection("Options", optionParams),
316
+ formatSection("Global Options", globalOptionParams),
317
+ commandSection
318
+ ];
319
+ const lines = [...headerLines, ""];
320
+ sections.forEach((section, idx) => {
321
+ lines.push(...section);
322
+ if (idx < sections.length - 1) {
323
+ lines.push("");
324
+ }
325
+ });
326
+ if (level === 1) {
327
+ const subCommandHelpText = sortedCommands.map(([, commandCLI]) => {
328
+ return _generateHelpTextHelper(commandCLI, { level: 2 });
329
+ });
330
+ if (subCommandHelpText.length > 0) {
331
+ subCommandHelpText.forEach((text) => {
332
+ lines.push("", ...text.split("\n"));
333
+ });
334
+ }
335
+ }
336
+ const indent = " ".repeat(Math.max(0, level - 1));
337
+ return lines.map((line) => {
338
+ return line === "" ? "" : `${indent}${line}`;
339
+ }).join("\n");
340
+ }
341
+ function generateHelpText(cli) {
342
+ return generateTerminalMessage(_generateHelpTextHelper(cli, { level: 1 }));
343
+ }
344
+ var COLOR_TOKEN_REGEX2 = /\|[a-zA-Z_]+\|/g;
345
+ function messageHasPunctuation(message) {
346
+ const stripped = message.replace(COLOR_TOKEN_REGEX2, "").trimEnd();
347
+ return stripped.endsWith(":") || stripped.endsWith("?");
348
+ }
349
+ async function requestTerminalInput(options) {
350
+ const { message, params, options: promptOptions } = options;
351
+ const notice = promptOptions.required ? "" : " |gray|(press Enter to leave empty)|reset|";
352
+ const booleanNotice = promptOptions.type === "boolean" ? " |reset|(y/n)" : "";
353
+ const promptMessage = `${message}${booleanNotice}${notice}`;
354
+ const promptText = messageHasPunctuation(message) ? `${promptMessage} ` : `${promptMessage}: `;
355
+ const prompt = generateTerminalMessage(promptText, params);
356
+ const rl = createInterface({ input: stdin, output: stdout });
357
+ try {
358
+ while (true) {
359
+ const answer = await rl.question(prompt);
360
+ const trimmed = answer.trim();
361
+ if (trimmed.length === 0) {
362
+ if (promptOptions.required) {
363
+ stdout.write(
364
+ generateTerminalMessage(
365
+ "|red|This value is required.|reset| Please enter a value.\n"
366
+ )
367
+ );
368
+ continue;
369
+ }
370
+ return void 0;
371
+ }
372
+ if (promptOptions.type === "boolean") {
373
+ const normalized = trimmed.toLowerCase();
374
+ if (normalized === "y" || normalized === "yes") {
375
+ return "true";
376
+ }
377
+ if (normalized === "n" || normalized === "no") {
378
+ return "false";
379
+ }
380
+ stdout.write(
381
+ generateTerminalMessage(
382
+ "|red|That was not a valid response.|reset| Please enter y or n.\n"
383
+ )
384
+ );
385
+ continue;
386
+ }
387
+ return answer;
388
+ }
389
+ } finally {
390
+ rl.close();
391
+ }
392
+ }
393
+
394
+ // src/AcclimateCLI/runCLI/runCLI.ts
183
395
  function _isValidFullOptionName(name) {
184
396
  return name.startsWith("--");
185
397
  }
@@ -248,7 +460,19 @@ function _replaceAliases({
248
460
  {}
249
461
  );
250
462
  }
251
- function _runCLIHelper(options) {
463
+ async function askForValue(options) {
464
+ const { argConfig } = options;
465
+ const askIfEmpty = argConfig.askIfEmpty;
466
+ const description = argConfig.description ? ` (${argConfig.description})` : "";
467
+ const baseMessage = typeof askIfEmpty === "object" ? askIfEmpty.message : `Please enter a value for ${argConfig.name}${description}`;
468
+ const coloredMessage = `|bright_cyan|${baseMessage}|reset|`;
469
+ return requestTerminalInput({
470
+ message: coloredMessage,
471
+ params: {},
472
+ options: { required: argConfig.required, type: argConfig.type }
473
+ });
474
+ }
475
+ async function _runCLIHelper(options) {
252
476
  const { rawGlobalOptionArgs, rawOptionArgs, rawPositionalArgs, cli } = {
253
477
  ...options,
254
478
  rawGlobalOptionArgs: _replaceAliases({
@@ -285,74 +509,79 @@ function _runCLIHelper(options) {
285
509
  count: rawPositionalArgs.length
286
510
  });
287
511
  }
288
- const parsedPositionalArgs = cli.state.positionalArgs.reduce(
289
- (acc, argConfig, idx) => {
290
- const rawVal = rawPositionalArgs[idx];
291
- if (argConfig.required && rawVal === void 0) {
292
- throw CLIError.missingRequiredPositionalArg({
293
- cliName,
294
- positionalArgName: argConfig.name
295
- });
296
- }
297
- acc[argConfig.name] = _parseAndValidateValue({
512
+ const parsedPositionalArgs = {};
513
+ for (const [idx, argConfig] of cli.state.positionalArgs.entries()) {
514
+ let rawVal = rawPositionalArgs[idx];
515
+ if (argConfig.askIfEmpty && rawVal === void 0) {
516
+ rawVal = await askForValue({ argConfig });
517
+ }
518
+ if (argConfig.required && rawVal === void 0) {
519
+ throw CLIError.missingRequiredPositionalArg({
298
520
  cliName,
299
- inputValue: rawVal,
300
- defaultValue: argConfig.defaultValue,
301
- paramConfig: argConfig
521
+ positionalArgName: argConfig.name
302
522
  });
303
- return acc;
304
- },
305
- {}
306
- );
307
- const parsedOptionArgs = Object.values(
523
+ }
524
+ parsedPositionalArgs[argConfig.name] = _parseAndValidateValue({
525
+ cliName,
526
+ inputValue: rawVal,
527
+ defaultValue: argConfig.defaultValue,
528
+ paramConfig: argConfig
529
+ });
530
+ }
531
+ const parsedOptionArgs = {};
532
+ for (const argConfig of Object.values(
308
533
  cli.state.optionArgs
309
- ).reduce(
310
- (acc, argConfig) => {
311
- const rawVal = rawOptionArgs[argConfig.name];
312
- if (argConfig.required && rawVal === void 0) {
313
- throw CLIError.missingRequiredOption({
314
- cliName,
315
- optionName: argConfig.name
316
- });
317
- }
318
- acc[camelCase(argConfig.name)] = _parseAndValidateValue({
534
+ )) {
535
+ let rawVal = rawOptionArgs[argConfig.name];
536
+ if (argConfig.askIfEmpty && rawVal === void 0) {
537
+ rawVal = await askForValue({ argConfig });
538
+ }
539
+ if (argConfig.required && rawVal === void 0) {
540
+ throw CLIError.missingRequiredOption({
319
541
  cliName,
320
- inputValue: rawVal,
321
- defaultValue: argConfig.defaultValue,
322
- paramConfig: argConfig
542
+ optionName: argConfig.name
323
543
  });
324
- return acc;
325
- },
326
- {}
327
- );
328
- const parsedGlobalOptionArgs = Object.values(
544
+ }
545
+ parsedOptionArgs[camelCase(argConfig.name)] = _parseAndValidateValue({
546
+ cliName,
547
+ inputValue: rawVal,
548
+ defaultValue: argConfig.defaultValue,
549
+ paramConfig: argConfig
550
+ });
551
+ }
552
+ const parsedGlobalOptionArgs = {};
553
+ for (const argConfig of Object.values(
329
554
  cli.state.globalOptionArgs
330
- ).reduce(
331
- (acc, argConfig) => {
332
- const rawVal = rawGlobalOptionArgs[argConfig.name];
333
- if (argConfig.required && rawVal === void 0) {
334
- throw CLIError.missingRequiredOption({
335
- cliName,
336
- optionName: argConfig.name
337
- });
338
- }
339
- acc[camelCase(argConfig.name)] = _parseAndValidateValue({
555
+ )) {
556
+ let rawVal = rawGlobalOptionArgs[argConfig.name];
557
+ if (argConfig.askIfEmpty && rawVal === void 0) {
558
+ rawVal = await askForValue({ argConfig });
559
+ }
560
+ if (argConfig.required && rawVal === void 0) {
561
+ throw CLIError.missingRequiredOption({
340
562
  cliName,
341
- inputValue: rawVal,
342
- defaultValue: argConfig.defaultValue,
343
- paramConfig: argConfig
563
+ optionName: argConfig.name
344
564
  });
345
- return acc;
346
- },
347
- {}
348
- );
349
- cli.state.action({
350
- ...parsedPositionalArgs,
351
- ...parsedOptionArgs,
352
- ...parsedGlobalOptionArgs
353
- });
565
+ }
566
+ parsedGlobalOptionArgs[camelCase(argConfig.name)] = _parseAndValidateValue({
567
+ cliName,
568
+ inputValue: rawVal,
569
+ defaultValue: argConfig.defaultValue,
570
+ paramConfig: argConfig
571
+ });
572
+ }
573
+ const action = cli.state.action;
574
+ if (action) {
575
+ action({
576
+ ...parsedPositionalArgs,
577
+ ...parsedOptionArgs,
578
+ ...parsedGlobalOptionArgs
579
+ });
580
+ return;
581
+ }
582
+ console.log(generateHelpText(cli));
354
583
  }
355
- function runCLI(options) {
584
+ async function runCLI(options) {
356
585
  const { input, cli } = options;
357
586
  const firstOptionIdx = input.findIndex((token) => {
358
587
  return token.startsWith("-");
@@ -380,57 +609,25 @@ function runCLI(options) {
380
609
  currentVals.push(argVal);
381
610
  }
382
611
  }
383
- _runCLIHelper({ rawPositionalArgs, rawOptionArgs, rawGlobalOptionArgs, cli });
612
+ await _runCLIHelper({
613
+ rawPositionalArgs,
614
+ rawOptionArgs,
615
+ rawGlobalOptionArgs,
616
+ cli
617
+ });
384
618
  }
385
619
 
386
620
  // src/Acclimate.ts
387
- var COLOR_CODES = {
388
- reset: "\x1B[0m",
389
- black: "\x1B[30m",
390
- red: "\x1B[31m",
391
- green: "\x1B[32m",
392
- yellow: "\x1B[33m",
393
- blue: "\x1B[34m",
394
- magenta: "\x1B[35m",
395
- cyan: "\x1B[36m",
396
- white: "\x1B[37m",
397
- bright_black: "\x1B[90m",
398
- gray: "\x1B[90m",
399
- grey: "\x1B[90m",
400
- bright_red: "\x1B[91m",
401
- bright_green: "\x1B[92m",
402
- bright_yellow: "\x1B[93m",
403
- bright_blue: "\x1B[94m",
404
- bright_magenta: "\x1B[95m",
405
- bright_cyan: "\x1B[96m",
406
- bright_white: "\x1B[97m"
407
- };
408
- var PARAM_TOKEN_REGEX = /\$([a-zA-Z0-9_]+)\$/g;
409
- var COLOR_TOKEN_REGEX = /\|([a-zA-Z_]+)\|/g;
410
- function interpolateParams(message, params) {
411
- return message.replace(PARAM_TOKEN_REGEX, (match2, key) => {
412
- const value = params[key];
413
- return value ?? match2;
414
- });
415
- }
416
- function applyColors(message) {
417
- return message.replace(COLOR_TOKEN_REGEX, (match2, colorName) => {
418
- const code = COLOR_CODES[colorName.toLowerCase()];
419
- return code ?? match2;
420
- });
421
- }
422
621
  var Acclimate = {
423
622
  createCLI,
424
623
  run: (cli) => {
425
- runCLI({ cli, input: process.argv.slice(2) });
624
+ void runCLI({ cli, input: process.argv.slice(2) });
426
625
  },
427
626
  log: (message, params = {}) => {
428
- const hasColorToken = Boolean(message.match(COLOR_TOKEN_REGEX));
429
- const endsWithReset = message.trimEnd().endsWith("|reset|");
430
- const withReset = hasColorToken && !endsWithReset ? `${message}|reset|` : message;
431
- const interpolated = interpolateParams(withReset, params);
432
- const colorized = applyColors(interpolated);
433
- console.log(colorized);
627
+ console.log(generateTerminalMessage(message, params));
628
+ },
629
+ requestInput: (options) => {
630
+ return requestTerminalInput(options);
434
631
  }
435
632
  };
436
633