Skip to content

Commit 6fced6d

Browse files
committed
Merge branch 'mh/server-img' into 'develop'
Build server image See merge request polycentric/polycentric!497
2 parents 5bec7c3 + cd16895 commit 6fced6d

14 files changed

Lines changed: 209 additions & 32 deletions

File tree

.gitlab-ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ include:
22
- local: '.gitlab/ci/build_rs_core.yml'
33
- local: '.gitlab/ci/build_js_sdks.yml'
44
- local: '.gitlab/ci/build_server.yml'
5+
- local: '.gitlab/ci/build_app.yml'
56
- local: '.gitlab/ci/deploy_pages.yml'
67
- local: '.gitlab/ci/scan_dependencies.yml'
78
stages:

.gitlab/ci/build_app.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
.app-workflow:
2+
image: gitlab.futo.org:5050/devops/manifest-repo/library/docker:latest
3+
rules:
4+
- if: $CI_COMMIT_TAG =~ /^app-/
5+
when: always
6+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
7+
changes:
8+
- apps/**/*
9+
- .gitlab/ci/build_app.yml
10+
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
11+
when: never
12+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
13+
changes:
14+
- sapps/**/*
15+
- .gitlab/ci/build_app.yml
16+
17+
app-web-image:
18+
extends: .app-workflow
19+
image:
20+
name: moby/buildkit:rootless
21+
entrypoint: ['']
22+
needs:
23+
- job: rs-sdk-build-wasm-browser
24+
artifacts: true
25+
before_script:
26+
- mkdir -p ~/.docker
27+
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
28+
stage: build
29+
script:
30+
- |
31+
buildctl-daemonless.sh build \
32+
--frontend dockerfile.v0 \
33+
--local context=. \
34+
--local dockerfile=./apps/polycentric \
35+
--output type=image,name=$CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHA,push=true

.gitlab/ci/build_rs_core.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
77
changes:
88
- packages/rs-core/**/*
9-
- packages/rs-common/**/*
9+
- packages/rs-common/**/*A
1010
- .gitlab/ci/build_rs_core.yml
1111
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
1212
when: never
@@ -95,6 +95,10 @@ rs-sdk-clippy:
9595

9696
rs-sdk-build-wasm-browser:
9797
extends: .rs-sdk-build-wasm
98+
rules:
99+
- !reference [.rs-sdk-workflow, rules]
100+
- !reference [.js-sdk-workflow, rules]
101+
- !reference [.app-workflow, rules]
98102
script:
99103
- just build-wasm-web
100104
artifacts:

.gitlab/ci/build_server.yml

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@
1515
- .gitlab/ci/build_server.yml
1616
variables:
1717
RUNNER_SCRIPT_TIMEOUT: 179m
18-
DOCKER_FULL_IMAGE_NAME: '${CI_REGISTRY}/${CI_PROJECT_PATH}'
1918
RUST_IMAGE: '${CI_REGISTRY}/${CI_PROJECT_PATH}:base'
20-
before_script:
21-
- cd services/server
2219

2320
.server-rust-base:
2421
extends: .server-workflow
@@ -28,19 +25,36 @@
2825
server-format-check:
2926
extends: .server-rust-base
3027
script:
31-
- cargo fmt --check
28+
- cargo fmt --check --package server
3229

3330
server-clippy:
3431
extends: .server-rust-base
3532
script:
36-
- cargo clippy -- -D warnings
33+
- cargo clippy --package server -- -D warnings
3734

3835
server-build:
3936
extends: .server-rust-base
4037
stage: build
4138
script:
42-
- cargo build --release
39+
- cargo build --release --package server
4340
artifacts:
4441
paths:
4542
- target/release/server
4643
expire_in: 1 week
44+
45+
server-image:
46+
extends: .server-workflow
47+
image:
48+
name: moby/buildkit:rootless
49+
entrypoint: ['']
50+
before_script:
51+
- mkdir -p ~/.docker
52+
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
53+
stage: build
54+
script:
55+
- |
56+
buildctl-daemonless.sh build \
57+
--frontend dockerfile.v0 \
58+
--local context=. \
59+
--local dockerfile=./services/server \
60+
--output type=image,name=$CI_REGISTRY_IMAGE/server:$CI_COMMIT_SHA,push=true

