@majordigital/create-acorn 1.4.1 → 1.5.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.
Files changed (2) hide show
  1. package/bin/create-acorn.mjs +427 -15
  2. package/package.json +1 -1
@@ -345,7 +345,7 @@ async function setupDato(projectName) {
345
345
  // Install DatoCMS dependencies
346
346
  console.log('Installing DatoCMS dependencies...');
347
347
  await runCommand('npm', ['install', '--legacy-peer-deps',
348
- '@datocms/cda-client', 'react-datocms',
348
+ '@datocms/cda-client', '@datocms/cma-client-node', 'react-datocms',
349
349
  'datocms-structured-text-to-plain-text', 'datocms-structured-text-utils',
350
350
  'graphql', 'graphql-request', 'dotenv'
351
351
  ]);
@@ -356,6 +356,85 @@ async function setupDato(projectName) {
356
356
  ]);
357
357
  console.log('');
358
358
 
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
+ if (apiToken) {
387
+ console.log('');
388
+ console.log('Scaffolding DatoCMS models...');
389
+ console.log('');
390
+
391
+ try {
392
+ await scaffoldDatoModels(apiToken);
393
+ console.log('');
394
+ console.log('DatoCMS models created successfully!');
395
+ console.log('');
396
+
397
+ // Fetch the read-only CDA token for .env.local
398
+ let cdaToken = '';
399
+ try {
400
+ const { buildClient } = await import('@datocms/cma-client-node');
401
+ const client = buildClient({ apiToken });
402
+ const tokens = await client.accessTokens.list();
403
+ const readOnly = tokens.find(t => t.name === 'Read-only API token');
404
+ if (readOnly?.token) {
405
+ cdaToken = readOnly.token;
406
+ }
407
+ } catch {
408
+ // Could not fetch CDA token — user will add manually
409
+ }
410
+
411
+ // Write .env.local with tokens
412
+ const envLocalPath = join(process.cwd(), '.env.local');
413
+ writeFileSync(envLocalPath, `NEXT_DATOCMS_API_TOKEN=${cdaToken}
414
+ DATO_API_TOKEN=${apiToken}
415
+ NEXT_DATOCMS_ENVIRONMENT=draft
416
+ `);
417
+ if (cdaToken) {
418
+ console.log('Created .env.local with your API tokens.');
419
+ } else {
420
+ console.log('Created .env.local — add your Read-only API token to NEXT_DATOCMS_API_TOKEN.');
421
+ console.log('You can find it in Settings > API Tokens > Read-only API token.');
422
+ }
423
+ } catch (err) {
424
+ console.log('');
425
+ console.log('Warning: Could not scaffold DatoCMS models automatically.');
426
+ console.log(` Error: ${err.message}`);
427
+ console.log('');
428
+ console.log('You can create models manually in the DatoCMS dashboard.');
429
+ console.log('The GraphQL queries expect: Page, Hero (block), Button (block),');
430
+ console.log('Layout (singleton), MenuItem (block), CaseStudy, LegalPage, Post,');
431
+ console.log('CaseStudiesListing (singleton), PostListing (singleton).');
432
+ }
433
+ } else {
434
+ console.log('');
435
+ console.log('Skipping model scaffolding — you can set up models manually later.');
436
+ }
437
+
359
438
  // Update next.config.ts — replace Prismic image patterns with DatoCMS
360
439
  const nextConfigPath = join(process.cwd(), 'next.config.ts');
