diff --git a/.github/workflows/release-gluals.yml b/.github/workflows/release-gluals.yml index e5bbabe..689d87c 100644 --- a/.github/workflows/release-gluals.yml +++ b/.github/workflows/release-gluals.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version: "22" - - name: Decide what to publish + - name: Decide what to generate id: gate env: EVENT: ${{ github.event_name }} @@ -28,20 +28,21 @@ jobs: run: | set -e - # schedule + manual dispatch always publish everything (wiki content may have changed) + # Scheduled and manual runs need generation because upstream wiki content can change + # without any repository diff. Publishing is still gated later by output comparison. if [ "$EVENT" != "push" ]; then - echo "Reason: event=$EVENT -> publish all" - echo "should_run=true" >> "$GITHUB_OUTPUT" + echo "Reason: event=$EVENT -> generate and compare output" + echo "should_generate=true" >> "$GITHUB_OUTPUT" echo "publish_base=true" >> "$GITHUB_OUTPUT" echo "publish_all_plugins=true" >> "$GITHUB_OUTPUT" exit 0 fi - # push event -> diff against parent. Fail open (publish everything) if we can't. + # Push event -> diff against parent. Fail open (generate everything) if we can't. if [ -z "$BEFORE_SHA" ] || [ "$BEFORE_SHA" = "0000000000000000000000000000000000000000" ] \ || ! git cat-file -e "$BEFORE_SHA" 2>/dev/null; then - echo "No usable base SHA (got '$BEFORE_SHA') -> publish all to be safe" - echo "should_run=true" >> "$GITHUB_OUTPUT" + echo "No usable base SHA (got '$BEFORE_SHA') -> generate all to be safe" + echo "should_generate=true" >> "$GITHUB_OUTPUT" echo "publish_base=true" >> "$GITHUB_OUTPUT" echo "publish_all_plugins=true" >> "$GITHUB_OUTPUT" exit 0 @@ -88,45 +89,36 @@ jobs: PLUGINS_TO_PUBLISH="$CHANGED_PLUGIN_IDS" fi - SHOULD_RUN=false + SHOULD_GENERATE=false if $PUBLISH_BASE || $PUBLISH_ALL_PLUGINS || [ -n "$PLUGINS_TO_PUBLISH" ]; then - SHOULD_RUN=true + SHOULD_GENERATE=true fi - echo "should_run=$SHOULD_RUN" >> "$GITHUB_OUTPUT" + echo "should_generate=$SHOULD_GENERATE" >> "$GITHUB_OUTPUT" echo "publish_base=$PUBLISH_BASE" >> "$GITHUB_OUTPUT" echo "publish_all_plugins=$PUBLISH_ALL_PLUGINS" >> "$GITHUB_OUTPUT" echo "plugins_to_publish=$PLUGINS_TO_PUBLISH" >> "$GITHUB_OUTPUT" { - echo "## release-gluals gate" + echo "## release-gluals generation gate" echo "- event: \`$EVENT\`" - echo "- should_run: \`$SHOULD_RUN\`" + echo "- should_generate: \`$SHOULD_GENERATE\`" echo "- publish_base: \`$PUBLISH_BASE\`" echo "- publish_all_plugins: \`$PUBLISH_ALL_PLUGINS\`" echo "- plugins_to_publish: \`${PLUGINS_TO_PUBLISH:-}\`" } >> "$GITHUB_STEP_SUMMARY" - name: Install dependencies - if: steps.gate.outputs.should_run == 'true' + if: steps.gate.outputs.should_generate == 'true' run: npm ci - name: Set build timestamp id: build_ts - if: steps.gate.outputs.should_run == 'true' + if: steps.gate.outputs.should_generate == 'true' run: echo "value=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT" - name: Scrape wiki - if: steps.gate.outputs.should_run == 'true' + if: steps.gate.outputs.should_generate == 'true' run: npm run scrape-wiki - - name: Stamp metadata with build timestamp - if: steps.gate.outputs.should_run == 'true' - run: | - node -e " - const fs = require('fs'); - const meta = JSON.parse(fs.readFileSync('output/__metadata.json', 'utf8')); - meta.lastUpdate = '${{ steps.build_ts.outputs.value }}'; - fs.writeFileSync('output/__metadata.json', JSON.stringify(meta, null, 2)); - " - name: Regenerate Lua + plugin artifacts metadata - if: steps.gate.outputs.should_run == 'true' + if: steps.gate.outputs.should_generate == 'true' run: | npm run generate-lua -- \ --output ./output \ @@ -142,41 +134,28 @@ jobs: --artifactManifest plugin.json \ --version "${{ steps.build_ts.outputs.value }}" \ --generatedAt "${{ steps.build_ts.outputs.value }}" - - name: Run tests - if: steps.gate.outputs.should_run == 'true' - run: npm test - name: Format the output with StyLua - if: steps.gate.outputs.should_run == 'true' + if: steps.gate.outputs.should_generate == 'true' uses: JohnnyMorganz/stylua-action@v2.0.0 with: token: ${{ secrets.GITHUB_TOKEN }} version: latest args: --no-editorconfig output/ - - name: Tag latest scrape revision - if: steps.gate.outputs.should_run == 'true' - run: | - build_tag=$(echo "${{ steps.build_ts.outputs.value }}" | sed 's/:/-/g' | sed 's/T/_/' | sed 's/Z//') - tag="$build_tag" - if git rev-parse -q --verify "refs/tags/$tag" >/dev/null 2>&1; then - echo "Tag $tag already exists. Falling back to build run number." - tag="${build_tag}-${GITHUB_RUN_NUMBER}" - fi - git tag "$tag" - git push origin "refs/tags/$tag" - - name: Publish annotations to branch - if: steps.gate.outputs.should_run == 'true' + - name: Prepare release payloads + id: payloads + if: steps.gate.outputs.should_generate == 'true' env: PUBLISH_BASE: ${{ steps.gate.outputs.publish_base }} PUBLISH_ALL_PLUGINS: ${{ steps.gate.outputs.publish_all_plugins }} PLUGINS_TO_PUBLISH: ${{ steps.gate.outputs.plugins_to_publish }} run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" + set -e tmpdir=$(mktemp -d) base_dir="$tmpdir/base" plugin_stage_dir="$tmpdir/plugins" - mkdir -p "$base_dir/plugin" "$plugin_stage_dir" + compare_dir="$tmpdir/compare" + mkdir -p "$base_dir/plugin" "$plugin_stage_dir" "$compare_dir" # Stage base annotation branch payload (core lua + metadata + plugin index only). find output/ -maxdepth 1 -type f -name '*.lua' -exec cp {} "$base_dir/" \; @@ -186,6 +165,188 @@ jobs: cp -R output-plugins/. "$plugin_stage_dir/" fi + normalize_payload() { + local source_dir="$1" + local target_dir="$2" + + mkdir -p "$target_dir" + cp -R "$source_dir/." "$target_dir/" + + node - "$target_dir" <<'NODE' + const fs = require('fs'); + const path = require('path'); + + const root = process.argv[2]; + const rewriteJson = (relativePath, rewrite) => { + const filePath = path.join(root, relativePath); + if (!fs.existsSync(filePath)) return; + const json = JSON.parse(fs.readFileSync(filePath, 'utf8')); + rewrite(json); + fs.writeFileSync(filePath, `${JSON.stringify(json, null, 2)}\n`, 'utf8'); + }; + + rewriteJson('__metadata.json', (json) => { + json.lastUpdate = '__normalized__'; + }); + + rewriteJson(path.join('plugin', 'index.json'), (json) => { + delete json.generatedAt; + if (Array.isArray(json.plugins)) { + for (const plugin of json.plugins) { + if (plugin.artifact) delete plugin.artifact.version; + } + } + }); + NODE + } + + branch_has_changes() { + local branch_name="$1" + local source_dir="$2" + local existing_dir="$compare_dir/existing-$branch_name" + local normalized_existing="$compare_dir/existing-normalized-$branch_name" + local normalized_source="$compare_dir/source-normalized-$branch_name" + + rm -rf "$existing_dir" "$normalized_existing" "$normalized_source" + mkdir -p "$existing_dir" + + set +e + git ls-remote --exit-code --heads origin "$branch_name" >/dev/null 2>&1 + remote_status=$? + set -e + + if [ "$remote_status" -eq 2 ]; then + echo "Branch '$branch_name' does not exist yet." + return 0 + fi + + if [ "$remote_status" -ne 0 ]; then + echo "Unable to check remote branch '$branch_name'." + exit "$remote_status" + fi + + git fetch --depth=1 origin "refs/heads/$branch_name" >/dev/null + git archive "FETCH_HEAD" | tar -x -C "$existing_dir" + + normalize_payload "$existing_dir" "$normalized_existing" + normalize_payload "$source_dir" "$normalized_source" + + if diff -qr "$normalized_existing" "$normalized_source" >/dev/null; then + return 1 + fi + + return 0 + } + + should_publish_plugin() { + local plugin_id="$1" + + if [ "$PUBLISH_ALL_PLUGINS" = "true" ]; then + return 0 + fi + + for pid in $PLUGINS_TO_PUBLISH; do + if [ "$pid" = "$plugin_id" ]; then + return 0 + fi + done + + return 1 + } + + changed_base=false + changed_plugins="" + + if [ "$PUBLISH_BASE" = "true" ]; then + if branch_has_changes "gluals-annotations" "$base_dir"; then + changed_base=true + else + echo "Base annotations payload matches gluals-annotations." + fi + else + echo "Skipping base annotations comparison (no relevant source changes)." + fi + + for plugin_dir in "$plugin_stage_dir"/*; do + [ -d "$plugin_dir" ] || continue + plugin_id="$(basename "$plugin_dir")" + + if ! should_publish_plugin "$plugin_id"; then + echo "Skipping plugin '$plugin_id' comparison (no related source changes)." + continue + fi + + plugin_branch="gluals-annotations-plugin-${plugin_id}" + if branch_has_changes "$plugin_branch" "$plugin_dir"; then + changed_plugins="$changed_plugins $plugin_id" + else + echo "Plugin '$plugin_id' payload matches $plugin_branch." + fi + done + + changed_plugins=$(echo "$changed_plugins" | xargs || true) + if [ -n "$changed_plugins" ] && [ "$PUBLISH_BASE" = "true" ] && [ "$changed_base" != "true" ]; then + echo "Plugin payload changed; publishing base index so artifact versions stay current." + changed_base=true + fi + + should_publish=false + if [ "$changed_base" = "true" ] || [ -n "$changed_plugins" ]; then + should_publish=true + fi + + echo "tmpdir=$tmpdir" >> "$GITHUB_OUTPUT" + echo "base_dir=$base_dir" >> "$GITHUB_OUTPUT" + echo "plugin_stage_dir=$plugin_stage_dir" >> "$GITHUB_OUTPUT" + echo "should_publish=$should_publish" >> "$GITHUB_OUTPUT" + echo "changed_base=$changed_base" >> "$GITHUB_OUTPUT" + echo "changed_plugins=$changed_plugins" >> "$GITHUB_OUTPUT" + + { + echo "## release-gluals output comparison" + echo "- should_publish: \`$should_publish\`" + echo "- changed_base: \`$changed_base\`" + echo "- changed_plugins: \`${changed_plugins:-}\`" + } >> "$GITHUB_STEP_SUMMARY" + - name: Stop when generated output is unchanged + if: steps.gate.outputs.should_generate == 'true' && steps.payloads.outputs.should_publish != 'true' + run: echo "Generated output matches published branches. Nothing to release." + - name: Run tests + if: steps.payloads.outputs.should_publish == 'true' + run: npm test + - name: Stamp metadata with build timestamp + if: steps.payloads.outputs.should_publish == 'true' + run: | + node -e " + const fs = require('fs'); + const meta = JSON.parse(fs.readFileSync('${{ steps.payloads.outputs.base_dir }}/__metadata.json', 'utf8')); + meta.lastUpdate = '${{ steps.build_ts.outputs.value }}'; + fs.writeFileSync('${{ steps.payloads.outputs.base_dir }}/__metadata.json', JSON.stringify(meta, null, 2) + '\n'); + " + - name: Tag latest scrape revision + if: steps.payloads.outputs.should_publish == 'true' + run: | + build_tag=$(echo "${{ steps.build_ts.outputs.value }}" | sed 's/:/-/g' | sed 's/T/_/' | sed 's/Z//') + tag="$build_tag" + if git rev-parse -q --verify "refs/tags/$tag" >/dev/null 2>&1; then + echo "Tag $tag already exists. Falling back to build run number." + tag="${build_tag}-${GITHUB_RUN_NUMBER}" + fi + git tag "$tag" + git push origin "refs/tags/$tag" + - name: Publish annotations to branch + if: steps.payloads.outputs.should_publish == 'true' + env: + BASE_DIR: ${{ steps.payloads.outputs.base_dir }} + PLUGIN_STAGE_DIR: ${{ steps.payloads.outputs.plugin_stage_dir }} + CHANGED_BASE: ${{ steps.payloads.outputs.changed_base }} + CHANGED_PLUGINS: ${{ steps.payloads.outputs.changed_plugins }} + run: | + set -e + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + publish_branch() { local branch_name="$1" local source_dir="$2" @@ -203,31 +364,17 @@ jobs: now="$(date -u +%Y-%m-%dT%H:%M:%SZ)" - if [ "$PUBLISH_BASE" = "true" ]; then - publish_branch "gluals-annotations" "$base_dir" "Update GLuaLS annotations - $now" + if [ "$CHANGED_BASE" = "true" ]; then + publish_branch "gluals-annotations" "$BASE_DIR" "Update GLuaLS annotations - $now" else - echo "Skipping base annotations branch (no relevant changes)." + echo "Skipping base annotations branch (generated output unchanged)." fi - for plugin_dir in "$plugin_stage_dir"/*; do - [ -d "$plugin_dir" ] || continue - plugin_id="$(basename "$plugin_dir")" - - should_publish=false - if [ "$PUBLISH_ALL_PLUGINS" = "true" ]; then - should_publish=true - else - for pid in $PLUGINS_TO_PUBLISH; do - if [ "$pid" = "$plugin_id" ]; then - should_publish=true - break - fi - done - fi - - if [ "$should_publish" != "true" ]; then - echo "Skipping plugin '$plugin_id' (no related changes)." - continue + for plugin_id in $CHANGED_PLUGINS; do + plugin_dir="$PLUGIN_STAGE_DIR/$plugin_id" + if [ ! -d "$plugin_dir" ]; then + echo "Expected plugin payload directory missing: $plugin_dir" + exit 1 fi plugin_branch="gluals-annotations-plugin-${plugin_id}" diff --git a/.github/workflows/release-test.yml b/.github/workflows/release-test.yml deleted file mode 100644 index ee35c7e..0000000 --- a/.github/workflows/release-test.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: release-test -on: - workflow_dispatch: - push: - branches: - #- main - - test-plugin - # Only trigger on changes that actually impact our final output - paths: - - 'src/**' - - 'custom/**' - - '__tests__/**' - - 'package.json' - - 'package-lock.json' - - 'yarn.lock' - - 'jest.config.ts' - - 'tsconfig.json' - -jobs: - release: - permissions: write-all - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: actions/setup-node@v4 - with: - node-version: "22" - - - name: Install dependencies - run: npm ci - - - name: Scrape wiki - run: npm run scrape-wiki - - name: Run tests - run: npm test - - name: Format the output with StyLua - uses: JohnnyMorganz/stylua-action@v2.0.0 - with: - token: ${{ secrets.GITHUB_TOKEN }} - version: latest - args: --no-editorconfig output/ - - name: Publish annotations to test branch - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - # Stash only the final .lua files + metadata to a temp dir before switching branches. - # This is necessary because `git rm -rf .` only removes tracked files; output/ is - # gitignored (untracked) so it survives, causing duplicates when git add -A picks it up. - tmpdir=$(mktemp -d) - find output/ -maxdepth 1 -type f -name '*.lua' -exec cp {} "$tmpdir/" \; - cp output/__metadata.json "$tmpdir/" - - # Create orphan branch, then wipe EVERYTHING (tracked + untracked + ignored) - branchName="gluals-annotations-test" - git checkout --orphan "$branchName" - git rm -rf . - git clean -fdx - - # Restore only the final annotation files - cp "$tmpdir/"*.lua . - cp "$tmpdir/__metadata.json" . - git add -A - - # Commit with timestamp and branch reference - commitMsg="Update GLuaLS annotations (test) - $(date -u +%Y-%m-%dT%H:%M:%SZ) - from ${{ github.ref_name }}" - git commit -m "$commitMsg" - - # Force push to publish - git push -f origin "$branchName" diff --git a/__tests__/cli-generate-lua.spec.ts b/__tests__/cli-generate-lua.spec.ts index 9a87f68..6bb289c 100644 --- a/__tests__/cli-generate-lua.spec.ts +++ b/__tests__/cli-generate-lua.spec.ts @@ -19,7 +19,7 @@ describe('cli-generate-lua', () => { try { const command = process.platform === 'win32' ? 'npm.cmd' : 'npm'; const result = spawnSync( - `${command} run generate-lua -- --output "${outputPath}" --customOverrides ./custom`, + `${command} run generate-lua -- --output "${outputPath}" --custom-overrides ./custom`, [], { cwd: process.cwd(), @@ -36,6 +36,76 @@ describe('cli-generate-lua', () => { } }); + test('uses runtime-generic debug.getmetatable annotation override', () => { + const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gluals-generate-lua-debug-getmetatable-')); + const outputPath = path.join(tmpRoot, 'output'); + const customOverridesPath = path.join(tmpRoot, 'custom'); + const debugDir = path.join(outputPath, 'debug'); + + fs.mkdirSync(debugDir, { recursive: true }); + fs.mkdirSync(customOverridesPath, { recursive: true }); + + fs.copyFileSync( + path.join(process.cwd(), 'custom', 'debug.getmetatable.lua'), + path.join(customOverridesPath, 'debug.getmetatable.lua'), + ); + + const pagesPath = path.join(debugDir, 'getmetatable.json'); + fs.writeFileSync( + pagesPath, + JSON.stringify([ + { + type: 'libraryfunc', + parent: 'debug', + name: 'getmetatable', + address: 'debug.getmetatable', + description: 'Returns the metatable of the specified value.', + realm: 'shared', + url: 'https://wiki.facepunch.com/gmod/debug.getmetatable', + arguments: [ + { + args: [ + { + name: 'object', + type: 'any', + }, + ], + }, + ], + returns: [ + { + type: 'any', + }, + ], + }, + ], null, 2), + 'utf8', + ); + + try { + const command = process.platform === 'win32' ? 'npm.cmd' : 'npm'; + const result = spawnSync( + `${command} run generate-lua -- --output "${outputPath}" --custom-overrides "${customOverridesPath}"`, + [], + { + cwd: process.cwd(), + encoding: 'utf8', + shell: true, + }, + ); + + expect(result.status).toBe(0); + const debugLua = fs.readFileSync(path.join(outputPath, 'debug.lua'), 'utf8'); + expect(debugLua).toContain('---@generic T'); + expect(debugLua).toContain('---@param object T The value to get the metatable of.'); + expect(debugLua).toContain('---@return (definition) T # The metatable of the value.'); + expect(debugLua).not.toContain('`T`'); + expect(debugLua).not.toContain('---@generic T : table'); + } finally { + fs.rmSync(tmpRoot, { recursive: true, force: true }); + } + }); + test('applies typed Entity networked getter overrides', () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gluals-generate-lua-')); const outputPath = path.join(tmpRoot, 'output'); @@ -69,7 +139,7 @@ describe('cli-generate-lua', () => { try { const command = process.platform === 'win32' ? 'npm.cmd' : 'npm'; const result = spawnSync( - `${command} run generate-lua -- --output "${outputPath}" --customOverrides ./custom`, + `${command} run generate-lua -- --output "${outputPath}" --custom-overrides ./custom`, [], { cwd: process.cwd(), @@ -94,4 +164,130 @@ describe('cli-generate-lua', () => { fs.rmSync(tmpRoot, { recursive: true, force: true }); } }); + + test('applies custom overrides by default when --custom-overrides is not specified', () => { + const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gluals-generate-lua-defaults-')); + const outputPath = path.join(tmpRoot, 'output'); + const vectorDir = path.join(outputPath, 'vector'); + + fs.mkdirSync(vectorDir, { recursive: true }); + fs.writeFileSync( + path.join(vectorDir, 'pages.json'), + JSON.stringify([ + { + type: 'class', + address: 'Vector', + name: 'Vector', + description: 'A 3D vector.', + realm: 'shared', + url: 'https://wiki.facepunch.com/gmod/Vector', + parent: '', + }, + ], null, 2), + 'utf8', + ); + + try { + const command = process.platform === 'win32' ? 'npm.cmd' : 'npm'; + const result = spawnSync( + `${command} run generate-lua -- --output "${outputPath}"`, + [], + { + cwd: process.cwd(), + encoding: 'utf8', + shell: true, + }, + ); + + expect(result.status).toBe(0); + const vectorLua = fs.readFileSync(path.join(outputPath, 'vector.lua'), 'utf8'); + // These lines come from custom/class.Vector.lua overrides + expect(vectorLua).toContain('---@field x number'); + expect(vectorLua).toContain('---@field y number'); + expect(vectorLua).toContain('---@field z number'); + expect(vectorLua).toContain('---@operator add(Vector): Vector'); + } finally { + fs.rmSync(tmpRoot, { recursive: true, force: true }); + } + }); + + test('--raw-wiki skips applying custom overrides', () => { + const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gluals-generate-lua-rawwiki-')); + const outputPath = path.join(tmpRoot, 'output'); + const vectorDir = path.join(outputPath, 'vector'); + + fs.mkdirSync(vectorDir, { recursive: true }); + fs.writeFileSync( + path.join(vectorDir, 'pages.json'), + JSON.stringify([ + { + type: 'class', + address: 'Vector', + name: 'Vector', + description: 'A 3D vector.', + realm: 'shared', + url: 'https://wiki.facepunch.com/gmod/Vector', + parent: '', + }, + ], null, 2), + 'utf8', + ); + + try { + const command = process.platform === 'win32' ? 'npm.cmd' : 'npm'; + const result = spawnSync( + `${command} run generate-lua -- --output "${outputPath}" --raw-wiki`, + [], + { + cwd: process.cwd(), + encoding: 'utf8', + shell: true, + }, + ); + + expect(result.status).toBe(0); + const vectorLua = fs.readFileSync(path.join(outputPath, 'vector.lua'), 'utf8'); + // With --raw-wiki, custom overrides are skipped so @field/@operator from custom/class.Vector.lua should NOT appear + expect(vectorLua).not.toContain('---@field x number'); + expect(vectorLua).not.toContain('---@operator add(Vector): Vector'); + } finally { + fs.rmSync(tmpRoot, { recursive: true, force: true }); + } + }); + + test('--no-wipe-lua preserves existing Lua files', () => { + const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gluals-generate-lua-nowipe-')); + const outputPath = path.join(tmpRoot, 'output'); + const vectorDir = path.join(outputPath, 'vector'); + + fs.mkdirSync(vectorDir, { recursive: true }); + fs.writeFileSync( + path.join(vectorDir, 'pages.json'), + JSON.stringify([], null, 2), + 'utf8', + ); + + // Write a sentinel file that should survive when wipe is disabled + const sentinelFile = path.join(outputPath, 'sentinel.lua'); + fs.writeFileSync(sentinelFile, '-- sentinel', 'utf8'); + + try { + const command = process.platform === 'win32' ? 'npm.cmd' : 'npm'; + const result = spawnSync( + `${command} run generate-lua -- --output "${outputPath}" --no-wipe-lua --raw-wiki`, + [], + { + cwd: process.cwd(), + encoding: 'utf8', + shell: true, + }, + ); + + expect(result.status).toBe(0); + expect(fs.existsSync(sentinelFile)).toBe(true); + expect(fs.readFileSync(sentinelFile, 'utf8')).toBe('-- sentinel'); + } finally { + fs.rmSync(tmpRoot, { recursive: true, force: true }); + } + }); }); diff --git a/custom/Entity.NetworkVar.lua b/custom/Entity.NetworkVar.lua new file mode 100644 index 0000000..cc386a2 --- /dev/null +++ b/custom/Entity.NetworkVar.lua @@ -0,0 +1,13 @@ +---Creates a network variable and generated Get/Set accessors for the entity. +---@realm shared +---@source https://wiki.facepunch.com/gmod/Entity:NetworkVar +---@[overload_call_arg(0, "gmod.network_var", "type")] +---@[overload_call_arg(1, "gmod.network_var", "define")] +---@overload fun(type: string, name: string, extended?: table) +---@[call_arg("gmod.network_var", "type")] +---@param type string The NetworkVar type. +---@param slot number The NetworkVar slot. +---@[call_arg("gmod.network_var", "define")] +---@param name string Name of the variable, used for generated Get/Set accessors. +---@param extended? table Extra NetworkVar information. +function Entity:NetworkVar(type, slot, name, extended) end diff --git a/custom/Entity.NetworkVarElement.lua b/custom/Entity.NetworkVarElement.lua new file mode 100644 index 0000000..5ec1707 --- /dev/null +++ b/custom/Entity.NetworkVarElement.lua @@ -0,0 +1,14 @@ +---Creates Get/Set accessors for a vector or angle element NetworkVar. +---@realm shared +---@source https://wiki.facepunch.com/gmod/Entity:NetworkVarElement +---@[overload_call_arg(0, "gmod.network_var", "type")] +---@[overload_call_arg(2, "gmod.network_var", "define_element")] +---@overload fun(type: string, element: string, name: string, extended?: table) +---@[call_arg("gmod.network_var", "type")] +---@param type string The NetworkVar type. +---@param slot number The NetworkVar slot. +---@param element string The vector or angle element. +---@[call_arg("gmod.network_var", "define_element")] +---@param name string Name of the variable, used for generated Get/Set accessors. +---@param extended? table Extra NetworkVar information. +function Entity:NetworkVarElement(type, slot, element, name, extended) end diff --git a/custom/Global.Color.lua b/custom/Global.Color.lua new file mode 100644 index 0000000..b82b777 --- /dev/null +++ b/custom/Global.Color.lua @@ -0,0 +1,14 @@ +---Creates a new Color. +---@realm shared +---@realm menu +---@source https://wiki.facepunch.com/gmod/Global.Color +---@[call_arg("gmod.color", "r")] +---@param r number The red channel, from 0 to 255. +---@[call_arg("gmod.color", "g")] +---@param g number The green channel, from 0 to 255. +---@[call_arg("gmod.color", "b")] +---@param b number The blue channel, from 0 to 255. +---@[call_arg("gmod.color", "a")] +---@param a? number The alpha channel, from 0 to 255. +---@return Color +function _G.Color(r, g, b, a) end diff --git a/custom/Global.CreateClientConVar.lua b/custom/Global.CreateClientConVar.lua new file mode 100644 index 0000000..9fc30df --- /dev/null +++ b/custom/Global.CreateClientConVar.lua @@ -0,0 +1,13 @@ +---Creates a client-side console variable. +---@realm client +---@source https://wiki.facepunch.com/gmod/Global.CreateClientConVar +---@[call_arg("gmod.convar", "define_client")] +---@param name string +---@param default string|number +---@param shouldsave? boolean +---@param userinfo? boolean +---@param helptext? string +---@param min? number +---@param max? number +---@return (instance) ConVar +function _G.CreateClientConVar(name, default, shouldsave, userinfo, helptext, min, max) end diff --git a/custom/Global.CreateConVar.lua b/custom/Global.CreateConVar.lua index a8fcfef..5416bcc 100644 --- a/custom/Global.CreateConVar.lua +++ b/custom/Global.CreateConVar.lua @@ -4,6 +4,7 @@ ---@realm shared ---@realm menu ---@source https://wiki.facepunch.com/gmod/Global.CreateConVar +---@[call_arg("gmod.convar", "define_server")] ---@param name string ---@param value string|number ---@param flags? FCVAR|number[] diff --git a/custom/Global.DEFINE_BASECLASS.lua b/custom/Global.DEFINE_BASECLASS.lua new file mode 100644 index 0000000..dc51d60 --- /dev/null +++ b/custom/Global.DEFINE_BASECLASS.lua @@ -0,0 +1,7 @@ +---Declares the BaseClass helper for scripted classes. +---@realm shared +---@realm menu +---@source https://wiki.facepunch.com/gmod/Global.DEFINE_BASECLASS +---@[call_arg("gmod.class_base", "reference")] +---@param value string Base class name. +function _G.DEFINE_BASECLASS(value) end diff --git a/custom/Global.DeriveGamemode.lua b/custom/Global.DeriveGamemode.lua new file mode 100644 index 0000000..d3fa3a5 --- /dev/null +++ b/custom/Global.DeriveGamemode.lua @@ -0,0 +1,6 @@ +---Derives the current gamemode from another gamemode. +---@realm shared +---@source https://wiki.facepunch.com/gmod/Global.DeriveGamemode +---@[call_arg("gmod.gamemode", "reference")] +---@param base string Base gamemode folder name. +function _G.DeriveGamemode(base) end diff --git a/custom/Global.IsHostingGame.lua b/custom/Global.IsHostingGame.lua new file mode 100644 index 0000000..16decdb --- /dev/null +++ b/custom/Global.IsHostingGame.lua @@ -0,0 +1,4 @@ +---Returns true when the current menu session is hosting a local game. +---@realm menu +---@return boolean #True if the local client is hosting the active game session. +function _G.IsHostingGame() end diff --git a/custom/Global.error(lowercase).lua b/custom/Global.error(lowercase).lua new file mode 100644 index 0000000..64d54b0 --- /dev/null +++ b/custom/Global.error(lowercase).lua @@ -0,0 +1,8 @@ +---Throws a Lua error and breaks out of the current call stack. +---@realm shared +---@realm menu +---@source https://wiki.facepunch.com/gmod/Global.error(lowercase) +---@param message string # The error message to throw. +---@param errorLevel? number # The level to throw the error at. +---@return never +function _G.error(message, errorLevel) end diff --git a/custom/Panel.Add.lua b/custom/Panel.Add.lua index 0e60e0a..d6ecac3 100644 --- a/custom/Panel.Add.lua +++ b/custom/Panel.Add.lua @@ -3,7 +3,9 @@ ---@realm menu ---@source https://wiki.facepunch.com/gmod/Panel:Add ---@generic T : Panel +---@overload fun(self: Panel, panel: Panel): Panel # Parents an existing panel to this panel. ---@overload fun(self: Panel, panelTable: table): Panel # Creates a panel from a PANEL table and parents it to this panel. ----@param object `T`|T The panel to add, or a panel class name to create and add. ----@return (instance) T # The added or created panel -function Panel:Add(object) end +---@[call_arg("gmod.vgui_panel", "reference")] +---@param className `T` The panel class name to create and add. +---@return (instance) T # The created panel. +function Panel:Add(className) end diff --git a/custom/Panel.GetSkin.lua b/custom/Panel.GetSkin.lua new file mode 100644 index 0000000..51aad8c --- /dev/null +++ b/custom/Panel.GetSkin.lua @@ -0,0 +1,6 @@ +---Returns the table for the derma skin currently being used by this panel object. +---@realm client +---@realm menu +---@source https://wiki.facepunch.com/gmod/Panel:GetSkin +---@return SKIN # The derma skin table currently being used by this object. +function Panel:GetSkin() end diff --git a/custom/Panel.SetSkin.lua b/custom/Panel.SetSkin.lua new file mode 100644 index 0000000..e156c8b --- /dev/null +++ b/custom/Panel.SetSkin.lua @@ -0,0 +1,7 @@ +---Sets the derma skin that the panel object will use. +---@realm client +---@realm menu +---@source https://wiki.facepunch.com/gmod/Panel:SetSkin +---@[call_arg("gmod.derma_skin", "reference")] +---@param skinName string The name of the skin to use. The default derma skin is `Default`. +function Panel:SetSkin(skinName) end diff --git a/custom/PropertyAdd.lua b/custom/PropertyAdd.lua new file mode 100644 index 0000000..ebe48fa --- /dev/null +++ b/custom/PropertyAdd.lua @@ -0,0 +1,16 @@ +---Structure used for [properties.Add](https://wiki.facepunch.com/gmod/properties.Add). +---@realm shared +---@source https://wiki.facepunch.com/gmod/Structures/PropertyAdd +---@class (partial) PropertyAdd +---@field Type? string|"simple"|"toggle" Can be set to "toggle" to make this property a toggle property. +---@field MenuLabel string Label to show on opened menu. +---@field MenuIcon? string Icon to show on opened menu for this item. Optional for simple properties and unused for toggle properties. +---@field Order number Where in the list this property should be positioned, relative to other properties. +---@field PrependSpacer? boolean Whether to add a spacer before this property. +---@field Filter fun(self: table, ent: Entity, player: Player):(check: boolean) Used clientside to decide whether this property should be shown for an entity. +---@field Checked? fun(self: table, ent: Entity, tr: table):(check: boolean) Required only for toggle properties. +---@field Action fun(self: table, ent: Entity, tr: table) Called clientside when the property is clicked. +---@field Receive? fun(self: table, len: number, ply: Player) Called serverside if the client sends a message in the Action function. +---@field MenuOpen? fun(self: table, option: DMenuOption, ent: Entity, tr: table) Called clientside when the property option has been created in the right-click menu. +---@field OnCreate? fun(self: table, menu: DMenu, option: DMenuOption) Called clientside after the property option has been created. +local PropertyAdd = {} diff --git a/custom/VideoData.lua b/custom/VideoData.lua new file mode 100644 index 0000000..00fe4b0 --- /dev/null +++ b/custom/VideoData.lua @@ -0,0 +1,16 @@ +---Table structure used by [video.Record](https://wiki.facepunch.com/gmod/video.Record). +---@realm client +---@realm menu +---@source https://wiki.facepunch.com/gmod/Structures/VideoData +---@class (partial) VideoData +---@field container string The video container format. +---@field video string The video codec. +---@field audio string The audio codec. +---@field quality number The video quality. +---@field bitrate number The record bitrate. +---@field fps number Frames per second. +---@field lockfps? boolean Lock the frame count per second. +---@field name string The file name for the video. +---@field width number The video's width. +---@field height number The video's height. +local VideoData = {} diff --git a/custom/concommand.Add.lua b/custom/concommand.Add.lua new file mode 100644 index 0000000..19b8169 --- /dev/null +++ b/custom/concommand.Add.lua @@ -0,0 +1,12 @@ +---Creates a console command that runs the supplied callback. +---@realm shared +---@realm menu +---@source https://wiki.facepunch.com/gmod/concommand.Add +---@[call_arg("gmod.concommand", "define")] +---@param name string Name of the console command. +---@[call_arg("gmod.concommand", "callback")] +---@param callback fun(ply: Player, cmd: string, args: string[], argStr: string) Callback run when the command is executed. +---@param autoComplete? function +---@param helpText? string +---@param flags? FCVAR|number[] +function concommand.Add(name, callback, autoComplete, helpText, flags) end diff --git a/custom/debug.getmetatable.lua b/custom/debug.getmetatable.lua index f10834e..aefd2b3 100644 --- a/custom/debug.getmetatable.lua +++ b/custom/debug.getmetatable.lua @@ -1,8 +1,9 @@ ----Returns the metatable of the specified value. Can return any value. +---Returns the metatable of an object. This function ignores the metatable's __metatable field. +---@deprecated ---@realm shared ---@realm menu ---@source https://wiki.facepunch.com/gmod/debug.getmetatable ----@generic T : table ----@param object `T` The value to get the metatable of. ----@return (definition) `T` # The metatable of the value. +---@generic T +---@param object T The value to get the metatable of. +---@return (definition) T # The metatable of the value. function debug.getmetatable(object) end diff --git a/custom/derma.DefineControl.lua b/custom/derma.DefineControl.lua new file mode 100644 index 0000000..1660641 --- /dev/null +++ b/custom/derma.DefineControl.lua @@ -0,0 +1,14 @@ +---Defines a new Derma control with an optional base. +---@realm client +---@realm menu +---@source https://wiki.facepunch.com/gmod/derma.DefineControl +---@generic T: Panel +---@[call_arg("gmod.vgui_panel", "define_control")] +---@param name string Name of the newly created control. +---@param description string Description of the control. +---@[call_arg("gmod.vgui_panel", "table")] +---@param tab T Table containing control methods and properties. +---@[call_arg("gmod.vgui_panel", "base")] +---@param base string Derma control to base the new control off of. +---@return T # A table containing the new control's methods and properties. +function derma.DefineControl(name, description, tab, base) end diff --git a/custom/derma.DefineSkin.lua b/custom/derma.DefineSkin.lua new file mode 100644 index 0000000..3fd35b3 --- /dev/null +++ b/custom/derma.DefineSkin.lua @@ -0,0 +1,9 @@ +---Defines a new skin so that it is usable by Derma. +---@realm client +---@realm menu +---@source https://wiki.facepunch.com/gmod/derma.DefineSkin +---@[call_arg("gmod.derma_skin", "define")] +---@param name string Name of the skin. +---@param description string Description of the skin. +---@param skin SKIN Table containing skin data. +function derma.DefineSkin(name, description, skin) end diff --git a/custom/derma.GetDefaultSkin.lua b/custom/derma.GetDefaultSkin.lua new file mode 100644 index 0000000..c6447d6 --- /dev/null +++ b/custom/derma.GetDefaultSkin.lua @@ -0,0 +1,6 @@ +---Returns the default skin table. +---@realm client +---@realm menu +---@source https://wiki.facepunch.com/gmod/derma.GetDefaultSkin +---@return SKIN # The default skin table. +function derma.GetDefaultSkin() end diff --git a/custom/derma.GetNamedSkin.lua b/custom/derma.GetNamedSkin.lua new file mode 100644 index 0000000..c317eb6 --- /dev/null +++ b/custom/derma.GetNamedSkin.lua @@ -0,0 +1,8 @@ +---Returns the skin table of the skin with the supplied name. +---@realm client +---@realm menu +---@source https://wiki.facepunch.com/gmod/derma.GetNamedSkin +---@[call_arg("gmod.derma_skin", "reference")] +---@param name string Name of skin. +---@return SKIN? # The skin table. +function derma.GetNamedSkin(name) end diff --git a/custom/derma.GetSkinTable.lua b/custom/derma.GetSkinTable.lua new file mode 100644 index 0000000..59b1e92 --- /dev/null +++ b/custom/derma.GetSkinTable.lua @@ -0,0 +1,6 @@ +---Returns a copy of the table containing every Derma skin. +---@realm client +---@realm menu +---@source https://wiki.facepunch.com/gmod/derma.GetSkinTable +---@return table # Table of every Derma skin. +function derma.GetSkinTable() end diff --git a/custom/derma.SkinHook.lua b/custom/derma.SkinHook.lua new file mode 100644 index 0000000..65900f2 --- /dev/null +++ b/custom/derma.SkinHook.lua @@ -0,0 +1,10 @@ +---Checks if a matching hook function exists in the panel's skin, then calls it. +---@realm client +---@realm menu +---@source https://wiki.facepunch.com/gmod/derma.SkinHook +---@param type string The type of hook to run, usually `Paint`. +---@param name string The name of the hook or panel to run. Example: `Button`. +---@param panel Panel The panel to call the hook for. +---@param ... any Arguments forwarded to the skin hook. +---@return any # The returned variable from the skin hook. +function derma.SkinHook(type, name, panel, ...) end diff --git a/custom/hook.Add.lua b/custom/hook.Add.lua index 3843590..9a38735 100644 --- a/custom/hook.Add.lua +++ b/custom/hook.Add.lua @@ -2,7 +2,9 @@ ---@realm shared ---@realm menu ---@source https://wiki.facepunch.com/gmod/hook.Add +---@[call_arg("gmod.hook", "add")] ---@param eventName string The event to hook on to. This can be any GM_Hooks hook, gameevent after using gameevent.Listen, or custom hook run with hook.Call or hook.Run. ---@param identifier any The unique identifier, usually a string. This can be used elsewhere in the code to replace or remove the hook. The identifier **should** be unique so that you do not accidentally override some other mods hook, unless that's what you are trying to do. +---@[call_arg("gmod.hook", "callback")] ---@param func function The function to be called, arguments given to it depend on the identifier used. function hook.Add(eventName, identifier, func) end diff --git a/custom/hook.Call.lua b/custom/hook.Call.lua new file mode 100644 index 0000000..80a71a7 --- /dev/null +++ b/custom/hook.Call.lua @@ -0,0 +1,11 @@ +---Calls a hook and returns the first non-nil value returned by hook listeners. +---@realm shared +---@realm menu +---@source https://wiki.facepunch.com/gmod/hook.Call +---@[call_arg("gmod.hook", "emit")] +---@param eventName string The hook event to call. +---@[call_arg("gmod.hook", "gamemode_table")] +---@param gamemodeTable? table The gamemode table to call the hook on. +---@param ... any Arguments to pass to the hook. +---@return any +function hook.Call(eventName, gamemodeTable, ...) end diff --git a/custom/hook.Remove.lua b/custom/hook.Remove.lua new file mode 100644 index 0000000..c888d0f --- /dev/null +++ b/custom/hook.Remove.lua @@ -0,0 +1,8 @@ +---Removes a hook registered with hook.Add. +---@realm shared +---@realm menu +---@source https://wiki.facepunch.com/gmod/hook.Remove +---@[call_arg("gmod.hook", "remove")] +---@param eventName string The hook event name to remove from. +---@param identifier any The unique identifier previously used with hook.Add. +function hook.Remove(eventName, identifier) end diff --git a/custom/hook.Run.lua b/custom/hook.Run.lua new file mode 100644 index 0000000..c0e6c02 --- /dev/null +++ b/custom/hook.Run.lua @@ -0,0 +1,9 @@ +---Calls a hook without explicitly passing a gamemode table. +---@realm shared +---@realm menu +---@source https://wiki.facepunch.com/gmod/hook.Run +---@[call_arg("gmod.hook", "emit")] +---@param eventName string The hook event to call. +---@param ... any Arguments to pass to the hook. +---@return any +function hook.Run(eventName, ...) end diff --git a/custom/net.Receive.lua b/custom/net.Receive.lua new file mode 100644 index 0000000..226d13e --- /dev/null +++ b/custom/net.Receive.lua @@ -0,0 +1,7 @@ +---Registers a callback for a network message. +---@realm shared +---@source https://wiki.facepunch.com/gmod/net.Receive +---@[call_arg("gmod.net_message", "receive")] +---@param messageName string The message name to hook to. +---@param callback fun(len: number, ply: Player) The function to be called if the specified message was received. +function net.Receive(messageName, callback) end diff --git a/custom/net.Start.lua b/custom/net.Start.lua new file mode 100644 index 0000000..1bc40b2 --- /dev/null +++ b/custom/net.Start.lua @@ -0,0 +1,8 @@ +---Begins a new net message. +---@realm shared +---@source https://wiki.facepunch.com/gmod/net.Start +---@[call_arg("gmod.net_message", "start")] +---@param messageName string The name of the message to send. +---@param unreliable? boolean If set to `true`, the message is not guaranteed to reach its destination. +---@return boolean # `true` if the message has been started. +function net.Start(messageName, unreliable) end diff --git a/custom/scripted_ents.Register.lua b/custom/scripted_ents.Register.lua new file mode 100644 index 0000000..4951dca --- /dev/null +++ b/custom/scripted_ents.Register.lua @@ -0,0 +1,8 @@ +---Registers an ENT table with a classname. Reregistering an existing classname will automatically update the functions of all existing entities of that class. +--- +---The input is a registration table. Garry's Mod fills and inherits fields such as `ClassName`, `BaseClass`, and base-provided `Type` later during scripted entity registration and lookup. +---@realm shared +---@source https://wiki.facepunch.com/gmod/scripted_ents.Register +---@param ENT table The ENT table to register. +---@param classname string The classname to register. +function scripted_ents.Register(ENT, classname) end diff --git a/custom/timer.Create.lua b/custom/timer.Create.lua new file mode 100644 index 0000000..4b3bfa0 --- /dev/null +++ b/custom/timer.Create.lua @@ -0,0 +1,11 @@ +---Creates a new named timer. +---@realm shared +---@realm menu +---@source https://wiki.facepunch.com/gmod/timer.Create +---@[call_arg("gmod.timer", "define")] +---@param identifier string Identifier of the timer to create. +---@param delay number The delay interval in seconds. +---@param repetitions number The number of times to repeat the timer. Use `0` for infinite repetitions. +---@[call_arg("gmod.timer", "callback")] +---@param func function Function called when timer has finished the countdown. +function timer.Create(identifier, delay, repetitions, func) end diff --git a/custom/timer.Simple.lua b/custom/timer.Simple.lua new file mode 100644 index 0000000..c265c14 --- /dev/null +++ b/custom/timer.Simple.lua @@ -0,0 +1,8 @@ +---Creates a simple one-shot timer. +---@realm shared +---@realm menu +---@source https://wiki.facepunch.com/gmod/timer.Simple +---@param delay number Delay in seconds. +---@[call_arg("gmod.timer", "simple")] +---@param func function Function called when timer has finished the countdown. +function timer.Simple(delay, func) end diff --git a/custom/util.AddNetworkString.lua b/custom/util.AddNetworkString.lua new file mode 100644 index 0000000..3523c77 --- /dev/null +++ b/custom/util.AddNetworkString.lua @@ -0,0 +1,7 @@ +---Adds the specified string to the network string table. +---@realm server +---@source https://wiki.facepunch.com/gmod/util.AddNetworkString +---@[call_arg("gmod.net_message", "define")] +---@param str string Adds the specified string to the string table. +---@return number # The id of the string that was added to the string table. +function util.AddNetworkString(str) end diff --git a/custom/vgui.Create.lua b/custom/vgui.Create.lua index 713d932..3f7d59a 100644 --- a/custom/vgui.Create.lua +++ b/custom/vgui.Create.lua @@ -4,6 +4,7 @@ ---@realm menu ---@source https://wiki.facepunch.com/gmod/vgui.Create ---@generic T: Panel +---@[call_arg("gmod.vgui_panel", "reference")] ---@param classname `T` Classname of the panel to create. --- --- Default panel classnames can be found on the VGUI Element List. diff --git a/custom/vgui.CreateFromTable.lua b/custom/vgui.CreateFromTable.lua index 1ff486e..ecfa400 100644 --- a/custom/vgui.CreateFromTable.lua +++ b/custom/vgui.CreateFromTable.lua @@ -2,7 +2,10 @@ ---@realm client ---@realm menu ---@source https://wiki.facepunch.com/gmod/vgui.CreateFromTable ----@param metatable table Your PANEL table. +---@generic T: table +---@[call_arg("gmod.vgui_panel", "register_table")] +---@[call_arg_field("gmod.vgui_panel", "base", "Base")] +---@param metatable T Your PANEL table. ---@param parent? Panel Which panel to parent the newly created panel to. ---@param name? string Custom name of the created panel for scripting/debugging purposes. Can be retrieved with Panel:GetName. ---@return (instance) Panel # The created panel, or `nil` if creation failed for whatever reason. diff --git a/custom/vgui.CreateX.lua b/custom/vgui.CreateX.lua index 3ee13ed..4cff4a8 100644 --- a/custom/vgui.CreateX.lua +++ b/custom/vgui.CreateX.lua @@ -4,6 +4,7 @@ ---@realm menu ---@source https://wiki.facepunch.com/gmod/vgui.CreateX ---@generic T : Panel +---@[call_arg("gmod.vgui_panel", "reference")] ---@param class `T` Class of the panel to create ---@param parent? Panel If specified, parents created panel to given one ---@param name? string Name of the created panel diff --git a/custom/vgui.Register.lua b/custom/vgui.Register.lua new file mode 100644 index 0000000..859b3f8 --- /dev/null +++ b/custom/vgui.Register.lua @@ -0,0 +1,13 @@ +---Registers a panel for later creation via vgui.Create. +---@realm client +---@realm menu +---@source https://wiki.facepunch.com/gmod/vgui.Register +---@generic T: Panel +---@[call_arg("gmod.vgui_panel", "define")] +---@param classname string Classname of the panel to register. +---@[call_arg("gmod.vgui_panel", "table")] +---@param panelTable T The table containing the panel information. +---@[call_arg("gmod.vgui_panel", "base")] +---@param baseName? string Classname of a panel to inherit functionality from. +---@return T # The given panel table from second argument. +function vgui.Register(classname, panelTable, baseName) end diff --git a/custom/vgui.RegisterTable.lua b/custom/vgui.RegisterTable.lua new file mode 100644 index 0000000..a053cc2 --- /dev/null +++ b/custom/vgui.RegisterTable.lua @@ -0,0 +1,13 @@ +---Registers a table to use as a panel, to be used with [vgui.CreateFromTable](https://wiki.facepunch.com/gmod/vgui.CreateFromTable). +--- +--- All this function does is assigns Base key to your table and returns the table. +---@realm client +---@realm menu +---@source https://wiki.facepunch.com/gmod/vgui.RegisterTable +---@generic T: table +---@[call_arg("gmod.vgui_panel", "register_table")] +---@param panel T The PANEL table. +---@[call_arg("gmod.vgui_panel", "base")] +---@param base? string A base for the panel. +---@return T # The PANEL table +function vgui.RegisterTable(panel, base) end diff --git a/package.json b/package.json index e11cc86..b14eb37 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "wiki-check-changed": "tsx ./src/cli-change-checker.ts", "scrape-wiki": "tsx ./src/cli-scraper.ts --output ./output/ --customOverrides ./custom/ --wipe && npm run generate-all", "generate-lua": "tsx ./src/cli-generate-lua.ts", - "generate-all": "npm run generate-lua -- --output ./output --customOverrides ./custom --wipeLua && npm run generate-plugin-index && npm run generate-plugin-artifacts", + "generate-all": "npm run generate-lua -- --output ./output --custom-overrides ./custom && npm run generate-plugin-index && npm run generate-plugin-artifacts", "generate-plugin-index": "tsx ./src/cli-generate-plugin-index.ts --pluginRoot ./plugin --output ./plugin/index.json", "generate-plugin-artifacts": "tsx ./src/cli-generate-plugin-artifacts.ts --pluginRoot ./plugin --indexOutput ./plugin/index.json --annotationsOutput ./output --pluginBundlesOutput ./output-plugins", "pack-release": "tsx ./src/cli-release-packer.ts --input ./output/ --output ./dist/release/", diff --git a/src/cli-generate-lua.ts b/src/cli-generate-lua.ts index 2eb4eed..a61a78b 100644 --- a/src/cli-generate-lua.ts +++ b/src/cli-generate-lua.ts @@ -53,13 +53,14 @@ async function main() { program .description('Regenerate Lua annotations from existing JSON pages (no wiki scrape)') .option('-o, --output ', 'Output directory containing wiki JSON and Lua files', './output') - .option('-c, --customOverrides [path]', 'Custom override directory') - .option('--wipeLua', 'Delete existing top-level Lua files before regenerating', true) + .option('-c, --custom-overrides ', 'Custom override directory', './custom') + .option('--no-wipe-lua', 'Skip deleting existing top-level Lua files before regenerating') + .option('--raw-wiki', 'Skip applying custom overrides (use raw wiki data only)') .parse(process.argv); const options = program.opts(); const outputDirectory = options.output.replace(/\/$/, ''); - const customDirectory = options.customOverrides?.replace(/\/$/, ''); + const customDirectory = options.customOverrides.replace(/\/$/, ''); if (!fs.existsSync(outputDirectory)) { throw new Error(`Output directory does not exist: ${outputDirectory}`); @@ -71,7 +72,7 @@ async function main() { wipeLuaFiles(outputDirectory); } - if (customDirectory) { + if (!options.rawWiki) { if (!fs.existsSync(customDirectory)) { throw new Error(`Custom overrides directory does not exist: ${customDirectory}`); } diff --git a/src/utils/filesystem.ts b/src/utils/filesystem.ts index 889b34a..821db35 100644 --- a/src/utils/filesystem.ts +++ b/src/utils/filesystem.ts @@ -62,6 +62,13 @@ export async function zipFiles(outputFile: string, filePaths: string[], trimPath return new Promise(async (resolve, reject) => { const outputDirectory = path.dirname(outputFile); + for (const filePath of filePaths) { + if (!fs.existsSync(filePath)) { + reject(new Error(`File ${filePath} does not exist.`)); + return; + } + } + if (!fs.existsSync(outputDirectory)) fs.mkdirSync(outputDirectory, { recursive: true }); @@ -72,6 +79,10 @@ export async function zipFiles(outputFile: string, filePaths: string[], trimPath resolve(archive); }); + outputStream.on('error', function (err) { + reject(err); + }); + archive.on('error', function (err) { reject(err); }); @@ -79,9 +90,6 @@ export async function zipFiles(outputFile: string, filePaths: string[], trimPath archive.pipe(outputStream); for (const filePath of filePaths) { - if (!fs.existsSync(filePath)) - reject(new Error(`File ${filePath} does not exist.`)); - archive.file(filePath, { name: trimPath ? path.relative(trimPath, filePath) : filePath }); }