Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into feat/predefined-con…
Browse files Browse the repository at this point in the history
…figs
  • Loading branch information
Keyrxng committed Nov 29, 2024
2 parents 8fcb91a + b52f2db commit ddbcb52
Show file tree
Hide file tree
Showing 14 changed files with 413 additions and 251 deletions.
2 changes: 1 addition & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"dictionaries": ["typescript", "node", "software-terms"],
"import": ["@cspell/dict-typescript/cspell-ext.json", "@cspell/dict-node/cspell-ext.json", "@cspell/dict-software-terms"],
"ignoreRegExpList": ["[0-9a-fA-F]{6}"],
"ignoreWords": ["ubiquibot", "Supabase", "supabase", "SUPABASE", "sonarjs", "mischeck"]
"ignoreWords": ["ubiquibot", "Supabase", "supabase", "SUPABASE", "sonarjs", "mischeck", "Typebox"]
}
4 changes: 1 addition & 3 deletions static/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ export async function mainModule() {
const killNotification = toastNotification("Fetching manifest data...", { type: "info", shouldAutoDismiss: true });
renderOrgSelector(renderer, []);

const manifestCache = await fetcher.fetchMarketplaceManifests();
localStorage.setItem("manifestCache", JSON.stringify(manifestCache));

await fetcher.fetchMarketplaceManifests();
await fetcher.fetchOfficialPluginConfig();
killNotification();
renderer.manifestGuiBody.dataset.loading = "false";
Expand Down
64 changes: 25 additions & 39 deletions static/scripts/config-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ import { toastNotification } from "../utils/toaster";
import { CONFIG_FULL_PATH, CONFIG_ORG_REPO } from "@ubiquity-os/plugin-sdk/constants";
import { AuthService } from "./authentication";

/**
* Responsible for fetching, parsing, and updating the user's installed plugin configurations.
*
* - `configRepoExistenceCheck` checks if the user has a config repo and creates one if not
* - `repoFileExistenceCheck` checks if the user has a config file and creates one if not
* - `fetchUserInstalledConfig` fetches the user's installed config from the config repo
*/
export class ConfigParser {
repoConfig: string | null = null;
repoConfigSha: string | null = null;
Expand Down Expand Up @@ -109,38 +116,7 @@ export class ConfigParser {
return YAML.parse(`${this.newConfigYml}`);
}

async updateConfig(org: string, octokit: Octokit, option: "add" | "remove", path = CONFIG_FULL_PATH, repo = CONFIG_ORG_REPO) {
let repoPlugins = this.parseConfig(this.repoConfig).plugins;
const newPlugins = this.parseConfig().plugins;

if (!newPlugins?.length && option === "add") {
throw new Error("No plugins found in the config");
}

if (option === "add") {
// update if it exists, add if it doesn't
newPlugins.forEach((newPlugin) => {
const existingPlugin = repoPlugins.find((p) => p.uses[0].plugin === newPlugin.uses[0].plugin);
if (existingPlugin) {
existingPlugin.uses[0].with = newPlugin.uses[0].with;
} else {
repoPlugins.push(newPlugin);
}
});

this.newConfigYml = YAML.stringify({ plugins: repoPlugins });
} else if (option === "remove") {
// remove only this plugin, keep all others
newPlugins.forEach((newPlugin) => {
const existingPlugin = repoPlugins.find((p) => p.uses[0].plugin === newPlugin.uses[0].plugin);
if (existingPlugin) {
repoPlugins = repoPlugins.filter((p) => p.uses[0].plugin !== newPlugin.uses[0].plugin);
}
});
this.newConfigYml = YAML.stringify({ plugins: newPlugins });
}

this.saveConfig();
async updateConfig(org: string, octokit: Octokit, path = CONFIG_FULL_PATH, repo = CONFIG_ORG_REPO) {
return this.createOrUpdateFileContents(org, repo, path, octokit);
}

Expand All @@ -165,34 +141,44 @@ export class ConfigParser {
throw new Error("No content to push");
}

this.repoConfig = this.newConfigYml;

return octokit.repos.createOrUpdateFileContents({
owner: org,
repo: repo,
path,
message: `chore: updating config`,
message: `chore: Plugin Installer UI - update`,
content: btoa(this.newConfigYml),
sha,
});
}

addPlugin(plugin: Plugin) {
const config = this.loadConfig();
const parsedConfig = YAML.parse(config);
const parsedConfig = this.parseConfig(this.repoConfig);
parsedConfig.plugins ??= [];
parsedConfig.plugins.push(plugin);

const existingPlugin = parsedConfig.plugins.find((p) => p.uses[0].plugin === plugin.uses[0].plugin);
if (existingPlugin) {
existingPlugin.uses[0].with = plugin.uses[0].with;
} else {
parsedConfig.plugins.push(plugin);
}

this.newConfigYml = YAML.stringify(parsedConfig);
this.repoConfig = this.newConfigYml;
this.saveConfig();
}

removePlugin(plugin: Plugin) {
const config = this.loadConfig();
const parsedConfig = YAML.parse(config);
const parsedConfig = this.parseConfig(this.repoConfig);
if (!parsedConfig.plugins) {
console.log("No plugins to remove");
toastNotification("No plugins found in config", { type: "error" });
return;
}

parsedConfig.plugins = parsedConfig.plugins.filter((p: Plugin) => p.uses[0].plugin !== plugin.uses[0].plugin);
this.newConfigYml = YAML.stringify(parsedConfig);
this.repoConfig = this.newConfigYml;
this.saveConfig();
}

Expand Down
90 changes: 41 additions & 49 deletions static/scripts/fetch-manifest.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import { Octokit } from "@octokit/rest";
import { Manifest, ManifestPreDecode } from "../types/plugins";
import { DEV_CONFIG_FULL_PATH, CONFIG_FULL_PATH, CONFIG_ORG_REPO } from "@ubiquity-os/plugin-sdk/constants";

import { getOfficialPluginConfig } from "../utils/storage";

/**
* Responsible for:
* - Mainly UbiquityOS Marketplace data fetching (config-parser fetches user configs)
* - Fetching the manifest.json files from the marketplace
* - Fetching the README.md files from the marketplace
* - Fetching the official plugin config from the orgs
* - Capturing the worker and action urls from the official plugin config (will be taken from the manifest directly soon)
* - Storing the fetched data in localStorage
*/
export class ManifestFetcher {
private _orgs: string[];
private _octokit: Octokit | null;
Expand All @@ -23,15 +33,12 @@ export class ManifestFetcher {
}
const repos = await this._octokit.repos.listForOrg({ org });
const manifestCache = this.checkManifestCache();
function makeUrl(org: string, repo: string, file: string) {
return `https://raw.githubusercontent.com/${org}/${repo}/development/${file}`;
}

for (const repo of repos.data) {
const manifestUrl = makeUrl(org, repo.name, "manifest.json");
const manifest = await this.fetchActionManifest(manifestUrl);
const manifestUrl = this.createGithubRawEndpoint(org, repo.name, "development", "manifest.json");
const manifest = await this.fetchPluginManifest(manifestUrl);
const decoded = this.decodeManifestFromFetch(manifest);
const readme = await this._fetchPluginReadme(makeUrl(org, repo.name, "README.md"));
const readme = await this.fetchPluginReadme(this.createGithubRawEndpoint(org, repo.name, "development", "README.md"));

if (decoded) {
manifestCache[manifestUrl] = { ...decoded, readme };
Expand All @@ -43,7 +50,6 @@ export class ManifestFetcher {
}

checkManifestCache(): Record<string, ManifestPreDecode> {
// check if the manifest is already in the cache
const manifestCache = localStorage.getItem("manifestCache");
if (manifestCache) {
return JSON.parse(manifestCache);
Expand All @@ -60,9 +66,9 @@ export class ManifestFetcher {
}
}

createActionEndpoint(owner: string, repo: string, branch: string) {
createGithubRawEndpoint(owner: string, repo: string, branch: string, path: string) {
// no endpoint so we fetch the raw content from the owner/repo/branch
return `https://raw.githubusercontent.com/${owner}/${repo}/refs/heads/${branch}/manifest.json`;
return `https://raw.githubusercontent.com/${owner}/${repo}/refs/heads/${branch}/${path}`;
}

captureActionUrls(config: string) {
Expand All @@ -74,7 +80,7 @@ export class ManifestFetcher {

async fetchOfficialPluginConfig() {
await this.fetchOrgsUbiquityOsConfigs();
const officialPluginConfig = JSON.parse(localStorage.getItem("officialPluginConfig") || "{}") || {};
const officialPluginConfig = getOfficialPluginConfig();

this.workerUrls.forEach((url) => {
officialPluginConfig[url] = { workerUrl: url };
Expand All @@ -91,33 +97,7 @@ export class ManifestFetcher {
return officialPluginConfig;
}

async fetchWorkerManifest(workerUrl: string) {
const url = workerUrl + "/manifest.json";
try {
const response = await fetch(url, {
headers: {
"Content-Type": "application/json",
},
method: "GET",
});
return await response.json();
} catch (e) {
let error = e;
try {
const res = await fetch(url.replace(/development/g, "main"));
return await res.json();
} catch (e) {
error = e;
}
console.error(error);
if (error instanceof Error) {
return { workerUrl, error: error.message };
}
return { workerUrl, error: String(error) };
}
}

async fetchActionManifest(actionUrl: string) {
async fetchPluginManifest(actionUrl: string) {
try {
const response = await fetch(actionUrl);
return await response.json();
Expand All @@ -130,24 +110,36 @@ export class ManifestFetcher {
}
}

private async _fetchPluginReadme(pluginUrl: string) {
async fetchPluginReadme(pluginUrl: string) {
async function handle404(result: string, octokit?: Octokit | null) {
if (result.includes("404: Not Found")) {
const [owner, repo] = pluginUrl.split("/").slice(3, 5);
const readme = await octokit?.repos.getContent({
owner,
repo,
path: "README.md",
});

if (readme && "content" in readme.data) {
return atob(readme.data.content);
} else {
return "No README.md found";
}
}

return result;
}
try {
const response = await fetch(pluginUrl, {
headers: {
"Content-Type": "application/json",
},
method: "GET",
});
return await response.text();
const response = await fetch(pluginUrl, { signal: new AbortController().signal });
return await handle404(await response.text(), this._octokit);
} catch (e) {
let error = e;
try {
const res = await fetch(pluginUrl.replace(/development/g, "main"));
return await res.text();
const res = await fetch(pluginUrl.replace(/development/g, "main"), { signal: new AbortController().signal });
return await handle404(await res.text(), this._octokit);
} catch (e) {
error = e;
}
console.error(error);
if (error instanceof Error) {
return error.message;
}
Expand Down
4 changes: 4 additions & 0 deletions static/scripts/render-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import { createBackButton } from "./rendering/navigation";

type NavSteps = "orgSelector" | "pluginSelector" | "templateSelector" | "configEditor";

/**
* More of a controller than a renderer, this is responsible for rendering the manifest GUI
* and managing the state of the GUI with the help of the rendering functions.
*/
export class ManifestRenderer {
private _manifestGui: HTMLElement;
private _manifestGuiBody: ExtendedHtmlElement;
Expand Down
Loading

0 comments on commit ddbcb52

Please sign in to comment.