Skip to content

feat: support spec:inputs for root pipeline via --input and --inputs-file - #1814

Merged
firecow merged 4 commits into
firecow:masterfrom
gyanranjan:feat/spec-inputs-pipeline
Apr 17, 2026
Merged

feat: support spec:inputs for root pipeline via --input and --inputs-file#1814
firecow merged 4 commits into
firecow:masterfrom
gyanranjan:feat/spec-inputs-pipeline

Conversation

@gyanranjan

@gyanranjan gyanranjan commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds support for providing spec:inputs values to the root .gitlab-ci.yml pipeline file — not just included files. This closes #1720.

Problem

When the root .gitlab-ci.yml uses spec:inputs, there was no way to pass values for required inputs (those without a default). The root file was always loaded with an empty context:

// parser.ts — was:
const gitlabCiData = await Parser.loadYaml(`${cwd}/${file}`, {}, ...);

Inputs on included files worked fine because parser-includes.ts forwarded value.inputs from the include: block. But the root file had no equivalent mechanism.

Solution

New CLI options

  • --input KEY=value (repeatable) — provide global inputs
  • --input component:KEY=value — provide component-specific inputs
  • --inputs-file PATH — path to inputs YAML file (default: .gitlab-ci-local-inputs.yml)

Inputs file formats

Flat (all inputs are global):

environment: production
replicas: 5

Structured (global + component-specific):

_global:
  environment: production
deploy:
  replicas: 5
build:
  go_version: "1.21"

Priority order (highest to lowest)

  1. Component-specific CLI inputs (--input component:key=value)
  2. Component-specific file inputs
  3. Global CLI inputs (--input key=value)
  4. Global file inputs (_global: or flat format)
  5. Inline inputs in .gitlab-ci.yml (include: inputs:)
  6. Spec defaults

Changes

File Change
src/argv.ts inputsFile getter, input getter with key=value and component:key=value parsing
src/index.ts --inputs-file and --input yargs options
src/parser.ts loadInputs() method; root loadYaml() now passes {inputs: rootInputs} instead of {}; absolute path support for --inputs-file
src/parser-includes.ts inputs in opts type; merge logic for global/component-specific inputs across all include types
tests/test-cases/component-inputs-cli/ New test case: single component with CLI and file inputs
tests/test-cases/component-inputs-multiple/ New test case: multiple components with structured inputs
tests/test-cases/.gitignore Exceptions for .gitlab-ci-local-inputs.yml fixture files

Testing

  • 29 input-related tests pass (19 existing + 10 new)
  • 130 core unit tests pass
  • 88 non-Docker integration tests pass — zero regressions

Corner cases verified

  • Root required input via --input
  • Root defaults used when not overridden ✅
  • CLI overrides file inputs ✅
  • Structured file with _global:
  • Number/boolean type coercion ✅
  • Options validation (valid + invalid) ✅
  • Dynamic job names using inputs ✅
  • Inputs in include paths ✅
  • Type mismatch rejection ✅
  • Normal pipelines without spec:inputs unaffected ✅
  • Absolute --inputs-file path ✅

Example

# .gitlab-ci.yml
---
spec:
  inputs:
    environment:
      type: string
      default: dev
    build_type:
      type: string
---
stages:
  - build
build-job:
  stage: build
  script:
    - echo "env=$[[ inputs.environment ]] type=$[[ inputs.build_type ]]"
# Before: fails with "build_type input: required value has not been provided"
gitlab-ci-local --list

# After: works
gitlab-ci-local --input build_type=nightly --list
gitlab-ci-local --input build_type=nightly --input environment=prod --list

Fixes #1720


Summary by cubic

Adds support for passing spec:inputs to the root .gitlab-ci.yml, with values from CLI or an inputs file. Inputs are applied to the root and all include types.

  • New Features

    • --input KEY=value (repeatable) and --input component:KEY=value for global and component inputs.
    • --inputs-file PATH (default: .gitlab-ci-local-inputs.yml) with flat or structured formats (_global + components).
    • Inputs are merged into the root pipeline and all include types (local, project, component, template, remote).
    • Precedence: component CLI > file component > global CLI > file global > inline include inputs > spec defaults.
  • Bug Fixes

    • Corrected merge order and separated CLI namespaces so component-specific CLI values win over global CLI on conflicts.
    • Allowed / in component names and blocked prototype-pollution keys (__proto__, constructor, prototype) in --input.
    • Support absolute paths for --inputs-file; ESLint cleanups (no functional changes).

