template-git-repo 0.1.22 → 0.1.24

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 CHANGED
@@ -9,6 +9,15 @@
9
9
  <a href="https://www.npmjs.com/package/template-git-repo"><img src="https://img.shields.io/npm/types/template-git-repo" alt="TypeScript types" /></a>
10
10
  <a href="https://packagephobia.com/result?p=template-git-repo"><img src="https://packagephobia.com/badge?p=template-git-repo" alt="Install size" /></a>
11
11
  <a href="https://app.codecov.io/gh/OpenSourceAGI/dev-tools-starter-agent/flags"><img src="https://img.shields.io/codecov/c/github/OpenSourceAGI/dev-tools-starter-agent?flag=template-git-repo&label=template-git-repo%20coverage&logo=codecov&logoColor=white" alt="Coverage" /></a>
12
+ <br />
13
+ <a href="https://github.com/OpenSourceAGI/dev-tools-starter-agent/stargazers"><img src="https://img.shields.io/github/stars/OpenSourceAGI/dev-tools-starter-agent" alt="GitHub Stars" /></a>
14
+ <a href="https://github.com/OpenSourceAGI/dev-tools-starter-agent/issues"><img src="https://img.shields.io/github/issues/OpenSourceAGI/dev-tools-starter-agent?logo=github" alt="GitHub Issues" /></a>
15
+ <a href="https://github.com/OpenSourceAGI/dev-tools-starter-agent/pulls"><img src="https://img.shields.io/github/issues-pr/OpenSourceAGI/dev-tools-starter-agent?logo=github&label=PRs" alt="Open Pull Requests" /></a>
16
+ <a href="https://github.com/OpenSourceAGI/dev-tools-starter-agent/pulls?q=is%3Apr+is%3Aclosed"><img src="https://img.shields.io/github/issues-pr-closed/OpenSourceAGI/dev-tools-starter-agent?logo=github&label=PRs%20merged&color=8957e5" alt="Merged Pull Requests" /></a>
17
+ <a href="https://github.com/OpenSourceAGI/dev-tools-starter-agent/discussions"><img src="https://img.shields.io/github/discussions/OpenSourceAGI/dev-tools-starter-agent" alt="GitHub Discussions" /></a>
18
+ <a href="https://github.com/OpenSourceAGI/dev-tools-starter-agent/commits/master/"><img src="https://img.shields.io/github/last-commit/OpenSourceAGI/dev-tools-starter-agent.svg" alt="GitHub last commit" /></a>
19
+ <br />
20
+ <img src="https://img.shields.io/badge/Bun-14151A?logo=bun&logoColor=white" alt="Bun" /> <img src="https://img.shields.io/badge/Vitest-6E9F18?logo=vitest&logoColor=white" alt="Vitest" />
12
21
  </p>
13
22
  <!-- template-git-repo:badges:end -->
14
23
 
@@ -53,6 +53,7 @@ Context (all optional — detected where possible)
53
53
 
54
54
  Badge inputs (each enables the badge that needs it)
55
55
  --doi <10.5281/...> --docs <url> --api <url>
56
+ --website <url>
56
57
  --youtube <url> --uptime <url> --test-report <url>
57
58
  --discord-id <id> --discord-invite <url>
58
59
  --stack <A,B,C> --cloudflare-deploy
