prismic-ts-codegen 0.0.2 → 0.0.5

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
@@ -20,7 +20,7 @@ An experimental Prismic model-to-TypeScript-type generator.
20
20
  ## Install
21
21
 
22
22
  ```bash
23
- npm install --save-dev prismic-ts-codegen
23
+ npm install --save-dev prismic-ts-codegen @prismicio/types
24
24
  ```
25
25
 
26
26
  ## Usage
@@ -62,7 +62,60 @@ npm install --save-dev prismic-ts-codegen
62
62
 
63
63
  ## Documentation
64
64
 
65
- To discover what's new on this package check out [the changelog][changelog]. For full documentation, visit the [official Prismic documentation][prismic-docs].
65
+ ### Configuration
66
+
67
+ `prismic-ts-codegen` is configured using a `prismicCodegen.config.ts` file. It must export a configuration object as the default export.
68
+
69
+ #### Interface
70
+
71
+ See the [`Config` TypeScript interface](./src/cli/types.ts) for a complete reference of each configuration property.
72
+
73
+ #### Full example
74
+
75
+ ```typescript
76
+ // prismicCodegen.config.ts
77
+
78
+ import type { Config } from "prismic-ts-codegen";
79
+
80
+ const config: Config = {
81
+ repositoryName: "nextjs-blog-demo",
82
+ accessToken: "abc123xyz345",
83
+ customTypesAPIToken: "abc123xyz345",
84
+
85
+ output: "./types.generated.ts",
86
+
87
+ clientIntegration: {
88
+ includeCreateClientInterface: true,
89
+ },
90
+
91
+ locales: {
92
+ ids: ["en-us", "fr-fr", "en-gb"],
93
+ fetchFromRepository: true,
94
+ },
95
+
96
+ models: {
97
+ files: ["./customtypes/**/index.json", "./slices/**/model.json"],
98
+ fetchFromRepository: true,
99
+ },
100
+
101
+ fields: {
102
+ embed: {
103
+ providerTypes: {
104
+ YouTube: 'import("./types").OEmbedYouTube',
105
+ Twitter: 'import("./types").OEmbedTwitter',
106
+ Vimeo: 'import("./types").OEmbedVimeo',
107
+ },
108
+ },
109
+ integrationFields: {
110
+ catalogTypes: {
111
+ shop: 'import("./types").IntegrationFieldShop',
112
+ },
113
+ },
114
+ },
115
+ };
116
+
117
+ export default config;
118
+ ```
66
119
 
67
120
  ## Contributing
68
121
 
package/bin/cli.mjs CHANGED
@@ -4,7 +4,8 @@ import { readFileSync, existsSync, writeFileSync } from 'fs';
4
4
  import { resolve } from 'path';
5
5
  import meow from 'meow';
6
6
  import { stripIndent } from 'common-tags';
7
- import { Project } from 'ts-morph';
7
+ import { Project, ModuleDeclarationKind } from 'ts-morph';
8
+ import { CustomTypeModelFieldType, CustomTypeModelLinkSelectType, CustomTypeModelSliceType } from '@prismicio/types';
8
9
  import { pascalCase as pascalCase$1, pascalCaseTransformMerge } from 'pascal-case';
9
10
  import Joi from 'joi';
10
11
  import * as prismic from '@prismicio/client';
@@ -39,33 +40,6 @@ const FIELD_DOCUMENTATION_URLS = {
39
40
  Slices: "https://prismic.io/docs/core-concepts/slices"
40
41
  };
41
42
 
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
43
  const isCustomTypeModelField = (model) => {
70
44
  return typeof model === "object" && model !== null && "type" in model && "config" in model;
71
45
  };
@@ -180,6 +154,9 @@ const writeFieldDocsDescription = (config) => {
180
154
  if (config.apiIDPath) {
181
155
  config.writer.writeLine(`- **API ID Path**: ${config.apiIDPath}`);
182
156
  }
157
+ if (config.tabName) {
158
+ config.writer.writeLine(`- **Tab**: ${config.tabName}`);
159
+ }
183
160
  if (config.documentationURL) {
184
161
  config.writer.writeLine(`- **Documentation**: ${config.documentationURL}`);
185
162
  }
@@ -211,6 +188,7 @@ const buildFieldDocs = (config) => {
211
188
  placeholder,
212
189
  defaultValue,
213
190
  apiIDPath,
191
+ tabName: config.tabName,
214
192
  documentationURL: getFieldDocumentationURL({ model: config.model })
215
193
  });
