diff --git a/.github/scripts/docs-site.mjs b/.github/scripts/docs-site.mjs index cc9a3f4..9965456 100644 --- a/.github/scripts/docs-site.mjs +++ b/.github/scripts/docs-site.mjs @@ -5,20 +5,21 @@ import { spawnSync } from "node:child_process"; import { dirname, isAbsolute, relative, resolve, sep } from "node:path"; import { fileURLToPath } from "node:url"; -export const REQUIRED_DOC_FILES = [ +export const COMMON_DOC_FILES = [ "projects/typed-peripheral-digitalitems/build.gradle.kts", "projects/typed-peripheral-digitalitems/package.json", "projects/typed-peripheral-digitalitems/package-lock.json", - "projects/typed-peripheral-digitalitems/typedoc.json", - "projects/typed-peripheral-digitalitems/tsconfig.docs.json", "projects/typed-peripheral-digitalitems/shared.ts", "projects/typed-peripheral-digitalitems/digitizer.ts", "projects/typed-peripheral-digitalitems/advanced_digitizer.ts", "projects/typed-peripheral-digitalitems/documentation/index.md", - "projects/typed-peripheral-digitalitems/documentation/theme/plugin.mjs", "projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css", "projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.js", ]; +const DOC_GENERATORS = [ + ["projects/typed-peripheral-digitalitems/typedoc.json", "projects/typed-peripheral-digitalitems/documentation/theme/plugin.mjs"], + ["projects/typed-peripheral-digitalitems/mkdocs.yml", "projects/typed-peripheral-digitalitems/requirements-docs.txt", "projects/typed-peripheral-digitalitems/documentation/generate-docs.mjs"], +]; const BRANCHES = ["1.20", "1.21"]; const TAG_RE = /^v\d+(?:\.\d+)+(?:-[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?$/; @@ -119,9 +120,10 @@ function show(repository, ref, path) { } function refHasDocs(repository, ref) { - return REQUIRED_DOC_FILES.every((path) => - git(repository, ["cat-file", "-e", `${ref}:${path}`], true).status === 0 - ) && /\bgenerateDocs\b/.test(show(repository, ref, REQUIRED_DOC_FILES[0])); + const has = (path) => git(repository, ["cat-file", "-e", `${ref}:${path}`], true).status === 0; + return COMMON_DOC_FILES.every(has) + && DOC_GENERATORS.some((files) => files.every(has)) + && /\bgenerateDocs\b/.test(show(repository, ref, COMMON_DOC_FILES[0])); } function metadata(repository, ref) { @@ -265,7 +267,10 @@ function localTarget(root, html, rawLink) { fail(`Malformed local link in ${html}: ${rawLink}`); } if (pathname.includes("\\") || pathname.includes("\0")) fail(`Unsafe local link in ${html}: ${rawLink}`); - const target = pathname.startsWith("/") ? resolve(root, `.${pathname}`) : resolve(dirname(html), pathname); + const versionedPath = pathname.match(/^\/.*?\/((?:branch|tag)\/.*)$/)?.[1]; + const target = pathname.startsWith("/") + ? resolve(root, versionedPath ?? `.${pathname}`) + : resolve(dirname(html), pathname); if (!within(root, target)) fail(`Local link escapes site in ${html}: ${rawLink}`); return target; } @@ -290,9 +295,6 @@ export async function validateSite(site) { for (const entry of plan) { const versionRoot = resolve(root, entry.path); await regularFile(resolve(versionRoot, "index.html"), `${entry.path} index.html`); - for (const asset of ["search.js", "navigation.js", "custom.css", "custom.js"]) { - await regularFile(resolve(versionRoot, "assets", asset), `${entry.path} ${asset}`); - } } const canonicalRoot = await realpath(root); for (const html of await htmlFiles(root)) { diff --git a/.github/scripts/docs-site.test.mjs b/.github/scripts/docs-site.test.mjs index 33f96cf..5e4a8f8 100644 --- a/.github/scripts/docs-site.test.mjs +++ b/.github/scripts/docs-site.test.mjs @@ -23,11 +23,11 @@ function entry(kind, name, sha = SHA_A) { }; } -async function fragment(root, path, html = 'asset') { +async function fragment(root, path, html = '') { const directory = join(root, path); await mkdir(join(directory, "assets"), { recursive: true }); await writeFile(join(directory, "index.html"), html); - for (const asset of ["main.js", "search.js", "navigation.js", "custom.css", "custom.js"]) { + for (const asset of ["main.js", "main.css"]) { await writeFile(join(directory, "assets", asset), "// docs\n"); } } @@ -107,14 +107,23 @@ test("validate rejects links escaping the site", async () => { await assert.rejects(validateSite(output), /escapes site/); }); -test("validate rejects missing TypeDoc assets", async () => { +test("validate resolves hosted version paths below the Pages project prefix", async () => { + const root = await mkdtemp(join(tmpdir(), "docs-site-prefix-")); + const input = join(root, "input"); + const output = join(root, "output"); + await fragment(input, "branch/1.20", ''); + await assembleSite({ input, output, plan: [entry("branch", "1.20")] }); + await validateSite(output); +}); + +test("validate rejects a missing linked asset", async () => { const root = await mkdtemp(join(tmpdir(), "docs-site-assets-")); const input = join(root, "input"); const output = join(root, "output"); await fragment(input, "branch/1.20"); await assembleSite({ input, output, plan: [entry("branch", "1.20")] }); - await unlink(join(output, "branch", "1.20", "assets", "search.js")); - await assert.rejects(validateSite(output), /Missing branch\/1.20 search.js/); + await unlink(join(output, "branch", "1.20", "assets", "main.css")); + await assert.rejects(validateSite(output), /Broken local link.*assets\/main.css/); }); test("assemble rejects a symlinked output ancestor", async () => { diff --git a/.github/workflows/docs-check.yml b/.github/workflows/docs-check.yml index 1260f1f..2e13d94 100644 --- a/.github/workflows/docs-check.yml +++ b/.github/workflows/docs-check.yml @@ -42,6 +42,16 @@ jobs: - name: Set up Gradle uses: gradle/actions/setup-gradle@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + cache-dependency-path: projects/typed-peripheral-digitalitems/requirements-docs.txt + + - name: Install documentation dependencies + run: timeout --foreground 120s python -m pip install -r projects/typed-peripheral-digitalitems/requirements-docs.txt + - name: Build package and documentation run: timeout --foreground 600s ./gradlew :typed-peripheral-digitalitems:compileTypeScript :typed-peripheral-digitalitems:generateDocs --no-daemon @@ -55,7 +65,7 @@ jobs: run: | set -euo pipefail NODE="$PWD/.gradle/nodejs/node-v22.14.0-linux-x64/bin/node" - timeout --foreground 60s "$NODE" --test .github/scripts/docs-site.test.mjs + timeout --foreground 60s "$NODE" --test .github/scripts/docs-site.test.mjs projects/typed-peripheral-digitalitems/documentation/generate-docs.test.mjs mkdir -p build/docs-fragments/branch/1.21 cp -a projects/typed-peripheral-digitalitems/docs/. build/docs-fragments/branch/1.21/ timeout --foreground 60s "$NODE" .github/scripts/docs-site.mjs assemble --input build/docs-fragments --output build/docs-site --plan-json "$DOCS_PLAN" diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml index ffc47e6..67cdb2c 100644 --- a/.github/workflows/docs-pages.yml +++ b/.github/workflows/docs-pages.yml @@ -67,6 +67,17 @@ jobs: - name: Set up Gradle uses: gradle/actions/setup-gradle@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install MkDocs for migrated refs + run: | + if [[ -f projects/typed-peripheral-digitalitems/requirements-docs.txt ]]; then + timeout --foreground 120s python -m pip install -r projects/typed-peripheral-digitalitems/requirements-docs.txt + fi + - name: Generate documentation with ref-owned task env: DOCS_PATH: ${{ matrix.path }} diff --git a/.gitignore b/.gitignore index 906e4a0..e1bc5f2 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,5 @@ run/ .env UnlimitedPeripheralWorks-* .kotlin + +.venv \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index f1b39dd..92a0eb7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,17 +1,73 @@ -# Repository Guidelines +# Digital Items + +## Overview + +Digital Items is a Minecraft 1.20.1 mod that adds digital item storage peripherals for CC:Tweaked. It supports both Fabric and Forge from a shared core module. + +## Tech Stack + +- Runtime: Minecraft 1.20.1 / Java 17 +- Languages: Kotlin 2.0, Java, TypeScript, and Lua +- Loaders: Fabric Loader 0.15 and Forge 47 +- Computer mod: CC:Tweaked 1.113 +- Testing: Testiarium GameTests and TypeScript-to-Lua fixtures +- Build system: Gradle Wrapper + +## Key Commands + +Log handling is important. Always use an explicit timeout and silently save complete output. + +- Build: `mkdir -p build; LOG="build/gradle-$(date +%Y%m%d-%H%M%S).log"; timeout --foreground 10m ./gradlew build --no-daemon >"$LOG" 2>&1` +- GameTests: `mkdir -p build; LOG="build/gametests-$(date +%Y%m%d-%H%M%S).log"; timeout --foreground 20m xvfb-run -a ./gradlew gameTest --no-daemon -PminimalTestEnvironment >"$LOG" 2>&1` +- TypeScript fixtures: `mkdir -p build; LOG="build/typescript-tests-$(date +%Y%m%d-%H%M%S).log"; timeout --foreground 5m ./gradlew :typescript-tests:compileTestLua --no-daemon >"$LOG" 2>&1` + +Increase timeouts only when required. Stop development clients and servers after collecting results. Report the command, exit code, duration, log path, and relevant errors; inspect only the relevant failure window. ## Project Structure -DigitalItems is a Gradle multi-project repository. Subprojects live under `projects/`: +```text +projects/ + core/ # Shared implementation, resources, and GameTests + fabric/ # Fabric integration and test mod + forge/ # Forge integration and test mod + typed-peripheral-digitalitems/ # Publishable TypeScriptToLua peripheral API + typescript-tests/ # TypeScript sources compiled to ComputerCraft Lua fixtures +gradle/ # Version catalog and Gradle configuration +``` -- `core`: loader-independent Minecraft mod code and shared test-mod sources. -- `forge`: Forge implementation, generated resources, and Forge GameTests. -- `fabric`: Fabric implementation, generated resources, and Fabric GameTests. -- `typed-peripheral-digitalitems`: publishable TypeScriptToLua peripheral API package. -- `typescript-tests`: TypeScript GameTest programs compiled to Lua and included in the shared test mod. +Production code lives in `src/main/`. Shared GameTests and fixtures live in `projects/core/src/testMod/`; loader test-mod entry points and metadata live in each loader's `src/testMod/`. The TypeScript tests consume `typed-peripheral-digitalitems` through a local npm file dependency. Gradle compiles the package before installing the test project's npm dependencies. ## Contribution Workflow Every new task must be implemented on a dedicated branch and submitted as a GitHub pull request. Do not commit task changes directly to the base branch. + +## Conventions + +- Keep loader-independent behavior in `projects/core/`. +- Keep loader API usage in the corresponding `fabric` or `forge` module. +- Follow the official Kotlin code style configured in `gradle.properties`. +- Reuse existing project patterns before adding helpers, abstractions, or dependencies. +- Comments explain why, not what. + +## DO NOT MODIFY + +- Never edit generated build output; change its source and rerun the relevant Gradle task. +- Never commit `build/`, development run directories, logs, EULA files, or `node_modules/`. +- Do not modify unrelated user changes in a dirty worktree. + +## Testing Approach + +- Run `:typescript-tests:compileTestLua` after changing TypeScript fixtures or the typed peripheral package. +- Run the root `gameTest` task for both Fabric and Forge GameTests, using `xvfb-run` for graphics-dependent Minecraft startup. +- Run the timed multi-loader build before marking code changes complete. +- Fix failing tests rather than skipping them. + +## Code Style + +- Prefer the smallest correct change. +- Do not add speculative abstractions or dependencies. +- Keep shared and loader-specific responsibilities separated. +- Preserve validation, error handling, and dedicated-server safety. +- If requirements are unclear, ask instead of assuming. diff --git a/projects/typed-peripheral-digitalitems/.gitignore b/projects/typed-peripheral-digitalitems/.gitignore index b4504e2..dff745a 100644 --- a/projects/typed-peripheral-digitalitems/.gitignore +++ b/projects/typed-peripheral-digitalitems/.gitignore @@ -2,3 +2,4 @@ *.d.ts node_modules/ docs/ +.mkdocs-build/ diff --git a/projects/typed-peripheral-digitalitems/build.gradle.kts b/projects/typed-peripheral-digitalitems/build.gradle.kts index 0eaa4b2..ba09555 100644 --- a/projects/typed-peripheral-digitalitems/build.gradle.kts +++ b/projects/typed-peripheral-digitalitems/build.gradle.kts @@ -54,10 +54,11 @@ val generateDocs by tasks.registering(NpmTask::class) { "package.json", "package-lock.json", "tsconfig.json", - "tsconfig.docs.json", - "typedoc.json", + "mkdocs.yml", + "requirements-docs.txt", "*.ts", "documentation/**/*.md", + "documentation/**/*.mjs", "documentation/theme/**", ) exclude("node_modules/**", "*.d.ts") @@ -71,6 +72,7 @@ tasks.assemble { tasks.clean { delete(file("docs")) + delete(file(".mkdocs-build")) delete(fileTree(projectDir) { include("*.d.ts", "*.lua") }) diff --git a/projects/typed-peripheral-digitalitems/documentation/generate-docs.mjs b/projects/typed-peripheral-digitalitems/documentation/generate-docs.mjs new file mode 100644 index 0000000..b2b60ad --- /dev/null +++ b/projects/typed-peripheral-digitalitems/documentation/generate-docs.mjs @@ -0,0 +1,292 @@ +import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; +import { spawnSync } from "node:child_process"; +import { dirname, relative, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { inflateSync } from "node:zlib"; +import ts from "typescript"; + +const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const peripherals = [ + { interfaceName: "Digitizer", page: "guides/basic-digitizer.md" }, + { interfaceName: "AdvancedDigitizer", page: "guides/advanced-items.md" }, +]; + +function text(parts) { + return ts.displayPartsToString(parts).trim(); +} + +function tagsFor(signature, checker) { + const result = { params: new Map(), returns: "", throws: [] }; + for (const tag of signature.getJsDocTags(checker)) { + const value = text(tag.text); + if (tag.name === "param") { + const match = /^(\S+)\s*-?\s*(.*)$/s.exec(value); + if (match) result.params.set(match[1], match[2]); + } else if (tag.name === "returns" || tag.name === "return") result.returns = value; + else if (tag.name === "throws") result.throws.push(value); + } + return result; +} + +function sourceInterface(symbol) { + const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0]; + let current = declaration; + while (current && !ts.isInterfaceDeclaration(current)) current = current.parent; + return current?.name.text; +} + +export function extractPeripheral(program, interfaceName) { + const checker = program.getTypeChecker(); + const matches = []; + for (const sourceFile of program.getSourceFiles()) { + if (sourceFile.isDeclarationFile && sourceFile.fileName.includes("typescript/lib/")) continue; + ts.forEachChild(sourceFile, (node) => { + if (ts.isInterfaceDeclaration(node) && node.name.text === interfaceName) matches.push(node); + }); + } + if (matches.length !== 1) throw new Error(`Expected one ${interfaceName} interface, found ${matches.length}`); + const declaration = matches[0]; + let peripheralType = null; + for (const sourceFile of program.getSourceFiles()) { + const visit = (node) => { + if (ts.isNewExpression(node) + && node.typeArguments?.[0]?.getText() === interfaceName + && node.arguments?.[0] + && ts.isStringLiteral(node.arguments[0])) { + peripheralType = node.arguments[0].text; + } + ts.forEachChild(node, visit); + }; + ts.forEachChild(sourceFile, visit); + } + if (!peripheralType) throw new Error(`No peripheral provider found for ${interfaceName}`); + const type = checker.getTypeAtLocation(declaration); + const methods = checker.getPropertiesOfType(type).flatMap((symbol) => { + const methodType = checker.getTypeOfSymbolAtLocation(symbol, declaration); + const signatures = checker.getSignaturesOfType(methodType, ts.SignatureKind.Call); + if (!signatures.length) return []; + return [{ + name: symbol.name, + inheritedFrom: sourceInterface(symbol) === interfaceName ? null : sourceInterface(symbol), + signatures: signatures.map((signature) => { + const tags = tagsFor(signature, checker); + const parameters = signature.getParameters().map((parameter) => { + const parameterDeclaration = parameter.valueDeclaration ?? parameter.declarations?.[0] ?? declaration; + return { + name: parameter.name, + type: checker.typeToString(checker.getTypeOfSymbolAtLocation(parameter, parameterDeclaration), declaration, ts.TypeFormatFlags.NoTruncation), + optional: Boolean(parameter.flags & ts.SymbolFlags.Optional) || Boolean(parameterDeclaration.questionToken), + description: tags.params.get(parameter.name) ?? "", + }; + }); + return { + signature: `${symbol.name}${checker.signatureToString(signature, declaration, ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope)}`, + summary: text(signature.getDocumentationComment(checker)), + parameters, + returns: tags.returns, + throws: tags.throws, + }; + }), + }]; + }); + if (!methods.length) throw new Error(`${interfaceName} has no methods`); + return { interfaceName, peripheralType, methods }; +} + +function escapeCell(value) { + return value.replaceAll("|", "\\|").replaceAll("\n", " "); +} + +function paeth(left, above, upperLeft) { + const estimate = left + above - upperLeft; + const leftDistance = Math.abs(estimate - left); + const aboveDistance = Math.abs(estimate - above); + const upperLeftDistance = Math.abs(estimate - upperLeft); + return leftDistance <= aboveDistance && leftDistance <= upperLeftDistance ? left : aboveDistance <= upperLeftDistance ? above : upperLeft; +} + +function decodePng(data) { + let width; + let height; + let channels; + const compressed = []; + for (let offset = 8; offset < data.length;) { + const length = data.readUInt32BE(offset); + const type = data.toString("ascii", offset + 4, offset + 8); + const chunk = data.subarray(offset + 8, offset + 8 + length); + if (type === "IHDR") { + width = chunk.readUInt32BE(0); + height = chunk.readUInt32BE(4); + // ponytail: decode only the PNG formats used by Minecraft textures; expand if an asset requires it. + if (chunk[8] !== 8 || chunk[12] !== 0 || ![2, 6].includes(chunk[9])) throw new Error("Unsupported block texture PNG"); + channels = chunk[9] === 6 ? 4 : 3; + } else if (type === "IDAT") compressed.push(chunk); + offset += length + 12; + } + if (!width || !height || !channels) throw new Error("Invalid block texture PNG"); + + const raw = inflateSync(Buffer.concat(compressed)); + const stride = width * channels; + const pixels = []; + let previous = Buffer.alloc(stride); + let offset = 0; + for (let y = 0; y < height; y++) { + const filter = raw[offset++]; + const row = Buffer.from(raw.subarray(offset, offset + stride)); + offset += stride; + for (let index = 0; index < stride; index++) { + const left = index >= channels ? row[index - channels] : 0; + const above = previous[index]; + const upperLeft = index >= channels ? previous[index - channels] : 0; + const predictor = filter === 0 ? 0 + : filter === 1 ? left + : filter === 2 ? above + : filter === 3 ? Math.floor((left + above) / 2) + : filter === 4 ? paeth(left, above, upperLeft) + : NaN; + if (Number.isNaN(predictor)) throw new Error(`Unsupported PNG filter ${filter}`); + row[index] = (row[index] + predictor) & 0xff; + } + for (let x = 0; x < width; x++) { + const index = x * channels; + pixels.push([row[index], row[index + 1], row[index + 2], channels === 4 ? row[index + 3] : 255]); + } + previous = row; + } + return { width, height, pixels }; +} + +function renderFace(texture, shade, project) { + if (texture.width !== 16 || texture.height !== 16) throw new Error("Block textures must be 16x16"); + const paths = []; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const [red, green, blue, alpha] = texture.pixels[y * 16 + x]; + if (alpha === 0) continue; + const color = [red, green, blue].map((channel) => Math.round(channel * shade).toString(16).padStart(2, "0")).join(""); + const points = [project(x, y), project(x + 1, y), project(x + 1, y + 1), project(x, y + 1)]; + paths.push(` `); + } + } + return paths.join("\n"); +} + +export async function renderBlock(textureRoot, block) { + const texture = async (face) => decodePng(await readFile(resolve(textureRoot, `${block}_${face}.png`))); + const [side, front, top] = await Promise.all([texture("side"), texture("front_on"), texture("top")]); + const sideFace = renderFace(side, 0.72, (x, y) => [16 + x * 3, 32 + x * 1.5 + y * 3]); + const frontFace = renderFace(front, 0.9, (x, y) => [64 + x * 3, 56 - x * 1.5 + y * 3]); + const topFace = renderFace(top, 1, (x, y) => [64 + (x - y) * 3, 8 + (x + y) * 1.5]); + return ` + +${sideFace} +${frontFace} +${topFace} +\n`; +} + +function methodHeading(method, signature) { + const literalType = /^"[^"]+"(?: \| "[^"]+")*$/; + const parameters = signature.parameters.map((parameter, index) => ( + method.signatures.length > 1 && index === 0 && literalType.test(parameter.type) ? parameter.type : parameter.name + )); + return `${method.name}(${parameters.join(", ")})`; +} + +export function renderPeripheral(peripheral, revision = "") { + const lines = [ + "## Peripheral methods", + "", + "The reference below is generated from the published TypeScript interface.", + "", + ]; + for (const method of peripheral.methods) { + for (const signature of method.signatures) { + lines.push(`### \`${methodHeading(method, signature)}\``, ""); + if (method.inheritedFrom) lines.push(`*Inherited from \`${method.inheritedFrom}\`.*`, ""); + if (signature.summary) lines.push(signature.summary, ""); + if (signature.parameters.length) { + lines.push("| Parameter | Type | Description |", "| --- | --- | --- |"); + for (const parameter of signature.parameters) { + const name = `\`${parameter.name}${parameter.optional ? "?" : ""}\``; + lines.push(`| ${name} | \`${escapeCell(parameter.type)}\` | ${escapeCell(parameter.description)} |`); + } + lines.push(""); + } + if (signature.returns) lines.push(`**Returns:** ${signature.returns}`, ""); + for (const error of signature.throws) lines.push(`**Throws:** ${error}`, ""); + } + } + if (revision) lines.push(`[View TypeScript source](https://github.com/SirEdvin/DigitalItems/tree/${revision}/projects/typed-peripheral-digitalitems)`, ""); + return lines.join("\n"); +} + +function options(args) { + const result = { out: resolve(projectRoot, "docs"), revision: "", siteUrl: "" }; + for (let index = 0; index < args.length; index += 2) { + const value = args[index + 1]; + if (!value) throw new Error(`Missing value for ${args[index]}`); + if (args[index] === "--out") result.out = resolve(value); + else if (args[index] === "--gitRevision") result.revision = value; + else if (args[index] === "--hostedBaseUrl") result.siteUrl = value; + else throw new Error(`Unknown option: ${args[index]}`); + } + return result; +} + +export async function buildDocs(args = []) { + const config = options(args); + const buildRoot = resolve(projectRoot, ".mkdocs-build"); + await rm(buildRoot, { recursive: true, force: true }); + await cp(resolve(projectRoot, "documentation"), buildRoot, { + recursive: true, + filter: (path) => !path.endsWith("generate-docs.mjs") && !path.endsWith("generate-docs.test.mjs") && !path.includes("/theme/"), + }); + await mkdir(resolve(buildRoot, "assets/peripherals"), { recursive: true }); + const textureRoot = resolve(projectRoot, "../core/src/main/resources/assets/digitalitems/textures/block"); + for (const block of ["digitizer", "advanced_digitizer"]) { + await writeFile(resolve(buildRoot, "assets/peripherals", `${block}.svg`), await renderBlock(textureRoot, block)); + } + await mkdir(resolve(buildRoot, "assets/stylesheets"), { recursive: true }); + await mkdir(resolve(buildRoot, "assets/javascripts"), { recursive: true }); + await cp(resolve(projectRoot, "documentation/theme/digitalitems.css"), resolve(buildRoot, "assets/stylesheets/digitalitems.css")); + await cp(resolve(projectRoot, "documentation/theme/digitalitems.js"), resolve(buildRoot, "assets/javascripts/digitalitems.js")); + await cp(resolve(projectRoot, "documentation/theme/favicon.svg"), resolve(buildRoot, "assets/favicon.svg")); + + const parsed = ts.parseJsonConfigFileContent( + ts.readConfigFile(resolve(projectRoot, "tsconfig.json"), ts.sys.readFile).config, + ts.sys, + projectRoot, + { noEmit: true, strictNullChecks: true }, + ); + const program = ts.createProgram(parsed.fileNames, parsed.options); + const errors = ts.getPreEmitDiagnostics(program); + if (errors.length) throw new Error(ts.formatDiagnosticsWithColorAndContext(errors, { + getCanonicalFileName: (name) => name, + getCurrentDirectory: () => projectRoot, + getNewLine: () => "\n", + })); + for (const peripheral of peripherals) { + const path = resolve(buildRoot, peripheral.page); + const markdown = await readFile(path, "utf8"); + const extracted = extractPeripheral(program, peripheral.interfaceName); + await writeFile(path, `${markdown.replaceAll("{{ peripheralType }}", extracted.peripheralType).trim()}\n\n${renderPeripheral(extracted, config.revision)}\n`); + } + + await rm(config.out, { recursive: true, force: true }); + const result = spawnSync("python3", ["-m", "mkdocs", "build", "--strict", "--clean", "--site-dir", config.out], { + cwd: projectRoot, + env: config.siteUrl ? { ...process.env, DOCS_SITE_URL: config.siteUrl } : process.env, + encoding: "utf8", + }); + if (result.status !== 0) throw new Error((result.stderr || result.stdout).trim()); + await rm(buildRoot, { recursive: true, force: true }); + return relative(projectRoot, config.out); +} + +if (resolve(process.argv[1] ?? "") === fileURLToPath(import.meta.url)) { + buildDocs(process.argv.slice(2)).catch((error) => { + process.stderr.write(`generate-docs: ${error.message}\n`); + process.exitCode = 1; + }); +} diff --git a/projects/typed-peripheral-digitalitems/documentation/generate-docs.test.mjs b/projects/typed-peripheral-digitalitems/documentation/generate-docs.test.mjs new file mode 100644 index 0000000..9dfdbf9 --- /dev/null +++ b/projects/typed-peripheral-digitalitems/documentation/generate-docs.test.mjs @@ -0,0 +1,51 @@ +import assert from "node:assert/strict"; +import { resolve } from "node:path"; +import test from "node:test"; +import ts from "typescript"; +import { extractPeripheral, renderBlock, renderPeripheral } from "./generate-docs.mjs"; + +const root = resolve(import.meta.dirname, ".."); +const parsed = ts.parseJsonConfigFileContent( + ts.readConfigFile(resolve(root, "tsconfig.json"), ts.sys.readFile).config, + ts.sys, + root, + { noEmit: true, strictNullChecks: true }, +); +const program = ts.createProgram(parsed.fileNames, parsed.options); + +test("extracts direct, inherited, and generic digitizer methods", () => { + const peripheral = extractPeripheral(program, "Digitizer"); + assert.equal(peripheral.peripheralType, "digitizer"); + assert.equal(peripheral.methods.length, 15); + assert.match(peripheral.methods.find(({ name }) => name === "getConfiguration").signatures[0].signature, /DigitizerConfiguration/); + assert.equal(peripheral.methods.find(({ name }) => name === "size").inheritedFrom, "InventoryViewAPI"); + const markdown = renderPeripheral(peripheral); + assert.match(markdown, /\*\*Throws:\*\* A Lua error if the slot is empty/); + assert.match(markdown, /### `getConfiguration\(\)`/); + assert.match(markdown, /### `digitize\(slot\)`/); + assert.doesNotMatch(markdown, /### `[^`]*: [^`]*`/); + assert.doesNotMatch(markdown, /```typescript/); +}); + +test("preserves advanced digitizer overloads and nullable tuple unions", () => { + const peripheral = extractPeripheral(program, "AdvancedDigitizer"); + assert.equal(peripheral.peripheralType, "advanced_digitizer"); + assert.equal(peripheral.methods.length, 11); + const digitize = peripheral.methods.find(({ name }) => name === "digitize"); + assert.equal(digitize.signatures.length, 2); + assert.equal(peripheral.methods.find(({ name }) => name === "get").signatures.length, 3); + assert.match(digitize.signatures[0].signature, /filter\?: number \| string \| LuaTable \| null/); + assert.match(digitize.signatures[0].signature, /LuaMultiReturn<\[null, string\] \| \[string, null\]>/); + assert.match(peripheral.methods.find(({ name }) => name === "rematerialize").signatures[0].signature, /LuaMultiReturn<\[null, string\] \| \[number, null\]>/); + const markdown = renderPeripheral(peripheral); + assert.match(markdown, /### `digitize\("item", source, filter, limit, destination\)`/); + assert.match(markdown, /### `digitize\("fluid" \| "energy", source, filter, limit, destination\)`/); +}); + +test("renders all 16x16 block texture pixels as crisp SVG faces", async () => { + const textureRoot = resolve(root, "../core/src/main/resources/assets/digitalitems/textures/block"); + const svg = await renderBlock(textureRoot, "digitizer"); + assert.match(svg, /shape-rendering="crispEdges"/); + assert.equal(svg.match(/ +
Advanced digitizer block
+
MULTI-STORAGE PERIPHERAL

The advanced digitizer moves items, fluids, and energy between attached storage and durable digital identifiers.

Peripheral type
{{ peripheralType }}
Storage
Items, fluids, energy
Failure style
Recoverable result pairs
+ The advanced digitizer can extract from its internal inventory with source `self`, or from an attached inventory/item storage by peripheral name. The source defaults to `self`. +> **Mode matters:** an identifier belongs to exactly one of `item`, `fluid`, or +> `energy`. Always use the same mode when inspecting, refreshing, or restoring it. + Item filters may be: - A one-based slot number. This requires slotted storage. diff --git a/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md b/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md index 8ce2709..4aa1636 100644 --- a/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md +++ b/projects/typed-peripheral-digitalitems/documentation/guides/basic-digitizer.md @@ -1,8 +1,20 @@ +--- +peripheralInterface: Digitizer +--- + # Basic Digitizer -The basic digitizer handles items in its own inventory. Slots are one-based and -default to slot 1. `digitize()` removes the complete stack; `digitizeAmount()` -requires an exact positive amount no greater than the physical stack. +
+
Digitizer block
+
ITEM PERIPHERAL

The basic digitizer turns physical item stacks into portable digital identifiers and restores them through its internal inventory.

Peripheral type
{{ peripheralType }}
Storage
Items
Failure style
Lua errors
+
+ +Slots are one-based and default to slot 1. `digitize()` removes the complete +stack; `digitizeAmount()` requires an exact positive amount no greater than the +physical stack. + +> **Inherited inventory API:** `size`, `list`, `getItemDetail`, `getItemLimit`, +> `pushItems`, and `pullItems` are also available on this peripheral. ## Round trip a partial stack @@ -29,6 +41,8 @@ local inserted = digitizer.rematerializeAmount(id, 8) print("Inserted " .. inserted) ``` +## Errors and remaining items + All failures throw. Empty slots, non-positive amounts, requesting more than the physical or digital count, and unknown or decayed identifiers are errors. diff --git a/projects/typed-peripheral-digitalitems/documentation/index.md b/projects/typed-peripheral-digitalitems/documentation/index.md index e992658..13ce6da 100644 --- a/projects/typed-peripheral-digitalitems/documentation/index.md +++ b/projects/typed-peripheral-digitalitems/documentation/index.md @@ -1,16 +1,27 @@ -# Digital Items Typed Peripheral API +# Digital Items Peripheral Manual -This package supplies TypeScriptToLua declarations and peripheral providers for -Digital Items 3. It covers the item-only `digitizer` and the multi-storage -`advanced_digitizer` peripherals. +Player-focused documentation and typed APIs for the Digital Items 3 peripherals. +Use these pages whether you write programs directly in CraftOS Lua or compile +TypeScript with TypeScriptToLua. -## Choose a peripheral +## Choose your digitizer -- Use the basic digitizer for simple item workflows through its internal inventory. -- Use the advanced digitizer to address remote item, fluid, or energy storage, merge content into existing identifiers, and handle recoverable failures. -- Both peripherals inherit inventory methods. The advanced digitizer additionally reports decay and stack limits through `getConfiguration()`. + -## Documentation +Both peripherals include the standard CC:Tweaked inventory methods. The advanced +digitizer also exposes configured decay and storage limits through +`getConfiguration()`. + +## Start here - [Getting started](getting-started.md) - [Digital identifiers](digital-identifiers.md) @@ -18,6 +29,6 @@ Digital Items 3. It covers the item-only `digitizer` and the multi-storage - [Advanced item guide](guides/advanced-items.md) - [Fluid and energy guide](guides/fluids-and-energy.md) -The generated API reference documents every exported type, method overload, and -provider. Guide examples are shown first in TypeScriptToLua and then in direct -CraftOS Lua. +The **Guides** explain behavior in player terms and include both Lua and +TypeScript examples. The **API reference** is the precise source for signatures, +overloads, return types, and provider declarations. diff --git a/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css b/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css index 604ba7f..729a032 100644 --- a/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css +++ b/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.css @@ -1,168 +1,84 @@ -:root { - --di-ink: #071116; - --di-panel: #0c1c23; - --di-panel-raised: #102832; - --di-grid: rgba(75, 225, 210, 0.08); - --di-cyan: #4be1d2; - --di-cyan-soft: #a0fff4; - --di-amber: #ffbd59; - --di-muted: #9fb4ba; - --color-background: var(--di-ink); - --color-background-secondary: var(--di-panel); - --color-background-active: var(--di-panel-raised); - --color-accent: var(--di-cyan); - --color-text: #e4f2f2; - --color-text-aside: var(--di-muted); - --color-link: var(--di-cyan-soft); - --color-ts-keyword: #ff8d72; - --color-ts-string: #b7e67b; -} - -html[data-theme="light"] { - --di-ink: #eef6f4; - --di-panel: #deebe8; - --di-panel-raised: #cce1dc; - --di-grid: rgba(0, 104, 104, 0.09); - --di-cyan: #007f7c; - --di-cyan-soft: #006a69; - --di-amber: #a55a00; - --di-muted: #496669; - --color-text: #102b2e; -} - -body { - background-color: var(--di-ink); - background-image: - linear-gradient(var(--di-grid) 1px, transparent 1px), - linear-gradient(90deg, var(--di-grid) 1px, transparent 1px), - radial-gradient(circle at 83% 9%, rgba(75, 225, 210, 0.11), transparent 28rem); - background-size: 48px 48px, 48px 48px, auto; - font-family: "Trebuchet MS", "Gill Sans", sans-serif; -} - -.di-signal-line { - position: fixed; - z-index: 100; - top: 0; - right: 0; - left: 0; - height: 3px; - background: linear-gradient(90deg, var(--di-cyan) 0 12%, transparent 12% 13%, var(--di-amber) 13% 18%, transparent 18% 100%); - box-shadow: 0 0 18px color-mix(in srgb, var(--di-cyan) 65%, transparent); -} - -.container-main { - backdrop-filter: blur(2px); -} +@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&family=Manrope:wght@400;600;700;800&display=swap"); -.col-content { - position: relative; -} - -.col-content::before { - content: "DIGITAL ITEMS // PERIPHERAL ARCHIVE"; - display: block; +:root { + --md-text-font: "Manrope"; + --md-code-font: "IBM Plex Mono"; + --di-line: color-mix(in srgb, var(--md-primary-fg-color) 28%, transparent); + --di-panel: color-mix(in srgb, var(--md-primary-fg-color) 7%, var(--md-default-bg-color)); + --di-warm: #e29a32; +} + +.md-grid { max-width: 78rem; } +.md-typeset h1 { font-weight: 800; letter-spacing: -.04em; } +.md-typeset h2 { margin-top: 2.5em; padding-bottom: .35em; border-bottom: 1px solid var(--di-line); } +.md-typeset h3 code { font-size: .9em; color: var(--md-accent-fg-color); } + +.di-peripheral-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1rem; margin: 1.5rem 0 2rem; - color: var(--di-cyan); - font: 700 0.72rem/1.2 "Lucida Console", Monaco, monospace; - letter-spacing: 0.16em; -} - -h1, -h2, -h3, -h4, -.site-menu, -.tsd-page-title { - font-family: "Lucida Console", Monaco, monospace; - letter-spacing: -0.035em; } -h1 { - text-wrap: balance; -} - -h2 { - border-bottom: 1px solid color-mix(in srgb, var(--di-cyan) 28%, transparent); - padding-bottom: 0.55rem; -} - -.tsd-panel, -.tsd-member, -pre, -table { - border: 1px solid color-mix(in srgb, var(--di-cyan) 18%, transparent); - border-radius: 4px; - box-shadow: 6px 6px 0 rgba(0, 0, 0, 0.14); -} - -.tsd-signature, -code, -pre { - font-family: "Lucida Console", Monaco, monospace; -} - -a { - text-underline-offset: 0.2em; -} - -a:hover { - color: var(--di-amber); -} - -.di-version-panel { - margin: 0.75rem 0 1.35rem; - padding: 0.8rem; - border: 1px solid color-mix(in srgb, var(--di-cyan) 42%, transparent); - background: linear-gradient(135deg, var(--di-panel-raised), var(--di-panel)); - clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 0 100%); -} - -.di-version-label { - display: block; - margin-bottom: 0.45rem; - color: var(--di-amber); - font: 700 0.64rem/1 "Lucida Console", Monaco, monospace; - letter-spacing: 0.12em; -} - -#di-version-select { - width: 100%; - padding: 0.5rem 1.8rem 0.5rem 0.55rem; - border: 1px solid color-mix(in srgb, var(--di-cyan) 55%, transparent); - border-radius: 2px; - color: var(--color-text); - background: var(--di-ink); -} - -#di-version-select:focus-visible { - outline: 2px solid var(--di-amber); - outline-offset: 2px; -} - -@media (max-width: 769px) { - .col-content::before { - margin-top: 0.75rem; - font-size: 0.64rem; - } - - .tsd-signature, - pre, - table { - max-width: 100%; - overflow-x: auto; - } -} - -@media (prefers-reduced-motion: no-preference) { - .col-content > * { - animation: di-reveal 360ms ease-out both; - } - - @keyframes di-reveal { - from { - opacity: 0; - transform: translateY(8px); - } - } +.di-peripheral-card, +.di-peripheral-hero { + border: 1px solid var(--di-line); + background: linear-gradient(145deg, var(--di-panel), var(--md-default-bg-color)); + box-shadow: 0 14px 35px rgba(0, 0, 0, .08); +} + +.di-peripheral-card { + display: flex; + gap: 1.2rem; + min-height: 11rem; + padding: 1.2rem; + border-radius: .55rem; + color: var(--md-default-fg-color) !important; + transition: transform 160ms ease, border-color 160ms ease; +} + +.di-peripheral-card:hover { transform: translateY(-3px); border-color: var(--md-accent-fg-color); } +.di-peripheral-card--advanced, .di-peripheral-hero--advanced { border-color: color-mix(in srgb, var(--di-warm) 45%, transparent); } + +.di-peripheral-image { + display: grid; + flex: 0 0 7rem; + min-height: 7rem; + place-items: center; + border-radius: .35rem; + background: #091114 repeating-conic-gradient(rgba(255,255,255,.035) 0 25%, transparent 0 50%) 0 / 16px 16px; +} + +.di-peripheral-image img { width: 6rem; height: 6rem; image-rendering: pixelated; } +.di-peripheral-copy { display: flex; flex-direction: column; } +.di-peripheral-copy strong { font-size: 1.15rem; } +.di-peripheral-copy small { margin: .25rem 0 .65rem; opacity: .72; } +.di-peripheral-copy b { margin-top: auto; color: var(--md-accent-fg-color); font-size: .78rem; } + +.di-peripheral-hero { + display: grid; + grid-template-columns: 10rem minmax(0, 1fr); + gap: 1.5rem; + margin: 1rem 0 2rem; + padding: 1.4rem; + border-radius: .55rem; +} + +.di-peripheral-hero .di-peripheral-image { min-height: 10rem; } +.di-peripheral-hero .di-peripheral-image img { width: 8rem; height: 8rem; } +.di-eyebrow { color: var(--di-warm); font: 600 .66rem/1 "IBM Plex Mono"; letter-spacing: .12em; } +.di-peripheral-hero p { margin: .55rem 0 1rem; font-size: .95rem; } +.di-peripheral-hero dl { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .7rem; margin: 0; } +.di-peripheral-hero dt { opacity: .65; font-size: .62rem; text-transform: uppercase; letter-spacing: .06em; } +.di-peripheral-hero dd { margin: .15rem 0 0; overflow-wrap: anywhere; font-weight: 700; } + +.md-typeset table:not([class]) { display: table; width: 100%; } +.md-typeset table:not([class]) th { background: var(--di-panel); } +#di-version-select { max-width: 15rem; padding: .28rem .5rem; border: 1px solid var(--di-line); border-radius: .25rem; color: inherit; background: var(--md-default-bg-color); font: 600 .65rem "IBM Plex Mono"; } + +@media (max-width: 760px) { + .di-peripheral-grid { grid-template-columns: 1fr; } + .di-peripheral-hero { grid-template-columns: 1fr; } + .di-peripheral-hero dl { grid-template-columns: 1fr; } + .di-peripheral-hero .di-peripheral-image { min-height: 9rem; } } diff --git a/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.js b/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.js index 3f07312..9e1f407 100644 --- a/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.js +++ b/projects/typed-peripheral-digitalitems/documentation/theme/digitalitems.js @@ -1,22 +1,18 @@ -(() => { - const selector = document.querySelector("#di-version-select"); - if (!(selector instanceof HTMLSelectElement)) return; - - const script = document.currentScript ?? [...document.scripts].find((entry) => - entry.src.endsWith("/custom.js") - ); +document.addEventListener("DOMContentLoaded", () => { + const script = [...document.scripts].find((entry) => entry.src.endsWith("/assets/javascripts/digitalitems.js")); if (!script) return; - const scriptUrl = new URL(script.src); - const versionRoot = scriptUrl.pathname.match( - /^(.*\/)(?:branch|tag)\/[^/]+\/assets\/custom\.js$/ - ); - if (!versionRoot) return; - - const siteRoot = new URL(versionRoot[1], scriptUrl.origin); - const currentPath = decodeURIComponent( - scriptUrl.pathname.slice(versionRoot[1].length).replace(/assets\/custom\.js$/, "") - ); + const match = scriptUrl.pathname.match(/^(.*\/)((?:branch|tag)\/[^/]+)\/assets\/javascripts\/digitalitems\.js$/); + if (!match) return; + + const siteRoot = new URL(match[1], scriptUrl.origin); + const currentVersion = `${decodeURIComponent(match[2])}/`; + const select = document.createElement("select"); + select.id = "di-version-select"; + select.ariaLabel = "Documentation version"; + select.disabled = true; + select.append(new Option("Loading versions...")); + document.querySelector(".md-header__inner")?.append(select); fetch(new URL("versions.json", siteRoot)) .then((response) => { @@ -24,38 +20,15 @@ return response.json(); }) .then((manifest) => { - if (!manifest || !Array.isArray(manifest.versions)) { - throw new Error("Unsupported version manifest"); - } - - selector.replaceChildren(); - const groups = new Map([ - ["branch", document.createElement("optgroup")], - ["tag", document.createElement("optgroup")], - ]); - groups.get("branch").label = "Development branches"; - groups.get("tag").label = "Releases"; - - for (const entry of manifest.versions) { - if (!entry || !groups.has(entry.kind) || typeof entry.path !== "string") continue; - const option = document.createElement("option"); - option.value = entry.path; - option.textContent = entry.label || entry.name; - option.selected = entry.path === currentPath; - groups.get(entry.kind).append(option); - } - - for (const group of groups.values()) { - if (group.children.length > 0) selector.append(group); + select.replaceChildren(); + for (const entry of manifest.versions ?? []) { + const option = new Option(entry.label || entry.name, entry.path); + option.selected = entry.path === currentVersion; + select.append(option); } - selector.disabled = selector.options.length === 0; + select.disabled = select.options.length === 0; }) - .catch(() => { - selector.options[0].textContent = "Version list unavailable"; - }); + .catch(() => { select.options[0].textContent = "Versions unavailable"; }); - selector.addEventListener("change", () => { - if (!selector.value) return; - window.location.assign(new URL(selector.value, siteRoot)); - }); -})(); + select.addEventListener("change", () => window.location.assign(new URL(select.value, siteRoot))); +}); diff --git a/projects/typed-peripheral-digitalitems/documentation/theme/plugin.mjs b/projects/typed-peripheral-digitalitems/documentation/theme/plugin.mjs deleted file mode 100644 index 1c7e274..0000000 --- a/projects/typed-peripheral-digitalitems/documentation/theme/plugin.mjs +++ /dev/null @@ -1,23 +0,0 @@ -import { JSX } from "typedoc"; - -export function load(app) { - app.renderer.hooks.on("body.begin", () => - JSX.createElement("div", { - class: "di-signal-line", - "aria-hidden": "true", - }) - ); - - app.renderer.hooks.on("sidebar.begin", () => - JSX.createElement( - "section", - { class: "di-version-panel", "aria-label": "Documentation version" }, - JSX.createElement("span", { class: "di-version-label" }, "DOCUMENTATION VERSION"), - JSX.createElement( - "select", - { id: "di-version-select", disabled: true }, - JSX.createElement("option", null, "Local build") - ) - ) - ); -} diff --git a/projects/typed-peripheral-digitalitems/mkdocs.yml b/projects/typed-peripheral-digitalitems/mkdocs.yml new file mode 100644 index 0000000..6c1f4a6 --- /dev/null +++ b/projects/typed-peripheral-digitalitems/mkdocs.yml @@ -0,0 +1,65 @@ +site_name: Digital Items Peripheral API +site_description: TypeScript and Lua reference for Digital Items peripherals +site_url: !ENV [DOCS_SITE_URL, "https://siredvin.github.io/DigitalItems/branch/1.20/"] +repo_url: https://github.com/SirEdvin/DigitalItems +repo_name: SirEdvin/DigitalItems +docs_dir: .mkdocs-build +site_dir: docs +strict: true + +theme: + name: material + logo: assets/favicon.svg + favicon: assets/favicon.svg + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: teal + accent: amber + toggle: + icon: material/weather-night + name: Use dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: teal + accent: amber + toggle: + icon: material/weather-sunny + name: Use light mode + features: + - content.code.copy + - navigation.footer + - navigation.indexes + - navigation.sections + - navigation.top + - search.highlight + +nav: + - Overview: index.md + - Getting started: getting-started.md + - Digital identifiers: digital-identifiers.md + - Peripherals: + - Basic digitizer: guides/basic-digitizer.md + - Advanced digitizer: guides/advanced-items.md + - Guides: + - Fluids and energy: guides/fluids-and-energy.md + +markdown_extensions: + - admonition + - attr_list + - md_in_html + - pymdownx.details + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.superfences + - toc: + permalink: true + +extra_css: + - assets/stylesheets/digitalitems.css +extra_javascript: + - assets/javascripts/digitalitems.js +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/SirEdvin/DigitalItems diff --git a/projects/typed-peripheral-digitalitems/package-lock.json b/projects/typed-peripheral-digitalitems/package-lock.json index 5cac6cb..82e3e95 100644 --- a/projects/typed-peripheral-digitalitems/package-lock.json +++ b/projects/typed-peripheral-digitalitems/package-lock.json @@ -17,80 +17,16 @@ "@siredvin/typed-peripheral-base": "*" }, "devDependencies": { - "typedoc": "0.28.20", "typescript": "5.7.2", "typescript-to-lua": "1.29.1" } }, - "node_modules/@gerrit0/mini-shiki": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.23.0.tgz", - "integrity": "sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/engine-oniguruma": "^3.23.0", - "@shikijs/langs": "^3.23.0", - "@shikijs/themes": "^3.23.0", - "@shikijs/types": "^3.23.0", - "@shikijs/vscode-textmate": "^10.0.2" - } - }, "node_modules/@jackmacwindows/lua-types": { "version": "2.13.2", "resolved": "https://registry.npmjs.org/@jackmacwindows/lua-types/-/lua-types-2.13.2.tgz", "integrity": "sha512-XoqHmgtG1SLWzmMx8wQc0sJtPmrzjCFbiwNiBIZE7HOEXWdJi7NDKVlE1nl9A5745Yb5jIkfz2deOzMtHiHVcQ==", "license": "MIT" }, - "node_modules/@shikijs/engine-oniguruma": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", - "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.23.0", - "@shikijs/vscode-textmate": "^10.0.2" - } - }, - "node_modules/@shikijs/langs": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz", - "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.23.0" - } - }, - "node_modules/@shikijs/themes": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz", - "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.23.0" - } - }, - "node_modules/@shikijs/types": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz", - "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4" - } - }, - "node_modules/@shikijs/vscode-textmate": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", - "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", - "dev": true, - "license": "MIT" - }, "node_modules/@siredvin/api-types": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@siredvin/api-types/-/api-types-1.1.0.tgz", @@ -134,23 +70,6 @@ "@siredvin/craftos-types": "1.2.0" } }, - "node_modules/@types/hast": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", - "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@typescript-to-lua/language-extensions": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/@typescript-to-lua/language-extensions/-/language-extensions-1.19.0.tgz", @@ -158,36 +77,6 @@ "dev": true, "license": "MIT" }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/enhanced-resolve": { "version": "5.8.2", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz", @@ -202,19 +91,6 @@ "node": ">=10.13.0" } }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", @@ -271,84 +147,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/linkify-it": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", - "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/markdown-it" - } - ], - "license": "MIT", - "dependencies": { - "uc.micro": "^2.0.0" - } - }, - "node_modules/lunr": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", - "dev": true, - "license": "MIT" - }, - "node_modules/markdown-it": { - "version": "14.3.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.0.tgz", - "integrity": "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/markdown-it" - } - ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1", - "entities": "^4.5.0", - "linkify-it": "^5.0.2", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - }, - "bin": { - "markdown-it": "bin/markdown-it.mjs" - } - }, - "node_modules/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", - "dev": true, - "license": "MIT" - }, - "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -369,16 +167,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/resolve": { "version": "1.22.12", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", @@ -438,30 +226,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/typedoc": { - "version": "0.28.20", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.20.tgz", - "integrity": "sha512-uSKqkh8Cr48vllnEy+jdaAgOeR6Y+QCBW7usgUsKj7gJEfR7stw9U/fE49LBnj2tPRKPY0c0EBJSWe9Appmplg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@gerrit0/mini-shiki": "^3.23.0", - "lunr": "^2.3.9", - "markdown-it": "^14.3.0", - "minimatch": "^10.2.5", - "yaml": "^2.9.0" - }, - "bin": { - "typedoc": "bin/typedoc" - }, - "engines": { - "node": ">= 18", - "pnpm": ">= 10" - }, - "peerDependencies": { - "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x || 6.0.x" - } - }, "node_modules/typescript": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", @@ -498,29 +262,6 @@ "peerDependencies": { "typescript": "5.7.2" } - }, - "node_modules/uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "dev": true, - "license": "MIT" - }, - "node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } } } } diff --git a/projects/typed-peripheral-digitalitems/package.json b/projects/typed-peripheral-digitalitems/package.json index 0a8ab07..1741f11 100644 --- a/projects/typed-peripheral-digitalitems/package.json +++ b/projects/typed-peripheral-digitalitems/package.json @@ -14,8 +14,8 @@ "scripts": { "build": "tstl", "clean": "rm -f *.lua *.d.ts", - "docs": "typedoc", - "docs:watch": "typedoc --watch" + "docs": "node documentation/generate-docs.mjs", + "docs:test": "node --test documentation/generate-docs.test.mjs" }, "dependencies": { "@jackmacwindows/lua-types": "^2.13.1", @@ -26,7 +26,6 @@ "@siredvin/typed-peripheral-base": "*" }, "devDependencies": { - "typedoc": "0.28.20", "typescript": "5.7.2", "typescript-to-lua": "1.29.1" } diff --git a/projects/typed-peripheral-digitalitems/requirements-docs.txt b/projects/typed-peripheral-digitalitems/requirements-docs.txt new file mode 100644 index 0000000..c8de0c5 --- /dev/null +++ b/projects/typed-peripheral-digitalitems/requirements-docs.txt @@ -0,0 +1 @@ +mkdocs-material==9.6.14 diff --git a/projects/typed-peripheral-digitalitems/tsconfig.docs.json b/projects/typed-peripheral-digitalitems/tsconfig.docs.json deleted file mode 100644 index 35b63ea..0000000 --- a/projects/typed-peripheral-digitalitems/tsconfig.docs.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "skipLibCheck": true - }, - "include": ["./*.ts"] -} diff --git a/projects/typed-peripheral-digitalitems/typedoc.json b/projects/typed-peripheral-digitalitems/typedoc.json deleted file mode 100644 index afc0d6a..0000000 --- a/projects/typed-peripheral-digitalitems/typedoc.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "$schema": "https://typedoc.org/schema.json", - "entryPoints": ["./shared.ts", "./digitizer.ts", "./advanced_digitizer.ts"], - "tsconfig": "./tsconfig.docs.json", - "out": "./docs", - "name": "@siredvin/typed-peripheral-digitalitems", - "readme": "./documentation/index.md", - "projectDocuments": [ - "./documentation/getting-started.md", - "./documentation/digital-identifiers.md", - "./documentation/guides/*.md" - ], - "plugin": ["./documentation/theme/plugin.mjs"], - "customCss": "./documentation/theme/digitalitems.css", - "customJs": "./documentation/theme/digitalitems.js", - "favicon": "./documentation/theme/favicon.svg", - "highlightLanguages": ["typescript", "lua", "json", "bash"], - "excludeExternals": true, - "basePath": "../..", - "sourceLinkTemplate": "https://github.com/SirEdvin/DigitalItems/blob/{gitRevision}/{path}#L{line}", - "navigationLinks": { - "GitHub": "https://github.com/SirEdvin/DigitalItems" - }, - "blockTags": [ - "@defaultValue", - "@deprecated", - "@example", - "@jsx", - "@param", - "@privateRemarks", - "@remarks", - "@returns", - "@see", - "@throws", - "@typeParam", - "@author", - "@callback", - "@category", - "@categoryDescription", - "@default", - "@document", - "@extends", - "@augments", - "@yields", - "@group", - "@groupDescription", - "@import", - "@inheritDoc", - "@license", - "@module", - "@mergeModuleWith", - "@prop", - "@property", - "@return", - "@satisfies", - "@since", - "@sortStrategy", - "@template", - "@this", - "@type", - "@typedef", - "@summary", - "@preventInline", - "@inlineType", - "@preventExpand", - "@expandType", - "@noSelf" - ], - "excludeTags": [ - "@override", - "@virtual", - "@privateRemarks", - "@satisfies", - "@overload", - "@inline", - "@inlineType", - "@noSelf" - ], - "validation": { - "notDocumented": true, - "invalidLink": true, - "notExported": true - }, - "treatWarningsAsErrors": true -} diff --git a/projects/typescript-tests/package-lock.json b/projects/typescript-tests/package-lock.json index 78674c2..fa73958 100644 --- a/projects/typescript-tests/package-lock.json +++ b/projects/typescript-tests/package-lock.json @@ -35,7 +35,6 @@ "@siredvin/typed-peripheral-base": "*" }, "devDependencies": { - "typedoc": "0.28.20", "typescript": "5.7.2", "typescript-to-lua": "1.29.1" }