tackl 1.0.10 → 2.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 +15 -61
- package/bin/tackl.js +234 -169
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,75 +1,29 @@
|
|
|
1
1
|
# Tackl CLI
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install -g tackl
|
|
9
|
-
```
|
|
3
|
+
Scaffolds [Tackl](https://github.com/12-studio/tackl) — the animation-first Next.js starter kit — **into the current directory**. Made for the "empty git repo" workflow: create your repo, clone it, run one command.
|
|
10
4
|
|
|
11
5
|
## Usage
|
|
12
6
|
|
|
13
7
|
```bash
|
|
14
|
-
|
|
8
|
+
mkdir my-app && cd my-app # or clone your empty repo and cd in
|
|
9
|
+
bunx tackl # or: bun create tackl / npm create tackl@latest
|
|
15
10
|
```
|
|
16
11
|
|
|
17
|
-
|
|
12
|
+
You'll be asked which CMS to use — **DatoCMS**, **Sanity**, or **None** — and the scaffold is pruned to match: only the chosen adapter, its dependencies, its docs, and its `.env.example` block survive.
|
|
18
13
|
|
|
19
|
-
|
|
20
|
-
- `--no-install` - Skip dependency installation
|
|
21
|
-
- `--no-git` - Skip git initialization
|
|
22
|
-
- `--help` - Show help message
|
|
14
|
+
## What it does
|
|
23
15
|
|
|
24
|
-
|
|
16
|
+
1. Verifies the directory is empty (an existing `.git`, `.gitignore`, `README.md` or `LICENSE` is fine — your repo's `.git` and remote are kept)
|
|
17
|
+
2. Downloads the latest tagged release of the template (falls back to `main` if none)
|
|
18
|
+
3. Prunes repo-only cargo (the CLI itself, `.github`) and the unused CMS adapter
|
|
19
|
+
4. Sets your package name, initialises git if needed, and runs `bun install`
|
|
25
20
|
|
|
26
|
-
|
|
27
|
-
# Create a new project
|
|
28
|
-
tackl my-app
|
|
21
|
+
## Options
|
|
29
22
|
|
|
30
|
-
# Create with specific branch
|
|
31
|
-
tackl my-app --ref develop
|
|
32
|
-
|
|
33
|
-
# Skip installation and git
|
|
34
|
-
tackl my-app --no-install --no-git
|
|
35
23
|
```
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
- 🎭 **Styled Components** - CSS-in-JS with TypeScript
|
|
42
|
-
- 🎬 **GSAP** - Professional animations
|
|
43
|
-
- 📱 **Responsive Design** - Mobile-first approach
|
|
44
|
-
- 🔧 **TypeScript** - Full type safety
|
|
45
|
-
- 🎯 **Performance** - Optimized for speed
|
|
46
|
-
- 📦 **Modern Tooling** - ESLint, Prettier, Husky
|
|
47
|
-
|
|
48
|
-
## Features
|
|
49
|
-
|
|
50
|
-
### Waffl Grid System
|
|
51
|
-
|
|
52
|
-
- Smart breakpoint inheritance (only specify what changes!)
|
|
53
|
-
- Responsive grid columns
|
|
54
|
-
- Semantic components
|
|
55
|
-
- CSS custom properties
|
|
56
|
-
|
|
57
|
-
### Example Usage
|
|
58
|
-
|
|
59
|
-
```jsx
|
|
60
|
-
<Waffl>
|
|
61
|
-
<Section $s='1/-1' $l='3/9'>
|
|
62
|
-
// Mobile: full width, Desktop: 3/9 // Tablet inherits mobile (full
|
|
63
|
-
width) Content
|
|
64
|
-
</Section>
|
|
65
|
-
</Waffl>
|
|
24
|
+
--cms <dato|sanity|none> Skip the CMS prompt
|
|
25
|
+
--name <name> Package name (default: directory name)
|
|
26
|
+
--ref <tag|branch> Template ref (default: latest release)
|
|
27
|
+
--no-install Skip dependency installation
|
|
28
|
+
--no-git Skip git init (an existing .git is always kept)
|
|
66
29
|
```
|
|
67
|
-
|
|
68
|
-
## Repository
|
|
69
|
-
|
|
70
|
-
- **GitHub**: https://github.com/12-studio/tackl
|
|
71
|
-
- **Issues**: https://github.com/12-studio/tackl/issues
|
|
72
|
-
|
|
73
|
-
## License
|
|
74
|
-
|
|
75
|
-
MIT
|
package/bin/tackl.js
CHANGED
|
@@ -6,14 +6,10 @@ import path from 'path';
|
|
|
6
6
|
import pc from 'picocolors';
|
|
7
7
|
import prompts from 'prompts';
|
|
8
8
|
import * as tar from 'tar';
|
|
9
|
-
import { fileURLToPath } from 'url';
|
|
10
|
-
|
|
11
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
-
const __dirname = path.dirname(__filename);
|
|
13
9
|
|
|
14
10
|
const OWNER = '12-studio';
|
|
15
11
|
const REPO = 'tackl';
|
|
16
|
-
const
|
|
12
|
+
const FALLBACK_REF = 'main';
|
|
17
13
|
|
|
18
14
|
const log = {
|
|
19
15
|
info: m => console.log(pc.cyan('•'), m),
|
|
@@ -21,11 +17,10 @@ const log = {
|
|
|
21
17
|
warn: m => console.log(pc.yellow('▲'), m),
|
|
22
18
|
error: m => console.log(pc.red('✖'), m),
|
|
23
19
|
step: m => console.log(pc.blue('→'), m),
|
|
24
|
-
success: m => console.log(pc.green('🎉'), m),
|
|
25
20
|
};
|
|
26
21
|
|
|
27
22
|
const showBanner = () => {
|
|
28
|
-
const purple = str => `\x1b[38;5;99m${str}\x1b[0m`;
|
|
23
|
+
const purple = str => `\x1b[38;5;99m${str}\x1b[0m`;
|
|
29
24
|
console.log(
|
|
30
25
|
pc.bold(
|
|
31
26
|
purple(`
|
|
@@ -40,221 +35,294 @@ const showBanner = () => {
|
|
|
40
35
|
║ ║
|
|
41
36
|
║ The Ultimate Next.js Starter Kit ║
|
|
42
37
|
║ ║
|
|
43
|
-
╚═════════════════════════════════════════════════╝
|
|
38
|
+
╚═════════════════════════════════════════════════╝
|
|
44
39
|
`)
|
|
45
40
|
)
|
|
46
41
|
);
|
|
47
42
|
};
|
|
48
43
|
|
|
44
|
+
// SECTION • Directory checks
|
|
45
|
+
// NOTE • The CLI scaffolds INTO the current directory — run it inside an
|
|
46
|
+
// empty (or freshly-initialised) git repo. These files are allowed to
|
|
47
|
+
// already exist without counting as "not empty".
|
|
48
|
+
const ALLOWED_EXISTING = new Set(['.git', '.gitignore', 'README.md', 'LICENSE', '.DS_Store']);
|
|
49
|
+
|
|
50
|
+
const dirIsScaffoldable = dir => {
|
|
51
|
+
if (!fs.existsSync(dir)) return true;
|
|
52
|
+
return fs.readdirSync(dir).every(entry => ALLOWED_EXISTING.has(entry));
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// SECTION • Template ref resolution
|
|
56
|
+
// NOTE • Prefer the latest GitHub release so every scaffold is a tested,
|
|
57
|
+
// reproducible snapshot; fall back to main (with a warning) when no
|
|
58
|
+
// release exists. --ref overrides both.
|
|
59
|
+
const resolveRef = async explicitRef => {
|
|
60
|
+
if (explicitRef) return { ref: explicitRef, source: 'flag' };
|
|
61
|
+
try {
|
|
62
|
+
const res = await fetch(`https://api.github.com/repos/${OWNER}/${REPO}/releases/latest`, {
|
|
63
|
+
headers: { accept: 'application/vnd.github+json' },
|
|
64
|
+
});
|
|
65
|
+
if (res.ok) {
|
|
66
|
+
const json = await res.json();
|
|
67
|
+
if (json?.tag_name) return { ref: json.tag_name, source: 'release' };
|
|
68
|
+
}
|
|
69
|
+
} catch {}
|
|
70
|
+
return { ref: FALLBACK_REF, source: 'fallback' };
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// SECTION • Pruning
|
|
74
|
+
const rmrf = (root, rel) => {
|
|
75
|
+
const p = path.join(root, rel);
|
|
76
|
+
if (fs.existsSync(p)) fs.rmSync(p, { recursive: true, force: true });
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const stripEnvBlock = (root, marker) => {
|
|
80
|
+
const p = path.join(root, '.env.example');
|
|
81
|
+
if (!fs.existsSync(p)) return;
|
|
82
|
+
const lines = fs.readFileSync(p, 'utf8').split('\n');
|
|
83
|
+
const out = [];
|
|
84
|
+
let skipping = false;
|
|
85
|
+
for (const line of lines) {
|
|
86
|
+
if (line.startsWith(marker)) {
|
|
87
|
+
skipping = true;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (skipping && line.trim() === '') {
|
|
91
|
+
skipping = false;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (!skipping) out.push(line);
|
|
95
|
+
}
|
|
96
|
+
fs.writeFileSync(p, out.join('\n'));
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const stripDeps = (root, deps) => {
|
|
100
|
+
const p = path.join(root, 'package.json');
|
|
101
|
+
if (!fs.existsSync(p)) return;
|
|
102
|
+
const json = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
103
|
+
for (const d of deps) {
|
|
104
|
+
delete json.dependencies?.[d];
|
|
105
|
+
delete json.devDependencies?.[d];
|
|
106
|
+
}
|
|
107
|
+
fs.writeFileSync(p, JSON.stringify(json, null, '\t') + '\n');
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const CMS = {
|
|
111
|
+
dato: {
|
|
112
|
+
title: 'DatoCMS',
|
|
113
|
+
prune: root => {
|
|
114
|
+
rmrf(root, 'src/cms/sanity');
|
|
115
|
+
rmrf(root, 'docs/Sanity');
|
|
116
|
+
stripDeps(root, ['next-sanity']);
|
|
117
|
+
stripEnvBlock(root, '# CMS — Sanity');
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
sanity: {
|
|
121
|
+
title: 'Sanity',
|
|
122
|
+
prune: root => {
|
|
123
|
+
rmrf(root, 'src/cms/dato');
|
|
124
|
+
rmrf(root, 'docs/DatoCMS');
|
|
125
|
+
stripDeps(root, ['@datocms/cda-client', 'react-datocms', 'graphql', 'graphql-tag']);
|
|
126
|
+
stripEnvBlock(root, '# CMS — DatoCMS');
|
|
127
|
+
// Rewire the adapter entry to the kept adapter
|
|
128
|
+
const entry = path.join(root, 'src/cms/index.ts');
|
|
129
|
+
if (fs.existsSync(entry)) {
|
|
130
|
+
fs.writeFileSync(entry, fs.readFileSync(entry, 'utf8').replace(`'./dato'`, `'./sanity'`));
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
none: {
|
|
135
|
+
title: 'No CMS',
|
|
136
|
+
prune: root => {
|
|
137
|
+
rmrf(root, 'src/cms');
|
|
138
|
+
rmrf(root, 'docs/Sanity');
|
|
139
|
+
rmrf(root, 'docs/DatoCMS');
|
|
140
|
+
rmrf(root, 'docs/CMS.md');
|
|
141
|
+
stripDeps(root, ['next-sanity', '@datocms/cda-client', 'react-datocms', 'graphql', 'graphql-tag']);
|
|
142
|
+
stripEnvBlock(root, '# CMS — Sanity');
|
|
143
|
+
stripEnvBlock(root, '# CMS — DatoCMS');
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// NOTE • Repo-only cargo that should never land in a scaffolded project
|
|
149
|
+
const REPO_CARGO = ['tackl', '.github', 'bun.lock'];
|
|
150
|
+
|
|
49
151
|
async function main() {
|
|
50
152
|
const args = process.argv.slice(2);
|
|
51
153
|
|
|
52
|
-
// Handle help flag
|
|
53
154
|
if (args.includes('--help') || args.includes('-h')) {
|
|
54
155
|
console.log(`
|
|
55
156
|
${pc.bold('Tackl CLI')} - The ultimate Next.js starter kit
|
|
56
157
|
|
|
57
158
|
${pc.bold('Usage:')}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
${pc.bold('Options:')}
|
|
61
|
-
--no-install Skip dependency installation (default: install)
|
|
62
|
-
--no-git Skip git initialization (default: init)
|
|
63
|
-
-h, --help Show this help message
|
|
159
|
+
Run inside an empty directory (or a freshly-initialised git repo):
|
|
64
160
|
|
|
65
|
-
|
|
66
|
-
tackl
|
|
67
|
-
tackl my-app --no-install # Creates project with git init only
|
|
68
|
-
tackl my-app --no-git # Creates project with npm install only
|
|
69
|
-
tackl my-app --no-install --no-git # Creates project only
|
|
161
|
+
mkdir my-app && cd my-app
|
|
162
|
+
bunx tackl
|
|
70
163
|
|
|
71
|
-
${pc.bold('
|
|
164
|
+
${pc.bold('Options:')}
|
|
165
|
+
--cms <dato|sanity|none> Skip the CMS prompt
|
|
166
|
+
--name <name> Package name (default: the directory name)
|
|
167
|
+
--ref <tag|branch> Template ref (default: latest release)
|
|
168
|
+
--no-install Skip dependency installation
|
|
169
|
+
--no-git Skip git init (existing .git is always kept)
|
|
170
|
+
-h, --help Show this help message
|
|
171
|
+
|
|
172
|
+
${pc.bold('Repository:')} https://github.com/${OWNER}/${REPO}
|
|
72
173
|
`);
|
|
73
174
|
process.exit(0);
|
|
74
175
|
}
|
|
75
176
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
name: argName,
|
|
80
|
-
ref: DEFAULT_REF, // Always use main branch
|
|
81
|
-
install: !args.includes('--no-install'), // Default: true (install)
|
|
82
|
-
git: !args.includes('--no-git'), // Default: true (init git)
|
|
177
|
+
const flagValue = name => {
|
|
178
|
+
const i = args.indexOf(name);
|
|
179
|
+
return i !== -1 ? args[i + 1] : undefined;
|
|
83
180
|
};
|
|
84
181
|
|
|
85
|
-
|
|
86
|
-
const promptQuestions = [
|
|
87
|
-
{
|
|
88
|
-
type: initial.name ? null : 'text',
|
|
89
|
-
name: 'name',
|
|
90
|
-
message: 'Project directory',
|
|
91
|
-
initial: 'my-tackl-app',
|
|
92
|
-
validate: v =>
|
|
93
|
-
/^[a-z0-9\-._]+$/.test(v)
|
|
94
|
-
? true
|
|
95
|
-
: 'Use lowercase letters, numbers, - . _',
|
|
96
|
-
},
|
|
97
|
-
].filter(q => q.type !== null);
|
|
98
|
-
|
|
99
|
-
const answers = await prompts(promptQuestions, {
|
|
100
|
-
onCancel: () => {
|
|
101
|
-
log.warn('Cancelled.');
|
|
102
|
-
process.exit(1);
|
|
103
|
-
},
|
|
104
|
-
onSubmit: () => {
|
|
105
|
-
// Clean up terminal
|
|
106
|
-
process.stdout.write('\x1b[?25h');
|
|
107
|
-
},
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const projectName = (answers.name || initial.name).trim();
|
|
111
|
-
const ref = initial.ref; // Always use main branch
|
|
112
|
-
const doInstall = initial.install; // Default: true
|
|
113
|
-
const doGit = initial.git; // Default: true
|
|
182
|
+
const targetDir = process.cwd();
|
|
114
183
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
log.error('Project name is required.');
|
|
184
|
+
if (!dirIsScaffoldable(targetDir)) {
|
|
185
|
+
log.error('This directory is not empty. Run tackl inside an empty directory (a .git folder, .gitignore, README.md and LICENSE are fine).');
|
|
118
186
|
process.exit(1);
|
|
119
187
|
}
|
|
120
188
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
189
|
+
showBanner();
|
|
190
|
+
|
|
191
|
+
// SECTION • Gather choices
|
|
192
|
+
const cmsFlag = flagValue('--cms');
|
|
193
|
+
if (cmsFlag && !CMS[cmsFlag]) {
|
|
194
|
+
log.error(`Unknown --cms value "${cmsFlag}". Use dato, sanity or none.`);
|
|
126
195
|
process.exit(1);
|
|
127
196
|
}
|
|
128
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
129
197
|
|
|
130
|
-
|
|
131
|
-
|
|
198
|
+
const answers = await prompts(
|
|
199
|
+
[
|
|
200
|
+
{
|
|
201
|
+
type: cmsFlag ? null : 'select',
|
|
202
|
+
name: 'cms',
|
|
203
|
+
message: 'Which CMS should this project use?',
|
|
204
|
+
choices: [
|
|
205
|
+
{ title: 'DatoCMS', value: 'dato' },
|
|
206
|
+
{ title: 'Sanity', value: 'sanity' },
|
|
207
|
+
{ title: 'None', value: 'none' },
|
|
208
|
+
],
|
|
209
|
+
initial: 0,
|
|
210
|
+
},
|
|
211
|
+
].filter(q => q.type !== null),
|
|
212
|
+
{
|
|
213
|
+
onCancel: () => {
|
|
214
|
+
log.warn('Cancelled.');
|
|
215
|
+
process.exit(1);
|
|
216
|
+
},
|
|
217
|
+
}
|
|
218
|
+
);
|
|
132
219
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
220
|
+
const cms = cmsFlag || answers.cms;
|
|
221
|
+
const rawName = flagValue('--name') || path.basename(targetDir);
|
|
222
|
+
const projectName = rawName
|
|
223
|
+
.toLowerCase()
|
|
224
|
+
.replace(/[^a-z0-9\-._]/g, '-')
|
|
225
|
+
.replace(/^[-.]+|[-.]+$/g, '') || 'tackl-app';
|
|
226
|
+
const doInstall = !args.includes('--no-install');
|
|
227
|
+
const doGit = !args.includes('--no-git');
|
|
228
|
+
const hadGit = fs.existsSync(path.join(targetDir, '.git'));
|
|
136
229
|
|
|
137
|
-
const
|
|
138
|
-
|
|
230
|
+
const { ref, source } = await resolveRef(flagValue('--ref'));
|
|
231
|
+
if (source === 'fallback') log.warn(`No release found — using ${FALLBACK_REF} (untagged).`);
|
|
139
232
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
throw new Error(`Download failed: ${res.status} ${res.statusText}`);
|
|
233
|
+
console.log();
|
|
234
|
+
log.step(`Scaffolding ${pc.bold(pc.cyan(projectName))} in ${pc.bold(targetDir)}`);
|
|
235
|
+
log.step(`Template: ${pc.bold(pc.yellow(`${OWNER}/${REPO}@${ref}`))} · CMS: ${pc.bold(CMS[cms].title)}`);
|
|
236
|
+
console.log();
|
|
145
237
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
238
|
+
// NOTE • If the repo already has its own LICENSE, that choice wins —
|
|
239
|
+
// stash it and restore it after extraction (the template ships MIT)
|
|
240
|
+
const licensePath = path.join(targetDir, 'LICENSE');
|
|
241
|
+
const existingLicense = fs.existsSync(licensePath) ? fs.readFileSync(licensePath, 'utf8') : null;
|
|
150
242
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
243
|
+
// SECTION • Download + extract
|
|
244
|
+
// NOTE • TACKL_TEMPLATE_TAR points at a local tarball — used by tests/CI
|
|
245
|
+
const tmpTar = process.env.TACKL_TEMPLATE_TAR || path.join(os.tmpdir(), `tackl-${Date.now()}.tgz`);
|
|
246
|
+
|
|
247
|
+
if (!process.env.TACKL_TEMPLATE_TAR) {
|
|
248
|
+
const tarUrl = `https://codeload.github.com/${OWNER}/${REPO}/tar.gz/${encodeURIComponent(ref)}`;
|
|
249
|
+
try {
|
|
250
|
+
log.step('📥 Downloading template...');
|
|
251
|
+
const res = await fetch(tarUrl);
|
|
252
|
+
if (!res.ok) throw new Error(`Download failed: ${res.status} ${res.statusText}`);
|
|
253
|
+
fs.writeFileSync(tmpTar, Buffer.from(await res.arrayBuffer()));
|
|
254
|
+
log.ok('Template downloaded!');
|
|
255
|
+
} catch (e) {
|
|
256
|
+
log.error(`Download failed: ${e.message}`);
|
|
257
|
+
log.error('Please check your internet connection and try again.');
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
156
260
|
}
|
|
157
261
|
|
|
158
262
|
try {
|
|
159
263
|
log.step('📦 Extracting files...');
|
|
160
264
|
await tar.x({ file: tmpTar, cwd: targetDir, strip: 1 });
|
|
161
|
-
log.ok('Files extracted
|
|
265
|
+
log.ok('Files extracted!');
|
|
162
266
|
} catch (e) {
|
|
163
267
|
log.error(`Extraction failed: ${e.message}`);
|
|
164
|
-
log.error('Please try again or check if the template is valid.');
|
|
165
268
|
process.exit(1);
|
|
166
269
|
} finally {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
270
|
+
if (!process.env.TACKL_TEMPLATE_TAR) {
|
|
271
|
+
try {
|
|
272
|
+
fs.unlinkSync(tmpTar);
|
|
273
|
+
} catch {}
|
|
274
|
+
}
|
|
170
275
|
}
|
|
171
276
|
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
} catch {}
|
|
187
|
-
|
|
188
|
-
// Patch package name
|
|
189
|
-
log.step('📝 Updating project configuration...');
|
|
190
|
-
const pkg = path.join(targetDir, 'package.json');
|
|
191
|
-
if (fs.existsSync(pkg)) {
|
|
277
|
+
// SECTION • Prune
|
|
278
|
+
log.step('🧹 Pruning template...');
|
|
279
|
+
if (existingLicense !== null) {
|
|
280
|
+
fs.writeFileSync(licensePath, existingLicense);
|
|
281
|
+
log.info('Kept your existing LICENSE.');
|
|
282
|
+
}
|
|
283
|
+
for (const cargo of REPO_CARGO) rmrf(targetDir, cargo);
|
|
284
|
+
for (const lock of ['yarn.lock', 'package-lock.json', 'pnpm-lock.yaml']) rmrf(targetDir, lock);
|
|
285
|
+
CMS[cms].prune(targetDir);
|
|
286
|
+
log.ok(`Template pruned for ${CMS[cms].title}.`);
|
|
287
|
+
|
|
288
|
+
// SECTION • Patch package.json
|
|
289
|
+
const pkgPath = path.join(targetDir, 'package.json');
|
|
290
|
+
if (fs.existsSync(pkgPath)) {
|
|
192
291
|
try {
|
|
193
|
-
const json = JSON.parse(fs.readFileSync(
|
|
194
|
-
json.name = projectName
|
|
195
|
-
|
|
196
|
-
|
|
292
|
+
const json = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
293
|
+
json.name = projectName;
|
|
294
|
+
json.version = '0.1.0';
|
|
295
|
+
delete json.repository;
|
|
296
|
+
delete json.author;
|
|
297
|
+
fs.writeFileSync(pkgPath, JSON.stringify(json, null, '\t') + '\n');
|
|
298
|
+
log.ok('package.json updated.');
|
|
197
299
|
} catch (e) {
|
|
198
300
|
log.warn(`Could not update package.json: ${e.message}`);
|
|
199
301
|
}
|
|
200
302
|
}
|
|
201
303
|
|
|
202
|
-
//
|
|
203
|
-
|
|
204
|
-
const npmrcPath = path.join(targetDir, '.npmrc');
|
|
205
|
-
fs.writeFileSync(npmrcPath, 'package-lock=true\n');
|
|
206
|
-
log.step('📝 Created .npmrc to ensure npm usage');
|
|
207
|
-
} catch (e) {
|
|
208
|
-
log.warn(`Could not create .npmrc: ${e.message}`);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (doGit) {
|
|
304
|
+
// SECTION • Git
|
|
305
|
+
if (doGit && !hadGit) {
|
|
212
306
|
try {
|
|
213
|
-
log.step('🔧
|
|
307
|
+
log.step('🔧 Initialising git repository...');
|
|
214
308
|
execSync('git init', { cwd: targetDir, stdio: 'ignore' });
|
|
215
|
-
|
|
216
|
-
// Set default branch to main (not master)
|
|
217
309
|
execSync('git branch -M main', { cwd: targetDir, stdio: 'ignore' });
|
|
218
|
-
|
|
219
310
|
execSync('git add -A', { cwd: targetDir, stdio: 'ignore' });
|
|
220
|
-
execSync('git commit -m "chore: scaffold with tackl"', {
|
|
221
|
-
|
|
222
|
-
stdio: 'ignore',
|
|
223
|
-
});
|
|
224
|
-
log.ok('Git repository initialized!');
|
|
311
|
+
execSync('git commit -m "chore: scaffold with tackl" --no-verify', { cwd: targetDir, stdio: 'ignore' });
|
|
312
|
+
log.ok('Git repository initialised!');
|
|
225
313
|
} catch (e) {
|
|
226
314
|
log.warn(`Git init failed: ${e.message}`);
|
|
227
315
|
}
|
|
316
|
+
} else if (hadGit) {
|
|
317
|
+
log.info('Existing .git kept — review and commit the scaffold yourself.');
|
|
228
318
|
}
|
|
229
319
|
|
|
320
|
+
// SECTION • Install
|
|
230
321
|
if (doInstall) {
|
|
231
322
|
try {
|
|
232
|
-
log.step('📦 Installing dependencies with
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const originalEnv = { ...process.env };
|
|
236
|
-
process.env.HUSKY = '0';
|
|
237
|
-
|
|
238
|
-
execSync('npm install', { cwd: targetDir, stdio: 'inherit' });
|
|
239
|
-
|
|
240
|
-
// Restore environment
|
|
241
|
-
process.env = originalEnv;
|
|
242
|
-
|
|
243
|
-
// Try to fix audit issues automatically
|
|
244
|
-
try {
|
|
245
|
-
log.step('🔧 Fixing security vulnerabilities...');
|
|
246
|
-
execSync('npm audit fix', { cwd: targetDir, stdio: 'inherit' });
|
|
247
|
-
log.ok('Security vulnerabilities fixed!');
|
|
248
|
-
} catch (e) {
|
|
249
|
-
log.warn(
|
|
250
|
-
'Some vulnerabilities could not be automatically fixed.'
|
|
251
|
-
);
|
|
252
|
-
log.info(
|
|
253
|
-
'You can run "npm audit fix --force" manually if needed.'
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
log.ok('Dependencies installed successfully!');
|
|
323
|
+
log.step('📦 Installing dependencies with bun...');
|
|
324
|
+
execSync('bun install', { cwd: targetDir, stdio: 'inherit', env: { ...process.env, HUSKY: '0' } });
|
|
325
|
+
log.ok('Dependencies installed!');
|
|
258
326
|
} catch (e) {
|
|
259
327
|
log.warn(`Install failed: ${e.message}`);
|
|
260
328
|
}
|
|
@@ -262,14 +330,11 @@ ${pc.bold('Repository:')} https://github.com/12-studio/tackl
|
|
|
262
330
|
log.info('Skipped dependency install.');
|
|
263
331
|
}
|
|
264
332
|
|
|
265
|
-
|
|
266
|
-
console.log(
|
|
267
|
-
|
|
268
|
-
);
|
|
269
|
-
console.log(`
|
|
270
|
-
console.log(` 1) cd ${rel}`);
|
|
271
|
-
if (!doInstall) console.log(` 2) npm install`);
|
|
272
|
-
console.log(` ${doInstall ? '2' : '3'}) npm run dev`);
|
|
333
|
+
console.log('\n' + pc.bold(pc.green('Success!')) + ' Project scaffolded.\n');
|
|
334
|
+
console.log(' Next steps:');
|
|
335
|
+
console.log(' 1) cp .env.example .env # then fill in your values');
|
|
336
|
+
if (!doInstall) console.log(' 2) bun install');
|
|
337
|
+
console.log(` ${doInstall ? '2' : '3'}) bun run dev`);
|
|
273
338
|
}
|
|
274
339
|
|
|
275
340
|
main().catch(e => {
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tackl",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Scaffold a Tackl project into the current directory — Next.js, styled-components, Waffl grid, DatoCMS/Sanity adapters",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"tackl": "
|
|
7
|
+
"tackl": "bin/tackl.js"
|
|
8
8
|
},
|
|
9
9
|
"main": "index.js",
|
|
10
10
|
"scripts": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"repository": {
|
|
29
29
|
"type": "git",
|
|
30
|
-
"url": "https://github.com/12-studio/tackl"
|
|
30
|
+
"url": "git+https://github.com/12-studio/tackl.git"
|
|
31
31
|
},
|
|
32
32
|
"homepage": "https://github.com/12-studio/tackl",
|
|
33
33
|
"bugs": {
|