361
440
  try {
@@ -455,24 +534,357 @@ NEXT_DATOCMS_ENVIRONMENT=draft
455
534
  console.log('');
456
535
  console.log('=== Next Steps ===');
457
536
  console.log('');
458
- console.log(' 1. Go to https://www.datocms.com/dashboard and create a project (or use an existing one)');
459
- console.log('');
460
- console.log(' 2. Get your API token from Settings > API Tokens');
461
- console.log('');
462
- console.log(' 3. Add your token to .env.local:');
463
- console.log(' NEXT_DATOCMS_API_TOKEN=your_api_token');
464
- console.log(' NEXT_DATOCMS_ENVIRONMENT=draft');
465
- console.log('');
466
- console.log(' 4. Create your models in the DatoCMS dashboard');
467
- console.log('');
468
- console.log(' 5. Generate TypeScript types from your schema:');
469
- console.log(' npm run generate-types');
470
- console.log('');
471
- console.log(' 6. Start your dev server:');
537
+ if (!apiToken) {
538
+ console.log(' 1. Create a DatoCMS project at https://www.datocms.com/dashboard');
539
+ console.log('');
540
+ console.log(' 2. Get your API token from Settings > API Tokens');
541
+ console.log('');
542
+ console.log(' 3. Add your token to .env.local:');
543
+ console.log(' NEXT_DATOCMS_API_TOKEN=your_read_only_token');
544
+ console.log(' NEXT_DATOCMS_ENVIRONMENT=draft');
545
+ console.log('');
546
+ console.log(' 4. Create your models in the DatoCMS dashboard');
547
+ console.log('');
548
+ console.log(' 5. Generate TypeScript types from your schema:');
549
+ console.log(' npm run generate-types');
550
+ console.log('');
551
+ } else {
552
+ console.log(' 1. Generate TypeScript types from your schema:');
553
+ console.log(' npm run generate-types');
554
+ console.log('');
555
+ console.log(' 2. Start your dev server:');
556
+ console.log(' npm run dev');
557
+ console.log('');
558
+ console.log(' 3. Open your DatoCMS dashboard to add content to your models.');
559
+ console.log('');
560
+ }
561
+ console.log(' Start your dev server:');
472
562
  console.log(' npm run dev');
473
563
  console.log('');
474
564
  }
475
565
 
