Skip to content

Commit 9b867ce

Browse files
committed
fix: spec loading for sea
Signed-off-by: Chapman Pendery <cpendery@vt.edu>
1 parent 7e8177e commit 9b867ce

File tree

8 files changed

+142
-51
lines changed

8 files changed

+142
-51
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ If you are using a [NerdFont](https://www.nerdfonts.com/) patched font, you can
136136
useNerdFont = true
137137
```
138138

139+
## Unsupported Specs
140+
141+
Specs for the `az`, `gcloud`, & `aws` CLIs are not supported in inshellisense due to their large size.
142+
139143
## Contributing
140144

141145
This project welcomes contributions and suggestions. Most contributions require you to agree to a

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@
2121
"build": "tsc",
2222
"package": "tsx ./scripts/pkg.ts",
2323
"package:base": "tsx ./scripts/pkg-base.ts",
24-
"dev": "node --import=tsx src/index.ts -V",
24+
"dev": "node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --import=tsx src/index.ts -V",
2525
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
2626
"lint": "eslint src/ --ext .ts,.tsx && prettier src/ --check",
2727
"lint:fix": "eslint src/ --ext .ts,.tsx --fix && prettier src/ --write",
28-
"debug": "node --inspect --import=tsx src/index.ts -V"
28+
"debug": "node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --inspect --import=tsx src/index.ts -V"
2929
},
3030
"repository": {
3131
"type": "git",

scripts/pkg.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const PLATFORM_ARCH = `${process.platform}-${process.arch}`;
2424
const BINARY_NAME = process.platform === "win32" ? `inshellisense-${PLATFORM_ARCH}.exe` : `inshellisense-${PLATFORM_ARCH}`;
2525
const BINARY_PATH = path.join(PKG_DIR, BINARY_NAME);
2626
const NODE_VERSION = "22.21.1";
27+
const ASSET_PATH_SEP = "____";
2728

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

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

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

194+
const copyAutocompleteSpecs = (): void => {
195+
const srcDir = path.join(getAutocompletePath());
196+
if (!fs.existsSync(srcDir)) return;
197+
198+
const destDir = path.join(PKG_DIR, "specs");
199+
fs.mkdirSync(path.dirname(destDir), { recursive: true });
200+
fs.cpSync(srcDir, destDir, { recursive: true });
201+
console.log(`Copied specs: ${srcDir} -> ${destDir}`);
202+
};
203+
192204
const generateSeaConfig = (): void => {
193205
const shellAssets = fs.readdirSync("shell");
194206
const prebuildsDir = path.join(PKG_DIR, "prebuilds", PLATFORM_ARCH);
207+
const specsDir = path.join(PKG_DIR, "specs");
195208

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

215+
const specAssets = fs
216+
.readdirSync(specsDir, { recursive: true })
217+
.map(String)
218+
.filter((file) => file.endsWith(".js"))
219+
.filter((file) => fs.statSync(path.join(specsDir, file)).isFile())
220+
.filter((file) => !["gcloud", "az", "aws"].some((name) => file.startsWith(name + path.sep)));
221+
202222
const assets: Record<string, string> = {};
203223
shellAssets.forEach((file) => (assets[file] = `shell/${file}`));
204224
nativeAssets.forEach((file) => {
205-
assets[path.basename(file)] = `pkg/prebuilds/${PLATFORM_ARCH}/${file}`.replace(path.sep, "/");
225+
assets[file.replaceAll(path.sep, ASSET_PATH_SEP)] = `pkg/prebuilds/${PLATFORM_ARCH}/${file}`.replaceAll(path.sep, "/");
226+
});
227+
specAssets.forEach((file) => {
228+
assets[file.replaceAll(path.sep, ASSET_PATH_SEP)] = `pkg/specs/${file}`.replaceAll(path.sep, "/");
206229
});
207230

208231
const seaConfig = {
@@ -269,6 +292,7 @@ const main = async (): Promise<void> => {
269292
await buildBundle();
270293
await applyBundlePatches();
271294
copyNodePtyNatives();
295+
copyAutocompleteSpecs();
272296
await copyNodeExecutable();
273297
generateSeaConfig();
274298
generateSeaBlob();

src/commands/init.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,14 @@
33

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

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

1111
const action = (program: Command) => async (shell: string | undefined) => {
1212
await createShellConfigs();
13-
await unpackNativeModules();
14-
await permissionNativeModules();
15-
await unpackShellFiles();
13+
unpackResources();
1614

1715
if (shell == null) {
1816
await render();

src/runtime/runtime.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
import speclist, {
5-
diffVersionedCompletions as versionedSpeclist,
4+
import figSpecList, {
5+
diffVersionedCompletions as figVersionedSpeclist,
66
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
77
// @ts-ignore
88
} from "@withfig/autocomplete/build/index.js";
99
import path from "node:path";
10+
import { pathToFileURL } from "node:url";
1011
import { parseCommand, CommandToken } from "./parser.js";
1112
import { getArgDrivenRecommendation, getSubcommandDrivenRecommendation, SuggestionIcons } from "./suggestion.js";
1213
import { Suggestion, SuggestionBlob } from "./model.js";
@@ -15,9 +16,14 @@ import { Shell } from "../utils/shell.js";
1516
import { aliasExpand, getAliasNames } from "./alias.js";
1617
import { getConfig } from "../utils/config.js";
1718
import log from "../utils/log.js";
19+
import { specResourcesPath } from "../utils/constants.js";
20+
1821

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

2228
function loadSpecsSet(speclist: string[], versionedSpeclist: string[], specsPath: string) {
2329
speclist.forEach((s) => {
@@ -29,7 +35,7 @@ function loadSpecsSet(speclist: string[], versionedSpeclist: string[], specsPath
2935
}
3036
if (idx === specRoutes.length - 1) {
3137
const prefix = versionedSpeclist.includes(s) ? "/index.js" : `.js`;
32-
activeSet[route] = `${specsPath}/${s}${prefix}`;
38+
activeSet[route] = `${specsPath}${path.sep}${s}${prefix}`;
3339
} else {
3440
activeSet[route] = activeSet[route] || {};
3541
activeSet = activeSet[route];
@@ -38,7 +44,7 @@ function loadSpecsSet(speclist: string[], versionedSpeclist: string[], specsPath
3844
});
3945
}
4046

41-
loadSpecsSet(speclist as string[], versionedSpeclist, `@withfig/autocomplete/build`);
47+
loadSpecsSet(speclist as string[], versionedSpeclist, specResourcesPath);
4248

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

@@ -52,15 +58,19 @@ const loadSpec = async (cmd: CommandToken[]): Promise<Fig.Spec | undefined> => {
5258
return loadedSpecs[rootToken.token];
5359
}
5460
if (specSet[rootToken.token]) {
55-
const spec = (await import(specSet[rootToken.token])).default;
61+
const specPath = specSet[rootToken.token];
62+
const importPath = path.isAbsolute(specPath) ? pathToFileURL(specPath).href : specPath;
63+
const spec = (await import(importPath)).default;
5664
loadedSpecs[rootToken.token] = spec;
5765
return spec;
5866
}
5967
};
6068

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

6676
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- will be implemented in below TODO
@@ -71,16 +81,18 @@ const lazyLoadSpecLocation = async (location: Fig.SpecLocation): Promise<Fig.Spe
7181
export const loadLocalSpecsSet = async () => {
7282
const specsPath = getConfig().specs.path;
7383
await Promise.allSettled(
74-
specsPath.map((specPath) =>
75-
import(path.join(specPath, "index.js"))
84+
specsPath.map((specPath) => {
85+
const indexPath = path.join(specPath, "index.js");
86+
const importPath = path.isAbsolute(indexPath) ? pathToFileURL(indexPath).href : indexPath;
87+
return import(importPath)
7688
.then((res) => {
7789
const { default: speclist, diffVersionedCompletions: versionedSpeclist } = res;
7890
loadSpecsSet(speclist, versionedSpeclist, specPath);
7991
})
8092
.catch((e) => {
8193
log.debug({ msg: `no local specs imported from '${specPath}', this will not break the current session`, e: (e as Error).message, specPath });
82-
}),
83-
),
94+
});
95+
}),
8496
);
8597
};
8698

src/ui/ui-reinit.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,21 @@
22
// Licensed under the MIT License.
33

44
import chalk from "chalk";
5-
import { permissionNativeModules, unpackNativeModules, unpackShellFiles } from "../utils/node.js";
5+
import { unpackResources } from "../utils/node.js";
66
import { createShellConfigs } from "../utils/shell.js";
7-
import { shellResourcesPath, nativeResourcesPath, loggingResourcesPath, initResourcesPath } from "../utils/constants.js";
7+
import { shellResourcesPath, nativeResourcesPath, loggingResourcesPath, initResourcesPath, specResourcesPath } from "../utils/constants.js";
88
import fs from "node:fs";
99

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

1718
await createShellConfigs();
18-
await unpackNativeModules();
19-
await permissionNativeModules();
20-
await unpackShellFiles();
19+
await unpackResources();
20+
2121
process.stdout.write(chalk.green("✓") + " successfully installed inshellisense \n");
2222
};

src/utils/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ export const allResourcesPath = path.join(os.homedir(), inshellisenseFolderName)
99
export const loggingResourcesPath = path.join(os.homedir(), inshellisenseFolderName, "log");
1010
export const nativeResourcesPath = path.join(os.homedir(), inshellisenseFolderName, "native");
1111
export const shellResourcesPath = path.join(os.homedir(), inshellisenseFolderName, "shell");
12+
export const specResourcesPath = path.join(os.homedir(), inshellisenseFolderName, "spec");
1213
export const initResourcesPath = path.join(os.homedir(), inshellisenseFolderName, "init");

src/utils/node.ts

Lines changed: 81 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,58 @@ import path from "node:path";
55
import sea from "node:sea";
66
import fsAsync from "node:fs/promises";
77
import fs from "node:fs";
8-
import { nativeResourcesPath, shellResourcesPath } from "./constants.js";
8+
import { nativeResourcesPath, shellResourcesPath, specResourcesPath } from "./constants.js";
99

10-
export const unpackNativeModules = async (): Promise<void> => {
11-
if (!sea.isSea()) return;
10+
const ASSET_PATH_SEP = "____";
11+
12+
type AssetType = "native" | "shell" | "spec";
13+
14+
const getAssetKeys = (assetType: AssetType) => {
15+
if (!sea.isSea()) return [];
16+
17+
const allKeys = sea.getAssetKeys();
18+
switch (assetType) {
19+
case "native":
20+
return allKeys.filter((key) => !key.includes("shellIntegration") && !key.includes("preexec") && !key.endsWith(".js"));
21+
case "shell":
22+
return allKeys.filter((key) => key.includes("shellIntegration") || key.includes("preexec"));
23+
case "spec":
24+
return allKeys.filter((key) => key.endsWith(".js"));
25+
default:
26+
return [];
27+
}
28+
};
29+
30+
const getAssetFolder = (assetType: AssetType) => {
31+
switch (assetType) {
32+
case "native":
33+
return nativeResourcesPath;
34+
case "shell":
35+
return shellResourcesPath;
36+
case "spec":
37+
return specResourcesPath;
38+
default:
39+
return "";
40+
}
41+
};
1242

13-
const assetKeys = sea.getAssetKeys().filter((key) => !key.includes("shellIntegration") && !key.includes("preexec"));
43+
const copyFiles = async(assetType: AssetType, files: string[], sourceFolder: string) => {
44+
await Promise.all(
45+
files.map(async (file) => {
46+
const sourcePath = path.join(sourceFolder, file);
47+
const destPath = path.join(getAssetFolder(assetType), file);
48+
if (fs.existsSync(destPath)) return;
49+
await fsAsync.mkdir(path.dirname(destPath), { recursive: true });
50+
await fsAsync.copyFile(sourcePath, destPath);
51+
}),
52+
);
53+
};
1454

55+
const copyAssets = async (assetType: AssetType) => {
1556
await Promise.all(
16-
assetKeys.map(async (assetKey) => {
17-
const assetPath = assetKey == "conpty.dll" || assetKey == "OpenConsole.exe" ? path.join("conpty", assetKey) : assetKey;
18-
const outputPath = path.join(nativeResourcesPath, assetPath);
57+
getAssetKeys(assetType).map(async (assetKey) => {
58+
const assetPath = assetKey.replaceAll(ASSET_PATH_SEP, path.sep);
59+
const outputPath = path.join(getAssetFolder(assetType), assetPath);
1960
if (fs.existsSync(outputPath)) return;
2061
const assetBlob = sea.getRawAsset(assetKey);
2162
await fsAsync.mkdir(path.dirname(outputPath), { recursive: true });
@@ -24,7 +65,13 @@ export const unpackNativeModules = async (): Promise<void> => {
2465
);
2566
};
2667

27-
export const permissionNativeModules = async (): Promise<void> => {
68+
const unpackNativeModules = async (): Promise<void> => {
69+
if (!sea.isSea()) return;
70+
71+
await copyAssets("native");
72+
};
73+
74+
const permissionNativeModules = async (): Promise<void> => {
2875
if (!sea.isSea()) return;
2976

3077
const spawnHelper = path.join(nativeResourcesPath, "spawn-helper");
@@ -33,31 +80,36 @@ export const permissionNativeModules = async (): Promise<void> => {
3380
}
3481
};
3582

36-
export const unpackShellFiles = async (): Promise<void> => {
83+
const unpackSpecs = async (): Promise<void> => {
84+
if (!sea.isSea()) {
85+
const autocompleteSpecFolderPath = path.join(process.cwd(), "node_modules", "@withfig", "autocomplete", "build");
86+
const entries = await fsAsync.readdir(autocompleteSpecFolderPath, { recursive: true });
87+
const files = entries.filter((f) => {
88+
const fullPath = path.join(autocompleteSpecFolderPath, f.toString());
89+
return fs.statSync(fullPath).isFile();
90+
}).map((f) => f.toString());
91+
92+
await copyFiles("spec", files, autocompleteSpecFolderPath);
93+
}
94+
else {
95+
await copyAssets("spec");
96+
}
97+
};
98+
99+
const unpackShellFiles = async (): Promise<void> => {
37100
if (!sea.isSea()) {
38101
const shellFolderPath = path.join(process.cwd(), "shell");
39102
const files = (await fsAsync.readdir(shellFolderPath)).map((f) => path.basename(f));
40103

41-
await Promise.all(
42-
files.map(async (file) => {
43-
const sourcePath = path.join(shellFolderPath, file);
44-
const destPath = path.join(shellResourcesPath, file);
45-
if (fs.existsSync(destPath)) return;
46-
await fsAsync.mkdir(path.dirname(destPath), { recursive: true });
47-
await fsAsync.copyFile(sourcePath, destPath);
48-
}),
49-
);
104+
await copyFiles("shell", files, shellFolderPath);
50105
} else {
51-
const assetKeys = sea.getAssetKeys().filter((key) => key.includes("shellIntegration") || key.includes("preexec"));
52-
53-
await Promise.all(
54-
assetKeys.map(async (assetKey) => {
55-
const outputPath = path.join(shellResourcesPath, assetKey);
56-
if (fs.existsSync(outputPath)) return;
57-
const assetBlob = sea.getRawAsset(assetKey);
58-
await fsAsync.mkdir(path.dirname(outputPath), { recursive: true });
59-
await fsAsync.writeFile(outputPath, Buffer.from(assetBlob));
60-
}),
61-
);
106+
await copyAssets("shell");
62107
}
63108
};
109+
110+
export const unpackResources = async (): Promise<void> => {
111+
await unpackNativeModules();
112+
await permissionNativeModules();
113+
await unpackShellFiles();
114+
await unpackSpecs();
115+
}

0 commit comments

Comments
 (0)