politty 0.5.0 → 0.5.1

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.
@@ -1,7 +1,7 @@
1
1
  import { a as toCamelCase, h as arg, m as resolveSubCommandMeta, n as getAllAliases, t as extractFields, u as resolveSubCommandAlias } from "./schema-extractor-C50R-1re.js";
2
2
  import { z } from "zod";
3
3
  import { execSync, spawn } from "node:child_process";
4
- import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
4
+ import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, writeFileSync } from "node:fs";
5
5
  import { dirname, join } from "node:path";
6
6
 
7
7
  //#region src/core/command.ts
@@ -1923,6 +1923,87 @@ function buildHeaderLines(opts) {
1923
1923
  return lines;
1924
1924
  }
1925
1925
 
1926
+ //#endregion
1927
+ //#region src/completion/self-refresh.ts
1928
+ /**
1929
+ * Self-refresh guards embedded in generated bash/zsh scripts.
1930
+ *
1931
+ * These guards make the default `completion <shell>` output safe to
1932
+ * save as a static completion file: when the CLI binary changes, the
1933
+ * script asks the hidden refresh subcommand to rewrite the sourced
1934
+ * file in place, then sources the fresh file and stops executing the
1935
+ * stale body.
1936
+ */
1937
+ function statSigExpr() {
1938
+ return `$(stat -L -c '%Y' "$_bin" 2>/dev/null || stat -L -f '%m' "$_bin" 2>/dev/null)`;
1939
+ }
1940
+ function generateBashSelfRefresh(opts) {
1941
+ const { programName, binPath } = opts;
1942
+ const fn = sanitize(programName);
1943
+ const sig = computeBinSig(resolveBinPath(programName, binPath));
1944
+ const refreshFn = `__${fn}_self_refresh`;
1945
+ return [
1946
+ `${refreshFn}() {`,
1947
+ ` local _self _bin _sig`,
1948
+ ` _self=\${BASH_SOURCE[0]:-}`,
1949
+ ` [[ -n "$_self" && -f "$_self" ]] || return 1`,
1950
+ ` head -n 8 "$_self" 2>/dev/null | grep -qF "# politty-completion-version:" || return 1`,
1951
+ ` head -n 8 "$_self" 2>/dev/null | grep -qF "# program: ${programName}" || return 1`,
1952
+ ` head -n 8 "$_self" 2>/dev/null | grep -qF "# shell: bash" || return 1`,
1953
+ ` _bin=$(type -P ${programName} 2>/dev/null)`,
1954
+ ` [[ -n "$_bin" ]] || return 1`,
1955
+ ` _sig=${statSigExpr()} || return 1`,
1956
+ ` [[ "$_sig" != "${sig}" ]] || return 1`,
1957
+ ` "$_bin" __refresh-completion bash "$_self" 2>/dev/null || return 1`,
1958
+ ` head -n 8 "$_self" 2>/dev/null | grep -qF "# politty-bin-sig: $_sig" || return 1`,
1959
+ ` source "$_self" 2>/dev/null || return 1`,
1960
+ ` return 0`,
1961
+ `}`,
1962
+ `if ${refreshFn}; then`,
1963
+ ` unset -f ${refreshFn}`,
1964
+ ` return 0 2>/dev/null || true`,
1965
+ `else`,
1966
+ ` unset -f ${refreshFn}`,
1967
+ `fi`,
1968
+ ``
1969
+ ];
1970
+ }
1971
+ function generateZshSelfRefresh(opts) {
1972
+ const { programName, binPath } = opts;
1973
+ const fn = sanitize(programName);
1974
+ const completionFn = `_${programName}`;
1975
+ const sig = computeBinSig(resolveBinPath(programName, binPath));
1976
+ const refreshFn = `__${fn}_self_refresh`;
1977
+ return [
1978
+ `${refreshFn}() {`,
1979
+ ` emulate -L zsh`,
1980
+ ` setopt local_options no_aliases`,
1981
+ ` local _self _bin _sig`,
1982
+ ` _self="\${(%):-%x}"`,
1983
+ ` [[ -n "$_self" && -f "$_self" ]] || return 1`,
1984
+ ` head -n 8 "$_self" 2>/dev/null | grep -qF "# politty-completion-version:" || return 1`,
1985
+ ` head -n 8 "$_self" 2>/dev/null | grep -qF "# program: ${programName}" || return 1`,
1986
+ ` head -n 8 "$_self" 2>/dev/null | grep -qF "# shell: zsh" || return 1`,
1987
+ ` _bin=$(whence -p ${programName} 2>/dev/null)`,
1988
+ ` [[ -n "$_bin" ]] || return 1`,
1989
+ ` _sig=${statSigExpr()} || return 1`,
1990
+ ` [[ "$_sig" != "${sig}" ]] || return 1`,
1991
+ ` "$_bin" __refresh-completion zsh "$_self" 2>/dev/null || return 1`,
1992
+ ` head -n 8 "$_self" 2>/dev/null | grep -qF "# politty-bin-sig: $_sig" || return 1`,
1993
+ ` source "$_self" 2>/dev/null || return 1`,
1994
+ ` ${completionFn} "$@"`,
1995
+ ` return 0`,
1996
+ `}`,
1997
+ `if ${refreshFn} "$@"; then`,
1998
+ ` unfunction ${refreshFn} 2>/dev/null`,
1999
+ ` return 0 2>/dev/null || true`,
2000
+ `else`,
2001
+ ` unfunction ${refreshFn} 2>/dev/null`,
2002
+ `fi`,
2003
+ ``
2004
+ ];
2005
+ }
2006
+
1926
2007
  //#endregion
