diff --git a/.github/gbp.toml b/.github/gbp.toml new file mode 100644 index 0000000000..6eb7ece4f0 --- /dev/null +++ b/.github/gbp.toml @@ -0,0 +1,23 @@ +no_balance_label = "GBP: No Update" +reset_label = "GBP: Reset" + +[points] +"Accessibility" = 3 +"Administration" = 2 +"Balance" = -5 +"Code Improvement" = 2 +"Documentation" = 1 +"Feature" = -6 +"Good First PR" = 6 +"Fix" = 3 +"Grammar and Formatting" = 1 +"Hard Deletes" = 12 +"Logging" = 1 +"Sev: 0" = 20 +"Sev: 1-Blocker" = 20 +"Sev: 2-High" = 15 +"Quality of Life" = 1 +"Refactor" = 10 +"Sound" = 3 +"Sprites" = 3 +"Unit Tests" = 6 diff --git a/.github/workflows/autowiki.yml b/.github/workflows/autowiki.yml new file mode 100644 index 0000000000..2f6cadbbf9 --- /dev/null +++ b/.github/workflows/autowiki.yml @@ -0,0 +1,45 @@ +name: Autowiki +on: + schedule: + - cron: "5 4 * * *" + workflow_dispatch: +permissions: + contents: read + +jobs: + autowiki: + runs-on: ubuntu-latest + steps: + - name: "Check for AUTOWIKI_USERNAME" + id: secrets_set + env: + ENABLER_SECRET: ${{ secrets.AUTOWIKI_USERNAME }} + run: | + unset SECRET_EXISTS + if [ -n "$ENABLER_SECRET" ]; then SECRET_EXISTS=true ; fi + echo "SECRETS_ENABLED=$SECRET_EXISTS" >> $GITHUB_OUTPUT + - name: Checkout + if: steps.secrets_set.outputs.SECRETS_ENABLED + uses: actions/checkout@v5 + - name: Install BYOND + if: steps.secrets_set.outputs.SECRETS_ENABLED + uses: ./.github/actions/restore_or_install_byond + - name: Install rust-g + if: steps.secrets_set.outputs.SECRETS_ENABLED + run: | + bash tools/ci/install_rust_g.sh + - name: Compile and generate Autowiki files + if: steps.secrets_set.outputs.SECRETS_ENABLED + run: | + source $HOME/BYOND/byond/bin/byondsetup + tools/build/build.sh --ci autowiki + - name: Run Autowiki + if: steps.secrets_set.outputs.SECRETS_ENABLED + env: + USERNAME: ${{ secrets.AUTOWIKI_USERNAME }} + PASSWORD: ${{ secrets.AUTOWIKI_PASSWORD }} + run: | + cd tools/autowiki + npm install + cd ../.. + node tools/autowiki/autowiki.js data/autowiki_edits.txt data/autowiki_files/ diff --git a/.github/workflows/gbp.yml b/.github/workflows/gbp.yml new file mode 100644 index 0000000000..7e9bacd8ea --- /dev/null +++ b/.github/workflows/gbp.yml @@ -0,0 +1,66 @@ +name: Label and GBP +on: + pull_request_target: + types: [closed, opened, synchronize] +jobs: + # labeler must run before gbp because gbp calculates itself based on labels + labeler: + runs-on: ubuntu-latest + if: github.event.action == 'opened' || github.event.action == 'synchronize' + permissions: + pull-requests: write # to apply labels + issues: write # to apply labels + steps: + - name: Checkout + uses: actions/checkout@v5 + - name: Run Auto Labeler + uses: actions/github-script@v8 + with: + script: | + const { get_updated_label_set } = await import('${{ github.workspace }}/tools/pull_request_hooks/autoLabel.js'); + const new_labels = await get_updated_label_set({ github, context }); + github.rest.issues.setLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: new_labels, + }); + console.log(`Labels updated: ${new_labels}`); + gbp: + runs-on: ubuntu-latest + if: github.event.action == 'opened' || github.event.action == 'closed' + steps: + - name: "Check for ACTION_ENABLER secret and pass true to output if it exists to be checked by later steps" + id: value_holder + env: + ENABLER_SECRET: ${{ secrets.ACTION_ENABLER }} + run: | + unset SECRET_EXISTS + if [ -n "$ENABLER_SECRET" ]; then SECRET_EXISTS=true ; fi + echo "ACTIONS_ENABLED=$SECRET_EXISTS" >> $GITHUB_OUTPUT + - name: Checkout + if: steps.value_holder.outputs.ACTIONS_ENABLED + uses: actions/checkout@v5 + - name: Setup git + if: steps.value_holder.outputs.ACTIONS_ENABLED + run: | + git config --global user.name "gbp-action" + git config --global user.email "<>" + - name: Checkout alternate branch + if: steps.value_holder.outputs.ACTIONS_ENABLED + uses: actions/checkout@v5 + with: + ref: "gbp-balances" # The branch name + path: gbp-balances + # This is to ensure we keep the gbp.toml from master + # without having to update our separate branch. + - name: Copy configuration + if: steps.value_holder.outputs.ACTIONS_ENABLED + run: cp ./.github/gbp.toml ./gbp-balances/.github/gbp.toml + - name: GBP action + if: steps.value_holder.outputs.ACTIONS_ENABLED + uses: tgstation/gbp-action@master + with: + branch: "gbp-balances" + directory: ./gbp-balances + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/gbp_collect.yml b/.github/workflows/gbp_collect.yml new file mode 100644 index 0000000000..1e83e860ab --- /dev/null +++ b/.github/workflows/gbp_collect.yml @@ -0,0 +1,44 @@ +name: GBP Collection +# Every hour at the :20 minute mark. GitHub tells us to pick odd hours, instead of just using the start. +on: + schedule: + - cron: "20 * * * *" + workflow_dispatch: +jobs: + gbp_collection: + runs-on: ubuntu-latest + steps: + - name: "Check for ACTION_ENABLER secret and pass true to output if it exists to be checked by later steps" + id: value_holder + env: + ENABLER_SECRET: ${{ secrets.ACTION_ENABLER }} + run: | + unset SECRET_EXISTS + if [ -n "$ENABLER_SECRET" ]; then SECRET_EXISTS=true ; fi + echo "ACTIONS_ENABLED=$SECRET_EXISTS" >> $GITHUB_OUTPUT + - name: Checkout + if: steps.value_holder.outputs.ACTIONS_ENABLED + uses: actions/checkout@v5 + - name: Setup git + if: steps.value_holder.outputs.ACTIONS_ENABLED + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" + - name: Checkout alternate branch + if: steps.value_holder.outputs.ACTIONS_ENABLED + uses: actions/checkout@v5 + with: + ref: "gbp-balances" # The branch name + path: gbp-balances + # This is to ensure we keep the gbp.toml from master + # without having to update our separate branch. + - name: Copy configuration + if: steps.value_holder.outputs.ACTIONS_ENABLED + run: cp ./.github/gbp.toml ./gbp-balances/.github/gbp.toml + - name: GBP action + if: steps.value_holder.outputs.ACTIONS_ENABLED + uses: tgstation/gbp-action@master + with: + collect: "true" + directory: ./gbp-balances + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/code/_compile_options.dm b/code/_compile_options.dm index bc43d51fa2..872beb1c8a 100644 --- a/code/_compile_options.dm +++ b/code/_compile_options.dm @@ -3,5 +3,9 @@ #define USE_CUSTOM_ERROR_HANDLER #endif +/// If this is uncommented, Autowiki will generate edits and shut down the server. +/// Prefer the autowiki build target instead. +// #define AUTOWIKI + // We do not have dreamlua implemented #define DISABLE_DREAMLUAU diff --git a/code/modules/autowiki/autowiki.dm b/code/modules/autowiki/autowiki.dm new file mode 100644 index 0000000000..f7a7d957c3 --- /dev/null +++ b/code/modules/autowiki/autowiki.dm @@ -0,0 +1,33 @@ +/// When the `AUTOWIKI` define is enabled, will generate an output file for tools/autowiki/autowiki.js to consume. +/// Autowiki code intentionally still *exists* even without the define, to ensure developers notice +/// when they break it immediately, rather than until CI or worse, call time. +#if defined(AUTOWIKI) || defined(UNIT_TESTS) +/proc/setup_autowiki() + Master.sleep_offline_after_initializations = FALSE + SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(generate_autowiki))) + SSticker.start_immediately = TRUE + CONFIG_SET(number/round_end_countdown, 0) + +/proc/generate_autowiki() + var/output = generate_autowiki_output() + rustg_file_write(output, "data/autowiki_edits.txt") + qdel(world) +#endif + +/// Returns a string of the autowiki output file +/proc/generate_autowiki_output() + var/total_output = "" + + for (var/datum/autowiki/autowiki_type as anything in subtypesof(/datum/autowiki)) + var/datum/autowiki/autowiki = new autowiki_type + var/output = autowiki.generate() + + if (!istext(output)) + CRASH("[autowiki_type] does not generate a proper output!") + + total_output += json_encode(list( + "title" = autowiki.page, + "text" = output, + )) + "\n" + + return total_output diff --git a/code/modules/autowiki/page/base.dm b/code/modules/autowiki/page/base.dm new file mode 100644 index 0000000000..ce32bfd403 --- /dev/null +++ b/code/modules/autowiki/page/base.dm @@ -0,0 +1,59 @@ +/// A representation of an automated wiki page. +/datum/autowiki + /// The page on the wiki to be replaced. + /// This should never be a user-facing page, like "Guide to circuits". + /// It should always be a template that only Autowiki should touch. + /// For example: "Template:Autowiki/CircuitInfo". + var/page + +/// Override and return the new text of the page. +/// This proc can be impure, usually to call `upload_file`. +/datum/autowiki/proc/generate() + SHOULD_CALL_PARENT(FALSE) + CRASH("[type] does not implement generate()!") + +/// Generates an auto formatted template user. +/// Your autowiki should ideally be a *lot* of these. +/// It lets wiki editors edit it much easier later, without having to enter repo. +/// Parameters will be passed in by name. That means your template should expect +/// something that looks like `{{ Autowiki_Circuit|name=Combiner|description=This combines }}` +/// Lists, which must be array-like (no keys), will be turned into a flat list with their key and a number, +/// such that list("food" = list("fruit", "candy")) -> food1=fruit|food2=candy +/datum/autowiki/proc/include_template(name, parameters) + var/template_text = "{{[name]" + + var/list/prepared_parameters = list() + for (var/key in parameters) + var/value = parameters[key] + if (islist(value)) + for (var/index in 1 to length(value)) + prepared_parameters["[key][index]"] = "[value[index]]" + else + prepared_parameters[key] = value + + for (var/parameter_name in prepared_parameters) + template_text += "|[parameter_name]=" + template_text += "[prepared_parameters[parameter_name]]" + + template_text += "}}" + + return template_text + +/// Takes an icon and uploads it to Autowiki-name.png. +/// Do your best to make sure this is unique, so it doesn't clash with other autowiki icons. +/datum/autowiki/proc/upload_icon(icon/icon, name) + // Fuck you + if (IsAdminAdvancedProcCall()) + return + + var/static/uploaded_icons = list() + if(uploaded_icons["[name]"]) + CRASH("We tried uploading an icon, but the name \"[name]\" was already taken!") + + fcopy(icon, "data/autowiki_files/[name].png") + uploaded_icons["[name]"] = TRUE + +/// Escape a parameter such that it can be correctly put inside a wiki output +/datum/autowiki/proc/escape_value(parameter) + // | is a special character in MediaWiki, and must be escaped by...using another template. + return replacetextEx(parameter, "|", "{{!}}") diff --git a/code/modules/autowiki/page/symptom.dm b/code/modules/autowiki/page/symptom.dm new file mode 100644 index 0000000000..29aedd3210 --- /dev/null +++ b/code/modules/autowiki/page/symptom.dm @@ -0,0 +1,36 @@ +/datum/autowiki/symptom + page = "Template:Autowiki/Content/Symptoms" + +/datum/autowiki/symptom/generate() + var/output = "" + + var/list/template_list = list() + + for(var/the_symptom in subtypesof(/datum/symptom)) + var/datum/symptom/symptom = new the_symptom + + if(symptom.level < 0) // Skip base/admin symptoms + continue + + template_list["name"] = escape_value(symptom.name) + template_list["stealth"] = symptom.stealth + template_list["resistance"] = symptom.resistance + template_list["speed"] = symptom.stage_speed + template_list["transmission"] = symptom.transmission + template_list["level"] = symptom.level + template_list["effect"] = escape_value(symptom.desc) + template_list["thresholds"] = length(symptom.threshold_descs) ? generate_thresholds(symptom.threshold_descs) : "None" + + output += include_template("Autowiki/SymptomTemplate", template_list) + + return include_template("Autowiki/SymptomTableTemplate", list("content" = output)) + +/datum/autowiki/symptom/proc/generate_thresholds(var/list/thresholds) + var/compiled_thresholds = "" + + for(var/threshold in thresholds) + var/description = thresholds[threshold] + if(length(threshold)) + compiled_thresholds += "