Vibecoded CI/CD
This commit is contained in:
36
frontend/desktop/build.mjs
Normal file
36
frontend/desktop/build.mjs
Normal file
@@ -0,0 +1,36 @@
|
||||
/* The bundle the desktop app loads.
|
||||
|
||||
nuxt.config.ts reads a MATHEW_DESKTOP flag and, in that shape, writes the
|
||||
wiki as plain files — no server, no `/api` proxy, since a window has neither
|
||||
— into dist-desktop/public, which is the `frontendDist` src-tauri/tauri.conf.json
|
||||
names. `tauri build` gets here through `npm run desktop:generate`, which is
|
||||
that config's `beforeBuildCommand`.
|
||||
|
||||
The reason this file exists rather than a script line: `MATHEW_DESKTOP=1 nuxt
|
||||
generate` is a bashism, and desktop builds are something Windows is asked for
|
||||
too. Setting the variable here works from any shell.
|
||||
|
||||
The wiki the app ships pointed at is overridable like any other build
|
||||
setting — set it in the environment before `npm run desktop:build`:
|
||||
NUXT_PUBLIC_API_BASE=http://localhost:5000 a wiki on this machine
|
||||
NUXT_PUBLIC_LEAN_URL=https://lean.example a Lean server the app can reach
|
||||
(Lean's default is empty, which leaves /lean saying there is no server here
|
||||
rather than reaching for a localhost that is not on the machine). */
|
||||
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
||||
|
||||
const run = spawnSync(npm, ['exec', '--', 'nuxt', 'generate'], {
|
||||
cwd: fileURLToPath(new URL('..', import.meta.url)),
|
||||
env: { ...process.env, MATHEW_DESKTOP: '1' },
|
||||
stdio: 'inherit',
|
||||
})
|
||||
|
||||
if (run.error) {
|
||||
console.error(`desktop: could not run \`nuxt generate\` — ${run.error.message}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
process.exit(run.status ?? 1)
|
||||
BIN
frontend/desktop/icon-source.png
Normal file
BIN
frontend/desktop/icon-source.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
88
frontend/desktop/make-icon.py
Normal file
88
frontend/desktop/make-icon.py
Normal file
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Draw desktop/icon-source.png — the 1024² master the icon set is cut from.
|
||||
|
||||
The wiki's own mark is the ∑ in the sidebar (AppHeader.vue's .logo-mark) over
|
||||
the dark reading surface, so the app icon is that: the sigma, in the accent the
|
||||
wiki wears by default, on the dark navy of its dark theme.
|
||||
|
||||
python3 desktop/make-icon.py # writes desktop/icon-source.png
|
||||
npm run desktop:icons # cuts src-tauri/icons/ from it
|
||||
|
||||
Requires Pillow (pip install pillow). Nothing else in the project uses it —
|
||||
this is build tooling, not app code.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
SIZE = 1024
|
||||
PAD = 64 # the margin macOS wants round an app icon; harmless elsewhere
|
||||
RADIUS = 200
|
||||
TOP = (16, 21, 44) # the navy a touch above --bg, so the square reads as an object
|
||||
BOTTOM = (6, 8, 14) # ... down to just under it
|
||||
ACCENT = (124, 140, 255) # --accent, dark theme (#7c8cff)
|
||||
MARK = "\u2211" # ∑ — the wiki's mark
|
||||
|
||||
FONTS = [
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSerif-Bold.ttf",
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
|
||||
"/usr/share/fonts/truetype/liberation/LiberationSerif-Bold.ttf",
|
||||
]
|
||||
|
||||
OUT = Path(__file__).resolve().parent / "icon-source.png"
|
||||
|
||||
|
||||
def load_font(size: int) -> ImageFont.FreeTypeFont:
|
||||
"""The first system font that actually carries a sigma."""
|
||||
for path in FONTS:
|
||||
try:
|
||||
font = ImageFont.truetype(path, size)
|
||||
except OSError:
|
||||
continue
|
||||
if font.getbbox(MARK)[2] > 0:
|
||||
return font
|
||||
raise SystemExit(f"no font with a {MARK} found — look for one of: {', '.join(FONTS)}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
# vertical gradient, then clipped to a rounded square
|
||||
gradient = Image.new("RGB", (1, SIZE))
|
||||
for y in range(SIZE):
|
||||
t = y / (SIZE - 1)
|
||||
gradient.putpixel((0, y), tuple(round(a + (b - a) * t) for a, b in zip(TOP, BOTTOM)))
|
||||
gradient = gradient.resize((SIZE, SIZE))
|
||||
|
||||
mask = Image.new("L", (SIZE, SIZE), 0)
|
||||
ImageDraw.Draw(mask).rounded_rectangle(
|
||||
[PAD, PAD, SIZE - PAD, SIZE - PAD], radius=RADIUS, fill=255
|
||||
)
|
||||
|
||||
icon = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0))
|
||||
icon.paste(gradient, (0, 0), mask)
|
||||
|
||||
draw = ImageDraw.Draw(icon)
|
||||
# a hairline of accent light along the edge, so the dark square does not
|
||||
# vanish into a dark taskbar
|
||||
draw.rounded_rectangle(
|
||||
[PAD, PAD, SIZE - PAD, SIZE - PAD], radius=RADIUS, outline=(124, 140, 255, 56), width=3
|
||||
)
|
||||
|
||||
font = load_font(600)
|
||||
left, top, right, bottom = draw.textbbox((0, 0), MARK, font=font)
|
||||
w, h = right - left, bottom - top
|
||||
# centred on the box the glyph is drawn in, not on its typographic cell:
|
||||
# an ascent-based centre would sit the sigma high
|
||||
draw.text(
|
||||
((SIZE - w) / 2 - left, (SIZE - h) / 2 - top),
|
||||
MARK,
|
||||
font=font,
|
||||
fill=ACCENT + (255,),
|
||||
)
|
||||
|
||||
icon.save(OUT)
|
||||
print(f"wrote {OUT}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user