@superblocksteam/cli 1.6.0 → 1.7.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/README.md +1 -1
- package/assets/custom-components/setup/package.json +1 -1
- package/dist/commands/login.js +1 -1
- package/dist/commands/pull.js +17 -2
- package/dist/commands/push.js +41 -3
- package/dist/common/version-control.d.ts +2 -0
- package/dist/common/version-control.js +135 -2
- package/oclif.manifest.json +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ $ npm install -g @superblocksteam/cli
|
|
|
12
12
|
$ superblocks COMMAND
|
|
13
13
|
running command...
|
|
14
14
|
$ superblocks (--version)
|
|
15
|
-
@superblocksteam/cli/1.
|
|
15
|
+
@superblocksteam/cli/1.7.0 linux-x64 node-v18.20.2
|
|
16
16
|
$ superblocks --help [COMMAND]
|
|
17
17
|
USAGE
|
|
18
18
|
$ superblocks COMMAND
|
package/dist/commands/login.js
CHANGED
|
@@ -39,7 +39,7 @@ class Login extends core_1.Command {
|
|
|
39
39
|
this.log((0, colorette_1.green)(`Welcome to the Superblocks 🐨 CLI ${user.user.name}!`));
|
|
40
40
|
}
|
|
41
41
|
catch (error) {
|
|
42
|
-
if (error
|
|
42
|
+
if (error instanceof util_1.FileAccessError) {
|
|
43
43
|
this.log((0, colorette_1.red)("Could not save token, ensure the Superblocks CLI has access to create folders in your home directory."));
|
|
44
44
|
return;
|
|
45
45
|
}
|
package/dist/commands/pull.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
5
5
|
const core_1 = require("@oclif/core");
|
|
6
|
+
const sdk_1 = require("@superblocksteam/sdk");
|
|
6
7
|
const util_1 = require("@superblocksteam/util");
|
|
7
8
|
const listr2_1 = require("listr2");
|
|
8
9
|
const authenticated_command_1 = require("../common/authenticated-command");
|
|
@@ -128,14 +129,26 @@ Would you like to also delete these resources from your filesystem?`,
|
|
|
128
129
|
var _a, _b;
|
|
129
130
|
task.title = `Validating git configuration...`;
|
|
130
131
|
const subtasks = [];
|
|
132
|
+
ctx.resourceIdsToSkip = new Set();
|
|
131
133
|
for (const resourceId of ctx.resourceIdsToPull) {
|
|
132
134
|
const resource = (_a = ctx.existingSuperblocksRootConfig) === null || _a === void 0 ? void 0 : _a.resources[resourceId];
|
|
133
135
|
const resourceTitle = `${((_b = resource.resourceType) !== null && _b !== void 0 ? _b : "").toLowerCase()} ${resourceId}`;
|
|
134
136
|
subtasks.push({
|
|
135
137
|
title: `Checking ${resourceTitle}...`,
|
|
136
138
|
task: async () => {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
+
try {
|
|
140
|
+
const { branchName } = await this.validateGitSetup(resource === null || resource === void 0 ? void 0 : resource.resourceType, resourceId, util_1.ComponentEvent.PULL, ctx.localBranchName);
|
|
141
|
+
ctx.branchToPullFrom.set(resourceId, branchName);
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
if ((0, version_control_1.isCI)() && error instanceof sdk_1.ValidateGitSetupError) {
|
|
145
|
+
this.log(`WARN: Failed to validate git setup for ${resourceTitle}. Skipping pull.\n\n${error.message}.`);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
ctx.resourceIdsToSkip.add(resourceId);
|
|
151
|
+
}
|
|
139
152
|
},
|
|
140
153
|
});
|
|
141
154
|
}
|
|
@@ -149,6 +162,8 @@ Would you like to also delete these resources from your filesystem?`,
|
|
|
149
162
|
var _a, _b;
|
|
150
163
|
task.title = `Pulling resources from branch ${ctx.localBranchName}...`;
|
|
151
164
|
const viewMode = await (0, version_control_1.getMode)(task, mode);
|
|
165
|
+
// Remove resources to skip from list of resources to push
|
|
166
|
+
ctx.resourceIdsToPull = ctx.resourceIdsToPull.filter((id) => !ctx.resourceIdsToSkip.has(id));
|
|
152
167
|
const subtasks = [];
|
|
153
168
|
for (const resourceId of ctx.resourceIdsToPull) {
|
|
154
169
|
const resource = (_a = ctx.existingSuperblocksRootConfig) === null || _a === void 0 ? void 0 : _a.resources[resourceId];
|
package/dist/commands/push.js
CHANGED
|
@@ -135,6 +135,7 @@ Would you like to also delete these resources from your filesystem?`,
|
|
|
135
135
|
var _a, _b;
|
|
136
136
|
task.title = `Validating git configuration...`;
|
|
137
137
|
const subtasks = [];
|
|
138
|
+
ctx.resourceIdsToSkip = new Set();
|
|
138
139
|
for (const resourceId of ctx.resourceIdsToPush) {
|
|
139
140
|
const resource = (_a = ctx.existingSuperblocksRootConfig) === null || _a === void 0 ? void 0 : _a.resources[resourceId];
|
|
140
141
|
// for user messages:
|
|
@@ -142,8 +143,43 @@ Would you like to also delete these resources from your filesystem?`,
|
|
|
142
143
|
subtasks.push({
|
|
143
144
|
title: `Checking ${resourceTitle}...`,
|
|
144
145
|
task: async () => {
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
try {
|
|
147
|
+
const { branchName } = await this.validateGitSetup(resource === null || resource === void 0 ? void 0 : resource.resourceType, resourceId, util_1.ComponentEvent.PUSH, ctx.localBranchName);
|
|
148
|
+
ctx.branchToPushTo.set(resourceId, branchName);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
if ((0, version_control_1.isCI)() && error instanceof sdk_1.ValidateGitSetupError) {
|
|
152
|
+
this.log(`WARN: Failed to validate git setup for ${resourceTitle}. Skipping push.\n\n${error.message}.`);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
ctx.resourceIdsToSkip.add(resourceId);
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return task.newListr(subtasks, {
|
|
163
|
+
concurrent: true,
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
task: async (ctx, task) => {
|
|
169
|
+
var _a, _b;
|
|
170
|
+
task.title = `Validating project structure...`;
|
|
171
|
+
const subtasks = [];
|
|
172
|
+
const superblocksRootPath = node_path_1.default.resolve(node_path_1.default.dirname(ctx.superblocksRootConfigPath), "..");
|
|
173
|
+
for (const resourceId of ctx.resourceIdsToPush) {
|
|
174
|
+
const resource = (_a = ctx.existingSuperblocksRootConfig) === null || _a === void 0 ? void 0 : _a.resources[resourceId];
|
|
175
|
+
const resourceTitle = `${((_b = resource.resourceType) !== null && _b !== void 0 ? _b : "").toLowerCase()} at ${resource.location}`;
|
|
176
|
+
subtasks.push({
|
|
177
|
+
title: `Validating ${resourceTitle}...`,
|
|
178
|
+
task: async () => {
|
|
179
|
+
const validationError = await (0, version_control_1.validateLocalResource)(superblocksRootPath, resource);
|
|
180
|
+
if (validationError) {
|
|
181
|
+
this.error(`Push failed for resource '${resource.location}' (id: ${resourceId}). ${validationError}`);
|
|
182
|
+
}
|
|
147
183
|
},
|
|
148
184
|
});
|
|
149
185
|
}
|
|
@@ -156,6 +192,8 @@ Would you like to also delete these resources from your filesystem?`,
|
|
|
156
192
|
task: async (ctx, task) => {
|
|
157
193
|
var _a;
|
|
158
194
|
task.title = `Pushing resources to branch ${ctx.localBranchName}...`;
|
|
195
|
+
// Remove resources to skip from list of resources to push
|
|
196
|
+
ctx.resourceIdsToPush = ctx.resourceIdsToPush.filter((id) => !ctx.resourceIdsToSkip.has(id));
|
|
159
197
|
const subtasks = [];
|
|
160
198
|
const superblocksRootPath = node_path_1.default.resolve(node_path_1.default.dirname(ctx.superblocksRootConfigPath), "..");
|
|
161
199
|
for (const resourceId of ctx.resourceIdsToPush) {
|
|
@@ -231,7 +269,7 @@ Would you like to also delete these resources from your filesystem?`,
|
|
|
231
269
|
});
|
|
232
270
|
}
|
|
233
271
|
catch (error) {
|
|
234
|
-
if (
|
|
272
|
+
if (error instanceof sdk_1.BranchNotCheckedOutError) {
|
|
235
273
|
this.log(`WARN: Workflow/Scheduled Job ${(0, version_control_1.extractApiName)(apiConfig)} failed to push, please check branch out in the Superblocks UI first in order to check this branch out for this resource.`);
|
|
236
274
|
}
|
|
237
275
|
else if (error instanceof sdk_1.CommitAlreadyExistsError) {
|
|
@@ -36,6 +36,8 @@ export declare function getCurrentGitBranchIfGit(): Promise<string | null>;
|
|
|
36
36
|
*/
|
|
37
37
|
export declare function getCurrentGitBranch(): Promise<string>;
|
|
38
38
|
export declare function getHeadCommit(branch: string): Promise<[string, string]>;
|
|
39
|
+
export declare function isCI(): boolean;
|
|
39
40
|
export declare function isGitRepoDirty(): Promise<boolean>;
|
|
40
41
|
export declare function extractApiName(api: ApiWrapper): string;
|
|
42
|
+
export declare function validateLocalResource(superblocksRootPath: string, resource: VersionedResourceConfig): Promise<string | undefined>;
|
|
41
43
|
export declare function deleteResourcesAndUpdateRootConfig(removedResourceIds: string[], existingSuperblocksRootConfig: SuperblocksMonorepoConfig, superblocksRootPath: string, superblocksRootConfigPath: string): Promise<void>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.deleteResourcesAndUpdateRootConfig = exports.extractApiName = exports.isGitRepoDirty = exports.getHeadCommit = exports.getCurrentGitBranch = exports.getCurrentGitBranchIfGit = exports.getLocalGitRepoState = exports.sortByKey = exports.getMode = exports.removeResourceFromDisk = exports.writeMultiPageApplicationToDisk = exports.writeResourceToDisk = exports.readApiFromDisk = exports.readMultiPageApplicationFromDisk = exports.readApplicationFromDisk = exports.getFileStructureType = exports.atLeastOneSelection = exports.MULTI_SELECT_PROMPT_HELP = exports.SELECT_PROMPT_HELP = exports.modeFlagToViewMode = exports.FileStructureType = exports.modeFlagValuesMap = exports.DEFAULT_BRANCH = exports.DEPLOYED_MODE = exports.MOST_RECENT_COMMIT_MODE = exports.LATEST_EDITS_MODE = void 0;
|
|
3
|
+
exports.deleteResourcesAndUpdateRootConfig = exports.validateLocalResource = exports.extractApiName = exports.isGitRepoDirty = exports.isCI = exports.getHeadCommit = exports.getCurrentGitBranch = exports.getCurrentGitBranchIfGit = exports.getLocalGitRepoState = exports.sortByKey = exports.getMode = exports.removeResourceFromDisk = exports.writeMultiPageApplicationToDisk = exports.writeResourceToDisk = exports.readApiFromDisk = exports.readMultiPageApplicationFromDisk = exports.readApplicationFromDisk = exports.getFileStructureType = exports.atLeastOneSelection = exports.MULTI_SELECT_PROMPT_HELP = exports.SELECT_PROMPT_HELP = exports.modeFlagToViewMode = exports.FileStructureType = exports.modeFlagValuesMap = exports.DEFAULT_BRANCH = exports.DEPLOYED_MODE = exports.MOST_RECENT_COMMIT_MODE = exports.LATEST_EDITS_MODE = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const https = tslib_1.__importStar(require("https"));
|
|
6
6
|
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
@@ -574,8 +574,12 @@ async function getHeadCommit(branch) {
|
|
|
574
574
|
return [headCommitId, headCommitMessage];
|
|
575
575
|
}
|
|
576
576
|
exports.getHeadCommit = getHeadCommit;
|
|
577
|
+
function isCI() {
|
|
578
|
+
return process.env.CI === "true";
|
|
579
|
+
}
|
|
580
|
+
exports.isCI = isCI;
|
|
577
581
|
async function isGitRepoDirty() {
|
|
578
|
-
if (
|
|
582
|
+
if (isCI()) {
|
|
579
583
|
// Skip dirtiness check in CI environments
|
|
580
584
|
return false;
|
|
581
585
|
}
|
|
@@ -600,6 +604,123 @@ function writeApi(api, originalApiName, appDirName) {
|
|
|
600
604
|
};
|
|
601
605
|
return handleApi();
|
|
602
606
|
}
|
|
607
|
+
function validateSinglePageApplication(applicationConfig, superblocksRootPath, location) {
|
|
608
|
+
// validate app level APIs
|
|
609
|
+
for (const apiName of Object.values(applicationConfig.apis)) {
|
|
610
|
+
const apiPath = node_path_1.default.resolve(superblocksRootPath, location, "apis", `${slugifyName(apiName)}.yaml`);
|
|
611
|
+
const validateApiError = validateYamlFile(apiPath);
|
|
612
|
+
if (validateApiError) {
|
|
613
|
+
return validateApiError;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
// validate page
|
|
617
|
+
const pagePath = node_path_1.default.resolve(superblocksRootPath, location, "page.yaml");
|
|
618
|
+
const validatePageError = validateYamlFile(pagePath);
|
|
619
|
+
if (validatePageError) {
|
|
620
|
+
return validatePageError;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
async function validateMultiPageApplication(applicationConfig, superblocksRootPath, location) {
|
|
624
|
+
var _a;
|
|
625
|
+
// validate app level APIs
|
|
626
|
+
for (const apiName of Object.values(applicationConfig.apis)) {
|
|
627
|
+
const apiPath = node_path_1.default.resolve(superblocksRootPath, location, "apis", `${slugifyName(apiName)}.yaml`);
|
|
628
|
+
const validateApiError = await validateYamlFile(apiPath);
|
|
629
|
+
if (validateApiError) {
|
|
630
|
+
return validateApiError;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
// validate pages
|
|
634
|
+
for (const page of Object.values((_a = applicationConfig.pages) !== null && _a !== void 0 ? _a : {})) {
|
|
635
|
+
const pagePath = node_path_1.default.resolve(superblocksRootPath, location, "pages", slugifyName(page.name), "page.yaml");
|
|
636
|
+
const validatePageError = await validateYamlFile(pagePath);
|
|
637
|
+
if (validatePageError) {
|
|
638
|
+
return validatePageError;
|
|
639
|
+
}
|
|
640
|
+
// validate page level APIs
|
|
641
|
+
for (const apiName of Object.values(page.apis)) {
|
|
642
|
+
const apiPath = node_path_1.default.resolve(superblocksRootPath, location, "pages", slugifyName(page.name), "apis", `${slugifyName(apiName)}.yaml`);
|
|
643
|
+
const validateApiError = await validateYamlFile(apiPath);
|
|
644
|
+
if (validateApiError) {
|
|
645
|
+
return validateApiError;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return undefined;
|
|
650
|
+
}
|
|
651
|
+
async function validateLocalResource(superblocksRootPath, resource) {
|
|
652
|
+
switch (resource.resourceType) {
|
|
653
|
+
case "APPLICATION": {
|
|
654
|
+
// make sure application config exists
|
|
655
|
+
const applicationConfigPath = node_path_1.default.resolve(superblocksRootPath, resource.location, util_2.RESOURCE_CONFIG_PATH);
|
|
656
|
+
if (!(await fs.pathExists(applicationConfigPath))) {
|
|
657
|
+
return `File ${relativeToCurrentDir(applicationConfigPath)} not found. Superblocks CLI commands cannot function without it.`;
|
|
658
|
+
}
|
|
659
|
+
let fileStructureType = undefined;
|
|
660
|
+
let applicationConfig = undefined;
|
|
661
|
+
try {
|
|
662
|
+
// make sure it's a well-formed application config
|
|
663
|
+
applicationConfig = await fs.readJSON(applicationConfigPath);
|
|
664
|
+
if (!applicationConfig) {
|
|
665
|
+
throw new Error();
|
|
666
|
+
}
|
|
667
|
+
fileStructureType =
|
|
668
|
+
getFileStructureTypeFromResourceConfig(applicationConfig);
|
|
669
|
+
}
|
|
670
|
+
catch {
|
|
671
|
+
return `File ${relativeToCurrentDir(applicationConfigPath)} is not a valid JSON file. Please be sure it's valid JSON and rerun the command.`;
|
|
672
|
+
}
|
|
673
|
+
const applicationYamlPath = node_path_1.default.resolve(superblocksRootPath, resource.location, "application.yaml");
|
|
674
|
+
// make sure application.yaml is a well-formed yaml file
|
|
675
|
+
try {
|
|
676
|
+
await readYamlFile(applicationYamlPath);
|
|
677
|
+
}
|
|
678
|
+
catch {
|
|
679
|
+
return `File ${relativeToCurrentDir(applicationYamlPath)} is not a valid YAML file. Please be sure it's valid YAML and rerun the command.`;
|
|
680
|
+
}
|
|
681
|
+
switch (fileStructureType) {
|
|
682
|
+
case FileStructureType.SINGLE_PAGE: {
|
|
683
|
+
validateSinglePageApplication(applicationConfig, superblocksRootPath, resource.location);
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
case FileStructureType.MULTI_PAGE: {
|
|
687
|
+
const validationError = validateMultiPageApplication(applicationConfig, superblocksRootPath, resource.location);
|
|
688
|
+
if (validationError) {
|
|
689
|
+
return validationError;
|
|
690
|
+
}
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
default: {
|
|
694
|
+
return `Invalid file structure type ${fileStructureType}`;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
case "BACKEND": {
|
|
700
|
+
// make sure the backend config exists
|
|
701
|
+
const backendConfigPath = node_path_1.default.resolve(superblocksRootPath, resource.location, util_2.RESOURCE_CONFIG_PATH);
|
|
702
|
+
if (!(await fs.pathExists(backendConfigPath))) {
|
|
703
|
+
return `File ${relativeToCurrentDir(backendConfigPath)} not found. Superblocks CLI commands cannot function without it.`;
|
|
704
|
+
}
|
|
705
|
+
// make sure it's a well-formed backend config
|
|
706
|
+
try {
|
|
707
|
+
await fs.readJSON(backendConfigPath);
|
|
708
|
+
}
|
|
709
|
+
catch {
|
|
710
|
+
return `File ${relativeToCurrentDir(backendConfigPath)} is not a valid JSON file. Please be sure it's valid JSON and rerun the command.`;
|
|
711
|
+
}
|
|
712
|
+
// make sure that api.yaml exists
|
|
713
|
+
const apiYamlPath = node_path_1.default.resolve(superblocksRootPath, resource.location, "api.yaml");
|
|
714
|
+
const validateYamlFileError = await validateYamlFile(apiYamlPath);
|
|
715
|
+
if (validateYamlFileError) {
|
|
716
|
+
return validateYamlFileError;
|
|
717
|
+
}
|
|
718
|
+
break;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return undefined;
|
|
722
|
+
}
|
|
723
|
+
exports.validateLocalResource = validateLocalResource;
|
|
603
724
|
async function deleteResourcesAndUpdateRootConfig(removedResourceIds, existingSuperblocksRootConfig, superblocksRootPath, superblocksRootConfigPath) {
|
|
604
725
|
for (const resourceId of removedResourceIds) {
|
|
605
726
|
const resource = existingSuperblocksRootConfig === null || existingSuperblocksRootConfig === void 0 ? void 0 : existingSuperblocksRootConfig.resources[resourceId];
|
|
@@ -612,3 +733,15 @@ async function deleteResourcesAndUpdateRootConfig(removedResourceIds, existingSu
|
|
|
612
733
|
await fs.writeFile(superblocksRootConfigPath, JSON.stringify(sortByKey(existingSuperblocksRootConfig), null, 2));
|
|
613
734
|
}
|
|
614
735
|
exports.deleteResourcesAndUpdateRootConfig = deleteResourcesAndUpdateRootConfig;
|
|
736
|
+
async function validateYamlFile(yamlPath) {
|
|
737
|
+
// make sure a yaml file is well-formed
|
|
738
|
+
try {
|
|
739
|
+
await readYamlFile(yamlPath);
|
|
740
|
+
}
|
|
741
|
+
catch {
|
|
742
|
+
return `File ${relativeToCurrentDir(yamlPath)} is not a valid YAML file. Please be sure it's valid YAML and rerun the command.`;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
function relativeToCurrentDir(applicationConfigPath) {
|
|
746
|
+
return `./${node_path_1.default.relative(process.cwd(), applicationConfigPath)}`;
|
|
747
|
+
}
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@superblocksteam/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Official Superblocks CLI",
|
|
5
5
|
"bin": {
|
|
6
6
|
"superblocks": "bin/run"
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
"@oclif/core": "^2.11.7",
|
|
19
19
|
"@oclif/plugin-help": "^5.2.16",
|
|
20
20
|
"@oclif/plugin-plugins": "^3.1.10",
|
|
21
|
-
"@superblocksteam/css-plugin": "1.
|
|
22
|
-
"@superblocksteam/react-shim": "1.
|
|
23
|
-
"@superblocksteam/sdk": "1.
|
|
24
|
-
"@superblocksteam/util": "1.
|
|
25
|
-
"@superblocksteam/vite-custom-component-reload-plugin": "1.
|
|
21
|
+
"@superblocksteam/css-plugin": "1.7.0",
|
|
22
|
+
"@superblocksteam/react-shim": "1.7.0",
|
|
23
|
+
"@superblocksteam/sdk": "1.7.0",
|
|
24
|
+
"@superblocksteam/util": "1.7.0",
|
|
25
|
+
"@superblocksteam/vite-custom-component-reload-plugin": "1.7.0",
|
|
26
26
|
"@vitejs/plugin-react": "^4.1.0",
|
|
27
27
|
"colorette": "^2.0.19",
|
|
28
28
|
"enquirer": "^2.3.6",
|