@meith/web 0.16.0 → 0.17.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
@@ -47,7 +47,15 @@
47
47
  * through real `dependencies` entries a hoisted `node_modules` would need.
48
48
  */
49
49
  import { spawn } from 'node:child_process'
50
- import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
50
+ import {
51
+ cpSync,
52
+ existsSync,
53
+ mkdirSync,
54
+ readFileSync,
55
+ realpathSync,
56
+ rmSync,
57
+ writeFileSync,
58
+ } from 'node:fs'
51
59
  import { createRequire } from 'node:module'
52
60
  import { dirname, join, relative, resolve, sep } from 'node:path'
53
61
  import { fileURLToPath } from 'node:url'
@@ -92,6 +100,24 @@ function toPosixRelative(from, to) {
92
100
  return rel.startsWith('.') ? rel : `./${rel}`
93
101
  }
94
102
 
103
+ export function rebaseGlobalsCssSources(css, cssDir, workspaceRootOverride) {
104
+ return css.replace(/@source "((?:\.\.\/)+)([^"]+)";/g, (_match, _dots, tail) => {
105
+ const target = join(workspaceRootOverride, ...tail.split('/'))
106
+ return `@source "${toPosixRelative(cssDir, target)}";`
107
+ })
108
+ }
109
+
110
+ function rewriteGlobalsCssSourcePaths() {
111
+ const workspaceRootOverride = process.env.FORUM_WORKSPACE_ROOT
112
+ if (!workspaceRootOverride) return
113
+
114
+ const cssPath = join(appDir, 'src', 'styles', 'globals.css')
115
+ if (!existsSync(cssPath)) return
116
+
117
+ const css = readFileSync(cssPath, 'utf8')
118
+ writeFileSync(cssPath, rebaseGlobalsCssSources(css, dirname(cssPath), workspaceRootOverride))
119
+ }
120
+
95
121
  /**
96
122
  * A board outside this monorepo has a hoisted `node_modules` (see the module
97
123
  * comment), so plain bare-specifier resolution reaches every `@meith/*`
@@ -156,6 +182,8 @@ function materialize() {
156
182
  cpSync(source, target, { recursive: true })
157
183
  }
158
184
 
185
+ rewriteGlobalsCssSourcePaths()
186
+
159
187
  const tsconfig = {
160
188
  compilerOptions: {
161
189
  target: 'ES2022',
@@ -213,29 +241,31 @@ function run(executable, args, cwd) {
213
241
  child.on('error', (error) => fail(error.message))
214
242
  }
215
243
 
216
- const [, , command, ...rest] = process.argv
244
+ if (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
245
+ const [, , command, ...rest] = process.argv
217
246
 
218
- if (!['dev', 'build', 'start'].includes(command ?? '')) {
219
- console.error('Usage: forum-web <dev|build|start> [next arguments]')
220
- process.exit(1)
221
- }
247
+ if (!['dev', 'build', 'start'].includes(command ?? '')) {
248
+ console.error('Usage: forum-web <dev|build|start> [next arguments]')
249
+ process.exit(1)
250
+ }
251
+
252
+ materialize()
222
253
 
223
- materialize()
224
-
225
- if (command === 'start') {
226
- // `next.config.mjs` sets `output: 'standalone'`, and a standalone build is
227
- // run from its own traced server.js, not `next start` (see docker/Dockerfile
228
- // and docker/entrypoint.sh, which run the image's board the same way). The
229
- // tracing root is the workspace root (see the module comment on why
230
- // `.meith/app` sits exactly two levels below it), so the standalone bundle
231
- // preserves that same relative path down to the app directory.
232
- const standaloneRoot = join(appDir, '.next', 'standalone')
233
- const serverScript = join(standaloneRoot, relative(workspaceRoot, appDir), 'server.js')
234
- if (!existsSync(serverScript)) {
235
- fail(`no standalone build at ${serverScript} run "forum-web build" first.`)
254
+ if (command === 'start') {
255
+ // `next.config.mjs` sets `output: 'standalone'`, and a standalone build is
256
+ // run from its own traced server.js, not `next start` (see docker/Dockerfile
257
+ // and docker/entrypoint.sh, which run the image's board the same way). The
258
+ // tracing root is the workspace root (see the module comment on why
259
+ // `.meith/app` sits exactly two levels below it), so the standalone bundle
260
+ // preserves that same relative path down to the app directory.
261
+ const standaloneRoot = join(appDir, '.next', 'standalone')
262
+ const serverScript = join(standaloneRoot, relative(workspaceRoot, appDir), 'server.js')
263
+ if (!existsSync(serverScript)) {
264
+ fail(`no standalone build at ${serverScript} run "forum-web build" first.`)
265
+ }
266
+ run(process.execPath, [serverScript, ...rest], standaloneRoot)
267
+ } else {
268
+ const nextBin = resolveNextBin()
269
+ run(process.execPath, [nextBin, command, ...rest], appDir)
236
270
  }
237
- run(process.execPath, [serverScript, ...rest], standaloneRoot)
238
- } else {
239
- const nextBin = resolveNextBin()
240
- run(process.execPath, [nextBin, command, ...rest], appDir)
241
271
  }
@@ -0,0 +1,37 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ // @ts-expect-error forum-web.mjs ships untyped, imported directly for its pure export
4
+ import { rebaseGlobalsCssSources } from './forum-web.mjs'
5
+
6
+ describe('rebaseGlobalsCssSources', () => {
7
+ it('rebases every @source line against the real workspace root', () => {
8
+ const css = [
9
+ '@import "tailwindcss";',
10
+ '',
11
+ '@source "../../../../themes";',
12
+ '@source "../../../../plugins";',
13
+ '@source "../../../../examples";',
14
+ '@source "../../../../packages/ui/src";',
15
+ '',
16
+ '.foo { color: red; }',
17
+ ].join('\n')
18
+
19
+ const cssDir = '/repo/boards/stock/.meith/app/src/styles'
20
+ const workspaceRoot = '/repo'
21
+
22
+ const rewritten = rebaseGlobalsCssSources(css, cssDir, workspaceRoot)
23
+
24
+ expect(rewritten).toContain('@source "../../../../../../themes";')
25
+ expect(rewritten).toContain('@source "../../../../../../plugins";')
26
+ expect(rewritten).toContain('@source "../../../../../../examples";')
27
+ expect(rewritten).toContain('@source "../../../../../../packages/ui/src";')
28
+ expect(rewritten).toContain('.foo { color: red; }')
29
+ })
30
+
31
+ it('is a no-op for a file with no @source lines', () => {
32
+ const css = '@import "tailwindcss";\n.foo { color: red; }\n'
33
+ expect(rebaseGlobalsCssSources(css, '/repo/boards/stock/.meith/app/src/styles', '/repo')).toBe(
34
+ css,
35
+ )
36
+ })
37
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meith/web",
3
- "version": "0.16.0",
3
+ "version": "0.17.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": "LGPL-3.0-or-later",
6
6
  "repository": {
@@ -42,52 +42,52 @@
42
42
  "typescript": "7.0.2",
43
43
  "@types/react": "^19",
44
44
  "@types/react-dom": "^19",
45
- "@meith/accounts": "0.16.0",
46
- "@meith/antispam": "0.16.0",
47
- "@meith/authorization": "0.16.0",
48
- "@meith/attachments": "0.16.0",
49
- "@meith/api": "0.16.0",
50
- "@meith/admin": "0.16.0",
51
- "@meith/avatars": "0.16.0",
52
- "@meith/db": "0.16.0",
53
- "@meith/drivers": "0.16.0",
54
- "@meith/events": "0.16.0",
55
- "@meith/drafts": "0.16.0",
56
- "@meith/forums": "0.16.0",
57
- "@meith/groups": "0.16.0",
58
- "@meith/core": "0.16.0",
59
- "@meith/demo": "0.16.0",
60
- "@meith/mail": "0.16.0",
61
- "@meith/install": "0.16.0",
62
- "@meith/import": "0.16.0",
63
- "@meith/markdown": "0.16.0",
64
- "@meith/marketplace": "0.16.0",
65
- "@meith/moderation": "0.16.0",
66
- "@meith/notifications": "0.16.0",
67
- "@meith/profile-fields": "0.16.0",
68
- "@meith/plugin-kit": "0.16.0",
69
- "@meith/posts": "0.16.0",
70
- "@meith/polls": "0.16.0",
71
- "@meith/i18n": "0.16.0",
72
- "@meith/reputation": "0.16.0",
73
- "@meith/search": "0.16.0",
74
- "@meith/signatures": "0.16.0",
75
- "@meith/settings": "0.16.0",
76
- "@meith/subscriptions": "0.16.0",
77
- "@meith/theme-clubhouse": "0.16.0",
78
- "@meith/relations": "0.16.0",
79
- "@meith/messages": "0.16.0",
80
- "@meith/runtime": "0.16.0",
81
- "@meith/plugin-dues": "0.16.0",
82
- "@meith/theme-kit": "0.16.0",
83
- "@meith/tasks": "0.16.0",
84
- "@meith/theme-midnight": "0.16.0",
85
- "@meith/theme-default": "0.16.0",
86
- "@meith/theme-phasebook": "0.16.0",
87
- "@meith/theme-raidframe": "0.16.0",
88
- "@meith/threads": "0.16.0",
89
- "@meith/upgrade": "0.16.0",
90
- "@meith/ui": "0.16.0"
45
+ "@meith/accounts": "0.17.1",
46
+ "@meith/admin": "0.17.1",
47
+ "@meith/antispam": "0.17.1",
48
+ "@meith/api": "0.17.1",
49
+ "@meith/attachments": "0.17.1",
50
+ "@meith/authorization": "0.17.1",
51
+ "@meith/avatars": "0.17.1",
52
+ "@meith/core": "0.17.1",
53
+ "@meith/db": "0.17.1",
54
+ "@meith/demo": "0.17.1",
55
+ "@meith/drafts": "0.17.1",
56
+ "@meith/groups": "0.17.1",
57
+ "@meith/events": "0.17.1",
58
+ "@meith/drivers": "0.17.1",
59
+ "@meith/forums": "0.17.1",
60
+ "@meith/i18n": "0.17.1",
61
+ "@meith/install": "0.17.1",
62
+ "@meith/mail": "0.17.1",
63
+ "@meith/markdown": "0.17.1",
64
+ "@meith/import": "0.17.1",
65
+ "@meith/messages": "0.17.1",
66
+ "@meith/marketplace": "0.17.1",
67
+ "@meith/notifications": "0.17.1",
68
+ "@meith/moderation": "0.17.1",
69
+ "@meith/plugin-dues": "0.17.1",
70
+ "@meith/plugin-kit": "0.17.1",
71
+ "@meith/posts": "0.17.1",
72
+ "@meith/profile-fields": "0.17.1",
73
+ "@meith/polls": "0.17.1",
74
+ "@meith/relations": "0.17.1",
75
+ "@meith/search": "0.17.1",
76
+ "@meith/reputation": "0.17.1",
77
+ "@meith/runtime": "0.17.1",
78
+ "@meith/settings": "0.17.1",
79
+ "@meith/signatures": "0.17.1",
80
+ "@meith/subscriptions": "0.17.1",
81
+ "@meith/tasks": "0.17.1",
82
+ "@meith/theme-clubhouse": "0.17.1",
83
+ "@meith/theme-kit": "0.17.1",
84
+ "@meith/theme-default": "0.17.1",
85
+ "@meith/theme-midnight": "0.17.1",
86
+ "@meith/theme-phasebook": "0.17.1",
87
+ "@meith/theme-raidframe": "0.17.1",
88
+ "@meith/ui": "0.17.1",
89
+ "@meith/threads": "0.17.1",
90
+ "@meith/upgrade": "0.17.1"
91
91
  },
92
92
  "scripts": {
93
93
  "dev": "next dev",
@@ -385,7 +385,7 @@ export function ThemeEditorForm({
385
385
  <fieldset
386
386
  key={group.titleKey}
387
387
  hidden={hydrated && matches === 0}
388
- className="flex flex-col gap-2 rounded-md border border-border p-3"
388
+ className="flex min-w-0 flex-col gap-2 rounded-md border border-border p-3"
389
389
  >
390
390
  <legend className="flex flex-wrap items-baseline gap-x-3 gap-y-1 px-1">
391
391
  <span className="text-sm font-medium">{copy[group.titleKey]}</span>
@@ -104,7 +104,7 @@ export function PaletteGrid({
104
104
  onSelect: (name: string) => void
105
105
  }) {
106
106
  return (
107
- <div className="grid grid-cols-[repeat(auto-fill,minmax(9.5rem,1fr))] gap-2">
107
+ <div className="grid grid-cols-2 gap-2 @lg:grid-cols-3">
108
108
  {cells.map((cell) => (
109
109
  <Cell
110
110
  key={cell.token.name}
@@ -53,7 +53,7 @@ export function UserBulkToolbar({
53
53
  <form
54
54
  id="admin-user-bulk"
55
55
  action={action}
56
- className="flex flex-col gap-3 sm:flex-row sm:items-end"
56
+ className="flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-end"
57
57
  >
58
58
  <label className="flex min-w-48 flex-col gap-1 text-sm">
59
59
  <span className="font-medium">{labels.action}</span>
@@ -81,7 +81,7 @@ export function UserBulkToolbar({
81
81
  </label>
82
82
  <button
83
83
  type="button"
84
- className="inline-flex h-10 items-center justify-center rounded-md border border-border px-4 text-sm font-medium"
84
+ className="inline-flex h-10 shrink-0 items-center justify-center whitespace-nowrap rounded-md border border-border px-4 text-sm font-medium"
85
85
  onClick={() => {
86
86
  for (const checkbox of document.querySelectorAll<HTMLInputElement>(
87
87
  '[data-admin-user-select]',
@@ -92,7 +92,9 @@ export function UserBulkToolbar({
92
92
  >
93
93
  {labels.selectPage}
94
94
  </button>
95
- <SubmitButton>{labels.apply}</SubmitButton>
95
+ <span className="min-w-48">
96
+ <SubmitButton>{labels.apply}</SubmitButton>
97
+ </span>
96
98
  </form>
97
99
  </div>
98
100
  )
@@ -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.16.0'
9
+ export const CODE_VERSION = '0.17.1'
10
10
 
11
11
  export async function pendingUpgradeNotice(): Promise<string | null> {
12
12
  if (env.DATA_SOURCE !== 'postgres') return null