mirrorneuron-cli 1.0.0__tar.gz

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.
Files changed (37) hide show
  1. mirrorneuron_cli-1.0.0/.github/workflows/ci.yml +193 -0
  2. mirrorneuron_cli-1.0.0/.github/workflows/release.yml +390 -0
  3. mirrorneuron_cli-1.0.0/.gitignore +34 -0
  4. mirrorneuron_cli-1.0.0/LICENSE +21 -0
  5. mirrorneuron_cli-1.0.0/PKG-INFO +73 -0
  6. mirrorneuron_cli-1.0.0/README.md +59 -0
  7. mirrorneuron_cli-1.0.0/RELEASE.md +63 -0
  8. mirrorneuron_cli-1.0.0/mirrorneuron_cli.egg-info/PKG-INFO +73 -0
  9. mirrorneuron_cli-1.0.0/mirrorneuron_cli.egg-info/SOURCES.txt +35 -0
  10. mirrorneuron_cli-1.0.0/mirrorneuron_cli.egg-info/dependency_links.txt +1 -0
  11. mirrorneuron_cli-1.0.0/mirrorneuron_cli.egg-info/entry_points.txt +2 -0
  12. mirrorneuron_cli-1.0.0/mirrorneuron_cli.egg-info/requires.txt +3 -0
  13. mirrorneuron_cli-1.0.0/mirrorneuron_cli.egg-info/top_level.txt +1 -0
  14. mirrorneuron_cli-1.0.0/mn_cli/__init__.py +0 -0
  15. mirrorneuron_cli-1.0.0/mn_cli/config.py +43 -0
  16. mirrorneuron_cli-1.0.0/mn_cli/error_handler.py +51 -0
  17. mirrorneuron_cli-1.0.0/mn_cli/libs/__init__.py +1 -0
  18. mirrorneuron_cli-1.0.0/mn_cli/libs/blueprint_cmds.py +598 -0
  19. mirrorneuron_cli-1.0.0/mn_cli/libs/job_cmds.py +160 -0
  20. mirrorneuron_cli-1.0.0/mn_cli/libs/run_cmds.py +780 -0
  21. mirrorneuron_cli-1.0.0/mn_cli/libs/sys_cmds.py +52 -0
  22. mirrorneuron_cli-1.0.0/mn_cli/libs/ui.py +162 -0
  23. mirrorneuron_cli-1.0.0/mn_cli/logging_config.py +38 -0
  24. mirrorneuron_cli-1.0.0/mn_cli/main.py +35 -0
  25. mirrorneuron_cli-1.0.0/mn_cli/server_cmds.py +331 -0
  26. mirrorneuron_cli-1.0.0/mn_cli/shared.py +13 -0
  27. mirrorneuron_cli-1.0.0/pyproject.toml +26 -0
  28. mirrorneuron_cli-1.0.0/scripts/check-release-artifacts.sh +28 -0
  29. mirrorneuron_cli-1.0.0/scripts/make-release-zip.sh +73 -0
  30. mirrorneuron_cli-1.0.0/scripts/validate-version-tag.sh +47 -0
  31. mirrorneuron_cli-1.0.0/setup.cfg +4 -0
  32. mirrorneuron_cli-1.0.0/tests/conftest.py +39 -0
  33. mirrorneuron_cli-1.0.0/tests/test_blueprint_cmds.py +508 -0
  34. mirrorneuron_cli-1.0.0/tests/test_job_cmds.py +163 -0
  35. mirrorneuron_cli-1.0.0/tests/test_run_cmds.py +443 -0
  36. mirrorneuron_cli-1.0.0/tests/test_server_cmds.py +294 -0
  37. mirrorneuron_cli-1.0.0/tests/test_sys_cmds.py +92 -0