216
194
  }
@@ -226,7 +204,7 @@ const buildSharedSliceInterfaceName = (config) => {
226
204
  return pascalCase(`${config.id} Slice`);
227
205
  };
228
206
 
229
- const addInterfacePropertyFromField = (config) => {
207
+ const addInterfacePropertyForField = (config) => {
230
208
  switch (config.model.type) {
231
209
  case "UID": {
232
210
  break;
@@ -238,7 +216,8 @@ const addInterfacePropertyFromField = (config) => {
238
216
  docs: buildFieldDocs({
239
217
  id: config.id,
240
218
  model: config.model,
241
- path: config.path
219
+ path: config.path,
220
+ tabName: config.tabName
242
221
  })
243
222
  });
244
223
  break;
@@ -250,7 +229,8 @@ const addInterfacePropertyFromField = (config) => {
250
229
  docs: buildFieldDocs({
251
230
  id: config.id,
252
231
  model: config.model,
253
- path: config.path
232
+ path: config.path,
233
+ tabName: config.tabName
254
234
  })
255
235
  });
256
236
  break;
@@ -262,7 +242,8 @@ const addInterfacePropertyFromField = (config) => {
262
242
  docs: buildFieldDocs({
263
243
  id: config.id,
264
244
  model: config.model,
265
- path: config.path
245
+ path: config.path,
246
+ tabName: config.tabName
266
247
  })
267
248
  });
268
249
  break;
@@ -281,7 +262,8 @@ const addInterfacePropertyFromField = (config) => {
281
262
  docs: buildFieldDocs({
282
263
  id: config.id,
283
264
  model: config.model,
284
- path: config.path
265
+ path: config.path,
266
+ tabName: config.tabName
285
267
  })
286
268
  });
287
269
  break;
@@ -293,7 +275,8 @@ const addInterfacePropertyFromField = (config) => {
293
275
  docs: buildFieldDocs({
294
276
  id: config.id,
295
277
  model: config.model,
296
- path: config.path
278
+ path: config.path,
279
+ tabName: config.tabName
297
280
  })
298
281
  });
299
282
  break;
@@ -307,7 +290,8 @@ const addInterfacePropertyFromField = (config) => {
307
290
  docs: buildFieldDocs({
308
291
  id: config.id,
309
292
  model: config.model,
310
- path: config.path
293
+ path: config.path,
294
+ tabName: config.tabName
311
295
  })
312
296
  });
313
297
  } else {
@@ -317,7 +301,8 @@ const addInterfacePropertyFromField = (config) => {
317
301
  docs: buildFieldDocs({
318
302
  id: config.id,
319
303
  model: config.model,
320
- path: config.path
304
+ path: config.path,
305
+ tabName: config.tabName
321
306
  })
322
307
  });
323
308
  }
@@ -331,7 +316,8 @@ const addInterfacePropertyFromField = (config) => {
331
316
  docs: buildFieldDocs({
332
317
  id: config.id,
333
318
  model: config.model,
334
- path: config.path
319
+ path: config.path,
320
+ tabName: config.tabName
335
321
  })
336
322
  });
337
323
  break;
@@ -345,7 +331,8 @@ const addInterfacePropertyFromField = (config) => {
345
331
  docs: buildFieldDocs({
346
332
  id: config.id,
347
333
  model: config.model,
348
- path: config.path
334
+ path: config.path,
335
+ tabName: config.tabName
349
336
  })
350
337
  });
351
338
  break;
@@ -357,7 +344,8 @@ const addInterfacePropertyFromField = (config) => {
357
344
  docs: buildFieldDocs({
358
345
  id: config.id,
359
346
  model: config.model,
360
- path: config.path
347
+ path: config.path,
348
+ tabName: config.tabName
361
349
  })
362
350
  });
363
351
  break;
@@ -369,7 +357,8 @@ const addInterfacePropertyFromField = (config) => {
369
357
  docs: buildFieldDocs({
370
358
  id: config.id,
371
359
  model: config.model,
372
- path: config.path
360
+ path: config.path,
361
+ tabName: config.tabName
373
362
  })
374
363
  });
375
364
  }