1927
2008
  //#region src/completion/bash.ts
1928
2009
  /** Escape a string for use inside bash double-quotes */
@@ -2185,6 +2266,10 @@ function generateBashCompletion(command, options) {
2185
2266
  }));
2186
2267
  lines.push(`# Generated by politty`);
2187
2268
  lines.push(``);
2269
+ lines.push(...generateBashSelfRefresh({
2270
+ programName,
2271
+ binPath: options.binPath
2272
+ }));
2188
2273
  const hasExpand = expandSpecs.length > 0;
2189
2274
  const arrayExpandSpecs = expandSpecs.filter((s) => s.isArrayOption);
2190
2275
  const hasArrayExpand = arrayExpandSpecs.length > 0;
@@ -2429,12 +2514,11 @@ function generateBashCompletion(command, options) {
2429
2514
  return {
2430
2515
  script: lines.join("\n"),
2431
2516
  shell: "bash",
2432
- installInstructions: `# To enable completions, add the following to your ~/.bashrc:
2433
-
2434
- # Option 1: Source directly
2517
+ installInstructions: `# To enable auto-refreshing bash completions, add this to your ~/.bashrc:
2435
2518
  eval "$(${programName} completion bash)"
2436
2519
 
2437
- # Option 2: Save to a file
2520
+ # For faster shell startup, save the script instead:
2521
+ mkdir -p ~/.local/share/bash-completion/completions
2438
2522
  ${programName} completion bash > ~/.local/share/bash-completion/completions/${programName}
2439
2523
 
2440
2524
  # Then reload your shell or run:
@@ -2818,8 +2902,9 @@ function generateFishCompletion(command, options) {
2818
2902
  lines.push(` test -z "$_bin"; and return 1`);
2819
2903
  lines.push(` set -l _sig (stat -L -c '%Y' "$_bin" 2>/dev/null; or stat -L -f '%m' "$_bin" 2>/dev/null)`);
2820
2904
  lines.push(` test "$_sig" = "${sig}"; and return 1`);
2821
- lines.push(` set -l _target "$__fish_config_dir/completions/${programName}.fish"`);
2822
- lines.push(` "$_bin" __refresh-completion fish 2>/dev/null`);
2905
+ lines.push(` set -l _target (status current-filename)`);
2906
+ lines.push(` test -n "$_target"; and test -f "$_target"; or return 1`);
2907
+ lines.push(` "$_bin" __refresh-completion fish "$_target" 2>/dev/null`);
2823
2908
  lines.push(` and source "$_target" 2>/dev/null`);
2824
2909
  lines.push(` and return 0`);
2825
2910
  lines.push(` return 1`);
@@ -3096,17 +3181,8 @@ function generateFishCompletion(command, options) {
3096
3181
  return {
3097
3182
  script: lines.join("\n"),
3098
3183
  shell: "fish",
3099
- installInstructions: `# To enable completions, run one of the following:
3100
-
3101
- # Option 1: Source directly
3102
- ${programName} completion fish | source
3103
-
3104
- # Option 2: Save to the fish completions directory
3105
- ${programName} completion fish > ~/.config/fish/completions/${programName}.fish
3106
-
3107
- # The completion will be available immediately in new shell sessions.
3108
- # To use in the current session, run:
3109
- source ~/.config/fish/completions/${programName}.fish`
3184
+ installInstructions: `# To enable auto-refreshing fish completions, run:
3185
+ ${programName} completion fish --install`
3110
3186
  };
3111
3187
  }
3112
3188
 
@@ -3135,11 +3211,11 @@ source ~/.config/fish/completions/${programName}.fish`
3135
3211
  * from env / config — we must not let metachars in the path execute as
3136
3212
  * commands when the rc snippet is sourced.
3137
3213
  */
3138
- function shSingleQuote(s) {
3214
+ function shSingleQuote$1(s) {
3139
3215
  return `'${s.replace(/'/g, "'\\''")}'`;
3140
3216
  }
