@ubiquity-os/plugin-sdk 3.8.4 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -10
- package/dist/configuration.d.mts +42 -19
- package/dist/configuration.d.ts +42 -19
- package/dist/configuration.js +430 -92
- package/dist/configuration.mjs +428 -91
- package/dist/{context-sqbr2o6i.d.mts → context-Dwl3aRX-.d.mts} +29 -0
- package/dist/{context-BbEmsEct.d.ts → context-zLHgu52i.d.ts} +29 -0
- package/dist/index.d.mts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +575 -224
- package/dist/index.mjs +574 -223
- package/dist/llm.d.mts +7 -43
- package/dist/llm.d.ts +7 -43
- package/dist/llm.js +175 -43
- package/dist/llm.mjs +175 -43
- package/dist/manifest.d.mts +2 -2
- package/dist/manifest.d.ts +2 -2
- package/dist/signature.js +3 -2
- package/dist/signature.mjs +3 -2
- package/package.json +7 -6
package/dist/configuration.js
CHANGED
|
@@ -33,7 +33,8 @@ __export(configuration_exports, {
|
|
|
33
33
|
CONFIG_DEV_FULL_PATH: () => CONFIG_DEV_FULL_PATH,
|
|
34
34
|
CONFIG_ORG_REPO: () => CONFIG_ORG_REPO,
|
|
35
35
|
CONFIG_PROD_FULL_PATH: () => CONFIG_PROD_FULL_PATH,
|
|
36
|
-
ConfigurationHandler: () => ConfigurationHandler
|
|
36
|
+
ConfigurationHandler: () => ConfigurationHandler,
|
|
37
|
+
isGithubPlugin: () => isGithubPlugin
|
|
37
38
|
});
|
|
38
39
|
module.exports = __toCommonJS(configuration_exports);
|
|
39
40
|
var import_value = require("@sinclair/typebox/value");
|
|
@@ -44,7 +45,11 @@ var import_node_buffer = require("node:buffer");
|
|
|
44
45
|
var import_webhooks = require("@octokit/webhooks");
|
|
45
46
|
var import_typebox = require("@sinclair/typebox");
|
|
46
47
|
var pluginNameRegex = new RegExp("^([0-9a-zA-Z-._]+)\\/([0-9a-zA-Z-._]+)(?::([0-9a-zA-Z-._]+))?(?:@([0-9a-zA-Z-._]+(?:\\/[0-9a-zA-Z-._]+)*))?$");
|
|
48
|
+
var urlRegex = /^https?:\/\/\S+$/;
|
|
47
49
|
function parsePluginIdentifier(value) {
|
|
50
|
+
if (urlRegex.test(value)) {
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
48
53
|
const matches = pluginNameRegex.exec(value);
|
|
49
54
|
if (!matches) {
|
|
50
55
|
throw new Error(`Invalid plugin name: ${value}`);
|
|
@@ -78,6 +83,7 @@ var pluginSettingsSchema = import_typebox.Type.Union(
|
|
|
78
83
|
);
|
|
79
84
|
var configSchema = import_typebox.Type.Object(
|
|
80
85
|
{
|
|
86
|
+
imports: import_typebox.Type.Optional(import_typebox.Type.Array(import_typebox.Type.String(), { default: [] })),
|
|
81
87
|
plugins: import_typebox.Type.Record(import_typebox.Type.String(), pluginSettingsSchema, { default: {} })
|
|
82
88
|
},
|
|
83
89
|
{
|
|
@@ -85,6 +91,15 @@ var configSchema = import_typebox.Type.Object(
|
|
|
85
91
|
}
|
|
86
92
|
);
|
|
87
93
|
|
|
94
|
+
// src/helpers/urls.ts
|
|
95
|
+
function normalizeBaseUrl(baseUrl) {
|
|
96
|
+
let normalized = baseUrl.trim();
|
|
97
|
+
while (normalized.endsWith("/")) {
|
|
98
|
+
normalized = normalized.slice(0, -1);
|
|
99
|
+
}
|
|
100
|
+
return normalized;
|
|
101
|
+
}
|
|
102
|
+
|
|
88
103
|
// src/types/manifest.ts
|
|
89
104
|
var import_typebox2 = require("@sinclair/typebox");
|
|
90
105
|
var import_webhooks2 = require("@octokit/webhooks");
|
|
@@ -118,14 +133,137 @@ var manifestSchema = import_typebox2.Type.Object({
|
|
|
118
133
|
var CONFIG_PROD_FULL_PATH = ".github/.ubiquity-os.config.yml";
|
|
119
134
|
var CONFIG_DEV_FULL_PATH = ".github/.ubiquity-os.config.dev.yml";
|
|
120
135
|
var CONFIG_ORG_REPO = ".ubiquity-os";
|
|
136
|
+
var EMPTY_STRING = "";
|
|
137
|
+
var ENVIRONMENT_TO_CONFIG_SUFFIX = {
|
|
138
|
+
development: "dev"
|
|
139
|
+
};
|
|
140
|
+
var VALID_CONFIG_SUFFIX = /^[a-z0-9][a-z0-9_-]*$/i;
|
|
141
|
+
var MAX_IMPORT_DEPTH = 6;
|
|
142
|
+
function normalizeEnvironmentName(environment) {
|
|
143
|
+
return String(environment ?? EMPTY_STRING).trim().toLowerCase();
|
|
144
|
+
}
|
|
145
|
+
function getConfigPathCandidatesForEnvironment(environment) {
|
|
146
|
+
const normalized = normalizeEnvironmentName(environment);
|
|
147
|
+
if (!normalized) {
|
|
148
|
+
return [CONFIG_PROD_FULL_PATH, CONFIG_DEV_FULL_PATH];
|
|
149
|
+
}
|
|
150
|
+
if (normalized === "production" || normalized === "prod") {
|
|
151
|
+
return [CONFIG_PROD_FULL_PATH];
|
|
152
|
+
}
|
|
153
|
+
const suffix = ENVIRONMENT_TO_CONFIG_SUFFIX[normalized] ?? normalized;
|
|
154
|
+
if (suffix === "dev") {
|
|
155
|
+
return [CONFIG_DEV_FULL_PATH];
|
|
156
|
+
}
|
|
157
|
+
if (!VALID_CONFIG_SUFFIX.test(suffix)) {
|
|
158
|
+
return [CONFIG_DEV_FULL_PATH];
|
|
159
|
+
}
|
|
160
|
+
return [`.github/.ubiquity-os.config.${suffix}.yml`, CONFIG_PROD_FULL_PATH];
|
|
161
|
+
}
|
|
162
|
+
function normalizeImportKey(location) {
|
|
163
|
+
return `${location.owner}`.trim().toLowerCase() + "/" + `${location.repo}`.trim().toLowerCase();
|
|
164
|
+
}
|
|
165
|
+
function isHttpUrl(value) {
|
|
166
|
+
const trimmed = value.trim();
|
|
167
|
+
return trimmed.startsWith("http://") || trimmed.startsWith("https://");
|
|
168
|
+
}
|
|
169
|
+
function resolveManifestUrl(pluginUrl) {
|
|
170
|
+
try {
|
|
171
|
+
const parsed = new URL(pluginUrl.trim());
|
|
172
|
+
let pathname = parsed.pathname;
|
|
173
|
+
while (pathname.endsWith("/") && pathname.length > 1) {
|
|
174
|
+
pathname = pathname.slice(0, -1);
|
|
175
|
+
}
|
|
176
|
+
if (pathname.endsWith(".json")) {
|
|
177
|
+
parsed.search = EMPTY_STRING;
|
|
178
|
+
parsed.hash = EMPTY_STRING;
|
|
179
|
+
return parsed.toString();
|
|
180
|
+
}
|
|
181
|
+
parsed.pathname = `${pathname}/manifest.json`;
|
|
182
|
+
parsed.search = EMPTY_STRING;
|
|
183
|
+
parsed.hash = EMPTY_STRING;
|
|
184
|
+
return parsed.toString();
|
|
185
|
+
} catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function parseImportSpec(value) {
|
|
190
|
+
const trimmed = value.trim();
|
|
191
|
+
if (!trimmed) return null;
|
|
192
|
+
const parts = trimmed.split("/");
|
|
193
|
+
if (parts.length !== 2) return null;
|
|
194
|
+
const [owner, repo] = parts;
|
|
195
|
+
if (!owner || !repo) return null;
|
|
196
|
+
return { owner, repo };
|
|
197
|
+
}
|
|
198
|
+
function readImports(logger, value, source) {
|
|
199
|
+
if (!value) return [];
|
|
200
|
+
if (!Array.isArray(value)) {
|
|
201
|
+
logger.warn("Invalid imports; expected a list of strings.", { source });
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
const seen = /* @__PURE__ */ new Set();
|
|
205
|
+
const imports = [];
|
|
206
|
+
for (const entry of value) {
|
|
207
|
+
if (typeof entry !== "string") {
|
|
208
|
+
logger.warn("Ignoring invalid import entry; expected string.", { source, entry });
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const parsed = parseImportSpec(entry);
|
|
212
|
+
if (!parsed) {
|
|
213
|
+
logger.warn("Ignoring invalid import entry; expected owner/repo.", { source, entry });
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
const key = normalizeImportKey(parsed);
|
|
217
|
+
if (seen.has(key)) continue;
|
|
218
|
+
seen.add(key);
|
|
219
|
+
imports.push(parsed);
|
|
220
|
+
}
|
|
221
|
+
return imports;
|
|
222
|
+
}
|
|
223
|
+
function stripImports(config) {
|
|
224
|
+
if (!config || typeof config !== "object") return config;
|
|
225
|
+
const rest = { ...config };
|
|
226
|
+
delete rest.imports;
|
|
227
|
+
return rest;
|
|
228
|
+
}
|
|
229
|
+
function mergeImportedConfigs(imported, base) {
|
|
230
|
+
if (!imported.length) {
|
|
231
|
+
return base;
|
|
232
|
+
}
|
|
233
|
+
let merged = imported[0];
|
|
234
|
+
for (let i = 1; i < imported.length; i++) {
|
|
235
|
+
merged = {
|
|
236
|
+
...merged,
|
|
237
|
+
...imported[i],
|
|
238
|
+
plugins: { ...merged.plugins, ...imported[i].plugins }
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return base ? {
|
|
242
|
+
...merged,
|
|
243
|
+
...base,
|
|
244
|
+
plugins: { ...merged.plugins, ...base.plugins }
|
|
245
|
+
} : merged;
|
|
246
|
+
}
|
|
247
|
+
function logOk(logger, message, metadata) {
|
|
248
|
+
if (logger.ok) {
|
|
249
|
+
logger.ok(message, metadata);
|
|
250
|
+
} else {
|
|
251
|
+
logger.info(message, metadata);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function isGithubPlugin(plugin) {
|
|
255
|
+
return typeof plugin !== "string";
|
|
256
|
+
}
|
|
121
257
|
var ConfigurationHandler = class {
|
|
122
|
-
constructor(_logger, _octokit, _environment = null) {
|
|
258
|
+
constructor(_logger, _octokit, _environment = null, options) {
|
|
123
259
|
this._logger = _logger;
|
|
124
260
|
this._octokit = _octokit;
|
|
125
261
|
this._environment = _environment;
|
|
262
|
+
this._octokitFactory = options?.octokitFactory;
|
|
126
263
|
}
|
|
127
264
|
_manifestCache = {};
|
|
128
265
|
_manifestPromiseCache = {};
|
|
266
|
+
_octokitFactory;
|
|
129
267
|
/**
|
|
130
268
|
* Retrieves the configuration for the current plugin based on its manifest.
|
|
131
269
|
* @param manifest - The plugin manifest containing the `short_name` identifier
|
|
@@ -134,9 +272,15 @@ var ConfigurationHandler = class {
|
|
|
134
272
|
**/
|
|
135
273
|
async getSelfConfiguration(manifest, location) {
|
|
136
274
|
const cfg = await this.getConfiguration(location);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
275
|
+
let selfConfig;
|
|
276
|
+
if (manifest.homepage_url) {
|
|
277
|
+
const name = manifest.homepage_url;
|
|
278
|
+
selfConfig = Object.keys(cfg.plugins).find((key) => normalizeBaseUrl(key) === normalizeBaseUrl(name));
|
|
279
|
+
} else {
|
|
280
|
+
const name = manifest.short_name.split("@")[0];
|
|
281
|
+
selfConfig = Object.keys(cfg.plugins).find((key) => new RegExp(`^${name}(?:$|@.+)`).exec(key.replace(/:[^@]+/, "")));
|
|
282
|
+
}
|
|
283
|
+
return selfConfig && cfg.plugins[selfConfig] ? cfg.plugins[selfConfig]?.["with"] : null;
|
|
140
284
|
}
|
|
141
285
|
/**
|
|
142
286
|
* Retrieves and merges configuration from organization and repository levels.
|
|
@@ -144,14 +288,22 @@ var ConfigurationHandler = class {
|
|
|
144
288
|
* @returns The merged plugin configuration with resolved plugin settings.
|
|
145
289
|
*/
|
|
146
290
|
async getConfiguration(location) {
|
|
147
|
-
const defaultConfiguration = import_value.Value.Decode(configSchema, import_value.Value.Default(configSchema, {}));
|
|
291
|
+
const defaultConfiguration = stripImports(import_value.Value.Decode(configSchema, import_value.Value.Default(configSchema, {})));
|
|
148
292
|
if (!location) {
|
|
149
|
-
this._logger.
|
|
293
|
+
this._logger.info("No location was provided, using the default configuration");
|
|
150
294
|
return defaultConfiguration;
|
|
151
295
|
}
|
|
296
|
+
const mergedConfiguration = await this._getMergedConfiguration(location, defaultConfiguration);
|
|
297
|
+
const resolvedPlugins = await this._resolvePlugins(mergedConfiguration, location);
|
|
298
|
+
return {
|
|
299
|
+
...mergedConfiguration,
|
|
300
|
+
plugins: resolvedPlugins
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
async _getMergedConfiguration(location, defaultConfiguration) {
|
|
152
304
|
const { owner, repo } = location;
|
|
153
305
|
let mergedConfiguration = defaultConfiguration;
|
|
154
|
-
this._logger.
|
|
306
|
+
this._logger.info("Fetching configurations from the organization and repository", {
|
|
155
307
|
orgRepo: `${owner}/${CONFIG_ORG_REPO}`,
|
|
156
308
|
repo: `${owner}/${repo}`
|
|
157
309
|
});
|
|
@@ -163,39 +315,50 @@ var ConfigurationHandler = class {
|
|
|
163
315
|
if (repoConfig.config) {
|
|
164
316
|
mergedConfiguration = this.mergeConfigurations(mergedConfiguration, repoConfig.config);
|
|
165
317
|
}
|
|
318
|
+
return mergedConfiguration;
|
|
319
|
+
}
|
|
320
|
+
async _resolvePlugins(mergedConfiguration, location) {
|
|
166
321
|
const resolvedPlugins = {};
|
|
167
|
-
this._logger
|
|
322
|
+
logOk(this._logger, "Found plugins enabled", { repo: `${location.owner}/${location.repo}`, plugins: Object.keys(mergedConfiguration.plugins).length });
|
|
168
323
|
for (const [pluginKey, pluginSettings] of Object.entries(mergedConfiguration.plugins)) {
|
|
324
|
+
const resolved = await this._resolvePluginSettings(pluginKey, pluginSettings);
|
|
325
|
+
if (!resolved) continue;
|
|
326
|
+
resolvedPlugins[pluginKey] = resolved;
|
|
327
|
+
}
|
|
328
|
+
return resolvedPlugins;
|
|
329
|
+
}
|
|
330
|
+
async _resolvePluginSettings(pluginKey, pluginSettings) {
|
|
331
|
+
const isUrlPlugin = isHttpUrl(pluginKey);
|
|
332
|
+
let manifest = null;
|
|
333
|
+
if (!isUrlPlugin) {
|
|
169
334
|
let pluginIdentifier;
|
|
170
335
|
try {
|
|
171
336
|
pluginIdentifier = parsePluginIdentifier(pluginKey);
|
|
172
337
|
} catch (error) {
|
|
173
|
-
this._logger.
|
|
174
|
-
|
|
338
|
+
this._logger.warn("Invalid plugin identifier; skipping", { plugin: pluginKey, err: error });
|
|
339
|
+
return null;
|
|
175
340
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
} else {
|
|
187
|
-
shouldSkipBotEvents = true;
|
|
341
|
+
manifest = await this.getManifest(pluginIdentifier);
|
|
342
|
+
} else {
|
|
343
|
+
manifest = await this._fetchUrlManifest(pluginKey);
|
|
344
|
+
}
|
|
345
|
+
let runsOn = pluginSettings?.runsOn ?? [];
|
|
346
|
+
let shouldSkipBotEvents = pluginSettings?.skipBotEvents;
|
|
347
|
+
if (manifest) {
|
|
348
|
+
if (!runsOn.length) {
|
|
349
|
+
runsOn = manifest["ubiquity:listeners"] ?? [];
|
|
188
350
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
};
|
|
351
|
+
if (shouldSkipBotEvents === void 0) {
|
|
352
|
+
shouldSkipBotEvents = manifest.skipBotEvents ?? true;
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
shouldSkipBotEvents = true;
|
|
195
356
|
}
|
|
196
357
|
return {
|
|
197
|
-
...
|
|
198
|
-
|
|
358
|
+
...pluginSettings,
|
|
359
|
+
with: pluginSettings?.with ?? {},
|
|
360
|
+
runsOn,
|
|
361
|
+
skipBotEvents: shouldSkipBotEvents
|
|
199
362
|
};
|
|
200
363
|
}
|
|
201
364
|
/**
|
|
@@ -205,87 +368,201 @@ var ConfigurationHandler = class {
|
|
|
205
368
|
* @param repository The repository name
|
|
206
369
|
*/
|
|
207
370
|
async getConfigurationFromRepo(owner, repository) {
|
|
371
|
+
const location = { owner, repo: repository };
|
|
372
|
+
const state = this._createImportState();
|
|
373
|
+
const octokit = await this._getOctokitForLocation(location, state);
|
|
374
|
+
if (!octokit) {
|
|
375
|
+
this._logger.warn("No Octokit available for configuration load", { owner, repository });
|
|
376
|
+
return { config: null, errors: null, rawData: null };
|
|
377
|
+
}
|
|
378
|
+
const { config, imports, errors, rawData } = await this._loadConfigSource(location, octokit);
|
|
379
|
+
if (!rawData) {
|
|
380
|
+
return { config: null, errors: null, rawData: null };
|
|
381
|
+
}
|
|
382
|
+
if (errors && errors.length) {
|
|
383
|
+
this._logger.warn("YAML could not be decoded", { owner, repository, errors });
|
|
384
|
+
return { config: null, errors, rawData };
|
|
385
|
+
}
|
|
386
|
+
if (!config) {
|
|
387
|
+
this._logger.warn("YAML could not be decoded", { owner, repository });
|
|
388
|
+
return { config: null, errors, rawData };
|
|
389
|
+
}
|
|
390
|
+
const importedConfigs = [];
|
|
391
|
+
for (const next of imports) {
|
|
392
|
+
const resolved = await this._resolveImportedConfiguration(next, state, 1);
|
|
393
|
+
if (resolved) importedConfigs.push(resolved);
|
|
394
|
+
}
|
|
395
|
+
const mergedConfig = mergeImportedConfigs(importedConfigs, config);
|
|
396
|
+
if (!mergedConfig) {
|
|
397
|
+
return { config: null, errors: null, rawData };
|
|
398
|
+
}
|
|
399
|
+
const decoded = this._decodeConfiguration(location, mergedConfig);
|
|
400
|
+
return { config: decoded.config, errors: decoded.errors, rawData };
|
|
401
|
+
}
|
|
402
|
+
_createImportState() {
|
|
403
|
+
return {
|
|
404
|
+
cache: /* @__PURE__ */ new Map(),
|
|
405
|
+
inFlight: /* @__PURE__ */ new Set(),
|
|
406
|
+
octokitByLocation: /* @__PURE__ */ new Map()
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
async _getOctokitForLocation(location, state) {
|
|
410
|
+
const key = normalizeImportKey(location);
|
|
411
|
+
if (state.octokitByLocation.has(key)) {
|
|
412
|
+
return state.octokitByLocation.get(key) ?? null;
|
|
413
|
+
}
|
|
414
|
+
if (this._octokitFactory) {
|
|
415
|
+
const resolved = await this._octokitFactory(location);
|
|
416
|
+
if (resolved) {
|
|
417
|
+
state.octokitByLocation.set(key, resolved);
|
|
418
|
+
return resolved;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
state.octokitByLocation.set(key, this._octokit);
|
|
422
|
+
return this._octokit;
|
|
423
|
+
}
|
|
424
|
+
async _loadConfigSource(location, octokit) {
|
|
208
425
|
const rawData = await this._download({
|
|
209
|
-
repository,
|
|
210
|
-
owner
|
|
426
|
+
repository: location.repo,
|
|
427
|
+
owner: location.owner,
|
|
428
|
+
octokit
|
|
211
429
|
});
|
|
212
|
-
this._logger.debug("Downloaded configuration file", { owner, repository });
|
|
213
430
|
if (!rawData) {
|
|
214
|
-
this._logger.
|
|
215
|
-
return { config: null, errors: null, rawData: null };
|
|
431
|
+
this._logger.warn("No raw configuration data", { owner: location.owner, repository: location.repo });
|
|
432
|
+
return { config: null, imports: [], errors: null, rawData: null };
|
|
433
|
+
}
|
|
434
|
+
logOk(this._logger, "Downloaded configuration file", { owner: location.owner, repository: location.repo });
|
|
435
|
+
const { yaml, errors } = this.parseYaml(rawData);
|
|
436
|
+
const imports = readImports(this._logger, yaml?.imports, location);
|
|
437
|
+
if (yaml && typeof yaml === "object" && !Array.isArray(yaml) && "imports" in yaml) {
|
|
438
|
+
delete yaml.imports;
|
|
216
439
|
}
|
|
217
|
-
const { yaml, errors } = this._parseYaml(rawData);
|
|
218
440
|
const targetRepoConfiguration = yaml;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
441
|
+
return { config: targetRepoConfiguration, imports, errors, rawData };
|
|
442
|
+
}
|
|
443
|
+
_decodeConfiguration(location, config) {
|
|
444
|
+
this._logger.info("Decoding configuration", { owner: location.owner, repository: location.repo });
|
|
445
|
+
try {
|
|
446
|
+
const configSchemaWithDefaults = import_value.Value.Default(configSchema, config);
|
|
447
|
+
const errors = import_value.Value.Errors(configSchema, configSchemaWithDefaults);
|
|
448
|
+
if (errors.First()) {
|
|
449
|
+
for (const error of errors) {
|
|
450
|
+
this._logger.warn("Configuration validation error", { err: error });
|
|
228
451
|
}
|
|
229
|
-
const decodedConfig = import_value.Value.Decode(configSchema, configSchemaWithDefaults);
|
|
230
|
-
return { config: decodedConfig, errors: errors2.First() ? errors2 : null, rawData };
|
|
231
|
-
} catch (error) {
|
|
232
|
-
this._logger.error("Error decoding configuration; Will ignore.", { err: error, owner, repository });
|
|
233
|
-
return { config: null, errors: [error instanceof import_value.TransformDecodeCheckError ? error.error : error], rawData };
|
|
234
452
|
}
|
|
453
|
+
const decodedConfig = import_value.Value.Decode(configSchema, configSchemaWithDefaults);
|
|
454
|
+
return { config: stripImports(decodedConfig), errors: errors.First() ? errors : null };
|
|
455
|
+
} catch (error) {
|
|
456
|
+
this._logger.warn("Error decoding configuration; Will ignore.", { err: error, owner: location.owner, repository: location.repo });
|
|
457
|
+
return { config: null, errors: [error instanceof import_value.TransformDecodeCheckError ? error.error : error] };
|
|
235
458
|
}
|
|
236
|
-
this._logger.error("YAML could not be decoded", { owner, repository, errors });
|
|
237
|
-
return { config: null, errors, rawData };
|
|
238
459
|
}
|
|
239
|
-
async
|
|
240
|
-
|
|
241
|
-
|
|
460
|
+
async _resolveImportedConfiguration(location, state, depth) {
|
|
461
|
+
const key = normalizeImportKey(location);
|
|
462
|
+
if (state.cache.has(key)) {
|
|
463
|
+
return state.cache.get(key) ?? null;
|
|
464
|
+
}
|
|
465
|
+
if (state.inFlight.has(key)) {
|
|
466
|
+
this._logger.warn("Skipping import due to circular reference.", { location });
|
|
242
467
|
return null;
|
|
243
468
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
pathList = [CONFIG_DEV_FULL_PATH];
|
|
248
|
-
break;
|
|
249
|
-
case "production":
|
|
250
|
-
pathList = [CONFIG_PROD_FULL_PATH];
|
|
251
|
-
break;
|
|
252
|
-
default:
|
|
253
|
-
pathList = [CONFIG_PROD_FULL_PATH, CONFIG_DEV_FULL_PATH];
|
|
469
|
+
if (depth > MAX_IMPORT_DEPTH) {
|
|
470
|
+
this._logger.warn("Skipping import; maximum depth exceeded.", { location, depth });
|
|
471
|
+
return null;
|
|
254
472
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
473
|
+
state.inFlight.add(key);
|
|
474
|
+
let resolved = null;
|
|
475
|
+
try {
|
|
476
|
+
const octokit = await this._getOctokitForLocation(location, state);
|
|
477
|
+
if (!octokit) {
|
|
478
|
+
this._logger.warn("Skipping import; no authorized Octokit for owner.", { location });
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
const { config, imports, errors } = await this._loadConfigSource(location, octokit);
|
|
482
|
+
if (errors && errors.length) {
|
|
483
|
+
this._logger.warn("Skipping import due to YAML parsing errors.", { location, errors });
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
if (!config) {
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
const importedConfigs = [];
|
|
490
|
+
for (const next of imports) {
|
|
491
|
+
const nested = await this._resolveImportedConfiguration(next, state, depth + 1);
|
|
492
|
+
if (nested) importedConfigs.push(nested);
|
|
272
493
|
}
|
|
494
|
+
const mergedConfig = mergeImportedConfigs(importedConfigs, config);
|
|
495
|
+
if (!mergedConfig) return null;
|
|
496
|
+
const decoded = this._decodeConfiguration(location, mergedConfig);
|
|
497
|
+
resolved = decoded.config;
|
|
498
|
+
} finally {
|
|
499
|
+
state.inFlight.delete(key);
|
|
500
|
+
state.cache.set(key, resolved);
|
|
501
|
+
}
|
|
502
|
+
return resolved;
|
|
503
|
+
}
|
|
504
|
+
async _download({ repository, owner, octokit }) {
|
|
505
|
+
if (!repository || !owner) {
|
|
506
|
+
this._logger.warn("Repo or owner is not defined, cannot download the requested file");
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
const pathList = getConfigPathCandidatesForEnvironment(this._environment);
|
|
510
|
+
for (const filePath of pathList) {
|
|
511
|
+
const content = await this._tryDownloadPath({ repository, owner, octokit, filePath });
|
|
512
|
+
if (content !== null) return content;
|
|
273
513
|
}
|
|
274
514
|
return null;
|
|
275
515
|
}
|
|
276
|
-
|
|
277
|
-
|
|
516
|
+
async _tryDownloadPath({
|
|
517
|
+
repository,
|
|
518
|
+
owner,
|
|
519
|
+
octokit,
|
|
520
|
+
filePath
|
|
521
|
+
}) {
|
|
522
|
+
try {
|
|
523
|
+
this._logger.info("Attempting to fetch configuration", { owner, repository, filePath });
|
|
524
|
+
const { data, headers } = await octokit.rest.repos.getContent({
|
|
525
|
+
owner,
|
|
526
|
+
repo: repository,
|
|
527
|
+
path: filePath,
|
|
528
|
+
mediaType: { format: "raw" }
|
|
529
|
+
});
|
|
530
|
+
logOk(this._logger, "Configuration file found", { owner, repository, filePath, rateLimitRemaining: headers?.["x-ratelimit-remaining"] });
|
|
531
|
+
return data;
|
|
532
|
+
} catch (err) {
|
|
533
|
+
this._handleDownloadError(err, { owner, repository, filePath });
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
_handleDownloadError(err, context) {
|
|
538
|
+
const status = err && typeof err === "object" && "status" in err ? Number(err.status) : null;
|
|
539
|
+
if (status === 404) {
|
|
540
|
+
this._logger.warn("No configuration file found", context);
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
const metadata = { err, ...context, ...status ? { status } : {} };
|
|
544
|
+
if (status && status >= 500) {
|
|
545
|
+
this._logger.error("Failed to download the requested file", metadata);
|
|
546
|
+
} else {
|
|
547
|
+
this._logger.warn("Failed to download the requested file", metadata);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
/*
|
|
551
|
+
* Parse the raw YAML content and returns the loaded YAML, or errors if any.
|
|
552
|
+
*/
|
|
553
|
+
parseYaml(data) {
|
|
554
|
+
this._logger.info("Will attempt to parse YAML data");
|
|
278
555
|
try {
|
|
279
556
|
if (data) {
|
|
280
557
|
const parsedData = import_js_yaml.default.load(data);
|
|
281
|
-
this._logger
|
|
558
|
+
logOk(this._logger, "Parsed yaml data successfully");
|
|
282
559
|
return { yaml: parsedData ?? null, errors: null };
|
|
283
560
|
}
|
|
284
561
|
} catch (error) {
|
|
285
|
-
this._logger.
|
|
562
|
+
this._logger.warn("Error parsing YAML", { error });
|
|
286
563
|
return { errors: [error], yaml: null };
|
|
287
564
|
}
|
|
288
|
-
this._logger.
|
|
565
|
+
this._logger.warn("Could not parse YAML");
|
|
289
566
|
return { yaml: null, errors: null };
|
|
290
567
|
}
|
|
291
568
|
mergeConfigurations(configuration1, configuration2) {
|
|
@@ -300,7 +577,67 @@ var ConfigurationHandler = class {
|
|
|
300
577
|
};
|
|
301
578
|
}
|
|
302
579
|
getManifest(plugin) {
|
|
303
|
-
return this._fetchActionManifest(plugin);
|
|
580
|
+
return isGithubPlugin(plugin) ? this._fetchActionManifest(plugin) : this._fetchWorkerManifest(plugin);
|
|
581
|
+
}
|
|
582
|
+
async _fetchWorkerManifest(url) {
|
|
583
|
+
if (this._manifestCache[url]) {
|
|
584
|
+
return this._manifestCache[url];
|
|
585
|
+
}
|
|
586
|
+
const manifestUrl = `${url}/manifest.json`;
|
|
587
|
+
try {
|
|
588
|
+
const result = await fetch(manifestUrl);
|
|
589
|
+
if (!result.ok) {
|
|
590
|
+
this._logger.error("Could not find a manifest for Worker", { manifestUrl, status: result.status });
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
const jsonData = await result.json();
|
|
594
|
+
const manifest = this._decodeManifest(jsonData);
|
|
595
|
+
this._manifestCache[url] = manifest;
|
|
596
|
+
return manifest;
|
|
597
|
+
} catch (e) {
|
|
598
|
+
this._logger.error("Could not find a manifest for Worker", { manifestUrl, err: e });
|
|
599
|
+
}
|
|
600
|
+
return null;
|
|
601
|
+
}
|
|
602
|
+
async _fetchUrlManifest(pluginUrl) {
|
|
603
|
+
const manifestUrl = resolveManifestUrl(pluginUrl);
|
|
604
|
+
if (!manifestUrl) {
|
|
605
|
+
this._logger.warn("Invalid plugin URL; cannot fetch manifest", { pluginUrl });
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
const manifestKey = `url:${manifestUrl}`;
|
|
609
|
+
if (this._manifestCache[manifestKey]) {
|
|
610
|
+
return this._manifestCache[manifestKey];
|
|
611
|
+
}
|
|
612
|
+
if (this._manifestPromiseCache[manifestKey]) {
|
|
613
|
+
return this._manifestPromiseCache[manifestKey];
|
|
614
|
+
}
|
|
615
|
+
const manifestPromise = (async () => {
|
|
616
|
+
if (typeof fetch !== "function") {
|
|
617
|
+
this._logger.warn("Fetch is unavailable; cannot load URL manifest", { manifestUrl });
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
try {
|
|
621
|
+
const response = await fetch(manifestUrl);
|
|
622
|
+
if (!response.ok) {
|
|
623
|
+
this._logger.warn("URL manifest request failed", { manifestUrl, status: response.status });
|
|
624
|
+
return null;
|
|
625
|
+
}
|
|
626
|
+
const data = await response.json();
|
|
627
|
+
const manifest = this._decodeManifest(data);
|
|
628
|
+
this._manifestCache[manifestKey] = manifest;
|
|
629
|
+
return manifest;
|
|
630
|
+
} catch (e) {
|
|
631
|
+
this._logger.warn("Could not load URL manifest", { manifestUrl, err: e });
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
})();
|
|
635
|
+
this._manifestPromiseCache[manifestKey] = manifestPromise;
|
|
636
|
+
try {
|
|
637
|
+
return await manifestPromise;
|
|
638
|
+
} finally {
|
|
639
|
+
delete this._manifestPromiseCache[manifestKey];
|
|
640
|
+
}
|
|
304
641
|
}
|
|
305
642
|
async _fetchActionManifest({ owner, repo, ref }) {
|
|
306
643
|
const manifestKey = ref ? `${owner}:${repo}:${ref}` : `${owner}:${repo}`;
|
|
@@ -326,7 +663,7 @@ var ConfigurationHandler = class {
|
|
|
326
663
|
return manifest;
|
|
327
664
|
}
|
|
328
665
|
} catch (e) {
|
|
329
|
-
this._logger.
|
|
666
|
+
this._logger.warn("Could not find a valid manifest", { owner, repo, err: e });
|
|
330
667
|
}
|
|
331
668
|
return null;
|
|
332
669
|
})();
|
|
@@ -341,7 +678,7 @@ var ConfigurationHandler = class {
|
|
|
341
678
|
const errors = [...import_value.Value.Errors(manifestSchema, manifest)];
|
|
342
679
|
if (errors.length) {
|
|
343
680
|
for (const error of errors) {
|
|
344
|
-
this._logger.
|
|
681
|
+
this._logger.warn("Manifest validation error", { error });
|
|
345
682
|
}
|
|
346
683
|
throw new Error("Manifest is invalid.");
|
|
347
684
|
}
|
|
@@ -354,5 +691,6 @@ var ConfigurationHandler = class {
|
|
|
354
691
|
CONFIG_DEV_FULL_PATH,
|
|
355
692
|
CONFIG_ORG_REPO,
|
|
356
693
|
CONFIG_PROD_FULL_PATH,
|
|
357
|
-
ConfigurationHandler
|
|
694
|
+
ConfigurationHandler,
|
|
695
|
+
isGithubPlugin
|
|
358
696
|
});
|