@meith/web 0.20.0 → 0.21.1

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/bin/forum-web.mjs CHANGED
@@ -306,10 +306,45 @@ function toPosixRelative(from, to) {
306
306
  return rel.startsWith('.') ? rel : `./${rel}`
307
307
  }
308
308
 
309
- export function rebaseGlobalsCssSources(css, cssDir, workspaceRootOverride) {
310
- return css.replace(/@source "((?:\.\.\/)+)([^"]+)";/g, (_match, _dots, tail) => {
311
- const target = join(workspaceRootOverride, ...tail.split('/'))
312
- return `@source "${toPosixRelative(cssDir, target)}";`
309
+ const SOURCE_LINE = /[ \t]*@source "((?:\.\.\/)+)([^"]+)";\n?/g
310
+ const BOARD_PACKAGES = ['node_modules', '@meith']
311
+
312
+ /**
313
+ * MEI-131: every `@source` in globals.css names a directory of *this*
314
+ * repository — `themes`, `plugins`, `packages/ui/src`. Rebasing them onto the
315
+ * board's workspace root is right for the one in-repo consumer (boards/stock,
316
+ * whose FORUM_WORKSPACE_ROOT is this repo) and wrong for every scaffolded
317
+ * board, where none of those directories exist: the same code is installed
318
+ * under `node_modules/@meith` instead. Tailwind does not fail on a `@source`
319
+ * that resolves to nothing — it builds green and emits no utilities for
320
+ * anything it could not scan — so a board deployed this way served a page
321
+ * with no styling at all.
322
+ *
323
+ * A source whose target is really there is kept and rebased as before. One
324
+ * that is not is dropped in favour of a single source over the installed
325
+ * packages, which covers the themes, `@meith/ui` and the plugins together.
326
+ * Tailwind ignores `node_modules` when detecting sources itself, but honours
327
+ * it when named here, and follows the symlinks a pnpm install leaves — both
328
+ * confirmed against the version this app pins.
329
+ */
330
+ export function rebaseGlobalsCssSources(css, cssDir, workspaceRoot, exists = existsSync) {
331
+ const rebased = []
332
+ let missing = false
333
+
334
+ for (const [, , tail] of css.matchAll(SOURCE_LINE)) {
335
+ const target = join(workspaceRoot, ...tail.split('/'))
336
+ if (exists(target)) rebased.push(toPosixRelative(cssDir, target))
337
+ else missing = true
338
+ }
339
+
340
+ const packages = join(workspaceRoot, ...BOARD_PACKAGES)
341
+ if (missing && exists(packages)) rebased.push(toPosixRelative(cssDir, packages))
342
+
343
+ let written = false
344
+ return css.replace(SOURCE_LINE, () => {
345
+ if (written) return ''
346
+ written = true
347
+ return rebased.map((source) => `@source "${source}";`).join('\n') + '\n'
313
348
  })
314
349
  }
315
350
 
@@ -9,6 +9,8 @@ import { afterAll, describe, expect, it } from 'vitest'
9
9
  // @ts-expect-error forum-web.mjs ships untyped, imported directly for its pure export
10
10
  import { rebaseGlobalsCssSources } from './forum-web.mjs'
11
11
 
