Skip to content

Commit adc0fc6

Browse files
committed
feat(cli): invoke bundled release script at runtime for offline scaffold
- Embed release scripts (bash + PowerShell) in wheel via pyproject.toml - Replace Python _generate_agent_commands() with subprocess invocation of the canonical create-release-packages.sh, guaranteeing byte-for-byte parity between 'specify init --offline' and GitHub release ZIPs - Fix macOS bash 3.2 compat in release script: replace cp --parents, local -n (nameref), and mapfile with POSIX-safe alternatives - Fix _TOML_AGENTS: remove qwen (uses markdown per release script) - Rename --from-github to --offline (opt-in to bundled assets) - Add _locate_release_script() for cross-platform script discovery - Update tests: remove bash 4+/GNU coreutils requirements, handle Kimi directory-per-skill layout, 576 tests passing - Update CHANGELOG and docs/installation.md
1 parent 52170d6 commit adc0fc6

6 files changed

Lines changed: 693 additions & 184 deletions

File tree

.github/workflows/scripts/create-release-packages.sh

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ build_variant() {
218218
esac
219219
fi
220220

221-
[[ -d templates ]] && { mkdir -p "$SPEC_DIR/templates"; find templates -type f -not -path "templates/commands/*" -not -name "vscode-settings.json" -exec cp --parents {} "$SPEC_DIR"/ \; ; echo "Copied templates -> .specify/templates"; }
221+
[[ -d templates ]] && { mkdir -p "$SPEC_DIR/templates"; find templates -type f -not -path "templates/commands/*" -not -name "vscode-settings.json" | while IFS= read -r f; do d="$SPEC_DIR/$(dirname "$f")"; mkdir -p "$d"; cp "$f" "$d/"; done; echo "Copied templates -> .specify/templates"; }
222222

223223
case $agent in
224224
claude)
@@ -303,34 +303,32 @@ build_variant() {
303303
ALL_AGENTS=(claude gemini copilot cursor-agent qwen opencode windsurf codex kilocode auggie roo codebuddy amp shai tabnine kiro-cli agy bob vibe qodercli kimi generic)
304304
ALL_SCRIPTS=(sh ps)
305305

306-
norm_list() {
307-
tr ',\n' ' ' | awk '{for(i=1;i<=NF;i++){if(!seen[$i]++){printf((out?"\n":"") $i);out=1}}}END{printf("\n")}'
308-
}
309-
310306
validate_subset() {
311-
local type=$1; shift; local -n allowed=$1; shift; local items=("$@")
307+
local type=$1; shift
308+
local sep="$1"; shift # separator-joined allowed values
312309
local invalid=0
313-
for it in "${items[@]}"; do
314-
local found=0
315-
for a in "${allowed[@]}"; do [[ $it == "$a" ]] && { found=1; break; }; done
316-
if [[ $found -eq 0 ]]; then
317-
echo "Error: unknown $type '$it' (allowed: ${allowed[*]})" >&2
318-
invalid=1
319-
fi
310+
for it in "$@"; do
311+
case ",$sep," in
312+
*,"$it",*) ;;
313+
*) echo "Error: unknown $type '$it' (allowed: ${sep//,/ })" >&2; invalid=1 ;;
314+
esac
320315
done
321316
return $invalid
322317
}
323318

319+
read_list() { tr ',\n' ' ' | awk '{for(i=1;i<=NF;i++){if(!seen[$i]++){printf((out?" ":"") $i);out=1}}}END{printf("\n")}'; }
320+
join_csv() { local IFS=,; echo "$*"; }
321+
324322
if [[ -n ${AGENTS:-} ]]; then
325-
mapfile -t AGENT_LIST < <(printf '%s' "$AGENTS" | norm_list)
326-
validate_subset agent ALL_AGENTS "${AGENT_LIST[@]}" || exit 1
323+
read -ra AGENT_LIST <<< "$(printf '%s' "$AGENTS" | read_list)"
324+
validate_subset agent "$(join_csv "${ALL_AGENTS[@]}")" "${AGENT_LIST[@]}" || exit 1
327325
else
328326
AGENT_LIST=("${ALL_AGENTS[@]}")
329327
fi
330328

331329
if [[ -n ${SCRIPTS:-} ]]; then
332-
mapfile -t SCRIPT_LIST < <(printf '%s' "$SCRIPTS" | norm_list)
333-
validate_subset script ALL_SCRIPTS "${SCRIPT_LIST[@]}" || exit 1
330+
read -ra SCRIPT_LIST <<< "$(printf '%s' "$SCRIPTS" | read_list)"
331+
validate_subset script "$(join_csv "${ALL_SCRIPTS[@]}")" "${SCRIPT_LIST[@]}" || exit 1
334332
else
335333
SCRIPT_LIST=("${ALL_SCRIPTS[@]}")
336334
fi

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3535

3636
### Added
3737

38-
- feat(cli): embed core templates/commands/scripts in wheel for air-gapped deployment; `specify init` now works offline by default (#1711)
39-
- feat(cli): add `--from-github` flag to `specify init` to force download from GitHub releases instead of using bundled assets
38+
- feat(cli): embed core templates/commands/scripts in wheel for air-gapped deployment; `specify init --offline` uses bundled assets without network access (#1711)
39+
- feat(cli): add `--offline` flag to `specify init` to scaffold from bundled assets instead of downloading from GitHub (for air-gapped/enterprise environments)
40+
- feat(cli): embed release scripts (bash + PowerShell) in wheel and invoke at runtime for guaranteed parity with GitHub release ZIPs
4041
- feat(release): build and publish `specify_cli-*.whl` Python wheel as a release asset for enterprise/offline installation (#1752)
4142
- feat(presets): Pluggable preset system with preset catalog and template resolver
4243
- Preset manifest (`preset.yml`) with validation for artifact, command, and script types

docs/installation.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,13 @@ pip install --no-index --find-links=./vendor specify_cli-*.whl
110110

111111
> **Note:** Python 3.11+ is required. The wheel is a pure-Python artifact, so it works on any platform without recompilation.
112112
113-
**Getting the latest templates without upgrading the CLI:**
113+
**Using bundled assets (offline / air-gapped):**
114114

115-
If you want to pull freshly generated command files from the latest GitHub release instead of the bundled copy, use:
115+
If you want to scaffold from the templates bundled inside the specify-cli
116+
package instead of downloading from GitHub, use:
116117

117118
```bash
118-
specify init my-project --ai claude --from-github
119+
specify init my-project --ai claude --offline
119120
```
120121

121122
### Git Credential Manager on Linux

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ packages = ["src/specify_cli"]
3232
"templates/commands" = "specify_cli/core_pack/commands"
3333
"scripts/bash" = "specify_cli/core_pack/scripts/bash"
3434
"scripts/powershell" = "specify_cli/core_pack/scripts/powershell"
35+
".github/workflows/scripts/create-release-packages.sh" = "specify_cli/core_pack/release_scripts/create-release-packages.sh"
36+
".github/workflows/scripts/create-release-packages.ps1" = "specify_cli/core_pack/release_scripts/create-release-packages.ps1"
3537

3638
[project.optional-dependencies]
3739
test = [

0 commit comments

Comments
 (0)