Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 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