@biffo/cli 0.198.4 → 0.198.6

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.
@@ -1,9 +1,16 @@
1
1
  import type { Metadata } from 'next'
2
2
  import type { ReactNode } from 'react'
3
+ import { SIBLING_TITLE } from '@/lib/branding'
3
4
  import './globals.css'
4
5
 
5
6
  export const metadata: Metadata = {
6
- title: 'Sibling App',
7
+ // Derived from this sibling's own name at build time — see lib/branding.ts.
8
+ // `title.template` composes per-page titles for free: a page exporting
9
+ // `metadata = { title: 'Weekly' }` then renders "<Sibling> - Weekly".
10
+ title: {
11
+ default: SIBLING_TITLE,
12
+ template: `${SIBLING_TITLE} - %s`,
13
+ },
7
14
  }
8
15
 
9
16
  export default function RootLayout({ children }: { children: ReactNode }) {
@@ -0,0 +1,35 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { formatSiblingTitle } from './branding'
4
+
5
+ // Regression coverage for biffo-template#963: the skeleton's root layout used
6
+ // to hard-code `metadata.title = 'Sibling App'`, so every sibling scaffolded
7
+ // from it shipped that literal in its deployed <title>. The title is now
8
+ // derived from NEXT_PUBLIC_SIBLING_NAME (the same build-time variable
9
+ // `page.tsx` reads), and this is that derivation.
10
+ describe('formatSiblingTitle', () => {
11
+ it('capitalises a single-word sibling name', () => {
12
+ expect(formatSiblingTitle('reports')).toBe('Reports')
13
+ })
14
+
15
+ it('splits hyphenated and underscored names into words', () => {
16
+ expect(formatSiblingTitle('field-ops')).toBe('Field Ops')
17
+ expect(formatSiblingTitle('tabsii_crm')).toBe('Tabsii Crm')
18
+ expect(formatSiblingTitle('a-b-c')).toBe('A B C')
19
+ })
20
+
21
+ it('leaves an already-capitalised name alone', () => {
22
+ expect(formatSiblingTitle('Reports')).toBe('Reports')
23
+ })
24
+
25
+ it('tolerates stray separators without emitting empty words', () => {
26
+ expect(formatSiblingTitle('--field--ops--')).toBe('Field Ops')
27
+ })
28
+
29
+ it('never returns the old hard-coded placeholder, even with no name set', () => {
30
+ for (const missing of [undefined, '', ' ', '---']) {
31
+ expect(formatSiblingTitle(missing)).toBe('Sibling')
32
+ expect(formatSiblingTitle(missing)).not.toBe('Sibling App')
33
+ }
34
+ })
35
+ })
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Sibling branding (issue #963).
3
+ *
4
+ * The skeleton's root layout used to hard-code `metadata.title = 'Sibling App'`,
5
+ * and nothing at `biffo sibling create` time ever touched it — so every sibling
6
+ * ever scaffolded was born mislabeled in every browser tab, visibly in the
7
+ * deployed `out/index.html`, until a human noticed and fixed it by hand.
8
+ *
9
+ * Instead, derive it from the sibling's own name the same way `page.tsx`
10
+ * already does: the build-time `NEXT_PUBLIC_SIBLING_NAME`. That variable is
11
+ * already wired end to end — `biffo sibling create` writes the repo variable
12
+ * `PROJECT_NAME`, `.github/workflows/deploy.yml` passes it to the frontend
13
+ * build as `NEXT_PUBLIC_SIBLING_NAME`, and `apps/frontend/.env.example` is
14
+ * templated with the sibling's name at create time for local runs. The
15
+ * frontend builds with `output: 'export'`, so the value is baked into the
16
+ * static bundle; there is nothing to read at runtime.
17
+ *
18
+ * This mirrors `apps/portal/src/lib/branding.ts` upstream (issue #389), which
19
+ * solved the identical problem for the portal with a build-time env var rather
20
+ * than a scaffold-time source patch. Reading an env var beats regex-patching a
21
+ * `.tsx` file at create time: one mechanism, no fragile source rewrite, and it
22
+ * keeps working when the sibling is later renamed.
23
+ *
24
+ * The title is the sibling's OWN name only, never `<core project> - <sibling>`.
25
+ * The template is deliberately brand-agnostic and has no reliable way to know
26
+ * an instance's brand (as opposed to its `project.name`) at sibling-create
27
+ * time. A sibling that wants a brand prefix owns its own `layout.tsx` and can
28
+ * add one.
29
+ */
30
+
31
+ /**
32
+ * Turn a project slug into something presentable in a browser tab:
33
+ * `reports` → `Reports`, `field-ops` → `Field Ops`, `tabsii_crm` → `Tabsii Crm`.
34
+ *
35
+ * Deliberately dumb and deterministic — it does not try to guess acronyms
36
+ * (`crm` → `CRM`), because guessing wrong is worse than being plain and a
37
+ * sibling that cares can override the title outright.
38
+ */
39
+ export function formatSiblingTitle(rawName: string | undefined): string {
40
+ const words = (rawName ?? '')
41
+ .split(/[-_\s]+/)
42
+ .filter((word) => word.length > 0)
43
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
44
+
45
+ // Fallback for a frontend run with no env at all (a bare `pnpm dev` before
46
+ // `.env.example` is copied). Matches page.tsx's own fallback for the same
47
+ // variable, and is deliberately NOT the old 'Sibling App' literal.
48
+ return words.length > 0 ? words.join(' ') : 'Sibling'
49
+ }
50
+
51
+ export const SIBLING_TITLE = formatSiblingTitle(process.env['NEXT_PUBLIC_SIBLING_NAME'])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biffo/cli",
3
- "version": "0.198.4",
3
+ "version": "0.198.6",
4
4
  "description": "Biffo project scaffolding CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",