Files
lobehub__lobehub/apps/share/scripts/deploy.ts
Innei 011f947cf7 👷 ci(share,workbench): upload PR previews to sibling -preview Workers (#19103)
* 👷 ci(share,workbench): upload PR previews to sibling -preview Workers

Cloudflare rolls back only to the 100 most recently uploaded versions of a
script, and PR preview uploads landed on the production scripts, so a busy
day pushed every real deployment out of the rollback window. Previews now
go to lobehub-share-preview and lobehub-workbench-preview; the preview
aliases move with them (pr<N>-lobehub-share-preview...).

Claude-Session: https://claude.ai/code/session_01BKMAksDxdePWPW4kgr9KpM

* 📝 docs(split-micro-app): previews upload to a sibling -preview Worker

Claude-Session: https://claude.ai/code/session_01BKMAksDxdePWPW4kgr9KpM

* 👷 ci(share,workbench): stamp actor, commit and run on every Worker version

Cloudflare records only the API token's owner as a version's author, so a
deploy from CI or a laptop was unattributable. wrangler deploy and PR preview
uploads now carry --tag <sha7> and --message "<actor> <repo>@<sha7> run <id>"
(local deploys: "<git email> local@<sha7>"), which 鳥居番's Deploys page
already shows per version.

Claude-Session: https://claude.ai/code/session_01BKMAksDxdePWPW4kgr9KpM

* 📝 docs(split-micro-app): 鳥居番 Deploys page has Releases and Previews tabs

Claude-Session: https://claude.ai/code/session_01BKMAksDxdePWPW4kgr9KpM

* 📝 docs(split-micro-app): releases and previews live on the target's Worker card

Claude-Session: https://claude.ai/code/session_01BKMAksDxdePWPW4kgr9KpM

* 📝 docs(split-micro-app): Set override writes the staging config directly

Claude-Session: https://claude.ai/code/session_01BKMAksDxdePWPW4kgr9KpM
2026-09-04 02:53:34 +08:00

86 lines
3.0 KiB
TypeScript

import { execFileSync, execSync } from 'node:child_process';
import { existsSync } from 'node:fs';
import { resolve } from 'node:path';
import dotenv from 'dotenv';
import { uploadAssets } from '../../../scripts/mobileSpaWorkflow/upload';
import { localOperator, workerDeployAnnotationArgs } from '../../../scripts/workerDeployAnnotations';
const shareRoot = resolve(__dirname, '..');
const repoRoot = resolve(shareRoot, '../..');
const assetsDir = resolve(shareRoot, 'build/client/assets');
// A host repo that builds this app from a submodule keeps its own .env; it
// points SHARE_ENV_FILE at it so local deploys pick up the same credentials.
dotenv.config({ path: process.env.SHARE_ENV_FILE || resolve(repoRoot, '.env') });
const firstEnv = (...names: string[]) => {
for (const name of names) {
const value = process.env[name];
if (value) return value;
}
return undefined;
};
const requireEnv = (...names: string[]): string => {
const value = firstEnv(...names);
if (!value) throw new Error(`Missing env: ${names.join(' / ')}`);
return value;
};
async function main() {
const publicDomain = new URL(requireEnv('ASSET_S3_PUBLIC_DOMAIN', 'MOBILE_S3_PUBLIC_DOMAIN'))
.origin;
// Same convention as cloud's resolveViteBase: a stable entry suffix, no version
// stamp — content-hashed filenames make uploads incremental and cache-immutable.
// Deliberately not the generic ASSET_S3_KEY_PREFIX: workbench reads that one
// too, and a shared value would put both apps on the same prefix.
const keyPrefix = (firstEnv('SHARE_S3_KEY_PREFIX') || 'share').replaceAll(/^\/+|\/+$/g, '');
const cdnBase = `${publicDomain}/${keyPrefix}/`;
console.log(`=== Step 1: Build share (VITE_CDN_BASE=${cdnBase}) ===`);
execSync('bun run build:rr', {
cwd: shareRoot,
env: {
...process.env,
NODE_ENV: 'production',
NODE_OPTIONS: '--max-old-space-size=8192',
VITE_CDN_BASE: cdnBase,
},
stdio: 'inherit',
});
if (!existsSync(assetsDir)) throw new Error(`Build output not found at ${assetsDir}`);
console.log('\n=== Step 2: Upload assets to S3 ===');
await uploadAssets(assetsDir, {
accessKeyId: requireEnv('ASSET_S3_ACCESS_KEY_ID', 'MOBILE_S3_ACCESS_KEY_ID'),
bucket: requireEnv('ASSET_S3_BUCKET', 'MOBILE_S3_BUCKET'),
endpoint: requireEnv('ASSET_S3_ENDPOINT', 'MOBILE_S3_ENDPOINT'),
keyPrefix,
publicDomain,
region: firstEnv('ASSET_S3_REGION', 'MOBILE_S3_REGION') || 'auto',
secretAccessKey: requireEnv('ASSET_S3_SECRET_ACCESS_KEY', 'MOBILE_S3_SECRET_ACCESS_KEY'),
});
console.log('\n=== Step 3: Deploy worker ===');
execFileSync(
'node_modules/.bin/wrangler',
[
'deploy',
'--config',
'build/server/wrangler.json',
...workerDeployAnnotationArgs(process.env, localOperator(shareRoot)),
],
{ cwd: shareRoot, stdio: 'inherit' },
);
console.log('\n=== Deploy complete ===');
}
main().catch((error) => {
console.error('Deploy failed:', error);
process.exit(1);
});