prismic-ts-codegen 0.0.6 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,9 +9,7 @@
9
9
 
10
10
  <!-- TODO: Replacing link to Prismic with [Prismic][prismic] is useful here -->
11
11
 
12
- > ⚠ This project is in an experimental state. Use it at your own risk or stay tuned for the official release!
13
-
14
- An experimental Prismic model-to-TypeScript-type generator.
12
+ A Prismic model-to-TypeScript-type generator.
15
13
 
16
14
  - Converts Custom Type and Shared Slice models to TypeScript types
17
15
  - Integrates with other Prismic TypeScript libraries
package/bin/cli.mjs CHANGED
@@ -5,7 +5,7 @@ import { resolve } from 'path';
5
5
  import meow from 'meow';
6
6
  import { stripIndent } from 'common-tags';
7
7
  import { Project, ModuleDeclarationKind } from 'ts-morph';
8
- import { CustomTypeModelFieldType, CustomTypeModelLinkSelectType, CustomTypeModelSliceType } from '@prismicio/types';
8
+ import { CustomTypeModelSliceType, CustomTypeModelFieldType, CustomTypeModelLinkSelectType } from '@prismicio/types';
9
9
  import { pascalCase as pascalCase$1, pascalCaseTransformMerge } from 'pascal-case';
10
10
  import Joi from 'joi';
11
11
  import * as prismic from '@prismicio/client';
@@ -37,19 +37,24 @@ const FIELD_DOCUMENTATION_URLS = {
37
37
  GeoPoint: "https://prismic.io/docs/core-concepts/geopoint",
38
38
  Group: "https://prismic.io/docs/core-concepts/group",
39
39
  IntegrationFields: "https://prismic.io/docs/core-concepts/integration-fields",
40
- Slices: "https://prismic.io/docs/core-concepts/slices"
40
+ Slices: "https://prismic.io/docs/core-concepts/slices",
41
+ LegacySlices: "https://prismic.io/docs/core-concepts/slices"
41
42
  };
42
43
 
43
44
  const isCustomTypeModelField = (model) => {
44
- return typeof model === "object" && model !== null && "type" in model && "config" in model;
45
+ return typeof model === "object" && model !== null && "type" in model;
46
+ };
47
+
48
+ const hasOwnProperty = (obj, prop) => {
49
+ return obj.hasOwnProperty(prop);
45
50
  };
46
51
 
47
52
  const isCustomTypeModelSlice = (model) => {
48
- return typeof model === "object" && model !== null && "non-repeat" in model && "repeat" in model;
53
+ return typeof model === "object" && model !== null && hasOwnProperty(model, "type") && model.type === CustomTypeModelSliceType.Slice;
49
54
  };
50
55
 
51
56
  const isSharedSliceModel$1 = (model) => {
52
- return typeof model === "object" && model !== null && "variations" in model;
57
+ return typeof model === "object" && model !== null && hasOwnProperty(model, "type") && model.type === CustomTypeModelSliceType.SharedSlice;
53
58
  };
54
59
 