566
+ /**
567
+ * Scaffold default DatoCMS models using the Management API.
568
+ * Creates models that match the GraphQL queries in the dato-api layer.
569
+ */
570
+ async function scaffoldDatoModels(apiToken) {
571
+ const { buildClient } = await import('@datocms/cma-client-node');
572
+ const client = buildClient({ apiToken });
573
+
574
+ // --- Block models (must be created first so we can reference them) ---
575
+
576
+ // Button block
577
+ console.log(' Creating Button block...');
578
+ const buttonBlock = await client.itemTypes.create({
579
+ name: 'Button',
580
+ api_key: 'button',
581
+ modular_block: true,
582
+ });
583
+ await client.fields.create(buttonBlock.id, {
584
+ label: 'Text',
585
+ field_type: 'string',
586
+ api_key: 'text',
587
+ validators: { required: {} },
588
+ });
589
+ await client.fields.create(buttonBlock.id, {
590
+ label: 'Variant',
591
+ field_type: 'string',
592
+ api_key: 'variant',
593
+ validators: { enum: { values: ['primary', 'secondary', 'outline'] } },
594
+ appearance: {
595
+ addons: [],
596
+ editor: 'string_select',
597
+ parameters: { options: [
598
+ { hint: '', label: 'Primary', value: 'primary' },
599
+ { hint: '', label: 'Secondary', value: 'secondary' },
600
+ { hint: '', label: 'Outline', value: 'outline' },
601
+ ]},
602
+ },
603
+ });
604
+ await client.fields.create(buttonBlock.id, {
605
+ label: 'Link',
606
+ field_type: 'json',
607
+ api_key: 'link',
608
+ });
609
+
610
+ // Hero block
611
+ console.log(' Creating Hero block...');
612
+ const heroBlock = await client.itemTypes.create({
613
+ name: 'Hero',
614
+ api_key: 'hero',
615
+ modular_block: true,
616
+ });
617
+ await client.fields.create(heroBlock.id, {
618
+ label: 'Heading',
619
+ field_type: 'string',
620
+ api_key: 'heading',
621
+ validators: { required: {} },
622
+ });
623
+ await client.fields.create(heroBlock.id, {
624
+ label: 'Eyebrow',
625
+ field_type: 'string',
626
+ api_key: 'eyebrow',
627
+ });
628
+ await client.fields.create(heroBlock.id, {
629
+ label: 'Content',
630
+ field_type: 'text',
631
+ api_key: 'content',
632
+ appearance: {
633
+ addons: [],
634
+ editor: 'textarea',
635
+ parameters: {},
636
+ },
637
+ });
638
+ await client.fields.create(heroBlock.id, {
639
+ label: 'Layout',
640
+ field_type: 'string',
641
+ api_key: 'layout',
642
+ validators: { enum: { values: ['default', 'centered', 'split'] } },
643
+ appearance: {
644
+ addons: [],
645
+ editor: 'string_select',
646
+ parameters: { options: [
647
+ { hint: '', label: 'Default', value: 'default' },
648
+ { hint: '', label: 'Centered', value: 'centered' },
649
+ { hint: '', label: 'Split', value: 'split' },
650
+ ]},
651
+ },
652
+ });
653
+ await client.fields.create(heroBlock.id, {
654
+ label: 'Theme',
655
+ field_type: 'string',
656
+ api_key: 'theme',
657
+ validators: { enum: { values: ['light', 'dark'] } },
658
+ appearance: {
659
+ addons: [],
660
+ editor: 'string_select',
661
+ parameters: { options: [
662
+ { hint: '', label: 'Light', value: 'light' },
663
+ { hint: '', label: 'Dark', value: 'dark' },
664
+ ]},
665
+ },
666
+ });
667
+ await client.fields.create(heroBlock.id, {
668
+ label: 'Image',
669
+ field_type: 'file',
670
+ api_key: 'image',
671
+ });
672
+ await client.fields.create(heroBlock.id, {
673
+ label: 'Actions',
674
+ field_type: 'rich_text',
675
+ api_key: 'actions',
676
+ validators: {
677
+ rich_text_blocks: { item_types: [buttonBlock.id] },
678
+ },
679
+ });
680
+
681
+ // MenuItem block
682
+ console.log(' Creating MenuItem block...');
683
+ const menuItemBlock = await client.itemTypes.create({
684
+ name: 'Menu Item',
685
+ api_key: 'menu_item',
686
+ modular_block: true,
687
+ });
688
+ await client.fields.create(menuItemBlock.id, {
689
+ label: 'Heading',
690
+ field_type: 'string',
691
+ api_key: 'heading',
692
+ validators: { required: {} },
693
+ });
694
+
695
+ // --- Regular models ---
696
+
697
+ // Page model
698
+ console.log(' Creating Page model...');
699
+ const pageModel = await client.itemTypes.create({
700
+ name: 'Page',
701
+ api_key: 'page',
702
+ draft_mode_active: true,
703
+ });
704
+ await client.fields.create(pageModel.id, {
705
+ label: 'Heading',
706
+ field_type: 'string',
707
+ api_key: 'heading',
708
+ validators: { required: {} },
709
+ });
710
+ await client.fields.create(pageModel.id, {
711
+ label: 'Description',
712
+ field_type: 'text',
713
+ api_key: 'description',
714
+ });
715
+ await client.fields.create(pageModel.id, {
716
+ label: 'Slug',
717
+ field_type: 'slug',
718
+ api_key: 'slug',
719
+ validators: {
720
+ required: {},
721
+ slug_title_field: { title_field_id: (await client.fields.list(pageModel.id)).find(f => f.api_key === 'heading').id },
722
+ },
723
+ });
724
+ await client.fields.create(pageModel.id, {
725
+ label: 'Parent Page',
726
+ field_type: 'link',
727
+ api_key: 'parent_page',
728
+ 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] },
730
+ },
731
+ });
732
+ await client.fields.create(pageModel.id, {
733
+ label: 'Body',
734
+ field_type: 'rich_text',
735
+ api_key: 'body',
736
+ validators: {
737
+ rich_text_blocks: { item_types: [heroBlock.id] },
738
+ },
739
+ });
740
+
741
+ // Add target link to MenuItem (now that Page exists)
742
+ await client.fields.create(menuItemBlock.id, {
743
+ label: 'Target',
744
+ field_type: 'link',
745
+ api_key: 'target',
746
+ 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] },
748
+ },
749
+ });
750
+
751
+ // Case Study model
752
+ console.log(' Creating Case Study model...');
753
+ const caseStudyModel = await client.itemTypes.create({
754
+ name: 'Case Study',
755
+ api_key: 'case_study',
756
+ draft_mode_active: true,
757
+ });
758
+ await client.fields.create(caseStudyModel.id, {
759
+ label: 'Title',
760
+ field_type: 'string',
761
+ api_key: 'title',
762
+ validators: { required: {} },
763
+ });
764
+ await client.fields.create(caseStudyModel.id, {
765
+ label: 'Slug',
766
+ field_type: 'slug',
767
+ api_key: 'slug',
768
+ validators: {
769
+ required: {},
770
+ slug_title_field: { title_field_id: (await client.fields.list(caseStudyModel.id)).find(f => f.api_key === 'title').id },
771
+ },
772
+ });
773
+
774
+ // Legal Page model
775
+ console.log(' Creating Legal Page model...');
776
+ const legalPageModel = await client.itemTypes.create({
777
+ name: 'Legal Page',
778
+ api_key: 'legal_page',
779
+ draft_mode_active: true,
780
+ });
781
+ await client.fields.create(legalPageModel.id, {
782
+ label: 'Title',
783
+ field_type: 'string',
784
+ api_key: 'title',
785
+ validators: { required: {} },
786
+ });
787
+ await client.fields.create(legalPageModel.id, {
788
+ label: 'Slug',
789
+ field_type: 'slug',
790
+ api_key: 'slug',
791
+ validators: {
792
+ required: {},
793
+ slug_title_field: { title_field_id: (await client.fields.list(legalPageModel.id)).find(f => f.api_key === 'title').id },
794
+ },
795
+ });
796
+ await client.fields.create(legalPageModel.id, {
797
+ label: 'Download',
798
+ field_type: 'file',
799
+ api_key: 'download',
800
+ });
801
+
802
+ // Post model
803
+ console.log(' Creating Post model...');
804
+ const postModel = await client.itemTypes.create({
805
+ name: 'Post',
806
+ api_key: 'post',
807
+ draft_mode_active: true,
808
+ });
809
+ await client.fields.create(postModel.id, {
810
+ label: 'Title',
811
+ field_type: 'string',
812
+ api_key: 'title',
813
+ validators: { required: {} },
814
+ });
815
+ await client.fields.create(postModel.id, {
816
+ label: 'Slug',
817
+ field_type: 'slug',
818
+ api_key: 'slug',
819
+ validators: {
820
+ required: {},
821
+ slug_title_field: { title_field_id: (await client.fields.list(postModel.id)).find(f => f.api_key === 'title').id },
822
+ },
823
+ });
824
+
825
+ // Case Studies Listing (singleton)
826
+ console.log(' Creating Case Studies Listing singleton...');
827
+ const caseStudiesListing = await client.itemTypes.create({
828
+ name: 'Case Studies Listing',
829
+ api_key: 'case_studies_listing',
830
+ singleton: true,
831
+ });
832
+ await client.fields.create(caseStudiesListing.id, {
833
+ label: 'Slug',
834
+ field_type: 'string',
835
+ api_key: 'slug',
836
+ validators: { required: {} },
837
+ });
838
+
839
+ // Post Listing (singleton)
840
+ console.log(' Creating Post Listing singleton...');
841
+ const postListing = await client.itemTypes.create({
842
+ name: 'Post Listing',
843
+ api_key: 'post_listing',
844
+ singleton: true,
845
+ });
846
+ await client.fields.create(postListing.id, {
847
+ label: 'Slug',
848
+ field_type: 'string',
849
+ api_key: 'slug',
850
+ validators: { required: {} },
851
+ });
852
+
853
+ // Layout (singleton)
854
+ console.log(' Creating Layout singleton...');
855
+ const layoutModel = await client.itemTypes.create({
856
+ name: 'Layout',
857
+ api_key: 'layout',
858
+ singleton: true,
859
+ });
860
+ await client.fields.create(layoutModel.id, {
861
+ label: 'Navigation',
862
+ field_type: 'rich_text',
863
+ api_key: 'navigation',
864
+ validators: {
865
+ rich_text_blocks: { item_types: [menuItemBlock.id] },
866
+ },
867
+ });
868
+ await client.fields.create(layoutModel.id, {
869
+ label: 'Footer Navigation',
870
+ field_type: 'rich_text',
871
+ api_key: 'footer_navigation',
872
+ validators: {
873
+ rich_text_blocks: { item_types: [menuItemBlock.id] },
874
+ },
875
+ });
876
+ await client.fields.create(layoutModel.id, {
877
+ label: 'Copyright',
878
+ field_type: 'string',
879
+ api_key: 'copyright',
880
+ });
881
+
882
+ console.log('');
883
+ console.log(' Models created: Page, Case Study, Legal Page, Post');
884
+ console.log(' Singletons created: Layout, Case Studies Listing, Post Listing');
885
+ console.log(' Blocks created: Hero, Button, Menu Item');
886
+ }
887
+
476
888
  function generateReadme(projectName, cms) {
477
889
  const title = projectName || 'Project Title';
478
890
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@majordigital/create-acorn",
3
- "version": "1.4.1",
3
+ "version": "1.5.1",
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",