Files
fission-ai__openspec/scripts/postinstall.js
Noam Meron 1adf3cea88 feature/oh-my-zsh-completions (#289)
* shell completions for zsh

* after code review changes

* expose only postinstall.js script

* Replace _openspec "$@" with compdef in zsh-generator.ts to prevent execution during load

* Update test/commands/completion.test.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* - Fix dispatcher to use $words instead of $line for subcommand routing
  - Add __complete endpoint with tab-separated output for safe parsing
  - Replace brittle awk parsing with __complete in completion helpers
  - Add uninstall confirmation prompt with --yes flag to skip
  - Prefer $ZSH env var for Oh My Zsh detection before dir check
  - Add fpath verification guidance for OMZ installations
  - Update cli-completion spec to document generate subcommand

* improve shell detection and installation handling

  - Return structured result from detectShell() with shell and detected name
  - Detect already-installed completions and skip reinstall
  - Add update detection with automatic backup of previous version
  - Add debug logging to silent catch blocks for diagnostics
  - Quote fpath directories to handle paths with spaces
  - Verify Oh My Zsh fpath configuration and add to .zshrc if needed
  - Show helpful error for detected but unsupported shells
  - Update all tests for new detection API

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Tabish Bidiwale <30385142+TabishB@users.noreply.github.com>
2025-12-12 21:30:40 +11:00

148 lines
4.1 KiB
JavaScript

#!/usr/bin/env node
/**
* Postinstall script for auto-installing shell completions
*
* This script runs automatically after npm install unless:
* - CI=true environment variable is set
* - OPENSPEC_NO_COMPLETIONS=1 environment variable is set
* - dist/ directory doesn't exist (dev setup scenario)
*
* The script never fails npm install - all errors are caught and handled gracefully.
*/
import { promises as fs } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
/**
* Check if we should skip installation
*/
function shouldSkipInstallation() {
// Skip in CI environments
if (process.env.CI === 'true' || process.env.CI === '1') {
return { skip: true, reason: 'CI environment detected' };
}
// Skip if user opted out
if (process.env.OPENSPEC_NO_COMPLETIONS === '1') {
return { skip: true, reason: 'OPENSPEC_NO_COMPLETIONS=1 set' };
}
return { skip: false };
}
/**
* Check if dist/ directory exists
*/
async function distExists() {
const distPath = path.join(__dirname, '..', 'dist');
try {
const stat = await fs.stat(distPath);
return stat.isDirectory();
} catch {
return false;
}
}
/**
* Detect the user's shell
*/
async function detectShell() {
try {
const { detectShell } = await import('../dist/utils/shell-detection.js');
const result = detectShell();
return result.shell;
} catch (error) {
// Fail silently if detection module doesn't exist
return undefined;
}
}
/**
* Install completions for the detected shell
*/
async function installCompletions(shell) {
try {
const { CompletionFactory } = await import('../dist/core/completions/factory.js');
const { COMMAND_REGISTRY } = await import('../dist/core/completions/command-registry.js');
// Check if shell is supported
if (!CompletionFactory.isSupported(shell)) {
console.log(`\nTip: Run 'openspec completion install' for shell completions`);
return;
}
// Generate completion script
const generator = CompletionFactory.createGenerator(shell);
const script = generator.generate(COMMAND_REGISTRY);
// Install completion script
const installer = CompletionFactory.createInstaller(shell);
const result = await installer.install(script);
if (result.success) {
// Show success message based on installation type
if (result.isOhMyZsh) {
console.log(`✓ Shell completions installed`);
console.log(` Restart shell: exec zsh`);
} else if (result.zshrcConfigured) {
console.log(`✓ Shell completions installed and configured`);
console.log(` Restart shell: exec zsh`);
} else {
console.log(`✓ Shell completions installed to ~/.zsh/completions/`);
console.log(` Add to ~/.zshrc: fpath=(~/.zsh/completions $fpath)`);
console.log(` Then: exec zsh`);
}
} else {
// Installation failed, show tip for manual install
console.log(`\nTip: Run 'openspec completion install' for shell completions`);
}
} catch (error) {
// Fail gracefully - show tip for manual install
console.log(`\nTip: Run 'openspec completion install' for shell completions`);
}
}
/**
* Main function
*/
async function main() {
try {
// Check if we should skip
const skipCheck = shouldSkipInstallation();
if (skipCheck.skip) {
// Silent skip - no output
return;
}
// Check if dist/ exists (skip silently if not - expected during dev setup)
if (!(await distExists())) {
return;
}
// Detect shell
const shell = await detectShell();
if (!shell) {
console.log(`\nTip: Run 'openspec completion install' for shell completions`);
return;
}
// Install completions
await installCompletions(shell);
} catch (error) {
// Fail gracefully - never break npm install
// Show tip for manual install
console.log(`\nTip: Run 'openspec completion install' for shell completions`);
}
}
// Run main and handle any unhandled errors
main().catch(() => {
// Silent failure - never break npm install
process.exit(0);
});