prismic-ts-codegen 0.0.2

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/bin/cli.mjs ADDED
@@ -0,0 +1,1166 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync, existsSync, writeFileSync } from 'fs';
4
+ import { resolve } from 'path';
5
+ import meow from 'meow';
6
+ import { stripIndent } from 'common-tags';
7
+ import { Project } from 'ts-morph';
8
+ import { pascalCase as pascalCase$1, pascalCaseTransformMerge } from 'pascal-case';
9
+ import Joi from 'joi';
10
+ import * as prismic from '@prismicio/client';
11
+ import fetch from 'node-fetch';
12
+ import fg from 'fast-glob';
13
+ import * as prismicCT from '@prismicio/custom-types-client';
14
+ import _jiti from 'jiti';
15
+
16
+ const BLANK_LINE_IDENTIFIER = "// ___BLANK_LINE_TO_BE_REPLACED___";
17
+ const NON_EDITABLE_FILE_HEADER = stripIndent`
18
+ // Code generated by prismic-ts-codegen. DO NOT EDIT.
19
+ ${BLANK_LINE_IDENTIFIER}
20
+ `;
21
+ const CUSTOM_TYPES_DOCUMENTATION_URL = "https://prismic.io/docs/core-concepts/custom-types";
22
+ const SHARED_SLICES_DOCUMENTATION_URL = "https://prismic.io/docs/core-concepts/reusing-slices";
23
+ const FIELD_DOCUMENTATION_URLS = {
24
+ UID: "https://prismic.io/docs/core-concepts/uid",
25
+ Boolean: "https://prismic.io/docs/core-concepts/boolean",
26
+ Color: "https://prismic.io/docs/core-concepts/color",
27
+ Date: "https://prismic.io/docs/core-concepts/date",
28
+ Timestamp: "https://prismic.io/docs/core-concepts/timestamp",
29
+ Number: "https://prismic.io/docs/core-concepts/number",
30
+ Text: "https://prismic.io/docs/core-concepts/key-text",
31
+ Select: "https://prismic.io/docs/core-concepts/select",
32
+ StructuredText: "https://prismic.io/docs/core-concepts/rich-text-title",
33
+ Image: "https://prismic.io/docs/core-concepts/image",
34
+ Link: "https://prismic.io/docs/core-concepts/link-content-relationship",
35
+ Embed: "https://prismic.io/docs/core-concepts/embed",
36
+ GeoPoint: "https://prismic.io/docs/core-concepts/geopoint",
37
+ Group: "https://prismic.io/docs/core-concepts/group",
38
+ IntegrationFields: "https://prismic.io/docs/core-concepts/integration-fields",
39
+ Slices: "https://prismic.io/docs/core-concepts/slices"
40
+ };
41
+
42
+ const CustomTypeModelFieldType = {
43
+ Boolean: "Boolean",
44
+ Color: "Color",
45
+ Date: "Date",
46
+ Embed: "Embed",
47
+ GeoPoint: "GeoPoint",
48
+ Group: "Group",
49
+ Image: "Image",
50
+ IntegrationFields: "IntegrationFields",
51
+ Link: "Link",
52
+ Number: "Number",
53
+ Select: "Select",
54
+ Slices: "Slices",
55
+ StructuredText: "StructuredText",
56
+ Text: "Text",
57
+ Timestamp: "Timestamp",
58
+ UID: "UID"
59
+ };
60
+ const CustomTypeModelLinkSelectType = {
61
+ Document: "document",
62
+ Media: "media"
63
+ };
64
+ const CustomTypeModelSliceType = {
65
+ Slice: "Slice",
66
+ SharedSlice: "SharedSlice"
67
+ };
68
+
69
+ const isCustomTypeModelField = (model) => {
70
+ return typeof model === "object" && model !== null && "type" in model && "config" in model;
71
+ };
72
+
73
+ const isCustomTypeModelSlice = (model) => {
74
+ return typeof model === "object" && model !== null && "non-repeat" in model && "repeat" in model;
75
+ };
76
+
77
+ const isSharedSliceModel$1 = (model) => {
78
+ return typeof model === "object" && model !== null && "variations" in model;
79
+ };
80
+
81
+ const getAPIIDPath = (config) => {
82
+ return config.path.map((element, i) => {
83
+ if (isCustomTypeModelField(element.model) && (element.model.type === CustomTypeModelFieldType.Group || element.model.type === CustomTypeModelFieldType.Slices)) {
84
+ return `${element.id}[]`;
85
+ } else {
86
+ if (element.id === "items") {
87
+ const previousElement = config.path[i - 1];
88
+ if (isCustomTypeModelSlice(previousElement.model) || isSharedSliceModel$1(previousElement.model)) {
89
+ return `${element.id}[]`;
90
+ }
91
+ }
92
+ return element.id;
93
+ }
94
+ }).join(".");
95
+ };
96
+
97
+ const getFieldDocumentationURL = (config) => {
98
+ return FIELD_DOCUMENTATION_URLS[config.model.type];
99
+ };
100
+
101
+ const isCustomTypeModel$1 = (model) => {
102
+ return typeof model === "object" && model !== null && "json" in model;
103
+ };
104
+
105
+ const getHumanReadableModelName = (config) => {
106
+ if (isCustomTypeModel$1(config.model)) {
107
+ return config.model.label;
108
+ } else if (isSharedSliceModel$1(config.model)) {
109
+ return config.model.name;
110
+ } else if (isCustomTypeModelField(config.model) && "label" in config.model.config) {
111
+ return config.model.config.label;
112
+ } else if (isCustomTypeModelSlice(config.model)) {
113
+ return config.model.fieldset;
114
+ } else {
115
+ return `Slice Zone (\`${config.id}\`)`;
116
+ }
117
+ };
118
+
119
+ const getHumanReadableFieldPath = (config) => {
120
+ return config.path.map((element) => {
121
+ if (element.label) {
122
+ return element.label;
123
+ } else if (element.model) {
124
+ return getHumanReadableModelName({
125
+ id: element.id,
126
+ model: element.model
127
+ });
128
+ } else {
129
+ return element.id;
130
+ }
131
+ }).join(" \u2192 ");
132
+ };
133
+
134
+ const getHumanReadableFieldType = (config) => {
135
+ switch (config.model.type) {
136
+ case "StructuredText": {
137
+ const isTitleField = "single" in config.model.config && config.model.config.single.split(",").every((blockType) => /heading/.test(blockType));
138
+ return isTitleField ? "Title" : "Rich Text";
139
+ }
140
+ case "IntegrationFields": {
141
+ return `Integration Fields (Catalog: \`${config.model.config.catalog}\`)`;
142
+ }
143
+ case "Link": {
144
+ switch (config.model.config.select) {
145
+ case CustomTypeModelLinkSelectType.Document: {
146
+ return "Content Relationship";
147
+ }
148
+ case CustomTypeModelLinkSelectType.Media: {
149
+ return "Link to Media";
150
+ }
151
+ default: {
152
+ return "Link";
153
+ }
154
+ }
155
+ }
156
+ case "Slices": {
157
+ return "Slice Zone";
158
+ }
159
+ default: {
160
+ return config.model.type;
161
+ }
162
+ }
163
+ };
164
+
165
+ const writeFieldDocsDescription = (config) => {
166
+ if (config.description) {
167
+ config.writer.writeLine(config.description);
168
+ }
169
+ config.writer.blankLineIfLastNot();
170
+ if (config.fieldType) {
171
+ config.writer.writeLine(`- **Field Type**: ${config.fieldType}`);
172
+ }
173
+ if (config.placeholder) {
174
+ config.writer.writeLine(`- **Placeholder**: ${config.placeholder}`);
175
+ }
176
+ if (config.defaultValue !== void 0) {
177
+ const stringifiedDefaultValue = typeof config.defaultValue === "boolean" ? `${config.defaultValue}` : config.defaultValue;
178
+ config.writer.writeLine(`- **Default Value**: ${stringifiedDefaultValue}`);
179
+ }
180
+ if (config.apiIDPath) {
181
+ config.writer.writeLine(`- **API ID Path**: ${config.apiIDPath}`);
182
+ }
183
+ if (config.documentationURL) {
184
+ config.writer.writeLine(`- **Documentation**: ${config.documentationURL}`);
185
+ }
186
+ };
187
+
188
+ const buildFieldDocs = (config) => {
189
+ return [
190
+ {
191
+ description: (writer) => {
192
+ const name = getHumanReadableModelName({
193
+ id: config.id,
194
+ model: config.model
195
+ });
196
+ const humanReadablePath = getHumanReadableFieldPath({
197
+ path: config.path
198
+ });
199
+ const fieldType = getHumanReadableFieldType({
200
+ model: config.model
201
+ });
202
+ const placeholder = "placeholder" in config.model.config && config.model.config.placeholder ? config.model.config.placeholder : `*None*`;
203
+ const defaultValue = "default_value" in config.model.config ? config.model.config.default_value : void 0;
204
+ const apiIDPath = getAPIIDPath({
205
+ path: [...config.path, { id: config.id, model: config.model }]
206
+ });
207
+ writeFieldDocsDescription({
208
+ writer,
209
+ description: `${name} field in *${humanReadablePath}*`,
210
+ fieldType,
211
+ placeholder,
212
+ defaultValue,
213
+ apiIDPath,
214
+ documentationURL: getFieldDocumentationURL({ model: config.model })
215
+ });
216
+ }
217
+ }
218
+ ];
219
+ };
220
+
221
+ const pascalCase = (input) => pascalCase$1(input, {
222
+ transform: pascalCaseTransformMerge
223
+ });
224
+
225
+ const buildSharedSliceInterfaceName = (config) => {
226
+ return pascalCase(`${config.id} Slice`);
227
+ };
228
+
229
+ const addInterfacePropertyFromField = (config) => {
230
+ switch (config.model.type) {
231
+ case "UID": {
232
+ break;
233
+ }
234
+ case "Boolean": {
235
+ config.interface.addProperty({
236
+ name: config.id,
237
+ type: "prismicT.BooleanField",
238
+ docs: buildFieldDocs({
239
+ id: config.id,
240
+ model: config.model,
241
+ path: config.path
242
+ })
243
+ });
244
+ break;
245
+ }
246
+ case "Color": {
247
+ config.interface.addProperty({
248
+ name: config.id,
249
+ type: "prismicT.ColorField",
250
+ docs: buildFieldDocs({
251
+ id: config.id,
252
+ model: config.model,
253
+ path: config.path
254
+ })
255
+ });
256
+ break;
257
+ }
258
+ case "Date": {
259
+ config.interface.addProperty({
260
+ name: config.id,
261
+ type: "prismicT.DateField",
262
+ docs: buildFieldDocs({
263
+ id: config.id,
264
+ model: config.model,
265
+ path: config.path
266
+ })
267
+ });
268
+ break;
269
+ }
270
+ case "Embed": {
271
+ const providerTypes = [];
272
+ if (config.fieldConfigs.embed?.providerTypes) {
273
+ for (const providerType in config.fieldConfigs.embed?.providerTypes) {
274
+ const configuredProviderType = config.fieldConfigs.embed?.providerTypes[providerType];
275
+ providerTypes.push(`({ provider_name: "${providerType}" } & ${configuredProviderType})`);
276
+ }
277
+ }
278
+ config.interface.addProperty({
279
+ name: config.id,
280
+ type: providerTypes.length > 0 ? `prismicT.EmbedField<prismicT.AnyOEmbed & prismicT.OEmbedExtra & (${providerTypes.join(" | ")})>` : "prismicT.EmbedField",
281
+ docs: buildFieldDocs({
282
+ id: config.id,
283
+ model: config.model,
284
+ path: config.path
285
+ })
286
+ });
287
+ break;
288
+ }
289
+ case "GeoPoint": {
290
+ config.interface.addProperty({
291
+ name: config.id,
292
+ type: "prismicT.GeoPointField",
293
+ docs: buildFieldDocs({
294
+ id: config.id,
295
+ model: config.model,
296
+ path: config.path
297
+ })
298
+ });
299
+ break;
300
+ }
301
+ case "Image": {
302
+ if (config.model.config.thumbnails.length > 0) {
303
+ const thumbnailNames = config.model.config.thumbnails.map((thumbnail) => `"${thumbnail.name}"`).join(" | ");
304
+ config.interface.addProperty({
305
+ name: config.id,
306
+ type: `prismicT.ImageField<${thumbnailNames}>`,
307
+ docs: buildFieldDocs({
308
+ id: config.id,
309
+ model: config.model,
310
+ path: config.path
311
+ })
312
+ });
313
+ } else {
314
+ config.interface.addProperty({
315
+ name: config.id,
316
+ type: "prismicT.ImageField<null>",
317
+ docs: buildFieldDocs({
318
+ id: config.id,
319
+ model: config.model,
320
+ path: config.path
321
+ })
322
+ });
323
+ }
324
+ break;
325
+ }
326
+ case "IntegrationFields": {
327
+ const catalogType = config.fieldConfigs.integrationFields?.catalogTypes?.[config.model.config.catalog];
328
+ config.interface.addProperty({
329
+ name: config.id,
330
+ type: catalogType ? `prismicT.IntegrationFields<${catalogType}>` : "prismicT.IntegrationFields",
331
+ docs: buildFieldDocs({
332
+ id: config.id,
333
+ model: config.model,
334
+ path: config.path
335
+ })
336
+ });
337
+ break;
338
+ }
339
+ case "Link": {
340
+ switch (config.model.config.select) {
341
+ case CustomTypeModelLinkSelectType.Document: {
342
+ config.interface.addProperty({
343
+ name: config.id,
344
+ type: config.model.config.customtypes && config.model.config.customtypes.length > 0 ? `prismicT.RelationField<${config.model.config.customtypes.map((type) => `"${type}"`).join(" | ")}>` : "prismicT.RelationField",
345
+ docs: buildFieldDocs({
346
+ id: config.id,
347
+ model: config.model,
348
+ path: config.path
349
+ })
350
+ });
351
+ break;
352
+ }
353
+ case CustomTypeModelLinkSelectType.Media: {
354
+ config.interface.addProperty({
355
+ name: config.id,
356
+ type: "prismicT.LinkToMediaField",
357
+ docs: buildFieldDocs({
358
+ id: config.id,
359
+ model: config.model,
360
+ path: config.path
361
+ })
362
+ });
363
+ break;
364
+ }
365
+ default: {
366
+ config.interface.addProperty({
367
+ name: config.id,
368
+ type: "prismicT.LinkField",
369
+ docs: buildFieldDocs({
370
+ id: config.id,
371
+ model: config.model,
372
+ path: config.path
373
+ })
374
+ });
375
+ }
376
+ }
377
+ break;
378
+ }
379
+ case "Number": {
380
+ config.interface.addProperty({
381
+ name: config.id,
382
+ type: "prismicT.NumberField",
383
+ docs: buildFieldDocs({
384
+ id: config.id,
385
+ model: config.model,
386
+ path: config.path
387
+ })
388
+ });
389
+ break;
390
+ }
391
+ case "StructuredText": {
392
+ const isTitleField = "single" in config.model.config && config.model.config.single.split(",").every((blockType) => /heading/.test(blockType));
393
+ if (isTitleField) {
394
+ config.interface.addProperty({
395
+ name: config.id,
396
+ type: "prismicT.TitleField",
397
+ docs: buildFieldDocs({
398
+ id: config.id,
399
+ model: config.model,
400
+ path: config.path
401
+ })
402
+ });
403
+ } else {
404
+ config.interface.addProperty({
405
+ name: config.id,
406
+ type: "prismicT.RichTextField",
407
+ docs: buildFieldDocs({
408
+ id: config.id,
409
+ model: config.model,
410
+ path: config.path
411
+ })
412
+ });
413
+ }
414
+ break;
415
+ }
416
+ case "Select": {
417
+ const options = config.model.config.options.map((option) => `"${option}"`).join(" | ");
418
+ const hasDefault = Boolean(config.model.config.default_value);
419
+ if (hasDefault) {
420
+ config.interface.addProperty({
421
+ name: config.id,
422
+ type: `prismicT.SelectField<${options}, "filled">`,
423
+ docs: buildFieldDocs({
424
+ id: config.id,
425
+ model: config.model,
426
+ path: config.path
427
+ })
428
+ });
429
+ } else {
430
+ config.interface.addProperty({
431
+ name: config.id,
432
+ type: `prismicT.SelectField<${options}>`,
433
+ docs: buildFieldDocs({
434
+ id: config.id,
435
+ model: config.model,
436
+ path: config.path
437
+ })
438
+ });
439
+ }
440
+ break;
441
+ }
442
+ case "Text": {
443
+ config.interface.addProperty({
444
+ name: config.id,
445
+ type: "prismicT.KeyTextField",
446
+ docs: buildFieldDocs({
447
+ id: config.id,
448
+ model: config.model,
449
+ path: config.path
450
+ })
451
+ });
452
+ break;
453
+ }
454
+ case "Timestamp": {
455
+ config.interface.addProperty({
456
+ name: config.id,
457
+ type: "prismicT.TimestampField",
458
+ docs: buildFieldDocs({
459
+ id: config.id,
460
+ model: config.model,
461
+ path: config.path
462
+ })
463
+ });
464
+ break;
465
+ }
466
+ case "Group": {
467
+ const itemInterface = config.sourceFile.addInterface({
468
+ name: pascalCase(`${config.path[0].id} Document Data ${config.id} Item`),
469
+ docs: [
470
+ {
471
+ description: (writer) => {
472
+ const humanReadablePath = getHumanReadableFieldPath({
473
+ path: [
474
+ ...config.path,
475
+ {
476
+ id: config.id,
477
+ model: config.model
478
+ }
479
+ ]
480
+ });
481
+ writer.writeLine(`Item in ${humanReadablePath}`);
482
+ }
483
+ }
484
+ ]
485
+ });
486
+ addInterfacePropertiesForFields({
487
+ interface: itemInterface,
488
+ sourceFile: config.sourceFile,
489
+ fields: config.model.config.fields,
490
+ path: [
491
+ ...config.path,
492
+ {
493
+ id: config.id,
494
+ model: config.model
495
+ }
496
+ ],
497
+ fieldConfigs: config.fieldConfigs
498
+ });
499
+ config.interface.addProperty({
500
+ name: config.id,
501
+ type: `prismicT.GroupField<Simplify<${itemInterface.getName()}>>`,
502
+ docs: buildFieldDocs({
503
+ id: config.id,
504
+ model: config.model,
505
+ path: config.path
506
+ })
507
+ });
508
+ break;
509
+ }
510
+ case "Slices": {
511
+ const choiceInterfaceNames = [];
512
+ for (const choiceId in config.model.config.choices) {
513
+ const choice = config.model.config.choices[choiceId];
514
+ if (choice.type === CustomTypeModelSliceType.SharedSlice) {
515
+ choiceInterfaceNames.push(buildSharedSliceInterfaceName({ id: choiceId }));
516
+ } else if (choice.type === CustomTypeModelSliceType.Slice) {
517
+ let primaryInterface;
518
+ if (Object.keys(choice["non-repeat"]).length > 0) {
519
+ primaryInterface = config.sourceFile.addInterface({
520
+ name: pascalCase(`${config.path[0].id} Document Data ${config.id} ${choiceId} Slice Primary`),
521
+ docs: [
522
+ {
523
+ description: (writer) => {
524
+ const humanReadablePath = getHumanReadableFieldPath({
525
+ path: [
526
+ ...config.path,
527
+ {
528
+ id: config.id,
529
+ model: config.model
530
+ },
531
+ {
532
+ id: choiceId,
533
+ model: choice
534
+ },
535
+ {
536
+ id: "primary",
537
+ label: "Primary"
538
+ }
539
+ ]
540
+ });
541
+ writer.writeLine(`Primary content in ${humanReadablePath}`);
542
+ }
543
+ }
544
+ ]
545
+ });
546
+ addInterfacePropertiesForFields({
547
+ interface: primaryInterface,
548
+ sourceFile: config.sourceFile,
549
+ fields: choice["non-repeat"],
550
+ path: [
551
+ ...config.path,
552
+ {
553
+ id: config.id,
554
+ model: config.model
555
+ },
556
+ {
557
+ id: choiceId,
558
+ model: choice
559
+ },
560
+ {
561
+ id: "primary",
562
+ label: "Primary"
563
+ }
564
+ ],
565
+ fieldConfigs: config.fieldConfigs
566
+ });
567
+ }
568
+ let itemInterface;
569
+ if (Object.keys(choice.repeat).length > 0) {
570
+ itemInterface = config.sourceFile.addInterface({
571
+ name: pascalCase(`${config.path[0].id} Document Data ${config.id} ${choiceId} Slice Item`),
572
+ docs: [
573
+ {
574
+ description: (writer) => {
575
+ const humanReadablePath = getHumanReadableFieldPath({
576
+ path: [
577
+ ...config.path,
578
+ {
579
+ id: config.id,
580
+ model: config.model
581
+ },
582
+ {
583
+ id: choiceId,
584
+ model: choice
585
+ },
586
+ {
587
+ id: "items",
588
+ label: "Items"
589
+ }
590
+ ]
591
+ });
592
+ writer.writeLine(`Item in ${humanReadablePath}`);
593
+ }
594
+ }
595
+ ]
596
+ });
597
+ addInterfacePropertiesForFields({
598
+ interface: itemInterface,
599
+ sourceFile: config.sourceFile,
600
+ fields: choice.repeat,
601
+ path: [
602
+ ...config.path,
603
+ {
604
+ id: config.id,
605
+ model: config.model
606
+ },
607
+ {
608
+ id: choiceId,
609
+ model: choice
610
+ },
611
+ {
612
+ id: "items",
613
+ label: "Items"
614
+ }
615
+ ],
616
+ fieldConfigs: config.fieldConfigs
617
+ });
618
+ }
619
+ const sliceType = config.sourceFile.addTypeAlias({
620
+ name: pascalCase(`${config.path[0].id} Document Data ${config.id} ${choiceId} Slice`),
621
+ type: `prismicT.Slice<"${choiceId}", ${primaryInterface ? `Simplify<${primaryInterface.getName()}>` : "Record<string, never>"}, ${itemInterface ? `Simplify<${itemInterface.getName()}>` : "never"}>`
622
+ });
623
+ choiceInterfaceNames.push(sliceType.getName());
624
+ }
625
+ }
626
+ const slicesType = config.sourceFile.addTypeAlias({
627
+ name: pascalCase(`${config.path[0].id} Document Data ${config.id} Slice`),
628
+ type: choiceInterfaceNames.length > 0 ? choiceInterfaceNames.join(" | ") : "never",
629
+ docs: [
630
+ {
631
+ description: (writer) => {
632
+ const humanReadablePath = getHumanReadableFieldPath({
633
+ path: [
634
+ ...config.path,
635
+ {
636
+ id: config.id,
637
+ model: config.model
638
+ }
639
+ ]
640
+ });
641
+ writer.writeLine(`Slice for *${humanReadablePath}*`);
642
+ }
643
+ }
644
+ ]
645
+ });
646
+ config.interface.addProperty({
647
+ name: config.id,
648
+ type: `prismicT.SliceZone<${slicesType.getName()}>`,
649
+ docs: buildFieldDocs({
650
+ id: config.id,
651
+ model: config.model,
652
+ path: config.path
653
+ })
654
+ });
655
+ break;
656
+ }
657
+ default: {
658
+ config.interface.addProperty({
659
+ name: config.id,
660
+ type: "unknown",
661
+ docs: buildFieldDocs({
662
+ id: config.id,
663
+ model: config.model,
664
+ path: config.path
665
+ })
666
+ });
667
+ }
668
+ }
669
+ };
670
+ const addInterfacePropertiesForFields = (config) => {
671
+ for (const name in config.fields) {
672
+ addInterfacePropertyFromField({
673
+ ...config,
674
+ id: name,
675
+ model: config.fields[name]
676
+ });
677
+ }
678
+ };
679
+
680
+ const collectCustomTypeFields = (model) => {
681
+ return Object.assign({}, ...Object.values(model.json));
682
+ };
683
+ const addTypeAliasForCustomType = ({
684
+ model,
685
+ sourceFile,
686
+ localeIDs,
687
+ fieldConfigs
688
+ }) => {
689
+ const { uid: uidField, ...fields } = collectCustomTypeFields(model);
690
+ const hasDataFields = Object.keys(fields).length > 0;
691
+ const hasUIDField = Boolean(uidField);
692
+ let dataInterface;
693
+ if (hasDataFields) {
694
+ dataInterface = sourceFile.addInterface({
695
+ name: pascalCase(`${model.id} Document Data`),
696
+ docs: [
697
+ {
698
+ description: `Content for ${model.label} documents`
699
+ }
700
+ ]
701
+ });
702
+ addInterfacePropertiesForFields({
703
+ fields,
704
+ interface: dataInterface,
705
+ sourceFile,
706
+ path: [
707
+ {
708
+ id: model.id,
709
+ model
710
+ }
711
+ ],
712
+ fieldConfigs
713
+ });
714
+ } else {
715
+ dataInterface = sourceFile.addTypeAlias({
716
+ name: pascalCase(`${model.id} Document Data`),
717
+ type: `Record<string, never>`,
718
+ docs: [
719
+ {
720
+ description: `Content for ${model.label} documents`
721
+ }
722
+ ]
723
+ });
724
+ }
725
+ return sourceFile.addTypeAlias({
726
+ name: pascalCase(`${model.id} Document`),
727
+ typeParameters: [
728
+ {
729
+ name: "Lang",
730
+ constraint: "string",
731
+ default: localeIDs.length > 0 ? localeIDs.map((localeID) => `"${localeID}"`).join(" | ") : "string"
732
+ }
733
+ ],
734
+ type: hasUIDField ? `prismicT.PrismicDocumentWithUID<Simplify<${dataInterface.getName()}>, "${model.id}", Lang>` : `prismicT.PrismicDocumentWithoutUID<Simplify<${dataInterface.getName()}>, "${model.id}", Lang>`,
735
+ docs: [
736
+ {
737
+ description: (writer) => {
738
+ writer.writeLine(`${model.label} document from Prismic`);
739
+ writer.blankLine();
740
+ writer.writeLine(`- **API ID**: \`${model.id}\``);
741
+ writer.writeLine(`- **Repeatable**: \`${model.repeatable}\``);
742
+ writer.writeLine(`- **Documentation**: ${CUSTOM_TYPES_DOCUMENTATION_URL}`);
743
+ },
744
+ tags: [
745
+ {
746
+ tagName: "typeParam",
747
+ text: "Lang - Language API ID of the document."
748
+ }
749
+ ]
750
+ }
751
+ ],
752
+ isExported: true
753
+ });
754
+ };
755
+
756
+ const addTypeAliasForSharedSlice = (config) => {
757
+ const variationTypeNames = [];
758
+ for (const variation of config.model.variations) {
759
+ let primaryInterface;
760
+ if (Object.keys(variation.primary).length > 0) {
761
+ primaryInterface = config.sourceFile.addInterface({
762
+ name: pascalCase(`${buildSharedSliceInterfaceName({ id: config.model.id })} ${variation.id} Primary`),
763
+ docs: [
764
+ {
765
+ description: (writer) => {
766
+ const humanReadablePath = getHumanReadableFieldPath({
767
+ path: [
768
+ {
769
+ id: config.model.id,
770
+ model: config.model
771
+ },
772
+ {
773
+ id: "primary",
774
+ label: "Primary"
775
+ }
776
+ ]
777
+ });
778
+ writer.writeLine(`Primary content in ${humanReadablePath}`);
779
+ }
780
+ }
781
+ ]
782
+ });
783
+ addInterfacePropertiesForFields({
784
+ interface: primaryInterface,
785
+ sourceFile: config.sourceFile,
786
+ fields: variation.primary,
787
+ path: [
788
+ {
789
+ id: config.model.id,
790
+ model: config.model
791
+ },
792
+ {
793
+ id: "primary",
794
+ label: "Primary"
795
+ }
796
+ ],
797
+ fieldConfigs: config.fieldConfigs
798
+ });
799
+ }
800
+ let itemInterface;
801
+ if (Object.keys(variation.items).length > 0) {
802
+ itemInterface = config.sourceFile.addInterface({
803
+ name: pascalCase(`${buildSharedSliceInterfaceName({ id: config.model.id })} ${variation.id} Item`),
804
+ docs: [
805
+ {
806
+ description: (writer) => {
807
+ const humanReadablePath = getHumanReadableFieldPath({
808
+ path: [
809
+ {
810
+ id: config.model.id,
811
+ model: config.model
812
+ },
813
+ {
814
+ id: "items",
815
+ label: "Items"
816
+ }
817
+ ]
818
+ });
819
+ writer.writeLine(`Item in ${humanReadablePath}`);
820
+ }
821
+ }
822
+ ]
823
+ });
824
+ addInterfacePropertiesForFields({
825
+ interface: itemInterface,
826
+ sourceFile: config.sourceFile,
827
+ fields: variation.items,
828
+ path: [
829
+ {
830
+ id: config.model.id,
831
+ model: config.model
832
+ },
833
+ {
834
+ id: "items",
835
+ label: "Items"
836
+ }
837
+ ],
838
+ fieldConfigs: config.fieldConfigs
839
+ });
840
+ }
841
+ const variationType = config.sourceFile.addTypeAlias({
842
+ name: pascalCase(`${buildSharedSliceInterfaceName({
843
+ id: config.model.id
844
+ })} ${variation.id}`),
845
+ type: `prismicT.SharedSliceVariation<"${variation.id}", ${primaryInterface ? `Simplify<${primaryInterface.getName()}>` : "Record<string, never>"}, ${itemInterface ? `Simplify<${itemInterface.getName()}>` : "never"}>`,
846
+ docs: [
847
+ {
848
+ description: (writer) => {
849
+ writer.writeLine(`${variation.name} variation for ${config.model.name} Slice`);
850
+ writer.blankLine();
851
+ writer.writeLine(`- **API ID**: \`${variation.id}\``);
852
+ writer.writeLine(`- **Description**: \`${variation.description}\``);
853
+ writer.writeLine(`- **Documentation**: ${SHARED_SLICES_DOCUMENTATION_URL}`);
854
+ }
855
+ }
856
+ ]
857
+ });
858
+ variationTypeNames.push(variationType.getName());
859
+ }
860
+ const variationsType = config.sourceFile.addTypeAlias({
861
+ name: pascalCase(`${config.model.id} Slice Variation`),
862
+ type: variationTypeNames.length > 0 ? variationTypeNames.join(" | ") : "never",
863
+ docs: [
864
+ {
865
+ description: (writer) => {
866
+ const humanReadablePath = getHumanReadableFieldPath({
867
+ path: [
868
+ {
869
+ id: config.model.id,
870
+ model: config.model
871
+ }
872
+ ]
873
+ });
874
+ writer.writeLine(`Slice variation for *${humanReadablePath}*`);
875
+ }
876
+ }
877
+ ]
878
+ });
879
+ return config.sourceFile.addTypeAlias({
880
+ name: pascalCase(buildSharedSliceInterfaceName({
881
+ id: config.model.id
882
+ })),
883
+ type: `prismicT.SharedSlice<"${config.model.id}", ${variationsType.getName()}>`,
884
+ docs: [
885
+ {
886
+ description: (writer) => {
887
+ writer.writeLine(`${config.model.name} Shared Slice`);
888
+ writer.blankLine();
889
+ writer.writeLine(`- **API ID**: \`${config.model.id}\``);
890
+ writer.writeLine(`- **Description**: \`${config.model.description}\``);
891
+ writer.writeLine(`- **Documentation**: ${SHARED_SLICES_DOCUMENTATION_URL}`);
892
+ }
893
+ }
894
+ ],
895
+ isExported: true
896
+ });
897
+ };
898
+
899
+ const getSourceFileText = (sourceFile) => {
900
+ const text = sourceFile.print().replace(new RegExp(BLANK_LINE_IDENTIFIER, "g"), "");
901
+ return text;
902
+ };
903
+
904
+ const generateTypes = (config = {}) => {
905
+ const project = new Project({
906
+ useInMemoryFileSystem: true
907
+ });
908
+ const sourceFile = project.createSourceFile("types.d.ts");
909
+ sourceFile.addImportDeclaration({
910
+ moduleSpecifier: "@prismicio/types",
911
+ namespaceImport: "prismicT",
912
+ isTypeOnly: true,
913
+ leadingTrivia: NON_EDITABLE_FILE_HEADER
914
+ });
915
+ sourceFile.addStatements(BLANK_LINE_IDENTIFIER);
916
+ sourceFile.addTypeAlias({
917
+ name: "Simplify",
918
+ typeParameters: [
919
+ {
920
+ name: "T"
921
+ }
922
+ ],
923
+ type: `{ [KeyType in keyof T]: T[KeyType] }`
924
+ });
925
+ if (config.customTypeModels) {
926
+ for (const model of config.customTypeModels) {
927
+ addTypeAliasForCustomType({
928
+ model,
929
+ sourceFile,
930
+ localeIDs: config.localeIDs || [],
931
+ fieldConfigs: config.fieldConfigs || {}
932
+ });
933
+ }
934
+ }
935
+ if (config.sharedSliceModels) {
936
+ for (const model of config.sharedSliceModels) {
937
+ addTypeAliasForSharedSlice({
938
+ model,
939
+ sourceFile,
940
+ fieldConfigs: config.fieldConfigs || {}
941
+ });
942
+ }
943
+ }
944
+ return getSourceFileText(sourceFile);
945
+ };
946
+
947
+ const configSchema = Joi.object({
948
+ repositoryName: Joi.string(),
949
+ accessToken: Joi.string(),
950
+ customTypesAPIToken: Joi.string(),
951
+ output: Joi.string(),
952
+ locales: Joi.alternatives(Joi.array().items(Joi.string().required()), Joi.object({
953
+ ids: Joi.array().items(Joi.string().required()),
954
+ fetchFromRepository: Joi.boolean()
955
+ })),
956
+ models: Joi.alternatives(Joi.array().items(Joi.string().required()), Joi.object({
957
+ files: Joi.array().items(Joi.string().required()),
958
+ fetchFromRepository: Joi.boolean()
959
+ })),
960
+ fields: Joi.object({
961
+ embed: Joi.object({
962
+ providerTypes: Joi.object().pattern(Joi.string(), Joi.string().required())
963
+ }),
964
+ integrationFields: Joi.object({
965
+ catalogTypes: Joi.object().pattern(Joi.string(), Joi.string().required())
966
+ })
967
+ })
968
+ }).when(Joi.object({
969
+ locales: Joi.object({
970
+ fetchFromRepository: Joi.valid(true)
971
+ })
972
+ }), {
973
+ then: Joi.object({
974
+ repositoryName: Joi.required()
975
+ })
976
+ }).when(Joi.object({
977
+ models: Joi.object({
978
+ fetchFromRepository: Joi.valid(true)
979
+ })
980
+ }), {
981
+ then: Joi.object({
982
+ repositoryName: Joi.required(),
983
+ customTypesAPIToken: Joi.required()
984
+ })
985
+ });
986
+
987
+ const loadLocaleIDs = async (config) => {
988
+ const localeIDs = new Set(config.localeIDs || []);
989
+ if ("repositoryName" in config && config.fetchFromRepository) {
990
+ const client = prismic.createClient(config.repositoryName, {
991
+ accessToken: config.accessToken,
992
+ fetch
993
+ });
994
+ const repository = await client.getRepository();
995
+ for (const language of repository.languages) {
996
+ localeIDs.add(language.id);
997
+ }
998
+ }
999
+ return [...localeIDs];
1000
+ };
1001
+
1002
+ const isCustomTypeModel = (input) => {
1003
+ return typeof input === "object" && input !== null && "json" in input;
1004
+ };
1005
+ const isSharedSliceModel = (input) => {
1006
+ return typeof input === "object" && input !== null && "variations" in input;
1007
+ };
1008
+ const readJSONFromGlob = async (globs) => {
1009
+ const paths = await fg(globs.split(",").map((path) => path.trim()));
1010
+ return paths.map((path) => {
1011
+ const raw = readFileSync(path, "utf8");
1012
+ return JSON.parse(raw);
1013
+ });
1014
+ };
1015
+ const loadModels = async (config) => {
1016
+ const customTypeModels = {};
1017
+ const sharedSliceModels = {};
1018
+ if ("customTypesAPIToken" in config) {
1019
+ const customTypesClient = prismicCT.createClient({
1020
+ repositoryName: config.repositoryName,
1021
+ token: config.customTypesAPIToken,
1022
+ fetch
1023
+ });
1024
+ if (config.fetchFromRepository) {
1025
+ const [remoteCustomTypeModels, remoteSharedSliceModels] = await Promise.all([
1026
+ customTypesClient.getAll(),
1027
+ customTypesClient.getAllSharedSlices()
1028
+ ]);
1029
+ for (const customTypeModel of remoteCustomTypeModels) {
1030
+ customTypeModels[customTypeModel.id] = customTypeModel;
1031
+ }
1032
+ for (const sharedSliceModel of remoteSharedSliceModels) {
1033
+ sharedSliceModels[sharedSliceModel.id] = sharedSliceModel;
1034
+ }
1035
+ }
1036
+ }
1037
+ if (config.localPaths) {
1038
+ const models = (await Promise.all(config.localPaths.map((glob) => readJSONFromGlob(glob)))).flat();
1039
+ for (const model of models) {
1040
+ if (isCustomTypeModel(model)) {
1041
+ customTypeModels[model.id] = model;
1042
+ } else if (isSharedSliceModel(model)) {
1043
+ sharedSliceModels[model.id] = model;
1044
+ }
1045
+ }
1046
+ }
1047
+ return {
1048
+ customTypeModels: Object.values(customTypeModels),
1049
+ sharedSliceModels: Object.values(sharedSliceModels)
1050
+ };
1051
+ };
1052
+
1053
+ const jiti = _jiti(process.cwd());
1054
+ const DEFAULT_CONFIG_PATHS = [
1055
+ "prismicCodegen.config.ts",
1056
+ "prismicCodegen.config.js"
1057
+ ];
1058
+ const loadConfig = (config) => {
1059
+ if (config.path) {
1060
+ if (existsSync(config.path)) {
1061
+ return jiti(config.path);
1062
+ } else {
1063
+ throw new Error(`Config file does not exist: ${config.path}`);
1064
+ }
1065
+ } else {
1066
+ for (const configPath of DEFAULT_CONFIG_PATHS) {
1067
+ if (existsSync(configPath)) {
1068
+ const mod = jiti(resolve(configPath));
1069
+ return "default" in mod ? mod.default : mod;
1070
+ }
1071
+ }
1072
+ }
1073
+ return {};
1074
+ };
1075
+
1076
+ const cli = meow(`
1077
+ Usage:
1078
+ prismic-ts-codegen [options...]
1079
+ prismic-ts-codegen init [options...]
1080
+
1081
+ Commands:
1082
+ init [options]
1083
+
1084
+ Options:
1085
+ -c, --config <path> Path to a prismic-ts-codegen configuration file.
1086
+ `, {
1087
+ importMeta: import.meta,
1088
+ flags: {
1089
+ config: {
1090
+ type: "string",
1091
+ alias: "c",
1092
+ isRequired: false
1093
+ }
1094
+ }
1095
+ });
1096
+ const main = async () => {
1097
+ if (cli.input[0] === "init") {
1098
+ const configPath = cli.flags.config || "prismicCodegen.config.ts";
1099
+ if (existsSync(configPath)) {
1100
+ console.info(`
1101
+ ${configPath} already exists.`);
1102
+ } else {
1103
+ let contents = "";
1104
+ if (existsSync("sm.json")) {
1105
+ contents = stripIndent`
1106
+ import type { Config } from "prismic-ts-codegen";
1107
+
1108
+ const config: Config = {
1109
+ output: "./types.generated.ts",
1110
+
1111
+ models: ["./customtypes/**/index.json", "./slices/**/model.json"],
1112
+ };
1113
+
1114
+ export default config;
1115
+ `;
1116
+ } else {
1117
+ contents = stripIndent`
1118
+ import type { Config } from "prismic-ts-codegen";
1119
+
1120
+ const config: Config = {
1121
+ output: "./types.generated.ts",
1122
+ };
1123
+
1124
+ export default config;
1125
+ `;
1126
+ }
1127
+ writeFileSync(configPath, contents);
1128
+ console.info(`
1129
+ Created prismic-ts-codegen config file: ${configPath}`);
1130
+ }
1131
+ } else {
1132
+ const unvalidatedConfig = loadConfig({ path: cli.flags.config });
1133
+ const { value: config, error } = configSchema.validate(unvalidatedConfig);
1134
+ if (config && !error) {
1135
+ const { customTypeModels, sharedSliceModels } = await loadModels({
1136
+ localPaths: Array.isArray(config.models) ? config.models : config.models?.files,
1137
+ repositoryName: config.repositoryName,
1138
+ customTypesAPIToken: config.customTypesAPIToken,
1139
+ fetchFromRepository: config.models && "fetchFromRepository" in config.models && config.models.fetchFromRepository
1140
+ });
1141
+ const localeIDs = await loadLocaleIDs({
1142
+ localeIDs: Array.isArray(config.locales) ? config.locales : config.locales?.ids,
1143
+ repositoryName: config.repositoryName,
1144
+ accessToken: config.accessToken,
1145
+ fetchFromRepository: config.locales && "fetchFromRepository" in config.locales && config.locales.fetchFromRepository
1146
+ });
1147
+ const types = generateTypes({
1148
+ customTypeModels,
1149
+ sharedSliceModels,
1150
+ localeIDs
1151
+ });
1152
+ if (config.output) {
1153
+ writeFileSync(resolve(config.output), types);
1154
+ } else {
1155
+ process.stdout.write(types + "\n");
1156
+ }
1157
+ } else {
1158
+ if (error) {
1159
+ console.error(error.message);
1160
+ return;
1161
+ }
1162
+ }
1163
+ }
1164
+ };
1165
+ main().catch((error) => console.error(error));
1166
+ //# sourceMappingURL=cli.mjs.map