robodev 0.39.0 → 0.40.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/package.json +1 -1
- package/src/emit-starter.test.ts +40 -0
- package/src/index.ts +39 -29
- package/src/parse-create-args.test.ts +86 -4
- package/src/parse-create-args.ts +51 -14
- package/templates/auth-chat/package.json +1 -1
- package/templates/backend/package.json +1 -1
- package/templates/catalog.json +1 -2
- package/templates/empty/README.md +8 -6
- package/templates/empty/index.html +12 -0
- package/templates/empty/package.json +10 -4
- package/templates/empty/src/App.tsx +8 -0
- package/templates/empty/src/app.css +10 -0
- package/templates/empty/src/main.tsx +5 -0
- package/templates/empty/tsconfig.json +2 -1
- package/templates/empty/vite.config.ts +5 -0
- package/templates/marketplace/package.json +1 -1
- package/templates/space/package.json +1 -1
package/package.json
CHANGED
package/src/emit-starter.test.ts
CHANGED
|
@@ -114,6 +114,46 @@ test("collectFiles for marketplace starter is Tiny APIs without apps/fe", async
|
|
|
114
114
|
assert.ok(files.length < 200);
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
+
test("emitStarter empty includes hosted Welcome files", async () => {
|
|
118
|
+
const dest = await mkdtemp(join(tmpdir(), "rd-emit-"));
|
|
119
|
+
try {
|
|
120
|
+
await emitStarter(join(cliRoot(), "..", "starters", "empty"), dest, {
|
|
121
|
+
name: "Blank",
|
|
122
|
+
packageName: "blank-app",
|
|
123
|
+
title: "Empty",
|
|
124
|
+
versions,
|
|
125
|
+
});
|
|
126
|
+
const index = await readFile(join(dest, "index.html"), "utf8");
|
|
127
|
+
assert.match(index, /<title>Blank<\/title>/);
|
|
128
|
+
assert.match(index, /src\/main\.tsx/);
|
|
129
|
+
const main = await readFile(join(dest, "src/main.tsx"), "utf8");
|
|
130
|
+
assert.match(main, /createRoot/);
|
|
131
|
+
const pkg = JSON.parse(await readFile(join(dest, "package.json"), "utf8")) as {
|
|
132
|
+
name: string;
|
|
133
|
+
scripts?: Record<string, string>;
|
|
134
|
+
dependencies?: Record<string, string>;
|
|
135
|
+
};
|
|
136
|
+
assert.equal(pkg.name, "blank-app");
|
|
137
|
+
assert.equal(pkg.scripts?.dev, "robodev dev");
|
|
138
|
+
assert.equal(pkg.scripts?.["dev:fe"], "vite");
|
|
139
|
+
assert.equal(pkg.dependencies?.["@robodev-ai/client"], "^0.12.0");
|
|
140
|
+
} finally {
|
|
141
|
+
await rm(dest, { recursive: true, force: true });
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("collectFiles for empty starter includes hosted Welcome source", async () => {
|
|
146
|
+
const files = await collectFiles(join(cliRoot(), "..", "starters", "empty"));
|
|
147
|
+
const paths = new Set(files.map((file) => file.path));
|
|
148
|
+
assert.ok(paths.has("robodev/database.ts"));
|
|
149
|
+
assert.ok(paths.has("robodev/api/health.ts"));
|
|
150
|
+
assert.ok(paths.has("index.html"));
|
|
151
|
+
assert.ok(paths.has("src/main.tsx"));
|
|
152
|
+
assert.ok(!paths.has("robodev/api/planets.ts"));
|
|
153
|
+
assert.ok([...paths].every((path) => !path.startsWith("apps/")));
|
|
154
|
+
assert.ok(files.length < 200);
|
|
155
|
+
});
|
|
156
|
+
|
|
117
157
|
test("collectFiles for space starter is Tiny APIs without apps/fe", async () => {
|
|
118
158
|
const files = await collectFiles(join(cliRoot(), "..", "starters", "space"));
|
|
119
159
|
const paths = new Set(files.map((file) => file.path));
|
package/src/index.ts
CHANGED
|
@@ -52,7 +52,7 @@ Commands:
|
|
|
52
52
|
logout Remove stored credentials
|
|
53
53
|
whoami Show the signed-in user
|
|
54
54
|
projects List your projects
|
|
55
|
-
create [path] [--starter <id>] [--empty] [--org <id>] Create a project, scaffold a starter, and deploy
|
|
55
|
+
create [path] [--template <id>] [--starter <id>] [--empty] [--org <id>] Create a project, scaffold a starter, and deploy
|
|
56
56
|
link [projectId] Write robodev/.robodev in the current folder
|
|
57
57
|
deploy [--force] [--name <name>] [--org <id>] Deploy schema, APIs, jobs, and frontend (creates and links if unlinked)
|
|
58
58
|
dev [options] Run this project's APIs locally on an embedded Postgres, no Starbase needed
|
|
@@ -397,40 +397,47 @@ async function resolveOrganization(flagId: string | undefined): Promise<string |
|
|
|
397
397
|
return found.id;
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
-
async function resolveStarter(
|
|
400
|
+
async function resolveStarter(parsed: {
|
|
401
|
+
starterId?: string;
|
|
402
|
+
promptTemplate?: boolean;
|
|
403
|
+
}): Promise<Starter> {
|
|
401
404
|
const starters = await loadCatalog();
|
|
402
|
-
if (
|
|
403
|
-
const found = starters.find((starter) => starter.id ===
|
|
405
|
+
if (parsed.starterId !== undefined) {
|
|
406
|
+
const found = starters.find((starter) => starter.id === parsed.starterId);
|
|
404
407
|
if (!found) {
|
|
405
|
-
throw new Error(`Unknown starter "${
|
|
408
|
+
throw new Error(`Unknown starter "${parsed.starterId}". ${validIdsMessage(starters)}`);
|
|
406
409
|
}
|
|
407
410
|
return found;
|
|
408
411
|
}
|
|
409
|
-
if (
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
412
|
+
if (parsed.promptTemplate) {
|
|
413
|
+
if (stdin.isTTY) {
|
|
414
|
+
console.log("Starters:");
|
|
415
|
+
starters.forEach((starter, i) => {
|
|
416
|
+
console.log(` ${i + 1}. ${starter.title} (${starter.id})`);
|
|
417
|
+
console.log(` ${starter.description}`);
|
|
418
|
+
});
|
|
419
|
+
const answer = (await prompt("Starter number or id: ")).trim();
|
|
420
|
+
if (!answer) {
|
|
421
|
+
throw new Error(`Unknown starter "". ${validIdsMessage(starters)}`);
|
|
422
|
+
}
|
|
423
|
+
const asIndex = Number(answer);
|
|
424
|
+
if (Number.isInteger(asIndex) && asIndex >= 1 && asIndex <= starters.length) {
|
|
425
|
+
return starters[asIndex - 1];
|
|
426
|
+
}
|
|
427
|
+
const found = starters.find((starter) => starter.id === answer);
|
|
428
|
+
if (!found) {
|
|
429
|
+
throw new Error(`Unknown starter "${answer}". ${validIdsMessage(starters)}`);
|
|
430
|
+
}
|
|
431
|
+
return found;
|
|
426
432
|
}
|
|
427
|
-
|
|
433
|
+
console.log("robodev create --template requires a starter id when stdin is not a TTY.");
|
|
434
|
+
usage();
|
|
428
435
|
}
|
|
429
|
-
const
|
|
430
|
-
if (!
|
|
431
|
-
throw new Error(`Unknown starter "
|
|
436
|
+
const empty = starters.find((starter) => starter.id === "empty");
|
|
437
|
+
if (!empty) {
|
|
438
|
+
throw new Error(`Unknown starter "empty". ${validIdsMessage(starters)}`);
|
|
432
439
|
}
|
|
433
|
-
return
|
|
440
|
+
return empty;
|
|
434
441
|
}
|
|
435
442
|
|
|
436
443
|
function projectNameFromPath(root: string): string {
|
|
@@ -528,7 +535,7 @@ function printFeNextSteps(displayPath: string, hosted: boolean): void {
|
|
|
528
535
|
/** Creates a Starbase project, copies the chosen starter, links, deploys, and installs deps. */
|
|
529
536
|
async function runCreate(args: string[]): Promise<void> {
|
|
530
537
|
const parsed = parseCreateArgs(args);
|
|
531
|
-
const starter = await resolveStarter(parsed
|
|
538
|
+
const starter = await resolveStarter(parsed);
|
|
532
539
|
console.log(`Using starter ${starter.title} (${starter.id})`);
|
|
533
540
|
|
|
534
541
|
const root = resolve(process.cwd(), parsed.path ?? ".");
|
|
@@ -623,7 +630,10 @@ async function runCreate(args: string[]): Promise<void> {
|
|
|
623
630
|
if (displayPath !== ".") {
|
|
624
631
|
console.log(` cd ${displayPath}`);
|
|
625
632
|
}
|
|
626
|
-
console.log("
|
|
633
|
+
console.log(" The project App URL serves the hosted Welcome page.");
|
|
634
|
+
console.log(" Edit index.html and src/, then robodev deploy (or Code + Save).");
|
|
635
|
+
console.log(" robodev dev # local APIs");
|
|
636
|
+
console.log(" npm run dev:fe # optional local Vite");
|
|
627
637
|
}
|
|
628
638
|
if (liveUrl) {
|
|
629
639
|
printLiveUrl(liveUrl);
|
|
@@ -2,17 +2,86 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import { test } from "node:test";
|
|
3
3
|
import { parseCreateArgs } from "./parse-create-args.js";
|
|
4
4
|
|
|
5
|
-
test("parseCreateArgs
|
|
5
|
+
test("parseCreateArgs defaults to empty without a template flag", () => {
|
|
6
|
+
assert.deepEqual(parseCreateArgs([]), {
|
|
7
|
+
path: undefined,
|
|
8
|
+
starterId: "empty",
|
|
9
|
+
orgId: undefined,
|
|
10
|
+
});
|
|
11
|
+
assert.deepEqual(parseCreateArgs(["my-app"]), {
|
|
12
|
+
path: "my-app",
|
|
13
|
+
starterId: "empty",
|
|
14
|
+
orgId: undefined,
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("parseCreateArgs accepts --template and --starter with an id", () => {
|
|
19
|
+
assert.deepEqual(parseCreateArgs(["app", "--template", "space", "--org", "org_1"]), {
|
|
20
|
+
path: "app",
|
|
21
|
+
starterId: "space",
|
|
22
|
+
orgId: "org_1",
|
|
23
|
+
});
|
|
24
|
+
assert.deepEqual(parseCreateArgs(["--template=auth-chat"]), {
|
|
25
|
+
path: undefined,
|
|
26
|
+
starterId: "auth-chat",
|
|
27
|
+
orgId: undefined,
|
|
28
|
+
});
|
|
6
29
|
assert.deepEqual(parseCreateArgs(["app", "--starter", "space", "--org", "org_1"]), {
|
|
7
30
|
path: "app",
|
|
8
31
|
starterId: "space",
|
|
9
32
|
orgId: "org_1",
|
|
10
33
|
});
|
|
34
|
+
assert.deepEqual(parseCreateArgs(["--starter=backend"]), {
|
|
35
|
+
path: undefined,
|
|
36
|
+
starterId: "backend",
|
|
37
|
+
orgId: undefined,
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("parseCreateArgs treats bare --template and --starter as a picker request", () => {
|
|
42
|
+
assert.deepEqual(parseCreateArgs(["--template"]), {
|
|
43
|
+
path: undefined,
|
|
44
|
+
starterId: undefined,
|
|
45
|
+
orgId: undefined,
|
|
46
|
+
promptTemplate: true,
|
|
47
|
+
});
|
|
48
|
+
assert.deepEqual(parseCreateArgs(["--template="]), {
|
|
49
|
+
path: undefined,
|
|
50
|
+
starterId: undefined,
|
|
51
|
+
orgId: undefined,
|
|
52
|
+
promptTemplate: true,
|
|
53
|
+
});
|
|
54
|
+
assert.deepEqual(parseCreateArgs(["my-app", "--template", "--org", "org_1"]), {
|
|
55
|
+
path: "my-app",
|
|
56
|
+
starterId: undefined,
|
|
57
|
+
orgId: "org_1",
|
|
58
|
+
promptTemplate: true,
|
|
59
|
+
});
|
|
60
|
+
assert.deepEqual(parseCreateArgs(["--starter"]), {
|
|
61
|
+
path: undefined,
|
|
62
|
+
starterId: undefined,
|
|
63
|
+
orgId: undefined,
|
|
64
|
+
promptTemplate: true,
|
|
65
|
+
});
|
|
66
|
+
assert.deepEqual(parseCreateArgs(["--starter="]), {
|
|
67
|
+
path: undefined,
|
|
68
|
+
starterId: undefined,
|
|
69
|
+
orgId: undefined,
|
|
70
|
+
promptTemplate: true,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("parseCreateArgs treats --empty as empty", () => {
|
|
11
75
|
assert.deepEqual(parseCreateArgs(["--empty", "--org=org_2"]), {
|
|
12
76
|
path: undefined,
|
|
13
77
|
starterId: "empty",
|
|
14
78
|
orgId: "org_2",
|
|
15
79
|
});
|
|
80
|
+
assert.deepEqual(parseCreateArgs(["--template", "empty"]), {
|
|
81
|
+
path: undefined,
|
|
82
|
+
starterId: "empty",
|
|
83
|
+
orgId: undefined,
|
|
84
|
+
});
|
|
16
85
|
});
|
|
17
86
|
|
|
18
87
|
test("parseCreateArgs rejects a missing --org value", () => {
|
|
@@ -21,13 +90,26 @@ test("parseCreateArgs rejects a missing --org value", () => {
|
|
|
21
90
|
assert.throws(() => parseCreateArgs(["--org", "--empty"]), /Missing value for --org/);
|
|
22
91
|
});
|
|
23
92
|
|
|
24
|
-
test("parseCreateArgs rejects unknown flags including --organization", () => {
|
|
93
|
+
test("parseCreateArgs rejects unknown flags including --organization and --empty=", () => {
|
|
25
94
|
assert.throws(() => parseCreateArgs(["--organization", "org_1"]), /Unknown flag --organization/);
|
|
95
|
+
assert.throws(() => parseCreateArgs(["--empty=empty"]), /Unknown flag --empty=empty/);
|
|
26
96
|
});
|
|
27
97
|
|
|
28
|
-
test("parseCreateArgs rejects
|
|
98
|
+
test("parseCreateArgs rejects two starter selectors", () => {
|
|
29
99
|
assert.throws(
|
|
30
100
|
() => parseCreateArgs(["--empty", "--starter", "empty"]),
|
|
31
|
-
/Use
|
|
101
|
+
/Use only one of --empty, --template, or --starter\./,
|
|
102
|
+
);
|
|
103
|
+
assert.throws(
|
|
104
|
+
() => parseCreateArgs(["--empty", "--template", "empty"]),
|
|
105
|
+
/Use only one of --empty, --template, or --starter\./,
|
|
106
|
+
);
|
|
107
|
+
assert.throws(
|
|
108
|
+
() => parseCreateArgs(["--template", "space", "--starter", "space"]),
|
|
109
|
+
/Use only one of --empty, --template, or --starter\./,
|
|
110
|
+
);
|
|
111
|
+
assert.throws(
|
|
112
|
+
() => parseCreateArgs(["--template", "--empty"]),
|
|
113
|
+
/Use only one of --empty, --template, or --starter\./,
|
|
32
114
|
);
|
|
33
115
|
});
|
package/src/parse-create-args.ts
CHANGED
|
@@ -1,31 +1,64 @@
|
|
|
1
|
-
export type CreateArgs = {
|
|
1
|
+
export type CreateArgs = {
|
|
2
|
+
path?: string;
|
|
3
|
+
starterId?: string;
|
|
4
|
+
orgId?: string;
|
|
5
|
+
promptTemplate?: boolean;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
function takeOptionalId(
|
|
9
|
+
args: string[],
|
|
10
|
+
i: number,
|
|
11
|
+
): { value?: string; prompt: boolean; consumed: number } {
|
|
12
|
+
const next = args[i + 1];
|
|
13
|
+
if (!next || next.startsWith("-")) {
|
|
14
|
+
return { prompt: true, consumed: 0 };
|
|
15
|
+
}
|
|
16
|
+
return { value: next, prompt: false, consumed: 1 };
|
|
17
|
+
}
|
|
2
18
|
|
|
3
19
|
export function parseCreateArgs(args: string[]): CreateArgs {
|
|
4
20
|
let path: string | undefined;
|
|
5
21
|
let starterId: string | undefined;
|
|
6
22
|
let orgId: string | undefined;
|
|
7
23
|
let empty = false;
|
|
24
|
+
let sawTemplate = false;
|
|
25
|
+
let sawStarter = false;
|
|
26
|
+
let promptTemplate = false;
|
|
27
|
+
|
|
8
28
|
for (let i = 0; i < args.length; i++) {
|
|
9
29
|
const arg = args[i];
|
|
10
30
|
if (arg === "--empty") {
|
|
11
31
|
empty = true;
|
|
12
32
|
continue;
|
|
13
33
|
}
|
|
34
|
+
if (arg === "--template") {
|
|
35
|
+
sawTemplate = true;
|
|
36
|
+
const taken = takeOptionalId(args, i);
|
|
37
|
+
if (taken.prompt) promptTemplate = true;
|
|
38
|
+
else starterId = taken.value;
|
|
39
|
+
i += taken.consumed;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (arg.startsWith("--template=")) {
|
|
43
|
+
sawTemplate = true;
|
|
44
|
+
const value = arg.slice("--template=".length);
|
|
45
|
+
if (!value) promptTemplate = true;
|
|
46
|
+
else starterId = value;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
14
49
|
if (arg === "--starter") {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
i += 1;
|
|
50
|
+
sawStarter = true;
|
|
51
|
+
const taken = takeOptionalId(args, i);
|
|
52
|
+
if (taken.prompt) promptTemplate = true;
|
|
53
|
+
else starterId = taken.value;
|
|
54
|
+
i += taken.consumed;
|
|
21
55
|
continue;
|
|
22
56
|
}
|
|
23
57
|
if (arg.startsWith("--starter=")) {
|
|
58
|
+
sawStarter = true;
|
|
24
59
|
const value = arg.slice("--starter=".length);
|
|
25
|
-
if (!value)
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
starterId = value;
|
|
60
|
+
if (!value) promptTemplate = true;
|
|
61
|
+
else starterId = value;
|
|
29
62
|
continue;
|
|
30
63
|
}
|
|
31
64
|
if (arg === "--org") {
|
|
@@ -53,11 +86,15 @@ export function parseCreateArgs(args: string[]): CreateArgs {
|
|
|
53
86
|
}
|
|
54
87
|
path = arg;
|
|
55
88
|
}
|
|
56
|
-
|
|
57
|
-
|
|
89
|
+
|
|
90
|
+
const selectors = Number(empty) + Number(sawTemplate) + Number(sawStarter);
|
|
91
|
+
if (selectors > 1) {
|
|
92
|
+
throw new Error("Use only one of --empty, --template, or --starter.");
|
|
58
93
|
}
|
|
59
94
|
if (empty) {
|
|
60
95
|
starterId = "empty";
|
|
96
|
+
} else if (!sawTemplate && !sawStarter) {
|
|
97
|
+
starterId = "empty";
|
|
61
98
|
}
|
|
62
|
-
return { path, starterId, orgId };
|
|
99
|
+
return { path, starterId, orgId, ...(promptTemplate ? { promptTemplate: true } : {}) };
|
|
63
100
|
}
|
package/templates/catalog.json
CHANGED
|
@@ -24,8 +24,7 @@
|
|
|
24
24
|
{
|
|
25
25
|
"id": "empty",
|
|
26
26
|
"title": "Empty",
|
|
27
|
-
"description": "Blank database.ts and GET /api/health
|
|
28
|
-
"kind": "backend"
|
|
27
|
+
"description": "Blank database.ts and GET /api/health plus a hosted Welcome page. No sample schema."
|
|
29
28
|
}
|
|
30
29
|
]
|
|
31
30
|
}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
# {{NAME}}
|
|
2
2
|
|
|
3
|
-
Blank `robodev/database.ts` and `GET /api/health`
|
|
3
|
+
Blank `robodev/database.ts` and `GET /api/health` plus a hosted Welcome page. No sample schema.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The project App URL serves the hosted Welcome page. Edit `index.html` and `src/`, then `robodev deploy` (or Code + Save).
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
8
|
+
robodev create my-app
|
|
9
|
+
robodev dev # local APIs
|
|
10
|
+
npm run dev:fe # optional local Vite
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
`robodev create` (TTY and non-TTY) uses this starter by default. Pass `--template space` for Space. Lovable, Bolt, or v0 should use `--template backend`.
|
|
12
14
|
|
|
13
|
-
Do not
|
|
15
|
+
Do not write a `frontend` block in `robodev/.robodev` — that block is for Tiny `apps/fe`. Root `index.html` + `src/` deploy as hosted React.
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
Docs: https://robodev.povio.dev/docs
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{NAME}}</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -5,13 +5,19 @@
|
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "robodev dev",
|
|
8
|
-
"deploy": "robodev deploy"
|
|
8
|
+
"deploy": "robodev deploy",
|
|
9
|
+
"dev:fe": "vite",
|
|
10
|
+
"build": "vite build"
|
|
9
11
|
},
|
|
10
12
|
"dependencies": {
|
|
11
|
-
"@robodev-ai/sdk": "^0.13.0"
|
|
13
|
+
"@robodev-ai/sdk": "^0.13.0",
|
|
14
|
+
"@robodev-ai/client": "^0.5.0",
|
|
15
|
+
"react": "^18.3.1",
|
|
16
|
+
"react-dom": "^18.3.1"
|
|
12
17
|
},
|
|
13
18
|
"devDependencies": {
|
|
14
|
-
"robodev": "^0.
|
|
15
|
-
"typescript": "^5.9.2"
|
|
19
|
+
"robodev": "^0.40.0",
|
|
20
|
+
"typescript": "^5.9.2",
|
|
21
|
+
"vite": "^6.0.0"
|
|
16
22
|
}
|
|
17
23
|
}
|