@boardwalk-labs/cli 0.1.9 → 0.1.11
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/README.md +2 -2
- package/dist/client.d.ts +2 -2
- package/dist/client.js +3 -3
- package/dist/commands/deploy.js +5 -5
- package/dist/commands/dev.js +1 -1
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +2 -2
- package/dist/commands/run.js +1 -1
- package/dist/commands/runs.js +2 -2
- package/dist/deployment.d.ts +5 -5
- package/dist/deployment.js +9 -9
- package/dist/dev/engine.d.ts +3 -3
- package/dist/dev/engine.js +3 -3
- package/dist/dev/engine.js.map +1 -1
- package/dist/project.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ boardwalk dev ./index.ts --stream phase,log
|
|
|
44
44
|
|
|
45
45
|
## Deploying
|
|
46
46
|
|
|
47
|
-
- **`deploy <file|dir> --org <slug>`** — create/update the workflow (idempotent by `meta.
|
|
47
|
+
- **`deploy <file|dir> --org <slug>`** — create/update the workflow (idempotent by `meta.slug`).
|
|
48
48
|
`--dry-run` prints the plan only.
|
|
49
49
|
- **`run <file|dir> --org <slug>`** — deploy the current source, trigger a **real run on the
|
|
50
50
|
platform**, and wait for it to finish. `--no-wait` triggers and exits.
|
|
@@ -59,7 +59,7 @@ your `meta` — the CLI never sends a hand-built manifest.
|
|
|
59
59
|
|
|
60
60
|
The first `deploy`/`run` in a directory writes a gitignored `.boardwalk/project.json`
|
|
61
61
|
with `{ orgSlug, workflowId }`. After that the workflow is identified by that stored **id**, so
|
|
62
|
-
`--org` is optional and renaming `meta.
|
|
62
|
+
`--org` is optional and renaming `meta.slug` or the entry file updates the same workflow instead of
|
|
63
63
|
forking a new one. On a fresh clone, pass `--org` once to re-link (it adopts an existing same-name
|
|
64
64
|
workflow if present, else creates one).
|
|
65
65
|
|
package/dist/client.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { FetchLike } from "./auth/pkce.js";
|
|
2
2
|
export interface WorkflowSummary {
|
|
3
3
|
id: string;
|
|
4
|
-
|
|
4
|
+
slug: string;
|
|
5
5
|
currentVersionId: string | null;
|
|
6
6
|
}
|
|
7
7
|
export interface DeployResult {
|
|
@@ -31,7 +31,7 @@ export interface RunSummary {
|
|
|
31
31
|
export interface RunListItem {
|
|
32
32
|
id: string;
|
|
33
33
|
workflowId: string;
|
|
34
|
-
|
|
34
|
+
workflowSlug: string | null;
|
|
35
35
|
status: string;
|
|
36
36
|
triggerKind: string | null;
|
|
37
37
|
createdAt: number;
|
package/dist/client.js
CHANGED
|
@@ -227,7 +227,7 @@ export class BoardwalkClient {
|
|
|
227
227
|
cachedReadTokens: numOr(cache.totalCachedRead, 0),
|
|
228
228
|
},
|
|
229
229
|
byModel: usageLines(u.byModel, "model"),
|
|
230
|
-
byWorkflow: usageLines(u.byWorkflowUsage, "
|
|
230
|
+
byWorkflow: usageLines(u.byWorkflowUsage, "workflowSlug"),
|
|
231
231
|
};
|
|
232
232
|
}
|
|
233
233
|
}
|
|
@@ -243,7 +243,7 @@ function parseRunRow(row) {
|
|
|
243
243
|
return {
|
|
244
244
|
id: row.id,
|
|
245
245
|
workflowId: typeof row.workflowId === "string" ? row.workflowId : "",
|
|
246
|
-
|
|
246
|
+
workflowSlug: typeof row.workflowSlug === "string" ? row.workflowSlug : null,
|
|
247
247
|
status: row.status,
|
|
248
248
|
triggerKind: typeof row.triggerKind === "string" ? row.triggerKind : null,
|
|
249
249
|
createdAt: numOr(row.createdAt, 0),
|
|
@@ -279,7 +279,7 @@ function isWorkflowSummary(value) {
|
|
|
279
279
|
if (!isRecord(value))
|
|
280
280
|
return false;
|
|
281
281
|
return (typeof value.id === "string" &&
|
|
282
|
-
typeof value.
|
|
282
|
+
typeof value.slug === "string" &&
|
|
283
283
|
(value.currentVersionId === null || typeof value.currentVersionId === "string"));
|
|
284
284
|
}
|
|
285
285
|
function isVersion(value) {
|
package/dist/commands/deploy.js
CHANGED
|
@@ -31,7 +31,7 @@ export async function runDeploy(opts, deps) {
|
|
|
31
31
|
const dep = await deployWithLink(client, { orgSlug: opts.org, target: opts.file, prog });
|
|
32
32
|
if (dep.gitignoreUpdated)
|
|
33
33
|
log(" linked → .boardwalk/project.json (added .boardwalk/ to .gitignore)");
|
|
34
|
-
log(`✓ ${dep.outcome} "${prog.
|
|
34
|
+
log(`✓ ${dep.outcome} "${prog.slug}" version ${String(dep.versionNumber)} (${dep.workflowId})`);
|
|
35
35
|
}
|
|
36
36
|
/** Read-only preview of what `deploy` would do (no writes). */
|
|
37
37
|
async function printPlan(client, opts, prog, log) {
|
|
@@ -41,12 +41,12 @@ async function printPlan(client, opts, prog, log) {
|
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
43
|
if (opts.org === undefined || opts.org.length === 0) {
|
|
44
|
-
log(`plan: CREATE "${prog.
|
|
44
|
+
log(`plan: CREATE "${prog.slug}" (unlinked — pass --org to check for an existing match)`);
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
|
-
const plan = planDeploy(await client.listWorkflows(opts.org), prog.
|
|
47
|
+
const plan = planDeploy(await client.listWorkflows(opts.org), prog.slug);
|
|
48
48
|
log(plan.action === "create"
|
|
49
|
-
? `plan: CREATE "${prog.
|
|
50
|
-
: `plan: ADOPT existing "${prog.
|
|
49
|
+
? `plan: CREATE "${prog.slug}" in org ${opts.org}`
|
|
50
|
+
: `plan: ADOPT existing "${prog.slug}" (${plan.workflowId ?? "?"}) → new version`);
|
|
51
51
|
}
|
|
52
52
|
//# sourceMappingURL=deploy.js.map
|
package/dist/commands/dev.js
CHANGED
|
@@ -70,7 +70,7 @@ export async function runDev(opts, deps = {}) {
|
|
|
70
70
|
});
|
|
71
71
|
try {
|
|
72
72
|
const workflow = engine.deploy(program);
|
|
73
|
-
const run = engine.start(workflow.
|
|
73
|
+
const run = engine.start(workflow.slug, input);
|
|
74
74
|
runId = run.id;
|
|
75
75
|
const result = await engine.wait(run.id);
|
|
76
76
|
if (result.status === "completed")
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -9,4 +9,4 @@ export interface InitDeps {
|
|
|
9
9
|
}
|
|
10
10
|
export declare function runInit(opts: InitOptions, deps?: InitDeps): Promise<void>;
|
|
11
11
|
/** Derive a manifest-legal workflow slug from the target directory's basename. */
|
|
12
|
-
export declare function
|
|
12
|
+
export declare function workflowSlugFor(absDir: string): string;
|
package/dist/commands/init.js
CHANGED
|
@@ -66,7 +66,7 @@ export async function runInit(opts, deps = {}) {
|
|
|
66
66
|
const builtin = BUILTIN_TEMPLATES[opts.template];
|
|
67
67
|
if (builtin !== undefined) {
|
|
68
68
|
const dir = resolve(opts.dir);
|
|
69
|
-
const slug =
|
|
69
|
+
const slug = workflowSlugFor(dir);
|
|
70
70
|
const title = titleCaseSlug(slug);
|
|
71
71
|
const files = Object.fromEntries(Object.entries(builtin).map(([rel, body]) => [
|
|
72
72
|
rel,
|
|
@@ -167,7 +167,7 @@ function isRegistryTemplate(value) {
|
|
|
167
167
|
value.files.length > 0);
|
|
168
168
|
}
|
|
169
169
|
/** Derive a manifest-legal workflow slug from the target directory's basename. */
|
|
170
|
-
export function
|
|
170
|
+
export function workflowSlugFor(absDir) {
|
|
171
171
|
const base = basename(absDir)
|
|
172
172
|
.toLowerCase()
|
|
173
173
|
.replace(/[^a-z0-9-]+/g, "-")
|
package/dist/commands/run.js
CHANGED
|
@@ -66,7 +66,7 @@ export async function runRun(opts, deps) {
|
|
|
66
66
|
const dep = await deployWithLink(client, { orgSlug: opts.org, target: opts.file, prog });
|
|
67
67
|
if (dep.gitignoreUpdated)
|
|
68
68
|
log(" linked → .boardwalk/project.json (added .boardwalk/ to .gitignore)");
|
|
69
|
-
log(`✓ ${dep.outcome} "${prog.
|
|
69
|
+
log(`✓ ${dep.outcome} "${prog.slug}" version ${String(dep.versionNumber)}`);
|
|
70
70
|
const input = parseInput(opts.input);
|
|
71
71
|
const run = await client.triggerRun(dep.orgSlug, dep.workflowId, input);
|
|
72
72
|
log(`▶ run ${run.id} triggered (${run.status})`);
|
package/dist/commands/runs.js
CHANGED
|
@@ -83,7 +83,7 @@ export function formatRuns(org, runs, now) {
|
|
|
83
83
|
` ${col("RUN ID", ID_W)}${col("WORKFLOW", WF_W)}${col("STATUS", STATUS_W)}${col("TRIGGER", TRIGGER_W)}${col("AGE", AGE_W)}DURATION`,
|
|
84
84
|
];
|
|
85
85
|
for (const r of runs) {
|
|
86
|
-
const wf = r.
|
|
86
|
+
const wf = r.workflowSlug ?? r.workflowId;
|
|
87
87
|
lines.push(` ${col(r.id, ID_W)}${col(wf, WF_W)}${col(r.status, STATUS_W)}${col(r.triggerKind ?? "—", TRIGGER_W)}${col(age(r.createdAt, now), AGE_W)}${duration(r.runtimeSeconds)}`);
|
|
88
88
|
}
|
|
89
89
|
return lines;
|
|
@@ -94,7 +94,7 @@ export function formatRunDetail(run, now) {
|
|
|
94
94
|
const lines = [
|
|
95
95
|
`Run ${run.id}`,
|
|
96
96
|
"",
|
|
97
|
-
field("Workflow", run.
|
|
97
|
+
field("Workflow", run.workflowSlug ?? run.workflowId),
|
|
98
98
|
field("Status", status),
|
|
99
99
|
field("Trigger", run.triggerKind ?? "—"),
|
|
100
100
|
field("Created", `${isoUtc(run.createdAt)} (${age(run.createdAt, now)} ago)`),
|
package/dist/deployment.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type BuiltArtifact } from "./artifact.js";
|
|
2
2
|
import type { BoardwalkClient, WorkflowSummary } from "./client.js";
|
|
3
3
|
export interface PreparedProgram {
|
|
4
|
-
|
|
4
|
+
slug: string;
|
|
5
5
|
/** Entry module inside the artifact (e.g. `index.mjs`). */
|
|
6
6
|
entry: string;
|
|
7
7
|
/** The built, content-addressed program artifact (tarball + digest + metadata). */
|
|
@@ -9,17 +9,17 @@ export interface PreparedProgram {
|
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* Resolve a target path to its deployable artifact: build the program (bundle + assets → tarball,
|
|
12
|
-
* content-addressed) and extract `meta.
|
|
12
|
+
* content-addressed) and extract `meta.slug` from the bundled entry for the deploy identity.
|
|
13
13
|
*/
|
|
14
14
|
export declare function loadProgram(file: string): Promise<PreparedProgram>;
|
|
15
15
|
export interface DeployPlan {
|
|
16
16
|
action: "create" | "update";
|
|
17
|
-
|
|
17
|
+
slug: string;
|
|
18
18
|
/** Present only for `update` — the existing workflow id to PUT. */
|
|
19
19
|
workflowId?: string;
|
|
20
20
|
}
|
|
21
|
-
/** Decide create vs update by matching the program
|
|
22
|
-
export declare function planDeploy(existing: readonly WorkflowSummary[],
|
|
21
|
+
/** Decide create vs update by matching the program slug against the org's existing workflows. */
|
|
22
|
+
export declare function planDeploy(existing: readonly WorkflowSummary[], slug: string): DeployPlan;
|
|
23
23
|
export interface DeployResultSummary {
|
|
24
24
|
workflowId: string;
|
|
25
25
|
orgSlug: string;
|
package/dist/deployment.js
CHANGED
|
@@ -15,19 +15,19 @@ import { buildArtifact } from "./artifact.js";
|
|
|
15
15
|
import { projectDirFor, readLink, writeLink } from "./project.js";
|
|
16
16
|
/**
|
|
17
17
|
* Resolve a target path to its deployable artifact: build the program (bundle + assets → tarball,
|
|
18
|
-
* content-addressed) and extract `meta.
|
|
18
|
+
* content-addressed) and extract `meta.slug` from the bundled entry for the deploy identity.
|
|
19
19
|
*/
|
|
20
20
|
export async function loadProgram(file) {
|
|
21
21
|
const artifact = await buildArtifact(file);
|
|
22
|
-
const
|
|
23
|
-
return {
|
|
22
|
+
const slug = extractWorkflowSlug(artifact.entrySource, artifact.entry);
|
|
23
|
+
return { slug, entry: artifact.entry, artifact };
|
|
24
24
|
}
|
|
25
|
-
/** Decide create vs update by matching the program
|
|
26
|
-
export function planDeploy(existing,
|
|
27
|
-
const match = existing.find((w) => w.
|
|
25
|
+
/** Decide create vs update by matching the program slug against the org's existing workflows. */
|
|
26
|
+
export function planDeploy(existing, slug) {
|
|
27
|
+
const match = existing.find((w) => w.slug === slug);
|
|
28
28
|
return match !== undefined
|
|
29
|
-
? { action: "update",
|
|
30
|
-
: { action: "create",
|
|
29
|
+
? { action: "update", slug, workflowId: match.id }
|
|
30
|
+
: { action: "create", slug };
|
|
31
31
|
}
|
|
32
32
|
/** The artifact reference recorded on the version (everything but the bytes, which go to storage). */
|
|
33
33
|
function refOf(artifact) {
|
|
@@ -56,7 +56,7 @@ export async function deployWithLink(client, ctx) {
|
|
|
56
56
|
// Unlinked: adopt an existing workflow with the same name, if any (so a second machine re-links
|
|
57
57
|
// instead of creating a duplicate). Otherwise we'll create below.
|
|
58
58
|
if (workflowId === null) {
|
|
59
|
-
const match = (await client.listWorkflows(orgSlug)).find((w) => w.
|
|
59
|
+
const match = (await client.listWorkflows(orgSlug)).find((w) => w.slug === ctx.prog.slug);
|
|
60
60
|
if (match !== undefined) {
|
|
61
61
|
workflowId = match.id;
|
|
62
62
|
outcome = "adopted";
|
package/dist/dev/engine.d.ts
CHANGED
|
@@ -10,12 +10,12 @@ export interface DevRunResult {
|
|
|
10
10
|
export interface DevEngine {
|
|
11
11
|
/** Subscribe to the run's stamped events (the envelope is already applied). */
|
|
12
12
|
onEvent(listener: (event: RunEvent) => void): () => void;
|
|
13
|
-
/** Deploy the bundled program; returns the derived workflow
|
|
13
|
+
/** Deploy the bundled program; returns the derived workflow slug. */
|
|
14
14
|
deploy(program: string): {
|
|
15
|
-
|
|
15
|
+
slug: string;
|
|
16
16
|
};
|
|
17
17
|
/** Queue + dispatch a run; returns its id immediately. */
|
|
18
|
-
start(
|
|
18
|
+
start(slug: string, input: JsonValue | undefined): {
|
|
19
19
|
id: string;
|
|
20
20
|
};
|
|
21
21
|
/** Resolve when the run reaches a terminal status. */
|
package/dist/dev/engine.js
CHANGED
|
@@ -23,10 +23,10 @@ export const createDevEngine = (opts) => {
|
|
|
23
23
|
}),
|
|
24
24
|
deploy: (program) => {
|
|
25
25
|
const workflow = engine.deployWorkflow({ program });
|
|
26
|
-
return {
|
|
26
|
+
return { slug: workflow.slug };
|
|
27
27
|
},
|
|
28
|
-
start: (
|
|
29
|
-
const run = engine.startRun(
|
|
28
|
+
start: (slug, input) => {
|
|
29
|
+
const run = engine.startRun(slug, input !== undefined ? { input } : {});
|
|
30
30
|
return { id: run.id };
|
|
31
31
|
},
|
|
32
32
|
wait: async (runId) => {
|
package/dist/dev/engine.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/dev/engine.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAE/B,uCAAuC;AACvC,EAAE;AACF,8FAA8F;AAC9F,8FAA8F;AAC9F,gGAAgG;AAChG,6FAA6F;AAC7F,oEAAoE;AACpE,EAAE;AACF,4FAA4F;AAC5F,6FAA6F;AAE7F,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAsChD,wEAAwE;AACxE,MAAM,CAAC,MAAM,eAAe,GAAqB,CAAC,IAAI,EAAE,EAAE;IACxD,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC;QACxB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACxB,CAAC,CAAC;IACH,OAAO;QACL,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE,CACpB,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;YACrB,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC,CAAC;QACJ,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE;YAClB,MAAM,QAAQ,GAAG,MAAM,CAAC,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;YACpD,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;QACjC,CAAC;QACD,KAAK,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/dev/engine.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAE/B,uCAAuC;AACvC,EAAE;AACF,8FAA8F;AAC9F,8FAA8F;AAC9F,gGAAgG;AAChG,6FAA6F;AAC7F,oEAAoE;AACpE,EAAE;AACF,4FAA4F;AAC5F,6FAA6F;AAE7F,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAsChD,wEAAwE;AACxE,MAAM,CAAC,MAAM,eAAe,GAAqB,CAAC,IAAI,EAAE,EAAE;IACxD,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC;QACxB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACxB,CAAC,CAAC;IACH,OAAO;QACL,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE,CACpB,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;YACrB,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC,CAAC;QACJ,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE;YAClB,MAAM,QAAQ,GAAG,MAAM,CAAC,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;YACpD,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;QACjC,CAAC;QACD,KAAK,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YACrB,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACxE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC;QACxB,CAAC;QACD,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YACpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YAC3C,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC;QACtE,CAAC;QACD,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC;QAC1C,KAAK,EAAE,GAAG,EAAE;YACV,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC,CAAC"}
|
package/dist/project.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Project link — the Vercel-style tie between a local workflow directory and its deployed workflow.
|
|
3
3
|
//
|
|
4
4
|
// `<projectDir>/.boardwalk/project.json` (gitignored) stores `{ orgSlug, workflowId }`. Once linked,
|
|
5
|
-
// `deploy`/`run` update the workflow BY ID — so renaming `meta.
|
|
5
|
+
// `deploy`/`run` update the workflow BY ID — so renaming `meta.slug` (or the entry file) just
|
|
6
6
|
// updates the same workflow instead of forking a new one, and `--org` is no longer needed. The id is
|
|
7
7
|
// environment-specific (a dev workflow id ≠ prod), so the link is gitignored, not committed.
|
|
8
8
|
//
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boardwalk-labs/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "The boardwalk CLI: author, validate, run, and deploy Boardwalk workflows.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"boardwalk": "tsx src/index.ts"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@boardwalk-labs/engine": "^0.1.
|
|
38
|
+
"@boardwalk-labs/engine": "^0.1.5",
|
|
39
39
|
"@boardwalk-labs/workflow": "^0.1.5",
|
|
40
40
|
"commander": "^13.1.0",
|
|
41
41
|
"env-paths": "^3.0.0",
|