@@ -383,7 +372,8 @@ const addInterfacePropertyFromField = (config) => {
383
372
  docs: buildFieldDocs({
384
373
  id: config.id,
385
374
  model: config.model,
386
- path: config.path
375
+ path: config.path,
376
+ tabName: config.tabName
387
377
  })
388
378
  });
389
379
  break;
@@ -397,7 +387,8 @@ const addInterfacePropertyFromField = (config) => {
397
387
  docs: buildFieldDocs({
398
388
  id: config.id,
399
389
  model: config.model,
400
- path: config.path
390
+ path: config.path,
391
+ tabName: config.tabName
401
392
  })
402
393
  });
403
394
  } else {
@@ -407,7 +398,8 @@ const addInterfacePropertyFromField = (config) => {
407
398
  docs: buildFieldDocs({
408
399
  id: config.id,
409
400
  model: config.model,
410
- path: config.path
401
+ path: config.path,
402
+ tabName: config.tabName
411
403
  })
412
404
  });
413
405
  }
@@ -423,7 +415,8 @@ const addInterfacePropertyFromField = (config) => {
423
415
  docs: buildFieldDocs({
424
416
  id: config.id,
425
417
  model: config.model,
426
- path: config.path
418
+ path: config.path,
419
+ tabName: config.tabName
427
420
  })
428
421
  });
429
422
  } else {
@@ -433,7 +426,8 @@ const addInterfacePropertyFromField = (config) => {
433
426
  docs: buildFieldDocs({
434
427
  id: config.id,
435
428
  model: config.model,
436
- path: config.path
429
+ path: config.path,
430
+ tabName: config.tabName
437
431
  })
438
432
  });
439
433
  }
@@ -446,7 +440,8 @@ const addInterfacePropertyFromField = (config) => {
446
440
  docs: buildFieldDocs({
447
441
  id: config.id,
448
442
  model: config.model,
449
- path: config.path
443
+ path: config.path,
444
+ tabName: config.tabName
450
445
  })
451
446
  });
452
447
  break;
@@ -458,7 +453,8 @@ const addInterfacePropertyFromField = (config) => {
458
453
  docs: buildFieldDocs({
459
454
  id: config.id,
460
455
  model: config.model,
461
- path: config.path
456
+ path: config.path,
457
+ tabName: config.tabName
462
458
  })
463
459
  });
464
460
  break;
@@ -481,7 +477,8 @@ const addInterfacePropertyFromField = (config) => {
481
477
  writer.writeLine(`Item in ${humanReadablePath}`);
482
478
  }
483
479
  }
484
- ]
480
+ ],
481
+ isExported: true
485
482
  });
486
483
  addInterfacePropertiesForFields({
487
484
  interface: itemInterface,
@@ -502,7 +499,8 @@ const addInterfacePropertyFromField = (config) => {
502
499
  docs: buildFieldDocs({
503
500
  id: config.id,
504
501
  model: config.model,
505
- path: config.path
502
+ path: config.path,
503
+ tabName: config.tabName
506
504
  })
507
505
  });
508
506
  break;
@@ -592,7 +590,8 @@ const addInterfacePropertyFromField = (config) => {
592
590
  writer.writeLine(`Item in ${humanReadablePath}`);
593
591
  }
594
592
  }
595
- ]
593
+ ],
594
+ isExported: true
596
595
  });
597
596
  addInterfacePropertiesForFields({
598
597
  interface: itemInterface,
@@ -618,7 +617,8 @@ const addInterfacePropertyFromField = (config) => {
618
617
  }
619
618
  const sliceType = config.sourceFile.addTypeAlias({
620
619
  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"}>`
620
+ type: `prismicT.Slice<"${choiceId}", ${primaryInterface ? `Simplify<${primaryInterface.getName()}>` : "Record<string, never>"}, ${itemInterface ? `Simplify<${itemInterface.getName()}>` : "never"}>`,
621
+ isExported: true
622
622
  });
623
623
  choiceInterfaceNames.push(sliceType.getName());
624
624
  }
@@ -649,7 +649,8 @@ const addInterfacePropertyFromField = (config) => {
649
649
  docs: buildFieldDocs({
650
650
  id: config.id,
651
651
  model: config.model,
652
- path: config.path
652
+ path: config.path,
653
+ tabName: config.tabName
653
654
  })
654
655
  });
655
656
  break;
@@ -661,7 +662,8 @@ const addInterfacePropertyFromField = (config) => {
661
662
  docs: buildFieldDocs({
662
663
  id: config.id,
663
664
  model: config.model,
664
- path: config.path
665
+ path: config.path,
666
+ tabName: config.tabName
665
667
  })
666
668
  });
