Skip to content

Commit 2f32990

Browse files
authored
feat: add windows portable zip artifacts (#107)
* ci/runtime: add windows portable zip artifacts * fix(ci/runtime): address portable artifact review comments * fix(ci): scope portable nightly normalization * refactor(ci): build windows portable zip from release outputs * fix(testing): address portable packaging review comments * fix(ci/runtime): tighten portable packaging inputs * fix(ci/runtime): share portable marker config * refactor(ci): lazily load portable packaging config
1 parent be7b469 commit 2f32990

13 files changed

Lines changed: 927 additions & 22 deletions

.github/workflows/build-desktop-tauri.yml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -432,8 +432,9 @@ jobs:
432432
runs-on: ${{ matrix.runner }}
433433
env:
434434
ASTRBOT_WINDOWS_BUNDLES: nsis
435-
WINDOWS_INSTALLER_EXE_GLOBS: |
435+
WINDOWS_RELEASE_ASSET_GLOBS: |
436436
src-tauri/target/release/bundle/nsis/*.exe
437+
src-tauri/target/release/bundle/nsis/*portable*.zip
437438
strategy:
438439
fail-fast: false
439440
matrix:
@@ -482,6 +483,14 @@ jobs:
482483
set -euo pipefail
483484
node scripts/ci/backend-smoke-test.mjs --label "windows-${{ matrix.arch }}"
484485
486+
- name: Package portable zip artifact (Windows)
487+
shell: bash
488+
run: |
489+
set -euo pipefail
490+
python3 -m scripts.ci.package_windows_portable \
491+
--bundle-dir src-tauri/target/release/bundle/nsis \
492+
--output-dir src-tauri/target/release/bundle/nsis
493+
485494
- name: Verify Windows installer outputs
486495
shell: bash
487496
run: bash scripts/ci/verify-windows-installer-outputs.sh
@@ -492,9 +501,10 @@ jobs:
492501
name: astrbot-desktop-tauri-${{ needs.resolve_build_context.outputs.astrbot_version }}-windows-${{ matrix.arch }}
493502
if-no-files-found: error
494503
# Only upload Windows installer executables.
495-
# A broad **/*.exe pattern also matches bundled Python runtime helpers.
504+
# Keep explicit globs here so we only publish the installer plus the
505+
# portable zip, not bundled helper executables from the embedded runtime.
496506
path: |
497-
${{ env.WINDOWS_INSTALLER_EXE_GLOBS }}
507+
${{ env.WINDOWS_RELEASE_ASSET_GLOBS }}
498508
src-tauri/target/release/bundle/nsis/*.exe.sig
499509
500510
release:
@@ -594,7 +604,8 @@ jobs:
594604
- Source: `${{ needs.resolve_build_context.outputs.source_git_url }}`
595605
- Ref: `${{ needs.resolve_build_context.outputs.source_git_ref }}`
596606
- Mode: `${{ needs.resolve_build_context.outputs.build_mode }}`
597-
- Windows installer format: `nsis`.
607+
- Windows package formats: `nsis installer` and `portable zip`.
608+
- Windows portable zip uses manual replacement updates and requires WebView2 to already exist on the host.
598609
generate_release_notes: true
599610
prerelease: ${{ needs.resolve_build_context.outputs.release_prerelease == 'true' }}
600611
make_latest: ${{ needs.resolve_build_context.outputs.release_make_latest == 'true' }}

.github/workflows/check-scripts.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,19 @@ jobs:
6464
exit 0
6565
fi
6666
python3 -m compileall -q scripts
67+
68+
- name: Run Python script behavior tests
69+
run: |
70+
set -euo pipefail
71+
if [ ! -d scripts/ci ]; then
72+
echo "scripts/ci directory does not exist, skip Python script tests."
73+
exit 0
74+
fi
75+
76+
mapfile -t test_files < <(find scripts/ci -type f -name 'test_*.py' | sort)
77+
if [ "${#test_files[@]}" -eq 0 ]; then
78+
echo "No Python script tests found (test_*.py), skip."
79+
exit 0
80+
fi
81+
82+
python3 -m unittest discover -s scripts/ci -p 'test_*.py'

scripts/ci/normalize_release_artifact_filenames.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
ARTIFACT_EXTENSIONS,
1414
LOCALE_PATTERN,
1515
MACOS_CANONICAL_ARTIFACT_STEM_PATTERN,
16+
SHORT_SHA_PATTERN,
1617
VERSION_PATTERN,
1718
WINDOWS_ARTIFACT_STEM_PATTERN_FRAGMENT,
1819
)
@@ -61,6 +62,12 @@
6162
MACOS_CANONICAL_ARTIFACT_STEM_PATTERN,
6263
"AstrBot_{version}_macos_{arch}",
6364
),
65+
(
66+
re.compile(
67+
rf"{WINDOWS_ARTIFACT_STEM_PATTERN_FRAGMENT}(?:-portable|_portable)(?P<nightly_suffix>_nightly_{SHORT_SHA_PATTERN})?$"
68+
),
69+
"AstrBot_{version}_windows_{arch}_portable{nightly_suffix}",
70+
),
6471
),
6572
".app.tar.gz": (
6673
(
@@ -148,14 +155,18 @@ def strip_nightly_suffix(stem: str) -> str:
148155
return NIGHTLY_HASH_PATTERN.sub("", stem)
149156

150157

158+
def should_preserve_original_nightly_suffix(stem: str, ext: str) -> bool:
159+
return ext == ".zip" and ("_portable" in stem or "-portable" in stem)
160+
161+
151162
def canonicalize_stem(
152163
stem: str, ext: str, warned_unknown_arches: set[str]
153164
) -> tuple[str, bool]:
154165
for pattern, normalized_template in CANONICALIZE_RULES.get(ext, ()):
155166
match = pattern.fullmatch(stem)
156167
if not match:
157168
continue
158-
groups = match.groupdict()
169+
groups = {key: value or "" for key, value in match.groupdict().items()}
159170
if "arch" in groups:
160171
groups["arch"] = normalize_arch(groups["arch"], warned_unknown_arches)
161172
return normalized_template.format(**groups), True
@@ -228,11 +239,21 @@ def main() -> int:
228239

229240
original_name = path.name
230241
original_stem = strip_extension(original_name, ext)
242+
canonical_ext = canonicalization_extension(ext)
231243

232244
stripped_stem = strip_nightly_suffix(original_stem)
233-
normalized_stem, matched = canonicalize_stem(
234-
stripped_stem, canonicalization_extension(ext), warned_unknown_arches
235-
)
245+
candidate_stems = [stripped_stem]
246+
if should_preserve_original_nightly_suffix(original_stem, canonical_ext):
247+
candidate_stems.insert(0, original_stem)
248+
249+
normalized_stem = original_stem
250+
matched = False
251+
for candidate_stem in candidate_stems:
252+
normalized_stem, matched = canonicalize_stem(
253+
candidate_stem, canonical_ext, warned_unknown_arches
254+
)
255+
if matched:
256+
break
236257

237258
if not matched:
238259
unmatched_messages.append(

0 commit comments

Comments
 (0)