Written for commit 4c3c13f. Summary will update on new commits.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 15 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/parser-includes.ts">

<violation number="1" location="src/parser-includes.ts:177">
P1: Component input merge order is incorrect: trailing `cliGlobalInputs` overrides component-specific CLI inputs on key conflicts.</violation>
</file>

<file name="src/argv.ts">

<violation number="1" location="src/argv.ts:226">
P2: Component-specific `--input` parsing is too restrictive (`[\w-]+`) and can misparse valid component names containing `/`, causing inputs to be dropped or assigned to the wrong component.</violation>

<violation number="2" location="src/argv.ts:243">
P1: Global and component inputs share one object namespace, allowing key collisions that can cause runtime errors or overwrite component data.</violation>

<violation number="3" location="src/argv.ts:243">
P1: User-controlled `component`/`key` are assigned into a plain object, allowing `__proto__`-based prototype pollution via `--input` parsing.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/parser-includes.ts Outdated
Comment thread src/argv.ts Outdated
Comment thread src/argv.ts Outdated
Comment thread src/argv.ts Outdated
@gyanranjan

Copy link
Copy Markdown
Contributor Author

@firecow could you please review this PR when you get a chance?

This adds support for root spec:inputs via --input and --inputs-file. I’ve included tests as well. I don’t seem to have permission to assign reviewers from my side, so tagging here for visibility.

@firecow

firecow commented Apr 16, 2026

Copy link
Copy Markdown
Owner

Nice feature — root spec:inputs support is a real gap. A few things I noticed:

1. Precedence order in the description doesn't match the code

