Capstone
The capstone closes the core path of the workshop. It is a small genomics-flavoured QC pipeline that combines every migration pattern from Parts 1–5: script-level syntax, module includes, process syntax, and strict-compatible nf-core-style config. Finish this exercise and you have the full strict-syntax migration habit; the extended path (Parts 6–8) is then optional.
Learning goals
- Apply the strict migration checklist across
main.nf, modules, processes, and config in a single small pipeline. - Keep the migrated pipeline behaviour-equivalent to the legacy version (same inputs, same per-sample outputs).
- Verify the migrated code with
nextflow lint, a smoke run, and annf-testsmoke test.
The pipeline
Two synthetic sample files (data/sample_a.txt, data/sample_b.txt) act as tiny placeholder inputs. The pipeline reads each one, counts lines as "reads", carries metadata (id, cohort), threads a RUN_ID env value through the process, and emits one TSV per sample:
sample sample_a
cohort validation
suffix _qc
run_id exercise_run
reads 3
That is the behaviour you must preserve. Everything else is syntax.
1. Start from the exercise
cd code/06-capstone/exercise
nextflow lint -project-dir . main.nf
nextflow run main.nf -profile test
The starting point fails on the first parser error it meets (the addParams clause). Migrate it in deliberate small steps so that each step's failure mode is the next learning signal:
- Script-level syntax in
main.nf(Part 2): dropString run_id = ...todef run_id, lowercaseChannel, use explicit closure parameters. - Workflow / module boundary (Part 3): remove
addParamsfrom theinclude, wrap the process in a namedQC_SUMMARIESsub-workflow withtake: records, suffix, run_id, stop readingparams.suffixfrom inside the process. - Process syntax (Part 4): quote
env 'RUN_ID', replaceshell:withscript:, keep the\$RUN_IDescape so the shell expands the env at runtime — not at compile time. - Config (Part 5): replace the top-level
def user_labeland thechooseQueuehelper with declarativeparamsand an immediately-invoked closure; replace${USER}with${System.getenv('USER') ?: 'user'}; addvalidation.ignoreParamsand thenf-schemaplugin. - Re-run the smoke until lint is silent and the pipeline produces two
*.qc.tsvfiles with the schema above.
2. Acceptance commands
Your migrated exercise is complete when these commands all pass from the exercise folder:
nextflow lint -project-dir . main.nf
nextflow run main.nf -profile test
The expected log lines include QC_SUMMARIES:QC_SUMMARY (the nested name proves the entry workflow now goes through the wrapper sub-workflow, not the bare process) and completed=2 in the summary.
Compare with the worked solution:
cd ../solution
nextflow lint -project-dir . main.nf
nextflow run main.nf -profile test
nf-test test tests/*.nf.test
Expected per-sample TSV (sample_a)
sample sample_a
cohort validation
suffix _qc
run_id exercise_run
reads 3
If run_id is empty in your output, the script: body wrote ${RUN_ID} (Nextflow tried to interpolate at compile time, found nothing) instead of "\$RUN_ID" (shell expansion at runtime). Re-read the Part 4 runtime-footgun callout.
Checkpoint
- The migrated workflow has the same inputs and outputs as the legacy starting point — same TSV schema, same number of records per sample.
- No process reads
params.*directly. All runtime configuration arrives throughtake:inputs or env values. - No legacy syntax remains anywhere in the workflow, the module, or the config.
-
nf-testpasses.
End of the core path. If you have time and the group is still warm, continue to Part 6: Static Typing and Records. Otherwise the Wrap-up collects the strict migration checklist in one page.