tackl 2.2.0 → 2.5.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.
Files changed (3) hide show
  1. package/README.md +4 -4
  2. package/bin/tackl.js +76 -22
  3. package/package.json +8 -7
package/README.md CHANGED
@@ -9,17 +9,17 @@ mkdir my-app && cd my-app # or clone your empty repo and cd in
9
9
  bunx tackl # or: bun create tackl / npm create tackl@latest
10
10
  ```
11
11
 
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.
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. Draft preview ships with both CMS choices (the `app/api/draft-mode/` routes resolve through the adapter seam). **None** keeps the same shape as the others — the `@cms` seam stays, rewired to a stub adapter whose `fetchContent` returns `null` — so wiring up a CMS later never touches app code; only the draft-mode routes and CMS files are removed.
13
13
 
14
14
  Choosing **Sanity** sets the whole thing up: an embedded Studio at `/studio`, example schemas, and draft-mode preview routes ship in the scaffold, and the CLI offers to run `sanity init` for you — logging in, creating/linking a project, and writing your `.env`. Scaffold → `bun run dev` → `/studio` → start editing.
15
15
 
16
- On your first `bun run dev`, a **setup wizard** greets you in the browser: it walks through every theme token (colours, spacing, motion, fonts, the type scale), writes your answers into `src/theme`, then deletes itself — skip it to keep the starter defaults. The scaffold commit the CLI makes is your restore point if you ever want to run it again.
16
+ Design tokens live in `src/theme` and type styles in `src/css/tackl/_type.scss` — set them from your Figma file before you start building.
17
17
 
18
18
  ## What it does
19
19
 
20
20
  1. Verifies the directory is empty (an existing `.git`, `.gitignore`, `README.md` or `LICENSE` is fine — your repo's `.git` and remote are kept)
21
- 2. Downloads the latest tagged release of the template (falls back to `main` if none)
22
- 3. Prunes repo-only cargo (the CLI itself, `.github`) and the unused CMS adapter
21
+ 2. Downloads the latest GitHub release of the template (falls back to `main` if none can be resolved). The lookup uses `GITHUB_TOKEN` / `GH_TOKEN` when set, and otherwise falls back to github.com's non-rate-limited release redirect — so shared IPs (offices, CI) still get the release
22
+ 3. Prunes repo-only cargo (the CLI itself, `.github` templates, internal `docs/plans`) and the unused CMS adapter
23
23
  4. Sets your package name, initialises git if needed, and runs `bun install`
24
24
 
25
25
  ## Options
package/bin/tackl.js CHANGED
@@ -55,18 +55,44 @@ const dirIsScaffoldable = dir => {
55
55
  // SECTION • Template ref resolution
56
56
  // NOTE • Prefer the latest GitHub release so every scaffold is a tested,
57
57
  // reproducible snapshot; fall back to main (with a warning) when no
58
- // release exists. --ref overrides both.
58
+ // release can be resolved. --ref overrides both.
59
+ // The REST API allows only 60 unauthenticated requests/hour per IP — shared
60
+ // IPs (offices, CI) hit that — so a GITHUB_TOKEN is used when present, and
61
+ // the github.com /releases/latest redirect (not rate-limited) is tried next.
62
+ const RELEASE_TAG_PATTERN = /\/releases\/tag\/([^/?#]+)/;
63
+
64
+ const latestReleaseFromApi = async () => {
65
+ const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
66
+ const res = await fetch(`https://api.github.com/repos/${OWNER}/${REPO}/releases/latest`, {
67
+ headers: {
68
+ accept: 'application/vnd.github+json',
69
+ 'user-agent': 'tackl-cli',
70
+ ...(token && { authorization: `Bearer ${token}` }),
71
+ },
72
+ });
73
+ if (!res.ok) return null;
74
+ const json = await res.json();
75
+ return json?.tag_name ?? null;
76
+ };
77
+
78
+ const latestReleaseFromRedirect = async () => {
79
+ const res = await fetch(`https://github.com/${OWNER}/${REPO}/releases/latest`, {
80
+ method: 'HEAD',
81
+ redirect: 'manual',
82
+ headers: { 'user-agent': 'tackl-cli' },
83
+ });
84
+ const match = res.headers.get('location')?.match(RELEASE_TAG_PATTERN);
85
+ return match ? decodeURIComponent(match[1]) : null;
86
+ };
87
+
59
88
  const resolveRef = async explicitRef => {
60
89
  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 {}
90
+ for (const lookup of [latestReleaseFromApi, latestReleaseFromRedirect]) {
91
+ try {
92
+ const tag = await lookup();
93
+ if (tag) return { ref: tag, source: 'release' };
94
+ } catch {}
95
+ }
70
96
  return { ref: FALLBACK_REF, source: 'fallback' };
71
97
  };
