@tristan2828/ui-foundation 4.8.0 → 4.9.0
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/package.json +1 -1
- package/vite/index.d.ts +15 -3
- package/vite/index.js +67 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tristan2828/ui-foundation",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.9.0",
|
|
4
4
|
"description": "Shared UI foundation for contract-first, database-backed React apps: shadcn primitives, DataTable/EntityForm composites, auth, the gateway error seam, design tokens, lint rules and the AI playbook.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
package/vite/index.d.ts
CHANGED
|
@@ -3,12 +3,24 @@ import type { Plugin } from 'vite'
|
|
|
3
3
|
export type BuildInfoOptions = {
|
|
4
4
|
/** The app's repository, e.g. `https://github.com/me/app`. `BUILD_REPO_URL` wins over it. */
|
|
5
5
|
repoUrl?: string
|
|
6
|
+
/**
|
|
7
|
+
* The Alembic versions folder, from the Vite root (`backend/migrations/versions`).
|
|
8
|
+
* Adds `schema`, the head revision, to `build-info.json`; no head or
|
|
9
|
+
* several fails the build.
|
|
10
|
+
*/
|
|
11
|
+
alembic?: string
|
|
12
|
+
/**
|
|
13
|
+
* More fields for `build-info.json`, merged in. `api`, `commit`, `pr`,
|
|
14
|
+
* `builtAt` and `schema` are the plugin's own: returning one fails the build.
|
|
15
|
+
*/
|
|
16
|
+
extra?: () => Record<string, unknown>
|
|
6
17
|
}
|
|
7
18
|
|
|
8
19
|
/**
|
|
9
20
|
* On `vite build`, writes `dist/build-info.json` (`api`: "real" or "mock",
|
|
10
|
-
* `commit`, `pr`, `builtAt`) and sets
|
|
11
|
-
* `VITE_BUILD_PR` and
|
|
12
|
-
* `BUILD_PR` and
|
|
21
|
+
* `commit`, `pr`, `builtAt`, and `schema` with `alembic`) and sets
|
|
22
|
+
* `import.meta.env.VITE_BUILD_COMMIT`, `VITE_BUILD_PR` and
|
|
23
|
+
* `VITE_BUILD_REPO_URL` from `BUILD_COMMIT`, `BUILD_PR` and
|
|
24
|
+
* `BUILD_REPO_URL`.
|
|
13
25
|
*/
|
|
14
26
|
export default function buildInfo(options?: BuildInfoOptions): Plugin
|
package/vite/index.js
CHANGED
|
@@ -13,21 +13,72 @@
|
|
|
13
13
|
// BUILD_REPO_URL (the repoUrl option when that's unset), for AppShell's
|
|
14
14
|
// `version`. Unset, they're empty strings and nothing is shown.
|
|
15
15
|
//
|
|
16
|
+
// - with `alembic` (the migrations' versions folder, from the Vite root),
|
|
17
|
+
// adds `schema`: the head revision the backend migrates to. Read from the
|
|
18
|
+
// files, so the build needs no Python. No head, or several, fails the
|
|
19
|
+
// build, as `alembic upgrade head` would refuse at start.
|
|
20
|
+
// - with `extra`, merges what it returns into the file. It can't replace the
|
|
21
|
+
// fields above: the backend's production check reads `api`.
|
|
22
|
+
//
|
|
16
23
|
// Build only: in development the three are undefined. Plain JavaScript, like
|
|
17
24
|
// the eslint entry: Vite loads the config before any TypeScript tooling runs.
|
|
18
25
|
|
|
26
|
+
import fs from 'node:fs'
|
|
27
|
+
import path from 'node:path'
|
|
28
|
+
|
|
29
|
+
const OWN_FIELDS = ['api', 'commit', 'pr', 'builtAt', 'schema']
|
|
30
|
+
|
|
19
31
|
/** @param {string | undefined} value */
|
|
20
32
|
function clean(value) {
|
|
21
33
|
const trimmed = value?.trim()
|
|
22
34
|
return trimmed ? trimmed : null
|
|
23
35
|
}
|
|
24
36
|
|
|
25
|
-
|
|
37
|
+
// `revision = "abc"` or `revision: str = "abc"`, as Alembic writes them.
|
|
38
|
+
const REVISION = /^revision(?:\s*:[^=\n]+)?\s*=\s*(['"])([^'"]+)\1/m
|
|
39
|
+
// None, one revision, or a tuple of them (a merge), possibly over lines.
|
|
40
|
+
const DOWN_REVISION = /^down_revision(?:\s*:[^=\n]+)?\s*=\s*(\([^)]*\)|[^\n]*)/m
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The one revision no other migration names as its down_revision.
|
|
44
|
+
* @param {string} dir
|
|
45
|
+
*/
|
|
46
|
+
function alembicHead(dir) {
|
|
47
|
+
if (!fs.existsSync(dir)) throw new Error(`buildInfo: no Alembic versions folder at ${dir}.`)
|
|
48
|
+
const revisions = new Set()
|
|
49
|
+
const downs = new Set()
|
|
50
|
+
for (const file of fs.readdirSync(dir).filter((name) => name.endsWith('.py'))) {
|
|
51
|
+
const source = fs.readFileSync(path.join(dir, file), 'utf8')
|
|
52
|
+
const revision = REVISION.exec(source)
|
|
53
|
+
if (!revision) continue
|
|
54
|
+
revisions.add(revision[2])
|
|
55
|
+
const down = DOWN_REVISION.exec(source)?.[1] ?? ''
|
|
56
|
+
for (const [, , name] of down.matchAll(/(['"])([^'"]+)\1/g)) downs.add(name)
|
|
57
|
+
}
|
|
58
|
+
const heads = [...revisions].filter((revision) => !downs.has(revision)).sort()
|
|
59
|
+
if (heads.length === 1) return heads[0]
|
|
60
|
+
throw new Error(
|
|
61
|
+
heads.length === 0
|
|
62
|
+
? `buildInfo: no Alembic head in ${dir} (no migrations, or they form a loop).`
|
|
63
|
+
: `buildInfo: several Alembic heads in ${dir} (${heads.join(', ')}); alembic upgrade head would refuse. Merge them first.`,
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {{
|
|
69
|
+
* repoUrl?: string,
|
|
70
|
+
* alembic?: string,
|
|
71
|
+
* extra?: () => Record<string, unknown>,
|
|
72
|
+
* }} [options]
|
|
73
|
+
*/
|
|
26
74
|
export default function buildInfo(options = {}) {
|
|
27
75
|
const commit = clean(process.env.BUILD_COMMIT)
|
|
28
76
|
const pr = clean(process.env.BUILD_PR)
|
|
29
77
|
const repoUrl = clean(process.env.BUILD_REPO_URL) ?? clean(options.repoUrl)
|
|
30
78
|
let api = 'mock'
|
|
79
|
+
let root = process.cwd()
|
|
80
|
+
/** @type {Record<string, unknown>} */
|
|
81
|
+
let fields = {}
|
|
31
82
|
|
|
32
83
|
return {
|
|
33
84
|
name: 'ui-foundation:build-info',
|
|
@@ -44,12 +95,26 @@ export default function buildInfo(options = {}) {
|
|
|
44
95
|
configResolved(config) {
|
|
45
96
|
// The same test as the app's main.tsx: anything but "real" ships MSW.
|
|
46
97
|
api = config.env.VITE_API === 'real' ? 'real' : 'mock'
|
|
98
|
+
root = config.root
|
|
99
|
+
},
|
|
100
|
+
// Read before bundling, so a bad migration chain fails the build early.
|
|
101
|
+
buildStart() {
|
|
102
|
+
fields = {}
|
|
103
|
+
if (options.alembic) fields.schema = alembicHead(path.resolve(root, options.alembic))
|
|
104
|
+
if (options.extra) {
|
|
105
|
+
const extra = options.extra()
|
|
106
|
+
const taken = Object.keys(extra).filter((key) => OWN_FIELDS.includes(key))
|
|
107
|
+
if (taken.length > 0) {
|
|
108
|
+
throw new Error(`buildInfo: extra can't set ${taken.join(', ')}; build-info.json's own fields.`)
|
|
109
|
+
}
|
|
110
|
+
Object.assign(fields, extra)
|
|
111
|
+
}
|
|
47
112
|
},
|
|
48
113
|
generateBundle() {
|
|
49
114
|
this.emitFile({
|
|
50
115
|
type: 'asset',
|
|
51
116
|
fileName: 'build-info.json',
|
|
52
|
-
source: `${JSON.stringify({ api, commit, pr, builtAt: new Date().toISOString() }, null, 2)}\n`,
|
|
117
|
+
source: `${JSON.stringify({ api, commit, pr, builtAt: new Date().toISOString(), ...fields }, null, 2)}\n`,
|
|
53
118
|
})
|
|
54
119
|
},
|
|
55
120
|
}
|