The description says global CLI (#2) beats component-specific file (#3), but parser-includes.ts:177 does the opposite:

const mergedInputs = {...(value.inputs ?? {}), ...globalInputs, ...fileComponentInputs, ...cliComponentSpecificInputs};

The test "file component-specific overrides global CLI" confirms this — file component-specific wins over global CLI. The code behavior makes sense (more specific wins), the docs just need updating.

2. Global and component inputs share one object in argv.input

At argv.ts:247-251, --input deploy=prod sets inputs["deploy"] = "prod". A subsequent --input deploy:replicas=5 checks !inputs["deploy"] which is falsy (truthy string), skips the object initialization at line 247, and inputs["deploy"]["replicas"] = 5 silently no-ops (property assignment on a string primitive). The reverse order also breaks — the global string overwrites the component object.

Unlikely in practice (requires naming a global input the same as a component), but the fix is straightforward — separate the namespaces.

3. Duplicated structured-format detection

The isStructured heuristic and global-input extraction is nearly identical at parser.ts:119-121 and parser-includes.ts:76-77. Worth extracting to a shared helper so they can't drift apart.

4. JSON.parse coercion can cause confusing type errors

At argv.ts:239-242, --input port=8080 for a type: string input gets parsed to number 8080 by JSON.parse, then fails spec validation with "provided value is not a string." The error is clear enough, but the cause isn't obvious to the user. Not a blocker, just something to be aware of — could note it in the --input help text or consider only coercing when the spec declares a non-string type.

@gyanranjan

Copy link
Copy Markdown
Contributor Author

Thanks for the review! Pushed a fix commit (0362bc8) addressing your feedback:

#1 — Precedence docs: You're right, the code behavior (more specific wins) is correct. Updated the PR description to match:

  1. Component-specific CLI inputs
  2. Component-specific file inputs
  3. Global CLI inputs
  4. Global file inputs
  5. Inline inputs in include blocks
  6. Spec defaults

#2 — Namespace collision: Separated argv.input into { _global: {...}, _components: {...} } so global and component keys can never collide. Added a test for the --input deploy=prod --input deploy:replicas=5 scenario.

#3 — Duplicated isStructured detection: Extracted Utils.isStructuredInputsFile() and Utils.getGlobalFileInputs() — both parser.ts and parser-includes.ts now use the shared helpers.

#4 — JSON.parse coercion: Noted — will leave as-is for now since it's not a blocker. Can add a help text note in a follow-up if you'd like.

All 29 input-related tests pass + 487 non-Docker tests pass with zero regressions.

@gyanranjan
gyanranjan force-pushed the feat/spec-inputs-pipeline branch from 0362bc8 to d219942 Compare April 17, 2026 09:52
@firecow

firecow commented Apr 17, 2026

Copy link
Copy Markdown
Owner

Thanks for the review! Pushed a fix commit (0362bc8) addressing your feedback:

#1 — Precedence docs: You're right, the code behavior (more specific wins) is correct. Updated the PR description to match:

  1. Component-specific CLI inputs
  2. Component-specific file inputs
  3. Global CLI inputs
  4. Global file inputs
  5. Inline inputs in include blocks
  6. Spec defaults

#2 — Namespace collision: Separated argv.input into { _global: {...}, _components: {...} } so global and component keys can never collide. Added a test for the --input deploy=prod --input deploy:replicas=5 scenario.

#3 — Duplicated isStructured detection: Extracted Utils.isStructuredInputsFile() and Utils.getGlobalFileInputs() — both parser.ts and parser-includes.ts now use the shared helpers.

#4 — JSON.parse coercion: Noted — will leave as-is for now since it's not a blocker. Can add a help text note in a follow-up if you'd like.

All 29 input-related tests pass + 487 non-Docker tests pass with zero regressions.

Kewl, god job.. Test job is failing.

Gyan Ranjan A added 4 commits April 17, 2026 14:42
…file

Add support for providing spec:inputs values to the root .gitlab-ci.yml
pipeline file, not just included files. This closes firecow#1720.

Changes:
- Add --input KEY=value CLI flag (repeatable) for global inputs
- Add --input component:KEY=value syntax for component-specific inputs
- Add --inputs-file flag (default: .gitlab-ci-local-inputs.yml)
- Pass inputs to root loadYaml() call in parser.ts (was hardcoded {})
- Merge external inputs into all include types (local, project,
  component, template, remote)
- Support structured inputs file with _global and component sections
- Fix absolute path handling for --inputs-file

Priority order (highest to lowest):
1. Component-specific CLI inputs (--input component:key=value)
2. Global CLI inputs (--input key=value)
3. Component-specific file inputs
4. Global file inputs (_global or flat format)
5. Inline inputs in .gitlab-ci.yml (include: inputs:)
6. Spec defaults

Fixes: firecow#1720
- Fix component input merge order: remove duplicate cliGlobalInputs
  spread that caused global CLI to override component-specific CLI
  inputs on key conflicts (P1)
- Broaden component name regex to allow '/' for paths like
  templates/deploy (P2)
- Add prototype pollution guard for __proto__, constructor,
  prototype keys in --input parsing (P1)
- Add 7 new tests covering edge cases
- Fix existing test that relied on buggy merge order
…uctured helper

Addresses review feedback:
- Separate global and component CLI inputs into _global/_components
  namespaces in argv.input to prevent key collisions (firecow#2)
- Extract isStructuredInputsFile() and getGlobalFileInputs() to Utils
  to deduplicate detection logic in parser.ts and parser-includes.ts (firecow#3)
- Add test for namespace collision scenario
@gyanranjan
gyanranjan force-pushed the feat/spec-inputs-pipeline branch from d219942 to 4c3c13f Compare April 17, 2026 14:47
@gyanranjan

Copy link
Copy Markdown
Contributor Author

Rebased onto latest master (67e7873) and force-pushed. The CI failures in the previous run were:

  • 8 × services/integration.test.ts — Docker container timeouts (60s)
  • 2 × predefined-variables/integration.test.ts — race condition (still running... line bleeding into output under load)

Verified locally that these are not caused by our changes — ran both test suites on clean master and on our branch back-to-back, got identical results (same 8 Docker timeouts on both, predefined-variables normal/custom-ports pass on both).

Our branch adds 16 tests across 6 new test files (552 → 568 total). All lightweight parsing tests, but the extra concurrency might be enough to push the service tests past their 60s timeout on a slower runner. The branch diff touches zero lines in the failing test files.

Could you re-run the CI on this latest push? Should be clean on a fresh runner.

@firecow

firecow commented Apr 17, 2026

Copy link
Copy Markdown
Owner

Rebased onto latest master (67e7873) and force-pushed. The CI failures in the previous run were:

  • 8 × services/integration.test.ts — Docker container timeouts (60s)
  • 2 × predefined-variables/integration.test.ts — race condition (still running... line bleeding into output under load)

Verified locally that these are not caused by our changes — ran both test suites on clean master and on our branch back-to-back, got identical results (same 8 Docker timeouts on both, predefined-variables normal/custom-ports pass on both).

Our branch adds 16 tests across 6 new test files (552 → 568 total). All lightweight parsing tests, but the extra concurrency might be enough to push the service tests past their 60s timeout on a slower runner. The branch diff touches zero lines in the failing test files.

Could you re-run the CI on this latest push? Should be clean on a fresh runner.

Oh, I didn't notice it was exiting unchanged tests that failed. Sorry about that, I'll take care of those flaky tests.

@firecow firecow left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@firecow
firecow merged commit 06b3fdb into firecow:master Apr 17, 2026
13 checks passed
@WatchTh1s

WatchTh1s commented Apr 29, 2026

Copy link
Copy Markdown

Seems like failing to me on with:

$ ~/.local/bin/gitlab-ci-local build-backend --input push_containers=true
parsing and downloads finished in 141 ms.
json schema validated in 308 ms
build-backend starting my.gitlab.dc/service-containers/kaniko-project/executor:v1.24.0-debug (build)
build-backend copied to docker volumes in 447 ms
build-backend imported artifacts in 28 ms
build-backend $ /bin/bash scripts/registry-login.sh
build-backend > [2026-04-29 11:55:09] Docker config for local-registry готов в /kaniko/.docker//config.json
build-backend $ /bin/bash scripts/buidscript.sh backend database $[[ inputs.push_containers ]]
build-backend > /gcl-cmd: line 7: [ inputs.push_containers ]: syntax error: operand expected (error token is "[ inputs.push_containers ]")
build-backend finished in 873 ms

Job is:

build-backend:
  extends:
    - .base-backend-job
    - .base-docker-job
  stage: build
  needs:
    - unit-test-backend
  script:
    - /bin/bash scripts/buidscript.sh backend database $[[ inputs.push_containers ]]

spec is:

spec:
  inputs:
    push_containers:
      type: boolean
      default: false
      description: "Allow containers to be pushed to registry"

master commit:

commit 12dd4ef8ce6e5085dcf92f9f04e06866b81e1d8b (HEAD -> master, origin/master, origin/HEAD)
Author: Paul Goulpié <46163406+Paul-Goulpie@users.noreply.github.com>
Date:   Wed Apr 29 11:10:42 2026 +0200

    fix: pass artifact paths to rsync via `--files-from`  (#1825)

@gyanranjan

Copy link
Copy Markdown
Contributor Author

@WatchTh1s Thanks for reporting this. A few things to check:

  1. Which version are you running? Can you confirm with gitlab-ci-local --version? The --input flag was added in this PR and hasn't been released to npm yet (latest npm is 4.71.0, which predates this merge). If you installed via npm/pipx, you'd need to build from source (bun install && bun run build) from current master.

  2. Is the $[[ inputs.push_containers ]] reference in the root .gitlab-ci.yml or in an included/extended file? Root spec:inputs interpolation only applies to the file that contains the spec: header. If the $[[ ]] token is in a file pulled in via include:, it won't be interpolated by the root spec.

  3. Do you have the --- YAML document separator between the spec: block and your jobs? It should look like:

    spec:
      inputs:
        push_containers:
          type: boolean
          default: false
    ---
    build-backend:
      script:
        - /bin/bash scripts/buidscript.sh backend database $[[ inputs.push_containers ]]

tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request May 13, 2026
This MR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Adoption](https://docs.renovatebot.com/merge-confidence/) | [Passing](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|---|---|
| [npm:gitlab-ci-local](https://github.com/firecow/gitlab-ci-local) | `4.71.0` → `4.72.0` | ![age](https://developer.mend.io/api/mc/badges/age/npm/gitlab-ci-local/4.72.0?slim=true) | ![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/gitlab-ci-local/4.72.0?slim=true) | ![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/gitlab-ci-local/4.71.0/4.72.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/gitlab-ci-local/4.71.0/4.72.0?slim=true) |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>firecow/gitlab-ci-local (npm:gitlab-ci-local)</summary>

### [`v4.72.0`](https://github.com/firecow/gitlab-ci-local/releases/tag/4.72.0)

[Compare Source](firecow/gitlab-ci-local@4.71.0...4.72.0)

#### What's Changed

- feat: support spec:inputs for root pipeline via --input and --inputs-file by [@&#8203;gyanranjan](https://github.com/gyanranjan) in [#&#8203;1814](firecow/gitlab-ci-local#1814)
- feat: implement workflow:rules:variables support ([#&#8203;1832](firecow/gitlab-ci-local#1832)) by [@&#8203;bcouetil](https://github.com/bcouetil) in [#&#8203;1833](firecow/gitlab-ci-local#1833)
- chore(deps): lock file maintenance by [@&#8203;renovate](https://github.com/renovate)\[bot] in [#&#8203;1834](firecow/gitlab-ci-local#1834)
- chore(deps): update all non-major by [@&#8203;renovate](https://github.com/renovate)\[bot] in [#&#8203;1835](firecow/gitlab-ci-local#1835)
- fix: pass artifact paths to rsync via `--files-from`  by [@&#8203;Paul-Goulpie](https://github.com/Paul-Goulpie) in [#&#8203;1825](firecow/gitlab-ci-local#1825)
- chore(deps): update sonarsource/sonarqube-scan-action action to v8 by [@&#8203;renovate](https://github.com/renovate)\[bot] in [#&#8203;1842](firecow/gitlab-ci-local#1842)
- fix: connect service containers to local registry network by [@&#8203;firecow](https://github.com/firecow) in [#&#8203;1847](firecow/gitlab-ci-local#1847)
- test: ignore 'still running' heartbeat in stdout assertions by [@&#8203;firecow](https://github.com/firecow) in [#&#8203;1851](firecow/gitlab-ci-local#1851)
- chore(deps): update github/codeql-action action to v4.35.4 by [@&#8203;renovate](https://github.com/renovate)\[bot] in [#&#8203;1843](firecow/gitlab-ci-local#1843)
- chore(deps): lock file maintenance by [@&#8203;renovate](https://github.com/renovate)\[bot] in [#&#8203;1845](firecow/gitlab-ci-local#1845)
- feat: add environment column to --list and --list-csv ([#&#8203;1837](firecow/gitlab-ci-local#1837)) by [@&#8203;bcouetil](https://github.com/bcouetil) in [#&#8203;1838](firecow/gitlab-ci-local#1838)
- feat: support needs\[].parallel.matrix and matrix expressions by [@&#8203;inistor](https://github.com/inistor) in [#&#8203;1848](firecow/gitlab-ci-local#1848)
- fix: reject empty rules array instead of silently skipping job by [@&#8203;firecow](https://github.com/firecow) in [#&#8203;1852](firecow/gitlab-ci-local#1852)
- fix: respect IGNORE\_PREDEFINED\_VARS in .gitlab-ci-local-env by [@&#8203;firecow](https://github.com/firecow) in [#&#8203;1853](firecow/gitlab-ci-local#1853)
- fix: reject ${VAR} in rules:if by [@&#8203;firecow](https://github.com/firecow) in [#&#8203;1854](firecow/gitlab-ci-local#1854)
- fix: wait for child stdio close before resolving exec by [@&#8203;firecow](https://github.com/firecow) in [#&#8203;1855](firecow/gitlab-ci-local#1855)
- chore: pin third-party actions to commit SHAs by [@&#8203;firecow](https://github.com/firecow) in [#&#8203;1857](firecow/gitlab-ci-local#1857)

#### New Contributors

- [@&#8203;gyanranjan](https://github.com/gyanranjan) made their first contribution in [#&#8203;1814](firecow/gitlab-ci-local#1814)
- [@&#8203;inistor](https://github.com/inistor) made their first contribution in [#&#8203;1848](firecow/gitlab-ci-local#1848)

**Full Changelog**: <firecow/gitlab-ci-local@4.71.0...4.72.0>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNzMuNSIsInVwZGF0ZWRJblZlciI6IjQzLjE3My41IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiLCJhdXRvbWF0aW9uOmJvdC1hdXRob3JlZCIsImRlcGVuZGVuY3ktdHlwZTo6bWlub3IiXX0=-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support spec::inputs for pipelines

3 participants