tackl 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 +75 -0
- package/bin/tackl.js +248 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Tackl CLI
|
|
2
|
+
|
|
3
|
+
The ultimate Next.js starter kit with Waffl Grid system.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g tackl
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
tackl my-app
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Options
|
|
18
|
+
|
|
19
|
+
- `--ref <branch>` - Template ref (branch or tag) (default: main)
|
|
20
|
+
- `--no-install` - Skip dependency installation
|
|
21
|
+
- `--no-git` - Skip git initialization
|
|
22
|
+
- `--help` - Show help message
|
|
23
|
+
|
|
24
|
+
### Examples
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Create a new project
|
|
28
|
+
tackl my-app
|
|
29
|
+
|
|
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
|
+
```
|
|
36
|
+
|
|
37
|
+
## What's Included
|
|
38
|
+
|
|
39
|
+
- ⚡ **Next.js 15** with App Router
|
|
40
|
+
- 🎨 **Waffl Grid System** - Responsive grid with smart breakpoint inheritance
|
|
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>
|
|
66
|
+
```
|
|
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
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import pc from 'picocolors';
|
|
7
|
+
import prompts from 'prompts';
|
|
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
|
+
|
|
14
|
+
const OWNER = 'joebentaylor1995';
|
|
15
|
+
const REPO = 'tackl';
|
|
16
|
+
const DEFAULT_REF = 'main';
|
|
17
|
+
|
|
18
|
+
const log = {
|
|
19
|
+
info: m => console.log(pc.cyan('•'), m),
|
|
20
|
+
ok: m => console.log(pc.green('✔'), m),
|
|
21
|
+
warn: m => console.log(pc.yellow('▲'), m),
|
|
22
|
+
error: m => console.log(pc.red('✖'), m),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
async function main() {
|
|
26
|
+
const args = process.argv.slice(2);
|
|
27
|
+
|
|
28
|
+
// Handle help flag
|
|
29
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
30
|
+
console.log(`
|
|
31
|
+
${pc.bold('Tackl CLI')} - The ultimate Next.js starter kit
|
|
32
|
+
|
|
33
|
+
${pc.bold('Usage:')}
|
|
34
|
+
tackl [project-name] [options]
|
|
35
|
+
|
|
36
|
+
${pc.bold('Options:')}
|
|
37
|
+
--ref <branch> Template ref (branch or tag) (default: main)
|
|
38
|
+
--no-install Skip dependency installation
|
|
39
|
+
--no-git Skip git initialization
|
|
40
|
+
-h, --help Show this help message
|
|
41
|
+
|
|
42
|
+
${pc.bold('Examples:')}
|
|
43
|
+
tackl my-app
|
|
44
|
+
tackl my-app --ref develop
|
|
45
|
+
tackl my-app --no-install --no-git
|
|
46
|
+
|
|
47
|
+
${pc.bold('Repository:')} https://github.com/12-studio/tackl
|
|
48
|
+
`);
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const argName = args.find(a => !a.startsWith('-'));
|
|
53
|
+
const getFlagVal = (key, fallback) => {
|
|
54
|
+
const idx = args.findIndex(a => a === key);
|
|
55
|
+
return idx >= 0 && args[idx + 1] && !args[idx + 1].startsWith('-')
|
|
56
|
+
? args[idx + 1]
|
|
57
|
+
: fallback;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const initial = {
|
|
61
|
+
name: argName,
|
|
62
|
+
ref: getFlagVal('--ref', DEFAULT_REF),
|
|
63
|
+
install: !args.includes('--no-install'),
|
|
64
|
+
git: !args.includes('--no-git'),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Filter out null prompts
|
|
68
|
+
const promptQuestions = [
|
|
69
|
+
{
|
|
70
|
+
type: initial.name ? null : 'text',
|
|
71
|
+
name: 'name',
|
|
72
|
+
message: 'Project directory',
|
|
73
|
+
initial: 'my-tackl-app',
|
|
74
|
+
validate: v =>
|
|
75
|
+
/^[a-z0-9\-._]+$/.test(v)
|
|
76
|
+
? true
|
|
77
|
+
: 'Use lowercase letters, numbers, - . _',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
type: 'text',
|
|
81
|
+
name: 'ref',
|
|
82
|
+
message: 'Template ref (branch or tag)',
|
|
83
|
+
initial: initial.ref,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
type: 'toggle',
|
|
87
|
+
name: 'install',
|
|
88
|
+
message: 'Install dependencies?',
|
|
89
|
+
initial: initial.install,
|
|
90
|
+
active: 'yes',
|
|
91
|
+
inactive: 'no',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
type: 'toggle',
|
|
95
|
+
name: 'git',
|
|
96
|
+
message: 'Initialize git?',
|
|
97
|
+
initial: initial.git,
|
|
98
|
+
active: 'yes',
|
|
99
|
+
inactive: 'no',
|
|
100
|
+
},
|
|
101
|
+
].filter(q => q.type !== null);
|
|
102
|
+
|
|
103
|
+
const answers = await prompts(promptQuestions, {
|
|
104
|
+
onCancel: () => {
|
|
105
|
+
log.warn('Cancelled.');
|
|
106
|
+
process.exit(1);
|
|
107
|
+
},
|
|
108
|
+
onSubmit: () => {
|
|
109
|
+
// Clean up terminal
|
|
110
|
+
process.stdout.write('\x1b[?25h');
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const projectName = (answers.name || initial.name).trim();
|
|
115
|
+
const ref = (answers.ref || initial.ref).trim();
|
|
116
|
+
const doInstall = answers.install;
|
|
117
|
+
const doGit = answers.git;
|
|
118
|
+
|
|
119
|
+
// Validate project name
|
|
120
|
+
if (!projectName) {
|
|
121
|
+
log.error('Project name is required.');
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const targetDir = path.resolve(process.cwd(), projectName);
|
|
126
|
+
if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
|
|
127
|
+
log.error(
|
|
128
|
+
`Directory "${projectName}" already exists and is not empty.`
|
|
129
|
+
);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
133
|
+
|
|
134
|
+
log.info(
|
|
135
|
+
`Scaffolding ${pc.bold(projectName)} from ${OWNER}/${REPO}@${ref} ...`
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const tarUrl = `https://codeload.github.com/${OWNER}/${REPO}/tar.gz/${encodeURIComponent(ref)}`;
|
|
139
|
+
const tmpTar = path.join(os.tmpdir(), `tackl-${Date.now()}.tgz`);
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const res = await fetch(tarUrl);
|
|
143
|
+
if (!res.ok)
|
|
144
|
+
throw new Error(`Download failed: ${res.status} ${res.statusText}`);
|
|
145
|
+
const out = fs.createWriteStream(tmpTar);
|
|
146
|
+
await new Promise((resolve, reject) => {
|
|
147
|
+
res.body.pipe(out);
|
|
148
|
+
res.body.on('error', reject);
|
|
149
|
+
out.on('finish', resolve);
|
|
150
|
+
});
|
|
151
|
+
log.ok('Downloaded template.');
|
|
152
|
+
} catch (e) {
|
|
153
|
+
log.error(e.message);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
await tar.x({ file: tmpTar, cwd: targetDir, strip: 1 });
|
|
159
|
+
log.ok('Extracted files.');
|
|
160
|
+
} catch (e) {
|
|
161
|
+
log.error(`Extraction failed: ${e.message}`);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
} finally {
|
|
164
|
+
try {
|
|
165
|
+
fs.unlinkSync(tmpTar);
|
|
166
|
+
} catch {}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Remove template .git if present
|
|
170
|
+
try {
|
|
171
|
+
const gitPath = path.join(targetDir, '.git');
|
|
172
|
+
if (fs.existsSync(gitPath))
|
|
173
|
+
fs.rmSync(gitPath, { recursive: true, force: true });
|
|
174
|
+
} catch {}
|
|
175
|
+
|
|
176
|
+
// Patch package name
|
|
177
|
+
const pkg = path.join(targetDir, 'package.json');
|
|
178
|
+
if (fs.existsSync(pkg)) {
|
|
179
|
+
try {
|
|
180
|
+
const json = JSON.parse(fs.readFileSync(pkg, 'utf8'));
|
|
181
|
+
json.name = projectName.toLowerCase();
|
|
182
|
+
fs.writeFileSync(pkg, JSON.stringify(json, null, 2));
|
|
183
|
+
log.ok('Updated package.json name.');
|
|
184
|
+
} catch (e) {
|
|
185
|
+
log.warn(`Could not update package.json: ${e.message}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (doGit) {
|
|
190
|
+
try {
|
|
191
|
+
execSync('git init', { cwd: targetDir, stdio: 'ignore' });
|
|
192
|
+
execSync('git add -A', { cwd: targetDir, stdio: 'ignore' });
|
|
193
|
+
execSync('git commit -m "chore: scaffold with tackl"', {
|
|
194
|
+
cwd: targetDir,
|
|
195
|
+
stdio: 'ignore',
|
|
196
|
+
});
|
|
197
|
+
log.ok('Initialized git repository.');
|
|
198
|
+
} catch (e) {
|
|
199
|
+
log.warn(`Git init failed: ${e.message}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (doInstall) {
|
|
204
|
+
try {
|
|
205
|
+
const pm = detectPM();
|
|
206
|
+
log.info(`Installing dependencies with ${pm} ...`);
|
|
207
|
+
execSync(
|
|
208
|
+
pm === 'pnpm'
|
|
209
|
+
? 'pnpm install'
|
|
210
|
+
: pm === 'yarn'
|
|
211
|
+
? 'yarn'
|
|
212
|
+
: 'npm install',
|
|
213
|
+
{ cwd: targetDir, stdio: 'inherit' }
|
|
214
|
+
);
|
|
215
|
+
log.ok('Dependencies installed.');
|
|
216
|
+
} catch (e) {
|
|
217
|
+
log.warn(`Install failed: ${e.message}`);
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
log.info('Skipped dependency install.');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const rel = path.relative(process.cwd(), targetDir) || '.';
|
|
224
|
+
console.log(
|
|
225
|
+
'\n' + pc.bold(pc.green('Success!')) + ' Project scaffolded.\n'
|
|
226
|
+
);
|
|
227
|
+
console.log(` Next steps:`);
|
|
228
|
+
console.log(` 1) cd ${rel}`);
|
|
229
|
+
if (!doInstall) console.log(` 2) npm install (or pnpm/yarn)`);
|
|
230
|
+
console.log(` ${doInstall ? '2' : '3'}) npm run dev`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function detectPM() {
|
|
234
|
+
try {
|
|
235
|
+
execSync('pnpm --version', { stdio: 'ignore' });
|
|
236
|
+
return 'pnpm';
|
|
237
|
+
} catch {}
|
|
238
|
+
try {
|
|
239
|
+
execSync('yarn --version', { stdio: 'ignore' });
|
|
240
|
+
return 'yarn';
|
|
241
|
+
} catch {}
|
|
242
|
+
return 'npm';
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
main().catch(e => {
|
|
246
|
+
console.error(e);
|
|
247
|
+
process.exit(1);
|
|
248
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tackl",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "The ultimate Next.js starter kit with Waffl Grid system",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"tackl": "./bin/tackl.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "index.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"nextjs",
|
|
15
|
+
"react",
|
|
16
|
+
"starter",
|
|
17
|
+
"template",
|
|
18
|
+
"grid",
|
|
19
|
+
"css",
|
|
20
|
+
"styled-components",
|
|
21
|
+
"typescript"
|
|
22
|
+
],
|
|
23
|
+
"author": "joebentaylor1995",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20.0.0"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/12-studio/tackl"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/12-studio/tackl",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/12-studio/tackl/issues"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"bin/",
|
|
38
|
+
"README.md"
|
|
39
|
+
],
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"picocolors": "^1.1.1",
|
|
42
|
+
"prompts": "^2.4.2",
|
|
43
|
+
"tar": "^7.5.1"
|
|
44
|
+
}
|
|
45
|
+
}
|