667
669
  }
@@ -669,10 +671,11 @@ const addInterfacePropertyFromField = (config) => {
669
671
  };
670
672
  const addInterfacePropertiesForFields = (config) => {
671
673
  for (const name in config.fields) {
672
- addInterfacePropertyFromField({
674
+ addInterfacePropertyForField({
673
675
  ...config,
674
- id: name,
675
- model: config.fields[name]
676
+ id: name.includes("-") ? `"${name}"` : name,
677
+ model: config.fields[name],
678
+ tabName: config.tabName
676
679
  });
677
680
  }
678
681
  };
@@ -699,18 +702,22 @@ const addTypeAliasForCustomType = ({
699
702
  }
700
703
  ]
701
704
  });
702
- addInterfacePropertiesForFields({
703
- fields,
704
- interface: dataInterface,
705
- sourceFile,
706
- path: [
707
- {
708
- id: model.id,
709
- model
710
- }
711
- ],
712
- fieldConfigs
713
- });
705
+ for (const tabName in model.json) {
706
+ const { uid: _uidField, ...tabFields } = model.json[tabName];
707
+ addInterfacePropertiesForFields({
708
+ fields: tabFields,
709
+ interface: dataInterface,
710
+ sourceFile,
711
+ path: [
712
+ {
713
+ id: model.id,
714
+ model
715
+ }
716
+ ],
717
+ fieldConfigs,
718
+ tabName
719
+ });
720
+ }
714
721
  } else {
715
722
  dataInterface = sourceFile.addTypeAlias({
716
723
  name: pascalCase(`${model.id} Document Data`),
@@ -819,7 +826,8 @@ const addTypeAliasForSharedSlice = (config) => {
819
826
  writer.writeLine(`Item in ${humanReadablePath}`);
820
827
  }
821
828
  }
822
- ]
829
+ ],
830
+ isExported: true
823
831
  });
824
832
  addInterfacePropertiesForFields({
825
833
  interface: itemInterface,
@@ -853,7 +861,8 @@ const addTypeAliasForSharedSlice = (config) => {
853
861
  writer.writeLine(`- **Documentation**: ${SHARED_SLICES_DOCUMENTATION_URL}`);
854
862
  }
855
863
  }
856
- ]
864
+ ],
865
+ isExported: true
857
866
  });
858
867
  variationTypeNames.push(variationType.getName());
859
868
  }
@@ -931,6 +940,13 @@ const generateTypes = (config = {}) => {
931
940
  fieldConfigs: config.fieldConfigs || {}
932
941
  });
933
942
  }
943
+ if (config.customTypeModels.length > 0) {
944
+ sourceFile.addTypeAlias({
945
+ name: "AllDocumentTypes",
946
+ type: config.customTypeModels.map((customTypeModel) => pascalCase(`${customTypeModel.id} Document`)).join(" | "),
947
+ isExported: true
948
+ });
949
+ }
934
950
  }
935
951
  if (config.sharedSliceModels) {
936
952
  for (const model of config.sharedSliceModels) {
@@ -941,20 +957,60 @@ const generateTypes = (config = {}) => {
941
957
  });
942
958
  }
943
959
  }
960
+ if (config.clientIntegration?.includeCreateClientInterface) {
961
+ sourceFile.addImportDeclaration({
962
+ moduleSpecifier: "@prismicio/client",
963
+ namespaceImport: "prismic",
964
+ isTypeOnly: true
965
+ });
966
+ const clientModuleDeclaration = sourceFile.addModule({
967
+ name: '"@prismicio/client"',
968
+ hasDeclareKeyword: true,
969
+ declarationKind: ModuleDeclarationKind.Module
970
+ });
971
+ clientModuleDeclaration.addInterface({
972
+ name: "CreateClient",
973
+ callSignatures: [
974
+ {
975
+ parameters: [
976
+ {
977
+ name: "repositoryNameOrEndpoint",
978
+ type: "string"
979
+ },
980
+ {
981
+ name: "options",
982
+ type: "prismic.ClientConfig",
983
+ hasQuestionToken: true
984
+ }
985
+ ],
986
+ returnType: (config.customTypeModels?.length || 0) > 0 ? "prismic.Client<AllDocumentTypes>" : "prismic.Client"
987
+ }
988
+ ]
989
+ });
990
+ }
944
991
  return getSourceFileText(sourceFile);