72
98
 
@@ -107,15 +133,29 @@ const stripDeps = (root, deps) => {
107
133
  fs.writeFileSync(p, `${JSON.stringify(json, null, '\t')}\n`);
108
134
  };
109
135
 
110
- // NOTE • Everything that exists solely for the embedded Sanity Studio
111
- const SANITY_STUDIO_PATHS = ['app/(studio)', 'app/api/draft-mode', 'sanity.config.ts', 'sanity'];
136
+ // NOTE • Everything that exists solely for the embedded Sanity Studio.
137
+ // app/api/draft-mode is NOT here — the draft routes are CMS-agnostic
138
+ // (they resolve through @cms/draft) and only go when no CMS is chosen.
139
+ const SANITY_STUDIO_PATHS = ['app/(studio)', 'sanity.config.ts', 'sanity'];
112
140
  const SANITY_DEPS = ['next-sanity', 'sanity', '@sanity/vision'];
113
141
 
142
+ // NOTE • The two files the CLI rewires to the chosen adapter
143
+ const ADAPTER_ENTRIES = ['src/cms/index.ts', 'src/cms/draft.ts'];
144
+ const rewireAdapter = (root, adapter) => {
145
+ for (const rel of ADAPTER_ENTRIES) {
146
+ const p = path.join(root, rel);
147
+ if (fs.existsSync(p)) {
148
+ fs.writeFileSync(p, fs.readFileSync(p, 'utf8').replaceAll(`'./dato`, `'./${adapter}`));
149
+ }
150
+ }
151
+ };
152
+
114
153
  const CMS = {
115
154
  dato: {
116
155
  title: 'DatoCMS',
117
156
  prune: root => {
118
157
  rmrf(root, 'src/cms/sanity');
158
+ rmrf(root, 'src/cms/none');
119
159
  rmrf(root, 'docs/Sanity');
120
160
  for (const p of SANITY_STUDIO_PATHS) rmrf(root, p);
121
161
  stripDeps(root, SANITY_DEPS);
@@ -126,33 +166,47 @@ const CMS = {
126
166
  title: 'Sanity',
127
167
  prune: root => {
128
168
  rmrf(root, 'src/cms/dato');
169
+ rmrf(root, 'src/cms/none');
129
170
  rmrf(root, 'docs/DatoCMS');
130
171
  stripDeps(root, ['@datocms/cda-client', 'react-datocms', 'graphql', 'graphql-tag']);
131
172
  stripEnvBlock(root, '# CMS — DatoCMS');
132
- // Rewire the adapter entry to the kept adapter
133
- const entry = path.join(root, 'src/cms/index.ts');
134
- if (fs.existsSync(entry)) {
135
- fs.writeFileSync(entry, fs.readFileSync(entry, 'utf8').replace(`'./dato'`, `'./sanity'`));
136
- }
173
+ rewireAdapter(root, 'sanity');
137
174
  },
138
175
  },
139
176
  none: {
140
177
  title: 'No CMS',
178
+ // NOTE • Same shape as the other choices — the '@cms' seam survives,
179
+ // rewired to the stub adapter (src/cms/none), so app code and the
180
+ // tsconfig alias stay valid and a CMS can be wired up later. Only the
181
+ // CMS-specific files go: adapters, draft-mode routes, studio, docs.
141
182
  prune: root => {
142
- rmrf(root, 'src/cms');
183
+ rmrf(root, 'src/cms/dato');
184
+ rmrf(root, 'src/cms/sanity');
185
+ rmrf(root, 'src/cms/draft.ts');
143
186
  rmrf(root, 'docs/Sanity');
144
187
  rmrf(root, 'docs/DatoCMS');
145
- rmrf(root, 'docs/CMS.md');
188
+ rmrf(root, 'app/api/draft-mode');
146
189
  for (const p of SANITY_STUDIO_PATHS) rmrf(root, p);
147
190
  stripDeps(root, [...SANITY_DEPS, '@datocms/cda-client', 'react-datocms', 'graphql', 'graphql-tag']);
148
191
  stripEnvBlock(root, '# CMS — Sanity');
149
192
  stripEnvBlock(root, '# CMS — DatoCMS');
193
+ rewireAdapter(root, 'none');
150
194
  },
151
195
  },
152
196
  };
153
197
 
154
- // NOTE • Repo-only cargo that should never land in a scaffolded project
155
- const REPO_CARGO = ['tackl', '.github', 'bun.lock'];
198
+ // NOTE • Repo-only cargo that should never land in a scaffolded project.
199
+ // .github is pruned file-by-file — the CI workflow (.github/workflows) ships
200
+ // with every scaffold so quality gates run from the first push.
201
+ const REPO_CARGO = [
202
+ 'tackl',
203
+ 'docs/plans',
204
+ '.github/FUNDING.yml',
205
+ '.github/ISSUE_TEMPLATE.md',
206
+ '.github/PULL_REQUEST_TEMPLATE.md',
207
+ '.github/workflows/release.yml',
208
+ 'bun.lock',
209
+ ];
156
210
 
157
211
  async function main() {
158
212
  const args = process.argv.slice(2);
@@ -370,7 +424,7 @@ ${pc.bold('Repository:')} https://github.com/${OWNER}/${REPO}
370
424
  console.log(` ${step++}) cp .env.example .env # then fill in your values`);
371
425
  }
372
426
  if (!doInstall) console.log(` ${step++}) bun install`);
373
- console.log(` ${step++}) bun run dev # a first-run setup wizard walks you through your theme`);
427
+ console.log(` ${step++}) bun run dev # then set your tokens in src/theme`);
374
428
  if (cms === 'sanity') console.log(` ${step}) open http://localhost:3000/studio to start editing content`);
375
429
  }
376
430
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tackl",
3
- "version": "2.2.0",
4
- "description": "Scaffold a Tackl project into the current directory — Next.js, styled-components, Waffl grid, DatoCMS/Sanity adapters",
3
+ "version": "2.5.0",
4
+ "description": "Scaffold a Tackl project into the current directory — Next.js, SCSS CSS Modules, Waffl grid, DatoCMS/Sanity adapters",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "tackl": "bin/tackl.js"
@@ -17,7 +17,8 @@
17
17
  "template",
18
18
  "grid",
19
19
  "css",
20
- "styled-components",
20
+ "scss",
21
+ "css-modules",
21
22
  "typescript"
22
23
  ],
23
24
  "author": "joebentaylor1995",
@@ -27,11 +28,11 @@
27
28
  },
28
29
  "repository": {
29
30
  "type": "git",
30
- "url": "git+https://github.com/12-studio/tackl.git"
31
+ "url": "git+https://github.com/12-Studio/Tackl.git"
31
32
  },
32
- "homepage": "https://github.com/12-studio/tackl",
33
+ "homepage": "https://github.com/12-Studio/Tackl",
33
34
  "bugs": {
34
- "url": "https://github.com/12-studio/tackl/issues"
35
+ "url": "https://github.com/12-Studio/Tackl/issues"
35
36
  },
36
37
  "files": [
37
38
  "bin/",
@@ -40,6 +41,6 @@
40
41
  "dependencies": {
41
42
  "picocolors": "^1.1.1",
42
43
  "prompts": "^2.4.2",
43
- "tar": "^7.5.1"
44
+ "tar": "^7.5.22"
44
45
  }
45
46
  }