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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,5 @@ tests/baselines/local

.nx/cache
.nx/workspace-data

.npmrc
509 changes: 401 additions & 108 deletions package-lock.json

Large diffs are not rendered by default.

31 changes: 29 additions & 2 deletions packages/auto-complete/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,39 @@
"require": "./dist/index.cjs"
},
"types": "dist/index.d.ts",
"bin": "dist/bin/cli.js",
"bin": "dist/cli/bin.js",
"scripts": {
"format": "prettier --config ../../.prettierrc -w .",
"format:check": "prettier --config ../../.prettierrc -c .",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"typecheck": "tsc -p tsconfig.json --noEmit",
"test": "mocha",
"coverage": "c8 npm test",
"build": "tsup --silent",
"prepublishOnly": "npm run build"
},
"mocha": {
"import": "tsx/esm",
"spec": "tests/**/*.spec.ts"
},
"c8": {
"exclude": [
"src/cli/**/*",
"src/util/*",
"tests/**/*"
],
"reporter": [
"text",
"lcovonly"
],
"check-coverage": true,
"skip-full": true
},
"tsup": {
"entry": [
"src/index.ts",
"src/bin/cli.ts"
"src/cli/bin.ts"
],
"format": [
"esm",
Expand All @@ -42,13 +61,21 @@
"clean": true
},
"devDependencies": {
"@types/chai": "^5.0.1",
"@types/mocha": "^10.0.10",
"@types/sinon": "^17.0.4",
"@typescript-eslint/eslint-plugin": "^8.2.0",
"@typescript-eslint/parser": "^8.2.0",
"c8": "^10.1.3",
"chai": "^5.2.0",
"eslint": "^8.57.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^5.0.0",
"mocha": "^11.1.0",
"prettier": "^3.0.0",
"sinon": "^19.0.2",
"tsup": "^6.7.0",
"tsx": "^4.19.3",
"typescript": "5.6.x"
},
"dependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
// Copyright 2024 Bloomberg Finance L.P.
// Distributed under the terms of the Apache 2.0 license.
import { buildApplication, buildRouteMap } from "@stricli/core";
import { installCommand, uninstallCommand } from "./commands";
import pkg from "../package.json";
import pkg from "../../package.json";
import { installAllCommand, installCommand, uninstallAllCommand, uninstallCommand } from "./commands/spec";

const root = buildRouteMap({
routes: {
install: installCommand,
installAll: installAllCommand,
uninstall: uninstallCommand,
uninstallAll: uninstallAllCommand,
},
aliases: {
i: "install",
I: "installAll",
u: "uninstall",
U: "uninstallAll",
},
docs: {
brief: "Manage auto-complete command installations for shells",
Expand All @@ -22,4 +27,7 @@ export const app = buildApplication(root, {
versionInfo: {
getCurrentVersion: async () => pkg.version,
},
scanner: {
caseStyle: "allow-kebab-for-camel",
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
// Copyright 2024 Bloomberg Finance L.P.
// Distributed under the terms of the Apache 2.0 license.
import { run } from "@stricli/core";
import { app } from "../app";
import { app } from "./app";
void run(app, process.argv.slice(2), globalThis);
86 changes: 86 additions & 0 deletions packages/auto-complete/src/cli/commands/impl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2024 Bloomberg Finance L.P.
// Distributed under the terms of the Apache 2.0 license.
import shells from "../../shells";
import type { Shell } from "../../types";
import type { StricliAutoCompleteContext } from "../context";
import type { ActiveShells, ShellAutoCompleteCommands } from "./spec";

export type InstallAllFlags = {
readonly autocompleteCommand: string;
readonly skipInactiveShells: boolean;
};

export async function installAll(
this: StricliAutoCompleteContext,
flags: InstallAllFlags,
targetCommand: string,
): Promise<void> {
for (const [shell, support] of Object.entries(shells)) {
if (flags.skipInactiveShells && !(await support.isShellActive(this))) {
this.process.stdout.write(`Skipping ${shell} as it is not active\n`);
continue;
}
const shellManager = await support.getCommandManager(this);
const message = await shellManager.install(targetCommand, `${flags.autocompleteCommand} ${shell}`);
this.process.stdout.write(`Installed autocomplete support for ${targetCommand} in ${shell}\n`);
if (message) {
this.process.stdout.write(`${message}\n`);
}
}
}

export type UninstallAllFlags = {
readonly skipInactiveShells: boolean;
};

export async function uninstallAll(
this: StricliAutoCompleteContext,
flags: UninstallAllFlags,
targetCommand: string,
): Promise<void> {
for (const [shell, support] of Object.entries(shells)) {
if (flags.skipInactiveShells && !(await support.isShellActive(this))) {
this.process.stdout.write(`Skipping ${shell} as it is not active\n`);
continue;
}
const shellManager = await support.getCommandManager(this);
const message = await shellManager.uninstall(targetCommand);
this.process.stdout.write(`Installed autocomplete support for ${targetCommand} in ${shell}\n`);
if (message) {
this.process.stdout.write(`${message}\n`);
}
}
}

export async function install(
this: StricliAutoCompleteContext,
flags: ShellAutoCompleteCommands,
targetCommand: string,
): Promise<void> {
for (const [shell, autocompleteCommand] of Object.entries(flags)) {
const shellManager = await shells[shell as Shell].getCommandManager(this);
const message = await shellManager.install(targetCommand, autocompleteCommand);
this.process.stdout.write(`Installed autocomplete support for ${targetCommand} in ${shell}\n`);
if (message) {
this.process.stdout.write(`${message}\n`);
}
}
}

export async function uninstall(
this: StricliAutoCompleteContext,
flags: ActiveShells,
targetCommand: string,
): Promise<void> {
const activeShells = Object.entries(flags)
.filter(([, active]) => active)
.map(([shell]) => shell as Shell);
for (const shell of activeShells) {
const shellManager = await shells[shell].getCommandManager(this);
const message = await shellManager.uninstall(targetCommand);
this.process.stdout.write(`Uninstalled autocomplete support for ${targetCommand} in ${shell}\n`);
if (message) {
this.process.stdout.write(`${message}\n`);
}
}
}
194 changes: 194 additions & 0 deletions packages/auto-complete/src/cli/commands/spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
// Copyright 2024 Bloomberg Finance L.P.
// Distributed under the terms of the Apache 2.0 license.
import { buildCommand, type Command, type TypedPositionalParameter } from "@stricli/core";
import { joinWithGrammar } from "../../util/formatting";
import type { Shell } from "../../types";
import type { StricliAutoCompleteContext } from "../context";
import type { InstallAllFlags, UninstallAllFlags } from "./impl";

export type ShellAutoCompleteCommands = Readonly<Partial<Record<Shell, string>>>;

export type ActiveShells = Readonly<Partial<Record<Shell, boolean>>>;

const targetCommandParameter: TypedPositionalParameter<string> = {
parse: String,
brief: "Target command run by user, typically the application name",
placeholder: "targetCommand",
};

export const installAllCommand = buildCommand({
loader: async () => {
const { installAll } = await import("./impl");
return installAll;
},
parameters: {
flags: {
autocompleteCommand: {
kind: "parsed",
brief: "Command to generate completion proposals, first argument is shell type",
parse: String,
placeholder: "command",
},
skipInactiveShells: {
kind: "boolean",
brief: "Skip installing for inactive shell(s)",
default: true,
},
},
positional: {
kind: "tuple",
parameters: [targetCommandParameter],
},
},
docs: {
brief: "Installs autocomplete support for target command on all shell types",
},
});

export function buildInstallAllCommand<CONTEXT extends StricliAutoCompleteContext>(
targetCommand: string,
flags: InstallAllFlags,
): Command<CONTEXT> {
return buildCommand({
loader: async () => {
const { installAll } = await import("./impl");
return function () {
return installAll.call(this, flags, targetCommand);
};
},
parameters: {},
docs: {
brief: `Installs autocomplete support for ${targetCommand} on all shell types`,
},
});
}

export const uninstallAllCommand = buildCommand({
loader: async () => {
const { uninstallAll } = await import("./impl");
return uninstallAll;
},
parameters: {
flags: {
skipInactiveShells: {
kind: "boolean",
brief: "Skip installing for inactive shell(s)",
default: true,
},
},
positional: {
kind: "tuple",
parameters: [targetCommandParameter],
},
},
docs: {
brief: "Uninstalls autocomplete support for target command on all shell types",
},
});

export function buildUninstallAllCommand<CONTEXT extends StricliAutoCompleteContext>(
targetCommand: string,
flags: UninstallAllFlags,
): Command<CONTEXT> {
return buildCommand({
loader: async () => {
const { uninstallAll } = await import("./impl");
return function () {
return uninstallAll.call(this, flags, targetCommand);
};
},
parameters: {},
docs: {
brief: `Uninstalls autocomplete support for ${targetCommand} on all shell types`,
},
});
}

export const installCommand = buildCommand({
loader: async () => {
const { install } = await import("./impl");
return install;
},
parameters: {
flags: {
bash: {
kind: "parsed",
brief: "Command executed by bash to generate completion proposals",
parse: String,
optional: true,
placeholder: "command",
},
},
positional: {
kind: "tuple",
parameters: [targetCommandParameter],
},
},
docs: {
brief: "Installs autocomplete support for target command on all provided shell types",
},
});

export function buildInstallCommand<CONTEXT extends StricliAutoCompleteContext>(
targetCommand: string,
commands: ShellAutoCompleteCommands,
): Command<CONTEXT> {
const shellsText = joinWithGrammar(Object.keys(commands), { conjunction: "and", serialComma: true });
return buildCommand({
loader: async () => {
const { install } = await import("./impl");
return function () {
return install.call(this, commands, targetCommand);
};
},
parameters: {},
docs: {
brief: `Installs ${shellsText} autocomplete support for ${targetCommand}`,
},
});
}

export const uninstallCommand = buildCommand({
loader: async () => {
const { uninstall } = await import("./impl");
return uninstall;
},
parameters: {
flags: {
bash: {
kind: "boolean",
brief: "Uninstall autocompletion for bash",
optional: true,
},
},
positional: {
kind: "tuple",
parameters: [targetCommandParameter],
},
},
docs: {
brief: "Uninstalls autocomplete support for target command on all selected shell types",
},
});

export function buildUninstallCommand<CONTEXT extends StricliAutoCompleteContext>(
targetCommand: string,
shells: ActiveShells,
): Command<CONTEXT> {
const activeShells = Object.entries(shells)
.filter(([, enabled]) => enabled)
.map(([shell]) => shell);
const shellsText = joinWithGrammar(activeShells, { conjunction: "and", serialComma: true });
return buildCommand({
loader: async () => {
const { uninstall } = await import("./impl");
return function () {
return uninstall.call(this, shells, targetCommand);
};
},
parameters: {},
docs: {
brief: `Uninstalls ${shellsText} autocomplete support for ${targetCommand}`,
},
});
}
Loading