@opys/dev 0.1.2

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../lib/plugin.ts","../lib/config.ts","../lib/engine.ts","../lib/overrides.ts","../lib/artifact-plugin.ts","../lib/paths.ts","../lib/scanner.ts","../lib/github.ts","../lib/loader.ts"],"sourcesContent":["import type { Artifact, ValDefs, Val, Valset } from '@opys/core';\n\n/** Build-time context handed to every plugin's `build` hook. */\nexport interface BuildContext {\n /** Sanctioned build-time logging channel; auto-prefixed by plugin name. */\n log: (scope: string, message: string) => void;\n /** Absolute directory of `opys.config.mjs` — anchor for relative paths. */\n configDir: string;\n /** Value of `opys build --mode <m>`; empty string when unset. */\n mode: string;\n}\n\n/**\n * Named launch fragments a plugin exposes for the config's `command`/`args`\n * accessor functions — e.g. `{ jvmArgs, mainClass, gameArgs }` or `{ bin }`.\n */\nexport type LaunchGroups = Record<string, Valset | Val | string>;\n\n/** What a plugin's `build` hook returns. */\nexport interface Contribution {\n /** Artifacts to download/copy/extract. */\n artifacts?: Artifact[];\n /** Manifest vars this plugin owns. */\n vars?: ValDefs;\n /** Named launch fragments, exposed to the config's accessor functions. */\n launch?: LaunchGroups;\n}\n\n/**\n * A opys plugin — a pure-to-construct, bundler-style hook object. The\n * constructor (`forge('1.20.1-best')`, …) does zero I/O; all network/fs work\n * happens inside `build`, which the engine drives.\n */\nexport interface OpysPlugin {\n name: string;\n build(ctx: BuildContext): Promise<Contribution> | Contribution;\n}\n\n/** Identity helper for authoring a plugin with inferred types. */\nexport function definePlugin(plugin: OpysPlugin): OpysPlugin {\n return plugin;\n}\n","import type { Artifact, ValDefs, Manifest, Val, Valset } from '@opys/core';\nimport type { OpysPlugin, LaunchGroups } from './plugin';\n\n/** Map of plugin name → its launch groups, passed to launch accessors. */\nexport type PluginMap = Record<string, LaunchGroups>;\n\n/** One entry of a config's assembled `args` — flattened to a `Valset`. */\nexport type ArgItem = Valset | Val | string;\n\nexport interface OpysManifestConfig {\n /** Hand-written literal artifacts, merged with plugin output (last wins). */\n artifacts?: Artifact[];\n /** Override/extra vars layered on top of the merged plugin vars. */\n vars?: ValDefs;\n /** Launch command — typically `({ java }) => java.bin`. */\n command: (plugins: PluginMap) => string;\n /** Launch args — author-ordered named groups, flattened to a `Valset`. */\n args: (plugins: PluginMap) => ArgItem[];\n /** Working directory for the launched process. */\n workdir?: string | ((plugins: PluginMap) => string);\n /** Environment variables for the launched process. */\n envs?: ValDefs | ((plugins: PluginMap) => ValDefs);\n /** `restrict` globs swept clean after install. */\n restrict?: string[];\n}\n\nexport interface OpysConfig {\n /** Default manifest output path, relative to the config file. */\n output?: string;\n /** The plugins whose `build` hooks produce the manifest. */\n plugins: OpysPlugin[];\n /** Declarative manifest fields, separate from tooling config. */\n manifest: OpysManifestConfig;\n /**\n * Launch-time manifest patch. Re-run on every `opys launch`; the returned\n * partial is shallow-merged (per field) over the loaded manifest.\n */\n runClient?: (manifest: Manifest) => Partial<Manifest>;\n}\n\nexport interface OpysConfigContext {\n /** Value of `opys build --mode <m>`; empty string when unset. */\n mode: string;\n}\n\nexport type OpysConfigInput =\n | OpysConfig\n | ((ctx: OpysConfigContext) => OpysConfig | Promise<OpysConfig>);\n\n/** Use as the default export of `opys.config.mjs`. */\nexport function defineConfig(input: OpysConfigInput): OpysConfigInput {\n return input;\n}\n\n/** Resolve a config input to a concrete `OpysConfig`. */\nexport async function resolveConfig(\n input: OpysConfigInput,\n ctx: OpysConfigContext,\n): Promise<OpysConfig> {\n return typeof input === 'function' ? input(ctx) : input;\n}\n","import {\n type Manifest,\n type Artifact,\n type Launch,\n type ValDefs,\n type Val,\n type Valset,\n deduplicateArtifacts,\n} from '@opys/core';\nimport type { OpysConfig, ArgItem, PluginMap } from './config';\nimport type { BuildContext } from './plugin';\n\n/** Flatten author-ordered launch groups into a single `Valset`. */\nfunction flattenArgs(items: ReadonlyArray<ArgItem>): Valset {\n const out: Val[] = [];\n for (const item of items) {\n if (typeof item === 'string') out.push({ rules: [], value: [item] });\n else if (Array.isArray(item)) out.push(...item);\n else out.push(item);\n }\n return out;\n}\n\n/**\n * Run every plugin's `build` hook in parallel, merge the contributions, and\n * assemble the final `Manifest`.\n */\nexport async function buildManifest(\n config: OpysConfig,\n ctx: BuildContext,\n): Promise<Manifest> {\n ctx.log('opys', `resolving ${config.plugins.length} plugin(s)`);\n const results = await Promise.all(\n config.plugins.map(async (p) => ({\n name: p.name,\n contribution: await p.build(ctx),\n })),\n );\n\n // Artifacts: plugin output in list order, then literal manifest.artifacts.\n const artifacts: Artifact[] = [];\n for (const r of results) {\n if (r.contribution.artifacts) artifacts.push(...r.contribution.artifacts);\n }\n if (config.manifest.artifacts) artifacts.push(...config.manifest.artifacts);\n const deduped = deduplicateArtifacts(artifacts);\n\n // Vars: merge plugin vars (list order, last wins); warn on collisions.\n const vars: Record<string, ValDefs[string]> = {};\n const owner: Record<string, string> = {};\n for (const r of results) {\n if (!r.contribution.vars) continue;\n for (const [key, value] of Object.entries(r.contribution.vars)) {\n const prev = owner[key];\n if (prev !== undefined && prev !== r.name) {\n ctx.log(\n 'opys',\n `warning: var '${key}' set by both '${prev}' and '${r.name}' — using '${r.name}'`,\n );\n }\n vars[key] = value;\n owner[key] = r.name;\n }\n }\n // Config `vars` is the sanctioned override layer (silent).\n if (config.manifest.vars) {\n for (const [key, value] of Object.entries(config.manifest.vars)) {\n vars[key] = value;\n }\n }\n\n // Launch: author functions over the plugin → launch-groups map.\n const pluginMap: PluginMap = Object.fromEntries(\n results.map((r) => [r.name, r.contribution.launch ?? {}]),\n );\n const m = config.manifest;\n const launch: Launch = {\n command: m.command(pluginMap),\n workdir:\n typeof m.workdir === 'function'\n ? m.workdir(pluginMap)\n : (m.workdir ?? '.'),\n args: flattenArgs(m.args(pluginMap)),\n envs: typeof m.envs === 'function' ? m.envs(pluginMap) : (m.envs ?? {}),\n };\n\n ctx.log(\n 'opys',\n `merged ${deduped.length} artifact(s) (${artifacts.length - deduped.length} deduped)`,\n );\n\n return {\n vars,\n launch,\n artifacts: deduped,\n ...(m.restrict && m.restrict.length > 0 ? { restrict: m.restrict } : {}),\n };\n}\n","import {\n type Artifact,\n type Ruleset,\n globToRegex,\n parseShortRuleset,\n} from '@opys/core';\n\n/**\n * Targets a subset of artifacts:\n *\n * - `string` / `string[]` — glob(s) matched against `artifact.path`\n * (multiple = OR).\n * - predicate — `(a) => boolean`, the escape hatch for matching on source\n * kind, size, metadata, …\n */\nexport type Selector = string | string[] | ((artifact: Artifact) => boolean);\n\nexport function matchesSelector(\n selector: Selector,\n artifact: Artifact,\n): boolean {\n if (typeof selector === 'function') return selector(artifact);\n const globs = (Array.isArray(selector) ? selector : [selector]).map(\n globToRegex,\n );\n return globs.some((re) => re.test(artifact.path));\n}\n\n/** A ruleset in any form `parseShortRuleset` accepts (shorthand or full). */\nexport type RulesetInput = Parameters<typeof parseShortRuleset>[0];\n\n/**\n * A per-selector patch over the artifacts a plugin produces. Each entry\n * names the files it `match`es and the changes to apply — drop them,\n * attach a ruleset, or clear integrity. Overrides run in list order; a\n * later entry sees the effect of an earlier one.\n */\nexport interface ArtifactOverride {\n /** Files this override applies to. */\n match: Selector;\n /** Drop matched artifacts entirely. */\n exclude?: boolean;\n /**\n * Ruleset to attach to matched artifacts — shorthand (`'allow.os.osx'`)\n * or a full `Ruleset`. Appended to each artifact's existing `rules`.\n */\n rules?: RulesetInput;\n /** `null` clears `integrity` + `discovery` (skip verification). */\n integrity?: null;\n}\n\n/** Apply an ordered list of {@link ArtifactOverride}s to a list of artifacts. */\nexport function applyOverrides(\n artifacts: readonly Artifact[],\n overrides: readonly ArtifactOverride[],\n): Artifact[] {\n if (overrides.length === 0) return [...artifacts];\n const out: Artifact[] = [];\n for (const artifact of artifacts) {\n let current: Artifact | null = artifact;\n for (const override of overrides) {\n if (current === null) break;\n if (!matchesSelector(override.match, current)) continue;\n if (override.exclude) {\n current = null;\n break;\n }\n if (override.rules !== undefined) {\n const extra: Ruleset = parseShortRuleset(override.rules);\n current = { ...current, rules: [...current.rules, ...extra] };\n }\n if (override.integrity === null) {\n current = { ...current, integrity: undefined, discovery: undefined };\n }\n }\n if (current !== null) out.push(current);\n }\n return out;\n}\n","import type { Contribution, OpysPlugin } from './plugin';\nimport { applyOverrides, type ArtifactOverride } from './overrides';\n\n/**\n * Wraps a {@link OpysPlugin} so that the artifacts it contributes are passed\n * through an ordered list of {@link ArtifactOverride}s before being returned.\n *\n * Use this to give artifact-producing plugins (`forge`, `curseforge`, …) the\n * same filter/patch support that `artifactScanner` already has natively. The\n * inner plugin's `build` runs unchanged; only `Contribution.artifacts` is\n * rewritten. Non-artifact contribution fields (`vars`, `launch`) pass through\n * untouched.\n *\n * @param plugin The inner plugin to wrap.\n * @param overrides Overrides applied to the inner plugin's artifacts. An empty\n * list is a no-op (artifacts pass through unchanged).\n */\nexport function defineArtifactPlugin(\n plugin: OpysPlugin,\n overrides: readonly ArtifactOverride[],\n): OpysPlugin {\n return {\n name: plugin.name,\n async build(ctx) {\n const contribution: Contribution = await plugin.build(ctx);\n if (contribution.artifacts === undefined) return contribution;\n return {\n ...contribution,\n artifacts: applyOverrides(contribution.artifacts, overrides),\n };\n },\n };\n}\n","import { homedir } from 'node:os';\nimport { join } from 'node:path';\n\n/**\n * Per-user data directory for an application, matching OS conventions:\n *\n * - Windows: `%APPDATA%\\<name>` (e.g. `C:\\Users\\you\\AppData\\Roaming\\<name>`)\n * - macOS: `~/Library/Application Support/<name>`\n * - Linux: `$XDG_DATA_HOME/<name>` or `~/.local/share/<name>`\n */\nexport function userDataDir(name: string): string {\n if (process.platform === 'win32') {\n return join(process.env.APPDATA ?? homedir(), name);\n }\n if (process.platform === 'darwin') {\n return join(homedir(), 'Library', 'Application Support', name);\n }\n const base = process.env.XDG_DATA_HOME ?? join(homedir(), '.local', 'share');\n return join(base, name);\n}\n","import { createHash } from 'node:crypto';\nimport { readFile, readdir, stat } from 'node:fs/promises';\nimport {\n basename,\n dirname,\n isAbsolute,\n join,\n relative,\n resolve,\n} from 'node:path';\nimport type { Artifact, Integrity, Source } from '@opys/core';\nimport { sourceFile, sourceUrl, interpolate } from '@opys/core';\nimport { definePlugin, type OpysPlugin } from './plugin';\nimport { applyOverrides, type ArtifactOverride } from './overrides';\n\n/** A file discovered by {@link artifactScanner}, passed to `path`/`url` functions. */\nexport interface ScannedFile {\n /** Path relative to `directory`, POSIX separators. */\n readonly rel: string;\n /** Directory portion of `rel` (`''` at the root). */\n readonly dir: string;\n /** Final path segment. */\n readonly filename: string;\n /** Absolute path on the build machine. */\n readonly abs: string;\n}\n\n/**\n * A `path` / `url` value: either a template string — interpolating the\n * per-file placeholders `${rel}` / `${dir}` / `${filename}`, with any other\n * `${var}` left for install — or a `(file) => string` function. Only the\n * function form receives the build-machine `abs` path.\n */\nexport type ScanTemplate = string | ((file: ScannedFile) => string);\n\nexport interface ArtifactScannerOptions {\n /** Directory to scan. */\n directory: string;\n /** URL for fetching each file — template string or `(file) => string`. */\n url: ScanTemplate;\n /** Destination path — template or function. Defaults to the file's `rel`. */\n path?: ScanTemplate;\n hash?: 'sha1' | 'sha256';\n /**\n * 'url' → emit sourceUrl + computed hash (default)\n * 'file' → emit sourceFile pointing at the local copy; skip hashing entirely\n */\n source?: 'url' | 'file';\n /**\n * Per-selector patches applied to the scanned artifacts — exclude files,\n * attach rulesets (OS / feature gates), or clear integrity.\n */\n overrides?: ArtifactOverride[];\n}\n\n/** A {@link ScannedFile} carrying its on-disk `size`. */\ntype ScannedEntry = ScannedFile & { readonly size: number };\n\nasync function walkDir(dir: string): Promise<ScannedEntry[]> {\n async function walk(cur: string): Promise<ScannedEntry[]> {\n const entries = await readdir(cur, { withFileTypes: true });\n const results = await Promise.all(\n entries.map(async (e) => {\n const abs = join(cur, e.name);\n if (e.isDirectory()) return walk(abs);\n if (e.isFile()) {\n const { size } = await stat(abs);\n const rel = relative(dir, abs).replace(/\\\\/g, '/');\n const d = dirname(rel);\n return [\n {\n rel,\n dir: d === '.' ? '' : d,\n filename: basename(rel),\n abs,\n size,\n },\n ];\n }\n return [];\n }),\n );\n return results.flat();\n }\n return walk(dir);\n}\n\nasync function hashFile(\n path: string,\n algo: 'sha1' | 'sha256',\n): Promise<string> {\n return createHash(algo)\n .update(await readFile(path))\n .digest('hex');\n}\n\nfunction applyTemplate(tpl: ScanTemplate, file: ScannedFile): string {\n if (typeof tpl === 'function') return tpl(file);\n return interpolate(tpl, {\n rel: file.rel,\n dir: file.dir,\n filename: file.filename,\n });\n}\n\nasync function* scanDirectory(\n options: ArtifactScannerOptions,\n baseDir: string,\n): AsyncGenerator<Artifact> {\n const algo = options.hash ?? 'sha1';\n const sourceKind = options.source ?? 'url';\n const files = await walkDir(baseDir);\n\n for (const file of files) {\n const artifactPath = options.path\n ? applyTemplate(options.path, file)\n : file.rel;\n\n let integrity: Integrity | undefined;\n let source: Source;\n if (sourceKind === 'file') {\n source = sourceFile(file.abs);\n // trust local file by path — skip hash computation\n } else {\n source = sourceUrl(applyTemplate(options.url, file));\n const digest = await hashFile(file.abs, algo);\n integrity = algo === 'sha1' ? { sha1: digest } : { sha256: digest };\n }\n\n yield {\n path: artifactPath,\n source,\n size: file.size,\n rules: [],\n integrity,\n };\n }\n}\n\n/** Scan a local directory tree into artifacts — a generic build-time plugin. */\nexport function artifactScanner(options: ArtifactScannerOptions): OpysPlugin {\n return definePlugin({\n name: 'artifactScanner',\n async build(ctx) {\n const baseDir = isAbsolute(options.directory)\n ? options.directory\n : resolve(ctx.configDir, options.directory);\n const scanned: Artifact[] = [];\n for await (const a of scanDirectory(options, baseDir)) scanned.push(a);\n const artifacts = applyOverrides(scanned, options.overrides ?? []);\n const dropped = scanned.length - artifacts.length;\n ctx.log(\n 'artifactScanner',\n `scanned ${scanned.length} file(s)` +\n (dropped > 0 ? `, ${dropped} excluded` : ''),\n );\n return { artifacts };\n },\n });\n}\n","/**\n * Shared GitHub Releases helpers.\n *\n * The forge-family loaders (cleanroom, lwjgl3ify, unimixins) all resolve\n * their versions straight off GitHub Releases. This module centralises the\n * release-listing request and the `sha256:<hex>` digest parsing so the\n * resolvers don't each carry their own copy.\n */\n\nimport {\n fetchWithRetry,\n sourceUrl,\n type Artifact,\n type Discovery,\n type ExtractRule,\n type Ruleset,\n} from '@opys/core';\n\n/** A single asset on a GitHub release, mirroring the `/releases` API shape. */\nexport interface GitHubAsset {\n name: string;\n size: number;\n browser_download_url: string;\n /** `sha256:<hex>` when present (introduced 2024); older releases lack it. */\n digest?: string;\n}\n\n/** A single GitHub release entry from the `/releases` API. */\nexport interface GitHubRelease {\n tag_name: string;\n prerelease: boolean;\n draft: boolean;\n published_at: string;\n assets: GitHubAsset[];\n}\n\n/**\n * List all releases for `repo` (`owner/name`), newest first. Fetches a\n * single page of up to 100 releases. `token` raises the rate limit.\n */\nexport async function listGitHubReleases(\n repo: string,\n token?: string,\n): Promise<GitHubRelease[]> {\n const headers: Record<string, string> = {\n Accept: 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n };\n if (token) headers.Authorization = `Bearer ${token}`;\n const res = await fetchWithRetry(\n `https://api.github.com/repos/${repo}/releases?per_page=100`,\n { headers },\n );\n if (!res.ok) {\n throw new Error(\n `GitHub API ${res.status} ${res.statusText} listing ${repo} releases`,\n );\n }\n return (await res.json()) as GitHubRelease[];\n}\n\n/**\n * Extract the hex sha256 from an asset's `digest` field. GitHub's `digest`\n * is `sha256:<hex>` when present (introduced 2024); older releases lack it.\n */\nexport function gitHubAssetSha256(asset: GitHubAsset): string | undefined {\n return asset.digest?.startsWith('sha256:')\n ? asset.digest.slice('sha256:'.length)\n : undefined;\n}\n\n/** Selector accepted by {@link pickGitHubRelease}. */\nexport type GitHubReleaseSelector =\n /** Newest non-prerelease. */\n | 'latest'\n /** Newest including prereleases. */\n | 'prerelease'\n /** Exact `tag_name` match. */\n | (string & {});\n\nexport interface PickGitHubReleaseOptions {\n /** Optional GitHub token for higher rate limits. */\n token?: string;\n /**\n * Restrict candidates before the selector runs. Returns true to keep.\n * Use this when the loader needs specific assets present (e.g. a\n * `version.json` next to a mod jar) — `'latest'` then falls through to\n * the newest release that actually qualifies.\n */\n filter?: (release: GitHubRelease) => boolean;\n}\n\n/**\n * Fetch `repo`'s releases and pick one matching `selector`. Drafts are\n * always skipped; the optional `filter` further narrows candidates before\n * the selector runs. Throws a descriptive error (with up to 5 available\n * tags) when nothing matches.\n */\nexport async function pickGitHubRelease(\n repo: string,\n selector: GitHubReleaseSelector,\n options: PickGitHubReleaseOptions = {},\n): Promise<GitHubRelease> {\n const candidates = (await listGitHubReleases(repo, options.token))\n .filter((r) => !r.draft)\n .filter(options.filter ?? (() => true));\n\n const picked = selectGitHubRelease(candidates, selector);\n if (picked) return picked;\n\n if (selector === 'latest' || selector === 'prerelease') {\n throw new Error(\n `No ${selector === 'latest' ? 'stable' : ''} GitHub release in ${repo}${\n options.filter ? ' matching the loader filter' : ''\n }`.replace(/ +/g, ' '),\n );\n }\n const tags = candidates.slice(0, 5).map((r) => r.tag_name);\n throw new Error(\n `GitHub release '${selector}' not found in ${repo}. Available: ${tags.join(\n ', ',\n )}${candidates.length > 5 ? ', …' : ''}`,\n );\n}\n\nfunction selectGitHubRelease(\n releases: GitHubRelease[],\n selector: GitHubReleaseSelector,\n): GitHubRelease | undefined {\n if (selector === 'latest') return releases.find((r) => !r.prerelease);\n if (selector === 'prerelease') return releases[0];\n return releases.find((r) => r.tag_name === selector);\n}\n\n/**\n * One asset → Artifact mapping inside a {@link gitHubReleaseArtifacts}\n * call. The asset's URL, size, and sha256 are auto-derived; the spec\n * supplies the install-time path plus any extra Artifact fields.\n */\nexport interface GitHubAssetSpec {\n /** Predicate to pick the asset on the resolved release. */\n match: (asset: GitHubAsset, release: GitHubRelease) => boolean;\n /**\n * Install-time destination path. Manifest template literals (`${var}`)\n * pass through unchanged. Use the function form when the path embeds a\n * build-time value like the release tag.\n */\n path: string | ((asset: GitHubAsset, release: GitHubRelease) => string);\n /** Human description used in the error if `match` finds nothing. */\n description?: string;\n /** Manifest rules (OS/arch constraints). Default `[]`. */\n rules?: Ruleset;\n /** Extract rules for jars / tarballs. */\n extract?: ExtractRule[];\n /** Optional install-time discovery hints. */\n discovery?: Discovery;\n /** Opaque metadata, forwarded into the manifest unchanged. */\n metadata?: unknown;\n}\n\nexport interface GitHubReleaseArtifactsOptions {\n /** GitHub token (raises rate limits). */\n token?: string;\n /**\n * Restrict candidate releases before the selector runs. Use when the\n * loader requires multiple assets present in the same release so that\n * `'latest'` falls through to the newest one that fully qualifies.\n */\n filter?: (release: GitHubRelease) => boolean;\n /** One spec per asset → Artifact mapping, in output order. */\n assets: GitHubAssetSpec[];\n}\n\nexport interface GitHubReleaseArtifactsResult {\n /** The picked release — handy for downstream metadata or side-fetches. */\n release: GitHubRelease;\n /** Mapped artifacts in input order, one per `assets` spec. */\n artifacts: Artifact[];\n}\n\n/**\n * Pick a GitHub release and project a list of asset specs into Artifacts\n * in one call. Throws if the release can't be picked or any spec has no\n * matching asset on the picked release.\n *\n * @example\n * const { artifacts } = await gitHubReleaseArtifacts(\n * 'me/myloader', version, {\n * assets: [{\n * match: (a) => a.name.endsWith('.jar'),\n * path: '${mods_directory}/myloader.jar',\n * }],\n * },\n * );\n */\nexport async function gitHubReleaseArtifacts(\n repo: string,\n selector: GitHubReleaseSelector,\n options: GitHubReleaseArtifactsOptions,\n): Promise<GitHubReleaseArtifactsResult> {\n const release = await pickGitHubRelease(repo, selector, {\n token: options.token,\n filter: options.filter,\n });\n const artifacts = options.assets.map((spec) =>\n gitHubAssetToArtifact(spec, release),\n );\n return { release, artifacts };\n}\n\nfunction gitHubAssetToArtifact(\n spec: GitHubAssetSpec,\n release: GitHubRelease,\n): Artifact {\n const asset = release.assets.find((a) => spec.match(a, release));\n if (!asset) {\n const what = spec.description ? ` (${spec.description})` : '';\n throw new Error(\n `No matching asset${what} on GitHub release ${release.tag_name}. ` +\n `Assets: ${release.assets.map((a) => a.name).join(', ') || '(none)'}`,\n );\n }\n const path =\n typeof spec.path === 'function' ? spec.path(asset, release) : spec.path;\n const sha256 = gitHubAssetSha256(asset);\n return {\n path,\n source: sourceUrl(asset.browser_download_url),\n size: asset.size,\n rules: spec.rules ?? [],\n ...(sha256 ? { integrity: { sha256 } } : {}),\n ...(spec.extract ? { extract: spec.extract } : {}),\n ...(spec.discovery ? { discovery: spec.discovery } : {}),\n ...(spec.metadata !== undefined ? { metadata: spec.metadata } : {}),\n };\n}\n","import type { Launch, Val, Valset } from '@opys/core';\nimport type { LaunchGroups } from './plugin';\n\n/** Shared shape of the vanilla / forge-family loader templates. */\nexport interface LoaderTemplate {\n launch: Launch;\n jvmArgs: Valset;\n mainClass: Val;\n gameArgs: Valset;\n}\n\n/** Project a loader template's launch surface into named groups. */\nexport function launchGroups(t: LoaderTemplate): LaunchGroups {\n return {\n command: t.launch.command,\n jvmArgs: t.jvmArgs,\n mainClass: t.mainClass,\n gameArgs: t.gameArgs,\n };\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@opys/dev",
3
+ "version": "0.1.2",
4
+ "main": "./dist/index.js",
5
+ "module": "./dist/index.js",
6
+ "exports": {
7
+ ".": {
8
+ "import": "./dist/index.mjs",
9
+ "require": "./dist/index.cjs"
10
+ }
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsdown lib/index.ts --format esm,cjs --dts --clean",
17
+ "typecheck": "tsc --noEmit -p tsconfig.json",
18
+ "test": "vitest run tests/unit --passWithNoTests"
19
+ },
20
+ "type": "module",
21
+ "dependencies": {
22
+ "@opys/core": "^0.1.2"
23
+ },
24
+ "peerDependencies": {
25
+ "zod": "^4.0.0"
26
+ },
27
+ "devDependencies": {
28
+ "vitest": "*"
29
+ },
30
+ "engines": {
31
+ "node": ">=20"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/harmoniya-net/opys.git",
36
+ "directory": "packages/dev"
37
+ }
38
+ }