1
1
mirror of https://github.com/go-gitea/gitea synced 2025-09-10 10:48:28 +00:00

Migrate tools and configs to typescript, require node.js >= 22.18.0 (#35421)

Migrate all JS config and tools to TS and fix a number of type issues.
This required Node.js 22.18.0 or greater where [type-stripping was
enabled](https://nodejs.org/en/blog/release/v22.18.0) by default.

Given that Node 22 is the current LTS, I think it's ok to assume that
the user has a recent version of it.

Webpack currently requires the `--disable-interpret` flag to work,
should be fixed eventually with
https://github.com/webpack/webpack-cli/issues/4525.
`fast-glob` is replaced by `fs.globSync`, available in Node 22.0.0 or
greater.
This commit is contained in:
silverwind
2025-09-06 14:58:25 +02:00
committed by GitHub
parent 1640e9a490
commit b8f1c9f048
14 changed files with 89 additions and 99 deletions

View File

@@ -18,6 +18,9 @@ RUN apk --no-cache add \
&& npm install -g pnpm@10 \ && npm install -g pnpm@10 \
&& rm -rf /var/cache/apk/* && rm -rf /var/cache/apk/*
# workaround for node >= 22.18.0 on alpine 3.22. Remove when upgrading to alpine 3.23
COPY --from=docker.io/node:22-alpine3.22 /usr/local/bin/node /usr/local/bin/node
# Setup repo # Setup repo
COPY . ${GOPATH}/src/code.gitea.io/gitea COPY . ${GOPATH}/src/code.gitea.io/gitea
WORKDIR ${GOPATH}/src/code.gitea.io/gitea WORKDIR ${GOPATH}/src/code.gitea.io/gitea

View File

@@ -18,6 +18,9 @@ RUN apk --no-cache add \
&& npm install -g pnpm@10 \ && npm install -g pnpm@10 \
&& rm -rf /var/cache/apk/* && rm -rf /var/cache/apk/*
# workaround for node >= 22.18.0 on alpine 3.22. Remove when upgrading to alpine 3.23
COPY --from=docker.io/node:22-alpine3.22 /usr/local/bin/node /usr/local/bin/node
# Setup repo # Setup repo
COPY . ${GOPATH}/src/code.gitea.io/gitea COPY . ${GOPATH}/src/code.gitea.io/gitea
WORKDIR ${GOPATH}/src/code.gitea.io/gitea WORKDIR ${GOPATH}/src/code.gitea.io/gitea

View File

@@ -127,7 +127,7 @@ GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/m
MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...)
WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f) WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f)
WEBPACK_CONFIGS := webpack.config.js tailwind.config.js WEBPACK_CONFIGS := webpack.config.ts tailwind.config.ts
WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css
WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts
@@ -153,9 +153,9 @@ TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(DIST)
GO_DIRS := build cmd models modules routers services tests GO_DIRS := build cmd models modules routers services tests
WEB_DIRS := web_src/js web_src/css WEB_DIRS := web_src/js web_src/css
ESLINT_FILES := web_src/js tools *.js *.ts *.cjs tests/e2e ESLINT_FILES := web_src/js tools *.ts *.cjs tests/e2e
STYLELINT_FILES := web_src/css web_src/js/components/*.vue STYLELINT_FILES := web_src/css web_src/js/components/*.vue
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.ini .github $(filter-out CHANGELOG.md, $(wildcard *.go *.js *.md *.yml *.yaml *.toml)) $(filter-out tools/misspellings.csv, $(wildcard tools/*)) SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.ini .github $(filter-out CHANGELOG.md, $(wildcard *.go *.md *.yml *.yaml *.toml)) $(filter-out tools/misspellings.csv, $(wildcard tools/*))
EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.ini EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.ini
GO_SOURCES := $(wildcard *.go) GO_SOURCES := $(wildcard *.go)
@@ -407,7 +407,7 @@ lint-actions: ## lint action workflow files
.PHONY: lint-templates .PHONY: lint-templates
lint-templates: .venv node_modules ## lint template files lint-templates: .venv node_modules ## lint template files
@node tools/lint-templates-svg.js @node tools/lint-templates-svg.ts
@uv run --frozen djlint $(shell find templates -type f -iname '*.tmpl') @uv run --frozen djlint $(shell find templates -type f -iname '*.tmpl')
.PHONY: lint-yaml .PHONY: lint-yaml
@@ -421,7 +421,7 @@ watch: ## watch everything and continuously rebuild
.PHONY: watch-frontend .PHONY: watch-frontend
watch-frontend: node-check node_modules ## watch frontend files and continuously rebuild watch-frontend: node-check node_modules ## watch frontend files and continuously rebuild
@rm -rf $(WEBPACK_DEST_ENTRIES) @rm -rf $(WEBPACK_DEST_ENTRIES)
NODE_ENV=development pnpm exec webpack --watch --progress NODE_ENV=development pnpm exec webpack --watch --progress --disable-interpret
.PHONY: watch-backend .PHONY: watch-backend
watch-backend: go-check ## watch backend files and continuously rebuild watch-backend: go-check ## watch backend files and continuously rebuild
@@ -877,13 +877,13 @@ $(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) pnpm-lock.yaml
@$(MAKE) -s node-check node_modules @$(MAKE) -s node-check node_modules
@rm -rf $(WEBPACK_DEST_ENTRIES) @rm -rf $(WEBPACK_DEST_ENTRIES)
@echo "Running webpack..." @echo "Running webpack..."
@BROWSERSLIST_IGNORE_OLD_DATA=true pnpm exec webpack @BROWSERSLIST_IGNORE_OLD_DATA=true pnpm exec webpack --disable-interpret
@touch $(WEBPACK_DEST) @touch $(WEBPACK_DEST)
.PHONY: svg .PHONY: svg
svg: node-check | node_modules ## build svg files svg: node-check | node_modules ## build svg files
rm -rf $(SVG_DEST_DIR) rm -rf $(SVG_DEST_DIR)
node tools/generate-svg.js node tools/generate-svg.ts
.PHONY: svg-check .PHONY: svg-check
svg-check: svg svg-check: svg
@@ -922,7 +922,7 @@ generate-gitignore: ## update gitignore files
.PHONY: generate-images .PHONY: generate-images
generate-images: | node_modules ## generate images generate-images: | node_modules ## generate images
cd tools && node generate-images.js $(TAGS) cd tools && node generate-images.ts $(TAGS)
.PHONY: generate-manpage .PHONY: generate-manpage
generate-manpage: ## generate manpage generate-manpage: ## generate manpage

View File

@@ -2,7 +2,7 @@
"type": "module", "type": "module",
"packageManager": "pnpm@10.0.0", "packageManager": "pnpm@10.0.0",
"engines": { "engines": {
"node": ">= 20.0.0", "node": ">= 22.18.0",
"pnpm": ">= 10.0.0" "pnpm": ">= 10.0.0"
}, },
"dependencies": { "dependencies": {
@@ -31,7 +31,6 @@
"dropzone": "6.0.0-beta.2", "dropzone": "6.0.0-beta.2",
"easymde": "2.20.0", "easymde": "2.20.0",
"esbuild-loader": "4.3.0", "esbuild-loader": "4.3.0",
"fast-glob": "3.3.3",
"htmx.org": "2.0.6", "htmx.org": "2.0.6",
"idiomorph": "0.7.3", "idiomorph": "0.7.3",
"jquery": "3.7.1", "jquery": "3.7.1",
@@ -110,7 +109,6 @@
"stylelint-config-recommended": "17.0.0", "stylelint-config-recommended": "17.0.0",
"stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0",
"stylelint-declaration-strict-value": "1.10.11", "stylelint-declaration-strict-value": "1.10.11",
"stylelint-define-config": "16.22.0",
"stylelint-value-no-unknown-custom-properties": "6.0.1", "stylelint-value-no-unknown-custom-properties": "6.0.1",
"svgo": "4.0.0", "svgo": "4.0.0",
"type-fest": "4.41.0", "type-fest": "4.41.0",

17
pnpm-lock.yaml generated
View File

@@ -83,9 +83,6 @@ importers:
esbuild-loader: esbuild-loader:
specifier: 4.3.0 specifier: 4.3.0
version: 4.3.0(webpack@5.101.0) version: 4.3.0(webpack@5.101.0)
fast-glob:
specifier: 3.3.3
version: 3.3.3
htmx.org: htmx.org:
specifier: 2.0.6 specifier: 2.0.6
version: 2.0.6 version: 2.0.6
@@ -315,9 +312,6 @@ importers:
stylelint-declaration-strict-value: stylelint-declaration-strict-value:
specifier: 1.10.11 specifier: 1.10.11
version: 1.10.11(stylelint@16.23.1(typescript@5.8.3)) version: 1.10.11(stylelint@16.23.1(typescript@5.8.3))
stylelint-define-config:
specifier: 16.22.0
version: 16.22.0(stylelint@16.23.1(typescript@5.8.3))
stylelint-value-no-unknown-custom-properties: stylelint-value-no-unknown-custom-properties:
specifier: 6.0.1 specifier: 6.0.1
version: 6.0.1(stylelint@16.23.1(typescript@5.8.3)) version: 6.0.1(stylelint@16.23.1(typescript@5.8.3))
@@ -4518,12 +4512,6 @@ packages:
peerDependencies: peerDependencies:
stylelint: '>=7 <=16' stylelint: '>=7 <=16'
stylelint-define-config@16.22.0:
resolution: {integrity: sha512-EEgHRugsryKo7LpenYyd4yLoZon3lHvRAi7WsMaZoRX9GPOkeDXrMga+N4VA4nK4Zus02EQwyYkndNQ64jaB2A==}
engines: {node: '>=18.0.0', npm: '>=9.0.0', pnpm: '>=8.6.0'}
peerDependencies:
stylelint: '>=16.0.0'
stylelint-value-no-unknown-custom-properties@6.0.1: stylelint-value-no-unknown-custom-properties@6.0.1:
resolution: {integrity: sha512-N60PTdaTknB35j6D4FhW0GL2LlBRV++bRpXMMldWMQZ240yFQaoltzlLY4lXXs7Z0J5mNUYZQ/gjyVtU2DhCMA==} resolution: {integrity: sha512-N60PTdaTknB35j6D4FhW0GL2LlBRV++bRpXMMldWMQZ240yFQaoltzlLY4lXXs7Z0J5mNUYZQ/gjyVtU2DhCMA==}
engines: {node: '>=18.12.0'} engines: {node: '>=18.12.0'}
@@ -9732,11 +9720,6 @@ snapshots:
dependencies: dependencies:
stylelint: 16.23.1(typescript@5.8.3) stylelint: 16.23.1(typescript@5.8.3)
stylelint-define-config@16.22.0(stylelint@16.23.1(typescript@5.8.3)):
dependencies:
csstype: 3.1.3
stylelint: 16.23.1(typescript@5.8.3)
stylelint-value-no-unknown-custom-properties@6.0.1(stylelint@16.23.1(typescript@5.8.3)): stylelint-value-no-unknown-custom-properties@6.0.1(stylelint@16.23.1(typescript@5.8.3)):
dependencies: dependencies:
postcss-value-parser: 4.2.0 postcss-value-parser: 4.2.0

View File

@@ -1,6 +1,5 @@
// @ts-check
import {defineConfig} from 'stylelint-define-config';
import {fileURLToPath} from 'node:url'; import {fileURLToPath} from 'node:url';
import type {Config} from 'stylelint';
const cssVarFiles = [ const cssVarFiles = [
fileURLToPath(new URL('web_src/css/base.css', import.meta.url)), fileURLToPath(new URL('web_src/css/base.css', import.meta.url)),
@@ -8,7 +7,7 @@ const cssVarFiles = [
fileURLToPath(new URL('web_src/css/themes/theme-gitea-dark.css', import.meta.url)), fileURLToPath(new URL('web_src/css/themes/theme-gitea-dark.css', import.meta.url)),
]; ];
export default defineConfig({ export default {
extends: 'stylelint-config-recommended', extends: 'stylelint-config-recommended',
reportUnscopedDisables: true, reportUnscopedDisables: true,
reportNeedlessDisables: true, reportNeedlessDisables: true,
@@ -124,7 +123,6 @@ export default defineConfig({
'csstools/value-no-unknown-custom-properties': [true, {importFrom: cssVarFiles}], 'csstools/value-no-unknown-custom-properties': [true, {importFrom: cssVarFiles}],
'declaration-block-no-duplicate-properties': [true, {ignore: ['consecutive-duplicates-with-different-values']}], 'declaration-block-no-duplicate-properties': [true, {ignore: ['consecutive-duplicates-with-different-values']}],
'declaration-block-no-redundant-longhand-properties': [true, {ignoreShorthands: ['flex-flow', 'overflow', 'grid-template']}], 'declaration-block-no-redundant-longhand-properties': [true, {ignoreShorthands: ['flex-flow', 'overflow', 'grid-template']}],
// @ts-expect-error - https://github.com/stylelint-types/stylelint-define-config/issues/1
'declaration-property-unit-disallowed-list': {'line-height': ['em']}, 'declaration-property-unit-disallowed-list': {'line-height': ['em']},
'declaration-property-value-disallowed-list': {'word-break': ['break-word']}, 'declaration-property-value-disallowed-list': {'word-break': ['break-word']},
'font-family-name-quotes': 'always-where-recommended', 'font-family-name-quotes': 'always-where-recommended',
@@ -148,4 +146,4 @@ export default defineConfig({
'shorthand-property-no-redundant-values': true, 'shorthand-property-no-redundant-values': true,
'value-no-vendor-prefix': [true, {ignoreValues: ['box', 'inline-box']}], 'value-no-vendor-prefix': [true, {ignoreValues: ['box', 'inline-box']}],
}, },
}); } satisfies Config;

View File

@@ -2,17 +2,18 @@ import {readFileSync} from 'node:fs';
import {env} from 'node:process'; import {env} from 'node:process';
import {parse} from 'postcss'; import {parse} from 'postcss';
import plugin from 'tailwindcss/plugin.js'; import plugin from 'tailwindcss/plugin.js';
import type {Config} from 'tailwindcss';
const isProduction = env.NODE_ENV !== 'development'; const isProduction = env.NODE_ENV !== 'development';
function extractRootVars(css) { function extractRootVars(css: string) {
const root = parse(css); const root = parse(css);
const vars = new Set(); const vars = new Set<string>();
root.walkRules((rule) => { root.walkRules((rule) => {
if (rule.selector !== ':root') return; if (rule.selector !== ':root') return;
rule.each((decl) => { rule.each((node) => {
if (decl.value && decl.prop.startsWith('--')) { if (node.type === 'decl' && node.value && node.prop.startsWith('--')) {
vars.add(decl.prop.substring(2)); vars.add(node.prop.substring(2));
} }
}); });
}); });
@@ -120,4 +121,4 @@ export default {
}); });
}), }),
], ],
}; } satisfies Config;

View File

@@ -4,12 +4,7 @@ import {optimize} from 'svgo';
import {readFile, writeFile} from 'node:fs/promises'; import {readFile, writeFile} from 'node:fs/promises';
import {argv, exit} from 'node:process'; import {argv, exit} from 'node:process';
function doExit(err) { async function generate(svg: string, path: string, {size, bg}: {size: number, bg?: boolean}) {
if (err) console.error(err);
exit(err ? 1 : 0);
}
async function generate(svg, path, {size, bg}) {
const outputFile = new URL(path, import.meta.url); const outputFile = new URL(path, import.meta.url);
if (String(outputFile).endsWith('.svg')) { if (String(outputFile).endsWith('.svg')) {
@@ -19,7 +14,9 @@ async function generate(svg, path, {size, bg}) {
'removeDimensions', 'removeDimensions',
{ {
name: 'addAttributesToSVGElement', name: 'addAttributesToSVGElement',
params: {attributes: [{width: size}, {height: size}]}, params: {
attributes: [{width: String(size)}, {height: String(size)}],
},
}, },
], ],
}); });
@@ -57,7 +54,8 @@ async function main() {
} }
try { try {
doExit(await main()); await main();
} catch (err) { } catch (err) {
doExit(err); console.error(err);
exit(1);
} }

View File

@@ -1,27 +1,29 @@
#!/usr/bin/env node #!/usr/bin/env node
import fastGlob from 'fast-glob';
import {optimize} from 'svgo'; import {optimize} from 'svgo';
import {parse} from 'node:path'; import {dirname, parse} from 'node:path';
import {globSync, writeFileSync} from 'node:fs';
import {readFile, writeFile, mkdir} from 'node:fs/promises'; import {readFile, writeFile, mkdir} from 'node:fs/promises';
import {fileURLToPath} from 'node:url'; import {fileURLToPath} from 'node:url';
import {exit} from 'node:process'; import {exit} from 'node:process';
import * as fs from 'node:fs'; import type {Manifest} from 'material-icon-theme';
const glob = (pattern) => fastGlob.sync(pattern, { const glob = (pattern: string) => globSync(pattern, {cwd: dirname(import.meta.dirname)});
cwd: fileURLToPath(new URL('..', import.meta.url)),
absolute: true,
});
async function processAssetsSvgFile(file, {prefix, fullName} = {}) { type Opts = {
prefix?: string,
fullName?: string,
};
async function processAssetsSvgFile(path: string, {prefix, fullName}: Opts = {}) {
let name = fullName; let name = fullName;
if (!name) { if (!name) {
name = parse(file).name; name = parse(path).name;
if (prefix) name = `${prefix}-${name}`; if (prefix) name = `${prefix}-${name}`;
if (prefix === 'octicon') name = name.replace(/-[0-9]+$/, ''); // chop of '-16' on octicons if (prefix === 'octicon') name = name.replace(/-[0-9]+$/, ''); // chop of '-16' on octicons
} }
// Set the `xmlns` attribute so that the files are displayable in standalone documents // Set the `xmlns` attribute so that the files are displayable in standalone documents
// The svg backend module will strip the attribute during startup for inline display // The svg backend module will strip the attribute during startup for inline display
const {data} = optimize(await readFile(file, 'utf8'), { const {data} = optimize(await readFile(path, 'utf8'), {
plugins: [ plugins: [
{name: 'preset-default'}, {name: 'preset-default'},
{name: 'removeDimensions'}, {name: 'removeDimensions'},
@@ -41,16 +43,16 @@ async function processAssetsSvgFile(file, {prefix, fullName} = {}) {
await writeFile(fileURLToPath(new URL(`../public/assets/img/svg/${name}.svg`, import.meta.url)), data); await writeFile(fileURLToPath(new URL(`../public/assets/img/svg/${name}.svg`, import.meta.url)), data);
} }
function processAssetsSvgFiles(pattern, opts) { function processAssetsSvgFiles(pattern: string, opts: Opts = {}) {
return glob(pattern).map((file) => processAssetsSvgFile(file, opts)); return glob(pattern).map((path) => processAssetsSvgFile(path, opts));
} }
async function processMaterialFileIcons() { async function processMaterialFileIcons() {
const files = glob('node_modules/material-icon-theme/icons/*.svg'); const paths = glob('node_modules/material-icon-theme/icons/*.svg');
const svgSymbols = {}; const svgSymbols: Record<string, string> = {};
for (const file of files) { for (const path of paths) {
// remove all unnecessary attributes, only keep "viewBox" // remove all unnecessary attributes, only keep "viewBox"
const {data} = optimize(await readFile(file, 'utf8'), { const {data} = optimize(await readFile(path, 'utf8'), {
plugins: [ plugins: [
{name: 'preset-default'}, {name: 'preset-default'},
{name: 'removeDimensions'}, {name: 'removeDimensions'},
@@ -58,16 +60,16 @@ async function processMaterialFileIcons() {
{name: 'removeAttrs', params: {attrs: 'xml:space', elemSeparator: ','}}, {name: 'removeAttrs', params: {attrs: 'xml:space', elemSeparator: ','}},
], ],
}); });
const svgName = parse(file).name; const svgName = parse(path).name;
// intentionally use single quote here to avoid escaping // intentionally use single quote here to avoid escaping
svgSymbols[svgName] = data.replace(/"/g, `'`); svgSymbols[svgName] = data.replace(/"/g, `'`);
} }
fs.writeFileSync(fileURLToPath(new URL(`../options/fileicon/material-icon-svgs.json`, import.meta.url)), JSON.stringify(svgSymbols, null, 2)); writeFileSync(fileURLToPath(new URL(`../options/fileicon/material-icon-svgs.json`, import.meta.url)), JSON.stringify(svgSymbols, null, 2));
const vscodeExtensionsJson = await readFile(fileURLToPath(new URL(`generate-svg-vscode-extensions.json`, import.meta.url))); const vscodeExtensionsJson = await readFile(fileURLToPath(new URL(`generate-svg-vscode-extensions.json`, import.meta.url)), 'utf8');
const vscodeExtensions = JSON.parse(vscodeExtensionsJson); const vscodeExtensions = JSON.parse(vscodeExtensionsJson) as Record<string, string>;
const iconRulesJson = await readFile(fileURLToPath(new URL(`../node_modules/material-icon-theme/dist/material-icons.json`, import.meta.url))); const iconRulesJson = await readFile(fileURLToPath(new URL(`../node_modules/material-icon-theme/dist/material-icons.json`, import.meta.url)), 'utf8');
const iconRules = JSON.parse(iconRulesJson); const iconRules = JSON.parse(iconRulesJson) as Manifest;
// The rules are from VSCode material-icon-theme, we need to adjust them to our needs // The rules are from VSCode material-icon-theme, we need to adjust them to our needs
// 1. We only use lowercase filenames to match (it should be good enough for most cases and more efficient) // 1. We only use lowercase filenames to match (it should be good enough for most cases and more efficient)
// 2. We do not have a "Language ID" system: // 2. We do not have a "Language ID" system:
@@ -91,7 +93,7 @@ async function processMaterialFileIcons() {
} }
} }
const iconRulesPretty = JSON.stringify(iconRules, null, 2); const iconRulesPretty = JSON.stringify(iconRules, null, 2);
fs.writeFileSync(fileURLToPath(new URL(`../options/fileicon/material-icon-rules.json`, import.meta.url)), iconRulesPretty); writeFileSync(fileURLToPath(new URL(`../options/fileicon/material-icon-rules.json`, import.meta.url)), iconRulesPretty);
} }
async function main() { async function main() {

View File

@@ -1,11 +1,10 @@
#!/usr/bin/env node #!/usr/bin/env node
import {readdirSync, readFileSync} from 'node:fs'; import {readdirSync, readFileSync, globSync} from 'node:fs';
import {parse, relative} from 'node:path'; import {parse, relative} from 'node:path';
import {fileURLToPath} from 'node:url'; import {fileURLToPath} from 'node:url';
import {exit} from 'node:process'; import {exit} from 'node:process';
import fastGlob from 'fast-glob';
const knownSvgs = new Set(); const knownSvgs = new Set<string>();
for (const file of readdirSync(new URL('../public/assets/img/svg', import.meta.url))) { for (const file of readdirSync(new URL('../public/assets/img/svg', import.meta.url))) {
knownSvgs.add(parse(file).name); knownSvgs.add(parse(file).name);
} }
@@ -13,7 +12,7 @@ for (const file of readdirSync(new URL('../public/assets/img/svg', import.meta.u
const rootPath = fileURLToPath(new URL('..', import.meta.url)); const rootPath = fileURLToPath(new URL('..', import.meta.url));
let hadErrors = false; let hadErrors = false;
for (const file of fastGlob.sync(fileURLToPath(new URL('../templates/**/*.tmpl', import.meta.url)))) { for (const file of globSync(fileURLToPath(new URL('../templates/**/*.tmpl', import.meta.url)))) {
const content = readFileSync(file, 'utf8'); const content = readFileSync(file, 'utf8');
for (const [_, name] of content.matchAll(/svg ["'`]([^"'`]+)["'`]/g)) { for (const [_, name] of content.matchAll(/svg ["'`]([^"'`]+)["'`]/g)) {
if (!knownSvgs.has(name)) { if (!knownSvgs.has(name)) {

View File

@@ -41,6 +41,7 @@
"types": [ "types": [
"vitest/globals", "vitest/globals",
"./web_src/js/globals.d.ts", "./web_src/js/globals.d.ts",
"./types.d.ts",
], ],
} }
} }

9
types.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
declare module 'add-asset-webpack-plugin' {
const plugin: any;
export = plugin
}
declare module '@techknowlogick/license-checker-webpack-plugin' {
const plugin: any;
export = plugin
}

View File

@@ -1,3 +1,5 @@
import type {Config} from 'updates';
export default { export default {
exclude: [ exclude: [
'@mcaptcha/vanilla-glue', // breaking changes in rc versions need to be handled '@mcaptcha/vanilla-glue', // breaking changes in rc versions need to be handled
@@ -11,4 +13,4 @@ export default {
'eslint-plugin-vitest', // need to migrate to eslint flat config first 'eslint-plugin-vitest', // need to migrate to eslint flat config first
'tailwindcss', // need to migrate 'tailwindcss', // need to migrate
], ],
}; } satisfies Config;

View File

@@ -1,4 +1,3 @@
import fastGlob from 'fast-glob';
import wrapAnsi from 'wrap-ansi'; import wrapAnsi from 'wrap-ansi';
import AddAssetPlugin from 'add-asset-webpack-plugin'; import AddAssetPlugin from 'add-asset-webpack-plugin';
import LicenseCheckerWebpackPlugin from '@techknowlogick/license-checker-webpack-plugin'; import LicenseCheckerWebpackPlugin from '@techknowlogick/license-checker-webpack-plugin';
@@ -6,28 +5,23 @@ import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin'; import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin';
import {VueLoaderPlugin} from 'vue-loader'; import {VueLoaderPlugin} from 'vue-loader';
import EsBuildLoader from 'esbuild-loader'; import EsBuildLoader from 'esbuild-loader';
import {parse, dirname} from 'node:path'; import {parse} from 'node:path';
import webpack from 'webpack'; import webpack, {type Configuration, type EntryObject} from 'webpack';
import {fileURLToPath} from 'node:url'; import {fileURLToPath} from 'node:url';
import {readFileSync} from 'node:fs'; import {readFileSync, globSync} from 'node:fs';
import {env} from 'node:process'; import {env} from 'node:process';
import tailwindcss from 'tailwindcss'; import tailwindcss from 'tailwindcss';
import tailwindConfig from './tailwind.config.js'; import tailwindConfig from './tailwind.config.ts';
import tailwindcssNesting from 'tailwindcss/nesting/index.js'; import tailwindcssNesting from 'tailwindcss/nesting/index.js';
import postcssNesting from 'postcss-nesting'; import postcssNesting from 'postcss-nesting';
const {EsbuildPlugin} = EsBuildLoader; const {EsbuildPlugin} = EsBuildLoader;
const {SourceMapDevToolPlugin, DefinePlugin, EnvironmentPlugin} = webpack; const {SourceMapDevToolPlugin, DefinePlugin, EnvironmentPlugin} = webpack;
const formatLicenseText = (licenseText) => wrapAnsi(licenseText || '', 80).trim(); const formatLicenseText = (licenseText: string) => wrapAnsi(licenseText || '', 80).trim();
const glob = (pattern) => fastGlob.sync(pattern, { const themes: EntryObject = {};
cwd: dirname(fileURLToPath(new URL(import.meta.url))), for (const path of globSync('web_src/css/themes/*.css', {cwd: import.meta.dirname})) {
absolute: true, themes[parse(path).name] = [`./${path}`];
});
const themes = {};
for (const path of glob('web_src/css/themes/*.css')) {
themes[parse(path).name] = [path];
} }
const isProduction = env.NODE_ENV !== 'development'; const isProduction = env.NODE_ENV !== 'development';
@@ -55,12 +49,12 @@ const webComponents = new Set([
'text-expander', 'text-expander',
]); ]);
const filterCssImport = (url, ...args) => { const filterCssImport = (url: string, ...args: Array<any>) => {
const cssFile = args[1] || args[0]; // resourcePath is 2nd argument for url and 3rd for import const cssFile = args[1] || args[0]; // resourcePath is 2nd argument for url and 3rd for import
const importedFile = url.replace(/[?#].+/, '').toLowerCase(); const importedFile = url.replace(/[?#].+/, '').toLowerCase();
if (cssFile.includes('fomantic')) { if (cssFile.includes('fomantic')) {
if (/brand-icons/.test(importedFile)) return false; if (importedFile.includes('brand-icons')) return false;
if (/(eot|ttf|otf|woff|svg)$/i.test(importedFile)) return false; if (/(eot|ttf|otf|woff|svg)$/i.test(importedFile)) return false;
} }
@@ -71,7 +65,6 @@ const filterCssImport = (url, ...args) => {
return true; return true;
}; };
/** @type {import("webpack").Configuration} */
export default { export default {
mode: isProduction ? 'production' : 'development', mode: isProduction ? 'production' : 'development',
entry: { entry: {
@@ -100,7 +93,7 @@ export default {
path: fileURLToPath(new URL('public/assets', import.meta.url)), path: fileURLToPath(new URL('public/assets', import.meta.url)),
filename: () => 'js/[name].js', filename: () => 'js/[name].js',
chunkFilename: ({chunk}) => { chunkFilename: ({chunk}) => {
const language = (/monaco.*languages?_.+?_(.+?)_/.exec(chunk.id) || [])[1]; const language = (/monaco.*languages?_.+?_(.+?)_/.exec(String(chunk.id)) || [])[1];
return `js/${language ? `monaco-language-${language.toLowerCase()}` : `[name]`}.[contenthash:8].js`; return `js/${language ? `monaco-language-${language.toLowerCase()}` : `[name]`}.[contenthash:8].js`;
}, },
}, },
@@ -129,7 +122,7 @@ export default {
loader: 'vue-loader', loader: 'vue-loader',
options: { options: {
compilerOptions: { compilerOptions: {
isCustomElement: (tag) => webComponents.has(tag), isCustomElement: (tag: string) => webComponents.has(tag),
}, },
}, },
}, },
@@ -225,10 +218,10 @@ export default {
}), }),
isProduction ? new LicenseCheckerWebpackPlugin({ isProduction ? new LicenseCheckerWebpackPlugin({
outputFilename: 'licenses.txt', outputFilename: 'licenses.txt',
outputWriter: ({dependencies}) => { outputWriter: ({dependencies}: {dependencies: Array<Record<string, string>>}) => {
const line = '-'.repeat(80); const line = '-'.repeat(80);
const goJson = readFileSync('assets/go-licenses.json', 'utf8'); const goJson = readFileSync('assets/go-licenses.json', 'utf8');
const goModules = JSON.parse(goJson).map(({name, licenseText}) => { const goModules = JSON.parse(goJson).map(({name, licenseText}: Record<string, string>) => {
return {name, body: formatLicenseText(licenseText)}; return {name, body: formatLicenseText(licenseText)};
}); });
const jsModules = dependencies.map(({name, version, licenseName, licenseText}) => { const jsModules = dependencies.map(({name, version, licenseName, licenseText}) => {
@@ -285,4 +278,4 @@ export default {
reasons: false, reasons: false,
runtimeModules: false, runtimeModules: false,
}, },
}; } satisfies Configuration;