@lwc-bolts/sf2ts 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,838 @@
1
+ 'use strict';
2
+
3
+ var webTreeSitterSfapex = require('web-tree-sitter-sfapex');
4
+ var xml2js = require('xml2js');
5
+
6
+ /** @import Parser from "web-tree-sitter" */
7
+ /** @import { Component, ConvertedComponent } from "../index.mjs" */
8
+
9
+ // TOOD: Apex Continuation
10
+
11
+ /** @type { Parser } */
12
+ let prsr;
13
+
14
+ const getParserPromise = webTreeSitterSfapex.getApexParser().then((newParser) => {
15
+ prsr = newParser;
16
+ });
17
+
18
+ /**
19
+ *
20
+ * @param {Component} comp
21
+ * @returns {ConvertedComponent}
22
+ */
23
+ function ApexClass(comp) {
24
+ // generate d.ts
25
+ if (prsr === undefined) {
26
+ /** @type { any } */
27
+ const err = new Error("Apex parser not ready yet");
28
+ err.waitFor = getParserPromise; // smuggle a "ready" signal out to the invoker
29
+ throw err;
30
+ }
31
+
32
+ const [className, declarationContent, mappingContent] =
33
+ buildDeclarationForApexFile(prsr, comp.path, comp.content, false);
34
+ if (declarationContent === undefined) {
35
+ throw new Error("Problem building declaration content");
36
+ }
37
+
38
+ return { ...comp, declarationContent: declarationContent, mapData: [] };
39
+ }
40
+
41
+ /**
42
+ * @typedef DeltaPoint
43
+ * @property {number} rowDelta
44
+ * @property {number} column
45
+ */
46
+
47
+ /**
48
+ * @typedef MappingPos
49
+ * @property {DeltaPoint} startPosition
50
+ * @property {DeltaPoint} endPosition
51
+ */
52
+
53
+ /**
54
+ * @typedef Mapping
55
+ * @property { MappingPos } dPos
56
+ * @property { {startPosition: Parser.Point, endPosition: Parser.Point }?} sPos
57
+ */
58
+
59
+ /** @type { Record<string, string> } */
60
+ const typeOverrides = {
61
+ string: "string",
62
+ datetime: "Date",
63
+ decimal: "number",
64
+ integer: "number",
65
+ boolean: "boolean",
66
+ id: "string",
67
+ };
68
+
69
+ /** @type { string[] } */
70
+ let localNestedClasses = [];
71
+ /** @type { string? } */
72
+ let outerClassName;
73
+
74
+ /**
75
+ *
76
+ * @param {Parser} parser
77
+ * @param {string} relativeFilePath
78
+ * @param {string} apexText
79
+ * @param {boolean} isSobject
80
+ * @returns { string[] }
81
+ */
82
+ function buildDeclarationForApexFile(
83
+ parser,
84
+ relativeFilePath,
85
+ apexText,
86
+ isSobject = false
87
+ ) {
88
+ try {
89
+ const tree = parser.parse(apexText);
90
+ // step into top-level class, if any
91
+ // if outer-most thing isn't a class the leave
92
+ const classes = tree.rootNode.descendantsOfType("class_declaration");
93
+
94
+ if (classes.length === 0 || !classes[0]) {
95
+ return []; // this isn't a class
96
+ }
97
+
98
+ // TODO: build mapping file
99
+ const [className, declarationText, mappings] = processClass(
100
+ classes[0],
101
+ isSobject
102
+ );
103
+ const mappingText = getMapFile(className, relativeFilePath, mappings);
104
+ localNestedClasses = [];
105
+ outerClassName = null;
106
+ return [className, declarationText, mappingText];
107
+ } catch (error) {
108
+ console.error(error);
109
+
110
+ localNestedClasses = [];
111
+ outerClassName = null;
112
+ return [];
113
+ }
114
+ }
115
+
116
+ /**
117
+ *
118
+ * @param {Parser.SyntaxNode} classNode
119
+ * @param {boolean} isSobject
120
+ * @returns { [string, string, Mapping[]] }
121
+ */
122
+ function processClass(classNode, isSobject) {
123
+ let className = classNode.childForFieldName("name")?.text ?? "";
124
+ if (!outerClassName) {
125
+ outerClassName = className;
126
+ }
127
+ let classBody = classNode.childForFieldName("body");
128
+ /** @type {Mapping[]} */
129
+ const mappings = [];
130
+ /** @type {string[]} */
131
+ let outputText = [];
132
+ if (!classBody) {
133
+ return [className, outputText.join(""), mappings];
134
+ }
135
+
136
+ const methodsToProcess = [];
137
+ const fieldsToProcess = [];
138
+ const classesToProcess = [];
139
+
140
+ for (const cNode of classBody.children) {
141
+ switch (cNode.type) {
142
+ case "method_declaration":
143
+ // gather methods because AuraEnabled methods create their own exports
144
+ methodsToProcess.push(cNode);
145
+ break;
146
+ case "field_declaration":
147
+ fieldsToProcess.push(cNode);
148
+ break;
149
+ case "class_declaration":
150
+ classesToProcess.push(cNode);
151
+ const innerNameNode = cNode.childForFieldName("name");
152
+ if (innerNameNode) {
153
+ // queue up nested class names so type resolution can use them
154
+ localNestedClasses.push(innerNameNode.text);
155
+ }
156
+ break;
157
+ }
158
+ }
159
+ if (fieldsToProcess.length > 0) {
160
+ outputText.push(`declare interface ${className} {\n`);
161
+ mappings.push({
162
+ dPos: {
163
+ startPosition: {
164
+ rowDelta: 0,
165
+ column: 0,
166
+ },
167
+ endPosition: {
168
+ rowDelta: 0,
169
+ column: 0,
170
+ },
171
+ },
172
+ sPos: null,
173
+ });
174
+ for (let fNode of fieldsToProcess) {
175
+ const [newOutputText, newMappings] = transformFieldDeclaration(
176
+ fNode,
177
+ isSobject
178
+ );
179
+ outputText.push(newOutputText);
180
+ mappings.push(...newMappings);
181
+ }
182
+ outputText.push("}\n");
183
+ mappings.push({
184
+ dPos: {
185
+ startPosition: {
186
+ rowDelta: 1,
187
+ column: 0,
188
+ },
189
+ endPosition: {
190
+ rowDelta: 0,
191
+ column: 0,
192
+ },
193
+ },
194
+ sPos: null,
195
+ });
196
+ }
197
+
198
+ if (classesToProcess.length > 0) {
199
+ outputText.push(`declare namespace ${className} {\n`);
200
+ mappings.push({
201
+ dPos: {
202
+ startPosition: {
203
+ rowDelta: 2,
204
+ column: 0,
205
+ },
206
+ endPosition: {
207
+ rowDelta: 0,
208
+ column: 0,
209
+ },
210
+ },
211
+ sPos: null,
212
+ });
213
+ for (const cNode of classesToProcess) {
214
+ const [className, newOutputText, newMappings] = processClass(
215
+ cNode,
216
+ isSobject
217
+ );
218
+ if (newOutputText.length > 0) {
219
+ outputText.push(" " + newOutputText.replaceAll(/\n(.)/g, "\n $1"));
220
+ mappings.push(...newMappings);
221
+ }
222
+ // TODO: somehow queue up local classes for type resolution
223
+ }
224
+
225
+ outputText.push(`}\n`);
226
+ mappings.push({
227
+ dPos: {
228
+ startPosition: {
229
+ rowDelta: 2,
230
+ column: 0,
231
+ },
232
+ endPosition: {
233
+ rowDelta: 0,
234
+ column: 0,
235
+ },
236
+ },
237
+ sPos: null,
238
+ });
239
+ }
240
+
241
+ for (const mNode of methodsToProcess) {
242
+ const [newOutputText, newMappings] = transformMethodDeclaration(
243
+ className,
244
+ mNode
245
+ );
246
+ outputText.push(newOutputText);
247
+ mappings.push(...newMappings);
248
+ }
249
+ return [className, outputText.join(""), mappings];
250
+ }
251
+
252
+ /**
253
+ *
254
+ * @param {string} className
255
+ * @param {Parser.SyntaxNode} methodNode
256
+ * @returns {[string, Mapping[]]}
257
+ */
258
+ function transformMethodDeclaration(className, methodNode) {
259
+ /** @type {Mapping[]} */
260
+ const mappings = [];
261
+ const methodNameNode = methodNode.childForFieldName("name");
262
+ if (!methodNameNode) {
263
+ return ["", mappings];
264
+ }
265
+ const methodName = methodNameNode.text;
266
+ const returnType = methodNode.childForFieldName("type");
267
+ if (!returnType) {
268
+ return ["", mappings];
269
+ }
270
+ const parameters = getMethodParameters(methodNode);
271
+
272
+ const auraEnabledNode = getAuraEnabledAnnotation(methodNode);
273
+ if (!auraEnabledNode) {
274
+ // method doesn't hold the AuraEnabled Annotation
275
+ return ["", mappings];
276
+ }
277
+ const isCacheableEnabled = getAuraEnabledIsCacheable(auraEnabledNode);
278
+
279
+ const [newOutputText, methodNameMapping] = declarationString(
280
+ className,
281
+ methodName,
282
+ getType(returnType),
283
+ parameters,
284
+ isCacheableEnabled
285
+ );
286
+ const declarationParts = newOutputText.split("\n");
287
+ methodNameMapping.rowDelta;
288
+
289
+ mappings.push({
290
+ dPos: {
291
+ startPosition: methodNameMapping,
292
+ endPosition: {
293
+ rowDelta: methodNameMapping.rowDelta,
294
+ column: methodNameMapping.column + methodName.length,
295
+ },
296
+ },
297
+ sPos: {
298
+ startPosition: methodNameNode.startPosition,
299
+ endPosition: methodNameNode.endPosition,
300
+ },
301
+ });
302
+ // add consumption of remaining lines
303
+ mappings.push({
304
+ dPos: {
305
+ startPosition: {
306
+ rowDelta: declarationParts.length - methodNameMapping.rowDelta,
307
+ column: 0,
308
+ },
309
+ endPosition: {
310
+ rowDelta: declarationParts.length - methodNameMapping.rowDelta,
311
+ column: 0,
312
+ },
313
+ },
314
+ sPos: null,
315
+ });
316
+ return [newOutputText, mappings];
317
+ }
318
+
319
+ /**
320
+ *
321
+ * @param {Parser.SyntaxNode} node
322
+ * @param {boolean} isSobject
323
+ * @returns {[string, Mapping[]]}
324
+ */
325
+ function transformFieldDeclaration(node, isSobject) {
326
+ /** @type {string[]} */
327
+ let deprecatedReasons = [];
328
+ let output = "";
329
+ const modifiers = node.descendantsOfType("modifiers");
330
+ /** @type {Mapping} */
331
+ const mapping = {
332
+ dPos: {
333
+ startPosition: {
334
+ rowDelta: 1,
335
+ column: 2,
336
+ },
337
+ endPosition: {
338
+ // TODO
339
+ rowDelta: 0,
340
+ column: 0,
341
+ },
342
+ },
343
+ sPos: {
344
+ startPosition: node.startPosition,
345
+ endPosition: node.endPosition,
346
+ },
347
+ };
348
+
349
+ if (modifiers.length === 0 || !modifiers[0]) {
350
+ deprecatedReasons.push("public or global");
351
+ } else {
352
+ // initialize so the generated SObjects don't flag missing AuraEnabled
353
+ let isAuraEnabled = isSobject;
354
+ for (const aNode of modifiers[0].descendantsOfType("annotation")) {
355
+ const aNameNode = aNode.childForFieldName("name");
356
+ if (aNameNode && aNameNode.text.toLowerCase() === "auraenabled") {
357
+ isAuraEnabled = true;
358
+ }
359
+ }
360
+ if (!isAuraEnabled) {
361
+ deprecatedReasons.push("@AuraEnabled");
362
+ }
363
+
364
+ let isAtLeastPublic = false;
365
+ for (const mNode of modifiers[0].descendantsOfType("modifier")) {
366
+ switch (mNode.text.toLowerCase()) {
367
+ case "public":
368
+ case "global":
369
+ isAtLeastPublic = true;
370
+ break;
371
+ case "static":
372
+ deprecatedReasons.push("non-static");
373
+ break;
374
+ }
375
+ }
376
+ if (!isAtLeastPublic) {
377
+ deprecatedReasons.push("public or global");
378
+ }
379
+ }
380
+ /** TODO:
381
+ * determine if AuraEnabled & public or global & not static
382
+ * otherwise mark as deprecated with note (dynamic based on missing access component)
383
+ * {property}: {getType({type})}?;
384
+ */
385
+ if (deprecatedReasons.length > 0) {
386
+ mapping.dPos.startPosition.rowDelta++;
387
+ output += ` /** @deprecated not exposed; property must be ${deprecatedReasons.join(
388
+ ", "
389
+ )} */\n`;
390
+ }
391
+ output += ` ${
392
+ node.childForFieldName("declarator")?.childForFieldName("name")?.text ?? ""
393
+ }?: ${getType(node.childForFieldName("type"))};\n`;
394
+ return [output, [mapping]];
395
+ }
396
+
397
+ /**
398
+ *
399
+ * @param {Parser.SyntaxNode} node
400
+ * @returns {boolean}
401
+ */
402
+ function getAuraEnabledIsCacheable(node) {
403
+ const args = node.childForFieldName("arguments");
404
+ if (args) {
405
+ for (const a of args.descendantsOfType("annotation_key_value")) {
406
+ const k = a.childForFieldName("key");
407
+ const v = a.childForFieldName("value");
408
+ if (
409
+ k &&
410
+ v &&
411
+ k.text.toLowerCase() === "cacheable" &&
412
+ v.text.toLowerCase() === "true"
413
+ ) {
414
+ return true;
415
+ }
416
+ }
417
+ }
418
+ return false;
419
+ }
420
+
421
+ /**
422
+ *
423
+ * @param {Parser.SyntaxNode} node
424
+ * @returns {Parser.SyntaxNode | null}
425
+ */
426
+ function getAuraEnabledAnnotation(node) {
427
+ for (const m of node.descendantsOfType("modifiers")) {
428
+ for (const a of m.descendantsOfType("annotation")) {
429
+ if (a.childForFieldName("name")?.text.toLowerCase() === "auraenabled") {
430
+ return a;
431
+ }
432
+ }
433
+ }
434
+ return null;
435
+ }
436
+
437
+ /**
438
+ *
439
+ * @param {Parser.SyntaxNode} node
440
+ * @returns {Map<string, string>}
441
+ */
442
+ function getMethodParameters(node) {
443
+ const params = new Map();
444
+ const parameters = node.childForFieldName("parameters");
445
+ if (!parameters) {
446
+ return params;
447
+ }
448
+ for (const p of parameters.namedChildren) {
449
+ params.set(
450
+ p.childForFieldName("name")?.text,
451
+ getType(p.childForFieldName("type"))
452
+ );
453
+ }
454
+ return params;
455
+ }
456
+
457
+ /**
458
+ *
459
+ * @param {string} className
460
+ * @param {string} methodName
461
+ * @param {string} returnType
462
+ * @param {Map<string, string>} params
463
+ * @param {boolean} isCacheable
464
+ * @returns {[string, DeltaPoint]}
465
+ */
466
+ function declarationString(
467
+ className,
468
+ methodName,
469
+ returnType,
470
+ params,
471
+ isCacheable
472
+ ) {
473
+ let text = `declare module "@salesforce/apex/${className}.${methodName}" {
474
+ const ${methodName}: {
475
+ (${
476
+ params.size === 0
477
+ ? ""
478
+ : `param: { ${Array.from(params.entries())
479
+ .map(([name, type]) => `${name}: ${type}`)
480
+ .join("; ")} }`
481
+ }): Promise<${returnType}>;${
482
+ // Apex can only be wired if it is cacheable
483
+ // the adapter is the WireAdapterConstructor that the @wire decorator consumes
484
+ !isCacheable
485
+ ? ""
486
+ : `
487
+ adapter: import("lwc").WireAdapterConstructor<
488
+ ${
489
+ params.size === 0
490
+ ? "never"
491
+ : `{ ${Array.from(params.entries())
492
+ .map(([name, type]) => `${name}: ${type}`)
493
+ .join("; ")} }`
494
+ },
495
+ { error?: any; data?: ${returnType}; }
496
+ >;`
497
+ }
498
+ };
499
+ export default `;
500
+ // analyze how much text we're adding before the method name
501
+ const lines = text.split("\n");
502
+ /** @type {DeltaPoint} */
503
+ const m = {
504
+ rowDelta: lines.length - 1,
505
+ column: lines[lines.length - 1]?.length ?? 0,
506
+ };
507
+ // finish building
508
+ text += `${methodName};
509
+ }
510
+ `;
511
+ return [text, m];
512
+ }
513
+
514
+ /**
515
+ *
516
+ * @param {string} className
517
+ * @param {string} relativeClassPath
518
+ * @param {Mapping[]} mappings
519
+ * @returns
520
+ */
521
+ function getMapFile(className, relativeClassPath, mappings) {
522
+ let mappingsString = "";
523
+ for (const m of mappings) {
524
+ mappingsString += ";".repeat(m.dPos.startPosition.rowDelta);
525
+ if (!m.sPos) {
526
+ continue;
527
+ }
528
+ /**
529
+ * Generated column
530
+ * Original file this appeared in
531
+ * Original line number relative to last given value in this field
532
+ * Original column relative to last given value in this field
533
+ */
534
+ // const incrementalColumnCount =
535
+ // m.sPos.startPosition.column - lastMethodColumnNum;
536
+
537
+ // mappingsString += `${encode(m.dPos.startPosition.column)}${encode(
538
+ // 0
539
+ // )}${encode(m.sPos.startPosition.row - lastSourceRow)}${encode(
540
+ // incrementalColumnCount
541
+ // )}`;
542
+
543
+ m.sPos.startPosition.column;
544
+ m.sPos.startPosition.row;
545
+ }
546
+ return JSON.stringify({
547
+ version: 3,
548
+ file: `${className}.d.ts`,
549
+ sourceRoot: "",
550
+ // TODO: not so badly hardcoded
551
+ sources: [`../../../../../${relativeClassPath}`],
552
+ names: [],
553
+ mappings: mappingsString,
554
+ });
555
+ }
556
+
557
+ /**
558
+ *
559
+ * @param {Parser.SyntaxNode | null} node
560
+ * @returns {string}
561
+ */
562
+ function getType(node) {
563
+ if (!node) {
564
+ return "";
565
+ }
566
+ // if generic type, see if it is a list or map
567
+ switch (node.type) {
568
+ case "generic_type":
569
+ const genType = getType(node.namedChildren[0]);
570
+ let typeNode = node.namedChildren[1];
571
+ if (!typeNode) {
572
+ return "";
573
+ }
574
+ switch (genType.toLowerCase()) {
575
+ case "list":
576
+ return getType(typeNode.namedChildren[0]) + "[]";
577
+ case "map":
578
+ return `Record<${getType(typeNode.namedChildren[0])}, ${getType(
579
+ typeNode.namedChildren[1]
580
+ )}>`;
581
+ case "set":
582
+ return `Set<${getType(typeNode.namedChildren[0])}>`;
583
+
584
+ default:
585
+ console.error("Unexpected generic type", genType);
586
+ }
587
+ }
588
+ if (localNestedClasses.includes(node.text)) {
589
+ return `${outerClassName}.${node.text}`;
590
+ }
591
+ return typeOverrides[node.text.toLowerCase()] ?? node.text;
592
+ }
593
+
594
+ /** @import { Component, ConvertedComponent } from "../index.mjs" */
595
+
596
+ /**
597
+ *
598
+ * @param {Component} component
599
+ * @returns {ConvertedComponent}
600
+ */
601
+ function ContentAsset(component) {
602
+ // generate d.ts
603
+ /** @type {any} */
604
+ let asset;
605
+ xml2js.parseString(component.content, (error, result) => {
606
+ if (error) {
607
+ throw error;
608
+ }
609
+ asset = result;
610
+ });
611
+ const dec = `declare module "@salesforce/contentAssetUrl/${asset.ContentAsset.masterLabel}" {
612
+ var ${asset.ContentAsset.masterLabel}: string;
613
+ export default ${asset.ContentAsset.masterLabel};
614
+ }`;
615
+ return { declarationContent: dec, mapData: [], ...component };
616
+ }
617
+
618
+ /** @import { Component, ConvertedComponent } from "../index.mjs" */
619
+
620
+ /**
621
+ *
622
+ * @param {Component} component
623
+ * @returns {ConvertedComponent}
624
+ */
625
+ function CustomPermission(component) {
626
+ // generate d.ts
627
+ /** @type {any} */
628
+ let perm;
629
+ xml2js.parseString(component.content, (error, result) => {
630
+ if (error) {
631
+ throw error;
632
+ }
633
+ perm = result;
634
+ });
635
+ const permName = component.path.split("/").pop()?.split(".")[0];
636
+ const dec = `declare module "@salesforce/customPermission/${permName}" {
637
+ /**
638
+ * ${perm.CustomPermission.label}
639
+ *
640
+ * @description ${perm.CustomPermission.description}
641
+ */
642
+ const has${permName}:boolean;
643
+ export default has${permName};
644
+ }`;
645
+ return { declarationContent: dec, mapData: [], ...component };
646
+ }
647
+
648
+ /** @import { Component, ConvertedComponent } from "../index.mjs" */
649
+
650
+ /**
651
+ *
652
+ * @param {Component} component
653
+ * @returns {ConvertedComponent}
654
+ */
655
+ function CustomField(component) {
656
+ // generate d.ts
657
+ /** @type {any} */
658
+ let field;
659
+ xml2js.parseString(component.content, (error, result) => {
660
+ if (error) {
661
+ throw error;
662
+ }
663
+ field = result;
664
+ });
665
+ const pathParts = component.path.split("/");
666
+ const fieldName = pathParts.pop()?.split(".")[0];
667
+ pathParts?.pop(); // discard `fields`
668
+ const objectName = pathParts.pop();
669
+ const dec = `declare module '@salesforce/schema/${objectName}.${fieldName}' {
670
+ /**
671
+ * @description ${field.CustomField.inlineHelpText}
672
+ * @description ${field.CustomField.description}
673
+ */
674
+ const ${fieldName}: {
675
+ fieldApiName: '${fieldName}';
676
+ objectApiName: '${objectName}';
677
+ }
678
+ export default ${fieldName};
679
+ }`;
680
+ return { declarationContent: dec, mapData: [], ...component };
681
+ }
682
+
683
+ /** @import { Component, ConvertedComponent } from "../index.mjs" */
684
+
685
+ /**
686
+ *
687
+ * @param {Component} component
688
+ * @returns {ConvertedComponent}
689
+ */
690
+ function CustomLabels(component) {
691
+ // generate d.ts
692
+ /** @type {any} */
693
+ let labels;
694
+ xml2js.parseString(component.content, (error, result) => {
695
+ if (error) {
696
+ throw error;
697
+ }
698
+ labels = result;
699
+ });
700
+ if (!Array.isArray(labels.CustomLabels.labels)) {
701
+ labels.CustomLabels.labels = [labels.CustomLabels.labels];
702
+ }
703
+ let dec = "";
704
+ for (let label of labels.CustomLabels.labels) {
705
+ dec += `declare module "@salesforce/label/c.${label.fullName}" {
706
+ /**
707
+ * @description ${label.shortDescription}
708
+ */
709
+ const lbl${label.fullName} = '${label.value}';
710
+ export default lbl${label.fullName};
711
+ }
712
+ `;
713
+ }
714
+ return { declarationContent: dec, mapData: [], ...component };
715
+ }
716
+
717
+ /** @import { Component, ConvertedComponent } from "../index.mjs" */
718
+
719
+ /**
720
+ *
721
+ * @param {Component} component
722
+ * @returns {ConvertedComponent}
723
+ */
724
+ function CustomObject(component) {
725
+ // generate d.ts
726
+ /** @type {any} */
727
+ let obj;
728
+ xml2js.parseString(component.content, (error, result) => {
729
+ if (error) {
730
+ throw error;
731
+ }
732
+ obj = result;
733
+ });
734
+ const objectName = component.path.split("/").pop()?.split(".")[0];
735
+ const dec = `declare module '@salesforce/schema/${objectName}' {
736
+ /**
737
+ * @description ${obj.CustomObject.description}
738
+ */
739
+ const ${objectName}: {
740
+ objectApiName: '${objectName}';
741
+ }
742
+ export default ${objectName};
743
+ }`;
744
+ return { declarationContent: dec, mapData: [], ...component };
745
+ }
746
+
747
+ /** @import { Component, ConvertedComponent } from "../index.mjs" */
748
+
749
+ /**
750
+ *
751
+ * @param {Component} component
752
+ * @returns {ConvertedComponent}
753
+ */
754
+ function StaticResource(component) {
755
+ // generate d.ts
756
+ /** @type {any} */
757
+ let resource;
758
+ xml2js.parseString(component.content, (error, result) => {
759
+ if (error) {
760
+ throw error;
761
+ }
762
+ resource = result;
763
+ });
764
+ const resourceName = component.path.split("/").pop()?.split(".")[0];
765
+ const dec = `declare module "@salesforce/resourceUrl/${resourceName}" {
766
+ /**
767
+ * @description ${resource.StaticResource.description}
768
+ * @access ${resource.StaticResource.cacheControl}
769
+ */
770
+ const ${resourceName}:string;
771
+ export default ${resourceName};
772
+ }`;
773
+ return { declarationContent: dec, mapData: [], ...component };
774
+ }
775
+
776
+ var converters = /*#__PURE__*/Object.freeze({
777
+ __proto__: null,
778
+ ApexClass: ApexClass,
779
+ ContentAsset: ContentAsset,
780
+ CustomField: CustomField,
781
+ CustomLabels: CustomLabels,
782
+ CustomObject: CustomObject,
783
+ CustomPermission: CustomPermission,
784
+ StaticResource: StaticResource
785
+ });
786
+
787
+ // maybe there is a smarter way to do this
788
+ /** @type {Readonly<{[p in keyof converters]: p}>} */
789
+ const MetadataType = Object.freeze(
790
+ Object.keys(converters).reduce((/** @type {any} */ t, v) => {
791
+ t[v] = v;
792
+ return t;
793
+ }, {})
794
+ );
795
+
796
+ /**
797
+ * @typedef MapPair
798
+ * @property {number} sourcePos
799
+ * @property {number} destPos
800
+ */
801
+
802
+ /**
803
+ * @typedef Component
804
+ * @property {string} path
805
+ * @property {keyof converters} type
806
+ * @property {string} content
807
+ */
808
+
809
+ /**
810
+ * @typedef { Component & {
811
+ * declarationContent: string;
812
+ * mapData: MapPair[]}} ConvertedComponent
813
+ */
814
+
815
+ /**
816
+ *
817
+ * @param {Component[]} components
818
+ * @returns {ConvertedComponent[]}
819
+ */
820
+ function convert(components) {
821
+ /** @type {ConvertedComponent[]} */
822
+ const convertedComponents = [];
823
+ for (const c of components) {
824
+ if (!converters[c.type]) {
825
+ throw new Error(
826
+ "Unsupported Metadata Type:" +
827
+ c.type +
828
+ ", must be one of " +
829
+ Object.keys(converters)
830
+ );
831
+ }
832
+ convertedComponents.push(converters[c.type](c));
833
+ }
834
+ return convertedComponents;
835
+ }
836
+
837
+ exports.MetadataType = MetadataType;
838
+ exports.convert = convert;