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