@doist/cli-core 0.4.0 → 0.6.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/CHANGELOG.md +12 -0
- package/README.md +94 -4
- package/dist/commands/changelog.d.ts +56 -0
- package/dist/commands/changelog.d.ts.map +1 -0
- package/dist/commands/changelog.js +159 -0
- package/dist/commands/changelog.js.map +1 -0
- package/dist/commands/errors.d.ts +7 -0
- package/dist/commands/errors.d.ts.map +1 -0
- package/dist/commands/errors.js +2 -0
- package/dist/commands/errors.js.map +1 -0
- package/dist/commands/index.d.ts +4 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +2 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/errors.d.ts +2 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/global-args.d.ts +129 -0
- package/dist/global-args.d.ts.map +1 -0
- package/dist/global-args.js +175 -0
- package/dist/global-args.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/markdown.d.ts +53 -0
- package/dist/markdown.d.ts.map +1 -0
- package/dist/markdown.js +71 -0
- package/dist/markdown.js.map +1 -0
- package/package.json +24 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [0.6.0](https://github.com/Doist/cli-core/compare/v0.5.0...v0.6.0) (2026-05-09)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* add ./markdown and ./commands subpaths (optional peer-dep extractions) ([#8](https://github.com/Doist/cli-core/issues/8)) ([6b2ad9d](https://github.com/Doist/cli-core/commit/6b2ad9d5a23b5d1d9ae83a2272e959e927a82d40))
|
|
6
|
+
|
|
7
|
+
## [0.5.0](https://github.com/Doist/cli-core/compare/v0.4.0...v0.5.0) (2026-05-08)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* add global args parser + factories ([#7](https://github.com/Doist/cli-core/issues/7)) ([419243e](https://github.com/Doist/cli-core/commit/419243e8543f180e15a2f6efe91d99a4c93bee40))
|
|
12
|
+
|
|
1
13
|
## [0.4.0](https://github.com/Doist/cli-core/compare/v0.3.0...v0.4.0) (2026-05-08)
|
|
2
14
|
|
|
3
15
|
### Features
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Shared core utilities for Doist CLI projects ([todoist-cli](https://github.com/Doist/todoist-cli), [twist-cli](https://github.com/Doist/twist-cli), [outline-cli](https://github.com/Doist/outline-cli)).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
TypeScript, ESM-only, Node ≥ 20.18.1.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -10,17 +10,107 @@ Shared core utilities for Doist CLI projects ([todoist-cli](https://github.com/D
|
|
|
10
10
|
npm install @doist/cli-core
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
## What's in it
|
|
14
|
+
|
|
15
|
+
| Module | Key exports | Purpose |
|
|
16
|
+
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
17
|
+
| `commands` (subpath) | `registerChangelogCommand` (more to come) | Commander wiring for cli-core's standard commands (e.g. `<cli> changelog`). **Requires** `commander` as an optional peer-dep. |
|
|
18
|
+
| `config` | `getConfigPath`, `readConfig`, `readConfigStrict`, `writeConfig`, `updateConfig` | Read / write a per-CLI JSON config file with typed error codes for broken or missing state. |
|
|
19
|
+
| `empty` | `printEmpty` | Print an empty-state message gated on `--json` / `--ndjson` so machine consumers never see human strings on stdout. |
|
|
20
|
+
| `errors` | `CliError` | Typed CLI error class with `code` and exit-code mapping. |
|
|
21
|
+
| `global-args` | `parseGlobalArgs`, `createGlobalArgsStore`, `createAccessibleGate`, `createSpinnerGate`, `getProgressJsonlPath`, `isProgressJsonlEnabled` | Parse well-known global flags (`--json`, `--ndjson`, `--quiet`, `--verbose`, `--accessible`, `--no-spinner`, `--progress-jsonl`) and derive predicates from them. |
|
|
22
|
+
| `json` | `formatJson`, `formatNdjson` | Stable JSON / newline-delimited JSON formatting for stdout. |
|
|
23
|
+
| `markdown` (subpath) | `preloadMarkdown`, `renderMarkdown`, `TerminalRendererOptions` | Lazy-init terminal markdown renderer. **Requires** `marked` and `marked-terminal-renderer` as peer-deps — install only if your CLI uses this subpath. |
|
|
24
|
+
| `options` | `ViewOptions` | Type contract for `{ json?, ndjson? }` per-command options that machine-output gates derive from. |
|
|
25
|
+
| `spinner` | `createSpinner` | Loading spinner factory wrapping `yocto-spinner` with disable gates. |
|
|
26
|
+
| `terminal` | `isCI`, `isStderrTTY`, `isStdinTTY`, `isStdoutTTY` | TTY / CI detection helpers. |
|
|
27
|
+
| `testing` (subpath) | `describeEmptyMachineOutput` | Vitest helpers reusable by consuming CLIs (e.g. parametrised empty-state suite covering `--json` / `--ndjson` / human modes). |
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
### Global args + spinner gate
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { createGlobalArgsStore, createSpinnerGate, createSpinner } from '@doist/cli-core'
|
|
35
|
+
|
|
36
|
+
const store = createGlobalArgsStore()
|
|
37
|
+
export const isJsonMode = () => store.get().json
|
|
38
|
+
|
|
39
|
+
const shouldDisableSpinner = createSpinnerGate({
|
|
40
|
+
envVar: 'TD_SPINNER',
|
|
41
|
+
getArgs: store.get,
|
|
42
|
+
})
|
|
43
|
+
const { withSpinner } = createSpinner({ isDisabled: shouldDisableSpinner })
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Empty-state print
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { printEmpty } from '@doist/cli-core'
|
|
50
|
+
|
|
51
|
+
if (tasks.length === 0) {
|
|
52
|
+
printEmpty({ options, message: 'No tasks found.' })
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Markdown rendering (optional subpath)
|
|
58
|
+
|
|
59
|
+
Install the peer-deps in the consuming CLI:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm install marked marked-terminal-renderer
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Then:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { preloadMarkdown, renderMarkdown } from '@doist/cli-core/markdown'
|
|
69
|
+
|
|
70
|
+
if (!options.json && !options.raw) {
|
|
71
|
+
await preloadMarkdown()
|
|
72
|
+
}
|
|
73
|
+
console.log(await renderMarkdown(comment.body))
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If the peer-deps are missing, `preloadMarkdown` throws a clear error pointing to the install command. TypeScript will also fail to resolve the subpath's types until the peers are installed.
|
|
77
|
+
|
|
78
|
+
### Standard commands (optional subpath)
|
|
79
|
+
|
|
80
|
+
Install the peer-dep in the consuming CLI:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npm install commander
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Then wire `<cli> changelog` in one call:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { dirname, join } from 'node:path'
|
|
90
|
+
import { fileURLToPath } from 'node:url'
|
|
91
|
+
import { registerChangelogCommand } from '@doist/cli-core/commands'
|
|
92
|
+
import packageJson from '../package.json' with { type: 'json' }
|
|
93
|
+
|
|
94
|
+
registerChangelogCommand(program, {
|
|
95
|
+
path: join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'CHANGELOG.md'),
|
|
96
|
+
repoUrl: 'https://github.com/Doist/todoist-cli',
|
|
97
|
+
version: packageJson.version,
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The helper throws `CliError` (`INVALID_TYPE` for a bad `--count`, `FILE_READ_ERROR` if the file can't be read) so the CLI's top-level error handler formats and exits.
|
|
102
|
+
|
|
13
103
|
## Development
|
|
14
104
|
|
|
15
105
|
```bash
|
|
16
106
|
npm install
|
|
17
107
|
npm run build
|
|
18
108
|
npm test
|
|
19
|
-
npm run check #
|
|
20
|
-
npm run fix #
|
|
109
|
+
npm run check # oxlint + oxfmt --check (PR gate)
|
|
110
|
+
npm run fix # oxlint --fix + oxfmt
|
|
21
111
|
```
|
|
22
112
|
|
|
23
|
-
|
|
113
|
+
See [AGENTS.md](AGENTS.md) for project conventions, including the rule that this README is kept in sync with public API changes.
|
|
24
114
|
|
|
25
115
|
## License
|
|
26
116
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
export type ChangelogHeadingLevel = 1 | 2 | 'flexible';
|
|
3
|
+
export type ChangelogBulletMarker = '*' | '-';
|
|
4
|
+
export type ChangelogCommandOptions = {
|
|
5
|
+
/**
|
|
6
|
+
* Absolute path to the consuming CLI's `CHANGELOG.md`. Resolve from the
|
|
7
|
+
* caller's `import.meta.url` so it works in both `src/` and built `dist/`.
|
|
8
|
+
*/
|
|
9
|
+
path: string;
|
|
10
|
+
/** Repo URL with no trailing slash; the `/blob/v<version>/CHANGELOG.md` suffix is appended. */
|
|
11
|
+
repoUrl: string;
|
|
12
|
+
/** Package version embedded in the "View full changelog" link. */
|
|
13
|
+
version: string;
|
|
14
|
+
/** Default value for the `-n/--count` flag. Default: `5`. */
|
|
15
|
+
defaultCount?: number;
|
|
16
|
+
/** Heading level used for version rows. Default: `2` (i.e. `## 1.2.3`). */
|
|
17
|
+
headingLevel?: ChangelogHeadingLevel;
|
|
18
|
+
/** Bullet markers parsed and rendered. Default: `['*']`. */
|
|
19
|
+
bulletMarkers?: ReadonlyArray<ChangelogBulletMarker>;
|
|
20
|
+
/** Indent continuation lines after a bullet (twist-style wrapped bullets). Default: `false`. */
|
|
21
|
+
continuationIndent?: boolean;
|
|
22
|
+
/** Drop versions empty after cleaning (e.g. deps-only releases). Default: `false`. */
|
|
23
|
+
filterEmptyVersions?: boolean;
|
|
24
|
+
};
|
|
25
|
+
export declare function formatInline(text: string): string;
|
|
26
|
+
export declare function formatForTerminal(text: string, options: ChangelogCommandOptions): string;
|
|
27
|
+
export declare function cleanChangelog(text: string, options: ChangelogCommandOptions): string;
|
|
28
|
+
export declare function parseChangelog(content: string, count: number, options: ChangelogCommandOptions): {
|
|
29
|
+
text: string;
|
|
30
|
+
hasMore: boolean;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Register the standard `<cli> changelog` command on a Commander program. The
|
|
34
|
+
* command reads `options.path`, prints the latest `--count` versions with
|
|
35
|
+
* conventional-commit boilerplate stripped, and appends a "View full
|
|
36
|
+
* changelog" link to the matching tag on GitHub.
|
|
37
|
+
*
|
|
38
|
+
* Errors as `CliError`: `INVALID_TYPE` for a non-positive `--count`,
|
|
39
|
+
* `FILE_READ_ERROR` if the file cannot be read. The consumer's top-level
|
|
40
|
+
* error handler is expected to format and exit.
|
|
41
|
+
*
|
|
42
|
+
* ```ts
|
|
43
|
+
* import { dirname, join } from 'node:path'
|
|
44
|
+
* import { fileURLToPath } from 'node:url'
|
|
45
|
+
* import { registerChangelogCommand } from '@doist/cli-core/commands'
|
|
46
|
+
* import packageJson from '../package.json' with { type: 'json' }
|
|
47
|
+
*
|
|
48
|
+
* registerChangelogCommand(program, {
|
|
49
|
+
* path: join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'CHANGELOG.md'),
|
|
50
|
+
* repoUrl: 'https://github.com/Doist/todoist-cli',
|
|
51
|
+
* version: packageJson.version,
|
|
52
|
+
* })
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function registerChangelogCommand(program: Command, options: ChangelogCommandOptions): void;
|
|
56
|
+
//# sourceMappingURL=changelog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changelog.d.ts","sourceRoot":"","sources":["../../src/commands/changelog.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAGxC,MAAM,MAAM,qBAAqB,GAAG,CAAC,GAAG,CAAC,GAAG,UAAU,CAAA;AACtD,MAAM,MAAM,qBAAqB,GAAG,GAAG,GAAG,GAAG,CAAA;AAE7C,MAAM,MAAM,uBAAuB,GAAG;IAClC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ,+FAA+F;IAC/F,OAAO,EAAE,MAAM,CAAA;IACf,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAA;IACf,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,qBAAqB,CAAA;IACpC,4DAA4D;IAC5D,aAAa,CAAC,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAA;IACpD,gGAAgG;IAChG,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,sFAAsF;IACtF,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAChC,CAAA;AA6BD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIjD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,uBAAuB,GAAG,MAAM,CA8BxF;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,uBAAuB,GAAG,MAAM,CAwBrF;AASD,wBAAgB,cAAc,CAC1B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,uBAAuB,GACjC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAoBpC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,uBAAuB,GAAG,IAAI,CA2BjG"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { CliError } from '../errors.js';
|
|
4
|
+
function resolve(options) {
|
|
5
|
+
return {
|
|
6
|
+
path: options.path,
|
|
7
|
+
repoUrl: options.repoUrl,
|
|
8
|
+
version: options.version,
|
|
9
|
+
defaultCount: options.defaultCount ?? 5,
|
|
10
|
+
headingLevel: options.headingLevel ?? 2,
|
|
11
|
+
bulletMarkers: options.bulletMarkers ?? ['*'],
|
|
12
|
+
continuationIndent: options.continuationIndent ?? false,
|
|
13
|
+
filterEmptyVersions: options.filterEmptyVersions ?? false,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function headingPrefixSrc(level) {
|
|
17
|
+
if (level === 1)
|
|
18
|
+
return '#';
|
|
19
|
+
if (level === 2)
|
|
20
|
+
return '##';
|
|
21
|
+
return '#{1,2}';
|
|
22
|
+
}
|
|
23
|
+
function bulletCharClass(markers) {
|
|
24
|
+
if (markers.length === 1)
|
|
25
|
+
return markers[0] === '*' ? '\\*' : '-';
|
|
26
|
+
return '[*-]';
|
|
27
|
+
}
|
|
28
|
+
export function formatInline(text) {
|
|
29
|
+
return text
|
|
30
|
+
.replace(/\*\*([^*]+)\*\*/g, (_, content) => chalk.bold(content))
|
|
31
|
+
.replace(/`([^`]+)`/g, (_, code) => chalk.cyan(code));
|
|
32
|
+
}
|
|
33
|
+
export function formatForTerminal(text, options) {
|
|
34
|
+
const { headingLevel, bulletMarkers, continuationIndent } = resolve(options);
|
|
35
|
+
const headerRe = new RegExp(`^${headingPrefixSrc(headingLevel)} `);
|
|
36
|
+
const isBulletLine = (line) => bulletMarkers.some((m) => line.startsWith(`${m} `));
|
|
37
|
+
let inBullet = false;
|
|
38
|
+
return text
|
|
39
|
+
.split('\n')
|
|
40
|
+
.map((line) => {
|
|
41
|
+
if (headerRe.test(line)) {
|
|
42
|
+
inBullet = false;
|
|
43
|
+
return chalk.green.bold(line.replace(headerRe, ''));
|
|
44
|
+
}
|
|
45
|
+
if (line.startsWith('### ')) {
|
|
46
|
+
inBullet = false;
|
|
47
|
+
return chalk.bold(line.slice(4));
|
|
48
|
+
}
|
|
49
|
+
if (isBulletLine(line)) {
|
|
50
|
+
inBullet = true;
|
|
51
|
+
return ` ${chalk.dim('•')} ${formatInline(line.slice(2))}`;
|
|
52
|
+
}
|
|
53
|
+
if (continuationIndent && inBullet && line.length > 0) {
|
|
54
|
+
return ` ${formatInline(line)}`;
|
|
55
|
+
}
|
|
56
|
+
if (line.length === 0) {
|
|
57
|
+
inBullet = false;
|
|
58
|
+
}
|
|
59
|
+
return formatInline(line);
|
|
60
|
+
})
|
|
61
|
+
.join('\n');
|
|
62
|
+
}
|
|
63
|
+
export function cleanChangelog(text, options) {
|
|
64
|
+
const { headingLevel, bulletMarkers } = resolve(options);
|
|
65
|
+
const bullets = bulletCharClass(bulletMarkers);
|
|
66
|
+
const headerSrc = headingPrefixSrc(headingLevel);
|
|
67
|
+
return (text
|
|
68
|
+
// Version headers: `## [1.2.3](url)` → `## 1.2.3` (heading level honoured).
|
|
69
|
+
.replace(new RegExp(`(${headerSrc}) \\[([^\\]]+)\\]\\([^)]*\\)`, 'g'), '$1 $2')
|
|
70
|
+
// Plain commit-hash parens: ` (abc1234)` and ` ([abc1234](url))`.
|
|
71
|
+
.replace(/ \([a-f0-9]{7}\)/g, '')
|
|
72
|
+
.replace(/ \(\[[a-f0-9]{7}\]\([^)]*\)\)/g, '')
|
|
73
|
+
// Issue / PR links: `[#nnn](url)` → `#nnn`.
|
|
74
|
+
.replace(/\[#(\d+)\]\([^)]*\)/g, '#$1')
|
|
75
|
+
// Drop `**deps:**` lines wholesale; not useful to end users.
|
|
76
|
+
.replace(new RegExp(`^${bullets} \\*\\*deps:\\*\\*.*$`, 'gm'), '')
|
|
77
|
+
// Drop `**scope:**` prefixes, keep the content: `**task:** foo` → `foo`.
|
|
78
|
+
.replace(/\*\*[\w-]+:\*\* /g, '')
|
|
79
|
+
// Collapse blank-line runs left by removed deps lines.
|
|
80
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
81
|
+
// Drop now-empty section headers (e.g. `### Bug Fixes` with no items).
|
|
82
|
+
// The lookahead includes `### ` so consecutive empty sections all peel
|
|
83
|
+
// away, not just the last one before a version row.
|
|
84
|
+
.replace(new RegExp(`### [\\w ]+\\n\\n(?=${headerSrc} |### |$)`, 'gm'), ''));
|
|
85
|
+
}
|
|
86
|
+
function isEmptyAfterClean(section, options) {
|
|
87
|
+
const { headingLevel } = resolve(options);
|
|
88
|
+
const cleaned = cleanChangelog(section, options);
|
|
89
|
+
const headerRe = new RegExp(`^${headingPrefixSrc(headingLevel)} .+$`, 'm');
|
|
90
|
+
return cleaned.replace(headerRe, '').trim().length === 0;
|
|
91
|
+
}
|
|
92
|
+
export function parseChangelog(content, count, options) {
|
|
93
|
+
const { headingLevel, filterEmptyVersions } = resolve(options);
|
|
94
|
+
const versionHeaderSrc = `${headingPrefixSrc(headingLevel)} (?:\\d|\\[)`;
|
|
95
|
+
const splitRe = new RegExp(`\\n(?=${versionHeaderSrc})`);
|
|
96
|
+
const matchRe = new RegExp(`^${versionHeaderSrc}`);
|
|
97
|
+
const allVersions = content.split(splitRe).filter((s) => matchRe.test(s));
|
|
98
|
+
const versionSections = filterEmptyVersions
|
|
99
|
+
? allVersions.filter((s) => !isEmptyAfterClean(s, options))
|
|
100
|
+
: allVersions;
|
|
101
|
+
const selected = versionSections.slice(0, count);
|
|
102
|
+
if (selected.length === 0) {
|
|
103
|
+
return { text: 'No changelog entries found.', hasMore: false };
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
text: cleanChangelog(selected.join('\n').trimEnd(), options),
|
|
107
|
+
hasMore: versionSections.length > count,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Register the standard `<cli> changelog` command on a Commander program. The
|
|
112
|
+
* command reads `options.path`, prints the latest `--count` versions with
|
|
113
|
+
* conventional-commit boilerplate stripped, and appends a "View full
|
|
114
|
+
* changelog" link to the matching tag on GitHub.
|
|
115
|
+
*
|
|
116
|
+
* Errors as `CliError`: `INVALID_TYPE` for a non-positive `--count`,
|
|
117
|
+
* `FILE_READ_ERROR` if the file cannot be read. The consumer's top-level
|
|
118
|
+
* error handler is expected to format and exit.
|
|
119
|
+
*
|
|
120
|
+
* ```ts
|
|
121
|
+
* import { dirname, join } from 'node:path'
|
|
122
|
+
* import { fileURLToPath } from 'node:url'
|
|
123
|
+
* import { registerChangelogCommand } from '@doist/cli-core/commands'
|
|
124
|
+
* import packageJson from '../package.json' with { type: 'json' }
|
|
125
|
+
*
|
|
126
|
+
* registerChangelogCommand(program, {
|
|
127
|
+
* path: join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'CHANGELOG.md'),
|
|
128
|
+
* repoUrl: 'https://github.com/Doist/todoist-cli',
|
|
129
|
+
* version: packageJson.version,
|
|
130
|
+
* })
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
export function registerChangelogCommand(program, options) {
|
|
134
|
+
const resolved = resolve(options);
|
|
135
|
+
program
|
|
136
|
+
.command('changelog')
|
|
137
|
+
.description('Show recent changelog entries')
|
|
138
|
+
.option('-n, --count <number>', 'Number of versions to show', String(resolved.defaultCount))
|
|
139
|
+
.action(async (commandOptions) => {
|
|
140
|
+
const count = Number(commandOptions.count);
|
|
141
|
+
if (!Number.isInteger(count) || count < 1) {
|
|
142
|
+
throw new CliError('INVALID_TYPE', 'Count must be a positive integer');
|
|
143
|
+
}
|
|
144
|
+
let content;
|
|
145
|
+
try {
|
|
146
|
+
content = await readFile(options.path, 'utf-8');
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
throw new CliError('FILE_READ_ERROR', 'Could not read changelog file');
|
|
150
|
+
}
|
|
151
|
+
const { text, hasMore } = parseChangelog(content, count, options);
|
|
152
|
+
console.log(formatForTerminal(text, options));
|
|
153
|
+
if (hasMore) {
|
|
154
|
+
const url = `${options.repoUrl}/blob/v${options.version}/CHANGELOG.md`;
|
|
155
|
+
console.log(chalk.dim(`\nView full changelog: ${url}`));
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=changelog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changelog.js","sourceRoot":"","sources":["../../src/commands/changelog.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error codes thrown by `@doist/cli-core/commands` registration helpers. Folded
|
|
3
|
+
* into the `CliErrorCode` aggregator in `../errors.ts` so consumers don't have
|
|
4
|
+
* to redeclare them in their own `TCode` union when catching.
|
|
5
|
+
*/
|
|
6
|
+
export type CommandErrorCode = 'INVALID_TYPE' | 'FILE_READ_ERROR';
|
|
7
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/commands/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG,iBAAiB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/commands/errors.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AACzD,YAAY,EACR,qBAAqB,EACrB,uBAAuB,EACvB,qBAAqB,GACxB,MAAM,gBAAgB,CAAA;AACvB,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA"}
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CommandErrorCode } from './commands/errors.js';
|
|
1
2
|
import type { ConfigErrorCode } from './config.js';
|
|
2
3
|
export type ErrorType = 'error' | 'info';
|
|
3
4
|
export type CliErrorOptions = {
|
|
@@ -15,7 +16,7 @@ export type CliErrorOptions = {
|
|
|
15
16
|
* export type CliErrorCode = ConfigErrorCode | SpinnerErrorCode | …
|
|
16
17
|
* ```
|
|
17
18
|
*/
|
|
18
|
-
export type CliErrorCode = ConfigErrorCode;
|
|
19
|
+
export type CliErrorCode = CommandErrorCode | ConfigErrorCode;
|
|
19
20
|
/**
|
|
20
21
|
* Generic CLI error carrying a structured code, optional hints, and a severity
|
|
21
22
|
* type.
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAElD,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,MAAM,CAAA;AAExC,MAAM,MAAM,eAAe,GAAG;IAC1B,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,IAAI,CAAC,EAAE,SAAS,CAAA;CACnB,CAAA;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,YAAY,GAAG,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAElD,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,MAAM,CAAA;AAExC,MAAM,MAAM,eAAe,GAAG;IAC1B,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,IAAI,CAAC,EAAE,SAAS,CAAA;CACnB,CAAA;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,YAAY,GAAG,gBAAgB,GAAG,eAAe,CAAA;AAE7D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,QAAQ,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,KAAK;IAC9D,QAAQ,CAAC,IAAI,EAAE,KAAK,GAAG,YAAY,CAAA;IACnC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;gBAEZ,IAAI,EAAE,KAAK,GAAG,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB;CAOzF"}
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAuBA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,OAAO,QAAwC,SAAQ,KAAK;IACrD,IAAI,CAAsB;IAC1B,KAAK,CAAW;IAChB,IAAI,CAAW;IAExB,YAAY,IAA0B,EAAE,OAAe,EAAE,UAA2B,EAAE;QAClF,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,UAAU,CAAA;QACtB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAA;QAC1B,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,CAAA;IACvC,CAAC;CACJ"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized, type-safe parsing of well-known global CLI flags shared
|
|
3
|
+
* across the Doist CLIs.
|
|
4
|
+
*
|
|
5
|
+
* Replaces scattered `process.argv.includes()` checks with a single parse
|
|
6
|
+
* that correctly handles grouped short flags (e.g., `-vq`), repeated flags
|
|
7
|
+
* (e.g., `-vvv`), `--flag=value` forms, and avoids false-positives from
|
|
8
|
+
* option values.
|
|
9
|
+
*
|
|
10
|
+
* The parser is pure — pass an explicit argv for testing, or use
|
|
11
|
+
* `createGlobalArgsStore` for the lazy-cached singleton pattern. The store
|
|
12
|
+
* is generic so per-CLI extensions (e.g. todoist's `--user`/`--raw`,
|
|
13
|
+
* twist's `--non-interactive`) can layer their own fields over `GlobalArgs`.
|
|
14
|
+
*/
|
|
15
|
+
import type { ViewOptions } from './options.js';
|
|
16
|
+
export type GlobalArgs = Required<Pick<ViewOptions, 'json' | 'ndjson'>> & {
|
|
17
|
+
quiet: boolean;
|
|
18
|
+
verbose: 0 | 1 | 2 | 3 | 4;
|
|
19
|
+
accessible: boolean;
|
|
20
|
+
noSpinner: boolean;
|
|
21
|
+
/** false = absent, true = present without path, string = path. */
|
|
22
|
+
progressJsonl: string | true | false;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Parse well-known global flags from `argv`. Pure — pass an explicit array
|
|
26
|
+
* for testing, or omit to read `process.argv.slice(2)`.
|
|
27
|
+
*
|
|
28
|
+
* The parser scans the entire argv: a CLI-specific positional that happens
|
|
29
|
+
* to look like a global short flag (`td comment add 123 -q`) will flip the
|
|
30
|
+
* matching global state. Workaround: use the standard `--` terminator
|
|
31
|
+
* (`td comment add 123 -- -q`) so the parser stops before the positional.
|
|
32
|
+
* The trade-off is intentional — callers run this before Commander has
|
|
33
|
+
* parsed argv, so we can't yet distinguish positionals from option values.
|
|
34
|
+
*
|
|
35
|
+
* `--progress-jsonl` accepts only the bare form (output to stderr) and the
|
|
36
|
+
* `--progress-jsonl=path` form. The space-separated `--progress-jsonl path`
|
|
37
|
+
* form is intentionally unsupported because it silently consumes the next
|
|
38
|
+
* positional argument (e.g., `td task add --progress-jsonl "Buy milk"`
|
|
39
|
+
* would treat `Buy milk` as a file path).
|
|
40
|
+
*/
|
|
41
|
+
export declare function parseGlobalArgs(argv?: string[]): GlobalArgs;
|
|
42
|
+
export type GlobalArgsStore<T extends GlobalArgs = GlobalArgs> = {
|
|
43
|
+
get(): T;
|
|
44
|
+
/** Clear the cached parse result. Call from test teardown. */
|
|
45
|
+
reset(): void;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Lazy-cached singleton wrapper around a parser function. Each CLI builds
|
|
49
|
+
* one store at startup; callers read via `store.get()`. Tests reset between
|
|
50
|
+
* cases so a mutated `process.argv` is re-parsed on next access.
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* // Vanilla — canonical fields only.
|
|
54
|
+
* const store = createGlobalArgsStore()
|
|
55
|
+
* export const isJsonMode = () => store.get().json
|
|
56
|
+
* export const resetGlobalArgs = store.reset
|
|
57
|
+
*
|
|
58
|
+
* // Extended — layer CLI-specific fields over GlobalArgs.
|
|
59
|
+
* type CliArgs = GlobalArgs & { user: string | undefined; raw: boolean }
|
|
60
|
+
* const store = createGlobalArgsStore<CliArgs>(() => {
|
|
61
|
+
* const base = parseGlobalArgs()
|
|
62
|
+
* const argv = process.argv.slice(2)
|
|
63
|
+
* return { ...base, user: parseUser(argv), raw: argv.includes('--raw') }
|
|
64
|
+
* })
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare function createGlobalArgsStore(): GlobalArgsStore<GlobalArgs>;
|
|
68
|
+
export declare function createGlobalArgsStore<T extends GlobalArgs>(parse: () => T): GlobalArgsStore<T>;
|
|
69
|
+
export declare function isProgressJsonlEnabled(args: GlobalArgs): boolean;
|
|
70
|
+
export declare function getProgressJsonlPath(args: GlobalArgs): string | undefined;
|
|
71
|
+
export type AccessibleGateOptions = {
|
|
72
|
+
/** Env var that, when set to `'1'`, forces accessible mode (e.g. `TD_ACCESSIBLE`). */
|
|
73
|
+
envVar: string;
|
|
74
|
+
getArgs: () => GlobalArgs;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Build an `isAccessible` predicate that combines the `--accessible` flag
|
|
78
|
+
* with a CLI-specific opt-in env var (e.g. `TD_ACCESSIBLE=1`).
|
|
79
|
+
*
|
|
80
|
+
* ```ts
|
|
81
|
+
* const store = createGlobalArgsStore()
|
|
82
|
+
* export const isAccessible = createAccessibleGate({
|
|
83
|
+
* envVar: 'TD_ACCESSIBLE',
|
|
84
|
+
* getArgs: store.get,
|
|
85
|
+
* })
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export declare function createAccessibleGate(opts: AccessibleGateOptions): () => boolean;
|
|
89
|
+
export type SpinnerGateOptions = {
|
|
90
|
+
/** Env var that, when set to `'false'`, force-disables the spinner (e.g. `TD_SPINNER`). */
|
|
91
|
+
envVar: string;
|
|
92
|
+
getArgs: () => GlobalArgs;
|
|
93
|
+
/**
|
|
94
|
+
* CLI-specific extra disable triggers — e.g. twist returns true when
|
|
95
|
+
* `--non-interactive` is set. Evaluated only after the canonical checks
|
|
96
|
+
* already returned false.
|
|
97
|
+
*/
|
|
98
|
+
extraTriggers?: () => boolean;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Build a `shouldDisableSpinner` predicate. Disables on:
|
|
102
|
+
* - env var equals `'false'`
|
|
103
|
+
* - `isCI()`
|
|
104
|
+
* - any of `--json`, `--ndjson`, `--no-spinner`, `--progress-jsonl`, `--verbose`
|
|
105
|
+
* - `extraTriggers?.()` returning true
|
|
106
|
+
*
|
|
107
|
+
* Pair with `createSpinner({ isDisabled })` from `./spinner.js`.
|
|
108
|
+
*
|
|
109
|
+
* ```ts
|
|
110
|
+
* const store = createGlobalArgsStore()
|
|
111
|
+
*
|
|
112
|
+
* // todoist: env var + canonical flags only.
|
|
113
|
+
* const shouldDisableSpinner = createSpinnerGate({
|
|
114
|
+
* envVar: 'TD_SPINNER',
|
|
115
|
+
* getArgs: store.get,
|
|
116
|
+
* })
|
|
117
|
+
*
|
|
118
|
+
* // twist: also disable when --non-interactive is set.
|
|
119
|
+
* const shouldDisableSpinner = createSpinnerGate({
|
|
120
|
+
* envVar: 'TW_SPINNER',
|
|
121
|
+
* getArgs: store.get,
|
|
122
|
+
* extraTriggers: () => isNonInteractive(),
|
|
123
|
+
* })
|
|
124
|
+
*
|
|
125
|
+
* const { withSpinner } = createSpinner({ isDisabled: shouldDisableSpinner })
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
export declare function createSpinnerGate(opts: SpinnerGateOptions): () => boolean;
|
|
129
|
+
//# sourceMappingURL=global-args.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"global-args.d.ts","sourceRoot":"","sources":["../src/global-args.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAG/C,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,GAAG;IACtE,KAAK,EAAE,OAAO,CAAA;IACd,OAAO,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC1B,UAAU,EAAE,OAAO,CAAA;IACnB,SAAS,EAAE,OAAO,CAAA;IAClB,kEAAkE;IAClE,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,KAAK,CAAA;CACvC,CAAA;AAOD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,UAAU,CAiD3D;AAED,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,IAAI;IAC7D,GAAG,IAAI,CAAC,CAAA;IACR,8DAA8D;IAC9D,KAAK,IAAI,IAAI,CAAA;CAChB,CAAA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CAAC,UAAU,CAAC,CAAA;AACpE,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAA;AAkB/F,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAEhE;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAEzE;AAED,MAAM,MAAM,qBAAqB,GAAG;IAChC,sFAAsF;IACtF,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,UAAU,CAAA;CAC5B,CAAA;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,qBAAqB,GAAG,MAAM,OAAO,CAE/E;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC7B,2FAA2F;IAC3F,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,UAAU,CAAA;IACzB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,OAAO,CAAA;CAChC,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,kBAAkB,GAAG,MAAM,OAAO,CAgBzE"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized, type-safe parsing of well-known global CLI flags shared
|
|
3
|
+
* across the Doist CLIs.
|
|
4
|
+
*
|
|
5
|
+
* Replaces scattered `process.argv.includes()` checks with a single parse
|
|
6
|
+
* that correctly handles grouped short flags (e.g., `-vq`), repeated flags
|
|
7
|
+
* (e.g., `-vvv`), `--flag=value` forms, and avoids false-positives from
|
|
8
|
+
* option values.
|
|
9
|
+
*
|
|
10
|
+
* The parser is pure — pass an explicit argv for testing, or use
|
|
11
|
+
* `createGlobalArgsStore` for the lazy-cached singleton pattern. The store
|
|
12
|
+
* is generic so per-CLI extensions (e.g. todoist's `--user`/`--raw`,
|
|
13
|
+
* twist's `--non-interactive`) can layer their own fields over `GlobalArgs`.
|
|
14
|
+
*/
|
|
15
|
+
import { isCI } from './terminal.js';
|
|
16
|
+
const SHORT_FLAGS = {
|
|
17
|
+
q: 'quiet',
|
|
18
|
+
v: 'verbose',
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Parse well-known global flags from `argv`. Pure — pass an explicit array
|
|
22
|
+
* for testing, or omit to read `process.argv.slice(2)`.
|
|
23
|
+
*
|
|
24
|
+
* The parser scans the entire argv: a CLI-specific positional that happens
|
|
25
|
+
* to look like a global short flag (`td comment add 123 -q`) will flip the
|
|
26
|
+
* matching global state. Workaround: use the standard `--` terminator
|
|
27
|
+
* (`td comment add 123 -- -q`) so the parser stops before the positional.
|
|
28
|
+
* The trade-off is intentional — callers run this before Commander has
|
|
29
|
+
* parsed argv, so we can't yet distinguish positionals from option values.
|
|
30
|
+
*
|
|
31
|
+
* `--progress-jsonl` accepts only the bare form (output to stderr) and the
|
|
32
|
+
* `--progress-jsonl=path` form. The space-separated `--progress-jsonl path`
|
|
33
|
+
* form is intentionally unsupported because it silently consumes the next
|
|
34
|
+
* positional argument (e.g., `td task add --progress-jsonl "Buy milk"`
|
|
35
|
+
* would treat `Buy milk` as a file path).
|
|
36
|
+
*/
|
|
37
|
+
export function parseGlobalArgs(argv) {
|
|
38
|
+
const args = argv ?? process.argv.slice(2);
|
|
39
|
+
const result = {
|
|
40
|
+
json: false,
|
|
41
|
+
ndjson: false,
|
|
42
|
+
quiet: false,
|
|
43
|
+
verbose: 0,
|
|
44
|
+
accessible: false,
|
|
45
|
+
noSpinner: false,
|
|
46
|
+
progressJsonl: false,
|
|
47
|
+
};
|
|
48
|
+
for (let i = 0; i < args.length; i++) {
|
|
49
|
+
const arg = args[i];
|
|
50
|
+
if (arg === '--')
|
|
51
|
+
break;
|
|
52
|
+
if (arg === '--json') {
|
|
53
|
+
result.json = true;
|
|
54
|
+
}
|
|
55
|
+
else if (arg === '--ndjson') {
|
|
56
|
+
result.ndjson = true;
|
|
57
|
+
}
|
|
58
|
+
else if (arg === '--quiet') {
|
|
59
|
+
result.quiet = true;
|
|
60
|
+
}
|
|
61
|
+
else if (arg === '--verbose') {
|
|
62
|
+
result.verbose = Math.min(result.verbose + 1, 4);
|
|
63
|
+
}
|
|
64
|
+
else if (arg === '--accessible') {
|
|
65
|
+
result.accessible = true;
|
|
66
|
+
}
|
|
67
|
+
else if (arg === '--no-spinner') {
|
|
68
|
+
result.noSpinner = true;
|
|
69
|
+
}
|
|
70
|
+
else if (arg === '--progress-jsonl') {
|
|
71
|
+
result.progressJsonl = true;
|
|
72
|
+
}
|
|
73
|
+
else if (arg.startsWith('--progress-jsonl=')) {
|
|
74
|
+
result.progressJsonl = arg.slice('--progress-jsonl='.length);
|
|
75
|
+
}
|
|
76
|
+
else if (arg.length > 1 && arg[0] === '-' && arg[1] !== '-') {
|
|
77
|
+
// Short-flag group: -v, -vq, -vvv, etc. Unknown shorts are
|
|
78
|
+
// silently ignored — they belong to Commander or subcommands.
|
|
79
|
+
for (let j = 1; j < arg.length; j++) {
|
|
80
|
+
const mapped = SHORT_FLAGS[arg[j]];
|
|
81
|
+
if (mapped === 'verbose') {
|
|
82
|
+
result.verbose = Math.min(result.verbose + 1, 4);
|
|
83
|
+
}
|
|
84
|
+
else if (mapped === 'quiet') {
|
|
85
|
+
result.quiet = true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
export function createGlobalArgsStore(parse) {
|
|
93
|
+
// Overloads ensure callers passing a custom `T` must supply a matching
|
|
94
|
+
// parser; the implementation default only kicks in for the no-arg
|
|
95
|
+
// canonical case where `T` collapses to `GlobalArgs`.
|
|
96
|
+
const parser = parse ?? parseGlobalArgs;
|
|
97
|
+
let cached = null;
|
|
98
|
+
return {
|
|
99
|
+
get() {
|
|
100
|
+
if (cached === null)
|
|
101
|
+
cached = parser();
|
|
102
|
+
return cached;
|
|
103
|
+
},
|
|
104
|
+
reset() {
|
|
105
|
+
cached = null;
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
export function isProgressJsonlEnabled(args) {
|
|
110
|
+
return args.progressJsonl !== false;
|
|
111
|
+
}
|
|
112
|
+
export function getProgressJsonlPath(args) {
|
|
113
|
+
return typeof args.progressJsonl === 'string' ? args.progressJsonl : undefined;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Build an `isAccessible` predicate that combines the `--accessible` flag
|
|
117
|
+
* with a CLI-specific opt-in env var (e.g. `TD_ACCESSIBLE=1`).
|
|
118
|
+
*
|
|
119
|
+
* ```ts
|
|
120
|
+
* const store = createGlobalArgsStore()
|
|
121
|
+
* export const isAccessible = createAccessibleGate({
|
|
122
|
+
* envVar: 'TD_ACCESSIBLE',
|
|
123
|
+
* getArgs: store.get,
|
|
124
|
+
* })
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
export function createAccessibleGate(opts) {
|
|
128
|
+
return () => process.env[opts.envVar] === '1' || opts.getArgs().accessible;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Build a `shouldDisableSpinner` predicate. Disables on:
|
|
132
|
+
* - env var equals `'false'`
|
|
133
|
+
* - `isCI()`
|
|
134
|
+
* - any of `--json`, `--ndjson`, `--no-spinner`, `--progress-jsonl`, `--verbose`
|
|
135
|
+
* - `extraTriggers?.()` returning true
|
|
136
|
+
*
|
|
137
|
+
* Pair with `createSpinner({ isDisabled })` from `./spinner.js`.
|
|
138
|
+
*
|
|
139
|
+
* ```ts
|
|
140
|
+
* const store = createGlobalArgsStore()
|
|
141
|
+
*
|
|
142
|
+
* // todoist: env var + canonical flags only.
|
|
143
|
+
* const shouldDisableSpinner = createSpinnerGate({
|
|
144
|
+
* envVar: 'TD_SPINNER',
|
|
145
|
+
* getArgs: store.get,
|
|
146
|
+
* })
|
|
147
|
+
*
|
|
148
|
+
* // twist: also disable when --non-interactive is set.
|
|
149
|
+
* const shouldDisableSpinner = createSpinnerGate({
|
|
150
|
+
* envVar: 'TW_SPINNER',
|
|
151
|
+
* getArgs: store.get,
|
|
152
|
+
* extraTriggers: () => isNonInteractive(),
|
|
153
|
+
* })
|
|
154
|
+
*
|
|
155
|
+
* const { withSpinner } = createSpinner({ isDisabled: shouldDisableSpinner })
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
export function createSpinnerGate(opts) {
|
|
159
|
+
return () => {
|
|
160
|
+
if (process.env[opts.envVar] === 'false')
|
|
161
|
+
return true;
|
|
162
|
+
if (isCI())
|
|
163
|
+
return true;
|
|
164
|
+
const args = opts.getArgs();
|
|
165
|
+
if (args.json ||
|
|
166
|
+
args.ndjson ||
|
|
167
|
+
args.noSpinner ||
|
|
168
|
+
isProgressJsonlEnabled(args) ||
|
|
169
|
+
args.verbose > 0) {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
return opts.extraTriggers?.() ?? false;
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=global-args.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"global-args.js","sourceRoot":"","sources":["../src/global-args.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export type { ConfigErrorCode, ReadConfigStrictResult, WriteConfigOptions } from
|
|
|
3
3
|
export { printEmpty } from './empty.js';
|
|
4
4
|
export { CliError } from './errors.js';
|
|
5
5
|
export type { CliErrorCode, CliErrorOptions, ErrorType } from './errors.js';
|
|
6
|
+
export { createAccessibleGate, createGlobalArgsStore, createSpinnerGate, getProgressJsonlPath, isProgressJsonlEnabled, parseGlobalArgs, } from './global-args.js';
|
|
7
|
+
export type { AccessibleGateOptions, GlobalArgs, GlobalArgsStore, SpinnerGateOptions, } from './global-args.js';
|
|
6
8
|
export { formatJson, formatNdjson } from './json.js';
|
|
7
9
|
export type { ViewOptions } from './options.js';
|
|
8
10
|
export { createSpinner } from './spinner.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,2BAA2B,EAC3B,aAAa,EACb,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,WAAW,GACd,MAAM,aAAa,CAAA;AACpB,YAAY,EAAE,eAAe,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAC9F,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC3E,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACpD,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC5C,YAAY,EACR,cAAc,EACd,YAAY,EACZ,aAAa,EACb,UAAU,EACV,cAAc,GACjB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,2BAA2B,EAC3B,aAAa,EACb,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,WAAW,GACd,MAAM,aAAa,CAAA;AACpB,YAAY,EAAE,eAAe,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAC9F,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC3E,OAAO,EACH,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACtB,eAAe,GAClB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACR,qBAAqB,EACrB,UAAU,EACV,eAAe,EACf,kBAAkB,GACrB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACpD,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC5C,YAAY,EACR,cAAc,EACd,YAAY,EACZ,aAAa,EACb,UAAU,EACV,cAAc,GACjB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { BROKEN_CONFIG_STATE_TO_CODE, getConfigPath, readConfig, readConfigStrict, updateConfig, writeConfig, } from './config.js';
|
|
2
2
|
export { printEmpty } from './empty.js';
|
|
3
3
|
export { CliError } from './errors.js';
|
|
4
|
+
export { createAccessibleGate, createGlobalArgsStore, createSpinnerGate, getProgressJsonlPath, isProgressJsonlEnabled, parseGlobalArgs, } from './global-args.js';
|
|
4
5
|
export { formatJson, formatNdjson } from './json.js';
|
|
5
6
|
export { createSpinner } from './spinner.js';
|
|
6
7
|
export { isCI, isStderrTTY, isStdinTTY, isStdoutTTY } from './terminal.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,2BAA2B,EAC3B,aAAa,EACb,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,WAAW,GACd,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEtC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAQ5C,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,2BAA2B,EAC3B,aAAa,EACb,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,WAAW,GACd,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEtC,OAAO,EACH,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACtB,eAAe,GAClB,MAAM,kBAAkB,CAAA;AAOzB,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAQ5C,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { TerminalRendererOptions } from 'marked-terminal-renderer';
|
|
2
|
+
export type { TerminalRendererOptions } from 'marked-terminal-renderer';
|
|
3
|
+
export type PreloadMarkdownOptions = {
|
|
4
|
+
/** Theme to drive the renderer; defaults to `darkTheme()`. */
|
|
5
|
+
theme?: TerminalRendererOptions;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Lazy-init a shared `marked` renderer for terminal output. Idempotent — safe
|
|
9
|
+
* to call multiple times; later calls are no-ops once initialised, so the
|
|
10
|
+
* `theme` from the first call wins.
|
|
11
|
+
*
|
|
12
|
+
* Defer the dynamic import to the call site so CLIs that never render
|
|
13
|
+
* markdown (e.g. `--json` / `--ndjson` runs) don't pay the load cost. The
|
|
14
|
+
* peer-dep packages (`marked`, `marked-terminal-renderer`) are loaded only
|
|
15
|
+
* inside this function — a missing peer surfaces here as a friendly error,
|
|
16
|
+
* never as a module-link crash on `import`.
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { preloadMarkdown, renderMarkdown } from '@doist/cli-core/markdown'
|
|
20
|
+
*
|
|
21
|
+
* if (!options.json && !options.raw) {
|
|
22
|
+
* await preloadMarkdown()
|
|
23
|
+
* }
|
|
24
|
+
* console.log(await renderMarkdown(comment.body))
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* Custom theme — import directly from the renderer package, which the
|
|
28
|
+
* consumer already has installed as a peer-dep:
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* import { preloadMarkdown } from '@doist/cli-core/markdown'
|
|
32
|
+
* import { lightTheme } from 'marked-terminal-renderer'
|
|
33
|
+
* await preloadMarkdown({ theme: lightTheme() })
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function preloadMarkdown(options?: PreloadMarkdownOptions): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Render a markdown string to ANSI-styled terminal output.
|
|
39
|
+
*
|
|
40
|
+
* Returns the input unchanged if `preloadMarkdown` has not yet run — keeps
|
|
41
|
+
* call sites simple in code paths that may execute before init (e.g. early
|
|
42
|
+
* errors). Trailing whitespace from the renderer is trimmed so callers can
|
|
43
|
+
* `console.log` the result without a stray blank line.
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* import { preloadMarkdown, renderMarkdown } from '@doist/cli-core/markdown'
|
|
47
|
+
*
|
|
48
|
+
* await preloadMarkdown()
|
|
49
|
+
* console.log(await renderMarkdown('# Title\n\n- one\n- two'))
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare function renderMarkdown(text: string): Promise<string>;
|
|
53
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAEvE,YAAY,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAEvE,MAAM,MAAM,sBAAsB,GAAG;IACjC,8DAA8D;IAC9D,KAAK,CAAC,EAAE,uBAAuB,CAAA;CAClC,CAAA;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,eAAe,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBrF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAIlE"}
|
package/dist/markdown.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
let markedInstance = null;
|
|
2
|
+
/**
|
|
3
|
+
* Lazy-init a shared `marked` renderer for terminal output. Idempotent — safe
|
|
4
|
+
* to call multiple times; later calls are no-ops once initialised, so the
|
|
5
|
+
* `theme` from the first call wins.
|
|
6
|
+
*
|
|
7
|
+
* Defer the dynamic import to the call site so CLIs that never render
|
|
8
|
+
* markdown (e.g. `--json` / `--ndjson` runs) don't pay the load cost. The
|
|
9
|
+
* peer-dep packages (`marked`, `marked-terminal-renderer`) are loaded only
|
|
10
|
+
* inside this function — a missing peer surfaces here as a friendly error,
|
|
11
|
+
* never as a module-link crash on `import`.
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { preloadMarkdown, renderMarkdown } from '@doist/cli-core/markdown'
|
|
15
|
+
*
|
|
16
|
+
* if (!options.json && !options.raw) {
|
|
17
|
+
* await preloadMarkdown()
|
|
18
|
+
* }
|
|
19
|
+
* console.log(await renderMarkdown(comment.body))
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Custom theme — import directly from the renderer package, which the
|
|
23
|
+
* consumer already has installed as a peer-dep:
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { preloadMarkdown } from '@doist/cli-core/markdown'
|
|
27
|
+
* import { lightTheme } from 'marked-terminal-renderer'
|
|
28
|
+
* await preloadMarkdown({ theme: lightTheme() })
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export async function preloadMarkdown(options) {
|
|
32
|
+
if (markedInstance)
|
|
33
|
+
return;
|
|
34
|
+
let modules;
|
|
35
|
+
try {
|
|
36
|
+
modules = await Promise.all([import('marked'), import('marked-terminal-renderer')]);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
if (err instanceof Error && 'code' in err && err.code === 'ERR_MODULE_NOT_FOUND') {
|
|
40
|
+
throw new Error("@doist/cli-core/markdown requires 'marked' and 'marked-terminal-renderer' as peer dependencies. " +
|
|
41
|
+
'Install them with: npm install marked marked-terminal-renderer', { cause: err });
|
|
42
|
+
}
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
const [{ Marked }, { createTerminalRenderer, darkTheme }] = modules;
|
|
46
|
+
const instance = new Marked();
|
|
47
|
+
instance.use(createTerminalRenderer(options?.theme ?? darkTheme()));
|
|
48
|
+
markedInstance = instance;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Render a markdown string to ANSI-styled terminal output.
|
|
52
|
+
*
|
|
53
|
+
* Returns the input unchanged if `preloadMarkdown` has not yet run — keeps
|
|
54
|
+
* call sites simple in code paths that may execute before init (e.g. early
|
|
55
|
+
* errors). Trailing whitespace from the renderer is trimmed so callers can
|
|
56
|
+
* `console.log` the result without a stray blank line.
|
|
57
|
+
*
|
|
58
|
+
* ```ts
|
|
59
|
+
* import { preloadMarkdown, renderMarkdown } from '@doist/cli-core/markdown'
|
|
60
|
+
*
|
|
61
|
+
* await preloadMarkdown()
|
|
62
|
+
* console.log(await renderMarkdown('# Title\n\n- one\n- two'))
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export async function renderMarkdown(text) {
|
|
66
|
+
if (!markedInstance)
|
|
67
|
+
return text;
|
|
68
|
+
const rendered = await markedInstance.parse(text);
|
|
69
|
+
return typeof rendered === 'string' ? rendered.trimEnd() : text;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"AAUA,IAAI,cAAc,GAAkB,IAAI,CAAA;AAExC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAAgC;IAClE,IAAI,cAAc;QAAE,OAAM;IAC1B,IAAI,OAA6E,CAAA;IACjF,IAAI,CAAC;QACD,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAA;IACvF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACX,IAAI,GAAG,YAAY,KAAK,IAAI,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,KAAK,sBAAsB,EAAE,CAAC;YAC/E,MAAM,IAAI,KAAK,CACX,kGAAkG;gBAC9F,gEAAgE,EACpE,EAAE,KAAK,EAAE,GAAG,EAAE,CACjB,CAAA;QACL,CAAC;QACD,MAAM,GAAG,CAAA;IACb,CAAC;IACD,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,sBAAsB,EAAE,SAAS,EAAE,CAAC,GAAG,OAAO,CAAA;IACnE,MAAM,QAAQ,GAAG,IAAI,MAAM,EAAE,CAAA;IAC7B,QAAQ,CAAC,GAAG,CAAC,sBAAsB,CAAC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,CAAC,CAAC,CAAA;IACnE,cAAc,GAAG,QAAQ,CAAA;AAC7B,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAY;IAC7C,IAAI,CAAC,cAAc;QAAE,OAAO,IAAI,CAAA;IAChC,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACjD,OAAO,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;AACnE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doist/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Shared core utilities for Doist CLI projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -10,6 +10,14 @@
|
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"import": "./dist/index.js"
|
|
12
12
|
},
|
|
13
|
+
"./commands": {
|
|
14
|
+
"types": "./dist/commands/index.d.ts",
|
|
15
|
+
"import": "./dist/commands/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./markdown": {
|
|
18
|
+
"types": "./dist/markdown.d.ts",
|
|
19
|
+
"import": "./dist/markdown.js"
|
|
20
|
+
},
|
|
13
21
|
"./testing": {
|
|
14
22
|
"types": "./dist/testing.d.ts",
|
|
15
23
|
"import": "./dist/testing.js"
|
|
@@ -55,8 +63,11 @@
|
|
|
55
63
|
"@semantic-release/exec": "7.1.0",
|
|
56
64
|
"@semantic-release/git": "10.0.1",
|
|
57
65
|
"@types/node": "25.6.0",
|
|
66
|
+
"commander": "14.0.3",
|
|
58
67
|
"conventional-changelog-conventionalcommits": "9.3.1",
|
|
59
68
|
"lefthook": "2.1.6",
|
|
69
|
+
"marked": "18.0.3",
|
|
70
|
+
"marked-terminal-renderer": "2.2.0",
|
|
60
71
|
"oxfmt": "0.46.0",
|
|
61
72
|
"oxlint": "1.61.0",
|
|
62
73
|
"semantic-release": "25.0.3",
|
|
@@ -68,9 +79,21 @@
|
|
|
68
79
|
"yocto-spinner": "1.1.0"
|
|
69
80
|
},
|
|
70
81
|
"peerDependencies": {
|
|
82
|
+
"commander": ">=14",
|
|
83
|
+
"marked": ">=18",
|
|
84
|
+
"marked-terminal-renderer": ">=2",
|
|
71
85
|
"vitest": ">=4.1"
|
|
72
86
|
},
|
|
73
87
|
"peerDependenciesMeta": {
|
|
88
|
+
"commander": {
|
|
89
|
+
"optional": true
|
|
90
|
+
},
|
|
91
|
+
"marked": {
|
|
92
|
+
"optional": true
|
|
93
|
+
},
|
|
94
|
+
"marked-terminal-renderer": {
|
|
95
|
+
"optional": true
|
|
96
|
+
},
|
|
74
97
|
"vitest": {
|
|
75
98
|
"optional": true
|
|
76
99
|
}
|