3141
3217
  function bashCachePathExpr(programName, cacheDir, shell) {
3142
- if (cacheDir) return shSingleQuote(`${cacheDir}/completion.${shell}`);
3218
+ if (cacheDir) return shSingleQuote$1(`${cacheDir}/completion.${shell}`);
3143
3219
  return `"\${XDG_CACHE_HOME:-$HOME/.cache}/${programName}/completion.${shell}"`;
3144
3220
  }
3145
3221
  function generateBashLoader(opts) {
@@ -3283,6 +3359,15 @@ function readCachedSig(path) {
3283
3359
  return null;
3284
3360
  }
3285
3361
  }
3362
+ function isManagedTarget(path, programName, shell) {
3363
+ try {
3364
+ if (!existsSync(path)) return false;
3365
+ const head = readFileSync(path, "utf8").split("\n", 8).join("\n");
3366
+ return /^# politty-completion-version: \S+/m.test(head) && head.includes(`# program: ${programName}`) && head.includes(`# shell: ${shell}`);
3367
+ } catch {
3368
+ return false;
3369
+ }
3370
+ }
3286
3371
  /**
3287
3372
  * Rewrite the cache only when stale. Used by:
3288
3373
  * - `<program> __refresh-completion <shell>` (the hidden subcommand
@@ -3298,7 +3383,8 @@ function readCachedSig(path) {
3298
3383
  */
