Skip to content

Commit 267cbbf

Browse files
authored
Merge branch 'TurboWarp:master' into master
2 parents b05b96b + 4b5cfcf commit 267cbbf

10 files changed

Lines changed: 347 additions & 39 deletions

File tree

.github/dependabot.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ updates:
33
- package-ecosystem: "npm"
44
directory: "/"
55
schedule:
6-
interval: "weekly"
7-
ignore:
8-
# Prettier updates that result in style changes result in cascading format validation
9-
# errors until all pull requests get rebased. It's not worth the effort to follow
10-
# every update. We'll do this manually when there's some change we want.
11-
- dependency-name: prettier
6+
interval: "daily"
7+
assignees:
8+
- "GarboMuffin"
9+
allow:
10+
# Only try to automatically bump first-party dependencies.
11+
- dependency-name: "@turbowarp/*"

.github/workflows/validate.yml

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
cache: npm
3636
- name: Install dependencies
3737
run: npm ci
38-
- name: Validate
38+
- name: Lint
3939
run: npm run lint
4040

4141
format:
@@ -52,5 +52,33 @@ jobs:
5252
cache: npm
5353
- name: Install dependencies
5454
run: npm ci
55-
- name: Validate
56-
run: npm run check-format
55+
- name: Check format
56+
shell: bash
57+
run: |
58+
# Rewrite the prettier output format into the GitHub Actions annotation format
59+
# This is a hack, but this is easier than reimplementing the prettier CLI's ignore
60+
# rules ourselves.
61+
NO_COLOR=1 npm run check-format 2> >(tee >( \
62+
grep -v 'Run Prettier with --write to fix' | \
63+
sed -e 's/^\[warn] \(.*\)$/::warning file=\1,line=1::File was not formatted with prettier. Usually, commenting !format will fix this./' \
64+
))
65+
66+
type-warnings:
67+
runs-on: ubuntu-latest
68+
steps:
69+
- name: Checkout
70+
uses: actions/checkout@v4
71+
with:
72+
persist-credentials: false
73+
- name: Install Node.js
74+
uses: actions/setup-node@v4
75+
with:
76+
node-version: 22
77+
cache: npm
78+
- name: Install dependencies
79+
run: npm ci
80+
- name: Generate type warnings
81+
run: node development/ci-generate-type-warnings.js
82+
env:
83+
PR_NUMBER: "${{ github.event.number }}"
84+
GH_REPO: "${{ github.repository }}"

