Skip to content
Merged
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ If you are using a [NerdFont](https://www.nerdfonts.com/) patched font, you can
useNerdFont = true
```

## Unsupported Specs

Specs for the `az`, `gcloud`, & `aws` CLIs are not supported in inshellisense due to their large size.

## Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@
"build": "tsc",
"package": "tsx ./scripts/pkg.ts",
"package:base": "tsx ./scripts/pkg-base.ts",
"dev": "node --import=tsx src/index.ts -V",
"dev": "node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --import=tsx src/index.ts -V",
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
"lint": "eslint src/ --ext .ts,.tsx && prettier src/ --check",
"lint:fix": "eslint src/ --ext .ts,.tsx --fix && prettier src/ --write",
"debug": "node --inspect --import=tsx src/index.ts -V"
"debug": "node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --inspect --import=tsx src/index.ts -V"
},
"repository": {
"type": "git",
Expand Down
26 changes: 25 additions & 1 deletion scripts/pkg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const PLATFORM_ARCH = `${process.platform}-${process.arch}`;
const BINARY_NAME = process.platform === "win32" ? `inshellisense-${PLATFORM_ARCH}.exe` : `inshellisense-${PLATFORM_ARCH}`;
const BINARY_PATH = path.join(PKG_DIR, BINARY_NAME);
const NODE_VERSION = "22.21.1";
const ASSET_PATH_SEP = "____";

/** SHA-256 checksums for Node.js binaries from https://nodejs.org/dist/vX.X.X/SHASUMS256.txt */
const NODE_SHASUMS: Record<string, string> = {
Expand All @@ -36,6 +37,7 @@ const NODE_SHASUMS: Record<string, string> = {
};

const getNodePtyPath = (): string => path.dirname(require.resolve("node-pty"));
const getAutocompletePath = (): string => path.dirname(require.resolve("@withfig/autocomplete"));

const getVersion = (): string => {
const packageJson = JSON.parse(fs.readFileSync("package.json", "utf-8"));
Expand Down Expand Up @@ -189,20 +191,41 @@ const copyNodePtyNatives = (): void => {
console.log(`Copied natives: ${srcDir} -> ${destDir}`);
};

const copyAutocompleteSpecs = (): void => {
const srcDir = path.join(getAutocompletePath());
if (!fs.existsSync(srcDir)) return;

const destDir = path.join(PKG_DIR, "specs");
fs.mkdirSync(path.dirname(destDir), { recursive: true });
fs.cpSync(srcDir, destDir, { recursive: true });
console.log(`Copied specs: ${srcDir} -> ${destDir}`);
};

const generateSeaConfig = (): void => {
const shellAssets = fs.readdirSync("shell");
const prebuildsDir = path.join(PKG_DIR, "prebuilds", PLATFORM_ARCH);
const specsDir = path.join(PKG_DIR, "specs");

const nativeAssets = fs
.readdirSync(prebuildsDir, { recursive: true })
.map(String)
.filter((file) => !file.endsWith(".pdb"))
.filter((file) => fs.statSync(path.join(prebuildsDir, file)).isFile());

const specAssets = fs
.readdirSync(specsDir, { recursive: true })
.map(String)
.filter((file) => file.endsWith(".js"))
.filter((file) => fs.statSync(path.join(specsDir, file)).isFile())
.filter((file) => !["gcloud", "az", "aws"].some((name) => file.startsWith(name + path.sep)));

const assets: Record<string, string> = {};
shellAssets.forEach((file) => (assets[file] = `shell/${file}`));
nativeAssets.forEach((file) => {
assets[path.basename(file)] = `pkg/prebuilds/${PLATFORM_ARCH}/${file}`.replace(path.sep, "/");
assets[file.replaceAll(path.sep, ASSET_PATH_SEP)] = `pkg/prebuilds/${PLATFORM_ARCH}/${file}`.replaceAll(path.sep, "/");
});
specAssets.forEach((file) => {
assets[file.replaceAll(path.sep, ASSET_PATH_SEP)] = `pkg/specs/${file}`.replaceAll(path.sep, "/");
});

const seaConfig = {
Expand Down Expand Up @@ -269,6 +292,7 @@ const main = async (): Promise<void> => {
await buildBundle();
await applyBundlePatches();
copyNodePtyNatives();
copyAutocompleteSpecs();
await copyNodeExecutable();
generateSeaConfig();
generateSeaBlob();
Expand Down
6 changes: 2 additions & 4 deletions src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@

import { Command } from "commander";
import { createShellConfigs, initSupportedShells as shells, getShellSourceCommand, Shell } from "../utils/shell.js";
import { permissionNativeModules, unpackNativeModules, unpackShellFiles } from "../utils/node.js";
import { unpackResources } from "../utils/node.js";
import { render } from "../ui/ui-init.js";

const supportedShells = shells.join(", ");

const action = (program: Command) => async (shell: string | undefined) => {
await createShellConfigs();
await unpackNativeModules();
await permissionNativeModules();
await unpackShellFiles();
unpackResources();

if (shell == null) {
await render();
Expand Down
9 changes: 8 additions & 1 deletion src/commands/root.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { render, renderConfirmation } from "../ui/ui-root.js";
import { render, renderConfirmation, renderMissingResources } from "../ui/ui-root.js";
import { Shell, supportedShells as shells, setupZshDotfiles } from "../utils/shell.js";
import { inferShell } from "../utils/shell.js";
import { loadConfig } from "../utils/config.js";
import { Command } from "commander";
import log from "../utils/log.js";
import { loadAliases } from "../runtime/alias.js";
import { loadLocalSpecsSet } from "../runtime/runtime.js";
import { checkUnpackedVersion } from "../utils/node.js";

export const supportedShells = shells.join(", ");

Expand All @@ -27,6 +28,12 @@ export const action = (program: Command) => async (options: RootCommandOptions)
process.exit(0);
}

const isVersionUpToDate = await checkUnpackedVersion();
if (!isVersionUpToDate) {
process.stdout.write(renderMissingResources());
process.exit(1);
}

if (options.verbose) await log.enable();

const [, inferredShell] = await Promise.all([loadConfig(program), options.shell ? Promise.resolve(options.shell) : inferShell()]);
Expand Down
31 changes: 21 additions & 10 deletions src/runtime/runtime.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import speclist, {
diffVersionedCompletions as versionedSpeclist,
import figSpecList, {
diffVersionedCompletions as figVersionedSpeclist,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
} from "@withfig/autocomplete/build/index.js";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { parseCommand, CommandToken } from "./parser.js";
import { getArgDrivenRecommendation, getSubcommandDrivenRecommendation, SuggestionIcons } from "./suggestion.js";
import { Suggestion, SuggestionBlob } from "./model.js";
Expand All @@ -15,9 +16,13 @@ import { Shell } from "../utils/shell.js";
import { aliasExpand, getAliasNames } from "./alias.js";
import { getConfig } from "../utils/config.js";
import log from "../utils/log.js";
import { specResourcesPath } from "../utils/constants.js";

// eslint-disable-next-line @typescript-eslint/no-explicit-any -- recursive type, setting as any
const specSet: any = {};
const ignoredSpecs = ["gcloud", "az", "aws"];
const speclist = figSpecList.filter((spec: string) => !ignoredSpecs.some((name) => spec.startsWith(name + "/")));
const versionedSpeclist = figVersionedSpeclist.filter((spec: string) => !ignoredSpecs.some((name) => spec.startsWith(name)));

function loadSpecsSet(speclist: string[], versionedSpeclist: string[], specsPath: string) {
speclist.forEach((s) => {
Expand All @@ -29,7 +34,7 @@ function loadSpecsSet(speclist: string[], versionedSpeclist: string[], specsPath
}
if (idx === specRoutes.length - 1) {
const prefix = versionedSpeclist.includes(s) ? "/index.js" : `.js`;
activeSet[route] = `${specsPath}/${s}${prefix}`;
activeSet[route] = `${specsPath}${path.sep}${s}${prefix}`;
} else {
activeSet[route] = activeSet[route] || {};
activeSet = activeSet[route];
Expand All @@ -38,7 +43,7 @@ function loadSpecsSet(speclist: string[], versionedSpeclist: string[], specsPath
});
}

loadSpecsSet(speclist as string[], versionedSpeclist, `@withfig/autocomplete/build`);
loadSpecsSet(speclist as string[], versionedSpeclist, specResourcesPath);

const loadedSpecs: { [key: string]: Fig.Spec } = {};

Expand All @@ -52,15 +57,19 @@ const loadSpec = async (cmd: CommandToken[]): Promise<Fig.Spec | undefined> => {
return loadedSpecs[rootToken.token];
}
if (specSet[rootToken.token]) {
const spec = (await import(specSet[rootToken.token])).default;
const specPath = specSet[rootToken.token];
const importPath = path.isAbsolute(specPath) ? pathToFileURL(specPath).href : specPath;
const spec = (await import(importPath)).default;
loadedSpecs[rootToken.token] = spec;
return spec;
}
};

// this load spec function should only be used for `loadSpec` on the fly as it is cacheless
const lazyLoadSpec = async (key: string): Promise<Fig.Spec | undefined> => {
return (await import(`@withfig/autocomplete/build/${key}.js`)).default;
const specPath = path.join(specResourcesPath, `${key}.js`);
const importPath = path.isAbsolute(specPath) ? pathToFileURL(specPath).href : specPath;
return (await import(importPath)).default;
};

// eslint-disable-next-line @typescript-eslint/no-unused-vars -- will be implemented in below TODO
Expand All @@ -71,16 +80,18 @@ const lazyLoadSpecLocation = async (location: Fig.SpecLocation): Promise<Fig.Spe
export const loadLocalSpecsSet = async () => {
const specsPath = getConfig().specs.path;
await Promise.allSettled(
specsPath.map((specPath) =>
import(path.join(specPath, "index.js"))
specsPath.map((specPath) => {
const indexPath = path.join(specPath, "index.js");
const importPath = path.isAbsolute(indexPath) ? pathToFileURL(indexPath).href : indexPath;
return import(importPath)
.then((res) => {
const { default: speclist, diffVersionedCompletions: versionedSpeclist } = res;
loadSpecsSet(speclist, versionedSpeclist, specPath);
})
.catch((e) => {
log.debug({ msg: `no local specs imported from '${specPath}', this will not break the current session`, e: (e as Error).message, specPath });
}),
),
});
}),
);
};

Expand Down
3 changes: 3 additions & 0 deletions src/tests/runtime/runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import fs from "node:fs";
import { getSuggestions } from "../../runtime/runtime.js";
import { Shell } from "../../utils/shell.js";
import { SuggestionIcons } from "../../runtime/suggestion.js";
import { unpackResources } from "../../utils/node.js";

const testData = [
{ name: "partialPrefixFilter", command: "git sta" },
Expand Down Expand Up @@ -33,6 +34,8 @@ const testData = [
{ name: "pathWithFileFilteredSuggestion", command: "source shell/shellIntegration.", maxSuggestions: 1 },
];

beforeAll(async () => await unpackResources());

describe(`parseCommand`, () => {
testData.forEach(({ command, name, skip, maxSuggestions }) => {
if (skip) return;
Expand Down
18 changes: 13 additions & 5 deletions src/ui/ui-reinit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,29 @@
// Licensed under the MIT License.

import chalk from "chalk";
import { permissionNativeModules, unpackNativeModules, unpackShellFiles } from "../utils/node.js";
import { unpackResources } from "../utils/node.js";
import { createShellConfigs } from "../utils/shell.js";
import { shellResourcesPath, nativeResourcesPath, loggingResourcesPath, initResourcesPath } from "../utils/constants.js";
import {
shellResourcesPath,
nativeResourcesPath,
loggingResourcesPath,
initResourcesPath,
specResourcesPath,
versionResourcePath,
} from "../utils/constants.js";
import fs from "node:fs";

export const render = async () => {
fs.rmSync(shellResourcesPath, { recursive: true, force: true });
fs.rmSync(nativeResourcesPath, { recursive: true, force: true });
fs.rmSync(loggingResourcesPath, { recursive: true, force: true });
fs.rmSync(initResourcesPath, { recursive: true, force: true });
fs.rmSync(specResourcesPath, { recursive: true, force: true });
fs.rmSync(versionResourcePath, { force: true });
process.stdout.write(chalk.green("✓") + " removed old inshellisense resources \n");

await createShellConfigs();
await unpackNativeModules();
await permissionNativeModules();
await unpackShellFiles();
await unpackResources();

process.stdout.write(chalk.green("✓") + " successfully installed inshellisense \n");
};
4 changes: 4 additions & 0 deletions src/ui/ui-root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export const renderConfirmation = (live: boolean): string => {
return `inshellisense session [${statusMessage}]\n`;
};

export const renderMissingResources = (): string => {
return chalk.red(`inshellisense resources out of date, run "is reinit" to refresh\n`);
};

const writeOutput = (data: string) => {
log.debug({ msg: "writing data", data });
process.stdout.write(data);
Expand Down
2 changes: 2 additions & 0 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ export const allResourcesPath = path.join(os.homedir(), inshellisenseFolderName)
export const loggingResourcesPath = path.join(os.homedir(), inshellisenseFolderName, "log");
export const nativeResourcesPath = path.join(os.homedir(), inshellisenseFolderName, "native");
export const shellResourcesPath = path.join(os.homedir(), inshellisenseFolderName, "shell");
export const specResourcesPath = path.join(os.homedir(), inshellisenseFolderName, "spec");
export const initResourcesPath = path.join(os.homedir(), inshellisenseFolderName, "init");
export const versionResourcePath = path.join(os.homedir(), inshellisenseFolderName, "version.txt");
Loading