55
60
  const getAPIIDPath = (config) => {
@@ -78,16 +83,19 @@ const isCustomTypeModel$1 = (model) => {
78
83
 
79
84
  const getHumanReadableModelName = (config) => {
80
85
  if (isCustomTypeModel$1(config.model)) {
81
- return config.model.label;
86
+ return config.model.label || config.model.id;
82
87
  } else if (isSharedSliceModel$1(config.model)) {
83
88
  return config.model.name;
84
- } else if (isCustomTypeModelField(config.model) && "label" in config.model.config) {
85
- return config.model.config.label;
89
+ } else if (isCustomTypeModelField(config.model)) {
90
+ if (config.model.config && "label" in config.model.config) {
91
+ return config.model.config.label || config.id;
92
+ } else if (config.model.config && "fieldset" in config.model) {
93
+ return config.model.fieldset || config.id;
94
+ }
86
95
  } else if (isCustomTypeModelSlice(config.model)) {
87
- return config.model.fieldset;
88
- } else {
89
- return `Slice Zone (\`${config.id}\`)`;
96
+ return config.model.fieldset || config.id;
90
97
  }
98
+ return `\`${config.id}\``;
91
99
  };
92
100
 
93
101
  const getHumanReadableFieldPath = (config) => {
@@ -108,14 +116,15 @@ const getHumanReadableFieldPath = (config) => {
108
116
  const getHumanReadableFieldType = (config) => {
109
117
  switch (config.model.type) {
110
118
  case "StructuredText": {
111
- const isTitleField = "single" in config.model.config && config.model.config.single.split(",").every((blockType) => /heading/.test(blockType));
119
+ const isTitleField = config.model.config && "single" in config.model.config && config.model.config.single && config.model.config.single.split(",").every((blockType) => /heading/.test(blockType));
112
120
  return isTitleField ? "Title" : "Rich Text";
113
121
  }
114
122
  case "IntegrationFields": {
115
- return `Integration Fields (Catalog: \`${config.model.config.catalog}\`)`;
123
+ const catalog = config.model.config?.catalog;
124
+ return `Integration Fields (Catalog: ${catalog ? `\`${catalog}\`` : "*unknown*"})`;
116
125
  }
117
126
  case "Link": {
118
- switch (config.model.config.select) {
127
+ switch (config.model.config?.select) {
119
128
  case CustomTypeModelLinkSelectType.Document: {
120
129
  return "Content Relationship";
121
130
  }
@@ -176,8 +185,8 @@ const buildFieldDocs = (config) => {
176
185
  const fieldType = getHumanReadableFieldType({
177
186
  model: config.model
178
187
  });
179
- const placeholder = "placeholder" in config.model.config && config.model.config.placeholder ? config.model.config.placeholder : `*None*`;
180
- const defaultValue = "default_value" in config.model.config ? config.model.config.default_value : void 0;
188
+ const placeholder = config.model.config && "placeholder" in config.model.config && config.model.config.placeholder ? config.model.config.placeholder : `*None*`;
189
+ const defaultValue = config.model.config && "default_value" in config.model.config ? config.model.config.default_value : void 0;
181
190
  const apiIDPath = getAPIIDPath({
182
191
  path: [...config.path, { id: config.id, model: config.model }]
183
192
  });
@@ -282,7 +291,7 @@ const addInterfacePropertyForField = (config) => {
282
291
  break;
283
292
  }
284
293
  case "Image": {
285
- if (config.model.config.thumbnails.length > 0) {
294
+ if (config.model.config?.thumbnails && config.model.config.thumbnails.length > 0) {
286
295
  const thumbnailNames = config.model.config.thumbnails.map((thumbnail) => `"${thumbnail.name}"`).join(" | ");
287
296
  config.interface.addProperty({
288
297
  name: config.id,
@@ -309,7 +318,7 @@ const addInterfacePropertyForField = (config) => {
309
318
  break;
310
319
  }
311
320
  case "IntegrationFields": {
312
- const catalogType = config.fieldConfigs.integrationFields?.catalogTypes?.[config.model.config.catalog];
321
+ const catalogType = config.model.config?.catalog ? config.fieldConfigs.integrationFields?.catalogTypes?.[config.model.config.catalog] : void 0;
313
322
  config.interface.addProperty({
314
323
  name: config.id,
315
324
  type: catalogType ? `prismicT.IntegrationFields<${catalogType}>` : "prismicT.IntegrationFields",
@@ -323,11 +332,12 @@ const addInterfacePropertyForField = (config) => {
323
332
  break;
324
333
  }
325
334
  case "Link": {
326
- switch (config.model.config.select) {
335
+ switch (config.model.config?.select) {
327
336
  case CustomTypeModelLinkSelectType.Document: {
337
+ config.model.config;
328
338
  config.interface.addProperty({
329
339
  name: config.id,
330
- type: config.model.config.customtypes && config.model.config.customtypes.length > 0 ? `prismicT.RelationField<${config.model.config.customtypes.map((type) => `"${type}"`).join(" | ")}>` : "prismicT.RelationField",
340
+ type: "customtypes" in config.model.config && config.model.config.customtypes && config.model.config.customtypes.length > 0 ? `prismicT.RelationField<${config.model.config.customtypes.map((type) => `"${type}"`).join(" | ")}>` : "prismicT.RelationField",
331
341
  docs: buildFieldDocs({
332
342
  id: config.id,
333
343
  model: config.model,
@@ -379,7 +389,7 @@ const addInterfacePropertyForField = (config) => {
379
389
  break;
380
390
  }
381
391
  case "StructuredText": {
382
- const isTitleField = "single" in config.model.config && config.model.config.single.split(",").every((blockType) => /heading/.test(blockType));
392
+ const isTitleField = config.model.config && "single" in config.model.config && config.model.config.single && config.model.config.single.split(",").every((blockType) => /heading/.test(blockType));
383
393
  if (isTitleField) {
384
394
  config.interface.addProperty({
385
395
  name: config.id,
@@ -406,12 +416,12 @@ const addInterfacePropertyForField = (config) => {
406
416
  break;
407
417
  }
408
418
  case "Select": {
409
- const options = config.model.config.options.map((option) => `"${option}"`).join(" | ");
410
- const hasDefault = Boolean(config.model.config.default_value);
419
+ const options = config.model.config?.options?.map((option) => `"${option}"`).join(" | ");
420
+ const hasDefault = Boolean(config.model.config?.default_value);
411
421
  if (hasDefault) {
412
422
  config.interface.addProperty({
413
423
  name: config.id,
414
- type: `prismicT.SelectField<${options}, "filled">`,
424
+ type: `prismicT.SelectField<${options || "string"}, "filled">`,
415
425
  docs: buildFieldDocs({
416
426
  id: config.id,
417
427
  model: config.model,
@@ -480,19 +490,21 @@ const addInterfacePropertyForField = (config) => {
480
490
  ],
481
491
  isExported: true
482
492
  });
483
- addInterfacePropertiesForFields({
484
- interface: itemInterface,
485
- sourceFile: config.sourceFile,
486
- fields: config.model.config.fields,
487
- path: [
488
- ...config.path,
489
- {
490
- id: config.id,
491
- model: config.model
492
- }
493
- ],
494
- fieldConfigs: config.fieldConfigs
495
- });
493
+ if (config.model.config?.fields) {
494
+ addInterfacePropertiesForFields({
495
+ interface: itemInterface,
496
+ sourceFile: config.sourceFile,
497
+ fields: config.model.config.fields,
498
+ path: [
499
+ ...config.path,
500
+ {
501
+ id: config.id,
502
+ model: config.model
503
+ }
504
+ ],
505
+ fieldConfigs: config.fieldConfigs
506
+ });
507
+ }
496
508
  config.interface.addProperty({
497
509
  name: config.id,
498
510
  type: `prismicT.GroupField<Simplify<${itemInterface.getName()}>>`,
@@ -507,120 +519,122 @@ const addInterfacePropertyForField = (config) => {
507
519
  }
508
520
  case "Slices": {
509
521
  const choiceInterfaceNames = [];
510
- for (const choiceId in config.model.config.choices) {
511
- const choice = config.model.config.choices[choiceId];
512
- if (choice.type === CustomTypeModelSliceType.SharedSlice) {
513
- choiceInterfaceNames.push(buildSharedSliceInterfaceName({ id: choiceId }));
514
- } else if (choice.type === CustomTypeModelSliceType.Slice) {
515
- let primaryInterface;
516
- if (Object.keys(choice["non-repeat"]).length > 0) {
517
- primaryInterface = config.sourceFile.addInterface({
518
- name: pascalCase(`${config.path[0].id} Document Data ${config.id} ${choiceId} Slice Primary`),
519
- docs: [
520
- {
521
- description: (writer) => {
522
- const humanReadablePath = getHumanReadableFieldPath({
523
- path: [
524
- ...config.path,
525
- {
526
- id: config.id,
527
- model: config.model
528
- },
529
- {
530
- id: choiceId,
531
- model: choice
532
- },
533
- {
534
- id: "primary",
535
- label: "Primary"
536
- }
537
- ]
538
- });
539
- writer.writeLine(`Primary content in ${humanReadablePath}`);
522
+ if (config.model.config?.choices) {
523
+ for (const choiceId in config.model.config.choices) {
524
+ const choice = config.model.config.choices[choiceId];
525
+ if (choice.type === CustomTypeModelSliceType.SharedSlice) {
526
+ choiceInterfaceNames.push(buildSharedSliceInterfaceName({ id: choiceId }));
527
+ } else if (choice.type === CustomTypeModelSliceType.Slice) {
528
+ let primaryInterface;
529
+ if (choice["non-repeat"] && Object.keys(choice["non-repeat"]).length > 0) {
530
+ primaryInterface = config.sourceFile.addInterface({
531
+ name: pascalCase(`${config.path[0].id} Document Data ${config.id} ${choiceId} Slice Primary`),
532
+ docs: [
533
+ {
534
+ description: (writer) => {
535
+ const humanReadablePath = getHumanReadableFieldPath({
536
+ path: [
537
+ ...config.path,
538
+ {
539
+ id: config.id,
540
+ model: config.model
541
+ },
542
+ {
543
+ id: choiceId,
544
+ model: choice
545
+ },
546
+ {
547
+ id: "primary",
548
+ label: "Primary"
549
+ }
550
+ ]
551
+ });
552
+ writer.writeLine(`Primary content in ${humanReadablePath}`);
553
+ }
540
554
  }
541
- }
542
- ]
543
- });
544
- addInterfacePropertiesForFields({
545
- interface: primaryInterface,
546
- sourceFile: config.sourceFile,
547
- fields: choice["non-repeat"],
548
- path: [
549
- ...config.path,
550
- {
551
- id: config.id,
552
- model: config.model
553
- },
554
- {
555
- id: choiceId,
556
- model: choice
557
- },
558
- {
559
- id: "primary",
560
- label: "Primary"
561
- }
562
- ],
563
- fieldConfigs: config.fieldConfigs
564
- });
565
- }
566
- let itemInterface;
567
- if (Object.keys(choice.repeat).length > 0) {
568
- itemInterface = config.sourceFile.addInterface({
569
- name: pascalCase(`${config.path[0].id} Document Data ${config.id} ${choiceId} Slice Item`),
570
- docs: [
571
- {
572
- description: (writer) => {
573
- const humanReadablePath = getHumanReadableFieldPath({
574
- path: [
575
- ...config.path,
576
- {
577
- id: config.id,
578
- model: config.model
579
- },
580
- {
581
- id: choiceId,
582
- model: choice
583
- },
584
- {
585
- id: "items",
586
- label: "Items"
587
- }
588
- ]
589
- });
590
- writer.writeLine(`Item in ${humanReadablePath}`);
555
+ ]
556
+ });
557
+ addInterfacePropertiesForFields({
558
+ interface: primaryInterface,
559
+ sourceFile: config.sourceFile,
560
+ fields: choice["non-repeat"],
561
+ path: [
562
+ ...config.path,
563
+ {
564
+ id: config.id,
565
+ model: config.model
566
+ },
567
+ {
568
+ id: choiceId,
569
+ model: choice
570
+ },
571
+ {
572
+ id: "primary",
573
+ label: "Primary"
574
+ }
575
+ ],
576
+ fieldConfigs: config.fieldConfigs
577
+ });
578
+ }
579
+ let itemInterface;
580
+ if (choice.repeat && Object.keys(choice.repeat).length > 0) {
581
+ itemInterface = config.sourceFile.addInterface({
582
+ name: pascalCase(`${config.path[0].id} Document Data ${config.id} ${choiceId} Slice Item`),
583
+ docs: [
584
+ {
585
+ description: (writer) => {
586
+ const humanReadablePath = getHumanReadableFieldPath({
587
+ path: [
588
+ ...config.path,
589
+ {
590
+ id: config.id,
591
+ model: config.model
592
+ },
593
+ {
594
+ id: choiceId,
595
+ model: choice
596
+ },
597
+ {
598
+ id: "items",
599
+ label: "Items"
600
+ }
601
+ ]
602
+ });
603
+ writer.writeLine(`Item in ${humanReadablePath}`);
604
+ }
605
+ }
606
+ ],
607
+ isExported: true
608
+ });
609
+ addInterfacePropertiesForFields({
610
+ interface: itemInterface,
611
+ sourceFile: config.sourceFile,
612
+ fields: choice.repeat,
613
+ path: [
614
+ ...config.path,
615
+ {
616
+ id: config.id,
617
+ model: config.model
618
+ },
619
+ {
620
+ id: choiceId,
621
+ model: choice
622
+ },
623
+ {
624
+ id: "items",
625
+ label: "Items"
591
626
  }
592
- }
593
- ],
627
+ ],
628
+ fieldConfigs: config.fieldConfigs
629
+ });
630
+ }
631
+ const sliceType = config.sourceFile.addTypeAlias({
632
+ name: pascalCase(`${config.path[0].id} Document Data ${config.id} ${choiceId} Slice`),
633
+ type: `prismicT.Slice<"${choiceId}", ${primaryInterface ? `Simplify<${primaryInterface.getName()}>` : "Record<string, never>"}, ${itemInterface ? `Simplify<${itemInterface.getName()}>` : "never"}>`,
594
634
  isExported: true
595
635
  });
596
- addInterfacePropertiesForFields({
597
- interface: itemInterface,
598
- sourceFile: config.sourceFile,
599
- fields: choice.repeat,
600
- path: [
601
- ...config.path,
602
- {
603
- id: config.id,
604
- model: config.model
605
- },
606
- {
607
- id: choiceId,
608
- model: choice
609
- },
610
- {
611
- id: "items",
612
- label: "Items"
613
- }
614
- ],
615
- fieldConfigs: config.fieldConfigs
616
- });
636
+ choiceInterfaceNames.push(sliceType.getName());
617
637
  }
618
- const sliceType = config.sourceFile.addTypeAlias({
619
- name: pascalCase(`${config.path[0].id} Document Data ${config.id} ${choiceId} Slice`),
620
- type: `prismicT.Slice<"${choiceId}", ${primaryInterface ? `Simplify<${primaryInterface.getName()}>` : "Record<string, never>"}, ${itemInterface ? `Simplify<${itemInterface.getName()}>` : "never"}>`,
621
- isExported: true
622
- });
623
- choiceInterfaceNames.push(sliceType.getName());
624
638
  }
625
639
  }
626
640
  const slicesType = config.sourceFile.addTypeAlias({
@@ -673,7 +687,7 @@ const addInterfacePropertiesForFields = (config) => {
673
687
  for (const name in config.fields) {
674
688
  addInterfacePropertyForField({
675
689
  ...config,
676
- id: name.includes("-") ? `"${name}"` : name,
690
+ id: name.includes("-") || /^[0-9]/.test(name) ? `"${name}"` : name,
677
691
  model: config.fields[name],
678
692
  tabName: config.tabName
679
693
  });
@@ -764,7 +778,7 @@ const addTypeAliasForSharedSlice = (config) => {
764
778
  const variationTypeNames = [];
765
779
  for (const variation of config.model.variations) {
766
780
  let primaryInterface;
767
- if (Object.keys(variation.primary).length > 0) {
781
+ if (variation.primary && Object.keys(variation.primary).length > 0) {
768
782
  primaryInterface = config.sourceFile.addInterface({
769
783
  name: pascalCase(`${buildSharedSliceInterfaceName({ id: config.model.id })} ${variation.id} Primary`),
770
784
  docs: [
@@ -805,7 +819,7 @@ const addTypeAliasForSharedSlice = (config) => {
805
819
  });
806
820
  }
807
821
  let itemInterface;
808
- if (Object.keys(variation.items).length > 0) {
822
+ if (variation.items && Object.keys(variation.items).length > 0) {
809
823
  itemInterface = config.sourceFile.addInterface({
810
824
  name: pascalCase(`${buildSharedSliceInterfaceName({ id: config.model.id })} ${variation.id} Item`),
811
825
  docs: [
@@ -1002,9 +1016,9 @@ const configSchema = Joi.object({
1002
1016
  accessToken: Joi.string(),
1003
1017
  customTypesAPIToken: Joi.string(),
1004
1018
  output: Joi.string(),
1005
- clientIntegration: {
1006
- includeCreateClientInterface: Joi.boolean
1007
- },
1019
+ clientIntegration: Joi.object({
1020
+ includeCreateClientInterface: Joi.boolean()
1021
+ }),
1008
1022
  locales: Joi.alternatives(Joi.array().items(Joi.string().required()), Joi.object({
1009
1023
  ids: Joi.array().items(Joi.string().required()),
1010
1024
  fetchFromRepository: Joi.boolean()