mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-13 23:53:25 +00:00
Compare commits
4 Commits
fix/clis-f
...
fix/window
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
976a78a8f5 | ||
|
|
f0cbf77924 | ||
|
|
17be06eb2f | ||
|
|
0e90785500 |
152
.github/workflows/release-server.yml
vendored
Normal file
152
.github/workflows/release-server.yml
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
name: Release BrowserOS Server
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Release version (e.g. 0.0.80)"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: release-server
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
environment: release-core
|
||||
permissions:
|
||||
contents: write
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/browseros-agent
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: "1.3.6"
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun ci
|
||||
|
||||
- name: Prepare production env file
|
||||
run: cp apps/server/.env.production.example apps/server/.env.production
|
||||
|
||||
- name: Validate version
|
||||
id: version
|
||||
env:
|
||||
REQUESTED_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
PACKAGE_VERSION=$(node -p "require('./apps/server/package.json').version")
|
||||
echo "package_version=$PACKAGE_VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "release_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
if [ "$PACKAGE_VERSION" != "$REQUESTED_VERSION" ]; then
|
||||
echo "Requested version $REQUESTED_VERSION does not match apps/server/package.json ($PACKAGE_VERSION)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build release artifacts
|
||||
env:
|
||||
BROWSEROS_CONFIG_URL: ${{ secrets.BROWSEROS_CONFIG_URL }}
|
||||
CODEGEN_SERVICE_URL: ${{ secrets.CODEGEN_SERVICE_URL }}
|
||||
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
run: bun run build:server:ci
|
||||
|
||||
- name: Verify release artifacts
|
||||
run: |
|
||||
mapfile -t ZIP_FILES < <(find dist/prod/server -maxdepth 1 -type f -name 'browseros-server-resources-*.zip' | sort)
|
||||
|
||||
if [ "${#ZIP_FILES[@]}" -eq 0 ]; then
|
||||
echo "No server release zip files were produced"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'Found release artifacts:\n%s\n' "${ZIP_FILES[@]}"
|
||||
|
||||
- name: Generate release notes
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PACKAGE_VERSION: ${{ steps.version.outputs.package_version }}
|
||||
run: |
|
||||
SERVER_APP_PATH="packages/browseros-agent/apps/server"
|
||||
SERVER_BUILD_DIR="packages/browseros-agent/scripts/build/server"
|
||||
SERVER_BUILD_ENTRY="packages/browseros-agent/scripts/build/server.ts"
|
||||
SERVER_RESOURCE_MANIFEST="packages/browseros-agent/scripts/build/config/server-prod-resources.json"
|
||||
SERVER_WORKSPACE_PKG="packages/browseros-agent/package.json"
|
||||
CURRENT_TAG="browseros-server-v$PACKAGE_VERSION"
|
||||
PREV_TAG=$(git tag -l "browseros-server-v*" --sort=-v:refname | grep -v "^${CURRENT_TAG}$" | head -n 1)
|
||||
|
||||
if [ -z "$PREV_TAG" ]; then
|
||||
echo "Initial release of browseros-server." > /tmp/release-notes.md
|
||||
else
|
||||
COMMITS=$(git log "$PREV_TAG"..HEAD --pretty=format:"%H" -- \
|
||||
"$SERVER_APP_PATH" \
|
||||
"$SERVER_BUILD_DIR" \
|
||||
"$SERVER_BUILD_ENTRY" \
|
||||
"$SERVER_RESOURCE_MANIFEST" \
|
||||
"$SERVER_WORKSPACE_PKG")
|
||||
|
||||
if [ -z "$COMMITS" ]; then
|
||||
echo "No notable changes." > /tmp/release-notes.md
|
||||
else
|
||||
echo "## What's Changed" > /tmp/release-notes.md
|
||||
echo "" >> /tmp/release-notes.md
|
||||
|
||||
while IFS= read -r SHA; do
|
||||
SUBJECT=$(git log -1 --pretty=format:"%s" "$SHA")
|
||||
PR_NUM=$(gh api "/repos/${{ github.repository }}/commits/${SHA}/pulls" --jq '.[0].number // empty' 2>/dev/null)
|
||||
|
||||
if [ -n "$PR_NUM" ] && ! echo "$SUBJECT" | grep -qF "(#${PR_NUM})"; then
|
||||
echo "- ${SUBJECT} (#${PR_NUM})" >> /tmp/release-notes.md
|
||||
else
|
||||
echo "- ${SUBJECT}" >> /tmp/release-notes.md
|
||||
fi
|
||||
done <<< "$COMMITS"
|
||||
fi
|
||||
fi
|
||||
working-directory: ${{ github.workspace }}
|
||||
|
||||
- name: Create GitHub release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PACKAGE_VERSION: ${{ steps.version.outputs.package_version }}
|
||||
RELEASE_SHA: ${{ steps.version.outputs.release_sha }}
|
||||
run: |
|
||||
TAG="browseros-server-v$PACKAGE_VERSION"
|
||||
TITLE="BrowserOS Server - v$PACKAGE_VERSION"
|
||||
mapfile -t ZIP_FILES < <(find packages/browseros-agent/dist/prod/server -maxdepth 1 -type f -name 'browseros-server-resources-*.zip' | sort)
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
if git rev-parse "$TAG" >/dev/null 2>&1; then
|
||||
echo "Tag $TAG already exists, skipping tag creation"
|
||||
else
|
||||
git tag -a "$TAG" -m "browseros-server v$PACKAGE_VERSION" "$RELEASE_SHA"
|
||||
fi
|
||||
|
||||
if git ls-remote --tags origin "$TAG" | grep -q "$TAG"; then
|
||||
echo "Tag $TAG already on remote, skipping push"
|
||||
else
|
||||
git push origin "$TAG"
|
||||
fi
|
||||
|
||||
if gh release view "$TAG" >/dev/null 2>&1; then
|
||||
echo "Release $TAG already exists, updating"
|
||||
gh release edit "$TAG" --title "$TITLE" --notes-file /tmp/release-notes.md
|
||||
gh release upload "$TAG" "${ZIP_FILES[@]}" --clobber
|
||||
else
|
||||
gh release create "$TAG" \
|
||||
--title "$TITLE" \
|
||||
--notes-file /tmp/release-notes.md \
|
||||
"${ZIP_FILES[@]}"
|
||||
fi
|
||||
working-directory: ${{ github.workspace }}
|
||||
@@ -2,13 +2,17 @@ BINARY := browseros-cli
|
||||
SOURCES := $(shell find . -name '*.go')
|
||||
VERSION ?= dev
|
||||
POSTHOG_API_KEY ?=
|
||||
DIST := dist
|
||||
LDFLAGS := -X main.version=$(VERSION) -X browseros-cli/analytics.posthogAPIKey=$(POSTHOG_API_KEY)
|
||||
HOST_OS := $(shell go env GOOS)
|
||||
HOST_ARCH := $(shell go env GOARCH)
|
||||
HOST_EXT := $(if $(filter windows,$(HOST_OS)),.exe,)
|
||||
HOST_BINARY = $(DIST)/$(BINARY)_$(HOST_OS)_$(HOST_ARCH)$(HOST_EXT)
|
||||
|
||||
$(BINARY): $(SOURCES)
|
||||
go build -ldflags "$(LDFLAGS)" -o $(BINARY) .
|
||||
|
||||
PLATFORMS := darwin/amd64 darwin/arm64 linux/amd64 linux/arm64 windows/amd64 windows/arm64
|
||||
DIST := dist
|
||||
|
||||
.PHONY: install clean vet test release
|
||||
|
||||
@@ -45,6 +49,11 @@ release:
|
||||
fi; \
|
||||
mv "$(DIST)/$(BINARY)$$EXT" "$(DIST)/$(BINARY)_$${OS}_$${ARCH}$$EXT"; \
|
||||
done
|
||||
@ACTUAL_VERSION=$$($(HOST_BINARY) --version | awk '{print $$3}'); \
|
||||
if [ "$$ACTUAL_VERSION" != "$(VERSION)" ]; then \
|
||||
echo "Error: expected $(HOST_BINARY) to report version $(VERSION), got $$ACTUAL_VERSION" >&2; \
|
||||
exit 1; \
|
||||
fi
|
||||
@cd $(DIST) && (command -v sha256sum >/dev/null 2>&1 && sha256sum *.tar.gz *.zip || shasum -a 256 *.tar.gz *.zip) > checksums.txt
|
||||
@echo "=== Built artifacts ==="
|
||||
@ls -lh $(DIST)
|
||||
|
||||
@@ -25,13 +25,17 @@ func init() {
|
||||
Long: `Set up the CLI by providing the MCP server URL from BrowserOS.
|
||||
|
||||
Open BrowserOS → Settings → BrowserOS MCP to find your Server URL.
|
||||
The URL looks like: http://127.0.0.1:9004/mcp
|
||||
The URL looks like: http://127.0.0.1:9000/mcp
|
||||
|
||||
The port varies per installation, so this step is required on first use.
|
||||
Run again if your port changes.
|
||||
|
||||
You can provide the full URL or just the port number:
|
||||
browseros-cli init http://127.0.0.1:9000/mcp
|
||||
browseros-cli init 9000
|
||||
|
||||
Three modes:
|
||||
browseros-cli init <url> Non-interactive, use the provided URL
|
||||
browseros-cli init <url> Non-interactive (full URL or port number)
|
||||
browseros-cli init --auto Auto-discover from ~/.browseros/server.json
|
||||
browseros-cli init Interactive prompt`,
|
||||
Annotations: map[string]string{"group": "Setup:"},
|
||||
@@ -65,13 +69,14 @@ Three modes:
|
||||
bold.Println("BrowserOS CLI Setup")
|
||||
fmt.Println()
|
||||
fmt.Println("Open BrowserOS → Settings → BrowserOS MCP")
|
||||
fmt.Println("Copy the Server URL shown there.")
|
||||
fmt.Println("Copy the Server URL or port number shown there.")
|
||||
fmt.Println()
|
||||
dim.Println("It looks like: http://127.0.0.1:9004/mcp")
|
||||
dim.Println("Examples: http://127.0.0.1:9000/mcp")
|
||||
dim.Println(" 9000")
|
||||
fmt.Println()
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Print("Server URL: ")
|
||||
fmt.Print("Server URL or port: ")
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
output.Error("failed to read input", 1)
|
||||
|
||||
@@ -34,6 +34,7 @@ const automaticUpdateDrainTimeout = 150 * time.Millisecond
|
||||
|
||||
func SetVersion(v string) {
|
||||
version = v
|
||||
rootCmd.Version = v
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -338,10 +339,27 @@ func loadBrowserosServerURL() string {
|
||||
|
||||
func normalizeServerURL(raw string) string {
|
||||
normalized := strings.TrimSpace(raw)
|
||||
|
||||
if isPortOnly(normalized) {
|
||||
normalized = "http://127.0.0.1:" + normalized
|
||||
}
|
||||
|
||||
normalized = strings.TrimSuffix(normalized, "/mcp")
|
||||
return strings.TrimSuffix(normalized, "/")
|
||||
}
|
||||
|
||||
func isPortOnly(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
for _, c := range s {
|
||||
if c < '0' || c > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func validateServerURL(raw string) (string, error) {
|
||||
baseURL := normalizeServerURL(raw)
|
||||
if baseURL != "" {
|
||||
|
||||
@@ -5,6 +5,24 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSetVersionUpdatesRootCommand(t *testing.T) {
|
||||
originalVersion := version
|
||||
originalRootVersion := rootCmd.Version
|
||||
t.Cleanup(func() {
|
||||
version = originalVersion
|
||||
rootCmd.Version = originalRootVersion
|
||||
})
|
||||
|
||||
SetVersion("1.2.3")
|
||||
|
||||
if version != "1.2.3" {
|
||||
t.Fatalf("version = %q, want %q", version, "1.2.3")
|
||||
}
|
||||
if rootCmd.Version != "1.2.3" {
|
||||
t.Fatalf("rootCmd.Version = %q, want %q", rootCmd.Version, "1.2.3")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -8,7 +8,13 @@
|
||||
|
||||
import { afterAll, describe, it } from 'bun:test'
|
||||
import assert from 'node:assert'
|
||||
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import {
|
||||
existsSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join, resolve } from 'node:path'
|
||||
|
||||
@@ -25,11 +31,14 @@ function getNativeTarget(): { id: string; ext: string } {
|
||||
}
|
||||
|
||||
// Stub values so the build config validation passes without real secrets
|
||||
const BUILD_ENV_STUBS: Record<string, string> = {
|
||||
const INLINE_ENV_STUBS: Record<string, string> = {
|
||||
BROWSEROS_CONFIG_URL: 'https://stub.test/config',
|
||||
CODEGEN_SERVICE_URL: 'https://stub.test/codegen',
|
||||
POSTHOG_API_KEY: 'phc_test_stub',
|
||||
SENTRY_DSN: 'https://stub@sentry.test/0',
|
||||
}
|
||||
|
||||
const R2_ENV_STUBS: Record<string, string> = {
|
||||
R2_ACCOUNT_ID: 'test',
|
||||
R2_ACCESS_KEY_ID: 'test',
|
||||
R2_SECRET_ACCESS_KEY: 'test',
|
||||
@@ -39,20 +48,51 @@ const BUILD_ENV_STUBS: Record<string, string> = {
|
||||
describe('server build', () => {
|
||||
const rootDir = resolve(import.meta.dir, '../../..')
|
||||
const serverPkgPath = resolve(rootDir, 'apps/server/package.json')
|
||||
const prodEnvPath = resolve(rootDir, 'apps/server/.env.production')
|
||||
const prodEnvTemplatePath = resolve(
|
||||
rootDir,
|
||||
'apps/server/.env.production.example',
|
||||
)
|
||||
const buildScript = resolve(rootDir, 'scripts/build/server.ts')
|
||||
const target = getNativeTarget()
|
||||
const binaryPath = resolve(
|
||||
rootDir,
|
||||
`dist/prod/server/.tmp/binaries/browseros-server-${target.id}${target.ext}`,
|
||||
)
|
||||
const zipPath = resolve(
|
||||
rootDir,
|
||||
`dist/prod/server/browseros-server-resources-${target.id}.zip`,
|
||||
)
|
||||
const createdProdEnv = !existsSync(prodEnvPath)
|
||||
|
||||
// Empty manifest so the build skips R2 resource downloads
|
||||
const tempDir = mkdtempSync(join(tmpdir(), 'browseros-build-test-'))
|
||||
const emptyManifestPath = join(tempDir, 'empty-manifest.json')
|
||||
writeFileSync(emptyManifestPath, JSON.stringify({ resources: [] }))
|
||||
if (createdProdEnv) {
|
||||
writeFileSync(prodEnvPath, readFileSync(prodEnvTemplatePath, 'utf-8'))
|
||||
}
|
||||
|
||||
function buildEnv(
|
||||
extraEnv: Record<string, string>,
|
||||
omitKeys: string[] = [],
|
||||
): NodeJS.ProcessEnv {
|
||||
const env: NodeJS.ProcessEnv = {
|
||||
...process.env,
|
||||
...INLINE_ENV_STUBS,
|
||||
...extraEnv,
|
||||
}
|
||||
for (const key of omitKeys) {
|
||||
delete env[key]
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
afterAll(() => {
|
||||
rmSync(tempDir, { recursive: true, force: true })
|
||||
if (createdProdEnv) {
|
||||
rmSync(prodEnvPath, { force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('compiles and --version outputs correct version', async () => {
|
||||
@@ -71,7 +111,7 @@ describe('server build', () => {
|
||||
cwd: rootDir,
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
env: { ...process.env, ...BUILD_ENV_STUBS },
|
||||
env: buildEnv(R2_ENV_STUBS),
|
||||
},
|
||||
)
|
||||
const buildExit = await build.exited
|
||||
@@ -97,4 +137,36 @@ describe('server build', () => {
|
||||
)
|
||||
assert.strictEqual(versionOutput.trim(), expectedVersion)
|
||||
}, 300_000)
|
||||
|
||||
it('archives compile-only builds without R2 config', async () => {
|
||||
rmSync(zipPath, { force: true })
|
||||
|
||||
const build = Bun.spawn(
|
||||
[
|
||||
'bun',
|
||||
buildScript,
|
||||
`--target=${target.id}`,
|
||||
'--compile-only',
|
||||
'--archive-compiled',
|
||||
],
|
||||
{
|
||||
cwd: rootDir,
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
env: buildEnv({}, [
|
||||
'R2_ACCOUNT_ID',
|
||||
'R2_ACCESS_KEY_ID',
|
||||
'R2_SECRET_ACCESS_KEY',
|
||||
'R2_BUCKET',
|
||||
]),
|
||||
},
|
||||
)
|
||||
const buildExit = await build.exited
|
||||
if (buildExit !== 0) {
|
||||
const stderr = await new Response(build.stderr).text()
|
||||
assert.fail(`Compile-only archive failed (exit ${buildExit}):\n${stderr}`)
|
||||
}
|
||||
|
||||
assert.ok(existsSync(zipPath), `Expected archive at ${zipPath}`)
|
||||
}, 300_000)
|
||||
})
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"start:agent": "bun run --filter @browseros/agent dev",
|
||||
"build": "bun run build:server && bun run build:agent",
|
||||
"build:server": "FORCE_COLOR=1 bun scripts/build/server.ts --target=all",
|
||||
"build:server:ci": "FORCE_COLOR=1 bun scripts/build/server.ts --target=all --compile-only",
|
||||
"build:server:ci": "FORCE_COLOR=1 bun scripts/build/server.ts --target=all --compile-only --archive-compiled",
|
||||
"build:server:test": "FORCE_COLOR=1 bun scripts/build/server.ts --target=darwin-arm64 --no-upload",
|
||||
"upload:cli-installers": "bun scripts/build/cli.ts",
|
||||
"start:server:test": "bun run build:server:test && set -a && . apps/server/.env.development && set +a && dist/prod/server/.tmp/binaries/browseros-server-darwin-arm64",
|
||||
|
||||
@@ -37,29 +37,39 @@ export async function archiveAndUploadArtifacts(
|
||||
r2: R2Config,
|
||||
upload: boolean,
|
||||
): Promise<UploadResult[]> {
|
||||
const results: UploadResult[] = []
|
||||
const results = await archiveArtifacts(artifacts)
|
||||
if (!upload) {
|
||||
return results
|
||||
}
|
||||
|
||||
for (const artifact of artifacts) {
|
||||
const zipPath = zipPathForArtifact(artifact)
|
||||
await zipArtifactRoot(artifact.rootDir, zipPath)
|
||||
|
||||
if (!upload) {
|
||||
results.push({ targetId: artifact.target.id, zipPath })
|
||||
continue
|
||||
}
|
||||
|
||||
const fileName = basename(zipPath)
|
||||
const uploadedResults: UploadResult[] = []
|
||||
for (const result of results) {
|
||||
const fileName = basename(result.zipPath)
|
||||
const latestR2Key = joinObjectKey(r2.uploadPrefix, 'latest', fileName)
|
||||
const versionR2Key = joinObjectKey(r2.uploadPrefix, version, fileName)
|
||||
await uploadFileToObject(client, r2, latestR2Key, zipPath)
|
||||
await uploadFileToObject(client, r2, versionR2Key, zipPath)
|
||||
results.push({
|
||||
targetId: artifact.target.id,
|
||||
zipPath,
|
||||
await uploadFileToObject(client, r2, latestR2Key, result.zipPath)
|
||||
await uploadFileToObject(client, r2, versionR2Key, result.zipPath)
|
||||
uploadedResults.push({
|
||||
targetId: result.targetId,
|
||||
zipPath: result.zipPath,
|
||||
latestR2Key,
|
||||
versionR2Key,
|
||||
})
|
||||
}
|
||||
|
||||
return uploadedResults
|
||||
}
|
||||
|
||||
export async function archiveArtifacts(
|
||||
artifacts: StagedArtifact[],
|
||||
): Promise<UploadResult[]> {
|
||||
const results: UploadResult[] = []
|
||||
|
||||
for (const artifact of artifacts) {
|
||||
const zipPath = zipPathForArtifact(artifact)
|
||||
await zipArtifactRoot(artifact.rootDir, zipPath)
|
||||
results.push({ targetId: artifact.target.id, zipPath })
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
@@ -25,20 +25,30 @@ export function parseBuildArgs(argv: string[]): BuildArgs {
|
||||
'--compile-only',
|
||||
'Compile binaries only (skip R2 staging and upload)',
|
||||
)
|
||||
.option(
|
||||
'--archive-compiled',
|
||||
'Archive compile-only binaries into local zip files without R2 resources',
|
||||
)
|
||||
program.parse(argv, { from: 'user' })
|
||||
const options = program.opts<{
|
||||
target: string
|
||||
manifest: string
|
||||
upload: boolean
|
||||
compileOnly: boolean
|
||||
archiveCompiled: boolean
|
||||
}>()
|
||||
|
||||
const compileOnly = options.compileOnly ?? false
|
||||
const archiveCompiled = options.archiveCompiled ?? false
|
||||
if (archiveCompiled && !compileOnly) {
|
||||
throw new Error('--archive-compiled requires --compile-only')
|
||||
}
|
||||
|
||||
return {
|
||||
targets: resolveTargets(options.target),
|
||||
manifestPath: options.manifest,
|
||||
upload: compileOnly ? false : (options.upload ?? true),
|
||||
compileOnly,
|
||||
archiveCompiled,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,23 @@ import { existsSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
import { log } from '../log'
|
||||
import { archiveAndUploadArtifacts } from './archive'
|
||||
import { archiveAndUploadArtifacts, archiveArtifacts } from './archive'
|
||||
import { parseBuildArgs } from './cli'
|
||||
import { compileServerBinaries, getDistProdRoot } from './compile'
|
||||
import { loadBuildConfig } from './config'
|
||||
import { getTargetRules, loadManifest } from './manifest'
|
||||
import { createR2Client } from './r2'
|
||||
import { stageTargetArtifact } from './stage'
|
||||
import { stageCompiledArtifact, stageTargetArtifact } from './stage'
|
||||
|
||||
function buildModeLabel(argv: {
|
||||
compileOnly: boolean
|
||||
archiveCompiled: boolean
|
||||
}): string {
|
||||
if (argv.compileOnly && argv.archiveCompiled) {
|
||||
return 'compile-only+archive'
|
||||
}
|
||||
return argv.compileOnly ? 'compile-only' : 'full'
|
||||
}
|
||||
|
||||
export async function runProdResourceBuild(argv: string[]): Promise<void> {
|
||||
const rootDir = resolve(import.meta.dir, '../../..')
|
||||
@@ -22,7 +32,7 @@ export async function runProdResourceBuild(argv: string[]): Promise<void> {
|
||||
|
||||
log.header(`Building BrowserOS server artifacts v${buildConfig.version}`)
|
||||
log.info(`Targets: ${args.targets.map((target) => target.id).join(', ')}`)
|
||||
log.info(`Mode: ${args.compileOnly ? 'compile-only' : 'full'}`)
|
||||
log.info(`Mode: ${buildModeLabel(args)}`)
|
||||
|
||||
const compiled = await compileServerBinaries(
|
||||
args.targets,
|
||||
@@ -32,6 +42,30 @@ export async function runProdResourceBuild(argv: string[]): Promise<void> {
|
||||
)
|
||||
|
||||
if (args.compileOnly) {
|
||||
if (args.archiveCompiled) {
|
||||
const distRoot = getDistProdRoot()
|
||||
const localArtifacts = []
|
||||
|
||||
for (const binary of compiled) {
|
||||
log.step(`Packaging ${binary.target.name}`)
|
||||
const staged = await stageCompiledArtifact(
|
||||
distRoot,
|
||||
binary.binaryPath,
|
||||
binary.target,
|
||||
buildConfig.version,
|
||||
)
|
||||
localArtifacts.push(staged)
|
||||
log.success(`Packaged ${binary.target.id}`)
|
||||
}
|
||||
|
||||
const archiveResults = await archiveArtifacts(localArtifacts)
|
||||
log.done('Compile-only archive build completed')
|
||||
for (const result of archiveResults) {
|
||||
log.info(`${result.targetId}: ${result.zipPath}`)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
log.done('Compile-only build completed')
|
||||
for (const binary of compiled) {
|
||||
log.info(`${binary.target.id}: ${binary.binaryPath}`)
|
||||
|
||||
@@ -32,6 +32,36 @@ async function copyServerBinary(
|
||||
}
|
||||
}
|
||||
|
||||
async function createArtifactRoot(
|
||||
distRoot: string,
|
||||
compiledBinaryPath: string,
|
||||
target: BuildTarget,
|
||||
): Promise<string> {
|
||||
const rootDir = artifactRoot(distRoot, target)
|
||||
await rm(rootDir, { recursive: true, force: true })
|
||||
await mkdir(rootDir, { recursive: true })
|
||||
await copyServerBinary(
|
||||
compiledBinaryPath,
|
||||
serverDestinationPath(rootDir, target),
|
||||
target,
|
||||
)
|
||||
return rootDir
|
||||
}
|
||||
|
||||
async function finalizeArtifact(
|
||||
rootDir: string,
|
||||
target: BuildTarget,
|
||||
version: string,
|
||||
): Promise<StagedArtifact> {
|
||||
const metadataPath = await writeArtifactMetadata(rootDir, target, version)
|
||||
return {
|
||||
target,
|
||||
rootDir,
|
||||
resourcesDir: join(rootDir, 'resources'),
|
||||
metadataPath,
|
||||
}
|
||||
}
|
||||
|
||||
function resolveDestination(rootDir: string, destination: string): string {
|
||||
const outputPath = join(rootDir, destination)
|
||||
const relativePath = relative(rootDir, outputPath)
|
||||
@@ -67,25 +97,21 @@ export async function stageTargetArtifact(
|
||||
r2: R2Config,
|
||||
version: string,
|
||||
): Promise<StagedArtifact> {
|
||||
const rootDir = artifactRoot(distRoot, target)
|
||||
await rm(rootDir, { recursive: true, force: true })
|
||||
await mkdir(rootDir, { recursive: true })
|
||||
|
||||
await copyServerBinary(
|
||||
compiledBinaryPath,
|
||||
serverDestinationPath(rootDir, target),
|
||||
target,
|
||||
)
|
||||
const rootDir = await createArtifactRoot(distRoot, compiledBinaryPath, target)
|
||||
|
||||
for (const rule of rules) {
|
||||
await stageRule(rootDir, rule, target, client, r2)
|
||||
}
|
||||
|
||||
const metadataPath = await writeArtifactMetadata(rootDir, target, version)
|
||||
return {
|
||||
target,
|
||||
rootDir,
|
||||
resourcesDir: join(rootDir, 'resources'),
|
||||
metadataPath,
|
||||
}
|
||||
return finalizeArtifact(rootDir, target, version)
|
||||
}
|
||||
|
||||
export async function stageCompiledArtifact(
|
||||
distRoot: string,
|
||||
compiledBinaryPath: string,
|
||||
target: BuildTarget,
|
||||
version: string,
|
||||
): Promise<StagedArtifact> {
|
||||
const rootDir = await createArtifactRoot(distRoot, compiledBinaryPath, target)
|
||||
return finalizeArtifact(rootDir, target, version)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface BuildArgs {
|
||||
manifestPath: string
|
||||
upload: boolean
|
||||
compileOnly: boolean
|
||||
archiveCompiled: boolean
|
||||
}
|
||||
|
||||
export interface R2Config {
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
diff --git a/chrome/install_static/chromium_install_modes.h b/chrome/install_static/chromium_install_modes.h
|
||||
index 0cf937413e08a..a61c438a77379 100644
|
||||
index ee62888f89705..7ec72d302bc4b 100644
|
||||
--- a/chrome/install_static/chromium_install_modes.h
|
||||
+++ b/chrome/install_static/chromium_install_modes.h
|
||||
@@ -33,48 +33,49 @@ inline constexpr auto kInstallModes = std::to_array<InstallConstants>({
|
||||
@@ -21,7 +21,7 @@ inline constexpr wchar_t kCompanyPathName[] = L"";
|
||||
|
||||
// The brand-specific product name to be included as a component of the install
|
||||
// and user data directory paths.
|
||||
-inline constexpr wchar_t kProductPathName[] = L"Chromium";
|
||||
+inline constexpr wchar_t kProductPathName[] = L"BrowserOS";
|
||||
|
||||
// The brand-specific safe browsing client name.
|
||||
inline constexpr char kSafeBrowsingName[] = "chromium";
|
||||
@@ -44,48 +44,49 @@ inline constexpr auto kInstallModes = std::to_array<InstallConstants>({
|
||||
L"", // Empty install_suffix for the primary install mode.
|
||||
.logo_suffix = L"", // No logo suffix for the primary install mode.
|
||||
.app_guid =
|
||||
|
||||
Reference in New Issue
Block a user