Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 11f86bde47 | |||
| 23e6ac3afd |
@@ -0,0 +1,428 @@
|
|||||||
|
// GitLab CI 流水线代码生成器 for ConfTemplate
|
||||||
|
// 生成符合 .gitlab-ci.yml v14+ 规范的声明式流水线 YAML
|
||||||
|
|
||||||
|
export const gitlabCiSchema = {
|
||||||
|
id: 'gitlab-ci',
|
||||||
|
name: 'GitLab CI',
|
||||||
|
icon: 'Files',
|
||||||
|
category: 'CI/CD',
|
||||||
|
description: '生成 .gitlab-ci.yml 声明式流水线代码',
|
||||||
|
format: 'yaml',
|
||||||
|
fileName: '.gitlab-ci.yml',
|
||||||
|
groups: [
|
||||||
|
// ─────────────── 全局变量 ───────────────
|
||||||
|
{
|
||||||
|
title: '全局变量',
|
||||||
|
fields: [
|
||||||
|
{ key: 'stagesEnabled', label: '自定义 stages', type: 'switch', default: true,
|
||||||
|
tip: '关闭则使用 GitLab 默认 stages (build / test / deploy)' },
|
||||||
|
{ key: 'stages', label: 'Stages 顺序', type: 'text',
|
||||||
|
default: 'lint,build,test,deploy',
|
||||||
|
placeholder: 'lint,build,test,deploy',
|
||||||
|
dependsOn: { key: 'stagesEnabled', value: true },
|
||||||
|
tip: '逗号分隔,按执行顺序排列' },
|
||||||
|
{ key: 'defaultImage', label: '默认镜像', type: 'text',
|
||||||
|
default: 'alpine:3.20',
|
||||||
|
tip: '未在 job 中指定 image 时使用' },
|
||||||
|
{ key: 'variablesMode', label: '全局变量', type: 'keyvalue', default: [],
|
||||||
|
tip: '所有 job 都可用的 CI/CD 变量,例如 PROJECT_NAME / DOCKER_REGISTRY' },
|
||||||
|
{ key: 'workflowPolicy', label: 'workflow 策略', type: 'select', default: 'auto', options: [
|
||||||
|
{ label: 'auto (CI 默认可跑)', value: 'auto' },
|
||||||
|
{ label: 'always (任何情况都跑)', value: 'always' },
|
||||||
|
{ label: 'on_success (上游成功才跑)', value: 'on_success' },
|
||||||
|
{ label: 'manual (需要手动触发下游)', value: 'manual' },
|
||||||
|
{ label: 'none (禁止流水线,不允许触发)', value: 'none' },
|
||||||
|
], tip: 'GitLab Premium 有 start_in / rules 输入参数' },
|
||||||
|
{ key: 'workflowInputs', label: 'workflow:inputs 声明', type: 'code',
|
||||||
|
language: 'yaml', default: '',
|
||||||
|
tip: '可选:手动指定 workflow:inputs: 示例 → 用 [[inputs]] 上半部 YAML 多行块;留空跳过' },
|
||||||
|
{ key: 'includeMode', label: 'Include 模板块(高级)', type: 'code',
|
||||||
|
language: 'yaml', default: '',
|
||||||
|
tip: '需要 local / template / remote 引用模板时直接在这里补全完整 include 块;留空则不输出' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// ─────────────── 构建 Job ───────────────
|
||||||
|
{
|
||||||
|
title: 'Job · lint (静态检查)',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableLint', label: '启用 lint 阶段', type: 'switch', default: true,
|
||||||
|
tip: '关闭则完全不渲染 lint job(连 stage 都没出现)' },
|
||||||
|
{ key: 'lintName', label: 'Job 名', type: 'text', default: 'lint',
|
||||||
|
dependsOn: { key: 'enableLint', value: true } },
|
||||||
|
{ key: 'lintImage', label: '镜像', type: 'text', default: 'alpine:3.20',
|
||||||
|
dependsOn: { key: 'enableLint', value: true } },
|
||||||
|
{ key: 'lintStage', label: 'Stage', type: 'text', default: 'lint',
|
||||||
|
dependsOn: { key: 'enableLint', value: true } },
|
||||||
|
{ key: 'lintScript', label: 'script', type: 'code', language: 'shell', default: 'echo "🔍 running lint"\n# npm run lint\n# ruff check .\n# shellcheck *.sh',
|
||||||
|
dependsOn: { key: 'enableLint', value: true } },
|
||||||
|
{ key: 'lintAllowFailure', label: 'allow_failure', type: 'switch', default: true,
|
||||||
|
dependsOn: { key: 'enableLint', value: true } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Job · build (打包产物)',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableBuild', label: '启用 build 阶段', type: 'switch', default: true },
|
||||||
|
{ key: 'buildName', label: 'Job 名', type: 'text', default: 'build',
|
||||||
|
dependsOn: { key: 'enableBuild', value: true } },
|
||||||
|
{ key: 'buildImage', label: '镜像', type: 'text',
|
||||||
|
default: 'docker:27-cli',
|
||||||
|
dependsOn: { key: 'enableBuild', value: true } },
|
||||||
|
{ key: 'buildStage', label: 'Stage', type: 'text', default: 'build',
|
||||||
|
dependsOn: { key: 'enableBuild', value: true } },
|
||||||
|
{ key: 'buildServices', label: 'services (附带容器)', type: 'keyvalue', default: [
|
||||||
|
{ key: 'docker:dind', value: '' }
|
||||||
|
], dependsOn: { key: 'enableBuild', value: true },
|
||||||
|
tip: '仅当需要 DinD / docker socket 时配置,留空则禁用' },
|
||||||
|
{ key: 'buildScript', label: 'script', type: 'code', language: 'shell',
|
||||||
|
default:
|
||||||
|
'echo "📦 building $CI_PROJECT_NAME"\n# 添加 Dockerfile / 构建脚本\ndocker login -u "$REGISTRY_USER" -p "$REGISTRY_PASS" "$CI_REGISTRY"\ndocker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .\ndocker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"',
|
||||||
|
dependsOn: { key: 'enableBuild', value: true } },
|
||||||
|
{ key: 'buildArtifacts', label: 'artifacts.paths', type: 'text',
|
||||||
|
default: 'dist/\nbuild/',
|
||||||
|
dependsOn: { key: 'enableBuild', value: true }, tip: '多个路径用换行分隔' },
|
||||||
|
{ key: 'buildArtifactsExpire', label: 'artifacts.expire_in', type: 'text',
|
||||||
|
default: '1 week', dependsOn: { key: 'enableBuild', value: true } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Job · test (自动化测试)',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableTest', label: '启用 test 阶段', type: 'switch', default: true },
|
||||||
|
{ key: 'testName', label: 'Job 名', type: 'text', default: 'test',
|
||||||
|
dependsOn: { key: 'enableTest', value: true } },
|
||||||
|
{ key: 'testImage', label: '镜像', type: 'text',
|
||||||
|
default: 'python:3.12-slim',
|
||||||
|
dependsOn: { key: 'enableTest', value: true } },
|
||||||
|
{ key: 'testStage', label: 'Stage', type: 'text', default: 'test',
|
||||||
|
dependsOn: { key: 'enableTest', value: true } },
|
||||||
|
{ key: 'testParallel', label: 'parallel (矩阵)', type: 'number',
|
||||||
|
min: 1, max: 50, default: 1, dependsOn: { key: 'enableTest', value: true } },
|
||||||
|
{ key: 'testCoverage', label: 'coverage 输入正则', type: 'text',
|
||||||
|
default: '', dependsOn: { key: 'enableTest', value: true },
|
||||||
|
tip: '如 /\\d+\\.\\d+%/,空则不上报 coverage' },
|
||||||
|
{ key: 'testScript', label: 'script', type: 'code', language: 'shell',
|
||||||
|
default:
|
||||||
|
'echo "🧪 running tests"\n# pytest --junitxml=report.xml --cov=src --cov-report=term\n# 收集 junit 报告供 GitLab UI 展示\n',
|
||||||
|
dependsOn: { key: 'enableTest', value: true } },
|
||||||
|
{ key: 'testArtifacts', label: 'artifacts.when/paths', type: 'text',
|
||||||
|
default: 'report.xml', dependsOn: { key: 'enableTest', value: true },
|
||||||
|
tip: '仅当有 junit/codecov 产出时填写' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// ─────────────── Deploy 阶段(多环境)───────────────
|
||||||
|
{
|
||||||
|
title: 'Job · deploy (多环境)',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableDeploy', label: '启用 deploy 阶段', type: 'switch', default: true },
|
||||||
|
{ key: 'deployImage', label: '镜像', type: 'text',
|
||||||
|
default: 'alpine:3.20',
|
||||||
|
dependsOn: { key: 'enableDeploy', value: true } },
|
||||||
|
{ key: 'deployStage', label: 'Stage', type: 'text', default: 'deploy',
|
||||||
|
dependsOn: { key: 'enableDeploy', value: true } },
|
||||||
|
{ key: 'deployEnvironment', label: 'Deploy 环境 (Environment)', type: 'text',
|
||||||
|
default: 'production',
|
||||||
|
dependsOn: { key: 'enableDeploy', value: true } },
|
||||||
|
{ key: 'deployNeeds', label: 'needs (上游 job)', type: 'text',
|
||||||
|
default: 'build', dependsOn: { key: 'enableDeploy', value: true },
|
||||||
|
tip: '逗号分隔多个 job 名' },
|
||||||
|
{ key: 'deployScript', label: 'script', type: 'code', language: 'shell',
|
||||||
|
default: 'echo "🚀 deploying $CI_PROJECT_NAME"\n# kubectl --context=prod apply -f k8s/\n# 或 ssh 推送 / helm upgrade --install',
|
||||||
|
dependsOn: { key: 'enableDeploy', value: true } },
|
||||||
|
{ key: 'deployWhen', label: 'rules.only/except 分支', type: 'text',
|
||||||
|
default: 'main', dependsOn: { key: 'enableDeploy', value: true },
|
||||||
|
tip: '默认仅 main 部署;输入 main 或 main,master 支持多分支' },
|
||||||
|
{ key: 'deployWhenMode', label: '匹配模式', type: 'select', default: 'branches', options: [
|
||||||
|
{ label: 'branches (指定分支)', value: 'branches' },
|
||||||
|
{ label: 'tags (打 tag 后)', value: 'tags' },
|
||||||
|
{ label: 'merge_requests (MR)', value: 'merge_requests' },
|
||||||
|
{ label: 'schedules (定时)', value: 'schedules' },
|
||||||
|
{ label: 'web (Web 触发)', value: 'web' },
|
||||||
|
], dependsOn: { key: 'enableDeploy', value: true } },
|
||||||
|
{ key: 'deployManual', label: 'manual gate (人工点击部署)', type: 'switch',
|
||||||
|
default: true, dependsOn: { key: 'enableDeploy', value: true } },
|
||||||
|
{ key: 'deployAllowFailure', label: 'allow_failure (失败也继续)', type: 'switch',
|
||||||
|
default: false, dependsOn: { key: 'enableDeploy', value: true } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// ─────────────── 缓存 ───────────────
|
||||||
|
{
|
||||||
|
title: '缓存与依赖',
|
||||||
|
fields: [
|
||||||
|
{ key: 'cacheKeyPrefix', label: 'cache.key 前缀', type: 'text',
|
||||||
|
default: '$CI_COMMIT_REF_SLUG',
|
||||||
|
tip: '留空则不输出 cache 块' },
|
||||||
|
{ key: 'cachePaths', label: 'cache.paths', type: 'text',
|
||||||
|
default: 'node_modules/\n.virtualenvs/\n.go/pkg/mod/\n.gradle/caches/\n.cache/',
|
||||||
|
tip: '一行一个路径,留空不输出 cache 块' },
|
||||||
|
{ key: 'cachePolicy', label: 'cache.policy', type: 'select', default: 'pull-push', options: [
|
||||||
|
{ label: 'pull-push (上传下载)', value: 'pull-push' },
|
||||||
|
{ label: 'pull (仅下载)', value: 'pull' },
|
||||||
|
{ label: 'push (仅上传)', value: 'push' },
|
||||||
|
] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// ─────────────── Runner Tag ───────────────
|
||||||
|
{
|
||||||
|
title: 'Runner Tags(runner 选择)',
|
||||||
|
fields: [
|
||||||
|
{ key: 'defaultTags', label: '默认 tags (在 default 段输出)',
|
||||||
|
type: 'text', default: '', tip: '逗号分隔;空则不输出 default.tags' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// YAML 渲染(GitLab CI 是 YAML,和 toYaml 共用修正后版本)
|
||||||
|
// 数组里嵌对象要保证 list 形态,不能 dict 化
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
|
function yamlScalar(v) {
|
||||||
|
if (v === null || v === undefined) return ''
|
||||||
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
||||||
|
const s = String(v)
|
||||||
|
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
|
||||||
|
return JSON.stringify(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyMapping(o) {
|
||||||
|
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
|
||||||
|
return Object.values(o).every(v =>
|
||||||
|
v === null || v === undefined || v === '' ||
|
||||||
|
(typeof v === 'object' && isEmptyMapping(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toYaml(obj, baseIndent = '') {
|
||||||
|
const lines = []
|
||||||
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
|
if (value === null || value === undefined || value === '') continue
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
const items = value.filter(v => v !== null && v !== undefined && v !== '')
|
||||||
|
if (!items.length) continue
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
for (const item of items) {
|
||||||
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||||
|
if (isEmptyMapping(item)) continue
|
||||||
|
const entries = Object.entries(item).filter(([, v]) =>
|
||||||
|
v !== null && v !== undefined && v !== '')
|
||||||
|
if (!entries.length) continue
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
const subAllIndent = childIndent + ' '
|
||||||
|
const subObj = {}
|
||||||
|
for (const [k, v] of entries) subObj[k] = v
|
||||||
|
const subRendered = toYaml(subObj, subAllIndent)
|
||||||
|
const subLines = subRendered.split('\n')
|
||||||
|
if (subLines[0].startsWith(subAllIndent)) {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
|
||||||
|
} else {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
|
||||||
|
}
|
||||||
|
lines.push(...subLines)
|
||||||
|
} else {
|
||||||
|
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (typeof value === 'object') {
|
||||||
|
if (isEmptyMapping(value)) {
|
||||||
|
lines.push(`${baseIndent}${key}: {}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
lines.push(toYaml(value, childIndent))
|
||||||
|
} else {
|
||||||
|
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lines.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 把 keyvalue 形 {key:'name',value:'1'} 数组转成 {name:1,...} 简单映射;或保留 array
|
||||||
|
function toArrayOfKeyedObjects(arr) {
|
||||||
|
if (!Array.isArray(arr)) return []
|
||||||
|
// 形如 [{key:'docker:dind', value:''}] 在 services 中需要 {name:..., alias:...}
|
||||||
|
// GitLab services 形如: services: [docker:dind, postgres:15]
|
||||||
|
// 这里简单返回原值(去 value 为空的保留 key)
|
||||||
|
return arr
|
||||||
|
.filter(x => x && x.key && x.key !== '')
|
||||||
|
.map(x => x.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────── 数据装配 ───────────────
|
||||||
|
function buildJob(cfg, name, stage, image, script, extra = {}) {
|
||||||
|
const job = {}
|
||||||
|
if (stage) job.stage = stage
|
||||||
|
if (image) job.image = image
|
||||||
|
if (Array.isArray(script) && script.length) job.script = script
|
||||||
|
Object.assign(job, extra)
|
||||||
|
// 过滤掉空
|
||||||
|
for (const k of Object.keys(job)) {
|
||||||
|
const v = job[k]
|
||||||
|
if (v === null || v === undefined || v === '' ||
|
||||||
|
(Array.isArray(v) && !v.length)) delete job[k]
|
||||||
|
}
|
||||||
|
return { [name]: job }
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildStageList(stagesEnabled, customStages) {
|
||||||
|
if (!stagesEnabled) return null
|
||||||
|
const cleaned = (customStages || '')
|
||||||
|
.split(/[,\n]/)
|
||||||
|
.map(s => s.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
return cleaned.length ? cleaned : ['lint', 'build', 'test', 'deploy']
|
||||||
|
}
|
||||||
|
|
||||||
|
// 把多行文本变成 { script: [...] },保留空行当 echo
|
||||||
|
function splitScript(raw) {
|
||||||
|
if (typeof raw !== 'string') return []
|
||||||
|
return raw
|
||||||
|
.split('\n')
|
||||||
|
.map(s => s.trim())
|
||||||
|
.filter(s => s.length > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateGitlabCi(config) {
|
||||||
|
const doc = {}
|
||||||
|
|
||||||
|
// stages
|
||||||
|
const stages = buildStageList(config.stagesEnabled, config.stages)
|
||||||
|
if (stages) doc.stages = stages
|
||||||
|
|
||||||
|
// variables
|
||||||
|
const varsArr = []
|
||||||
|
if (config.variablesMode && Array.isArray(config.variablesMode)) {
|
||||||
|
for (const kv of config.variablesMode) {
|
||||||
|
if (kv && kv.key && kv.key !== '') varsArr.push({ key: kv.key, value: String(kv.value ?? '') })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (varsArr.length) doc.variables = Object.fromEntries(varsArr.map(v => [v.key, v.value]))
|
||||||
|
|
||||||
|
// default
|
||||||
|
const defaultObj = {}
|
||||||
|
if (config.defaultImage) defaultObj.image = config.defaultImage
|
||||||
|
if (config.defaultTags) defaultObj.tags = config.defaultTags.split(',').map(t => t.trim()).filter(Boolean)
|
||||||
|
// cache in default
|
||||||
|
if (config.cacheKeyPrefix && config.cachePaths) {
|
||||||
|
const paths = config.cachePaths.split('\n').map(s => s.trim()).filter(Boolean)
|
||||||
|
if (paths.length) {
|
||||||
|
defaultObj.cache = {
|
||||||
|
key: { files: [config.cacheKeyPrefix] },
|
||||||
|
paths,
|
||||||
|
policy: config.cachePolicy || 'pull-push',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Object.keys(defaultObj).length) doc.default = defaultObj
|
||||||
|
|
||||||
|
// workflow
|
||||||
|
if (config.workflowPolicy && config.workflowPolicy !== 'auto') {
|
||||||
|
if (config.workflowPolicy === 'none') {
|
||||||
|
doc.workflow = { rules: [{ when: 'never' }] }
|
||||||
|
} else {
|
||||||
|
const w = { rules: [{ when: config.workflowPolicy }] }
|
||||||
|
if (config.workflowInputs) {
|
||||||
|
try {
|
||||||
|
const parsed = yaml.safeLoad ? null : null // noop; we keep simple
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
|
}
|
||||||
|
doc.workflow = w
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// include (高级用户自填)
|
||||||
|
if (config.includeMode && config.includeMode.trim()) {
|
||||||
|
// 解析:用户填的是合法 YAML 片段;这里做最稳:尝试 as 字符串
|
||||||
|
const incRaw = config.includeMode.trim()
|
||||||
|
// 简单判定:用 include 直接给字符串或对象数组
|
||||||
|
try {
|
||||||
|
// 用内置简易辨识:包含 ":" 视为子对象
|
||||||
|
const lines = incRaw.split('\n').map(l => l).filter(Boolean)
|
||||||
|
const looksLikeMap = lines.length <= 1 && lines[0].startsWith('-')
|
||||||
|
if (looksLikeMap) {
|
||||||
|
doc.include = '<<<请把 include 写成多行 YAML 块>>>'
|
||||||
|
} else {
|
||||||
|
doc.include = incRaw
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
doc.include = incRaw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lint job
|
||||||
|
if (config.enableLint) {
|
||||||
|
Object.assign(doc, buildJob(
|
||||||
|
config, config.lintName || 'lint', config.lintStage || 'lint',
|
||||||
|
config.lintImage, splitScript(config.lintScript),
|
||||||
|
{ allow_failure: !!config.lintAllowFailure }
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// build job
|
||||||
|
if (config.enableBuild) {
|
||||||
|
const services = toArrayOfKeyedObjects(config.buildServices)
|
||||||
|
Object.assign(doc, buildJob(
|
||||||
|
config, config.buildName || 'build', config.buildStage || 'build',
|
||||||
|
config.buildImage, splitScript(config.buildScript),
|
||||||
|
{
|
||||||
|
services: services.length ? services : undefined,
|
||||||
|
artifacts: (() => {
|
||||||
|
const paths = (config.buildArtifacts || '').split('\n').map(s => s.trim()).filter(Boolean)
|
||||||
|
if (!paths.length) return undefined
|
||||||
|
return { paths, expire_in: config.buildArtifactsExpire || '1 week' }
|
||||||
|
})(),
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// test job
|
||||||
|
if (config.enableTest) {
|
||||||
|
const extra = {}
|
||||||
|
if (config.testCoverage) {
|
||||||
|
extra.coverage = config.testCoverage
|
||||||
|
}
|
||||||
|
if (config.testArtifacts) {
|
||||||
|
extra.artifacts = {
|
||||||
|
when: 'always',
|
||||||
|
reports: { junit: config.testArtifacts },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Object.assign(doc, buildJob(
|
||||||
|
config, config.testName || 'test', config.testStage || 'test',
|
||||||
|
config.testImage, splitScript(config.testScript), extra
|
||||||
|
))
|
||||||
|
// parallel(matrix)
|
||||||
|
if (config.testParallel && Number(config.testParallel) > 1) {
|
||||||
|
const jobName = config.testName || 'test'
|
||||||
|
doc[jobName].parallel = Number(config.testParallel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deploy job
|
||||||
|
if (config.enableDeploy) {
|
||||||
|
const extras = {
|
||||||
|
environment: { name: config.deployEnvironment || 'production' },
|
||||||
|
needs: (config.deployNeeds || '').split(',').map(s => s.trim()).filter(Boolean),
|
||||||
|
when: config.deployManual ? 'manual' : 'on_success',
|
||||||
|
allow_failure: !!config.deployAllowFailure,
|
||||||
|
}
|
||||||
|
const rulesList = (config.deployWhen || '')
|
||||||
|
.split(/[,\n]/)
|
||||||
|
.map(s => s.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
if (rulesList.length) {
|
||||||
|
extras.rules = [{
|
||||||
|
if: [`$CI_COMMIT_${(config.deployWhenMode || 'branches').toUpperCase()} =~ /^(${rulesList.join('|')})$/`],
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
Object.assign(doc, buildJob(
|
||||||
|
config, 'deploy', config.deployStage || 'deploy',
|
||||||
|
config.deployImage, splitScript(config.deployScript), extras
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
const header = `# .gitlab-ci.yml — 由 ConfTemplate 生成\n# 生成时间: ${new Date().toLocaleString('zh-CN')}\n# 配套 GitLab Runner: 自托管或共享 Runner (tag 匹配)\n# 用法: 提交此文件至仓库根目录即可触发流水线\n\n`
|
||||||
|
return header + toYaml(doc)
|
||||||
|
}
|
||||||
@@ -77,7 +77,9 @@ import { ansiblePlaybookSchema, generateAnsiblePlaybookYaml } from './ansible-pl
|
|||||||
|
|
||||||
// ============ CI/CD ============
|
// ============ CI/CD ============
|
||||||
import { gitlabSchema, generateGitlabRb } from './gitlab'
|
import { gitlabSchema, generateGitlabRb } from './gitlab'
|
||||||
|
import { gitlabCiSchema, generateGitlabCi } from './gitlab-ci'
|
||||||
import { jenkinsSchema, generateJenkinsYaml } from './jenkins'
|
import { jenkinsSchema, generateJenkinsYaml } from './jenkins'
|
||||||
|
import { jenkinsPipelineSchema, generateJenkinsfile } from './jenkins-pipeline'
|
||||||
|
|
||||||
// ============ 高可用 ============
|
// ============ 高可用 ============
|
||||||
import { keepalivedSchema, generateKeepalivedConf } from './keepalived'
|
import { keepalivedSchema, generateKeepalivedConf } from './keepalived'
|
||||||
@@ -171,7 +173,9 @@ export const schemas = {
|
|||||||
'ansible-playbook': ansiblePlaybookSchema,
|
'ansible-playbook': ansiblePlaybookSchema,
|
||||||
// CI/CD
|
// CI/CD
|
||||||
gitlab: gitlabSchema,
|
gitlab: gitlabSchema,
|
||||||
|
'gitlab-ci': gitlabCiSchema,
|
||||||
jenkins: jenkinsSchema,
|
jenkins: jenkinsSchema,
|
||||||
|
'jenkins-pipeline': jenkinsPipelineSchema,
|
||||||
// 高可用
|
// 高可用
|
||||||
keepalived: keepalivedSchema,
|
keepalived: keepalivedSchema,
|
||||||
haproxy: haproxySchema,
|
haproxy: haproxySchema,
|
||||||
@@ -246,7 +250,9 @@ export const generators = {
|
|||||||
'ansible-playbook': generateAnsiblePlaybookYaml,
|
'ansible-playbook': generateAnsiblePlaybookYaml,
|
||||||
// CI/CD
|
// CI/CD
|
||||||
gitlab: generateGitlabRb,
|
gitlab: generateGitlabRb,
|
||||||
|
'gitlab-ci': generateGitlabCi,
|
||||||
jenkins: generateJenkinsYaml,
|
jenkins: generateJenkinsYaml,
|
||||||
|
'jenkins-pipeline': generateJenkinsfile,
|
||||||
// 高可用
|
// 高可用
|
||||||
keepalived: generateKeepalivedConf,
|
keepalived: generateKeepalivedConf,
|
||||||
haproxy: generateHaproxyCfg,
|
haproxy: generateHaproxyCfg,
|
||||||
@@ -357,7 +363,7 @@ export const categories = [
|
|||||||
{
|
{
|
||||||
name: 'CI/CD',
|
name: 'CI/CD',
|
||||||
icon: 'Files',
|
icon: 'Files',
|
||||||
items: ['gitlab', 'jenkins'],
|
items: ['gitlab', 'gitlab-ci', 'jenkins', 'jenkins-pipeline'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '高可用',
|
name: '高可用',
|
||||||
|
|||||||
@@ -0,0 +1,399 @@
|
|||||||
|
// Jenkins 声明式流水线 (Jenkinsfile) 生成器 for ConfTemplate
|
||||||
|
// 生成符合 Jenkins Declarative Pipeline 2.x 规范的 Groovy DSL
|
||||||
|
|
||||||
|
export const jenkinsPipelineSchema = {
|
||||||
|
id: 'jenkins-pipeline',
|
||||||
|
name: 'Jenkinsfile',
|
||||||
|
icon: 'Cpu',
|
||||||
|
category: 'CI/CD',
|
||||||
|
description: '生成 Jenkins 声明式流水线 (Declarative Pipeline)',
|
||||||
|
format: 'groovy',
|
||||||
|
fileName: 'Jenkinsfile',
|
||||||
|
groups: [
|
||||||
|
// ─────────────── Agent ───────────────
|
||||||
|
{
|
||||||
|
title: '执行 Agent',
|
||||||
|
fields: [
|
||||||
|
{ key: 'agentMode', label: 'agent 模式', type: 'select', default: 'docker', options: [
|
||||||
|
{ label: 'Docker 容器 (推荐)', value: 'docker' },
|
||||||
|
{ label: '任意可用节点', value: 'any' },
|
||||||
|
{ label: '指定 label 节点', value: 'label' },
|
||||||
|
{ label: '无 agent (在顶层 controller 跑)', value: 'none' },
|
||||||
|
{ label: 'Kubernetes Pod', value: 'kubernetes' },
|
||||||
|
] },
|
||||||
|
{ key: 'agentLabel', label: 'agent label',
|
||||||
|
type: 'text', default: 'linux',
|
||||||
|
dependsOn: { key: 'agentMode', value: 'label' } },
|
||||||
|
{ key: 'agentImage', label: 'Docker 镜像',
|
||||||
|
type: 'text', default: 'maven:3.9.6-eclipse-temurin-17',
|
||||||
|
dependsOn: { key: 'agentMode', value: 'docker' } },
|
||||||
|
{ key: 'agentDockerArgs', label: 'args (docker run 参数)',
|
||||||
|
type: 'text', default: '-v $HOME:/root -v /var/run/docker.sock:/var/run/docker.sock',
|
||||||
|
dependsOn: { key: 'agentMode', value: 'docker' },
|
||||||
|
tip: '需要 DinD / 宿主 docker / 缓存挂载时填' },
|
||||||
|
{ key: 'agentK8sYAML', label: 'kubernetes YAML',
|
||||||
|
type: 'code', language: 'yaml', default:
|
||||||
|
'apiVersion: v1\nkind: Pod\nspec:\n containers:\n - name: jnlp\n image: jenkins/inbound-agent:3293.vd85995_5c7116-1\n',
|
||||||
|
dependsOn: { key: 'agentMode', value: 'kubernetes' } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// ─────────────── Tools ───────────────
|
||||||
|
{
|
||||||
|
title: 'Tools (自动安装)',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableTools', label: 'tools 段', type: 'switch', default: false,
|
||||||
|
tip: '需要 Jenkins 配置好了 Maven / JDK / Gradle 等安装时启用' },
|
||||||
|
{ key: 'toolMaven', label: 'maven 安装名', type: 'text',
|
||||||
|
default: 'maven-3.9', dependsOn: { key: 'enableTools', value: true } },
|
||||||
|
{ key: 'toolJdk', label: 'jdk 安装名', type: 'text',
|
||||||
|
default: 'jdk-17', dependsOn: { key: 'enableTools', value: true } },
|
||||||
|
{ key: 'toolGradle', label: 'gradle 安装名', type: 'text',
|
||||||
|
default: '', dependsOn: { key: 'enableTools', value: true } },
|
||||||
|
{ key: 'toolNodejs', label: 'nodejs 安装名', type: 'text',
|
||||||
|
default: '', dependsOn: { key: 'enableTools', value: true } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// ─────────────── Environment ───────────────
|
||||||
|
{
|
||||||
|
title: '环境变量',
|
||||||
|
fields: [
|
||||||
|
{ key: 'envVars', label: 'environment env', type: 'keyvalue', default: [
|
||||||
|
{ key: 'PROJECT_NAME', value: 'my-app' },
|
||||||
|
{ key: 'DOCKER_REGISTRY', value: 'registry.example.com' },
|
||||||
|
], tip: 'Jenkinsfile 顶层 environment 段,Pipeline Agent 与所有 stages 都能用' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// ─────────────── Options ───────────────
|
||||||
|
{
|
||||||
|
title: 'options (全局行为)',
|
||||||
|
fields: [
|
||||||
|
{ key: 'optDisableConcurrent', label: 'disableConcurrentBuilds',
|
||||||
|
type: 'switch', default: true, tip: '禁止并发构建同一项目' },
|
||||||
|
{ key: 'optTimeoutMinutes', label: 'timeout', type: 'number',
|
||||||
|
min: 0, max: 1440, default: 60, tip: '0 = 不设置超时' },
|
||||||
|
{ key: 'optTimestamps', label: 'timestamps', type: 'switch', default: true },
|
||||||
|
{ key: 'optAnsiColor', label: 'ansiColor (彩色日志)',
|
||||||
|
type: 'switch', default: true },
|
||||||
|
{ key: 'optBuildDiscarderDays', label: 'buildDiscarder 保留天数',
|
||||||
|
type: 'number', min: 0, max: 365, default: 30, tip: '0 = 不设置' },
|
||||||
|
{ key: 'optSkipDefaultCheckout', label: 'skipDefaultCheckout',
|
||||||
|
type: 'switch', default: false },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// ─────────────── Triggers ───────────────
|
||||||
|
{
|
||||||
|
title: 'Triggers (触发器)',
|
||||||
|
fields: [
|
||||||
|
{ key: 'triggerWebhook', label: 'GitHub webhook (trigger { githubPush() })',
|
||||||
|
type: 'switch', default: true },
|
||||||
|
{ key: 'triggerPollSCM', label: 'pollSCM 定时轮询', type: 'switch', default: false },
|
||||||
|
{ key: 'triggerPollCron', label: 'pollSCM cron', type: 'text',
|
||||||
|
default: 'H/15 * * * *', dependsOn: { key: 'triggerPollSCM', value: true } },
|
||||||
|
{ key: 'triggerCron', label: 'cron 定时构建', type: 'text',
|
||||||
|
default: '', tip: 'H H(0-7) * * * ;空则不启用 cron 触发' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// ─────────────── Stages ───────────────
|
||||||
|
{
|
||||||
|
title: 'Stage · Checkout',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableCheckout', label: '启用 Checkout stage', type: 'switch', default: true },
|
||||||
|
{ key: 'checkoutScript', label: 'steps 脚本', type: 'code',
|
||||||
|
language: 'groovy', default:
|
||||||
|
'checkout scmGit(\n branches: [[name: env.BRANCH_NAME ?: \'*/main\']],\n userRemoteConfigs: [[url: env.GIT_URL ?: \'https://example.com/group/repo.git\']]\n)',
|
||||||
|
dependsOn: { key: 'enableCheckout', value: true },
|
||||||
|
tip: 'scmGit() 是 Pipeline: SCM Step 3.x 推荐写法' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Stage · Lint',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableLint', label: '启用 Lint stage', type: 'switch', default: true },
|
||||||
|
{ key: 'lintSteps', label: 'steps', type: 'code', language: 'groovy',
|
||||||
|
default: '// 静态检查:eslint / ruff / shellcheck / hadolint\nsh \'echo "🔍 running lint"\'\n// sh \'make lint\'',
|
||||||
|
dependsOn: { key: 'enableLint', value: true } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Stage · Build',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableBuild', label: '启用 Build stage', type: 'switch', default: true },
|
||||||
|
{ key: 'buildSteps', label: 'steps', type: 'code', language: 'groovy',
|
||||||
|
default: '// 多语言示例:Maven / Gradle / npm / docker build\nsh \'\'\'\n echo "📦 building ${env.PROJECT_NAME}"\n mvn -B clean package -DskipTests\n\'\'\'',
|
||||||
|
dependsOn: { key: 'enableBuild', value: true } },
|
||||||
|
{ key: 'buildPostArchive', label: 'archiveArtifacts', type: 'switch',
|
||||||
|
default: true, dependsOn: { key: 'enableBuild', value: true } },
|
||||||
|
{ key: 'buildArtifactsGlob', label: 'artifacts glob 路径',
|
||||||
|
type: 'text', default: 'target/**/*.jar\ndist/**',
|
||||||
|
dependsOn: { key: 'enableBuild', value: true },
|
||||||
|
tip: '多个 glob 一行一个' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Stage · Test',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableTest', label: '启用 Test stage', type: 'switch', default: true },
|
||||||
|
{ key: 'testSteps', label: 'steps', type: 'code', language: 'groovy',
|
||||||
|
default: 'sh \'mvn test\'\n// 也可用 junit + jacoco 步骤\n// junit \'target/surefire-reports/*.xml\'',
|
||||||
|
dependsOn: { key: 'enableTest', value: true } },
|
||||||
|
{ key: 'testPostJunit', label: 'junit 报表目录', type: 'text',
|
||||||
|
default: 'target/surefire-reports/*.xml',
|
||||||
|
dependsOn: { key: 'enableTest', value: true },
|
||||||
|
tip: '空则不输出 junit 步骤' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Stage · Deploy',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableDeploy', label: '启用 Deploy stage', type: 'switch', default: true },
|
||||||
|
{ key: 'deployWhenBranch', label: '仅当 BRANCH_NAME 为', type: 'text',
|
||||||
|
default: 'main',
|
||||||
|
dependsOn: { key: 'enableDeploy', value: true },
|
||||||
|
tip: '空则不添加 when 条件;多分支用逗号' },
|
||||||
|
{ key: 'deploySteps', label: 'steps', type: 'code', language: 'groovy',
|
||||||
|
default: 'echo "🚀 deploying ${env.PROJECT_NAME}"\n// sh \'kubectl --context=prod apply -f k8s/\'\n// 或 sshPublisher / rancher / helm',
|
||||||
|
dependsOn: { key: 'enableDeploy', value: true } },
|
||||||
|
{ key: 'deployInput', label: 'input (人工确认才执行)',
|
||||||
|
type: 'switch', default: true,
|
||||||
|
dependsOn: { key: 'enableDeploy', value: true } },
|
||||||
|
{ key: 'deployInputMessage', label: 'input.message',
|
||||||
|
type: 'text', default: '确认发布到生产环境?',
|
||||||
|
dependsOn: { key: 'enableDeploy', value: true } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// ─────────────── Post ───────────────
|
||||||
|
{
|
||||||
|
title: 'post 段 (行为钩子)',
|
||||||
|
fields: [
|
||||||
|
{ key: 'postAlways', label: 'always', type: 'code', language: 'groovy',
|
||||||
|
default: 'echo \'always runs\'' },
|
||||||
|
{ key: 'postSuccess', label: 'success', type: 'code', language: 'groovy',
|
||||||
|
default: 'echo \'✅ deployed successfully\'' },
|
||||||
|
{ key: 'postFailure', label: 'failure', type: 'code', language: 'groovy',
|
||||||
|
default: 'echo \'❌ failed\'\n// mail to: \'devops@example.com\', subject: "Build Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}"' },
|
||||||
|
{ key: 'postUnstable', label: 'unstable', type: 'code', language: 'groovy',
|
||||||
|
default: 'echo \'⚠️ unstable\'' },
|
||||||
|
{ key: 'postCleanup', label: 'cleanup', type: 'code', language: 'groovy',
|
||||||
|
default: '// 删除工作空间文件\n// cleanWs()' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// Groovy / Jenkinsfile 渲染器(专门为流水线 DSL 而非通用 Groovy)
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
|
function indent(s, n) {
|
||||||
|
if (!s) return ''
|
||||||
|
const pad = ' '.repeat(n)
|
||||||
|
return s.split('\n').map(l => l.trim() === '' ? '' : pad + l).join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
function escSteps(raw) {
|
||||||
|
// 用户多行 steps; 但当 steps 里需要 Groovy 多行字符串时为模板留 %% 我为括号的占位
|
||||||
|
return raw.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateJenkinsfile(config) {
|
||||||
|
const lines = []
|
||||||
|
lines.push('// Jenkinsfile — 由 ConfTemplate 生成')
|
||||||
|
lines.push('// 生成时间: ' + new Date().toLocaleString('zh-CN'))
|
||||||
|
lines.push('// 用法: 放到仓库根目录, Jenkins Job 选 "Pipeline from SCM"')
|
||||||
|
lines.push('')
|
||||||
|
lines.push('pipeline {')
|
||||||
|
// ============ agent ============
|
||||||
|
lines.push(' agent {')
|
||||||
|
if (config.agentMode === 'none') {
|
||||||
|
lines.push(' none')
|
||||||
|
} else if (config.agentMode === 'any') {
|
||||||
|
lines.push(' any')
|
||||||
|
} else if (config.agentMode === 'label') {
|
||||||
|
lines.push(" label '" + (config.agentLabel || 'linux') + "'")
|
||||||
|
} else if (config.agentMode === 'docker') {
|
||||||
|
lines.push(' docker {')
|
||||||
|
lines.push(" image '" + (config.agentImage || 'maven:3.9-eclipse-temurin-17') + "'")
|
||||||
|
if (config.agentDockerArgs && config.agentDockerArgs.trim()) {
|
||||||
|
// 用户的 args 可能含 -v $HOME:/root (含 $ 符号,单引号内 $HOME 在 Groovy 是字面量;要双引号则会被 Groovy 插值,把 $HOME 写为 ${HOME})
|
||||||
|
const a = config.agentDockerArgs.trim()
|
||||||
|
// 用三引号避免任何内部冲突
|
||||||
|
lines.push(" args '''" + a + "'''")
|
||||||
|
}
|
||||||
|
lines.push(' reuseNode true')
|
||||||
|
lines.push(' }')
|
||||||
|
} else if (config.agentMode === 'kubernetes') {
|
||||||
|
// 多行 YAML,用三引号嵌入
|
||||||
|
const yamlBlock = (config.agentK8sYAML || '').trim()
|
||||||
|
lines.push(' kubernetes {')
|
||||||
|
lines.push(" yaml '''" + yamlBlock + "'''")
|
||||||
|
lines.push(' }')
|
||||||
|
}
|
||||||
|
lines.push(' }')
|
||||||
|
|
||||||
|
// ============ tools ============
|
||||||
|
if (config.enableTools) {
|
||||||
|
lines.push(' tools {')
|
||||||
|
if (config.toolMaven) lines.push(" maven '" + config.toolMaven + "'")
|
||||||
|
if (config.toolJdk) lines.push(" jdk '" + config.toolJdk + "'")
|
||||||
|
if (config.toolGradle) lines.push(" gradle '" + config.toolGradle + "'")
|
||||||
|
if (config.toolNodejs) lines.push(" nodejs '" + config.toolNodejs + "'")
|
||||||
|
lines.push(' }')
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ environment ============
|
||||||
|
const envArr = (config.envVars || []).filter(kv => kv && kv.key)
|
||||||
|
if (envArr.length) {
|
||||||
|
lines.push(' environment {')
|
||||||
|
for (const kv of envArr) {
|
||||||
|
lines.push(" " + kv.key + " = '" + String(kv.value ?? '').replace(/'/g, "\\'") + "'")
|
||||||
|
}
|
||||||
|
lines.push(' }')
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ options ============
|
||||||
|
const opts = []
|
||||||
|
if (config.optDisableConcurrent) opts.push(" disableConcurrentBuilds()")
|
||||||
|
if (config.optTimeoutMinutes && Number(config.optTimeoutMinutes) > 0) {
|
||||||
|
opts.push(" timeout(time: " + Number(config.optTimeoutMinutes) + ", unit: 'MINUTES')")
|
||||||
|
}
|
||||||
|
if (config.optTimestamps) opts.push(" timestamps()")
|
||||||
|
if (config.optAnsiColor) opts.push(" ansiColor('xterm')")
|
||||||
|
if (config.optBuildDiscarderDays && Number(config.optBuildDiscarderDays) > 0) {
|
||||||
|
opts.push(" buildDiscarder(logRotator(numToKeepStr: '" + config.optBuildDiscarderDays + "'))")
|
||||||
|
}
|
||||||
|
if (config.optSkipDefaultCheckout) opts.push(" skipDefaultCheckout()")
|
||||||
|
if (opts.length) {
|
||||||
|
lines.push(' options {')
|
||||||
|
for (const o of opts) lines.push(' ' + o.trimStart())
|
||||||
|
lines.push(' }')
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ triggers ============
|
||||||
|
const triggers = []
|
||||||
|
if (config.triggerWebhook) triggers.push("githubPush()")
|
||||||
|
if (config.triggerCron) triggers.push("cron('" + config.triggerCron + "')")
|
||||||
|
if (config.triggerPollSCM && config.triggerPollCron)
|
||||||
|
triggers.push("pollSCM('" + config.triggerPollCron + "')")
|
||||||
|
if (triggers.length) {
|
||||||
|
lines.push(' triggers {')
|
||||||
|
for (const t of triggers) lines.push(' ' + t.trimStart())
|
||||||
|
lines.push(' }')
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ stages ============
|
||||||
|
lines.push(' stages {')
|
||||||
|
|
||||||
|
// Helper: 生成 stage(name) { steps { ... } },optionally 加 when、input、post
|
||||||
|
function addStage(name, opts) {
|
||||||
|
lines.push(" stage('" + name + "') {")
|
||||||
|
if (opts.when) {
|
||||||
|
lines.push(' when {')
|
||||||
|
for (const w of opts.when) lines.push(w)
|
||||||
|
lines.push(' }')
|
||||||
|
}
|
||||||
|
// steps 块
|
||||||
|
lines.push(' steps {')
|
||||||
|
if (opts.input) {
|
||||||
|
lines.push(" input {")
|
||||||
|
lines.push(" message '" + (opts.inputMessage || '确认?') + "'")
|
||||||
|
lines.push(" ok '确定'")
|
||||||
|
lines.push(" submitter 'admin'")
|
||||||
|
lines.push(" }")
|
||||||
|
}
|
||||||
|
if (opts.steps) lines.push(indent(opts.steps, 8))
|
||||||
|
lines.push(' }')
|
||||||
|
// stage 级别 post(独立于 steps),注意缩进跟 stage steps 同级
|
||||||
|
if (opts.post) {
|
||||||
|
lines.push(' post {')
|
||||||
|
for (const p of opts.post) {
|
||||||
|
lines.push(" " + p.cond + " {")
|
||||||
|
if (p.script) lines.push(indent(p.script, 10))
|
||||||
|
lines.push(' }')
|
||||||
|
}
|
||||||
|
lines.push(' }')
|
||||||
|
}
|
||||||
|
lines.push(' }')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checkout
|
||||||
|
if (config.enableCheckout) {
|
||||||
|
addStage('Checkout', {
|
||||||
|
steps: config.checkoutScript || "checkout scm",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Lint
|
||||||
|
if (config.enableLint) {
|
||||||
|
addStage('Lint', {
|
||||||
|
steps: config.lintSteps || 'sh \'echo "lint"\'',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Build
|
||||||
|
if (config.enableBuild) {
|
||||||
|
const post = []
|
||||||
|
if (config.buildPostArchive) {
|
||||||
|
const globs = (config.buildArtifactsGlob || '').split('\n').map(s => s.trim()).filter(Boolean)
|
||||||
|
if (globs.length) {
|
||||||
|
const arr = globs.map(g => "'" + g + "'").join(', ')
|
||||||
|
post.push({ cond: 'success', script: "archiveArtifacts artifacts: [" + arr + "], allowEmptyArchive: false" })
|
||||||
|
} else {
|
||||||
|
post.push({ cond: 'success', script: "// archiveArtifacts 配置为空,跳过" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addStage('Build', {
|
||||||
|
steps: config.buildSteps || 'sh \'echo "build"\'',
|
||||||
|
post,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Test
|
||||||
|
if (config.enableTest) {
|
||||||
|
const post = []
|
||||||
|
if (config.testPostJunit && config.testPostJunit.trim()) {
|
||||||
|
post.push({ cond: 'always', script: "junit '" + config.testPostJunit + "'" })
|
||||||
|
}
|
||||||
|
addStage('Test', {
|
||||||
|
steps: config.testSteps || 'sh \'echo "test"\'',
|
||||||
|
post,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Deploy
|
||||||
|
if (config.enableDeploy) {
|
||||||
|
const when = []
|
||||||
|
if (config.deployWhenBranch && config.deployWhenBranch.trim()) {
|
||||||
|
const branches = config.deployWhenBranch.split(/[,\n]/).map(s => s.trim()).filter(Boolean)
|
||||||
|
if (branches.length) {
|
||||||
|
const re = '^(' + branches.join('|') + ')$'
|
||||||
|
when.push(" branch '" + re + "'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addStage('Deploy', {
|
||||||
|
steps: config.deploySteps || 'sh \'echo "deploy"\'',
|
||||||
|
when: when.length ? when : null,
|
||||||
|
input: config.deployInput,
|
||||||
|
inputMessage: config.deployInputMessage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(' }') // stages 结束
|
||||||
|
|
||||||
|
// ============ post (顶层) ============
|
||||||
|
const postSections = [
|
||||||
|
{ cond: 'always', body: config.postAlways },
|
||||||
|
{ cond: 'success', body: config.postSuccess },
|
||||||
|
{ cond: 'failure', body: config.postFailure },
|
||||||
|
{ cond: 'unstable', body: config.postUnstable },
|
||||||
|
{ cond: 'cleanup', body: config.postCleanup },
|
||||||
|
].filter(p => p.body && String(p.body).trim().length)
|
||||||
|
if (postSections.length) {
|
||||||
|
lines.push(' post {')
|
||||||
|
for (const p of postSections) {
|
||||||
|
lines.push(' ' + p.cond + ' {')
|
||||||
|
lines.push(indent(p.body, 6))
|
||||||
|
lines.push(' }')
|
||||||
|
}
|
||||||
|
lines.push(' }')
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('}') // pipeline 结束
|
||||||
|
lines.push('')
|
||||||
|
return lines.join('\n')
|
||||||
|
}
|
||||||
@@ -76,36 +76,65 @@ export const k8sClusterRoleSchema = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
function toYaml(obj, indent = 0) {
|
|
||||||
const lines = []
|
|
||||||
const prefix = ' '.repeat(indent)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function yamlScalar(v) {
|
||||||
|
if (v === null || v === undefined) return ''
|
||||||
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
||||||
|
const s = String(v)
|
||||||
|
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
|
||||||
|
return JSON.stringify(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyMapping(o) {
|
||||||
|
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
|
||||||
|
return Object.values(o).every(v => v === null || v === undefined || v === '' || (typeof v === 'object' && isEmptyMapping(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toYaml(obj, baseIndent = '') {
|
||||||
|
const lines = []
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(`${prefix}${key}:`)
|
const items = value.filter(v => v !== null && v !== undefined && v !== '')
|
||||||
for (const item of value) {
|
if (!items.length) continue
|
||||||
if (typeof item === 'object' && item !== null) {
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
for (const item of items) {
|
||||||
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||||
|
if (isEmptyMapping(item)) continue
|
||||||
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
||||||
if (entries.length === 0) continue
|
if (!entries.length) continue
|
||||||
const first = entries[0]
|
const childIndent = baseIndent + ' '
|
||||||
lines.push(`${prefix} - ${first[0]}: ${first[1]}`)
|
const subAllIndent = childIndent + ' '
|
||||||
for (let i = 1; i < entries.length; i++) {
|
const subObj = {}
|
||||||
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`)
|
for (const [k, v] of entries) subObj[k] = v
|
||||||
|
const subRendered = toYaml(subObj, subAllIndent)
|
||||||
|
const subLines = subRendered.split('\n')
|
||||||
|
if (subLines[0].startsWith(subAllIndent)) {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
|
||||||
|
} else {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
|
||||||
}
|
}
|
||||||
|
lines.push(...subLines)
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix} - ${item}`)
|
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(`${prefix}${key}:`)
|
if (isEmptyMapping(value)) {
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(`${baseIndent}${key}: {}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
lines.push(toYaml(value, childIndent))
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix}${key}: ${value}`)
|
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,36 +75,65 @@ export const k8sClusterRoleBindingSchema = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
function toYaml(obj, indent = 0) {
|
|
||||||
const lines = []
|
|
||||||
const prefix = ' '.repeat(indent)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function yamlScalar(v) {
|
||||||
|
if (v === null || v === undefined) return ''
|
||||||
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
||||||
|
const s = String(v)
|
||||||
|
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
|
||||||
|
return JSON.stringify(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyMapping(o) {
|
||||||
|
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
|
||||||
|
return Object.values(o).every(v => v === null || v === undefined || v === '' || (typeof v === 'object' && isEmptyMapping(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toYaml(obj, baseIndent = '') {
|
||||||
|
const lines = []
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(`${prefix}${key}:`)
|
const items = value.filter(v => v !== null && v !== undefined && v !== '')
|
||||||
for (const item of value) {
|
if (!items.length) continue
|
||||||
if (typeof item === 'object' && item !== null) {
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
for (const item of items) {
|
||||||
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||||
|
if (isEmptyMapping(item)) continue
|
||||||
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
||||||
if (entries.length === 0) continue
|
if (!entries.length) continue
|
||||||
const first = entries[0]
|
const childIndent = baseIndent + ' '
|
||||||
lines.push(`${prefix} - ${first[0]}: ${first[1]}`)
|
const subAllIndent = childIndent + ' '
|
||||||
for (let i = 1; i < entries.length; i++) {
|
const subObj = {}
|
||||||
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`)
|
for (const [k, v] of entries) subObj[k] = v
|
||||||
|
const subRendered = toYaml(subObj, subAllIndent)
|
||||||
|
const subLines = subRendered.split('\n')
|
||||||
|
if (subLines[0].startsWith(subAllIndent)) {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
|
||||||
|
} else {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
|
||||||
}
|
}
|
||||||
|
lines.push(...subLines)
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix} - ${item}`)
|
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(`${prefix}${key}:`)
|
if (isEmptyMapping(value)) {
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(`${baseIndent}${key}: {}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
lines.push(toYaml(value, childIndent))
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix}${key}: ${value}`)
|
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,36 +90,65 @@ export const k8sConfigMapSchema = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
function toYaml(obj, indent = 0) {
|
|
||||||
const lines = []
|
|
||||||
const prefix = ' '.repeat(indent)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function yamlScalar(v) {
|
||||||
|
if (v === null || v === undefined) return ''
|
||||||
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
||||||
|
const s = String(v)
|
||||||
|
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
|
||||||
|
return JSON.stringify(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyMapping(o) {
|
||||||
|
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
|
||||||
|
return Object.values(o).every(v => v === null || v === undefined || v === '' || (typeof v === 'object' && isEmptyMapping(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toYaml(obj, baseIndent = '') {
|
||||||
|
const lines = []
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(`${prefix}${key}:`)
|
const items = value.filter(v => v !== null && v !== undefined && v !== '')
|
||||||
for (const item of value) {
|
if (!items.length) continue
|
||||||
if (typeof item === 'object' && item !== null) {
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
for (const item of items) {
|
||||||
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||||
|
if (isEmptyMapping(item)) continue
|
||||||
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
||||||
if (entries.length === 0) continue
|
if (!entries.length) continue
|
||||||
const first = entries[0]
|
const childIndent = baseIndent + ' '
|
||||||
lines.push(`${prefix} - ${first[0]}: ${first[1]}`)
|
const subAllIndent = childIndent + ' '
|
||||||
for (let i = 1; i < entries.length; i++) {
|
const subObj = {}
|
||||||
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`)
|
for (const [k, v] of entries) subObj[k] = v
|
||||||
|
const subRendered = toYaml(subObj, subAllIndent)
|
||||||
|
const subLines = subRendered.split('\n')
|
||||||
|
if (subLines[0].startsWith(subAllIndent)) {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
|
||||||
|
} else {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
|
||||||
}
|
}
|
||||||
|
lines.push(...subLines)
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix} - ${item}`)
|
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(`${prefix}${key}:`)
|
if (isEmptyMapping(value)) {
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(`${baseIndent}${key}: {}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
lines.push(toYaml(value, childIndent))
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix}${key}: ${value}`)
|
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+50
-18
@@ -237,31 +237,63 @@ export const k8sCronJobSchema = {
|
|||||||
|
|
||||||
// ======================== YAML 生成器 ========================
|
// ======================== YAML 生成器 ========================
|
||||||
|
|
||||||
function toYaml(obj, indent) {
|
|
||||||
indent = indent || 0
|
|
||||||
var lines = []
|
|
||||||
var prefix = ''
|
function yamlScalar(v) {
|
||||||
for (var p = 0; p < indent; p++) prefix += ' '
|
if (v === null || v === undefined) return ''
|
||||||
var keys = Object.keys(obj)
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
||||||
for (var ki = 0; ki < keys.length; ki++) {
|
const s = String(v)
|
||||||
var key = keys[ki]
|
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
|
||||||
var value = obj[key]
|
return JSON.stringify(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyMapping(o) {
|
||||||
|
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
|
||||||
|
return Object.values(o).every(v => v === null || v === undefined || v === '' || (typeof v === 'object' && isEmptyMapping(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toYaml(obj, baseIndent = '') {
|
||||||
|
const lines = []
|
||||||
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(prefix + key + ':')
|
const items = value.filter(v => v !== null && v !== undefined && v !== '')
|
||||||
for (var ai = 0; ai < value.length; ai++) {
|
if (!items.length) continue
|
||||||
var item = value[ai]
|
lines.push(`${baseIndent}${key}:`)
|
||||||
if (typeof item === 'object' && item !== null) {
|
for (const item of items) {
|
||||||
lines.push(renderArrayObject(item, indent + 1))
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||||
|
if (isEmptyMapping(item)) continue
|
||||||
|
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
||||||
|
if (!entries.length) continue
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
const subAllIndent = childIndent + ' '
|
||||||
|
const subObj = {}
|
||||||
|
for (const [k, v] of entries) subObj[k] = v
|
||||||
|
const subRendered = toYaml(subObj, subAllIndent)
|
||||||
|
const subLines = subRendered.split('\n')
|
||||||
|
if (subLines[0].startsWith(subAllIndent)) {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
|
||||||
|
} else {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
|
||||||
|
}
|
||||||
|
lines.push(...subLines)
|
||||||
} else {
|
} else {
|
||||||
lines.push(prefix + ' - ' + item)
|
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(prefix + key + ':')
|
if (isEmptyMapping(value)) {
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(`${baseIndent}${key}: {}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
lines.push(toYaml(value, childIndent))
|
||||||
} else {
|
} else {
|
||||||
lines.push(prefix + key + ': ' + value)
|
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
|
|||||||
@@ -357,40 +357,63 @@ export function generateK8sDaemonSetYaml(config) {
|
|||||||
return header + '\n' + toYaml(daemonset)
|
return header + '\n' + toYaml(daemonset)
|
||||||
}
|
}
|
||||||
|
|
||||||
function toYaml(obj, indent = 0) {
|
|
||||||
|
|
||||||
|
|
||||||
|
function yamlScalar(v) {
|
||||||
|
if (v === null || v === undefined) return ''
|
||||||
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
||||||
|
const s = String(v)
|
||||||
|
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
|
||||||
|
return JSON.stringify(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyMapping(o) {
|
||||||
|
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
|
||||||
|
return Object.values(o).every(v => v === null || v === undefined || v === '' || (typeof v === 'object' && isEmptyMapping(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toYaml(obj, baseIndent = '') {
|
||||||
const lines = []
|
const lines = []
|
||||||
const prefix = ' '.repeat(indent)
|
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(`${prefix}${key}:`)
|
const items = value.filter(v => v !== null && v !== undefined && v !== '')
|
||||||
for (const item of value) {
|
if (!items.length) continue
|
||||||
if (typeof item === 'object' && item !== null) {
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
for (const item of items) {
|
||||||
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||||
|
if (isEmptyMapping(item)) continue
|
||||||
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
||||||
if (entries.length === 0) continue
|
if (!entries.length) continue
|
||||||
const first = entries[0]
|
const childIndent = baseIndent + ' '
|
||||||
lines.push(`${prefix} - ${first[0]}: ${typeof first[1] === 'object' ? '' : first[1]}`)
|
const subAllIndent = childIndent + ' '
|
||||||
if (typeof first[1] === 'object') {
|
const subObj = {}
|
||||||
lines.push(toYaml(first[1], indent + 3))
|
for (const [k, v] of entries) subObj[k] = v
|
||||||
}
|
const subRendered = toYaml(subObj, subAllIndent)
|
||||||
for (let i = 1; i < entries.length; i++) {
|
const subLines = subRendered.split('\n')
|
||||||
const [k, v] = entries[i]
|
if (subLines[0].startsWith(subAllIndent)) {
|
||||||
if (typeof v === 'object') {
|
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
|
||||||
lines.push(`${prefix} ${k}:`)
|
} else {
|
||||||
lines.push(toYaml(v, indent + 4))
|
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
|
||||||
} else {
|
|
||||||
lines.push(`${prefix} ${k}: ${v}`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
lines.push(...subLines)
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix} - ${item}`)
|
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(`${prefix}${key}:`)
|
if (isEmptyMapping(value)) {
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(`${baseIndent}${key}: {}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
lines.push(toYaml(value, childIndent))
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix}${key}: ${value}`)
|
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
|
|||||||
@@ -433,40 +433,82 @@ export function generateK8sDeploymentYaml(config) {
|
|||||||
return header + '\n' + toYaml(deployment)
|
return header + '\n' + toYaml(deployment)
|
||||||
}
|
}
|
||||||
|
|
||||||
function toYaml(obj, indent = 0) {
|
// 序列化值为带引号的 YAML 标量字符串
|
||||||
|
|
||||||
|
// 过滤掉空值/null
|
||||||
|
function clean(obj) {
|
||||||
|
if (Array.isArray(obj)) return obj.map(clean).filter(v => v !== undefined)
|
||||||
|
if (obj && typeof obj === 'object') {
|
||||||
|
const out = {}
|
||||||
|
for (const [k, v] of Object.entries(obj)) {
|
||||||
|
if (v === null || v === undefined || v === '') continue
|
||||||
|
const cv = clean(v)
|
||||||
|
if (cv === undefined) continue
|
||||||
|
out[k] = cv
|
||||||
|
}
|
||||||
|
return Object.keys(out).length ? out : undefined
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用递归 YAML 序列化器:父级 + 本行缩进 + 子级缩进
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function yamlScalar(v) {
|
||||||
|
if (v === null || v === undefined) return ''
|
||||||
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
||||||
|
const s = String(v)
|
||||||
|
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
|
||||||
|
return JSON.stringify(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyMapping(o) {
|
||||||
|
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
|
||||||
|
return Object.values(o).every(v => v === null || v === undefined || v === '' || (typeof v === 'object' && isEmptyMapping(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toYaml(obj, baseIndent = '') {
|
||||||
const lines = []
|
const lines = []
|
||||||
const prefix = ' '.repeat(indent)
|
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(`${prefix}${key}:`)
|
const items = value.filter(v => v !== null && v !== undefined && v !== '')
|
||||||
for (const item of value) {
|
if (!items.length) continue
|
||||||
if (typeof item === 'object' && item !== null) {
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
for (const item of items) {
|
||||||
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||||
|
if (isEmptyMapping(item)) continue
|
||||||
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
||||||
if (entries.length === 0) continue
|
if (!entries.length) continue
|
||||||
const first = entries[0]
|
const childIndent = baseIndent + ' '
|
||||||
lines.push(`${prefix} - ${first[0]}: ${typeof first[1] === 'object' ? '' : first[1]}`)
|
const subAllIndent = childIndent + ' '
|
||||||
if (typeof first[1] === 'object') {
|
const subObj = {}
|
||||||
lines.push(toYaml(first[1], indent + 3))
|
for (const [k, v] of entries) subObj[k] = v
|
||||||
}
|
const subRendered = toYaml(subObj, subAllIndent)
|
||||||
for (let i = 1; i < entries.length; i++) {
|
const subLines = subRendered.split('\n')
|
||||||
const [k, v] = entries[i]
|
if (subLines[0].startsWith(subAllIndent)) {
|
||||||
if (typeof v === 'object') {
|
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
|
||||||
lines.push(`${prefix} ${k}:`)
|
} else {
|
||||||
lines.push(toYaml(v, indent + 4))
|
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
|
||||||
} else {
|
|
||||||
lines.push(`${prefix} ${k}: ${v}`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
lines.push(...subLines)
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix} - ${item}`)
|
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(`${prefix}${key}:`)
|
if (isEmptyMapping(value)) {
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(`${baseIndent}${key}: {}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
lines.push(toYaml(value, childIndent))
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix}${key}: ${value}`)
|
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
|
|||||||
+46
-17
@@ -136,36 +136,65 @@ export const k8sIngressSchema = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
function toYaml(obj, indent = 0) {
|
|
||||||
const lines = []
|
|
||||||
const prefix = ' '.repeat(indent)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function yamlScalar(v) {
|
||||||
|
if (v === null || v === undefined) return ''
|
||||||
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
||||||
|
const s = String(v)
|
||||||
|
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
|
||||||
|
return JSON.stringify(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyMapping(o) {
|
||||||
|
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
|
||||||
|
return Object.values(o).every(v => v === null || v === undefined || v === '' || (typeof v === 'object' && isEmptyMapping(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toYaml(obj, baseIndent = '') {
|
||||||
|
const lines = []
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(`${prefix}${key}:`)
|
const items = value.filter(v => v !== null && v !== undefined && v !== '')
|
||||||
for (const item of value) {
|
if (!items.length) continue
|
||||||
if (typeof item === 'object' && item !== null) {
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
for (const item of items) {
|
||||||
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||||
|
if (isEmptyMapping(item)) continue
|
||||||
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
||||||
if (entries.length === 0) continue
|
if (!entries.length) continue
|
||||||
const first = entries[0]
|
const childIndent = baseIndent + ' '
|
||||||
lines.push(`${prefix} - ${first[0]}: ${first[1]}`)
|
const subAllIndent = childIndent + ' '
|
||||||
for (let i = 1; i < entries.length; i++) {
|
const subObj = {}
|
||||||
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`)
|
for (const [k, v] of entries) subObj[k] = v
|
||||||
|
const subRendered = toYaml(subObj, subAllIndent)
|
||||||
|
const subLines = subRendered.split('\n')
|
||||||
|
if (subLines[0].startsWith(subAllIndent)) {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
|
||||||
|
} else {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
|
||||||
}
|
}
|
||||||
|
lines.push(...subLines)
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix} - ${item}`)
|
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(`${prefix}${key}:`)
|
if (isEmptyMapping(value)) {
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(`${baseIndent}${key}: {}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
lines.push(toYaml(value, childIndent))
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix}${key}: ${value}`)
|
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+50
-18
@@ -208,31 +208,63 @@ export const k8sJobSchema = {
|
|||||||
|
|
||||||
// ======================== YAML 生成器 ========================
|
// ======================== YAML 生成器 ========================
|
||||||
|
|
||||||
function toYaml(obj, indent) {
|
|
||||||
indent = indent || 0
|
|
||||||
var lines = []
|
|
||||||
var prefix = ''
|
function yamlScalar(v) {
|
||||||
for (var p = 0; p < indent; p++) prefix += ' '
|
if (v === null || v === undefined) return ''
|
||||||
var keys = Object.keys(obj)
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
||||||
for (var ki = 0; ki < keys.length; ki++) {
|
const s = String(v)
|
||||||
var key = keys[ki]
|
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
|
||||||
var value = obj[key]
|
return JSON.stringify(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyMapping(o) {
|
||||||
|
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
|
||||||
|
return Object.values(o).every(v => v === null || v === undefined || v === '' || (typeof v === 'object' && isEmptyMapping(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toYaml(obj, baseIndent = '') {
|
||||||
|
const lines = []
|
||||||
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(prefix + key + ':')
|
const items = value.filter(v => v !== null && v !== undefined && v !== '')
|
||||||
for (var ai = 0; ai < value.length; ai++) {
|
if (!items.length) continue
|
||||||
var item = value[ai]
|
lines.push(`${baseIndent}${key}:`)
|
||||||
if (typeof item === 'object' && item !== null) {
|
for (const item of items) {
|
||||||
lines.push(renderArrayObject(item, indent + 1))
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||||
|
if (isEmptyMapping(item)) continue
|
||||||
|
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
||||||
|
if (!entries.length) continue
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
const subAllIndent = childIndent + ' '
|
||||||
|
const subObj = {}
|
||||||
|
for (const [k, v] of entries) subObj[k] = v
|
||||||
|
const subRendered = toYaml(subObj, subAllIndent)
|
||||||
|
const subLines = subRendered.split('\n')
|
||||||
|
if (subLines[0].startsWith(subAllIndent)) {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
|
||||||
|
} else {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
|
||||||
|
}
|
||||||
|
lines.push(...subLines)
|
||||||
} else {
|
} else {
|
||||||
lines.push(prefix + ' - ' + item)
|
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(prefix + key + ':')
|
if (isEmptyMapping(value)) {
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(`${baseIndent}${key}: {}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
lines.push(toYaml(value, childIndent))
|
||||||
} else {
|
} else {
|
||||||
lines.push(prefix + key + ': ' + value)
|
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
|
|||||||
@@ -48,36 +48,65 @@ export const k8sNamespaceSchema = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
function toYaml(obj, indent = 0) {
|
|
||||||
const lines = []
|
|
||||||
const prefix = ' '.repeat(indent)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function yamlScalar(v) {
|
||||||
|
if (v === null || v === undefined) return ''
|
||||||
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
||||||
|
const s = String(v)
|
||||||
|
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
|
||||||
|
return JSON.stringify(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyMapping(o) {
|
||||||
|
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
|
||||||
|
return Object.values(o).every(v => v === null || v === undefined || v === '' || (typeof v === 'object' && isEmptyMapping(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toYaml(obj, baseIndent = '') {
|
||||||
|
const lines = []
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(`${prefix}${key}:`)
|
const items = value.filter(v => v !== null && v !== undefined && v !== '')
|
||||||
for (const item of value) {
|
if (!items.length) continue
|
||||||
if (typeof item === 'object' && item !== null) {
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
for (const item of items) {
|
||||||
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||||
|
if (isEmptyMapping(item)) continue
|
||||||
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
||||||
if (entries.length === 0) continue
|
if (!entries.length) continue
|
||||||
const first = entries[0]
|
const childIndent = baseIndent + ' '
|
||||||
lines.push(`${prefix} - ${first[0]}: ${first[1]}`)
|
const subAllIndent = childIndent + ' '
|
||||||
for (let i = 1; i < entries.length; i++) {
|
const subObj = {}
|
||||||
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`)
|
for (const [k, v] of entries) subObj[k] = v
|
||||||
|
const subRendered = toYaml(subObj, subAllIndent)
|
||||||
|
const subLines = subRendered.split('\n')
|
||||||
|
if (subLines[0].startsWith(subAllIndent)) {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
|
||||||
|
} else {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
|
||||||
}
|
}
|
||||||
|
lines.push(...subLines)
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix} - ${item}`)
|
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(`${prefix}${key}:`)
|
if (isEmptyMapping(value)) {
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(`${baseIndent}${key}: {}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
lines.push(toYaml(value, childIndent))
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix}${key}: ${value}`)
|
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,36 +114,65 @@ export const k8sNetworkPolicySchema = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
function toYaml(obj, indent = 0) {
|
|
||||||
const lines = []
|
|
||||||
const prefix = ' '.repeat(indent)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function yamlScalar(v) {
|
||||||
|
if (v === null || v === undefined) return ''
|
||||||
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
||||||
|
const s = String(v)
|
||||||
|
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
|
||||||
|
return JSON.stringify(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyMapping(o) {
|
||||||
|
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
|
||||||
|
return Object.values(o).every(v => v === null || v === undefined || v === '' || (typeof v === 'object' && isEmptyMapping(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toYaml(obj, baseIndent = '') {
|
||||||
|
const lines = []
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(`${prefix}${key}:`)
|
const items = value.filter(v => v !== null && v !== undefined && v !== '')
|
||||||
for (const item of value) {
|
if (!items.length) continue
|
||||||
if (typeof item === 'object' && item !== null) {
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
for (const item of items) {
|
||||||
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||||
|
if (isEmptyMapping(item)) continue
|
||||||
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
||||||
if (entries.length === 0) continue
|
if (!entries.length) continue
|
||||||
const first = entries[0]
|
const childIndent = baseIndent + ' '
|
||||||
lines.push(`${prefix} - ${first[0]}: ${first[1]}`)
|
const subAllIndent = childIndent + ' '
|
||||||
for (let i = 1; i < entries.length; i++) {
|
const subObj = {}
|
||||||
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`)
|
for (const [k, v] of entries) subObj[k] = v
|
||||||
|
const subRendered = toYaml(subObj, subAllIndent)
|
||||||
|
const subLines = subRendered.split('\n')
|
||||||
|
if (subLines[0].startsWith(subAllIndent)) {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
|
||||||
|
} else {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
|
||||||
}
|
}
|
||||||
|
lines.push(...subLines)
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix} - ${item}`)
|
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(`${prefix}${key}:`)
|
if (isEmptyMapping(value)) {
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(`${baseIndent}${key}: {}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
lines.push(toYaml(value, childIndent))
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix}${key}: ${value}`)
|
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+46
-23
@@ -445,40 +445,63 @@ export function generateK8sPodYaml(config) {
|
|||||||
return header + '\n' + toYaml(pod)
|
return header + '\n' + toYaml(pod)
|
||||||
}
|
}
|
||||||
|
|
||||||
function toYaml(obj, indent = 0) {
|
|
||||||
|
|
||||||
|
|
||||||
|
function yamlScalar(v) {
|
||||||
|
if (v === null || v === undefined) return ''
|
||||||
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
||||||
|
const s = String(v)
|
||||||
|
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
|
||||||
|
return JSON.stringify(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyMapping(o) {
|
||||||
|
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
|
||||||
|
return Object.values(o).every(v => v === null || v === undefined || v === '' || (typeof v === 'object' && isEmptyMapping(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toYaml(obj, baseIndent = '') {
|
||||||
const lines = []
|
const lines = []
|
||||||
const prefix = ' '.repeat(indent)
|
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(`${prefix}${key}:`)
|
const items = value.filter(v => v !== null && v !== undefined && v !== '')
|
||||||
for (const item of value) {
|
if (!items.length) continue
|
||||||
if (typeof item === 'object' && item !== null) {
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
for (const item of items) {
|
||||||
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||||
|
if (isEmptyMapping(item)) continue
|
||||||
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
||||||
if (entries.length === 0) continue
|
if (!entries.length) continue
|
||||||
const first = entries[0]
|
const childIndent = baseIndent + ' '
|
||||||
lines.push(`${prefix} - ${first[0]}: ${typeof first[1] === 'object' ? '' : first[1]}`)
|
const subAllIndent = childIndent + ' '
|
||||||
if (typeof first[1] === 'object') {
|
const subObj = {}
|
||||||
lines.push(toYaml(first[1], indent + 3))
|
for (const [k, v] of entries) subObj[k] = v
|
||||||
}
|
const subRendered = toYaml(subObj, subAllIndent)
|
||||||
for (let i = 1; i < entries.length; i++) {
|
const subLines = subRendered.split('\n')
|
||||||
const [k, v] = entries[i]
|
if (subLines[0].startsWith(subAllIndent)) {
|
||||||
if (typeof v === 'object') {
|
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
|
||||||
lines.push(`${prefix} ${k}:`)
|
} else {
|
||||||
lines.push(toYaml(v, indent + 4))
|
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
|
||||||
} else {
|
|
||||||
lines.push(`${prefix} ${k}: ${v}`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
lines.push(...subLines)
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix} - ${item}`)
|
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(`${prefix}${key}:`)
|
if (isEmptyMapping(value)) {
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(`${baseIndent}${key}: {}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
lines.push(toYaml(value, childIndent))
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix}${key}: ${value}`)
|
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
|
|||||||
+46
-17
@@ -125,36 +125,65 @@ export const k8sPvSchema = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
function toYaml(obj, indent = 0) {
|
|
||||||
const lines = []
|
|
||||||
const prefix = ' '.repeat(indent)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function yamlScalar(v) {
|
||||||
|
if (v === null || v === undefined) return ''
|
||||||
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
||||||
|
const s = String(v)
|
||||||
|
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
|
||||||
|
return JSON.stringify(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyMapping(o) {
|
||||||
|
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
|
||||||
|
return Object.values(o).every(v => v === null || v === undefined || v === '' || (typeof v === 'object' && isEmptyMapping(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toYaml(obj, baseIndent = '') {
|
||||||
|
const lines = []
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(`${prefix}${key}:`)
|
const items = value.filter(v => v !== null && v !== undefined && v !== '')
|
||||||
for (const item of value) {
|
if (!items.length) continue
|
||||||
if (typeof item === 'object' && item !== null) {
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
for (const item of items) {
|
||||||
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||||
|
if (isEmptyMapping(item)) continue
|
||||||
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
||||||
if (entries.length === 0) continue
|
if (!entries.length) continue
|
||||||
const first = entries[0]
|
const childIndent = baseIndent + ' '
|
||||||
lines.push(`${prefix} - ${first[0]}: ${first[1]}`)
|
const subAllIndent = childIndent + ' '
|
||||||
for (let i = 1; i < entries.length; i++) {
|
const subObj = {}
|
||||||
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`)
|
for (const [k, v] of entries) subObj[k] = v
|
||||||
|
const subRendered = toYaml(subObj, subAllIndent)
|
||||||
|
const subLines = subRendered.split('\n')
|
||||||
|
if (subLines[0].startsWith(subAllIndent)) {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
|
||||||
|
} else {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
|
||||||
}
|
}
|
||||||
|
lines.push(...subLines)
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix} - ${item}`)
|
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(`${prefix}${key}:`)
|
if (isEmptyMapping(value)) {
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(`${baseIndent}${key}: {}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
lines.push(toYaml(value, childIndent))
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix}${key}: ${value}`)
|
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+46
-17
@@ -72,36 +72,65 @@ export const k8sPvcSchema = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
function toYaml(obj, indent = 0) {
|
|
||||||
const lines = []
|
|
||||||
const prefix = ' '.repeat(indent)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function yamlScalar(v) {
|
||||||
|
if (v === null || v === undefined) return ''
|
||||||
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
||||||
|
const s = String(v)
|
||||||
|
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
|
||||||
|
return JSON.stringify(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyMapping(o) {
|
||||||
|
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
|
||||||
|
return Object.values(o).every(v => v === null || v === undefined || v === '' || (typeof v === 'object' && isEmptyMapping(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toYaml(obj, baseIndent = '') {
|
||||||
|
const lines = []
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(`${prefix}${key}:`)
|
const items = value.filter(v => v !== null && v !== undefined && v !== '')
|
||||||
for (const item of value) {
|
if (!items.length) continue
|
||||||
if (typeof item === 'object' && item !== null) {
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
for (const item of items) {
|
||||||
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||||
|
if (isEmptyMapping(item)) continue
|
||||||
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
||||||
if (entries.length === 0) continue
|
if (!entries.length) continue
|
||||||
const first = entries[0]
|
const childIndent = baseIndent + ' '
|
||||||
lines.push(`${prefix} - ${first[0]}: ${first[1]}`)
|
const subAllIndent = childIndent + ' '
|
||||||
for (let i = 1; i < entries.length; i++) {
|
const subObj = {}
|
||||||
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`)
|
for (const [k, v] of entries) subObj[k] = v
|
||||||
|
const subRendered = toYaml(subObj, subAllIndent)
|
||||||
|
const subLines = subRendered.split('\n')
|
||||||
|
if (subLines[0].startsWith(subAllIndent)) {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
|
||||||
|
} else {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
|
||||||
}
|
}
|
||||||
|
lines.push(...subLines)
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix} - ${item}`)
|
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(`${prefix}${key}:`)
|
if (isEmptyMapping(value)) {
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(`${baseIndent}${key}: {}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
lines.push(toYaml(value, childIndent))
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix}${key}: ${value}`)
|
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+46
-17
@@ -81,36 +81,65 @@ export const k8sRoleSchema = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
function toYaml(obj, indent = 0) {
|
|
||||||
const lines = []
|
|
||||||
const prefix = ' '.repeat(indent)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function yamlScalar(v) {
|
||||||
|
if (v === null || v === undefined) return ''
|
||||||
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
||||||
|
const s = String(v)
|
||||||
|
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
|
||||||
|
return JSON.stringify(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyMapping(o) {
|
||||||
|
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
|
||||||
|
return Object.values(o).every(v => v === null || v === undefined || v === '' || (typeof v === 'object' && isEmptyMapping(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toYaml(obj, baseIndent = '') {
|
||||||
|
const lines = []
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(`${prefix}${key}:`)
|
const items = value.filter(v => v !== null && v !== undefined && v !== '')
|
||||||
for (const item of value) {
|
if (!items.length) continue
|
||||||
if (typeof item === 'object' && item !== null) {
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
for (const item of items) {
|
||||||
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||||
|
if (isEmptyMapping(item)) continue
|
||||||
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
||||||
if (entries.length === 0) continue
|
if (!entries.length) continue
|
||||||
const first = entries[0]
|
const childIndent = baseIndent + ' '
|
||||||
lines.push(`${prefix} - ${first[0]}: ${first[1]}`)
|
const subAllIndent = childIndent + ' '
|
||||||
for (let i = 1; i < entries.length; i++) {
|
const subObj = {}
|
||||||
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`)
|
for (const [k, v] of entries) subObj[k] = v
|
||||||
|
const subRendered = toYaml(subObj, subAllIndent)
|
||||||
|
const subLines = subRendered.split('\n')
|
||||||
|
if (subLines[0].startsWith(subAllIndent)) {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
|
||||||
|
} else {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
|
||||||
}
|
}
|
||||||
|
lines.push(...subLines)
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix} - ${item}`)
|
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(`${prefix}${key}:`)
|
if (isEmptyMapping(value)) {
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(`${baseIndent}${key}: {}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
lines.push(toYaml(value, childIndent))
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix}${key}: ${value}`)
|
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,36 +82,65 @@ export const k8sRoleBindingSchema = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
function toYaml(obj, indent = 0) {
|
|
||||||
const lines = []
|
|
||||||
const prefix = ' '.repeat(indent)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function yamlScalar(v) {
|
||||||
|
if (v === null || v === undefined) return ''
|
||||||
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
||||||
|
const s = String(v)
|
||||||
|
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
|
||||||
|
return JSON.stringify(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyMapping(o) {
|
||||||
|
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
|
||||||
|
return Object.values(o).every(v => v === null || v === undefined || v === '' || (typeof v === 'object' && isEmptyMapping(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toYaml(obj, baseIndent = '') {
|
||||||
|
const lines = []
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(`${prefix}${key}:`)
|
const items = value.filter(v => v !== null && v !== undefined && v !== '')
|
||||||
for (const item of value) {
|
if (!items.length) continue
|
||||||
if (typeof item === 'object' && item !== null) {
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
for (const item of items) {
|
||||||
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||||
|
if (isEmptyMapping(item)) continue
|
||||||
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
||||||
if (entries.length === 0) continue
|
if (!entries.length) continue
|
||||||
const first = entries[0]
|
const childIndent = baseIndent + ' '
|
||||||
lines.push(`${prefix} - ${first[0]}: ${first[1]}`)
|
const subAllIndent = childIndent + ' '
|
||||||
for (let i = 1; i < entries.length; i++) {
|
const subObj = {}
|
||||||
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`)
|
for (const [k, v] of entries) subObj[k] = v
|
||||||
|
const subRendered = toYaml(subObj, subAllIndent)
|
||||||
|
const subLines = subRendered.split('\n')
|
||||||
|
if (subLines[0].startsWith(subAllIndent)) {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
|
||||||
|
} else {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
|
||||||
}
|
}
|
||||||
|
lines.push(...subLines)
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix} - ${item}`)
|
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(`${prefix}${key}:`)
|
if (isEmptyMapping(value)) {
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(`${baseIndent}${key}: {}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
lines.push(toYaml(value, childIndent))
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix}${key}: ${value}`)
|
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+46
-17
@@ -98,36 +98,65 @@ export const k8sSecretSchema = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
function toYaml(obj, indent = 0) {
|
|
||||||
const lines = []
|
|
||||||
const prefix = ' '.repeat(indent)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function yamlScalar(v) {
|
||||||
|
if (v === null || v === undefined) return ''
|
||||||
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
||||||
|
const s = String(v)
|
||||||
|
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
|
||||||
|
return JSON.stringify(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyMapping(o) {
|
||||||
|
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
|
||||||
|
return Object.values(o).every(v => v === null || v === undefined || v === '' || (typeof v === 'object' && isEmptyMapping(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toYaml(obj, baseIndent = '') {
|
||||||
|
const lines = []
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(`${prefix}${key}:`)
|
const items = value.filter(v => v !== null && v !== undefined && v !== '')
|
||||||
for (const item of value) {
|
if (!items.length) continue
|
||||||
if (typeof item === 'object' && item !== null) {
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
for (const item of items) {
|
||||||
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||||
|
if (isEmptyMapping(item)) continue
|
||||||
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
||||||
if (entries.length === 0) continue
|
if (!entries.length) continue
|
||||||
const first = entries[0]
|
const childIndent = baseIndent + ' '
|
||||||
lines.push(`${prefix} - ${first[0]}: ${first[1]}`)
|
const subAllIndent = childIndent + ' '
|
||||||
for (let i = 1; i < entries.length; i++) {
|
const subObj = {}
|
||||||
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`)
|
for (const [k, v] of entries) subObj[k] = v
|
||||||
|
const subRendered = toYaml(subObj, subAllIndent)
|
||||||
|
const subLines = subRendered.split('\n')
|
||||||
|
if (subLines[0].startsWith(subAllIndent)) {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
|
||||||
|
} else {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
|
||||||
}
|
}
|
||||||
|
lines.push(...subLines)
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix} - ${item}`)
|
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(`${prefix}${key}:`)
|
if (isEmptyMapping(value)) {
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(`${baseIndent}${key}: {}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
lines.push(toYaml(value, childIndent))
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix}${key}: ${value}`)
|
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+46
-17
@@ -134,36 +134,65 @@ export const k8sServiceSchema = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
function toYaml(obj, indent = 0) {
|
|
||||||
const lines = []
|
|
||||||
const prefix = ' '.repeat(indent)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function yamlScalar(v) {
|
||||||
|
if (v === null || v === undefined) return ''
|
||||||
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
||||||
|
const s = String(v)
|
||||||
|
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
|
||||||
|
return JSON.stringify(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyMapping(o) {
|
||||||
|
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
|
||||||
|
return Object.values(o).every(v => v === null || v === undefined || v === '' || (typeof v === 'object' && isEmptyMapping(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toYaml(obj, baseIndent = '') {
|
||||||
|
const lines = []
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(`${prefix}${key}:`)
|
const items = value.filter(v => v !== null && v !== undefined && v !== '')
|
||||||
for (const item of value) {
|
if (!items.length) continue
|
||||||
if (typeof item === 'object' && item !== null) {
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
for (const item of items) {
|
||||||
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||||
|
if (isEmptyMapping(item)) continue
|
||||||
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
||||||
if (entries.length === 0) continue
|
if (!entries.length) continue
|
||||||
const first = entries[0]
|
const childIndent = baseIndent + ' '
|
||||||
lines.push(`${prefix} - ${first[0]}: ${first[1]}`)
|
const subAllIndent = childIndent + ' '
|
||||||
for (let i = 1; i < entries.length; i++) {
|
const subObj = {}
|
||||||
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`)
|
for (const [k, v] of entries) subObj[k] = v
|
||||||
|
const subRendered = toYaml(subObj, subAllIndent)
|
||||||
|
const subLines = subRendered.split('\n')
|
||||||
|
if (subLines[0].startsWith(subAllIndent)) {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
|
||||||
|
} else {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
|
||||||
}
|
}
|
||||||
|
lines.push(...subLines)
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix} - ${item}`)
|
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(`${prefix}${key}:`)
|
if (isEmptyMapping(value)) {
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(`${baseIndent}${key}: {}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
lines.push(toYaml(value, childIndent))
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix}${key}: ${value}`)
|
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,36 +62,65 @@ export const k8sServiceAccountSchema = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
function toYaml(obj, indent = 0) {
|
|
||||||
const lines = []
|
|
||||||
const prefix = ' '.repeat(indent)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function yamlScalar(v) {
|
||||||
|
if (v === null || v === undefined) return ''
|
||||||
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
||||||
|
const s = String(v)
|
||||||
|
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
|
||||||
|
return JSON.stringify(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyMapping(o) {
|
||||||
|
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
|
||||||
|
return Object.values(o).every(v => v === null || v === undefined || v === '' || (typeof v === 'object' && isEmptyMapping(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toYaml(obj, baseIndent = '') {
|
||||||
|
const lines = []
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(`${prefix}${key}:`)
|
const items = value.filter(v => v !== null && v !== undefined && v !== '')
|
||||||
for (const item of value) {
|
if (!items.length) continue
|
||||||
if (typeof item === 'object' && item !== null) {
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
for (const item of items) {
|
||||||
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||||
|
if (isEmptyMapping(item)) continue
|
||||||
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
||||||
if (entries.length === 0) continue
|
if (!entries.length) continue
|
||||||
const first = entries[0]
|
const childIndent = baseIndent + ' '
|
||||||
lines.push(`${prefix} - ${first[0]}: ${first[1]}`)
|
const subAllIndent = childIndent + ' '
|
||||||
for (let i = 1; i < entries.length; i++) {
|
const subObj = {}
|
||||||
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`)
|
for (const [k, v] of entries) subObj[k] = v
|
||||||
|
const subRendered = toYaml(subObj, subAllIndent)
|
||||||
|
const subLines = subRendered.split('\n')
|
||||||
|
if (subLines[0].startsWith(subAllIndent)) {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
|
||||||
|
} else {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
|
||||||
}
|
}
|
||||||
|
lines.push(...subLines)
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix} - ${item}`)
|
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(`${prefix}${key}:`)
|
if (isEmptyMapping(value)) {
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(`${baseIndent}${key}: {}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
lines.push(toYaml(value, childIndent))
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix}${key}: ${value}`)
|
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -424,40 +424,63 @@ export function generateK8sStatefulSetYaml(config) {
|
|||||||
return header + '\n' + toYaml(statefulset)
|
return header + '\n' + toYaml(statefulset)
|
||||||
}
|
}
|
||||||
|
|
||||||
function toYaml(obj, indent = 0) {
|
|
||||||
|
|
||||||
|
|
||||||
|
function yamlScalar(v) {
|
||||||
|
if (v === null || v === undefined) return ''
|
||||||
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
||||||
|
const s = String(v)
|
||||||
|
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
|
||||||
|
return JSON.stringify(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyMapping(o) {
|
||||||
|
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
|
||||||
|
return Object.values(o).every(v => v === null || v === undefined || v === '' || (typeof v === 'object' && isEmptyMapping(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toYaml(obj, baseIndent = '') {
|
||||||
const lines = []
|
const lines = []
|
||||||
const prefix = ' '.repeat(indent)
|
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(`${prefix}${key}:`)
|
const items = value.filter(v => v !== null && v !== undefined && v !== '')
|
||||||
for (const item of value) {
|
if (!items.length) continue
|
||||||
if (typeof item === 'object' && item !== null) {
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
for (const item of items) {
|
||||||
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||||
|
if (isEmptyMapping(item)) continue
|
||||||
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
||||||
if (entries.length === 0) continue
|
if (!entries.length) continue
|
||||||
const first = entries[0]
|
const childIndent = baseIndent + ' '
|
||||||
lines.push(`${prefix} - ${first[0]}: ${typeof first[1] === 'object' ? '' : first[1]}`)
|
const subAllIndent = childIndent + ' '
|
||||||
if (typeof first[1] === 'object') {
|
const subObj = {}
|
||||||
lines.push(toYaml(first[1], indent + 3))
|
for (const [k, v] of entries) subObj[k] = v
|
||||||
}
|
const subRendered = toYaml(subObj, subAllIndent)
|
||||||
for (let i = 1; i < entries.length; i++) {
|
const subLines = subRendered.split('\n')
|
||||||
const [k, v] = entries[i]
|
if (subLines[0].startsWith(subAllIndent)) {
|
||||||
if (typeof v === 'object') {
|
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
|
||||||
lines.push(`${prefix} ${k}:`)
|
} else {
|
||||||
lines.push(toYaml(v, indent + 4))
|
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
|
||||||
} else {
|
|
||||||
lines.push(`${prefix} ${k}: ${v}`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
lines.push(...subLines)
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix} - ${item}`)
|
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(`${prefix}${key}:`)
|
if (isEmptyMapping(value)) {
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(`${baseIndent}${key}: {}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
lines.push(toYaml(value, childIndent))
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix}${key}: ${value}`)
|
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
|
|||||||
@@ -103,36 +103,65 @@ export const k8sStorageClassSchema = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
function toYaml(obj, indent = 0) {
|
|
||||||
const lines = []
|
|
||||||
const prefix = ' '.repeat(indent)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function yamlScalar(v) {
|
||||||
|
if (v === null || v === undefined) return ''
|
||||||
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
||||||
|
const s = String(v)
|
||||||
|
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
|
||||||
|
return JSON.stringify(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyMapping(o) {
|
||||||
|
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
|
||||||
|
return Object.values(o).every(v => v === null || v === undefined || v === '' || (typeof v === 'object' && isEmptyMapping(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toYaml(obj, baseIndent = '') {
|
||||||
|
const lines = []
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(`${prefix}${key}:`)
|
const items = value.filter(v => v !== null && v !== undefined && v !== '')
|
||||||
for (const item of value) {
|
if (!items.length) continue
|
||||||
if (typeof item === 'object' && item !== null) {
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
for (const item of items) {
|
||||||
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||||
|
if (isEmptyMapping(item)) continue
|
||||||
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
||||||
if (entries.length === 0) continue
|
if (!entries.length) continue
|
||||||
const first = entries[0]
|
const childIndent = baseIndent + ' '
|
||||||
lines.push(`${prefix} - ${first[0]}: ${first[1]}`)
|
const subAllIndent = childIndent + ' '
|
||||||
for (let i = 1; i < entries.length; i++) {
|
const subObj = {}
|
||||||
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`)
|
for (const [k, v] of entries) subObj[k] = v
|
||||||
|
const subRendered = toYaml(subObj, subAllIndent)
|
||||||
|
const subLines = subRendered.split('\n')
|
||||||
|
if (subLines[0].startsWith(subAllIndent)) {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
|
||||||
|
} else {
|
||||||
|
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
|
||||||
}
|
}
|
||||||
|
lines.push(...subLines)
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix} - ${item}`)
|
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(`${prefix}${key}:`)
|
if (isEmptyMapping(value)) {
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(`${baseIndent}${key}: {}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines.push(`${baseIndent}${key}:`)
|
||||||
|
const childIndent = baseIndent + ' '
|
||||||
|
lines.push(toYaml(value, childIndent))
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix}${key}: ${value}`)
|
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user