@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 +100 -0
- package/bin/create-acorn.mjs +146 -0
- package/package.json +68 -0
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
|
+
}
|