@fgv/ks 5.1.0-54 → 5.1.0-56

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.
@@ -1,12 +0,0 @@
1
- {
2
- "testMatch": ["<rootDir>/src/test/**/*.test.ts", "<rootDir>/test/**/*.test.ts"],
3
- "preset": "ts-jest",
4
- "testEnvironment": "node",
5
- "extensionsToTreatAsEsm": [],
6
- "moduleNameMapper": {
7
- "^clipboardy$": "<rootDir>/test/mocks/clipboardy.js"
8
- },
9
- "transform": {
10
- "^.+\\.ts$": ["ts-jest", { "tsconfig": "tsconfig.test.json" }]
11
- }
12
- }
package/config/rig.json DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
3
- "rigPackageName": "@rushstack/heft-node-rig"
4
- }
package/eslint.config.js DELETED
@@ -1,24 +0,0 @@
1
- // ESLint 9 flat config
2
- const nodeProfile = require('@rushstack/eslint-config/flat/profile/node');
3
- const packletsPlugin = require('@rushstack/eslint-config/flat/mixins/packlets');
4
- const tsdocPlugin = require('@rushstack/eslint-config/flat/mixins/tsdoc');
5
-
6
- module.exports = [
7
- ...nodeProfile,
8
- packletsPlugin,
9
- ...tsdocPlugin,
10
- {
11
- files: ['test/**/*.ts'],
12
- languageOptions: {
13
- parserOptions: {
14
- project: './tsconfig.test.json',
15
- tsconfigRootDir: __dirname
16
- }
17
- }
18
- },
19
- {
20
- rules: {
21
- '@rushstack/packlets/mechanics': 'warn'
22
- }
23
- }
24
- ];
@@ -1,3 +0,0 @@
1
- Caching build output folders: lib, temp, .rush/temp/operation/build
2
- Successfully set cache entry.
3
- Cache key: 7e9f7ed7a4e627b4a6f23b9bde09f506756fad25
@@ -1,7 +0,0 @@
1
- Invoking: heft build --clean
2
- ---- build started ----
3
- [build:typescript] The TypeScript compiler version 5.9.3 is newer than the latest version that was tested with Heft (5.8); it may not work correctly.
4
- [build:typescript] Using TypeScript version 5.9.3
5
- [build:lint] Using ESLint version 9.39.5
6
- ---- build finished (10.220s) ----
7
- -------------------- Finished (10.226s) --------------------
package/src/app.ts DELETED
@@ -1,626 +0,0 @@
1
- import { Command } from 'commander';
2
- import pkg from '../package.json';
3
-
4
- import { CryptoUtils } from '@fgv/ts-extras';
5
- import { Result, fail, succeed } from '@fgv/ts-utils';
6
-
7
- import {
8
- copyTextToClipboard,
9
- defaultKeystorePath,
10
- promptHidden,
11
- promptVisible,
12
- readAllFromStdin,
13
- readTextFile,
14
- resolvePath
15
- } from './io';
16
- import { getHelpText } from './help';
17
- import {
18
- changeKeystorePassword,
19
- createKeystore,
20
- listSecrets,
21
- readSecret,
22
- openKeystore,
23
- removeSecret,
24
- saveKeystoreFile,
25
- storeSecret
26
- } from './keystore';
27
- import { DEFAULT_ENCODING, encodeSecret, Encoding, ENCODINGS, parseEncoding } from './encoding';
28
- import { extractTemplateVariables, renderShellTemplate, shellQuote } from './template';
29
-
30
- interface IKeystoreCommandOptions {
31
- keystore?: string;
32
- passwordEnv?: string;
33
- passwordFile?: string;
34
- passwordStdin?: boolean;
35
- }
36
-
37
- interface ISecretValueOptions {
38
- stdin?: boolean;
39
- file?: string;
40
- }
41
-
42
- interface IPutCommandOptions extends IKeystoreCommandOptions, ISecretValueOptions {
43
- name?: string;
44
- description?: string;
45
- replace?: boolean;
46
- }
47
-
48
- interface IGetCommandOptions extends IKeystoreCommandOptions {
49
- clipboard?: boolean;
50
- encoding?: string;
51
- }
52
-
53
- interface IExportCommandOptions extends IKeystoreCommandOptions {
54
- templateFile?: string;
55
- templateString?: string;
56
- clipboard?: boolean;
57
- persistMissing?: boolean;
58
- encoding?: string;
59
- }
60
-
61
- interface IPasswordChangeOptions extends IKeystoreCommandOptions {
62
- newPasswordEnv?: string;
63
- newPasswordFile?: string;
64
- newPasswordStdin?: boolean;
65
- }
66
-
67
- function stripTrailingNewline(value: string): string {
68
- return value.replace(/\r?\n$/, '');
69
- }
70
-
71
- function addEncodingOption(command: Command, description: string): Command {
72
- return command.option(`--encoding <${ENCODINGS.join('|')}>`, description, DEFAULT_ENCODING);
73
- }
74
-
75
- function hasExplicitPasswordSource(options: IKeystoreCommandOptions | IPasswordChangeOptions): boolean {
76
- return Boolean(options.passwordEnv || options.passwordFile || options.passwordStdin);
77
- }
78
-
79
- async function readPasswordFromSource(
80
- options: IKeystoreCommandOptions | IPasswordChangeOptions
81
- ): Promise<Result<string>> {
82
- if (options.passwordFile) {
83
- const fileResult = await readTextFile(options.passwordFile);
84
- if (fileResult.isFailure()) {
85
- return fail(fileResult.message);
86
- }
87
- return succeed(stripTrailingNewline(fileResult.value));
88
- }
89
-
90
- if (options.passwordStdin) {
91
- const stdinResult = await readAllFromStdin();
92
- if (stdinResult.isFailure()) {
93
- return fail(stdinResult.message);
94
- }
95
- return succeed(stripTrailingNewline(stdinResult.value));
96
- }
97
-
98
- if (options.passwordEnv) {
99
- const envValue = process.env[options.passwordEnv];
100
- if (envValue === undefined || envValue.length === 0) {
101
- return fail(`Environment variable '${options.passwordEnv}' is not set`);
102
- }
103
- return succeed(envValue);
104
- }
105
-
106
- const defaultEnv = process.env.FGV_KS_PASSWORD ?? process.env.KS_PASSWORD;
107
- if (defaultEnv !== undefined && defaultEnv.length > 0) {
108
- return succeed(defaultEnv);
109
- }
110
-
111
- return fail('Password not provided');
112
- }
113
-
114
- async function resolvePassword(
115
- options: IKeystoreCommandOptions | IPasswordChangeOptions,
116
- label: string
117
- ): Promise<Result<string>> {
118
- const source = await readPasswordFromSource(options);
119
- if (source.isSuccess()) {
120
- return source;
121
- }
122
-
123
- // If an explicit source was configured and failed, surface the error rather than prompting
124
- if (hasExplicitPasswordSource(options)) {
125
- return source;
126
- }
127
-
128
- const prompted = await promptHidden(`${label}: `);
129
- if (prompted.isFailure()) {
130
- return fail(prompted.message);
131
- }
132
-
133
- return succeed(prompted.value);
134
- }
135
-
136
- async function resolveSecretName(
137
- positionalName: string | undefined,
138
- options: IPutCommandOptions
139
- ): Promise<Result<string>> {
140
- if (options.name !== undefined && options.name.length > 0) {
141
- return succeed(options.name);
142
- }
143
-
144
- if (positionalName !== undefined && positionalName.length > 0) {
145
- return succeed(positionalName);
146
- }
147
-
148
- const prompted = await promptVisible('Secret name: ');
149
- if (prompted.isFailure()) {
150
- return fail(prompted.message);
151
- }
152
-
153
- if (prompted.value.length === 0) {
154
- return fail('Secret name cannot be empty');
155
- }
156
-
157
- return succeed(prompted.value);
158
- }
159
-
160
- async function resolvePasswordConfirmed(
161
- options: IKeystoreCommandOptions | IPasswordChangeOptions,
162
- label: string
163
- ): Promise<Result<string>> {
164
- // Only skip confirmation when password comes from an explicit non-interactive source.
165
- // Ambient env vars (FGV_KS_PASSWORD/KS_PASSWORD) are for reading the current password
166
- // and must not silently bypass confirmation when setting a new one.
167
- if (hasExplicitPasswordSource(options)) {
168
- return resolvePassword(options, label);
169
- }
170
-
171
- const first = await promptHidden(`${label}: `);
172
- if (first.isFailure()) {
173
- return fail(first.message);
174
- }
175
-
176
- const second = await promptHidden(`${label} (confirm): `);
177
- if (second.isFailure()) {
178
- return fail(second.message);
179
- }
180
-
181
- if (first.value !== second.value) {
182
- return fail('Passwords do not match');
183
- }
184
-
185
- return succeed(first.value);
186
- }
187
-
188
- async function resolveSecretValue(options: ISecretValueOptions): Promise<Result<string>> {
189
- if (options.file && options.stdin) {
190
- return fail('Use either --file or --stdin, not both');
191
- }
192
-
193
- if (options.file) {
194
- const fileResult = await readTextFile(options.file);
195
- if (fileResult.isFailure()) {
196
- return fail(fileResult.message);
197
- }
198
- return succeed(stripTrailingNewline(fileResult.value));
199
- }
200
-
201
- if (options.stdin) {
202
- const stdinResult = await readAllFromStdin();
203
- if (stdinResult.isFailure()) {
204
- return fail(stdinResult.message);
205
- }
206
- return succeed(stripTrailingNewline(stdinResult.value));
207
- }
208
-
209
- const prompted = await promptHidden('Secret value: ');
210
- if (prompted.isFailure()) {
211
- return fail(prompted.message);
212
- }
213
-
214
- return succeed(prompted.value);
215
- }
216
-
217
- async function readTemplate(
218
- options: Pick<IExportCommandOptions, 'templateFile' | 'templateString'>
219
- ): Promise<Result<string>> {
220
- if (options.templateFile && options.templateString) {
221
- return fail('Use either --template-file or --template-string, not both');
222
- }
223
-
224
- if (options.templateFile) {
225
- const fileResult = await readTextFile(resolvePath(options.templateFile));
226
- if (fileResult.isFailure()) {
227
- return fail(fileResult.message);
228
- }
229
- return succeed(fileResult.value);
230
- }
231
-
232
- if (options.templateString !== undefined) {
233
- return succeed(options.templateString);
234
- }
235
-
236
- return fail('Specify either --template-file or --template-string');
237
- }
238
-
239
- interface ITemplateContextResult {
240
- readonly context: Record<string, string>;
241
- readonly missing: readonly [string, string][];
242
- }
243
-
244
- async function collectTemplateContext(
245
- keystore: CryptoUtils.KeyStore.KeyStore,
246
- template: string,
247
- encoding: Encoding
248
- ): Promise<Result<ITemplateContextResult>> {
249
- const variablesResult = extractTemplateVariables(template);
250
- if (variablesResult.isFailure()) {
251
- return fail(variablesResult.message);
252
- }
253
-
254
- const secretListResult = keystore.listSecrets();
255
- if (secretListResult.isFailure()) {
256
- return fail(`Failed to list secrets: ${secretListResult.message}`);
257
- }
258
- const knownSecrets = new Set(secretListResult.value);
259
-
260
- const context = Object.create(null) as Record<string, string>;
261
- const missing: Array<[string, string]> = [];
262
-
263
- for (const variable of variablesResult.value) {
264
- const secretResult = keystore.getApiKey(variable);
265
- if (secretResult.isSuccess()) {
266
- context[variable] = encodeSecret(secretResult.value, encoding);
267
- continue;
268
- }
269
-
270
- if (knownSecrets.has(variable)) {
271
- return fail(`Secret '${variable}' exists but is not an API key: ${secretResult.message}`);
272
- }
273
-
274
- const promptResult = await promptHidden(`Secret '${variable}' is missing. Enter value: `);
275
- if (promptResult.isFailure()) {
276
- return fail(promptResult.message);
277
- }
278
-
279
- context[variable] = encodeSecret(promptResult.value, encoding);
280
- missing.push([variable, promptResult.value]);
281
- }
282
-
283
- return succeed({ context, missing });
284
- }
285
-
286
- export class KsCli {
287
- private readonly _program: Command;
288
-
289
- public constructor() {
290
- this._program = new Command();
291
- this._setupCommands();
292
- }
293
-
294
- public async run(argv: string[]): Promise<void> {
295
- await this._program.parseAsync(argv);
296
- }
297
-
298
- private _setupCommands(): void {
299
- this._program.name('ks').description('Manage ts-extras keystore files').version(pkg.version);
300
- this._program.addHelpCommand(false);
301
-
302
- this._program
303
- .command('help [topic]')
304
- .description('Show overview, commands, password, or template help')
305
- .action((topic: string | undefined) => {
306
- const help = getHelpText(topic, this._program);
307
- if (help.isFailure()) {
308
- console.error(`Error: ${help.message}`);
309
- process.exit(1);
310
- }
311
-
312
- console.log(help.value);
313
- });
314
-
315
- this._program
316
- .command('init')
317
- .description('Initialize a new keystore')
318
- .option('--keystore <path>', 'Keystore file path', defaultKeystorePath())
319
- .option('--password-env <name>', 'Environment variable to read the password from')
320
- .option('--password-file <path>', 'Read the password from a file')
321
- .option('--password-stdin', 'Read the password from stdin', false)
322
- .action(async (options: IKeystoreCommandOptions) => {
323
- const password = await resolvePasswordConfirmed(options, 'New keystore password');
324
- if (password.isFailure()) {
325
- console.error(`Error: ${password.message}`);
326
- process.exit(1);
327
- }
328
-
329
- const created = await createKeystore(options.keystore, password.value);
330
- if (created.isFailure()) {
331
- console.error(`Error: ${created.message}`);
332
- process.exit(1);
333
- }
334
- });
335
-
336
- this._program
337
- .command('password')
338
- .description('Change the keystore password')
339
- .option('--keystore <path>', 'Keystore file path', defaultKeystorePath())
340
- .option('--password-env <name>', 'Environment variable to read the current password from')
341
- .option('--password-file <path>', 'Read the current password from a file')
342
- .option('--password-stdin', 'Read the current password from stdin', false)
343
- .option('--new-password-env <name>', 'Environment variable to read the new password from')
344
- .option('--new-password-file <path>', 'Read the new password from a file')
345
- .option('--new-password-stdin', 'Read the new password from stdin', false)
346
- .action(async (options: IPasswordChangeOptions) => {
347
- const currentPassword = await resolvePassword(options, 'Current keystore password');
348
- if (currentPassword.isFailure()) {
349
- console.error(`Error: ${currentPassword.message}`);
350
- process.exit(1);
351
- }
352
-
353
- const newPasswordOptions: IPasswordChangeOptions = {
354
- keystore: options.keystore,
355
- passwordEnv: options.newPasswordEnv,
356
- passwordFile: options.newPasswordFile,
357
- passwordStdin: options.newPasswordStdin
358
- };
359
- const nextPassword = await resolvePasswordConfirmed(newPasswordOptions, 'New keystore password');
360
- if (nextPassword.isFailure()) {
361
- console.error(`Error: ${nextPassword.message}`);
362
- process.exit(1);
363
- }
364
-
365
- const changed = await changeKeystorePassword(
366
- options.keystore,
367
- currentPassword.value,
368
- nextPassword.value
369
- );
370
- if (changed.isFailure()) {
371
- console.error(`Error: ${changed.message}`);
372
- process.exit(1);
373
- }
374
- });
375
-
376
- this._program
377
- .command('put [name]')
378
- .description('Store a secret in the keystore from stdin, a file, or interactive prompts')
379
- .option('--keystore <path>', 'Keystore file path', defaultKeystorePath())
380
- .option('--password-env <name>', 'Environment variable to read the password from')
381
- .option('--password-file <path>', 'Read the password from a file')
382
- .option('--password-stdin', 'Read the password from stdin', false)
383
- .option('--name <name>', 'Secret name')
384
- .option('--stdin', 'Read the secret from stdin', false)
385
- .option('--file <path>', 'Read the secret from a file')
386
- .option('--description <text>', 'Optional secret description')
387
- .option('--replace', 'Replace an existing secret', false)
388
- .action(async (positionalName: string | undefined, options: IPutCommandOptions) => {
389
- const name = await resolveSecretName(positionalName, options);
390
- if (name.isFailure()) {
391
- console.error(`Error: ${name.message}`);
392
- process.exit(1);
393
- }
394
-
395
- const password = await resolvePassword(options, 'Keystore password');
396
- if (password.isFailure()) {
397
- console.error(`Error: ${password.message}`);
398
- process.exit(1);
399
- }
400
-
401
- const secret = await resolveSecretValue(options);
402
- if (secret.isFailure()) {
403
- console.error(`Error: ${secret.message}`);
404
- process.exit(1);
405
- }
406
-
407
- const stored = await storeSecret(options.keystore, password.value, name.value, secret.value, {
408
- description: options.description,
409
- replace: options.replace
410
- });
411
- if (stored.isFailure()) {
412
- console.error(`Error: ${stored.message}`);
413
- process.exit(1);
414
- }
415
- });
416
-
417
- const getCommand = this._program
418
- .command('get <name>')
419
- .description('Read a secret from the keystore')
420
- .option('--keystore <path>', 'Keystore file path', defaultKeystorePath())
421
- .option('--password-env <name>', 'Environment variable to read the password from')
422
- .option('--password-file <path>', 'Read the password from a file')
423
- .option('--password-stdin', 'Read the password from stdin', false)
424
- .option('--clipboard', 'Copy the secret to the clipboard', false);
425
- addEncodingOption(getCommand, 'Encode the secret value before output (default: text)').action(
426
- async (name: string, options: IGetCommandOptions) => {
427
- const encoding = parseEncoding(options.encoding);
428
- if (encoding.isFailure()) {
429
- console.error(`Error: ${encoding.message}`);
430
- process.exit(1);
431
- }
432
-
433
- const password = await resolvePassword(options, 'Keystore password');
434
- if (password.isFailure()) {
435
- console.error(`Error: ${password.message}`);
436
- process.exit(1);
437
- }
438
-
439
- const secret = await readSecret(options.keystore, password.value, name);
440
- if (secret.isFailure()) {
441
- console.error(`Error: ${secret.message}`);
442
- process.exit(1);
443
- }
444
-
445
- const encoded = encodeSecret(secret.value, encoding.value);
446
-
447
- if (options.clipboard) {
448
- const copied = await copyTextToClipboard(encoded);
449
- if (copied.isFailure()) {
450
- console.error(`Error: ${copied.message}`);
451
- process.exit(1);
452
- }
453
- return;
454
- }
455
-
456
- console.log(encoded);
457
- }
458
- );
459
-
460
- this._program
461
- .command('list')
462
- .description('List secrets in the keystore')
463
- .option('--keystore <path>', 'Keystore file path', defaultKeystorePath())
464
- .option('--password-env <name>', 'Environment variable to read the password from')
465
- .option('--password-file <path>', 'Read the password from a file')
466
- .option('--password-stdin', 'Read the password from stdin', false)
467
- .action(async (options: IKeystoreCommandOptions) => {
468
- const password = await resolvePassword(options, 'Keystore password');
469
- if (password.isFailure()) {
470
- console.error(`Error: ${password.message}`);
471
- process.exit(1);
472
- }
473
-
474
- const names = await listSecrets(options.keystore, password.value);
475
- if (names.isFailure()) {
476
- console.error(`Error: ${names.message}`);
477
- process.exit(1);
478
- }
479
-
480
- for (const name of names.value) {
481
- console.log(name);
482
- }
483
- });
484
-
485
- this._program
486
- .command('remove <name>')
487
- .description('Remove a secret from the keystore')
488
- .option('--keystore <path>', 'Keystore file path', defaultKeystorePath())
489
- .option('--password-env <name>', 'Environment variable to read the password from')
490
- .option('--password-file <path>', 'Read the password from a file')
491
- .option('--password-stdin', 'Read the password from stdin', false)
492
- .action(async (name: string, options: IKeystoreCommandOptions) => {
493
- const password = await resolvePassword(options, 'Keystore password');
494
- if (password.isFailure()) {
495
- console.error(`Error: ${password.message}`);
496
- process.exit(1);
497
- }
498
-
499
- const removed = await removeSecret(options.keystore, password.value, name);
500
- if (removed.isFailure()) {
501
- console.error(`Error: ${removed.message}`);
502
- process.exit(1);
503
- }
504
- });
505
-
506
- const exportCommand = this._program
507
- .command('export')
508
- .description('Render a shell template using secrets from the keystore')
509
- .option('--keystore <path>', 'Keystore file path', defaultKeystorePath())
510
- .option('--password-env <name>', 'Environment variable to read the password from')
511
- .option('--password-file <path>', 'Read the password from a file')
512
- .option('--password-stdin', 'Read the password from stdin', false)
513
- .option('--template-file <path>', 'Read the shell template from a file')
514
- .option('--template-string <text>', 'Use the supplied shell template string')
515
- .option('--clipboard', 'Copy the rendered output to the clipboard', false)
516
- .option('--persist-missing', 'Persist prompted secrets back to the keystore', false);
517
- addEncodingOption(
518
- exportCommand,
519
- 'Encode each secret value before template substitution (default: text)'
520
- ).action(async (options: IExportCommandOptions) => {
521
- const encoding = parseEncoding(options.encoding);
522
- if (encoding.isFailure()) {
523
- console.error(`Error: ${encoding.message}`);
524
- process.exit(1);
525
- }
526
-
527
- const password = await resolvePassword(options, 'Keystore password');
528
- if (password.isFailure()) {
529
- console.error(`Error: ${password.message}`);
530
- process.exit(1);
531
- }
532
-
533
- const template = await readTemplate(options);
534
- if (template.isFailure()) {
535
- console.error(`Error: ${template.message}`);
536
- process.exit(1);
537
- }
538
-
539
- const opened = await openKeystore(options.keystore, password.value);
540
- if (opened.isFailure()) {
541
- console.error(`Error: ${opened.message}`);
542
- process.exit(1);
543
- }
544
-
545
- const contextResult = await collectTemplateContext(
546
- opened.value.keystore,
547
- template.value,
548
- encoding.value
549
- );
550
- if (contextResult.isFailure()) {
551
- console.error(`Error: ${contextResult.message}`);
552
- process.exit(1);
553
- }
554
-
555
- const rendered = renderShellTemplate(template.value, contextResult.value.context);
556
- if (rendered.isFailure()) {
557
- console.error(`Error: ${rendered.message}`);
558
- process.exit(1);
559
- }
560
-
561
- if (options.persistMissing && contextResult.value.missing.length > 0) {
562
- for (const [name, value] of contextResult.value.missing) {
563
- const stored = await opened.value.keystore.importApiKey(name, value, { replace: true });
564
- if (stored.isFailure()) {
565
- console.error(`Error: Failed to persist missing secret '${name}': ${stored.message}`);
566
- process.exit(1);
567
- }
568
- }
569
-
570
- const saved = await opened.value.keystore.save(password.value);
571
- if (saved.isFailure()) {
572
- console.error(`Error: ${saved.message}`);
573
- process.exit(1);
574
- }
575
-
576
- const persisted = await saveKeystoreFile(opened.value.path, saved.value);
577
- if (persisted.isFailure()) {
578
- console.error(`Error: ${persisted.message}`);
579
- process.exit(1);
580
- }
581
- }
582
-
583
- if (options.clipboard) {
584
- const copied = await copyTextToClipboard(rendered.value);
585
- if (copied.isFailure()) {
586
- console.error(`Error: ${copied.message}`);
587
- process.exit(1);
588
- }
589
- return;
590
- }
591
-
592
- console.log(rendered.value);
593
- });
594
-
595
- this._program
596
- .command('session')
597
- .description('Prompt for a password and emit a shell export statement')
598
- .option('--var <name>', 'Environment variable name to emit', 'FGV_KS_PASSWORD')
599
- .option('--clipboard', 'Copy the export statement to the clipboard', false)
600
- .action(async (options: { var?: string; clipboard?: boolean }) => {
601
- const varName = options.var ?? 'FGV_KS_PASSWORD';
602
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(varName)) {
603
- console.error(`Error: '--var' value '${varName}' is not a valid shell identifier`);
604
- process.exit(1);
605
- }
606
-
607
- const password = await promptHidden('Keystore password: ');
608
- if (password.isFailure()) {
609
- console.error(`Error: ${password.message}`);
610
- process.exit(1);
611
- }
612
-
613
- const output = `export ${varName}=${shellQuote(password.value)}`;
614
- if (options.clipboard) {
615
- const copied = await copyTextToClipboard(output);
616
- if (copied.isFailure()) {
617
- console.error(`Error: ${copied.message}`);
618
- process.exit(1);
619
- }
620
- return;
621
- }
622
-
623
- console.log(output);
624
- });
625
- }
626
- }
package/src/cli.ts DELETED
@@ -1 +0,0 @@
1
- export { KsCli } from './app';