feat: enhance plugin configuration with diagnostics options and refactor build hash computation

This commit is contained in:
Ben Allfree
2025-12-10 20:03:59 -08:00
parent 7349d81102
commit 56d13d3e08
9 changed files with 322 additions and 161 deletions
+2
View File
@@ -15,6 +15,7 @@ import type * as builds from "../builds.js";
import type * as helpers from "../helpers.js";
import type * as http from "../http.js";
import type * as lib_filename from "../lib/filename.js";
import type * as lib_flags from "../lib/flags.js";
import type * as lib_r2 from "../lib/r2.js";
import type * as plugins from "../plugins.js";
import type * as profiles from "../profiles.js";
@@ -33,6 +34,7 @@ declare const fullApi: ApiFromModules<{
helpers: typeof helpers;
http: typeof http;
"lib/filename": typeof lib_filename;
"lib/flags": typeof lib_flags;
"lib/r2": typeof lib_r2;
plugins: typeof plugins;
profiles: typeof profiles;
+41 -17
View File
@@ -4,6 +4,7 @@ import { api, internal } from "./_generated/api"
import type { Doc, Id } from "./_generated/dataModel"
import { internalMutation, mutation, query } from "./_generated/server"
import { ArtifactType, getArtifactFilenameBase } from "./lib/filename"
import { computeFlagsFromConfig } from "./lib/flags"
import { generateSignedDownloadUrl } from "./lib/r2"
import { buildFields } from "./schema"
@@ -31,18 +32,8 @@ export const getByHash = query({
},
})
/**
* Computes flags string from build config.
* Only excludes modules explicitly marked as excluded (config[id] === true).
*/
export function computeFlagsFromConfig(config: Doc<"builds">["config"]): string {
// Sort modules to ensure consistent order
return Object.keys(config.modulesExcluded)
.sort()
.filter(module => config.modulesExcluded[module])
.map((moduleExcludedName: string) => `-D${moduleExcludedName}=1`)
.join(" ")
}
// Re-export for backward compatibility
export { computeFlagsFromConfig } from "./lib/flags"
/**
* Encodes a byte array to base62 string.
@@ -77,16 +68,39 @@ async function computeBuildHashInternal(
version: string,
target: string,
flags: string,
plugins: string[]
plugins: string[],
pluginConfig?: Record<string, Record<string, boolean>>
): Promise<string> {
// Input is now the exact parameters used for the build
// Sort plugins array for consistent hashing
const sortedPlugins = [...plugins].sort()
// Sort plugin config for consistent hashing
const sortedPluginConfig = pluginConfig
? Object.keys(pluginConfig)
.sort()
.reduce(
(acc, pluginSlug) => {
const sortedOptions = Object.keys(pluginConfig[pluginSlug])
.sort()
.reduce(
(opts, optKey) => {
opts[optKey] = pluginConfig[pluginSlug][optKey]
return opts
},
{} as Record<string, boolean>
)
acc[pluginSlug] = sortedOptions
return acc
},
{} as Record<string, Record<string, boolean>>
)
: undefined
const input = JSON.stringify({
version,
target,
flags,
plugins: sortedPlugins,
pluginConfig: sortedPluginConfig,
})
// Use Web Crypto API for SHA-256 hashing
@@ -103,10 +117,13 @@ async function computeBuildHashInternal(
* Computes buildHash from build config.
* This is the single source of truth for build hash computation.
*/
export async function computeBuildHash(config: Doc<"builds">["config"]): Promise<{ hash: string; flags: string }> {
const flags = computeFlagsFromConfig(config)
export async function computeBuildHash(
config: Doc<"builds">["config"],
registryData?: Record<string, { configOptions?: Record<string, { define: string }> }>
): Promise<{ hash: string; flags: string }> {
const flags = computeFlagsFromConfig(config, registryData)
const plugins = config.pluginsEnabled ?? []
const hash = await computeBuildHashInternal(config.version, config.target, flags, plugins)
const hash = await computeBuildHashInternal(config.version, config.target, flags, plugins, config.pluginConfigs)
return { hash, flags }
}
@@ -185,6 +202,8 @@ export const ensureBuildFromConfig = mutation({
version: v.string(),
modulesExcluded: v.optional(v.record(v.string(), v.boolean())),
pluginsEnabled: v.optional(v.array(v.string())),
pluginConfigs: v.optional(v.record(v.string(), v.record(v.string(), v.boolean()))),
registryData: v.optional(v.any()),
profileName: v.optional(v.string()),
profileDescription: v.optional(v.string()),
},
@@ -195,10 +214,15 @@ export const ensureBuildFromConfig = mutation({
modulesExcluded: args.modulesExcluded ?? {},
target: args.target,
pluginsEnabled: args.pluginsEnabled,
pluginConfigs: args.pluginConfigs,
}
// Compute build hash (single source of truth)
const { hash: buildHash, flags } = await computeBuildHash(config)
// Registry data is optional - diagnostics works for all plugins without registry lookup
const registryData = args.registryData as
| Record<string, { configOptions?: Record<string, { define: string }> }>
| undefined
const { hash: buildHash, flags } = await computeBuildHash(config, registryData)
const existingBuild = await ctx.db
.query("builds")
+52
View File
@@ -0,0 +1,52 @@
import type { Doc } from "../_generated/dataModel"
/**
* Computes flags string from build config.
* Only excludes modules explicitly marked as excluded (config[id] === true).
* Also includes plugin config options (e.g., diagnostics).
*
* @param config - Build configuration with modulesExcluded and pluginConfigs
* @param registryData - Optional registry data for custom config options (not needed for diagnostics)
*/
export function computeFlagsFromConfig(
config: Doc<"builds">["config"],
registryData?: Record<string, { configOptions?: Record<string, { define: string }> }>
): string {
const flags: string[] = []
// Sort modules to ensure consistent order
const moduleFlags = Object.keys(config.modulesExcluded)
.sort()
.filter(module => config.modulesExcluded[module])
.map((moduleExcludedName: string) => `-D${moduleExcludedName}=1`)
flags.push(...moduleFlags)
// Add plugin config options (diagnostics is available for all plugins)
if (config.pluginConfigs) {
for (const [pluginSlug, pluginOptions] of Object.entries(config.pluginConfigs)) {
// Handle diagnostics option (available for all plugins)
if (pluginOptions.diagnostics) {
// Convert plugin slug to uppercase define name (e.g., "lofs" -> "LOFS_PLUGIN_DIAGNOSTICS")
const defineName = `${pluginSlug.toUpperCase().replace(/-/g, "_")}_PLUGIN_DIAGNOSTICS`
flags.push(`-D${defineName}`)
}
// Handle other custom config options from registry (if any)
if (registryData) {
const plugin = registryData[pluginSlug]
if (plugin?.configOptions) {
for (const [optionKey, enabled] of Object.entries(pluginOptions)) {
if (optionKey !== "diagnostics" && enabled) {
const option = plugin.configOptions[optionKey]
if (option?.define) {
flags.push(`-D${option.define}`)
}
}
}
}
}
}
}
return flags.join(" ")
}
+1
View File
@@ -7,6 +7,7 @@ export const buildConfigFields = {
modulesExcluded: v.record(v.string(), v.boolean()),
target: v.string(),
pluginsEnabled: v.optional(v.array(v.string())),
pluginConfigs: v.optional(v.record(v.string(), v.record(v.string(), v.boolean()))),
}
export const profileFields = {