@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
|
-
|
|
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'])
|