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
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: "CLA Assistant"
if: ${{ steps.sign-or-recheck.outputs.match != '' || github.event_name == 'pull_request_target' }}
# Alpha Release
uses: cla-assistant/github-action@v2.3.0
uses: contributor-assistant/github-action@v2.6.1
env:
# Generated and maintained by GitHub
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
86 changes: 75 additions & 11 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import type { Plugin } from '@opencode-ai/plugin';
import { appendFileSync, createWriteStream, readFileSync, existsSync, mkdirSync } from 'fs';
import {
appendFileSync,
createWriteStream,
readFileSync,
existsSync,
mkdirSync,
readdirSync,
rmSync,
statSync,
} from 'fs';
import { Readable } from 'stream';
import { pipeline } from 'stream/promises';
import { dirname, join } from 'path';
Expand Down Expand Up @@ -41,9 +50,9 @@ const fetchAndSaveFile = async (

const extractZip = async (
$: any,
directory: any,
skillZipFile: string,
skillName: string,
skillVersion: string,
skillZipDir: string,
log: (_msg: string) => void
): Promise<{ success: boolean; error?: string }> => {
Expand All @@ -53,11 +62,11 @@ const extractZip = async (
log(`Failed to extract JFrog ${skillName} skill: ${unzipResponse.stderr}`);
return {
success: false,
error: `Failed to extract JFrog ${skillName} skill: ${unzipResponse.stderr}`,
error: `Failed to extract JFrog ${skillName}-${skillVersion} skill: ${unzipResponse.stderr}`,
};
}
log(`JFrog ${skillName} skill extracted!`);
// remove zip file and its version directory
log(`JFrog ${skillName}-${skillVersion} skill extracted!`);
// remove zip file
await $`rm -fR ${skillZipFile}`;
log(`Jfrog ${skillName} skill zip file removed!`);
return { success: true };
Expand Down Expand Up @@ -223,12 +232,16 @@ const pullSkills = async (
mkdirSync(skillsDir, { recursive: true });
log(`Skills directory created: ${skillsDir}`);
}

// pull skills one by one
for (const skill of skillsToPull) {
const skillExists = await $`test -d ~/.config/opencode/skills/${skill.name}/`.nothrow().quiet();
if (skillExists.exitCode !== 0) {
log(`JFrog ${skill.name} skill not found, importing them locally!`);
const keepVersion = String(skill.version).trim();
const skillInstallDir = join(skillsDir, skill.name, keepVersion);
const skillExists = existsSync(skillInstallDir) && statSync(skillInstallDir).isDirectory();
if (!skillExists) {
log(`JFrog ${skill.name}-${keepVersion} skill not found, importing them locally!`);
const skillName = skill.name;
const skillVersion = skill.version;
const skillVersion = keepVersion;
const skillZipDir = join(skillsDir, skillName, skillVersion);
const skillZipFile = join(skillZipDir, `${skillName}-${skillVersion}.zip`);
const result = await fetchAndSaveFile(
Expand All @@ -242,19 +255,22 @@ const pullSkills = async (
} else {
const unzipResult = await extractZip(
$,
directory,
skillZipFile,
skillName,
skillVersion,
skillZipDir,
log
);
if (!unzipResult.success) {
log(`Failed to extract ${skillName} skill: ${unzipResult.error}`);
failedSkills.push(skillName);
} else {
log(`${skillName} skill extracted!`);
log(`${skillName} skill handling completed!`);
pruneNonManifestSkillVersions(skillsDir, skillName, keepVersion, log);
}
}
} else {
log(`JFrog ${skill.name}-${keepVersion} skill already present.`);
}
}
// return success if no failed skills, otherwise return failed skills
Expand All @@ -265,6 +281,54 @@ const pullSkills = async (
}
};

/** Runs whenever a skill is satisfied (fresh install or already on disk). Not inside import-only path. */
const pruneNonManifestSkillVersions = (
skillsDir: string,
skillName: string,
keepVersion: string,
log: (_msg: string) => void
) => {
const skillRoot = join(skillsDir, skillName);
if (!existsSync(skillRoot)) {
log(`No local version dirs for ${skillName} under ${skillRoot}`);
return;
}
let entries: string[];
try {
entries = readdirSync(skillRoot);
} catch (e) {
log(`Could not list versions under ${skillRoot}: ${e}`);
return;
}
log(
`Found version dirs for ${skillName}: ${entries.join(', ')} (latest version: ${keepVersion})`
);
for (const olderVersion of entries) {
const versionPath = join(skillRoot, olderVersion);
let isDir = false;
try {
isDir = statSync(versionPath).isDirectory();
} catch {
continue;
}
if (!isDir) {
continue;
}
if (olderVersion === keepVersion) {
continue;
}
log(
`Removing non-manifest version ${olderVersion} of ${skillName} (manifest: ${keepVersion})...`
);
try {
rmSync(versionPath, { recursive: true, force: true });
log(`Removed ${skillName}/${olderVersion}`);
} catch (e) {
log(`Failed to remove ${skillName}/${olderVersion}: ${e}`);
}
}
};

/** OpenCode loads plugins via the `server` export (see `PluginModule` in @opencode-ai/plugin). */
const jfrogOpencodePlugin: Plugin = async ({ client, $, directory }) => {
const logDir = dirname(LOG_FILE);
Expand Down
Loading