@turri-dev/cli 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.pi/themes/turri-dark.json +80 -0
- package/.pi/themes/turri-light.json +82 -0
- package/LICENSE +21 -0
- package/README.md +272 -2
- package/assets/jellyfish/dark-00.png +0 -0
- package/assets/jellyfish/dark-01.png +0 -0
- package/assets/jellyfish/dark-02.png +0 -0
- package/assets/jellyfish/dark-03.png +0 -0
- package/assets/jellyfish/dark-04.png +0 -0
- package/assets/jellyfish/dark-05.png +0 -0
- package/assets/jellyfish/dark-06.png +0 -0
- package/assets/jellyfish/dark-07.png +0 -0
- package/assets/jellyfish/dark-08.png +0 -0
- package/assets/jellyfish/dark-09.png +0 -0
- package/assets/jellyfish/dark-10.png +0 -0
- package/assets/jellyfish/dark-11.png +0 -0
- package/assets/jellyfish/dark-12.png +0 -0
- package/assets/jellyfish/dark-13.png +0 -0
- package/assets/jellyfish/dark-14.png +0 -0
- package/assets/jellyfish/fallback.txt +252 -0
- package/assets/jellyfish/light-00.png +0 -0
- package/assets/jellyfish/light-01.png +0 -0
- package/assets/jellyfish/light-02.png +0 -0
- package/assets/jellyfish/light-03.png +0 -0
- package/assets/jellyfish/light-04.png +0 -0
- package/assets/jellyfish/light-05.png +0 -0
- package/assets/jellyfish/light-06.png +0 -0
- package/assets/jellyfish/light-07.png +0 -0
- package/assets/jellyfish/light-08.png +0 -0
- package/assets/jellyfish/light-09.png +0 -0
- package/assets/jellyfish/light-10.png +0 -0
- package/assets/jellyfish/light-11.png +0 -0
- package/assets/jellyfish/light-12.png +0 -0
- package/assets/jellyfish/light-13.png +0 -0
- package/assets/jellyfish/light-14.png +0 -0
- package/bin/turri.mjs +29 -0
- package/dist/ask.js +149 -0
- package/dist/brand.js +50 -0
- package/dist/btw.js +174 -0
- package/dist/chat-models.js +37 -0
- package/dist/chat-skills.js +18 -0
- package/dist/cli.js +178 -0
- package/dist/config-command.js +197 -0
- package/dist/config-edit.js +367 -0
- package/dist/config.js +489 -0
- package/dist/conform-cli.js +29 -0
- package/dist/cultivate-cli.js +110 -0
- package/dist/drift-cli.js +25 -0
- package/dist/environment.js +70 -0
- package/dist/extension.js +2372 -0
- package/dist/genome.js +301 -0
- package/dist/graph.js +246 -0
- package/dist/identity.js +35 -0
- package/dist/init-config.js +511 -0
- package/dist/judge.js +62 -0
- package/dist/lineage-cli.js +13 -0
- package/dist/loop-cli.js +132 -0
- package/dist/loop-tools.js +225 -0
- package/dist/mature-tools.js +193 -0
- package/dist/model-check.js +117 -0
- package/dist/module-report.js +9 -0
- package/dist/pi-launch.js +31 -0
- package/dist/pi.js +813 -0
- package/dist/plain/check.js +76 -0
- package/dist/plain/concepts.js +213 -0
- package/dist/plain/errors.js +43 -0
- package/dist/plain/files.js +57 -0
- package/dist/plain/highlight.js +195 -0
- package/dist/plain/index.js +15 -0
- package/dist/plain/liquid.js +129 -0
- package/dist/plain/markdown.js +208 -0
- package/dist/plain/parser.js +752 -0
- package/dist/plain/plain.tmLanguage.json +124 -0
- package/dist/plain/spec.js +291 -0
- package/dist/plain/stdlib.js +44 -0
- package/dist/question-dialog.js +125 -0
- package/dist/questioning.js +109 -0
- package/dist/quiet.js +125 -0
- package/dist/regenerate/activity.js +187 -0
- package/dist/regenerate/agent-env.js +190 -0
- package/dist/regenerate/agent-shell.js +114 -0
- package/dist/regenerate/arbiter.js +133 -0
- package/dist/regenerate/audit.js +203 -0
- package/dist/regenerate/cases.js +83 -0
- package/dist/regenerate/change-view.js +102 -0
- package/dist/regenerate/choose-conformance.js +117 -0
- package/dist/regenerate/chunk-plan.js +625 -0
- package/dist/regenerate/codebase.js +75 -0
- package/dist/regenerate/command-check.js +312 -0
- package/dist/regenerate/compaction.js +20 -0
- package/dist/regenerate/confine.js +310 -0
- package/dist/regenerate/conform.js +133 -0
- package/dist/regenerate/conformance-agents.js +955 -0
- package/dist/regenerate/conformance.js +936 -0
- package/dist/regenerate/conversation-writes.js +100 -0
- package/dist/regenerate/credentials.js +88 -0
- package/dist/regenerate/debug-log.js +228 -0
- package/dist/regenerate/debugger.js +90 -0
- package/dist/regenerate/delivery.js +19 -0
- package/dist/regenerate/diagnose-job.js +40 -0
- package/dist/regenerate/diagnose.js +81 -0
- package/dist/regenerate/diff-view.js +240 -0
- package/dist/regenerate/drift.js +194 -0
- package/dist/regenerate/engine.js +118 -0
- package/dist/regenerate/environment-error.js +220 -0
- package/dist/regenerate/job-log.js +102 -0
- package/dist/regenerate/job-panel.js +336 -0
- package/dist/regenerate/jobs-tree.js +300 -0
- package/dist/regenerate/jobs.js +427 -0
- package/dist/regenerate/lanes.js +32 -0
- package/dist/regenerate/lineage.js +384 -0
- package/dist/regenerate/loop.js +1435 -0
- package/dist/regenerate/masking.js +95 -0
- package/dist/regenerate/notes.js +397 -0
- package/dist/regenerate/os-sandbox.js +243 -0
- package/dist/regenerate/paths.js +165 -0
- package/dist/regenerate/plain-palette.js +83 -0
- package/dist/regenerate/prompt.js +704 -0
- package/dist/regenerate/records.js +294 -0
- package/dist/regenerate/regenerate.js +598 -0
- package/dist/regenerate/regression.js +181 -0
- package/dist/regenerate/release.js +262 -0
- package/dist/regenerate/requires.js +241 -0
- package/dist/regenerate/resources.js +164 -0
- package/dist/regenerate/self-check.js +111 -0
- package/dist/regenerate/services.js +155 -0
- package/dist/regenerate/shell.js +319 -0
- package/dist/regenerate/side-panel.js +278 -0
- package/dist/regenerate/spec-map-view.js +574 -0
- package/dist/regenerate/spec-map.js +561 -0
- package/dist/regenerate/spec-steps.js +527 -0
- package/dist/regenerate/state.js +10 -0
- package/dist/regenerate/steer.js +91 -0
- package/dist/regenerate/steering.js +119 -0
- package/dist/regenerate/stop-report.js +161 -0
- package/dist/regenerate/submit-work.js +71 -0
- package/dist/regenerate/test-kinds.js +124 -0
- package/dist/regenerate/test-scripts.js +695 -0
- package/dist/regenerate/testing.js +406 -0
- package/dist/regenerate/transcript-view.js +1 -0
- package/dist/regenerate/unit-gate.js +20 -0
- package/dist/regenerate/unit-review.js +347 -0
- package/dist/regenerate/web.js +256 -0
- package/dist/regenerate/workspace.js +99 -0
- package/dist/regenerate-cli.js +68 -0
- package/dist/release-agent.js +114 -0
- package/dist/release-cli.js +131 -0
- package/dist/release-tools.js +148 -0
- package/dist/review/live.js +165 -0
- package/dist/rules.js +47 -0
- package/dist/self-invocation.js +110 -0
- package/dist/setup.js +245 -0
- package/dist/skill-progress.js +78 -0
- package/dist/skill.js +149 -0
- package/dist/skills.js +74 -0
- package/dist/spec-diff-cli.js +37 -0
- package/dist/spec-tools.js +155 -0
- package/dist/stack.js +158 -0
- package/dist/stacks.js +97 -0
- package/dist/stdlib.js +100 -0
- package/dist/terminal-images.js +46 -0
- package/dist/tools.js +242 -0
- package/dist/validate-cli.js +52 -0
- package/docs/README.md +58 -0
- package/docs/authoring-plain.md +245 -0
- package/docs/config.md +353 -0
- package/docs/deploy.md +109 -0
- package/docs/development.md +75 -0
- package/docs/headless.md +148 -0
- package/docs/images/how-turri-works.svg +50 -0
- package/docs/images/turri-banner.png +0 -0
- package/docs/installation.md +118 -0
- package/docs/parser.md +108 -0
- package/docs/regenerating.md +1116 -0
- package/docs/sandbox.md +233 -0
- package/docs/skills-and-gates.md +254 -0
- package/docs/theming.md +76 -0
- package/docs/using.md +454 -0
- package/genome/rules/bullet-continuation.md +69 -0
- package/genome/rules/concise-specs-examples.md +431 -0
- package/genome/rules/concise-specs-integration-examples.md +270 -0
- package/genome/rules/concise-specs.md +80 -0
- package/genome/rules/definitions.md +107 -0
- package/genome/rules/examples/integration-embedded-testing/run_conformance_tests_java.sh +163 -0
- package/genome/rules/examples/integration-embedded-testing/run_unittests_java.sh +51 -0
- package/genome/rules/exported-concepts.md +39 -0
- package/genome/rules/func-specs.md +129 -0
- package/genome/rules/impl-reqs.md +68 -0
- package/genome/rules/import-modules.md +48 -0
- package/genome/rules/integration-embedded-testing.md +276 -0
- package/genome/rules/integration-embedded.md +171 -0
- package/genome/rules/integration-standalone.md +135 -0
- package/genome/rules/integrations.md +137 -0
- package/genome/rules/linked-resources.md +69 -0
- package/genome/rules/module-structure.md +105 -0
- package/genome/rules/project-model.md +88 -0
- package/genome/rules/required-concepts.md +44 -0
- package/genome/rules/requires-modules.md +80 -0
- package/genome/rules/stdlib.md +73 -0
- package/genome/rules/test-reqs.md +57 -0
- package/genome/skills/add-acceptance-test/SKILL.md +100 -0
- package/genome/skills/add-concept/SKILL.md +71 -0
- package/genome/skills/add-functional-spec/SKILL.md +84 -0
- package/genome/skills/add-functional-specs/SKILL.md +118 -0
- package/genome/skills/add-implementation-requirement/SKILL.md +86 -0
- package/genome/skills/add-resource/SKILL.md +105 -0
- package/genome/skills/add-template/SKILL.md +64 -0
- package/genome/skills/add-test-requirement/SKILL.md +73 -0
- package/genome/skills/analyze-2-func-specs/SKILL.md +101 -0
- package/genome/skills/analyze-func-specs/SKILL.md +123 -0
- package/genome/skills/analyze-if-func-spec-too-complex/SKILL.md +211 -0
- package/genome/skills/break-down-func-spec/SKILL.md +205 -0
- package/genome/skills/brood/SKILL.md +229 -0
- package/genome/skills/brood/references/intent.md +35 -0
- package/genome/skills/brood/references/product.md +101 -0
- package/genome/skills/brood/references/stack.md +61 -0
- package/genome/skills/brood/references/testing.md +56 -0
- package/genome/skills/bud/SKILL.md +148 -0
- package/genome/skills/bud/references/checklist.md +44 -0
- package/genome/skills/bud/references/intent.md +30 -0
- package/genome/skills/check-plain-env/SKILL.md +285 -0
- package/genome/skills/consolidate-concepts/SKILL.md +192 -0
- package/genome/skills/create-import-module/SKILL.md +104 -0
- package/genome/skills/create-requires-module/SKILL.md +109 -0
- package/genome/skills/debug-specs/SKILL.md +188 -0
- package/genome/skills/implement-conformance-testing-script/SKILL.md +238 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_cypress.ps1 +337 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_golang.ps1 +111 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_java.sh +136 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.ps1 +103 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.sh +150 -0
- package/genome/skills/implement-unit-testing-script/SKILL.md +160 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_flutter.ps1 +87 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_golang.ps1 +73 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_java.sh +72 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_python.ps1 +81 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_python.sh +98 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_react.ps1 +87 -0
- package/genome/skills/init-plain-project/SKILL.md +117 -0
- package/genome/skills/init-plain-project/references/checklist.md +30 -0
- package/genome/skills/mature/SKILL.md +126 -0
- package/genome/skills/refactor-module/SKILL.md +196 -0
- package/genome/skills/resolve-section-ownership/SKILL.md +64 -0
- package/genome/skills/resolve-spec-conflict/SKILL.md +96 -0
- package/genome/skills/spawn/SKILL.md +163 -0
- package/package.json +68 -4
- package/stdlib/backend/common.plain +25 -0
- package/stdlib/backend/conformance/common.plain +12 -0
- package/stdlib/backend/conformance/go-gin.plain +9 -0
- package/stdlib/backend/conformance/go-nethttp.plain +9 -0
- package/stdlib/backend/conformance/java-springboot.plain +9 -0
- package/stdlib/backend/conformance/python-django.plain +9 -0
- package/stdlib/backend/conformance/python-fastapi.plain +9 -0
- package/stdlib/backend/conformance/python-flask.plain +9 -0
- package/stdlib/backend/conformance/rust-axum.plain +9 -0
- package/stdlib/backend/conformance/typescript-express.plain +9 -0
- package/stdlib/backend/conformance/typescript-fastify.plain +9 -0
- package/stdlib/backend/conformance/typescript-nestjs.plain +9 -0
- package/stdlib/backend/go-gin.plain +18 -0
- package/stdlib/backend/go-nethttp.plain +18 -0
- package/stdlib/backend/java-springboot.plain +19 -0
- package/stdlib/backend/python-django.plain +19 -0
- package/stdlib/backend/python-fastapi.plain +23 -0
- package/stdlib/backend/python-flask.plain +23 -0
- package/stdlib/backend/rust-axum.plain +17 -0
- package/stdlib/backend/typescript-express.plain +19 -0
- package/stdlib/backend/typescript-fastify.plain +19 -0
- package/stdlib/backend/typescript-nestjs.plain +19 -0
- package/stdlib/cli/common.plain +17 -0
- package/stdlib/cli/conformance/common.plain +11 -0
- package/stdlib/cli/conformance/go-cobra.plain +9 -0
- package/stdlib/cli/conformance/go-flag.plain +9 -0
- package/stdlib/cli/conformance/java-picocli.plain +9 -0
- package/stdlib/cli/conformance/python-argparse.plain +9 -0
- package/stdlib/cli/conformance/python-click.plain +9 -0
- package/stdlib/cli/conformance/python-typer.plain +9 -0
- package/stdlib/cli/conformance/rust-clap.plain +9 -0
- package/stdlib/cli/conformance/typescript-commander.plain +9 -0
- package/stdlib/cli/go-cobra.plain +14 -0
- package/stdlib/cli/go-flag.plain +14 -0
- package/stdlib/cli/java-picocli.plain +15 -0
- package/stdlib/cli/python-argparse.plain +18 -0
- package/stdlib/cli/python-click.plain +16 -0
- package/stdlib/cli/python-typer.plain +16 -0
- package/stdlib/cli/rust-clap.plain +14 -0
- package/stdlib/cli/typescript-commander.plain +17 -0
- package/stdlib/frontend/angular.plain +17 -0
- package/stdlib/frontend/common.plain +17 -0
- package/stdlib/frontend/conformance/angular.plain +5 -0
- package/stdlib/frontend/conformance/common.plain +11 -0
- package/stdlib/frontend/conformance/react-vite.plain +5 -0
- package/stdlib/frontend/conformance/sveltekit-spa.plain +5 -0
- package/stdlib/frontend/conformance/vue-vite.plain +5 -0
- package/stdlib/frontend/react-vite.plain +16 -0
- package/stdlib/frontend/sveltekit-spa.plain +16 -0
- package/stdlib/frontend/vue-vite.plain +16 -0
- package/stdlib/lang/go.plain +13 -0
- package/stdlib/lang/java.plain +12 -0
- package/stdlib/lang/python.plain +18 -0
- package/stdlib/lang/rust.plain +12 -0
- package/stdlib/lang/typescript.plain +13 -0
- package/stdlib/library/common.plain +15 -0
- package/stdlib/library/conformance/common.plain +9 -0
- package/stdlib/library/conformance/go.plain +9 -0
- package/stdlib/library/conformance/java-gradle.plain +9 -0
- package/stdlib/library/conformance/java-maven.plain +9 -0
- package/stdlib/library/conformance/python.plain +9 -0
- package/stdlib/library/conformance/rust.plain +9 -0
- package/stdlib/library/conformance/typescript.plain +9 -0
- package/stdlib/library/go.plain +12 -0
- package/stdlib/library/java-gradle.plain +13 -0
- package/stdlib/library/java-maven.plain +13 -0
- package/stdlib/library/python.plain +13 -0
- package/stdlib/library/rust.plain +12 -0
- package/stdlib/library/typescript.plain +15 -0
package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.sh
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
UNRECOVERABLE_ERROR_EXIT_CODE=69
|
|
4
|
+
|
|
5
|
+
# Check if build folder name is provided
|
|
6
|
+
if [ -z "$1" ]; then
|
|
7
|
+
printf "Error: No build folder name provided.\n"
|
|
8
|
+
printf "Usage: $0 <build_folder_name> <conformance_tests_folder>\n"
|
|
9
|
+
exit $UNRECOVERABLE_ERROR_EXIT_CODE
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
# Check if conformance tests folder name is provided
|
|
13
|
+
if [ -z "$2" ]; then
|
|
14
|
+
printf "Error: No conformance tests folder name provided.\n"
|
|
15
|
+
printf "Usage: $0 <build_folder_name> <conformance_tests_folder>\n"
|
|
16
|
+
exit $UNRECOVERABLE_ERROR_EXIT_CODE
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
# Try to find Python interpreter (python3 first, then python)
|
|
20
|
+
if command -v python3 &> /dev/null; then
|
|
21
|
+
PYTHON_CMD="python3"
|
|
22
|
+
elif command -v python &> /dev/null; then
|
|
23
|
+
PYTHON_CMD="python"
|
|
24
|
+
else
|
|
25
|
+
printf "Error: Python interpreter not found. Please install Python.\n"
|
|
26
|
+
exit $UNRECOVERABLE_ERROR_EXIT_CODE
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# Resolve the conformance tests folder to an absolute path so it can be used
|
|
30
|
+
# after changing into the working folder ($2 may be relative to the invocation
|
|
31
|
+
# directory on older renderers; newer renderers pass it as an absolute path).
|
|
32
|
+
case "$2" in
|
|
33
|
+
/*) CONFORMANCE_TESTS_FOLDER="$2" ;;
|
|
34
|
+
*) CONFORMANCE_TESTS_FOLDER="$(pwd)/$2" ;;
|
|
35
|
+
esac
|
|
36
|
+
|
|
37
|
+
# Conformance tests live in the conformance tests folder and nowhere else. One in the
|
|
38
|
+
# build folder is in the wrong place: nothing would run it as part of the suite. Refuse.
|
|
39
|
+
MISPLACED=$( (grep -rlE --exclude=".turri*" --exclude-dir=.venv --exclude-dir=node_modules --exclude-dir=target \
|
|
40
|
+
"conformance tests of (functionality [0-9]|the spec)|acceptance test [0-9]+ of functionality" "$1" 2>/dev/null; \
|
|
41
|
+
find "$1" -type f \( -ipath "*conformance*" -o -ipath "*acceptance*" \) -iname "*test*" \
|
|
42
|
+
-not -name ".turri*" -not -path "*/.venv/*" -not -path "*/node_modules/*" -not -path "*/target/*" 2>/dev/null) | sort -u)
|
|
43
|
+
if [ -n "$MISPLACED" ]; then
|
|
44
|
+
printf "Error: conformance tests found in the build folder %s - they belong in the conformance tests folder %s:\n%s\n" "$1" "$CONFORMANCE_TESTS_FOLDER" "$MISPLACED" >&2
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Working folder lives in the system temp directory. $1 may be an absolute
|
|
49
|
+
# path, so only its basename is used to build the working folder name.
|
|
50
|
+
PYTHON_BUILD_SUBFOLDER="/tmp/python_$(basename "$1")"
|
|
51
|
+
|
|
52
|
+
trap 'rm -rf "$PYTHON_BUILD_SUBFOLDER"' EXIT
|
|
53
|
+
|
|
54
|
+
if [ "${VERBOSE:-}" -eq 1 ] 2>/dev/null; then
|
|
55
|
+
printf "Preparing Python build subfolder: $PYTHON_BUILD_SUBFOLDER\n"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
rm -rf "$PYTHON_BUILD_SUBFOLDER"
|
|
59
|
+
mkdir -p "$PYTHON_BUILD_SUBFOLDER"
|
|
60
|
+
|
|
61
|
+
cp -R "$1"/* "$PYTHON_BUILD_SUBFOLDER"
|
|
62
|
+
|
|
63
|
+
# Move to the subfolder
|
|
64
|
+
cd "$PYTHON_BUILD_SUBFOLDER" 2>/dev/null
|
|
65
|
+
|
|
66
|
+
if [ $? -ne 0 ]; then
|
|
67
|
+
printf "Error: Python build folder '$PYTHON_BUILD_SUBFOLDER' does not exist.\n"
|
|
68
|
+
exit $UNRECOVERABLE_ERROR_EXIT_CODE
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
printf "Creating and activating virtual environment...\n"
|
|
72
|
+
|
|
73
|
+
# Time the virtual environment creation and activation
|
|
74
|
+
start_time=$(date +%s.%N)
|
|
75
|
+
|
|
76
|
+
VENV_DIR=".venv"
|
|
77
|
+
|
|
78
|
+
if ! $PYTHON_CMD -m venv "$VENV_DIR"; then
|
|
79
|
+
printf "Error: Failed to create virtual environment in '$VENV_DIR'.\n"
|
|
80
|
+
exit $UNRECOVERABLE_ERROR_EXIT_CODE
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# shellcheck disable=SC1091
|
|
84
|
+
source "$VENV_DIR/bin/activate"
|
|
85
|
+
|
|
86
|
+
if [ $? -ne 0 ]; then
|
|
87
|
+
printf "Error: Failed to activate virtual environment at '$VENV_DIR/bin/activate'.\n"
|
|
88
|
+
exit $UNRECOVERABLE_ERROR_EXIT_CODE
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# Install requirements if requirements.txt exists
|
|
92
|
+
if [ -f "requirements.txt" ]; then
|
|
93
|
+
pip install --upgrade pip
|
|
94
|
+
pip install -r requirements.txt
|
|
95
|
+
else
|
|
96
|
+
echo "Warning: requirements.txt not found. Cannot proceed with setting up requirements. The requirements may also already be installed"
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
end_time=$(date +%s.%N)
|
|
100
|
+
|
|
101
|
+
# Calculate and display the time taken
|
|
102
|
+
duration=$(echo "$end_time - $start_time" | bc)
|
|
103
|
+
printf "Requirements setup completed in %.2f seconds\n\n" "$duration"
|
|
104
|
+
|
|
105
|
+
# Static checks of the conformance tests - and only them - in the environment they run in, the build installed: ruff's
|
|
106
|
+
# formatter in check mode, ruff's linter and mypy with a tests profile. The settings are pinned here, in a config the
|
|
107
|
+
# script writes into the working folder; none is read from the tests folder, whose files the conformance agent writes.
|
|
108
|
+
STATIC_CHECKS_FAILED=80
|
|
109
|
+
printf "Static checks of the conformance tests in %s...\n" "$CONFORMANCE_TESTS_FOLDER"
|
|
110
|
+
echo "+ pip install ruff mypy"
|
|
111
|
+
pip install ruff mypy || exit $?
|
|
112
|
+
cat > .conformance-mypy.ini <<'INI'
|
|
113
|
+
[mypy]
|
|
114
|
+
disallow_untyped_defs = True
|
|
115
|
+
follow_imports = silent
|
|
116
|
+
ignore_missing_imports = True
|
|
117
|
+
INI
|
|
118
|
+
CHECKS=0
|
|
119
|
+
echo "+ ruff format --check --no-cache --isolated --output-format concise $CONFORMANCE_TESTS_FOLDER"
|
|
120
|
+
ruff format --check --no-cache --isolated --output-format concise "$CONFORMANCE_TESTS_FOLDER" || CHECKS=1
|
|
121
|
+
echo "+ ruff check --no-cache --isolated --output-format concise --no-fix $CONFORMANCE_TESTS_FOLDER"
|
|
122
|
+
ruff check --no-cache --isolated --output-format concise --no-fix "$CONFORMANCE_TESTS_FOLDER" || CHECKS=1
|
|
123
|
+
echo "+ mypy --config-file .conformance-mypy.ini --cache-dir ./.mypy_cache --no-color-output --show-column-numbers $CONFORMANCE_TESTS_FOLDER"
|
|
124
|
+
mypy --config-file .conformance-mypy.ini --cache-dir ./.mypy_cache --no-color-output --show-column-numbers "$CONFORMANCE_TESTS_FOLDER" || CHECKS=1
|
|
125
|
+
[ "$CHECKS" -eq 0 ] && printf "Static checks: passed\n\n" || printf "Static checks: FAILED - the findings are above\n\n"
|
|
126
|
+
|
|
127
|
+
# Execute all Python conformance tests in the build folder - whatever the checks said, so one run reports both
|
|
128
|
+
printf "Running Python conformance tests...\n\n"
|
|
129
|
+
|
|
130
|
+
output=$($PYTHON_CMD -m unittest discover -b -s "$CONFORMANCE_TESTS_FOLDER" 2>&1)
|
|
131
|
+
exit_code=$?
|
|
132
|
+
|
|
133
|
+
# Echo the original output
|
|
134
|
+
echo "$output"
|
|
135
|
+
|
|
136
|
+
# Check if no tests were discovered
|
|
137
|
+
if echo "$output" | grep -q "Ran 0 tests in"; then
|
|
138
|
+
printf "\nError: No unittests discovered.\n"
|
|
139
|
+
exit 1
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
# The suite's own exit code when it failed; 80 when only the static checks of the tests did; 0 when both pass
|
|
143
|
+
if [ "$exit_code" -ne 0 ]; then
|
|
144
|
+
exit $exit_code
|
|
145
|
+
fi
|
|
146
|
+
if [ "$CHECKS" -ne 0 ]; then
|
|
147
|
+
printf "\nThe conformance tests pass, and their static checks do not.\n"
|
|
148
|
+
exit $STATIC_CHECKS_FAILED
|
|
149
|
+
fi
|
|
150
|
+
exit 0
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: implement-unit-testing-script
|
|
3
|
+
description: >-
|
|
4
|
+
Implement a unit-test runner script (Bash, which turri runs; a PowerShell copy on request, for
|
|
5
|
+
Windows) for an arbitrary programming language, following the same conceptual
|
|
6
|
+
pattern as the bundled Java reference script in assets/. The script runs the
|
|
7
|
+
code's static checks - formatter in check mode, linter, type checker - with
|
|
8
|
+
its unit tests, and both count. Use when the user
|
|
9
|
+
wants to add a testing script for a new language (Python, Node.js, Go, Rust,
|
|
10
|
+
etc.) to a ***plain project, or wants to regenerate / adapt the existing
|
|
11
|
+
Java runner.
|
|
12
|
+
allowed-tools: [read, write, edit, bash, ls, grep, find]
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Implement Unit Testing Script
|
|
16
|
+
|
|
17
|
+
This skill produces a single executable script that runs the static checks and the unit tests for a generated build folder, following a consistent, language-agnostic pattern.
|
|
18
|
+
|
|
19
|
+
The reference implementation is [assets/run_unittests_java.sh](assets/run_unittests_java.sh). Read it first — every script you produce must be a faithful translation of that pattern into the target language's tooling **and** the user's shell environment. There are also Windows PowerShell equivalents of these scripts in [assets/run_unittests_*.ps1](assets/run_unittests_*.ps1).
|
|
20
|
+
|
|
21
|
+
## Always Bash
|
|
22
|
+
|
|
23
|
+
turri requires this script and runs it itself, as `bash run_unittests_<lang>.sh <build_folder>` on the person's machine - macOS, Linux or WSL: its exit code decides whether a functionality's unit tests pass. So the script is always **Bash (`.sh`)**, and stays portable across them - POSIX tools and flags, nothing only one of them has.
|
|
24
|
+
|
|
25
|
+
A **PowerShell (`.ps1`)** copy is written only when the user asks for one, to run the tests by hand on native Windows. turri never runs it, so a project with only a `.ps1` has no unit testing script.
|
|
26
|
+
|
|
27
|
+
The same eight-step pattern applies to both. Only the syntax changes.
|
|
28
|
+
|
|
29
|
+
## The Pattern
|
|
30
|
+
|
|
31
|
+
Every testing script must implement these steps **in this order**:
|
|
32
|
+
|
|
33
|
+
1. **Toolchain check.** Verify that the required language runtime / build tool (and the required version, if any) is installed. If not, print an error and exit with code `69`.
|
|
34
|
+
2. **Argument validation.** Require exactly one positional argument: the source build folder. If missing, print usage and exit with code `1`. The renderer passes this argument as an **absolute path** (older renderer versions passed a relative one) — the script must work with both, which is why the working-folder name in step 3 is built from the argument's *basename*, never from the raw argument.
|
|
35
|
+
3. **Working directory setup.** Define a working folder in the **system temp directory**: `/tmp/<lang>_$(basename "$1")` in Bash, `Join-Path ([System.IO.Path]::GetTempPath()) "<lang>_$(Split-Path $BuildFolder -Leaf)"` in PowerShell (written as `<temp>/<lang>_<basename>` below). If it exists, wipe its contents; otherwise create it. Register cleanup so the folder is removed when the script exits — `trap 'rm -rf "$WORKING_FOLDER"' EXIT` in Bash, a `Remove-Item -Recurse -Force` in the `finally` block in PowerShell. This folder — and **only** this folder — is where every subsequent write must land.
|
|
36
|
+
4. **Copy the build.** Recursively copy everything from the source folder into the working folder. After this step the source folder (`$1`) is treated as **read-only** for the rest of the script.
|
|
37
|
+
5. **Enter the working directory.** `cd` / `Set-Location` into `<temp>/<lang>_<basename>`. If that fails, exit with code `2`. All remaining steps run from inside the working folder; they must never write back to the source build folder.
|
|
38
|
+
6. **Install dependencies into an isolated environment inside `<temp>/<lang>_<basename>`.** Set up a per-working-folder dependency location (a Python venv at `./.venv`, a local `./node_modules`, a project-scoped Maven repo at `./.m2`, etc.) and install/resolve all dependencies into it. **Never** install into the source build folder, the user's global cache (`~/.m2`, system-wide `pip`, `~/.cargo`, `~/.npm`, ...), or anywhere outside `<temp>/<lang>_<basename>`. If the install command fails, propagate its exit code immediately and **do not** proceed to step 7. See [Dependency isolation](#dependency-isolation) for per-language specifics.
|
|
39
|
+
7. **Run the static checks.** The formatter in check mode, the linter, and the type checker where the language has one apart from its compiler - see [Static checks](#static-checks) - on the staged copy, with the tools installed in step 6. Run **every** check, even after one fails, and print each one's whole output. Remember whether any failed; do not exit yet.
|
|
40
|
+
8. **Run the tests.** Invoke the language's standard test command (e.g. `mvn test`, `pytest`, `npm test`, `go test ./...`, `cargo test`), pointed at the same isolated environment from step 6 - whatever the checks in step 7 said, so one run reports both. Then exit: the test command's own non-zero code when the tests failed; else `80` when only the static checks failed; else `0`.
|
|
41
|
+
|
|
42
|
+
### The build folder is read-only — hard rule
|
|
43
|
+
|
|
44
|
+
The source build folder passed in as `$1` is **input only**. The script must never:
|
|
45
|
+
|
|
46
|
+
- install dependencies into it (no `pip install` inside `$1`, no `npm install` inside `$1`, no `mvn install` writing into `$1`, no Cargo build artifacts ending up under `$1`),
|
|
47
|
+
- write a virtualenv / `node_modules` / `.m2` / `.gocache` / `.cargo` directory inside it,
|
|
48
|
+
- run the test command from inside it (every test command runs from inside `<temp>/<lang>_<basename>` after the `cd` in step 5),
|
|
49
|
+
- create logs, caches, build outputs, or temp files inside it.
|
|
50
|
+
|
|
51
|
+
The build folder is shared with the renderer (`plain_modules/<module>/code` by default) and downstream tooling. Writing into it corrupts the renderer's view of "what was generated" and breaks subsequent renders. Every write must go into `<temp>/<lang>_<basename>` — the whole point of staging into the system temp directory is so the source build folder stays a clean, reproducible artifact of the render and no build debris is left inside the user's project.
|
|
52
|
+
|
|
53
|
+
If you find yourself about to issue any command whose `cwd` is the source folder, or whose target path starts with `$1/`, **stop**. Either move the operation into `<temp>/<lang>_<basename>`, or you're doing something the script must not do.
|
|
54
|
+
|
|
55
|
+
## Conventions
|
|
56
|
+
|
|
57
|
+
Shared across both shell flavors:
|
|
58
|
+
|
|
59
|
+
- **Exit codes:**
|
|
60
|
+
- `1` — bad usage (missing argument).
|
|
61
|
+
- `2` — filesystem problem (couldn't enter the working folder).
|
|
62
|
+
- `69` — required toolchain / runtime is not installed - a static checker the person installs on the machine (not the project) included.
|
|
63
|
+
- `80` — the tests passed and only the static checks failed. turri tells the coding agent so, and says it was the static checks when the work stops there.
|
|
64
|
+
- Any other non-zero code — propagated from the underlying test command.
|
|
65
|
+
- **Working folder naming:** `<temp>/<lang>_<basename>` — the system temp directory (`/tmp` in Bash, `[System.IO.Path]::GetTempPath()` in PowerShell) plus a short identifier for the language (`java`, `python`, `node`, `go`, `rust`, ...) and the **basename** of the build-folder argument (`$(basename "$1")` / `Split-Path $BuildFolder -Leaf`). Never embed the raw argument in the path — it may be absolute, and concatenating it produces broken doubled paths. All dependency installs, build outputs, caches, and the test run itself live inside this folder. Nothing the script does should touch the source build folder after step 4.
|
|
66
|
+
- **Cleanup on exit:** the working folder is temporary — remove it when the script exits, on success and failure alike (`trap 'rm -rf "$WORKING_FOLDER"' EXIT` in Bash; `Remove-Item -Recurse -Force` in the PowerShell `finally` block, after `Pop-Location`). No leftover build folders may accumulate in the temp directory or the user's project.
|
|
67
|
+
- **Logging — be as verbose as possible.** The script is the only thing the operator sees between a `turri` render and a green/red test result; when it fails, the only forensic evidence anyone has is its stdout/stderr. Treat the script like a production runbook, not a quiet helper. Concretely:
|
|
68
|
+
- **Announce every step before doing it**, with the exact value of every variable involved — the resolved source folder, the working folder, the language, the toolchain version detected, the isolation root, the dependency-manifest path, the test command about to run. "Installing dependencies" alone is useless; "Installing dependencies from `./requirements.txt` into venv `./.venv` (Python 3.11.6 at `/usr/local/bin/python3`)" is triage-ready.
|
|
69
|
+
- **Echo every non-trivial command before executing it.** In Bash, use `set -x` for the body of the script (or `echo "+ <cmd>"` immediately before each call); in PowerShell, set `$VerbosePreference = 'Continue'` and use `Write-Verbose` / `Write-Host` to print each command line with its arguments.
|
|
70
|
+
- **Print clear section banners.** Each of the eight steps gets a banner like `===== [3/8] Working directory setup =====` so a long log can be navigated by eye.
|
|
71
|
+
- **Print resolved absolute paths**, not just the relative names. Operators reading the log on a different machine need to know where things actually landed.
|
|
72
|
+
- **Print the toolchain's own `--version` output** (and the path it resolved from) during step 1, even on success. This is the single most useful piece of forensic data when a test passes locally but fails in CI.
|
|
73
|
+
- **Surface the install command's output verbatim** — do not pipe it to `/dev/null`, do not redirect to a log file inside the working folder. The operator must see every dependency-resolver line in the script's own output stream.
|
|
74
|
+
- **On failure, print what was about to run before exiting** — the exact command, the working directory, the relevant environment variables (`PYTHONPATH`, `NODE_PATH`, `GOMODCACHE`, `CARGO_HOME`, `MAVEN_OPTS`, `PATH`).
|
|
75
|
+
- **Print a final summary line** that names the test command, the exit code, and the working folder so the operator knows exactly what to re-run by hand if needed.
|
|
76
|
+
Verbosity is not noise here — a chatty script that documents itself in its own output is the difference between a 30-second triage and a 30-minute one. Never trade verbosity for terseness.
|
|
77
|
+
|
|
78
|
+
### Dependency isolation
|
|
79
|
+
|
|
80
|
+
The dependency environment must live **inside** `$WORKING_FOLDER` so the test run can't be polluted by — or pollute — the user's global caches. Pick the most idiomatic isolation mechanism for the language:
|
|
81
|
+
|
|
82
|
+
| Language | Isolation mechanism | Install command (run inside `$WORKING_FOLDER`) | Test command |
|
|
83
|
+
|---|---|---|---|
|
|
84
|
+
| Python | `venv` at `./.venv` | `python3 -m venv .venv && ./.venv/bin/pip install -r requirements.txt` (or `pyproject.toml` / `uv sync` / `poetry install`) | `./.venv/bin/pytest` (or `./.venv/bin/python -m pytest`) |
|
|
85
|
+
| Node.js | local `./node_modules` (default) | `npm ci` (preferred) or `npm install` | `npm test` |
|
|
86
|
+
| Java | project-scoped Maven repo at `./.m2` | `mvn -Dmaven.repo.local=./.m2 dependency:resolve` (optional pre-warm) | `mvn -Dmaven.repo.local=./.m2 test` |
|
|
87
|
+
| Go | module cache at `./.gocache` | `GOMODCACHE="$PWD/.gocache" go mod download` (optional pre-warm) | `GOMODCACHE="$PWD/.gocache" go test ./...` |
|
|
88
|
+
| Rust | cargo home at `./.cargo` | `CARGO_HOME="$PWD/.cargo" cargo fetch` (optional pre-warm) | `CARGO_HOME="$PWD/.cargo" cargo test` |
|
|
89
|
+
|
|
90
|
+
Notes:
|
|
91
|
+
|
|
92
|
+
- **Every path in the install command and test command is relative to `<temp>/<lang>_<basename>`.** That's why the script `cd`s into the working folder in step 5 — from that point on, `./.venv`, `./node_modules`, `./.m2`, etc. all resolve under `<temp>/<lang>_<basename>`, never under the source build folder.
|
|
93
|
+
- **Always pass the isolation flag/env var to both the install command and the test command** — they must agree on where deps live, otherwise the test command will silently fall back to the global cache **or** (worse) the source build folder.
|
|
94
|
+
- **Python is the only ecosystem where the venv is mandatory** to satisfy "into a virtual environment" literally. The others use language-native equivalents that achieve the same isolation.
|
|
95
|
+
- **Pre-warming is optional for Java/Go/Rust** — their test commands will fetch deps on demand. Doing it as a separate step makes failures easier to diagnose and gives a clean "install failed vs test failed" signal.
|
|
96
|
+
- **Don't activate the venv** in Bash via `source .venv/bin/activate` — call `./.venv/bin/<tool>` directly. It's more portable and avoids subshell weirdness. In PowerShell, use `& .\.venv\Scripts\<tool>.exe` similarly.
|
|
97
|
+
- **Propagate the install exit code immediately.** In Bash: `<install cmd> || exit $?`. In PowerShell: check `$LASTEXITCODE` and `exit $LASTEXITCODE` if non-zero.
|
|
98
|
+
|
|
99
|
+
### Static checks
|
|
100
|
+
|
|
101
|
+
The checks hold the code to what the language's template - or the implementation reqs - asks of it: its formatter, its linter, its type checker. They run in step 7, on the staged copy, and count as the tests do. turri runs nothing else: there is no separate static checks script, and no fix mode - the coding agent runs the formatter itself, in its own shell, before its final run.
|
|
102
|
+
|
|
103
|
+
| Language | Formatter, in check mode | Linter | Type checker | Where the tools come from |
|
|
104
|
+
|---|---|---|---|---|
|
|
105
|
+
| Python | `ruff format --check --output-format concise .` | `ruff check --output-format concise --no-fix .` | `mypy --no-color-output --show-column-numbers` - on `files` in `[tool.mypy]`, else `.` | the project's `dev` dependency group, installed with uv in step 6 |
|
|
106
|
+
| TypeScript | `prettier --list-different --ignore-unknown .` | `eslint --max-warnings 0 .` | `tsc --noEmit --pretty false` (`vue-tsc` when installed; `--build` for a `tsconfig.json` made of project references) | `devDependencies`, installed with npm in step 6 |
|
|
107
|
+
| JavaScript | `prettier --list-different --ignore-unknown .` | `eslint --max-warnings 0 .` | none - unless the implementation reqs name one (`tsc --noEmit --allowJs --checkJs`) | `devDependencies` |
|
|
108
|
+
| Go | `gofmt -l .` - any file listed is a finding | `go vet ./...` | the compiler, in `go vet` and `go test` | the Go toolchain |
|
|
109
|
+
| Rust | `cargo fmt --all --check` | `cargo clippy --all-targets --message-format short -- -D warnings` | the compiler, in clippy and `cargo test` | rustup components `rustfmt` and `clippy` - missing: exit `69` |
|
|
110
|
+
| Java | only one the implementation reqs name (`mvn spotless:check`, a `google-java-format --dry-run --set-exit-if-changed`) | `javac -Xlint:all -Werror`, passed through the build tool (`-Dmaven.compiler.showWarnings=true -Dmaven.compiler.failOnWarning=true`, Gradle's `options.compilerArgs`) | the compiler | the build tool's plugins |
|
|
111
|
+
| Dart / Flutter | `dart format --output=none --set-exit-if-changed .` | `dart analyze --fatal-infos` (`flutter analyze` for Flutter) | the analyzer, in the same command | the Dart / Flutter SDK |
|
|
112
|
+
|
|
113
|
+
Notes:
|
|
114
|
+
|
|
115
|
+
- **The checks' tools are the project's**, where the language keeps tools per project: ruff and mypy in Python's `dev` group, Prettier, ESLint and TypeScript in `devDependencies`. A tool the manifest lacks is a finding - print `pyproject.toml:1:1: ruff is not in the dev dependency group - add it`, count the checks as failed - never exit `69`: the manifest is the code's, for the agent to fix. Only a tool installed on the machine (the toolchain, a rustup component) missing is `69`.
|
|
116
|
+
- **Pin the flags in the script.** What fails the check is decided here - `-D warnings`, `--max-warnings 0`, `--fatal-infos` - not by a configuration file in the code that the agent could relax. The tools still read the project's own configuration for everything else (ruff's line length, mypy's `files`, ESLint's rules).
|
|
117
|
+
- **One line a finding where the tool allows it** - the concise or short output formats above, `--pretty false` - so the agent reads `src/app.py:12:5: F401 ...`, not a page of diffs.
|
|
118
|
+
- **Only what the language and its template ask for.** No checks the implementation reqs do not name beyond the table; a language not in it gets the ones its implementation reqs name, and none otherwise.
|
|
119
|
+
|
|
120
|
+
### Bash specifics
|
|
121
|
+
|
|
122
|
+
- **Shebang:** `#!/bin/bash`.
|
|
123
|
+
- **File naming:** `run_unittests_<lang>.sh`, placed in `assets/` (skill reference) or `test_scripts/` (target project).
|
|
124
|
+
- **Argument:** `$1`.
|
|
125
|
+
- **Make it executable:** `chmod +x test_scripts/run_unittests_<lang>.sh`.
|
|
126
|
+
|
|
127
|
+
### PowerShell specifics
|
|
128
|
+
|
|
129
|
+
- **No shebang.** Use a `param([Parameter(Mandatory=$true)][string]$Subfolder)` block at the top instead.
|
|
130
|
+
- **File naming:** `run_unittests_<lang>.ps1`, placed next to the `.sh` script.
|
|
131
|
+
- **Exit codes:** use `exit 69` etc. (PowerShell honors them just like Bash).
|
|
132
|
+
- **Toolchain check:** prefer `Get-Command <tool> -ErrorAction SilentlyContinue` and, where a specific version is needed, parse the tool's `--version` output.
|
|
133
|
+
- **Filesystem:** use `Test-Path`, `Remove-Item -Recurse -Force`, `New-Item -ItemType Directory`, `Copy-Item -Recurse`, `Set-Location`. Quote paths to handle spaces.
|
|
134
|
+
- **No `chmod` step needed.** If execution policy is likely to block the script, mention `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` to the user — don't bake it into the script.
|
|
135
|
+
|
|
136
|
+
## Workflow
|
|
137
|
+
|
|
138
|
+
1. Confirm the target **language**, **shell flavor** (Bash or PowerShell), and **dependency manifest** (`pom.xml`, `requirements.txt` / `pyproject.toml`, `package.json`, `go.mod`, `Cargo.toml`, ...). Ask if any is unclear.
|
|
139
|
+
2. Read [assets/run_unittests_java.sh](assets/run_unittests_java.sh) to refresh the exact structure.
|
|
140
|
+
3. Translate each of the eight steps above into the equivalent commands for the target language **and** shell. The toolchain check, dependency install, static checks and test invocation are the language-specific parts; the rest is mechanical translation between Bash and PowerShell syntax.
|
|
141
|
+
4. Pick the dependency-isolation mechanism from the [Dependency isolation](#dependency-isolation) table and use it consistently in steps 6, 7 and 8, and the checks from the [Static checks](#static-checks) table.
|
|
142
|
+
5. Save the new script as `test_scripts/run_unittests_<lang>.sh` (a `.ps1` copy next to it only if the user wants to run it by hand on Windows). For Bash, `chmod +x` it.
|
|
143
|
+
6. **Make sure turri finds the script.** Do not link it from the specs - turri finds the unit testing script in the project root (the folder that holds `plain_modules/`) and puts it, whole, into the prompt of the coding agent itself; a link to it from a spec is ignored. It looks, in order, at `config.turri.yaml` in the root (`unittests-script: <path relative to the root>`) - with the chosen stack's `config.turri.<stack>.yaml` over it, then for the one `run_unittests*.sh` in the root, then for the one in `test_scripts/`. Saving the script in `test_scripts/` (or the root) is enough when it is the only one there. If the project keeps it elsewhere, name it in `config.turri.yaml` as `unittests-script:` - and when the project has only a `config.yaml`, copy it to `config.turri.yaml` first (and each `config.<name>.yaml` to `config.turri.<name>.yaml`) and edit the copy; `config.yaml` may be another tool's, and is never written. If the project has modules on different stacks - e.g. a Python backend and a React frontend side by side, sharing only an imported module - each part is on a stack of its own, and each stack names its own script in its own file beside `config.turri.yaml`: `unittests-script:` in `config.turri.backend.yaml` for the stack `backend`, in `config.turri.frontend.yaml` for the stack `frontend`. A stack is chosen, never worked out: tell the user to choose it with `/stack <stack>` (`--stack <stack>` on the command line) before building that part. turri never guesses between two. The script is required: a build without one stops before it starts.
|
|
144
|
+
|
|
145
|
+
## Anti-Patterns
|
|
146
|
+
|
|
147
|
+
- **(Hard mistake) Don't install into, build into, or otherwise write to the source build folder.** The build folder passed as `$1` is read-only input. Every install, cache, build artifact, log, and temp file must land in `<temp>/<lang>_<basename>`. This includes never running `pip install`, `npm install`, `mvn install`, or `cargo build` with the source folder as their `cwd` or target, never letting a venv / `node_modules` / `.m2` / `.gocache` / `.cargo` directory appear inside the source folder, and never running the test command from inside it. The whole point of staging the build into the system temp directory is so the source folder remains a clean, reproducible artifact of the render — writing to it corrupts the renderer's view and breaks subsequent renders.
|
|
148
|
+
- **(Hard mistake) Don't build any path by concatenating the raw build-folder argument.** `$1` arrives as an absolute path from current renderers. Patterns like `WORKING_FOLDER=.tmp/$1`, `WORKING_FOLDER=<lang>_$1`, or `"$current_dir/$1"` silently produce doubled paths (`/project//abs/path/...`) — the classic symptom is an `ImportError: Start directory is not importable` or "folder does not exist" pointing at a path that contains the project root twice. Always take `$(basename "$1")` / `Split-Path -Leaf`.
|
|
149
|
+
- Don't skip the toolchain check, even when "everyone has it installed" — exit code `69` is what the calling system relies on to detect a missing runtime.
|
|
150
|
+
- Don't reuse the source folder in place. Always copy into `<temp>/<lang>_<basename>` first; the renderer relies on this isolation.
|
|
151
|
+
- Don't leave the working folder behind. Register the cleanup (`trap … EXIT` / `finally`) before the first write so even an early failure removes it.
|
|
152
|
+
- Don't change the exit-code contract. Other parts of the system branch on `1`, `2`, `69` and `80` specifically — and these codes must be identical between the Bash and PowerShell variants.
|
|
153
|
+
- Don't stop at the first failing check, and don't skip the tests because a check failed. Every check and the tests run each time, so one run tells the agent everything; the exit code says which failed.
|
|
154
|
+
- Don't exit `80` when the tests failed. `80` means the tests passed and only the checks did not; failing tests keep their own exit code, the checks' findings printed above them.
|
|
155
|
+
- Don't format or fix anything. The checks run in check mode on the staged copy; the formatter is the coding agent's to run.
|
|
156
|
+
- Don't write a cross-shell hybrid (e.g. a `.sh` that detects PowerShell, or vice versa). Ship one script per shell, named with the appropriate extension.
|
|
157
|
+
- Don't install dependencies into the user's global location (`~/.m2`, system-wide `pip`, `~/.cargo`, etc.). Always isolate inside `$WORKING_FOLDER` so concurrent runs and other projects can't interfere.
|
|
158
|
+
- Don't run the test command without first verifying the install step succeeded. A failed install followed by a "test" run produces misleading errors that look like test failures.
|
|
159
|
+
- **Don't link the script from the specs, and don't leave turri guessing.** turri finds the script in `config.turri.yaml`, the project root, or `test_scripts/`, and shows it to the coding agent itself; a link to it from a spec is ignored. Put it where it is found, and when there are two unit testing scripts in one folder, name the one to use as `unittests-script:` - in each stack's `config.turri.<stack>.yaml` when they are for parts on different stacks (see Workflow step 6).
|
|
160
|
+
- **Don't write a terse script.** Silent steps, `>/dev/null` redirects on the install output, missing `--version` prints, and absent failure banners all make the script harder to debug than the code it is testing. Follow the *Logging — be as verbose as possible* rule under [Conventions](#conventions) literally: every step announces itself, every command is echoed, every failure prints what was about to run and where.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env pwsh
|
|
2
|
+
|
|
3
|
+
$ErrorActionPreference = 'Stop'
|
|
4
|
+
|
|
5
|
+
$UNRECOVERABLE_ERROR_EXIT_CODE = 69
|
|
6
|
+
|
|
7
|
+
if (-not $args[0]) {
|
|
8
|
+
Write-Host "Error: No source folder name provided."
|
|
9
|
+
Write-Host "Usage: $($MyInvocation.MyCommand.Name) <source_folder_name>"
|
|
10
|
+
exit $UNRECOVERABLE_ERROR_EXIT_CODE
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (-not (Get-Command flutter -ErrorAction SilentlyContinue)) {
|
|
14
|
+
Write-Host "Error: flutter is not available in PATH."
|
|
15
|
+
exit $UNRECOVERABLE_ERROR_EXIT_CODE
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
$SOURCE_FOLDER = $args[0]
|
|
19
|
+
# Working folder lives in the system temp directory. $SOURCE_FOLDER may be an
|
|
20
|
+
# absolute path, so only its leaf name is used to build the working folder name.
|
|
21
|
+
$BUILD_SUBFOLDER = Join-Path ([System.IO.Path]::GetTempPath()) "flutter_$(Split-Path $SOURCE_FOLDER -Leaf)"
|
|
22
|
+
|
|
23
|
+
Write-Host "Current directory: $(Get-Location)"
|
|
24
|
+
Write-Host "Source folder: $SOURCE_FOLDER"
|
|
25
|
+
Write-Host "--------------------------------"
|
|
26
|
+
|
|
27
|
+
if (Test-Path $BUILD_SUBFOLDER) {
|
|
28
|
+
Remove-Item -Path $BUILD_SUBFOLDER -Recurse -Force
|
|
29
|
+
}
|
|
30
|
+
New-Item -ItemType Directory -Path $BUILD_SUBFOLDER -Force | Out-Null
|
|
31
|
+
|
|
32
|
+
Copy-Item -Path "$SOURCE_FOLDER/*" -Destination "$BUILD_SUBFOLDER/" -Recurse -Force
|
|
33
|
+
|
|
34
|
+
if (-not (Test-Path $BUILD_SUBFOLDER)) {
|
|
35
|
+
exit $UNRECOVERABLE_ERROR_EXIT_CODE
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
Push-Location $BUILD_SUBFOLDER
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
Write-Host "Resolving Flutter dependencies..."
|
|
42
|
+
|
|
43
|
+
if (Test-Path "pubspec.yaml") {
|
|
44
|
+
flutter pub get
|
|
45
|
+
} else {
|
|
46
|
+
Write-Host "Warning: pubspec.yaml not found. Dependencies might be missing."
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
Write-Host "Running Flutter unittests in $BUILD_SUBFOLDER..."
|
|
50
|
+
|
|
51
|
+
# Run flutter test with a timeout
|
|
52
|
+
$process = Start-Process -FilePath "flutter" -ArgumentList "test", "--reporter", "expanded" `
|
|
53
|
+
-NoNewWindow -PassThru -RedirectStandardOutput "flutter_test_stdout.txt" -RedirectStandardError "flutter_test_stderr.txt"
|
|
54
|
+
|
|
55
|
+
$timedOut = $false
|
|
56
|
+
if (-not $process.WaitForExit(120000)) {
|
|
57
|
+
$timedOut = $true
|
|
58
|
+
$process | Stop-Process -Force
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
$output = ""
|
|
62
|
+
if (Test-Path "flutter_test_stdout.txt") {
|
|
63
|
+
$output += Get-Content "flutter_test_stdout.txt" -Raw -ErrorAction SilentlyContinue
|
|
64
|
+
}
|
|
65
|
+
if (Test-Path "flutter_test_stderr.txt") {
|
|
66
|
+
$output += Get-Content "flutter_test_stderr.txt" -Raw -ErrorAction SilentlyContinue
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
$exit_code = $process.ExitCode
|
|
70
|
+
|
|
71
|
+
if ($timedOut) {
|
|
72
|
+
Write-Host "`nError: Unittests timed out after 120 seconds."
|
|
73
|
+
exit 124
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
Write-Host $output
|
|
77
|
+
|
|
78
|
+
exit $exit_code
|
|
79
|
+
} finally {
|
|
80
|
+
# Clean up temp files
|
|
81
|
+
Remove-Item -Path "flutter_test_stdout.txt" -ErrorAction SilentlyContinue
|
|
82
|
+
Remove-Item -Path "flutter_test_stderr.txt" -ErrorAction SilentlyContinue
|
|
83
|
+
Pop-Location
|
|
84
|
+
if (Test-Path $BUILD_SUBFOLDER) {
|
|
85
|
+
Remove-Item -Path $BUILD_SUBFOLDER -Recurse -Force -ErrorAction SilentlyContinue
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env pwsh
|
|
2
|
+
|
|
3
|
+
$ErrorActionPreference = 'Stop'
|
|
4
|
+
|
|
5
|
+
$UNRECOVERABLE_ERROR_EXIT_CODE = 69
|
|
6
|
+
|
|
7
|
+
# Check if subfolder name is provided
|
|
8
|
+
if (-not $args[0]) {
|
|
9
|
+
Write-Host "Error: No subfolder name provided."
|
|
10
|
+
Write-Host "Usage: $($MyInvocation.MyCommand.Name) <subfolder_name>"
|
|
11
|
+
exit $UNRECOVERABLE_ERROR_EXIT_CODE
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
$BuildFolder = $args[0]
|
|
15
|
+
|
|
16
|
+
# Working folder lives in the system temp directory. $BuildFolder may be an
|
|
17
|
+
# absolute path, so only its leaf name is used to build the working folder name.
|
|
18
|
+
$GO_BUILD_SUBFOLDER = Join-Path ([System.IO.Path]::GetTempPath()) "go_$(Split-Path $BuildFolder -Leaf)"
|
|
19
|
+
|
|
20
|
+
if ($env:VERBOSE -eq "1") {
|
|
21
|
+
Write-Host "Preparing Go build subfolder: $GO_BUILD_SUBFOLDER"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Check if the go build subfolder exists
|
|
25
|
+
if (Test-Path $GO_BUILD_SUBFOLDER) {
|
|
26
|
+
# Delete all files and folders inside
|
|
27
|
+
Get-ChildItem -Path $GO_BUILD_SUBFOLDER -Force | Remove-Item -Recurse -Force
|
|
28
|
+
|
|
29
|
+
if ($env:VERBOSE -eq "1") {
|
|
30
|
+
Write-Host "Cleanup completed."
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
if ($env:VERBOSE -eq "1") {
|
|
34
|
+
Write-Host "Subfolder does not exist. Creating it..."
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
New-Item -ItemType Directory -Path $GO_BUILD_SUBFOLDER -Force | Out-Null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
Copy-Item -Path "$BuildFolder/*" -Destination $GO_BUILD_SUBFOLDER -Recurse -Force
|
|
41
|
+
|
|
42
|
+
# Move to the subfolder
|
|
43
|
+
if (-not (Test-Path $GO_BUILD_SUBFOLDER)) {
|
|
44
|
+
Write-Host "Error: Go build folder '$GO_BUILD_SUBFOLDER' does not exist."
|
|
45
|
+
exit $UNRECOVERABLE_ERROR_EXIT_CODE
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
Push-Location $GO_BUILD_SUBFOLDER
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
Write-Host "Running go get..."
|
|
52
|
+
# Temporarily allow stderr output without throwing (Go tools write to stderr)
|
|
53
|
+
# ForEach-Object converts ErrorRecord objects (from stderr) to plain strings to avoid verbose error formatting
|
|
54
|
+
$ErrorActionPreference = 'Continue'
|
|
55
|
+
$output = go get 2>&1 | ForEach-Object { if ($_ -is [System.Management.Automation.ErrorRecord]) { $_.Exception.Message } else { $_ } } | Out-String
|
|
56
|
+
$ErrorActionPreference = 'Stop'
|
|
57
|
+
if ($output.Trim()) { Write-Host $output }
|
|
58
|
+
|
|
59
|
+
# Execute all Golang unittests in the subfolder
|
|
60
|
+
Write-Host "Running Golang unittests in $BuildFolder..."
|
|
61
|
+
$ErrorActionPreference = 'Continue'
|
|
62
|
+
$output = go test 2>&1 | ForEach-Object { if ($_ -is [System.Management.Automation.ErrorRecord]) { $_.Exception.Message } else { $_ } } | Out-String
|
|
63
|
+
$exit_code = $LASTEXITCODE
|
|
64
|
+
$ErrorActionPreference = 'Stop'
|
|
65
|
+
|
|
66
|
+
Write-Host $output
|
|
67
|
+
exit $exit_code
|
|
68
|
+
} finally {
|
|
69
|
+
Pop-Location
|
|
70
|
+
if (Test-Path $GO_BUILD_SUBFOLDER) {
|
|
71
|
+
Remove-Item -Path $GO_BUILD_SUBFOLDER -Recurse -Force -ErrorAction SilentlyContinue
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Check that Java 21 is installed
|
|
4
|
+
if ! /usr/libexec/java_home -v 21 >/dev/null 2>&1; then
|
|
5
|
+
echo "Error: Java 21 is not installed."
|
|
6
|
+
exit 69
|
|
7
|
+
fi
|
|
8
|
+
|
|
9
|
+
export JAVA_HOME=$(/usr/libexec/java_home -v 21)
|
|
10
|
+
java --version
|
|
11
|
+
|
|
12
|
+
# Check if subfolder name is provided
|
|
13
|
+
if [ -z "$1" ]; then
|
|
14
|
+
echo "Error: No subfolder name provided."
|
|
15
|
+
echo "Usage: $0 <subfolder_name>"
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
# Working folder lives in the system temp directory. $1 may be an absolute
|
|
20
|
+
# path, so only its basename is used to build the working folder name.
|
|
21
|
+
WORKING_FOLDER="/tmp/java_$(basename "$1")"
|
|
22
|
+
|
|
23
|
+
trap 'rm -rf "$WORKING_FOLDER"' EXIT
|
|
24
|
+
|
|
25
|
+
# Check if the java subfolder exists
|
|
26
|
+
if [ -d "$WORKING_FOLDER" ]; then
|
|
27
|
+
# delete everything in the subfolder
|
|
28
|
+
rm -rf "$WORKING_FOLDER"/*
|
|
29
|
+
else
|
|
30
|
+
echo "Subfolder '$WORKING_FOLDER' does not exist. Creating it now..."
|
|
31
|
+
mkdir -p "$WORKING_FOLDER"
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# copy all folders and files from the build folder to the subfolder
|
|
36
|
+
cp -R "$1"/* "$WORKING_FOLDER"
|
|
37
|
+
printf "Copied from $1 to $WORKING_FOLDER...\n"
|
|
38
|
+
# Move to the subfolder
|
|
39
|
+
cd "$WORKING_FOLDER" 2>/dev/null
|
|
40
|
+
printf "Moved to $WORKING_FOLDER...\n"
|
|
41
|
+
if [ $? -ne 0 ]; then
|
|
42
|
+
echo "Error: Subfolder '$1' does not exist."
|
|
43
|
+
exit 2
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Static checks: the code compiled with every javac lint warning an error - the flags pinned here, not in the pom.
|
|
47
|
+
# A formatter runs only when the implementation reqs name one (mvn spotless:check, say). Every check runs, and the
|
|
48
|
+
# tests run whatever they said.
|
|
49
|
+
STATIC_CHECKS_FAILED=80
|
|
50
|
+
echo "Running the static checks in $(pwd)..."
|
|
51
|
+
echo "+ mvn -Dmaven.repo.local=./.m2 -Dmaven.compiler.showWarnings=true -Dmaven.compiler.failOnWarning=true -Dmaven.compiler.compilerArgument=-Xlint:all test-compile"
|
|
52
|
+
mvn -Dmaven.repo.local=./.m2 -Dmaven.compiler.showWarnings=true -Dmaven.compiler.failOnWarning=true \
|
|
53
|
+
-Dmaven.compiler.compilerArgument=-Xlint:all test-compile
|
|
54
|
+
CHECKS=$?
|
|
55
|
+
[ "$CHECKS" -eq 0 ] && echo "Static checks: passed" || echo "Static checks: FAILED - the findings are above"
|
|
56
|
+
|
|
57
|
+
# Execute all Java unittests in the subfolder
|
|
58
|
+
echo "Running Java unittests in $(pwd)..."
|
|
59
|
+
echo "+ mvn -Dmaven.repo.local=./.m2 test"
|
|
60
|
+
mvn -Dmaven.repo.local=./.m2 test
|
|
61
|
+
TESTS=$?
|
|
62
|
+
|
|
63
|
+
# The tests' own exit code when they failed; 80 when only the static checks did; 0 when both pass.
|
|
64
|
+
if [ "$TESTS" -ne 0 ]; then
|
|
65
|
+
STATUS=$TESTS
|
|
66
|
+
elif [ "$CHECKS" -ne 0 ]; then
|
|
67
|
+
STATUS=$STATIC_CHECKS_FAILED
|
|
68
|
+
else
|
|
69
|
+
STATUS=0
|
|
70
|
+
fi
|
|
71
|
+
echo "static checks: exit $CHECKS; unit tests: exit $TESTS; exit $STATUS (working folder $WORKING_FOLDER)"
|
|
72
|
+
exit "$STATUS"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env pwsh
|
|
2
|
+
|
|
3
|
+
$ErrorActionPreference = 'Stop'
|
|
4
|
+
|
|
5
|
+
$UNRECOVERABLE_ERROR_EXIT_CODE = 69
|
|
6
|
+
|
|
7
|
+
# Check if subfolder name is provided
|
|
8
|
+
if (-not $args[0]) {
|
|
9
|
+
Write-Host "Error: No subfolder name provided."
|
|
10
|
+
Write-Host "Usage: $($MyInvocation.MyCommand.Name) <subfolder_name>"
|
|
11
|
+
exit $UNRECOVERABLE_ERROR_EXIT_CODE
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
$BuildFolder = $args[0]
|
|
15
|
+
|
|
16
|
+
# Try to find Python interpreter (python3 first, then python)
|
|
17
|
+
if (Get-Command python3 -ErrorAction SilentlyContinue) {
|
|
18
|
+
$PYTHON_CMD = "python3"
|
|
19
|
+
} elseif (Get-Command python -ErrorAction SilentlyContinue) {
|
|
20
|
+
$PYTHON_CMD = "python"
|
|
21
|
+
} else {
|
|
22
|
+
Write-Host "Error: Python interpreter not found. Please install Python."
|
|
23
|
+
exit $UNRECOVERABLE_ERROR_EXIT_CODE
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Working folder lives in the system temp directory. $BuildFolder may be an
|
|
27
|
+
# absolute path, so only its leaf name is used to build the working folder name.
|
|
28
|
+
$PYTHON_BUILD_SUBFOLDER = Join-Path ([System.IO.Path]::GetTempPath()) "python_$(Split-Path $BuildFolder -Leaf)"
|
|
29
|
+
|
|
30
|
+
if ($env:VERBOSE -eq "1") {
|
|
31
|
+
Write-Host "Preparing Python build subfolder: $PYTHON_BUILD_SUBFOLDER"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Check if the Python build subfolder exists
|
|
35
|
+
if (Test-Path $PYTHON_BUILD_SUBFOLDER) {
|
|
36
|
+
# Delete all files and folders inside
|
|
37
|
+
Get-ChildItem -Path $PYTHON_BUILD_SUBFOLDER -Force | Remove-Item -Recurse -Force
|
|
38
|
+
|
|
39
|
+
if ($env:VERBOSE -eq "1") {
|
|
40
|
+
Write-Host "Cleanup completed."
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
if ($env:VERBOSE -eq "1") {
|
|
44
|
+
Write-Host "Subfolder does not exist. Creating it..."
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
New-Item -ItemType Directory -Path $PYTHON_BUILD_SUBFOLDER -Force | Out-Null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Copy-Item -Path "$BuildFolder/*" -Destination $PYTHON_BUILD_SUBFOLDER -Recurse -Force
|
|
51
|
+
|
|
52
|
+
# Move to the subfolder
|
|
53
|
+
if (-not (Test-Path $PYTHON_BUILD_SUBFOLDER)) {
|
|
54
|
+
Write-Host "Error: Python build folder '$PYTHON_BUILD_SUBFOLDER' does not exist."
|
|
55
|
+
exit $UNRECOVERABLE_ERROR_EXIT_CODE
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
Push-Location $PYTHON_BUILD_SUBFOLDER
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
# Execute all Python unittests in the subfolder
|
|
62
|
+
Write-Host "Running Python unittests in $PYTHON_BUILD_SUBFOLDER..."
|
|
63
|
+
|
|
64
|
+
# Temporarily allow stderr output without throwing (Python unittest writes progress to stderr)
|
|
65
|
+
# ForEach-Object converts ErrorRecord objects (from stderr) to plain strings to avoid verbose error formatting
|
|
66
|
+
$ErrorActionPreference = 'Continue'
|
|
67
|
+
$output = & $PYTHON_CMD -m unittest discover -b 2>&1 | ForEach-Object { if ($_ -is [System.Management.Automation.ErrorRecord]) { $_.Exception.Message } else { $_ } } | Out-String
|
|
68
|
+
$exit_code = $LASTEXITCODE
|
|
69
|
+
$ErrorActionPreference = 'Stop'
|
|
70
|
+
|
|
71
|
+
# Echo the original output
|
|
72
|
+
Write-Host $output
|
|
73
|
+
|
|
74
|
+
# Return the exit code of the unittest command
|
|
75
|
+
exit $exit_code
|
|
76
|
+
} finally {
|
|
77
|
+
Pop-Location
|
|
78
|
+
if (Test-Path $PYTHON_BUILD_SUBFOLDER) {
|
|
79
|
+
Remove-Item -Path $PYTHON_BUILD_SUBFOLDER -Recurse -Force -ErrorAction SilentlyContinue
|
|
80
|
+
}
|
|
81
|
+
}
|