@@ -0,0 +1,193 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ ci:
14
+ name: Test and build
15
+ runs-on: ubuntu-latest
16
+
17
+ steps:
18
+ - name: Check out repository
19
+ uses: actions/checkout@v4
20
+ with:
21
+ fetch-depth: 0
22
+
23
+ - name: Detect project type
24
+ id: detect
25
+ shell: bash
26
+ run: |
27
+ set -euo pipefail
28
+
29
+ has_python_package="false"
30
+ has_python_tests="false"
31
+ has_npm="false"
32
+ has_elixir="false"
33
+ has_shell_scripts="false"
34
+
35
+ if find . -maxdepth 2 -name pyproject.toml -print -quit | grep -q .; then
36
+ has_python_package="true"
37
+ fi
38
+
39
+ if [[ -f pytest.ini || -f requirements.txt ]] || find . -path './.git' -prune -o -name 'test_*.py' -print -quit | grep -q .; then
40
+ has_python_tests="true"
41
+ fi
42
+
43
+ if [[ -f package.json ]]; then
44
+ has_npm="true"
45
+ fi
46
+
47
+ if [[ -f mix.exs ]]; then
48
+ has_elixir="true"
49
+ fi
50
+
51
+ if find . -path './.git' -prune -o -name '*.sh' -print -quit | grep -q .; then
52
+ has_shell_scripts="true"
53
+ fi
54
+
55
+ {
56
+ echo "has_python_package=$has_python_package"
57
+ echo "has_python_tests=$has_python_tests"
58
+ echo "has_npm=$has_npm"
59
+ echo "has_elixir=$has_elixir"
60
+ echo "has_shell_scripts=$has_shell_scripts"
61
+ } >> "$GITHUB_OUTPUT"
62
+
63
+ - name: Set up Python
64
+ if: steps.detect.outputs.has_python_package == 'true' || steps.detect.outputs.has_python_tests == 'true'
65
+ uses: actions/setup-python@v5
66
+ with:
67
+ python-version: "3.11"
68
+ cache: pip
69
+
70
+ - name: Set up Node.js
71
+ if: steps.detect.outputs.has_npm == 'true'
72
+ uses: actions/setup-node@v4
73
+ with:
74
+ node-version: "20"
75
+ cache: npm
76
+
77
+ - name: Set up Elixir
78
+ if: steps.detect.outputs.has_elixir == 'true'
79
+ uses: erlef/setup-beam@v1
80
+ with:
81
+ elixir-version: "1.16"
82
+ otp-version: "26"
83
+
84
+ - name: Install Python dependencies
85
+ if: steps.detect.outputs.has_python_package == 'true' || steps.detect.outputs.has_python_tests == 'true'
86
+ shell: bash
87
+ run: |
88
+ set -euo pipefail
89
+
90
+ python -m pip install --upgrade pip
91
+ python -m pip install build twine pytest pytest-github-actions-annotate-failures
92
+
93
+ if [[ "$GITHUB_REPOSITORY" == "MirrorNeuronLab/mn-api" || "$GITHUB_REPOSITORY" == "MirrorNeuronLab/mn-cli" ]]; then
94
+ python -m pip install "mirrorneuron-python-sdk @ git+https://github.com/MirrorNeuronLab/mn-python-sdk.git"
95
+ fi
96
+
97
+ if [[ "$GITHUB_REPOSITORY" == "MirrorNeuronLab/mn-blueprints" ]]; then
98
+ python -m pip install "mirrorneuron-python-sdk @ git+https://github.com/MirrorNeuronLab/mn-python-sdk.git"
99
+ python -m pip install "mirrorneuron-blueprint-support-skill @ git+https://github.com/MirrorNeuronLab/mn-skills.git#subdirectory=blueprint_support_skill"
100
+ fi
101
+
102
+ if [[ "$GITHUB_REPOSITORY" == "MirrorNeuronLab/mn-system-tests" ]]; then
103
+ python -m pip install pytest pytest-cov requests
104
+ python -m pip install "mirrorneuron-python-sdk @ git+https://github.com/MirrorNeuronLab/mn-python-sdk.git"
105
+ python -m pip install "mn-cli @ git+https://github.com/MirrorNeuronLab/mn-cli.git"
106
+ elif [[ -f requirements.txt && "${{ steps.detect.outputs.has_python_package }}" != "true" ]]; then
107
+ python -m pip install -r requirements.txt
108
+ fi
109
+
110
+ mapfile -t pyprojects < <(find . -maxdepth 2 -name pyproject.toml | sort)
111
+ for pyproject in "${pyprojects[@]}"; do
112
+ package_dir="$(dirname "$pyproject")"
113
+ python -m pip install -e "$package_dir"
114
+ done
115
+
116
+ - name: Run configured Python checks
117
+ if: steps.detect.outputs.has_python_package == 'true'
118
+ shell: bash
119
+ run: |
120
+ set -euo pipefail
121
+
122
+ if grep -R --include pyproject.toml -q '^\[tool\.ruff\]' .; then
123
+ python -m pip install ruff
124
+ python -m ruff check .
125
+ fi
126
+
127
+ if grep -R --include pyproject.toml -q '^\[tool\.mypy\]' .; then
128
+ python -m pip install mypy
129
+ python -m mypy .
130
+ fi
131
+
132
+ - name: Run Python tests
133
+ if: steps.detect.outputs.has_python_tests == 'true'
134
+ env:
135
+ COLUMNS: "240"
136
+ run: python -m pytest
137
+
138
+ - name: Build Python distributions
139
+ if: steps.detect.outputs.has_python_package == 'true'
140
+ shell: bash
141
+ run: |
142
+ set -euo pipefail
143
+
144
+ rm -rf dist
145
+ mkdir -p dist
146
+ mapfile -t pyprojects < <(find . -maxdepth 2 -name pyproject.toml | sort)
147
+ for pyproject in "${pyprojects[@]}"; do
148
+ package_dir="$(dirname "$pyproject")"
149
+ python -m build "$package_dir" --outdir dist
150
+ done
151
+
152
+ python -m twine check dist/*
153
+
154
+ - name: Install npm dependencies
155
+ if: steps.detect.outputs.has_npm == 'true'
156
+ run: npm ci
157
+
158
+ - name: Run npm lint
159
+ if: steps.detect.outputs.has_npm == 'true'
160
+ run: npm run lint --if-present
161
+
162
+ - name: Run npm tests
163
+ if: steps.detect.outputs.has_npm == 'true'
164
+ run: npm test --if-present
165
+
166
+ - name: Build npm project
167
+ if: steps.detect.outputs.has_npm == 'true'
168
+ run: npm run build --if-present
169
+
170
+ - name: Install Elixir dependencies
171
+ if: steps.detect.outputs.has_elixir == 'true'
172
+ run: mix deps.get
173
+
174
+ - name: Check Elixir formatting
175
+ if: steps.detect.outputs.has_elixir == 'true'
176
+ run: mix format --check-formatted
177
+
178
+ - name: Run Elixir tests
179
+ if: steps.detect.outputs.has_elixir == 'true'
180
+ run: mix test
181
+
182
+ - name: Build Elixir project
183
+ if: steps.detect.outputs.has_elixir == 'true'
184
+ run: mix compile --warnings-as-errors
185
+
186
+ - name: Check shell scripts
187
+ if: steps.detect.outputs.has_shell_scripts == 'true'
188
+ shell: bash
189
+ run: |
190
+ set -euo pipefail
191
+ while IFS= read -r -d '' script; do
192
+ bash -n "$script"
193
+ done < <(find . -path './.git' -prune -o -name '*.sh' -print0)
@@ -0,0 +1,390 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+ workflow_dispatch:
8
+ inputs:
9
+ tag:
10
+ description: "Existing release tag to publish, such as v1.0.1 or v1.0.1-rc.1"
11
+ required: true
12
+ type: string
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ jobs:
18
+ build:
19
+ name: Validate, test, and build
20
+ runs-on: ubuntu-latest
21
+ permissions:
22
+ contents: read
23
+ outputs:
24
+ tag_name: ${{ steps.version.outputs.tag_name }}
25
+ version: ${{ steps.version.outputs.version }}
26
+ is_prerelease: ${{ steps.version.outputs.is_prerelease }}
27
+ has_python_package: ${{ steps.detect.outputs.has_python_package }}
28
+
29
+ steps:
30
+ - name: Check out tag
31
+ uses: actions/checkout@v4
32
+ with:
33
+ fetch-depth: 0
34
+ ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }}
35
+
36
+ - name: Validate release tag
37
+ id: version
38
+ shell: bash
39
+ env:
40
+ INPUT_TAG: ${{ inputs.tag }}
41
+ run: |
42
+ set -euo pipefail
43
+ tag="$GITHUB_REF_NAME"
44
+ if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then
45
+ tag="$INPUT_TAG"
46
+ fi
47
+ scripts/validate-version-tag.sh "$tag"
48
+
49
+ - name: Detect project type
50
+ id: detect
51
+ shell: bash
52
+ run: |
53
+ set -euo pipefail
54
+
55
+ has_python_package="false"
56
+ has_python_tests="false"
57
+ has_npm="false"
58
+ has_elixir="false"
59
+ has_shell_scripts="false"
60
+
61
+ if find . -maxdepth 2 -name pyproject.toml -print -quit | grep -q .; then
62
+ has_python_package="true"
63
+ fi
64
+
65
+ if [[ -f pytest.ini || -f requirements.txt ]] || find . -path './.git' -prune -o -name 'test_*.py' -print -quit | grep -q .; then
66
+ has_python_tests="true"
67
+ fi
68
+
69
+ if [[ -f package.json ]]; then
70
+ has_npm="true"
71
+ fi
72
+
73
+ if [[ -f mix.exs ]]; then
74
+ has_elixir="true"
75
+ fi
76
+
77
+ if find . -path './.git' -prune -o -name '*.sh' -print -quit | grep -q .; then
78
+ has_shell_scripts="true"
79
+ fi
80
+
81
+ {
82
+ echo "has_python_package=$has_python_package"
83
+ echo "has_python_tests=$has_python_tests"
84
+ echo "has_npm=$has_npm"
85
+ echo "has_elixir=$has_elixir"
86
+ echo "has_shell_scripts=$has_shell_scripts"
87
+ } >> "$GITHUB_OUTPUT"
88
+
89
+ - name: Set up Python
90
+ if: steps.detect.outputs.has_python_package == 'true' || steps.detect.outputs.has_python_tests == 'true'
91
+ uses: actions/setup-python@v5
92
+ with:
93
+ python-version: "3.11"
94
+ cache: pip
95
+
96
+ - name: Set up Node.js
97
+ if: steps.detect.outputs.has_npm == 'true'
98
+ uses: actions/setup-node@v4
99
+ with:
100
+ node-version: "20"
101
+ cache: npm
102
+
103
+ - name: Set up Elixir
104
+ if: steps.detect.outputs.has_elixir == 'true'
105
+ uses: erlef/setup-beam@v1
106
+ with:
107
+ elixir-version: "1.16"
108
+ otp-version: "26"
109
+
110
+ - name: Install Python dependencies
111
+ if: steps.detect.outputs.has_python_package == 'true' || steps.detect.outputs.has_python_tests == 'true'
112
+ shell: bash
113
+ run: |
114
+ set -euo pipefail
115
+
116
+ python -m pip install --upgrade pip
117
+ python -m pip install build twine pytest pytest-github-actions-annotate-failures packaging
118
+
119
+ if [[ "$GITHUB_REPOSITORY" == "MirrorNeuronLab/mn-api" || "$GITHUB_REPOSITORY" == "MirrorNeuronLab/mn-cli" ]]; then
120
+ python -m pip install "mirrorneuron-python-sdk @ git+https://github.com/MirrorNeuronLab/mn-python-sdk.git"
121
+ fi
122
+
123
+ if [[ "$GITHUB_REPOSITORY" == "MirrorNeuronLab/mn-blueprints" ]]; then
124
+ python -m pip install "mirrorneuron-python-sdk @ git+https://github.com/MirrorNeuronLab/mn-python-sdk.git"
125
+ python -m pip install "mirrorneuron-blueprint-support-skill @ git+https://github.com/MirrorNeuronLab/mn-skills.git#subdirectory=blueprint_support_skill"
126
+ fi
127
+
128
+ if [[ "$GITHUB_REPOSITORY" == "MirrorNeuronLab/mn-system-tests" ]]; then
129
+ python -m pip install pytest pytest-cov requests
130
+ python -m pip install "mirrorneuron-python-sdk @ git+https://github.com/MirrorNeuronLab/mn-python-sdk.git"
131
+ python -m pip install "mn-cli @ git+https://github.com/MirrorNeuronLab/mn-cli.git"
132
+ elif [[ -f requirements.txt && "${{ steps.detect.outputs.has_python_package }}" != "true" ]]; then
133
+ python -m pip install -r requirements.txt
134
+ fi
135
+
136
+ mapfile -t pyprojects < <(find . -maxdepth 2 -name pyproject.toml | sort)
137
+ for pyproject in "${pyprojects[@]}"; do
138
+ package_dir="$(dirname "$pyproject")"
139
+ python -m pip install -e "$package_dir"
140
+ done
141
+
142
+ - name: Run configured Python checks
143
+ if: steps.detect.outputs.has_python_package == 'true'
144
+ shell: bash
145
+ run: |
146
+ set -euo pipefail
147
+
148
+ if grep -R --include pyproject.toml -q '^\[tool\.ruff\]' .; then
149
+ python -m pip install ruff
150
+ python -m ruff check .
151
+ fi
152
+
153
+ if grep -R --include pyproject.toml -q '^\[tool\.mypy\]' .; then
154
+ python -m pip install mypy
155
+ python -m mypy .
156
+ fi
157
+
158
+ - name: Run Python tests
159
+ if: steps.detect.outputs.has_python_tests == 'true'
160
+ env:
161
+ COLUMNS: "240"
162
+ run: python -m pytest
163
+
164
+ - name: Build Python distributions
165
+ if: steps.detect.outputs.has_python_package == 'true'
166
+ shell: bash
167
+ run: |
168
+ set -euo pipefail
169
+
170
+ rm -rf dist
171
+ mkdir -p dist
172
+ mapfile -t pyprojects < <(find . -maxdepth 2 -name pyproject.toml | sort)
173
+ for pyproject in "${pyprojects[@]}"; do
174
+ package_dir="$(dirname "$pyproject")"
175
+ python -m build "$package_dir" --outdir dist
176
+ done
177
+
178
+ python -m twine check dist/*
179
+
180
+ - name: Verify Python distribution versions
181
+ if: steps.detect.outputs.has_python_package == 'true'
182
+ shell: bash
183
+ run: |
184
+ set -euo pipefail
185
+
186
+ python - "$VERSION" <<'PY'
187
+ import email.parser
188
+ import glob
189
+ import pathlib
190
+ import sys
191
+ import tarfile
192
+ import zipfile
193
+
194
+ from packaging.version import Version
195
+
196
+ expected = str(Version(sys.argv[1]))
197
+ versions = {}
198
+
199
+ for wheel in glob.glob("dist/*.whl"):
200
+ with zipfile.ZipFile(wheel) as archive:
201
+ metadata_name = next(name for name in archive.namelist() if name.endswith(".dist-info/METADATA"))
202
+ metadata = email.parser.Parser().parsestr(archive.read(metadata_name).decode("utf-8"))
203
+ versions[pathlib.Path(wheel).name] = metadata["Version"]
204
+
205
+ for sdist in glob.glob("dist/*.tar.gz"):
206
+ with tarfile.open(sdist) as archive:
207
+ metadata_member = next(member for member in archive.getmembers() if member.name.endswith("/PKG-INFO"))
208
+ metadata_file = archive.extractfile(metadata_member)
209
+ if metadata_file is None:
210
+ raise SystemExit(f"Could not read PKG-INFO from {sdist}")
211
+ metadata = email.parser.Parser().parsestr(metadata_file.read().decode("utf-8"))
212
+ versions[pathlib.Path(sdist).name] = metadata["Version"]
213
+
214
+ if not versions:
215
+ raise SystemExit("No Python distributions were found in dist/.")
216
+
217
+ mismatches = {
218
+ name: version
219
+ for name, version in versions.items()
220
+ if str(Version(version)) != expected
221
+ }
222
+
223
+ if mismatches:
224
+ for name, version in mismatches.items():
225
+ print(f"{name}: expected {expected}, found {version}", file=sys.stderr)
226
+ raise SystemExit(1)
227
+
228
+ print(f"Python distribution versions match {expected}.")
229
+ PY
230
+
231
+ - name: Install npm dependencies
232
+ if: steps.detect.outputs.has_npm == 'true'
233
+ run: npm ci
234
+
235
+ - name: Apply tag version to npm metadata
236
+ if: steps.detect.outputs.has_npm == 'true'
237
+ run: npm version "$VERSION" --no-git-tag-version --allow-same-version
238
+
239
+ - name: Run npm lint
240
+ if: steps.detect.outputs.has_npm == 'true'
241
+ run: npm run lint --if-present
242
+
243
+ - name: Run npm tests
244
+ if: steps.detect.outputs.has_npm == 'true'
245
+ run: npm test --if-present
246
+
247
+ - name: Build npm project
248
+ if: steps.detect.outputs.has_npm == 'true'
249
+ run: npm run build --if-present
250
+
251
+ - name: Install Elixir dependencies
252
+ if: steps.detect.outputs.has_elixir == 'true'
253
+ run: mix deps.get
254
+
255
+ - name: Check Elixir formatting
256
+ if: steps.detect.outputs.has_elixir == 'true'
257
+ run: mix format --check-formatted
258
+
259
+ - name: Run Elixir tests
260
+ if: steps.detect.outputs.has_elixir == 'true'
261
+ run: mix test
262
+
263
+ - name: Build Elixir project
264
+ if: steps.detect.outputs.has_elixir == 'true'
265
+ env:
266
+ MIX_PROJECT_VERSION: ${{ steps.version.outputs.version }}
267
+ run: mix compile --warnings-as-errors
268
+
269
+ - name: Check shell scripts
270
+ if: steps.detect.outputs.has_shell_scripts == 'true'
271
+ shell: bash
272
+ run: |
273
+ set -euo pipefail
274
+ while IFS= read -r -d '' script; do
275
+ bash -n "$script"
276
+ done < <(find . -path './.git' -prune -o -name '*.sh' -print0)
277
+
278
+ - name: Create release ZIP
279
+ shell: bash
280
+ run: |
281
+ set -euo pipefail
282
+ mkdir -p dist/release-assets
283
+ scripts/make-release-zip.sh "$TAG_NAME" dist/release-assets
284
+
285
+ - name: Create SHA256 checksums
286
+ shell: bash
287
+ run: |
288
+ set -euo pipefail
289
+ mapfile -t artifacts < <(find dist dist/release-assets -maxdepth 1 -type f \( -name '*.zip' -o -name '*.whl' -o -name '*.tar.gz' \) | LC_ALL=C sort)
290
+ if [[ "${#artifacts[@]}" -eq 0 ]]; then
291
+ echo "No release artifacts found for checksums." >&2
292
+ exit 1
293
+ fi
294
+ sha256sum "${artifacts[@]}" > dist/SHA256SUMS.txt
295
+
296
+ - name: Validate release artifacts
297
+ shell: bash
298
+ run: |
299
+ set -euo pipefail
300
+ mapfile -t artifacts < <(find dist dist/release-assets -maxdepth 1 -type f \( -name '*.zip' -o -name '*.whl' -o -name '*.tar.gz' -o -name 'SHA256SUMS.txt' \) | LC_ALL=C sort)
301
+ scripts/check-release-artifacts.sh "${artifacts[@]}"
302
+
303
+ - name: Upload release artifacts
304
+ uses: actions/upload-artifact@v4
305
+ with:
306
+ name: release-assets
307
+ path: |
308
+ dist/release-assets/*.zip
309
+ dist/SHA256SUMS.txt
310
+ dist/*.whl
311
+ dist/*.tar.gz
312
+ if-no-files-found: error
313
+
314
+ - name: Upload Python distributions
315
+ if: steps.detect.outputs.has_python_package == 'true'
316
+ uses: actions/upload-artifact@v4
317
+ with:
318
+ name: python-dist
319
+ path: |
320
+ dist/*.whl
321
+ dist/*.tar.gz
322
+ if-no-files-found: error
323
+
324
+ github-release:
325
+ name: Create GitHub Release
326
+ runs-on: ubuntu-latest
327
+ needs: build
328
+ permissions:
329
+ contents: write
330
+
331
+ steps:
332
+ - name: Download release artifacts
333
+ uses: actions/download-artifact@v4
334
+ with:
335
+ name: release-assets
336
+ path: release-assets
337
+
338
+ - name: Create GitHub Release
339
+ shell: bash
340
+ env:
341
+ GH_TOKEN: ${{ github.token }}
342
+ GH_REPO: ${{ github.repository }}
343
+ TAG_NAME: ${{ needs.build.outputs.tag_name }}
344
+ IS_PRERELEASE: ${{ needs.build.outputs.is_prerelease }}
345
+ run: |
346
+ set -euo pipefail
347
+
348
+ if gh release view "$TAG_NAME" >/dev/null 2>&1; then
349
+ echo "Release $TAG_NAME already exists; refusing to overwrite it." >&2
350
+ exit 1
351
+ fi
352
+
353
+ mapfile -t assets < <(find release-assets -type f | LC_ALL=C sort)
354
+ if [[ "${#assets[@]}" -eq 0 ]]; then
355
+ echo "No release assets were downloaded." >&2
356
+ exit 1
357
+ fi
358
+
359
+ release_args=(release create "$TAG_NAME")
360
+ release_args+=("${assets[@]}")
361
+ release_args+=(--title "$TAG_NAME" --verify-tag --generate-notes)
362
+
363
+ if [[ "$IS_PRERELEASE" == "true" ]]; then
364
+ release_args+=(--prerelease)
365
+ fi
366
+
367
+ gh "${release_args[@]}"
368
+
369
+ publish-pypi:
370
+ name: Publish to PyPI
371
+ runs-on: ubuntu-latest
372
+ needs:
373
+ - build
374
+ - github-release
375
+ if: needs.build.outputs.has_python_package == 'true' && (needs.build.outputs.is_prerelease == 'false' || vars.PUBLISH_PRERELEASES_TO_PYPI == 'true')
376
+ permissions:
377
+ contents: read
378
+ id-token: write
379
+ environment:
380
+ name: pypi
381
+
382
+ steps:
383
+ - name: Download Python distributions
384
+ uses: actions/download-artifact@v4
385
+ with:
386
+ name: python-dist
387
+ path: dist
388
+
389
+ - name: Publish Python distributions to PyPI
390
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,34 @@
1
+ # Environments
2
+ .env
3
+ .venv
4
+ env/
5
+ venv/
6
+ ENV/
7
+ env.bak/
8
+ venv.bak/
9
+
10
+ # Build and Distribution
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+
28
+ # Caches
29
+ __pycache__/
30
+ *.py[cod]
31
+ *$py.class
32
+ .pytest_cache/
33
+ .coverage
34
+ htmlcov/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MirrorNeuronLab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.