Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/install/one-time.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,10 @@ If you plan to use Spec Kit regularly, a persistent installation is recommended:
- Tool stays installed and available in PATH
- No re-download on every invocation
- Better tool management with `uv tool list`, `uv tool upgrade`, `uv tool uninstall`
- Required for [presets](../community/presets.md) that provide a **script** template: the generated
script resolves its composition by invoking `specify` at run time, so it needs the CLI (or an
importable `specify_cli`) to still be present after `init` finishes. The `uvx` flow above discards
its environment as soon as the command exits, so a script-providing preset installed this way will
fail the first time its script runs. Use a persistent installation if you plan to use such a preset.

See the main [Installation Guide](../installation.md) for persistent installation instructions.
6 changes: 6 additions & 0 deletions src/specify_cli/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Allow ``python -m specify_cli`` to run the CLI."""

from specify_cli import main

if __name__ == "__main__":
main()
20 changes: 20 additions & 0 deletions src/specify_cli/command_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,26 @@ def init(

ensure_executable_scripts(project_path, tracker=tracker)

# install_shared_infra above may have just overwritten
# .specify/scripts/bash/<name>.sh with the bundled core,
# clobbering any generated continuation dispatcher for a
# script an already-enabled preset provides (e.g. on
# `specify init --force` against an existing project).
# Restore those dispatchers before any *new* --preset
# install below runs its own reconciliation.
try:
from .presets import PresetManager as _ExistingPresetManager

_ExistingPresetManager(project_path).reconcile_all_script_chains()
Comment on lines +831 to +834
except Exception as exc:
_print_cli_warning(
"reconcile script presets after",
"init",
str(project_path),
exc,
continuing="Run 'specify preset script-chain <name>' to diagnose.",
)

if preset:
try:
from .presets import PresetCatalog, PresetError, PresetManager
Expand Down
22 changes: 22 additions & 0 deletions src/specify_cli/integrations/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,28 @@ def _set_default_integration(
f"Failed to refresh shared infrastructure for '{key}': {exc}"
) from exc

# _install_shared_infra above may have just overwritten
# .specify/scripts/bash/<name>.sh with the bundled core when
# refresh_templates_force is True, clobbering any generated
# continuation dispatcher for a script an already-enabled preset
# provides. Restore those dispatchers now, mirroring the same call
# in command_init.py and command_upgrade.py.
if refresh_templates_force:
try:
Comment on lines +348 to +355
from ..presets import PresetManager as _ExistingPresetManager

_ExistingPresetManager(project_root).reconcile_all_script_chains()
except Exception as exc:
from .. import _print_cli_warning

_print_cli_warning(
"reconcile script presets after",
"integration use/switch",
str(project_root),
exc,
continuing="Run 'specify preset script-chain <name>' to diagnose.",
)

_write_integration_json(project_root, key, installed_keys, settings)
_update_init_options_for_integration(
project_root, integration, script_type=resolved_script, parsed_options=parsed_options
Expand Down
22 changes: 22 additions & 0 deletions src/specify_cli/integrations/command_switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,28 @@ def integration_switch(
from .. import ensure_executable_scripts
ensure_executable_scripts(project_root)

# The forced refresh above may have just overwritten
# .specify/scripts/bash/<name>.sh with the bundled core, clobbering any
# generated continuation dispatcher for a script an already-enabled
# preset provides. Restore those dispatchers now: this call happens
# before _set_default_integration below, whose own reconciliation
# (_helpers.py) is gated on refresh_templates_force and would not fire
# here since this phase's force comes from --refresh-shared-infra, not
# from that helper's own parameter.
if refresh_shared_infra:
try:
from ..presets import PresetManager as _ExistingPresetManager

_ExistingPresetManager(project_root).reconcile_all_script_chains()
except Exception as exc:
_print_cli_warning(
"reconcile script presets after",
"integration switch --refresh-shared-infra",
str(project_root),
exc,
continuing="Run 'specify preset script-chain <name>' to diagnose.",
)

# Phase 2: Install target integration
console.print(f"Installing integration: [cyan]{target}[/cyan]")
manifest = IntegrationManifest(
Expand Down
21 changes: 21 additions & 0 deletions src/specify_cli/integrations/command_upgrade.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,27 @@ def integration_upgrade(
from .. import ensure_executable_scripts
ensure_executable_scripts(project_root)

# _install_shared_infra_or_exit above may have just overwritten
# .specify/scripts/bash/<name>.sh with the bundled core when force=True,
# clobbering any generated continuation dispatcher for a script an
# already-enabled preset provides. Restore those dispatchers now,
# mirroring the same call in command_init.py.
if force:
try:
from ..presets import PresetManager as _ExistingPresetManager

_ExistingPresetManager(project_root).reconcile_all_script_chains()
Comment on lines +224 to +228
except Exception as exc:
from .. import _print_cli_warning

_print_cli_warning(
"reconcile script presets after",
"integration upgrade",
str(project_root),
exc,
continuing="Run 'specify preset script-chain <name>' to diagnose.",
)

# Phase 1: Install new files (overwrites existing; old-only files remain)
console.print(f"Upgrading integration: [cyan]{key}[/cyan]")
new_manifest = IntegrationManifest(key, project_root, version=_get_speckit_version())
Expand Down
Loading
Loading