3299
3384
  function refreshIfStale(ctx, shell) {
3300
3385
  try {
3301
- const target = installPath(ctx.programName, shell, ctx.cacheDir);
3386
+ const target = ctx.targetPath ? realpathSync(ctx.targetPath) : installPath(ctx.programName, shell, ctx.cacheDir);
3387
+ if (ctx.targetPath && !isManagedTarget(target, ctx.programName, shell)) return;
3302
3388
  const binPath = resolveBinPath(ctx.programName, ctx.binPath);
3303
3389
  if (!binPath) return;
3304
3390
  let currentSig;
@@ -3360,6 +3446,10 @@ function escapeDesc(s) {
3360
3446
  function escapeDescribeValue(s) {
3361
3447
  return s.replace(/\\/g, "\\\\").replace(/:/g, "\\:");
3362
3448
  }
3449
+ /** Escape a string for use inside zsh double-quotes. */
3450
+ function escapeZshDQ(s) {
3451
+ return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$").replace(/`/g, "\\`");
3452
+ }
3363
3453
  /**
3364
3454
  * Generate zsh value completion lines for a ValueCompletion spec.
3365
3455
  * Uses `_vals` array (must be declared in the calling function scope).
@@ -3546,6 +3636,8 @@ function generateZshCompletion(command, options) {
3546
3636
  const { programName } = options;
3547
3637
  const data = extractCompletionData(command, programName, options.globalArgsSchema);
3548
3638
  const fn = sanitize(programName);
3639
+ const completionFn = `_${programName}`;
3640
+ const autoloadCheck = `"\${funcstack[1]:-}" == "${escapeZshDQ(completionFn)}"`;
3549
3641
  const root = data.command;
3550
3642
  const visibleSubs = getVisibleSubs(root.subcommands);
3551
3643
  const expandSpecs = collectExpandSpecs(root);
@@ -3564,6 +3656,10 @@ function generateZshCompletion(command, options) {
3564
3656
  }));
3565
3657
  lines.push(`# Generated by politty`);
3566
3658
  lines.push(``);
3659
+ lines.push(...generateZshSelfRefresh({
3660
+ programName,
3661
+ binPath: options.binPath
3662
+ }));
3567
3663
  for (const spec of expandSpecs) {
3568
3664
  const varName = expandTableVarName(fn, spec.funcSuffix, spec.fieldName);
3569
3665
  if (spec.vc.table.length === 0) lines.push(`typeset -gA ${varName}=()`);
@@ -3706,7 +3802,7 @@ function generateZshCompletion(command, options) {
3706
3802
  lines.push(`}`);
3707
3803
  lines.push(``);
3708
3804
  const subRouting = subDispatchCaseLines(routeEntries, fn).join("\n");
3709
- lines.push(`_${fn}() {`);
3805
+ lines.push(`${completionFn}() {`);
3710
3806
  lines.push(` (( CURRENT )) || CURRENT=\${#words}`);
3711
3807
  lines.push(``);
3712
3808
  lines.push(` local _subcmd="" _after_dd=0 _pos_count=0 _skip_next=0`);
@@ -3772,22 +3868,28 @@ function generateZshCompletion(command, options) {
3772
3868
  lines.push(``);
3773
3869
  lines.push(`zstyle ':completion:*:*:${programName}:*' file-patterns '%p:globbed-files *(-/):directories'`);
3774
3870
  lines.push(``);
3775
- lines.push(`compdef _${fn} ${programName}`);
3871
+ lines.push(`if [[ ${autoloadCheck} ]]; then`);
3872
+ lines.push(` ${completionFn} "$@"`);
3873
+ lines.push(`else`);
3874
+ lines.push(` compdef ${completionFn} ${programName}`);
3875
+ lines.push(`fi`);
3776
3876
  lines.push(``);
3777
3877
  return {
3778
3878
  script: lines.join("\n"),
3779
3879
  shell: "zsh",
3780
- installInstructions: `# To enable completions, add the following to your ~/.zshrc:
3781
-
3782
- # Option 1: Source directly (add before compinit)
3880
+ installInstructions: `# To enable auto-refreshing zsh completions, add this to your ~/.zshrc after compinit:
3783
3881
  eval "$(${programName} completion zsh)"
3784
3882
 
3785
- # Option 2: Save to a file in your fpath
3883
+ # For faster shell startup, save the script in your fpath:
3884
+ mkdir -p ~/.zsh/completions
3786
3885
  ${programName} completion zsh > ~/.zsh/completions/_${programName}
3787
3886
 
3788
- # Make sure your fpath includes the completions directory:
3789
- # fpath=(~/.zsh/completions $fpath)
3790
- # autoload -Uz compinit && compinit
3887
+ # Make sure your ~/.zshrc includes the fpath line before compinit:
3888
+ fpath=(~/.zsh/completions $fpath)
3889
+ autoload -Uz compinit && compinit
3890
+
3891
+ # If ~/.zshrc already calls compinit, add only the fpath line before
3892
+ # the existing compinit call.
3791
3893
 
3792
3894
  # Then reload your shell or run:
3793
3895
  source ~/.zshrc`
@@ -3842,6 +3944,21 @@ function getSupportedShells() {
3842
3944
  "fish"
3843
3945
  ];
3844
3946
  }
3947
+ function shSingleQuote(s) {
3948
+ return `'${s.replace(/'/g, "'\\''")}'`;
3949
+ }
3950
+ function printZshFpathSetup(programName, target) {
3951
+ console.error("");
3952
+ console.error("Configure zsh fpath with:");
3953
+ console.error("");
3954
+ console.error(" mkdir -p ~/.zsh/completions");
3955
+ console.error(` ln -sf ${shSingleQuote(target)} ~/.zsh/completions/_${programName}`);
3956
+ console.error("");
3957
+ console.error("Add only this block to your ~/.zshrc before compinit:");
3958
+ console.error("");
3959
+ console.error(" fpath=(~/.zsh/completions $fpath)");
3960
+ console.error(" autoload -Uz compinit && compinit");
3961
+ }
3845
3962
  /**
3846
3963
  * Detect the current shell from environment
3847
3964
  */
@@ -3872,15 +3989,22 @@ const completionArgsSchema = z.object({
3872
3989
  loader: arg(z.boolean().default(false), { description: "Print just the rc loader snippet (bash/zsh). Add it to ~/.bashrc or ~/.zshrc; it auto-regenerates the cache when the binary changes." }),
3873
3990
  install: arg(z.boolean().default(false), { description: "Write the completion script to its on-disk cache (bash/zsh) or autoload location (fish) instead of printing it." })
3874
3991
  });
3875
- const refreshArgsSchema = z.object({ shell: arg(z.enum([
3876
- "bash",
3877
- "zsh",
3878
- "fish"
3879
- ]), {
3880
- positional: true,
3881
- description: "Shell to refresh",
3882
- placeholder: "SHELL"
3883
- }) });
3992
+ const refreshArgsSchema = z.object({
3993
+ shell: arg(z.enum([
3994
+ "bash",
3995
+ "zsh",
3996
+ "fish"
3997
+ ]), {
3998
+ positional: true,
3999
+ description: "Shell to refresh",
4000
+ placeholder: "SHELL"
4001
+ }),
4002
+ target: arg(z.string().optional(), {
4003
+ positional: true,
4004
+ description: "Existing politty-generated completion file to refresh",
4005
+ placeholder: "TARGET"
4006
+ })
4007
+ });
3884
4008
  /**
3885
4009
  * Create a completion subcommand for your CLI
3886
4010
  *
@@ -3942,7 +4066,7 @@ function createCompletionCommand(rootCommand, programName, globalArgsSchema, ext
3942
4066
  throw new Error(`install failed: ${e instanceof Error ? e.message : String(e)}`);
3943
4067
  }
3944
4068
  console.error(`installed: ${target}`);
3945
- if (shellType !== "fish") {
4069
+ if (shellType === "bash") {
3946
4070
  console.error("");
3947
4071
  console.error(`Add to your ~/.${shellType}rc:`);
3948
4072
  console.error("");
@@ -3950,7 +4074,7 @@ function createCompletionCommand(rootCommand, programName, globalArgsSchema, ext
3950
4074
  ...loaderOptsBase,
3951
4075
  shell: shellType
3952
4076
  }).trim().replace(/^/gm, " "));
3953
- }
4077
+ } else if (shellType === "zsh") printZshFpathSetup(resolvedProgramName, target);
3954
4078
  return;
3955
4079
  }
3956
4080
  if (args.loader) {
@@ -3988,7 +4112,8 @@ function createRefreshCompletionCommand(rootCommand, programName, extra = {}) {
3988
4112
  refreshIfStale({
3989
4113
  rootCommand,
3990
4114
  programName,
3991
- ...extra
4115
+ ...extra,
4116
+ ...args.target !== void 0 && { targetPath: args.target }
3992
4117
  }, args.shell);
3993
4118
  }
3994
4119
  });
@@ -4064,4 +4189,4 @@ function maybeSpawnRefresh(argv, ctx) {
4064
4189
 
4065
4190
  //#endregion
4066
4191
  export { defineCommand as _, getSupportedShells as a, hasCompleteCommand as c, extractPositionals as d, CompletionDirective as f, createDefineCommand as g, resolveValueCompletion as h, generateCompletion as i, formatForShell as l, parseCompletionContext as m, createRefreshCompletionCommand as n, withCompletionCommand as o, generateCandidates as p, detectShell as r, createDynamicCompleteCommand as s, createCompletionCommand as t, extractCompletionData as u };
4067
- //# sourceMappingURL=completion-BA5JMvVG.js.map
4192
+ //# sourceMappingURL=completion-K5LGh1hO.js.map