@majordigital/create-acorn 1.5.2 → 1.5.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.
@@ -335,11 +335,21 @@ async function setupStoryblok(projectName) {
335
335
  console.log('');
336
336
  }
337
337
 
338
- async function setupDato(projectName) {
338
+ async function setupDato() {
339
339
  console.log('Setting up DatoCMS...');
340
340
  console.log('');
341
+ console.log('NOTE: DatoCMS does not support creating projects via CLI or API.');
342
+ console.log('You must create your DatoCMS project in the dashboard before continuing:');
343
+ console.log(' https://www.datocms.com/dashboard');
344
+ console.log('');
345
+ console.log('Once your project is ready, provide the Full-access API token below');
346
+ console.log('and we will automatically scaffold all models (Page, Hero, Button, Layout, etc.).');
347
+ console.log('');
348
+ console.log('Token location: Settings > API Tokens > Full-access API token');
349
+ console.log('Make sure "Access the Content Management API" is enabled and the role is Admin.');
350
+ console.log('');
341
351
 
342
- const datoProjectName = await ask('Enter your DatoCMS project name', projectName);
352
+ const apiToken = await ask('Paste your Full-access API token (or press Enter to skip)');
343
353
  console.log('');
344
354
 
345
355
  // Install DatoCMS dependencies
@@ -356,33 +366,6 @@ async function setupDato(projectName) {
356
366
  ]);
357
367
  console.log('');
358
368
 
359
- // --- DatoCMS Project Connection & Model Scaffolding ---
360
- const hasExisting = await ask('Do you already have a DatoCMS project? (y/n)', 'n');
361
- const isExisting = hasExisting.toLowerCase() === 'y' || hasExisting.toLowerCase() === 'yes';
362
-
363
- if (!isExisting) {
364
- console.log('');
365
- console.log('NOTE: DatoCMS does not support creating projects via CLI or API.');
366
- console.log('You need to create the project manually first:');
367
- console.log('');
368
- console.log(' 1. Go to https://www.datocms.com/dashboard');
369
- console.log(' 2. Click "New Project" and create your project');
370
- console.log(' 3. Go to Settings > API Tokens > Full-access API token');
371
- console.log(' 4. Copy the token and paste it below');
372
- console.log('');
373
- console.log('If you provide the token, we will automatically scaffold all models');
374
- console.log('(Page, Hero, Button, Layout, etc.) in your DatoCMS project.');
375
- console.log('');
376
- } else {
377
- console.log('');
378
- console.log('Great! We can automatically scaffold all models in your project.');
379
- console.log('Grab your Full-access API token from:');
380
- console.log(' Settings > API Tokens > Full-access API token');
381
- console.log('');
382
- }
383
-
384
- const apiToken = await ask('Paste your Full-access API token (or press Enter to skip)');
385
-
386
369
  if (apiToken) {
387
370
  console.log('');
388
371
  console.log('Scaffolding DatoCMS models...');
@@ -571,22 +554,41 @@ async function scaffoldDatoModels(apiToken) {
571
554
  const { buildClient } = await import('@datocms/cma-client-node');
572
555
  const client = buildClient({ apiToken });
573
556
 
557
+ // Fetch existing models so we can skip duplicates (handles re-runs)
558
+ const existingTypes = await client.itemTypes.list();
559
+ const existingKeys = new Set(existingTypes.map(t => t.api_key));
560
+
561
+ async function findOrCreateItemType(config) {
562
+ if (existingKeys.has(config.api_key)) {
563
+ console.log(` Skipping ${config.name} (already exists)...`);
564
+ return existingTypes.find(t => t.api_key === config.api_key);
565
+ }
566
+ return client.itemTypes.create(config);
567
+ }
568
+
569
+ async function findOrCreateField(modelId, config) {
570
+ const existingFields = await client.fields.list(modelId);
571
+ const existing = existingFields.find(f => f.api_key === config.api_key);
572
+ if (existing) return existing;
573
+ return client.fields.create(modelId, config);
574
+ }
575
+
574
576
  // --- Block models (must be created first so we can reference them) ---
575
577
 
576
578
  // Button block
577
579
  console.log(' Creating Button block...');
578
- const buttonBlock = await client.itemTypes.create({
580
+ const buttonBlock = await findOrCreateItemType({
579
581
  name: 'Button',
580
582
  api_key: 'button',
581
583
  modular_block: true,
582
584
  });
583
- await client.fields.create(buttonBlock.id, {
585
+ await findOrCreateField(buttonBlock.id, {
584
586
  label: 'Text',
585
587
  field_type: 'string',
586
588
  api_key: 'text',
587
589
  validators: { required: {} },
588
590
  });
589
- await client.fields.create(buttonBlock.id, {
591
+ await findOrCreateField(buttonBlock.id, {
590
592
  label: 'Variant',
591
593
  field_type: 'string',
592
594
  api_key: 'variant',
@@ -601,7 +603,7 @@ async function scaffoldDatoModels(apiToken) {
601
603
  ]},
602
604
  },
603
605
  });
604
- await client.fields.create(buttonBlock.id, {
606
+ await findOrCreateField(buttonBlock.id, {
605
607
  label: 'Link',
606
608
  field_type: 'json',
607
609
  api_key: 'link',
@@ -609,23 +611,23 @@ async function scaffoldDatoModels(apiToken) {
609
611
 
610
612
  // Hero block
611
613
  console.log(' Creating Hero block...');
612
- const heroBlock = await client.itemTypes.create({
614
+ const heroBlock = await findOrCreateItemType({
613
615
  name: 'Hero',
614
616
  api_key: 'hero',
615
617
  modular_block: true,
616
618
  });
617
- await client.fields.create(heroBlock.id, {
619
+ await findOrCreateField(heroBlock.id, {
618
620
  label: 'Heading',
619
621
  field_type: 'string',
620
622
  api_key: 'heading',
621
623
  validators: { required: {} },
622
624
  });
623
- await client.fields.create(heroBlock.id, {
625
+ await findOrCreateField(heroBlock.id, {
624
626
  label: 'Eyebrow',
625
627
  field_type: 'string',
626
628
  api_key: 'eyebrow',
627
629
  });
628
- await client.fields.create(heroBlock.id, {
630
+ await findOrCreateField(heroBlock.id, {
629
631
  label: 'Content',
630
632
  field_type: 'text',
631
633
  api_key: 'content',
@@ -635,7 +637,7 @@ async function scaffoldDatoModels(apiToken) {
635
637
  parameters: {},
636
638
  },
637
639
  });
638
- await client.fields.create(heroBlock.id, {
640
+ await findOrCreateField(heroBlock.id, {
639
641
  label: 'Layout',
640
642
  field_type: 'string',
641
643
  api_key: 'layout',
@@ -650,7 +652,7 @@ async function scaffoldDatoModels(apiToken) {
650
652
  ]},
651
653
  },
652
654
  });
653
- await client.fields.create(heroBlock.id, {
655
+ await findOrCreateField(heroBlock.id, {
654
656
  label: 'Theme',
655
657
  field_type: 'string',
656
658
  api_key: 'theme',
@@ -664,12 +666,12 @@ async function scaffoldDatoModels(apiToken) {
664
666
  ]},
665
667
  },
666
668
  });
667
- await client.fields.create(heroBlock.id, {
669
+ await findOrCreateField(heroBlock.id, {
668
670
  label: 'Image',
669
671
  field_type: 'file',
670
672
  api_key: 'image',
671
673
  });
672
- await client.fields.create(heroBlock.id, {
674
+ await findOrCreateField(heroBlock.id, {
673
675
  label: 'Actions',
674
676
  field_type: 'rich_text',
675
677
  api_key: 'actions',
@@ -680,12 +682,12 @@ async function scaffoldDatoModels(apiToken) {
680
682
 
681
683
  // MenuItem block
682
684
  console.log(' Creating MenuItem block...');
683
- const menuItemBlock = await client.itemTypes.create({
685
+ const menuItemBlock = await findOrCreateItemType({
684
686
  name: 'Menu Item',
685
687
  api_key: 'menu_item',
686
688
  modular_block: true,
687
689
  });
688
- await client.fields.create(menuItemBlock.id, {
690
+ await findOrCreateField(menuItemBlock.id, {
689
691
  label: 'Heading',
690
692
  field_type: 'string',
691
693
  api_key: 'heading',
@@ -696,40 +698,40 @@ async function scaffoldDatoModels(apiToken) {
696
698
 
697
699
  // Page model
698
700
  console.log(' Creating Page model...');
699
- const pageModel = await client.itemTypes.create({
701
+ const pageModel = await findOrCreateItemType({
700
702
  name: 'Page',
701
703
  api_key: 'page',
702
704
  draft_mode_active: true,
703
705
  });
704
- await client.fields.create(pageModel.id, {
706
+ const pageHeadingField = await findOrCreateField(pageModel.id, {
705
707
  label: 'Heading',
706
708
  field_type: 'string',
707
709
  api_key: 'heading',
708
710
  validators: { required: {} },
709
711
  });
710
- await client.fields.create(pageModel.id, {
712
+ await findOrCreateField(pageModel.id, {
711
713
  label: 'Description',
712
714
  field_type: 'text',
713
715
  api_key: 'description',
714
716
  });
715
- await client.fields.create(pageModel.id, {
717
+ await findOrCreateField(pageModel.id, {
716
718
  label: 'Slug',
717
719
  field_type: 'slug',
718
720
  api_key: 'slug',
719
721
  validators: {
720
722
  required: {},
721
- slug_title_field: { title_field_id: (await client.fields.list(pageModel.id)).find(f => f.api_key === 'heading').id },
723
+ slug_title_field: { title_field_id: pageHeadingField.id },
722
724
  },
723
725
  });
724
- await client.fields.create(pageModel.id, {
726
+ await findOrCreateField(pageModel.id, {
725
727
  label: 'Parent Page',
726
728
  field_type: 'link',
727
729
  api_key: 'parent_page',
728
730
  validators: {
729
- item_item_type: { on_publish_with_unpublished_references_strategy: 'fail', on_reference_unpublish_strategy: 'fail', on_reference_delete_strategy: 'set_null', item_types: [pageModel.id] },
731
+ item_item_type: { item_types: [pageModel.id] },
730
732
  },
731
733
  });
732
- await client.fields.create(pageModel.id, {
734
+ await findOrCreateField(pageModel.id, {
733
735
  label: 'Body',
734
736
  field_type: 'rich_text',
735
737
  api_key: 'body',
@@ -739,61 +741,61 @@ async function scaffoldDatoModels(apiToken) {
739
741
  });
740
742
 
741
743
  // Add target link to MenuItem (now that Page exists)
742
- await client.fields.create(menuItemBlock.id, {
744
+ await findOrCreateField(menuItemBlock.id, {
743
745
  label: 'Target',
744
746
  field_type: 'link',
745
747
  api_key: 'target',
746
748
  validators: {
747
- item_item_type: { on_publish_with_unpublished_references_strategy: 'fail', on_reference_unpublish_strategy: 'fail', on_reference_delete_strategy: 'set_null', item_types: [pageModel.id] },
749
+ item_item_type: { item_types: [pageModel.id] },
748
750
  },
749
751
  });
750
752
 
751
753
  // Case Study model
752
754
  console.log(' Creating Case Study model...');
753
- const caseStudyModel = await client.itemTypes.create({
755
+ const caseStudyModel = await findOrCreateItemType({
754
756
  name: 'Case Study',
755
757
  api_key: 'case_study',
756
758
  draft_mode_active: true,
757
759
  });
758
- await client.fields.create(caseStudyModel.id, {
760
+ const caseStudyTitleField = await findOrCreateField(caseStudyModel.id, {
759
761
  label: 'Title',
760
762
  field_type: 'string',
761
763
  api_key: 'title',
762
764
  validators: { required: {} },
763
765
  });
764
- await client.fields.create(caseStudyModel.id, {
766
+ await findOrCreateField(caseStudyModel.id, {
765
767
  label: 'Slug',
766
768
  field_type: 'slug',
767
769
  api_key: 'slug',
768
770
  validators: {
769
771
  required: {},
770
- slug_title_field: { title_field_id: (await client.fields.list(caseStudyModel.id)).find(f => f.api_key === 'title').id },
772
+ slug_title_field: { title_field_id: caseStudyTitleField.id },
771
773
  },
772
774
  });
773
775
 
774
776
  // Legal Page model
775
777
  console.log(' Creating Legal Page model...');
776
- const legalPageModel = await client.itemTypes.create({
778
+ const legalPageModel = await findOrCreateItemType({
777
779
  name: 'Legal Page',
778
780
  api_key: 'legal_page',
779
781
  draft_mode_active: true,
780
782
  });
781
- await client.fields.create(legalPageModel.id, {
783
+ const legalTitleField = await findOrCreateField(legalPageModel.id, {
782
784
  label: 'Title',
783
785
  field_type: 'string',
784
786
  api_key: 'title',
785
787
  validators: { required: {} },
786
788
  });
787
- await client.fields.create(legalPageModel.id, {
789
+ await findOrCreateField(legalPageModel.id, {
788
790
  label: 'Slug',
789
791
  field_type: 'slug',
790
792
  api_key: 'slug',
791
793
  validators: {
792
794
  required: {},
793
- slug_title_field: { title_field_id: (await client.fields.list(legalPageModel.id)).find(f => f.api_key === 'title').id },
795
+ slug_title_field: { title_field_id: legalTitleField.id },
794
796
  },
795
797
  });
796
- await client.fields.create(legalPageModel.id, {
798
+ await findOrCreateField(legalPageModel.id, {
797
799
  label: 'Download',
798
800
  field_type: 'file',
799
801
  api_key: 'download',
@@ -801,35 +803,35 @@ async function scaffoldDatoModels(apiToken) {
801
803
 
802
804
  // Post model
803
805
  console.log(' Creating Post model...');
804
- const postModel = await client.itemTypes.create({
806
+ const postModel = await findOrCreateItemType({
805
807
  name: 'Post',
806
808
  api_key: 'post',
807
809
  draft_mode_active: true,
808
810
  });
809
- await client.fields.create(postModel.id, {
811
+ const postTitleField = await findOrCreateField(postModel.id, {
810
812
  label: 'Title',
811
813
  field_type: 'string',
812
814
  api_key: 'title',
813
815
  validators: { required: {} },
814
816
  });
815
- await client.fields.create(postModel.id, {
817
+ await findOrCreateField(postModel.id, {
816
818
  label: 'Slug',
817
819
  field_type: 'slug',
818
820
  api_key: 'slug',
819
821
  validators: {
820
822
  required: {},
821
- slug_title_field: { title_field_id: (await client.fields.list(postModel.id)).find(f => f.api_key === 'title').id },
823
+ slug_title_field: { title_field_id: postTitleField.id },
822
824
  },
823
825
  });
824
826
 
825
827
  // Case Studies Listing (singleton)
826
828
  console.log(' Creating Case Studies Listing singleton...');
827
- const caseStudiesListing = await client.itemTypes.create({
829
+ const caseStudiesListing = await findOrCreateItemType({
828
830
  name: 'Case Studies Listing',
829
831
  api_key: 'case_studies_listing',
830
832
  singleton: true,
831
833
  });
832
- await client.fields.create(caseStudiesListing.id, {
834
+ await findOrCreateField(caseStudiesListing.id, {
833
835
  label: 'Slug',
834
836
  field_type: 'string',
835
837
  api_key: 'slug',
@@ -838,12 +840,12 @@ async function scaffoldDatoModels(apiToken) {
838
840
 
839
841
  // Post Listing (singleton)
840
842
  console.log(' Creating Post Listing singleton...');
841
- const postListing = await client.itemTypes.create({
843
+ const postListing = await findOrCreateItemType({
842
844
  name: 'Post Listing',
843
845
  api_key: 'post_listing',
844
846
  singleton: true,
845
847
  });
846
- await client.fields.create(postListing.id, {
848
+ await findOrCreateField(postListing.id, {
847
849
  label: 'Slug',
848
850
  field_type: 'string',
849
851
  api_key: 'slug',
@@ -852,12 +854,12 @@ async function scaffoldDatoModels(apiToken) {
852
854
 
853
855
  // Layout (singleton)
854
856
  console.log(' Creating Layout singleton...');
855
- const layoutModel = await client.itemTypes.create({
857
+ const layoutModel = await findOrCreateItemType({
856
858
  name: 'Layout',
857
859
  api_key: 'layout',
858
860
  singleton: true,
859
861
  });
860
- await client.fields.create(layoutModel.id, {
862
+ await findOrCreateField(layoutModel.id, {
861
863
  label: 'Navigation',
862
864
  field_type: 'rich_text',
863
865
  api_key: 'navigation',
@@ -865,7 +867,7 @@ async function scaffoldDatoModels(apiToken) {
865
867
  rich_text_blocks: { item_types: [menuItemBlock.id] },
866
868
  },
867
869
  });
868
- await client.fields.create(layoutModel.id, {
870
+ await findOrCreateField(layoutModel.id, {
869
871
  label: 'Footer Navigation',
870
872
  field_type: 'rich_text',
871
873
  api_key: 'footer_navigation',
@@ -873,7 +875,7 @@ async function scaffoldDatoModels(apiToken) {
873
875
  rich_text_blocks: { item_types: [menuItemBlock.id] },
874
876
  },
875
877
  });
876
- await client.fields.create(layoutModel.id, {
878
+ await findOrCreateField(layoutModel.id, {
877
879
  label: 'Copyright',
878
880
  field_type: 'string',
879
881
  api_key: 'copyright',
@@ -1125,7 +1127,7 @@ NEXT_PUBLIC_SITE_URL=
1125
1127
  } else if (selection.key === 'storyblok') {
1126
1128
  await setupStoryblok(projectDir);
1127
1129
  } else if (selection.key === 'dato') {
1128
- await setupDato(projectDir);
1130
+ await setupDato();
1129
1131
  }
1130
1132
  }
1131
1133
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@majordigital/create-acorn",
3
- "version": "1.5.2",
3
+ "version": "1.5.5",
4
4
  "description": "Interactive scaffold for Acorn with Storyblok/Prismic/DatoCMS, TypeScript, and Tailwind.",
5
5
  "bin": {
6
6
  "create-acorn": "bin/create-acorn.mjs",