@@ -108,6 +109,7 @@ function overridesFrom(flags) {
108
109
  npmPackage: text('npm-package'),
109
110
  workflowFile: text('workflow'),
110
111
  doi: text('doi'),
112
+ websiteUrl: text('website'),
111
113
  docsUrl: text('docs'),
112
114
  apiUrl: text('api'),
113
115
  youtubeUrl: text('youtube'),
package/docs/BADGES.md CHANGED
@@ -23,12 +23,20 @@ package or finish setting up Codecov.
23
23
  | --- | --- | --- |
24
24
  | [Zenodo DOI](#doi) `doi` | doi | `--doi <10.5281/zenodo.NNNNNNN>` |
25
25
  | [Ask DeepWiki](#deepwiki) `deepwiki` | — | on by default |
26
+ | [Live app](#website) `website` | websiteUrl | `--website <url>` |
26
27
  | [Documentation](#docs) `docs` | docsUrl | `--docs <url>` |
27
28
  | [API reference](#api) `api` | apiUrl | `--api <url>` |
28
29
  | [YouTube demo](#youtube) `youtube` | youtubeUrl | `--youtube <url>` |
29
30
  | [Deploy to Cloudflare Workers](#deploy-cloudflare) `deploy-cloudflare` | cloudflareDeploy | `--cloudflare-deploy` |
30
31
  | [Open in StackBlitz](#stackblitz) `stackblitz` | stackblitzUrl | `--stackblitz <url>` |
32
+ | [Open in GitHub Codespaces](#codespaces) `codespaces` | — | on by default |
31
33
  | [GitHub stars](#stars) `stars` | — | on by default |
34
+ | [GitHub forks](#forks) `forks` | — | on by default |
35
+ | [Contributors](#contributors) `contributors` | — | on by default |
36
+ | [Open issues](#issues) `issues` | — | on by default |
37
+ | [Open pull requests](#pull-requests) `pull-requests` | — | on by default |
38
+ | [Merged pull requests](#prs-merged) `prs-merged` | — | on by default |
39
+ | [GitHub Discussions](#discussions) `discussions` | — | on by default |
32
40
  | [npm monthly downloads](#npm-downloads) `npm-downloads` | npmPackage | `--npm-package <name>` |
33
41
  | [npm version](#npm-version) `npm-version` | npmPackage | `--npm-package <name>` |
34
42
  | [npm total downloads](#npm-total-downloads) `npm-total-downloads` | npmPackage | `--npm-package <name>` |
@@ -64,6 +72,14 @@ Nothing to configure for a public repo — visit deepwiki.com/<owner>/<repo> onc
64
72
 
65
73
  Included by default — no configuration needed.
66
74
 
75
+ ### website
76
+
77
+ **Live app**
78
+
79
+ The deployed thing, not the repo. It is first in the row on purpose: a visitor who can click through to a running app decides in seconds whether to read the rest. Static badge — nothing checks that the URL is up, which is what the uptime badge is for.
80
+
81
+ Enable with: `--website <url>`
82
+
67
83
  ### docs
68
84
 
69
85
  **Documentation**
@@ -104,6 +120,14 @@ Nothing to configure — StackBlitz imports a public repo straight from a URL: `
104
120
 
105
121
  Enable with: `--stackblitz <url>`
106
122
 
123
+ ### codespaces
124
+
125
+ **Open in GitHub Codespaces**
126
+
127
+ Nothing to configure — the link boots the repo in a default container. Add a `.devcontainer/devcontainer.json` if the default image is missing something your install needs, because the visitor sees the failure, not you. Codespaces bills the visitor's own free hours, so the button costs you nothing.
128
+
129
+ Included by default — no configuration needed.
130
+
107
131
  ## Quality — is it working, is it tested, is it shipped
108
132
 
109
133
  ### npm-downloads
@@ -196,6 +220,54 @@ Nothing to configure. Public repos only — shields.io cannot read a private rep
196
220
 
197
221
  Included by default — no configuration needed.
198
222
 
223
+ ### forks
224
+
225
+ **GitHub forks**
226
+
227
+ Nothing to configure. Counts direct forks only, not forks of forks, so it undercounts a repo that has been forked along a chain. Public repos only.
228
+
229
+ Included by default — no configuration needed.
230
+
231
+ ### contributors
232
+
233
+ **Contributors**
234
+
235
+ Nothing to configure, but it counts commit authors GitHub could match to an account — commits made with an unregistered email are attributed to nobody and do not appear here. Use `contributors-anon` instead if your history has many of those.
236
+
237
+ Included by default — no configuration needed.
238
+
239
+ ### issues
240
+
241
+ **Open issues**
242
+
243
+ Nothing to configure beyond having Issues enabled (Settings → Features). Read it as a work-in-flight number, not a defect count: shields.io asks GitHub Search, which counts pull requests as issues unless the badge path excludes them — this one uses the `/issues/` path, which already does.
244
+
245
+ Included by default — no configuration needed.
246
+
247
+ ### pull-requests
248
+
249
+ **Open pull requests**
250
+
251
+ Nothing to configure. Counts open PRs including drafts. Pair it with the merged count below — an open count alone reads the same whether the queue moves in a day or has been stuck for a year.
252
+
253
+ Included by default — no configuration needed.
254
+
255
+ ### prs-merged
256
+
257
+ **Merged pull requests**
258
+
259
+ Nothing to configure. shields.io has no "merged" endpoint — `issues-pr-closed` counts every PR that is no longer open, so PRs closed without merging are in this number too. On a repo that closes a lot of stale PRs, label it "PRs closed" instead of overstating what landed.
260
+
261
+ Included by default — no configuration needed.
262
+
263
+ ### discussions
264
+
265
+ **GitHub Discussions**
266
+
267
+ Discussions has to be turned on (Settings → Features → Discussions) or the badge reads "repo not found" — which looks identical to a private repo. Turn it on before adding the badge, not after.
268
+
269
+ Included by default — no configuration needed.
270
+
199
271
  ### commit-activity
200
272
 
201
273
  **Commit activity**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "template-git-repo",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "One command to set up a repo with GitHub Actions, README badges, Turborepo and Codecov — the CI setup from qwksearch-research-agent, as a template",
5
5
  "type": "module",
6
6
  "bin": {
package/src/badges.js CHANGED
@@ -63,6 +63,19 @@ export const BADGES = [
63
63
  setup:
64
64
  'Nothing to configure for a public repo — visit deepwiki.com/<owner>/<repo> once to trigger the first index. Private repos need the DeepWiki GitHub App installed.',
65
65
  },
66
+ {
67
+ id: 'website',
68
+ title: 'Live app',
69
+ group: 'identity',
70
+ needs: ['websiteUrl'],
71
+ alt: 'Website',
72
+ href: (c) => c.websiteUrl,
73
+ img: () =>
74
+ shieldsBadge('App', 'blueviolet', { style: 'for-the-badge', logo: 'googlechrome', logoColor: 'white' }),
75
+ setup:
76
+ 'The deployed thing, not the repo. It is first in the row on purpose: a visitor who can click through to a running app decides in seconds whether to read the rest. Static badge — nothing checks that the URL is up, which is what the uptime badge is for.',
77
+ height: '20px',
78
+ },
66
79
  {
67
80
  id: 'docs',
68
81
  title: 'Documentation',
@@ -119,6 +132,18 @@ export const BADGES = [
119
132
  'Nothing to configure — StackBlitz imports a public repo straight from a URL: `https://stackblitz.com/github/<owner>/<repo>/tree/<branch>/<path>`. Point it at a directory that boots on its own (a package with its own package.json, or an `examples/` folder), because StackBlitz installs from the manifest at that path and a bare monorepo root will not run. Node APIs need the WebContainer runtime, which means the directory must be ESM and its deps must be installable from npm.',
120
133
  height: '20px',
121
134
  },
135
+ {
136
+ id: 'codespaces',
137
+ title: 'Open in GitHub Codespaces',
138
+ group: 'identity',
139
+ needs: [],
140
+ alt: 'Open in GitHub Codespaces',
141
+ href: (c) => `https://codespaces.new/${c.repoSlug}`,
142
+ img: () => 'https://github.com/codespaces/badge.svg',
143
+ setup:
144
+ 'Nothing to configure — the link boots the repo in a default container. Add a `.devcontainer/devcontainer.json` if the default image is missing something your install needs, because the visitor sees the failure, not you. Codespaces bills the visitor\'s own free hours, so the button costs you nothing.',
145
+ height: '20px',
146
+ },
122
147
  {
123
148
  id: 'stars',
124
149
  title: 'GitHub stars',
@@ -129,6 +154,73 @@ export const BADGES = [
129
154
  img: (c) => `https://img.shields.io/github/stars/${c.repoSlug}`,
130
155
  setup: 'Nothing to configure. Public repos only — shields.io cannot read a private repo.',
131
156
  },
157
+ {
158
+ id: 'forks',
159
+ title: 'GitHub forks',
160
+ group: 'community',
161
+ needs: [],
162
+ alt: 'GitHub Forks',
163
+ href: (c) => `https://github.com/${c.repoSlug}/forks`,
164
+ img: (c) => `https://img.shields.io/github/forks/${c.repoSlug}`,
165
+ setup:
166
+ 'Nothing to configure. Counts direct forks only, not forks of forks, so it undercounts a repo that has been forked along a chain. Public repos only.',
167
+ },
168
+ {
169
+ id: 'contributors',
170
+ title: 'Contributors',
171
+ group: 'community',
172
+ needs: [],
173
+ alt: 'Contributors',
174
+ href: (c) => `https://github.com/${c.repoSlug}/graphs/contributors`,
175
+ img: (c) => `https://img.shields.io/github/contributors/${c.repoSlug}`,
176
+ setup:
177
+ 'Nothing to configure, but it counts commit authors GitHub could match to an account — commits made with an unregistered email are attributed to nobody and do not appear here. Use `contributors-anon` instead if your history has many of those.',
178
+ },
179
+ {
180
+ id: 'issues',
181
+ title: 'Open issues',
182
+ group: 'community',
183
+ needs: [],
184
+ alt: 'GitHub Issues',
185
+ href: (c) => `https://github.com/${c.repoSlug}/issues`,
186
+ img: (c) => `https://img.shields.io/github/issues/${c.repoSlug}?logo=github`,
187
+ setup:
188
+ 'Nothing to configure beyond having Issues enabled (Settings → Features). Read it as a work-in-flight number, not a defect count: shields.io asks GitHub Search, which counts pull requests as issues unless the badge path excludes them — this one uses the `/issues/` path, which already does.',
189
+ },
190
+ {
191
+ id: 'pull-requests',
192
+ title: 'Open pull requests',
193
+ group: 'community',
194
+ needs: [],
195
+ alt: 'Open Pull Requests',
196
+ href: (c) => `https://github.com/${c.repoSlug}/pulls`,
197
+ img: (c) => `https://img.shields.io/github/issues-pr/${c.repoSlug}?logo=github&label=PRs`,
198
+ setup:
199
+ 'Nothing to configure. Counts open PRs including drafts. Pair it with the merged count below — an open count alone reads the same whether the queue moves in a day or has been stuck for a year.',
200
+ },
201
+ {
202
+ id: 'prs-merged',
203
+ title: 'Merged pull requests',
204
+ group: 'community',
205
+ needs: [],
206
+ alt: 'Merged Pull Requests',
207
+ href: (c) => `https://github.com/${c.repoSlug}/pulls?q=is%3Apr+is%3Aclosed`,
208
+ img: (c) =>
209
+ `https://img.shields.io/github/issues-pr-closed/${c.repoSlug}?logo=github&label=PRs%20merged&color=8957e5`,
210
+ setup:
211
+ 'Nothing to configure. shields.io has no "merged" endpoint — `issues-pr-closed` counts every PR that is no longer open, so PRs closed without merging are in this number too. On a repo that closes a lot of stale PRs, label it "PRs closed" instead of overstating what landed.',
212
+ },
213
+ {
214
+ id: 'discussions',
215
+ title: 'GitHub Discussions',
216
+ group: 'community',
217
+ needs: [],
218
+ alt: 'GitHub Discussions',
219
+ href: (c) => `https://github.com/${c.repoSlug}/discussions`,
220
+ img: (c) => `https://img.shields.io/github/discussions/${c.repoSlug}`,
221
+ setup:
222
+ 'Discussions has to be turned on (Settings → Features → Discussions) or the badge reads "repo not found" — which looks identical to a private repo. Turn it on before adding the badge, not after.',
223
+ },
132
224
  {
133
225
  id: 'npm-downloads',
134
226
  title: 'npm monthly downloads',
@@ -309,18 +401,90 @@ export const BADGES = [
309
401
  },
310
402
  ];
311
403
 
312
- /** Brand colors for the stack chips, so the common ones look right by default. */
404
+ /**
405
+ * Brand colors for the stack chips, so the common ones look right by default.
406
+ *
407
+ * Keyed by the lowercased chip name. Anything not listed falls back to a
408
+ * neutral grey, which is a perfectly good chip — this map is a nicety, not a
409
+ * gate on what you can name.
410
+ */
313
411
  const STACK_COLORS = {
314
412
  claude: 'D97757',
315
413
  cloudflare: 'F38020',
414
+ 'cloudflare workers': 'F38020',
415
+ d1: 'F38020',
416
+ r2: 'F38020',
417
+ wrangler: 'F38020',
316
418
  'next.js': 'black',
317
419
  react: '20232A',
420
+ svelte: 'FF3E00',
421
+ 'vue.js': '4FC08D',
318
422
  typescript: '3178C6',
423
+ javascript: 'F7DF1E',
424
+ 'node.js': '5FA04E',
319
425
  bun: '14151A',
426
+ npm: 'CB3837',
427
+ pnpm: 'F69220',
428
+ turborepo: 'EF4444',
320
429
  vite: '646CFF',
430
+ vitest: '6E9F18',
431
+ jest: 'C21325',
432
+ playwright: '2EAD33',
433
+ biome: '60A5FA',
321
434
  vercel: 'black',
322
435
  postgresql: '4169E1',
436
+ sqlite: '003B57',
437
+ 'drizzle orm': 'C5F74F',
323
438
  tailwindcss: '06B6D4',
439
+ 'tailwind css': '06B6D4',
440
+ 'shadcn/ui': '000000',
441
+ 'radix ui': '161618',
442
+ 'better-auth': '000000',
443
+ hono: 'E36002',
444
+ stripe: '635BFF',
445
+ zod: '3E67B1',
446
+ tauri: '24C8D8',
447
+ electron: '47848F',
448
+ prosemirror: '000000',
449
+ tiptap: '000000',
450
+ fumadocs: '000000',
451
+ mcp: '000000',
452
+ 'vercel ai sdk': 'black',
453
+ openai: '412991',
454
+ python: '3776AB',
455
+ docker: '2496ED',
456
+ 'github actions': '2088FF',
457
+ };
458
+
459
+ /**
460
+ * simple-icons slugs for chips whose name does not reduce to one.
461
+ *
462
+ * `?logo=` takes a simpleicons.org slug, and the derivation below (lowercase,
463
+ * drop dots, spaces and slashes) gets most of them right — `Next.js` really is
464
+ * `nextjs`. It gets a handful wrong, and a wrong slug renders a chip with a
465
+ * blank square where the logo should be, so those are written out.
466
+ *
467
+ * A name with no icon at all (`better-auth`, `MCP`) maps to the empty string:
468
+ * the chip renders as plain text rather than carrying someone else's logo.
469
+ */
470
+ const STACK_LOGOS = {
471
+ 'cloudflare workers': 'cloudflareworkers',
472
+ d1: 'cloudflare',
473
+ r2: 'cloudflare',
474
+ 'drizzle orm': 'drizzle',
475
+ 'tailwind css': 'tailwindcss',
476
+ 'vue.js': 'vuedotjs',
477
+ 'node.js': 'nodedotjs',
478
+ 'next.js': 'nextdotjs',
479
+ 'radix ui': 'radixui',
480
+ 'github actions': 'githubactions',
481
+ 'vercel ai sdk': 'vercel',
482
+ claude: 'claude',
483
+ 'better-auth': '',
484
+ mcp: '',
485
+ prosemirror: '',
486
+ fumadocs: '',
487
+ zod: 'zod',
324
488
  };
325
489
 
326
490
  /**
@@ -328,10 +492,13 @@ const STACK_COLORS = {
328
492
  * @returns {string} an `<img>` chip for one stack entry
329
493
  */
330
494
  export function stackChip(name) {
331
- const key = name.trim().toLowerCase();
495
+ const label = name.trim();
496
+ const key = label.toLowerCase();
332
497
  const color = STACK_COLORS[key] ?? '555555';
333
- const logo = key.replace(/[.\s]/g, '');
334
- return `<img src="${shieldsBadge(name.trim(), color, { logo, logoColor: 'white' })}" alt="${name.trim()}" />`;
498
+ const logo = STACK_LOGOS[key] ?? key.replace(/[.\s/]/g, '');
499
+
500
+ const params = logo ? { logo, logoColor: 'white' } : {};
501
+ return `<img src="${shieldsBadge(label, color, params)}" alt="${label}" />`;
335
502
  }
336
503
 
337
504
  /**
package/src/context.js CHANGED
@@ -160,6 +160,146 @@ export function detectNpmPackage(root, packagesDir) {
160
160
  return undefined;
161
161
  }
162
162
 
163
+ /**
164
+ * Dependency name -> the stack chip it implies, in the order the chips should
165
+ * read: language and runtime first, then framework, then the things bolted onto
166
+ * it, then the test runner.
167
+ *
168
+ * Keyed on the dependency rather than on a guess about the project, because the
169
+ * manifest is the one place that cannot lie about what a package is built with.
170
+ * A hand-written stack row goes stale the week someone swaps a framework out and
171
+ * nobody remembers the README says otherwise.
172
+ *
173
+ * Several entries collapse many packages into one chip — a ProseMirror app
174
+ * depends on eight `prosemirror-*` modules and is built with ProseMirror once.
175
+ * Duplicates are removed by `detectStack`, so listing every alias is safe.
176
+ */
177
+ const STACK_BY_DEPENDENCY = [
178
+ ['typescript', 'TypeScript'],
179
+ ['next', 'Next.js'],
180
+ ['react', 'React'],
181
+ ['svelte', 'Svelte'],
182
+ ['vue', 'Vue.js'],
183
+ ['@tauri-apps/api', 'Tauri'],
184
+ ['@tauri-apps/cli', 'Tauri'],
185
+ ['electron', 'Electron'],
186
+ ['wrangler', 'Cloudflare Workers'],
187
+ ['hono', 'Hono'],
188
+ ['tailwindcss', 'Tailwind CSS'],
189
+ ['@radix-ui/react-dialog', 'shadcn/ui'],
190
+ ['drizzle-orm', 'Drizzle ORM'],
191
+ ['better-auth', 'better-auth'],
192
+ ['stripe', 'Stripe'],
193
+ ['ai', 'Vercel AI SDK'],
194
+ ['@modelcontextprotocol/sdk', 'MCP'],
195
+ ['@tiptap/core', 'TipTap'],
196
+ ['prosemirror-state', 'ProseMirror'],
197
+ ['prosemirror-view', 'ProseMirror'],
198
+ ['zod', 'Zod'],
199
+ ['fumadocs-ui', 'Fumadocs'],
200
+ ['vite', 'Vite'],
201
+ ['turbo', 'Turborepo'],
202
+ ['vitest', 'Vitest'],
203
+ ['jest', 'Jest'],
204
+ ['@playwright/test', 'Playwright'],
205
+ ];
206
+
207
+ /** The chip for each package manager, so the runtime shows up even with no deps. */
208
+ const STACK_BY_PACKAGE_MANAGER = { bun: 'Bun', pnpm: 'pnpm', yarn: 'Yarn', npm: 'npm' };
209
+
210
+ /**
211
+ * De-duplicate chips and put them back in catalog order.
212
+ *
213
+ * Needed because the workspace union collects chips manifest by manifest, and
214
+ * alphabetical directory order is not a sensible reading order for a stack row —
215
+ * it lands on "Drizzle ORM, Vitest, TypeScript, React". Anything not in the
216
+ * catalog (a `prepend` like Claude) keeps its position at the front.
217
+ *
218
+ * @param {string[]} chips
219
+ * @returns {string[]}
220
+ */
221
+ function orderChips(chips) {
222
+ const order = STACK_BY_DEPENDENCY.map(([, chip]) => chip);
223
+ const rank = (chip) => {
224
+ const index = order.indexOf(chip);
225
+ return index === -1 ? -1 : index;
226
+ };
227
+
228
+ const unique = [...new Set(chips)];
229
+ const leading = unique.filter((chip) => rank(chip) === -1);
230
+ const known = unique.filter((chip) => rank(chip) !== -1).sort((a, b) => rank(a) - rank(b));
231
+
232
+ return [...leading, ...known];
233
+ }
234
+
235
+ /**
236
+ * The stack chips for one manifest, read off its dependencies.
237
+ *
238
+ * Returns a comma-separated string because that is what the `stack` badge takes
239
+ * and what `--stack` passes on the command line. An empty result means the
240
+ * manifest declared nothing recognizable, and the badge is skipped — better
241
+ * than a row asserting a framework the package does not use.
242
+ *
243
+ * @param {Record<string, any> | null} manifest
244
+ * @param {{ prepend?: string[], packageManager?: string }} [options]
245
+ * @returns {string | undefined}
246
+ */
247
+ export function detectStack(manifest, options = {}) {
248
+ const { prepend = [], packageManager } = options;
249
+
250
+ const declared = new Set([
251
+ ...Object.keys(manifest?.dependencies ?? {}),
252
+ ...Object.keys(manifest?.devDependencies ?? {}),
253
+ ...Object.keys(manifest?.peerDependencies ?? {}),
254
+ ]);
255
+
256
+ const chips = [...prepend];
257
+
258
+ const runtime = packageManager ? STACK_BY_PACKAGE_MANAGER[packageManager] : undefined;
259
+ if (runtime) chips.push(runtime);
260
+
261
+ for (const [dependency, chip] of STACK_BY_DEPENDENCY) {
262
+ if (declared.has(dependency)) chips.push(chip);
263
+ }
264
+
265
+ const ordered = orderChips(chips);
266
+ return ordered.length > 0 ? ordered.join(',') : undefined;
267
+ }
268
+
269
+ /**
270
+ * The stack for a whole workspace: the union of every member's chips.
271
+ *
272
+ * A monorepo root manifest lists turbo and little else, so reading it alone
273
+ * describes the build tooling and none of the product. This walks the workspace
274
+ * directory instead, which is what the root README's stack row should say.
275
+ *
276
+ * @param {string} root
277
+ * @param {{ packagesDirs?: string[], prepend?: string[], packageManager?: string }} [options]
278
+ * @returns {string | undefined}
279
+ */
280
+ export function detectWorkspaceStack(root, options = {}) {
281
+ const { packagesDirs = ['packages', 'apps'], prepend = [], packageManager } = options;
282
+
283
+ const manifests = [readJson(path.join(root, 'package.json'))];
284
+
285
+ for (const dir of packagesDirs) {
286
+ const parent = path.join(root, dir);
287
+ if (!fs.existsSync(parent)) continue;
288
+
289
+ for (const entry of fs.readdirSync(parent, { withFileTypes: true })) {
290
+ if (!entry.isDirectory()) continue;
291
+ manifests.push(readJson(path.join(parent, entry.name, 'package.json')));
292
+ }
293
+ }
294
+
295
+ const merged = manifests.filter(Boolean).flatMap((manifest) =>
296
+ (detectStack(manifest, { packageManager }) ?? '').split(',').filter(Boolean),
297
+ );
298
+
299
+ const ordered = orderChips([...prepend, ...merged]);
300
+ return ordered.length > 0 ? ordered.join(',') : undefined;
301
+ }
302
+
163
303
  /**
164
304
  * Everything the templates and badges need, detected then overridden by flags.
165
305
  *
@@ -194,6 +334,7 @@ export function buildContext(options = {}) {
194
334
  // Everything below has no sensible default — a badge that needs one of
195
335
  // these is skipped until it is passed in.
196
336
  doi: overrides.doi,
337
+ websiteUrl: overrides.websiteUrl,
197
338
  docsUrl: overrides.docsUrl,
198
339
  apiUrl: overrides.apiUrl,
199
340
  youtubeUrl: overrides.youtubeUrl,
package/src/index.js CHANGED
@@ -19,6 +19,8 @@ export {
19
19
  detectNpmPackage,
20
20
  detectPackageManager,
21
21
  detectPackagesDir,
22
+ detectStack,
23
+ detectWorkspaceStack,
22
24
  findRepoRoot,
23
25
  parseRepoSlug,
24
26
  substitute,