12
+ const ALL_PRESENT = () => true
13
+
12
14
  describe('rebaseGlobalsCssSources', () => {
13
15
  it('rebases every @source line against the real workspace root', () => {
14
16
  const css = [
@@ -25,7 +27,7 @@ describe('rebaseGlobalsCssSources', () => {
25
27
  const cssDir = '/repo/boards/stock/.meith/app/src/styles'
26
28
  const workspaceRoot = '/repo'
27
29
 
28
- const rewritten = rebaseGlobalsCssSources(css, cssDir, workspaceRoot)
30
+ const rewritten = rebaseGlobalsCssSources(css, cssDir, workspaceRoot, ALL_PRESENT)
29
31
 
30
32
  expect(rewritten).toContain('@source "../../../../../../themes";')
31
33
  expect(rewritten).toContain('@source "../../../../../../plugins";')
@@ -49,12 +51,22 @@ describe('rebaseGlobalsCssSources', () => {
49
51
 
50
52
  it('leaves the shipped globals.css byte for byte alone at the default depth', () => {
51
53
  expect(
52
- rebaseGlobalsCssSources(shippedGlobalsCss, '/board/.meith/app/src/styles', '/board'),
54
+ rebaseGlobalsCssSources(
55
+ shippedGlobalsCss,
56
+ '/board/.meith/app/src/styles',
57
+ '/board',
58
+ ALL_PRESENT,
59
+ ),
53
60
  ).toBe(shippedGlobalsCss)
54
61
  })
55
62
 
56
63
  it('rebases every shipped @source against the board root at depth zero', () => {
57
- const rewritten = rebaseGlobalsCssSources(shippedGlobalsCss, '/board/src/styles', '/board')
64
+ const rewritten = rebaseGlobalsCssSources(
65
+ shippedGlobalsCss,
66
+ '/board/src/styles',
67
+ '/board',
68
+ ALL_PRESENT,
69
+ )
58
70
  const sources = [...rewritten.matchAll(/@source "([^"]+)";/g)].map((match) => match[1])
59
71
 
60
72
  expect(sources.length).toBeGreaterThan(0)
@@ -65,9 +77,83 @@ describe('rebaseGlobalsCssSources', () => {
65
77
 
66
78
  it('is a no-op for a file with no @source lines', () => {
67
79
  const css = '@import "tailwindcss";\n.foo { color: red; }\n'
68
- expect(rebaseGlobalsCssSources(css, '/repo/boards/stock/.meith/app/src/styles', '/repo')).toBe(
69
- css,
70
- )
80
+ expect(
81
+ rebaseGlobalsCssSources(
82
+ css,
83
+ '/repo/boards/stock/.meith/app/src/styles',
84
+ '/repo',
85
+ ALL_PRESENT,
86
+ ),
87
+ ).toBe(css)
88
+ })
89
+
90
+ /**
91
+ * MEI-131. A scaffolded board has none of the directories these sources
92
+ * name — the same code is installed under `node_modules/@meith` — and
93
+ * Tailwind emits no utilities for a source it cannot scan while still
94
+ * exiting 0, so the failure is a board that serves an unstyled page rather
95
+ * than a build that stops.
96
+ */
97
+ describe('in a board, where none of those directories exist', () => {
98
+ const inBoard = (path: string) => path.includes('node_modules')
99
+
100
+ function sourcesFor(cssDir: string): string[] {
101
+ const rewritten = rebaseGlobalsCssSources(shippedGlobalsCss, cssDir, '/board', inBoard)
102
+ return [...rewritten.matchAll(/@source "([^"]+)";/g)].map((match) => match[1]!)
103
+ }
104
+
105
+ it('scans the installed packages, which is where that code actually is', () => {
106
+ expect(sourcesFor('/board/src/styles')).toEqual(['../../node_modules/@meith'])
107
+ })
108
+
109
+ it('names no directory that is not there, at either materialization depth', () => {
110
+ for (const sources of [
111
+ sourcesFor('/board/src/styles'),
112
+ sourcesFor('/board/.meith/app/src/styles'),
113
+ ]) {
114
+ for (const source of sources) expect(source).toContain('node_modules/@meith')
115
+ }
116
+ })
117
+
118
+ it('leaves the rest of the stylesheet untouched', () => {
119
+ const rewritten = rebaseGlobalsCssSources(
120
+ shippedGlobalsCss,
121
+ '/board/src/styles',
122
+ '/board',
123
+ inBoard,
124
+ )
125
+
126
+ expect(rewritten).toContain('@import "tailwindcss";')
127
+ expect(rewritten.replace(/@source "[^"]+";\n/g, '')).toBe(
128
+ shippedGlobalsCss.replace(/@source "[^"]+";\n/g, ''),
129
+ )
130
+ })
131
+
132
+ it('keeps a source that is really there, so a hybrid layout loses nothing', () => {
133
+ const present = (path: string) =>
134
+ path.includes('node_modules') || path.endsWith('/board/themes')
135
+ const rewritten = rebaseGlobalsCssSources(
136
+ shippedGlobalsCss,
137
+ '/board/src/styles',
138
+ '/board',
139
+ present,
140
+ )
141
+ const sources = [...rewritten.matchAll(/@source "([^"]+)";/g)].map((match) => match[1])
142
+
143
+ expect(sources).toContain('../../themes')
144
+ expect(sources).toContain('../../node_modules/@meith')
145
+ })
146
+
147
+ it('adds nothing when the packages are not installed either', () => {
148
+ const rewritten = rebaseGlobalsCssSources(
149
+ shippedGlobalsCss,
150
+ '/board/src/styles',
151
+ '/board',
152
+ () => false,
153
+ )
154
+
155
+ expect(rewritten).not.toContain('@source')
156
+ })
71
157
  })
72
158
  })
73
159
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meith/web",
3
- "version": "0.20.0",
3
+ "version": "0.21.1",
4
4
  "description": "The board itself: the Next.js app, and the forum-web bin that materializes it into an external board workspace.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -43,52 +43,52 @@
43
43
  "typescript": "7.0.2",
44
44
  "@types/react": "^19",
45
45
  "@types/react-dom": "^19",
46
- "@meith/admin": "0.20.0",
47
- "@meith/antispam": "0.20.0",
48
- "@meith/api": "0.20.0",
49
- "@meith/accounts": "0.20.0",
50
- "@meith/attachments": "0.20.0",
51
- "@meith/authorization": "0.20.0",
52
- "@meith/avatars": "0.20.0",
53
- "@meith/core": "0.20.0",
54
- "@meith/db": "0.20.0",
55
- "@meith/demo": "0.20.0",
56
- "@meith/events": "0.20.0",
57
- "@meith/drafts": "0.20.0",
58
- "@meith/drivers": "0.20.0",
59
- "@meith/i18n": "0.20.0",
60
- "@meith/install": "0.20.0",
61
- "@meith/import": "0.20.0",
62
- "@meith/mail": "0.20.0",
63
- "@meith/markdown": "0.20.0",
64
- "@meith/groups": "0.20.0",
65
- "@meith/marketplace": "0.20.0",
66
- "@meith/forums": "0.20.0",
67
- "@meith/notifications": "0.20.0",
68
- "@meith/moderation": "0.20.0",
69
- "@meith/plugin-kit": "0.20.0",
70
- "@meith/plugin-dues": "0.20.0",
71
- "@meith/posts": "0.20.0",
72
- "@meith/messages": "0.20.0",
73
- "@meith/profile-fields": "0.20.0",
74
- "@meith/polls": "0.20.0",
75
- "@meith/reputation": "0.20.0",
76
- "@meith/relations": "0.20.0",
77
- "@meith/runtime": "0.20.0",
78
- "@meith/search": "0.20.0",
79
- "@meith/settings": "0.20.0",
80
- "@meith/signatures": "0.20.0",
81
- "@meith/subscriptions": "0.20.0",
82
- "@meith/theme-clubhouse": "0.20.0",
83
- "@meith/theme-default": "0.20.0",
84
- "@meith/theme-kit": "0.20.0",
85
- "@meith/theme-midnight": "0.20.0",
86
- "@meith/tasks": "0.20.0",
87
- "@meith/theme-raidframe": "0.20.0",
88
- "@meith/threads": "0.20.0",
89
- "@meith/theme-phasebook": "0.20.0",
90
- "@meith/upgrade": "0.20.0",
91
- "@meith/ui": "0.20.0"
46
+ "@meith/admin": "0.21.1",
47
+ "@meith/antispam": "0.21.1",
48
+ "@meith/accounts": "0.21.1",
49
+ "@meith/api": "0.21.1",
50
+ "@meith/attachments": "0.21.1",
51
+ "@meith/authorization": "0.21.1",
52
+ "@meith/avatars": "0.21.1",
53
+ "@meith/core": "0.21.1",
54
+ "@meith/db": "0.21.1",
55
+ "@meith/demo": "0.21.1",
56
+ "@meith/drivers": "0.21.1",
57
+ "@meith/events": "0.21.1",
58
+ "@meith/forums": "0.21.1",
59
+ "@meith/i18n": "0.21.1",
60
+ "@meith/groups": "0.21.1",
61
+ "@meith/drafts": "0.21.1",
62
+ "@meith/import": "0.21.1",
63
+ "@meith/install": "0.21.1",
64
+ "@meith/mail": "0.21.1",
65
+ "@meith/marketplace": "0.21.1",
66
+ "@meith/markdown": "0.21.1",
67
+ "@meith/messages": "0.21.1",
68
+ "@meith/moderation": "0.21.1",
69
+ "@meith/notifications": "0.21.1",
70
+ "@meith/plugin-dues": "0.21.1",
71
+ "@meith/plugin-kit": "0.21.1",
72
+ "@meith/polls": "0.21.1",
73
+ "@meith/posts": "0.21.1",
74
+ "@meith/profile-fields": "0.21.1",
75
+ "@meith/relations": "0.21.1",
76
+ "@meith/runtime": "0.21.1",
77
+ "@meith/reputation": "0.21.1",
78
+ "@meith/settings": "0.21.1",
79
+ "@meith/subscriptions": "0.21.1",
80
+ "@meith/signatures": "0.21.1",
81
+ "@meith/tasks": "0.21.1",
82
+ "@meith/search": "0.21.1",
83
+ "@meith/theme-clubhouse": "0.21.1",
84
+ "@meith/theme-default": "0.21.1",
85
+ "@meith/theme-midnight": "0.21.1",
86
+ "@meith/theme-kit": "0.21.1",
87
+ "@meith/theme-phasebook": "0.21.1",
88
+ "@meith/threads": "0.21.1",
89
+ "@meith/ui": "0.21.1",
90
+ "@meith/theme-raidframe": "0.21.1",
91
+ "@meith/upgrade": "0.21.1"
92
92
  },
93
93
  "scripts": {
94
94
  "dev": "next dev",
@@ -6,7 +6,7 @@ import { planUpgrade, type UpgradeState, upgradeNotice } from '@meith/upgrade'
6
6
 
7
7
  import { activeDefinitions } from './plugin-host'
8
8
 
9
- export const CODE_VERSION = '0.20.0'
9
+ export const CODE_VERSION = '0.21.1'
10
10
 
11
11
  export async function pendingUpgradeNotice(): Promise<string | null> {
12
12
  if (env.DATA_SOURCE !== 'postgres') return null
@@ -10,9 +10,12 @@
10
10
  * the build is green, the classes are simply absent, and the page renders
11
11
  * unstyled with no error anywhere to explain why.
12
12
  *
13
- * Paths are relative to this file, and deliberately not the `node_modules`
14
- * symlinks: scanning through a symlinked workspace package is inconsistent
15
- * between pnpm layouts.
13
+ * Paths are relative to this file and name this repository's own directories,
14
+ * which is all they can do from here. A board installed from npm has none of
15
+ * them the same code arrives under `node_modules/@meith` — so
16
+ * `apps/community/bin/forum-web.mjs` rebases these at materialization and
17
+ * substitutes that directory for the ones that are not there. Editing a path
18
+ * here without reading `rebaseGlobalsCssSources` will only work in this repo.
16
19
  */
17
20
  @source "../../../../themes";
18
21
  @source "../../../../plugins";