@medusajs/admin-vite-plugin 0.0.1

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 ADDED
@@ -0,0 +1 @@
1
+ # plugin-extension
@@ -0,0 +1,8 @@
1
+ import { PluginOption } from 'vite';
2
+
3
+ type InjectArgs = {
4
+ sources?: string[];
5
+ };
6
+ declare function inject(args?: InjectArgs): PluginOption;
7
+
8
+ export { inject as default };
@@ -0,0 +1,8 @@
1
+ import { PluginOption } from 'vite';
2
+
3
+ type InjectArgs = {
4
+ sources?: string[];
5
+ };
6
+ declare function inject(args?: InjectArgs): PluginOption;
7
+
8
+ export { inject as default };
package/dist/index.js ADDED
@@ -0,0 +1,688 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ default: () => inject
34
+ });
35
+ module.exports = __toCommonJS(src_exports);
36
+ var import_parser = require("@babel/parser");
37
+ var import_traverse = __toESM(require("@babel/traverse"));
38
+ var import_chokidar = __toESM(require("chokidar"));
39
+ var import_fdir = require("fdir");
40
+ var import_promises = __toESM(require("fs/promises"));
41
+ var import_magic_string = __toESM(require("magic-string"));
42
+ var import_path = __toESM(require("path"));
43
+
44
+ // ../admin-shared/dist/constants.js
45
+ var injectionZones = [
46
+ // Order injection zones
47
+ "order.details.before",
48
+ "order.details.after",
49
+ "order.list.before",
50
+ "order.list.after",
51
+ // Draft order injection zones
52
+ "draft_order.list.before",
53
+ "draft_order.list.after",
54
+ "draft_order.details.before",
55
+ "draft_order.details.after",
56
+ // Customer injection zones
57
+ "customer.details.before",
58
+ "customer.details.after",
59
+ "customer.list.before",
60
+ "customer.list.after",
61
+ // Customer group injection zones
62
+ "customer_group.details.before",
63
+ "customer_group.details.after",
64
+ "customer_group.list.before",
65
+ "customer_group.list.after",
66
+ // Product injection zones
67
+ "product.details.before",
68
+ "product.details.after",
69
+ "product.list.before",
70
+ "product.list.after",
71
+ "product.details.side.before",
72
+ "product.details.side.after",
73
+ // Product collection injection zones
74
+ "product_collection.details.before",
75
+ "product_collection.details.after",
76
+ "product_collection.list.before",
77
+ "product_collection.list.after",
78
+ // Product category injection zones
79
+ "product_category.details.before",
80
+ "product_category.details.after",
81
+ "product_category.list.before",
82
+ "product_category.list.after",
83
+ // Price list injection zones
84
+ "price_list.details.before",
85
+ "price_list.details.after",
86
+ "price_list.list.before",
87
+ "price_list.list.after",
88
+ // Discount injection zones
89
+ "discount.details.before",
90
+ "discount.details.after",
91
+ "discount.list.before",
92
+ "discount.list.after",
93
+ // Promotion injection zones
94
+ "promotion.details.before",
95
+ "promotion.details.after",
96
+ "promotion.list.before",
97
+ "promotion.list.after",
98
+ // Gift card injection zones
99
+ "gift_card.details.before",
100
+ "gift_card.details.after",
101
+ "gift_card.list.before",
102
+ "gift_card.list.after",
103
+ "custom_gift_card.before",
104
+ "custom_gift_card.after",
105
+ // Login
106
+ "login.before",
107
+ "login.after"
108
+ ];
109
+
110
+ // src/index.ts
111
+ var traverse = import_traverse.default.default;
112
+ var VIRTUAL_PREFIX = "/@virtual/medusajs-admin-vite-plugin/";
113
+ var IMPORT_PREFIX = "medusa-admin:";
114
+ var WIDGET_MODULE = `${IMPORT_PREFIX}widgets/`;
115
+ var WIDGET_MODULES = injectionZones.map((zone) => {
116
+ return `${WIDGET_MODULE}${zone.replace(/\./g, "/")}`;
117
+ });
118
+ var ROUTE_PAGE_MODULE = `${IMPORT_PREFIX}routes/pages`;
119
+ var ROUTE_LINK_MODULE = `${IMPORT_PREFIX}routes/links`;
120
+ var ROUTE_MODULES = [ROUTE_PAGE_MODULE, ROUTE_LINK_MODULE];
121
+ var SETTING_PAGE_MODULE = `${IMPORT_PREFIX}settings/pages`;
122
+ var SETTING_CARD_MODULE = `${IMPORT_PREFIX}settings/cards`;
123
+ var SETTING_MODULE = [SETTING_PAGE_MODULE, SETTING_CARD_MODULE];
124
+ var MODULES = [...WIDGET_MODULES, ...ROUTE_MODULES, ...SETTING_MODULE];
125
+ function inject(args) {
126
+ const _extensionGraph = /* @__PURE__ */ new Map();
127
+ const _sources = /* @__PURE__ */ new Set([...args?.sources || []]);
128
+ let server;
129
+ let watcher;
130
+ let logger;
131
+ async function traverseDirectory(dir, file, depth) {
132
+ const baseDepth = dir.split(import_path.default.sep).length;
133
+ const crawler = new import_fdir.fdir().withBasePath().exclude((dirName) => dirName.startsWith("_")).filter((path2) => path2.endsWith(".tsx") || path2.endsWith(".jsx"));
134
+ if (file) {
135
+ crawler.filter(
136
+ (path2) => path2.endsWith(`${file}.tsx`) || path2.endsWith(`${file}.jsx`)
137
+ );
138
+ }
139
+ if (depth) {
140
+ crawler.filter((file2) => {
141
+ const directoryDepth = file2.split(import_path.default.sep).length - 1;
142
+ if (depth.max && directoryDepth > baseDepth + depth.max) {
143
+ return false;
144
+ }
145
+ if (directoryDepth < baseDepth + depth.min) {
146
+ return false;
147
+ }
148
+ return true;
149
+ });
150
+ }
151
+ return await crawler.crawl(dir).withPromise();
152
+ }
153
+ function generateModule(code) {
154
+ const magicString = new import_magic_string.default(code);
155
+ return {
156
+ code: magicString.toString(),
157
+ map: magicString.generateMap({ hires: true })
158
+ };
159
+ }
160
+ function validateDefaultExport(path2, ast) {
161
+ let hasComponentExport = false;
162
+ const declaration = path2.node.declaration;
163
+ if (declaration && (declaration.type === "Identifier" || declaration.type === "FunctionDeclaration")) {
164
+ const exportName = declaration.type === "Identifier" ? declaration.name : declaration.id && declaration.id.name;
165
+ if (exportName) {
166
+ try {
167
+ traverse(ast, {
168
+ VariableDeclarator({ node, scope }) {
169
+ let isDefaultExport = false;
170
+ if (node.id.type === "Identifier" && node.id.name === exportName) {
171
+ isDefaultExport = true;
172
+ }
173
+ if (!isDefaultExport) {
174
+ return;
175
+ }
176
+ traverse(
177
+ node,
178
+ {
179
+ ReturnStatement(path3) {
180
+ if (path3.node.argument?.type === "JSXElement" || path3.node.argument?.type === "JSXFragment") {
181
+ hasComponentExport = true;
182
+ }
183
+ }
184
+ },
185
+ scope
186
+ );
187
+ }
188
+ });
189
+ } catch (e) {
190
+ console.error(
191
+ `An error occured while validating the default export of '${path2}'. The following error must be resolved before continuing:
192
+ ${e}`
193
+ );
194
+ return false;
195
+ }
196
+ }
197
+ }
198
+ return hasComponentExport;
199
+ }
200
+ function getProperties(path2) {
201
+ const declaration = path2.node.declaration;
202
+ if (declaration && declaration.type === "VariableDeclaration") {
203
+ const configDeclaration = declaration.declarations.find(
204
+ (d) => d.type === "VariableDeclarator" && d.id.type === "Identifier" && d.id.name === "config"
205
+ );
206
+ if (configDeclaration && configDeclaration.init?.type === "ObjectExpression") {
207
+ return configDeclaration.init.properties;
208
+ }
209
+ }
210
+ return null;
211
+ }
212
+ function validateInjectionZone(zone) {
213
+ return injectionZones.includes(zone);
214
+ }
215
+ function validateWidgetConfig(path2, zone) {
216
+ const properties = getProperties(path2);
217
+ if (!properties) {
218
+ return { zoneIsValid: false, zoneValue: void 0 };
219
+ }
220
+ const zoneProperty = properties.find(
221
+ (p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "zone"
222
+ );
223
+ if (!zoneProperty) {
224
+ return { zoneIsValid: false, zoneValue: void 0 };
225
+ }
226
+ let zoneIsValid = false;
227
+ let zoneValue = void 0;
228
+ if (zoneProperty.value.type === "StringLiteral") {
229
+ zoneIsValid = !zone ? validateInjectionZone(zoneProperty.value.value) : zone === zoneProperty.value.value;
230
+ zoneValue = zoneProperty.value.value;
231
+ } else if (zoneProperty.value.type === "ArrayExpression") {
232
+ zoneIsValid = zoneProperty.value.elements.every((_zone) => {
233
+ if (!_zone || _zone.type !== "StringLiteral") {
234
+ return false;
235
+ }
236
+ const isZoneMatch = !zone ? true : zone === _zone.value;
237
+ return validateInjectionZone(_zone.value) && isZoneMatch;
238
+ });
239
+ zoneValue = zoneProperty.value.elements.map((e) => {
240
+ if (e && e.type === "StringLiteral") {
241
+ return e.value;
242
+ }
243
+ }).filter(Boolean);
244
+ }
245
+ return { zoneIsValid, zoneValue };
246
+ }
247
+ async function validateWidget(file, zone) {
248
+ const content = await import_promises.default.readFile(file, "utf-8");
249
+ const parserOptions = {
250
+ sourceType: "module",
251
+ plugins: ["jsx"]
252
+ };
253
+ if (file.endsWith(".tsx")) {
254
+ parserOptions.plugins?.push("typescript");
255
+ }
256
+ let ast;
257
+ try {
258
+ ast = (0, import_parser.parse)(content, parserOptions);
259
+ } catch (err) {
260
+ logger.error(
261
+ `An error occured while parsing the content of ${file}:
262
+ ${err}`,
263
+ {
264
+ error: err,
265
+ timestamp: true
266
+ }
267
+ );
268
+ return { isValidWidget: false, zoneValue: void 0 };
269
+ }
270
+ let hasDefaultExport = false;
271
+ let hasNamedExport = false;
272
+ let zoneValue;
273
+ try {
274
+ traverse(ast, {
275
+ ExportDefaultDeclaration(path2) {
276
+ hasDefaultExport = validateDefaultExport(path2, ast);
277
+ },
278
+ ExportNamedDeclaration(path2) {
279
+ const { zoneIsValid, zoneValue: value } = validateWidgetConfig(
280
+ path2,
281
+ zone
282
+ );
283
+ hasNamedExport = zoneIsValid;
284
+ zoneValue = value;
285
+ }
286
+ });
287
+ } catch (err) {
288
+ logger.error(`An error occured while validating the content of ${file}`, {
289
+ error: err,
290
+ timestamp: true
291
+ });
292
+ return { isValidWidget: false, zoneValue: void 0 };
293
+ }
294
+ return { isValidWidget: hasDefaultExport && hasNamedExport, zoneValue };
295
+ }
296
+ async function generateWidgetEntrypoint(zone) {
297
+ const files = (await Promise.all(
298
+ Array.from(_sources).map(
299
+ async (source) => traverseDirectory(`${source}/widgets`)
300
+ )
301
+ )).flat();
302
+ const validatedWidgets = (await Promise.all(
303
+ files.map(async (widget) => {
304
+ const { isValidWidget } = await validateWidget(widget, zone);
305
+ return isValidWidget ? widget : null;
306
+ })
307
+ )).filter(Boolean);
308
+ if (!validatedWidgets.length) {
309
+ const code2 = `export default {
310
+ widgets: [],
311
+ }`;
312
+ return { module: generateModule(code2), paths: [] };
313
+ }
314
+ const importString = validatedWidgets.map((path2, index) => `import WidgetExt${index} from "${path2}";`).join("\n");
315
+ const exportString = `export default {
316
+ widgets: [${validatedWidgets.map((_, index) => `{ Component: WidgetExt${index} }`).join(", ")}],
317
+ }`;
318
+ const code = `${importString}
319
+ ${exportString}`;
320
+ return { module: generateModule(code), paths: validatedWidgets };
321
+ }
322
+ function validateRouteConfig(path2, requireLink) {
323
+ const properties = getProperties(path2);
324
+ if (!properties) {
325
+ return false;
326
+ }
327
+ const linkProperty = properties.find(
328
+ (p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "link"
329
+ );
330
+ if (!linkProperty && !requireLink) {
331
+ return true;
332
+ }
333
+ const linkValue = linkProperty?.value;
334
+ if (!linkValue) {
335
+ return false;
336
+ }
337
+ let labelIsValid = false;
338
+ if (linkValue.properties.some(
339
+ (p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "label" && p.value.type === "StringLiteral"
340
+ )) {
341
+ labelIsValid = true;
342
+ }
343
+ return labelIsValid;
344
+ }
345
+ async function validateRoute(file, requireLink) {
346
+ const content = await import_promises.default.readFile(file, "utf-8");
347
+ const parserOptions = {
348
+ sourceType: "module",
349
+ plugins: ["jsx"]
350
+ };
351
+ if (file.endsWith(".tsx")) {
352
+ parserOptions.plugins?.push("typescript");
353
+ }
354
+ let ast;
355
+ try {
356
+ ast = (0, import_parser.parse)(content, parserOptions);
357
+ } catch (err) {
358
+ logger.error("An error occured while validating a route.", {
359
+ error: err,
360
+ timestamp: true
361
+ });
362
+ return false;
363
+ }
364
+ let hasDefaultExport = false;
365
+ let hasNamedExport = false;
366
+ try {
367
+ traverse(ast, {
368
+ ExportDefaultDeclaration(path2) {
369
+ hasDefaultExport = validateDefaultExport(path2, ast);
370
+ },
371
+ ExportNamedDeclaration(path2) {
372
+ hasNamedExport = validateRouteConfig(path2, requireLink);
373
+ }
374
+ });
375
+ } catch (err) {
376
+ logger.error("An error occured while validating a route.", {
377
+ error: err,
378
+ timestamp: true
379
+ });
380
+ return false;
381
+ }
382
+ return hasDefaultExport && hasNamedExport;
383
+ }
384
+ function createPath(file) {
385
+ return file.replace(/.*\/admin\/(routes|settings)/, "").replace(/\[([^\]]+)\]/g, ":$1").replace(/\/page\.(tsx|jsx)/, "");
386
+ }
387
+ async function generateRouteEntrypoint(get) {
388
+ const files = (await Promise.all(
389
+ Array.from(_sources).map(
390
+ async (source) => traverseDirectory(`${source}/routes`, "page", { min: 1 })
391
+ )
392
+ )).flat();
393
+ const validatedRoutes = (await Promise.all(
394
+ files.map(async (route) => {
395
+ const isValid = await validateRoute(route, get === "link");
396
+ return isValid ? route : null;
397
+ })
398
+ )).filter(Boolean);
399
+ if (!validatedRoutes.length) {
400
+ const code2 = `export default {
401
+ ${get}s: [],
402
+ }`;
403
+ return { module: generateModule(code2), paths: [] };
404
+ }
405
+ const importString = validatedRoutes.map((path2, index) => {
406
+ return get === "page" ? `import RouteExt${index} from "${path2}";` : `import { config as routeConfig${index} } from "${path2}";`;
407
+ }).join("\n");
408
+ const exportString = `export default {
409
+ ${get}s: [${validatedRoutes.map((file, index) => {
410
+ return get === "page" ? `{ path: "${createPath(file)}", file: "${file}" }` : `{ path: "${createPath(file)}", ...routeConfig${index}.link }`;
411
+ }).join(", ")}],
412
+ }`;
413
+ const code = `${importString}
414
+ ${exportString}`;
415
+ return { module: generateModule(code), paths: validatedRoutes };
416
+ }
417
+ async function validateSetting(file) {
418
+ const content = await import_promises.default.readFile(file, "utf-8");
419
+ const parserOptions = {
420
+ sourceType: "module",
421
+ plugins: ["jsx"]
422
+ };
423
+ if (file.endsWith(".tsx")) {
424
+ parserOptions.plugins?.push("typescript");
425
+ }
426
+ let ast;
427
+ try {
428
+ ast = (0, import_parser.parse)(content, parserOptions);
429
+ } catch (err) {
430
+ logger.error("An error occured while validating a setting.", {
431
+ error: err,
432
+ timestamp: true
433
+ });
434
+ return false;
435
+ }
436
+ let hasDefaultExport = false;
437
+ let hasNamedExport = false;
438
+ try {
439
+ traverse(ast, {
440
+ ExportDefaultDeclaration(path2) {
441
+ hasDefaultExport = validateDefaultExport(path2, ast);
442
+ },
443
+ ExportNamedDeclaration(path2) {
444
+ hasNamedExport = validateSettingConfig(path2);
445
+ }
446
+ });
447
+ } catch (err) {
448
+ logger.error("An error occured while validating a setting.", {
449
+ error: err,
450
+ timestamp: true
451
+ });
452
+ return false;
453
+ }
454
+ return hasDefaultExport && hasNamedExport;
455
+ }
456
+ function validateSettingConfig(path2) {
457
+ const properties = getProperties(path2);
458
+ if (!properties) {
459
+ return false;
460
+ }
461
+ const cardProperty = properties.find(
462
+ (p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "card"
463
+ );
464
+ if (!cardProperty) {
465
+ return false;
466
+ }
467
+ const cardValue = cardProperty.value;
468
+ let hasLabel = false;
469
+ let hasDescription = false;
470
+ if (cardValue.properties.some(
471
+ (p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "label" && p.value.type === "StringLiteral"
472
+ )) {
473
+ hasLabel = true;
474
+ }
475
+ if (cardValue.properties.some(
476
+ (p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "description" && p.value.type === "StringLiteral"
477
+ )) {
478
+ hasDescription = true;
479
+ }
480
+ return hasLabel && hasDescription;
481
+ }
482
+ async function generateSettingEntrypoint(get) {
483
+ const files = (await Promise.all(
484
+ Array.from(_sources).map(
485
+ async (source) => traverseDirectory(`${source}/settings`, "page", { min: 1, max: 1 })
486
+ )
487
+ )).flat();
488
+ const validatedSettings = (await Promise.all(
489
+ files.map(async (setting) => {
490
+ const isValid = await validateSetting(setting);
491
+ return isValid ? setting : null;
492
+ })
493
+ )).filter(Boolean);
494
+ if (!validatedSettings.length) {
495
+ const code2 = `export default {
496
+ ${get}s: [],
497
+ }`;
498
+ return { module: generateModule(code2), paths: [] };
499
+ }
500
+ const importString = validatedSettings.map((path2, index) => {
501
+ return get === "page" ? `import SettingExt${index} from "${path2}";` : `import { config as settingConfig${index} } from "${path2}";`;
502
+ }).join("\n");
503
+ const exportString = `export default {
504
+ ${get}s: [${validatedSettings.map((file, index) => {
505
+ return get === "page" ? `{ path: "${createPath(file)}", file: "${file}" }` : `{ path: "${createPath(file)}", ...settingConfig${index}.card }`;
506
+ }).join(", ")}],
507
+ }`;
508
+ const code = `${importString}
509
+ ${exportString}`;
510
+ return { module: generateModule(code), paths: validatedSettings };
511
+ }
512
+ async function loadModule(options) {
513
+ switch (options.type) {
514
+ case "widget": {
515
+ return await generateWidgetEntrypoint(options.get);
516
+ }
517
+ case "route": {
518
+ return await generateRouteEntrypoint(options.get);
519
+ }
520
+ case "setting": {
521
+ return await generateSettingEntrypoint(options.get);
522
+ }
523
+ }
524
+ }
525
+ function getExtensionType(file) {
526
+ const normalizedPath = import_path.default.normalize(file);
527
+ if (normalizedPath.includes(import_path.default.normalize("/admin/widgets/"))) {
528
+ return "widget";
529
+ } else if (normalizedPath.includes(import_path.default.normalize("/admin/routes/"))) {
530
+ return "route";
531
+ } else if (normalizedPath.includes(import_path.default.normalize("/admin/settings/"))) {
532
+ return "setting";
533
+ } else {
534
+ return "none";
535
+ }
536
+ }
537
+ async function handleWidgetChange(file) {
538
+ const { isValidWidget, zoneValue } = await validateWidget(file);
539
+ if (!isValidWidget || !zoneValue) {
540
+ _extensionGraph.delete(file);
541
+ return;
542
+ }
543
+ const zoneValues = Array.isArray(zoneValue) ? zoneValue : [zoneValue];
544
+ for (const zone of zoneValues) {
545
+ const zonePath = zone.replace(/\./g, "/");
546
+ const moduleId = `${VIRTUAL_PREFIX}${WIDGET_MODULE}${zonePath}`;
547
+ const module2 = server.moduleGraph.getModuleById(moduleId);
548
+ if (module2) {
549
+ await server.reloadModule(module2);
550
+ }
551
+ }
552
+ }
553
+ async function handleRouteChange(file) {
554
+ const isValidRoute = await validateRoute(file, false);
555
+ if (!isValidRoute) {
556
+ _extensionGraph.delete(file);
557
+ return;
558
+ }
559
+ for (const moduleId of ROUTE_MODULES) {
560
+ const fullModuleId = `${VIRTUAL_PREFIX}${moduleId}`;
561
+ const module2 = server.moduleGraph.getModuleById(fullModuleId);
562
+ if (module2) {
563
+ await server.reloadModule(module2);
564
+ }
565
+ }
566
+ }
567
+ async function handleSettingChange(file) {
568
+ const isValidSetting = await validateSetting(file);
569
+ if (!isValidSetting) {
570
+ _extensionGraph.delete(file);
571
+ return;
572
+ }
573
+ for (const moduleId of SETTING_MODULE) {
574
+ const fullModuleId = `${VIRTUAL_PREFIX}${moduleId}`;
575
+ const module2 = server.moduleGraph.getModuleById(fullModuleId);
576
+ if (module2) {
577
+ await server.reloadModule(module2);
578
+ }
579
+ }
580
+ }
581
+ async function handleExtensionUnlink(file) {
582
+ const moduleIds = _extensionGraph.get(file);
583
+ if (!moduleIds) {
584
+ return;
585
+ }
586
+ for (const moduleId of moduleIds) {
587
+ const module2 = server.moduleGraph.getModuleById(moduleId);
588
+ if (module2) {
589
+ _extensionGraph.delete(file);
590
+ await server.reloadModule(module2);
591
+ }
592
+ }
593
+ }
594
+ async function loadModuleAndUpdateGraph(id, options) {
595
+ const { module: module2, paths } = await loadModule(options);
596
+ for (const path2 of paths) {
597
+ const ids = _extensionGraph.get(path2) || /* @__PURE__ */ new Set();
598
+ ids.add(id);
599
+ _extensionGraph.set(path2, ids);
600
+ }
601
+ return module2;
602
+ }
603
+ return {
604
+ name: "@medusajs/admin-vite-plugin",
605
+ configureServer(s) {
606
+ server = s;
607
+ logger = s.config.logger;
608
+ watcher = import_chokidar.default.watch(Array.from(_sources), {
609
+ persistent: true,
610
+ ignoreInitial: true
611
+ });
612
+ watcher.on("add", async (file) => {
613
+ const type = getExtensionType(file);
614
+ if (type === "none") {
615
+ return;
616
+ }
617
+ if (type === "widget") {
618
+ await handleWidgetChange(file);
619
+ return;
620
+ }
621
+ if (type === "route") {
622
+ await handleRouteChange(file);
623
+ return;
624
+ }
625
+ if (type === "setting") {
626
+ await handleSettingChange(file);
627
+ return;
628
+ }
629
+ return;
630
+ });
631
+ watcher.on("change", async (file) => {
632
+ const type = getExtensionType(file);
633
+ if (type === "none") {
634
+ return;
635
+ }
636
+ if (type === "widget") {
637
+ await handleWidgetChange(file);
638
+ return;
639
+ }
640
+ if (type === "route") {
641
+ await handleRouteChange(file);
642
+ return;
643
+ }
644
+ if (type === "setting") {
645
+ await handleSettingChange(file);
646
+ return;
647
+ }
648
+ return;
649
+ });
650
+ watcher.on("unlink", async (file) => {
651
+ await handleExtensionUnlink(file);
652
+ return;
653
+ });
654
+ },
655
+ resolveId(id) {
656
+ if (MODULES.includes(id)) {
657
+ return VIRTUAL_PREFIX + id;
658
+ }
659
+ return null;
660
+ },
661
+ async load(id) {
662
+ if (!id.startsWith(VIRTUAL_PREFIX)) {
663
+ return null;
664
+ }
665
+ const idNoPrefix = id.slice(VIRTUAL_PREFIX.length);
666
+ const moduleMap = {
667
+ [ROUTE_PAGE_MODULE]: { type: "route", get: "page" },
668
+ [ROUTE_LINK_MODULE]: { type: "route", get: "link" },
669
+ [SETTING_PAGE_MODULE]: { type: "setting", get: "page" },
670
+ [SETTING_CARD_MODULE]: { type: "setting", get: "card" }
671
+ };
672
+ if (WIDGET_MODULES.includes(idNoPrefix)) {
673
+ const zone = idNoPrefix.replace(WIDGET_MODULE, "").replace(/\//g, ".");
674
+ return loadModuleAndUpdateGraph(id, { type: "widget", get: zone });
675
+ }
676
+ const moduleOptions = moduleMap[idNoPrefix];
677
+ if (moduleOptions) {
678
+ return loadModuleAndUpdateGraph(id, moduleOptions);
679
+ }
680
+ return null;
681
+ },
682
+ async closeBundle() {
683
+ if (watcher) {
684
+ await watcher.close();
685
+ }
686
+ }
687
+ };
688
+ }
package/dist/index.mjs ADDED
@@ -0,0 +1,657 @@
1
+ // src/index.ts
2
+ import { parse } from "@babel/parser";
3
+ import _traverse from "@babel/traverse";
4
+ import chokidar from "chokidar";
5
+ import { fdir } from "fdir";
6
+ import fs from "fs/promises";
7
+ import MagicString from "magic-string";
8
+ import path from "path";
9
+
10
+ // ../admin-shared/dist/constants.js
11
+ var injectionZones = [
12
+ // Order injection zones
13
+ "order.details.before",
14
+ "order.details.after",
15
+ "order.list.before",
16
+ "order.list.after",
17
+ // Draft order injection zones
18
+ "draft_order.list.before",
19
+ "draft_order.list.after",
20
+ "draft_order.details.before",
21
+ "draft_order.details.after",
22
+ // Customer injection zones
23
+ "customer.details.before",
24
+ "customer.details.after",
25
+ "customer.list.before",
26
+ "customer.list.after",
27
+ // Customer group injection zones
28
+ "customer_group.details.before",
29
+ "customer_group.details.after",
30
+ "customer_group.list.before",
31
+ "customer_group.list.after",
32
+ // Product injection zones
33
+ "product.details.before",
34
+ "product.details.after",
35
+ "product.list.before",
36
+ "product.list.after",
37
+ "product.details.side.before",
38
+ "product.details.side.after",
39
+ // Product collection injection zones
40
+ "product_collection.details.before",
41
+ "product_collection.details.after",
42
+ "product_collection.list.before",
43
+ "product_collection.list.after",
44
+ // Product category injection zones
45
+ "product_category.details.before",
46
+ "product_category.details.after",
47
+ "product_category.list.before",
48
+ "product_category.list.after",
49
+ // Price list injection zones
50
+ "price_list.details.before",
51
+ "price_list.details.after",
52
+ "price_list.list.before",
53
+ "price_list.list.after",
54
+ // Discount injection zones
55
+ "discount.details.before",
56
+ "discount.details.after",
57
+ "discount.list.before",
58
+ "discount.list.after",
59
+ // Promotion injection zones
60
+ "promotion.details.before",
61
+ "promotion.details.after",
62
+ "promotion.list.before",
63
+ "promotion.list.after",
64
+ // Gift card injection zones
65
+ "gift_card.details.before",
66
+ "gift_card.details.after",
67
+ "gift_card.list.before",
68
+ "gift_card.list.after",
69
+ "custom_gift_card.before",
70
+ "custom_gift_card.after",
71
+ // Login
72
+ "login.before",
73
+ "login.after"
74
+ ];
75
+
76
+ // src/index.ts
77
+ var traverse = _traverse.default;
78
+ var VIRTUAL_PREFIX = "/@virtual/medusajs-admin-vite-plugin/";
79
+ var IMPORT_PREFIX = "medusa-admin:";
80
+ var WIDGET_MODULE = `${IMPORT_PREFIX}widgets/`;
81
+ var WIDGET_MODULES = injectionZones.map((zone) => {
82
+ return `${WIDGET_MODULE}${zone.replace(/\./g, "/")}`;
83
+ });
84
+ var ROUTE_PAGE_MODULE = `${IMPORT_PREFIX}routes/pages`;
85
+ var ROUTE_LINK_MODULE = `${IMPORT_PREFIX}routes/links`;
86
+ var ROUTE_MODULES = [ROUTE_PAGE_MODULE, ROUTE_LINK_MODULE];
87
+ var SETTING_PAGE_MODULE = `${IMPORT_PREFIX}settings/pages`;
88
+ var SETTING_CARD_MODULE = `${IMPORT_PREFIX}settings/cards`;
89
+ var SETTING_MODULE = [SETTING_PAGE_MODULE, SETTING_CARD_MODULE];
90
+ var MODULES = [...WIDGET_MODULES, ...ROUTE_MODULES, ...SETTING_MODULE];
91
+ function inject(args) {
92
+ const _extensionGraph = /* @__PURE__ */ new Map();
93
+ const _sources = /* @__PURE__ */ new Set([...args?.sources || []]);
94
+ let server;
95
+ let watcher;
96
+ let logger;
97
+ async function traverseDirectory(dir, file, depth) {
98
+ const baseDepth = dir.split(path.sep).length;
99
+ const crawler = new fdir().withBasePath().exclude((dirName) => dirName.startsWith("_")).filter((path2) => path2.endsWith(".tsx") || path2.endsWith(".jsx"));
100
+ if (file) {
101
+ crawler.filter(
102
+ (path2) => path2.endsWith(`${file}.tsx`) || path2.endsWith(`${file}.jsx`)
103
+ );
104
+ }
105
+ if (depth) {
106
+ crawler.filter((file2) => {
107
+ const directoryDepth = file2.split(path.sep).length - 1;
108
+ if (depth.max && directoryDepth > baseDepth + depth.max) {
109
+ return false;
110
+ }
111
+ if (directoryDepth < baseDepth + depth.min) {
112
+ return false;
113
+ }
114
+ return true;
115
+ });
116
+ }
117
+ return await crawler.crawl(dir).withPromise();
118
+ }
119
+ function generateModule(code) {
120
+ const magicString = new MagicString(code);
121
+ return {
122
+ code: magicString.toString(),
123
+ map: magicString.generateMap({ hires: true })
124
+ };
125
+ }
126
+ function validateDefaultExport(path2, ast) {
127
+ let hasComponentExport = false;
128
+ const declaration = path2.node.declaration;
129
+ if (declaration && (declaration.type === "Identifier" || declaration.type === "FunctionDeclaration")) {
130
+ const exportName = declaration.type === "Identifier" ? declaration.name : declaration.id && declaration.id.name;
131
+ if (exportName) {
132
+ try {
133
+ traverse(ast, {
134
+ VariableDeclarator({ node, scope }) {
135
+ let isDefaultExport = false;
136
+ if (node.id.type === "Identifier" && node.id.name === exportName) {
137
+ isDefaultExport = true;
138
+ }
139
+ if (!isDefaultExport) {
140
+ return;
141
+ }
142
+ traverse(
143
+ node,
144
+ {
145
+ ReturnStatement(path3) {
146
+ if (path3.node.argument?.type === "JSXElement" || path3.node.argument?.type === "JSXFragment") {
147
+ hasComponentExport = true;
148
+ }
149
+ }
150
+ },
151
+ scope
152
+ );
153
+ }
154
+ });
155
+ } catch (e) {
156
+ console.error(
157
+ `An error occured while validating the default export of '${path2}'. The following error must be resolved before continuing:
158
+ ${e}`
159
+ );
160
+ return false;
161
+ }
162
+ }
163
+ }
164
+ return hasComponentExport;
165
+ }
166
+ function getProperties(path2) {
167
+ const declaration = path2.node.declaration;
168
+ if (declaration && declaration.type === "VariableDeclaration") {
169
+ const configDeclaration = declaration.declarations.find(
170
+ (d) => d.type === "VariableDeclarator" && d.id.type === "Identifier" && d.id.name === "config"
171
+ );
172
+ if (configDeclaration && configDeclaration.init?.type === "ObjectExpression") {
173
+ return configDeclaration.init.properties;
174
+ }
175
+ }
176
+ return null;
177
+ }
178
+ function validateInjectionZone(zone) {
179
+ return injectionZones.includes(zone);
180
+ }
181
+ function validateWidgetConfig(path2, zone) {
182
+ const properties = getProperties(path2);
183
+ if (!properties) {
184
+ return { zoneIsValid: false, zoneValue: void 0 };
185
+ }
186
+ const zoneProperty = properties.find(
187
+ (p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "zone"
188
+ );
189
+ if (!zoneProperty) {
190
+ return { zoneIsValid: false, zoneValue: void 0 };
191
+ }
192
+ let zoneIsValid = false;
193
+ let zoneValue = void 0;
194
+ if (zoneProperty.value.type === "StringLiteral") {
195
+ zoneIsValid = !zone ? validateInjectionZone(zoneProperty.value.value) : zone === zoneProperty.value.value;
196
+ zoneValue = zoneProperty.value.value;
197
+ } else if (zoneProperty.value.type === "ArrayExpression") {
198
+ zoneIsValid = zoneProperty.value.elements.every((_zone) => {
199
+ if (!_zone || _zone.type !== "StringLiteral") {
200
+ return false;
201
+ }
202
+ const isZoneMatch = !zone ? true : zone === _zone.value;
203
+ return validateInjectionZone(_zone.value) && isZoneMatch;
204
+ });
205
+ zoneValue = zoneProperty.value.elements.map((e) => {
206
+ if (e && e.type === "StringLiteral") {
207
+ return e.value;
208
+ }
209
+ }).filter(Boolean);
210
+ }
211
+ return { zoneIsValid, zoneValue };
212
+ }
213
+ async function validateWidget(file, zone) {
214
+ const content = await fs.readFile(file, "utf-8");
215
+ const parserOptions = {
216
+ sourceType: "module",
217
+ plugins: ["jsx"]
218
+ };
219
+ if (file.endsWith(".tsx")) {
220
+ parserOptions.plugins?.push("typescript");
221
+ }
222
+ let ast;
223
+ try {
224
+ ast = parse(content, parserOptions);
225
+ } catch (err) {
226
+ logger.error(
227
+ `An error occured while parsing the content of ${file}:
228
+ ${err}`,
229
+ {
230
+ error: err,
231
+ timestamp: true
232
+ }
233
+ );
234
+ return { isValidWidget: false, zoneValue: void 0 };
235
+ }
236
+ let hasDefaultExport = false;
237
+ let hasNamedExport = false;
238
+ let zoneValue;
239
+ try {
240
+ traverse(ast, {
241
+ ExportDefaultDeclaration(path2) {
242
+ hasDefaultExport = validateDefaultExport(path2, ast);
243
+ },
244
+ ExportNamedDeclaration(path2) {
245
+ const { zoneIsValid, zoneValue: value } = validateWidgetConfig(
246
+ path2,
247
+ zone
248
+ );
249
+ hasNamedExport = zoneIsValid;
250
+ zoneValue = value;
251
+ }
252
+ });
253
+ } catch (err) {
254
+ logger.error(`An error occured while validating the content of ${file}`, {
255
+ error: err,
256
+ timestamp: true
257
+ });
258
+ return { isValidWidget: false, zoneValue: void 0 };
259
+ }
260
+ return { isValidWidget: hasDefaultExport && hasNamedExport, zoneValue };
261
+ }
262
+ async function generateWidgetEntrypoint(zone) {
263
+ const files = (await Promise.all(
264
+ Array.from(_sources).map(
265
+ async (source) => traverseDirectory(`${source}/widgets`)
266
+ )
267
+ )).flat();
268
+ const validatedWidgets = (await Promise.all(
269
+ files.map(async (widget) => {
270
+ const { isValidWidget } = await validateWidget(widget, zone);
271
+ return isValidWidget ? widget : null;
272
+ })
273
+ )).filter(Boolean);
274
+ if (!validatedWidgets.length) {
275
+ const code2 = `export default {
276
+ widgets: [],
277
+ }`;
278
+ return { module: generateModule(code2), paths: [] };
279
+ }
280
+ const importString = validatedWidgets.map((path2, index) => `import WidgetExt${index} from "${path2}";`).join("\n");
281
+ const exportString = `export default {
282
+ widgets: [${validatedWidgets.map((_, index) => `{ Component: WidgetExt${index} }`).join(", ")}],
283
+ }`;
284
+ const code = `${importString}
285
+ ${exportString}`;
286
+ return { module: generateModule(code), paths: validatedWidgets };
287
+ }
288
+ function validateRouteConfig(path2, requireLink) {
289
+ const properties = getProperties(path2);
290
+ if (!properties) {
291
+ return false;
292
+ }
293
+ const linkProperty = properties.find(
294
+ (p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "link"
295
+ );
296
+ if (!linkProperty && !requireLink) {
297
+ return true;
298
+ }
299
+ const linkValue = linkProperty?.value;
300
+ if (!linkValue) {
301
+ return false;
302
+ }
303
+ let labelIsValid = false;
304
+ if (linkValue.properties.some(
305
+ (p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "label" && p.value.type === "StringLiteral"
306
+ )) {
307
+ labelIsValid = true;
308
+ }
309
+ return labelIsValid;
310
+ }
311
+ async function validateRoute(file, requireLink) {
312
+ const content = await fs.readFile(file, "utf-8");
313
+ const parserOptions = {
314
+ sourceType: "module",
315
+ plugins: ["jsx"]
316
+ };
317
+ if (file.endsWith(".tsx")) {
318
+ parserOptions.plugins?.push("typescript");
319
+ }
320
+ let ast;
321
+ try {
322
+ ast = parse(content, parserOptions);
323
+ } catch (err) {
324
+ logger.error("An error occured while validating a route.", {
325
+ error: err,
326
+ timestamp: true
327
+ });
328
+ return false;
329
+ }
330
+ let hasDefaultExport = false;
331
+ let hasNamedExport = false;
332
+ try {
333
+ traverse(ast, {
334
+ ExportDefaultDeclaration(path2) {
335
+ hasDefaultExport = validateDefaultExport(path2, ast);
336
+ },
337
+ ExportNamedDeclaration(path2) {
338
+ hasNamedExport = validateRouteConfig(path2, requireLink);
339
+ }
340
+ });
341
+ } catch (err) {
342
+ logger.error("An error occured while validating a route.", {
343
+ error: err,
344
+ timestamp: true
345
+ });
346
+ return false;
347
+ }
348
+ return hasDefaultExport && hasNamedExport;
349
+ }
350
+ function createPath(file) {
351
+ return file.replace(/.*\/admin\/(routes|settings)/, "").replace(/\[([^\]]+)\]/g, ":$1").replace(/\/page\.(tsx|jsx)/, "");
352
+ }
353
+ async function generateRouteEntrypoint(get) {
354
+ const files = (await Promise.all(
355
+ Array.from(_sources).map(
356
+ async (source) => traverseDirectory(`${source}/routes`, "page", { min: 1 })
357
+ )
358
+ )).flat();
359
+ const validatedRoutes = (await Promise.all(
360
+ files.map(async (route) => {
361
+ const isValid = await validateRoute(route, get === "link");
362
+ return isValid ? route : null;
363
+ })
364
+ )).filter(Boolean);
365
+ if (!validatedRoutes.length) {
366
+ const code2 = `export default {
367
+ ${get}s: [],
368
+ }`;
369
+ return { module: generateModule(code2), paths: [] };
370
+ }
371
+ const importString = validatedRoutes.map((path2, index) => {
372
+ return get === "page" ? `import RouteExt${index} from "${path2}";` : `import { config as routeConfig${index} } from "${path2}";`;
373
+ }).join("\n");
374
+ const exportString = `export default {
375
+ ${get}s: [${validatedRoutes.map((file, index) => {
376
+ return get === "page" ? `{ path: "${createPath(file)}", file: "${file}" }` : `{ path: "${createPath(file)}", ...routeConfig${index}.link }`;
377
+ }).join(", ")}],
378
+ }`;
379
+ const code = `${importString}
380
+ ${exportString}`;
381
+ return { module: generateModule(code), paths: validatedRoutes };
382
+ }
383
+ async function validateSetting(file) {
384
+ const content = await fs.readFile(file, "utf-8");
385
+ const parserOptions = {
386
+ sourceType: "module",
387
+ plugins: ["jsx"]
388
+ };
389
+ if (file.endsWith(".tsx")) {
390
+ parserOptions.plugins?.push("typescript");
391
+ }
392
+ let ast;
393
+ try {
394
+ ast = parse(content, parserOptions);
395
+ } catch (err) {
396
+ logger.error("An error occured while validating a setting.", {
397
+ error: err,
398
+ timestamp: true
399
+ });
400
+ return false;
401
+ }
402
+ let hasDefaultExport = false;
403
+ let hasNamedExport = false;
404
+ try {
405
+ traverse(ast, {
406
+ ExportDefaultDeclaration(path2) {
407
+ hasDefaultExport = validateDefaultExport(path2, ast);
408
+ },
409
+ ExportNamedDeclaration(path2) {
410
+ hasNamedExport = validateSettingConfig(path2);
411
+ }
412
+ });
413
+ } catch (err) {
414
+ logger.error("An error occured while validating a setting.", {
415
+ error: err,
416
+ timestamp: true
417
+ });
418
+ return false;
419
+ }
420
+ return hasDefaultExport && hasNamedExport;
421
+ }
422
+ function validateSettingConfig(path2) {
423
+ const properties = getProperties(path2);
424
+ if (!properties) {
425
+ return false;
426
+ }
427
+ const cardProperty = properties.find(
428
+ (p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "card"
429
+ );
430
+ if (!cardProperty) {
431
+ return false;
432
+ }
433
+ const cardValue = cardProperty.value;
434
+ let hasLabel = false;
435
+ let hasDescription = false;
436
+ if (cardValue.properties.some(
437
+ (p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "label" && p.value.type === "StringLiteral"
438
+ )) {
439
+ hasLabel = true;
440
+ }
441
+ if (cardValue.properties.some(
442
+ (p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "description" && p.value.type === "StringLiteral"
443
+ )) {
444
+ hasDescription = true;
445
+ }
446
+ return hasLabel && hasDescription;
447
+ }
448
+ async function generateSettingEntrypoint(get) {
449
+ const files = (await Promise.all(
450
+ Array.from(_sources).map(
451
+ async (source) => traverseDirectory(`${source}/settings`, "page", { min: 1, max: 1 })
452
+ )
453
+ )).flat();
454
+ const validatedSettings = (await Promise.all(
455
+ files.map(async (setting) => {
456
+ const isValid = await validateSetting(setting);
457
+ return isValid ? setting : null;
458
+ })
459
+ )).filter(Boolean);
460
+ if (!validatedSettings.length) {
461
+ const code2 = `export default {
462
+ ${get}s: [],
463
+ }`;
464
+ return { module: generateModule(code2), paths: [] };
465
+ }
466
+ const importString = validatedSettings.map((path2, index) => {
467
+ return get === "page" ? `import SettingExt${index} from "${path2}";` : `import { config as settingConfig${index} } from "${path2}";`;
468
+ }).join("\n");
469
+ const exportString = `export default {
470
+ ${get}s: [${validatedSettings.map((file, index) => {
471
+ return get === "page" ? `{ path: "${createPath(file)}", file: "${file}" }` : `{ path: "${createPath(file)}", ...settingConfig${index}.card }`;
472
+ }).join(", ")}],
473
+ }`;
474
+ const code = `${importString}
475
+ ${exportString}`;
476
+ return { module: generateModule(code), paths: validatedSettings };
477
+ }
478
+ async function loadModule(options) {
479
+ switch (options.type) {
480
+ case "widget": {
481
+ return await generateWidgetEntrypoint(options.get);
482
+ }
483
+ case "route": {
484
+ return await generateRouteEntrypoint(options.get);
485
+ }
486
+ case "setting": {
487
+ return await generateSettingEntrypoint(options.get);
488
+ }
489
+ }
490
+ }
491
+ function getExtensionType(file) {
492
+ const normalizedPath = path.normalize(file);
493
+ if (normalizedPath.includes(path.normalize("/admin/widgets/"))) {
494
+ return "widget";
495
+ } else if (normalizedPath.includes(path.normalize("/admin/routes/"))) {
496
+ return "route";
497
+ } else if (normalizedPath.includes(path.normalize("/admin/settings/"))) {
498
+ return "setting";
499
+ } else {
500
+ return "none";
501
+ }
502
+ }
503
+ async function handleWidgetChange(file) {
504
+ const { isValidWidget, zoneValue } = await validateWidget(file);
505
+ if (!isValidWidget || !zoneValue) {
506
+ _extensionGraph.delete(file);
507
+ return;
508
+ }
509
+ const zoneValues = Array.isArray(zoneValue) ? zoneValue : [zoneValue];
510
+ for (const zone of zoneValues) {
511
+ const zonePath = zone.replace(/\./g, "/");
512
+ const moduleId = `${VIRTUAL_PREFIX}${WIDGET_MODULE}${zonePath}`;
513
+ const module = server.moduleGraph.getModuleById(moduleId);
514
+ if (module) {
515
+ await server.reloadModule(module);
516
+ }
517
+ }
518
+ }
519
+ async function handleRouteChange(file) {
520
+ const isValidRoute = await validateRoute(file, false);
521
+ if (!isValidRoute) {
522
+ _extensionGraph.delete(file);
523
+ return;
524
+ }
525
+ for (const moduleId of ROUTE_MODULES) {
526
+ const fullModuleId = `${VIRTUAL_PREFIX}${moduleId}`;
527
+ const module = server.moduleGraph.getModuleById(fullModuleId);
528
+ if (module) {
529
+ await server.reloadModule(module);
530
+ }
531
+ }
532
+ }
533
+ async function handleSettingChange(file) {
534
+ const isValidSetting = await validateSetting(file);
535
+ if (!isValidSetting) {
536
+ _extensionGraph.delete(file);
537
+ return;
538
+ }
539
+ for (const moduleId of SETTING_MODULE) {
540
+ const fullModuleId = `${VIRTUAL_PREFIX}${moduleId}`;
541
+ const module = server.moduleGraph.getModuleById(fullModuleId);
542
+ if (module) {
543
+ await server.reloadModule(module);
544
+ }
545
+ }
546
+ }
547
+ async function handleExtensionUnlink(file) {
548
+ const moduleIds = _extensionGraph.get(file);
549
+ if (!moduleIds) {
550
+ return;
551
+ }
552
+ for (const moduleId of moduleIds) {
553
+ const module = server.moduleGraph.getModuleById(moduleId);
554
+ if (module) {
555
+ _extensionGraph.delete(file);
556
+ await server.reloadModule(module);
557
+ }
558
+ }
559
+ }
560
+ async function loadModuleAndUpdateGraph(id, options) {
561
+ const { module, paths } = await loadModule(options);
562
+ for (const path2 of paths) {
563
+ const ids = _extensionGraph.get(path2) || /* @__PURE__ */ new Set();
564
+ ids.add(id);
565
+ _extensionGraph.set(path2, ids);
566
+ }
567
+ return module;
568
+ }
569
+ return {
570
+ name: "@medusajs/admin-vite-plugin",
571
+ configureServer(s) {
572
+ server = s;
573
+ logger = s.config.logger;
574
+ watcher = chokidar.watch(Array.from(_sources), {
575
+ persistent: true,
576
+ ignoreInitial: true
577
+ });
578
+ watcher.on("add", async (file) => {
579
+ const type = getExtensionType(file);
580
+ if (type === "none") {
581
+ return;
582
+ }
583
+ if (type === "widget") {
584
+ await handleWidgetChange(file);
585
+ return;
586
+ }
587
+ if (type === "route") {
588
+ await handleRouteChange(file);
589
+ return;
590
+ }
591
+ if (type === "setting") {
592
+ await handleSettingChange(file);
593
+ return;
594
+ }
595
+ return;
596
+ });
597
+ watcher.on("change", async (file) => {
598
+ const type = getExtensionType(file);
599
+ if (type === "none") {
600
+ return;
601
+ }
602
+ if (type === "widget") {
603
+ await handleWidgetChange(file);
604
+ return;
605
+ }
606
+ if (type === "route") {
607
+ await handleRouteChange(file);
608
+ return;
609
+ }
610
+ if (type === "setting") {
611
+ await handleSettingChange(file);
612
+ return;
613
+ }
614
+ return;
615
+ });
616
+ watcher.on("unlink", async (file) => {
617
+ await handleExtensionUnlink(file);
618
+ return;
619
+ });
620
+ },
621
+ resolveId(id) {
622
+ if (MODULES.includes(id)) {
623
+ return VIRTUAL_PREFIX + id;
624
+ }
625
+ return null;
626
+ },
627
+ async load(id) {
628
+ if (!id.startsWith(VIRTUAL_PREFIX)) {
629
+ return null;
630
+ }
631
+ const idNoPrefix = id.slice(VIRTUAL_PREFIX.length);
632
+ const moduleMap = {
633
+ [ROUTE_PAGE_MODULE]: { type: "route", get: "page" },
634
+ [ROUTE_LINK_MODULE]: { type: "route", get: "link" },
635
+ [SETTING_PAGE_MODULE]: { type: "setting", get: "page" },
636
+ [SETTING_CARD_MODULE]: { type: "setting", get: "card" }
637
+ };
638
+ if (WIDGET_MODULES.includes(idNoPrefix)) {
639
+ const zone = idNoPrefix.replace(WIDGET_MODULE, "").replace(/\//g, ".");
640
+ return loadModuleAndUpdateGraph(id, { type: "widget", get: zone });
641
+ }
642
+ const moduleOptions = moduleMap[idNoPrefix];
643
+ if (moduleOptions) {
644
+ return loadModuleAndUpdateGraph(id, moduleOptions);
645
+ }
646
+ return null;
647
+ },
648
+ async closeBundle() {
649
+ if (watcher) {
650
+ await watcher.close();
651
+ }
652
+ }
653
+ };
654
+ }
655
+ export {
656
+ inject as default
657
+ };
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@medusajs/admin-vite-plugin",
3
+ "version": "0.0.1",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "module": "dist/index.mjs",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.mjs",
10
+ "require": "./dist/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsup"
18
+ },
19
+ "devDependencies": {
20
+ "@babel/types": "7.22.5",
21
+ "@medusajs/admin-shared": "0.0.1",
22
+ "@types/babel__traverse": "7.20.5",
23
+ "@types/node": "^20.10.4",
24
+ "tsup": "8.0.1",
25
+ "typescript": "5.3.3",
26
+ "vite": "^5.2.11"
27
+ },
28
+ "peerDependencies": {
29
+ "vite": "^5.0.0"
30
+ },
31
+ "dependencies": {
32
+ "@babel/parser": "7.23.5",
33
+ "@babel/traverse": "7.23.5",
34
+ "chokidar": "3.5.3",
35
+ "fdir": "6.1.1",
36
+ "magic-string": "0.30.5"
37
+ },
38
+ "packageManager": "yarn@3.2.1"
39
+ }