@meith/web 0.21.0 → 0.21.2
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 +39 -4
- package/bin/forum-web.test.ts +92 -6
- package/package.json +47 -47
- package/src/components/install/install-form.tsx +3 -1
- package/src/server/install.ts +13 -2
- package/src/server/upgrade-notice.ts +1 -1
- package/src/styles/globals.css +6 -3
- package/src/view/install-copy.ts +3 -0
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
|
package/bin/forum-web.test.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
69
|
-
|
|
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.21.
|
|
3
|
+
"version": "0.21.2",
|
|
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/
|
|
47
|
-
"@meith/
|
|
48
|
-
"@meith/antispam": "0.21.
|
|
49
|
-
"@meith/
|
|
50
|
-
"@meith/
|
|
51
|
-
"@meith/
|
|
52
|
-
"@meith/avatars": "0.21.
|
|
53
|
-
"@meith/
|
|
54
|
-
"@meith/
|
|
55
|
-
"@meith/demo": "0.21.
|
|
56
|
-
"@meith/
|
|
57
|
-
"@meith/
|
|
58
|
-
"@meith/
|
|
59
|
-
"@meith/
|
|
60
|
-
"@meith/i18n": "0.21.
|
|
61
|
-
"@meith/
|
|
62
|
-
"@meith/
|
|
63
|
-
"@meith/install": "0.21.
|
|
64
|
-
"@meith/mail": "0.21.
|
|
65
|
-
"@meith/markdown": "0.21.
|
|
66
|
-
"@meith/marketplace": "0.21.
|
|
67
|
-
"@meith/messages": "0.21.
|
|
68
|
-
"@meith/moderation": "0.21.
|
|
69
|
-
"@meith/
|
|
70
|
-
"@meith/
|
|
71
|
-
"@meith/
|
|
72
|
-
"@meith/
|
|
73
|
-
"@meith/
|
|
74
|
-
"@meith/
|
|
75
|
-
"@meith/
|
|
76
|
-
"@meith/
|
|
77
|
-
"@meith/
|
|
78
|
-
"@meith/
|
|
79
|
-
"@meith/
|
|
80
|
-
"@meith/
|
|
81
|
-
"@meith/
|
|
82
|
-
"@meith/
|
|
83
|
-
"@meith/theme-default": "0.21.
|
|
84
|
-
"@meith/
|
|
85
|
-
"@meith/theme-
|
|
86
|
-
"@meith/theme-
|
|
87
|
-
"@meith/theme-
|
|
88
|
-
"@meith/theme-
|
|
89
|
-
"@meith/threads": "0.21.
|
|
90
|
-
"@meith/ui": "0.21.
|
|
91
|
-
"@meith/upgrade": "0.21.
|
|
46
|
+
"@meith/accounts": "0.21.2",
|
|
47
|
+
"@meith/admin": "0.21.2",
|
|
48
|
+
"@meith/antispam": "0.21.2",
|
|
49
|
+
"@meith/authorization": "0.21.2",
|
|
50
|
+
"@meith/api": "0.21.2",
|
|
51
|
+
"@meith/attachments": "0.21.2",
|
|
52
|
+
"@meith/avatars": "0.21.2",
|
|
53
|
+
"@meith/db": "0.21.2",
|
|
54
|
+
"@meith/core": "0.21.2",
|
|
55
|
+
"@meith/demo": "0.21.2",
|
|
56
|
+
"@meith/drivers": "0.21.2",
|
|
57
|
+
"@meith/events": "0.21.2",
|
|
58
|
+
"@meith/drafts": "0.21.2",
|
|
59
|
+
"@meith/forums": "0.21.2",
|
|
60
|
+
"@meith/i18n": "0.21.2",
|
|
61
|
+
"@meith/import": "0.21.2",
|
|
62
|
+
"@meith/groups": "0.21.2",
|
|
63
|
+
"@meith/install": "0.21.2",
|
|
64
|
+
"@meith/mail": "0.21.2",
|
|
65
|
+
"@meith/markdown": "0.21.2",
|
|
66
|
+
"@meith/marketplace": "0.21.2",
|
|
67
|
+
"@meith/messages": "0.21.2",
|
|
68
|
+
"@meith/moderation": "0.21.2",
|
|
69
|
+
"@meith/plugin-dues": "0.21.2",
|
|
70
|
+
"@meith/notifications": "0.21.2",
|
|
71
|
+
"@meith/polls": "0.21.2",
|
|
72
|
+
"@meith/plugin-kit": "0.21.2",
|
|
73
|
+
"@meith/reputation": "0.21.2",
|
|
74
|
+
"@meith/relations": "0.21.2",
|
|
75
|
+
"@meith/profile-fields": "0.21.2",
|
|
76
|
+
"@meith/runtime": "0.21.2",
|
|
77
|
+
"@meith/settings": "0.21.2",
|
|
78
|
+
"@meith/search": "0.21.2",
|
|
79
|
+
"@meith/posts": "0.21.2",
|
|
80
|
+
"@meith/signatures": "0.21.2",
|
|
81
|
+
"@meith/subscriptions": "0.21.2",
|
|
82
|
+
"@meith/theme-clubhouse": "0.21.2",
|
|
83
|
+
"@meith/theme-default": "0.21.2",
|
|
84
|
+
"@meith/tasks": "0.21.2",
|
|
85
|
+
"@meith/theme-kit": "0.21.2",
|
|
86
|
+
"@meith/theme-phasebook": "0.21.2",
|
|
87
|
+
"@meith/theme-raidframe": "0.21.2",
|
|
88
|
+
"@meith/theme-midnight": "0.21.2",
|
|
89
|
+
"@meith/threads": "0.21.2",
|
|
90
|
+
"@meith/ui": "0.21.2",
|
|
91
|
+
"@meith/upgrade": "0.21.2"
|
|
92
92
|
},
|
|
93
93
|
"scripts": {
|
|
94
94
|
"dev": "next dev",
|
|
@@ -314,7 +314,9 @@ function Outcome({ state, copy }: { state: InstallFormState; copy: Copy }) {
|
|
|
314
314
|
<AlertTitle>
|
|
315
315
|
{formatFromCopy(copy, 'install.outcome.didNotFinish', { title: failed.title })}
|
|
316
316
|
</AlertTitle>{' '}
|
|
317
|
-
{fieldErrors.length > 0
|
|
317
|
+
{fieldErrors.length > 0
|
|
318
|
+
? fromCopy(copy, 'install.outcome.oneAnswer')
|
|
319
|
+
: fromCopy(copy, failed.error)}
|
|
318
320
|
</>
|
|
319
321
|
) : (
|
|
320
322
|
<AlertTitle>
|
package/src/server/install.ts
CHANGED
|
@@ -16,10 +16,10 @@ import {
|
|
|
16
16
|
isInstalled,
|
|
17
17
|
markInstalled,
|
|
18
18
|
migrationUrl,
|
|
19
|
+
missingTables,
|
|
19
20
|
PostgresAdminRepository,
|
|
20
21
|
PostgresForumRepository,
|
|
21
22
|
PostgresSettingsRepository,
|
|
22
|
-
runMigrations,
|
|
23
23
|
SEED_GROUP_KEY,
|
|
24
24
|
withInstallLock,
|
|
25
25
|
} from '@meith/db'
|
|
@@ -47,6 +47,7 @@ const INSTALLED_VERSION = '0.1.0'
|
|
|
47
47
|
const INSTALL_IN_FLIGHT = 'install.raced.inFlight'
|
|
48
48
|
|
|
49
49
|
const INSTALL_RACED = 'install.raced.sealed'
|
|
50
|
+
const SCHEMA_INCOMPLETE = 'install.schema.missing'
|
|
50
51
|
|
|
51
52
|
export async function gatherPreflight(): Promise<readonly Check[]> {
|
|
52
53
|
const dataSource = env.DATA_SOURCE === 'postgres' ? 'postgres' : 'fixture'
|
|
@@ -164,7 +165,17 @@ async function performInstall(input: InstallInput): Promise<readonly StepOutcome
|
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
|
|
167
|
-
if (
|
|
168
|
+
if (
|
|
169
|
+
!(await step('migrate', async () => {
|
|
170
|
+
const missing = await missingTables(db)
|
|
171
|
+
if (missing.length === 0) return
|
|
172
|
+
|
|
173
|
+
logger().error({ step: 'migrate', missing: missing.slice(0, 20) }, 'schema incomplete')
|
|
174
|
+
throw new Error(SCHEMA_INCOMPLETE)
|
|
175
|
+
}))
|
|
176
|
+
) {
|
|
177
|
+
return report
|
|
178
|
+
}
|
|
168
179
|
|
|
169
180
|
const settings = new PostgresSettingsRepository(db)
|
|
170
181
|
if (
|
|
@@ -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.21.
|
|
9
|
+
export const CODE_VERSION = '0.21.2'
|
|
10
10
|
|
|
11
11
|
export async function pendingUpgradeNotice(): Promise<string | null> {
|
|
12
12
|
if (env.DATA_SOURCE !== 'postgres') return null
|
package/src/styles/globals.css
CHANGED
|
@@ -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
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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";
|
package/src/view/install-copy.ts
CHANGED
|
@@ -61,6 +61,9 @@ export function installFormCopy(
|
|
|
61
61
|
'install.steps.failed',
|
|
62
62
|
'install.steps.notRun',
|
|
63
63
|
'install.outcome.oneAnswer',
|
|
64
|
+
'install.schema.missing',
|
|
65
|
+
'install.raced.inFlight',
|
|
66
|
+
'install.raced.sealed',
|
|
64
67
|
'install.outcome.passwords',
|
|
65
68
|
'install.field.boardName',
|
|
66
69
|
'install.field.boardUrl',
|