From d8ea155d6d07170f447289ec9963d8c45d001cdf Mon Sep 17 00:00:00 2001 From: hoangphuc841 Date: Mon, 1 Jun 2026 21:57:22 +0700 Subject: [PATCH 1/5] feat: add multi-option react scaffolder template and fix files array evaluation - Create the 'multi-option-react-template' under apps/portal/examples. - Implement parameter schemas allowing the choice of Vite/Webpack, Tailwind/Vanilla CSS, Zustand/Redux/None, and React Query/None. - Add dynamic file configure and cleanup steps using single-line Nunjucks expressions returning array values, resolving 'instance.files is not of a type(s) array' schema validation errors. - Wrap template expressions in single quotes to resolve YAML syntax parsing errors in the Backstage catalog loader. - Set targetPath to './source' and './gitops' to structure output directories correctly for file rename, delete, and publish steps. - Add corresponding boilerplate template files for react sources and GitOps manifests. --- .../crd/bases/app.helios.io_heliosapps.yaml | 6 +- apps/portal/app-config.yaml | 6 + .../content/gitops/argocd-app.yaml | 22 ++ .../content/gitops/helios-app.yaml | 28 +++ .../content/gitops/pipeline.yaml | 40 ++++ .../content/gitops/triggers.yaml | 92 ++++++++ .../content/source/.eslintignore | 8 + .../content/source/.eslintrc.json | 31 +++ .../content/source/.gitignore | 5 + .../content/source/Dockerfile | 14 ++ .../content/source/babel.config.json | 6 + .../content/source/catalog-info.yaml | 13 + .../content/source/index.vite.html | 13 + .../content/source/index.webpack.html | 12 + .../content/source/package.json | 62 +++++ .../content/source/postcss.config.cjs | 6 + .../content/source/src/App.jsx | 114 +++++++++ .../content/source/src/index.css | 142 +++++++++++ .../content/source/src/main.jsx | 32 +++ .../content/source/src/store/reduxStore.js | 24 ++ .../content/source/src/store/zustandStore.js | 7 + .../content/source/tailwind.config.cjs | 11 + .../content/source/vite.config.js | 10 + .../content/source/webpack.config.js | 52 ++++ .../multi-option-react-template/template.yaml | 223 ++++++++++++++++++ 25 files changed, 976 insertions(+), 3 deletions(-) create mode 100644 apps/portal/examples/multi-option-react-template/content/gitops/argocd-app.yaml create mode 100644 apps/portal/examples/multi-option-react-template/content/gitops/helios-app.yaml create mode 100644 apps/portal/examples/multi-option-react-template/content/gitops/pipeline.yaml create mode 100644 apps/portal/examples/multi-option-react-template/content/gitops/triggers.yaml create mode 100644 apps/portal/examples/multi-option-react-template/content/source/.eslintignore create mode 100644 apps/portal/examples/multi-option-react-template/content/source/.eslintrc.json create mode 100644 apps/portal/examples/multi-option-react-template/content/source/.gitignore create mode 100644 apps/portal/examples/multi-option-react-template/content/source/Dockerfile create mode 100644 apps/portal/examples/multi-option-react-template/content/source/babel.config.json create mode 100644 apps/portal/examples/multi-option-react-template/content/source/catalog-info.yaml create mode 100644 apps/portal/examples/multi-option-react-template/content/source/index.vite.html create mode 100644 apps/portal/examples/multi-option-react-template/content/source/index.webpack.html create mode 100644 apps/portal/examples/multi-option-react-template/content/source/package.json create mode 100644 apps/portal/examples/multi-option-react-template/content/source/postcss.config.cjs create mode 100644 apps/portal/examples/multi-option-react-template/content/source/src/App.jsx create mode 100644 apps/portal/examples/multi-option-react-template/content/source/src/index.css create mode 100644 apps/portal/examples/multi-option-react-template/content/source/src/main.jsx create mode 100644 apps/portal/examples/multi-option-react-template/content/source/src/store/reduxStore.js create mode 100644 apps/portal/examples/multi-option-react-template/content/source/src/store/zustandStore.js create mode 100644 apps/portal/examples/multi-option-react-template/content/source/tailwind.config.cjs create mode 100644 apps/portal/examples/multi-option-react-template/content/source/vite.config.js create mode 100644 apps/portal/examples/multi-option-react-template/content/source/webpack.config.js create mode 100644 apps/portal/examples/multi-option-react-template/template.yaml diff --git a/apps/operator/config/crd/bases/app.helios.io_heliosapps.yaml b/apps/operator/config/crd/bases/app.helios.io_heliosapps.yaml index c620b1c..02d412b 100644 --- a/apps/operator/config/crd/bases/app.helios.io_heliosapps.yaml +++ b/apps/operator/config/crd/bases/app.helios.io_heliosapps.yaml @@ -101,9 +101,9 @@ spec: description: ContextSubpath is the path where the Dockerfile is located type: string databaseSecretRef: - default: api-db-secret - description: DatabaseSecretRef is the name of the secret containing - database credentials for migrations + description: |- + DatabaseSecretRef is the name of the secret containing database credentials for migrations. + Defaults to {appName}-db-secret if not set. type: string description: description: Description of the application diff --git a/apps/portal/app-config.yaml b/apps/portal/app-config.yaml index eda8f48..45b7e51 100644 --- a/apps/portal/app-config.yaml +++ b/apps/portal/app-config.yaml @@ -132,6 +132,12 @@ catalog: rules: - allow: [Template] + # Multi-Option React Scaffolder Template + - type: file + target: ../../examples/multi-option-react-template/template.yaml + rules: + - allow: [Template] + # NestJS + Prisma Template - type: file target: ../../examples/nestjs-prisma-template/template.yaml diff --git a/apps/portal/examples/multi-option-react-template/content/gitops/argocd-app.yaml b/apps/portal/examples/multi-option-react-template/content/gitops/argocd-app.yaml new file mode 100644 index 0000000..997304e --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/gitops/argocd-app.yaml @@ -0,0 +1,22 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: ${{ values.name }}-service + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: ${{ values.gitopsRepo }} + targetRevision: HEAD + path: ./ + destination: + server: https://kubernetes.default.svc + namespace: default + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/apps/portal/examples/multi-option-react-template/content/gitops/helios-app.yaml b/apps/portal/examples/multi-option-react-template/content/gitops/helios-app.yaml new file mode 100644 index 0000000..6969975 --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/gitops/helios-app.yaml @@ -0,0 +1,28 @@ +apiVersion: app.helios.io/v1alpha1 +kind: HeliosApp +metadata: + name: ${{ values.name }} + namespace: default +spec: + owner: ${{ values.owner }} + description: "React application: ${{ values.name }}" + gitRepo: ${{ values.sourceRepo }} + gitBranch: main + imageRepo: ${{ values.image }} + gitopsRepo: ${{ values.gitopsRepo }} + gitopsPath: ${{ values.name }} + pipelineName: from-code-to-cluster + webhookSecret: git-credentials-${{ values.name }} + port: ${{ values.port }} + testCommand: "npm install && npm run build" + components: + - name: ${{ values.name }} + type: web-service + properties: + image: ${{ values.image }}:latest + port: ${{ values.port }} + replicas: 1 + traits: + - type: service + properties: + port: ${{ values.port }} diff --git a/apps/portal/examples/multi-option-react-template/content/gitops/pipeline.yaml b/apps/portal/examples/multi-option-react-template/content/gitops/pipeline.yaml new file mode 100644 index 0000000..e654a20 --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/gitops/pipeline.yaml @@ -0,0 +1,40 @@ +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + name: ${{ values.name }}-pipeline-run + annotations: + janus-idp.io/tekton: ${{ values.name }} +spec: + serviceAccountName: pipeline + pipelineRef: + name: from-code-to-cluster + params: + - name: image-repo + value: ${{ values.image }} + - name: app-repo-url + value: ${{ values.sourceRepo }} + - name: app-repo-revision + value: main + - name: GITOPS_REPO_URL + value: ${{ values.gitopsRepo }} + - name: GITOPS_REPO_BRANCH + value: main + - name: MANIFEST_PATH + value: ./deployment.yaml + - name: docker-secret + value: docker-credentials + - name: test-command + value: ${{ values.testCommand }} + - name: argocd-namespace + value: argocd + - name: argocd-app-name + value: ${{ values.name }}-argocd + workspaces: + - name: source-workspace + volumeClaimTemplate: + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/apps/portal/examples/multi-option-react-template/content/gitops/triggers.yaml b/apps/portal/examples/multi-option-react-template/content/gitops/triggers.yaml new file mode 100644 index 0000000..1c4a853 --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/gitops/triggers.yaml @@ -0,0 +1,92 @@ +apiVersion: triggers.tekton.dev/v1beta1 +kind: TriggerBinding +metadata: + name: ${{ values.name }}-git-binding +spec: + params: + - name: git-repo-url + value: $(body.repository.clone_url) + - name: git-revision + value: $(body.head_commit.id) + - name: git-repo-name + value: $(body.repository.name) + - name: git-owner + value: $(body.repository.owner.login) + +--- +apiVersion: triggers.tekton.dev/v1beta1 +kind: TriggerTemplate +metadata: + name: ${{ values.name }}-git-template +spec: + resourcetemplates: + - apiVersion: tekton.dev/v1beta1 + kind: PipelineRun + metadata: + generateName: ${{ values.name }}-run- + labels: + tekton.dev/pipeline: from-code-to-cluster + app.kubernetes.io/instance: ${{ values.name }} + spec: + serviceAccountName: pipeline + pipelineRef: + name: from-code-to-cluster + params: + - name: app-repo-url + value: $(tt.params.git-repo-url) + - name: app-repo-revision + value: $(tt.params.git-revision) + - name: image-repo + value: "${{ values.image }}" + - name: GITOPS_REPO_URL + value: "$(tt.params.git-repo-url)-gitops" + - name: GITOPS_REPO_BRANCH + value: "main" + - name: MANIFEST_PATH + value: "deployment.yaml" + - name: docker-secret + value: "docker-credentials" + +--- +apiVersion: triggers.tekton.dev/v1beta1 +kind: EventListener +metadata: + name: ${{ values.name }}-listener +spec: + serviceAccountName: ${{ values.name }}-sa + triggers: + - binding: + name: ${{ values.name }}-git-binding + template: + name: ${{ values.name }}-git-template + +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: ${{ values.name }}-sa +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: ${{ values.name }}-listener-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: tekton-triggers-eventlistener-roles +subjects: + - kind: ServiceAccount + name: ${{ values.name }}-sa +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: ${{ values.name }}-listener-clusterbinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: tekton-triggers-eventlistener-clusterroles +subjects: + - kind: ServiceAccount + name: ${{ values.name }}-sa + namespace: default diff --git a/apps/portal/examples/multi-option-react-template/content/source/.eslintignore b/apps/portal/examples/multi-option-react-template/content/source/.eslintignore new file mode 100644 index 0000000..176c58d --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/source/.eslintignore @@ -0,0 +1,8 @@ +node_modules/ +dist/ +*.config.js +*.config.cjs +postcss.config.cjs +tailwind.config.cjs +vite.config.js +webpack.config.js diff --git a/apps/portal/examples/multi-option-react-template/content/source/.eslintrc.json b/apps/portal/examples/multi-option-react-template/content/source/.eslintrc.json new file mode 100644 index 0000000..d07b507 --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/source/.eslintrc.json @@ -0,0 +1,31 @@ +{ + "env": { + "browser": true, + "es2020": true + }, + "extends": [ + "eslint:recommended", + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended" + ], + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "settings": { + "react": { + "version": "18.2" + } + }, + "plugins": [ + "react-refresh" + ], + "rules": { + "react-refresh/only-export-components": [ + "warn", + { "allowConstantExport": true } + ], + "react/prop-types": "off" + } +} diff --git a/apps/portal/examples/multi-option-react-template/content/source/.gitignore b/apps/portal/examples/multi-option-react-template/content/source/.gitignore new file mode 100644 index 0000000..3b6e931 --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/source/.gitignore @@ -0,0 +1,5 @@ +node_modules +dist +.env +.env.local +.eslintcache diff --git a/apps/portal/examples/multi-option-react-template/content/source/Dockerfile b/apps/portal/examples/multi-option-react-template/content/source/Dockerfile new file mode 100644 index 0000000..dfcc13c --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/source/Dockerfile @@ -0,0 +1,14 @@ +# Build stage +FROM node:22-alpine AS build +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +RUN npm run build + +# Production stage +FROM nginx:alpine +COPY --from=build /app/dist /usr/share/nginx/html +RUN sed -i 's/listen\( *\)80;/listen ${{ values.port }};/' /etc/nginx/conf.d/default.conf +EXPOSE ${{ values.port }} +CMD ["nginx", "-g", "daemon off;"] diff --git a/apps/portal/examples/multi-option-react-template/content/source/babel.config.json b/apps/portal/examples/multi-option-react-template/content/source/babel.config.json new file mode 100644 index 0000000..08d007e --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/source/babel.config.json @@ -0,0 +1,6 @@ +{ + "presets": [ + "@babel/preset-env", + ["@babel/preset-react", { "runtime": "automatic" }] + ] +} diff --git a/apps/portal/examples/multi-option-react-template/content/source/catalog-info.yaml b/apps/portal/examples/multi-option-react-template/content/source/catalog-info.yaml new file mode 100644 index 0000000..776375d --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/source/catalog-info.yaml @@ -0,0 +1,13 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: ${{ values.name | dump }} + {%- if values.description %} + description: ${{ values.description | dump }} + {%- endif %} + annotations: + backstage.io/techdocs-ref: dir:. +spec: + type: website + lifecycle: experimental + owner: ${{ values.owner | dump }} diff --git a/apps/portal/examples/multi-option-react-template/content/source/index.vite.html b/apps/portal/examples/multi-option-react-template/content/source/index.vite.html new file mode 100644 index 0000000..0a1335f --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/source/index.vite.html @@ -0,0 +1,13 @@ + + + + + + + ${{ values.name }} + + +
+ + + diff --git a/apps/portal/examples/multi-option-react-template/content/source/index.webpack.html b/apps/portal/examples/multi-option-react-template/content/source/index.webpack.html new file mode 100644 index 0000000..c3238c8 --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/source/index.webpack.html @@ -0,0 +1,12 @@ + + + + + + + ${{ values.name }} + + +
+ + diff --git a/apps/portal/examples/multi-option-react-template/content/source/package.json b/apps/portal/examples/multi-option-react-template/content/source/package.json new file mode 100644 index 0000000..10df480 --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/source/package.json @@ -0,0 +1,62 @@ +{ + "name": "${{ values.name }}", + "private": true, + "version": "1.0.0", + {% if values.buildTool == 'vite' -%} + "type": "module", + {%- endif %} + "scripts": { + {% if values.buildTool == 'vite' -%} + "dev": "vite", + "build": "vite build", + "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + {%- else -%} + "dev": "webpack serve --mode development", + "build": "webpack --mode production", + "lint": "eslint src/ --ext js,jsx --report-unused-disable-directives --max-warnings 0" + {%- endif %} + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + {%- if values.stateManagement == 'redux' -%}, + "@reduxjs/toolkit": "^2.2.5", + "react-redux": "^9.1.2" + {%- elif values.stateManagement == 'zustand' -%}, + "zustand": "^4.5.2" + {%- endif %} + {%- if values.dataFetching == 'react-query' -%}, + "@tanstack/react-query": "^5.40.1" + {%- endif %} + }, + "devDependencies": { + "eslint": "^8.57.0", + "eslint-plugin-react": "^7.34.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6" + {%- if values.buildTool == 'vite' -%}, + "vite": "^5.2.11", + "@vitejs/plugin-react": "^4.2.1" + {%- else -%}, + "webpack": "^5.91.0", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^5.0.4", + "html-webpack-plugin": "^5.6.0", + "babel-loader": "^9.1.3", + "@babel/core": "^7.24.5", + "@babel/preset-env": "^7.24.5", + "@babel/preset-react": "^7.24.1", + "style-loader": "^4.0.0", + "css-loader": "^7.1.1" + {%- endif %} + {%- if values.styling == 'tailwind' -%}, + "tailwindcss": "^3.4.3", + "postcss": "^8.4.38", + "autoprefixer": "^10.4.19" + {%- if values.buildTool == 'webpack' -%}, + "postcss-loader": "^8.1.1" + {%- endif %} + {%- endif %} + } +} diff --git a/apps/portal/examples/multi-option-react-template/content/source/postcss.config.cjs b/apps/portal/examples/multi-option-react-template/content/source/postcss.config.cjs new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/source/postcss.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/apps/portal/examples/multi-option-react-template/content/source/src/App.jsx b/apps/portal/examples/multi-option-react-template/content/source/src/App.jsx new file mode 100644 index 0000000..950a2e8 --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/source/src/App.jsx @@ -0,0 +1,114 @@ +{% if values.stateManagement == 'none' or values.dataFetching == 'none' -%} +import * as React from 'react' +{%- endif %} +{% if values.stateManagement == 'zustand' -%} +import { useStore } from './store/store.js' +{%- elif values.stateManagement == 'redux' -%} +import { useSelector, useDispatch } from 'react-redux' +import { increment, decrement } from './store/store.js' +{%- endif %} + +{% if values.dataFetching == 'react-query' -%} +import { useQuery } from '@tanstack/react-query' +{%- endif %} + +function App() { + {% if values.stateManagement == 'zustand' -%} + const { count, increment, decrement } = useStore() + {%- elif values.stateManagement == 'redux' -%} + const count = useSelector((state) => state.counter.value) + const dispatch = useDispatch() + {%- else -%} + const [count, setCount] = React.useState(0) + {%- endif %} + + {% if values.dataFetching == 'react-query' -%} + const { data, isLoading, error } = useQuery({ + queryKey: ['repoData'], + queryFn: () => + fetch('https://api.github.com/repos/facebook/react').then((res) => + res.json(), + ), + }) + {%- else -%} + const [data, setData] = React.useState(null) + const [isLoading, setIsLoading] = React.useState(false) + const [error, setError] = React.useState(null) + + React.useEffect(() => { + setIsLoading(true) + fetch('https://api.github.com/repos/facebook/react') + .then((res) => { + if (!res.ok) { + throw new Error('Network response was not ok') + } + return res.json() + }) + .then((data) => { + setData(data) + setIsLoading(false) + }) + .catch((err) => { + setError(err) + setIsLoading(false) + }) + }, []) + {%- endif %} + + return ( +
+
+

+ React App Scaffolder Template +

+ +
+

Tech Stack

+
    +
  • Build Tool: ${{ values.buildTool | capitalize }}
  • +
  • Styling: ${{ values.styling | capitalize }}
  • +
  • State Management: ${{ values.stateManagement | capitalize }}
  • +
  • Data Fetching: {% if values.dataFetching == 'react-query' %}React Query{% else %}Standard Fetch{% endif %}
  • +
+
+ + {/* Counter Section */} +
+

Counter State Example (${{ values.stateManagement | capitalize }})

+
+ + {count} + +
+
+ + {/* Data Fetching Section */} +
+

Data Fetching Example ({% if values.dataFetching == 'react-query' %}React Query{% else %}Fetch{% endif %})

+ {isLoading &&

Loading React repo stats...

} + {error &&

Error fetching data: {error.message || 'Unknown error'}

} + {data && ( +
+

Repo Name: {data.full_name}

+

Stars: {data.stargazers_count?.toLocaleString()}

+

Forks: {data.forks_count?.toLocaleString()}

+

Open Issues: {data.open_issues_count?.toLocaleString()}

+
+ )} +
+
+
+ ) +} + +export default App diff --git a/apps/portal/examples/multi-option-react-template/content/source/src/index.css b/apps/portal/examples/multi-option-react-template/content/source/src/index.css new file mode 100644 index 0000000..502bc8e --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/source/src/index.css @@ -0,0 +1,142 @@ +{% if values.styling == 'tailwind' -%} +@tailwind base; +@tailwind components; +@tailwind utilities; +{%- else -%} +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #0f172a; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +.app-container { + min-height: 100vh; + width: 100vw; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 2rem; + box-sizing: border-box; +} + +.app-card { + max-width: 600px; + width: 100%; + background-color: #1e293b; + border-radius: 0.75rem; + padding: 2rem; + box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25); + border: 1px solid #334155; + box-sizing: border-box; +} + +.app-title { + font-size: 1.875rem; + line-height: 2.25rem; + font-weight: 700; + margin-top: 0; + margin-bottom: 1.5rem; + background: linear-gradient(to right, #22d3ee, #3b82f6); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.tech-stack-panel { + margin-bottom: 2rem; + padding: 1rem; + background-color: rgba(51, 65, 85, 0.5); + border-radius: 0.5rem; +} + +.section-title { + font-size: 1.125rem; + font-weight: 600; + margin-top: 0; + margin-bottom: 0.5rem; + color: #67e8f9; +} + +.tech-stack-list { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.5rem; + font-size: 0.875rem; + list-style-type: none; + padding-left: 0; + margin: 0; +} + +.counter-section { + margin-bottom: 2rem; +} + +.counter-controls { + display: flex; + align-items: center; + gap: 1rem; +} + +.btn { + padding: 0.5rem 1rem; + background-color: #334155; + border: none; + border-radius: 0.25rem; + color: white; + font-weight: 700; + cursor: pointer; + transition: background-color 0.2s; +} + +.btn:hover { + background-color: #475569; +} + +.btn-primary { + background-color: #0891b2; +} + +.btn-primary:hover { + background-color: #06b6d4; +} + +.counter-value { + font-family: monospace; + font-size: 1.5rem; + width: 3rem; + text-align: center; +} + +.data-section { + border-top: 1px solid #334155; + padding-top: 1.5rem; +} + +.stats-grid { + font-size: 0.875rem; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.error-msg { + color: #f87171; +} +{%- endif %} diff --git a/apps/portal/examples/multi-option-react-template/content/source/src/main.jsx b/apps/portal/examples/multi-option-react-template/content/source/src/main.jsx new file mode 100644 index 0000000..fd1d130 --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/source/src/main.jsx @@ -0,0 +1,32 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.jsx' +import './index.css' + +{% if values.dataFetching == 'react-query' -%} +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +const queryClient = new QueryClient() +{%- endif %} + +{% if values.stateManagement == 'redux' -%} +import { Provider } from 'react-redux' +import { store } from './store/store.js' +{%- endif %} + +ReactDOM.createRoot(document.getElementById('root')).render( + + {% if values.dataFetching == 'react-query' -%} + + {%- endif %} + {% if values.stateManagement == 'redux' -%} + + {%- endif %} + + {% if values.stateManagement == 'redux' -%} + + {%- endif %} + {% if values.dataFetching == 'react-query' -%} + + {%- endif %} + , +) diff --git a/apps/portal/examples/multi-option-react-template/content/source/src/store/reduxStore.js b/apps/portal/examples/multi-option-react-template/content/source/src/store/reduxStore.js new file mode 100644 index 0000000..4192c2e --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/source/src/store/reduxStore.js @@ -0,0 +1,24 @@ +import { configureStore, createSlice } from '@reduxjs/toolkit' + +const counterSlice = createSlice({ + name: 'counter', + initialState: { + value: 0, + }, + reducers: { + increment: (state) => { + state.value += 1 + }, + decrement: (state) => { + state.value -= 1 + }, + }, +}) + +export const { increment, decrement } = counterSlice.actions + +export const store = configureStore({ + reducer: { + counter: counterSlice.reducer, + }, +}) diff --git a/apps/portal/examples/multi-option-react-template/content/source/src/store/zustandStore.js b/apps/portal/examples/multi-option-react-template/content/source/src/store/zustandStore.js new file mode 100644 index 0000000..6a5863a --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/source/src/store/zustandStore.js @@ -0,0 +1,7 @@ +import { create } from 'zustand' + +export const useStore = create((set) => ({ + count: 0, + increment: () => set((state) => ({ count: state.count + 1 })), + decrement: () => set((state) => ({ count: state.count - 1 })), +})) diff --git a/apps/portal/examples/multi-option-react-template/content/source/tailwind.config.cjs b/apps/portal/examples/multi-option-react-template/content/source/tailwind.config.cjs new file mode 100644 index 0000000..1c3b7e1 --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/source/tailwind.config.cjs @@ -0,0 +1,11 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: {}, + }, + plugins: [], +} diff --git a/apps/portal/examples/multi-option-react-template/content/source/vite.config.js b/apps/portal/examples/multi-option-react-template/content/source/vite.config.js new file mode 100644 index 0000000..c72ca71 --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/source/vite.config.js @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + port: 3000, + }, +}) diff --git a/apps/portal/examples/multi-option-react-template/content/source/webpack.config.js b/apps/portal/examples/multi-option-react-template/content/source/webpack.config.js new file mode 100644 index 0000000..01a693c --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/source/webpack.config.js @@ -0,0 +1,52 @@ +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +module.exports = { + entry: './src/main.jsx', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'bundle.js', + clean: true, + }, + resolve: { + extensions: ['.js', '.jsx'], + }, + module: { + rules: [ + { + test: /\.(js|jsx)$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-env', '@babel/preset-react'], + }, + }, + }, + { + test: /\.css$/, + use: [ + 'style-loader', + 'css-loader', + {% if values.styling == 'tailwind' -%} + 'postcss-loader', + {%- endif %} + ], + }, + ], + }, + plugins: [ + new HtmlWebpackPlugin({ + template: './index.html', + }), + ], + devServer: { + static: { + directory: path.join(__dirname, 'public'), + }, + compress: true, + port: 3000, + hot: true, + historyApiFallback: true, + }, +}; diff --git a/apps/portal/examples/multi-option-react-template/template.yaml b/apps/portal/examples/multi-option-react-template/template.yaml new file mode 100644 index 0000000..f0b4592 --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/template.yaml @@ -0,0 +1,223 @@ +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + name: multi-option-react-template + title: Multi-Option React Scaffolder Template + description: A flexible React template that lets you customize your build tool (Vite/Webpack), styling (Tailwind CSS), state management (Zustand/Redux), and data fetching (React Query), and deploys it automatically. +spec: + owner: user:guest + type: service + + parameters: + - title: Component Details + required: + - name + - port + - dockerOrg + - repoName + properties: + name: + title: Name + type: string + description: Unique name of the component + ui:autofocus: true + port: + title: Port + type: number + description: The port to expose + default: 8080 + dockerOrg: + title: Docker Registry Org/User + type: string + description: Your Docker Hub username or Organization + default: phamhoangkha1403 + repoName: + title: Docker Repository Name + type: string + description: The name of the repository (e.g. my-react-app) + description: + title: Description + type: string + description: A brief description of the component + - title: Tech Stack Options + required: + - buildTool + - styling + - stateManagement + - dataFetching + properties: + buildTool: + title: Build Tool + type: string + description: Select the build tool and bundler + enum: + - vite + - webpack + enumNames: + - Vite + - Webpack + default: vite + styling: + title: Styling + type: string + description: Choose the styling method + enum: + - tailwind + - vanilla + enumNames: + - Tailwind CSS + - Vanilla CSS + default: tailwind + stateManagement: + title: State Management + type: string + description: Choose a state management library + enum: + - none + - zustand + - redux + enumNames: + - None + - Zustand + - Redux (Toolkit) + default: none + dataFetching: + title: Data Fetching + type: string + description: Choose a data fetching library + enum: + - none + - react-query + enumNames: + - None (Standard fetch) + - React Query (TanStack Query) + default: none + - title: Choose a location + required: + - repoUrl + properties: + repoUrl: + title: Repository Location + type: string + ui:field: RepoUrlPicker + ui:options: + allowedHosts: + - localhost:3030 + + steps: + # 1. Fetch Source Code Template + - id: fetch-source + name: Fetch Source Code Template + action: fetch:template + input: + url: ./content/source + targetPath: ./source + values: + name: ${{ parameters.repoName }} + description: ${{ parameters.description }} + buildTool: ${{ parameters.buildTool }} + styling: ${{ parameters.styling }} + stateManagement: ${{ parameters.stateManagement }} + dataFetching: ${{ parameters.dataFetching }} + port: ${{ parameters.port }} + owner: ${{ user.entity.metadata.name or 'guest' }} + image: index.docker.io/${{ parameters.dockerOrg }}/${{ parameters.repoName }} + + # 2. Configure Selected Stack (Always run, dynamic list) + - id: configure-files + name: Configure Selected Stack + action: fs:rename + input: + files: '${{ [ { from: "./source/index." + parameters.buildTool + ".html", to: "./source/index.html" } ].concat( [ { from: "./source/src/store/reduxStore.js", to: "./source/src/store/store.js" } ] if parameters.stateManagement == "redux" else ( [ { from: "./source/src/store/zustandStore.js", to: "./source/src/store/store.js" } ] if parameters.stateManagement == "zustand" else [] ) ) }}' + + # 3. Clean up Unused Files (Always run, dynamic list) + - id: cleanup-files + name: Clean up Unused Files + action: fs:delete + input: + files: '${{ ( [ "./source/index.webpack.html", "./source/webpack.config.js", "./source/babel.config.json" ] if parameters.buildTool == "vite" else [ "./source/index.vite.html", "./source/vite.config.js" ] ).concat( [ "./source/tailwind.config.cjs", "./source/postcss.config.cjs" ] if parameters.styling == "vanilla" else [] ).concat( [ "./source/src/store/zustandStore.js" ] if parameters.stateManagement == "redux" else ( [ "./source/src/store/reduxStore.js" ] if parameters.stateManagement == "zustand" else [ "./source/src/store" ] ) ) }}' + + # 4. Publish Source Code to Gitea + - id: publish-source + name: Publish Source Code + action: publish:gitea + input: + description: Source Code for ${{ parameters.name }} + repoUrl: ${{ parameters.repoUrl }} + sourcePath: ./source + repoVisibility: public + + # 5. Create Gitea Webhook for Tekton CI + - id: create-webhook + name: Create Webhook + action: gitea:create-webhook + input: + repoUrl: ${{ parameters.repoUrl }} + webhookUrl: http://el-${{ parameters.repoName }}-listener.default.svc.cluster.local:8080 + webhookSecret: ${{ parameters.repoName }} + events: + - push + + # 6. Fetch GitOps Manifests Template + - id: fetch-gitops + name: Fetch GitOps Manifests + action: fetch:template + input: + url: ./content/gitops + targetPath: ./gitops + values: + name: ${{ parameters.repoName }} + image: index.docker.io/${{ parameters.dockerOrg }}/${{ parameters.repoName }} + dockerOrg: ${{ parameters.dockerOrg }} + repoName: ${{ parameters.repoName }} + port: ${{ parameters.port }} + owner: ${{ user.entity.metadata.name or 'guest' }} + sourceRepo: ${{ steps['publish-source'].output.remoteUrl }} + gitopsRepo: ${{ steps['publish-source'].output.remoteUrl | replace(".git", "") }}-gitops + testCommand: "npm install && npm run build" + + # 7. Publish GitOps Manifests to Gitea + - id: publish-gitops + name: Publish GitOps Manifests + action: publish:gitea + input: + description: GitOps Manifests for ${{ parameters.name }} + repoUrl: ${{ parameters.repoUrl }}-gitops + sourcePath: ./gitops + repoVisibility: public + + # 8. Create Git Credentials Secret on Kubernetes + - id: create-secret + name: Create Git Credentials Secret + action: kubernetes:create-git-credentials-secret + input: + name: ${{ parameters.repoName }} + namespace: default + username: ${{ (parameters.repoUrl | parseRepoUrl).owner }} + webhookSecret: ${{ parameters.repoName }} + + # 9. Deploy HeliosApp to Kubernetes + - id: apply-helios + name: Deploy to Kubernetes + action: kubernetes:apply + input: + manifestPath: ./gitops/helios-app.yaml + namespaced: true + + # 10. Register Component in Catalog + - id: register + name: Register Component + action: catalog:register + input: + repoContentsUrl: ${{ steps['publish-source'].output.repoContentsUrl }} + catalogInfoPath: 'catalog-info.yaml' + + output: + links: + - title: Source Repository + url: ${{ steps['publish-source'].output.remoteUrl }} + - title: GitOps Repository + url: ${{ steps['publish-gitops'].output.remoteUrl }} + - title: Open in Catalog + icon: catalog + entityRef: ${{ steps['register'].output.entityRef }} From 1a04d4de77da4b77fe50f36203b92abb2bfd52b4 Mon Sep 17 00:00:00 2001 From: hoangphuc841 Date: Tue, 2 Jun 2026 09:39:33 +0700 Subject: [PATCH 2/5] fix(portal): resolve configuration, syntax, and security issues in multi-option react template --- .../content/gitops/argocd-app.yaml | 2 +- .../content/gitops/pipeline.yaml | 2 +- .../content/gitops/triggers.yaml | 4 ++-- .../content/source/Dockerfile | 5 +++-- .../content/source/catalog-info.yaml | 4 +--- .../content/source/src/App.jsx | 11 +++++++---- .../content/source/src/index.tailwind.css | 3 +++ .../src/{index.css => index.vanilla.css} | 6 ------ .../content/source/webpack.config.js | 8 +------- .../multi-option-react-template/template.yaml | 18 ++++++++++++------ .../packages/backend/catalog.sqlite-journal | Bin 0 -> 12824 bytes 11 files changed, 31 insertions(+), 32 deletions(-) create mode 100644 apps/portal/examples/multi-option-react-template/content/source/src/index.tailwind.css rename apps/portal/examples/multi-option-react-template/content/source/src/{index.css => index.vanilla.css} (94%) create mode 100644 apps/portal/packages/backend/catalog.sqlite-journal diff --git a/apps/portal/examples/multi-option-react-template/content/gitops/argocd-app.yaml b/apps/portal/examples/multi-option-react-template/content/gitops/argocd-app.yaml index 997304e..f980436 100644 --- a/apps/portal/examples/multi-option-react-template/content/gitops/argocd-app.yaml +++ b/apps/portal/examples/multi-option-react-template/content/gitops/argocd-app.yaml @@ -1,7 +1,7 @@ apiVersion: argoproj.io/v1alpha1 kind: Application metadata: - name: ${{ values.name }}-service + name: ${{ values.name }}-argocd namespace: argocd finalizers: - resources-finalizer.argocd.argoproj.io diff --git a/apps/portal/examples/multi-option-react-template/content/gitops/pipeline.yaml b/apps/portal/examples/multi-option-react-template/content/gitops/pipeline.yaml index e654a20..17eee4e 100644 --- a/apps/portal/examples/multi-option-react-template/content/gitops/pipeline.yaml +++ b/apps/portal/examples/multi-option-react-template/content/gitops/pipeline.yaml @@ -20,7 +20,7 @@ spec: - name: GITOPS_REPO_BRANCH value: main - name: MANIFEST_PATH - value: ./deployment.yaml + value: ./helios-app.yaml - name: docker-secret value: docker-credentials - name: test-command diff --git a/apps/portal/examples/multi-option-react-template/content/gitops/triggers.yaml b/apps/portal/examples/multi-option-react-template/content/gitops/triggers.yaml index 1c4a853..9ca8775 100644 --- a/apps/portal/examples/multi-option-react-template/content/gitops/triggers.yaml +++ b/apps/portal/examples/multi-option-react-template/content/gitops/triggers.yaml @@ -39,11 +39,11 @@ spec: - name: image-repo value: "${{ values.image }}" - name: GITOPS_REPO_URL - value: "$(tt.params.git-repo-url)-gitops" + value: "${{ values.gitopsRepo }}" - name: GITOPS_REPO_BRANCH value: "main" - name: MANIFEST_PATH - value: "deployment.yaml" + value: "helios-app.yaml" - name: docker-secret value: "docker-credentials" diff --git a/apps/portal/examples/multi-option-react-template/content/source/Dockerfile b/apps/portal/examples/multi-option-react-template/content/source/Dockerfile index dfcc13c..d3a2f63 100644 --- a/apps/portal/examples/multi-option-react-template/content/source/Dockerfile +++ b/apps/portal/examples/multi-option-react-template/content/source/Dockerfile @@ -9,6 +9,7 @@ RUN npm run build # Production stage FROM nginx:alpine COPY --from=build /app/dist /usr/share/nginx/html -RUN sed -i 's/listen\( *\)80;/listen ${{ values.port }};/' /etc/nginx/conf.d/default.conf -EXPOSE ${{ values.port }} +ARG PORT=${{ values.port }} +RUN sed -i "s/listen\( *\)80;/listen ${PORT};/" /etc/nginx/conf.d/default.conf +EXPOSE ${PORT} CMD ["nginx", "-g", "daemon off;"] diff --git a/apps/portal/examples/multi-option-react-template/content/source/catalog-info.yaml b/apps/portal/examples/multi-option-react-template/content/source/catalog-info.yaml index 776375d..edfd2b9 100644 --- a/apps/portal/examples/multi-option-react-template/content/source/catalog-info.yaml +++ b/apps/portal/examples/multi-option-react-template/content/source/catalog-info.yaml @@ -2,9 +2,7 @@ apiVersion: backstage.io/v1alpha1 kind: Component metadata: name: ${{ values.name | dump }} - {%- if values.description %} - description: ${{ values.description | dump }} - {%- endif %} + description: ${{ (values.description or "") | dump }} annotations: backstage.io/techdocs-ref: dir:. spec: diff --git a/apps/portal/examples/multi-option-react-template/content/source/src/App.jsx b/apps/portal/examples/multi-option-react-template/content/source/src/App.jsx index 950a2e8..640c7f4 100644 --- a/apps/portal/examples/multi-option-react-template/content/source/src/App.jsx +++ b/apps/portal/examples/multi-option-react-template/content/source/src/App.jsx @@ -25,10 +25,13 @@ function App() { {% if values.dataFetching == 'react-query' -%} const { data, isLoading, error } = useQuery({ queryKey: ['repoData'], - queryFn: () => - fetch('https://api.github.com/repos/facebook/react').then((res) => - res.json(), - ), + queryFn: async () => { + const res = await fetch('https://api.github.com/repos/facebook/react') + if (!res.ok) { + throw new Error('Network response was not ok') + } + return res.json() + }, }) {%- else -%} const [data, setData] = React.useState(null) diff --git a/apps/portal/examples/multi-option-react-template/content/source/src/index.tailwind.css b/apps/portal/examples/multi-option-react-template/content/source/src/index.tailwind.css new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/apps/portal/examples/multi-option-react-template/content/source/src/index.tailwind.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/apps/portal/examples/multi-option-react-template/content/source/src/index.css b/apps/portal/examples/multi-option-react-template/content/source/src/index.vanilla.css similarity index 94% rename from apps/portal/examples/multi-option-react-template/content/source/src/index.css rename to apps/portal/examples/multi-option-react-template/content/source/src/index.vanilla.css index 502bc8e..45dcc8f 100644 --- a/apps/portal/examples/multi-option-react-template/content/source/src/index.css +++ b/apps/portal/examples/multi-option-react-template/content/source/src/index.vanilla.css @@ -1,8 +1,3 @@ -{% if values.styling == 'tailwind' -%} -@tailwind base; -@tailwind components; -@tailwind utilities; -{%- else -%} :root { font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; @@ -139,4 +134,3 @@ body { .error-msg { color: #f87171; } -{%- endif %} diff --git a/apps/portal/examples/multi-option-react-template/content/source/webpack.config.js b/apps/portal/examples/multi-option-react-template/content/source/webpack.config.js index 01a693c..eb09703 100644 --- a/apps/portal/examples/multi-option-react-template/content/source/webpack.config.js +++ b/apps/portal/examples/multi-option-react-template/content/source/webpack.config.js @@ -25,13 +25,7 @@ module.exports = { }, { test: /\.css$/, - use: [ - 'style-loader', - 'css-loader', - {% if values.styling == 'tailwind' -%} - 'postcss-loader', - {%- endif %} - ], + use: {% if values.styling == 'tailwind' %}['style-loader', 'css-loader', 'postcss-loader']{% else %}['style-loader', 'css-loader']{% endif %}, }, ], }, diff --git a/apps/portal/examples/multi-option-react-template/template.yaml b/apps/portal/examples/multi-option-react-template/template.yaml index f0b4592..64e9d8e 100644 --- a/apps/portal/examples/multi-option-react-template/template.yaml +++ b/apps/portal/examples/multi-option-react-template/template.yaml @@ -95,6 +95,7 @@ spec: - title: Choose a location required: - repoUrl + - webhookSecret properties: repoUrl: title: Repository Location @@ -103,6 +104,11 @@ spec: ui:options: allowedHosts: - localhost:3030 + webhookSecret: + title: Webhook Secret + type: string + description: A secure random secret to secure the Git webhook + ui:widget: password steps: # 1. Fetch Source Code Template @@ -113,7 +119,7 @@ spec: url: ./content/source targetPath: ./source values: - name: ${{ parameters.repoName }} + name: ${{ parameters.name }} description: ${{ parameters.description }} buildTool: ${{ parameters.buildTool }} styling: ${{ parameters.styling }} @@ -128,14 +134,14 @@ spec: name: Configure Selected Stack action: fs:rename input: - files: '${{ [ { from: "./source/index." + parameters.buildTool + ".html", to: "./source/index.html" } ].concat( [ { from: "./source/src/store/reduxStore.js", to: "./source/src/store/store.js" } ] if parameters.stateManagement == "redux" else ( [ { from: "./source/src/store/zustandStore.js", to: "./source/src/store/store.js" } ] if parameters.stateManagement == "zustand" else [] ) ) }}' + files: '${{ [ { from: "./source/index." + parameters.buildTool + ".html", to: "./source/index.html" }, { from: "./source/src/index." + parameters.styling + ".css", to: "./source/src/index.css" } ].concat( [ { from: "./source/src/store/reduxStore.js", to: "./source/src/store/store.js" } ] if parameters.stateManagement == "redux" else ( [ { from: "./source/src/store/zustandStore.js", to: "./source/src/store/store.js" } ] if parameters.stateManagement == "zustand" else [] ) ) }}' # 3. Clean up Unused Files (Always run, dynamic list) - id: cleanup-files name: Clean up Unused Files action: fs:delete input: - files: '${{ ( [ "./source/index.webpack.html", "./source/webpack.config.js", "./source/babel.config.json" ] if parameters.buildTool == "vite" else [ "./source/index.vite.html", "./source/vite.config.js" ] ).concat( [ "./source/tailwind.config.cjs", "./source/postcss.config.cjs" ] if parameters.styling == "vanilla" else [] ).concat( [ "./source/src/store/zustandStore.js" ] if parameters.stateManagement == "redux" else ( [ "./source/src/store/reduxStore.js" ] if parameters.stateManagement == "zustand" else [ "./source/src/store" ] ) ) }}' + files: '${{ ( [ "./source/index.webpack.html", "./source/webpack.config.js", "./source/babel.config.json" ] if parameters.buildTool == "vite" else [ "./source/index.vite.html", "./source/vite.config.js" ] ).concat( [ "./source/tailwind.config.cjs", "./source/postcss.config.cjs" ] if parameters.styling == "vanilla" else [] ).concat( [ "./source/src/store/zustandStore.js" ] if parameters.stateManagement == "redux" else ( [ "./source/src/store/reduxStore.js" ] if parameters.stateManagement == "zustand" else [ "./source/src/store" ] ) ).concat( [ "./source/src/index.vanilla.css" ] if parameters.styling == "tailwind" else [ "./source/src/index.tailwind.css" ] ) }}' # 4. Publish Source Code to Gitea - id: publish-source @@ -154,7 +160,7 @@ spec: input: repoUrl: ${{ parameters.repoUrl }} webhookUrl: http://el-${{ parameters.repoName }}-listener.default.svc.cluster.local:8080 - webhookSecret: ${{ parameters.repoName }} + webhookSecret: ${{ parameters.webhookSecret }} events: - push @@ -166,7 +172,7 @@ spec: url: ./content/gitops targetPath: ./gitops values: - name: ${{ parameters.repoName }} + name: ${{ parameters.name }} image: index.docker.io/${{ parameters.dockerOrg }}/${{ parameters.repoName }} dockerOrg: ${{ parameters.dockerOrg }} repoName: ${{ parameters.repoName }} @@ -194,7 +200,7 @@ spec: name: ${{ parameters.repoName }} namespace: default username: ${{ (parameters.repoUrl | parseRepoUrl).owner }} - webhookSecret: ${{ parameters.repoName }} + webhookSecret: ${{ parameters.webhookSecret }} # 9. Deploy HeliosApp to Kubernetes - id: apply-helios diff --git a/apps/portal/packages/backend/catalog.sqlite-journal b/apps/portal/packages/backend/catalog.sqlite-journal new file mode 100644 index 0000000000000000000000000000000000000000..3c08de397e2cff9d08e1659d51a4d566a840f2df GIT binary patch literal 12824 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 Date: Wed, 3 Jun 2026 11:17:57 +0700 Subject: [PATCH 3/5] fix(react-template): resolve CI/CD triggering and portal visibility - Use repoName in template variables to align resource and secret names. - Change webhookSecret parameter to use secure ui:field: Secret. - Template testCommand in helios-app.yaml instead of using a hardcoded value. - Add missing Backstage annotations for Tekton, ArgoCD, and Kubernetes status. --- .../content/gitops/helios-app.yaml | 2 +- .../content/source/catalog-info.yaml | 7 +++++++ .../multi-option-react-template/template.yaml | 10 +++++----- .../packages/backend/catalog.sqlite-journal | Bin 12824 -> 0 bytes 4 files changed, 13 insertions(+), 6 deletions(-) delete mode 100644 apps/portal/packages/backend/catalog.sqlite-journal diff --git a/apps/portal/examples/multi-option-react-template/content/gitops/helios-app.yaml b/apps/portal/examples/multi-option-react-template/content/gitops/helios-app.yaml index 6969975..65d61e8 100644 --- a/apps/portal/examples/multi-option-react-template/content/gitops/helios-app.yaml +++ b/apps/portal/examples/multi-option-react-template/content/gitops/helios-app.yaml @@ -14,7 +14,7 @@ spec: pipelineName: from-code-to-cluster webhookSecret: git-credentials-${{ values.name }} port: ${{ values.port }} - testCommand: "npm install && npm run build" + testCommand: ${{ values.testCommand }} components: - name: ${{ values.name }} type: web-service diff --git a/apps/portal/examples/multi-option-react-template/content/source/catalog-info.yaml b/apps/portal/examples/multi-option-react-template/content/source/catalog-info.yaml index edfd2b9..0870464 100644 --- a/apps/portal/examples/multi-option-react-template/content/source/catalog-info.yaml +++ b/apps/portal/examples/multi-option-react-template/content/source/catalog-info.yaml @@ -4,7 +4,14 @@ metadata: name: ${{ values.name | dump }} description: ${{ (values.description or "") | dump }} annotations: + gitea.org/repo-url: http://localhost:3030/${{ values.owner }}/${{ values.name }} backstage.io/techdocs-ref: dir:. + backstage.io/kubernetes-id: ${{ values.name }} + backstage.io/kubernetes-label-selector: app.kubernetes.io/name=${{ values.name }} + backstage.io/kubernetes-namespace: default + janus-idp.io/tekton: ${{ values.name }} + tekton.dev/ci-cd: "true" + argocd/app-name: ${{ values.name }}-argocd spec: type: website lifecycle: experimental diff --git a/apps/portal/examples/multi-option-react-template/template.yaml b/apps/portal/examples/multi-option-react-template/template.yaml index 64e9d8e..84b61ef 100644 --- a/apps/portal/examples/multi-option-react-template/template.yaml +++ b/apps/portal/examples/multi-option-react-template/template.yaml @@ -108,7 +108,7 @@ spec: title: Webhook Secret type: string description: A secure random secret to secure the Git webhook - ui:widget: password + ui:field: Secret steps: # 1. Fetch Source Code Template @@ -119,7 +119,7 @@ spec: url: ./content/source targetPath: ./source values: - name: ${{ parameters.name }} + name: ${{ parameters.repoName }} description: ${{ parameters.description }} buildTool: ${{ parameters.buildTool }} styling: ${{ parameters.styling }} @@ -160,7 +160,7 @@ spec: input: repoUrl: ${{ parameters.repoUrl }} webhookUrl: http://el-${{ parameters.repoName }}-listener.default.svc.cluster.local:8080 - webhookSecret: ${{ parameters.webhookSecret }} + webhookSecret: ${{ secrets.webhookSecret }} events: - push @@ -172,7 +172,7 @@ spec: url: ./content/gitops targetPath: ./gitops values: - name: ${{ parameters.name }} + name: ${{ parameters.repoName }} image: index.docker.io/${{ parameters.dockerOrg }}/${{ parameters.repoName }} dockerOrg: ${{ parameters.dockerOrg }} repoName: ${{ parameters.repoName }} @@ -200,7 +200,7 @@ spec: name: ${{ parameters.repoName }} namespace: default username: ${{ (parameters.repoUrl | parseRepoUrl).owner }} - webhookSecret: ${{ parameters.webhookSecret }} + webhookSecret: ${{ secrets.webhookSecret }} # 9. Deploy HeliosApp to Kubernetes - id: apply-helios diff --git a/apps/portal/packages/backend/catalog.sqlite-journal b/apps/portal/packages/backend/catalog.sqlite-journal deleted file mode 100644 index 3c08de397e2cff9d08e1659d51a4d566a840f2df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12824 zcmeHNOKcm*8RklsD9Ive$F&(ZwY_0cNE>lQE|<$!(g!2awpvTFDJhL$2+P^sA+;5` zq`6C3gdr4E(joyGr#TcwF9mWca_pr*j|F<^F(}$1Jrw9M$D*gem;SR{K9<~-CBG6h zq%3p!&EtQ}|9}6?h@bs!;tTp?`5zvSXZ+@k4{PxLJCA1!-p;}A==C-5z$-IgWN7wN zkB{?R4tyT?H1Nm3$AR|)_X692Y=8-z_kH5~$k+5C--`eD{@?gpe${_x_H%#Q|Eho5 z_aEP9KFa@v|KI+<`2GZwj9w!Tj65*%z{mq54~#r8^1#RgBM*!`F!I2Q^gwW&r()4q zf{nsIj^>067tIK%kRu)!UUkHUBt48r(-|&#%>}xC=gZX`G+7=Y?t4%^SZAL3+OKoBGHzM&0#Lcc=afkE7SK z^T0#?YGCW`d)psg7S8j0Ykg-&!h$3yILXHNWP*(+dBmcklw>(UlH+nrkTHtN)k>|Z zVa>=WxQH5MBVu6PVCxvk28(JnR;f4Z4edb))zr6eU017GC=-&9yr&zegcsFnWS>Lj z+AiWk;n1F{DbT#snG~Q3Hjsi06v{jZX{dss*--*?Rj@AC)tb?kk)=sL8sxK}08M_Y zMcUZKfLCf|WMCMAG_7hNGD0T;Y2U zQbDQ~fr(MMT4GhLSY2$QO8FRVUZ@&}hvAT3!!mK9(X5d*x{oDYHCTWrE^^ z8m_Aq2p2?@uin>i9pF*}Rw{IO_+V(Y^%|1NKr6T&vtoU#@j~tYN(aThitn=!ir~c; z8jM&`jb|WNB=ejw{`G33F5~B!f`b|7$PthY&z`9oDq+TxP;v<5KPlTjn=CdAh&CY{ zl99X%+56z|@b1n~QRkBVP&^Zd{Y&b(Q>K)Zl>{G4r=vKTciDXfVE2)&65Ckd8rDU!M1C%t5CZ%`^#U-v7gZe9Ni62aOJU%FzuX}dl zMWk@Jm}cdog4nne6&+P-I1j7Zb%|GzKRDsDNBFEl6nr zDrqv2M#V&mm!P1=({ftkB&fbISx&@BffW+T6x3LRq%@yWQn6_C=8a$d0ZJ~lapxAC zE@*OiLI%ylH$f=M^8=cO?}dC1+Tr^l-PaL3o#}ppv3<`jk1)sL2alkpd#J!8(kB>r zH2d4xQ|CQ@ho7f>rMb!A>Kx^PQ!qZz z@0Gzs5jBje`B{WxXdUajA{?9n=WHJ@w#FmgpukZrI31F~=1y2FHp*p@>_tUYIe=4_ zYN%L;iQwExj=V1|<#XB19KEu(oO_!tI1MV$RgEqT4=vF1L;VZP(ek1<$Z^!Wyn&=L z9v(J~oVRq?%)WUm=O(Frwg#oOl4wrR1cM5+yo>7de1f6j5Kn7hYk1g*0=M^aTUcyGtgRVUx2C-Vw=snz=FE}g8(6Cszj$wzV_e?Cz7r+RHI9S1D4B@<# z-B`*l=NNw>wKU7zWgSR(GDa--+kHh9jyl8!?Lma;vNtfM($fziU=AP-W{ zI!Xoe24)Q$FB1m1N+fN}J9964FJ`k-KieVYv2$P-8#TD@!6ICAh~$z+H{4q+1>Rc8 z{g`&|MQ1l=X!Es^yOmqoqz!KIHTrsfebs3Yv|7D?xW2MxJ!_Y&>z;{T+REp1Yn$Tc%4%+7GrM{lBv)Wk zxa_kN0pMNB`Y8!;GhLb{{o2P^RAnQM{z>d~(P`0L`GPP=dnvjM(kfi?1{W5nqx*Iu zZYTXfB44nt)Icj)CCiin1I-L~W7jsR43Mu$(Kfq#Guy51DB)@}1GRQxl9@D1Zgbun z{QecH^=&(KO-8Ne1;^%wrrvAd9>sTLp0)h3wYG9+t6LpAt>WM?JIfJwb<1vniM%!$ zT)N^`(z|nVohSd%RmK~buPLLE2+QU_M26F2rsxwdud7Ku%`d>;tP zwe?NfYAs>7mo>Ktz2xj`?wMy_UTZYDt+(>Cja*;P=T>g4(Lc&T@^cuIBqmfvV9nEac`T_&v0#9nO4XPJ2-)Al}sh~G<+ad)KYsLbf|~&o)b1@ zSKXUC1l9E zUE2WYx=keP-s5=&PfP}z8*Z`F{mYI>p0K8fwT4tyWf6!@7u)}HC+IZUs-E!%H#VrF zoav<#3^_rU3A;B>FrwpOH;Oj-+pMFVa3IWZP*dTU0j1yW=WdM*+hV2@ygwcH26de} z`p$_gc8KlA+2b(e%;`MhFvBy Date: Wed, 3 Jun 2026 17:55:04 +0700 Subject: [PATCH 4/5] fix(scaffolder,portal): resolve template issues and database merge crash --- apps/portal/app-config.yaml | 3 +- .../content/gitops/argocd-app.yaml | 2 +- .../content/gitops/helios-app.yaml | 8 ++-- .../content/gitops/pipeline.yaml | 6 +-- .../content/gitops/triggers.yaml | 41 +++++++++++++------ .../content/source/catalog-info.yaml | 12 +++--- .../content/source/package.json | 2 +- .../{webpack.config.js => webpack.config.ejs} | 0 .../multi-option-react-template/template.yaml | 15 +++---- 9 files changed, 53 insertions(+), 36 deletions(-) rename apps/portal/examples/multi-option-react-template/content/source/{webpack.config.js => webpack.config.ejs} (100%) diff --git a/apps/portal/app-config.yaml b/apps/portal/app-config.yaml index 25037ea..f3c2c2b 100644 --- a/apps/portal/app-config.yaml +++ b/apps/portal/app-config.yaml @@ -31,7 +31,8 @@ backend: # The production database configuration is stored in app-config.production.yaml database: client: better-sqlite3 - connection: ':memory:' + connection: + filename: ':memory:' # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir integrations: diff --git a/apps/portal/examples/multi-option-react-template/content/gitops/argocd-app.yaml b/apps/portal/examples/multi-option-react-template/content/gitops/argocd-app.yaml index f980436..16ef386 100644 --- a/apps/portal/examples/multi-option-react-template/content/gitops/argocd-app.yaml +++ b/apps/portal/examples/multi-option-react-template/content/gitops/argocd-app.yaml @@ -1,7 +1,7 @@ apiVersion: argoproj.io/v1alpha1 kind: Application metadata: - name: ${{ values.name }}-argocd + name: ${{ values.repoName }}-argocd namespace: argocd finalizers: - resources-finalizer.argocd.argoproj.io diff --git a/apps/portal/examples/multi-option-react-template/content/gitops/helios-app.yaml b/apps/portal/examples/multi-option-react-template/content/gitops/helios-app.yaml index 65d61e8..9885625 100644 --- a/apps/portal/examples/multi-option-react-template/content/gitops/helios-app.yaml +++ b/apps/portal/examples/multi-option-react-template/content/gitops/helios-app.yaml @@ -1,7 +1,7 @@ apiVersion: app.helios.io/v1alpha1 kind: HeliosApp metadata: - name: ${{ values.name }} + name: ${{ values.repoName }} namespace: default spec: owner: ${{ values.owner }} @@ -10,13 +10,13 @@ spec: gitBranch: main imageRepo: ${{ values.image }} gitopsRepo: ${{ values.gitopsRepo }} - gitopsPath: ${{ values.name }} + gitopsPath: ${{ values.repoName }} pipelineName: from-code-to-cluster - webhookSecret: git-credentials-${{ values.name }} + webhookSecret: git-credentials-${{ values.repoName }} port: ${{ values.port }} testCommand: ${{ values.testCommand }} components: - - name: ${{ values.name }} + - name: ${{ values.repoName }} type: web-service properties: image: ${{ values.image }}:latest diff --git a/apps/portal/examples/multi-option-react-template/content/gitops/pipeline.yaml b/apps/portal/examples/multi-option-react-template/content/gitops/pipeline.yaml index 17eee4e..08d7988 100644 --- a/apps/portal/examples/multi-option-react-template/content/gitops/pipeline.yaml +++ b/apps/portal/examples/multi-option-react-template/content/gitops/pipeline.yaml @@ -1,9 +1,9 @@ apiVersion: tekton.dev/v1beta1 kind: PipelineRun metadata: - name: ${{ values.name }}-pipeline-run + name: ${{ values.repoName }}-pipeline-run annotations: - janus-idp.io/tekton: ${{ values.name }} + janus-idp.io/tekton: ${{ values.repoName }} spec: serviceAccountName: pipeline pipelineRef: @@ -28,7 +28,7 @@ spec: - name: argocd-namespace value: argocd - name: argocd-app-name - value: ${{ values.name }}-argocd + value: ${{ values.repoName }}-argocd workspaces: - name: source-workspace volumeClaimTemplate: diff --git a/apps/portal/examples/multi-option-react-template/content/gitops/triggers.yaml b/apps/portal/examples/multi-option-react-template/content/gitops/triggers.yaml index 9ca8775..b1201b3 100644 --- a/apps/portal/examples/multi-option-react-template/content/gitops/triggers.yaml +++ b/apps/portal/examples/multi-option-react-template/content/gitops/triggers.yaml @@ -1,7 +1,7 @@ apiVersion: triggers.tekton.dev/v1beta1 kind: TriggerBinding metadata: - name: ${{ values.name }}-git-binding + name: ${{ values.repoName }}-git-binding spec: params: - name: git-repo-url @@ -17,16 +17,16 @@ spec: apiVersion: triggers.tekton.dev/v1beta1 kind: TriggerTemplate metadata: - name: ${{ values.name }}-git-template + name: ${{ values.repoName }}-git-template spec: resourcetemplates: - apiVersion: tekton.dev/v1beta1 kind: PipelineRun metadata: - generateName: ${{ values.name }}-run- + generateName: ${{ values.repoName }}-run- labels: tekton.dev/pipeline: from-code-to-cluster - app.kubernetes.io/instance: ${{ values.name }} + app.kubernetes.io/instance: ${{ values.repoName }} spec: serviceAccountName: pipeline pipelineRef: @@ -46,47 +46,62 @@ spec: value: "helios-app.yaml" - name: docker-secret value: "docker-credentials" + - name: test-command + value: "${{ values.testCommand }}" + - name: argocd-namespace + value: "argocd" + - name: argocd-app-name + value: "${{ values.repoName }}-argocd" + workspaces: + - name: source-workspace + volumeClaimTemplate: + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi --- apiVersion: triggers.tekton.dev/v1beta1 kind: EventListener metadata: - name: ${{ values.name }}-listener + name: ${{ values.repoName }}-listener spec: - serviceAccountName: ${{ values.name }}-sa + serviceAccountName: ${{ values.repoName }}-sa triggers: - binding: - name: ${{ values.name }}-git-binding + name: ${{ values.repoName }}-git-binding template: - name: ${{ values.name }}-git-template + name: ${{ values.repoName }}-git-template --- apiVersion: v1 kind: ServiceAccount metadata: - name: ${{ values.name }}-sa + name: ${{ values.repoName }}-sa --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: - name: ${{ values.name }}-listener-binding + name: ${{ values.repoName }}-listener-binding roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: tekton-triggers-eventlistener-roles subjects: - kind: ServiceAccount - name: ${{ values.name }}-sa + name: ${{ values.repoName }}-sa --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: - name: ${{ values.name }}-listener-clusterbinding + name: ${{ values.repoName }}-listener-clusterbinding roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: tekton-triggers-eventlistener-clusterroles subjects: - kind: ServiceAccount - name: ${{ values.name }}-sa + name: ${{ values.repoName }}-sa namespace: default diff --git a/apps/portal/examples/multi-option-react-template/content/source/catalog-info.yaml b/apps/portal/examples/multi-option-react-template/content/source/catalog-info.yaml index 0870464..39928b3 100644 --- a/apps/portal/examples/multi-option-react-template/content/source/catalog-info.yaml +++ b/apps/portal/examples/multi-option-react-template/content/source/catalog-info.yaml @@ -1,17 +1,17 @@ apiVersion: backstage.io/v1alpha1 kind: Component metadata: - name: ${{ values.name | dump }} + name: ${{ values.repoName | dump }} description: ${{ (values.description or "") | dump }} annotations: - gitea.org/repo-url: http://localhost:3030/${{ values.owner }}/${{ values.name }} + gitea.org/repo-url: http://localhost:3030/${{ values.owner }}/${{ values.repoName }} backstage.io/techdocs-ref: dir:. - backstage.io/kubernetes-id: ${{ values.name }} - backstage.io/kubernetes-label-selector: app.kubernetes.io/name=${{ values.name }} + backstage.io/kubernetes-id: ${{ values.repoName }} + backstage.io/kubernetes-label-selector: app.kubernetes.io/name=${{ values.repoName }} backstage.io/kubernetes-namespace: default - janus-idp.io/tekton: ${{ values.name }} + janus-idp.io/tekton: ${{ values.repoName }} tekton.dev/ci-cd: "true" - argocd/app-name: ${{ values.name }}-argocd + argocd/app-name: ${{ values.repoName }}-argocd spec: type: website lifecycle: experimental diff --git a/apps/portal/examples/multi-option-react-template/content/source/package.json b/apps/portal/examples/multi-option-react-template/content/source/package.json index 10df480..1ecb28e 100644 --- a/apps/portal/examples/multi-option-react-template/content/source/package.json +++ b/apps/portal/examples/multi-option-react-template/content/source/package.json @@ -1,5 +1,5 @@ { - "name": "${{ values.name }}", + "name": "${{ values.repoName }}", "private": true, "version": "1.0.0", {% if values.buildTool == 'vite' -%} diff --git a/apps/portal/examples/multi-option-react-template/content/source/webpack.config.js b/apps/portal/examples/multi-option-react-template/content/source/webpack.config.ejs similarity index 100% rename from apps/portal/examples/multi-option-react-template/content/source/webpack.config.js rename to apps/portal/examples/multi-option-react-template/content/source/webpack.config.ejs diff --git a/apps/portal/examples/multi-option-react-template/template.yaml b/apps/portal/examples/multi-option-react-template/template.yaml index 84b61ef..fd6e1ff 100644 --- a/apps/portal/examples/multi-option-react-template/template.yaml +++ b/apps/portal/examples/multi-option-react-template/template.yaml @@ -119,14 +119,15 @@ spec: url: ./content/source targetPath: ./source values: - name: ${{ parameters.repoName }} + name: ${{ parameters.name }} + repoName: ${{ parameters.repoName }} description: ${{ parameters.description }} buildTool: ${{ parameters.buildTool }} styling: ${{ parameters.styling }} stateManagement: ${{ parameters.stateManagement }} dataFetching: ${{ parameters.dataFetching }} port: ${{ parameters.port }} - owner: ${{ user.entity.metadata.name or 'guest' }} + owner: ${{ (parameters.repoUrl | parseRepoUrl).owner }} image: index.docker.io/${{ parameters.dockerOrg }}/${{ parameters.repoName }} # 2. Configure Selected Stack (Always run, dynamic list) @@ -134,14 +135,14 @@ spec: name: Configure Selected Stack action: fs:rename input: - files: '${{ [ { from: "./source/index." + parameters.buildTool + ".html", to: "./source/index.html" }, { from: "./source/src/index." + parameters.styling + ".css", to: "./source/src/index.css" } ].concat( [ { from: "./source/src/store/reduxStore.js", to: "./source/src/store/store.js" } ] if parameters.stateManagement == "redux" else ( [ { from: "./source/src/store/zustandStore.js", to: "./source/src/store/store.js" } ] if parameters.stateManagement == "zustand" else [] ) ) }}' + files: '${{ [ { from: "./source/index." + parameters.buildTool + ".html", to: "./source/index.html" }, { from: "./source/src/index." + parameters.styling + ".css", to: "./source/src/index.css" } ].concat( [ { from: "./source/src/store/reduxStore.js", to: "./source/src/store/store.js" } ] if parameters.stateManagement == "redux" else ( [ { from: "./source/src/store/zustandStore.js", to: "./source/src/store/store.js" } ] if parameters.stateManagement == "zustand" else [] ) ).concat( [ { from: "./source/webpack.config.ejs", to: "./source/webpack.config.js" } ] if parameters.buildTool == "webpack" else [] ) }}' # 3. Clean up Unused Files (Always run, dynamic list) - id: cleanup-files name: Clean up Unused Files action: fs:delete input: - files: '${{ ( [ "./source/index.webpack.html", "./source/webpack.config.js", "./source/babel.config.json" ] if parameters.buildTool == "vite" else [ "./source/index.vite.html", "./source/vite.config.js" ] ).concat( [ "./source/tailwind.config.cjs", "./source/postcss.config.cjs" ] if parameters.styling == "vanilla" else [] ).concat( [ "./source/src/store/zustandStore.js" ] if parameters.stateManagement == "redux" else ( [ "./source/src/store/reduxStore.js" ] if parameters.stateManagement == "zustand" else [ "./source/src/store" ] ) ).concat( [ "./source/src/index.vanilla.css" ] if parameters.styling == "tailwind" else [ "./source/src/index.tailwind.css" ] ) }}' + files: '${{ ( [ "./source/index.webpack.html", "./source/webpack.config.ejs", "./source/babel.config.json" ] if parameters.buildTool == "vite" else [ "./source/index.vite.html", "./source/vite.config.js" ] ).concat( [ "./source/tailwind.config.cjs", "./source/postcss.config.cjs" ] if parameters.styling == "vanilla" else [] ).concat( [ "./source/src/store/zustandStore.js" ] if parameters.stateManagement == "redux" else ( [ "./source/src/store/reduxStore.js" ] if parameters.stateManagement == "zustand" else [ "./source/src/store" ] ) ).concat( [ "./source/src/index.vanilla.css" ] if parameters.styling == "tailwind" else [ "./source/src/index.tailwind.css" ] ) }}' # 4. Publish Source Code to Gitea - id: publish-source @@ -172,12 +173,12 @@ spec: url: ./content/gitops targetPath: ./gitops values: - name: ${{ parameters.repoName }} + name: ${{ parameters.name }} + repoName: ${{ parameters.repoName }} image: index.docker.io/${{ parameters.dockerOrg }}/${{ parameters.repoName }} dockerOrg: ${{ parameters.dockerOrg }} - repoName: ${{ parameters.repoName }} port: ${{ parameters.port }} - owner: ${{ user.entity.metadata.name or 'guest' }} + owner: ${{ (parameters.repoUrl | parseRepoUrl).owner }} sourceRepo: ${{ steps['publish-source'].output.remoteUrl }} gitopsRepo: ${{ steps['publish-source'].output.remoteUrl | replace(".git", "") }}-gitops testCommand: "npm install && npm run build" From fe0212bdad3e52bcac9876731ae12c30bb7f664d Mon Sep 17 00:00:00 2001 From: hoangphuc841 Date: Wed, 3 Jun 2026 18:12:32 +0700 Subject: [PATCH 5/5] fix(react template): add pattern to repoName to avoid illegal characters. Comments added for Nunjucks logic. --- .../content/gitops/pipeline.yaml | 2 +- .../content/gitops/triggers.yaml | 2 +- .../multi-option-react-template/content/source/package.json | 2 +- .../examples/multi-option-react-template/template.yaml | 6 ++++++ 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/portal/examples/multi-option-react-template/content/gitops/pipeline.yaml b/apps/portal/examples/multi-option-react-template/content/gitops/pipeline.yaml index 08d7988..8bbfe9a 100644 --- a/apps/portal/examples/multi-option-react-template/content/gitops/pipeline.yaml +++ b/apps/portal/examples/multi-option-react-template/content/gitops/pipeline.yaml @@ -1,4 +1,4 @@ -apiVersion: tekton.dev/v1beta1 +apiVersion: tekton.dev/v1 kind: PipelineRun metadata: name: ${{ values.repoName }}-pipeline-run diff --git a/apps/portal/examples/multi-option-react-template/content/gitops/triggers.yaml b/apps/portal/examples/multi-option-react-template/content/gitops/triggers.yaml index b1201b3..1fec45c 100644 --- a/apps/portal/examples/multi-option-react-template/content/gitops/triggers.yaml +++ b/apps/portal/examples/multi-option-react-template/content/gitops/triggers.yaml @@ -20,7 +20,7 @@ metadata: name: ${{ values.repoName }}-git-template spec: resourcetemplates: - - apiVersion: tekton.dev/v1beta1 + - apiVersion: tekton.dev/v1 kind: PipelineRun metadata: generateName: ${{ values.repoName }}-run- diff --git a/apps/portal/examples/multi-option-react-template/content/source/package.json b/apps/portal/examples/multi-option-react-template/content/source/package.json index 1ecb28e..964a8e8 100644 --- a/apps/portal/examples/multi-option-react-template/content/source/package.json +++ b/apps/portal/examples/multi-option-react-template/content/source/package.json @@ -1,5 +1,5 @@ { - "name": "${{ values.repoName }}", + "name": "${{ values.repoName | lower }}", "private": true, "version": "1.0.0", {% if values.buildTool == 'vite' -%} diff --git a/apps/portal/examples/multi-option-react-template/template.yaml b/apps/portal/examples/multi-option-react-template/template.yaml index fd6e1ff..bb034cc 100644 --- a/apps/portal/examples/multi-option-react-template/template.yaml +++ b/apps/portal/examples/multi-option-react-template/template.yaml @@ -35,6 +35,7 @@ spec: title: Docker Repository Name type: string description: The name of the repository (e.g. my-react-app) + pattern: "^(?:@[a-z0-9-~][a-z0-9-._~]*/)?[a-z0-9-~][a-z0-9-._~]*$" description: title: Description type: string @@ -131,6 +132,11 @@ spec: image: index.docker.io/${{ parameters.dockerOrg }}/${{ parameters.repoName }} # 2. Configure Selected Stack (Always run, dynamic list) + # File rename logic: + # - index.{buildTool}.html -> index.html (vite or webpack) + # - src/index.{styling}.css -> src/index.css (tailwind or vanilla) + # - src/store/{redux|zustand}Store.js -> src/store/store.js (if not "none") + # - webpack.config.ejs -> webpack.config.js (if webpack) - id: configure-files name: Configure Selected Stack action: fs:rename