Skip to content
Draft
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 src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1125,6 +1125,10 @@ export async function activate(context: ExtensionContext): Promise<void> {
}),
);
context.subscriptions.push(eeBuilderCommand);

// Initialize workspace context for ansible roles (async, at the end of activate
// to avoid blocking early extension host initialization)
await lightSpeedManager.setContext();
}

const startClient = async (
Expand Down
61 changes: 50 additions & 11 deletions src/features/lightspeed/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export class LightSpeedManager {
| undefined;
public providerManager: ProviderManager;
private logger: Log;
private _watchers: vscode.Disposable[] = [];
private _contextInitAbort: AbortController | null = null;

constructor(
context: vscode.ExtensionContext,
Expand Down Expand Up @@ -112,9 +114,6 @@ export class LightSpeedManager {
// Generative AI features are now in the LLM Provider Settings panel
this.lightspeedExplorerProvider = undefined;

// create workspace context for ansible roles
this.setContext();

// set custom when clause for controlling visibility of views
this.setCustomWhenClauseContext();
}
Expand All @@ -133,7 +132,7 @@ export class LightSpeedManager {
} else {
this.lightSpeedAuthenticationProvider.initialize();
this.statusBarProvider.setLightSpeedStatusBarTooltip();
this.setContext();
await this.setContext();
}

// set custom when clause for controlling visibility of views
Expand All @@ -143,22 +142,62 @@ export class LightSpeedManager {
private resetContext(): void {
this.ansibleVarFilesCache = {};
this.ansibleRolesCache = {};
for (const d of this._watchers) {
d.dispose();
}
this._watchers = [];
this._contextInitAbort?.abort();
this._contextInitAbort = null;
}

private setContext(): void {
public async setContext(): Promise<void> {
this._contextInitAbort?.abort();
const abort = new AbortController();
this._contextInitAbort = abort;

const newWatchers: vscode.Disposable[] = [];

const workspaceFolders = vscode.workspace.workspaceFolders;
if (workspaceFolders) {
for (const workspaceFolder of workspaceFolders) {
if (abort.signal.aborted) return;
const workSpaceRoot = workspaceFolder.uri.fsPath;
const rolesPath = getCustomRolePaths(workSpaceRoot);
for (const rolePath of rolesPath) {
watchRolesDirectory(this, rolePath, workSpaceRoot);
}
const rolesPath = await getCustomRolePaths(workSpaceRoot);
await this.watchRolePaths(
rolesPath,
newWatchers,
abort.signal,
workSpaceRoot,
);
}
}

const commonRolesPath = getCommonRoles() || [];
for (const rolePath of commonRolesPath) {
watchRolesDirectory(this, rolePath);
await this.watchRolePaths(commonRolesPath, newWatchers, abort.signal);

if (abort.signal.aborted) {
for (const d of newWatchers) {
d.dispose();
}
} else {
this._watchers = newWatchers;
}
}

private async watchRolePaths(
rolePaths: string[],
watchers: vscode.Disposable[],
signal: AbortSignal,
workSpaceRoot?: string,
): Promise<void> {
for (const rolePath of rolePaths) {
if (signal.aborted) return;
const disposables = await watchRolesDirectory(
this,
rolePath,
workSpaceRoot,
);
watchers.push(...disposables);
}
}

Expand Down
37 changes: 20 additions & 17 deletions src/features/lightspeed/utils/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,26 +279,29 @@ const matchKeyword = (keywordsRegex: RegExp[], line: string) =>
export async function getRoleYamlFiles(
rolePath: string,
): Promise<GenerationListEntry[]> {
const files = [] as GenerationListEntry[];
const files: GenerationListEntry[] = [];
const directories = ["defaults", "tasks"];
directories.forEach(async (dir) => {
for (const dir of directories) {
const dirPath = `${rolePath}/${dir}`;
if (fs.existsSync(dirPath)) {
const yamlFiles = await fs
.readdirSync(dirPath)
.filter((file) => /\.(yml|yaml)$/.test(file));
yamlFiles.forEach((file) => {
const fileContents = readVarFiles(`${dirPath}/${file}`);
if (fileContents) {
files.push({
path: `${dir}/${file}`,
file_type: dir.slice(0, -1) as RoleFileType,
content: fileContents,
} as GenerationListEntry);
}
});
let yamlFiles: string[];
try {
yamlFiles = (await fs.promises.readdir(dirPath)).filter((file) =>
/\.(yml|yaml)$/.test(file),
);
} catch {
continue;
}
});
for (const file of yamlFiles) {
const fileContents = await readVarFiles(`${dirPath}/${file}`);
if (fileContents) {
files.push({
path: `${dir}/${file}`,
file_type: dir.slice(0, -1) as RoleFileType,
content: fileContents,
} as GenerationListEntry);
}
}
}

return files;
}
9 changes: 4 additions & 5 deletions src/features/lightspeed/utils/readVarFiles.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import * as fs from "fs";
import * as yaml from "yaml";

export function readVarFiles(varFile: string): string | undefined {
export async function readVarFiles(
varFile: string,
): Promise<string | undefined> {
try {
if (!fs.existsSync(varFile)) {
return undefined;
}
const contents = fs.readFileSync(varFile, "utf8");
const contents = await fs.promises.readFile(varFile, "utf8");
const parsedAnsibleVars = yaml.parse(contents, {
keepSourceTokens: true,
});
Expand Down
82 changes: 37 additions & 45 deletions src/features/lightspeed/utils/updateRolesContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,26 @@ import {
} from "@src/interfaces/lightspeed";
import { readVarFiles } from "@src/features/lightspeed/utils/readVarFiles";

function getVarsFromRoles(
async function getVarsFromRoles(
rolePath: string,
varType: VarType,
): IVarsContext | undefined {
): Promise<IVarsContext | undefined> {
const varsRootPath = path.join(rolePath, varType);
if (!fs.existsSync(varsRootPath)) {
return;
}
let dirContent;
let dirContent: string[];
try {
dirContent = fs.readdirSync(varsRootPath);
} catch (err) {
console.error(`Failed to read a var directory with error ${err}`);
dirContent = await fs.promises.readdir(varsRootPath);
} catch {
return;
}

const varsFiles =
dirContent
.filter((name) => [".yml", ".yaml"].includes(path.extname(name)))
.map((name) => name)
.map((name) => path.join(varsRootPath, name)) || [];
const varsFiles = dirContent
.filter((name) => [".yml", ".yaml"].includes(path.extname(name)))
.map((name) => path.join(varsRootPath, name));
for (const varsFile of varsFiles) {
if (!fs.existsSync(varsFile)) {
continue;
}

try {
const varsContext: IVarsContext = {};
const varsFileName = path.basename(varsFile);
const varsFileContent = readVarFiles(varsFile);
const varsFileContent = await readVarFiles(varsFile);
if (varsFileContent) {
varsContext[varsFileName] = varsFileContent;
return varsContext;
Expand All @@ -50,35 +40,40 @@ function getVarsFromRoles(
return;
}

export function updateRolesContext(
export async function updateRolesContext(
ansibleRolesCache: IWorkSpaceRolesContext,
rolesRootPath: string,
workSpaceRoot: string,
): IWorkSpaceRolesContext | undefined {
if (!fs.existsSync(rolesRootPath) || !rolesRootPath.endsWith("roles")) {
): Promise<IWorkSpaceRolesContext | undefined> {
if (!rolesRootPath.endsWith("roles")) {
return;
}
let dirContent;
let dirContent: string[];
try {
dirContent = fs.readdirSync(rolesRootPath);
dirContent = await fs.promises.readdir(rolesRootPath);
} catch (error) {
console.error(`Cannot read the directory: ${error}`);
return;
}

const roleNames = dirContent.filter((name) =>
fs.statSync(path.join(rolesRootPath, name)).isDirectory(),
);
for (const roleName of roleNames) {
const rolePath = path.join(rolesRootPath, roleName);
updateRoleContext(ansibleRolesCache, rolePath, workSpaceRoot);
for (const name of dirContent) {
try {
const stat = await fs.promises.stat(path.join(rolesRootPath, name));
if (stat.isDirectory()) {
const rolePath = path.join(rolesRootPath, name);
await updateRoleContext(ansibleRolesCache, rolePath, workSpaceRoot);
}
} catch {
// skip entries that can't be stat'd
}
}
}
export function updateRoleContext(

export async function updateRoleContext(
ansibleRolesCache: IWorkSpaceRolesContext,
rolePath: string,
workSpaceRoot: string,
) {
): Promise<void> {
if (!ansibleRolesCache[workSpaceRoot]) {
ansibleRolesCache[workSpaceRoot] = {};
}
Expand All @@ -89,21 +84,18 @@ export function updateRoleContext(

// Get all the task files in the tasks directory
const tasksPath = path.join(rolePath, "tasks");
if (fs.existsSync(tasksPath)) {
try {
const dirContent = fs.readdirSync(tasksPath);

const taskNames = dirContent
.filter((name) => [".yml", ".yaml"].includes(path.extname(name)))
.map((name) => path.basename(name, path.extname(name)));
rolesContext[rolePath]["tasks"] = taskNames;
} catch (err) {
console.error(`Failed to read "tasks" directory with error ${err}`);
}
try {
const dirContent = await fs.promises.readdir(tasksPath);
const taskNames = dirContent
.filter((name) => [".yml", ".yaml"].includes(path.extname(name)))
.map((name) => path.basename(name, path.extname(name)));
rolesContext[rolePath]["tasks"] = taskNames;
} catch {
// tasks directory doesn't exist or can't be read
}

rolesContext[rolePath]["roleVars"] = {
defaults: getVarsFromRoles(rolePath, "defaults") || {},
vars: getVarsFromRoles(rolePath, "vars") || {},
defaults: (await getVarsFromRoles(rolePath, "defaults")) || {},
vars: (await getVarsFromRoles(rolePath, "vars")) || {},
};
}
Loading
Loading