@vltpkg/cli-sdk 1.3.8 → 1.5.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/allow-skills.d.ts +36 -0
- package/dist/allow-skills.js +49 -0
- package/dist/commands/build.d.ts +7 -1
- package/dist/commands/build.js +19 -9
- package/dist/commands/cache.d.ts +2 -2
- package/dist/commands/cache.js +22 -7
- package/dist/commands/ci.d.ts +1 -1
- package/dist/commands/ci.js +10 -2
- package/dist/commands/init.js +7 -8
- package/dist/commands/install/reporter.js +46 -0
- package/dist/commands/install.d.ts +29 -1
- package/dist/commands/install.js +35 -1
- package/dist/commands/list.js +3 -1
- package/dist/commands/pack.js +2 -1
- package/dist/commands/pkg.js +4 -1
- package/dist/commands/publish.d.ts +0 -1
- package/dist/commands/publish.js +31 -17
- package/dist/commands/query.js +3 -1
- package/dist/commands/skills.d.ts +56 -0
- package/dist/commands/skills.js +268 -0
- package/dist/commands/uninstall.d.ts +5 -1
- package/dist/commands/uninstall.js +4 -2
- package/dist/commands/unpublish.d.ts +0 -1
- package/dist/commands/unpublish.js +17 -4
- package/dist/commands/update.d.ts +12 -0
- package/dist/commands/update.js +18 -1
- package/dist/commands/version.js +4 -1
- package/dist/config/definition.d.ts +26 -2
- package/dist/config/definition.js +75 -11
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.js +22 -6
- package/dist/config/spec-aliases.d.ts +22 -0
- package/dist/config/spec-aliases.js +47 -0
- package/dist/custom-help.js +4 -0
- package/dist/exec-command.js +12 -6
- package/dist/index.js +18 -8
- package/dist/load-command.d.ts +2 -0
- package/dist/migrate-workspaces.d.ts +16 -0
- package/dist/migrate-workspaces.js +44 -0
- package/dist/output.js +2 -1
- package/dist/print-err.js +4 -1
- package/dist/publish-config.d.ts +9 -0
- package/dist/publish-config.js +8 -0
- package/dist/save-types.d.ts +20 -0
- package/dist/save-types.js +105 -0
- package/dist/skills-prompt.d.ts +28 -0
- package/dist/skills-prompt.js +78 -0
- package/dist/sort-scope-locations.d.ts +11 -0
- package/dist/sort-scope-locations.js +57 -0
- package/package.json +28 -28
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AddImportersDependenciesMap } from '@vltpkg/graph';
|
|
2
|
+
import type { Manifest } from '@vltpkg/types';
|
|
3
|
+
import type { LoadedConfig } from './config/index.ts';
|
|
4
|
+
type TypedManifest = Pick<Manifest, 'exports'> & {
|
|
5
|
+
types?: unknown;
|
|
6
|
+
typings?: unknown;
|
|
7
|
+
typesVersions?: unknown;
|
|
8
|
+
};
|
|
9
|
+
/** `foo` → `@types/foo`, `@scope/foo` → `@types/scope__foo` */
|
|
10
|
+
export declare const typesName: (name: string) => string;
|
|
11
|
+
/** Does the manifest declare its own TypeScript types? */
|
|
12
|
+
export declare const hasOwnTypes: (m: TypedManifest) => boolean;
|
|
13
|
+
/**
|
|
14
|
+
* For each added registry package that ships no types, add the newest
|
|
15
|
+
* non-deprecated `@types/*` package in the same major (major.minor for
|
|
16
|
+
* 0.x), else its older-major `latest`, to that importer's
|
|
17
|
+
* devDependencies.
|
|
18
|
+
*/
|
|
19
|
+
export declare const addTypesDeps: (conf: LoadedConfig, add: AddImportersDependenciesMap) => Promise<void>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { splitDepID } from '@vltpkg/dep-id';
|
|
2
|
+
import { addKey, asDependency } from '@vltpkg/graph';
|
|
3
|
+
import { parse } from '@vltpkg/semver';
|
|
4
|
+
import { Spec } from '@vltpkg/spec';
|
|
5
|
+
import { isErrorWithCause, isObject, longDependencyTypes, } from '@vltpkg/types';
|
|
6
|
+
import { resolve } from 'node:path';
|
|
7
|
+
/** `foo` → `@types/foo`, `@scope/foo` → `@types/scope__foo` */
|
|
8
|
+
export const typesName = (name) => `@types/${name.startsWith('@') ? name.slice(1).replace('/', '__') : name}`;
|
|
9
|
+
const exportsHaveTypes = (e) => !!e &&
|
|
10
|
+
typeof e === 'object' &&
|
|
11
|
+
Object.entries(e).some(([k, v]) => k === 'types' || k.startsWith('types@') || exportsHaveTypes(v));
|
|
12
|
+
// root entry only: typed subpaths don't type `import 'pkg'`
|
|
13
|
+
const rootExport = (e) => isObject(e) && Object.keys(e).some(k => k.startsWith('.')) ?
|
|
14
|
+
e['.']
|
|
15
|
+
: e;
|
|
16
|
+
/** Does the manifest declare its own TypeScript types? */
|
|
17
|
+
export const hasOwnTypes = (m) => !!(m.types || m.typings || m.typesVersions) ||
|
|
18
|
+
exportsHaveTypes(rootExport(m.exports));
|
|
19
|
+
const isResolveError = (er) => isErrorWithCause(er) &&
|
|
20
|
+
isObject(er.cause) &&
|
|
21
|
+
er.cause.code === 'ERESOLVE';
|
|
22
|
+
/**
|
|
23
|
+
* For each added registry package that ships no types, add the newest
|
|
24
|
+
* non-deprecated `@types/*` package in the same major (major.minor for
|
|
25
|
+
* 0.x), else its older-major `latest`, to that importer's
|
|
26
|
+
* devDependencies.
|
|
27
|
+
*/
|
|
28
|
+
export const addTypesDeps = async (conf, add) => {
|
|
29
|
+
const { options } = conf;
|
|
30
|
+
const { packageInfo, packageJson, projectRoot } = options;
|
|
31
|
+
const exact = conf.values['save-exact'];
|
|
32
|
+
const prefix = conf.values['save-prefix'];
|
|
33
|
+
const lookups = new Map();
|
|
34
|
+
const lookup = async (spec, name) => {
|
|
35
|
+
const types = (range) => packageInfo.manifest(Spec.parse(name, range, options));
|
|
36
|
+
try {
|
|
37
|
+
const m = await packageInfo.manifest(spec);
|
|
38
|
+
const { version = '' } = m;
|
|
39
|
+
const v = parse(version);
|
|
40
|
+
if (!v || hasOwnTypes(m))
|
|
41
|
+
return;
|
|
42
|
+
let tm;
|
|
43
|
+
try {
|
|
44
|
+
// @types track lib major (major.minor for 0.x); x-range, `^0`
|
|
45
|
+
// would only match 0.0.0
|
|
46
|
+
tm = await types(v.major ? String(v.major) : `0.${v.minor}`);
|
|
47
|
+
}
|
|
48
|
+
catch (er) {
|
|
49
|
+
if (!isResolveError(er))
|
|
50
|
+
throw er;
|
|
51
|
+
// @types often lag lib majors: take `latest` if older
|
|
52
|
+
tm = await types('latest');
|
|
53
|
+
if (!parse(tm.version ?? '')?.lessThan(v))
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (!tm.version || tm.deprecated)
|
|
57
|
+
return;
|
|
58
|
+
// abbreviated packuments omit types fields: recheck on full one
|
|
59
|
+
const { versions } = await packageInfo.packument(spec, {
|
|
60
|
+
full: true,
|
|
61
|
+
});
|
|
62
|
+
if (hasOwnTypes({ ...versions[version] }))
|
|
63
|
+
return;
|
|
64
|
+
return Spec.parse(name, exact ? tm.version : `${prefix}${tm.version}`, options);
|
|
65
|
+
}
|
|
66
|
+
catch (er) {
|
|
67
|
+
if (isResolveError(er))
|
|
68
|
+
return;
|
|
69
|
+
throw er;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
await Promise.all([...add].map(async ([importer, deps]) => {
|
|
73
|
+
const mani = packageJson.maybeRead(resolve(projectRoot, splitDepID(importer)[1])) ?? {};
|
|
74
|
+
const found = await Promise.all([...deps.values()].map(async ({ spec }) => {
|
|
75
|
+
const { final } = spec;
|
|
76
|
+
const name = typesName(spec.name);
|
|
77
|
+
if (final.type !== 'registry' ||
|
|
78
|
+
final.name !== spec.name ||
|
|
79
|
+
spec.name.startsWith('@types/') ||
|
|
80
|
+
deps.has(name) ||
|
|
81
|
+
[...longDependencyTypes].some(t => mani[t]?.[name] !== undefined) ||
|
|
82
|
+
// other registry: its @types/<name> is a different pkg
|
|
83
|
+
final.registry !==
|
|
84
|
+
Spec.parse(name, '', options).final.registry) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const key = String(spec);
|
|
88
|
+
let p = lookups.get(key);
|
|
89
|
+
if (!p)
|
|
90
|
+
lookups.set(key, (p = lookup(spec, name)));
|
|
91
|
+
return p;
|
|
92
|
+
}));
|
|
93
|
+
const types = found.filter(s => s !== undefined);
|
|
94
|
+
if (!types.length)
|
|
95
|
+
return;
|
|
96
|
+
// fresh Map: importers may share one deps Map
|
|
97
|
+
add.set(importer, new Map([
|
|
98
|
+
...deps,
|
|
99
|
+
...types.map(spec => [
|
|
100
|
+
addKey(spec),
|
|
101
|
+
asDependency({ spec, type: 'dev' }),
|
|
102
|
+
]),
|
|
103
|
+
]));
|
|
104
|
+
}));
|
|
105
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Streams to prompt on; default stdin and stdout. */
|
|
2
|
+
export type PromptStreams = {
|
|
3
|
+
input?: NodeJS.ReadableStream;
|
|
4
|
+
output?: NodeJS.WritableStream;
|
|
5
|
+
};
|
|
6
|
+
/** Prompts can be answered: stdin is a TTY. */
|
|
7
|
+
export declare const interactive: () => boolean;
|
|
8
|
+
/**
|
|
9
|
+
* `[y/N]` question: true on `y`/`yes` (any case), else false. Closed
|
|
10
|
+
* input (EOF, ctrl+C/D): undefined (cancel).
|
|
11
|
+
*/
|
|
12
|
+
export declare const confirm: (question: string, s?: PromptStreams) => Promise<boolean | undefined>;
|
|
13
|
+
export type EditQueryOptions = PromptStreams & {
|
|
14
|
+
/** stored query, shown above the prompt; unset: nothing stored */
|
|
15
|
+
current?: string;
|
|
16
|
+
/** editable answer */
|
|
17
|
+
prefill: string;
|
|
18
|
+
/**
|
|
19
|
+
* throws on an invalid query: its message is printed and the
|
|
20
|
+
* question asked again
|
|
21
|
+
*/
|
|
22
|
+
check: (q: string) => Promise<unknown>;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Ask for a new `allow-skills`, prefilled. Empty answer or closed
|
|
26
|
+
* input: undefined (cancel).
|
|
27
|
+
*/
|
|
28
|
+
export declare const editQuery: ({ current, prefill, check, ...s }: EditQueryOptions) => Promise<string | undefined>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline/promises';
|
|
2
|
+
import { asError } from '@vltpkg/types';
|
|
3
|
+
import { styleTextStdout } from "./output.js";
|
|
4
|
+
/** Prompts can be answered: stdin is a TTY. */
|
|
5
|
+
export const interactive = () => process.stdin.isTTY;
|
|
6
|
+
const open = (s) => {
|
|
7
|
+
const rl = createInterface({
|
|
8
|
+
input: s.input ?? process.stdin,
|
|
9
|
+
output: s.output ?? process.stdout,
|
|
10
|
+
// prefill and line editing
|
|
11
|
+
terminal: true,
|
|
12
|
+
});
|
|
13
|
+
// ctrl+C cancels
|
|
14
|
+
rl.on('SIGINT', () => rl.close());
|
|
15
|
+
let done = false;
|
|
16
|
+
const closed = new Promise(r => rl.once('close', () => {
|
|
17
|
+
done = true;
|
|
18
|
+
r(undefined);
|
|
19
|
+
}));
|
|
20
|
+
// the answer; undefined once the input is closed (EOF, ctrl+C/D)
|
|
21
|
+
const ask = async (query, prefill) => {
|
|
22
|
+
if (done)
|
|
23
|
+
return undefined;
|
|
24
|
+
const q = rl.question(query).catch(() => undefined);
|
|
25
|
+
if (prefill)
|
|
26
|
+
rl.write(prefill);
|
|
27
|
+
return Promise.race([q, closed]);
|
|
28
|
+
};
|
|
29
|
+
return { rl, ask };
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* `[y/N]` question: true on `y`/`yes` (any case), else false. Closed
|
|
33
|
+
* input (EOF, ctrl+C/D): undefined (cancel).
|
|
34
|
+
*/
|
|
35
|
+
export const confirm = async (question, s = {}) => {
|
|
36
|
+
const { rl, ask } = open(s);
|
|
37
|
+
try {
|
|
38
|
+
const a = await ask(question);
|
|
39
|
+
return a === undefined ? undefined : /^y(es)?$/i.test(a.trim());
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
rl.close();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Ask for a new `allow-skills`, prefilled. Empty answer or closed
|
|
47
|
+
* input: undefined (cancel).
|
|
48
|
+
*/
|
|
49
|
+
export const editQuery = async ({ current, prefill, check, ...s }) => {
|
|
50
|
+
const output = s.output ?? process.stdout;
|
|
51
|
+
if (current !== undefined) {
|
|
52
|
+
output.write('\n⚠️ The new allow-skills replaces the current one. Make sure it still\n' +
|
|
53
|
+
'selects every skill you want: skills it no longer selects are unlinked.\n\n' +
|
|
54
|
+
styleTextStdout('yellow', `Current allow-skills: ${current}`) +
|
|
55
|
+
'\n');
|
|
56
|
+
}
|
|
57
|
+
const prompt = styleTextStdout('cyan', 'New allow-skills: ');
|
|
58
|
+
const { rl, ask } = open(s);
|
|
59
|
+
try {
|
|
60
|
+
let fill = prefill;
|
|
61
|
+
for (;;) {
|
|
62
|
+
const a = (await ask(prompt, fill))?.trim();
|
|
63
|
+
if (!a)
|
|
64
|
+
return undefined;
|
|
65
|
+
try {
|
|
66
|
+
await check(a);
|
|
67
|
+
return a;
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
output.write(`${asError(e).message}\n`);
|
|
71
|
+
fill = a;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
rl.close();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Monorepo } from '@vltpkg/workspaces';
|
|
2
|
+
/**
|
|
3
|
+
* Reorder `--scope` target locations so each workspace follows the
|
|
4
|
+
* workspaces it depends on (directly or via unmatched workspaces),
|
|
5
|
+
* matching --workspace/--recursive. Ties broken by query order (a dep
|
|
6
|
+
* may move ahead of unrelated matches); cycles broken
|
|
7
|
+
* deterministically. Non-workspace
|
|
8
|
+
* locations (root, installed deps) keep their index. Accepts absolute
|
|
9
|
+
* or projectRoot-relative locations; returns the original strings.
|
|
10
|
+
*/
|
|
11
|
+
export declare const sortScopeLocations: (locations: string[], monorepo?: Monorepo) => string[];
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { graphRunSync } from '@vltpkg/graph-run';
|
|
3
|
+
/**
|
|
4
|
+
* Reorder `--scope` target locations so each workspace follows the
|
|
5
|
+
* workspaces it depends on (directly or via unmatched workspaces),
|
|
6
|
+
* matching --workspace/--recursive. Ties broken by query order (a dep
|
|
7
|
+
* may move ahead of unrelated matches); cycles broken
|
|
8
|
+
* deterministically. Non-workspace
|
|
9
|
+
* locations (root, installed deps) keep their index. Accepts absolute
|
|
10
|
+
* or projectRoot-relative locations; returns the original strings.
|
|
11
|
+
*/
|
|
12
|
+
export const sortScopeLocations = (locations, monorepo) => {
|
|
13
|
+
if (!monorepo || locations.length < 2)
|
|
14
|
+
return locations;
|
|
15
|
+
// resolve first: workspaces are also keyed by name and rel path
|
|
16
|
+
const wsAt = (loc) => monorepo.get(resolve(monorepo.projectRoot, loc));
|
|
17
|
+
const slots = locations.map(loc => [loc, wsAt(loc)]);
|
|
18
|
+
const locOf = new Map();
|
|
19
|
+
for (const [loc, ws] of slots) {
|
|
20
|
+
if (ws && !locOf.has(ws))
|
|
21
|
+
locOf.set(ws, loc);
|
|
22
|
+
}
|
|
23
|
+
const [first, ...rest] = locOf.keys();
|
|
24
|
+
if (!first || !rest.length)
|
|
25
|
+
return locations;
|
|
26
|
+
// matched deps, walking through unmatched workspaces
|
|
27
|
+
const getDeps = (ws) => {
|
|
28
|
+
const found = [];
|
|
29
|
+
const seen = new Set([ws]);
|
|
30
|
+
const queue = monorepo.getDeps(ws);
|
|
31
|
+
for (const dep of queue) {
|
|
32
|
+
if (seen.has(dep))
|
|
33
|
+
continue;
|
|
34
|
+
seen.add(dep);
|
|
35
|
+
if (locOf.has(dep))
|
|
36
|
+
found.push(dep);
|
|
37
|
+
else
|
|
38
|
+
queue.push(...monorepo.getDeps(dep));
|
|
39
|
+
}
|
|
40
|
+
return found;
|
|
41
|
+
};
|
|
42
|
+
// DFS post-order: deps first, ties in query order
|
|
43
|
+
const sorted = graphRunSync({
|
|
44
|
+
graph: [first, ...rest],
|
|
45
|
+
getDeps,
|
|
46
|
+
visit: () => { },
|
|
47
|
+
}).keys();
|
|
48
|
+
const placed = new Set();
|
|
49
|
+
return slots.map(([loc, ws]) => {
|
|
50
|
+
if (!ws || placed.has(ws))
|
|
51
|
+
return loc;
|
|
52
|
+
placed.add(ws);
|
|
53
|
+
const next = sorted.next().value;
|
|
54
|
+
/* c8 ignore next - same count as placed slots */
|
|
55
|
+
return (next && locOf.get(next)) ?? loc;
|
|
56
|
+
});
|
|
57
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vltpkg/cli-sdk",
|
|
3
3
|
"description": "The source for the vlt CLI",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.5.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/vltpkg/vltpkg.git",
|
|
@@ -14,33 +14,33 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@resvg/resvg-wasm": "^2.6.2",
|
|
17
|
-
"@vltpkg/config": "1.
|
|
18
|
-
"@vltpkg/dep-id": "1.
|
|
19
|
-
"@vltpkg/dot-prop": "1.
|
|
20
|
-
"@vltpkg/error-cause": "1.
|
|
21
|
-
"@vltpkg/git": "1.
|
|
22
|
-
"@vltpkg/graph": "1.
|
|
23
|
-
"@vltpkg/graph-run": "1.
|
|
24
|
-
"@vltpkg/init": "1.
|
|
25
|
-
"@vltpkg/output": "1.
|
|
26
|
-
"@vltpkg/package-info": "1.
|
|
27
|
-
"@vltpkg/package-json": "1.
|
|
28
|
-
"@vltpkg/pick-manifest": "1.
|
|
29
|
-
"@vltpkg/promise-spawn": "1.
|
|
30
|
-
"@vltpkg/query": "1.
|
|
31
|
-
"@vltpkg/registry-client": "1.
|
|
32
|
-
"@vltpkg/rollback-remove": "1.
|
|
33
|
-
"@vltpkg/run": "1.
|
|
34
|
-
"@vltpkg/security-archive": "1.
|
|
35
|
-
"@vltpkg/semver": "1.
|
|
36
|
-
"@vltpkg/spec": "1.
|
|
37
|
-
"@vltpkg/tar": "1.
|
|
38
|
-
"@vltpkg/types": "1.
|
|
39
|
-
"@vltpkg/url-open": "1.
|
|
40
|
-
"@vltpkg/vlt-json": "1.
|
|
41
|
-
"@vltpkg/vlx": "1.
|
|
42
|
-
"@vltpkg/workspaces": "1.
|
|
43
|
-
"@vltpkg/xdg": "1.
|
|
17
|
+
"@vltpkg/config": "1.5.0",
|
|
18
|
+
"@vltpkg/dep-id": "1.5.0",
|
|
19
|
+
"@vltpkg/dot-prop": "1.5.0",
|
|
20
|
+
"@vltpkg/error-cause": "1.5.0",
|
|
21
|
+
"@vltpkg/git": "1.5.0",
|
|
22
|
+
"@vltpkg/graph": "1.5.0",
|
|
23
|
+
"@vltpkg/graph-run": "1.5.0",
|
|
24
|
+
"@vltpkg/init": "1.5.0",
|
|
25
|
+
"@vltpkg/output": "1.5.0",
|
|
26
|
+
"@vltpkg/package-info": "1.5.0",
|
|
27
|
+
"@vltpkg/package-json": "1.5.0",
|
|
28
|
+
"@vltpkg/pick-manifest": "1.5.0",
|
|
29
|
+
"@vltpkg/promise-spawn": "1.5.0",
|
|
30
|
+
"@vltpkg/query": "1.5.0",
|
|
31
|
+
"@vltpkg/registry-client": "1.5.0",
|
|
32
|
+
"@vltpkg/rollback-remove": "1.5.0",
|
|
33
|
+
"@vltpkg/run": "1.5.0",
|
|
34
|
+
"@vltpkg/security-archive": "1.5.0",
|
|
35
|
+
"@vltpkg/semver": "1.5.0",
|
|
36
|
+
"@vltpkg/spec": "1.5.0",
|
|
37
|
+
"@vltpkg/tar": "1.5.0",
|
|
38
|
+
"@vltpkg/types": "1.5.0",
|
|
39
|
+
"@vltpkg/url-open": "1.5.0",
|
|
40
|
+
"@vltpkg/vlt-json": "1.5.0",
|
|
41
|
+
"@vltpkg/vlx": "1.5.0",
|
|
42
|
+
"@vltpkg/workspaces": "1.5.0",
|
|
43
|
+
"@vltpkg/xdg": "1.5.0",
|
|
44
44
|
"ansi-to-pre": "^1.0.6",
|
|
45
45
|
"beautiful-mermaid": "^1.1.3",
|
|
46
46
|
"chalk": "^5.6.2",
|