stack-service-base 0.0.99 → 0.0.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/stack-service-base/command_init.rb +5 -3
- data/lib/stack-service-base/frontend_template/github/.github/workflows/main.yml +19 -0
- data/lib/stack-service-base/frontend_template/github/docker/docker-compose.yml +15 -0
- data/lib/stack-service-base/frontend_template/gitlab/.gitlab-ci.yml +48 -0
- data/lib/stack-service-base/frontend_template/gitlab/docker/Dockerfile.build +36 -0
- data/lib/stack-service-base/frontend_template/gitlab/docker/docker-compose.yml +15 -0
- data/lib/stack-service-base/frontend_template/gitlab/docker/local_build.sh +10 -0
- data/lib/stack-service-base/frontend_template/gitlab-c/.gitlab-ci.yml +24 -0
- data/lib/stack-service-base/frontend_template/gitlab-c/docker/Dockerfile.build +17 -0
- data/lib/stack-service-base/frontend_template/gitlab-c/docker/docker-compose.yml +15 -0
- data/lib/stack-service-base/frontend_template/gitlab-c/docker/local_build.sh +11 -0
- data/lib/stack-service-base/frontend_template/home/.editorconfig +12 -0
- data/lib/stack-service-base/frontend_template/home/.gitattributes +4 -0
- data/lib/stack-service-base/frontend_template/home/.gitignore +17 -0
- data/lib/stack-service-base/frontend_template/home/AGENTS.md +3 -0
- data/lib/stack-service-base/frontend_template/home/README.md +68 -0
- data/lib/stack-service-base/frontend_template/home/docker/.env +3 -0
- data/lib/stack-service-base/frontend_template/home/docker/frontend/15-runtime-settings.envsh +90 -0
- data/lib/stack-service-base/frontend_template/home/docker/frontend/40-path-prefix-settings.sh +30 -0
- data/lib/stack-service-base/frontend_template/home/docker/frontend/45-runtime-config-settings.sh +24 -0
- data/lib/stack-service-base/frontend_template/home/docker/frontend/Dockerfile +46 -0
- data/lib/stack-service-base/frontend_template/home/docker/frontend/nginx.conf.template +75 -0
- data/lib/stack-service-base/frontend_template/home/src/.dockerignore +9 -0
- data/lib/stack-service-base/frontend_template/home/src/.env.example +7 -0
- data/lib/stack-service-base/frontend_template/home/src/.version +1 -0
- data/lib/stack-service-base/frontend_template/home/src/app/main.ts +4 -0
- data/lib/stack-service-base/frontend_template/home/src/app/runtime-config.ts +22 -0
- data/lib/stack-service-base/frontend_template/home/src/app/runtime-config.types.ts +5 -0
- data/lib/stack-service-base/frontend_template/home/src/app/style.css +82 -0
- data/lib/stack-service-base/frontend_template/home/src/app/vite-env.d.ts +1 -0
- data/lib/stack-service-base/frontend_template/home/src/eslint.config.js +34 -0
- data/lib/stack-service-base/frontend_template/home/src/index.html +24 -0
- data/lib/stack-service-base/frontend_template/home/src/package-lock.json +2812 -0
- data/lib/stack-service-base/frontend_template/home/src/package.json +30 -0
- data/lib/stack-service-base/frontend_template/home/src/proxy.config.json +8 -0
- data/lib/stack-service-base/frontend_template/home/src/public/runtime-config.js +1 -0
- data/lib/stack-service-base/frontend_template/home/src/tests/build-proxy.test.ts +28 -0
- data/lib/stack-service-base/frontend_template/home/src/tests/javascript-support.test.js +23 -0
- data/lib/stack-service-base/frontend_template/home/src/tests/path-prefix.test.ts +17 -0
- data/lib/stack-service-base/frontend_template/home/src/tests/runtime-config.test.ts +22 -0
- data/lib/stack-service-base/frontend_template/home/src/tests/vite-config.test.ts +16 -0
- data/lib/stack-service-base/frontend_template/home/src/tools/vite/build-proxy.ts +62 -0
- data/lib/stack-service-base/frontend_template/home/src/tools/vite/path-prefix.ts +27 -0
- data/lib/stack-service-base/frontend_template/home/src/tools/vite/runtime-config-dev-plugin.ts +27 -0
- data/lib/stack-service-base/frontend_template/home/src/tools/vite/runtime-config.ts +10 -0
- data/lib/stack-service-base/frontend_template/home/src/tsconfig.json +27 -0
- data/lib/stack-service-base/frontend_template/home/src/vite.config.ts +40 -0
- data/lib/stack-service-base/version.rb +1 -1
- metadata +47 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6d908ed870ee59a5ea085accfe3bc86f2276baeca5780407917af9a59e5eebb7
|
|
4
|
+
data.tar.gz: b7d31c74bbdcd98f55d827ed9630c17f42aaf5f503a9585ade7288b8c6754def
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e8e3b4ccb5793e85757223306b50afde56a28078ce28468baebd68e0a0dd302b5413afef0415d0679a5792c374521e5cf0a571f0399449399739dc312f6fd1a9
|
|
7
|
+
data.tar.gz: d68eadab213ce6003e64ad54495758a0cc5da87e0ae5ecf83c0587d097205488a386edc106e2f8da233870d79e5b1bd9f79c5c0555b18d289ac59f105dc2a47d
|
|
@@ -6,12 +6,13 @@ SSBase::CommandLine::COMMANDS[:init] = Class.new do
|
|
|
6
6
|
parser.on('', '--gitlab-c', 'Gitlab C CI/CD')
|
|
7
7
|
parser.on('', '--gitlab', 'Gitlab CI/CD')
|
|
8
8
|
parser.on('', '--github', 'GitHub CI/CD')
|
|
9
|
+
parser.on('', '--frontend', 'Vite frontend service')
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
def run(obj, params, args, _extra)
|
|
12
13
|
if params.empty? || args.empty?
|
|
13
14
|
puts 'Usage:'
|
|
14
|
-
puts "\tssbase init [option] <service name>"
|
|
15
|
+
puts "\tssbase init [--frontend] [option] <service name>"
|
|
15
16
|
puts "\tssbase init stack [option]"
|
|
16
17
|
puts 'option: --gitlab-c, --gitlab, --github'
|
|
17
18
|
return
|
|
@@ -23,13 +24,14 @@ SSBase::CommandLine::COMMANDS[:init] = Class.new do
|
|
|
23
24
|
type = :stack
|
|
24
25
|
service_name = 'fake-service-name'
|
|
25
26
|
else
|
|
26
|
-
type = :project
|
|
27
|
+
type = params[:frontend] ? :frontend : :project
|
|
27
28
|
service_name = first_arg
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
copy_folder type, :home
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
default_platform = params.values_at(:gitlab_c, :gitlab, :github).none?
|
|
34
|
+
copy_folder type, 'gitlab-c' if params[:gitlab_c] || default_platform
|
|
33
35
|
copy_folder type, 'gitlab' if params[:gitlab]
|
|
34
36
|
copy_folder type, 'github' if params[:github]
|
|
35
37
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
on: { push: { branches: [main,release] } }
|
|
2
|
+
|
|
3
|
+
jobs:
|
|
4
|
+
build_service_images:
|
|
5
|
+
runs-on: ubuntu-latest
|
|
6
|
+
steps:
|
|
7
|
+
- uses: actions/checkout@v3
|
|
8
|
+
- uses: docker/login-action@v2
|
|
9
|
+
with: { username: '${{ secrets.DOCKER_HUB_LOGIN }}', password: '${{ secrets.DOCKER_HUB_PASSWORD }}' }
|
|
10
|
+
- uses: docker/setup-buildx-action@v3
|
|
11
|
+
- uses: ruby/setup-ruby@v1
|
|
12
|
+
with: { ruby-version: 3.4.4 }
|
|
13
|
+
- run: gem install build-labels
|
|
14
|
+
- run: env && cd docker && build-labels -n -c docker-compose.yml $(if [ "${GITHUB_REF##*/}" = "release" ]; then echo "--full-version"; fi) github set_version to_dockerfiles to_compose | tee bake.yml
|
|
15
|
+
env:
|
|
16
|
+
GITHUB_RUN_NUMBER: ${{ github.run_number }}
|
|
17
|
+
- uses: docker/bake-action@v5
|
|
18
|
+
env: { REGISTRY_HOST: '${{ secrets.DOCKER_HUB_LOGIN }}' }
|
|
19
|
+
with: { workdir: ./docker, files: bake.yml, push: true }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
services:
|
|
2
|
+
${service_name}:
|
|
3
|
+
image: ${REGISTRY_HOST}/${service_name}
|
|
4
|
+
build:
|
|
5
|
+
context: ../src
|
|
6
|
+
additional_contexts:
|
|
7
|
+
docker: ../docker
|
|
8
|
+
dockerfile: ../docker/frontend/Dockerfile
|
|
9
|
+
ports:
|
|
10
|
+
- "7000:8080"
|
|
11
|
+
environment:
|
|
12
|
+
API_BASE_URL: ${API_BASE_URL:-}
|
|
13
|
+
BACKEND_URL: ${BACKEND_URL:-http://127.0.0.1:65535}
|
|
14
|
+
LOG_LEVEL: ${LOG_LEVEL:-info}
|
|
15
|
+
PATH_PREFIX: ${PATH_PREFIX:-}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
stages:
|
|
2
|
+
- test
|
|
3
|
+
- build
|
|
4
|
+
|
|
5
|
+
.build_block: &build_block
|
|
6
|
+
# tags: [ build ]
|
|
7
|
+
image: docker:27.5.1-cli
|
|
8
|
+
services:
|
|
9
|
+
- name: docker:27.5.1-dind
|
|
10
|
+
alias: docker
|
|
11
|
+
variables:
|
|
12
|
+
DOCKER_HOST: tcp://docker:2375
|
|
13
|
+
DOCKER_TLS_CERTDIR: ""
|
|
14
|
+
script:
|
|
15
|
+
- export CI_COMMIT_TAG="${CI_COMMIT_TAG:-0.0.0}"
|
|
16
|
+
- |
|
|
17
|
+
if [ -n "${CI_REGISTRY:-}" ] && [ -n "${CI_REGISTRY_USER:-}" ] && [ -n "${CI_REGISTRY_PASSWORD:-}" ]; then
|
|
18
|
+
echo "${CI_REGISTRY_PASSWORD}" | docker login "${CI_REGISTRY}" -u "${CI_REGISTRY_USER}" --password-stdin
|
|
19
|
+
fi
|
|
20
|
+
- docker buildx create --name "${CI_PROJECT_NAME}-wrapper-builder" --driver docker-container --use || docker buildx use "${CI_PROJECT_NAME}-wrapper-builder"
|
|
21
|
+
- docker buildx inspect --bootstrap
|
|
22
|
+
- |
|
|
23
|
+
docker buildx build --load \
|
|
24
|
+
-t build/${CI_PROJECT_NAME} \
|
|
25
|
+
-f docker/Dockerfile.build \
|
|
26
|
+
--cache-from type=registry,ref=${CI_REGISTRY_IMAGE}/ci-wrapper:${CI_COMMIT_REF_SLUG}-cache \
|
|
27
|
+
--cache-to type=registry,ref=${CI_REGISTRY_IMAGE}/ci-wrapper:${CI_COMMIT_REF_SLUG}-cache,mode=max \
|
|
28
|
+
.
|
|
29
|
+
- docker run --rm `env | grep -o '^CI_[^=]*' | sed 's/^/-e /'`
|
|
30
|
+
-e REGISTRY_HOST=$CI_REGISTRY/$CI_PROJECT_NAMESPACE
|
|
31
|
+
-v /var/run/docker.sock:/var/run/docker.sock
|
|
32
|
+
-v /root/.docker:/root/.docker
|
|
33
|
+
build/${CI_PROJECT_NAME} 2>&1
|
|
34
|
+
|
|
35
|
+
build push:
|
|
36
|
+
<<: *build_block
|
|
37
|
+
stage: build
|
|
38
|
+
needs: [ tests ]
|
|
39
|
+
|
|
40
|
+
tests:
|
|
41
|
+
<<: *build_block
|
|
42
|
+
stage: test
|
|
43
|
+
variables:
|
|
44
|
+
DOCKER_HOST: tcp://docker:2375
|
|
45
|
+
DOCKER_TLS_CERTDIR: ""
|
|
46
|
+
CI_SKIP_PUSH: true
|
|
47
|
+
# build-labels sets target for each service in docker-compose.yml
|
|
48
|
+
CI_BUILD_TARGET: tests
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
FROM ruby:3.4.4-slim-bookworm AS base
|
|
2
|
+
RUN apt-get update && apt-get install -y --no-install-recommends bash ca-certificates curl git tar \
|
|
3
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
4
|
+
RUN gem install build-labels:0.0.79
|
|
5
|
+
RUN BUILDX_VERSION=v0.20.1 COMPOSE_VERSION=v2.33.0 install-docker-static 27.5.1
|
|
6
|
+
|
|
7
|
+
WORKDIR /build
|
|
8
|
+
|
|
9
|
+
COPY .. .
|
|
10
|
+
|
|
11
|
+
CMD ["bash", "-euo", "pipefail", "-c", "\
|
|
12
|
+
cd /build/docker; \
|
|
13
|
+
if [ -n \"${CI_REGISTRY:-}\" ] && [ -n \"${CI_REGISTRY_USER:-}\" ] && [ -n \"${CI_REGISTRY_PASSWORD:-}\" ]; then \
|
|
14
|
+
echo \"$CI_REGISTRY_PASSWORD\" | docker login \"$CI_REGISTRY\" -u \"$CI_REGISTRY_USER\" --password-stdin; \
|
|
15
|
+
fi; \
|
|
16
|
+
build-labels -n -c docker-compose.yml changed gitlab set_version cache to_dockerfiles to_compose | tee bake.yml; \
|
|
17
|
+
export OTEL_RESOURCE_ATTRIBUTES=\"service.name=docker-builder,pipeline.id=${CI_PIPELINE_ID:-local},project.name=${service_name}\"; \
|
|
18
|
+
export REGISTRY_HOST=\"${REGISTRY_HOST:-${CI_REGISTRY_HOST:-${CI_REGISTRY_IMAGE:-}}}\"; \
|
|
19
|
+
: \"${REGISTRY_HOST:?REGISTRY_HOST, CI_REGISTRY_IMAGE, or CI_REGISTRY_HOST is required}\"; \
|
|
20
|
+
export BUILDX_BAKE_ENTITLEMENTS_FS=0; \
|
|
21
|
+
if grep -q \"services: {}\" bake.yml; then \
|
|
22
|
+
echo \"No changed services to build.\"; \
|
|
23
|
+
exit 0; \
|
|
24
|
+
fi; \
|
|
25
|
+
docker buildx create --name \"${service_name}-builder\" --driver docker-container --use || docker buildx use \"${service_name}-builder\"; \
|
|
26
|
+
docker buildx inspect --bootstrap; \
|
|
27
|
+
if [ -n \"${CI_SKIP_PUSH:-}\" ]; then \
|
|
28
|
+
docker buildx bake --load --allow=fs.read=../src -f bake.yml; \
|
|
29
|
+
else \
|
|
30
|
+
docker buildx bake --push --allow=fs.read=../src -f bake.yml; \
|
|
31
|
+
fi; \
|
|
32
|
+
if [ \"${CI_BUILD_TARGET:-}\" = \"tests\" ]; then \
|
|
33
|
+
docker compose -f bake.yml down --remove-orphans; \
|
|
34
|
+
docker compose -f bake.yml up --no-build --force-recreate --abort-on-container-failure; \
|
|
35
|
+
fi \
|
|
36
|
+
"]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
services:
|
|
2
|
+
${service_name}:
|
|
3
|
+
image: ${REGISTRY_HOST}/${service_name}/${CI_COMMIT_REF_SLUG:-local}
|
|
4
|
+
build:
|
|
5
|
+
context: ../src
|
|
6
|
+
additional_contexts:
|
|
7
|
+
docker: ../docker
|
|
8
|
+
dockerfile: ../docker/frontend/Dockerfile
|
|
9
|
+
ports:
|
|
10
|
+
- "7000:8080"
|
|
11
|
+
environment:
|
|
12
|
+
API_BASE_URL: ${API_BASE_URL:-}
|
|
13
|
+
BACKEND_URL: ${BACKEND_URL:-http://127.0.0.1:65535}
|
|
14
|
+
LOG_LEVEL: ${LOG_LEVEL:-info}
|
|
15
|
+
PATH_PREFIX: ${PATH_PREFIX:-}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export CI_PROJECT_NAME=${service_name}
|
|
2
|
+
export CI_PIPELINE_ID=local
|
|
3
|
+
export CI_PIPELINE_IID=0
|
|
4
|
+
export CI_REGISTRY_HOST=localhost
|
|
5
|
+
export CI_SKIP_PUSH=true
|
|
6
|
+
|
|
7
|
+
# -v /root/.docker:/root/.docker \
|
|
8
|
+
docker run --rm --env-file <(env | grep ^CI_) \
|
|
9
|
+
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
10
|
+
$(docker build -q -t build/${CI_PROJECT_NAME} -f Dockerfile.build ..) 2>&1
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
stages:
|
|
2
|
+
- first
|
|
3
|
+
|
|
4
|
+
.build_block: &build_block
|
|
5
|
+
stage: first
|
|
6
|
+
tags: [ build ]
|
|
7
|
+
needs: [ ]
|
|
8
|
+
image: docker:27.5.1
|
|
9
|
+
script:
|
|
10
|
+
- export CI_COMMIT_TAG="${CI_COMMIT_TAG:-0.0.0}"
|
|
11
|
+
- docker run --rm `env | grep -o '^CI_[^=]*' | sed 's/^/-e /'`
|
|
12
|
+
-v /var/run/docker.sock:/var/run/docker.sock
|
|
13
|
+
-v /root/.docker:/root/.docker
|
|
14
|
+
$(docker build -q -t build/${CI_PROJECT_NAME} -f docker/Dockerfile.build .) 2>&1
|
|
15
|
+
|
|
16
|
+
build push:
|
|
17
|
+
<<: *build_block
|
|
18
|
+
|
|
19
|
+
tests:
|
|
20
|
+
<<: *build_block
|
|
21
|
+
variables:
|
|
22
|
+
CI_SKIP_PUSH: true
|
|
23
|
+
# build-labels sets target for each service in docker-compose.yml
|
|
24
|
+
CI_BUILD_TARGET: tests
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
FROM ruby:3.4.4-slim-bookworm AS base
|
|
2
|
+
RUN apt update && apt install -y --no-install-recommends bash git
|
|
3
|
+
RUN gem install build-labels:0.0.79
|
|
4
|
+
RUN BUILDX_VERSION=v0.20.1 COMPOSE_VERSION=v2.33.0 install-docker-static 27.5.1
|
|
5
|
+
|
|
6
|
+
WORKDIR /build
|
|
7
|
+
|
|
8
|
+
COPY . .
|
|
9
|
+
|
|
10
|
+
CMD ["bash", "-c", "-x", "\
|
|
11
|
+
env && cd /build/docker && \
|
|
12
|
+
build-labels -n -c docker-compose.yml changed gitlab set_version to_dockerfiles to_compose | tee bake.yml && \
|
|
13
|
+
export OTEL_RESOURCE_ATTRIBUTES=service.name=docker-builder,pipeline.id=${CI_PIPELINE_ID},project.name=${CI_PROJECT_NAME} && \
|
|
14
|
+
export REGISTRY_HOST=$CI_REGISTRY_HOST && export BUILDX_BAKE_ENTITLEMENTS_FS=0 && \
|
|
15
|
+
grep \"services: {}\" bake.yml || docker buildx bake -f bake.yml $([ -z \"$CI_SKIP_PUSH\" ] && echo \"--push\") && \
|
|
16
|
+
([ \"$CI_BUILD_TARGET\" = \"tests\" ] && (docker compose -f bake.yml down && docker compose -f bake.yml up --force-recreate ) || true) \
|
|
17
|
+
"]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
services:
|
|
2
|
+
${service_name}:
|
|
3
|
+
image: ${REGISTRY_HOST}/${service_name}/${CI_COMMIT_BRANCH}
|
|
4
|
+
build:
|
|
5
|
+
context: ../src
|
|
6
|
+
additional_contexts:
|
|
7
|
+
docker: ../docker
|
|
8
|
+
dockerfile: ../docker/frontend/Dockerfile
|
|
9
|
+
ports:
|
|
10
|
+
- "7000:8080"
|
|
11
|
+
environment:
|
|
12
|
+
API_BASE_URL: ${API_BASE_URL:-}
|
|
13
|
+
BACKEND_URL: ${BACKEND_URL:-http://127.0.0.1:65535}
|
|
14
|
+
LOG_LEVEL: ${LOG_LEVEL:-info}
|
|
15
|
+
PATH_PREFIX: ${PATH_PREFIX:-}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
CI_PROJECT_NAME=${service_name}
|
|
2
|
+
export CI_PIPELINE_ID=local
|
|
3
|
+
export CI_PIPELINE_IID=0
|
|
4
|
+
export CI_REGISTRY_HOST=localhost
|
|
5
|
+
export CI_SKIP_PUSH=true
|
|
6
|
+
|
|
7
|
+
# -v /root/.docker:/root/.docker \
|
|
8
|
+
docker run --rm --env-file <(env | grep ^CI_) \
|
|
9
|
+
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
10
|
+
$(docker build -q -t build/${CI_PROJECT_NAME} -f Dockerfile.build ..) 2>&1
|
|
11
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# ${service_name}
|
|
2
|
+
|
|
3
|
+
Framework-neutral frontend service starter for the corporate stack. It includes
|
|
4
|
+
local development, checks, production build, nginx runtime, containers, and CI.
|
|
5
|
+
Choose the UI framework, router, and domain structure, and adapt the supplied
|
|
6
|
+
infrastructure to the service requirements.
|
|
7
|
+
|
|
8
|
+
## Development
|
|
9
|
+
|
|
10
|
+
Node.js 24 LTS is required for local development.
|
|
11
|
+
|
|
12
|
+
cd src
|
|
13
|
+
cp .env.example .env.local
|
|
14
|
+
npm ci
|
|
15
|
+
npm start
|
|
16
|
+
|
|
17
|
+
The development server listens on http://localhost:3000. Set BACKEND_URL in
|
|
18
|
+
.env.local to enable the same-origin /api proxy.
|
|
19
|
+
|
|
20
|
+
Application code and tests may use JavaScript, TypeScript, or both. TypeScript
|
|
21
|
+
remains strict; JavaScript requires no type annotations and is checked by ESLint.
|
|
22
|
+
|
|
23
|
+
## Checks
|
|
24
|
+
|
|
25
|
+
cd src
|
|
26
|
+
npm test
|
|
27
|
+
npm run check
|
|
28
|
+
|
|
29
|
+
`npm test` runs the tests. `npm run check` additionally runs TypeScript
|
|
30
|
+
validation, ESLint, and a production build.
|
|
31
|
+
|
|
32
|
+
Run a local production preview with:
|
|
33
|
+
|
|
34
|
+
npm run preview
|
|
35
|
+
|
|
36
|
+
The preview command builds before serving. For a prefixed deployment, run
|
|
37
|
+
`PATH_PREFIX=/example npm run preview` and open
|
|
38
|
+
http://localhost:3000/example/.
|
|
39
|
+
|
|
40
|
+
## Container
|
|
41
|
+
|
|
42
|
+
docker-compose -f docker/docker-compose.yml up --build
|
|
43
|
+
|
|
44
|
+
The service is available at http://localhost:7000. nginx runs unprivileged on
|
|
45
|
+
container port 8080, exposes /healthcheck, and supports PATH_PREFIX without
|
|
46
|
+
rebuilding the image.
|
|
47
|
+
|
|
48
|
+
PATH_PREFIX may be empty or contain letters, digits, `_`, `-`, and `/`.
|
|
49
|
+
The complete prefixes `/api`, `/assets`, and `/healthcheck` are reserved.
|
|
50
|
+
|
|
51
|
+
Browser-visible runtime configuration:
|
|
52
|
+
|
|
53
|
+
- PATH_PREFIX
|
|
54
|
+
- API_BASE_URL
|
|
55
|
+
- LOG_LEVEL
|
|
56
|
+
|
|
57
|
+
runtime-config.js is public; never put credentials or secrets into these values.
|
|
58
|
+
BACKEND_URL is server-only and configures the nginx /api proxy.
|
|
59
|
+
|
|
60
|
+
nginx uses a same-origin Content Security Policy. Extend the corresponding
|
|
61
|
+
directive in `docker/frontend/nginx.conf.template` when external resources are
|
|
62
|
+
required.
|
|
63
|
+
|
|
64
|
+
## CI
|
|
65
|
+
|
|
66
|
+
GitLab follows the same corporate `build-labels` orchestration as Ruby services.
|
|
67
|
+
Ruby only runs that tool; the frontend is tested and built with Node and served
|
|
68
|
+
by nginx at runtime.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
raw_path_prefix=$(printf '%s' "${PATH_PREFIX:-}" |
|
|
4
|
+
sed -E 's#^[[:space:]]+|[[:space:]]+$##g; s#^/+##; s#/+$##')
|
|
5
|
+
|
|
6
|
+
case "$raw_path_prefix" in
|
|
7
|
+
'')
|
|
8
|
+
PATH_PREFIX=''
|
|
9
|
+
PATH_PREFIX_RULE=''
|
|
10
|
+
;;
|
|
11
|
+
*[!A-Za-z0-9_/-]* | *'//'*)
|
|
12
|
+
echo "Invalid PATH_PREFIX: ${PATH_PREFIX:-}" >&2
|
|
13
|
+
exit 1
|
|
14
|
+
;;
|
|
15
|
+
api | assets | healthcheck)
|
|
16
|
+
echo "PATH_PREFIX is reserved by the service runtime: /$raw_path_prefix" >&2
|
|
17
|
+
exit 1
|
|
18
|
+
;;
|
|
19
|
+
*)
|
|
20
|
+
PATH_PREFIX="/$raw_path_prefix"
|
|
21
|
+
PATH_PREFIX_RULE=$(printf \
|
|
22
|
+
'location = %s { absolute_redirect off; return 308 %s/; }\n location ^~ %s/ { rewrite ^%s/(.*)$ /$1 last; }' \
|
|
23
|
+
"$PATH_PREFIX" "$PATH_PREFIX" "$PATH_PREFIX" "$PATH_PREFIX")
|
|
24
|
+
;;
|
|
25
|
+
esac
|
|
26
|
+
|
|
27
|
+
dns_resolver=${DNS_RESOLVER:-$(awk '$1 == "nameserver" { print $2; exit }' /etc/resolv.conf)}
|
|
28
|
+
|
|
29
|
+
case "$dns_resolver" in
|
|
30
|
+
'' | *[!A-Fa-f0-9:.]*)
|
|
31
|
+
echo "Unable to determine a valid DNS resolver" >&2
|
|
32
|
+
exit 1
|
|
33
|
+
;;
|
|
34
|
+
esac
|
|
35
|
+
|
|
36
|
+
DNS_RESOLVER=$dns_resolver
|
|
37
|
+
|
|
38
|
+
backend_url=${BACKEND_URL:-http://127.0.0.1:65535}
|
|
39
|
+
backend_url=${backend_url%/}
|
|
40
|
+
|
|
41
|
+
case "$backend_url" in
|
|
42
|
+
http://* | https://*) ;;
|
|
43
|
+
*)
|
|
44
|
+
echo "BACKEND_URL must be an http or https origin" >&2
|
|
45
|
+
exit 1
|
|
46
|
+
;;
|
|
47
|
+
esac
|
|
48
|
+
|
|
49
|
+
BACKEND_PROTO=${backend_url%%://*}
|
|
50
|
+
backend_authority=${backend_url#*://}
|
|
51
|
+
|
|
52
|
+
case "$backend_authority" in
|
|
53
|
+
'' | */* | *'?'* | *'#'* | *'@'*)
|
|
54
|
+
echo "BACKEND_URL must not contain credentials, query, fragment, or path" >&2
|
|
55
|
+
exit 1
|
|
56
|
+
;;
|
|
57
|
+
esac
|
|
58
|
+
|
|
59
|
+
case "$backend_authority" in
|
|
60
|
+
*:*)
|
|
61
|
+
BACKEND_HOST=${backend_authority%%:*}
|
|
62
|
+
BACKEND_PORT=${backend_authority##*:}
|
|
63
|
+
;;
|
|
64
|
+
*)
|
|
65
|
+
BACKEND_HOST=$backend_authority
|
|
66
|
+
if [ "$BACKEND_PROTO" = 'https' ]; then
|
|
67
|
+
BACKEND_PORT=443
|
|
68
|
+
else
|
|
69
|
+
BACKEND_PORT=80
|
|
70
|
+
fi
|
|
71
|
+
;;
|
|
72
|
+
esac
|
|
73
|
+
|
|
74
|
+
case "$BACKEND_HOST" in
|
|
75
|
+
'' | *[!A-Za-z0-9_.-]*)
|
|
76
|
+
echo "BACKEND_URL contains an invalid host" >&2
|
|
77
|
+
exit 1
|
|
78
|
+
;;
|
|
79
|
+
esac
|
|
80
|
+
|
|
81
|
+
case "$BACKEND_PORT" in
|
|
82
|
+
'' | *[!0-9]*)
|
|
83
|
+
echo "BACKEND_URL contains an invalid port" >&2
|
|
84
|
+
exit 1
|
|
85
|
+
;;
|
|
86
|
+
esac
|
|
87
|
+
|
|
88
|
+
export PATH_PREFIX PATH_PREFIX_RULE DNS_RESOLVER BACKEND_PROTO BACKEND_HOST BACKEND_PORT
|
|
89
|
+
|
|
90
|
+
unset raw_path_prefix dns_resolver backend_url backend_authority
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
compiled_app_dir=${COMPILED_APP_DIR:-/usr/share/nginx/html}
|
|
5
|
+
placeholder='/__PATH_PREFIX__/'
|
|
6
|
+
path_prefix=$(printf '%s' "${PATH_PREFIX:-}" |
|
|
7
|
+
sed -E 's#^[[:space:]]+|[[:space:]]+$##g; s#^/+##; s#/+$##')
|
|
8
|
+
|
|
9
|
+
if [ -n "$path_prefix" ]; then
|
|
10
|
+
replacement="/$path_prefix/"
|
|
11
|
+
else
|
|
12
|
+
replacement='/'
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
escaped_replacement=$(printf '%s' "$replacement" | sed -e 's/[\\&|]/\\&/g')
|
|
16
|
+
|
|
17
|
+
find "$compiled_app_dir" -type f \
|
|
18
|
+
\( -name '*.html' -o -name '*.js' -o -name '*.css' -o -name '*.map' \) -print |
|
|
19
|
+
while IFS= read -r file; do
|
|
20
|
+
if grep -q "$placeholder" "$file"; then
|
|
21
|
+
sed -i "s|$placeholder|$escaped_replacement|g" "$file"
|
|
22
|
+
fi
|
|
23
|
+
done
|
|
24
|
+
|
|
25
|
+
if find "$compiled_app_dir" -type f \
|
|
26
|
+
\( -name '*.html' -o -name '*.js' -o -name '*.css' -o -name '*.map' \) \
|
|
27
|
+
-exec grep -q "$placeholder" {} \; -print | grep -q .; then
|
|
28
|
+
echo "PATH_PREFIX placeholder remains in compiled assets" >&2
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
data/lib/stack-service-base/frontend_template/home/docker/frontend/45-runtime-config-settings.sh
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
compiled_app_dir=${COMPILED_APP_DIR:-/usr/share/nginx/html}
|
|
5
|
+
output="$compiled_app_dir/runtime-config.js"
|
|
6
|
+
temporary_output="$output.tmp"
|
|
7
|
+
path_prefix=$(printf '%s' "${PATH_PREFIX:-}" |
|
|
8
|
+
sed -E 's#^[[:space:]]+|[[:space:]]+$##g; s#^/+##; s#/+$##')
|
|
9
|
+
|
|
10
|
+
if [ -n "$path_prefix" ]; then
|
|
11
|
+
path_prefix="/$path_prefix"
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
{
|
|
15
|
+
printf 'window.__APP_CONFIG__ = '
|
|
16
|
+
jq -cn \
|
|
17
|
+
--arg pathPrefix "$path_prefix" \
|
|
18
|
+
--arg apiBaseUrl "${API_BASE_URL:-}" \
|
|
19
|
+
--arg logLevel "${LOG_LEVEL:-info}" \
|
|
20
|
+
'{PATH_PREFIX: $pathPrefix, API_BASE_URL: $apiBaseUrl, LOG_LEVEL: $logLevel}'
|
|
21
|
+
printf ';\n'
|
|
22
|
+
} > "$temporary_output"
|
|
23
|
+
|
|
24
|
+
mv "$temporary_output" "$output"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
FROM node:24.18-alpine AS base
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
COPY package.json package-lock.json ./
|
|
6
|
+
RUN npm ci
|
|
7
|
+
|
|
8
|
+
COPY . .
|
|
9
|
+
|
|
10
|
+
FROM base AS tests
|
|
11
|
+
CMD ["npm", "run", "check"]
|
|
12
|
+
|
|
13
|
+
FROM base AS build
|
|
14
|
+
|
|
15
|
+
RUN npm run build
|
|
16
|
+
|
|
17
|
+
FROM nginxinc/nginx-unprivileged:1.28.1-alpine AS deploy
|
|
18
|
+
|
|
19
|
+
USER root
|
|
20
|
+
RUN apk add --no-cache ca-certificates curl jq \
|
|
21
|
+
&& rm -rf /usr/share/nginx/html/*
|
|
22
|
+
|
|
23
|
+
ENV PORT=8080 \
|
|
24
|
+
NODE_ENV=production \
|
|
25
|
+
PATH_PREFIX= \
|
|
26
|
+
API_BASE_URL= \
|
|
27
|
+
LOG_LEVEL=info \
|
|
28
|
+
BACKEND_URL=http://127.0.0.1:65535
|
|
29
|
+
|
|
30
|
+
COPY --from=docker /frontend/nginx.conf.template /etc/nginx/templates/default.conf.template
|
|
31
|
+
COPY --chmod=755 --from=docker /frontend/15-runtime-settings.envsh /docker-entrypoint.d/
|
|
32
|
+
COPY --chmod=755 --from=docker /frontend/40-path-prefix-settings.sh /docker-entrypoint.d/
|
|
33
|
+
COPY --chmod=755 --from=docker /frontend/45-runtime-config-settings.sh /docker-entrypoint.d/
|
|
34
|
+
RUN rm -f /etc/nginx/conf.d/default.conf \
|
|
35
|
+
&& chown -R 101:101 /etc/nginx/conf.d /usr/share/nginx/html
|
|
36
|
+
|
|
37
|
+
COPY --chown=101:101 --from=build /app/dist /usr/share/nginx/html
|
|
38
|
+
|
|
39
|
+
USER 101:101
|
|
40
|
+
|
|
41
|
+
HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=10s \
|
|
42
|
+
CMD curl -fsS "http://127.0.0.1:${PORT}/healthcheck" || exit 1
|
|
43
|
+
|
|
44
|
+
EXPOSE 8080
|
|
45
|
+
|
|
46
|
+
CMD ["nginx", "-g", "daemon off;"]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
server {
|
|
2
|
+
listen ${PORT};
|
|
3
|
+
server_name _;
|
|
4
|
+
|
|
5
|
+
resolver ${DNS_RESOLVER} valid=30s ipv6=off;
|
|
6
|
+
resolver_timeout 10s;
|
|
7
|
+
|
|
8
|
+
root /usr/share/nginx/html;
|
|
9
|
+
index index.html;
|
|
10
|
+
|
|
11
|
+
gzip on;
|
|
12
|
+
gzip_min_length 1024;
|
|
13
|
+
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
|
|
14
|
+
|
|
15
|
+
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; connect-src 'self'; font-src 'self' data:; form-action 'self'; frame-ancestors 'self'; img-src 'self' data: blob:; object-src 'none'; script-src 'self'; style-src 'self'; worker-src 'self' blob:" always;
|
|
16
|
+
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
17
|
+
add_header X-Content-Type-Options "nosniff" always;
|
|
18
|
+
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
19
|
+
|
|
20
|
+
location = /healthcheck {
|
|
21
|
+
access_log off;
|
|
22
|
+
default_type application/json;
|
|
23
|
+
return 200 '{"Status":"Healthy"}';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
location ^~ /api {
|
|
27
|
+
set $backend_origin "${BACKEND_PROTO}://${BACKEND_HOST}:${BACKEND_PORT}";
|
|
28
|
+
|
|
29
|
+
proxy_pass $backend_origin;
|
|
30
|
+
proxy_http_version 1.1;
|
|
31
|
+
proxy_set_header Connection "upgrade";
|
|
32
|
+
proxy_set_header Host ${BACKEND_HOST};
|
|
33
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
34
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
35
|
+
proxy_set_header X-Forwarded-Host $host;
|
|
36
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
37
|
+
proxy_ssl_name ${BACKEND_HOST};
|
|
38
|
+
proxy_ssl_server_name on;
|
|
39
|
+
proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
|
|
40
|
+
proxy_ssl_verify on;
|
|
41
|
+
proxy_cookie_domain ~.* $host;
|
|
42
|
+
proxy_cookie_path / /;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
location = /runtime-config.js {
|
|
46
|
+
expires -1;
|
|
47
|
+
add_header Cache-Control "no-store" always;
|
|
48
|
+
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; connect-src 'self'; font-src 'self' data:; form-action 'self'; frame-ancestors 'self'; img-src 'self' data: blob:; object-src 'none'; script-src 'self'; style-src 'self'; worker-src 'self' blob:" always;
|
|
49
|
+
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
50
|
+
add_header X-Content-Type-Options "nosniff" always;
|
|
51
|
+
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
52
|
+
try_files $uri =404;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
location = /index.html {
|
|
56
|
+
expires -1;
|
|
57
|
+
add_header Cache-Control "no-store" always;
|
|
58
|
+
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; connect-src 'self'; font-src 'self' data:; form-action 'self'; frame-ancestors 'self'; img-src 'self' data: blob:; object-src 'none'; script-src 'self'; style-src 'self'; worker-src 'self' blob:" always;
|
|
59
|
+
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
60
|
+
add_header X-Content-Type-Options "nosniff" always;
|
|
61
|
+
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
62
|
+
try_files $uri =404;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
location /assets/ {
|
|
66
|
+
expires 1y;
|
|
67
|
+
try_files $uri =404;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
location / {
|
|
71
|
+
try_files $uri $uri/ /index.html;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
${PATH_PREFIX_RULE}
|
|
75
|
+
}
|