Use this skill to create or repair GitLab CI for projects that need conservative, reviewable pipelines.
## Workflow
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.
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.
Determine whether the project builds one module or multiple modules.
Determine whether the project expects `build_x + release_x` pairs.
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.
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`.
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.
3. Confirm the GitLab compatibility target.
If the user mentions an old GitLab version such as 11.x, assume conservative compatibility.
For GitLab 11.1.4 or similarly old versions, avoid `extends`, `needs`, `rules`, and other newer pipeline features unless verified locally or explicitly provided by the user.
Prefer `stages`, `variables`, `only`, explicit `tags`, and YAML anchors.
Detect the project Node major version from primary sources such as `package.json``engines`, `package.json``volta`, `.nvmrc`, `.node-version`, existing official `node:X` images in CI, or the project’s documented runtime. Prefer official images like `node:16-alpine` or `node:24-alpine`. Do not introduce repository-specific custom build images unless the repository already depends on them or the user explicitly requires them.
4. Generate outputs as code, not just advice.
Create or update `.gitlab-ci.yml`.
Create helper shell scripts when the build or release logic is repeated.
Create or update a usage document when the pipeline is non-trivial.
Preserve mandatory comment blocks exactly when the user says they must not change.
If the request matches the default legacy pattern, start from the bundled asset template instead of rewriting the whole skeleton from scratch.
## Output Pattern
Default to this structure unless the repo clearly needs something else:
-`build_*` jobs only compile and copy dist to a shared transfer directory.
-`release_*` jobs only package and publish from the transfer directory.
- Dist paths are isolated by module or environment, for example `/cache/transfer/${CI_PIPELINE_ID}/demo/dist`.
- 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.
- 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
When targeting old GitLab versions:
- Use explicit `tags:` on every concrete job.
- Use `only: [tags]` when the project wants tag-only pipelines.
- Use `only: refs + variables` when the project wants tag-prefix routing.
- Use stage ordering instead of `needs`.
- Use YAML anchors instead of `extends`.
- Do not introduce `rules`, `workflow`, `parallel:matrix`, child pipelines, or other modern features unless verified.
- Avoid advanced regex such as negative lookahead on GitLab 11.1.4. Prefer disjoint prefixes like `demo-default-*` and `demo-bingo-*` instead of overlapping patterns that require complex expressions.
- Keep syntax flat and predictable.
## Recommended Prefix Naming
When the project uses tag-prefix routing, prefer readable and mutually exclusive prefixes.
- For a default environment plus a variant, use `demo-default-*` and `demo-bingo-*` instead of `demo-*` and `demo-bingo-*`.
- For multiple independent environments, use full environment names such as `dev-bingo-*`, `qsh-bingo-*`, and `demo-bingo-*`.
- For multiple frontend modules, use module names such as `web-*` and `admin-*`.
- If the user wants one tag to trigger all pairs, introduce a dedicated shared prefix such as `all-*` and add it explicitly to each intended job pair.
Prefer explicitness over brevity. On older GitLab versions, a slightly longer but non-overlapping prefix is safer than a short prefix that requires complex regex to disambiguate.
## Build and Release Rules
Keep build and release separated unless the user explicitly asks to combine them.
For each environment or module X:
- Add `build_x` and `release_x` together.
- 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 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.
## Shell Script Rules
When the repository already uses `.sh` build scripts:
- Prefer calling repository shell scripts or their equivalent logic from CI.
- If several jobs share the same sequence, factor it into `ci/build-dist.sh` and `ci/release-dist.sh`.
- 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`.
- For npm-based repositories in this skill, use `npm install`. Do not use `npm ci`.
## Validation
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.
- Every concrete job has `tags: ["web"]` if the project requires that runner tag.
- 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.
- 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.
## References
Read [`references/legacy-gitlab-patterns.md`](references/legacy-gitlab-patterns.md) when you need the detailed checklist, compatibility notes, or reusable output skeleton.
## 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__`.
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-*`.
Use this template when the repository has multiple frontend modules such as `web` and `admin`.
## Replace these placeholders
-`__RUNNER_TAG__`
-`__BUILD_IMAGE__`
-`__RELEASE_IMAGE__`
-`__WEB_PACKAGE_DIR__`
-`__WEB_INSTALL_COMMAND__`
-`__WEB_BUILD_COMMAND__`
-`__WEB_DIST_SUBDIR__`
-`__WEB_OSS_NAME__`
-`__WEB_TAG_PREFIX__`
-`__ADMIN_PACKAGE_DIR__`
-`__ADMIN_INSTALL_COMMAND__`
-`__ADMIN_BUILD_COMMAND__`
-`__ADMIN_DIST_SUBDIR__`
-`__ADMIN_OSS_NAME__`
-`__ADMIN_TAG_PREFIX__`
## Default behavior
1.`build_web` and `build_admin` compile independently.
2. Each module writes its output to its own transfer directory.
3.`release_web` and `release_admin` package and upload independently.
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.
## 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
Do not assume all modules use `pnpm` just because one copied project note mentions it.
Do not use `npm ci` as the default npm install command in this template.
## Prefix design rule
On older GitLab versions, use simple non-overlapping prefixes.
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.
If you need per-environment selective triggering on GitLab 11.x, prefer `only: refs + variables` with disjoint prefixes.
Good: `demo-default-*` and `demo-bingo-*`
Bad: `demo-*` and `demo-bingo-*` together, because the first pattern also matches the second.
## Recommended prefix conventions
Use prefixes that are:
1. Human-readable
2. Mutually exclusive
3. Stable across time
Recommended patterns:
- Single environment only: keep one explicit prefix such as `release-*`
- Default plus variant: `demo-default-*`, `demo-bingo-*`
- Many environments: `dev-bingo-*`, `qsh-bingo-*`, `demo-bingo-*`
- Many modules: `web-*`, `admin-*`
- Run all pairs intentionally: `all-*`
Avoid designing prefixes that need complex regex subtraction to distinguish them.
Avoid these on old GitLab unless verified:
-`extends`
-`needs`
-`rules`
-`workflow`
-`parallel:matrix`
- child pipeline syntax
- negative lookahead or other advanced regex features that older GitLab variable expressions may reject
## Default file set
When the request is to build a reusable CI setup, create these files when useful:
-`.gitlab-ci.yml`
-`ci/build-dist.sh`
-`ci/release-dist.sh`
-`CI_USAGE.md`
If the repository follows the common frontend release pattern, start from the bundled asset template in `assets/gitlab-tag-release-template/` and then trim or expand jobs to match only the active environments.
If the repository has two or more frontend modules, start from `assets/gitlab-multi-frontend-template/` and replace the module-specific placeholders before adding or removing pairs.
## Default build script behavior
`ci/build-dist.sh` usually should:
1. Read build target and build command name from arguments.
2. Install dependencies with the repository package manager.
3. Enter the module directory.
4. Run the specific build command.
5. Copy `dist` to `$TRANSFER_BASE_DIR/<target>/dist`.
Pick the install command from real repository evidence:
-`pnpm install --frozen-lockfile` when the repo actually uses `pnpm`
-`npm install` when the repo actually uses `npm`
-`yarn install --frozen-lockfile` when the repo actually uses `yarn`
Do not force `pnpm` because a copied comment block says so if the checked-in repository does not support it.
Do not use `npm ci` in this skill. For npm-based repositories, use `npm install`.
## Default release script behavior
`ci/release-dist.sh` usually should:
1. Read build target and publish name from arguments.
2. Load dist from `$TRANSFER_BASE_DIR/<target>/dist`.
3. Generate dated and latest zip files.
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.
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.
## Common failure patterns
### `unknown keys: extends`
The GitLab instance is too old for the chosen syntax.
Replace `extends` with YAML anchors and merge keys.
### `unknown keys: needs`
Use stage ordering only.
Do not add `needs` on GitLab 11.1.4.
### `only 变量 invalid expression syntax`
The GitLab instance may reject advanced regex syntax.
Replace complex expressions with simple prefix matches and redesign prefixes so they do not overlap.
Example: replace `demo-*` plus `demo-bingo-*` with `demo-default-*` plus `demo-bingo-*`.
### Tag can’t trigger any job
The configured prefixes may not match the tag the user is actually creating, or the CI may have been rewritten from selective routing to all-tags or vice versa without confirming intent.
Re-check the real build-script-to-prefix mapping and ensure the selected tag format matches one concrete `build_x + release_x` pair.
### Skill copied a sample project section as if it were real
The “第二部分:项目级说明” may be a reusable example rather than a description of the current repository.
Treat it as a hint, then confirm against `package.json`, root scripts, and existing CI before applying it.
### Custom repository image drift
Using a project-specific custom image can hide the actual Node version decision and make CI less portable.
Prefer official images such as `node:16-alpine` or `node:24-alpine`, selected from project evidence.
### `npm ci` breaks the build setup
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`.
### Uploader says source path not found
The uploader may expect a directory instead of a file.
Stage the zip file inside an upload directory, then pass the directory path to the uploader.
### Release template lost the required OSS contract
The skill may have simplified release into `artifacts`, a generic Alpine image, or a different script signature and accidentally removed the real uploader flow.
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.
### UI shows jobs the user no longer wants
Remove the matching `build_x` and `release_x` jobs together.
Also remove outdated references in usage docs and sample paths.
## Anti-patterns
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 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 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.