apps/polycentric/Dockerfile

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Stage 1: Base — Node + pnpm
2+
FROM node:24-slim AS base
3+
ENV PNPM_HOME="/pnpm"
4+
ENV PATH="$PNPM_HOME:$PATH"
5+
RUN corepack enable && corepack prepare pnpm@10.19.0 --activate
6+
7+
# Stage 2: Dependencies + Build
8+
FROM base AS build
9+
WORKDIR /app
10+
11+
COPY .npmrc turbo.jsonc pnpm-lock.yaml pnpm-workspace.yaml package.json ./
12+
COPY apps apps
13+
COPY packages packages
14+
COPY tools tools
15+
RUN pnpm install --frozen-lockfile
16+
# Build js-*
17+
RUN pnpm run build
18+
# Export Expo static app
19+
RUN cd apps/polycentric && npx expo export --platform web
20+
# Self-contained prod bundle: real-file node_modules + package.json (no symlinks)
21+
RUN pnpm --filter polycentric-app --prod --legacy deploy /deploy
22+
23+
# Stage 3: Run
24+
FROM base AS runner
25+
WORKDIR /app
26+
ENV NODE_ENV=production
27+
28+
COPY --from=build /deploy/node_modules ./node_modules
29+
COPY --from=build /deploy/package.json ./package.json
30+
COPY --from=build /app/apps/polycentric/dist ./dist
31+
COPY --from=build /app/apps/polycentric/server.js ./server.js
32+
33+
EXPOSE 3000
34+
CMD ["node", "server.js"]

apps/polycentric/app.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"icon": "./src/common/assets/images/AppIcon.png",
88
"scheme": "polycentric",
99
"web": {
10-
"output": "static"
10+
"output": "server"
1111
},
1212
"userInterfaceStyle": "automatic",
1313
"ios": {

apps/polycentric/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@expo/vector-icons": "^15.0.3",
1818
"@gorhom/bottom-sheet": "^5.2.8",
1919
"@lodev09/react-native-true-sheet": "^3.0.4",
20+
"@noble/hashes": "catalog:",
2021
"@op-engineering/op-sqlite": "^15.2.5",
2122
"@polycentric/react-native": "workspace:*",
2223
"@react-native-async-storage/async-storage": "^2.2.0",
@@ -38,6 +39,7 @@
3839
"expo-image-picker": "~55.0.10",
3940
"expo-linking": "~55.0.7",
4041
"expo-router": "~55.0.3",
42+
"expo-server": "~55.0.8",
4143
"expo-splash-screen": "~55.0.10",
4244
"expo-status-bar": "~55.0.4",
4345
"expo-symbols": "~55.0.4",

apps/polycentric/server.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const path = require('node:path');
2+
const fs = require('node:fs');
3+
const { createServer } = require('node:http');
4+
const { createRequestHandler } = require('expo-server/vendor/http');
5+
6+
const CLIENT_DIR = path.join(__dirname, 'dist', 'client');
7+
const handler = createRequestHandler({
8+
build: path.join(__dirname, 'dist', 'server'),
9+
});
10+
const port = process.env.PORT || 3000;
11+
12+
const MIME_TYPES = {
13+
'.html': 'text/html',
14+
'.js': 'application/javascript',
15+
'.css': 'text/css',
16+
'.json': 'application/json',
17+
'.png': 'image/png',
18+
'.jpg': 'image/jpeg',
19+
'.svg': 'image/svg+xml',
20+
'.ico': 'image/x-icon',
21+
'.woff': 'font/woff',
22+
'.woff2': 'font/woff2',
23+
'.ttf': 'font/ttf',
24+
'.map': 'application/json',
25+
};
26+
27+
function serveStatic(req, res, next) {
28+
if (req.method !== 'GET' && req.method !== 'HEAD') return next();
29+
30+
const urlPath = decodeURIComponent(req.url.split('?')[0]);
31+
const filePath = path.join(CLIENT_DIR, urlPath);
32+
33+
if (!filePath.startsWith(CLIENT_DIR)) return next();
34+
35+
fs.stat(filePath, (err, stats) => {
36+
if (err || !stats.isFile()) return next();
37+
38+
const ext = path.extname(filePath);
39+
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
40+
41+
res.setHeader('Content-Type', contentType);
42+
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
43+
fs.createReadStream(filePath).pipe(res);
44+
});
45+
}
46+
47+
createServer((req, res) => {
48+
serveStatic(req, res, () => {
49+
handler(req, res, (err) => {
50+
if (err) {
51+
console.error(err);
52+
res.statusCode = 500;
53+
res.end('Internal Server Error');
54+
}
55+
});
56+
});
57+
}).listen(port, () => {
58+
console.log(`Server listening on port ${port}`);
59+
});

packages/js-browser/examples/react/src/components/posts/post-compose.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const PostCompose = () => {
2020
oneofKind: 'post',
2121
post: {
2222
text: postField.current.value,
23+
images: [],
2324
},
2425
});
2526
await client.contentManager.save(content);

packages/js-browser/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"dependencies": {
3434
"@polycentric/js-core": "workspace:*",
3535
"@noble/curves": "^1.9.0",
36-
"@noble/hashes": "^1.8.0",
36+
"@noble/hashes": "catalog:",
3737
"@sqlite.org/sqlite-wasm": "3.49.1-build4",
3838
"js-base64": "^3.7.8"
3939
},

0 commit comments

Comments
 (0)