945
992
  };
946
993
 
947
994
  const configSchema = Joi.object({
948
- repositoryName: Joi.string(),
995
+ repositoryName: Joi.string().when("locales.fetchFromRepository", {
996
+ is: true,
997
+ then: Joi.required()
998
+ }).when("models.fetchFromRepository", {
999
+ is: true,
1000
+ then: Joi.required()
1001
+ }),
949
1002
  accessToken: Joi.string(),
950
1003
  customTypesAPIToken: Joi.string(),
951
1004
  output: Joi.string(),
1005
+ clientIntegration: {
1006
+ includeCreateClientInterface: Joi.boolean
1007
+ },
952
1008
  locales: Joi.alternatives(Joi.array().items(Joi.string().required()), Joi.object({
953
1009
  ids: Joi.array().items(Joi.string().required()),
954
1010
  fetchFromRepository: Joi.boolean()
955
1011
  })),
956
- models: Joi.alternatives(Joi.array().items(Joi.string().required()), Joi.object({
957
- files: Joi.array().items(Joi.string().required()),
1012
+ models: Joi.alternatives(Joi.array().items(Joi.string()), Joi.object({
1013
+ files: Joi.array().items(Joi.string()),
958
1014
  fetchFromRepository: Joi.boolean()
959
1015
  })),
960
1016
  fields: Joi.object({
@@ -965,23 +1021,6 @@ const configSchema = Joi.object({
965
1021
  catalogTypes: Joi.object().pattern(Joi.string(), Joi.string().required())
966
1022
  })
967
1023
  })
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
1024
  });
986
1025
 
987
1026
  const loadLocaleIDs = async (config) => {
@@ -1023,7 +1062,7 @@ const loadModels = async (config) => {
1023
1062
  });
1024
1063
  if (config.fetchFromRepository) {
1025
1064
  const [remoteCustomTypeModels, remoteSharedSliceModels] = await Promise.all([
1026
- customTypesClient.getAll(),
1065
+ customTypesClient.getAllCustomTypes(),
1027
1066
  customTypesClient.getAllSharedSlices()
1028
1067
  ]);
1029
1068
  for (const customTypeModel of remoteCustomTypeModels) {
@@ -1045,8 +1084,12 @@ const loadModels = async (config) => {
1045
1084
  }
1046
1085
  }
1047
1086
  return {
1048
- customTypeModels: Object.values(customTypeModels),
1049
- sharedSliceModels: Object.values(sharedSliceModels)
1087
+ customTypeModels: Object.values(customTypeModels).sort((a, b) => {
1088
+ return a.id.localeCompare(b.id);
1089
+ }),
1090
+ sharedSliceModels: Object.values(sharedSliceModels).sort((a, b) => {
1091
+ return a.id.localeCompare(b.id);
1092
+ })
1050
1093
  };
1051
1094
  };
1052
1095
 
@@ -1144,13 +1187,22 @@ Created prismic-ts-codegen config file: ${configPath}`);
1144
1187
  accessToken: config.accessToken,
1145
1188
  fetchFromRepository: config.locales && "fetchFromRepository" in config.locales && config.locales.fetchFromRepository
1146
1189
  });
1190
+ const hasCustomTypeModels = customTypeModels.length > 0;
1147
1191
  const types = generateTypes({
1148
1192
  customTypeModels,
1149
1193
  sharedSliceModels,
1150
- localeIDs
1194
+ localeIDs,
1195
+ clientIntegration: {
1196
+ includeCreateClientInterface: hasCustomTypeModels ? config.clientIntegration?.includeCreateClientInterface ?? true : false
1197
+ }
1151
1198
  });
1199
+ if (config.clientIntegration?.includeCreateClientInterface && !hasCustomTypeModels) {
1200
+ console.info("[INFO]: prismic-ts-codegen was configured to automatically integrate with `@prismicio/client`, but the integration was generated because no Custom Type models were found. Automatic integration requires at least one Custom Type model.");
1201
+ }
1152
1202
  if (config.output) {
1153
1203
  writeFileSync(resolve(config.output), types);
1204
+ console.info(`
1205
+ Generated types in: ${config.output}`);
1154
1206
  } else {
1155
1207
  process.stdout.write(types + "\n");
1156
1208
  }