development/builder.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -960,12 +960,13 @@ class Builder {
960960
async validate() {
961961
const errors = [];
962962
const build = await this.build();
963-
for (const [fileName, file] of Object.entries(build.files)) {
963+
for (const [outputFileName, file] of Object.entries(build.files)) {
964964
try {
965965
await file.validate();
966966
} catch (e) {
967967
errors.push({
968-
fileName,
968+
outputFileName,
969+
inputFilePath: file.sourcePath,
969970
error: e,
970971
});
971972
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import pathUtil from "node:path";
2+
import ts from "typescript";
3+
import { createAnnotation, getChangedFiles, isCI } from "./ci-interop.js";
4+
5+
/**
6+
* @fileoverview Generates CI annotations for type warnings in files modified by the
7+
* pull request. Standard TypeScript CLI will include all files and its output is
8+
* detected as errors, so not usable for us.
9+
*/
10+
11+
const check = async () => {
12+
const rootDir = pathUtil.join(import.meta.dirname, "..");
13+
14+
const changedFiles = Array.from(await getChangedFiles()).sort();
15+
const changedFilesAbsolute = changedFiles.map((f) =>
16+
pathUtil.join(rootDir, f)
17+
);
18+
19+
console.log(`${changedFiles.size} changed files:`);
20+
console.log(Array.from(changedFiles).sort().join("\n"));
21+
console.log("");
22+
23+
const tsconfigPath = pathUtil.join(rootDir, "tsconfig.json");
24+
const commandLine = ts.getParsedCommandLineOfConfigFile(
25+
tsconfigPath,
26+
{},
27+
ts.sys
28+
);
29+
30+
const program = ts.createProgram({
31+
rootNames: commandLine.fileNames,
32+
options: commandLine.options,
33+
});
34+
const emitted = program.emit();
35+
36+
const diagnostics = [
37+
...ts.getPreEmitDiagnostics(program),
38+
...emitted.diagnostics,
39+
];
40+
41+
let numWarnings = 0;
42+
43+
for (const diagnostic of diagnostics) {
44+
if (!changedFilesAbsolute.includes(diagnostic.file.fileName)) {
45+
continue;
46+
}
47+
48+
const startPosition = ts.getLineAndCharacterOfPosition(
49+
diagnostic.file,
50+
diagnostic.start
51+
);
52+
const endPosition = ts.getLineAndCharacterOfPosition(
53+
diagnostic.file,
54+
diagnostic.start
55+
);
56+
// Won't be the most readable, but it's probably fine.
57+
const flattened = ts.flattenDiagnosticMessageText(
58+
diagnostic.messageText,
59+
" ",
60+
0
61+
);
62+
63+
numWarnings++;
64+
createAnnotation({
65+
type: "warning",
66+
file: diagnostic.file.fileName,
67+
title: "Type warning - may indicate a bug - ignore if no bug",
68+
onlyIfChanged: true,
69+
message: flattened,
70+
line: startPosition.line + 1,
71+
col: startPosition.character + 1,
72+
endLine: endPosition.line + 1,
73+
endCol: endPosition.character + 1,
74+
});
75+
}
76+
77+
console.log(`Warnings in changed files: ${numWarnings}`);
78+
console.log(`Warnings in all files: ${diagnostics.length}`);
79+
};
80+
81+
if (isCI()) {
82+
check();
83+
} else {
84+
console.error(
85+
"This script is only intended to be used in CI. For development, use normal TypeScript CLI instead."
86+
);
87+
process.exit(1);
88+
}

development/ci-interop.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import pathUtil from "node:path";
2+
3+
/**
4+
* @fileoverview Various utilities related to integrating with CI.
5+
*/
6+
7+
// https://github.com/actions/toolkit/blob/main/docs/problem-matchers.md#limitations
8+
export const MAX_ANNOTATIONS_PER_TYPE_PER_STEP = 10;
9+
export const MAX_ANNOTATIONS_PER_JOB = 50;
10+
11+
/**
12+
* @returns {boolean} true if running in CI (GitHub Actions, etc.)
13+
*/
14+
export const isCI = () => !!process.env.CI;
15+
16+
/**
17+
* Note: PR may have changed between when this CI job was queued and when this job runs.
18+
* So, don't use this for security-critical things where accuracy is a must.
19+
* @returns {Promise<Set<string>>} List of paths (relative to root without leading / or ./) changed by this PR.
20+
*/
21+
export const getChangedFiles = async () => {
22+
if (!isCI()) {
23+
return [];
24+
}
25+
26+
const prNumber = +process.env.PR_NUMBER;
27+
if (!prNumber) {
28+
throw new Error("Missing PR_NUMBER");
29+
}
30+
31+
const repo = process.env.GH_REPO;
32+
if (typeof repo !== "string" || !repo.includes("/")) {
33+
throw new Error("Missing GH_REPO");
34+
}
35+
36+
const diffResponse = await fetch(
37+
`https://patch-diff.githubusercontent.com/raw/${repo}/pull/${prNumber}.diff`
38+
);
39+
const diffText = await diffResponse.text();
40+
const fileMatches = [
41+
...diffText.matchAll(/^(?:---|\+\+\+) [ab]\/(.+)$/gm),
42+
].map((match) => match[1]);
43+
44+
return new Set(fileMatches);
45+
};
46+
47+
/**
48+
* @typedef Annotation
49+
* @property {'notice'|'warning'|'error'} type
50+
* @property {string} file Absolute path to file or relative from repository root
51+
* @property {string} title
52+
* @property {string} message
53+
* @property {number} [line] 1-indexed
54+
* @property {number} [col] 1-indexed
55+
* @property {number} [endLine] 1-indexed
56+
* @property {number} [endCol] 1-indexed
57+
*/
58+
59+
/**
60+
* @param {Annotation} annotation
61+
*/
62+
export const createAnnotation = (annotation) => {
63+
const rootDir = pathUtil.join(import.meta.dirname, "..");
64+
const relativeFileName = pathUtil.relative(rootDir, annotation.file);
65+
66+
// https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands
67+
68+
let output = "";
69+
output += `::${annotation.type} `;
70+
output += `file=${relativeFileName}`;
71+
72+
// Documentation says line number is not required, but in practice that gets interpreted as
73+
// line 0 and ends up not showing up. So, we'll default to line 1.
74+
if (typeof annotation.line === "number") {
75+
output += `,line=${annotation.line}`;
76+
} else {
77+
output += ",line=1";
78+
}
79+
80+
// These are all actually optional.
81+
if (typeof annotation.col === "number") {
82+
output += `,col=${annotation.col}`;
83+
}
84+
if (typeof annotation.endLine === "number") {
85+
output += `,endLine=${annotation.endLine}`;
86+
}
87+
if (typeof annotation.endCol === "number") {
88+
output += `,endCol=${annotation.endCol}`;
89+
}
90+
91+
output += `,title=${annotation.title}::`;
92+
output += annotation.message;
93+
94+
console.log(output);
95+
};

development/validate.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
import pathUtil from "node:path";
12
import Builder from "./builder.js";
3+
import { createAnnotation, isCI } from "./ci-interop.js";
24
import * as Colors from "./colors.js";
35

46
const builder = new Builder("production");
57
const errors = await builder.validate();
68

9+
const rootDir = pathUtil.join(import.meta.dirname, "..");
10+
711
if (errors.length === 0) {
812
console.log(
913
`${Colors.GREEN}${Colors.BOLD}Validation checks passed.${Colors.RESET}`
@@ -15,10 +19,25 @@ if (errors.length === 0) {
1519
errors.length === 1 ? "file" : "files"
1620
} failed validation.${Colors.RESET}`
1721
);
22+
1823
console.error("");
19-
for (const { fileName, error } of errors) {
20-
console.error(`${Colors.BOLD}${fileName}${Colors.RESET}: ${error}`);
24+
25+
for (const { outputFileName, inputFilePath, error } of errors) {
26+
const displayName = inputFilePath
27+
? pathUtil.relative(rootDir, inputFilePath)
28+
: outputFileName;
29+
console.error(`${Colors.BOLD}${displayName}${Colors.RESET}: ${error}`);
30+
31+
if (isCI() && inputFilePath) {
32+
createAnnotation({
33+
type: "error",
34+
file: inputFilePath,
35+
title: "Validation error",
36+
message: error,
37+
});
38+
}
2139
}
22-
console.error(``);
40+
41+
console.error("");
2342
process.exit(1);
2443
}

extensions/Lily/Skins.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@
428428
const skinId = createdSkins[skinName];
429429
if (!skins[skinId]) {
430430
// If the skin doesnt exist in the renderer we should probably just delete it on our end...
431-
this.safeDispose(skinId, skinName);
431+
this._disposeSafe(skinId, skinName);
432432
return 0;
433433
}
434434

0 commit comments

Comments
 (0)