dockerfile builds
This commit is contained in:
@@ -280,8 +280,8 @@ func serve(cctx *cli.Context) error {
|
||||
path := c.Request().URL.Path
|
||||
maxAge := 1 * (60 * 60) // default is 1 hour
|
||||
|
||||
// all assets in /static/js, /static/css, /static/media are content-hashed and can be cached for a long time
|
||||
if strings.HasPrefix(path, "/static/js/") || strings.HasPrefix(path, "/static/css/") || strings.HasPrefix(path, "/static/media/") {
|
||||
// all assets in /static/_expo, /static/assets, /static/media are content-hashed and can be cached for a long time
|
||||
if strings.HasPrefix(path, "/static/_expo/") || strings.HasPrefix(path, "/static/assets/") || strings.HasPrefix(path, "/static/media/") {
|
||||
maxAge = 365 * (60 * 60 * 24) // 1 year
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -2,7 +2,10 @@ package bskyweb
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed static/*
|
||||
// `all:` disables the default exclusion of files/dirs beginning with `_` or `.`,
|
||||
// which is needed because Metro emits chunks like `__common-...js` and
|
||||
// `__expo-metro-runtime-...js`.
|
||||
//go:embed all:static
|
||||
var StaticFS embed.FS
|
||||
|
||||
//go:embed embedr-static/*
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
<!-- Hello Humans! API docs at https://atproto.com -->
|
||||
|
||||
<link rel="preload" as="font" type="font/woff2" href="{{ staticCDNHost }}/static/media/InterVariable.2bf3d951bf9d6109437ea0874bbf12fb.woff2" crossorigin>
|
||||
<link rel="preload" as="font" type="font/woff2" href="{{ staticCDNHost }}/static/assets/assets/fonts/inter/InterVariable.2bf3d951bf9d6109437ea0874bbf12fb.woff2" crossorigin>
|
||||
|
||||
<style>
|
||||
/**
|
||||
@@ -25,14 +25,14 @@
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'InterVariable';
|
||||
src: url("{{ staticCDNHost }}/static/media/InterVariable.2bf3d951bf9d6109437ea0874bbf12fb.woff2") format('woff2');
|
||||
src: url("{{ staticCDNHost }}/static/assets/assets/fonts/inter/InterVariable.2bf3d951bf9d6109437ea0874bbf12fb.woff2") format('woff2');
|
||||
font-weight: 300 1000;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'InterVariableItalic';
|
||||
src: url("{{ staticCDNHost }}/static/media/InterVariable-Italic.8d1dc86da25893737b6bb8a9c64b790e.woff2") format('woff2');
|
||||
src: url("{{ staticCDNHost }}/static/assets/assets/fonts/inter/InterVariable-Italic.8d1dc86da25893737b6bb8a9c64b790e.woff2") format('woff2');
|
||||
font-weight: 300 1000;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@
|
||||
"web": "expo start --web",
|
||||
"use-build-number": "./scripts/useBuildNumberEnv.sh",
|
||||
"use-build-number-with-bump": "./scripts/useBuildNumberEnvWithBump.sh",
|
||||
"build-web": "expo export:web && node ./scripts/post-web-build.js",
|
||||
"build-web": "expo export --platform web --source-maps && node ./scripts/post-web-build.js",
|
||||
"build-all": "pnpm intl:build && pnpm use-build-number-with-bump eas build --platform all",
|
||||
"build-ios": "pnpm use-build-number-with-bump eas build -p ios",
|
||||
"build-android": "pnpm use-build-number-with-bump eas build -p android",
|
||||
|
||||
+174
-42
@@ -1,48 +1,180 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const path = require('node:path')
|
||||
const fs = require('node:fs')
|
||||
const {execFileSync} = require('node:child_process')
|
||||
|
||||
const projectRoot = path.join(__dirname, '..')
|
||||
const templateFile = path.join(
|
||||
projectRoot,
|
||||
'bskyweb',
|
||||
'templates',
|
||||
'scripts.html',
|
||||
)
|
||||
const distDir = path.join(projectRoot, 'dist')
|
||||
const bskywebStatic = path.join(projectRoot, 'bskyweb', 'static')
|
||||
const templatesDir = path.join(projectRoot, 'bskyweb', 'templates')
|
||||
const templateFile = path.join(templatesDir, 'scripts.html')
|
||||
const fontsTemplateFile = path.join(templatesDir, 'fonts.html')
|
||||
|
||||
const {entrypoints} = require(
|
||||
path.join(projectRoot, 'web-build/asset-manifest.json'),
|
||||
)
|
||||
// Parse dist/index.html for entrypoint scripts and stylesheets injected by Metro.
|
||||
// Metro injects <link rel="preload/stylesheet"> and <script> tags referencing
|
||||
// /_expo/static/ paths, optionally prefixed by `experiments.baseUrl` (e.g. /static).
|
||||
// We capture from /_expo/static/ onward so the captured path is independent of baseUrl.
|
||||
//
|
||||
// We preserve the _expo/static/ structure when copying to bskyweb/static, because
|
||||
// Metro's runtime chunk loader and source maps embed these paths. Rewriting them
|
||||
// would mean rewriting the chunk loader and every .map file's references; not worth it.
|
||||
const indexHtml = fs.readFileSync(path.join(distDir, 'index.html'), 'utf8')
|
||||
|
||||
console.log(`Found ${entrypoints.length} entrypoints`)
|
||||
console.log(`Writing ${templateFile}`)
|
||||
|
||||
const outputFile = entrypoints
|
||||
.map(name => {
|
||||
const file = path.basename(name)
|
||||
const ext = path.extname(file)
|
||||
|
||||
if (ext === '.js') {
|
||||
return `<script defer="defer" src="{{ staticCDNHost }}/static/js/${file}"></script>`
|
||||
}
|
||||
if (ext === '.css') {
|
||||
return `<link rel="stylesheet" href="{{ staticCDNHost }}/static/css/${file}">`
|
||||
}
|
||||
|
||||
return ''
|
||||
})
|
||||
.join('\n')
|
||||
fs.writeFileSync(templateFile, outputFile)
|
||||
|
||||
function copyFiles(sourceDir, targetDir) {
|
||||
const files = fs.readdirSync(path.join(projectRoot, sourceDir))
|
||||
files.forEach(file => {
|
||||
const sourcePath = path.join(projectRoot, sourceDir, file)
|
||||
const targetPath = path.join(projectRoot, targetDir, file)
|
||||
fs.copyFileSync(sourcePath, targetPath)
|
||||
console.log(`Copied ${sourcePath} to ${targetPath}`)
|
||||
})
|
||||
// Find all CSS stylesheet links pointing to _expo (skips preload links).
|
||||
const cssEntries = []
|
||||
const cssRegex = /<link\b[^>]*href="[^"]*?(\/_expo\/static\/css\/[^"]+)"[^>]*>/g
|
||||
let match
|
||||
while ((match = cssRegex.exec(indexHtml)) !== null) {
|
||||
if (match[0].includes('rel="stylesheet"')) {
|
||||
cssEntries.push(match[1])
|
||||
}
|
||||
}
|
||||
|
||||
copyFiles('web-build/static/js', 'bskyweb/static/js')
|
||||
copyFiles('web-build/static/css', 'bskyweb/static/css')
|
||||
copyFiles('web-build/static/media', 'bskyweb/static/media')
|
||||
// Find all script src entries pointing to _expo
|
||||
const jsEntries = []
|
||||
const jsRegex = /<script\b[^>]*src="[^"]*?(\/_expo\/static\/js\/[^"]+)"[^>]*>/g
|
||||
while ((match = jsRegex.exec(indexHtml)) !== null) {
|
||||
jsEntries.push(match[1])
|
||||
}
|
||||
|
||||
if (jsEntries.length === 0) {
|
||||
// Fail loudly: an empty scripts.html silently breaks the deployed app.
|
||||
throw new Error(
|
||||
'No JS entrypoints found in dist/index.html. ' +
|
||||
'Metro may have changed its output format; update post-web-build.js.',
|
||||
)
|
||||
}
|
||||
|
||||
console.log(`Found ${jsEntries.length} script entrypoints`)
|
||||
console.log(`Found ${cssEntries.length} CSS entrypoints`)
|
||||
|
||||
// Generate scripts.html template.
|
||||
// Map /_expo/static/... to {{ staticCDNHost }}/static/_expo/static/... so the
|
||||
// path matches what we copy into bskyweb/static below.
|
||||
const outputLines = []
|
||||
|
||||
for (const href of cssEntries) {
|
||||
const cdnPath = href.replace(/^\//, '{{ staticCDNHost }}/static/')
|
||||
outputLines.push(`<link rel="stylesheet" href="${cdnPath}">`)
|
||||
}
|
||||
|
||||
for (const src of jsEntries) {
|
||||
const cdnPath = src.replace(/^\//, '{{ staticCDNHost }}/static/')
|
||||
outputLines.push(`<script defer="defer" src="${cdnPath}"></script>`)
|
||||
}
|
||||
|
||||
console.log(`Writing ${templateFile}`)
|
||||
fs.writeFileSync(templateFile, outputLines.join('\n'))
|
||||
|
||||
// Generate fonts.html — preload + @font-face for the splash, using the same
|
||||
// content-hashed paths Metro emits for the bundle. Avoids shipping a duplicate
|
||||
// font copy under /static/media/ and ensures the preload is actually used.
|
||||
const fontsDir = path.join(distDir, 'assets', 'assets', 'fonts', 'inter')
|
||||
function findFontHash(prefix) {
|
||||
if (!fs.existsSync(fontsDir)) return null
|
||||
const match = fs
|
||||
.readdirSync(fontsDir)
|
||||
.find(name => name.startsWith(prefix) && name.endsWith('.woff2'))
|
||||
return match || null
|
||||
}
|
||||
const interRegular = findFontHash('InterVariable.')
|
||||
const interItalic = findFontHash('InterVariable-Italic.')
|
||||
if (!interRegular || !interItalic) {
|
||||
throw new Error(
|
||||
`Could not find Inter font files in ${fontsDir}. ` +
|
||||
'Update post-web-build.js if the font emit path changed.',
|
||||
)
|
||||
}
|
||||
|
||||
const interRegularPath = `{{ staticCDNHost }}/static/assets/assets/fonts/inter/${interRegular}`
|
||||
const interItalicPath = `{{ staticCDNHost }}/static/assets/assets/fonts/inter/${interItalic}`
|
||||
|
||||
const fontsHtml = `<link rel="preload" as="font" type="font/woff2" href="${interRegularPath}" crossorigin>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'InterVariable';
|
||||
src: url("${interRegularPath}") format('woff2');
|
||||
font-weight: 300 1000;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'InterVariableItalic';
|
||||
src: url("${interItalicPath}") format('woff2');
|
||||
font-weight: 300 1000;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
</style>
|
||||
`
|
||||
console.log(`Writing ${fontsTemplateFile}`)
|
||||
fs.writeFileSync(fontsTemplateFile, fontsHtml)
|
||||
|
||||
// Clean previous build output to avoid stale files
|
||||
function cleanDir(dir) {
|
||||
if (fs.existsSync(dir)) {
|
||||
fs.rmSync(dir, {recursive: true})
|
||||
console.log(`Cleaned ${dir}`)
|
||||
}
|
||||
}
|
||||
|
||||
cleanDir(path.join(bskywebStatic, '_expo'))
|
||||
cleanDir(path.join(bskywebStatic, 'assets'))
|
||||
|
||||
// Recursively copy a directory.
|
||||
function copyDir(sourceDir, targetDir) {
|
||||
if (!fs.existsSync(sourceDir)) {
|
||||
console.log(`Skipping ${sourceDir} (does not exist)`)
|
||||
return
|
||||
}
|
||||
fs.mkdirSync(targetDir, {recursive: true})
|
||||
const entries = fs.readdirSync(sourceDir, {withFileTypes: true})
|
||||
for (const entry of entries) {
|
||||
const sourcePath = path.join(sourceDir, entry.name)
|
||||
const targetPath = path.join(targetDir, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
copyDir(sourcePath, targetPath)
|
||||
} else {
|
||||
fs.copyFileSync(sourcePath, targetPath)
|
||||
console.log(`Copied ${sourcePath} to ${targetPath}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy Metro's _expo/ tree wholesale so JS chunks (including lazy chunks
|
||||
// referenced by hash) and source maps resolve at the same paths the runtime expects.
|
||||
copyDir(path.join(distDir, '_expo'), path.join(bskywebStatic, '_expo'))
|
||||
|
||||
// Copy assets (fonts, images, icons, etc.) referenced as /static/assets/... by the bundle.
|
||||
copyDir(path.join(distDir, 'assets'), path.join(bskywebStatic, 'assets'))
|
||||
|
||||
// Upload source maps to Sentry
|
||||
if (process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_AUTH_TOKEN != 'unknown') {
|
||||
const release =
|
||||
process.env.SENTRY_RELEASE ||
|
||||
require(path.join(projectRoot, 'package.json')).version
|
||||
const dist = process.env.SENTRY_DIST || undefined
|
||||
const sourceMapDir = path.join(distDir, '_expo', 'static')
|
||||
|
||||
console.log(`Uploading source maps to Sentry (release: ${release})`)
|
||||
const args = [
|
||||
'sourcemaps',
|
||||
'upload',
|
||||
'--org',
|
||||
'blueskyweb',
|
||||
'--project',
|
||||
'app',
|
||||
'--release',
|
||||
release,
|
||||
...(dist ? ['--dist', dist] : []),
|
||||
sourceMapDir,
|
||||
]
|
||||
try {
|
||||
execFileSync('sentry-cli', args, {stdio: 'inherit', cwd: projectRoot})
|
||||
console.log('Sentry source map upload complete')
|
||||
} catch (e) {
|
||||
console.error('Sentry source map upload failed:', e.message)
|
||||
process.exit(1)
|
||||
}
|
||||
} else {
|
||||
console.log('Skipping Sentry source map upload (SENTRY_AUTH_TOKEN not set)')
|
||||
}
|
||||
Reference in New Issue
Block a user