@majordigital/create-acorn 1.0.0

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 ADDED
@@ -0,0 +1,100 @@
1
+ # Acorn
2
+
3
+ ## Introduction πŸ“–
4
+
5
+ This is MAJOR's starter boilerplate for NextJs projects. You can either use it without a headless CMS, connect it to a headless CMS of your choice, or use the blueprints we have for our current evolving selection of headless CMS's which includes:
6
+
7
+ - Primsic
8
+ - Storyblok
9
+
10
+ ## Running This Project πŸš€
11
+
12
+ This website is built using the [NextJS](https://nextjs.org/) framework, utilising [TypeScript](https://www.typescriptlang.org/) to strongly check types/props and provide helpful intelisense, and the utility-first CSS framework [Tailwind](https://tailwindcss.com/) as the styling library.
13
+
14
+ To run this project, following the following instructions:
15
+
16
+ 1. **Install dependencies**
17
+ Navigate to the root directory.
18
+
19
+ ```bash
20
+ npm i
21
+ ```
22
+
23
+ 2. **Run the website**
24
+ In the root directory.
25
+
26
+ ```bash
27
+ npm run dev
28
+ ```
29
+
30
+ ## Create Acorn CLI 🧰
31
+
32
+ First iteration of an interactive CLI to scaffold a Headless CMS skeleton with Next.js 15 and Acorn components.
33
+
34
+ - Default framework: Next.js 15
35
+ - UI base: Acorn components (this repo)
36
+ - CMS options: Prismic, Storyblok, Dato
37
+
38
+ Recommended usage (once published to npm):
39
+
40
+ - Use npm init-style:
41
+
42
+ ```bash
43
+ npm create acorn@latest
44
+ ```
45
+
46
+ - Or via npx with the alias:
47
+
48
+ ```bash
49
+ npx major-acorn@latest
50
+ ```
51
+
52
+ Local (from this repo):
53
+
54
+ ```bash
55
+ npm run create
56
+ # or
57
+ npm run acorn
58
+ ```
59
+
60
+ ### Prismic setup
61
+
62
+ If you select Prismic, the CLI follows the official Next.js guide and runs Slice Machine init for you:
63
+
64
+ ```bash
65
+ npx @slicemachine/init@latest --repository <your-repository-name>
66
+ ```
67
+
68
+ You can also pass the repository name non-interactively:
69
+
70
+ ```bash
71
+ npm run acorn -- --cms prismic --repo my-repo
72
+ # or
73
+ npx major-acorn@latest --cms prismic --repo my-repo
74
+ ```
75
+
76
+ Repository name rules: lowercase letters, numbers, and hyphens only.
77
+
78
+ Or pass a non-interactive flag:
79
+
80
+ ```bash
81
+ node bin/create-acorn.mjs --cms prismic
82
+ node bin/create-acorn.mjs --cms storyblok
83
+ node bin/create-acorn.mjs --cms dato
84
+ ```
85
+
86
+ Notes:
87
+ - This first iteration collects your CMS choice only and prints the selection. In the next iteration, the CLI will scaffold the appropriate folder structure and config for your chosen CMS.
88
+ - To expose this CLI as global commands (`create-acorn` and `major-acorn`) via `npm create`/`npx`, remove `"private": true` and publish to npm.
89
+ - While it’s technically possible to trigger prompts on `npm install major-acorn` via a `postinstall` script, it’s discouraged as it surprises installs and breaks CI. The recommended flow is `npm create acorn` or `npx major-acorn` to start a new project.
90
+
91
+ ## Contacts πŸ§‘β€πŸ’»
92
+
93
+ The primary contact for this project is [Davs Howard](mailto:davs@majordigital.com).
94
+
95
+ ### Roadmap
96
+
97
+ - [Commitizen](https://github.com/commitizen/cz-cli)
98
+ - Integration and E2E testing
99
+ - Storybook
100
+ - GitHub workflows
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+ /** biome-ignore-all assist/source/organizeImports: necessary for setup */
3
+ import readline from 'node:readline';
4
+ import { argv, exit } from 'node:process';
5
+ import { spawn } from 'node:child_process';
6
+ import { basename } from 'node:path';
7
+
8
+ const CMS_CHOICES = [
9
+ { key: 'prismic', label: 'Prismic' },
10
+ { key: 'storyblok', label: 'Storyblok' },
11
+ { key: 'dato', label: 'Dato' }
12
+ ];
13
+
14
+ function printHeader() {
15
+ console.log('');
16
+ console.log('Create Acorn β€” Next.js 15 + Acorn Components');
17
+ console.log('');
18
+ }
19
+
20
+ function printChoices() {
21
+ console.log('Which headless CMS would you like to use?');
22
+ CMS_CHOICES.forEach((c, i) => {
23
+ console.log(` ${i + 1}) ${c.label}`);
24
+ });
25
+ console.log('');
26
+ }
27
+
28
+ function parseNonInteractiveArg() {
29
+ // Support: --cms prismic|storyblok|dato
30
+ const idx = argv.indexOf('--cms');
31
+ if (idx !== -1 && argv[idx + 1]) {
32
+ const val = String(argv[idx + 1]).toLowerCase();
33
+ const choice = CMS_CHOICES.find((c) => c.key === val);
34
+ if (choice) return choice;
35
+ }
36
+ return null;
37
+ }
38
+
39
+ async function promptInteractive() {
40
+ return new Promise((resolve) => {
41
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
42
+ printChoices();
43
+ rl.question('Enter a number (1-3): ', (answer) => {
44
+ const num = Number(answer);
45
+ const idx = Number.isFinite(num) ? num - 1 : -1;
46
+ rl.close();
47
+ resolve(CMS_CHOICES[idx]);
48
+ });
49
+ });
50
+ }
51
+
52
+ function ask(question, defaultValue) {
53
+ return new Promise((resolve) => {
54
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
55
+ const prompt = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `;
56
+ rl.question(prompt, (answer) => {
57
+ rl.close();
58
+ resolve(answer && answer.trim() ? answer.trim() : defaultValue);
59
+ });
60
+ });
61
+ }
62
+
63
+ function parseFlag(name) {
64
+ const idx = argv.findIndex((a) => a === `--${name}`);
65
+ if (idx !== -1 && argv[idx + 1]) return String(argv[idx + 1]);
66
+ return undefined;
67
+ }
68
+
69
+ function runCommand(cmd, args, options = {}) {
70
+ return new Promise((resolve, reject) => {
71
+ const child = spawn(cmd, args, { stdio: 'inherit', shell: false, ...options });
72
+ child.on('close', (code) => {
73
+ if (code === 0) resolve();
74
+ else reject(new Error(`${cmd} ${args.join(' ')} exited with code ${code}`));
75
+ });
76
+ child.on('error', reject);
77
+ });
78
+ }
79
+
80
+ async function setupPrismic() {
81
+ const nonInteractiveRepo = parseFlag('repo');
82
+ const cwdName = basename(process.cwd())
83
+ .toLowerCase()
84
+ .replace(/[^a-z0-9-]/g, '-')
85
+ .replace(/-{2,}/g, '-')
86
+ .replace(/^-+|-+$/g, '');
87
+ const defaultBase = cwdName && cwdName !== 'acorn' ? cwdName : 'acorn';
88
+ const prefixed = defaultBase.startsWith('majordigital-') ? defaultBase : `majordigital-${defaultBase}`;
89
+ const suggested = nonInteractiveRepo || prefixed;
90
+
91
+ let repo = nonInteractiveRepo;
92
+ if (!repo) {
93
+ repo = await ask('Enter your Prismic repository name', suggested);
94
+ }
95
+
96
+ if (!/^[a-z0-9-]+$/.test(repo)) {
97
+ console.error('Repository name must be lowercase letters, numbers, and hyphens only.');
98
+ exit(1);
99
+ }
100
+
101
+ console.log('');
102
+ console.log(`Initializing Prismic Slice Machine for repo: ${repo}`);
103
+ console.log('');
104
+
105
+ await runCommand('npx', ['@slicemachine/init@latest', '--repository', repo]);
106
+
107
+ console.log('');
108
+ console.log('Prismic Slice Machine initialization complete.');
109
+ console.log('Review newly added files and follow any prompts from Prismic.');
110
+ console.log('');
111
+ }
112
+
113
+ async function main() {
114
+ printHeader();
115
+ let selection = parseNonInteractiveArg();
116
+ if (!selection) {
117
+ selection = await promptInteractive();
118
+ }
119
+
120
+ if (!selection) {
121
+ console.error('Invalid choice. Please run again and choose 1-3, or pass --cms prismic|storyblok|dato');
122
+ exit(1);
123
+ }
124
+
125
+ console.log('');
126
+ console.log(`You selected: ${selection.label}`);
127
+ console.log('');
128
+
129
+ // Default stack context
130
+ console.log('- Default framework: Next.js 15');
131
+ console.log('- UI base: Acorn components from this repo');
132
+ console.log('');
133
+
134
+ if (selection.key === 'prismic') {
135
+ await setupPrismic();
136
+ } else {
137
+ console.log(`CMS preset ${selection.label} scaffolding is coming next.`);
138
+ console.log('This run only confirms selection for non-Prismic options.');
139
+ console.log('');
140
+ }
141
+ }
142
+
143
+ main().catch((err) => {
144
+ console.error(err);
145
+ exit(1);
146
+ });
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@majordigital/create-acorn",
3
+ "version": "1.0.0",
4
+ "description": "Interactive scaffold for Acorn with Storyblok/Prismic/DatoCMS, TypeScript, and Tailwind.",
5
+ "bin": {
6
+ "create-acorn": "bin/create-acorn.mjs",
7
+ "major-acorn": "bin/create-acorn.mjs"
8
+ },
9
+ "type": "module",
10
+ "license": "MIT",
11
+ "private": false,
12
+ "publishConfig": {
13
+ "access": "public",
14
+ "registry": "https://registry.npmjs.org"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/MajorDigital/majordigital-acorn.git"
19
+ },
20
+ "files": [
21
+ "bin",
22
+ "README.md"
23
+ ],
24
+ "engines": {
25
+ "node": ">=22"
26
+ },
27
+ "scripts": {
28
+ "create": "node bin/create-acorn.mjs",
29
+ "acorn": "node bin/create-acorn.mjs",
30
+ "dev": "next dev --turbo",
31
+ "build": "next build",
32
+ "start": "next start",
33
+ "preview": "next build && next start",
34
+ "slicemachine": "start-slicemachine"
35
+ },
36
+ "dependencies": {
37
+ "@headlessui/react": "^2.2.3",
38
+ "@headlessui/tailwindcss": "^0.2.2",
39
+ "@heroicons/react": "^2.2.0",
40
+ "@hookform/error-message": "^2.0.1",
41
+ "@hookform/resolvers": "^5.0.1",
42
+ "@next/bundle-analyzer": "^15.3.2",
43
+ "clsx": "^2.1.1",
44
+ "next": "15.5.9",
45
+ "next-seo": "^6.6.0",
46
+ "react": "19.2.3",
47
+ "react-accessible-accordion": "^5.0.1",
48
+ "react-dom": "19.2.3",
49
+ "react-hook-form": "^7.70.0",
50
+ "tailwindcss": "^3.4.17",
51
+ "zod": "^3.24.4"
52
+ },
53
+ "devDependencies": {
54
+ "@commitlint/cli": "^19.8.1",
55
+ "@commitlint/config-conventional": "^19.8.1",
56
+ "@slicemachine/adapter-next": "^0.3.93",
57
+ "@svgr/webpack": "^8.1.0",
58
+ "@types/lodash.get": "^4.4.9",
59
+ "@types/node": "22.15.18",
60
+ "@types/react": "19.1.4",
61
+ "@types/react-dom": "19.1.5",
62
+ "autoprefixer": "^10.4.21",
63
+ "cross-env": "^7.0.3",
64
+ "postcss": "^8.5.3",
65
+ "slice-machine-ui": "^2.20.5",
66
+ "typescript": "^5.8.3"
67
+ }
68
+ }