@etohq/admin-vite-plugin 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/dist/index.js ADDED
@@ -0,0 +1,1868 @@
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: () => src_default
34
+ });
35
+ module.exports = __toCommonJS(src_exports);
36
+
37
+ // src/plugin.ts
38
+ var import_path2 = __toESM(require("path"));
39
+
40
+ // src/custom-fields/generate-custom-field-displays.ts
41
+ var import_admin_shared2 = require("@etohq/admin-shared");
42
+ var import_promises = __toESM(require("fs/promises"));
43
+
44
+ // src/babel.ts
45
+ var import_parser = require("@babel/parser");
46
+ var import_traverse = __toESM(require("@babel/traverse"));
47
+ var import_types = require("@babel/types");
48
+ var traverse;
49
+ if (typeof import_traverse.default === "function") {
50
+ traverse = import_traverse.default;
51
+ } else {
52
+ traverse = import_traverse.default.default;
53
+ }
54
+
55
+ // src/logger.ts
56
+ var import_picocolors = __toESM(require("picocolors"));
57
+ function getTimestamp() {
58
+ const now = /* @__PURE__ */ new Date();
59
+ return now.toLocaleTimeString("en-US", { hour12: true });
60
+ }
61
+ function getPrefix(type) {
62
+ const timestamp = import_picocolors.default.dim(getTimestamp());
63
+ const typeColor = type === "warn" ? import_picocolors.default.yellow : type === "info" ? import_picocolors.default.green : import_picocolors.default.red;
64
+ const prefix = typeColor("[@etohq/admin-vite-plugin]");
65
+ return `${timestamp} ${prefix}`;
66
+ }
67
+ function getFile(options) {
68
+ if (!options.file) {
69
+ return "";
70
+ }
71
+ const value = Array.isArray(options.file) ? options.file.map((f) => f).join(", ") : options.file;
72
+ return import_picocolors.default.dim(`${value}`);
73
+ }
74
+ function formatError(error) {
75
+ if (error instanceof Error) {
76
+ return import_picocolors.default.red(`${error.name}: ${error.message}
77
+ ${error.stack}`);
78
+ } else if (typeof error === "object") {
79
+ return import_picocolors.default.red(JSON.stringify(error, null, 2));
80
+ } else {
81
+ return import_picocolors.default.red(String(error));
82
+ }
83
+ }
84
+ var logger = {
85
+ warn(msg, options = {}) {
86
+ console.warn(`${getPrefix("warn")} ${msg} ${getFile(options)}`);
87
+ },
88
+ info(msg, options = {}) {
89
+ console.info(`${getPrefix("info")} ${msg} ${getFile(options)}`);
90
+ },
91
+ error(msg, options = {}) {
92
+ console.error(`${getPrefix("error")} ${msg} ${getFile(options)}`);
93
+ if (options.error) {
94
+ console.error(formatError(options.error));
95
+ }
96
+ }
97
+ };
98
+
99
+ // src/utils.ts
100
+ var import_fdir = require("fdir");
101
+ var import_magic_string = __toESM(require("magic-string"));
102
+ var import_node_crypto = __toESM(require("crypto"));
103
+ var import_path = __toESM(require("path"));
104
+ function normalizePath(file) {
105
+ return import_path.default.normalize(file).replace(/\\/g, "/");
106
+ }
107
+ function getParserOptions(file) {
108
+ const options = {
109
+ sourceType: "module",
110
+ plugins: ["jsx"]
111
+ };
112
+ if (file.endsWith(".tsx")) {
113
+ options.plugins?.push("typescript");
114
+ }
115
+ return options;
116
+ }
117
+ function generateModule(code) {
118
+ const magicString = new import_magic_string.default(code);
119
+ return {
120
+ code: magicString.toString(),
121
+ map: magicString.generateMap({ hires: true })
122
+ };
123
+ }
124
+ var VALID_FILE_EXTENSIONS = [".tsx", ".jsx"];
125
+ async function crawl(dir, file, depth) {
126
+ const dirDepth = dir.split(import_path.default.sep).length;
127
+ const crawler = new import_fdir.fdir().withBasePath().exclude((dirName) => dirName.startsWith("_")).filter((path3) => {
128
+ return VALID_FILE_EXTENSIONS.some((ext) => path3.endsWith(ext));
129
+ });
130
+ if (file) {
131
+ crawler.filter((path3) => {
132
+ return VALID_FILE_EXTENSIONS.some((ext) => path3.endsWith(file + ext));
133
+ });
134
+ }
135
+ if (depth) {
136
+ crawler.filter((file2) => {
137
+ const pathDepth = file2.split(import_path.default.sep).length - 1;
138
+ if (depth.max && pathDepth > dirDepth + depth.max) {
139
+ return false;
140
+ }
141
+ if (pathDepth < dirDepth + depth.min) {
142
+ return false;
143
+ }
144
+ return true;
145
+ });
146
+ }
147
+ return crawler.crawl(dir).withPromise();
148
+ }
149
+ function getConfigObjectProperties(path3) {
150
+ const declaration = path3.node.declaration;
151
+ if ((0, import_types.isVariableDeclaration)(declaration)) {
152
+ const configDeclaration = declaration.declarations.find(
153
+ (d) => (0, import_types.isVariableDeclarator)(d) && (0, import_types.isIdentifier)(d.id, { name: "config" })
154
+ );
155
+ if (configDeclaration && (0, import_types.isCallExpression)(configDeclaration.init) && configDeclaration.init.arguments.length > 0 && (0, import_types.isObjectExpression)(configDeclaration.init.arguments[0])) {
156
+ return configDeclaration.init.arguments[0].properties;
157
+ }
158
+ }
159
+ return null;
160
+ }
161
+ async function hasDefaultExport(ast) {
162
+ let hasDefaultExport2 = false;
163
+ traverse(ast, {
164
+ ExportDefaultDeclaration() {
165
+ hasDefaultExport2 = true;
166
+ }
167
+ });
168
+ return hasDefaultExport2;
169
+ }
170
+ function generateHash(content) {
171
+ return import_node_crypto.default.createHash("md5").update(content).digest("hex");
172
+ }
173
+ function isFileInAdminSubdirectory(file, subdirectory) {
174
+ const normalizedPath = normalizePath(file);
175
+ return normalizedPath.includes(`/src/admin/${subdirectory}/`);
176
+ }
177
+
178
+ // src/custom-fields/helpers.ts
179
+ var import_admin_shared = require("@etohq/admin-shared");
180
+ function getModel(path3, file) {
181
+ const configArgument = getConfigArgument(path3);
182
+ if (!configArgument) {
183
+ return null;
184
+ }
185
+ const modelProperty = configArgument.properties.find(
186
+ (p) => (0, import_types.isObjectProperty)(p) && (0, import_types.isIdentifier)(p.key, { name: "model" })
187
+ );
188
+ if (!modelProperty) {
189
+ return null;
190
+ }
191
+ if ((0, import_types.isTemplateLiteral)(modelProperty.value)) {
192
+ logger.warn(
193
+ `'model' property cannot be a template literal (e.g. \`product\`).`,
194
+ { file }
195
+ );
196
+ return null;
197
+ }
198
+ if (!(0, import_types.isStringLiteral)(modelProperty.value)) {
199
+ logger.warn(
200
+ `'model' is invalid. The 'model' property must be a string literal, e.g. 'product' or 'customer'.`,
201
+ { file }
202
+ );
203
+ return null;
204
+ }
205
+ const model = modelProperty.value.value.trim();
206
+ if (!(0, import_admin_shared.isValidCustomFieldModel)(model)) {
207
+ logger.warn(
208
+ `'model' is invalid, received: ${model}. The 'model' property must be set to a valid model, e.g. 'product' or 'customer'.`,
209
+ { file }
210
+ );
211
+ return null;
212
+ }
213
+ return model;
214
+ }
215
+ function getConfigArgument(path3) {
216
+ if (!(0, import_types.isCallExpression)(path3.node.declaration)) {
217
+ return null;
218
+ }
219
+ if (!(0, import_types.isIdentifier)(path3.node.declaration.callee, {
220
+ name: "unstable_defineCustomFieldsConfig"
221
+ })) {
222
+ return null;
223
+ }
224
+ const configArgument = path3.node.declaration.arguments[0];
225
+ if (!(0, import_types.isObjectExpression)(configArgument)) {
226
+ return null;
227
+ }
228
+ return configArgument;
229
+ }
230
+ function validateLink(path3, file) {
231
+ const configArgument = getConfigArgument(path3);
232
+ if (!configArgument) {
233
+ return false;
234
+ }
235
+ const linkProperty = configArgument.properties.find(
236
+ (p) => (0, import_types.isObjectProperty)(p) && (0, import_types.isIdentifier)(p.key, { name: "link" })
237
+ );
238
+ if (!linkProperty) {
239
+ logger.warn(`'link' property is missing.`, { file });
240
+ return false;
241
+ }
242
+ return true;
243
+ }
244
+
245
+ // src/custom-fields/generate-custom-field-displays.ts
246
+ async function generateCustomFieldDisplays(sources) {
247
+ const files = await getFilesFromSources(sources);
248
+ const results = await getCustomFieldDisplayResults(files);
249
+ const imports = results.map((result) => result.import).flat();
250
+ const code = generateDisplayCode(results);
251
+ return {
252
+ imports,
253
+ code
254
+ };
255
+ }
256
+ async function getFilesFromSources(sources) {
257
+ const files = (await Promise.all(
258
+ Array.from(sources).map(
259
+ async (source) => crawl(`${source}/custom-fields`)
260
+ )
261
+ )).flat();
262
+ return files;
263
+ }
264
+ function generateDisplayCode(results) {
265
+ const groupedByModel = /* @__PURE__ */ new Map();
266
+ results.forEach((result) => {
267
+ const model = result.model;
268
+ if (!groupedByModel.has(model)) {
269
+ groupedByModel.set(model, []);
270
+ }
271
+ groupedByModel.get(model).push(result);
272
+ });
273
+ const segments = [];
274
+ groupedByModel.forEach((results2, model) => {
275
+ const displays = results2.map((result) => formatDisplays(result.displays)).filter((display) => display !== "").join(",\n");
276
+ segments.push(`
277
+ ${model}: [
278
+ ${displays}
279
+ ],
280
+ `);
281
+ });
282
+ return `
283
+ displays: {
284
+ ${segments.join("\n")}
285
+ }
286
+ `;
287
+ }
288
+ function formatDisplays(displays) {
289
+ if (!displays || displays.length === 0) {
290
+ return "";
291
+ }
292
+ return displays.map(
293
+ (display) => `
294
+ {
295
+ zone: "${display.zone}",
296
+ Component: ${display.Component},
297
+ }
298
+ `
299
+ ).join(",\n");
300
+ }
301
+ async function getCustomFieldDisplayResults(files) {
302
+ return (await Promise.all(
303
+ files.map(async (file, index) => parseDisplayFile(file, index))
304
+ )).filter(Boolean);
305
+ }
306
+ async function parseDisplayFile(file, index) {
307
+ const content = await import_promises.default.readFile(file, "utf8");
308
+ let ast;
309
+ try {
310
+ ast = (0, import_parser.parse)(content, getParserOptions(file));
311
+ } catch (e) {
312
+ logger.error(`An error occurred while parsing the file`, { file, error: e });
313
+ return null;
314
+ }
315
+ const import_ = generateImport(file, index);
316
+ let displays = null;
317
+ let model = null;
318
+ let hasLink = false;
319
+ try {
320
+ traverse(ast, {
321
+ ExportDefaultDeclaration(path3) {
322
+ const _model = getModel(path3, file);
323
+ if (!_model) {
324
+ return;
325
+ }
326
+ model = _model;
327
+ displays = getDisplays(path3, model, index, file);
328
+ hasLink = validateLink(path3, file);
329
+ }
330
+ });
331
+ } catch (err) {
332
+ logger.error(`An error occurred while traversing the file.`, {
333
+ file,
334
+ error: err
335
+ });
336
+ return null;
337
+ }
338
+ if (!model) {
339
+ logger.warn(`'model' property is missing.`, { file });
340
+ return null;
341
+ }
342
+ if (!hasLink) {
343
+ logger.warn(`'link' property is missing.`, { file });
344
+ return null;
345
+ }
346
+ return {
347
+ import: import_,
348
+ model,
349
+ displays
350
+ };
351
+ }
352
+ function getDisplays(path3, model, index, file) {
353
+ const configArgument = getConfigArgument(path3);
354
+ if (!configArgument) {
355
+ return null;
356
+ }
357
+ const displayProperty = configArgument.properties.find(
358
+ (p) => (0, import_types.isObjectProperty)(p) && (0, import_types.isIdentifier)(p.key, { name: "displays" })
359
+ );
360
+ if (!displayProperty) {
361
+ return null;
362
+ }
363
+ if (!(0, import_types.isArrayExpression)(displayProperty.value)) {
364
+ logger.warn(
365
+ `'displays' is not an array. The 'displays' property must be an array of objects.`,
366
+ { file }
367
+ );
368
+ return null;
369
+ }
370
+ const displays = [];
371
+ displayProperty.value.elements.forEach((element, j) => {
372
+ if (!(0, import_types.isObjectExpression)(element)) {
373
+ return;
374
+ }
375
+ const zoneProperty = element.properties.find(
376
+ (p) => (0, import_types.isObjectProperty)(p) && (0, import_types.isIdentifier)(p.key, { name: "zone" })
377
+ );
378
+ if (!zoneProperty) {
379
+ logger.warn(
380
+ `'zone' property is missing at the ${j} index of the 'displays' property.`,
381
+ { file }
382
+ );
383
+ return;
384
+ }
385
+ if (!(0, import_types.isStringLiteral)(zoneProperty.value)) {
386
+ logger.warn(
387
+ `'zone' property at index ${j} in the 'displays' property is not a string literal. 'zone' must be a string literal, e.g. 'general' or 'attributes'.`,
388
+ { file }
389
+ );
390
+ return;
391
+ }
392
+ const zone = zoneProperty.value.value;
393
+ const fullPath = getDisplayEntryPath(model, zone);
394
+ if (!(0, import_admin_shared2.isValidCustomFieldDisplayZone)(zone) || !(0, import_admin_shared2.isValidCustomFieldDisplayPath)(fullPath)) {
395
+ logger.warn(
396
+ `'zone' is invalid at index ${j} in the 'displays' property. Received: ${zone}.`,
397
+ { file }
398
+ );
399
+ return;
400
+ }
401
+ const componentProperty = element.properties.find(
402
+ (p) => (0, import_types.isObjectProperty)(p) && (0, import_types.isIdentifier)(p.key, { name: "component" })
403
+ );
404
+ if (!componentProperty) {
405
+ logger.warn(
406
+ `'component' property is missing at index ${j} in the 'displays' property.`,
407
+ { file }
408
+ );
409
+ return;
410
+ }
411
+ displays.push({
412
+ zone,
413
+ Component: getDisplayComponent(index, j)
414
+ });
415
+ });
416
+ return displays.length > 0 ? displays : null;
417
+ }
418
+ function getDisplayEntryPath(model, zone) {
419
+ return `${model}.${zone}.$display`;
420
+ }
421
+ function getDisplayComponent(fileIndex, displayEntryIndex) {
422
+ const import_ = generateCustomFieldConfigName(fileIndex);
423
+ return `${import_}.displays[${displayEntryIndex}].component`;
424
+ }
425
+ function generateCustomFieldConfigName(index) {
426
+ return `CustomFieldConfig${index}`;
427
+ }
428
+ function generateImport(file, index) {
429
+ const path3 = normalizePath(file);
430
+ return `import ${generateCustomFieldConfigName(index)} from "${path3}"`;
431
+ }
432
+
433
+ // src/custom-fields/generate-custom-field-forms.ts
434
+ var import_admin_shared3 = require("@etohq/admin-shared");
435
+ var import_promises2 = __toESM(require("fs/promises"));
436
+ var import_outdent = require("outdent");
437
+ async function generateCustomFieldForms(sources) {
438
+ const files = await getFilesFromSources2(sources);
439
+ const results = await getCustomFieldResults(files);
440
+ const imports = results.map((result) => result.import).flat();
441
+ const code = generateCode(results);
442
+ return {
443
+ imports,
444
+ code
445
+ };
446
+ }
447
+ async function getFilesFromSources2(sources) {
448
+ const files = (await Promise.all(
449
+ Array.from(sources).map(
450
+ async (source) => crawl(`${source}/custom-fields`)
451
+ )
452
+ )).flat();
453
+ return files;
454
+ }
455
+ function generateCode(results) {
456
+ const groupedByModel = /* @__PURE__ */ new Map();
457
+ results.forEach((result) => {
458
+ const model = result.model;
459
+ if (!groupedByModel.has(model)) {
460
+ groupedByModel.set(model, []);
461
+ }
462
+ groupedByModel.get(model).push(result);
463
+ });
464
+ const segments = [];
465
+ groupedByModel.forEach((results2, model) => {
466
+ const configs = results2.map((result) => formatConfig(result.configs)).filter((config) => config !== "").join(",\n");
467
+ const forms = results2.map((result) => formatForms(result.forms)).filter((form) => form !== "").join(",\n");
468
+ segments.push(import_outdent.outdent`
469
+ ${model}: {
470
+ configs: [
471
+ ${configs}
472
+ ],
473
+ forms: [
474
+ ${forms}
475
+ ],
476
+ }
477
+ `);
478
+ });
479
+ return import_outdent.outdent`
480
+ customFields: {
481
+ ${segments.join("\n")}
482
+ }
483
+ `;
484
+ }
485
+ function formatConfig(configs) {
486
+ if (!configs || configs.length === 0) {
487
+ return "";
488
+ }
489
+ return import_outdent.outdent`
490
+ ${configs.map(
491
+ (config) => import_outdent.outdent`
492
+ {
493
+ zone: "${config.zone}",
494
+ fields: {
495
+ ${config.fields.map(
496
+ (field) => `${field.name}: {
497
+ defaultValue: ${field.defaultValue},
498
+ validation: ${field.validation},
499
+ }`
500
+ ).join(",\n")}
501
+ },
502
+ }
503
+ `
504
+ ).join(",\n")}
505
+ `;
506
+ }
507
+ function formatForms(forms) {
508
+ if (!forms || forms.length === 0) {
509
+ return "";
510
+ }
511
+ return forms.map(
512
+ (form) => import_outdent.outdent`
513
+ {
514
+ zone: "${form.zone}",
515
+ tab: ${form.tab === void 0 ? void 0 : `"${form.tab}"`},
516
+ fields: {
517
+ ${form.fields.map(
518
+ (field) => `${field.name}: {
519
+ validation: ${field.validation},
520
+ Component: ${field.Component},
521
+ label: ${field.label},
522
+ description: ${field.description},
523
+ placeholder: ${field.placeholder},
524
+ }`
525
+ ).join(",\n")}
526
+ },
527
+ }
528
+ `
529
+ ).join(",\n");
530
+ }
531
+ async function getCustomFieldResults(files) {
532
+ return (await Promise.all(files.map(async (file, index) => parseFile(file, index)))).filter(Boolean);
533
+ }
534
+ async function parseFile(file, index) {
535
+ const content = await import_promises2.default.readFile(file, "utf8");
536
+ let ast;
537
+ try {
538
+ ast = (0, import_parser.parse)(content, getParserOptions(file));
539
+ } catch (e) {
540
+ logger.error(`An error occurred while parsing the file`, { file, error: e });
541
+ return null;
542
+ }
543
+ const import_ = generateImport2(file, index);
544
+ let configs = [];
545
+ let forms = [];
546
+ let model = null;
547
+ let hasLink = false;
548
+ try {
549
+ traverse(ast, {
550
+ ExportDefaultDeclaration(path3) {
551
+ const _model = getModel(path3, file);
552
+ if (!_model) {
553
+ return;
554
+ }
555
+ model = _model;
556
+ hasLink = validateLink(path3, file);
557
+ configs = getConfigs(path3, model, index, file);
558
+ forms = getForms(path3, model, index, file);
559
+ }
560
+ });
561
+ } catch (err) {
562
+ logger.error(`An error occurred while traversing the file.`, {
563
+ file,
564
+ error: err
565
+ });
566
+ return null;
567
+ }
568
+ if (!model) {
569
+ logger.warn(`'model' property is missing.`, { file });
570
+ return null;
571
+ }
572
+ if (!hasLink) {
573
+ logger.warn(`'link' property is missing.`, { file });
574
+ return null;
575
+ }
576
+ return {
577
+ import: import_,
578
+ model,
579
+ configs,
580
+ forms
581
+ };
582
+ }
583
+ function generateCustomFieldConfigName2(index) {
584
+ return `CustomFieldConfig${index}`;
585
+ }
586
+ function generateImport2(file, index) {
587
+ const path3 = normalizePath(file);
588
+ return `import ${generateCustomFieldConfigName2(index)} from "${path3}"`;
589
+ }
590
+ function getForms(path3, model, index, file) {
591
+ const formArray = getFormsArgument(path3, file);
592
+ if (!formArray) {
593
+ return null;
594
+ }
595
+ const forms = [];
596
+ formArray.elements.forEach((element, j) => {
597
+ if (!(0, import_types.isObjectExpression)(element)) {
598
+ return;
599
+ }
600
+ const zoneProperty = element.properties.find(
601
+ (p) => (0, import_types.isObjectProperty)(p) && (0, import_types.isIdentifier)(p.key, { name: "zone" })
602
+ );
603
+ if (!zoneProperty) {
604
+ logger.warn(
605
+ `'zone' property is missing from the ${j} index of the 'forms' property. The 'zone' property is required to load a custom field form.`,
606
+ { file }
607
+ );
608
+ return;
609
+ }
610
+ if (!(0, import_types.isStringLiteral)(zoneProperty.value)) {
611
+ logger.warn(
612
+ `'zone' property at the ${j} index of the 'forms' property is not a string literal. The 'zone' property must be a string literal, e.g. 'general' or 'attributes'.`,
613
+ { file }
614
+ );
615
+ return;
616
+ }
617
+ const tabProperty = element.properties.find(
618
+ (p) => (0, import_types.isObjectProperty)(p) && (0, import_types.isIdentifier)(p.key, { name: "tab" })
619
+ );
620
+ let tab;
621
+ if (tabProperty) {
622
+ if (!(0, import_types.isStringLiteral)(tabProperty.value)) {
623
+ logger.warn(
624
+ `'tab' property at the ${j} index of the 'forms' property is not a string literal. The 'tab' property must be a string literal, e.g. 'general' or 'attributes'.`,
625
+ { file }
626
+ );
627
+ return;
628
+ }
629
+ tab = tabProperty.value.value;
630
+ }
631
+ if (tab && !(0, import_admin_shared3.isValidCustomFieldFormTab)(tab)) {
632
+ logger.warn(
633
+ `'tab' property at the ${j} index of the 'forms' property is not a valid custom field form tab for the '${model}' model. Received: ${tab}.`,
634
+ { file }
635
+ );
636
+ return;
637
+ }
638
+ const zone = zoneProperty.value.value;
639
+ const fullPath = getFormEntryFieldPath(model, zone, tab);
640
+ if (!(0, import_admin_shared3.isValidCustomFieldFormZone)(zone) || !(0, import_admin_shared3.isValidCustomFieldFormFieldPath)(fullPath)) {
641
+ logger.warn(
642
+ `'zone' and 'tab' properties at the ${j} index of the 'forms' property are not a valid for the '${model}' model. Received: { zone: ${zone}, tab: ${tab} }.`,
643
+ { file }
644
+ );
645
+ return;
646
+ }
647
+ const fieldsObject = element.properties.find(
648
+ (p) => (0, import_types.isObjectProperty)(p) && (0, import_types.isIdentifier)(p.key, { name: "fields" })
649
+ );
650
+ if (!fieldsObject) {
651
+ logger.warn(
652
+ `The 'fields' property is missing at the ${j} index of the 'forms' property. The 'fields' property is required to load a custom field form.`,
653
+ { file }
654
+ );
655
+ return;
656
+ }
657
+ const fields = [];
658
+ if (!(0, import_types.isObjectExpression)(fieldsObject.value)) {
659
+ logger.warn(
660
+ `The 'fields' property at the ${j} index of the 'forms' property is malformed. The 'fields' property must be an object.`,
661
+ { file }
662
+ );
663
+ return;
664
+ }
665
+ fieldsObject.value.properties.forEach((field) => {
666
+ if (!(0, import_types.isObjectProperty)(field) || !(0, import_types.isIdentifier)(field.key)) {
667
+ return;
668
+ }
669
+ const name = field.key.name;
670
+ if (!(0, import_types.isObjectExpression)(field.value) && !((0, import_types.isCallExpression)(field.value) && (0, import_types.isMemberExpression)(field.value.callee) && (0, import_types.isIdentifier)(field.value.callee.object) && (0, import_types.isIdentifier)(field.value.callee.property) && field.value.callee.object.name === "form" && field.value.callee.property.name === "define" && field.value.arguments.length === 1 && (0, import_types.isObjectExpression)(field.value.arguments[0]))) {
671
+ logger.warn(
672
+ `'${name}' property in the 'fields' property at the ${j} index of the 'forms' property in ${file} is malformed. The property must be an object or a call to form.define().`,
673
+ { file }
674
+ );
675
+ return;
676
+ }
677
+ const fieldObject = (0, import_types.isObjectExpression)(field.value) ? field.value : field.value.arguments[0];
678
+ const labelProperty = fieldObject.properties.find(
679
+ (p) => (0, import_types.isObjectProperty)(p) && (0, import_types.isIdentifier)(p.key, { name: "label" })
680
+ );
681
+ const descriptionProperty = fieldObject.properties.find(
682
+ (p) => (0, import_types.isObjectProperty)(p) && (0, import_types.isIdentifier)(p.key, { name: "description" })
683
+ );
684
+ const componentProperty = fieldObject.properties.find(
685
+ (p) => (0, import_types.isObjectProperty)(p) && (0, import_types.isIdentifier)(p.key, { name: "component" })
686
+ );
687
+ const validationProperty = fieldObject.properties.find(
688
+ (p) => (0, import_types.isObjectProperty)(p) && (0, import_types.isIdentifier)(p.key, { name: "validation" })
689
+ );
690
+ const placeholderProperty = fieldObject.properties.find(
691
+ (p) => (0, import_types.isObjectProperty)(p) && (0, import_types.isIdentifier)(p.key, { name: "placeholder" })
692
+ );
693
+ const label = getFormFieldSectionValue(
694
+ !!labelProperty,
695
+ index,
696
+ j,
697
+ name,
698
+ "label"
699
+ );
700
+ const description = getFormFieldSectionValue(
701
+ !!descriptionProperty,
702
+ index,
703
+ j,
704
+ name,
705
+ "description"
706
+ );
707
+ const placeholder = getFormFieldSectionValue(
708
+ !!placeholderProperty,
709
+ index,
710
+ j,
711
+ name,
712
+ "placeholder"
713
+ );
714
+ const component = getFormFieldSectionValue(
715
+ !!componentProperty,
716
+ index,
717
+ j,
718
+ name,
719
+ "component"
720
+ );
721
+ const validation = getFormFieldSectionValue(
722
+ !!validationProperty,
723
+ index,
724
+ j,
725
+ name,
726
+ "validation"
727
+ );
728
+ fields.push({
729
+ name,
730
+ label,
731
+ description,
732
+ Component: component,
733
+ validation,
734
+ placeholder
735
+ });
736
+ });
737
+ forms.push({
738
+ zone,
739
+ tab,
740
+ fields
741
+ });
742
+ });
743
+ return forms.length > 0 ? forms : null;
744
+ }
745
+ function getFormFieldSectionValue(exists, fileIndex, formIndex, fieldKey, value) {
746
+ if (!exists) {
747
+ return "undefined";
748
+ }
749
+ const import_ = generateCustomFieldConfigName2(fileIndex);
750
+ return `${import_}.forms[${formIndex}].fields.${fieldKey}.${value}`;
751
+ }
752
+ function getFormEntryFieldPath(model, zone, tab) {
753
+ return `${model}.${zone}.${tab ? `${tab}.` : ""}$field`;
754
+ }
755
+ function getConfigs(path3, model, index, file) {
756
+ const formArray = getFormsArgument(path3, file);
757
+ if (!formArray) {
758
+ logger.warn(`'forms' property is missing.`, { file });
759
+ return null;
760
+ }
761
+ const configs = [];
762
+ formArray.elements.forEach((element, j) => {
763
+ if (!(0, import_types.isObjectExpression)(element)) {
764
+ logger.warn(
765
+ `'forms' property at the ${j} index is malformed. The 'forms' property must be an object.`,
766
+ { file }
767
+ );
768
+ return;
769
+ }
770
+ const zoneProperty = element.properties.find(
771
+ (p) => (0, import_types.isObjectProperty)(p) && (0, import_types.isIdentifier)(p.key, { name: "zone" })
772
+ );
773
+ if (!zoneProperty) {
774
+ logger.warn(
775
+ `'zone' property is missing from the ${j} index of the 'forms' property.`,
776
+ { file }
777
+ );
778
+ return;
779
+ }
780
+ if ((0, import_types.isTemplateLiteral)(zoneProperty.value)) {
781
+ logger.warn(
782
+ `'zone' property at the ${j} index of the 'forms' property cannot be a template literal (e.g. \`general\`).`,
783
+ { file }
784
+ );
785
+ return;
786
+ }
787
+ if (!(0, import_types.isStringLiteral)(zoneProperty.value)) {
788
+ logger.warn(
789
+ `'zone' property at the ${j} index of the 'forms' property is not a string literal (e.g. 'general' or 'attributes').`,
790
+ { file }
791
+ );
792
+ return;
793
+ }
794
+ const zone = zoneProperty.value.value;
795
+ const fullPath = getFormEntryConfigPath(model, zone);
796
+ if (!(0, import_admin_shared3.isValidCustomFieldFormZone)(zone) || !(0, import_admin_shared3.isValidCustomFieldFormConfigPath)(fullPath)) {
797
+ logger.warn(
798
+ `'zone' property at the ${j} index of the 'forms' property is not a valid custom field form zone for the '${model}' model. Received: ${zone}.`
799
+ );
800
+ return;
801
+ }
802
+ const fieldsObject = element.properties.find(
803
+ (p) => (0, import_types.isObjectProperty)(p) && (0, import_types.isIdentifier)(p.key, { name: "fields" })
804
+ );
805
+ if (!fieldsObject) {
806
+ logger.warn(
807
+ `'fields' property is missing from the ${j} entry in the 'forms' property in ${file}.`,
808
+ { file }
809
+ );
810
+ return;
811
+ }
812
+ const fields = [];
813
+ if (!(0, import_types.isObjectExpression)(fieldsObject.value)) {
814
+ logger.warn(
815
+ `'fields' property at the ${j} index of the 'forms' property is malformed. The 'fields' property must be an object.`,
816
+ { file }
817
+ );
818
+ return;
819
+ }
820
+ fieldsObject.value.properties.forEach((field) => {
821
+ if (!(0, import_types.isObjectProperty)(field) || !(0, import_types.isIdentifier)(field.key)) {
822
+ return;
823
+ }
824
+ const name = field.key.name;
825
+ if (!(0, import_types.isObjectExpression)(field.value) && !((0, import_types.isCallExpression)(field.value) && (0, import_types.isMemberExpression)(field.value.callee) && (0, import_types.isIdentifier)(field.value.callee.object) && (0, import_types.isIdentifier)(field.value.callee.property) && field.value.callee.object.name === "form" && field.value.callee.property.name === "define" && field.value.arguments.length === 1 && (0, import_types.isObjectExpression)(field.value.arguments[0]))) {
826
+ logger.warn(
827
+ `'${name}' property in the 'fields' property at the ${j} index of the 'forms' property in ${file} is malformed. The property must be an object or a call to form.define().`,
828
+ { file }
829
+ );
830
+ return;
831
+ }
832
+ const fieldObject = (0, import_types.isObjectExpression)(field.value) ? field.value : field.value.arguments[0];
833
+ const defaultValueProperty = fieldObject.properties.find(
834
+ (p) => (0, import_types.isObjectProperty)(p) && (0, import_types.isIdentifier)(p.key, { name: "defaultValue" })
835
+ );
836
+ if (!defaultValueProperty) {
837
+ logger.warn(
838
+ `'defaultValue' property is missing at the ${j} index of the 'forms' property in ${file}.`,
839
+ { file }
840
+ );
841
+ return;
842
+ }
843
+ const validationProperty = fieldObject.properties.find(
844
+ (p) => (0, import_types.isObjectProperty)(p) && (0, import_types.isIdentifier)(p.key, { name: "validation" })
845
+ );
846
+ if (!validationProperty) {
847
+ logger.warn(
848
+ `'validation' property is missing at the ${j} index of the 'forms' property in ${file}.`,
849
+ { file }
850
+ );
851
+ return;
852
+ }
853
+ const defaultValue = getFormFieldValue(index, j, name, "defaultValue");
854
+ const validation = getFormFieldValue(index, j, name, "validation");
855
+ fields.push({
856
+ name,
857
+ defaultValue,
858
+ validation
859
+ });
860
+ });
861
+ configs.push({
862
+ zone,
863
+ fields
864
+ });
865
+ });
866
+ return configs.length > 0 ? configs : null;
867
+ }
868
+ function getFormFieldValue(fileIndex, formIndex, fieldKey, value) {
869
+ const import_ = generateCustomFieldConfigName2(fileIndex);
870
+ return `${import_}.forms[${formIndex}].fields.${fieldKey}.${value}`;
871
+ }
872
+ function getFormEntryConfigPath(model, zone) {
873
+ return `${model}.${zone}.$config`;
874
+ }
875
+ function getFormsArgument(path3, file) {
876
+ const configArgument = getConfigArgument(path3);
877
+ if (!configArgument) {
878
+ return null;
879
+ }
880
+ const formProperty = configArgument.properties.find(
881
+ (p) => (0, import_types.isObjectProperty)(p) && (0, import_types.isIdentifier)(p.key, { name: "forms" })
882
+ );
883
+ if (!formProperty) {
884
+ return null;
885
+ }
886
+ if (!(0, import_types.isArrayExpression)(formProperty.value)) {
887
+ logger.warn(
888
+ `The 'forms' property is malformed. The 'forms' property must be an array of objects.`,
889
+ { file }
890
+ );
891
+ return null;
892
+ }
893
+ return formProperty.value;
894
+ }
895
+
896
+ // src/custom-fields/generate-custom-field-hashes.ts
897
+ var import_promises3 = __toESM(require("fs/promises"));
898
+ async function generateCustomFieldHashes(sources) {
899
+ const files = await getFilesFromSources3(sources);
900
+ const contents = await Promise.all(files.map(getCustomFieldContents));
901
+ const linkContents = contents.map((c) => c.link).filter(Boolean);
902
+ const formContents = contents.map((c) => c.form).filter(Boolean);
903
+ const displayContents = contents.map((c) => c.display).filter(Boolean);
904
+ const totalLinkContent = linkContents.join("");
905
+ const totalFormContent = formContents.join("");
906
+ const totalDisplayContent = displayContents.join("");
907
+ return {
908
+ linkHash: generateHash(totalLinkContent),
909
+ formHash: generateHash(totalFormContent),
910
+ displayHash: generateHash(totalDisplayContent)
911
+ };
912
+ }
913
+ async function getFilesFromSources3(sources) {
914
+ return (await Promise.all(
915
+ Array.from(sources).map(
916
+ async (source) => crawl(`${source}/custom-fields`)
917
+ )
918
+ )).flat();
919
+ }
920
+ async function getCustomFieldContents(file) {
921
+ const code = await import_promises3.default.readFile(file, "utf-8");
922
+ let ast = null;
923
+ try {
924
+ ast = (0, import_parser.parse)(code, getParserOptions(file));
925
+ } catch (e) {
926
+ logger.error(`An error occurred while parsing the file.`, {
927
+ file,
928
+ error: e
929
+ });
930
+ return { link: null, form: null, display: null };
931
+ }
932
+ let linkContent = null;
933
+ let formContent = null;
934
+ let displayContent = null;
935
+ try {
936
+ traverse(ast, {
937
+ ExportDefaultDeclaration(path3) {
938
+ const configArgument = getConfigArgument(path3);
939
+ if (!configArgument) {
940
+ return;
941
+ }
942
+ configArgument.properties.forEach((prop) => {
943
+ if (!(0, import_types.isObjectProperty)(prop) || !prop.key || !(0, import_types.isIdentifier)(prop.key)) {
944
+ return;
945
+ }
946
+ switch (prop.key.name) {
947
+ case "link":
948
+ linkContent = code.slice(prop.start, prop.end);
949
+ break;
950
+ case "forms":
951
+ formContent = code.slice(prop.start, prop.end);
952
+ break;
953
+ case "display":
954
+ displayContent = code.slice(prop.start, prop.end);
955
+ break;
956
+ }
957
+ });
958
+ }
959
+ });
960
+ } catch (e) {
961
+ logger.error(
962
+ `An error occurred while processing ${file}. See the below error for more details:
963
+ ${e}`,
964
+ { file, error: e }
965
+ );
966
+ return { link: null, form: null, display: null };
967
+ }
968
+ return { link: linkContent, form: formContent, display: displayContent };
969
+ }
970
+
971
+ // src/custom-fields/generate-custom-field-links.ts
972
+ var import_promises4 = __toESM(require("fs/promises"));
973
+ async function generateCustomFieldLinks(sources) {
974
+ const files = await getFilesFromSources4(sources);
975
+ const results = await getCustomFieldLinkResults(files);
976
+ const imports = results.map((result) => result.import);
977
+ const code = generateCode2(results);
978
+ return {
979
+ imports,
980
+ code
981
+ };
982
+ }
983
+ async function getFilesFromSources4(sources) {
984
+ const files = (await Promise.all(
985
+ Array.from(sources).map(
986
+ async (source) => crawl(`${source}/custom-fields`)
987
+ )
988
+ )).flat();
989
+ return files;
990
+ }
991
+ function generateCode2(results) {
992
+ const groupedByModel = /* @__PURE__ */ new Map();
993
+ results.forEach((result) => {
994
+ const model = result.model;
995
+ if (!groupedByModel.has(model)) {
996
+ groupedByModel.set(model, []);
997
+ }
998
+ groupedByModel.get(model).push(result);
999
+ });
1000
+ const segments = [];
1001
+ groupedByModel.forEach((results2, model) => {
1002
+ const links = results2.map((result) => result.link).join(",\n");
1003
+ segments.push(`
1004
+ ${model}: [
1005
+ ${links}
1006
+ ],
1007
+ `);
1008
+ });
1009
+ return `
1010
+ links: {
1011
+ ${segments.join("\n")}
1012
+ }
1013
+ `;
1014
+ }
1015
+ async function getCustomFieldLinkResults(files) {
1016
+ return (await Promise.all(files.map(async (file, index) => parseFile2(file, index)))).filter(Boolean);
1017
+ }
1018
+ async function parseFile2(file, index) {
1019
+ const content = await import_promises4.default.readFile(file, "utf8");
1020
+ let ast;
1021
+ try {
1022
+ ast = (0, import_parser.parse)(content, getParserOptions(file));
1023
+ } catch (e) {
1024
+ logger.error(`An error occurred while parsing the file`, { file, error: e });
1025
+ return null;
1026
+ }
1027
+ const import_ = generateImport3(file, index);
1028
+ let link = null;
1029
+ let model = null;
1030
+ try {
1031
+ traverse(ast, {
1032
+ ExportDefaultDeclaration(path3) {
1033
+ const _model = getModel(path3, file);
1034
+ if (!_model) {
1035
+ return;
1036
+ }
1037
+ model = _model;
1038
+ link = getLink(path3, index, file);
1039
+ }
1040
+ });
1041
+ } catch (err) {
1042
+ logger.error(`An error occurred while traversing the file.`, {
1043
+ file,
1044
+ error: err
1045
+ });
1046
+ return null;
1047
+ }
1048
+ if (!link || !model) {
1049
+ return null;
1050
+ }
1051
+ return {
1052
+ import: import_,
1053
+ model,
1054
+ link
1055
+ };
1056
+ }
1057
+ function generateCustomFieldConfigName3(index) {
1058
+ return `CustomFieldConfig${index}`;
1059
+ }
1060
+ function generateImport3(file, index) {
1061
+ const path3 = normalizePath(file);
1062
+ return `import ${generateCustomFieldConfigName3(index)} from "${path3}"`;
1063
+ }
1064
+ function getLink(path3, index, file) {
1065
+ const configArgument = getConfigArgument(path3);
1066
+ if (!configArgument) {
1067
+ return null;
1068
+ }
1069
+ const linkProperty = configArgument.properties.find(
1070
+ (p) => (0, import_types.isObjectProperty)(p) && (0, import_types.isIdentifier)(p.key, { name: "link" })
1071
+ );
1072
+ if (!linkProperty) {
1073
+ logger.warn(`'link' is missing.`, { file });
1074
+ return null;
1075
+ }
1076
+ const import_ = generateCustomFieldConfigName3(index);
1077
+ return `${import_}.link`;
1078
+ }
1079
+
1080
+ // src/routes/generate-menu-items.ts
1081
+ var import_promises5 = __toESM(require("fs/promises"));
1082
+ var import_outdent2 = require("outdent");
1083
+
1084
+ // src/routes/helpers.ts
1085
+ function getRoute(file) {
1086
+ const importPath = normalizePath(file);
1087
+ return importPath.replace(/.*\/admin\/(routes)/, "").replace(/\[([^\]]+)\]/g, ":$1").replace(/\/page\.(tsx|jsx)/, "");
1088
+ }
1089
+
1090
+ // src/routes/generate-menu-items.ts
1091
+ var import_admin_shared4 = require("@etohq/admin-shared");
1092
+ async function generateMenuItems(sources) {
1093
+ const files = await getFilesFromSources5(sources);
1094
+ const results = await getMenuItemResults(files);
1095
+ const imports = results.map((result) => result.import);
1096
+ const code = generateCode3(results);
1097
+ return { imports, code };
1098
+ }
1099
+ function generateCode3(results) {
1100
+ return import_outdent2.outdent`
1101
+ menuItems: [
1102
+ ${results.map((result) => formatMenuItem(result.menuItem)).join(",\n")}
1103
+ ]
1104
+ }
1105
+ `;
1106
+ }
1107
+ function formatMenuItem(route) {
1108
+ const { label, icon, path: path3, nested } = route;
1109
+ return `{
1110
+ label: ${label},
1111
+ icon: ${icon || "undefined"},
1112
+ path: "${path3}",
1113
+ nested: ${nested ? `"${nested}"` : "undefined"}
1114
+ }`;
1115
+ }
1116
+ async function getFilesFromSources5(sources) {
1117
+ const files = (await Promise.all(
1118
+ Array.from(sources).map(
1119
+ async (source) => crawl(`${source}/routes`, "page", { min: 1 })
1120
+ )
1121
+ )).flat();
1122
+ return files;
1123
+ }
1124
+ async function getMenuItemResults(files) {
1125
+ const results = await Promise.all(files.map(parseFile3));
1126
+ return results.filter((item) => item !== null);
1127
+ }
1128
+ async function parseFile3(file, index) {
1129
+ const config = await getRouteConfig(file);
1130
+ if (!config) {
1131
+ return null;
1132
+ }
1133
+ if (!config.label) {
1134
+ logger.warn(`Config is missing a label.`, {
1135
+ file
1136
+ });
1137
+ }
1138
+ const import_ = generateImport4(file, index);
1139
+ const menuItem = generateMenuItem(config, file, index);
1140
+ return {
1141
+ import: import_,
1142
+ menuItem
1143
+ };
1144
+ }
1145
+ function generateImport4(file, index) {
1146
+ const path3 = normalizePath(file);
1147
+ return `import { config as ${generateRouteConfigName(index)} } from "${path3}"`;
1148
+ }
1149
+ function generateMenuItem(config, file, index) {
1150
+ const configName = generateRouteConfigName(index);
1151
+ return {
1152
+ label: `${configName}.label`,
1153
+ icon: config.icon ? `${configName}.icon` : void 0,
1154
+ path: getRoute(file),
1155
+ nested: config.nested
1156
+ };
1157
+ }
1158
+ async function getRouteConfig(file) {
1159
+ const code = await import_promises5.default.readFile(file, "utf-8");
1160
+ let ast = null;
1161
+ try {
1162
+ ast = (0, import_parser.parse)(code, getParserOptions(file));
1163
+ } catch (e) {
1164
+ logger.error(`An error occurred while parsing the file.`, {
1165
+ file,
1166
+ error: e
1167
+ });
1168
+ return null;
1169
+ }
1170
+ let config = null;
1171
+ try {
1172
+ traverse(ast, {
1173
+ ExportNamedDeclaration(path3) {
1174
+ const properties = getConfigObjectProperties(path3);
1175
+ if (!properties) {
1176
+ return;
1177
+ }
1178
+ const hasProperty = (name) => properties.some(
1179
+ (prop) => (0, import_types.isObjectProperty)(prop) && (0, import_types.isIdentifier)(prop.key, { name })
1180
+ );
1181
+ const hasLabel = hasProperty("label");
1182
+ if (!hasLabel) {
1183
+ return;
1184
+ }
1185
+ const nested = properties.find(
1186
+ (prop) => (0, import_types.isObjectProperty)(prop) && (0, import_types.isIdentifier)(prop.key, { name: "nested" })
1187
+ );
1188
+ const nestedValue = nested ? nested.value.value : void 0;
1189
+ if (nestedValue && !import_admin_shared4.NESTED_ROUTE_POSITIONS.includes(nestedValue)) {
1190
+ logger.error(
1191
+ `Invalid nested route position: "${nestedValue}". Allowed values are: ${import_admin_shared4.NESTED_ROUTE_POSITIONS.join(
1192
+ ", "
1193
+ )}`,
1194
+ {
1195
+ file
1196
+ }
1197
+ );
1198
+ return;
1199
+ }
1200
+ config = {
1201
+ label: hasLabel,
1202
+ icon: hasProperty("icon"),
1203
+ nested: nestedValue
1204
+ };
1205
+ }
1206
+ });
1207
+ } catch (e) {
1208
+ logger.error(`An error occurred while traversing the file.`, {
1209
+ file,
1210
+ error: e
1211
+ });
1212
+ }
1213
+ return config;
1214
+ }
1215
+ function generateRouteConfigName(index) {
1216
+ return `RouteConfig${index}`;
1217
+ }
1218
+
1219
+ // src/routes/generate-route-hashes.ts
1220
+ var import_promises6 = __toESM(require("fs/promises"));
1221
+ async function generateRouteHashes(sources) {
1222
+ const files = await getFilesFromSources6(sources);
1223
+ const contents = await Promise.all(files.map(getRouteContents));
1224
+ const defaultExportContents = contents.map((c) => c.defaultExport).filter(Boolean);
1225
+ const configContents = contents.map((c) => c.config).filter(Boolean);
1226
+ const totalDefaultExportContent = defaultExportContents.join("");
1227
+ const totalConfigContent = configContents.join("");
1228
+ return {
1229
+ defaultExportHash: generateHash(totalDefaultExportContent),
1230
+ configHash: generateHash(totalConfigContent)
1231
+ };
1232
+ }
1233
+ async function getFilesFromSources6(sources) {
1234
+ return (await Promise.all(
1235
+ Array.from(sources).map(
1236
+ async (source) => crawl(`${source}/routes`, "page", { min: 1 })
1237
+ )
1238
+ )).flat();
1239
+ }
1240
+ async function getRouteContents(file) {
1241
+ const code = await import_promises6.default.readFile(file, "utf-8");
1242
+ let ast = null;
1243
+ try {
1244
+ ast = (0, import_parser.parse)(code, getParserOptions(file));
1245
+ } catch (e) {
1246
+ logger.error(`An error occurred while parsing the file.`, {
1247
+ file,
1248
+ error: e
1249
+ });
1250
+ return { defaultExport: null, config: null };
1251
+ }
1252
+ let defaultExportContent = null;
1253
+ let configContent = null;
1254
+ try {
1255
+ traverse(ast, {
1256
+ ExportDefaultDeclaration(path3) {
1257
+ defaultExportContent = code.slice(path3.node.start, path3.node.end);
1258
+ },
1259
+ ExportNamedDeclaration(path3) {
1260
+ const properties = getConfigObjectProperties(path3);
1261
+ if (properties) {
1262
+ configContent = code.slice(path3.node.start, path3.node.end);
1263
+ }
1264
+ }
1265
+ });
1266
+ } catch (e) {
1267
+ logger.error(
1268
+ `An error occurred while processing ${file}. See the below error for more details:
1269
+ ${e}`,
1270
+ { file, error: e }
1271
+ );
1272
+ return { defaultExport: null, config: null };
1273
+ }
1274
+ return { defaultExport: defaultExportContent, config: configContent };
1275
+ }
1276
+
1277
+ // src/routes/generate-routes.ts
1278
+ var import_promises7 = __toESM(require("fs/promises"));
1279
+ var import_outdent3 = require("outdent");
1280
+ async function generateRoutes(sources) {
1281
+ const files = await getFilesFromSources7(sources);
1282
+ const results = await getRouteResults(files);
1283
+ const imports = results.map((result) => result.imports).flat();
1284
+ const code = generateCode4(results);
1285
+ return {
1286
+ imports,
1287
+ code
1288
+ };
1289
+ }
1290
+ function generateCode4(results) {
1291
+ return import_outdent3.outdent`
1292
+ routes: [
1293
+ ${results.map((result) => formatRoute(result.route)).join(",\n")}
1294
+ ]
1295
+ }
1296
+ `;
1297
+ }
1298
+ function formatRoute(route) {
1299
+ return `{
1300
+ Component: ${route.Component},
1301
+ path: "${route.path}",
1302
+ }`;
1303
+ }
1304
+ async function getFilesFromSources7(sources) {
1305
+ const files = (await Promise.all(
1306
+ Array.from(sources).map(
1307
+ async (source) => crawl(`${source}/routes`, "page", { min: 1 })
1308
+ )
1309
+ )).flat();
1310
+ return files;
1311
+ }
1312
+ async function getRouteResults(files) {
1313
+ const results = (await Promise.all(files.map(parseFile4))).filter(
1314
+ (result) => result !== null
1315
+ );
1316
+ return results;
1317
+ }
1318
+ async function parseFile4(file, index) {
1319
+ if (!await isValidRouteFile(file)) {
1320
+ return null;
1321
+ }
1322
+ const routePath = getRoute(file);
1323
+ const imports = generateImports(file, index);
1324
+ const route = generateRoute(routePath, index);
1325
+ return {
1326
+ imports,
1327
+ route
1328
+ };
1329
+ }
1330
+ async function isValidRouteFile(file) {
1331
+ const code = await import_promises7.default.readFile(file, "utf-8");
1332
+ let ast = null;
1333
+ try {
1334
+ ast = (0, import_parser.parse)(code, getParserOptions(file));
1335
+ } catch (e) {
1336
+ logger.error("An error occurred while parsing the file.", {
1337
+ file,
1338
+ error: e
1339
+ });
1340
+ return false;
1341
+ }
1342
+ try {
1343
+ return await hasDefaultExport(ast);
1344
+ } catch (e) {
1345
+ logger.error(
1346
+ `An error occurred while checking for a default export in ${file}. The file will be ignored. See the below error for more details:
1347
+ ${e}`
1348
+ );
1349
+ return false;
1350
+ }
1351
+ }
1352
+ function generateImports(file, index) {
1353
+ const imports = [];
1354
+ const route = generateRouteComponentName(index);
1355
+ const importPath = normalizePath(file);
1356
+ imports.push(`import ${route} from "${importPath}"`);
1357
+ return imports;
1358
+ }
1359
+ function generateRoute(route, index) {
1360
+ return {
1361
+ Component: generateRouteComponentName(index),
1362
+ path: route
1363
+ };
1364
+ }
1365
+ function generateRouteComponentName(index) {
1366
+ return `RouteComponent${index}`;
1367
+ }
1368
+
1369
+ // src/virtual-modules/generate-virtual-display-module.ts
1370
+ var import_outdent4 = require("outdent");
1371
+ async function generateVirtualDisplayModule(sources) {
1372
+ const displays = await generateCustomFieldDisplays(sources);
1373
+ const code = import_outdent4.outdent`
1374
+ ${displays.imports.join("\n")}
1375
+
1376
+ export default {
1377
+ ${displays.code}
1378
+ }
1379
+ `;
1380
+ return generateModule(code);
1381
+ }
1382
+
1383
+ // src/virtual-modules/generate-virtual-form-module.ts
1384
+ var import_outdent6 = __toESM(require("outdent"));
1385
+
1386
+ // src/widgets/generate-widget-hash.ts
1387
+ var import_promises8 = __toESM(require("fs/promises"));
1388
+
1389
+ // src/widgets/helpers.ts
1390
+ async function getWidgetFilesFromSources(sources) {
1391
+ return (await Promise.all(
1392
+ Array.from(sources).map(async (source) => crawl(`${source}/widgets`))
1393
+ )).flat();
1394
+ }
1395
+
1396
+ // src/widgets/generate-widget-hash.ts
1397
+ async function generateWidgetHash(sources) {
1398
+ const files = await getWidgetFilesFromSources(sources);
1399
+ const contents = await Promise.all(files.map(getWidgetContents));
1400
+ const totalContent = contents.flatMap(({ config, defaultExport }) => [config, defaultExport]).filter(Boolean).join("");
1401
+ return generateHash(totalContent);
1402
+ }
1403
+ async function getWidgetContents(file) {
1404
+ const code = await import_promises8.default.readFile(file, "utf-8");
1405
+ let ast;
1406
+ try {
1407
+ ast = (0, import_parser.parse)(code, getParserOptions(file));
1408
+ } catch (e) {
1409
+ logger.error(
1410
+ `An error occurred while parsing the file. Due to the error we cannot validate whether the widget has changed. If your changes aren't correctly reflected try restarting the dev server.`,
1411
+ {
1412
+ file,
1413
+ error: e
1414
+ }
1415
+ );
1416
+ return { config: null, defaultExport: null };
1417
+ }
1418
+ let configContent = null;
1419
+ let defaultExportContent = null;
1420
+ traverse(ast, {
1421
+ ExportNamedDeclaration(path3) {
1422
+ const properties = getConfigObjectProperties(path3);
1423
+ if (properties) {
1424
+ configContent = code.slice(path3.node.start, path3.node.end);
1425
+ }
1426
+ },
1427
+ ExportDefaultDeclaration(path3) {
1428
+ defaultExportContent = code.slice(path3.node.start, path3.node.end);
1429
+ }
1430
+ });
1431
+ return { config: configContent, defaultExport: defaultExportContent };
1432
+ }
1433
+
1434
+ // src/widgets/generate-widgets.ts
1435
+ var import_admin_shared5 = require("@etohq/admin-shared");
1436
+ var import_promises9 = __toESM(require("fs/promises"));
1437
+ var import_outdent5 = __toESM(require("outdent"));
1438
+ async function generateWidgets(sources) {
1439
+ const files = await getWidgetFilesFromSources(sources);
1440
+ const results = await getWidgetResults(files);
1441
+ const imports = results.map((r) => r.import);
1442
+ const code = generateCode5(results);
1443
+ return {
1444
+ imports,
1445
+ code
1446
+ };
1447
+ }
1448
+ async function getWidgetResults(files) {
1449
+ return (await Promise.all(files.map(parseFile5))).filter(
1450
+ (r) => r !== null
1451
+ );
1452
+ }
1453
+ function generateCode5(results) {
1454
+ return import_outdent5.default`
1455
+ widgets: [
1456
+ ${results.map((r) => formatWidget(r.widget)).join(",\n")}
1457
+ ]
1458
+ `;
1459
+ }
1460
+ function formatWidget(widget) {
1461
+ return import_outdent5.default`
1462
+ {
1463
+ Component: ${widget.Component},
1464
+ zone: [${widget.zone.map((z) => `"${z}"`).join(", ")}]
1465
+ }
1466
+ `;
1467
+ }
1468
+ async function parseFile5(file, index) {
1469
+ const code = await import_promises9.default.readFile(file, "utf-8");
1470
+ let ast;
1471
+ try {
1472
+ ast = (0, import_parser.parse)(code, getParserOptions(file));
1473
+ } catch (e) {
1474
+ logger.error(`An error occurred while parsing the file.`, {
1475
+ file,
1476
+ error: e
1477
+ });
1478
+ return null;
1479
+ }
1480
+ let fileHasDefaultExport = false;
1481
+ try {
1482
+ fileHasDefaultExport = await hasDefaultExport(ast);
1483
+ } catch (e) {
1484
+ logger.error(`An error occurred while checking for a default export.`, {
1485
+ file,
1486
+ error: e
1487
+ });
1488
+ return null;
1489
+ }
1490
+ if (!fileHasDefaultExport) {
1491
+ return null;
1492
+ }
1493
+ let zone;
1494
+ try {
1495
+ zone = await getWidgetZone(ast, file);
1496
+ } catch (e) {
1497
+ logger.error(`An error occurred while traversing the file.`, {
1498
+ file,
1499
+ error: e
1500
+ });
1501
+ return null;
1502
+ }
1503
+ if (!zone) {
1504
+ logger.warn(`'zone' property is missing from the widget config.`, { file });
1505
+ return null;
1506
+ }
1507
+ const import_ = generateImport5(file, index);
1508
+ const widget = generateWidget(zone, index);
1509
+ return {
1510
+ widget,
1511
+ import: import_
1512
+ };
1513
+ }
1514
+ function generateWidgetComponentName(index) {
1515
+ return `WidgetComponent${index}`;
1516
+ }
1517
+ function generateWidgetConfigName(index) {
1518
+ return `WidgetConfig${index}`;
1519
+ }
1520
+ function generateImport5(file, index) {
1521
+ const path3 = normalizePath(file);
1522
+ return `import ${generateWidgetComponentName(
1523
+ index
1524
+ )}, { config as ${generateWidgetConfigName(index)} } from "${path3}"`;
1525
+ }
1526
+ function generateWidget(zone, index) {
1527
+ return {
1528
+ Component: generateWidgetComponentName(index),
1529
+ zone
1530
+ };
1531
+ }
1532
+ async function getWidgetZone(ast, file) {
1533
+ const zones = [];
1534
+ traverse(ast, {
1535
+ ExportNamedDeclaration(path3) {
1536
+ const properties = getConfigObjectProperties(path3);
1537
+ if (!properties) {
1538
+ return;
1539
+ }
1540
+ const zoneProperty = properties.find(
1541
+ (p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "zone"
1542
+ );
1543
+ if (!zoneProperty) {
1544
+ logger.warn(`'zone' property is missing from the widget config.`, {
1545
+ file
1546
+ });
1547
+ return;
1548
+ }
1549
+ if ((0, import_types.isTemplateLiteral)(zoneProperty.value)) {
1550
+ logger.warn(
1551
+ `'zone' property cannot be a template literal (e.g. \`product.details.after\`).`,
1552
+ { file }
1553
+ );
1554
+ return;
1555
+ }
1556
+ if ((0, import_types.isStringLiteral)(zoneProperty.value)) {
1557
+ zones.push(zoneProperty.value.value);
1558
+ } else if ((0, import_types.isArrayExpression)(zoneProperty.value)) {
1559
+ const values = [];
1560
+ for (const element of zoneProperty.value.elements) {
1561
+ if ((0, import_types.isStringLiteral)(element)) {
1562
+ values.push(element.value);
1563
+ }
1564
+ }
1565
+ zones.push(...values);
1566
+ }
1567
+ }
1568
+ });
1569
+ const validatedZones = zones.filter(import_admin_shared5.isValidInjectionZone);
1570
+ return validatedZones.length > 0 ? validatedZones : null;
1571
+ }
1572
+
1573
+ // src/virtual-modules/generate-virtual-form-module.ts
1574
+ async function generateVirtualFormModule(sources) {
1575
+ const menuItems = await generateMenuItems(sources);
1576
+ const widgets = await generateWidgets(sources);
1577
+ const customFields = await generateCustomFieldForms(sources);
1578
+ const imports = [
1579
+ ...menuItems.imports,
1580
+ ...widgets.imports,
1581
+ ...customFields.imports
1582
+ ];
1583
+ const code = import_outdent6.default`
1584
+ ${imports.join("\n")}
1585
+
1586
+ export default {
1587
+ ${menuItems.code},
1588
+ ${widgets.code},
1589
+ ${customFields.code},
1590
+ }
1591
+ `;
1592
+ return generateModule(code);
1593
+ }
1594
+
1595
+ // src/virtual-modules/generate-virtual-link-module.ts
1596
+ var import_outdent7 = require("outdent");
1597
+ async function generateVirtualLinkModule(sources) {
1598
+ const links = await generateCustomFieldLinks(sources);
1599
+ const code = import_outdent7.outdent`
1600
+ ${links.imports.join("\n")}
1601
+
1602
+ export default {
1603
+ ${links.code}
1604
+ }
1605
+ `;
1606
+ return generateModule(code);
1607
+ }
1608
+
1609
+ // src/virtual-modules/generate-virtual-menu-item-module.ts
1610
+ var import_outdent8 = __toESM(require("outdent"));
1611
+ async function generateVirtualMenuItemModule(sources) {
1612
+ const menuItems = await generateMenuItems(sources);
1613
+ const code = import_outdent8.default`
1614
+ ${menuItems.imports.join("\n")}
1615
+
1616
+ export default {
1617
+ ${menuItems.code},
1618
+ }
1619
+ `;
1620
+ return generateModule(code);
1621
+ }
1622
+
1623
+ // src/virtual-modules/generate-virtual-route-module.ts
1624
+ var import_outdent9 = require("outdent");
1625
+ async function generateVirtualRouteModule(sources) {
1626
+ const routes = await generateRoutes(sources);
1627
+ const imports = [...routes.imports];
1628
+ const code = import_outdent9.outdent`
1629
+ ${imports.join("\n")}
1630
+
1631
+ export default {
1632
+ ${routes.code}
1633
+ }
1634
+ `;
1635
+ return generateModule(code);
1636
+ }
1637
+
1638
+ // src/virtual-modules/generate-virtual-widget-module.ts
1639
+ var import_outdent10 = __toESM(require("outdent"));
1640
+ async function generateVirtualWidgetModule(sources) {
1641
+ const widgets = await generateWidgets(sources);
1642
+ const imports = [...widgets.imports];
1643
+ const code = import_outdent10.default`
1644
+ ${imports.join("\n")}
1645
+
1646
+ export default {
1647
+ ${widgets.code},
1648
+ }
1649
+ `;
1650
+ return generateModule(code);
1651
+ }
1652
+
1653
+ // src/vmod.ts
1654
+ var import_admin_shared6 = require("@etohq/admin-shared");
1655
+ var RESOLVED_LINK_VIRTUAL_MODULE = `\0${import_admin_shared6.LINK_VIRTUAL_MODULE}`;
1656
+ var RESOLVED_FORM_VIRTUAL_MODULE = `\0${import_admin_shared6.FORM_VIRTUAL_MODULE}`;
1657
+ var RESOLVED_DISPLAY_VIRTUAL_MODULE = `\0${import_admin_shared6.DISPLAY_VIRTUAL_MODULE}`;
1658
+ var RESOLVED_ROUTE_VIRTUAL_MODULE = `\0${import_admin_shared6.ROUTE_VIRTUAL_MODULE}`;
1659
+ var RESOLVED_MENU_ITEM_VIRTUAL_MODULE = `\0${import_admin_shared6.MENU_ITEM_VIRTUAL_MODULE}`;
1660
+ var RESOLVED_WIDGET_VIRTUAL_MODULE = `\0${import_admin_shared6.WIDGET_VIRTUAL_MODULE}`;
1661
+ var VIRTUAL_MODULES = [
1662
+ import_admin_shared6.LINK_VIRTUAL_MODULE,
1663
+ import_admin_shared6.FORM_VIRTUAL_MODULE,
1664
+ import_admin_shared6.DISPLAY_VIRTUAL_MODULE,
1665
+ import_admin_shared6.ROUTE_VIRTUAL_MODULE,
1666
+ import_admin_shared6.MENU_ITEM_VIRTUAL_MODULE,
1667
+ import_admin_shared6.WIDGET_VIRTUAL_MODULE
1668
+ ];
1669
+ var RESOLVED_VIRTUAL_MODULES = [
1670
+ RESOLVED_LINK_VIRTUAL_MODULE,
1671
+ RESOLVED_FORM_VIRTUAL_MODULE,
1672
+ RESOLVED_DISPLAY_VIRTUAL_MODULE,
1673
+ RESOLVED_ROUTE_VIRTUAL_MODULE,
1674
+ RESOLVED_MENU_ITEM_VIRTUAL_MODULE,
1675
+ RESOLVED_WIDGET_VIRTUAL_MODULE
1676
+ ];
1677
+ function resolveVirtualId(id) {
1678
+ return `\0${id}`;
1679
+ }
1680
+ function isVirtualModuleId(id) {
1681
+ return VIRTUAL_MODULES.includes(id);
1682
+ }
1683
+ function isResolvedVirtualModuleId(id) {
1684
+ return RESOLVED_VIRTUAL_MODULES.includes(
1685
+ id
1686
+ );
1687
+ }
1688
+ var resolvedVirtualModuleIds = {
1689
+ link: RESOLVED_LINK_VIRTUAL_MODULE,
1690
+ form: RESOLVED_FORM_VIRTUAL_MODULE,
1691
+ display: RESOLVED_DISPLAY_VIRTUAL_MODULE,
1692
+ route: RESOLVED_ROUTE_VIRTUAL_MODULE,
1693
+ menuItem: RESOLVED_MENU_ITEM_VIRTUAL_MODULE,
1694
+ widget: RESOLVED_WIDGET_VIRTUAL_MODULE
1695
+ };
1696
+ var virtualModuleIds = {
1697
+ link: import_admin_shared6.LINK_VIRTUAL_MODULE,
1698
+ form: import_admin_shared6.FORM_VIRTUAL_MODULE,
1699
+ display: import_admin_shared6.DISPLAY_VIRTUAL_MODULE,
1700
+ route: import_admin_shared6.ROUTE_VIRTUAL_MODULE,
1701
+ menuItem: import_admin_shared6.MENU_ITEM_VIRTUAL_MODULE,
1702
+ widget: import_admin_shared6.WIDGET_VIRTUAL_MODULE
1703
+ };
1704
+ var vmod = {
1705
+ resolved: resolvedVirtualModuleIds,
1706
+ virtual: virtualModuleIds
1707
+ };
1708
+
1709
+ // src/plugin.ts
1710
+ var etoVitePlugin = (options) => {
1711
+ const hashMap = /* @__PURE__ */ new Map();
1712
+ const _sources = new Set(options?.sources ?? []);
1713
+ let watcher;
1714
+ function isFileInSources(file) {
1715
+ for (const source of _sources) {
1716
+ if (file.startsWith(import_path2.default.resolve(source))) {
1717
+ return true;
1718
+ }
1719
+ }
1720
+ return false;
1721
+ }
1722
+ async function loadVirtualModule(config) {
1723
+ const hash = await config.hashGenerator(_sources);
1724
+ hashMap.set(config.hashKey, hash);
1725
+ return config.moduleGenerator(_sources);
1726
+ }
1727
+ async function handleFileChange(server, config) {
1728
+ const hashes = await config.hashGenerator(_sources);
1729
+ for (const module2 of config.modules) {
1730
+ const newHash = hashes[module2.hashKey];
1731
+ if (newHash !== hashMap.get(module2.virtualModule)) {
1732
+ const moduleToReload = server.moduleGraph.getModuleById(
1733
+ module2.resolvedModule
1734
+ );
1735
+ if (moduleToReload) {
1736
+ await server.reloadModule(moduleToReload);
1737
+ }
1738
+ hashMap.set(module2.virtualModule, newHash);
1739
+ }
1740
+ }
1741
+ }
1742
+ return {
1743
+ name: "@etohq/admin-vite-plugin",
1744
+ enforce: "pre",
1745
+ configureServer(server) {
1746
+ watcher = server.watcher;
1747
+ watcher?.add(Array.from(_sources));
1748
+ watcher?.on("all", async (_event, file) => {
1749
+ if (!isFileInSources(file)) {
1750
+ return;
1751
+ }
1752
+ for (const config of watcherConfigs) {
1753
+ if (isFileInAdminSubdirectory(file, config.subdirectory)) {
1754
+ await handleFileChange(server, config);
1755
+ }
1756
+ }
1757
+ });
1758
+ },
1759
+ resolveId(id) {
1760
+ if (!isVirtualModuleId(id)) {
1761
+ return null;
1762
+ }
1763
+ return resolveVirtualId(id);
1764
+ },
1765
+ async load(id) {
1766
+ if (!isResolvedVirtualModuleId(id)) {
1767
+ return null;
1768
+ }
1769
+ const config = loadConfigs[id];
1770
+ if (!config) {
1771
+ return null;
1772
+ }
1773
+ return loadVirtualModule(config);
1774
+ },
1775
+ async closeBundle() {
1776
+ if (watcher) {
1777
+ await watcher.close();
1778
+ }
1779
+ }
1780
+ };
1781
+ };
1782
+ var loadConfigs = {
1783
+ [vmod.resolved.widget]: {
1784
+ hashGenerator: async (sources) => generateWidgetHash(sources),
1785
+ moduleGenerator: async (sources) => generateVirtualWidgetModule(sources),
1786
+ hashKey: vmod.virtual.widget
1787
+ },
1788
+ [vmod.resolved.link]: {
1789
+ hashGenerator: async (sources) => (await generateCustomFieldHashes(sources)).linkHash,
1790
+ moduleGenerator: async (sources) => generateVirtualLinkModule(sources),
1791
+ hashKey: vmod.virtual.link
1792
+ },
1793
+ [vmod.resolved.form]: {
1794
+ hashGenerator: async (sources) => (await generateCustomFieldHashes(sources)).formHash,
1795
+ moduleGenerator: async (sources) => generateVirtualFormModule(sources),
1796
+ hashKey: vmod.virtual.form
1797
+ },
1798
+ [vmod.resolved.display]: {
1799
+ hashGenerator: async (sources) => (await generateCustomFieldHashes(sources)).displayHash,
1800
+ moduleGenerator: async (sources) => generateVirtualDisplayModule(sources),
1801
+ hashKey: vmod.virtual.display
1802
+ },
1803
+ [vmod.resolved.route]: {
1804
+ hashGenerator: async (sources) => (await generateRouteHashes(sources)).defaultExportHash,
1805
+ moduleGenerator: async (sources) => generateVirtualRouteModule(sources),
1806
+ hashKey: vmod.virtual.route
1807
+ },
1808
+ [vmod.resolved.menuItem]: {
1809
+ hashGenerator: async (sources) => (await generateRouteHashes(sources)).configHash,
1810
+ moduleGenerator: async (sources) => generateVirtualMenuItemModule(sources),
1811
+ hashKey: vmod.virtual.menuItem
1812
+ }
1813
+ };
1814
+ var watcherConfigs = [
1815
+ {
1816
+ subdirectory: "routes",
1817
+ hashGenerator: async (sources) => generateRouteHashes(sources),
1818
+ modules: [
1819
+ {
1820
+ virtualModule: vmod.virtual.route,
1821
+ resolvedModule: vmod.resolved.route,
1822
+ hashKey: "defaultExportHash"
1823
+ },
1824
+ {
1825
+ virtualModule: vmod.virtual.menuItem,
1826
+ resolvedModule: vmod.resolved.menuItem,
1827
+ hashKey: "configHash"
1828
+ }
1829
+ ]
1830
+ },
1831
+ {
1832
+ subdirectory: "widgets",
1833
+ hashGenerator: async (sources) => ({
1834
+ widgetConfigHash: await generateWidgetHash(sources)
1835
+ }),
1836
+ modules: [
1837
+ {
1838
+ virtualModule: vmod.virtual.widget,
1839
+ resolvedModule: vmod.resolved.widget,
1840
+ hashKey: "widgetConfigHash"
1841
+ }
1842
+ ]
1843
+ },
1844
+ {
1845
+ subdirectory: "custom-fields",
1846
+ hashGenerator: async (sources) => generateCustomFieldHashes(sources),
1847
+ modules: [
1848
+ {
1849
+ virtualModule: vmod.virtual.link,
1850
+ resolvedModule: vmod.resolved.link,
1851
+ hashKey: "linkHash"
1852
+ },
1853
+ {
1854
+ virtualModule: vmod.virtual.form,
1855
+ resolvedModule: vmod.resolved.form,
1856
+ hashKey: "formHash"
1857
+ },
1858
+ {
1859
+ virtualModule: vmod.virtual.display,
1860
+ resolvedModule: vmod.resolved.display,
1861
+ hashKey: "displayHash"
1862
+ }
1863
+ ]
1864
+ }
1865
+ ];
1866
+
1867
+ // src/index.ts
1868
+ var src_default = etoVitePlugin;