vite-plugin-server-actions 0.1.0 ā 1.0.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/LICENSE +21 -0
- package/README.md +616 -123
- package/index.d.ts +200 -0
- package/package.json +81 -16
- package/src/build-utils.js +101 -0
- package/src/index.js +531 -58
- package/src/middleware.js +89 -0
- package/src/openapi.js +369 -0
- package/src/types.ts +35 -0
- package/src/validation.js +307 -0
- package/.editorconfig +0 -20
- package/.prettierrc +0 -7
- package/examples/todo-app/.prettierrc +0 -17
- package/examples/todo-app/README.md +0 -58
- package/examples/todo-app/index.html +0 -12
- package/examples/todo-app/jsconfig.json +0 -32
- package/examples/todo-app/package.json +0 -22
- package/examples/todo-app/src/App.svelte +0 -155
- package/examples/todo-app/src/actions/auth.server.js +0 -14
- package/examples/todo-app/src/actions/todo.server.js +0 -57
- package/examples/todo-app/src/app.css +0 -0
- package/examples/todo-app/src/main.js +0 -8
- package/examples/todo-app/svelte.config.js +0 -7
- package/examples/todo-app/todos.json +0 -27
- package/examples/todo-app/vite.config.js +0 -12
- package/examples/todo-app/yarn.lock +0 -658
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import util from "util";
|
|
2
|
+
import { createValidationMiddleware } from "./validation.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Built-in logging middleware for server actions
|
|
6
|
+
* Logs the action being triggered and the request body with syntax highlighting
|
|
7
|
+
*/
|
|
8
|
+
export function loggingMiddleware(req, res, next) {
|
|
9
|
+
const timestamp = new Date().toISOString();
|
|
10
|
+
const method = req.method;
|
|
11
|
+
const url = req.url;
|
|
12
|
+
|
|
13
|
+
// Extract action name from URL (format: /api/module_name/functionName)
|
|
14
|
+
const urlParts = url.split("/");
|
|
15
|
+
const functionName = urlParts[urlParts.length - 1];
|
|
16
|
+
const moduleName = urlParts[urlParts.length - 2];
|
|
17
|
+
|
|
18
|
+
// Log action trigger
|
|
19
|
+
console.log(`\n[${timestamp}] š Server Action Triggered`);
|
|
20
|
+
console.log(`āā Module: ${moduleName}`);
|
|
21
|
+
console.log(`āā Function: ${functionName}`);
|
|
22
|
+
console.log(`āā Method: ${method}`);
|
|
23
|
+
console.log(`āā Endpoint: ${url}`);
|
|
24
|
+
|
|
25
|
+
// Log request body with syntax highlighting
|
|
26
|
+
if (req.body && Object.keys(req.body).length > 0) {
|
|
27
|
+
console.log("\nš¦ Request Body:");
|
|
28
|
+
// Use util.inspect for colored output
|
|
29
|
+
console.log(
|
|
30
|
+
util.inspect(req.body, {
|
|
31
|
+
colors: true,
|
|
32
|
+
depth: null,
|
|
33
|
+
compact: false,
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
} else {
|
|
37
|
+
console.log("\nš¦ Request Body: (empty)");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Track response time
|
|
41
|
+
const startTime = Date.now();
|
|
42
|
+
|
|
43
|
+
// Override res.json to log response
|
|
44
|
+
const originalJson = res.json.bind(res);
|
|
45
|
+
res.json = function (data) {
|
|
46
|
+
const duration = Date.now() - startTime;
|
|
47
|
+
console.log(`\nā
Response sent in ${duration}ms`);
|
|
48
|
+
if (data) {
|
|
49
|
+
console.log("š¤ Response data:");
|
|
50
|
+
console.log(
|
|
51
|
+
util.inspect(data, {
|
|
52
|
+
colors: true,
|
|
53
|
+
depth: null,
|
|
54
|
+
compact: false,
|
|
55
|
+
maxArrayLength: 10, // Limit array output
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
console.log("ā".repeat(50));
|
|
60
|
+
return originalJson(data);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Handle errors
|
|
64
|
+
const originalStatus = res.status.bind(res);
|
|
65
|
+
res.status = function (statusCode) {
|
|
66
|
+
if (statusCode >= 400) {
|
|
67
|
+
const duration = Date.now() - startTime;
|
|
68
|
+
console.log(`\nā Error response (${statusCode}) sent in ${duration}ms`);
|
|
69
|
+
}
|
|
70
|
+
return originalStatus(statusCode);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
next();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Create validation middleware with custom options
|
|
78
|
+
* @param {object} options - Validation options
|
|
79
|
+
* @returns {function} Validation middleware
|
|
80
|
+
*/
|
|
81
|
+
export function createValidationMiddlewareWithOptions(options = {}) {
|
|
82
|
+
return createValidationMiddleware(options);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Export a namespace for all built-in middleware
|
|
86
|
+
export const middleware = {
|
|
87
|
+
logging: loggingMiddleware,
|
|
88
|
+
validation: createValidationMiddleware,
|
|
89
|
+
};
|
package/src/openapi.js
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import swaggerUi from "swagger-ui-express";
|
|
2
|
+
import { defaultAdapter } from "./validation.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* OpenAPI specification generator
|
|
6
|
+
*/
|
|
7
|
+
export class OpenAPIGenerator {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.adapter = options.adapter || defaultAdapter;
|
|
10
|
+
this.info = {
|
|
11
|
+
title: "Server Actions API",
|
|
12
|
+
version: "1.0.0",
|
|
13
|
+
description: "Auto-generated API documentation for Vite Server Actions",
|
|
14
|
+
...options.info,
|
|
15
|
+
};
|
|
16
|
+
this.servers = options.servers || [
|
|
17
|
+
{
|
|
18
|
+
url: "http://localhost:5173",
|
|
19
|
+
description: "Development server",
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Generate complete OpenAPI specification
|
|
26
|
+
* @param {Map} serverFunctions - Map of server functions
|
|
27
|
+
* @param {SchemaDiscovery} schemaDiscovery - Schema discovery instance
|
|
28
|
+
* @param {object} options - Generation options
|
|
29
|
+
* @returns {object} Complete OpenAPI 3.0 specification
|
|
30
|
+
*/
|
|
31
|
+
generateSpec(serverFunctions, schemaDiscovery, options = {}) {
|
|
32
|
+
const spec = {
|
|
33
|
+
openapi: "3.0.3",
|
|
34
|
+
info: this.info,
|
|
35
|
+
servers: this.servers,
|
|
36
|
+
paths: {},
|
|
37
|
+
components: {
|
|
38
|
+
schemas: {},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Generate paths for each server function
|
|
43
|
+
for (const [moduleName, { functions, filePath }] of serverFunctions) {
|
|
44
|
+
for (const functionName of functions) {
|
|
45
|
+
// Use routeTransform if provided, otherwise fall back to legacy format
|
|
46
|
+
let routePath;
|
|
47
|
+
if (options.routeTransform && filePath) {
|
|
48
|
+
routePath = options.routeTransform(filePath, functionName);
|
|
49
|
+
} else {
|
|
50
|
+
// Fallback to legacy format for backward compatibility
|
|
51
|
+
routePath = `${moduleName}/${functionName}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const path = `${options.apiPrefix || "/api"}/${routePath}`;
|
|
55
|
+
const schema = schemaDiscovery.getSchema(moduleName, functionName);
|
|
56
|
+
|
|
57
|
+
spec.paths[path] = this.generatePathItem(moduleName, functionName, schema);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return spec;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Generate OpenAPI path item for a server function
|
|
66
|
+
* @param {string} moduleName - Module name
|
|
67
|
+
* @param {string} functionName - Function name
|
|
68
|
+
* @param {any} schema - Validation schema
|
|
69
|
+
* @returns {object} OpenAPI path item
|
|
70
|
+
*/
|
|
71
|
+
generatePathItem(moduleName, functionName, schema) {
|
|
72
|
+
const operationId = `${moduleName}_${functionName}`;
|
|
73
|
+
const tags = [moduleName];
|
|
74
|
+
|
|
75
|
+
const pathItem = {
|
|
76
|
+
post: {
|
|
77
|
+
operationId,
|
|
78
|
+
tags,
|
|
79
|
+
summary: `Execute ${functionName}`,
|
|
80
|
+
description: `Execute the ${functionName} server action from ${moduleName} module`,
|
|
81
|
+
requestBody: {
|
|
82
|
+
required: true,
|
|
83
|
+
content: {
|
|
84
|
+
"application/json": {
|
|
85
|
+
schema: this.generateRequestSchema(schema),
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
responses: {
|
|
90
|
+
200: {
|
|
91
|
+
description: "Successful response",
|
|
92
|
+
content: {
|
|
93
|
+
"application/json": {
|
|
94
|
+
schema: {
|
|
95
|
+
type: "object",
|
|
96
|
+
description: "Function result",
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
400: {
|
|
102
|
+
description: "Validation error",
|
|
103
|
+
content: {
|
|
104
|
+
"application/json": {
|
|
105
|
+
schema: this.getErrorSchema(),
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
404: {
|
|
110
|
+
description: "Function not found",
|
|
111
|
+
content: {
|
|
112
|
+
"application/json": {
|
|
113
|
+
schema: this.getErrorSchema(),
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
500: {
|
|
118
|
+
description: "Internal server error",
|
|
119
|
+
content: {
|
|
120
|
+
"application/json": {
|
|
121
|
+
schema: this.getErrorSchema(),
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
return pathItem;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Generate request schema for a server function
|
|
134
|
+
* @param {any} schema - Validation schema
|
|
135
|
+
* @returns {object} OpenAPI schema for request body
|
|
136
|
+
*/
|
|
137
|
+
generateRequestSchema(schema) {
|
|
138
|
+
if (!schema) {
|
|
139
|
+
return {
|
|
140
|
+
type: "array",
|
|
141
|
+
description: "Function arguments array",
|
|
142
|
+
items: {
|
|
143
|
+
type: "object",
|
|
144
|
+
description: "Function argument",
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Server functions receive arguments as an array
|
|
150
|
+
// But if schema is defined, we assume it validates the first argument
|
|
151
|
+
return {
|
|
152
|
+
type: "array",
|
|
153
|
+
description: "Function arguments",
|
|
154
|
+
items: this.adapter.toOpenAPISchema(schema),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get standard error response schema
|
|
160
|
+
* @returns {object} OpenAPI error schema
|
|
161
|
+
*/
|
|
162
|
+
getErrorSchema() {
|
|
163
|
+
return {
|
|
164
|
+
type: "object",
|
|
165
|
+
properties: {
|
|
166
|
+
error: {
|
|
167
|
+
type: "string",
|
|
168
|
+
description: "Error message",
|
|
169
|
+
},
|
|
170
|
+
details: {
|
|
171
|
+
type: "string",
|
|
172
|
+
description: "Error details",
|
|
173
|
+
},
|
|
174
|
+
validationErrors: {
|
|
175
|
+
type: "array",
|
|
176
|
+
description: "Validation errors (if applicable)",
|
|
177
|
+
items: {
|
|
178
|
+
type: "object",
|
|
179
|
+
properties: {
|
|
180
|
+
path: {
|
|
181
|
+
type: "string",
|
|
182
|
+
description: "Field path",
|
|
183
|
+
},
|
|
184
|
+
message: {
|
|
185
|
+
type: "string",
|
|
186
|
+
description: "Error message",
|
|
187
|
+
},
|
|
188
|
+
code: {
|
|
189
|
+
type: "string",
|
|
190
|
+
description: "Error code",
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
required: ["error"],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Create Swagger UI middleware
|
|
203
|
+
* @param {object} openAPISpec - OpenAPI specification
|
|
204
|
+
* @param {object} options - Swagger UI options
|
|
205
|
+
* @returns {Array} Array of middleware functions
|
|
206
|
+
*/
|
|
207
|
+
export function createSwaggerMiddleware(openAPISpec, options = {}) {
|
|
208
|
+
const swaggerOptions = {
|
|
209
|
+
customCss: `
|
|
210
|
+
.swagger-ui .topbar { display: none; }
|
|
211
|
+
.swagger-ui .info .title { color: #3b82f6; }
|
|
212
|
+
`,
|
|
213
|
+
customSiteTitle: "Server Actions API Documentation",
|
|
214
|
+
...options.swaggerOptions,
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
return [swaggerUi.serve, swaggerUi.setup(openAPISpec, swaggerOptions)];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Setup OpenAPI endpoints for development
|
|
222
|
+
* @param {Express} app - Express app instance
|
|
223
|
+
* @param {object} openAPISpec - OpenAPI specification
|
|
224
|
+
* @param {object} options - Setup options
|
|
225
|
+
*/
|
|
226
|
+
export function setupOpenAPIEndpoints(app, openAPISpec, options = {}) {
|
|
227
|
+
const docsPath = options.docsPath || "/api/docs";
|
|
228
|
+
const specPath = options.specPath || "/api/openapi.json";
|
|
229
|
+
|
|
230
|
+
// Serve OpenAPI specification as JSON
|
|
231
|
+
app.get(specPath, (req, res) => {
|
|
232
|
+
res.json(openAPISpec);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Serve Swagger UI
|
|
236
|
+
if (options.enableSwaggerUI !== false) {
|
|
237
|
+
const swaggerMiddleware = createSwaggerMiddleware(openAPISpec, options);
|
|
238
|
+
app.use(docsPath, ...swaggerMiddleware);
|
|
239
|
+
|
|
240
|
+
console.log(`š API Documentation: http://localhost:${process.env.PORT || 5173}${docsPath}`);
|
|
241
|
+
console.log(`š OpenAPI Spec: http://localhost:${process.env.PORT || 5173}${specPath}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Generate OpenAPI-compatible parameter descriptions from JSDoc
|
|
247
|
+
* @param {string} jsdoc - JSDoc comment string
|
|
248
|
+
* @returns {Array} Array of parameter descriptions
|
|
249
|
+
*/
|
|
250
|
+
export function parseJSDocParameters(jsdoc) {
|
|
251
|
+
if (!jsdoc) {
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const paramRegex = /@param\s+\{([^}]+)\}\s+(\[?[\w.]+\]?)\s*-?\s*(.*)/g;
|
|
256
|
+
const parameters = [];
|
|
257
|
+
let match;
|
|
258
|
+
|
|
259
|
+
while ((match = paramRegex.exec(jsdoc)) !== null) {
|
|
260
|
+
const [, type, name, description] = match;
|
|
261
|
+
const isOptional = name.startsWith("[") && name.endsWith("]");
|
|
262
|
+
const paramName = name.replace(/^\[|\]$/g, "");
|
|
263
|
+
|
|
264
|
+
parameters.push({
|
|
265
|
+
name: paramName,
|
|
266
|
+
type: type.toLowerCase(),
|
|
267
|
+
description: description.trim(),
|
|
268
|
+
required: !isOptional,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return parameters;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Enhanced OpenAPI generator with JSDoc support
|
|
277
|
+
*/
|
|
278
|
+
export class EnhancedOpenAPIGenerator extends OpenAPIGenerator {
|
|
279
|
+
/**
|
|
280
|
+
* Generate path item with JSDoc enhancement
|
|
281
|
+
* @param {string} moduleName - Module name
|
|
282
|
+
* @param {string} functionName - Function name
|
|
283
|
+
* @param {any} schema - Validation schema
|
|
284
|
+
* @param {string} jsdoc - JSDoc comment
|
|
285
|
+
* @returns {object} Enhanced OpenAPI path item
|
|
286
|
+
*/
|
|
287
|
+
generatePathItemWithJSDoc(moduleName, functionName, schema, jsdoc) {
|
|
288
|
+
const pathItem = this.generatePathItem(moduleName, functionName, schema);
|
|
289
|
+
|
|
290
|
+
if (jsdoc) {
|
|
291
|
+
const jsDocParams = parseJSDocParameters(jsdoc);
|
|
292
|
+
|
|
293
|
+
// Extract description from JSDoc
|
|
294
|
+
const descriptionMatch = jsdoc.match(/\/\*\*\s*\n\s*\*\s*([^@\n]*)/);
|
|
295
|
+
if (descriptionMatch) {
|
|
296
|
+
pathItem.post.description = descriptionMatch[1].trim();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Enhance request schema with JSDoc information
|
|
300
|
+
if (jsDocParams.length > 0 && !schema) {
|
|
301
|
+
pathItem.post.requestBody.content["application/json"].schema = {
|
|
302
|
+
type: "array",
|
|
303
|
+
description: "Function arguments",
|
|
304
|
+
items: this.generateSchemaFromJSDoc(jsDocParams),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return pathItem;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Generate OpenAPI schema from JSDoc parameters
|
|
314
|
+
* @param {Array} jsDocParams - JSDoc parameter descriptions
|
|
315
|
+
* @returns {object} OpenAPI schema
|
|
316
|
+
*/
|
|
317
|
+
generateSchemaFromJSDoc(jsDocParams) {
|
|
318
|
+
if (jsDocParams.length === 1) {
|
|
319
|
+
// Single parameter
|
|
320
|
+
return this.jsDocTypeToOpenAPISchema(jsDocParams[0]);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Multiple parameters - create object schema
|
|
324
|
+
const properties = {};
|
|
325
|
+
const required = [];
|
|
326
|
+
|
|
327
|
+
for (const param of jsDocParams) {
|
|
328
|
+
properties[param.name] = this.jsDocTypeToOpenAPISchema(param);
|
|
329
|
+
if (param.required) {
|
|
330
|
+
required.push(param.name);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
type: "object",
|
|
336
|
+
properties,
|
|
337
|
+
required: required.length > 0 ? required : undefined,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Convert JSDoc type to OpenAPI schema
|
|
343
|
+
* @param {object} param - JSDoc parameter object
|
|
344
|
+
* @returns {object} OpenAPI schema
|
|
345
|
+
*/
|
|
346
|
+
jsDocTypeToOpenAPISchema(param) {
|
|
347
|
+
const { type, description } = param;
|
|
348
|
+
|
|
349
|
+
switch (type.toLowerCase()) {
|
|
350
|
+
case "string":
|
|
351
|
+
return { type: "string", description };
|
|
352
|
+
case "number":
|
|
353
|
+
return { type: "number", description };
|
|
354
|
+
case "boolean":
|
|
355
|
+
return { type: "boolean", description };
|
|
356
|
+
case "object":
|
|
357
|
+
return { type: "object", description };
|
|
358
|
+
case "array":
|
|
359
|
+
return { type: "array", items: { type: "object" }, description };
|
|
360
|
+
default:
|
|
361
|
+
// Handle union types like 'low'|'medium'|'high'
|
|
362
|
+
if (type.includes("|")) {
|
|
363
|
+
const enumValues = type.split("|").map((v) => v.replace(/['"]/g, "").trim());
|
|
364
|
+
return { type: "string", enum: enumValues, description };
|
|
365
|
+
}
|
|
366
|
+
return { type: "object", description };
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
import type { Express } from "express";
|
|
3
|
+
|
|
4
|
+
export interface ServerActionOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Custom API prefix for server action endpoints
|
|
7
|
+
* @default "/api"
|
|
8
|
+
*/
|
|
9
|
+
apiPrefix?: string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Include patterns for server action files
|
|
13
|
+
* @default "**\/*.server.js"
|
|
14
|
+
*/
|
|
15
|
+
include?: string | string[];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Exclude patterns for server action files
|
|
19
|
+
*/
|
|
20
|
+
exclude?: string | string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ServerFunction {
|
|
24
|
+
name: string;
|
|
25
|
+
isAsync: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ServerModule {
|
|
29
|
+
functions: string[];
|
|
30
|
+
id: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ServerActionsPlugin extends Plugin {
|
|
34
|
+
name: "vite-plugin-server-actions";
|
|
35
|
+
}
|