@ubiquity-os/plugin-sdk
Version:
SDK for plugin support.
741 lines (733 loc) • 28.3 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/configuration.ts
var configuration_exports = {};
__export(configuration_exports, {
CONFIG_DEV_FULL_PATH: () => CONFIG_DEV_FULL_PATH,
CONFIG_ORG_REPO: () => CONFIG_ORG_REPO,
CONFIG_PROD_FULL_PATH: () => CONFIG_PROD_FULL_PATH,
ConfigurationHandler: () => ConfigurationHandler,
configSchema: () => configSchema,
isGithubPlugin: () => isGithubPlugin
});
module.exports = __toCommonJS(configuration_exports);
var import_value = require("@sinclair/typebox/value");
var import_js_yaml = __toESM(require("js-yaml"));
var import_node_buffer = require("node:buffer");
// src/configuration/schema.ts
var import_webhooks = require("@octokit/webhooks");
var import_typebox = require("@sinclair/typebox");
var pluginNameRegex = new RegExp("^([0-9a-zA-Z-._]+)\\/([0-9a-zA-Z-._]+)(?::([0-9a-zA-Z-._]+))?(?:@([0-9a-zA-Z-._]+(?:\\/[0-9a-zA-Z-._]+)*))?$");
var urlRegex = /^https?:\/\/\S+$/;
function parsePluginIdentifier(value) {
if (urlRegex.test(value)) {
return value;
}
const matches = pluginNameRegex.exec(value);
if (!matches) {
throw new Error(`Invalid plugin name: ${value}`);
}
return {
owner: matches[1],
repo: matches[2],
workflowId: matches[3] || "compute.yml",
ref: matches[4] || void 0
};
}
function stringLiteralUnion(values) {
const literals = values.map((value) => import_typebox.Type.Literal(value));
return import_typebox.Type.Union(literals);
}
var emitterType = stringLiteralUnion(import_webhooks.emitterEventNames);
var runsOnSchema = import_typebox.Type.Array(emitterType, { default: [] });
var pluginSettingsSchema = import_typebox.Type.Union(
[
import_typebox.Type.Null(),
import_typebox.Type.Object(
{
with: import_typebox.Type.Record(import_typebox.Type.String(), import_typebox.Type.Unknown(), { default: {} }),
runsOn: import_typebox.Type.Optional(runsOnSchema),
skipBotEvents: import_typebox.Type.Optional(import_typebox.Type.Boolean())
},
{ default: {} }
)
],
{ default: null }
);
var configSchema = import_typebox.Type.Object(
{
imports: import_typebox.Type.Optional(import_typebox.Type.Array(import_typebox.Type.String(), { default: [] })),
plugins: import_typebox.Type.Record(import_typebox.Type.String(), pluginSettingsSchema, { default: {} })
},
{
additionalProperties: true
}
);
// src/helpers/urls.ts
function normalizeBaseUrl(baseUrl) {
let normalized = baseUrl.trim();
while (normalized.endsWith("/")) {
normalized = normalized.slice(0, -1);
}
return normalized;
}
// src/types/manifest.ts
var import_typebox2 = require("@sinclair/typebox");
var import_webhooks2 = require("@octokit/webhooks");
// src/helpers/runtime-manifest.ts
var EMPTY_VALUE = String();
// src/types/manifest.ts
var runEvent = import_typebox2.Type.Union(import_webhooks2.emitterEventNames.map((o) => import_typebox2.Type.Literal(o)));
var exampleCommandExecutionSchema = import_typebox2.Type.Object({
commandInvocation: import_typebox2.Type.String({ minLength: 1 }),
githubContext: import_typebox2.Type.Optional(import_typebox2.Type.Record(import_typebox2.Type.String(), import_typebox2.Type.Any())),
expectedToolCallResult: import_typebox2.Type.Object({
function: import_typebox2.Type.String({ minLength: 1 }),
parameters: import_typebox2.Type.Record(import_typebox2.Type.String(), import_typebox2.Type.Any())
})
});
var commandSchema = import_typebox2.Type.Object({
description: import_typebox2.Type.String({ minLength: 1 }),
"ubiquity:example": import_typebox2.Type.String({ minLength: 1 }),
parameters: import_typebox2.Type.Optional(import_typebox2.Type.Record(import_typebox2.Type.String(), import_typebox2.Type.Any())),
examples: import_typebox2.Type.Optional(import_typebox2.Type.Array(exampleCommandExecutionSchema, { default: [] }))
});
var manifestSchema = import_typebox2.Type.Object({
name: import_typebox2.Type.String({ minLength: 1 }),
short_name: import_typebox2.Type.String({ minLength: 1 }),
description: import_typebox2.Type.Optional(import_typebox2.Type.String({ default: "" })),
commands: import_typebox2.Type.Optional(import_typebox2.Type.Record(import_typebox2.Type.String({ pattern: "^[A-Za-z-_]+$" }), commandSchema, { default: {} })),
"ubiquity:listeners": import_typebox2.Type.Optional(import_typebox2.Type.Array(runEvent, { default: [] })),
configuration: import_typebox2.Type.Optional(import_typebox2.Type.Record(import_typebox2.Type.String(), import_typebox2.Type.Any(), { default: {} })),
skipBotEvents: import_typebox2.Type.Optional(import_typebox2.Type.Boolean({ default: true })),
homepage_url: import_typebox2.Type.Optional(import_typebox2.Type.String())
});
// src/configuration.ts
var CONFIG_PROD_FULL_PATH = ".github/.ubiquity-os.config.yml";
var CONFIG_DEV_FULL_PATH = ".github/.ubiquity-os.config.dev.yml";
var CONFIG_ORG_REPO = ".ubiquity-os";
var EMPTY_STRING = "";
var ENVIRONMENT_TO_CONFIG_SUFFIX = {
development: "dev"
};
var VALID_CONFIG_SUFFIX = /^[a-z0-9][a-z0-9_-]*$/i;
var MAX_IMPORT_DEPTH = 6;
function normalizeEnvironmentName(environment) {
return String(environment ?? EMPTY_STRING).trim().toLowerCase();
}
function getConfigPathCandidatesForEnvironment(environment) {
const normalized = normalizeEnvironmentName(environment);
if (!normalized) {
return [CONFIG_PROD_FULL_PATH, CONFIG_DEV_FULL_PATH];
}
if (normalized === "production" || normalized === "prod") {
return [CONFIG_PROD_FULL_PATH];
}
const suffix = ENVIRONMENT_TO_CONFIG_SUFFIX[normalized] ?? normalized;
if (suffix === "dev") {
return [CONFIG_DEV_FULL_PATH];
}
if (!VALID_CONFIG_SUFFIX.test(suffix)) {
return [CONFIG_DEV_FULL_PATH];
}
return [`.github/.ubiquity-os.config.${suffix}.yml`, CONFIG_PROD_FULL_PATH];
}
function normalizeImportKey(location) {
const env = normalizeEnvironmentName(location.environment ?? "") || "default";
return `${location.owner}`.trim().toLowerCase() + "/" + `${location.repo}`.trim().toLowerCase() + "@" + env;
}
function isHttpUrl(value) {
const trimmed = value.trim();
return trimmed.startsWith("http://") || trimmed.startsWith("https://");
}
function resolveManifestUrl(pluginUrl) {
try {
const parsed = new URL(pluginUrl.trim());
let pathname = parsed.pathname;
while (pathname.endsWith("/") && pathname.length > 1) {
pathname = pathname.slice(0, -1);
}
if (pathname === "/") {
pathname = EMPTY_STRING;
}
if (pathname.endsWith(".json")) {
parsed.search = EMPTY_STRING;
parsed.hash = EMPTY_STRING;
return parsed.toString();
}
parsed.pathname = `${pathname}/manifest.json`;
parsed.search = EMPTY_STRING;
parsed.hash = EMPTY_STRING;
return parsed.toString();
} catch {
return null;
}
}
function parseImportSpec(value, defaultEnvironment) {
const trimmed = value.trim();
if (!trimmed) return null;
const [repoSpec, envSpecRaw] = trimmed.split("@");
const parts = repoSpec.split("/");
if (parts.length !== 2) return null;
const [owner, repo] = parts;
if (!owner || !repo) return null;
const envSpec = envSpecRaw?.trim();
if (envSpec && !VALID_CONFIG_SUFFIX.test(envSpec)) {
return null;
}
const inheritedEnvironment = defaultEnvironment?.trim();
return { owner, repo, environment: envSpec || inheritedEnvironment || void 0 };
}
function readImports(logger, value, source) {
if (!value) return [];
if (!Array.isArray(value)) {
logger.warn("Invalid imports; expected a list of strings.", { source });
return [];
}
const seen = /* @__PURE__ */ new Set();
const imports = [];
for (const entry of value) {
if (typeof entry !== "string") {
logger.warn("Ignoring invalid import entry; expected string.", { source, entry });
continue;
}
const parsed = parseImportSpec(entry, source.environment);
if (!parsed) {
logger.warn("Ignoring invalid import entry; expected owner/repo.", { source, entry });
continue;
}
const key = normalizeImportKey(parsed);
if (seen.has(key)) continue;
seen.add(key);
imports.push(parsed);
}
return imports;
}
function stripImports(config) {
if (!config || typeof config !== "object") return config;
const rest = { ...config };
delete rest.imports;
return rest;
}
function unwrapTypeBoxError(error) {
if (error && typeof error === "object" && "error" in error) {
const inner = error.error;
return inner ?? error;
}
return error;
}
function mergeImportedConfigs(imported, base) {
if (!imported.length) {
return base;
}
let merged = imported[0];
for (let i = 1; i < imported.length; i++) {
merged = {
...merged,
...imported[i],
plugins: { ...merged.plugins, ...imported[i].plugins }
};
}
return base ? {
...merged,
...base,
plugins: { ...merged.plugins, ...base.plugins }
} : merged;
}
function logOk(logger, message, metadata) {
if (logger.ok) {
logger.ok(message, metadata);
} else {
logger.info(message, metadata);
}
}
function isGithubPlugin(plugin) {
return typeof plugin !== "string";
}
var ConfigurationHandler = class {
constructor(_logger, _octokit, _environment = null, options) {
this._logger = _logger;
this._octokit = _octokit;
this._environment = _environment;
this._octokitFactory = options?.octokitFactory;
}
_manifestCache = {};
_manifestPromiseCache = {};
_octokitFactory;
/**
* Retrieves the configuration for the current plugin based on its manifest.
* @param manifest - The plugin manifest containing the `short_name` identifier
* @param location - Optional repository location (`owner/repo`)
* @param options - Optional refs for organization/repository configuration lookups
* @returns The plugin's configuration or null if not found
**/
async getSelfConfiguration(manifest, location, options) {
const cfg = await this.getConfiguration(location, options);
let selfConfig;
if (manifest.homepage_url) {
const name = manifest.homepage_url;
selfConfig = Object.keys(cfg.plugins).find((key) => normalizeBaseUrl(key) === normalizeBaseUrl(name));
}
if (!selfConfig) {
const name = manifest.short_name.split("@")[0];
selfConfig = Object.keys(cfg.plugins).find((key) => new RegExp(`^${name}(?:$|@.+)`).exec(key.replace(/:[^@]+/, "")));
}
return selfConfig && cfg.plugins[selfConfig] ? cfg.plugins[selfConfig]?.["with"] : null;
}
/**
* Retrieves and merges configuration from organization and repository levels.
* @param location - Optional repository location (`owner` and `repo`). If not provided, returns the default configuration.
* @param options - Optional refs for organization/repository configuration lookups
* @returns The merged plugin configuration with resolved plugin settings.
*/
async getConfiguration(location, options) {
const defaultConfiguration = stripImports(import_value.Value.Decode(configSchema, import_value.Value.Default(configSchema, {})));
if (!location) {
this._logger.info("No location was provided, using the default configuration");
return defaultConfiguration;
}
const mergedConfiguration = await this._getMergedConfiguration(location, defaultConfiguration, options);
const resolvedPlugins = await this._resolvePlugins(mergedConfiguration, location);
return {
...mergedConfiguration,
plugins: resolvedPlugins
};
}
async _getMergedConfiguration(location, defaultConfiguration, options) {
const { owner, repo } = location;
let mergedConfiguration = defaultConfiguration;
this._logger.info("Fetching configurations from the organization and repository", {
orgRepo: `${owner}/${CONFIG_ORG_REPO}`,
repo: `${owner}/${repo}`
});
const orgConfig = await this.getConfigurationFromRepo(owner, CONFIG_ORG_REPO, options?.orgRef ? { ref: options.orgRef } : void 0);
const repoConfig = await this.getConfigurationFromRepo(owner, repo, options?.repoRef ? { ref: options.repoRef } : void 0);
if (orgConfig.config) {
mergedConfiguration = this.mergeConfigurations(mergedConfiguration, orgConfig.config);
}
if (repoConfig.config) {
mergedConfiguration = this.mergeConfigurations(mergedConfiguration, repoConfig.config);
}
return mergedConfiguration;
}
async _resolvePlugins(mergedConfiguration, location) {
const resolvedPlugins = {};
logOk(this._logger, "Found plugins enabled", { repo: `${location.owner}/${location.repo}`, plugins: Object.keys(mergedConfiguration.plugins).length });
for (const [pluginKey, pluginSettings] of Object.entries(mergedConfiguration.plugins)) {
const resolved = await this._resolvePluginSettings(pluginKey, pluginSettings);
if (!resolved) continue;
resolvedPlugins[pluginKey] = resolved;
}
return resolvedPlugins;
}
async _resolvePluginSettings(pluginKey, pluginSettings) {
const isUrlPlugin = isHttpUrl(pluginKey);
let manifest = null;
if (!isUrlPlugin) {
let pluginIdentifier;
try {
pluginIdentifier = parsePluginIdentifier(pluginKey);
} catch (error) {
this._logger.warn("Invalid plugin identifier; skipping", { plugin: pluginKey, err: error });
return null;
}
manifest = await this.getManifest(pluginIdentifier);
} else {
manifest = await this._fetchUrlManifest(pluginKey);
}
let runsOn = pluginSettings?.runsOn ?? [];
let shouldSkipBotEvents = pluginSettings?.skipBotEvents;
if (manifest) {
if (!runsOn.length) {
runsOn = manifest["ubiquity:listeners"] ?? [];
}
if (shouldSkipBotEvents === void 0) {
shouldSkipBotEvents = manifest.skipBotEvents ?? true;
}
} else {
shouldSkipBotEvents = true;
}
return {
...pluginSettings,
with: pluginSettings?.with ?? {},
runsOn,
skipBotEvents: shouldSkipBotEvents
};
}
/**
* Retrieves the configuration from the given owner/repository. Also returns the raw data and errors, if any.
*
* @param owner The repository owner
* @param repository The repository name
* @param options - Optional ref (branch, tag, or commit SHA) for the configuration lookup
*/
async getConfigurationFromRepo(owner, repository, options) {
const location = { owner, repo: repository, environment: options?.environment };
const state = this._createImportState();
const octokit = await this._getOctokitForLocation(location, state);
if (!octokit) {
this._logger.warn("No Octokit available for configuration load", { owner, repository });
return { config: null, errors: null, rawData: null };
}
const { config, imports, errors, rawData } = await this._loadConfigSource(location, octokit, options?.ref);
if (!rawData) {
return { config: null, errors: null, rawData: null };
}
if (errors && errors.length) {
this._logger.warn("YAML could not be decoded", { owner, repository, errors });
return { config: null, errors, rawData };
}
if (!config) {
this._logger.warn("YAML could not be decoded", { owner, repository });
return { config: null, errors, rawData };
}
const importedConfigs = [];
for (const next of imports) {
const resolved = await this._resolveImportedConfiguration(next, state, 1);
if (resolved) importedConfigs.push(resolved);
}
const mergedConfig = mergeImportedConfigs(importedConfigs, config);
if (!mergedConfig) {
return { config: null, errors: null, rawData };
}
const decoded = this._decodeConfiguration(location, mergedConfig);
return { config: decoded.config, errors: decoded.errors, rawData };
}
_createImportState() {
return {
cache: /* @__PURE__ */ new Map(),
inFlight: /* @__PURE__ */ new Set(),
octokitByLocation: /* @__PURE__ */ new Map()
};
}
async _getOctokitForLocation(location, state) {
const key = normalizeImportKey(location);
if (state.octokitByLocation.has(key)) {
return state.octokitByLocation.get(key) ?? null;
}
if (this._octokitFactory) {
const resolved = await this._octokitFactory(location);
if (resolved) {
state.octokitByLocation.set(key, resolved);
return resolved;
}
}
state.octokitByLocation.set(key, this._octokit);
return this._octokit;
}
async _loadConfigSource(location, octokit, ref) {
const rawData = await this._download({
repository: location.repo,
owner: location.owner,
octokit,
ref,
environment: location.environment ?? this._environment
});
if (!rawData) {
this._logger.warn("No raw configuration data", { owner: location.owner, repository: location.repo });
return { config: null, imports: [], errors: null, rawData: null };
}
logOk(this._logger, "Downloaded configuration file", { owner: location.owner, repository: location.repo });
const { yaml, errors } = this.parseYaml(rawData);
const imports = readImports(this._logger, yaml?.imports, location);
if (yaml && typeof yaml === "object" && !Array.isArray(yaml) && "imports" in yaml) {
delete yaml.imports;
}
const targetRepoConfiguration = yaml;
return { config: targetRepoConfiguration, imports, errors, rawData };
}
_decodeConfiguration(location, config) {
this._logger.debug("Decoding configuration", { owner: location.owner, repository: location.repo });
try {
const configSchemaWithDefaults = import_value.Value.Default(configSchema, config);
const errors = import_value.Value.Errors(configSchema, configSchemaWithDefaults);
if (errors.First()) {
for (const error of errors) {
this._logger.warn("Configuration validation error", { err: error });
}
}
const decodedConfig = import_value.Value.Decode(configSchema, configSchemaWithDefaults);
return { config: stripImports(decodedConfig), errors: errors.First() ? errors : null };
} catch (error) {
this._logger.warn("Error decoding configuration; Will ignore.", { err: error, owner: location.owner, repository: location.repo });
const decodedError = unwrapTypeBoxError(error);
return { config: null, errors: [decodedError] };
}
}
async _resolveImportedConfiguration(location, state, depth) {
const key = normalizeImportKey(location);
if (state.cache.has(key)) {
return state.cache.get(key) ?? null;
}
if (state.inFlight.has(key)) {
this._logger.warn("Skipping import due to circular reference.", { location });
return null;
}
if (depth > MAX_IMPORT_DEPTH) {
this._logger.warn("Skipping import; maximum depth exceeded.", { location, depth });
return null;
}
state.inFlight.add(key);
let resolved = null;
try {
const octokit = await this._getOctokitForLocation(location, state);
if (!octokit) {
this._logger.warn("Skipping import; no authorized Octokit for owner.", { location });
return null;
}
const { config, imports, errors } = await this._loadConfigSource(location, octokit);
if (errors && errors.length) {
this._logger.warn("Skipping import due to YAML parsing errors.", { location, errors });
return null;
}
if (!config) {
return null;
}
const importedConfigs = [];
for (const next of imports) {
const nested = await this._resolveImportedConfiguration(next, state, depth + 1);
if (nested) importedConfigs.push(nested);
}
const mergedConfig = mergeImportedConfigs(importedConfigs, config);
if (!mergedConfig) return null;
const decoded = this._decodeConfiguration(location, mergedConfig);
resolved = decoded.config;
} finally {
state.inFlight.delete(key);
state.cache.set(key, resolved);
}
return resolved;
}
async _download({
repository,
owner,
octokit,
ref,
environment
}) {
if (!repository || !owner) {
this._logger.warn("Repo or owner is not defined, cannot download the requested file");
return null;
}
const pathList = getConfigPathCandidatesForEnvironment(environment ?? this._environment);
for (const filePath of pathList) {
const content = await this._tryDownloadPath({ repository, owner, octokit, filePath, ref });
if (content !== null) return content;
}
return null;
}
async _tryDownloadPath({
repository,
owner,
octokit,
filePath,
ref
}) {
try {
this._logger.info("Attempting to fetch configuration", { owner, repository, filePath });
const { data, headers } = await octokit.rest.repos.getContent({
owner,
repo: repository,
path: filePath,
...ref ? { ref } : {},
mediaType: { format: "raw" }
});
this._logger.debug("Configuration file found", { owner, repository, filePath, rateLimitRemaining: headers?.["x-ratelimit-remaining"] });
return data;
} catch (err) {
this._handleDownloadError(err, { owner, repository, filePath });
return null;
}
}
_handleDownloadError(err, context) {
const status = err && typeof err === "object" && "status" in err ? Number(err.status) : null;
if (status === 404) {
this._logger.warn("No configuration file found", context);
return;
}
const metadata = { err, ...context, ...status ? { status } : {} };
if (status && status >= 500) {
this._logger.error("Failed to download the requested file", metadata);
} else {
this._logger.warn("Failed to download the requested file", metadata);
}
}
/*
* Parse the raw YAML content and returns the loaded YAML, or errors if any.
*/
parseYaml(data) {
this._logger.debug("Will attempt to parse YAML data");
try {
if (data) {
const parsedData = import_js_yaml.default.load(data);
this._logger.debug("Parsed yaml data successfully");
return { yaml: parsedData ?? null, errors: null };
}
} catch (error) {
this._logger.warn("Error parsing YAML", { error });
return { errors: [error], yaml: null };
}
this._logger.warn("Could not parse YAML");
return { yaml: null, errors: null };
}
mergeConfigurations(configuration1, configuration2) {
const mergedPlugins = {
...configuration1.plugins,
...configuration2.plugins
};
return {
...configuration1,
...configuration2,
plugins: mergedPlugins
};
}
getManifest(plugin) {
return isGithubPlugin(plugin) ? this._fetchActionManifest(plugin) : this._fetchWorkerManifest(plugin);
}
async _fetchWorkerManifest(url) {
const manifestUrl = resolveManifestUrl(url);
if (!manifestUrl) {
this._logger.warn("Invalid plugin URL; cannot fetch manifest", { pluginUrl: url });
return null;
}
const manifestKey = `url:${manifestUrl}`;
if (this._manifestCache[manifestKey]) {
return this._manifestCache[manifestKey];
}
try {
const result = await fetch(manifestUrl);
if (!result.ok) {
this._logger.error("Could not find a manifest for Worker", { manifestUrl, status: result.status });
return null;
}
const jsonData = await result.json();
const manifest = this._decodeManifest(jsonData);
this._manifestCache[manifestKey] = manifest;
return manifest;
} catch (e) {
this._logger.error("Could not find a manifest for Worker", { manifestUrl, err: e });
}
return null;
}
async _fetchUrlManifest(pluginUrl) {
const manifestUrl = resolveManifestUrl(pluginUrl);
if (!manifestUrl) {
this._logger.warn("Invalid plugin URL; cannot fetch manifest", { pluginUrl });
return null;
}
const manifestKey = `url:${manifestUrl}`;
if (this._manifestCache[manifestKey]) {
return this._manifestCache[manifestKey];
}
if (this._manifestPromiseCache[manifestKey]) {
return this._manifestPromiseCache[manifestKey];
}
const manifestPromise = (async () => {
if (typeof fetch !== "function") {
this._logger.warn("Fetch is unavailable; cannot load URL manifest", { manifestUrl });
return null;
}
try {
const response = await fetch(manifestUrl);
if (!response.ok) {
this._logger.warn("URL manifest request failed", { manifestUrl, status: response.status });
return null;
}
const data = await response.json();
const manifest = this._decodeManifest(data);
this._manifestCache[manifestKey] = manifest;
return manifest;
} catch (e) {
this._logger.warn("Could not load URL manifest", { manifestUrl, err: e });
return null;
}
})();
this._manifestPromiseCache[manifestKey] = manifestPromise;
try {
return await manifestPromise;
} finally {
delete this._manifestPromiseCache[manifestKey];
}
}
async _fetchActionManifest({ owner, repo, ref }) {
const manifestKey = ref ? `${owner}:${repo}:${ref}` : `${owner}:${repo}`;
if (this._manifestCache[manifestKey]) {
return this._manifestCache[manifestKey];
}
if (this._manifestPromiseCache[manifestKey]) {
return this._manifestPromiseCache[manifestKey];
}
const manifestPromise = (async () => {
try {
const { data } = await this._octokit.rest.repos.getContent({
owner,
repo,
path: "manifest.json",
ref
});
if ("content" in data) {
const content = import_node_buffer.Buffer.from(data.content, "base64").toString();
const contentParsed = JSON.parse(content);
const manifest = this._decodeManifest(contentParsed);
this._manifestCache[manifestKey] = manifest;
return manifest;
}
} catch (e) {
this._logger.warn(`Could not find a valid manifest for ${owner}/${repo}${ref ? "@" + ref : ""}`, { owner, repo, ref, err: e });
}
return null;
})();
this._manifestPromiseCache[manifestKey] = manifestPromise;
try {
return await manifestPromise;
} finally {
delete this._manifestPromiseCache[manifestKey];
}
}
_decodeManifest(manifest) {
const errors = [...import_value.Value.Errors(manifestSchema, manifest)];
if (errors.length) {
for (const error of errors) {
this._logger.warn("Manifest validation error", { error });
}
throw new Error("Manifest is invalid.");
}
const defaultManifest = import_value.Value.Default(manifestSchema, manifest);
return defaultManifest;
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
CONFIG_DEV_FULL_PATH,
CONFIG_ORG_REPO,
CONFIG_PROD_FULL_PATH,
ConfigurationHandler,
configSchema,
isGithubPlugin
});