提交 2d1f4481 authored 作者: likai's avatar likai

fix: enforce oss-only gitlab ci skill

上级 d0ceddc9
......@@ -7,9 +7,10 @@
- 需要生成或修复 `.gitlab-ci.yml`
- 需要严格区分 `build_x``release_x`
- 项目依赖 `ci/*.sh` 脚本执行构建与发布
- Bingo / SGKT 系列前端项目必须保持组织内 OSS 发布契约,本技能固定走 OSS 发布
- 需要按环境、模块或 tag 前缀精确触发流水线
- 需要兼容 GitLab 11.x 等较老版本,避免 `extends``needs``rules` 等新语法
- 发布阶段需要走 OSS 上传,而不是仅保留 GitLab artifacts
- 发布阶段固定走 OSS 上传,而不是仅保留 GitLab artifacts
## 核心能力
......@@ -20,6 +21,8 @@
- 支持按 tag 前缀路由不同环境或模块,例如 `web-*``admin-*``demo-default-*`
- 支持生成 `ci/build-dist.sh``ci/release-dist.sh``CI_USAGE.md`
- 强调与已有发布约定兼容,例如固定镜像、固定脚本签名、固定上传变量名等
- 对 Bingo / SGKT 系列仓库优先保留 `springjk/ci-helper:latest``tags: ["web"]``sh ci/release-dist.sh "$BUILD_TARGET" "$OSS_DEPLOY_NAME"` 这组三要素
- 在当前仓库证据不足时,继续搜索同工作区同系列仓库来恢复并固定组织内标准发布契约
## 仓库结构
......@@ -65,15 +68,18 @@
模板内常见占位符包括:
- `__PACKAGE_DIR__`
- `__RUNNER_TAG__`
- `__BUILD_IMAGE__`
- `__RELEASE_IMAGE__`
- `__BUILD_TARGET__`
- `__INSTALL_COMMAND__`
- `__PACKAGE_MANAGER__`
- `__BUILD_COMMAND__`
- `__MODULE_NAME__`
- `__JOB_SUFFIX__`
- `__TAG_PREFIX__`
- `__OSS_DEPLOY_NAME__`
对 Bingo / SGKT 系列项目,这个模板默认保留以下组织内发布骨架:
- `release` 镜像固定为 `springjk/ci-helper:latest`
- 所有 concrete job 默认使用 `tags: ["web"]`
- `release` 始终调用 `sh ci/release-dist.sh "$BUILD_TARGET" "$OSS_DEPLOY_NAME"`
### 2. `gitlab-multi-frontend-template`
......@@ -82,6 +88,7 @@
- 每个模块拥有独立的 `build_x + release_x`
- 每个模块有独立的构建命令、产物目录与 OSS 发布名
- 可按模块 tag 前缀分别触发,例如 `web-*``admin-*`
- 对同系列前端仓库,默认仍使用 `springjk/ci-helper:latest``tags: ["web"]`
## 技能工作流
......@@ -91,18 +98,25 @@
2. 读取已有 `.gitlab-ci.yml``package.json`、shell 脚本和项目级 `AGENTS.md`
3. 确认是单模块还是多模块,是否存在固定发布约定
4. 识别真实的 Node 版本、包管理器与构建入口
5. 选择合适模板或按约束手工生成 CI
6. 补齐需要的 `ci/*.sh` 脚本和说明文档
7. 校验 YAML、shell 语法以及 job 配对关系
5. 按“用户明确片段 > 当前仓库 CI / 脚本 > 当前仓库组织线索 > 同工作区同系列仓库 > 通用模板”的顺序识别发布契约
6. 对 Bingo / SGKT / `nceduc` / `dhq` / `xinyu` / `sr` 等前端项目,优先按组织内标准前端发布模型处理
7. 显式核对 `package.json``pnpm-lock.yaml``package-lock.json``yarn.lock``pnpm-workspace.yaml` 是否真实存在
8. 选择合适模板或按约束手工生成 CI
9. 补齐需要的 `ci/*.sh` 脚本和说明文档
10. 校验 YAML、shell 语法、job 配对关系,以及 CI/文档里的 lockfile 假设是否和仓库现状一致
## 设计原则
- 以“保守兼容、便于审查、易于维护”为优先目标
- 不盲目套用示例注释或“项目级说明”,必须以真实仓库内容为准
- 不把文档、样例或注释中的 lockfile 假设直接当成仓库事实
- 不默认引入 GitLab 新特性,尤其在 11.x 场景下
- 不把发布逻辑弱化成仅 artifacts 输出
- 不随意改写用户已经确定的发布契约、镜像、变量名或脚本参数顺序
- 一旦命中组织内标准契约,必须把 `springjk/ci-helper:latest``tags: ["web"]``sh ci/release-dist.sh "$BUILD_TARGET" "$OSS_DEPLOY_NAME"` 三项一起保留
- 当前仓库证据不足时,不会直接否定 OSS 契约,而是继续参考同工作区同系列仓库,并仍按固定 OSS 模式生成
- npm 项目默认使用 `npm install`,而不是 `npm ci`
- pnpm 项目只有在真实存在 `pnpm-lock.yaml` 时才使用 `pnpm install --frozen-lockfile`;否则回退到 `pnpm install --no-frozen-lockfile` 并给出明确提示
## 推荐使用方式
......@@ -129,6 +143,13 @@
具体文件是否生成,取决于目标仓库是否真的需要这些内容。
输出说明里还会区分两类环境映射:
- 已确认映射:能从当前仓库 CI、脚本、变量或同名约定中直接确认的 `OSS_DEPLOY_NAME`
- 推断映射:依据 `package.json` `build:*` 与同系列仓库命名风格推断出的 `OSS_DEPLOY_NAME`
如果存在推断映射,说明会明确提示“这只是变量值推断,可按线上 OSS 名称调整,无需改脚本结构”。
## 参考资料
- [SKILL.md](./SKILL.md)
......
......@@ -12,6 +12,7 @@ Use this skill to create or repair GitLab CI for projects that need conservative
1. Inspect the repository before writing CI.
Read the existing `.gitlab-ci.yml` if present.
Search for build entrypoints such as `package.json`, `pnpm` scripts, root `.sh` files, Docker-based build scripts, OSS upload helpers, and any project `AGENTS.md` rules.
Explicitly verify whether `package.json`, `pnpm-lock.yaml`, `package-lock.json`, `yarn.lock`, and `pnpm-workspace.yaml` really exist before choosing an install strategy.
Treat project-level comment blocks and copied examples as hints until they are confirmed by the repository or by the user. Do not copy a sample “第二部分:项目级说明” into the final CI as if it were automatically true for the current project.
2. Identify the deployment model.
......@@ -20,9 +21,12 @@ Use this skill to create or repair GitLab CI for projects that need conservative
Determine whether artifacts are passed through a shared transfer directory instead of GitLab artifacts.
Determine whether build output directories must be isolated by module or environment.
Determine whether the user wants tag-prefix routing so only one environment or module job set runs for a given tag.
Identify the release contract with this evidence priority: user-provided CI snippets or variable names first, then the current repository `.gitlab-ci.yml` and release scripts, then organization clues in the current repository such as `springjk/ci-helper:latest`, `springjk/webdev:*`, `oss-deploy`, `OSS_DEPLOY_NAME`, and `tags: [web]`, then neighboring repositories in the same workspace that clearly belong to the same Bingo or SGKT family, and finally align the generated result to the fixed OSS release pattern used by this skill.
Determine whether the repository or the user has already established a release contract, such as a fixed `.release_template`, a required release image like `springjk/ci-helper:latest`, a required script signature like `sh ci/release-dist.sh "$BUILD_TARGET" "$OSS_DEPLOY_NAME"`, or a required uploader like `oss-deploy`.
For Bingo, SGKT, `nceduc`, `dhq`, `xinyu`, and `sr` style frontend repositories, or repositories that use `springjk/webdev`, multiple `.env.*`, multiple `build:*` scripts, or have same-family neighbors in the workspace, assume the organization-standard frontend release model first: `build_x + release_x` pairing, `release` on `springjk/ci-helper:latest`, explicit `tags: ["web"]`, `sh ci/release-dist.sh "$BUILD_TARGET" "$OSS_DEPLOY_NAME"`, and OSS upload through `oss-deploy`.
Unless the user explicitly asks to collapse stages, preserve strict `build_x + release_x` pairing and keep release focused on zip packaging plus OSS upload.
If an existing repository or user-provided snippet establishes the release contract, treat that contract as mandatory and preserve it unless the user explicitly asks to replace it.
This skill does not downgrade release to artifacts-only. If repository evidence is incomplete, continue collecting evidence from neighboring same-family repositories and still generate the fixed OSS release structure.
3. Confirm the GitLab compatibility target.
If the user mentions an old GitLab version such as 11.x, assume conservative compatibility.
......@@ -47,7 +51,8 @@ Default to this structure unless the repo clearly needs something else:
- Helper scripts live under `ci/` when they are reused by multiple jobs.
- Usage documentation lives in `CI_USAGE.md` when the pipeline has multiple environments or non-obvious constraints.
- If the user wants selective execution, add explicit tag-prefix conditions per job pair so one tag only triggers the intended `build_x + release_x`.
- Prefer OSS-oriented release behavior by default: generate `dist-x-<date>.zip` and `dist-x-latest.zip`, stage them into upload directories when needed, and upload from release jobs. Do not silently replace this with “copy to server directory + switch current symlink” unless the repository already uses that deployment model and the user wants to preserve it.
- For Bingo or SGKT family frontend repositories, default the generated release contract to `image: springjk/ci-helper:latest`, `tags: ["web"]`, and `sh ci/release-dist.sh "$BUILD_TARGET" "$OSS_DEPLOY_NAME"` unless stronger evidence from the repository or user overrides it with another organization-standard contract.
- Use fixed OSS-oriented release behavior: generate `dist-x-<date>.zip` and `dist-x-latest.zip`, stage them into upload directories when needed, and upload from release jobs. Do not replace this skill's release stage with GitLab artifacts-only output.
- If the repository or the user already defines a concrete OSS release skeleton, preserve the release image, script entry, argument order, uploader contract, and variable names unless the user explicitly requests a migration. Do not downgrade that release stage to GitLab artifacts-only output, ad hoc server-directory copying, or a different uploader contract.
## Legacy GitLab Rules
......@@ -84,9 +89,13 @@ For each environment or module X:
- Put build output in `$TRANSFER_BASE_DIR/x/dist`.
- Package release output as `dist-x-<date>.zip` and `dist-x-latest.zip` when the project wants versioned zips.
- If the uploader only accepts directories, stage zip files inside dedicated upload directories before calling the uploader.
- If any evidence shows the organization contract `image: springjk/ci-helper:latest` plus `tags: ["web"]` plus `sh ci/release-dist.sh "$BUILD_TARGET" "$OSS_DEPLOY_NAME"`, keep all three elements together. Do not preserve only one or two of them.
- If the project already uses a release template such as `image: springjk/ci-helper:latest` plus `sh ci/release-dist.sh "$BUILD_TARGET" "$OSS_DEPLOY_NAME"`, keep that structure and wire every `release_x` job through `OSS_DEPLOY_NAME`. Do not rename the variable or replace the image unless the user explicitly approves the change.
- If using tag-prefix routing, give each pair a unique non-overlapping prefix and apply the same prefix condition to both `build_x` and `release_x`.
- Do not keep only `build_x` when the skill is supposed to follow the default OSS release pattern; if a release job is intentionally omitted, document why and confirm that this is a user choice rather than an accidental drift from the standard pattern.
- Never output an artifacts-only `release` stage from this skill. The fixed mode is `build_x + release_x` plus OSS upload.
- Map `OSS_DEPLOY_NAME` from real `package.json` `build:*` targets first, then infer naming style from same-family repositories when needed. Do not mechanically reuse the build target as the OSS name if the family convention points to names like `bingo-prod`, `bingo-dev`, `shangrao-dev`, or `xinyu-prod`.
- When any `OSS_DEPLOY_NAME` value is inferred rather than confirmed, say so explicitly in the final explanation and tell the user that only the variable value should change, not the script structure.
## Shell Script Rules
......@@ -97,6 +106,8 @@ When the repository already uses `.sh` build scripts:
- Make scripts POSIX `sh` unless the repository clearly needs `bash`.
- Validate shell syntax after editing.
- Match the install command to the actual repository package manager. Do not force `pnpm` only because a copied comment block says so if the repository actually uses `npm` or `yarn`.
- If CI uses `pnpm`, only use `pnpm install --frozen-lockfile` when the repository really contains `pnpm-lock.yaml`. If `pnpm-lock.yaml` is missing, fall back to `pnpm install --no-frozen-lockfile`, log the mismatch clearly, and recommend adding the lockfile later.
- If docs, README, comments, or copied examples claim a lockfile exists but the repository does not contain it, treat the repository state as source of truth and update the docs to remove the mismatch.
- For npm-based repositories in this skill, use `npm install`. Do not use `npm ci`.
## Validation
......@@ -106,14 +117,19 @@ After edits, validate at least these points:
- YAML parses successfully.
- Unsupported keys for the target GitLab version are absent.
- Every active `build_*` job has a matching `release_*` job.
- The final pipeline still preserves `build_x + release_x` pairs and does not drift to build-only or artifacts-only release.
- `springjk/ci-helper:latest` is still present whenever the established organization release contract requires it.
- Every concrete job has `tags: ["web"]` if the project requires that runner tag.
- The release command still calls `sh ci/release-dist.sh "$BUILD_TARGET" "$OSS_DEPLOY_NAME"` when that contract was established.
- Tag-prefix expressions do not overlap unless the user explicitly wants multiple pairs to run.
- Shell scripts pass `sh -n`.
- Documentation matches the actual active jobs.
- Lockfile assumptions in CI, scripts, and docs all match the repository state that was actually checked.
- The selected Node image is an official `node:X-alpine` image whose version matches the project evidence.
- Project-level notes copied into comments have been verified against real build scripts before being treated as facts.
- If the repository or user provided a release snippet, the final `.release_template` still uses the required release image, script command, argument order, and uploader variable names.
- The release path still stages zip files into directories before calling the uploader when that uploader contract requires directories.
- Every `release_*` job has `OSS_DEPLOY_NAME`, and the final explanation distinguishes confirmed mappings from inferred mappings.
## References
......@@ -122,7 +138,7 @@ Read [`references/legacy-gitlab-patterns.md`](references/legacy-gitlab-patterns.
## Assets
When the project fits the common pattern of tag-triggered frontend publishing with shell-based build helpers, reuse the bundled template at [`assets/gitlab-tag-release-template`](assets/gitlab-tag-release-template).
Copy only the files that fit the target repository, then replace placeholders such as `__MODULE_NAME__`, `__BUILD_TARGET__`, `__PNPM_BUILD_SCRIPT__`, `__RUNNER_TAG__`, `__PACKAGE_DIR__`, and `__TAG_PREFIX__`.
Copy only the files that fit the target repository, then replace placeholders such as `__BUILD_TARGET__`, `__BUILD_COMMAND__`, `__PACKAGE_MANAGER__`, `__PACKAGE_DIR__`, `__TAG_PREFIX__`, and `__OSS_DEPLOY_NAME__`.
When the project has multiple frontend modules such as `web` and `admin`, prefer [`assets/gitlab-multi-frontend-template`](assets/gitlab-multi-frontend-template).
Use it when each module needs its own isolated build output directory, its own `build_x + release_x` pair, and optionally its own tag prefix such as `web-*` or `admin-*`.
......@@ -10,22 +10,17 @@ stages:
stage: build
image: node:24-alpine
tags:
- __RUNNER_TAG__
before_script:
- corepack enable
- corepack prepare pnpm@10.28.2 --activate
- pnpm config set registry https://registry.npmmirror.com
- pnpm config set store-dir "$PNPM_HOME"
- web
script:
- sh ci/build-dist.sh "$MODULE_NAME" "$PACKAGE_DIR" "$PNPM_BUILD_SCRIPT" "$DIST_SUBDIR"
- sh ci/build-dist.sh "$MODULE_NAME" "$PACKAGE_DIR" "$PACKAGE_MANAGER" "$BUILD_COMMAND" "$DIST_SUBDIR"
only:
- tags
.release_template: &release_template
stage: release
image: __RELEASE_IMAGE__
image: springjk/ci-helper:latest
tags:
- __RUNNER_TAG__
- web
script:
- sh ci/release-dist.sh "$MODULE_NAME" "$OSS_DEPLOY_NAME"
only:
......@@ -34,7 +29,7 @@ stages:
build_web:
<<: *build_template
tags:
- __RUNNER_TAG__
- web
only:
refs:
- tags
......@@ -43,13 +38,14 @@ build_web:
variables:
MODULE_NAME: "web"
PACKAGE_DIR: "__WEB_PACKAGE_DIR__"
PNPM_BUILD_SCRIPT: "__WEB_BUILD_SCRIPT__"
PACKAGE_MANAGER: "__WEB_PACKAGE_MANAGER__"
BUILD_COMMAND: "__WEB_BUILD_COMMAND__"
DIST_SUBDIR: "__WEB_DIST_SUBDIR__"
release_web:
<<: *release_template
tags:
- __RUNNER_TAG__
- web
only:
refs:
- tags
......@@ -62,7 +58,7 @@ release_web:
build_admin:
<<: *build_template
tags:
- __RUNNER_TAG__
- web
only:
refs:
- tags
......@@ -71,13 +67,14 @@ build_admin:
variables:
MODULE_NAME: "admin"
PACKAGE_DIR: "__ADMIN_PACKAGE_DIR__"
PNPM_BUILD_SCRIPT: "__ADMIN_BUILD_SCRIPT__"
PACKAGE_MANAGER: "__ADMIN_PACKAGE_MANAGER__"
BUILD_COMMAND: "__ADMIN_BUILD_COMMAND__"
DIST_SUBDIR: "__ADMIN_DIST_SUBDIR__"
release_admin:
<<: *release_template
tags:
- __RUNNER_TAG__
- web
only:
refs:
- tags
......
......@@ -4,17 +4,14 @@ Use this template when the repository has multiple frontend modules such as `web
## Replace these placeholders
- `__RUNNER_TAG__`
- `__BUILD_IMAGE__`
- `__RELEASE_IMAGE__`
- `__WEB_PACKAGE_DIR__`
- `__WEB_INSTALL_COMMAND__`
- `__WEB_PACKAGE_MANAGER__`
- `__WEB_BUILD_COMMAND__`
- `__WEB_DIST_SUBDIR__`
- `__WEB_OSS_NAME__`
- `__WEB_TAG_PREFIX__`
- `__ADMIN_PACKAGE_DIR__`
- `__ADMIN_INSTALL_COMMAND__`
- `__ADMIN_PACKAGE_MANAGER__`
- `__ADMIN_BUILD_COMMAND__`
- `__ADMIN_DIST_SUBDIR__`
- `__ADMIN_OSS_NAME__`
......@@ -28,18 +25,42 @@ Use this template when the repository has multiple frontend modules such as `web
4. Dist directories are normalized to `/cache/transfer/${CI_PIPELINE_ID}/web/dist` and `/cache/transfer/${CI_PIPELINE_ID}/admin/dist`.
5. Only tags matching the configured module prefix trigger that module pair.
6. Prefer an official Node image such as `node:16-alpine` or `node:24-alpine`, selected from project evidence.
7. Each build script checks the real lockfile state first, so pnpm repositories without `pnpm-lock.yaml` automatically fall back to `pnpm install --no-frozen-lockfile`.
8. For Bingo / SGKT family repositories, `release_*` defaults to `springjk/ci-helper:latest` and all concrete jobs default to `tags: ["web"]`.
9. Each `release_*` keeps the contract `sh ci/release-dist.sh "$MODULE_NAME" "$OSS_DEPLOY_NAME"` and this skill does not downgrade release to artifacts-only.
## Package manager rule
Choose each module's install and build commands from the checked-in repository:
- npm module: `__WEB_INSTALL_COMMAND__` could be `npm install`, `__WEB_BUILD_COMMAND__` could be `npm run build`
- pnpm module: `__ADMIN_INSTALL_COMMAND__` could be `pnpm install --frozen-lockfile`, `__ADMIN_BUILD_COMMAND__` could be `pnpm run build --filter=@vben/web-antdv-next`
- yarn module: use `yarn install --frozen-lockfile` plus the module's real build command
- First verify whether these files really exist in the repository: `package.json`, `pnpm-lock.yaml`, `package-lock.json`, `yarn.lock`, `pnpm-workspace.yaml`
- Also verify each module's real build entrypoints from `package.json` scripts, existing `build*.sh`, and existing `ci/*.sh`
- npm module: set `__WEB_PACKAGE_MANAGER__` or `__ADMIN_PACKAGE_MANAGER__` to `npm`, then use the module's real `__WEB_BUILD_COMMAND__` or `__ADMIN_BUILD_COMMAND__`
- pnpm module with `pnpm-lock.yaml`: set the module package manager to `pnpm`, then use the real build command
- pnpm module without `pnpm-lock.yaml`: still use `pnpm`, but let the script fall back to `pnpm install --no-frozen-lockfile` and keep the warning in logs
- yarn module with `yarn.lock`: use `yarn` plus the module's real build command
- yarn module without `yarn.lock`: let the script fall back to `yarn install`
Do not assume all modules use `pnpm` just because one copied project note mentions it.
Do not hardcode `pnpm install --frozen-lockfile` unless the repository actually contains `pnpm-lock.yaml`.
If documentation or copied samples claim a lockfile exists but the repository does not contain it, follow the repository state and update the docs to match.
Do not use `npm ci` as the default npm install command in this template.
## Release contract rule
When the target repository is part of the Bingo / SGKT family and no stronger repository-specific contract overrides it, keep these elements together:
- `image: springjk/ci-helper:latest`
- `tags: ["web"]`
- `sh ci/release-dist.sh "$MODULE_NAME" "$OSS_DEPLOY_NAME"`
For `__WEB_OSS_NAME__`, `__ADMIN_OSS_NAME__`, and similar values, distinguish between:
- confirmed mappings from the current repository or explicit user input
- inferred mappings from same-family repository naming patterns
If a mapping is inferred, call it out in the final explanation and tell the user they only need to adjust the variable value if the real OSS name differs.
## Prefix design rule
On older GitLab versions, use simple non-overlapping prefixes.
......
......@@ -3,22 +3,90 @@ set -eu
MODULE_NAME="${1:?missing module name}"
PACKAGE_DIR="${2:?missing package dir}"
DIST_SUBDIR="${3:-dist}"
PACKAGE_MANAGER="${3:?missing package manager}"
BUILD_COMMAND="${4:?missing build command}"
DIST_SUBDIR="${5:-dist}"
TRANSFER_BASE_DIR="${TRANSFER_BASE_DIR:?missing TRANSFER_BASE_DIR}"
INSTALL_COMMAND="${INSTALL_COMMAND:?missing install command}"
BUILD_COMMAND="${BUILD_COMMAND:?missing build command}"
PNPM_HOME="${PNPM_HOME:-/pnpm-store}"
REPO_DIR="$(pwd)"
PACKAGE_DIR_ABS="${REPO_DIR}/${PACKAGE_DIR}"
TARGET_DIR="${TRANSFER_BASE_DIR}/${MODULE_NAME}"
cd "${PACKAGE_DIR}"
sh -lc "${INSTALL_COMMAND}"
sh -lc "${BUILD_COMMAND}"
if [ ! -d "${PACKAGE_DIR_ABS}" ]; then
echo "未找到包目录: ${PACKAGE_DIR}" >&2
exit 1
fi
if [ ! -f "${PACKAGE_DIR_ABS}/package.json" ]; then
echo "未找到 package.json: ${PACKAGE_DIR}/package.json" >&2
exit 1
fi
run_in_dir() {
RUN_DIR="${1:?missing run dir}"
RUN_COMMAND="${2:?missing run command}"
(
cd "${RUN_DIR}"
sh -lc "${RUN_COMMAND}"
)
}
has_file() {
FILE_NAME="${1:?missing file name}"
[ -f "${REPO_DIR}/${FILE_NAME}" ] || [ -f "${PACKAGE_DIR_ABS}/${FILE_NAME}" ]
}
if [ "${PACKAGE_MANAGER}" = "pnpm" ] && [ -f "${REPO_DIR}/pnpm-workspace.yaml" ]; then
INSTALL_DIR="${REPO_DIR}"
BUILD_DIR="${REPO_DIR}"
else
INSTALL_DIR="${PACKAGE_DIR_ABS}"
BUILD_DIR="${PACKAGE_DIR_ABS}"
fi
case "${PACKAGE_MANAGER}" in
pnpm)
if command -v corepack >/dev/null 2>&1; then
corepack enable
corepack prepare pnpm@10.28.2 --activate
fi
pnpm config set registry https://registry.npmmirror.com
pnpm config set store-dir "${PNPM_HOME}"
if has_file "pnpm-lock.yaml"; then
run_in_dir "${INSTALL_DIR}" "pnpm install --frozen-lockfile"
else
echo "警告: 当前仓库缺少 pnpm-lock.yaml,CI 已回退为 pnpm install --no-frozen-lockfile 以兼容现状。建议后续补齐并提交 pnpm-lock.yaml 以恢复可复现安装。" >&2
run_in_dir "${INSTALL_DIR}" "pnpm install --no-frozen-lockfile"
fi
;;
npm)
run_in_dir "${INSTALL_DIR}" "npm install"
;;
yarn)
if command -v corepack >/dev/null 2>&1; then
corepack enable
fi
if has_file "yarn.lock"; then
run_in_dir "${INSTALL_DIR}" "yarn install --frozen-lockfile"
else
echo "警告: 当前仓库缺少 yarn.lock,CI 已回退为 yarn install 以兼容现状。建议后续补齐并提交 yarn.lock 以恢复更严格的依赖安装。" >&2
run_in_dir "${INSTALL_DIR}" "yarn install"
fi
;;
*)
echo "不支持的包管理器: ${PACKAGE_MANAGER}" >&2
exit 1
;;
esac
run_in_dir "${BUILD_DIR}" "${BUILD_COMMAND}"
if [ ! -d "${DIST_SUBDIR}" ]; then
if [ ! -d "${PACKAGE_DIR_ABS}/${DIST_SUBDIR}" ]; then
echo "未找到构建产物目录: ${PACKAGE_DIR}/${DIST_SUBDIR}" >&2
exit 1
fi
mkdir -p "${TARGET_DIR}"
rm -rf "${TARGET_DIR}/dist"
cp -R "${DIST_SUBDIR}" "${TARGET_DIR}/dist"
cp -R "${PACKAGE_DIR_ABS}/${DIST_SUBDIR}" "${TARGET_DIR}/dist"
......@@ -11,22 +11,17 @@ stages:
stage: build
image: node:24-alpine
tags:
- __RUNNER_TAG__
before_script:
- corepack enable
- corepack prepare pnpm@10.28.2 --activate
- pnpm config set registry https://registry.npmmirror.com
- pnpm config set store-dir "$PNPM_HOME"
- web
script:
- sh ci/build-dist.sh "$BUILD_TARGET" "$PNPM_BUILD_SCRIPT"
- sh ci/build-dist.sh "$BUILD_TARGET" "$PACKAGE_DIR" "$PACKAGE_MANAGER" "$BUILD_COMMAND"
only:
- tags
.release_template: &release_template
stage: release
image: __RELEASE_IMAGE__
image: springjk/ci-helper:latest
tags:
- __RUNNER_TAG__
- web
script:
- sh ci/release-dist.sh "$BUILD_TARGET" "$OSS_DEPLOY_NAME"
only:
......@@ -35,7 +30,7 @@ stages:
build___JOB_SUFFIX__:
<<: *build_template
tags:
- __RUNNER_TAG__
- web
only:
refs:
- tags
......@@ -43,12 +38,14 @@ build___JOB_SUFFIX__:
- $CI_COMMIT_TAG =~ /^__TAG_PREFIX__.*$/
variables:
BUILD_TARGET: "__BUILD_TARGET__"
PNPM_BUILD_SCRIPT: "__PNPM_BUILD_SCRIPT__"
PACKAGE_DIR: "__PACKAGE_DIR__"
PACKAGE_MANAGER: "__PACKAGE_MANAGER__"
BUILD_COMMAND: "__BUILD_COMMAND__"
release___JOB_SUFFIX__:
<<: *release_template
tags:
- __RUNNER_TAG__
- web
only:
refs:
- tags
......@@ -56,4 +53,4 @@ release___JOB_SUFFIX__:
- $CI_COMMIT_TAG =~ /^__TAG_PREFIX__.*$/
variables:
BUILD_TARGET: "__BUILD_TARGET__"
OSS_DEPLOY_NAME: "__MODULE_NAME__"
OSS_DEPLOY_NAME: "__OSS_DEPLOY_NAME__"
......@@ -11,15 +11,12 @@ Use this template when the project needs:
## Replace these placeholders
- `__PACKAGE_DIR__`
- `__RUNNER_TAG__`
- `__BUILD_IMAGE__`
- `__RELEASE_IMAGE__`
- `__BUILD_TARGET__`
- `__INSTALL_COMMAND__`
- `__PACKAGE_MANAGER__`
- `__BUILD_COMMAND__`
- `__MODULE_NAME__`
- `__JOB_SUFFIX__`
- `__TAG_PREFIX__`
- `__OSS_DEPLOY_NAME__`
## Required files
......@@ -29,24 +26,55 @@ Use this template when the project needs:
## Default behavior
1. Build job installs dependencies and runs one target build command.
1. Build job first checks the real repository files, then installs dependencies and runs one target build command.
2. Dist is copied to `$TRANSFER_BASE_DIR/<target>/dist`.
3. Release job creates dated and latest zip files.
4. Each zip is staged inside an upload directory before calling `oss-deploy`.
5. Only tags matching `__TAG_PREFIX__*` trigger this job pair.
6. Prefer an official Node image such as `node:16-alpine` or `node:24-alpine`, selected from project evidence.
7. If the repo uses pnpm but has no `pnpm-lock.yaml`, the build script logs the mismatch and falls back to `pnpm install --no-frozen-lockfile`.
8. For Bingo / SGKT family repositories, `release` defaults to `springjk/ci-helper:latest` and all concrete jobs default to `tags: ["web"]`.
9. The release command shape stays `sh ci/release-dist.sh "$BUILD_TARGET" "$OSS_DEPLOY_NAME"` and this skill does not downgrade release to artifacts-only.
## Package manager rule
Choose install and build commands from the checked-in repository, not from copied comments:
Choose package manager and build command from the checked-in repository, not from copied comments.
Before finalizing CI, verify whether these files really exist in the repository:
- npm repo: `__INSTALL_COMMAND__` might be `npm install`, `__BUILD_COMMAND__` might be `npm run build:demo`
- pnpm repo: `__INSTALL_COMMAND__` might be `pnpm install --frozen-lockfile`, `__BUILD_COMMAND__` might be `pnpm run build:demo`
- yarn repo: `__INSTALL_COMMAND__` might be `yarn install --frozen-lockfile`, `__BUILD_COMMAND__` might be `yarn build:demo`
- `package.json`
- `pnpm-lock.yaml`
- `package-lock.json`
- `yarn.lock`
- `pnpm-workspace.yaml`
Do not hardcode `pnpm` in the template output unless the repository actually uses `pnpm`.
Also verify the real build entrypoints:
- `package.json` scripts
- existing `build*.sh`
- existing `ci/*.sh`
The template shell script branches on the real files that exist at runtime:
- npm repo: set `__PACKAGE_MANAGER__` to `npm`, `__BUILD_COMMAND__` might be `npm run build:demo`
- pnpm repo with `pnpm-lock.yaml`: set `__PACKAGE_MANAGER__` to `pnpm`, `__BUILD_COMMAND__` might be `pnpm run build:demo`
- pnpm repo without `pnpm-lock.yaml`: still set `__PACKAGE_MANAGER__` to `pnpm`, and let the script fall back to `pnpm install --no-frozen-lockfile`
- yarn repo with `yarn.lock`: set `__PACKAGE_MANAGER__` to `yarn`, `__BUILD_COMMAND__` might be `yarn build:demo`
- yarn repo without `yarn.lock`: let the script fall back to `yarn install`
Do not hardcode `pnpm install --frozen-lockfile` unless the repository actually contains `pnpm-lock.yaml`.
If documentation or copied examples claim `pnpm-lock.yaml` exists but the repository does not contain it, follow the repository state, log the mismatch, and update the docs.
Do not use `npm ci` as the default npm install command in this template.
## Release contract rule
When the target repository is part of the Bingo / SGKT family and no stronger repository-specific contract overrides it, keep these elements together:
- `image: springjk/ci-helper:latest`
- `tags: ["web"]`
- `sh ci/release-dist.sh "$BUILD_TARGET" "$OSS_DEPLOY_NAME"`
If `OSS_DEPLOY_NAME` cannot be confirmed from the current repository, infer it from same-family repositories and mark it in the final explanation as an inferred value that can be adjusted without changing the script structure.
## Prefix design rule
On older GitLab versions, use simple non-overlapping prefixes.
......
......@@ -2,22 +2,90 @@
set -eu
BUILD_TARGET="${1:?missing build target}"
PACKAGE_DIR="${PACKAGE_DIR:-__PACKAGE_DIR__}"
PACKAGE_DIR="${2:?missing package dir}"
PACKAGE_MANAGER="${3:?missing package manager}"
BUILD_COMMAND="${4:?missing build command}"
TRANSFER_BASE_DIR="${TRANSFER_BASE_DIR:?missing TRANSFER_BASE_DIR}"
INSTALL_COMMAND="${INSTALL_COMMAND:-__INSTALL_COMMAND__}"
BUILD_COMMAND="${BUILD_COMMAND:-__BUILD_COMMAND__}"
PNPM_HOME="${PNPM_HOME:-/pnpm-store}"
REPO_DIR="$(pwd)"
PACKAGE_DIR_ABS="${REPO_DIR}/${PACKAGE_DIR}"
TARGET_DIR="${TRANSFER_BASE_DIR}/${BUILD_TARGET}"
cd "${PACKAGE_DIR}"
sh -lc "${INSTALL_COMMAND}"
sh -lc "${BUILD_COMMAND}"
if [ ! -d "${PACKAGE_DIR_ABS}" ]; then
echo "未找到包目录: ${PACKAGE_DIR}" >&2
exit 1
fi
if [ ! -f "${PACKAGE_DIR_ABS}/package.json" ]; then
echo "未找到 package.json: ${PACKAGE_DIR}/package.json" >&2
exit 1
fi
run_in_dir() {
RUN_DIR="${1:?missing run dir}"
RUN_COMMAND="${2:?missing run command}"
(
cd "${RUN_DIR}"
sh -lc "${RUN_COMMAND}"
)
}
has_file() {
FILE_NAME="${1:?missing file name}"
[ -f "${REPO_DIR}/${FILE_NAME}" ] || [ -f "${PACKAGE_DIR_ABS}/${FILE_NAME}" ]
}
if [ "${PACKAGE_MANAGER}" = "pnpm" ] && [ -f "${REPO_DIR}/pnpm-workspace.yaml" ]; then
INSTALL_DIR="${REPO_DIR}"
BUILD_DIR="${REPO_DIR}"
else
INSTALL_DIR="${PACKAGE_DIR_ABS}"
BUILD_DIR="${PACKAGE_DIR_ABS}"
fi
case "${PACKAGE_MANAGER}" in
pnpm)
if command -v corepack >/dev/null 2>&1; then
corepack enable
corepack prepare pnpm@10.28.2 --activate
fi
pnpm config set registry https://registry.npmmirror.com
pnpm config set store-dir "${PNPM_HOME}"
if has_file "pnpm-lock.yaml"; then
run_in_dir "${INSTALL_DIR}" "pnpm install --frozen-lockfile"
else
echo "警告: 当前仓库缺少 pnpm-lock.yaml,CI 已回退为 pnpm install --no-frozen-lockfile 以兼容现状。建议后续补齐并提交 pnpm-lock.yaml 以恢复可复现安装。" >&2
run_in_dir "${INSTALL_DIR}" "pnpm install --no-frozen-lockfile"
fi
;;
npm)
run_in_dir "${INSTALL_DIR}" "npm install"
;;
yarn)
if command -v corepack >/dev/null 2>&1; then
corepack enable
fi
if has_file "yarn.lock"; then
run_in_dir "${INSTALL_DIR}" "yarn install --frozen-lockfile"
else
echo "警告: 当前仓库缺少 yarn.lock,CI 已回退为 yarn install 以兼容现状。建议后续补齐并提交 yarn.lock 以恢复更严格的依赖安装。" >&2
run_in_dir "${INSTALL_DIR}" "yarn install"
fi
;;
*)
echo "不支持的包管理器: ${PACKAGE_MANAGER}" >&2
exit 1
;;
esac
run_in_dir "${BUILD_DIR}" "${BUILD_COMMAND}"
if [ ! -d "dist" ]; then
if [ ! -d "${PACKAGE_DIR_ABS}/dist" ]; then
echo "未找到构建产物目录: ${PACKAGE_DIR}/dist" >&2
exit 1
fi
mkdir -p "${TARGET_DIR}"
rm -rf "${TARGET_DIR}/dist"
cp -R dist "${TARGET_DIR}/dist"
cp -R "${PACKAGE_DIR_ABS}/dist" "${TARGET_DIR}/dist"
......@@ -4,16 +4,38 @@
1. Read project `AGENTS.md` and preserve any mandatory comment blocks.
2. Inspect `.gitlab-ci.yml`, root shell scripts, package manager files, and module layout.
3. Verify whether any copied “项目级说明” block is factual for this repo or only a reusable example; do not assume it is true without checking.
4. Map each environment to a concrete build command.
5. Detect the Node major version from primary evidence and prefer an official `node:X-alpine` image with the matching major version.
6. Decide whether helper scripts under `ci/` reduce repetition.
7. Generate `build_x + release_x` pairs only for active environments.
8. If the user wants one tag to run only one pair, assign unique tag prefixes per pair.
9. Remove paused environments completely, including matching release jobs and documentation references.
10. If the repository or user provides a release snippet, preserve its release image, script signature, uploader contract, and variable names unless explicitly asked to migrate.
11. Validate YAML and shell syntax.
12. Update usage docs if you changed active jobs, file names, or release behavior.
3. Collect release-contract evidence in this order: user-provided CI snippets and variable names, current repository CI and release scripts, organization clues inside the current repository, neighboring same-family repositories in the same workspace, then align the generated result to this skill's fixed OSS release pattern.
4. Explicitly verify whether `package.json`, `pnpm-lock.yaml`, `package-lock.json`, `yarn.lock`, and `pnpm-workspace.yaml` really exist before deciding how CI installs dependencies.
5. Verify whether any copied “项目级说明” block is factual for this repo or only a reusable example; do not assume it is true without checking.
6. If the repository name, environments, or neighboring projects indicate Bingo, SGKT, `nceduc`, `dhq`, `xinyu`, or `sr`, start from the organization-standard frontend release model instead of a generic template.
7. Map each environment to a concrete build command.
8. Detect the Node major version from primary evidence and prefer an official `node:X-alpine` image with the matching major version.
9. Decide whether helper scripts under `ci/` reduce repetition.
10. Generate `build_x + release_x` pairs only for active environments.
11. If the user wants one tag to run only one pair, assign unique tag prefixes per pair.
12. Remove paused environments completely, including matching release jobs and documentation references.
13. If the repository or user provides a release snippet, preserve its release image, script signature, uploader contract, and variable names unless explicitly asked to migrate.
14. Validate YAML and shell syntax.
15. Update usage docs if you changed active jobs, file names, release behavior, or lockfile assumptions.
## Organization-standard frontend release model
When any of these signals are present, assume the repository belongs to the organization-standard frontend family first:
- Repository or environment names include `sgkt`, `bingo`, `nceduc`, `dhq`, `xinyu`, or `sr`
- The repository uses `springjk/webdev`
- The repository contains multiple `.env.*` files and multiple `build:*` scripts
- The same workspace contains clearly related frontend repositories
The default model is:
- `build_x + release_x`
- `release` uses `springjk/ci-helper:latest`
- Every concrete job uses `tags: ["web"]`
- `release` calls `sh ci/release-dist.sh "$BUILD_TARGET" "$OSS_DEPLOY_NAME"`
- Release uploads dated and latest packages through `oss-deploy`
If current-repository evidence is incomplete, search same-family neighbors and still keep the final result on the fixed OSS release pattern.
## Conservative syntax for GitLab 11.x
......@@ -30,7 +52,7 @@ stages:
tags:
- web
script:
- sh ci/build-dist.sh "$BUILD_TARGET" "$PNPM_BUILD_SCRIPT"
- sh ci/build-dist.sh "$BUILD_TARGET" "$PACKAGE_DIR" "$PACKAGE_MANAGER" "$BUILD_COMMAND"
only:
- tags
```
......@@ -49,7 +71,9 @@ build_demo:
- $CI_COMMIT_TAG =~ /^demo-default-.*$/
variables:
BUILD_TARGET: "demo"
PNPM_BUILD_SCRIPT: "build:demo"
PACKAGE_DIR: "."
PACKAGE_MANAGER: "pnpm"
BUILD_COMMAND: "pnpm run build:demo"
```
If you need per-environment selective triggering on GitLab 11.x, prefer `only: refs + variables` with disjoint prefixes.
......@@ -108,11 +132,14 @@ If the repository has two or more frontend modules, start from `assets/gitlab-mu
Pick the install command from real repository evidence:
- `pnpm install --frozen-lockfile` when the repo actually uses `pnpm`
- `pnpm install --frozen-lockfile` only when the repo actually uses `pnpm` and really contains `pnpm-lock.yaml`
- `pnpm install --no-frozen-lockfile` when the repo uses `pnpm` but `pnpm-lock.yaml` is currently missing
- `npm install` when the repo actually uses `npm`
- `yarn install --frozen-lockfile` when the repo actually uses `yarn`
- `yarn install --frozen-lockfile` when the repo actually uses `yarn` and really contains `yarn.lock`
- `yarn install` when the repo uses `yarn` but `yarn.lock` is currently missing
Do not force `pnpm` because a copied comment block says so if the checked-in repository does not support it.
If copied docs or examples mention `pnpm-lock.yaml` but the repository does not contain it, treat that as a documentation mismatch, follow the repository state, and fix the docs.
Do not use `npm ci` in this skill. For npm-based repositories, use `npm install`.
## Default release script behavior
......@@ -125,8 +152,9 @@ Do not use `npm ci` in this skill. For npm-based repositories, use `npm install`
4. If uploader only accepts directories, copy each zip into a dedicated upload directory.
5. Call the uploader for the dated package and latest package.
Default to OSS-oriented release logic. Only switch to server-directory deployment when the repository already uses that model and the user wants to preserve it.
Default to OSS-oriented release logic in this skill. Do not switch to artifacts-only release.
If the repository or the user already established a release contract such as `springjk/ci-helper:latest` and `OSS_DEPLOY_NAME`, preserve that contract exactly unless the user explicitly requests a migration.
If any evidence establishes the triple `springjk/ci-helper:latest` plus `tags: ["web"]` plus `sh ci/release-dist.sh "$BUILD_TARGET" "$OSS_DEPLOY_NAME"`, keep all three together. Do not downgrade to generic Alpine, omit the `web` runner tag, or replace the script with artifacts-only behavior.
## Common failure patterns
......@@ -166,6 +194,11 @@ Prefer official images such as `node:16-alpine` or `node:24-alpine`, selected fr
Some repositories or runner environments fail on `npm ci` even though `npm install` works normally.
For npm-based repositories in this skill, always use `npm install`.
### `ERR_PNPM_NO_LOCKFILE`
CI used `pnpm install --frozen-lockfile`, but the repository does not actually contain `pnpm-lock.yaml`.
Treat the missing lockfile as the root cause, fall back to `pnpm install --no-frozen-lockfile`, and update the docs so they stop claiming the lockfile exists.
### Uploader says source path not found
The uploader may expect a directory instead of a file.
......@@ -177,6 +210,13 @@ The skill may have simplified release into `artifacts`, a generic Alpine image,
Re-check whether the repository or the user already specified a release image, `oss-deploy`, `OSS_DEPLOY_NAME`, or a fixed `ci/release-dist.sh` call shape.
When those exist, they are not optional hints. Restore them and align every `release_x` job to the same contract.
### Current repo looks blank but same-family repos already use OSS release
Some Bingo or SGKT repositories have incomplete local CI history even though the organization contract is stable in neighboring repositories.
Do not conclude “no OSS contract” from the current repo alone.
Search same-family repositories in the workspace for `springjk/ci-helper:latest`, `tags: [web]`, `OSS_DEPLOY_NAME`, `oss-deploy`, and `ci/release-dist.sh`.
If those neighbors consistently use the organization contract, inherit that contract and mark `OSS_DEPLOY_NAME` mappings as confirmed or inferred in the final explanation.
### UI shows jobs the user no longer wants
Remove the matching `build_x` and `release_x` jobs together.
......@@ -188,7 +228,10 @@ Do not do these unless the user explicitly wants them:
- Do not replace the standard `build_x + release_x` OSS flow with server-directory deployment.
- Do not replace an existing or user-specified OSS release template such as `springjk/ci-helper:latest` plus `sh ci/release-dist.sh "$BUILD_TARGET" "$OSS_DEPLOY_NAME"` with a generic image, `artifacts`, or another release contract.
- Do not preserve only one or two parts of the organization-standard release triple. If `springjk/ci-helper:latest`, `tags: ["web"]`, and `sh ci/release-dist.sh "$BUILD_TARGET" "$OSS_DEPLOY_NAME"` are established, keep all three.
- Do not use custom repository-specific Node images when an official `node:X-alpine` image can be selected from project evidence.
- Do not treat copied “项目级说明” sections as proven facts without checking the repository.
- Do not hardcode `pnpm install --frozen-lockfile` unless `pnpm-lock.yaml` really exists in the repository.
- Do not use `npm ci` as the default npm install command.
- Do not leave only `build_x` without `release_x` when the default OSS release pattern is expected.
- Do not output artifacts-only release from this skill. The fixed mode is `build_x + release_x` plus OSS upload.
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论