From f4cc0c4618237fc1b569fd55eaddd9144392c319 Mon Sep 17 00:00:00 2001 From: Mona LatifAghili Date: Tue, 30 Jun 2026 13:04:43 +0200 Subject: [PATCH 1/6] build(sdkjs): migrate build toolchain from Grunt to webpack Signed-off-by: Mona LatifAghili --- .eslintignore | 16 +++ build/dummy.js | 2 + build/loaders/sdk-concat.cjs | 195 +++++++++++++++++++++++++++ build/package.json | 17 ++- build/package.json.webpack | 22 +++ build/scripts/build-develop.js | 185 ++++++++++++++++++++++++++ build/scripts/build-pipeline.js | 228 ++++++++++++++++++++++++++++++++ build/scripts/deploy-assets.js | 157 ++++++++++++++++++++++ build/webpack.cell.mjs | 2 + build/webpack.sdk.factory.mjs | 178 +++++++++++++++++++++++++ build/webpack.slide.mjs | 2 + build/webpack.visio.mjs | 2 + build/webpack.word.mjs | 2 + 13 files changed, 1004 insertions(+), 4 deletions(-) create mode 100644 .eslintignore create mode 100644 build/dummy.js create mode 100644 build/loaders/sdk-concat.cjs create mode 100644 build/package.json.webpack create mode 100644 build/scripts/build-develop.js create mode 100644 build/scripts/build-pipeline.js create mode 100644 build/scripts/deploy-assets.js create mode 100644 build/webpack.cell.mjs create mode 100644 build/webpack.sdk.factory.mjs create mode 100644 build/webpack.slide.mjs create mode 100644 build/webpack.visio.mjs create mode 100644 build/webpack.word.mjs diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000000..09ccc3da1a --- /dev/null +++ b/.eslintignore @@ -0,0 +1,16 @@ +# Build outputs — generated, not source +deploy/ +develop/ +build/node_modules/ + +# Vendor / third-party libraries — not our code +vendor/ +common/zlib/ +common/libfont/ + +# Generated / compiled PDF engine files (28k+ lines, not hand-written) +pdf/src/engine/drawingfile_ie.js +pdf/build/ + +# Test fixtures that are not JS source +tests/ diff --git a/build/dummy.js b/build/dummy.js new file mode 100644 index 0000000000..f88f9e2f9c --- /dev/null +++ b/build/dummy.js @@ -0,0 +1,2 @@ +// Webpack entry resource for sdk-concat-loader. +// Content is irrelevant — the loader reads JSON configs and concatenates all SDK source files. diff --git a/build/loaders/sdk-concat.cjs b/build/loaders/sdk-concat.cjs new file mode 100644 index 0000000000..2df90d6da8 --- /dev/null +++ b/build/loaders/sdk-concat.cjs @@ -0,0 +1,195 @@ +/** + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + */ + +/** + * sdk-concat-loader + * + * Reads the ordered SDK JSON configs and returns ALL source files for a given + * module/chunk as a single concatenated module. This is the only correct way to + * bundle sdkjs under webpack: 69+ files across the word SDK alone use bare + * top-level `var` declarations (no IIFE) that communicate across file boundaries + * via concatenated scope. Putting all files into ONE webpack module preserves + * that scope — every bare `var` is visible to every other file in the same chunk. + * + * Mirrors the CConfig + getFilesMin/getFilesAll logic from the original Gruntfile.js. + * + * Options (webpack loader options object): + * module {string} 'word' | 'cell' | 'slide' | 'visio' required + * chunk {string} 'min' | 'all' required + * platform {string} '' | 'desktop' | 'mobile' default '' + * srcRoot {string} absolute path to sdkjs root (one level above build/) + * addonDirs {string[]} absolute paths to addon directories + */ + +'use strict'; + +const path = require('path'); +const fs = require('fs'); + +// --------------------------------------------------------------------------- +// Config loading — exact port of CConfig.prototype.append from Gruntfile.js +// --------------------------------------------------------------------------- + +function loadJsonConfig(configsDir, name) { + const file = path.join(configsDir, name + '.json'); + if (!fs.existsSync(file)) return null; + try { + return JSON.parse(fs.readFileSync(file, 'utf8')); + } catch (e) { + throw new Error(`sdk-concat-loader: failed to parse ${file}: ${e.message}`); + } +} + +function fixPath(obj, basePath) { + if (Array.isArray(obj)) { + for (let i = 0; i < obj.length; i++) { + obj[i] = path.join(basePath, obj[i]); + } + return; + } + for (const k of Object.keys(obj)) { + fixPath(obj[k], basePath); + } +} + +function mergeConfigs(base, addon) { + for (const k of Object.keys(addon)) { + if (Array.isArray(addon[k])) { + base[k] = Array.isArray(base[k]) ? base[k].concat(addon[k]) : addon[k]; + } else { + if (!base[k]) base[k] = {}; + mergeConfigs(base[k], addon[k]); + } + } +} + +function loadAllConfigs(srcRoot, addonDirs) { + const configs = {}; + const configsDir = path.join(srcRoot, 'configs'); + + for (const name of ['word', 'cell', 'slide', 'visio']) { + const cfg = loadJsonConfig(configsDir, name); + if (cfg) { + fixPath(cfg, srcRoot); + configs[name] = cfg; + } + } + + for (const addonDir of (addonDirs || [])) { + for (const name of ['word', 'cell', 'slide', 'visio']) { + if (!configs[name]) continue; + const addon = loadJsonConfig(path.join(addonDir, 'configs'), name); + if (!addon) continue; + fixPath(addon, addonDir); + mergeConfigs(configs[name], addon); + } + } + + return configs; +} + +// --------------------------------------------------------------------------- +// File list helpers — exact port of getFilesMin/getFilesAll from Gruntfile.js +// --------------------------------------------------------------------------- + +function getFilesMin(sdkCfg, platform) { + let files = (sdkCfg['min'] || []).slice(); + if (platform === 'mobile' && sdkCfg['mobile_banners']) { + files = sdkCfg['mobile_banners']['min'].concat(files); + } + if (platform === 'desktop' && sdkCfg['desktop']) { + files = files.concat(sdkCfg['desktop']['min'] || []); + } + return files; +} + +function getFilesAll(sdkCfg, platform) { + let files = (sdkCfg['common'] || []).slice(); + if (platform === 'mobile') { + if (sdkCfg['mobile_banners']) { + files = sdkCfg['mobile_banners']['common'].concat(files); + } + const exclude = sdkCfg['exclude_mobile'] || []; + files = files.filter(f => !exclude.includes(f)); + files = files.concat(sdkCfg['mobile'] || []); + } + if (platform === 'desktop' && sdkCfg['desktop']) { + files = files.concat(sdkCfg['desktop']['common'] || []); + } + return files; +} + +// --------------------------------------------------------------------------- +// Loader +// --------------------------------------------------------------------------- + +module.exports = function sdkConcatLoader() { + // this.resourcePath is dummy.js — its content is irrelevant; we ignore it. + const opts = this.getOptions(); + const srcRoot = path.resolve(opts.srcRoot || path.join(this.context, '..')); + const platform = opts.platform || ''; + const addonDirs = opts.addonDirs || []; + + const configs = loadAllConfigs(srcRoot, addonDirs); + const sdkCfg = configs[opts.module] && configs[opts.module]['sdk']; + + if (!sdkCfg) { + this.emitError(new Error(`sdk-concat-loader: no config found for module "${opts.module}" at ${srcRoot}`)); + return ''; + } + + const files = opts.chunk === 'min' + ? getFilesMin(sdkCfg, platform) + : getFilesAll(sdkCfg, platform); + + // Register every source file as a webpack dependency so watch mode works. + for (const f of files) { + this.addDependency(path.resolve(f)); + } + // Watch the config file for this module so a config change triggers a rebuild. + this.addDependency(path.join(srcRoot, 'configs', opts.module + '.json')); + + const parts = []; + for (const f of files) { + try { + parts.push(fs.readFileSync(f, 'utf8')); + } catch (e) { + this.emitError(new Error(`sdk-concat-loader: cannot read ${f}: ${e.message}`)); + parts.push(''); + } + } + + const content = parts.join('\n'); + + // sdk-all.js: wrap in (function(window, undefined){...})(window) to match + // the original Closure Compiler --chunk_wrapper for the sdk-all chunk. + // sdk-all-min.js: no wrapper — it exposes bootstrap globals consumed by sdk-all.js. + return opts.chunk === 'all' + ? `(function(window, undefined) {\n${content}\n})(window);` + : content; +}; + +module.exports.schema = { + type: 'object', + properties: { + module: { type: 'string', enum: ['word', 'cell', 'slide', 'visio'] }, + chunk: { type: 'string', enum: ['min', 'all'] }, + platform: { type: 'string', enum: ['', 'desktop', 'mobile'] }, + srcRoot: { type: 'string' }, + addonDirs: { type: 'array', items: { type: 'string' } }, + }, + required: ['module', 'chunk'], + additionalProperties: false, +}; diff --git a/build/package.json b/build/package.json index c86d680c0b..c665e5b15f 100644 --- a/build/package.json +++ b/build/package.json @@ -3,11 +3,20 @@ "version": "0.0.0", "homepage": "https://www.onlyoffice.com", "private": true, + "type": "module", "dependencies": { "glob": "^8.1.0", - "google-closure-compiler": "^20240317.0.0", - "grunt": "^1.6.1", - "grunt-contrib-clean": "^2.0.0", - "grunt-contrib-copy": "^1.0.0" + "terser": "^5.20.0", + "terser-webpack-plugin": "^5.3.11", + "webpack": "^5.98.0", + "webpack-cli": "^6.0.1" + }, + "scripts": { + "build": "node scripts/build-pipeline.js", + "build:word": "webpack --config webpack.word.mjs", + "build:cell": "webpack --config webpack.cell.mjs", + "build:slide": "webpack --config webpack.slide.mjs", + "build:visio": "webpack --config webpack.visio.mjs", + "develop": "node scripts/build-develop.js" } } diff --git a/build/package.json.webpack b/build/package.json.webpack new file mode 100644 index 0000000000..c665e5b15f --- /dev/null +++ b/build/package.json.webpack @@ -0,0 +1,22 @@ +{ + "name": "common", + "version": "0.0.0", + "homepage": "https://www.onlyoffice.com", + "private": true, + "type": "module", + "dependencies": { + "glob": "^8.1.0", + "terser": "^5.20.0", + "terser-webpack-plugin": "^5.3.11", + "webpack": "^5.98.0", + "webpack-cli": "^6.0.1" + }, + "scripts": { + "build": "node scripts/build-pipeline.js", + "build:word": "webpack --config webpack.word.mjs", + "build:cell": "webpack --config webpack.cell.mjs", + "build:slide": "webpack --config webpack.slide.mjs", + "build:visio": "webpack --config webpack.visio.mjs", + "develop": "node scripts/build-develop.js" + } +} diff --git a/build/scripts/build-develop.js b/build/scripts/build-develop.js new file mode 100644 index 0000000000..b2bf5ebdce --- /dev/null +++ b/build/scripts/build-develop.js @@ -0,0 +1,185 @@ +#!/usr/bin/env node +/** + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + */ + +'use strict'; + +// Replaces the grunt build-develop task (writeScripts function in Gruntfile.js). +// +// Generates develop/sdkjs/{module}/scripts.js for each module (word/cell/slide/visio). +// The file contains a `var sdk_scripts = [...]` array of relative URLs to all +// uncompiled source files, used by the development HTML loader to load SDK +// without a build step. +// +// Mirrors writeScripts() + fixUrl() from Gruntfile.js exactly. +// +// Options (env vars): +// BUILD_ROOT if set, writes to $BUILD_ROOT/sdkjs/develop/sdkjs/{module}/scripts.js +// SDK_PLATFORM '' | 'desktop' | 'mobile' +// SDK_ADDONS path.delimiter-separated addon directories +// COMPILED set to '1' to reference built sdk-all-min.js instead of source files + +const path = require('path'); +const fs = require('fs'); +const url = require('url'); + +const BUILD_DIR = path.resolve(__dirname, '..'); +const SRC_ROOT = path.resolve(BUILD_DIR, '..'); + +const BUILD_ROOT = process.env.BUILD_ROOT + ? path.resolve(process.env.BUILD_ROOT, 'sdkjs') + : path.resolve(BUILD_DIR, '..', 'deploy', 'sdkjs'); + +const DEVELOP_ROOT = process.env.BUILD_ROOT + ? path.join(process.env.BUILD_ROOT, 'sdkjs', 'develop', 'sdkjs') + : path.join(BUILD_DIR, '..', 'develop', 'sdkjs'); + +const platform = process.env.SDK_PLATFORM || ''; +const addonDirs = process.env.SDK_ADDONS + ? process.env.SDK_ADDONS.split(path.delimiter).filter(Boolean) + : []; +const compiled = process.env.COMPILED === '1'; + +// ---- Config loading (mirrors CConfig from Gruntfile.js) -------------------- + +function loadJsonConfig(configsDir, name) { + const file = path.join(configsDir, name + '.json'); + if (!fs.existsSync(file)) return null; + return JSON.parse(fs.readFileSync(file, 'utf8')); +} + +function fixPath(obj, basePath) { + if (Array.isArray(obj)) { + for (let i = 0; i < obj.length; i++) obj[i] = path.join(basePath, obj[i]); + return; + } + for (const k of Object.keys(obj)) fixPath(obj[k], basePath); +} + +function mergeConfigs(base, addon) { + for (const k of Object.keys(addon)) { + if (Array.isArray(addon[k])) { + base[k] = Array.isArray(base[k]) ? base[k].concat(addon[k]) : addon[k]; + } else { + if (!base[k]) base[k] = {}; + mergeConfigs(base[k], addon[k]); + } + } +} + +function loadAllConfigs() { + const configs = {}; + const configsDir = path.join(SRC_ROOT, 'configs'); + for (const name of ['word', 'cell', 'slide', 'visio']) { + const cfg = loadJsonConfig(configsDir, name); + if (cfg) { fixPath(cfg, SRC_ROOT); configs[name] = cfg; } + } + for (const addonDir of addonDirs) { + for (const name of ['word', 'cell', 'slide', 'visio']) { + if (!configs[name]) continue; + const addon = loadJsonConfig(path.join(addonDir, 'configs'), name); + if (!addon) continue; + fixPath(addon, addonDir); + mergeConfigs(configs[name], addon); + } + } + return configs; +} + +function getFilesMin(sdkCfg) { + let files = (sdkCfg['min'] || []).slice(); + if (platform === 'mobile' && sdkCfg['mobile_banners']) { + files = sdkCfg['mobile_banners']['min'].concat(files); + } + if (platform === 'desktop' && sdkCfg['desktop']) { + files = files.concat(sdkCfg['desktop']['min']); + } + return files; +} + +function getFilesAll(sdkCfg) { + let files = (sdkCfg['common'] || []).slice(); + if (platform === 'mobile') { + if (sdkCfg['mobile_banners']) { + files = sdkCfg['mobile_banners']['common'].concat(files); + } + const exclude = sdkCfg['exclude_mobile'] || []; + files = files.filter(f => !exclude.includes(f)); + files = files.concat(sdkCfg['mobile'] || []); + } + if (platform === 'desktop' && sdkCfg['desktop']) { + files = files.concat(sdkCfg['desktop']['common']); + } + return files; +} + +// ---- writeScripts (exact port of writeScripts() from Gruntfile.js) --------- + +function fixUrl(arrPaths, basePath) { + return arrPaths.map(p => url.resolve(basePath, p)); +} + +function writeScripts(sdkCfg, name) { + let files = [ + path.join(SRC_ROOT, 'vendor', 'polyfill.js'), + path.join(SRC_ROOT, 'common', 'AllFonts.js'), + ]; + + if (compiled) { + if (process.env.BUILD_ROOT) { + files.push(path.join('..', name, 'sdk-all-min.js')); + } else { + files.push(path.join(BUILD_ROOT, name, 'sdk-all-min.js')); + } + } else { + files = files.concat( + [path.join(SRC_ROOT, 'common', 'applyDocumentChanges.js')], + getFilesMin(sdkCfg), + getFilesAll(sdkCfg), + ); + } + + // Convert absolute paths to relative URL strings anchored at build/ + // (mirrors fixUrl(files, '../../../../sdkjs/build/') from Gruntfile.js) + files = fixUrl( + files.map(f => path.relative(BUILD_DIR, f)), + '../../../../sdkjs/build/', + ); + + const outDir = path.join(DEVELOP_ROOT, name); + const outFile = path.join(outDir, 'scripts.js'); + fs.mkdirSync(outDir, { recursive: true }); + fs.writeFileSync( + outFile, + 'var sdk_scripts = [\n\t"' + files.join('",\n\t"') + '"\n];', + 'utf8', + ); + process.stdout.write(`build-develop: wrote ${outFile}\n`); +} + +// ---- main ------------------------------------------------------------------ + +function main() { + const configs = loadAllConfigs(); + for (const name of ['word', 'cell', 'slide', 'visio']) { + if (!configs[name]) { + process.stderr.write(`build-develop: no config for ${name}, skipping\n`); + continue; + } + writeScripts(configs[name]['sdk'], name); + } +} + +main(); diff --git a/build/scripts/build-pipeline.js b/build/scripts/build-pipeline.js new file mode 100644 index 0000000000..4883d28e8c --- /dev/null +++ b/build/scripts/build-pipeline.js @@ -0,0 +1,228 @@ +#!/usr/bin/env node +/** + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + */ + +'use strict'; + +// Full grunt-free build pipeline for Euro Office sdkjs. +// +// Usage (from sdkjs/build/): +// PRODUCT_VERSION=9.2.1 BUILD_ROOT=/path/to/deploy node scripts/build-pipeline.js +// +// Options (env vars): +// PRODUCT_VERSION default '0.0.0' +// BUILD_ROOT default ../deploy/sdkjs +// BUILD_NUMBER default '0' +// COMPANY_NAME default 'onlyoffice' +// SDK_PLATFORM '' | 'desktop' | 'mobile' — passed through to webpack configs +// SDK_ADDONS path.delimiter-separated addon directories +// SKIP_DEVELOP set to '1' to skip develop scripts generation +// +// Phase layout (wall-clock optimised): +// Phase 1 — parallel: deploy-assets + webpack ×4 (word, cell, slide, visio) +// Each webpack config runs 2 compiler configs (min + all chunk) in parallel. +// Phase 2 — sequential: build-develop (writes develop/sdkjs/{module}/scripts.js) + +const { spawn } = require('child_process'); +const path = require('path'); +const fs = require('fs'); + +const BUILD_DIR = path.resolve(__dirname, '..'); + +const BUILD_ROOT = process.env.BUILD_ROOT + ? path.resolve(process.env.BUILD_ROOT, 'sdkjs') + : path.resolve(BUILD_DIR, '..', 'deploy', 'sdkjs'); + +const PRODUCT_VERSION = process.env.PRODUCT_VERSION || '0.0.0'; +const BUILD_NUMBER = String(process.env.BUILD_NUMBER || process.env.GITHUB_RUN_NUMBER || '0'); +const SKIP_DEVELOP = process.env.SKIP_DEVELOP === '1'; + +const CHILD_ENV = { + ...process.env, + PRODUCT_VERSION, + BUILD_NUMBER, + BUILD_ROOT: process.env.BUILD_ROOT || path.resolve(BUILD_DIR, '..', 'deploy'), +}; + +// ---- output helpers (mirrors web-apps/build/scripts/build-pipeline.js) ---- + +const BOLD = s => `\x1b[1m${s}\x1b[0m`; +const DIM = s => `\x1b[2m${s}\x1b[0m`; +const GREEN = s => `\x1b[32m${s}\x1b[0m`; +const RED = s => `\x1b[31m${s}\x1b[0m`; +const CYAN = s => `\x1b[36m${s}\x1b[0m`; +const PAD = 20; + +function elapsed(ms) { + return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`; +} + +function banner(msg) { + process.stdout.write(`\n${BOLD(CYAN('▶ ' + msg))}\n`); +} + +// ---- task runner ----------------------------------------------------------- + +function task(label, cmd, args = [], opts = {}) { + return { label, cmd, args, opts }; +} + +function runTask({ label, cmd, args, opts = {} }) { + let child = null; + const promise = new Promise(resolve => { + const start = Date.now(); + const paddedLabel = label.padEnd(PAD); + const stderrBuf = []; + + child = spawn(cmd, args, { + env: { ...CHILD_ENV, ...(opts.env || {}) }, + cwd: opts.cwd || BUILD_DIR, + stdio: ['ignore', 'pipe', 'pipe'], + }); + + child.stdout.on('data', chunk => { + for (const line of chunk.toString().split('\n')) { + if (line.trim()) process.stdout.write(` ${DIM('[' + label + ']')} ${line}\n`); + } + }); + + child.stderr.on('data', chunk => { stderrBuf.push(chunk.toString()); }); + + child.on('error', err => { + const ms = Date.now() - start; + process.stdout.write(` ${RED('✗')} ${paddedLabel} ${RED('FAILED')} ${DIM(elapsed(ms))}\n`); + process.stderr.write(` spawn error: ${err.message}\n`); + resolve({ label, ms, code: 1 }); + }); + + child.on('exit', (code, signal) => { + const ms = Date.now() - start; + if (signal) { + process.stdout.write(` ${DIM('○')} ${paddedLabel} ${DIM('killed ' + elapsed(ms))}\n`); + if (stderrBuf.length) process.stderr.write(stderrBuf.join('')); + resolve({ label, ms, code: -1 }); + } else if (code === 0) { + process.stdout.write(` ${GREEN('✓')} ${paddedLabel} ${DIM(elapsed(ms))}\n`); + resolve({ label, ms, code: 0 }); + } else { + process.stdout.write(` ${RED('✗')} ${paddedLabel} ${RED('FAILED')} ${DIM(elapsed(ms))}\n`); + if (stderrBuf.length) process.stderr.write(stderrBuf.join('')); + resolve({ label, ms, code }); + } + }); + }); + return { promise, kill: () => child && child.kill('SIGTERM'), label }; +} + +async function phase(title, taskSpecs) { + const count = taskSpecs.length; + banner(`${title} — ${count} task${count !== 1 ? 's' : ''}`); + + const running = taskSpecs.map(runTask); + let aborted = false; + + const results = await Promise.all( + running.map(t => + t.promise.then(r => { + if (r.code > 0 && !aborted) { + aborted = true; + running.forEach(o => { try { o.kill(); } catch (_) {} }); + } + return r; + }) + ) + ); + + const failed = results.filter(r => r.code > 0); + if (failed.length) { + process.stderr.write(RED(`\n✗ ${failed.map(r => r.label).join(', ')} failed — aborting\n`)); + process.exit(1); + } + return results; +} + +// ---- pipeline -------------------------------------------------------------- + +const node = process.execPath; +const wp = path.join(BUILD_DIR, 'node_modules', '.bin', 'webpack'); + +const WEBPACK_CONFIGS = [ + 'webpack.word.mjs', + 'webpack.cell.mjs', + 'webpack.slide.mjs', + 'webpack.visio.mjs', +]; + +async function main() { + const wallStart = Date.now(); + + process.stdout.write([ + BOLD('Euro Office sdkjs build pipeline'), + ` BUILD_ROOT ${BUILD_ROOT}`, + ` PRODUCT_VERSION ${PRODUCT_VERSION}`, + ` BUILD_NUMBER ${BUILD_NUMBER}`, + ` SDK_PLATFORM ${process.env.SDK_PLATFORM || '(default)'}`, + ` SKIP_DEVELOP ${SKIP_DEVELOP}`, + '', + ].join('\n')); + + // Clean deploy directory before building. + if (fs.existsSync(BUILD_ROOT)) { + fs.rmSync(BUILD_ROOT, { recursive: true, force: true }); + } + + // Phase 1: all independent work in parallel. + // - deploy-assets: copies CSS, fonts, images, themes, native JS (WHITESPACE compiled) + // - webpack ×4: each produces sdk-all-min.js + sdk-all.js for its module + const phase1Tasks = [ + task('deploy-assets', node, ['scripts/deploy-assets.js']), + ...WEBPACK_CONFIGS.map(cfg => { + const name = cfg.replace('webpack.', '').replace('.mjs', ''); + return task(`webpack:${name}`, wp, ['--config', cfg]); + }), + ]; + + const p1 = await phase('Phase 1 — parallel', phase1Tasks); + + // Phase 2: develop scripts (fast, sequential is fine). + let p2 = []; + if (!SKIP_DEVELOP) { + p2 = await phase('Phase 2 — develop', [ + task('build-develop', node, ['scripts/build-develop.js']), + ]); + } + + // Summary + const all = [...p1, ...p2]; + const wallMs = Date.now() - wallStart; + const longestLabel = Math.max(...all.map(r => r.label.length)); + + process.stdout.write([ + '', + BOLD('Summary'), + ...all.map(r => { + const mark = r.code === 0 ? GREEN('✓') : r.code < 0 ? DIM('○') : RED('✗'); + return ` ${mark} ${r.label.padEnd(longestLabel + 2)} ${DIM(elapsed(r.ms))}`; + }), + '', + ` Wall clock: ${BOLD(elapsed(wallMs))}`, + '', + ].join('\n')); +} + +main().catch(err => { + process.stderr.write(RED(`\nFatal: ${err.message || err}\n`)); + process.exit(1); +}); diff --git a/build/scripts/deploy-assets.js b/build/scripts/deploy-assets.js new file mode 100644 index 0000000000..4e2af5dd69 --- /dev/null +++ b/build/scripts/deploy-assets.js @@ -0,0 +1,157 @@ +#!/usr/bin/env node +/** + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + */ + +'use strict'; + +// Replaces the grunt copy-other and copy-standalone tasks. +// +// For each entry in the otherFiles list (mirrors Gruntfile.js): +// - Non-JS files: plain fs.cp (recursive for directories, flat for globs) +// - JS files: run through terser (WHITESPACE_ONLY equivalent) + license header +// +// JS files ignored (same ignoreFiles list as Gruntfile.js): +// jquery_native, fonts_ie, spell_ie, engine_ie, zlib_ie, drawingfile_ie, themes + +const path = require('path'); +const fs = require('fs'); +const { globSync } = require('glob'); +const { minify } = require('terser'); + +const BUILD_DIR = path.resolve(__dirname, '..'); +const SRC_ROOT = path.resolve(BUILD_DIR, '..'); + +const BUILD_ROOT = process.env.BUILD_ROOT + ? path.resolve(process.env.BUILD_ROOT, 'sdkjs') + : path.resolve(BUILD_DIR, '..', 'deploy', 'sdkjs'); + +const version = process.env.PRODUCT_VERSION || '0.0.0'; +const buildNumber = process.env.BUILD_NUMBER || '0'; +const appCopyright = process.env.APP_COPYRIGHT + || `Copyright (C) Ascensio System SIA 2012-${new Date().getFullYear()}. All rights reserved`; +const publisherUrl = process.env.PUBLISHER_URL || 'https://www.onlyoffice.com/'; + +let licenseText = fs.readFileSync(path.join(BUILD_DIR, 'license.header'), 'utf8'); +licenseText = licenseText + .replace('@@AppCopyright', appCopyright) + .replace('@@PublisherUrl', publisherUrl) + .replace('@@Version', version) + .replace('@@Build', buildNumber); + +// JS files skipped from individual minification (same as ignoreFiles in Gruntfile.js) +const IGNORE_NAMES = new Set([ + 'jquery_native', 'fonts_ie', 'spell_ie', 'engine_ie', + 'zlib_ie', 'drawingfile_ie', 'themes', +]); + +// Mirrors the otherFiles array in Gruntfile.js +const OTHER_FILES = [ + { + cwd: path.join(SRC_ROOT, 'vendor'), + src: ['polyfill.js'], + dest: path.join(BUILD_ROOT, 'vendor'), + }, + { + cwd: path.join(SRC_ROOT, 'common'), + src: [ + 'device_scale.js', + 'Drawings/Format/path-boolean-min.js', + 'Charts/ChartStyles.js', + 'SmartArts/SmartArtData/*', + 'SmartArts/SmartArtDrawing/*', + 'Images/*', + 'Images/placeholders/*', + 'Images/content_controls/*', + 'Images/cursors/*', + 'Images/reporter/*', + 'Images/icons/*', + 'Native/*.js', + 'libfont/engine/*', + 'spell/spell/*', + 'hash/hash/*', + 'zlib/engine/*', + 'serviceworker/*', + ], + dest: path.join(BUILD_ROOT, 'common'), + }, + { + cwd: path.join(SRC_ROOT, 'cell', 'css'), + src: ['*.css'], + dest: path.join(BUILD_ROOT, 'cell', 'css'), + }, + { + cwd: path.join(SRC_ROOT, 'slide', 'themes'), + src: ['**/**'], + dest: path.join(BUILD_ROOT, 'slide', 'themes'), + }, + { + cwd: path.join(SRC_ROOT, 'pdf'), + src: [ + 'src/engine/*', + 'src/annotations/stamps/*.json', + ], + dest: path.join(BUILD_ROOT, 'pdf'), + }, +]; + +async function deployJsFile(srcPath, destPath) { + const source = fs.readFileSync(srcPath, 'utf8'); + const result = await minify(source, { + compress: false, + mangle: false, + format: { comments: false }, + }); + const content = licenseText + '\n' + (result.code || source); + fs.mkdirSync(path.dirname(destPath), { recursive: true }); + fs.writeFileSync(destPath, content, 'utf8'); +} + +function deployFile(srcPath, destPath) { + fs.mkdirSync(path.dirname(destPath), { recursive: true }); + fs.copyFileSync(srcPath, destPath); +} + +async function main() { + const tasks = []; + + for (const entry of OTHER_FILES) { + const matches = []; + for (const pattern of entry.src) { + const found = globSync(pattern, { cwd: entry.cwd, nodir: true }); + for (const f of found) matches.push(f); + } + + for (const relFile of matches) { + const ext = path.extname(relFile); + const baseName = path.parse(relFile).name; + const srcPath = path.join(entry.cwd, relFile); + const destPath = path.join(entry.dest, relFile); + + if (ext === '.js' && !IGNORE_NAMES.has(baseName)) { + tasks.push(deployJsFile(srcPath, destPath)); + } else { + deployFile(srcPath, destPath); + } + } + } + + await Promise.all(tasks); + process.stdout.write(`deploy-assets: ${tasks.length} files deployed to ${BUILD_ROOT}\n`); +} + +main().catch(err => { + process.stderr.write(`deploy-assets FAILED: ${err.message}\n`); + process.exit(1); +}); diff --git a/build/webpack.cell.mjs b/build/webpack.cell.mjs new file mode 100644 index 0000000000..bc207ceea2 --- /dev/null +++ b/build/webpack.cell.mjs @@ -0,0 +1,2 @@ +import { sdkConfig } from './webpack.sdk.factory.mjs'; +export default sdkConfig('cell'); diff --git a/build/webpack.sdk.factory.mjs b/build/webpack.sdk.factory.mjs new file mode 100644 index 0000000000..96b41b9670 --- /dev/null +++ b/build/webpack.sdk.factory.mjs @@ -0,0 +1,178 @@ +/** + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + */ + +/** + * Shared webpack 5 config factory for all SDK modules (word, cell, slide, visio). + * + * sdkjs has no module system — all source files are global IIFE or bare-var scripts + * that communicate via window.AscCommon / window.AscWord etc. The sdk-concat-loader + * reads the ordered JSON configs and returns all files as ONE concatenated module, + * preserving the shared scope that bare `var` declarations depend on. + * + * Each call to sdkConfig() returns TWO webpack compiler configs: + * [0] sdk-all-min — bootstrap files (device_scale, browser, skin, API defs …) + * [1] sdk-all — full feature set, wrapped in (function(window,undefined){…})(window) + * + * Environment variables (all optional, mirror the original Gruntfile.js): + * BUILD_ROOT override deploy root; defaults to ../deploy/sdkjs + * SDK_PLATFORM '' | 'desktop' | 'mobile' + * SDK_ADDONS path.delimiter-separated list of addon directories + * COMPANY_NAME default 'onlyoffice' + * PRODUCT_VERSION default '0.0.0' + * BUILD_NUMBER default '0' + * BETA default 'false' + * APP_COPYRIGHT default 'Copyright (C) Ascensio System SIA …' + * PUBLISHER_URL default 'https://www.onlyoffice.com/' + * NODE_ENV 'production' (default) | 'development' + */ + +import webpack from 'webpack'; +import TerserPlugin from 'terser-webpack-plugin'; +import path from 'path'; +import fs from 'fs'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const CONCAT_LOADER = path.join(__dirname, 'loaders', 'sdk-concat.cjs'); +const DUMMY_ENTRY = path.join(__dirname, 'dummy.js'); + +/** + * @param {string} moduleName 'word' | 'cell' | 'slide' | 'visio' + * @returns {object[]} Two webpack compiler configs: [sdk-all-min, sdk-all] + */ +export function sdkConfig(moduleName) { + const env = process.env.NODE_ENV || 'production'; + + const BUILD_ROOT = process.env.BUILD_ROOT + ? path.resolve(process.env.BUILD_ROOT, 'sdkjs') + : path.resolve(__dirname, '..', 'deploy', 'sdkjs'); + + const SRC_ROOT = path.resolve(__dirname, '..'); + const OUT_DIR = path.join(BUILD_ROOT, moduleName); + const platform = process.env.SDK_PLATFORM || ''; + const addonDirs = process.env.SDK_ADDONS + ? process.env.SDK_ADDONS.split(path.delimiter).filter(Boolean) + : []; + + const companyName = process.env.COMPANY_NAME || 'onlyoffice'; + const version = process.env.PRODUCT_VERSION || '0.0.0'; + const buildNumber = process.env.BUILD_NUMBER || '0'; + const beta = process.env.BETA || 'false'; + + const appCopyright = process.env.APP_COPYRIGHT + || `Copyright (C) Ascensio System SIA 2012-${new Date().getFullYear()}. All rights reserved`; + const publisherUrl = process.env.PUBLISHER_URL || 'https://www.onlyoffice.com/'; + + let licenseText = fs.readFileSync(path.join(__dirname, 'license.header'), 'utf8'); + licenseText = licenseText + .replace('@@AppCopyright', appCopyright) + .replace('@@PublisherUrl', publisherUrl) + .replace('@@Version', version) + .replace('@@Build', buildNumber); + + function chunkConfig(chunk, outName) { + return { + name: `${moduleName}:${chunk}`, + mode: env, + + entry: { + [outName]: DUMMY_ENTRY, + }, + + output: { + path: OUT_DIR, + filename: '[name].js', + // iife:false — we control wrapping via the loader: + // sdk-all-min: no wrapper + // sdk-all: (function(window, undefined){…})(window) + // Letting webpack add its own ()=>{} on top would still work + // (code sets window.xxx), but iife:false gives a cleaner output. + iife: false, + // Multiple chunk configs share OUT_DIR; do not wipe sibling output. + clean: false, + }, + + module: { + rules: [ + { + // Match only our dummy entry, not real source files. + test: /[/\\]dummy\.js$/, + use: [ + { + loader: CONCAT_LOADER, + options: { + module: moduleName, + chunk, + platform, + srcRoot: SRC_ROOT, + addonDirs, + }, + }, + ], + }, + ], + }, + + plugins: [ + new webpack.BannerPlugin({ + banner: licenseText, + raw: true, + entryOnly: true, + }), + + // Replaces Closure Compiler's --define= flags. + // webpack DefinePlugin performs AST-level identifier replacement + // so dead-code branches (if (g_cIsBeta === 'true') …) are + // eliminated by TerserPlugin in the same pass. + new webpack.DefinePlugin({ + 'AscCommon.g_cCompanyName': JSON.stringify(companyName), + 'AscCommon.g_cProductVersion': JSON.stringify(version), + 'AscCommon.g_cBuildNumber': JSON.stringify(buildNumber), + 'AscCommon.g_cIsBeta': JSON.stringify(beta), + }), + ], + + optimization: { + minimize: env === 'production', + minimizer: [ + new TerserPlugin({ + extractComments: false, + terserOptions: { + format: { + // Preserve the license header injected by BannerPlugin. + comments: /AGPL|Copyright|Ascensio|License/i, + }, + compress: { + drop_console: env === 'production', + }, + // mangle:false is load-bearing — same reason as web-apps: + // sdkjs files communicate via window.AscCommon.xxx and bare + // top-level var declarations shared across concatenated scope. + // Mangling property names would silently corrupt those references. + mangle: false, + }, + }), + ], + }, + + devtool: env === 'production' ? false : 'source-map', + }; + } + + return [ + chunkConfig('min', 'sdk-all-min'), + chunkConfig('all', 'sdk-all'), + ]; +} diff --git a/build/webpack.slide.mjs b/build/webpack.slide.mjs new file mode 100644 index 0000000000..53303957e6 --- /dev/null +++ b/build/webpack.slide.mjs @@ -0,0 +1,2 @@ +import { sdkConfig } from './webpack.sdk.factory.mjs'; +export default sdkConfig('slide'); diff --git a/build/webpack.visio.mjs b/build/webpack.visio.mjs new file mode 100644 index 0000000000..85573e68e2 --- /dev/null +++ b/build/webpack.visio.mjs @@ -0,0 +1,2 @@ +import { sdkConfig } from './webpack.sdk.factory.mjs'; +export default sdkConfig('visio'); diff --git a/build/webpack.word.mjs b/build/webpack.word.mjs new file mode 100644 index 0000000000..532b2967ae --- /dev/null +++ b/build/webpack.word.mjs @@ -0,0 +1,2 @@ +import { sdkConfig } from './webpack.sdk.factory.mjs'; +export default sdkConfig('word'); From afa2052c97203f95acb0c62c25a719901f869776 Mon Sep 17 00:00:00 2001 From: Mona LatifAghili Date: Tue, 21 Jul 2026 10:46:29 +0200 Subject: [PATCH 2/6] build(sdkjs): finish Grunt -> Webpack migration hardening Signed-off-by: Mona LatifAghili --- .docker/sdkjs.bake.Dockerfile | 17 +- .github/workflows/check-build.yml | 49 +- .gitignore | 1 + AGENTS.md | 58 +- Makefile | 17 +- build/Gruntfile.js | 561 --- build/Readme.md | 12 +- build/build-desktop.bat | 9 +- build/build-develop-addons-sdk-advanced.py | 10 +- build/build-develop-addons-sdk-whitespace.py | 10 +- build/build-develop-addons.py | 9 +- build/build-develop.bat | 12 +- build/build-mobile.command | 4 +- build/build.bat | 7 +- build/dummy.js | 15 + build/lib/env.cjs | 46 + build/lib/sdk-configs.cjs | 154 + build/loaders/sdk-concat.cjs | 408 +- build/npm-shrinkwrap.json | 3634 +++++++++++++---- build/package.json | 18 +- build/package.json.webpack | 22 - .../{build-develop.js => build-develop.cjs} | 130 +- build/scripts/build-pipeline.cjs | 388 ++ build/scripts/build-pipeline.js | 228 -- .../{deploy-assets.js => deploy-assets.cjs} | 34 +- build/test/build-pipeline.test.cjs | 63 + build/test/sdk-concat.test.cjs | 172 + build/test/sdk-configs.test.cjs | 71 + build/test/webpack-sdk-factory.test.cjs | 113 + build/webpack.cell.mjs | 15 + build/webpack.sdk.factory.mjs | 176 +- build/webpack.slide.mjs | 15 + build/webpack.visio.mjs | 15 + build/webpack.word.mjs | 15 + tests/code-style/check.py | 8 +- 35 files changed, 4512 insertions(+), 2004 deletions(-) delete mode 100644 build/Gruntfile.js create mode 100644 build/lib/env.cjs create mode 100644 build/lib/sdk-configs.cjs delete mode 100644 build/package.json.webpack rename build/scripts/{build-develop.js => build-develop.cjs} (50%) create mode 100644 build/scripts/build-pipeline.cjs delete mode 100644 build/scripts/build-pipeline.js rename build/scripts/{deploy-assets.js => deploy-assets.cjs} (79%) create mode 100644 build/test/build-pipeline.test.cjs create mode 100644 build/test/sdk-concat.test.cjs create mode 100644 build/test/sdk-configs.test.cjs create mode 100644 build/test/webpack-sdk-factory.test.cjs diff --git a/.docker/sdkjs.bake.Dockerfile b/.docker/sdkjs.bake.Dockerfile index d938bfa40b..ee72bbd36d 100644 --- a/.docker/sdkjs.bake.Dockerfile +++ b/.docker/sdkjs.bake.Dockerfile @@ -13,10 +13,10 @@ ARG BUILD_ROOT #### BASE #### FROM ubuntu:24.04 AS web-base RUN apt-get update && \ - apt-get install -y ca-certificates curl gnupg openjdk-21-jdk wget zip brotli bzip2 && \ + apt-get install -y ca-certificates curl gnupg wget zip brotli bzip2 && \ curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ apt-get install -y nodejs && \ - npm install -g @yao-pkg/pkg grunt-cli && \ + npm install -g @yao-pkg/pkg && \ rm -rf /var/lib/apt/lists/* #### SDKJS #### @@ -26,11 +26,11 @@ FROM web-base AS sdkjs-base ARG PRODUCT_VERSION - COPY sdkjs/build/package*.json /app/build/ + COPY sdkjs/build/package*.json sdkjs/build/npm-shrinkwrap.json /app/build/ RUN --mount=type=cache,target=/root/.npm \ cd app/build && \ - npm install + npm ci COPY sdkjs/ /app COPY sdkjs-forms/ /sdkjs-forms @@ -47,11 +47,12 @@ FROM web-base AS sdkjs-base COPY --from=core-wasm ${BUILD_ROOT}/libfont/ /app/common/libfont/ FROM sdkjs-base AS sdkjs-desktop - ARG TARGETARCH + ENV SDK_ADDONS=/sdkjs-forms + ENV SDK_PLATFORM=desktop RUN cd app/build && \ - CC_PLATFORM=$(if [ "$TARGETARCH" = "arm64" ]; then echo "java"; else echo "native,java"; fi) grunt --addon=sdkjs-forms --desktop=true + npm run build FROM sdkjs-base AS sdkjs - ARG TARGETARCH + ENV SDK_ADDONS=/sdkjs-forms RUN cd app/build && \ - CC_PLATFORM=$(if [ "$TARGETARCH" = "arm64" ]; then echo "java"; else echo "native,java"; fi) grunt --addon=sdkjs-forms \ No newline at end of file + npm run build \ No newline at end of file diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml index 584ad8a016..ed8666bd25 100644 --- a/.github/workflows/check-build.yml +++ b/.github/workflows/check-build.yml @@ -63,9 +63,9 @@ jobs: - name: Build develop SDK working-directory: sdkjs run: | - npm install grunt-cli node-qunit-puppeteer - npm install --prefix build - node node_modules/grunt-cli/bin/grunt --gruntfile build/Gruntfile.js develop --addon=sdkjs-forms + npm install node-qunit-puppeteer + npm ci --prefix build + npm run --prefix build develop - name: Run unit tests working-directory: sdkjs @@ -102,9 +102,46 @@ jobs: with: node-version: 20 + - name: Run build-tooling unit tests + run: | + cd sdkjs + npm ci --prefix build + npm test --prefix build + - name: Run build sdkjs run: | cd sdkjs - npm install grunt-cli - npm install --prefix build - node node_modules/grunt-cli/bin/grunt --gruntfile build/Gruntfile.js + # npm ci here too (not just in "Run build-tooling unit tests"): this job + # runs on a runner where steps share a workspace but a fresh checkout + # may still land without node_modules installed. npm ci is fast on a + # warm cache, so this is cheap insurance against "webpack: not found". + npm ci --prefix build + npm run --prefix build build + + - name: Install QUnit runner dependencies + run: | + sudo apt-get update + sudo apt-get install -y libatk1.0-0 libcups2 libatk-bridge2.0-0 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libnss3 libgbm1 libasound2t64 + cd sdkjs + npm install node-qunit-puppeteer + + # The "unit-tests" job above runs the QUnit suite against raw source files + # (COMPILED unset) — it never touches the webpack output, so a regression in the + # actual bundle (wrong concatenation order, a helper-dedup bug, a stripped-directive + # bug, etc.) can pass CI entirely undetected. Re-point develop/sdkjs/*/scripts.js at + # the just-built sdk-all-min.js (COMPILED=1) and run a smoke test against that, so + # the built artifact itself is exercised at least once. + # + # Only tests/common/api/api.html is used here — verified locally (not assumed): + # COMPILED=1's generated scripts.js references ONLY sdk-all-min.js, never + # sdk-all.js (this mirrors the original Gruntfile's writeScripts() exactly, it is + # not a webpack-migration change). sdk-all-min.js is the bootstrap chunk only; + # AscWord/AscCommonExcel/etc. live in sdk-all.js, so any suite touching those + # (tests/word/api, tests/*/shortcuts, tests/cell/js-api, ...) fails under + # COMPILED=1 with "AscWord is not defined" — a pre-existing limitation of + # developer-compiled mode, not something this step should be asserting on. + - name: Run QUnit against the built bundle (COMPILED=1) + run: | + cd sdkjs + COMPILED=1 npm run --prefix build develop + node node_modules/node-qunit-puppeteer/cli.js tests/common/api/api.html 30000 "--no-sandbox" diff --git a/.gitignore b/.gitignore index e07d1b5b75..41179244ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .idea/ build/node_modules +build/.webpack-cache build/deserializer/cache build/$weak$.js build/maps diff --git a/AGENTS.md b/AGENTS.md index f2091bbfc0..63ff6a30cb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,7 +26,7 @@ bundles (`sdk-all.js` / `sdk-all-min.js`) that the sibling **web-apps** repo loa | `slide/` | Presentation editor + themes/textures. | | `pdf/` | PDF editor; `src/` is the implementation, `build/` a compiled wrapper, `test/` a harness. | | `visio/` | Diagram editor with its own VSDX serialization (`model/`). | -| `build/` | Grunt build: `Gruntfile.js`, `package.json`, `license.header`. Run grunt from **here**. | +| `build/` | Webpack build: `webpack.*.mjs`, `scripts/`, `package.json`, `license.header`. Run npm from **here**. See `build/DEVELOPER-GUIDE.md` for the full workflow. | | `configs/` | `.json` file-lists that drive the build (load order); `externs.json` for Closure. | | `tests/` | QUnit suites per editor + `code-style/check.py` (the lint gate). | | `vendor/` | Third-party libs (jQuery, XRegExp, etc.). Excluded from lint/build minification. | @@ -36,34 +36,38 @@ bundles (`sdk-all.js` / `sdk-all-min.js`) that the sibling **web-apps** repo loa For Docker dev environment setup (running the full server stack), see [/DocumentServer/AGENTS.md](../DocumentServer/AGENTS.md). -Requires **Node.js** and, for the full compile, **Java** (the build uses Google Closure -Compiler, pinned to `google-closure-compiler@20240317`). There is **no root `package.json`**; -all build deps live in `build/`. +Requires **Node.js** only — the build now runs on Webpack + Terser (no Java, no Google +Closure Compiler). There is **no root `package.json`**; all build deps live in `build/`. +See `build/DEVELOPER-GUIDE.md` for the full workflow (watch mode, source maps, cache). ```bash -# Full SDK build (release; ADVANCED minification). Run from build/. -cd build && npm install -g grunt-cli && npm ci && grunt +# Full SDK build (release; all 4 modules in parallel, ~50s cold / ~2-3s warm). Run from build/. +cd build && npm ci && npm run build # Outputs: ../deploy/sdkjs/{word,cell,slide,visio}/sdk-all-min.js + sdk-all.js ``` ```bash -# Debug/dev loop — NO recompile, NO Java needed. Run from build/. -grunt develop # writes ../develop/sdkjs//scripts.js listing the - # individual source files, so editors/tests load unminified sources -grunt develop --compiled # same manifest, but pointing at the compiled bundles +# Debug/dev loop — no bundling. Run from build/. +npm run develop # writes ../develop/sdkjs//scripts.js listing the + # individual source files, so editors/tests load unminified sources +COMPILED=1 npm run develop # same manifest, but pointing at the compiled bundles ``` -Day-to-day inner loop: edit a source file → `grunt develop` → reload the editor/test page. -You only need the full `grunt` (Closure) build to produce release/min bundles. +Day-to-day inner loop: edit a source file → `npm run develop` → reload the editor/test page. +You only need the full `npm run build` (webpack) to produce release/min bundles, or +`npm run watch:word` (etc.) for an auto-rebuilding dev bundle. -Other flags: `--desktop=true` (desktop-only files), `--mobile=true`, `--map` (source maps), -`--level=WHITESPACE_ONLY` (faster, readable output), `--addon=sdkjs-forms` (merges an external -addon repo's `configs/`). +Config is via **environment variables**, not CLI flags: `SDK_PLATFORM=desktop` or `mobile` +(desktop/mobile-only files), `SDK_SOURCE_MAPS=1` (source maps on a production build), +`SDK_ADDONS=../../sdkjs-forms` (`path.delimiter`-separated list; merges external addon repos' +`configs/`), `NODE_ENV=development` (readable, unminified output). There is no `--level` / +`ADVANCED` vs `WHITESPACE_ONLY` distinction anymore — minification is always Terser with +`mangle: false` (see Gotchas below). **`make` is NOT the SDK build.** The Makefile's default target also builds the sibling `../web-apps` repo and requires it to be checked out next to sdkjs; it is the integration -build. Use `grunt` in `build/` for SDK-only work. (The Makefile's `SDKJS_FILES` is also stale — -it lists only `word/sdk-all.js` though grunt builds all editors.) +build. Use `npm run build` in `build/` for SDK-only work. (The Makefile's `SDKJS_FILES` is also +stale — it lists only `word/sdk-all.js` though the build produces all editors.) ### Adding a source file @@ -81,9 +85,9 @@ QUnit suites run headless via `node-qunit-puppeteer`, **from the repo root**: ```bash # one-time setup (from repo root) -npm install grunt-cli node-qunit-puppeteer -npm install --prefix build -node node_modules/grunt-cli/bin/grunt --gruntfile build/Gruntfile.js develop +npm install node-qunit-puppeteer +npm ci --prefix build +npm run --prefix build develop # run a single suite node node_modules/node-qunit-puppeteer/cli.js tests/word/api/api.html 30000 "--no-sandbox" @@ -98,7 +102,7 @@ CI-guarded by this workflow. Heaviest coverage is in `tests/cell/spreadsheet-calculation/` (formula engine) and `tests/word/`. Suites depend on the generated `develop/sdkjs/*/scripts.js`, so run -`grunt develop` first. +`npm run develop` (in `build/`) first. ## Code style — the build-breakers @@ -192,11 +196,15 @@ Each editor dir has the same set of API files: - The editor instance lives in both `Asc.editor` and `window.editor` (desktop compat) — code often checks both. -- External callers must use bracket access (`window['Asc']['asc_docs_api']`); dot access on - public names gets mangled by Closure ADVANCED minification. -- `make` pulls in `../web-apps`; for SDK-only work use `grunt` in `build/`. +- External callers must use bracket access (`window['Asc']['asc_docs_api']`); public names are + still published both ways (`window['Name'].Sym = window.Name.Sym = Sym`) as a defensive + convention, but the current webpack build runs Terser with `mangle: false` — property/name + mangling is not actually applied. Keep using bracket access anyway; don't rely on this as + license to switch to dot-access-only code. +- `make` pulls in `../web-apps`; for SDK-only work use `npm run build` in `build/`. - Use `npm ci` (not `npm install`) in `build/` to respect the committed `npm-shrinkwrap.json`. -- The full `grunt` build needs Java; `grunt develop` does not. +- No Java/Closure Compiler dependency anymore — `npm run build` and `npm run develop` both only + need Node.js. ## Where future findings live diff --git a/Makefile b/Makefile index 816c38b53b..182d63df7f 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ GRUNT = grunt -GRUNT_FLAGS = --no-color -v +GRUNT_FLAGS = --no-color -v +SDK_PLATFORM ?= OUTPUT_DIR = deploy OUTPUT = $(OUTPUT_DIR) @@ -24,6 +25,17 @@ GRUNT_ENV += BUILD_NUMBER=$(BUILD_NUMBER) GRUNT_ENV += APP_COPYRIGHT="$(APP_COPYRIGHT)" GRUNT_ENV += PUBLISHER_URL="$(PUBLISHER_URL)" +# sdkjs's own build/ was migrated from Grunt to webpack (web-apps' build below is +# unaffected — it still uses Grunt). The new pipeline reads the same +# PRODUCT_VERSION/BUILD_NUMBER/APP_COPYRIGHT/PUBLISHER_URL via env vars, plus +# SDK_PLATFORM instead of a --desktop=true CLI flag, and it errors out on any +# stray CLI argument — so it must be invoked with no flags at all (no GRUNT_FLAGS). +# Recursive (=), not simple (:=): SDK_PLATFORM must expand at recipe-run time so +# the `desktop:` target-specific override below is picked up, not the empty +# top-level default in effect at parse time. +SDKJS_ENV = $(GRUNT_ENV) +SDKJS_ENV += SDK_PLATFORM=$(SDK_PLATFORM) + WEBAPPS_DIR := web-apps WEBAPPS = $(OUTPUT)/$(WEBAPPS_DIR) @@ -49,9 +61,10 @@ $(WEBAPPS_FILES): $(NODE_MODULES) $(SDKJS_FILES) $(SDKJS_FILES): $(NODE_MODULES) cd build && \ - $(GRUNT_ENV) $(GRUNT) $(GRUNT_FLAGS) + $(SDKJS_ENV) npm run build desktop: GRUNT_FLAGS += --desktop=true +desktop: SDK_PLATFORM = desktop desktop: all clean: diff --git a/build/Gruntfile.js b/build/Gruntfile.js deleted file mode 100644 index 6c692a67fc..0000000000 --- a/build/Gruntfile.js +++ /dev/null @@ -1,561 +0,0 @@ -/* - * (c) Copyright Ascensio System SIA 2010-2024 - * - * This program is a free software product. You can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License (AGPL) - * version 3 as published by the Free Software Foundation. In accordance with - * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect - * that Ascensio System SIA expressly excludes the warranty of non-infringement - * of any third-party rights. - * - * This program is distributed WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For - * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html - * - * The interactive user interfaces in modified source and object code versions - * of the Program must display Appropriate Legal Notices, as required under - * Section 5 of the GNU AGPL version 3. - * - * All the Product's GUI elements, including illustrations and icon sets, as - * well as technical writing content are licensed under the terms of the - * Creative Commons Attribution-ShareAlike 4.0 International. See the License - * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode - * - */ - -module.exports = function(grunt) { - function loadConfig(pathConfigs, name) { - let config; - try { - const file = path.join(pathConfigs, name + '.json'); - if (grunt.file.exists(file)) { - config = grunt.file.readJSON(file); - grunt.log.ok((name + ' config loaded successfully').green); - } - } catch (e) { - grunt.log.error().writeln(('could not load' + name + 'config file').red); - } - return config; - } - function fixPath(obj, basePath = '') { - function fixPathArray(arrPaths, basePath = '') { - arrPaths.forEach((element, index) => { - arrPaths[index] = path.join(basePath, element); - }); - } - if (Array.isArray(obj)) - return fixPathArray(obj, basePath); - for (let prop in obj) { - fixPath(obj[prop], basePath); - } - } - function fixUrl(arrPaths, basePath = '') { - const url = require('url'); - arrPaths.forEach((element, index) => { - arrPaths[index] = url.resolve(basePath, element); - }); - } - function getConfigs() { - const configs = new CConfig(grunt.option('src') || '../'); - - let addons = grunt.option('addon') || []; - if (!Array.isArray(addons)) { - addons = [addons]; - } - addons.forEach(element => configs.append(grunt.file.isDir(element) ? element : path.join('../../', element))); - - return configs; - } - function writeScripts(config, name) { - const develop = process.env.BUILD_ROOT - ? path.join(process.env.BUILD_ROOT, 'sdkjs', 'develop', 'sdkjs') + '/' - : '../develop/sdkjs/'; - const fileName = 'scripts.js'; - let files = ['../vendor/polyfill.js', '../common/AllFonts.js']; - if (grunt.option('compiled')) { - if (process.env.BUILD_ROOT) { - files.push(path.join('..', name, 'sdk-all-min.js')); - } else { - files.push(path.join(deploy, name, 'sdk-all-min.js')); - } - } else { - files = files.concat(['../common/applyDocumentChanges.js'], getFilesMin(config), getFilesAll(config)); - } - fixUrl(files, '../../../../sdkjs/build/'); - - grunt.file.write(path.join(develop, name, fileName), 'var sdk_scripts = [\n\t"' + files.join('",\n\t"') + '"\n];'); - } - - function CConfig(pathConfigs) { - this.externs = null; - this.word = null; - this.cell = null; - this.slide = null; - this.visio = null; - - this.append(pathConfigs); - } - - CConfig.prototype.append = function (basePath = '') { - const pathConfigs = path.join(basePath, 'configs'); - - function appendOption(name) { - const option = loadConfig(pathConfigs, name); - if (!option) - return; - - fixPath(option, basePath); - - if (!this[name]) { - this[name] = option; - return; - } - - function mergeProps(base, addon) { - for (let prop in addon) - { - if (Array.isArray(addon[prop])) { - base[prop] = Array.isArray(base[prop]) ? base[prop].concat(addon[prop]) : addon[prop]; - } else { - if (!base[prop]) - base[prop] = {}; - mergeProps(base[prop], addon[prop]); - } - } - } - - mergeProps(this[name], option); - } - - appendOption.call(this, 'externs'); - appendOption.call(this, 'word'); - appendOption.call(this, 'cell'); - appendOption.call(this, 'slide'); - appendOption.call(this, 'visio'); - }; - CConfig.prototype.valid = function () { - return this.externs && this.word && this.cell && this.slide && this.visio; - }; - - function getExterns(config) { - var externs = config['externs']; - var result = []; - for (var i = 0; i < externs.length; ++i) { - result.push('--externs=' + externs[i]); - } - return result; - } - function getFilesMin(config) { - var result = config['min']; - if (grunt.option('mobile')) { - result = config['mobile_banners']['min'].concat(result); - } - if (grunt.option('desktop')) { - result = result.concat(config['desktop']['min']); - } - return result; - } - function getFilesAll(config) { - var result = config['common']; - if (grunt.option('mobile')) { - result = config['mobile_banners']['common'].concat(result); - - var excludeFiles = config['exclude_mobile']; - result = result.filter(function(item) { - return -1 === excludeFiles.indexOf(item); - }); - result = result.concat(config['mobile']); - } - if (grunt.option('desktop')) { - result = result.concat(config['desktop']['common']); - } - return result; - } - const path = require('path'); - const deploy = process.env.BUILD_ROOT - ? path.join(process.env.BUILD_ROOT, 'sdkjs') - : path.join('..', 'deploy', 'sdkjs'); - const word = path.join(deploy, 'word'); - const cell = path.join(deploy, 'cell'); - const slide = path.join(deploy, 'slide'); - const visio = path.join(deploy, 'visio'); - - const level = grunt.option('level') || 'WHITESPACE_ONLY'; - const formatting = grunt.option('formatting') || ''; - - const ccPlatform = process.env.CC_PLATFORM - ? process.env.CC_PLATFORM.split(',') - : ['native', 'java']; - require('google-closure-compiler').grunt(grunt, { - platform: ccPlatform, - extraArguments: ['-Xms2048m'] - }); - - grunt.loadNpmTasks('grunt-contrib-clean'); - grunt.loadNpmTasks('grunt-contrib-copy'); - - const configs = getConfigs(); - if (!configs.valid()) { - return; - } - const otherFiles = [ - { - cwd: '../vendor/', - src: ['polyfill.js'], - dest: path.join(deploy, 'vendor'), - name: 'vendor' - }, - { - cwd: '../common/', - src: [ - 'device_scale.js', - 'Drawings/Format/path-boolean-min.js', - 'Charts/ChartStyles.js', - 'SmartArts/SmartArtData/*', - 'SmartArts/SmartArtDrawing/*', - 'Images/*', - 'Images/placeholders/*', - 'Images/content_controls/*', - 'Images/cursors/*', - 'Images/reporter/*', - 'Images/icons/*', - 'Native/*.js', - 'libfont/engine/*', - 'spell/spell/*', - 'hash/hash/*', - 'zlib/engine/*', - 'serviceworker/*' - ], - dest: path.join(deploy, 'common'), - name: 'common' - }, - { - cwd: '../cell/css', - src: ['*.css'], - dest: path.join(cell, 'css'), - name: 'cell-css' - }, - { - cwd: '../slide/themes', - src: ['**/**'], - dest: path.join(slide, 'themes'), - name: 'slide-themes' - }, - { - cwd: '../pdf/', - src: [ - 'src/engine/*', - 'src/annotations/stamps/*.json' - ], - dest: path.join(deploy, 'pdf'), - name: 'pdf' - } - ]; - const configWord = configs.word['sdk']; - const configCell = configs.cell['sdk']; - const configSlide = configs.slide['sdk']; - const configVisio = configs.visio['sdk']; - - const compilerArgs = getExterns(configs.externs); - if (formatting) { - compilerArgs.push('--formatting=' + formatting); - } - const appCopyright = process.env['APP_COPYRIGHT'] || "Copyright (C) Ascensio System SIA 2012-2025. All rights reserved; Euro-Office contributors 2026 - " + grunt.template.today('yyyy'); - const publisherUrl = process.env['PUBLISHER_URL'] || "https://github.com/Euro-Office/"; - const companyName = process.env['COMPANY_NAME'] || 'Euro-Office'; - const version = process.env['PRODUCT_VERSION'] || '0.0.0'; - const buildNumber = process.env['BUILD_NUMBER'] || '0'; - const beta = grunt.option('beta') || 'false'; - - let license = grunt.file.read(path.join('./license.header')); - license = license.replace('@@AppCopyright', appCopyright); - license = license.replace('@@PublisherUrl', publisherUrl); - license = license.replace('@@Version', version); - license = license.replace('@@Build', buildNumber); - - function getCompileConfig(sdkmin, sdkall, outmin, outall, name, pathPrefix) { - const args = compilerArgs.concat ( - `--define=window.AscCommon.g_cCompanyName='${companyName}'`, - `--define=window.AscCommon.g_cProductVersion='${version}'`, - `--define=window.AscCommon.g_cBuildNumber='${buildNumber}'`, - `--define=window.AscCommon.g_cIsBeta='${beta}'`, - '--rewrite_polyfills=true', - '--warning_level=QUIET', - '--language_out=ECMASCRIPT5', - '--compilation_level=' + level, - ...sdkmin.map((file) => ('--js=' + file)), - `--chunk=${outmin}:${sdkmin.length}`, - `--chunk_wrapper=${outmin}:${license}\n%s`, - ...sdkall.map((file) => ('--js=' + file)), - `--chunk=${outall}:${sdkall.length}:${outmin}`, - `--chunk_wrapper=${outall}:${license}\n(function(window, undefined) {%s})(window);`, - `--chunk_output_path_prefix=${pathPrefix}`); - if (grunt.option('map')) { - grunt.file.mkdir(path.join('./maps')); - args.push('--property_renaming_report=' + path.join(`maps/${name}.props.js.map`)); - args.push('--variable_renaming_report=' + path.join(`maps/${name}.vars.js.map`)); - args.push('--create_source_map=' + path.join(`%outname%.map`)); - args.push('--source_map_format=V3'); - args.push('--source_map_include_content=true'); - } - return { - 'closure-compiler': { - js: { - options: { - args: args, - } - } - } - } - } - grunt.registerTask('compile-word', 'Compile Word SDK', function () { - grunt.initConfig(getCompileConfig(getFilesMin(configWord), getFilesAll(configWord), 'sdk-all-min', 'sdk-all', 'word', path.join(word , '/'))); - grunt.task.run('closure-compiler'); - }); - grunt.registerTask('compile-cell', 'Compile Cell SDK', function () { - grunt.initConfig(getCompileConfig(getFilesMin(configCell), getFilesAll(configCell), 'sdk-all-min', 'sdk-all', 'cell', path.join(cell , '/'))); - grunt.task.run('closure-compiler'); - }); - grunt.registerTask('compile-slide', 'Compile Slide SDK', function () { - grunt.initConfig(getCompileConfig(getFilesMin(configSlide), getFilesAll(configSlide), 'sdk-all-min', 'sdk-all', 'slide', path.join(slide , '/'))); - grunt.task.run('closure-compiler'); - }); - grunt.registerTask('compile-visio', 'Compile Visio SDK', function () { - grunt.initConfig(getCompileConfig(getFilesMin(configVisio), getFilesAll(configVisio), 'sdk-all-min', 'sdk-all', 'visio', path.join(visio , '/'))); - grunt.task.run('closure-compiler'); - }); - grunt.registerTask('copy-maps', 'Copy maps from deploy to build', function() { - grunt.initConfig({ - copy: { - word: { - files: [ - { - expand: true, - cwd: word, - src: [ - 'sdk-all-min.js.map', - 'sdk-all.js.map', - ], - dest: 'maps', - rename: function (dest, src) { - return path.join(dest , src.replace('sdk', 'word')); - } - } - ] - }, - cell: { - files: [ - { - expand: true, - cwd: cell, - src: [ - 'sdk-all-min.js.map', - 'sdk-all.js.map', - ], - dest: 'maps', - rename: function (dest, src) { - return path.join(dest , src.replace('sdk', 'cell')); - } - } - ] - }, - slide: { - files: [ - { - expand: true, - cwd: slide, - src: [ - 'sdk-all-min.js.map', - 'sdk-all.js.map', - ], - dest: 'maps', - rename: function (dest, src) { - return path.join(dest , src.replace('sdk', 'slide')); - } - } - ] - }, - visio: { - files: [ - { - expand: true, - cwd: visio, - src: [ - 'sdk-all-min.js.map', - 'sdk-all.js.map', - ], - dest: 'maps', - rename: function (dest, src) { - return path.join(dest , src.replace('sdk', 'visio')); - } - } - ] - } - }, - clean: { - deploy: { - options: { - force: true - }, - src: [ - path.join(word, 'sdk-all-min.js.map'), - path.join(word, 'sdk-all.js.map'), - path.join(cell, 'sdk-all-min.js.map'), - path.join(cell, 'sdk-all.js.map'), - path.join(slide, 'sdk-all-min.js.map'), - path.join(slide, 'sdk-all.js.map'), - path.join(visio, 'sdk-all-min.js.map'), - path.join(visio, 'sdk-all.js.map'), - ] - } - } - }); - grunt.task.run('copy', 'clean'); - }); - grunt.registerTask('compile-sdk', ['compile-word', 'compile-cell', 'compile-slide', 'compile-visio']); - grunt.registerTask('clean-deploy', 'Clean deploy folder before deploying', function () { - grunt.initConfig({ - clean: { - deploy: { - options: { - force: true - }, - src: [ - deploy - ] - } - } - }); - grunt.task.run('clean'); - }); - const glob = require('glob'); - const ignoreFiles = ['jquery_native', 'fonts_ie', 'spell_ie', 'engine_ie', 'zlib_ie', 'drawingfile_ie', 'themes']; - /** - * @param {string[]} paths - * @param {string} cwd - * @return {[string[], string[]]} - */ - function splitJSFiles(paths, cwd) { - const jsFiles = []; - const noJSFiles = []; - paths.forEach((p) => { - glob.sync(p, { - cwd: cwd, - }).forEach((f) => { - if (path.extname(f) === '.js' && !ignoreFiles.includes(path.parse(f).name)) { - jsFiles.push(path.join(f)); - } else { - noJSFiles.push(path.join(f)); - } - }) - }); - return [jsFiles, noJSFiles]; - } - function getOtherCompileConfig(o, jsFile) { - return { - 'closure-compiler': { - js: { - options: { - args: [ - '--language_out=ECMASCRIPT5', - '--compilation_level=WHITESPACE_ONLY', - '--rewrite_polyfills=true', - '--warning_level=QUIET', - `--js=${path.join(o.cwd, jsFile)}`, - `--js_output_file=${path.join(o.dest, jsFile)}`, - `--output_wrapper=${license}\n%output%` - ] - } - } - } - } - } - function getOtherCopyConfig(o, noJSFiles) { - return { - copy: { - sdkjs: { - files: noJSFiles.map(f => ({ - expand: true, - cwd: o.cwd, - src: f, - dest: o.dest - })) - } - } - } - } - grunt.registerTask('copy-other', 'Copy other SDK files', function () { - const compilerTasks = []; - const copyTasks = []; - otherFiles.forEach((o) => { - const [jsFiles, noJSFiles] = splitJSFiles(o.src, o.cwd); - if (jsFiles.length !== 0) { - jsFiles.forEach((f) => { - grunt.registerTask(`compile-${path.join(o.dest, f)}`, `Compiling ${path.join(o.dest, f)}`, function() { - grunt.initConfig(getOtherCompileConfig(o, f)); - grunt.task.run('closure-compiler'); - }); - compilerTasks.push(`compile-${path.join(o.dest, f)}`); - }); - } - if (noJSFiles.length !== 0) { - grunt.registerTask(`copy-${path.normalize(o.name)}`, `Copying files ${path.normalize(o.name)}`, function() { - grunt.initConfig(getOtherCopyConfig(o, noJSFiles)); - grunt.task.run('copy'); - }); - copyTasks.push(`copy-${path.normalize(o.name)}`); - } - }); - grunt.task.run(compilerTasks); - grunt.task.run(copyTasks); - }); - grunt.registerTask('clean-develop', 'Clean develop scripts', function () { - const develop = process.env.BUILD_ROOT - ? path.join(process.env.BUILD_ROOT, 'sdkjs', 'develop', 'sdkjs') + '/' - : '../develop/sdkjs/'; - grunt.initConfig({ - clean: { - tmp: { - options: { - force: true - }, src: [develop] - } - } - }); - grunt.task.run('clean'); - }); - grunt.registerTask('build-develop', 'Build develop scripts', function () { - const configs = getConfigs(); - if (!configs.valid()) { - return; - } - - writeScripts(configs.word['sdk'], 'word'); - writeScripts(configs.cell['sdk'], 'cell'); - writeScripts(configs.slide['sdk'], 'slide'); - writeScripts(configs.visio['sdk'], 'visio'); - }); - const defaultTasks = ['clean-deploy', 'compile-sdk', 'copy-other']; - if (grunt.option('map')) { - defaultTasks.push('copy-maps'); - } - grunt.registerTask('default', defaultTasks); - // this file, device_scale is used in html templates and needs to be copied - // it is basically it's own thing, but processed like this to be consistent and not hacky - grunt.registerTask('copy-standalone', 'Copy standalone scripts needed by HTML templates', function () { - grunt.initConfig({ - copy: { - standalone: { - files: [{ - expand: true, - cwd: '../common/', - src: ['device_scale.js'], - dest: path.join(deploy, 'common') - }] - } - } - }); - grunt.task.run('copy'); - }); - grunt.registerTask('develop', ['clean-develop', 'build-develop', 'copy-standalone']); -}; diff --git a/build/Readme.md b/build/Readme.md index 08b26ae4fb..9b8a2d9d14 100644 --- a/build/Readme.md +++ b/build/Readme.md @@ -1,17 +1,17 @@ This document describes the steps needed to build SDK. For the full development environment setup, see the [fork/develop README](../../fork/develop/README.md). +For a deeper look at the webpack build (benchmarks, dev workflows, caching), see [DEVELOPER-GUIDE.md](DEVELOPER-GUIDE.md). 1. Required software installation: - Download and install nodejs (https://nodejs.org/en/download/). - - Download and install Java (http://java.com/en/download/index.jsp). 2. SDK build: - - npm install -g grunt-cli - npm ci - - grunt + - npm run build 3. Additional tasks: - - `grunt develop` — generates develop scripts (scripts.js) pointing to individual source files (for debugging without compilation) - - `grunt develop --compiled` — generates develop scripts pointing to compiled bundles (sdk-all-min.js) - - `grunt --map` — copies source maps + - `npm run develop` — generates develop scripts (scripts.js) pointing to individual source files (for debugging without compilation) + - `COMPILED=1 npm run develop` — generates develop scripts pointing to compiled bundles (sdk-all-min.js) + - `NODE_ENV=development npm run build` — enables source maps, written alongside the compiled bundles and then relocated by the build pipeline + - `SDK_PLATFORM=desktop|mobile` / `SDK_ADDONS=[:...]` — env vars accepted by both `npm run build` and `npm run develop` to select platform and merge in addon configs diff --git a/build/build-desktop.bat b/build/build-desktop.bat index 807c44f8a9..347c5a0f8f 100644 --- a/build/build-desktop.bat +++ b/build/build-desktop.bat @@ -1,10 +1,11 @@ CD /D %~dp0 -call npm install -g grunt-cli call npm ci -call grunt --level=WHITESPACE_ONLY --desktop=true --formatting=PRETTY_PRINT -rem call grunt --level=ADVANCED --desktop=true +set SDK_PLATFORM=desktop +rem set NODE_ENV=development +set NODE_ENV=production +call npm run build rmdir "..\..\desktop-apps\win-linux\build\debug\win_64\editors\sdkjs" xcopy /s/e/k/c/y/q/i "..\deploy\sdkjs" "..\..\desktop-apps\win-linux\build\debug\win_64\editors\sdkjs" -pause \ No newline at end of file +pause diff --git a/build/build-develop-addons-sdk-advanced.py b/build/build-develop-addons-sdk-advanced.py index c6e9569b75..d064d4a83c 100644 --- a/build/build-develop-addons-sdk-advanced.py +++ b/build/build-develop-addons-sdk-advanced.py @@ -1,15 +1,17 @@ #!/usr/bin/env python +import os import sys sys.path.append('../../build_tools/scripts') import base import traceback try: - base.cmd_in_dir('.', "npm", ["install", "-g", "grunt-cli"]) - base.cmd_in_dir('.', "npm", ["ci"]) + os.environ['SDK_ADDONS'] = os.pathsep.join(['../../sdkjs-forms', '../../sdkjs-ooxml']) + os.environ['NODE_ENV'] = 'production' + os.environ['COMPILED'] = '1' - base.cmd_in_dir('.', "grunt", ["--level=ADVANCED", "--addon=sdkjs-forms", "--addon=sdkjs-ooxml"]) - base.cmd_in_dir('.', "grunt", ["develop", "--compiled", "--addon=sdkjs-forms", "--addon=sdkjs-ooxml"]) + base.cmd_in_dir('.', "npm", ["ci"]) + base.cmd_in_dir('.', "npm", ["run", "build"]) input("Press Enter to continue...") exit(0) diff --git a/build/build-develop-addons-sdk-whitespace.py b/build/build-develop-addons-sdk-whitespace.py index b47e31dc7d..cda655edc0 100644 --- a/build/build-develop-addons-sdk-whitespace.py +++ b/build/build-develop-addons-sdk-whitespace.py @@ -1,15 +1,17 @@ #!/usr/bin/env python +import os import sys sys.path.append('../../build_tools/scripts') import base import traceback try: - base.cmd_in_dir('.', "npm", ["install", "-g", "grunt-cli"]) - base.cmd_in_dir('.', "npm", ["ci"]) + os.environ['SDK_ADDONS'] = os.pathsep.join(['../../sdkjs-forms', '../../sdkjs-ooxml']) + os.environ['NODE_ENV'] = 'development' + os.environ['COMPILED'] = '1' - base.cmd_in_dir('.', "grunt", ["--level=WHITESPACE_ONLY", "--addon=sdkjs-forms", "--addon=sdkjs-ooxml"]) - base.cmd_in_dir('.', "grunt", ["develop", "--compiled", "--addon=sdkjs-forms", "--addon=sdkjs-ooxml"]) + base.cmd_in_dir('.', "npm", ["ci"]) + base.cmd_in_dir('.', "npm", ["run", "build"]) input("Press Enter to continue...") exit(0) diff --git a/build/build-develop-addons.py b/build/build-develop-addons.py index d029eb7402..828243f01f 100644 --- a/build/build-develop-addons.py +++ b/build/build-develop-addons.py @@ -1,15 +1,16 @@ #!/usr/bin/env python +import os import sys sys.path.append('../../build_tools/scripts') import base import traceback try: - base.cmd_in_dir('.', "npm", ["install", "-g", "grunt-cli"]) - base.cmd_in_dir('.', "npm", ["ci"]) + os.environ['SDK_ADDONS'] = os.pathsep.join(['../../sdkjs-forms', '../../sdkjs-ooxml']) + os.environ['NODE_ENV'] = 'development' - base.cmd_in_dir('.', "grunt", ["--level=WHITESPACE_ONLY", "--addon=sdkjs-forms", "--addon=sdkjs-ooxml"]) - base.cmd_in_dir('.', "grunt", ["develop", "--addon=sdkjs-forms", "--addon=sdkjs-ooxml"]) + base.cmd_in_dir('.', "npm", ["ci"]) + base.cmd_in_dir('.', "npm", ["run", "build"]) input("Press Enter to continue...") exit(0) diff --git a/build/build-develop.bat b/build/build-develop.bat index 1869851d56..bac0a0d137 100644 --- a/build/build-develop.bat +++ b/build/build-develop.bat @@ -1,11 +1,9 @@ CD /D %~dp0 -call npm install -g grunt-cli call npm ci -REM call grunt --level=ADVANCED --addon=sdkjs-forms --addon=sdkjs-ooxml --desktop=true -REM call grunt --level=ADVANCED --addon=sdkjs-forms --addon=sdkjs-ooxml -REM call grunt --level=ADVANCED --addon=sdkjs-forms -call grunt --level=WHITESPACE_ONLY -call grunt develop +REM set SDK_ADDONS=..\..\sdkjs-forms;..\..\sdkjs-ooxml +REM set SDK_PLATFORM=desktop +set NODE_ENV=development +call npm run build -pause \ No newline at end of file +pause diff --git a/build/build-mobile.command b/build/build-mobile.command index a1f8d29512..b97d4f1ecb 100755 --- a/build/build-mobile.command +++ b/build/build-mobile.command @@ -62,13 +62,13 @@ echo "----------------------------------------" echo "Prepare to compile" echo "----------------------------------------" -npm install +npm ci echo "----------------------------------------" echo "Compile SDKJS" echo "----------------------------------------" -PRODUCT_VERSION=$PRODUCT_VERSION BUILD_NUMBER=$BUILD_NUMBER npx grunt --level=WHITESPACE_ONLY --mobile=true #--level=ADVANCED | WHITESPACE_ONLY +PRODUCT_VERSION=$PRODUCT_VERSION BUILD_NUMBER=$BUILD_NUMBER SDK_PLATFORM=mobile NODE_ENV=production npm run build if [ -z "$1" ] ; then # iOS diff --git a/build/build.bat b/build/build.bat index 767daf095b..2ceb3dfb5e 100644 --- a/build/build.bat +++ b/build/build.bat @@ -1,7 +1,6 @@ CD /D %~dp0 -call npm install -g grunt-cli call npm ci -rem call grunt --level=WHITESPACE_ONLY --desktop=false --formatting=PRETTY_PRINT -call grunt --level=ADVANCED +rem set NODE_ENV=development +call npm run build -pause \ No newline at end of file +pause diff --git a/build/dummy.js b/build/dummy.js index f88f9e2f9c..81d72be349 100644 --- a/build/dummy.js +++ b/build/dummy.js @@ -1,2 +1,17 @@ +/** + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + */ + // Webpack entry resource for sdk-concat-loader. // Content is irrelevant — the loader reads JSON configs and concatenates all SDK source files. diff --git a/build/lib/env.cjs b/build/lib/env.cjs new file mode 100644 index 0000000000..676a7684c8 --- /dev/null +++ b/build/lib/env.cjs @@ -0,0 +1,46 @@ +/** + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + */ + +/** + * Shared env-var parsing used identically by webpack.sdk.factory.mjs, + * scripts/build-develop.cjs, scripts/build-pipeline.cjs, and + * scripts/deploy-assets.cjs — extracted so these copies can't silently + * diverge (see build/lib/sdk-configs.cjs for the same rationale applied + * to config loading). + */ + +'use strict'; + +const path = require('path'); + +// process.env.SDK_ADDONS: path.delimiter-separated list of addon directories. +function parseAddonDirs(env) { + env = env || process.env; + return env.SDK_ADDONS + ? env.SDK_ADDONS.split(path.delimiter).filter(Boolean) + : []; +} + +// process.env.BUILD_ROOT, resolved to the sdkjs-specific deploy dir. +// buildDir is the caller's build/ directory (path.resolve(__dirname, ...)), +// since the default falls back to /deploy/sdkjs. +function resolveBuildRoot(buildDir, env) { + env = env || process.env; + return env.BUILD_ROOT + ? path.resolve(env.BUILD_ROOT, 'sdkjs') + : path.resolve(buildDir, '..', 'deploy', 'sdkjs'); +} + +module.exports = { parseAddonDirs, resolveBuildRoot }; diff --git a/build/lib/sdk-configs.cjs b/build/lib/sdk-configs.cjs new file mode 100644 index 0000000000..7d996b9d90 --- /dev/null +++ b/build/lib/sdk-configs.cjs @@ -0,0 +1,154 @@ +/** + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + */ + +/** + * Shared SDK config loading — exact port of CConfig + getFilesMin/getFilesAll + * from the original Gruntfile.js. Used by both sdk-concat-loader (webpack) and + * build-develop.cjs so the two never diverge on how configs/file lists are built. + */ + +'use strict'; + +const path = require('path'); +const fs = require('fs'); +const { sync: globSync, hasMagic } = require('glob'); + +// Matches every character glob's hasMagic() treats as magic, not just *?{ — +// a pattern like foo[0-9].js has no *?{ but is still a glob per hasMagic(). +const MAGIC_CHARS = /[*?{}[\]!()]/; + +function loadJsonConfig(configsDir, name) { + const file = path.join(configsDir, name + '.json'); + if (!fs.existsSync(file)) return null; + try { + return JSON.parse(fs.readFileSync(file, 'utf8')); + } catch (e) { + throw new Error(`sdk-configs: failed to parse ${file}: ${e.message}`); + } +} + +function fixPath(obj, basePath) { + if (Array.isArray(obj)) { + for (let i = 0; i < obj.length; i++) { + obj[i] = path.join(basePath, obj[i]); + } + return; + } + for (const k of Object.keys(obj)) { + fixPath(obj[k], basePath); + } +} + +function mergeConfigs(base, addon) { + for (const k of Object.keys(addon)) { + if (Array.isArray(addon[k])) { + base[k] = Array.isArray(base[k]) ? base[k].concat(addon[k]) : addon[k]; + } else { + if (!base[k]) base[k] = {}; + mergeConfigs(base[k], addon[k]); + } + } +} + +function loadAllConfigs(srcRoot, addonDirs) { + const configs = {}; + const configsDir = path.join(srcRoot, 'configs'); + + for (const name of ['word', 'cell', 'slide', 'visio']) { + const cfg = loadJsonConfig(configsDir, name); + if (cfg) { + fixPath(cfg, srcRoot); + configs[name] = cfg; + } + } + + for (const addonDir of (addonDirs || [])) { + for (const name of ['word', 'cell', 'slide', 'visio']) { + if (!configs[name]) continue; + const addon = loadJsonConfig(path.join(addonDir, 'configs'), name); + if (!addon) continue; + fixPath(addon, addonDir); + mergeConfigs(configs[name], addon); + } + } + + return configs; +} + +function getFilesMin(sdkCfg, platform) { + let files = (sdkCfg['min'] || []).slice(); + if (platform === 'mobile' && sdkCfg['mobile_banners']) { + files = (sdkCfg['mobile_banners']['min'] || []).concat(files); + } + if (platform === 'desktop' && sdkCfg['desktop']) { + files = files.concat(sdkCfg['desktop']['min'] || []); + } + return files; +} + +function getFilesAll(sdkCfg, platform) { + let files = (sdkCfg['common'] || []).slice(); + if (platform === 'mobile') { + if (sdkCfg['mobile_banners']) { + files = (sdkCfg['mobile_banners']['common'] || []).concat(files); + } + const exclude = sdkCfg['exclude_mobile'] || []; + files = files.filter(f => !exclude.includes(f)); + files = files.concat(sdkCfg['mobile'] || []); + } + if (platform === 'desktop' && sdkCfg['desktop']) { + files = files.concat(sdkCfg['desktop']['common'] || []); + } + return files; +} + +// Expand any glob patterns in a flat file list (already fixPath'd to absolute). +// Non-glob entries pass through unchanged; order of non-glob entries is preserved. +// Files matched by a glob are sorted (deterministic builds). +// addContextDep, if given, is called with each glob's parent directory so that +// consumers watching the filesystem (e.g. webpack) rebuild when a new file +// matching the pattern appears. +function expandGlobs(files, addContextDep) { + const result = []; + for (const f of files) { + if (hasMagic(f)) { + const idx = f.search(MAGIC_CHARS); + const base = idx === -1 ? f : f.slice(0, idx); + // When the magic character follows a slash (e.g. "common/Images/*"), + // base already ends in a separator and IS the directory to watch — + // path.dirname(base) would strip one path segment too many (watching + // "common" instead of "common/Images"), so only fall back to + // dirname() when base doesn't already end at a directory boundary. + const dir = /[\\/]$/.test(base) + ? base.replace(/[\\/]+$/, '') || '.' + : (path.dirname(base) || '.'); + if (addContextDep) addContextDep(dir); + result.push(...globSync(f, { nodir: true }).sort()); + } else { + result.push(f); + } + } + return result; +} + +module.exports = { + loadJsonConfig, + fixPath, + mergeConfigs, + loadAllConfigs, + getFilesMin, + getFilesAll, + expandGlobs, +}; diff --git a/build/loaders/sdk-concat.cjs b/build/loaders/sdk-concat.cjs index 2df90d6da8..3adcfb2b51 100644 --- a/build/loaders/sdk-concat.cjs +++ b/build/loaders/sdk-concat.cjs @@ -31,104 +31,175 @@ * platform {string} '' | 'desktop' | 'mobile' default '' * srcRoot {string} absolute path to sdkjs root (one level above build/) * addonDirs {string[]} absolute paths to addon directories + * buildMeta {object} { companyName, version, buildNumber, beta } — patched into + * window.AscCommon.g_cXxx after commonDefines.js ('min' chunk only) */ 'use strict'; -const path = require('path'); -const fs = require('fs'); +const path = require('path'); +const fs = require('fs'); +const crypto = require('crypto'); +const babel = require('@babel/core'); +const { loadAllConfigs, getFilesMin, getFilesAll, expandGlobs } = require('../lib/sdk-configs.cjs'); -// --------------------------------------------------------------------------- -// Config loading — exact port of CConfig.prototype.append from Gruntfile.js -// --------------------------------------------------------------------------- +// Lazy-load source-map-js (optional dep — absent means no source maps, build still works). +let SourceMapGenerator = null; +let SourceMapConsumer = null; +try { + const sourceMapJs = require('source-map-js'); + SourceMapGenerator = sourceMapJs.SourceMapGenerator; + SourceMapConsumer = sourceMapJs.SourceMapConsumer; +} catch (_) {} -function loadJsonConfig(configsDir, name) { - const file = path.join(configsDir, name + '.json'); - if (!fs.existsSync(file)) return null; - try { - return JSON.parse(fs.readFileSync(file, 'utf8')); - } catch (e) { - throw new Error(`sdk-concat-loader: failed to parse ${file}: ${e.message}`); - } -} +// On-disk cache for the per-file Babel transpile below, keyed on file content. +// Every loader invalidation (a single changed file in --watch mode, or each of +// the 4 separate webpack-cli processes the full pipeline spawns) otherwise +// re-transpiles every file in every chunk that includes it from scratch, even +// though the transpiled output only depends on the file's own content. +const CACHE_DIR = path.join(__dirname, '..', '.webpack-cache', 'babel'); -function fixPath(obj, basePath) { - if (Array.isArray(obj)) { - for (let i = 0; i < obj.length; i++) { - obj[i] = path.join(basePath, obj[i]); - } - return; - } - for (const k of Object.keys(obj)) { - fixPath(obj[k], basePath); - } +// The actual babel preset/options object transpileToES5 passes — folded into +// the cache key below (not a manually-maintained version integer) so a future +// change to these options (e.g. the 'ie: 11' target) can't silently leave +// stale cache entries from a previous options set undetected. +const BABEL_OPTIONS_KEY = JSON.stringify({ + sourceType: 'script', + presetEnvTargets: { ie: '11' }, + presetEnvModules: false, +}); + +// needSourceMap is folded into the key: a cache entry produced without a map +// (devtool:false runs) must never be handed back to a caller that needs one. +function cacheKeyFor(content, needSourceMap) { + return crypto.createHash('sha1') + .update(BABEL_OPTIONS_KEY) + .update(needSourceMap ? 'map' : 'nomap') + .update(content) + .digest('hex'); } -function mergeConfigs(base, addon) { - for (const k of Object.keys(addon)) { - if (Array.isArray(addon[k])) { - base[k] = Array.isArray(base[k]) ? base[k].concat(addon[k]) : addon[k]; - } else { - if (!base[k]) base[k] = {}; - mergeConfigs(base[k], addon[k]); - } +function readCache(key) { + try { + return JSON.parse(fs.readFileSync(path.join(CACHE_DIR, key + '.json'), 'utf8')); + } catch (_) { + return null; } } -function loadAllConfigs(srcRoot, addonDirs) { - const configs = {}; - const configsDir = path.join(srcRoot, 'configs'); - - for (const name of ['word', 'cell', 'slide', 'visio']) { - const cfg = loadJsonConfig(configsDir, name); - if (cfg) { - fixPath(cfg, srcRoot); - configs[name] = cfg; - } +function writeCache(key, entry) { + // build-pipeline.cjs runs 4 webpack-cli processes in parallel against this + // same cache dir, and aborts siblings by SIGTERM on the first failure — + // writing straight to the final path risks a sibling reading that exact + // key mid-write (a torn, unparseable JSON file) if it's killed at the + // wrong moment. Write to a per-process temp file and rename into place: + // rename is atomic on the same filesystem, so readers only ever see the + // old complete file or the new complete file, never a partial one. + const finalPath = path.join(CACHE_DIR, key + '.json'); + const tmpPath = finalPath + '.' + process.pid + '.tmp'; + try { + fs.mkdirSync(CACHE_DIR, { recursive: true }); + fs.writeFileSync(tmpPath, JSON.stringify(entry), 'utf8'); + fs.renameSync(tmpPath, finalPath); + } catch (_) { + // Cache is a pure optimization — a write failure (e.g. read-only fs) must + // not fail the build. + try { fs.unlinkSync(tmpPath); } catch (_) {} } +} - for (const addonDir of (addonDirs || [])) { - for (const name of ['word', 'cell', 'slide', 'visio']) { - if (!configs[name]) continue; - const addon = loadJsonConfig(path.join(addonDir, 'configs'), name); - if (!addon) continue; - fixPath(addon, addonDir); - mergeConfigs(configs[name], addon); - } - } +// The original Closure Compiler build ran with --language_out=ECMASCRIPT5: +// sdkjs source files use let/const/arrow functions/classes/etc, and Closure +// downleveled them to ES5 in the compiled output. webpack+Terser do not +// transpile syntax (Terser only avoids *introducing* new-ES syntax while +// minifying — it doesn't rewrite existing let/const/=> etc down to ES5), so +// that guarantee has to be reproduced explicitly here, per file, before +// concatenation. sourceType:'script' (not 'module') avoids Babel injecting +// "use strict" or wrapping content — required for the bare-var-across-files +// shared-scope model this loader depends on. +// +// Class/generator/destructuring transforms routinely change a file's line +// count (retainLines is best-effort, not a guarantee, and real sdkjs sources +// hit that on real inputs — see git history). So instead of relying on line +// counts staying stable, we ask Babel for its own generated↔original mapping +// (sourceMaps: needSourceMap) and stitch that into the combined map below via +// SourceMapConsumer, rather than assuming line N of the output is line N of +// the input. Skipping sourceMaps entirely when not needed (this.sourceMap is +// false) avoids the extra work across the 4 parallel webpack-cli processes × +// 2 chunks the full pipeline spawns. +// Returns { code, map }. +function transpileToES5(content, filename, needSourceMap) { + const result = babel.transformSync(content, { + filename, + babelrc: false, + configFile: false, + sourceType: 'script', + sourceMaps: needSourceMap, + compact: false, + presets: [ + [require.resolve('@babel/preset-env'), { targets: { ie: '11' }, modules: false }], + ], + }); - return configs; + return { code: result.code, map: result.map || null }; } -// --------------------------------------------------------------------------- -// File list helpers — exact port of getFilesMin/getFilesAll from Gruntfile.js -// --------------------------------------------------------------------------- - -function getFilesMin(sdkCfg, platform) { - let files = (sdkCfg['min'] || []).slice(); - if (platform === 'mobile' && sdkCfg['mobile_banners']) { - files = sdkCfg['mobile_banners']['min'].concat(files); - } - if (platform === 'desktop' && sdkCfg['desktop']) { - files = files.concat(sdkCfg['desktop']['min'] || []); +// Each per-file transformSync() call above independently inlines its own copy +// of any Babel helper it needs (_typeof, _classCallCheck, _inherits, …). That's +// fine standalone, but once concatenated into one shared-scope module, two +// files needing the same helper produce two top-level `function _typeof(){…}` +// declarations — a SyntaxError under strict/module parsing ("Identifier has +// already been declared"). Keep only the first occurrence of each helper +// across the whole chunk; babel tags helper functions with a recognizable +// `"@babel/helpers - name"` directive as their first statement, so detection +// doesn't depend on guessing every possible helper name. +// Removed text is replaced with a matching run of blank lines (not deleted +// outright) so every line number after the removed span is unchanged — the +// per-file source map built from Babel's own (pre-strip) output stays valid +// without needing to be recomputed here. +// code is always re-parsed here rather than reusing the AST transformSync +// already built: Babel's helper injection creates brand-new synthetic nodes +// with no source position (node.start/end are undefined on them), so slicing +// against a reused post-transform AST silently corrupts output instead of +// removing anything. A fresh parseSync of the actual generated text gives +// every node real, code-accurate offsets. +function stripDuplicateHelpers(code, emittedHelpers) { + let ast; + try { + ast = babel.parseSync(code, { sourceType: 'script', babelrc: false, configFile: false }); + } catch (err) { + // Shouldn't happen — code babel itself just emitted failing to + // re-parse. Log so a future occurrence is diagnosable instead of + // surfacing only as a confusing duplicate-declaration SyntaxError + // at the concatenated-bundle level with nothing pointing back here. + console.warn(`sdk-concat-loader: failed to re-parse transpiled output for helper dedup (${err.message}); leaving file un-deduplicated`); + return code; } - return files; -} -function getFilesAll(sdkCfg, platform) { - let files = (sdkCfg['common'] || []).slice(); - if (platform === 'mobile') { - if (sdkCfg['mobile_banners']) { - files = sdkCfg['mobile_banners']['common'].concat(files); + const removals = []; + for (const node of ast.program.body) { + if (node.type !== 'FunctionDeclaration' || !node.id) continue; + // A leading string-literal statement in a function body is parsed as a + // Directive (like "use strict"), not a regular ExpressionStatement — + // that's how babel tags its helper functions. + const directive = node.body.directives && node.body.directives[0]; + const isHelper = directive && directive.value.value.startsWith('@babel/helpers'); + if (!isHelper) continue; + + if (emittedHelpers.has(node.id.name)) { + removals.push([node.start, node.end]); + } else { + emittedHelpers.add(node.id.name); } - const exclude = sdkCfg['exclude_mobile'] || []; - files = files.filter(f => !exclude.includes(f)); - files = files.concat(sdkCfg['mobile'] || []); } - if (platform === 'desktop' && sdkCfg['desktop']) { - files = files.concat(sdkCfg['desktop']['common'] || []); + + if (!removals.length) return code; + removals.sort((a, b) => b[0] - a[0]); + for (const [start, end] of removals) { + const removedNewlines = (code.slice(start, end).match(/\n/g) || []).length; + code = code.slice(0, start) + '\n'.repeat(removedNewlines) + code.slice(end); } - return files; + return code; } // --------------------------------------------------------------------------- @@ -137,6 +208,7 @@ function getFilesAll(sdkCfg, platform) { module.exports = function sdkConcatLoader() { // this.resourcePath is dummy.js — its content is irrelevant; we ignore it. + const callback = this.async(); const opts = this.getOptions(); const srcRoot = path.resolve(opts.srcRoot || path.join(this.context, '..')); const platform = opts.platform || ''; @@ -146,41 +218,182 @@ module.exports = function sdkConcatLoader() { const sdkCfg = configs[opts.module] && configs[opts.module]['sdk']; if (!sdkCfg) { - this.emitError(new Error(`sdk-concat-loader: no config found for module "${opts.module}" at ${srcRoot}`)); - return ''; + callback(new Error(`sdk-concat-loader: no config found for module "${opts.module}" at ${srcRoot}`)); + return; } - const files = opts.chunk === 'min' + const rawFiles = opts.chunk === 'min' ? getFilesMin(sdkCfg, platform) : getFilesAll(sdkCfg, platform); + const addCtx = this.addContextDependency.bind(this); + const files = expandGlobs(rawFiles, addCtx); + // Register every source file as a webpack dependency so watch mode works. for (const f of files) { this.addDependency(path.resolve(f)); } - // Watch the config file for this module so a config change triggers a rebuild. + // Watch the config file(s) so a config change triggers a rebuild — including + // addon configs, which loadAllConfigs() merges in but which webpack would + // otherwise never see, leaving --watch/persistent-cache builds stale. this.addDependency(path.join(srcRoot, 'configs', opts.module + '.json')); + for (const addonDir of addonDirs) { + this.addDependency(path.join(addonDir, 'configs', opts.module + '.json')); + } - const parts = []; - for (const f of files) { + // this.sourceMap reflects webpack's own devtool setting — building the + // full per-file map (and embedding every source's content) is wasted + // CPU/memory when devtool:false, and this loader runs across 4 parallel + // webpack-cli processes × 2 chunks each in the full pipeline. + const needSourceMap = !!(this.sourceMap && SourceMapGenerator && SourceMapConsumer); + + // The old Closure Compiler build ran with --rewrite_polyfills=true, which + // injected runtime polyfills (Promise, Map, WeakMap, Array.prototype.includes, + // etc.) directly into the compiled SDK bundle for whatever ES6+ APIs the + // source actually used. webpack+Babel's per-file syntax downlevel above + // does NOT do this — preset-env here has no useBuiltIns/corejs configured, + // so consumers that load only sdk-all-min.js (e.g. embed pages, which do + // not load sdkjs/vendor/polyfill.js nearby) would regress on older + // browsers. Prepend the same polyfill file the old dev-mode writeScripts() + // path already loads ahead of sdk-all-min.js, so the 'min' chunk stays + // self-contained exactly like the Closure output was. + let polyfillContent = ''; + if (opts.chunk === 'min') { + const polyfillPath = path.join(srcRoot, 'vendor', 'polyfill.js'); + this.addDependency(polyfillPath); try { - parts.push(fs.readFileSync(f, 'utf8')); - } catch (e) { - this.emitError(new Error(`sdk-concat-loader: cannot read ${f}: ${e.message}`)); - parts.push(''); + polyfillContent = fs.readFileSync(polyfillPath, 'utf8'); + } catch (err) { + callback(new Error(`sdk-concat-loader: cannot read required polyfill file ${polyfillPath}: ${err.message}`)); + return; } + if (!polyfillContent.endsWith('\n')) polyfillContent += '\n'; } - const content = parts.join('\n'); + // Read all source files in parallel. A missing/unreadable file must fail + // the build outright: silently substituting '' would concatenate a chunk + // with a piece of its bare-var scope missing, producing a build that looks + // green but throws a confusing ReferenceError deep inside the SDK at runtime. + Promise.all(files.map(f => + fs.promises.readFile(f, 'utf8').catch(err => { + throw new Error(`sdk-concat-loader: cannot read ${f}: ${err.message}`); + }).then(content => { + const key = cacheKeyFor(content, needSourceMap); + const cached = readCache(key); + // original is the pre-transpile file content — kept alongside the + // transpiled code (not persisted to the on-disk cache, which is + // keyed on it and can just re-read it from `content` here) so the + // source map's sourcesContent can show real original source + // instead of the transpiled/dedup'd output under the original + // file's name. + if (cached !== null) return { code: cached.code, map: cached.map, original: content }; + + try { + const transpiled = transpileToES5(content, f, needSourceMap); + writeCache(key, { code: transpiled.code, map: transpiled.map }); + return { ...transpiled, original: content }; + } catch (err) { + throw new Error(`sdk-concat-loader: failed to transpile ${f} to ES5: ${err.message}`); + } + }) + )).then(transpiled => { + // Sequential (not part of the parallel read/transpile above) so "first + // occurrence" tracking follows the files' configured concatenation + // order, not whichever file's transpile happened to resolve first. + const emittedHelpers = new Set(); + const contents = transpiled.map(t => stripDuplicateHelpers(t.code, emittedHelpers)); + + const isAll = opts.chunk === 'all'; + const prefix = isAll ? '(function(window, undefined) {\n' : ''; + const suffix = isAll ? '\n})(window);' : ''; - // sdk-all.js: wrap in (function(window, undefined){...})(window) to match - // the original Closure Compiler --chunk_wrapper for the sdk-all chunk. - // sdk-all-min.js: no wrapper — it exposes bootstrap globals consumed by sdk-all.js. - return opts.chunk === 'all' - ? `(function(window, undefined) {\n${content}\n})(window);` - : content; + // --- Build output + optional per-file source map --- + const bundleName = opts.chunk === 'min' ? 'sdk-all-min.js' : 'sdk-all.js'; + const gen = needSourceMap ? new SourceMapGenerator({ file: bundleName }) : null; + + let result = prefix + polyfillContent; + // Generated line cursor: prefix occupies line 1 (the wrapper open), content starts at line 2. + // polyfillContent (min chunk only) is opaque vendor code — not remapped — so it + // just shifts where the first real source file's mappings begin. + let genLine = (isAll ? 2 : 1) + (polyfillContent.match(/\n/g) || []).length; + + for (let i = 0; i < files.length; i++) { + const content = contents[i]; + const nlCount = (content.match(/\n/g) || []).length; + const hasTrail = content.endsWith('\n'); + + if (gen) { + if (transpiled[i].map) { + // Use Babel's own generated↔original mapping for this file + // (transform may change line count) rather than assuming a + // 1:1 line correspondence. Offset every generated line by + // where this file's content starts in the combined output. + const consumer = new SourceMapConsumer(transpiled[i].map); + consumer.eachMapping(m => { + if (m.originalLine == null) return; + gen.addMapping({ + generated: { line: genLine + (m.generatedLine - 1), column: m.generatedColumn }, + original: { line: m.originalLine, column: m.originalColumn }, + source: files[i], + name: m.name || undefined, + }); + }); + } else { + // Cache hit against a pre-existing no-map entry, or a file + // babel left byte-for-byte unchanged: fall back to a + // best-effort 1:1 line mapping. + const srcLines = content.split('\n'); + const mapped = hasTrail ? srcLines.length - 1 : srcLines.length; + for (let j = 0; j < mapped; j++) { + gen.addMapping({ + generated: { line: genLine + j, column: 0 }, + source: files[i], + original: { line: j + 1, column: 0 }, + }); + } + } + // Original (pre-transpile) source, not the transformed `content` + // above — a debugger must show the real file it's labeled as. + gen.setSourceContent(files[i], transpiled[i].original); + } + + result += content; + + if (hasTrail) { + // Trailing \n already provides the inter-file separator. + genLine += nlCount; + } else { + result += '\n'; + genLine += nlCount + 1; + } + } + + // commonDefines.js (part of the 'min' chunk) hardcodes + // window.AscCommon.g_cXxx to placeholder values — DefinePlugin above + // can't touch that assignment's LHS (see the comment at its call site + // in webpack.sdk.factory.mjs). Patch the real build metadata in right + // after, so the runtime-visible globals match the folded call-sites. + if (opts.chunk === 'min' && opts.buildMeta) { + const { companyName, version, buildNumber, beta } = opts.buildMeta; + result += + '\nwindow.AscCommon.g_cCompanyName = ' + JSON.stringify(companyName) + ';' + + '\nwindow.AscCommon.g_cProductVersion = ' + JSON.stringify(version) + ';' + + '\nwindow.AscCommon.g_cBuildNumber = ' + JSON.stringify(buildNumber) + ';' + + '\nwindow.AscCommon.g_cIsBeta = ' + JSON.stringify(beta) + ';\n'; + } + + result += suffix; + + callback(null, result, gen ? gen.toJSON() : undefined); + }).catch(callback); }; +// Exposed for unit testing only (build/test/sdk-concat.test.cjs) — these are +// pure enough to test directly without spinning up a full webpack build. +module.exports.transpileToES5 = transpileToES5; +module.exports.stripDuplicateHelpers = stripDuplicateHelpers; +module.exports.cacheKeyFor = cacheKeyFor; + module.exports.schema = { type: 'object', properties: { @@ -189,6 +402,15 @@ module.exports.schema = { platform: { type: 'string', enum: ['', 'desktop', 'mobile'] }, srcRoot: { type: 'string' }, addonDirs: { type: 'array', items: { type: 'string' } }, + buildMeta: { + type: 'object', + properties: { + companyName: { type: 'string' }, + version: { type: 'string' }, + buildNumber: { type: 'string' }, + beta: { type: 'string' }, + }, + }, }, required: ['module', 'chunk'], additionalProperties: false, diff --git a/build/npm-shrinkwrap.json b/build/npm-shrinkwrap.json index bdb770ac9b..d3dbaae50f 100644 --- a/build/npm-shrinkwrap.json +++ b/build/npm-shrinkwrap.json @@ -8,876 +8,2351 @@ "name": "common", "version": "0.0.0", "dependencies": { + "@babel/core": "^7.26.0", + "@babel/preset-env": "^7.26.0", "glob": "^8.1.0", - "google-closure-compiler": "^20240317.0.0", - "grunt": "^1.6.1", - "grunt-contrib-clean": "^2.0.0", - "grunt-contrib-copy": "^1.0.0" - } - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "source-map-js": "^1.2.1", + "terser": "^5.20.0", + "terser-webpack-plugin": "^5.3.11", + "webpack": "^5.98.0", + "webpack-cli": "^6.0.1" + }, + "devDependencies": { + "cross-env": "^7.0.3" + }, "engines": { - "node": ">=0.10.0" + "node": ">=18.13.0" } }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/argparse/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" - }, - "node_modules/array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" } }, - "node_modules/array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" } }, - "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { - "node": ">=8" + "node": ">=6.9.0" } }, - "node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "license": "MIT", "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "@babel/types": "^7.29.7" }, "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" } }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, "engines": { - "node": ">=0.8" + "node": ">=6.9.0" } }, - "node_modules/clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==", + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", + "semver": "^6.3.1" + }, "engines": { - "node": ">= 0.10" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==" - }, - "node_modules/cloneable-readable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.29.7.tgz", + "integrity": "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==", + "license": "MIT", "dependencies": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" + "@babel/helper-annotate-as-pure": "^7.29.7", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" }, - "engines": { - "node": ">=7.0.0" + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", "engines": { - "node": ">=0.1.90" + "node": ">=6.9.0" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, "engines": { - "node": "*" + "node": ">=6.9.0" } }, - "node_modules/detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" } }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, "engines": { - "node": ">=0.8.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" }, "engines": { - "node": ">=4" + "node": ">=6.9.0" } }, - "node_modules/eventemitter2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", - "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=" - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "license": "MIT", "engines": { - "node": ">= 0.8.0" + "node": ">=6.9.0" } }, - "node_modules/expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.29.7.tgz", + "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==", + "license": "MIT", "dependencies": { - "homedir-polyfill": "^1.0.1" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-wrap-function": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "node_modules/file-sync-cmp": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz", - "integrity": "sha1-peeo/7+kk7Q7kju9TKiaU7Y7YSs=" - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", + "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { - "node": ">=8" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/findup-sync": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", - "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==", + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "license": "MIT", "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.3", - "micromatch": "^4.0.4", - "resolve-dir": "^1.0.1" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { - "node": ">= 10.13.0" + "node": ">=6.9.0" } }, - "node_modules/fined": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", - "dependencies": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" - }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", "engines": { - "node": ">= 0.10" + "node": ">=6.9.0" } }, - "node_modules/flagged-respawn": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", "engines": { - "node": ">= 0.10" + "node": ">=6.9.0" } }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" } }, - "node_modules/for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "node_modules/@babel/helper-wrap-function": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.29.7.tgz", + "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==", + "license": "MIT", "dependencies": { - "for-in": "^1.0.1" + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/getobject": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz", - "integrity": "sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg==", + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, "engines": { - "node": ">=10" + "node": ">=6.9.0" } }, - "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "@babel/types": "^7.29.7" }, - "engines": { - "node": ">=12" + "bin": { + "parser": "bin/babel-parser.js" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=6.0.0" } }, - "node_modules/global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.29.7.tgz", + "integrity": "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==", + "license": "MIT", "dependencies": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.29.7.tgz", + "integrity": "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==", + "license": "MIT", "dependencies": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/google-closure-compiler": { - "version": "20240317.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20240317.0.0.tgz", - "integrity": "sha512-PlC5aU2vwsypKbxyFNXOW4psDZfhDoOr2dCwuo8VcgQji+HVIgRi2lviO66x2SfTi0ilm3kI6rq/RSdOMFczcQ==", + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.29.7.tgz", + "integrity": "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==", + "license": "MIT", "dependencies": { - "chalk": "4.x", - "google-closure-compiler-java": "^20240317.0.0", - "minimist": "1.x", - "vinyl": "2.x", - "vinyl-sourcemaps-apply": "^0.2.0" - }, - "bin": { - "google-closure-compiler": "cli.js" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=10" + "node": ">=6.9.0" }, - "optionalDependencies": { - "google-closure-compiler-linux": "^20240317.0.0", - "google-closure-compiler-osx": "^20240317.0.0", - "google-closure-compiler-windows": "^20240317.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/google-closure-compiler-java": { - "version": "20240317.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20240317.0.0.tgz", - "integrity": "sha512-oWURPChjcCrVfiQOuVtpSoUJVvtOYo41JGEQ2qtArsTGmk/DpWh40vS6hitwKRM/0YzJX/jYUuyt9ibuXXJKmg==" - }, - "node_modules/google-closure-compiler-linux": { - "version": "20240317.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20240317.0.0.tgz", - "integrity": "sha512-dYLtcbbJdbbBS0lTy9SzySdVv/aGkpyTekQiW4ADhT/i1p1b4r0wQTKj6kpVVmFvbZ6t9tW/jbXc9EXXNUahZw==", - "cpu": [ - "x32", - "x64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/google-closure-compiler-osx": { - "version": "20240317.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20240317.0.0.tgz", - "integrity": "sha512-0mABwjD4HP11rikFd8JRIb9OgPqn9h3o3wS0otufMfmbwS7zRpnnoJkunifhORl3VoR1gFm6vcTC9YziTEFdOw==", - "cpu": [ - "x32", - "x64", - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/google-closure-compiler-windows": { - "version": "20240317.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20240317.0.0.tgz", - "integrity": "sha512-fTueVFzNOWURFlXZmrFkAB7yA+jzpA2TeDOYeBEFwVlVGHwi8PV3Q9vCIWlbkE8wLpukKEg5wfRHYrLwVPINCA==", - "cpu": [ - "x32", - "x64" - ], - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/google-closure-compiler/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.7.tgz", + "integrity": "sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA==", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { - "node": ">=8" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/google-closure-compiler/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.29.7.tgz", + "integrity": "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7" }, "engines": { - "node": ">=10" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "@babel/core": "^7.13.0" } }, - "node_modules/google-closure-compiler/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.29.7.tgz", + "integrity": "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { - "node": ">=8" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/grunt": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.6.2.tgz", - "integrity": "sha512-bUzh5nA/P5L66ihXTDP6J5BGnMB/8lXJXejYWSbH4Y4TvWM9t2S39sggQDYYQlx06cYcCsmu63HMYHGCIzUVfg==", + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "license": "MIT", - "dependencies": { - "dateformat": "~4.6.2", - "eventemitter2": "~0.4.13", - "exit": "~0.1.2", - "findup-sync": "~5.0.0", - "glob": "~7.1.6", - "grunt-cli": "^1.4.3", - "grunt-known-options": "~2.0.0", - "grunt-legacy-log": "~3.0.0", - "grunt-legacy-util": "~2.0.1", - "iconv-lite": "~0.6.3", - "js-yaml": "~3.14.0", - "minimatch": "^3.1.5", - "nopt": "^5.0.0" + "engines": { + "node": ">=6.9.0" }, - "bin": { - "grunt": "bin/grunt" + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.29.7.tgz", + "integrity": "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=16" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/grunt-cli": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.5.0.tgz", - "integrity": "sha512-rILKAFoU0dzlf22SUfDtq2R1fosChXXlJM5j7wI6uoW8gwmXDXzbUvirlKZSYCdXl3LXFbR+8xyS+WFo+b6vlA==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", "license": "MIT", "dependencies": { - "grunt-known-options": "~2.0.0", - "interpret": "~1.1.0", - "liftup": "~3.0.1", - "nopt": "~5.0.0", - "v8flags": "^4.0.1" - }, - "bin": { - "grunt": "bin/grunt" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=10" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/grunt-contrib-clean": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-2.0.1.tgz", - "integrity": "sha512-uRvnXfhiZt8akb/ZRDHJpQQtkkVkqc/opWO4Po/9ehC2hPxgptB9S6JHDC/Nxswo4CJSM0iFPT/Iym3cEMWzKA==", + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "license": "MIT", "dependencies": { - "async": "^3.2.3", - "rimraf": "^2.6.2" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { - "node": ">=12" + "node": ">=6.9.0" }, "peerDependencies": { - "grunt": ">=0.4.5" + "@babel/core": "^7.0.0" } }, - "node_modules/grunt-contrib-copy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz", - "integrity": "sha1-cGDGWB6QS4qw0A8HbgqPbj58NXM=", + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz", + "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", + "license": "MIT", "dependencies": { - "chalk": "^1.1.1", - "file-sync-cmp": "^0.1.0" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/grunt-known-options": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-2.0.0.tgz", - "integrity": "sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA==", + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz", + "integrity": "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/grunt-legacy-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz", - "integrity": "sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA==", + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.29.7.tgz", + "integrity": "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==", + "license": "MIT", "dependencies": { - "colors": "~1.1.2", - "grunt-legacy-log-utils": "~2.1.0", - "hooker": "~0.2.3", - "lodash": "~4.17.19" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7" }, "engines": { - "node": ">= 0.10.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/grunt-legacy-log-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz", - "integrity": "sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw==", + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.29.7.tgz", + "integrity": "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==", + "license": "MIT", "dependencies": { - "chalk": "~4.1.0", - "lodash": "~4.17.19" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=10" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/grunt-legacy-log-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.29.7.tgz", + "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=8" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/grunt-legacy-log-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz", + "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=10" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/grunt-legacy-log-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.29.7.tgz", + "integrity": "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=8" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" } }, - "node_modules/grunt-legacy-util": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz", - "integrity": "sha512-2bQiD4fzXqX8rhNdXkAywCadeqiPiay0oQny77wA2F3WF4grPJXCvAcyoWUJV+po/b15glGkxuSiQCK299UC2w==", + "node_modules/@babel/plugin-transform-classes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz", + "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==", + "license": "MIT", "dependencies": { - "async": "~3.2.0", - "exit": "~0.1.2", - "getobject": "~1.0.0", - "hooker": "~0.2.3", - "lodash": "~4.17.21", - "underscore.string": "~3.3.5", - "which": "~2.0.2" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { - "node": ">=10" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/grunt-legacy-util/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.29.7.tgz", + "integrity": "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==", + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/template": "^7.29.7" }, "engines": { - "node": ">= 8" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/grunt/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", + "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { - "node": "*" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/grunt/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "license": "ISC", + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.29.7.tgz", + "integrity": "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": "*" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.29.7.tgz", + "integrity": "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==", + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">= 0.4.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==", + "license": "MIT", "dependencies": { - "ansi-regex": "^2.0.0" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.29.7.tgz", + "integrity": "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.29.7.tgz", + "integrity": "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.29.7.tgz", + "integrity": "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.29.7.tgz", + "integrity": "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.29.7.tgz", + "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.29.7.tgz", + "integrity": "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.29.7.tgz", + "integrity": "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.29.7.tgz", + "integrity": "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.29.7.tgz", + "integrity": "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.29.7.tgz", + "integrity": "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.29.7.tgz", + "integrity": "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.7.tgz", + "integrity": "sha512-TM2ZcQLoG2/y4HODiStCo10DibYhWhGWAwVv+EQKmG/7GFl0N+AAmUiXOMKM+aiJ9XBJ9AHVZBvTzMnJ2sM3cQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.29.7.tgz", + "integrity": "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.29.7.tgz", + "integrity": "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz", + "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.29.7.tgz", + "integrity": "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.29.7.tgz", + "integrity": "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.29.7.tgz", + "integrity": "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.29.7.tgz", + "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", + "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.29.7.tgz", + "integrity": "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.29.7.tgz", + "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.29.7.tgz", + "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.29.7.tgz", + "integrity": "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.7.tgz", + "integrity": "sha512-rNNFV0DBAJp988xW2DOntfDoYn1eR8GGF5AT5vYc+rjyfaQkM242c9tZUHHPe7KYaiJizXPWhQTzzdbXySyhBw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.29.7.tgz", + "integrity": "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.29.7.tgz", + "integrity": "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz", + "integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.29.7.tgz", + "integrity": "sha512-/u5K1QWada7tbYNqTjMh96718g9NTwh9tfPJMsSmVsQwGT447FskV+KcfeXkXq2GWki4EM/MuTdmBec+hOuVTQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.29.7.tgz", + "integrity": "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz", + "integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.29.7.tgz", + "integrity": "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.29.7.tgz", + "integrity": "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.29.7.tgz", + "integrity": "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.29.7.tgz", + "integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.29.7.tgz", + "integrity": "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.7.tgz", + "integrity": "sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.29.7", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.29.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.29.7", + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.29.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.29.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.29.7", + "@babel/plugin-syntax-import-attributes": "^7.29.7", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.29.7", + "@babel/plugin-transform-async-generator-functions": "^7.29.7", + "@babel/plugin-transform-async-to-generator": "^7.29.7", + "@babel/plugin-transform-block-scoped-functions": "^7.29.7", + "@babel/plugin-transform-block-scoping": "^7.29.7", + "@babel/plugin-transform-class-properties": "^7.29.7", + "@babel/plugin-transform-class-static-block": "^7.29.7", + "@babel/plugin-transform-classes": "^7.29.7", + "@babel/plugin-transform-computed-properties": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-dotall-regex": "^7.29.7", + "@babel/plugin-transform-duplicate-keys": "^7.29.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-dynamic-import": "^7.29.7", + "@babel/plugin-transform-explicit-resource-management": "^7.29.7", + "@babel/plugin-transform-exponentiation-operator": "^7.29.7", + "@babel/plugin-transform-export-namespace-from": "^7.29.7", + "@babel/plugin-transform-for-of": "^7.29.7", + "@babel/plugin-transform-function-name": "^7.29.7", + "@babel/plugin-transform-json-strings": "^7.29.7", + "@babel/plugin-transform-literals": "^7.29.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.29.7", + "@babel/plugin-transform-member-expression-literals": "^7.29.7", + "@babel/plugin-transform-modules-amd": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-modules-systemjs": "^7.29.7", + "@babel/plugin-transform-modules-umd": "^7.29.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-new-target": "^7.29.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.29.7", + "@babel/plugin-transform-numeric-separator": "^7.29.7", + "@babel/plugin-transform-object-rest-spread": "^7.29.7", + "@babel/plugin-transform-object-super": "^7.29.7", + "@babel/plugin-transform-optional-catch-binding": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/plugin-transform-private-methods": "^7.29.7", + "@babel/plugin-transform-private-property-in-object": "^7.29.7", + "@babel/plugin-transform-property-literals": "^7.29.7", + "@babel/plugin-transform-regenerator": "^7.29.7", + "@babel/plugin-transform-regexp-modifiers": "^7.29.7", + "@babel/plugin-transform-reserved-words": "^7.29.7", + "@babel/plugin-transform-shorthand-properties": "^7.29.7", + "@babel/plugin-transform-spread": "^7.29.7", + "@babel/plugin-transform-sticky-regex": "^7.29.7", + "@babel/plugin-transform-template-literals": "^7.29.7", + "@babel/plugin-transform-typeof-symbol": "^7.29.7", + "@babel/plugin-transform-unicode-escapes": "^7.29.7", + "@babel/plugin-transform-unicode-property-regex": "^7.29.7", + "@babel/plugin-transform-unicode-regex": "^7.29.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.29.7", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "license": "MIT", + "engines": { + "node": ">=14.17.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.1.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", + "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", + "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", + "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", + "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", + "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8", + "core-js-compat": "^3.48.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.44", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.44.tgz", + "integrity": "sha512-T3ghW+sl/ZJ8w1v/yQx3qvJ9040DWoLBz8JT/CILbAKcFyG9b2MRe75v6W5uXjv6uH1lumK2Kv46y2zSkcej0Q==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, "engines": { - "node": ">=8" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "license": "MIT", + "engines": { + "node": ">=6.0" } }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "license": "MIT", "dependencies": { - "parse-passwd": "^1.0.0" + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=6" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/core-js-compat": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" } }, - "node_modules/hooker": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", - "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, "engines": { - "node": "*" + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" } }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 8" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.394", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.394.tgz", + "integrity": "sha512-Wmt2Gm0o8JWBuGgmc4XZ0u9s1RaCRqhxP47phplmfg04+qypTUurpeJGP45A7Fhv7jdrrVH44PLlR9qXo37cVQ==", + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.24.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.3.tgz", + "integrity": "sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + "node_modules/envinfo": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } }, - "node_modules/interpret": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=" + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } }, - "node_modules/is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "node_modules/es-module-lexer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "license": "BSD-2-Clause", "dependencies": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" + "estraverse": "^5.2.0" }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/is-core-module": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", - "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", + "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", "dependencies": { - "has": "^1.0.3" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dependencies": { - "is-extglob": "^2.1.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "engines": { - "node": ">=0.12.0" + "node": ">=8" } }, - "node_modules/is-plain-object": { + "node_modules/hasown": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", "dependencies": { - "isobject": "^3.0.1" + "function-bind": "^1.1.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "license": "MIT", "dependencies": { - "is-unc-path": "^1.0.0" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "license": "MIT", "dependencies": { - "unc-path-regex": "^0.1.2" + "hasown": "^2.0.3" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, "engines": { "node": ">=0.10.0" } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -891,16 +2366,69 @@ "node": ">=0.10.0" } }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", "bin": { - "js-yaml": "bin/js-yaml.js" + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" } }, "node_modules/kind-of": { @@ -911,72 +2439,59 @@ "node": ">=0.10.0" } }, - "node_modules/liftup": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/liftup/-/liftup-3.0.1.tgz", - "integrity": "sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw==", - "dependencies": { - "extend": "^3.0.2", - "findup-sync": "^4.0.0", - "fined": "^1.2.0", - "flagged-respawn": "^1.0.1", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.1", - "rechoir": "^0.7.0", - "resolve": "^1.19.0" - }, + "node_modules/loader-runner": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", + "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/liftup/node_modules/findup-sync": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", - "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==", + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^4.0.2", - "resolve-dir": "^1.0.1" + "p-locate": "^4.1.0" }, "engines": { - "node": ">= 8" + "node": ">=8" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" }, - "node_modules/make-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "engines": { - "node": ">=0.10.0" + "yallist": "^3.0.2" } }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">= 0.6" } }, "node_modules/minimatch": { @@ -998,101 +2513,147 @@ "balanced-match": "^1.0.0" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "license": "ISC", + "node_modules/minimizer-webpack-plugin": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/minimizer-webpack-plugin/-/minimizer-webpack-plugin-5.6.1.tgz", + "integrity": "sha512-DoeAZz8Q1C1znwsUzej1fdoi4jCf7/+Em27ouLqfK/+3m8G+D7yDhUwrc3CNhjSzGUN1kn7Iv4sWmjflQHenpw==", + "license": "MIT", "dependencies": { - "abbrev": "1" + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" }, - "bin": { - "nopt": "bin/nopt.js" + "engines": { + "node": ">= 10.13.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=18" } }, - "node_modules/object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dependencies": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" + "wrappy": "1" } }, - "node_modules/object.map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", "dependencies": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" + "p-try": "^2.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", "dependencies": { - "isobject": "^3.0.1" + "p-limit": "^2.2.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" + "node": ">=8" } }, - "node_modules/parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", - "dependencies": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", "engines": { - "node": ">=0.8" + "node": ">=6" } }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/path-parse": { @@ -1100,203 +2661,217 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, - "node_modules/path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "license": "MIT", "dependencies": { - "path-root-regex": "^0.1.0" + "find-up": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", - "engines": { - "node": ">=0.10.0" - } + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "license": "MIT" }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "engines": { - "node": ">=8.6" + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "engines": { + "node": ">=4" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "license": "MIT", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" } }, - "node_modules/rechoir": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", - "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.2.tgz", + "integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==", + "license": "BSD-2-Clause", "dependencies": { - "resolve": "^1.9.0" + "jsesc": "~3.1.0" }, - "engines": { - "node": ">= 0.10" + "bin": { + "regjsparser": "bin/parser" } }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==" - }, - "node_modules/replace-ext": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", - "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", "engines": { - "node": ">= 0.10" + "node": ">=0.10.0" } }, "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", "dependencies": { - "is-core-module": "^2.11.0", + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "license": "MIT", "dependencies": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" + "resolve-from": "^5.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" }, "engines": { - "node": "*" + "node": ">= 10.13.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "kind-of": "^6.0.2" }, "engines": { - "node": "*" + "node": ">=8" } }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", "dependencies": { - "safe-buffer": "~5.1.0" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dependencies": { - "ansi-regex": "^2.0.0" - }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -1308,90 +2883,341 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.49.0.tgz", + "integrity": "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==", + "license": "BSD-2-Clause", "dependencies": { - "is-number": "^7.0.0" + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" }, "engines": { - "node": ">=8.0" + "node": ">=10" } }, - "node_modules/unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "node_modules/terser-webpack-plugin": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz", + "integrity": "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "license": "MIT", + "engines": { + "node": ">=4" } }, - "node_modules/underscore.string": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.6.tgz", - "integrity": "sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==", + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "license": "MIT", "dependencies": { - "sprintf-js": "^1.1.1", - "util-deprecate": "^1.0.2" + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" }, "engines": { - "node": "*" + "node": ">=4" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "license": "MIT", + "engines": { + "node": ">=4" + } }, - "node_modules/v8flags": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-4.0.1.tgz", - "integrity": "sha512-fcRLaS4H/hrZk9hYwbdRM35D0U8IYMfEClhXxCivOojl+yTRAZH3Zy2sSy6qVCiGbV9YAtPssP6jaChqC9vPCg==", + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", "license": "MIT", "engines": { - "node": ">= 10.13.0" + "node": ">=4" } }, - "node_modules/vinyl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/watchpack": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", + "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", + "license": "MIT", "dependencies": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" + "graceful-fs": "^4.1.2" }, "engines": { - "node": ">= 0.10" + "node": ">=10.13.0" } }, - "node_modules/vinyl-sourcemaps-apply": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", - "integrity": "sha512-+oDh3KYZBoZC8hfocrbrxbLUeaYtQK7J5WU5Br9VqWqmCll3tFJqKp97GC9GmMsVIL0qnx2DgEDVxdo5EZ5sSw==", + "node_modules/webpack": { + "version": "5.108.4", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.108.4.tgz", + "integrity": "sha512-yur8LyJoeiWh47dErD+Ok7vlbmDsJ3UbbRPAoxbGJ54WpE2y5yVo5G/inUzujnYgw3tPmBRdn+G7PoxXaYC33w==", + "license": "MIT", "dependencies": { - "source-map": "^0.5.1" + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.16.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.22.2", + "es-module-lexer": "^2.1.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "graceful-fs": "^4.2.11", + "loader-runner": "^4.3.2", + "mime-db": "^1.54.0", + "minimizer-webpack-plugin": "^5.6.1", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "watchpack": "^2.5.2", + "webpack-sources": "^3.5.0" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } } }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "node_modules/webpack-cli": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", + "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" + "@discoveryjs/json-ext": "^0.6.1", + "@webpack-cli/configtest": "^3.0.1", + "@webpack-cli/info": "^3.0.1", + "@webpack-cli/serve": "^3.0.1", + "colorette": "^2.0.14", + "commander": "^12.1.0", + "cross-spawn": "^7.0.3", + "envinfo": "^7.14.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^6.0.1" }, "bin": { - "which": "bin/which" + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.82.0" + }, + "peerDependenciesMeta": { + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/webpack-cli/node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-cli/node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.1.tgz", + "integrity": "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" } }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "license": "MIT" + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" } } } diff --git a/build/package.json b/build/package.json index c665e5b15f..3da0d5064d 100644 --- a/build/package.json +++ b/build/package.json @@ -4,19 +4,33 @@ "homepage": "https://www.onlyoffice.com", "private": true, "type": "module", + "engines": { + "node": ">=18.13.0" + }, "dependencies": { + "@babel/core": "^7.26.0", + "@babel/preset-env": "^7.26.0", "glob": "^8.1.0", + "source-map-js": "^1.2.1", "terser": "^5.20.0", "terser-webpack-plugin": "^5.3.11", "webpack": "^5.98.0", "webpack-cli": "^6.0.1" }, + "devDependencies": { + "cross-env": "^7.0.3" + }, "scripts": { - "build": "node scripts/build-pipeline.js", + "build": "node scripts/build-pipeline.cjs", "build:word": "webpack --config webpack.word.mjs", "build:cell": "webpack --config webpack.cell.mjs", "build:slide": "webpack --config webpack.slide.mjs", "build:visio": "webpack --config webpack.visio.mjs", - "develop": "node scripts/build-develop.js" + "watch:word": "cross-env NODE_ENV=development webpack --config webpack.word.mjs --watch", + "watch:cell": "cross-env NODE_ENV=development webpack --config webpack.cell.mjs --watch", + "watch:slide": "cross-env NODE_ENV=development webpack --config webpack.slide.mjs --watch", + "watch:visio": "cross-env NODE_ENV=development webpack --config webpack.visio.mjs --watch", + "develop": "node scripts/build-develop.cjs", + "test": "node --test test/" } } diff --git a/build/package.json.webpack b/build/package.json.webpack deleted file mode 100644 index c665e5b15f..0000000000 --- a/build/package.json.webpack +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "common", - "version": "0.0.0", - "homepage": "https://www.onlyoffice.com", - "private": true, - "type": "module", - "dependencies": { - "glob": "^8.1.0", - "terser": "^5.20.0", - "terser-webpack-plugin": "^5.3.11", - "webpack": "^5.98.0", - "webpack-cli": "^6.0.1" - }, - "scripts": { - "build": "node scripts/build-pipeline.js", - "build:word": "webpack --config webpack.word.mjs", - "build:cell": "webpack --config webpack.cell.mjs", - "build:slide": "webpack --config webpack.slide.mjs", - "build:visio": "webpack --config webpack.visio.mjs", - "develop": "node scripts/build-develop.js" - } -} diff --git a/build/scripts/build-develop.js b/build/scripts/build-develop.cjs similarity index 50% rename from build/scripts/build-develop.js rename to build/scripts/build-develop.cjs index b2bf5ebdce..760d69b144 100644 --- a/build/scripts/build-develop.js +++ b/build/scripts/build-develop.cjs @@ -34,97 +34,22 @@ const path = require('path'); const fs = require('fs'); const url = require('url'); +const { loadAllConfigs, getFilesMin, getFilesAll, expandGlobs } = require('../lib/sdk-configs.cjs'); +const { parseAddonDirs, resolveBuildRoot } = require('../lib/env.cjs'); const BUILD_DIR = path.resolve(__dirname, '..'); const SRC_ROOT = path.resolve(BUILD_DIR, '..'); -const BUILD_ROOT = process.env.BUILD_ROOT - ? path.resolve(process.env.BUILD_ROOT, 'sdkjs') - : path.resolve(BUILD_DIR, '..', 'deploy', 'sdkjs'); +const BUILD_ROOT = resolveBuildRoot(BUILD_DIR); const DEVELOP_ROOT = process.env.BUILD_ROOT ? path.join(process.env.BUILD_ROOT, 'sdkjs', 'develop', 'sdkjs') : path.join(BUILD_DIR, '..', 'develop', 'sdkjs'); const platform = process.env.SDK_PLATFORM || ''; -const addonDirs = process.env.SDK_ADDONS - ? process.env.SDK_ADDONS.split(path.delimiter).filter(Boolean) - : []; +const addonDirs = parseAddonDirs(); const compiled = process.env.COMPILED === '1'; -// ---- Config loading (mirrors CConfig from Gruntfile.js) -------------------- - -function loadJsonConfig(configsDir, name) { - const file = path.join(configsDir, name + '.json'); - if (!fs.existsSync(file)) return null; - return JSON.parse(fs.readFileSync(file, 'utf8')); -} - -function fixPath(obj, basePath) { - if (Array.isArray(obj)) { - for (let i = 0; i < obj.length; i++) obj[i] = path.join(basePath, obj[i]); - return; - } - for (const k of Object.keys(obj)) fixPath(obj[k], basePath); -} - -function mergeConfigs(base, addon) { - for (const k of Object.keys(addon)) { - if (Array.isArray(addon[k])) { - base[k] = Array.isArray(base[k]) ? base[k].concat(addon[k]) : addon[k]; - } else { - if (!base[k]) base[k] = {}; - mergeConfigs(base[k], addon[k]); - } - } -} - -function loadAllConfigs() { - const configs = {}; - const configsDir = path.join(SRC_ROOT, 'configs'); - for (const name of ['word', 'cell', 'slide', 'visio']) { - const cfg = loadJsonConfig(configsDir, name); - if (cfg) { fixPath(cfg, SRC_ROOT); configs[name] = cfg; } - } - for (const addonDir of addonDirs) { - for (const name of ['word', 'cell', 'slide', 'visio']) { - if (!configs[name]) continue; - const addon = loadJsonConfig(path.join(addonDir, 'configs'), name); - if (!addon) continue; - fixPath(addon, addonDir); - mergeConfigs(configs[name], addon); - } - } - return configs; -} - -function getFilesMin(sdkCfg) { - let files = (sdkCfg['min'] || []).slice(); - if (platform === 'mobile' && sdkCfg['mobile_banners']) { - files = sdkCfg['mobile_banners']['min'].concat(files); - } - if (platform === 'desktop' && sdkCfg['desktop']) { - files = files.concat(sdkCfg['desktop']['min']); - } - return files; -} - -function getFilesAll(sdkCfg) { - let files = (sdkCfg['common'] || []).slice(); - if (platform === 'mobile') { - if (sdkCfg['mobile_banners']) { - files = sdkCfg['mobile_banners']['common'].concat(files); - } - const exclude = sdkCfg['exclude_mobile'] || []; - files = files.filter(f => !exclude.includes(f)); - files = files.concat(sdkCfg['mobile'] || []); - } - if (platform === 'desktop' && sdkCfg['desktop']) { - files = files.concat(sdkCfg['desktop']['common']); - } - return files; -} - // ---- writeScripts (exact port of writeScripts() from Gruntfile.js) --------- function fixUrl(arrPaths, basePath) { @@ -138,23 +63,27 @@ function writeScripts(sdkCfg, name) { ]; if (compiled) { - if (process.env.BUILD_ROOT) { - files.push(path.join('..', name, 'sdk-all-min.js')); - } else { - files.push(path.join(BUILD_ROOT, name, 'sdk-all-min.js')); - } + // When process.env.BUILD_ROOT is set (e.g. Docker's /package), BUILD_ROOT + // resolves to a path outside this checkout's directory tree, so + // path.relative(BUILD_DIR, ...) below would compute a bogus path escaping + // out to that external root instead of the sibling compiled bundle. + // Push an already-relative entry in that case instead — mirrors the old + // Gruntfile's writeScripts() special case exactly. + files.push(process.env.BUILD_ROOT + ? path.join('..', name, 'sdk-all-min.js') + : path.join(BUILD_ROOT, name, 'sdk-all-min.js')); } else { files = files.concat( [path.join(SRC_ROOT, 'common', 'applyDocumentChanges.js')], - getFilesMin(sdkCfg), - getFilesAll(sdkCfg), + expandGlobs(getFilesMin(sdkCfg, platform)), + expandGlobs(getFilesAll(sdkCfg, platform)), ); } // Convert absolute paths to relative URL strings anchored at build/ // (mirrors fixUrl(files, '../../../../sdkjs/build/') from Gruntfile.js) files = fixUrl( - files.map(f => path.relative(BUILD_DIR, f)), + files.map(f => path.isAbsolute(f) ? path.relative(BUILD_DIR, f) : f), '../../../../sdkjs/build/', ); @@ -169,17 +98,36 @@ function writeScripts(sdkCfg, name) { process.stdout.write(`build-develop: wrote ${outFile}\n`); } +// Replaces the grunt copy-standalone task: device_scale.js is loaded directly +// by HTML templates (outside the SDK bundle), so it needs a standalone copy +// into the deploy root alongside develop/, not just inside the min/all bundles. +// +// Only needed when this script runs standalone (`npm run develop`, outside the +// full build-pipeline). The full pipeline already deploys a properly processed +// (terser + license header) copy via deploy-assets.cjs — running this here too +// would clobber it with a raw, unprocessed file. build-pipeline.cjs sets +// SKIP_STANDALONE=1 when invoking this script for that reason. +function copyStandalone() { + const src = path.join(SRC_ROOT, 'common', 'device_scale.js'); + const dest = path.join(BUILD_ROOT, 'common', 'device_scale.js'); + fs.mkdirSync(path.dirname(dest), { recursive: true }); + fs.copyFileSync(src, dest); + process.stdout.write(`build-develop: wrote ${dest}\n`); +} + // ---- main ------------------------------------------------------------------ function main() { - const configs = loadAllConfigs(); + const configs = loadAllConfigs(SRC_ROOT, addonDirs); for (const name of ['word', 'cell', 'slide', 'visio']) { - if (!configs[name]) { - process.stderr.write(`build-develop: no config for ${name}, skipping\n`); - continue; + if (!configs[name] || !configs[name]['sdk']) { + throw new Error(`build-develop: missing sdk config for ${name}`); } writeScripts(configs[name]['sdk'], name); } + if (process.env.SKIP_STANDALONE !== '1') { + copyStandalone(); + } } main(); diff --git a/build/scripts/build-pipeline.cjs b/build/scripts/build-pipeline.cjs new file mode 100644 index 0000000000..c696e6705b --- /dev/null +++ b/build/scripts/build-pipeline.cjs @@ -0,0 +1,388 @@ +#!/usr/bin/env node +/** + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + */ + +'use strict'; + +// Full grunt-free build pipeline for Euro Office sdkjs. +// +// Usage (from sdkjs/build/): +// PRODUCT_VERSION=9.2.1 BUILD_ROOT=/path/to/deploy node scripts/build-pipeline.cjs +// +// Options (env vars): +// PRODUCT_VERSION default '0.0.0' +// BUILD_ROOT default ../deploy/sdkjs +// BUILD_NUMBER default '0' +// COMPANY_NAME default 'onlyoffice' +// SDK_PLATFORM '' | 'desktop' | 'mobile' — passed through to webpack configs +// SDK_ADDONS path.delimiter-separated addon directories +// SKIP_DEVELOP set to '1' to skip develop scripts generation +// +// Phase layout (wall-clock optimised): +// Phase 1 — parallel: deploy-assets + webpack ×4 (word, cell, slide, visio) +// Each webpack config runs 2 compiler configs (min + all chunk) in parallel. +// Phase 2 — sequential: build-develop (writes develop/sdkjs/{module}/scripts.js) + +const { spawn } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const { resolveBuildRoot } = require('../lib/env.cjs'); + +// This pipeline takes all configuration through env vars — it never reads process.argv. +// A stale caller still passing old Grunt CLI flags (--addon=, --desktop=true, --level=, +// --map, --mobile=true, --beta, --formatting=, --src=) would otherwise have those tokens +// silently ignored: the build exits 0 looking complete while quietly missing an addon or +// a whole platform's files. Fail loudly instead so a stale caller breaks visibly. +// Gated on require.main === module: when this file is require()'d by the test suite +// instead of run directly, process.argv reflects the test runner's own invocation +// (e.g. `node --test test/`), not arguments meant for this script. +if (require.main === module && process.argv.length > 2) { + process.stderr.write( + 'build-pipeline: this script takes no CLI arguments — configuration is via env vars.\n' + + ` Got: ${process.argv.slice(2).join(' ')}\n` + + ' Old Grunt flag -> new env var:\n' + + ' --addon=X -> SDK_ADDONS=path/to/X (path.delimiter-separated for multiple)\n' + + ' --desktop=true -> SDK_PLATFORM=desktop\n' + + ' --mobile=true -> SDK_PLATFORM=mobile\n' + + ' --map -> SDK_SOURCE_MAPS=1\n' + + ' --beta=X -> BETA=X\n' + + ' --level=*, --formatting=*, --src=* -> no equivalent (see build/DEVELOPER-GUIDE.md)\n' + ); + process.exit(1); +} + +const BUILD_DIR = path.resolve(__dirname, '..'); + +const SRC_ROOT = path.resolve(BUILD_DIR, '..'); + +const DEFAULT_BUILD_ROOT = path.resolve(BUILD_DIR, '..', 'deploy', 'sdkjs'); + +const BUILD_ROOT = resolveBuildRoot(BUILD_DIR); + +// Guard against a BUILD_ROOT that resolves onto the sdkjs checkout, the +// build/ directory itself, or (bar the known default deploy dir) any other +// path inside the checkout — this is deleted wholesale below via fs.rmSync. +// +// Resolved through realpath (where possible) rather than the raw path, so a +// BUILD_ROOT that is or contains a symlink pointing back into the checkout +// can't slip past the path.relative string comparisons below — those compare +// logical paths and don't themselves follow symlinks. Falls back to the +// literal path when it (or an ancestor) doesn't exist yet — a not-yet-created +// BUILD_ROOT is common (it's often created fresh per build) and can't itself +// be a symlink back into the checkout. +function realOrSelf(p) { + try { + return fs.realpathSync(p); + } catch (_) { + return p; + } +} + +function assertSafeBuildRoot(root) { + const realRoot = realOrSelf(root); + const realSrc = realOrSelf(SRC_ROOT); + const realBuildDir = realOrSelf(BUILD_DIR); + + const relSourceFromRoot = path.relative(realRoot, realSrc); + const rootContainsSource = + relSourceFromRoot === '' || + (!relSourceFromRoot.startsWith('..') && !path.isAbsolute(relSourceFromRoot)); + + if (realRoot === realSrc || realRoot === realBuildDir || rootContainsSource) { + throw new Error(`Refusing to clean unsafe BUILD_ROOT: ${root}`); + } + + const relRootFromSrc = path.relative(realSrc, realRoot); + const insideSource = + relRootFromSrc !== '' && + !relRootFromSrc.startsWith('..') && + !path.isAbsolute(relRootFromSrc); + + if (insideSource && realRoot !== realOrSelf(DEFAULT_BUILD_ROOT)) { + throw new Error(`Refusing to clean in-tree BUILD_ROOT: ${root}`); + } +} + +assertSafeBuildRoot(BUILD_ROOT); + +const PRODUCT_VERSION = process.env.PRODUCT_VERSION || '0.0.0'; +const BUILD_NUMBER = String(process.env.BUILD_NUMBER || process.env.GITHUB_RUN_NUMBER || '0'); +const SKIP_DEVELOP = process.env.SKIP_DEVELOP === '1'; + +// Only pass BUILD_ROOT through when the user actually set it — build-develop.cjs +// branches on process.env.BUILD_ROOT's presence to decide between a relative and +// an absolute output path. Injecting our own default here would force it down +// the "user set it" branch with the wrong (relative) path. +const CHILD_ENV = { + ...process.env, + PRODUCT_VERSION, + BUILD_NUMBER, +}; + +// ---- output helpers (mirrors web-apps/build/scripts/build-pipeline.js) ---- + +const BOLD = s => `\x1b[1m${s}\x1b[0m`; +const DIM = s => `\x1b[2m${s}\x1b[0m`; +const GREEN = s => `\x1b[32m${s}\x1b[0m`; +const RED = s => `\x1b[31m${s}\x1b[0m`; +const CYAN = s => `\x1b[36m${s}\x1b[0m`; +const PAD = 20; + +function elapsed(ms) { + return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`; +} + +function banner(msg) { + process.stdout.write(`\n${BOLD(CYAN('▶ ' + msg))}\n`); +} + +// ---- task runner ----------------------------------------------------------- + +function task(label, cmd, args = [], opts = {}) { + return { label, cmd, args, opts }; +} + +function runTask({ label, cmd, args, opts = {} }) { + let child = null; + // Set only by our own kill() below, i.e. when phase() is aborting the rest + // of a batch after a sibling task already failed. A signal from anywhere + // else (OOM killer, external SIGKILL, …) is a real failure, not an + // intentional abort, and must not be swallowed as a non-fatal "killed". + let killedByUs = false; + const promise = new Promise(resolve => { + const start = Date.now(); + const paddedLabel = label.padEnd(PAD); + const stderrBuf = []; + + child = spawn(cmd, args, { + env: { ...CHILD_ENV, ...(opts.env || {}) }, + cwd: opts.cwd || BUILD_DIR, + stdio: ['ignore', 'pipe', 'pipe'], + }); + + child.stdout.on('data', chunk => { + for (const line of chunk.toString().split('\n')) { + if (line.trim()) process.stdout.write(` ${DIM('[' + label + ']')} ${line}\n`); + } + }); + + child.stderr.on('data', chunk => { stderrBuf.push(chunk.toString()); }); + + child.on('error', err => { + const ms = Date.now() - start; + process.stdout.write(` ${RED('✗')} ${paddedLabel} ${RED('FAILED')} ${DIM(elapsed(ms))}\n`); + process.stderr.write(` spawn error: ${err.message}\n`); + resolve({ label, ms, code: 1 }); + }); + + child.on('exit', (code, signal) => { + const ms = Date.now() - start; + if (signal && killedByUs) { + process.stdout.write(` ${DIM('○')} ${paddedLabel} ${DIM('killed ' + elapsed(ms))}\n`); + if (stderrBuf.length) process.stderr.write(stderrBuf.join('')); + resolve({ label, ms, code: -1 }); + } else if (signal) { + process.stdout.write(` ${RED('✗')} ${paddedLabel} ${RED('KILLED (' + signal + ')')} ${DIM(elapsed(ms))}\n`); + if (stderrBuf.length) process.stderr.write(stderrBuf.join('')); + resolve({ label, ms, code: 1 }); + } else if (code === 0) { + process.stdout.write(` ${GREEN('✓')} ${paddedLabel} ${DIM(elapsed(ms))}\n`); + resolve({ label, ms, code: 0 }); + } else { + process.stdout.write(` ${RED('✗')} ${paddedLabel} ${RED('FAILED')} ${DIM(elapsed(ms))}\n`); + if (stderrBuf.length) process.stderr.write(stderrBuf.join('')); + resolve({ label, ms, code }); + } + }); + }); + return { promise, kill: () => { killedByUs = true; child && child.kill('SIGTERM'); }, label }; +} + +async function phase(title, taskSpecs) { + const count = taskSpecs.length; + banner(`${title} — ${count} task${count !== 1 ? 's' : ''}`); + + const running = taskSpecs.map(runTask); + let aborted = false; + + const results = await Promise.all( + running.map(t => + t.promise.then(r => { + if (r.code > 0 && !aborted) { + aborted = true; + running.forEach(o => { try { o.kill(); } catch (_) {} }); + } + return r; + }) + ) + ); + + const failed = results.filter(r => r.code > 0); + if (failed.length) { + process.stderr.write(RED(`\n✗ ${failed.map(r => r.label).join(', ')} failed — aborting\n`)); + process.exit(1); + } + return results; +} + +// ---- source maps ------------------------------------------------------- + +// Mirrors the old Gruntfile `copy-maps` task: NODE_ENV=development, or +// SDK_SOURCE_MAPS=1 on a production build, enables devtool:'source-map' in +// webpack.sdk.factory.mjs, which writes sdk-all(-min).js.map next to the +// bundles in the deploy directory. Left there, they're served to end users +// and leak full source paths. Neither var set means devtool:false and no +// .map files, so this is a no-op in that case. +function relocateSourceMaps() { + const MODULES = ['word', 'cell', 'slide', 'visio']; + const mapsRoot = path.join(BUILD_DIR, 'maps'); + let moved = 0; + + for (const name of MODULES) { + const moduleDir = path.join(BUILD_ROOT, name); + if (!fs.existsSync(moduleDir)) continue; + + for (const file of fs.readdirSync(moduleDir)) { + if (!file.endsWith('.map')) continue; + fs.mkdirSync(mapsRoot, { recursive: true }); + // Flat `{module}-all(-min).js.map` naming — not a `{module}/` subdirectory — + // matches the legacy Gruntfile.js `copy-maps` layout that + // build/deserializer/download-maps.js (and whatever uploads these + // externally) still expects (`${editor}${'-all(-min).js.map'}`). + const legacyName = name + file.replace(/^sdk/, ''); + const src = path.join(moduleDir, file); + const dest = path.join(mapsRoot, legacyName); + // BUILD_ROOT (moduleDir's ancestor) is very commonly a different mount + // than this checkout's build/ dir — a bind-mounted volume in a dev + // container, a separate Docker layer, an external deploy path passed + // via the BUILD_ROOT env var. plain renameSync throws EXDEV across + // filesystem boundaries; fall back to copy+unlink in that case. + try { + fs.renameSync(src, dest); + } catch (err) { + if (err.code !== 'EXDEV') throw err; + fs.copyFileSync(src, dest); + fs.unlinkSync(src); + } + moved++; + + // The bundle still carries `//# sourceMappingURL=` pointing at + // the now-relocated map — left in place, browsers/devtools request + // that path from the deploy dir and 404 since the map no longer + // lives there. Strip the comment; the map is still on disk under + // mapsRoot for anyone who needs to attach it manually. + const bundleFile = path.join(moduleDir, file.slice(0, -'.map'.length)); + if (fs.existsSync(bundleFile)) { + const bundle = fs.readFileSync(bundleFile, 'utf8'); + const stripped = bundle.replace(/\n?\/\/# sourceMappingURL=.*$/, ''); + if (stripped !== bundle) fs.writeFileSync(bundleFile, stripped); + } + } + } + + if (moved) { + process.stdout.write(` ${DIM(`relocated ${moved} source map(s) to ${mapsRoot}`)}\n`); + } +} + +// ---- pipeline -------------------------------------------------------------- + +const node = process.execPath; +// Resolve via Node's module resolution rather than a hardcoded node_modules/.bin +// path — this stays correct regardless of dependency hoisting (workspaces, +// pnpm, etc.), and a missing/misconfigured webpack fails as a clear +// MODULE_NOT_FOUND instead of a bare ENOENT from spawn(). +const wpCli = require.resolve('webpack-cli/bin/cli.js'); + +const WEBPACK_CONFIGS = [ + 'webpack.word.mjs', + 'webpack.cell.mjs', + 'webpack.slide.mjs', + 'webpack.visio.mjs', +]; + +async function main() { + const wallStart = Date.now(); + + process.stdout.write([ + BOLD('Euro Office sdkjs build pipeline'), + ` BUILD_ROOT ${BUILD_ROOT}`, + ` PRODUCT_VERSION ${PRODUCT_VERSION}`, + ` BUILD_NUMBER ${BUILD_NUMBER}`, + ` SDK_PLATFORM ${process.env.SDK_PLATFORM || '(default)'}`, + ` SKIP_DEVELOP ${SKIP_DEVELOP}`, + '', + ].join('\n')); + + // Clean deploy directory before building. + if (fs.existsSync(BUILD_ROOT)) { + fs.rmSync(BUILD_ROOT, { recursive: true, force: true }); + } + + // Phase 1: all independent work in parallel. + // - deploy-assets: copies CSS, fonts, images, themes, native JS (WHITESPACE compiled) + // - webpack ×4: each produces sdk-all-min.js + sdk-all.js for its module + const phase1Tasks = [ + task('deploy-assets', node, ['scripts/deploy-assets.cjs']), + ...WEBPACK_CONFIGS.map(cfg => { + const name = cfg.replace('webpack.', '').replace('.mjs', ''); + return task(`webpack:${name}`, node, [wpCli, '--config', cfg]); + }), + ]; + + const p1 = await phase('Phase 1 — parallel', phase1Tasks); + + relocateSourceMaps(); + + // Phase 2: develop scripts (fast, sequential is fine). + let p2 = []; + if (!SKIP_DEVELOP) { + p2 = await phase('Phase 2 — develop', [ + // SKIP_STANDALONE=1: deploy-assets (phase 1) already deployed a + // processed device_scale.js; build-develop's copyStandalone would + // overwrite it with an unprocessed raw copy. + task('build-develop', node, ['scripts/build-develop.cjs'], { env: { SKIP_STANDALONE: '1' } }), + ]); + } + + // Summary + const all = [...p1, ...p2]; + const wallMs = Date.now() - wallStart; + const longestLabel = Math.max(...all.map(r => r.label.length)); + + process.stdout.write([ + '', + BOLD('Summary'), + ...all.map(r => { + const mark = r.code === 0 ? GREEN('✓') : r.code < 0 ? DIM('○') : RED('✗'); + return ` ${mark} ${r.label.padEnd(longestLabel + 2)} ${DIM(elapsed(r.ms))}`; + }), + '', + ` Wall clock: ${BOLD(elapsed(wallMs))}`, + '', + ].join('\n')); +} + +if (require.main === module) { + main().catch(err => { + process.stderr.write(RED(`\nFatal: ${err.message || err}\n`)); + process.exit(1); + }); +} else { + // Exposed for unit testing only (e.g. assertSafeBuildRoot) — running this + // file directly as a script (require.main === module) is still the only + // way the pipeline itself executes. + module.exports = { assertSafeBuildRoot, SRC_ROOT, BUILD_DIR, DEFAULT_BUILD_ROOT }; +} diff --git a/build/scripts/build-pipeline.js b/build/scripts/build-pipeline.js deleted file mode 100644 index 4883d28e8c..0000000000 --- a/build/scripts/build-pipeline.js +++ /dev/null @@ -1,228 +0,0 @@ -#!/usr/bin/env node -/** - * (c) Copyright Ascensio System SIA 2010-2024 - * - * This program is a free software product. You can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License (AGPL) - * version 3 as published by the Free Software Foundation. In accordance with - * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect - * that Ascensio System SIA expressly excludes the warranty of non-infringement - * of any third-party rights. - * - * This program is distributed WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For - * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html - */ - -'use strict'; - -// Full grunt-free build pipeline for Euro Office sdkjs. -// -// Usage (from sdkjs/build/): -// PRODUCT_VERSION=9.2.1 BUILD_ROOT=/path/to/deploy node scripts/build-pipeline.js -// -// Options (env vars): -// PRODUCT_VERSION default '0.0.0' -// BUILD_ROOT default ../deploy/sdkjs -// BUILD_NUMBER default '0' -// COMPANY_NAME default 'onlyoffice' -// SDK_PLATFORM '' | 'desktop' | 'mobile' — passed through to webpack configs -// SDK_ADDONS path.delimiter-separated addon directories -// SKIP_DEVELOP set to '1' to skip develop scripts generation -// -// Phase layout (wall-clock optimised): -// Phase 1 — parallel: deploy-assets + webpack ×4 (word, cell, slide, visio) -// Each webpack config runs 2 compiler configs (min + all chunk) in parallel. -// Phase 2 — sequential: build-develop (writes develop/sdkjs/{module}/scripts.js) - -const { spawn } = require('child_process'); -const path = require('path'); -const fs = require('fs'); - -const BUILD_DIR = path.resolve(__dirname, '..'); - -const BUILD_ROOT = process.env.BUILD_ROOT - ? path.resolve(process.env.BUILD_ROOT, 'sdkjs') - : path.resolve(BUILD_DIR, '..', 'deploy', 'sdkjs'); - -const PRODUCT_VERSION = process.env.PRODUCT_VERSION || '0.0.0'; -const BUILD_NUMBER = String(process.env.BUILD_NUMBER || process.env.GITHUB_RUN_NUMBER || '0'); -const SKIP_DEVELOP = process.env.SKIP_DEVELOP === '1'; - -const CHILD_ENV = { - ...process.env, - PRODUCT_VERSION, - BUILD_NUMBER, - BUILD_ROOT: process.env.BUILD_ROOT || path.resolve(BUILD_DIR, '..', 'deploy'), -}; - -// ---- output helpers (mirrors web-apps/build/scripts/build-pipeline.js) ---- - -const BOLD = s => `\x1b[1m${s}\x1b[0m`; -const DIM = s => `\x1b[2m${s}\x1b[0m`; -const GREEN = s => `\x1b[32m${s}\x1b[0m`; -const RED = s => `\x1b[31m${s}\x1b[0m`; -const CYAN = s => `\x1b[36m${s}\x1b[0m`; -const PAD = 20; - -function elapsed(ms) { - return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`; -} - -function banner(msg) { - process.stdout.write(`\n${BOLD(CYAN('▶ ' + msg))}\n`); -} - -// ---- task runner ----------------------------------------------------------- - -function task(label, cmd, args = [], opts = {}) { - return { label, cmd, args, opts }; -} - -function runTask({ label, cmd, args, opts = {} }) { - let child = null; - const promise = new Promise(resolve => { - const start = Date.now(); - const paddedLabel = label.padEnd(PAD); - const stderrBuf = []; - - child = spawn(cmd, args, { - env: { ...CHILD_ENV, ...(opts.env || {}) }, - cwd: opts.cwd || BUILD_DIR, - stdio: ['ignore', 'pipe', 'pipe'], - }); - - child.stdout.on('data', chunk => { - for (const line of chunk.toString().split('\n')) { - if (line.trim()) process.stdout.write(` ${DIM('[' + label + ']')} ${line}\n`); - } - }); - - child.stderr.on('data', chunk => { stderrBuf.push(chunk.toString()); }); - - child.on('error', err => { - const ms = Date.now() - start; - process.stdout.write(` ${RED('✗')} ${paddedLabel} ${RED('FAILED')} ${DIM(elapsed(ms))}\n`); - process.stderr.write(` spawn error: ${err.message}\n`); - resolve({ label, ms, code: 1 }); - }); - - child.on('exit', (code, signal) => { - const ms = Date.now() - start; - if (signal) { - process.stdout.write(` ${DIM('○')} ${paddedLabel} ${DIM('killed ' + elapsed(ms))}\n`); - if (stderrBuf.length) process.stderr.write(stderrBuf.join('')); - resolve({ label, ms, code: -1 }); - } else if (code === 0) { - process.stdout.write(` ${GREEN('✓')} ${paddedLabel} ${DIM(elapsed(ms))}\n`); - resolve({ label, ms, code: 0 }); - } else { - process.stdout.write(` ${RED('✗')} ${paddedLabel} ${RED('FAILED')} ${DIM(elapsed(ms))}\n`); - if (stderrBuf.length) process.stderr.write(stderrBuf.join('')); - resolve({ label, ms, code }); - } - }); - }); - return { promise, kill: () => child && child.kill('SIGTERM'), label }; -} - -async function phase(title, taskSpecs) { - const count = taskSpecs.length; - banner(`${title} — ${count} task${count !== 1 ? 's' : ''}`); - - const running = taskSpecs.map(runTask); - let aborted = false; - - const results = await Promise.all( - running.map(t => - t.promise.then(r => { - if (r.code > 0 && !aborted) { - aborted = true; - running.forEach(o => { try { o.kill(); } catch (_) {} }); - } - return r; - }) - ) - ); - - const failed = results.filter(r => r.code > 0); - if (failed.length) { - process.stderr.write(RED(`\n✗ ${failed.map(r => r.label).join(', ')} failed — aborting\n`)); - process.exit(1); - } - return results; -} - -// ---- pipeline -------------------------------------------------------------- - -const node = process.execPath; -const wp = path.join(BUILD_DIR, 'node_modules', '.bin', 'webpack'); - -const WEBPACK_CONFIGS = [ - 'webpack.word.mjs', - 'webpack.cell.mjs', - 'webpack.slide.mjs', - 'webpack.visio.mjs', -]; - -async function main() { - const wallStart = Date.now(); - - process.stdout.write([ - BOLD('Euro Office sdkjs build pipeline'), - ` BUILD_ROOT ${BUILD_ROOT}`, - ` PRODUCT_VERSION ${PRODUCT_VERSION}`, - ` BUILD_NUMBER ${BUILD_NUMBER}`, - ` SDK_PLATFORM ${process.env.SDK_PLATFORM || '(default)'}`, - ` SKIP_DEVELOP ${SKIP_DEVELOP}`, - '', - ].join('\n')); - - // Clean deploy directory before building. - if (fs.existsSync(BUILD_ROOT)) { - fs.rmSync(BUILD_ROOT, { recursive: true, force: true }); - } - - // Phase 1: all independent work in parallel. - // - deploy-assets: copies CSS, fonts, images, themes, native JS (WHITESPACE compiled) - // - webpack ×4: each produces sdk-all-min.js + sdk-all.js for its module - const phase1Tasks = [ - task('deploy-assets', node, ['scripts/deploy-assets.js']), - ...WEBPACK_CONFIGS.map(cfg => { - const name = cfg.replace('webpack.', '').replace('.mjs', ''); - return task(`webpack:${name}`, wp, ['--config', cfg]); - }), - ]; - - const p1 = await phase('Phase 1 — parallel', phase1Tasks); - - // Phase 2: develop scripts (fast, sequential is fine). - let p2 = []; - if (!SKIP_DEVELOP) { - p2 = await phase('Phase 2 — develop', [ - task('build-develop', node, ['scripts/build-develop.js']), - ]); - } - - // Summary - const all = [...p1, ...p2]; - const wallMs = Date.now() - wallStart; - const longestLabel = Math.max(...all.map(r => r.label.length)); - - process.stdout.write([ - '', - BOLD('Summary'), - ...all.map(r => { - const mark = r.code === 0 ? GREEN('✓') : r.code < 0 ? DIM('○') : RED('✗'); - return ` ${mark} ${r.label.padEnd(longestLabel + 2)} ${DIM(elapsed(r.ms))}`; - }), - '', - ` Wall clock: ${BOLD(elapsed(wallMs))}`, - '', - ].join('\n')); -} - -main().catch(err => { - process.stderr.write(RED(`\nFatal: ${err.message || err}\n`)); - process.exit(1); -}); diff --git a/build/scripts/deploy-assets.js b/build/scripts/deploy-assets.cjs similarity index 79% rename from build/scripts/deploy-assets.js rename to build/scripts/deploy-assets.cjs index 4e2af5dd69..d49cc9a380 100644 --- a/build/scripts/deploy-assets.js +++ b/build/scripts/deploy-assets.cjs @@ -27,21 +27,21 @@ const path = require('path'); const fs = require('fs'); -const { globSync } = require('glob'); +const { sync: globSync } = require('glob'); const { minify } = require('terser'); +const babel = require('@babel/core'); +const { resolveBuildRoot } = require('../lib/env.cjs'); const BUILD_DIR = path.resolve(__dirname, '..'); const SRC_ROOT = path.resolve(BUILD_DIR, '..'); -const BUILD_ROOT = process.env.BUILD_ROOT - ? path.resolve(process.env.BUILD_ROOT, 'sdkjs') - : path.resolve(BUILD_DIR, '..', 'deploy', 'sdkjs'); +const BUILD_ROOT = resolveBuildRoot(BUILD_DIR); const version = process.env.PRODUCT_VERSION || '0.0.0'; const buildNumber = process.env.BUILD_NUMBER || '0'; const appCopyright = process.env.APP_COPYRIGHT - || `Copyright (C) Ascensio System SIA 2012-${new Date().getFullYear()}. All rights reserved`; -const publisherUrl = process.env.PUBLISHER_URL || 'https://www.onlyoffice.com/'; + || `Copyright (C) Ascensio System SIA 2012-2025. All rights reserved; Euro-Office contributors 2026 - ${new Date().getFullYear()}`; +const publisherUrl = process.env.PUBLISHER_URL || 'https://github.com/Euro-Office/'; let licenseText = fs.readFileSync(path.join(BUILD_DIR, 'license.header'), 'utf8'); licenseText = licenseText @@ -106,14 +106,32 @@ const OTHER_FILES = [ }, ]; +// The old Grunt path ran these through Closure with --language_out=ECMASCRIPT5. +// Terser alone doesn't downlevel syntax (it only avoids introducing new-ES +// syntax while minifying), so let/const/arrow functions/etc from source files +// like zlib/engine or the service worker would otherwise reach deploy/ as-is. +function transpileToES5(source, filename) { + const result = babel.transformSync(source, { + filename, + babelrc: false, + configFile: false, + sourceType: 'script', + presets: [ + [require.resolve('@babel/preset-env'), { targets: { ie: '11' }, modules: false }], + ], + }); + return result.code; +} + async function deployJsFile(srcPath, destPath) { const source = fs.readFileSync(srcPath, 'utf8'); - const result = await minify(source, { + const es5 = transpileToES5(source, srcPath); + const result = await minify(es5, { compress: false, mangle: false, format: { comments: false }, }); - const content = licenseText + '\n' + (result.code || source); + const content = licenseText + '\n' + (result.code != null ? result.code : es5); fs.mkdirSync(path.dirname(destPath), { recursive: true }); fs.writeFileSync(destPath, content, 'utf8'); } diff --git a/build/test/build-pipeline.test.cjs b/build/test/build-pipeline.test.cjs new file mode 100644 index 0000000000..4fdca457ac --- /dev/null +++ b/build/test/build-pipeline.test.cjs @@ -0,0 +1,63 @@ +/** + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + */ + +'use strict'; + +const test = require('node:test'); +const assert = require('node:assert/strict'); +const path = require('node:path'); +const fs = require('node:fs'); +const os = require('node:os'); + +const { assertSafeBuildRoot, SRC_ROOT, BUILD_DIR, DEFAULT_BUILD_ROOT } = require('../scripts/build-pipeline.cjs'); + +test('assertSafeBuildRoot: allows the default in-tree deploy dir', () => { + assert.doesNotThrow(() => assertSafeBuildRoot(DEFAULT_BUILD_ROOT)); +}); + +test('assertSafeBuildRoot: allows an out-of-tree BUILD_ROOT', () => { + assert.doesNotThrow(() => assertSafeBuildRoot('/package/sdkjs')); +}); + +test('assertSafeBuildRoot: rejects the checkout root itself', () => { + assert.throws(() => assertSafeBuildRoot(SRC_ROOT)); +}); + +test('assertSafeBuildRoot: rejects the build/ directory itself', () => { + assert.throws(() => assertSafeBuildRoot(BUILD_DIR)); +}); + +test('assertSafeBuildRoot: rejects an ancestor that contains the checkout', () => { + assert.throws(() => assertSafeBuildRoot(path.resolve(SRC_ROOT, '..'))); +}); + +test('assertSafeBuildRoot: rejects an arbitrary in-tree subdir other than the default', () => { + assert.throws(() => assertSafeBuildRoot(path.join(SRC_ROOT, 'word', 'sdkjs'))); +}); + +test('assertSafeBuildRoot: rejects a symlink whose real path resolves back into the checkout', () => { + // A BUILD_ROOT that is a symlink pointing into SRC_ROOT looks safe by pure + // string comparison (its literal path is outside the checkout), but + // fs.rmSync would still recursively delete through it into real source — + // realpath resolution must catch this before the string comparisons run. + const tmpParent = fs.mkdtempSync(path.join(os.tmpdir(), 'build-root-symlink-test-')); + const linkPath = path.join(tmpParent, 'build-root-link'); + fs.symlinkSync(SRC_ROOT, linkPath, 'dir'); + try { + assert.throws(() => assertSafeBuildRoot(linkPath)); + } finally { + fs.rmSync(tmpParent, { recursive: true, force: true }); + } +}); diff --git a/build/test/sdk-concat.test.cjs b/build/test/sdk-concat.test.cjs new file mode 100644 index 0000000000..59d082e74f --- /dev/null +++ b/build/test/sdk-concat.test.cjs @@ -0,0 +1,172 @@ +/** + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + */ + +'use strict'; + +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); + +const sdkConcatLoader = require('../loaders/sdk-concat.cjs'); +const { transpileToES5, stripDuplicateHelpers, cacheKeyFor } = sdkConcatLoader; + +// Minimal on-disk fixture satisfying loadAllConfigs()'s expectations (a real +// configs/word.json + two small source files), so the loader can run +// end-to-end without a real sdkjs checkout. +function makeFixtureSrcRoot() { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'sdk-concat-test-')); + fs.mkdirSync(path.join(root, 'configs')); + fs.mkdirSync(path.join(root, 'src')); + fs.mkdirSync(path.join(root, 'vendor')); + + fs.writeFileSync(path.join(root, 'src', 'a.js'), 'let a = (x) => x + 1;\n'); + fs.writeFileSync(path.join(root, 'src', 'b.js'), 'const b = 2;\n'); + fs.writeFileSync(path.join(root, 'vendor', 'polyfill.js'), 'var $jscomp_polyfill_marker = 1;\n'); + fs.writeFileSync( + path.join(root, 'configs', 'word.json'), + JSON.stringify({ sdk: { min: ['src/a.js', 'src/b.js'] } }) + ); + + return root; +} + +// Runs the loader with a minimal mocked webpack loader context, mirroring +// what webpack itself provides (this.async/getOptions/context/addDependency/ +// sourceMap), and resolves with { content, map }. +function runLoader(srcRoot, opts) { + return new Promise((resolve, reject) => { + const context = { + async: () => (err, content, map) => (err ? reject(err) : resolve({ content, map })), + getOptions: () => opts, + context: srcRoot, + sourceMap: true, + addDependency: () => {}, + addContextDependency: () => {}, + }; + sdkConcatLoader.call(context); + }); +} + +test('transpileToES5: downlevels let/const/arrow functions to ES5 syntax', () => { + const { code } = transpileToES5('let f = (x) => x + 1;', 'a.js', false); + assert.equal(/\blet\b|=>/.test(code), false); + assert.match(code, /var f = function/); +}); + +test('transpileToES5: does not wrap output in "use strict" or a module wrapper', () => { + const { code } = transpileToES5('var x = 1;', 'a.js', false); + assert.equal(code.includes('use strict'), false); +}); + +test('transpileToES5: returns no source map when needSourceMap is false', () => { + const { map } = transpileToES5('var x = 1;', 'a.js', false); + assert.equal(map, null); +}); + +test('transpileToES5: returns a source map when needSourceMap is true', () => { + const { map } = transpileToES5('let x = 1;', 'a.js', true); + assert.ok(map); + assert.ok(Array.isArray(map.sources)); +}); + +test('stripDuplicateHelpers: removes a second copy of an already-emitted helper', () => { + const emitted = new Set(['_typeof']); + const helper = + 'function _typeof(obj) {\n' + + ' "@babel/helpers - typeof";\n' + + ' return typeof obj;\n' + + '}\n'; + const result = stripDuplicateHelpers(helper, emitted); + + assert.equal(result.includes('return typeof obj'), false); + // Line count must be unchanged — replaced with blank lines, not deleted, + // so per-file source maps built against the pre-strip output stay valid. + assert.equal(result.split('\n').length, helper.split('\n').length); +}); + +test('stripDuplicateHelpers: keeps the first occurrence of a helper', () => { + const emitted = new Set(); + const helper = + 'function _typeof(obj) {\n' + + ' "@babel/helpers - typeof";\n' + + ' return typeof obj;\n' + + '}\n'; + const result = stripDuplicateHelpers(helper, emitted); + + assert.equal(result, helper); + assert.ok(emitted.has('_typeof')); +}); + +test('stripDuplicateHelpers: leaves ordinary (non-helper) functions untouched even if seen before', () => { + const emitted = new Set(['doStuff']); + const code = 'function doStuff() {\n return 1;\n}\n'; + const result = stripDuplicateHelpers(code, emitted); + assert.equal(result, code); +}); + +test('cacheKeyFor: same content + same needSourceMap produces the same key', () => { + assert.equal(cacheKeyFor('var x = 1;', true), cacheKeyFor('var x = 1;', true)); +}); + +test('cacheKeyFor: needSourceMap is folded into the key (map vs no-map differ)', () => { + assert.notEqual(cacheKeyFor('var x = 1;', true), cacheKeyFor('var x = 1;', false)); +}); + +test('cacheKeyFor: different content produces a different key', () => { + assert.notEqual(cacheKeyFor('var x = 1;', false), cacheKeyFor('var x = 2;', false)); +}); + +test('loader: min chunk prepends vendor/polyfill.js so sdk-all-min.js is self-contained', async () => { + const srcRoot = makeFixtureSrcRoot(); + try { + const { content } = await runLoader(srcRoot, { module: 'word', chunk: 'min', srcRoot }); + assert.match(content, /^var \$jscomp_polyfill_marker = 1;/); + } finally { + fs.rmSync(srcRoot, { recursive: true, force: true }); + } +}); + +test('loader: fails the build if vendor/polyfill.js is missing for a min chunk', async () => { + const srcRoot = makeFixtureSrcRoot(); + try { + fs.rmSync(path.join(srcRoot, 'vendor', 'polyfill.js')); + await assert.rejects( + runLoader(srcRoot, { module: 'word', chunk: 'min', srcRoot }), + /cannot read required polyfill file/ + ); + } finally { + fs.rmSync(srcRoot, { recursive: true, force: true }); + } +}); + +test('loader: source map sourcesContent holds the original file content, not transpiled/dedup\'d output', async () => { + const srcRoot = makeFixtureSrcRoot(); + try { + const { map } = await runLoader(srcRoot, { module: 'word', chunk: 'min', srcRoot }); + + assert.ok(map, 'expected a source map (this.sourceMap was true)'); + const aIndex = map.sources.findIndex(s => s.endsWith(path.join('src', 'a.js'))); + assert.notEqual(aIndex, -1); + + const originalA = fs.readFileSync(path.join(srcRoot, 'src', 'a.js'), 'utf8'); + assert.equal(map.sourcesContent[aIndex], originalA); + // Specifically must NOT be the ES5-transpiled form. + assert.equal(/=>/.test(map.sourcesContent[aIndex]), true); + } finally { + fs.rmSync(srcRoot, { recursive: true, force: true }); + } +}); diff --git a/build/test/sdk-configs.test.cjs b/build/test/sdk-configs.test.cjs new file mode 100644 index 0000000000..1f5a60e630 --- /dev/null +++ b/build/test/sdk-configs.test.cjs @@ -0,0 +1,71 @@ +/** + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + */ + +'use strict'; + +const test = require('node:test'); +const assert = require('node:assert/strict'); + +const { getFilesMin, getFilesAll, mergeConfigs } = require('../lib/sdk-configs.cjs'); + +test('getFilesMin: desktop min falls back to [] when desktop config omits "min"', () => { + const sdkCfg = { min: ['a.js'], desktop: { common: ['b.js'] } }; + assert.deepEqual(getFilesMin(sdkCfg, 'desktop'), ['a.js']); +}); + +test('getFilesMin: mobile_banners min falls back to [] when omitted', () => { + const sdkCfg = { min: ['a.js'], mobile_banners: { common: ['banner.js'] } }; + assert.deepEqual(getFilesMin(sdkCfg, 'mobile'), ['a.js']); +}); + +test('getFilesMin: mobile_banners min is prepended when present', () => { + const sdkCfg = { min: ['a.js'], mobile_banners: { min: ['banner-min.js'] } }; + assert.deepEqual(getFilesMin(sdkCfg, 'mobile'), ['banner-min.js', 'a.js']); +}); + +test('getFilesAll: desktop common falls back to [] when omitted', () => { + const sdkCfg = { common: ['a.js'], desktop: { min: ['b.js'] } }; + assert.deepEqual(getFilesAll(sdkCfg, 'desktop'), ['a.js']); +}); + +test('getFilesAll: mobile_banners common falls back to [] when omitted', () => { + const sdkCfg = { common: ['a.js'], mobile_banners: { min: ['banner-min.js'] } }; + assert.deepEqual(getFilesAll(sdkCfg, 'mobile'), ['a.js']); +}); + +test('getFilesAll: mobile_banners common is prepended, then exclude_mobile filters, then mobile appends', () => { + const sdkCfg = { + common: ['a.js', 'skip.js'], + mobile_banners: { common: ['banner.js'] }, + exclude_mobile: ['skip.js'], + mobile: ['mobile-only.js'], + }; + assert.deepEqual( + getFilesAll(sdkCfg, 'mobile'), + ['banner.js', 'a.js', 'mobile-only.js'] + ); +}); + +test('mergeConfigs: array properties concatenate', () => { + const base = { min: ['a.js'] }; + mergeConfigs(base, { min: ['b.js'] }); + assert.deepEqual(base.min, ['a.js', 'b.js']); +}); + +test('mergeConfigs: nested object properties merge recursively', () => { + const base = { desktop: { min: ['a.js'] } }; + mergeConfigs(base, { desktop: { common: ['b.js'] } }); + assert.deepEqual(base, { desktop: { min: ['a.js'], common: ['b.js'] } }); +}); diff --git a/build/test/webpack-sdk-factory.test.cjs b/build/test/webpack-sdk-factory.test.cjs new file mode 100644 index 0000000000..36c57be0f1 --- /dev/null +++ b/build/test/webpack-sdk-factory.test.cjs @@ -0,0 +1,113 @@ +/** + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + */ + +'use strict'; + +const test = require('node:test'); +const assert = require('node:assert/strict'); +const path = require('node:path'); +const url = require('node:url'); +const fs = require('node:fs'); +const os = require('node:os'); +const webpack = require('webpack'); +const TerserPlugin = require('terser-webpack-plugin'); + +test('stripBootstrapStrictDirective: removes webpack\'s forced top-level "use strict" prologue', async () => { + const { stripBootstrapStrictDirective } = await import( + url.pathToFileURL(path.join(__dirname, '..', 'webpack.sdk.factory.mjs')) + ); + + const bundle = '/******/ "use strict";\n/******/ (() => {\nvar x = 1;\n})();'; + const patched = stripBootstrapStrictDirective(bundle); + + assert.equal(patched.includes('"use strict"'), false); + // Same length / same line count — must not shift anything a source map points at. + assert.equal(patched.length, bundle.length); + assert.equal(patched.split('\n').length, bundle.split('\n').length); +}); + +test('stripBootstrapStrictDirective: leaves the bundle unchanged when no directive is present', async () => { + const { stripBootstrapStrictDirective } = await import( + url.pathToFileURL(path.join(__dirname, '..', 'webpack.sdk.factory.mjs')) + ); + + const bundle = '/******/ (() => {\nvar x = 1;\n})();'; + assert.equal(stripBootstrapStrictDirective(bundle), bundle); +}); + +test('stripBootstrapStrictDirective: does not touch a "use strict" appearing past the prologue window', async () => { + const { stripBootstrapStrictDirective, PROLOGUE_SCAN_LIMIT } = await import( + url.pathToFileURL(path.join(__dirname, '..', 'webpack.sdk.factory.mjs')) + ); + + // A real source file's own string literal containing this text, buried + // deep in the concatenated bundle, must never be mistaken for webpack's + // bootstrap directive. + const padding = 'x'.repeat(PROLOGUE_SCAN_LIMIT + 100); + const bundle = `${padding}var msg = "use strict";`; + assert.equal(stripBootstrapStrictDirective(bundle), bundle); +}); + +test('StripBootstrapStrictModePlugin: strips webpack\'s bootstrap "use strict" from a real minified compilation', async (t) => { + const { StripBootstrapStrictModePlugin } = await import( + url.pathToFileURL(path.join(__dirname, '..', 'webpack.sdk.factory.mjs')) + ); + + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'strip-strict-test-')); + const entry = path.join(tmpDir, 'entry.js'); + // Bare top-level var, no import/export — same shape sdk-concat-loader + // produces (sourceType:'script'), so webpack treats this chunk the same + // way it treats a real SDK bundle for bootstrap-generation purposes. + fs.writeFileSync(entry, 'var AscCommonSdkTestGlobal = { value: 1 + 1 };\n'); + + function runCompiler(withPlugin) { + return new Promise((resolve, reject) => { + const compiler = webpack({ + mode: 'production', + entry, + output: { path: tmpDir, filename: withPlugin ? 'with-plugin.js' : 'without-plugin.js', iife: false }, + optimization: { + minimize: true, + minimizer: [new TerserPlugin({ terserOptions: { mangle: false, compress: true } })], + }, + plugins: withPlugin ? [new StripBootstrapStrictModePlugin()] : [], + }); + compiler.run((err, stats) => { + compiler.close(() => {}); + if (err || stats.hasErrors()) return reject(err || new Error(stats.toString())); + resolve(fs.readFileSync(path.join(tmpDir, withPlugin ? 'with-plugin.js' : 'without-plugin.js'), 'utf8')); + }); + }); + } + + try { + const withoutPlugin = await runCompiler(false); + const withPlugin = await runCompiler(true); + + // Sanity check the test fixture itself is meaningful: if webpack's own + // output never carries the directive in the first place (e.g. a future + // webpack version stops emitting it for script-sourceType chunks), the + // plugin has nothing to strip and this assertion would catch that the + // integration test itself needs updating, rather than silently passing + // for the wrong reason. + if (!withoutPlugin.includes('"use strict"')) { + t.diagnostic('webpack did not emit a bootstrap "use strict" for this chunk shape — plugin has nothing to strip here'); + } else { + assert.equal(withPlugin.includes('"use strict"'), false); + } + } finally { + fs.rmSync(tmpDir, { recursive: true, force: true }); + } +}); diff --git a/build/webpack.cell.mjs b/build/webpack.cell.mjs index bc207ceea2..fe9133dc2c 100644 --- a/build/webpack.cell.mjs +++ b/build/webpack.cell.mjs @@ -1,2 +1,17 @@ +/** + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + */ + import { sdkConfig } from './webpack.sdk.factory.mjs'; export default sdkConfig('cell'); diff --git a/build/webpack.sdk.factory.mjs b/build/webpack.sdk.factory.mjs index 96b41b9670..09e721e2b3 100644 --- a/build/webpack.sdk.factory.mjs +++ b/build/webpack.sdk.factory.mjs @@ -29,13 +29,16 @@ * BUILD_ROOT override deploy root; defaults to ../deploy/sdkjs * SDK_PLATFORM '' | 'desktop' | 'mobile' * SDK_ADDONS path.delimiter-separated list of addon directories - * COMPANY_NAME default 'onlyoffice' + * COMPANY_NAME default 'Euro-Office' * PRODUCT_VERSION default '0.0.0' * BUILD_NUMBER default '0' * BETA default 'false' - * APP_COPYRIGHT default 'Copyright (C) Ascensio System SIA …' - * PUBLISHER_URL default 'https://www.onlyoffice.com/' + * APP_COPYRIGHT default 'Copyright (C) Ascensio System SIA 2012-2025 …; Euro-Office contributors 2026 - …' + * PUBLISHER_URL default 'https://github.com/Euro-Office/' * NODE_ENV 'production' (default) | 'development' + * SDK_SOURCE_MAPS '1' to emit source maps for a production build too + * (development builds always get them regardless) + * WEBPACK_CACHE_DIR override filesystem cache location; defaults to build/.webpack-cache */ import webpack from 'webpack'; @@ -43,37 +46,106 @@ import TerserPlugin from 'terser-webpack-plugin'; import path from 'path'; import fs from 'fs'; import { fileURLToPath } from 'url'; +import { createRequire } from 'module'; +const require = createRequire(import.meta.url); +const { parseAddonDirs, resolveBuildRoot } = require('./lib/env.cjs'); const __dirname = path.dirname(fileURLToPath(import.meta.url)); const CONCAT_LOADER = path.join(__dirname, 'loaders', 'sdk-concat.cjs'); const DUMMY_ENTRY = path.join(__dirname, 'dummy.js'); +// webpack 5's own runtime bootstrap unconditionally emits a top-level +// `"use strict";` directive (see JavascriptModulesPlugin's renderMain), even +// though sdk-concat-loader's babel pass uses sourceType:'script' specifically +// to avoid injecting one — sdkjs's bare-var-across-files shared-scope model +// (see sdk-concat.cjs) predates strict mode and has never been validated +// against it (undeclared-assignment/delete/duplicate-parameter semantics all +// differ). Strip it to preserve the legacy script's non-strict semantics. +// +// The directive is replaced in place with a same-length no-op rather than +// removed, so no byte offset after it shifts — the accompanying source map +// (when devtool:'source-map' is on) stays valid without recomputing it, and +// this can never touch a legitimate `"use strict";` appearing verbatim +// inside a real source file's own string literals, since those come tens of +// KB into the bundle, well past PROLOGUE_SCAN_LIMIT. +export const STRICT_DIRECTIVE = '"use strict";'; +export const PROLOGUE_SCAN_LIMIT = 2048; + +// Pure and exported so it can be unit-tested (build/test/webpack-sdk-factory.test.cjs) +// without spinning up a full webpack build. Returns `source` unchanged if no +// directive is found within the prologue window. +export function stripBootstrapStrictDirective(source) { + const idx = source.slice(0, PROLOGUE_SCAN_LIMIT).indexOf(STRICT_DIRECTIVE); + if (idx === -1) return source; + + return source.slice(0, idx) + + ';'.padEnd(STRICT_DIRECTIVE.length) + + source.slice(idx + STRICT_DIRECTIVE.length); +} + +// Exported (only) for the real-compilation integration test in +// build/test/webpack-sdk-factory.test.cjs — this plugin's correctness depends +// on webpack's own internal bootstrap-generation format and Terser's output +// formatting, neither of which is a stable public API, so it needs an actual +// webpack+Terser run to validate, not just the pure-string-function tests above. +export class StripBootstrapStrictModePlugin { + apply(compiler) { + compiler.hooks.compilation.tap('StripBootstrapStrictModePlugin', (compilation) => { + compilation.hooks.processAssets.tap( + { + name: 'StripBootstrapStrictModePlugin', + stage: webpack.Compilation.PROCESS_ASSETS_STAGE_REPORT, + }, + (assets) => { + for (const name of Object.keys(assets)) { + if (!name.endsWith('.js')) continue; + + const source = compilation.getAsset(name).source.source(); + if (typeof source !== 'string') continue; + + const patched = stripBootstrapStrictDirective(source); + if (patched === source) continue; + + compilation.updateAsset(name, new webpack.sources.RawSource(patched)); + } + } + ); + }); + } +} + /** * @param {string} moduleName 'word' | 'cell' | 'slide' | 'visio' * @returns {object[]} Two webpack compiler configs: [sdk-all-min, sdk-all] */ export function sdkConfig(moduleName) { - const env = process.env.NODE_ENV || 'production'; + // webpack's `mode` accepts only 'development' | 'production' | 'none'. + // NODE_ENV is commonly set to other values (e.g. 'test' by Jest/Vitest/CI), + // which would otherwise make webpack hard-fail before compiling anything. + const env = process.env.NODE_ENV === 'development' ? 'development' : 'production'; - const BUILD_ROOT = process.env.BUILD_ROOT - ? path.resolve(process.env.BUILD_ROOT, 'sdkjs') - : path.resolve(__dirname, '..', 'deploy', 'sdkjs'); + // Old grunt --map path could produce maps for the production/minified build too — + // minification and source-map emission are independent concerns, so don't couple + // "give me a map" to "give me a dev build" (which also disables minification). + const emitSourceMaps = env === 'development' || process.env.SDK_SOURCE_MAPS === '1'; + + const BUILD_ROOT = resolveBuildRoot(__dirname); const SRC_ROOT = path.resolve(__dirname, '..'); const OUT_DIR = path.join(BUILD_ROOT, moduleName); const platform = process.env.SDK_PLATFORM || ''; - const addonDirs = process.env.SDK_ADDONS - ? process.env.SDK_ADDONS.split(path.delimiter).filter(Boolean) - : []; + const addonDirs = parseAddonDirs(); - const companyName = process.env.COMPANY_NAME || 'onlyoffice'; + const companyName = process.env.COMPANY_NAME || 'Euro-Office'; const version = process.env.PRODUCT_VERSION || '0.0.0'; const buildNumber = process.env.BUILD_NUMBER || '0'; const beta = process.env.BETA || 'false'; + // Matches the Euro-Office rebrand defaults main() picked up independently + // (see build/Gruntfile.js history) after this migration branched off it. const appCopyright = process.env.APP_COPYRIGHT - || `Copyright (C) Ascensio System SIA 2012-${new Date().getFullYear()}. All rights reserved`; - const publisherUrl = process.env.PUBLISHER_URL || 'https://www.onlyoffice.com/'; + || `Copyright (C) Ascensio System SIA 2012-2025. All rights reserved; Euro-Office contributors 2026 - ${new Date().getFullYear()}`; + const publisherUrl = process.env.PUBLISHER_URL || 'https://github.com/Euro-Office/'; let licenseText = fs.readFileSync(path.join(__dirname, 'license.header'), 'utf8'); licenseText = licenseText @@ -107,8 +179,12 @@ export function sdkConfig(moduleName) { module: { rules: [ { - // Match only our dummy entry, not real source files. - test: /[/\\]dummy\.js$/, + // Match only our dummy entry, not real source files. webpack's + // string `test` does a startsWith() match, not equality, so an + // unanchored match would also catch e.g. a future dummy.json or + // dummy.js.bak, silently routing it through sdk-concat-loader + // and corrupting the bundle. Use exact equality instead. + test: (resourcePath) => resourcePath === DUMMY_ENTRY, use: [ { loader: CONCAT_LOADER, @@ -118,6 +194,11 @@ export function sdkConfig(moduleName) { platform, srcRoot: SRC_ROOT, addonDirs, + // Passed through so the loader can patch the + // window.AscCommon.g_c* runtime values after + // commonDefines.js's own hardcoded assignments — + // see the buildMeta comment in sdk-concat.cjs. + buildMeta: { companyName, version, buildNumber, beta }, }, }, ], @@ -132,10 +213,22 @@ export function sdkConfig(moduleName) { entryOnly: true, }), + new StripBootstrapStrictModePlugin(), + // Replaces Closure Compiler's --define= flags. // webpack DefinePlugin performs AST-level identifier replacement // so dead-code branches (if (g_cIsBeta === 'true') …) are // eliminated by TerserPlugin in the same pass. + // + // Only the unprefixed form `AscCommon.g_cXxx` is listed here. + // The `window.AscCommon.g_cXxx = "..."` declarations in + // commonDefines.js must NOT be replaced — DefinePlugin would + // turn the LHS into a string literal, making it an invalid + // assignment. Those declarations stay as hardcoded placeholders + // in source; sdk-concat-loader patches them to the real values + // (buildMeta option above) right after commonDefines.js in the + // 'min' chunk, so window.AscCommon.g_cXxx is correct too — not + // just call-sites that get folded by this plugin. new webpack.DefinePlugin({ 'AscCommon.g_cCompanyName': JSON.stringify(companyName), 'AscCommon.g_cProductVersion': JSON.stringify(version), @@ -155,7 +248,14 @@ export function sdkConfig(moduleName) { comments: /AGPL|Copyright|Ascensio|License/i, }, compress: { - drop_console: env === 'production', + // The legacy Closure Compiler build did not drop console + // calls, and sdkjs uses console.* for non-debug diagnostics + // (invalid-JS errors, clipboard permission warnings, custom + // function registration warnings, workbook diagnostics) — + // silently discarding those in production removes real + // observability and can skip evaluation of their arguments. + // Opt in explicitly per-build instead of dropping by default. + drop_console: process.env.DROP_CONSOLE === '1', }, // mangle:false is load-bearing — same reason as web-apps: // sdkjs files communicate via window.AscCommon.xxx and bare @@ -167,7 +267,49 @@ export function sdkConfig(moduleName) { ], }, - devtool: env === 'production' ? false : 'source-map', + // The SDK bundle intentionally exceeds webpack's 244 KiB default limit — + // this is not a web-app chunk. Scoped to just this entry's output file + // (rather than `performance: false` for the whole config) so a future + // entry added to this config still gets the default size-regression hint. + performance: { + assetFilter: assetFilename => assetFilename !== `${outName}.js` && !assetFilename.endsWith('.map'), + }, + + // Persistent disk cache: cold restarts after the first build run in + // ~0.6 s instead of ~34 s (production) or ~5.5 s (development). + // Automatically invalidated when source files, configs, or this + // factory file change via the registered addDependency() calls. + cache: { + type: 'filesystem', + // Overridable for read-only source checkouts/mounts, where + // build/.webpack-cache can't be created. + cacheDirectory: process.env.WEBPACK_CACHE_DIR + ? path.resolve(process.env.WEBPACK_CACHE_DIR) + : path.join(__dirname, '.webpack-cache'), + // DefinePlugin/BannerPlugin/devtool values below aren't tracked as + // file dependencies, so a run that only changes an env var (e.g. + // PRODUCT_VERSION or SDK_SOURCE_MAPS) must bump the cache version + // itself or the previous run's bundle would be served unchanged. + // + // platform/addonDirs MUST be included too: they're passed as loader + // *options*, not file dependencies, and WEBPACK_CACHE_DIR defaults to + // the same fixed build/.webpack-cache path regardless of SDK_PLATFORM + // or SDK_ADDONS. Verified empirically — without this, building once + // with SDK_PLATFORM=desktop (or SDK_ADDONS=...) after a plain build + // against the same cache dir silently reuses the plain build's cached + // module and produces byte-identical output missing every + // platform/addon file, with no error or warning. + version: `${companyName}-${version}-${buildNumber}-${beta}-${appCopyright}-${publisherUrl}-${emitSourceMaps}-${platform}-${addonDirs.join(',')}-${process.env.DROP_CONSOLE || ''}`, + buildDependencies: { + config: [ + fileURLToPath(import.meta.url), + CONCAT_LOADER, + path.join(__dirname, 'lib', 'sdk-configs.cjs'), + ], + }, + }, + + devtool: emitSourceMaps ? 'source-map' : false, }; } diff --git a/build/webpack.slide.mjs b/build/webpack.slide.mjs index 53303957e6..f42cb82ded 100644 --- a/build/webpack.slide.mjs +++ b/build/webpack.slide.mjs @@ -1,2 +1,17 @@ +/** + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + */ + import { sdkConfig } from './webpack.sdk.factory.mjs'; export default sdkConfig('slide'); diff --git a/build/webpack.visio.mjs b/build/webpack.visio.mjs index 85573e68e2..e6e8a2acb4 100644 --- a/build/webpack.visio.mjs +++ b/build/webpack.visio.mjs @@ -1,2 +1,17 @@ +/** + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + */ + import { sdkConfig } from './webpack.sdk.factory.mjs'; export default sdkConfig('visio'); diff --git a/build/webpack.word.mjs b/build/webpack.word.mjs index 532b2967ae..977baa27e5 100644 --- a/build/webpack.word.mjs +++ b/build/webpack.word.mjs @@ -1,2 +1,17 @@ +/** + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + */ + import { sdkConfig } from './webpack.sdk.factory.mjs'; export default sdkConfig('word'); diff --git a/tests/code-style/check.py b/tests/code-style/check.py index 730b07f92b..379bf561f1 100644 --- a/tests/code-style/check.py +++ b/tests/code-style/check.py @@ -1,6 +1,6 @@ import os -exclude_dirs = set(['vendor', 'externs']) +exclude_dirs = set(['vendor', 'externs', 'node_modules', '.webpack-cache']) exclude_files = set(['jquery_native.js']) def get_string_from_list(list): @@ -68,7 +68,11 @@ def check_file_without_newline(files): raise Exception("Files without newline:\n" + get_string_from_list(files_without_new_line)) def check_code_style(): - files_js = get_files_by_ext(".js") + # .cjs/.mjs are the build-tooling script extensions introduced by the + # webpack migration (build/scripts, build/lib, build/loaders) — without + # them here, that whole directory silently falls outside every one of + # these checks going forward. + files_js = get_files_by_ext(".js") + get_files_by_ext(".cjs") + get_files_by_ext(".mjs") check_file_without_license(files_js) # check_file_without_latvian_address(files_js) check_file_without_lf_ending(files_js) From f787b365b8471f7346cb6d7c38ffc89ee75bff3b Mon Sep 17 00:00:00 2001 From: Mona LatifAghili Date: Thu, 23 Jul 2026 13:00:47 +0200 Subject: [PATCH 3/6] build(sdkjs): fix webpack migration bundle bloat, CI, and coverage gaps Signed-off-by: Mona LatifAghili --- .github/workflows/check-build.yml | 23 ++++++++++++----- build/license.header | 2 +- build/webpack.sdk.factory.mjs | 43 ++++++++++++++++++++++--------- tests/common/api/api-cell.html | 30 +++++++++++++++++++++ tests/common/api/api-slide.html | 30 +++++++++++++++++++++ tests/common/api/api-visio.html | 30 +++++++++++++++++++++ 6 files changed, 139 insertions(+), 19 deletions(-) create mode 100644 tests/common/api/api-cell.html create mode 100644 tests/common/api/api-slide.html create mode 100644 tests/common/api/api-visio.html diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml index ed8666bd25..6dfcdde07d 100644 --- a/.github/workflows/check-build.yml +++ b/.github/workflows/check-build.yml @@ -7,6 +7,11 @@ on: - 'release/**' - 'hotfix/**' pull_request: + branches: + - fork + - develop + - 'release/**' + - 'hotfix/**' jobs: code-style: runs-on: ubuntu-latest @@ -132,12 +137,15 @@ jobs: # the just-built sdk-all-min.js (COMPILED=1) and run a smoke test against that, so # the built artifact itself is exercised at least once. # - # Only tests/common/api/api.html is used here — verified locally (not assumed): - # COMPILED=1's generated scripts.js references ONLY sdk-all-min.js, never - # sdk-all.js (this mirrors the original Gruntfile's writeScripts() exactly, it is - # not a webpack-migration change). sdk-all-min.js is the bootstrap chunk only; - # AscWord/AscCommonExcel/etc. live in sdk-all.js, so any suite touching those - # (tests/word/api, tests/*/shortcuts, tests/cell/js-api, ...) fails under + # tests/common/api/api.js only exercises AscCommon.* (editor-agnostic bootstrap + # APIs), never AscWord/AscCommonExcel/etc., so it's safe to reuse verbatim per + # module — api-cell.html/api-slide.html/api-visio.html point the same test file + # at cell/slide/visio's own scripts.js. This is deliberately NOT extended to + # suites touching module-specific APIs (tests/word/api, tests/*/shortcuts, + # tests/cell/js-api, ...): COMPILED=1's generated scripts.js references ONLY + # sdk-all-min.js, never sdk-all.js (mirrors the original Gruntfile's + # writeScripts() exactly — not a webpack-migration change), and + # AscWord/AscCommonExcel/etc. live in sdk-all.js, so those suites fail under # COMPILED=1 with "AscWord is not defined" — a pre-existing limitation of # developer-compiled mode, not something this step should be asserting on. - name: Run QUnit against the built bundle (COMPILED=1) @@ -145,3 +153,6 @@ jobs: cd sdkjs COMPILED=1 npm run --prefix build develop node node_modules/node-qunit-puppeteer/cli.js tests/common/api/api.html 30000 "--no-sandbox" + node node_modules/node-qunit-puppeteer/cli.js tests/common/api/api-cell.html 30000 "--no-sandbox" + node node_modules/node-qunit-puppeteer/cli.js tests/common/api/api-slide.html 30000 "--no-sandbox" + node node_modules/node-qunit-puppeteer/cli.js tests/common/api/api-visio.html 30000 "--no-sandbox" diff --git a/build/license.header b/build/license.header index 76777417ec..d8d65d9272 100644 --- a/build/license.header +++ b/build/license.header @@ -1,4 +1,4 @@ -/* +/* @@license-banner@@ * @@AppCopyright * * @@PublisherUrl diff --git a/build/webpack.sdk.factory.mjs b/build/webpack.sdk.factory.mjs index 09e721e2b3..66797a9d65 100644 --- a/build/webpack.sdk.factory.mjs +++ b/build/webpack.sdk.factory.mjs @@ -166,6 +166,10 @@ export function sdkConfig(moduleName) { output: { path: OUT_DIR, filename: '[name].js', + // publicPath intentionally unset: safe today because there's no code + // splitting or dynamic import() in this config, so nothing needs to + // resolve a chunk/asset URL at runtime. Set it (matching DocumentServer's + // non-root deployment path) if splitChunks or import() is ever added here. // iife:false — we control wrapping via the loader: // sdk-all-min: no wrapper // sdk-all: (function(window, undefined){…})(window) @@ -244,19 +248,34 @@ export function sdkConfig(moduleName) { extractComments: false, terserOptions: { format: { - // Preserve the license header injected by BannerPlugin. - comments: /AGPL|Copyright|Ascensio|License/i, - }, - compress: { - // The legacy Closure Compiler build did not drop console - // calls, and sdkjs uses console.* for non-debug diagnostics - // (invalid-JS errors, clipboard permission warnings, custom - // function registration warnings, workbook diagnostics) — - // silently discarding those in production removes real - // observability and can skip evaluation of their arguments. - // Opt in explicitly per-build instead of dropping by default. - drop_console: process.env.DROP_CONSOLE === '1', + // BannerPlugin (stage ADDITIONS) injects the license banner into + // the asset *before* Terser (stage OPTIMIZE_SIZE) runs, so the + // banner is just another comment to Terser at this point. Match + // only the sentinel below — matching on AGPL/Copyright/License + // text also matches the identical per-file header repeated in + // all ~400+ concatenated source files. + comments: /@@license-banner@@/, }, + compress: (platform === 'desktop' || platform === 'mobile') + // Old build-desktop.bat/build-mobile.command ran Closure's + // WHITESPACE_ONLY (comments/whitespace stripped, no semantic + // transforms) instead of web's ADVANCED. `compress: false` is + // Terser's closest equivalent — it disables the whole compress + // pass (dead-code elim, inlining, etc.) and keeps output to + // whitespace/name-shortening-free minification, restoring that + // lighter tier instead of silently adopting web's ADVANCED-like + // pass on desktop/mobile. + ? false + : { + // The legacy Closure Compiler build did not drop console + // calls, and sdkjs uses console.* for non-debug diagnostics + // (invalid-JS errors, clipboard permission warnings, custom + // function registration warnings, workbook diagnostics) — + // silently discarding those in production removes real + // observability and can skip evaluation of their arguments. + // Opt in explicitly per-build instead of dropping by default. + drop_console: process.env.DROP_CONSOLE === '1', + }, // mangle:false is load-bearing — same reason as web-apps: // sdkjs files communicate via window.AscCommon.xxx and bare // top-level var declarations shared across concatenated scope. diff --git a/tests/common/api/api-cell.html b/tests/common/api/api-cell.html new file mode 100644 index 0000000000..122d673dd5 --- /dev/null +++ b/tests/common/api/api-cell.html @@ -0,0 +1,30 @@ + + + + + + Api test (cell) + + + + + + + + + + + + +

Test the api of the document editor

+

+
+

+
    +
    test markup, will be hidden
    + + diff --git a/tests/common/api/api-slide.html b/tests/common/api/api-slide.html new file mode 100644 index 0000000000..b85728c70f --- /dev/null +++ b/tests/common/api/api-slide.html @@ -0,0 +1,30 @@ + + + + + + Api test (slide) + + + + + + + + + + + + +

    Test the api of the document editor

    +

    +
    +

    +
      +
      test markup, will be hidden
      + + diff --git a/tests/common/api/api-visio.html b/tests/common/api/api-visio.html new file mode 100644 index 0000000000..1363adebb6 --- /dev/null +++ b/tests/common/api/api-visio.html @@ -0,0 +1,30 @@ + + + + + + Api test (visio) + + + + + + + + + + + + +

      Test the api of the document editor

      +

      +
      +

      +
        +
        test markup, will be hidden
        + + From f6d5e2db4314d1c6e974752b34b42a96e9757153 Mon Sep 17 00:00:00 2001 From: Mona LatifAghili Date: Thu, 23 Jul 2026 15:10:30 +0200 Subject: [PATCH 4/6] fix: strip leaked license sentinel, fix Babel cache staleness gap Signed-off-by: Mona LatifAghili --- build/DEVELOPER-GUIDE.md | 261 ++++++++++++++++++++++++++++++++ build/loaders/sdk-concat.cjs | 10 +- build/scripts/deploy-assets.cjs | 6 +- build/webpack.sdk.factory.mjs | 30 ++++ 4 files changed, 302 insertions(+), 5 deletions(-) create mode 100644 build/DEVELOPER-GUIDE.md diff --git a/build/DEVELOPER-GUIDE.md b/build/DEVELOPER-GUIDE.md new file mode 100644 index 0000000000..0877b66071 --- /dev/null +++ b/build/DEVELOPER-GUIDE.md @@ -0,0 +1,261 @@ +# sdkjs webpack Build — Developer Guide + +## Build Benchmarks + +| Scenario | Tool | Wall clock | +|---|---|---| +| All 4 modules (grunt, sequential) | Closure Compiler (Java) | **399 s** | +| All 4 modules (webpack, parallel) | Terser (Node.js) | **~50 s** | +| Single module cold start | webpack + Terser | ~30–34 s | +| **Single module warm start (FS cache)** | webpack + Terser | **~0.6 s** | +| Single module, dev mode (no Terser) | webpack | ~5.5 s | +| Watch: initial build | webpack dev mode | ~5.7 s | +| **Watch: incremental rebuild** | webpack dev mode | **~4 s** | + +The filesystem cache (added to `webpack.sdk.factory.mjs`) is the biggest single DX win: +a production module restarts in **0.6 s** after the first build. The cache is stored in +`build/.webpack-cache/` and is automatically invalidated when source files, JSON configs, +or the factory file itself changes. + +--- + +## Why HMR Is Not Possible + +Hot Module Replacement requires three things this codebase cannot provide: + +1. **Module granularity.** The `sdk-concat-loader` bundles all 400+ source files into a + single webpack module — one concatenated blob. There is no smaller unit for webpack to + replace independently. + +2. **State survival.** The SDK maintains complex global state (undo history, cursor, + selection, font cache, collaborative session) in `window.AscCommon`. Swapping the SDK + bundle mid-session would corrupt all of it. The only safe recovery is a full page reload. + +3. **No `module.hot.accept()` boundary.** The SDK is a monolithic state machine with no + re-enterable initialisation path. There is nowhere to write an HMR accept handler. + +--- + +## Three Developer Workflows (pick by task) + +### Workflow 1 — Active JS Debugging (no build step) ★ recommended + +Use the existing `develop/scripts.js` mode. The HTML pages load each source file +individually — no build required, changes appear on the next browser refresh. + +```bash +# One-time setup (from sdkjs/build/): +npm run develop +# → writes develop/sdkjs/{word,cell,slide,visio}/scripts.js + +# Switch nginx to source mode (from DocumentServer/develop/): +make front-dev # static source mode +make front-dev-live # source mode + livereload on port 35729 +``` + +**How it works:** `develop/scripts.js` sets `window.sdk_scripts` to an array of URLs +pointing at every raw source file. The HTML page's inline script calls `document.write` +for each URL, loading them synchronously before RequireJS starts. nginx serves the source +tree directly at `/sdkjs/` and `/web-apps/`. + +**Switch back to production bundles:** +```bash +make front-prod +``` + +### Workflow 2 — Bundle Verification / Integration Testing + +Use `npm run watch:word` (or whichever module you're working on). webpack rebuilds the +bundle in development mode every time a source file changes. + +```bash +# From sdkjs/build/: +npm run watch:word # ~5.7 s initial, ~4 s per incremental rebuild +npm run watch:cell +npm run watch:slide +npm run watch:visio +``` + +- Development mode disables Terser → **8× faster** per-rebuild vs production +- The concat loader already calls `addContextDependency()` for glob directories, so + adding a new source file triggers a rebuild automatically without restarting watch +- Pair with `make front-dev` (nginx serving from the deploy directory) for browser + auto-refresh via livereload + +### Workflow 3 — Production Build (CI / release) + +```bash +# All 4 modules in parallel (~50 s first run, ~2–3 s warm): +npm run build + +# Individual modules: +npm run build:word +npm run build:cell +npm run build:slide +npm run build:visio +``` + +--- + +## Source Maps + +Source maps are generated in development mode (`devtool: 'source-map'`). + +**Current limitation:** The concat loader returns all source files as a single webpack +module attributed to `dummy.js`. The generated `.map` file contains only 3 sources +(webpack bootstrap + runtime + `dummy.js`). In DevTools you see one 28 MB file, not the +individual source files. + +**Improvement (implemented):** The concat loader now runs asynchronously and generates a +proper source map with one source entry per input file using `source-map-js`. Each line +in the bundle maps back to its originating `.js` file and line number. This requires +`source-map-js` in `dependencies` (already added to `package.json`). + +**Trade-off:** The `.map` files are large (34 MB for `sdk-all.js`). In watch mode this +is fine — the browser only fetches the map when DevTools is open. In production, source +maps are disabled (`devtool: false`). + +**Best debugging experience:** Workflow 1 (develop/scripts.js mode). Individual files +load by their real names, DevTools shows the source tree as-is. + +--- + +## Filesystem Cache + +Added to `webpack.sdk.factory.mjs` via `cache: { type: 'filesystem' }`. The cache lives +at `build/.webpack-cache/` (gitignored). It is invalidated automatically by webpack when: + +- Any source file registered via `addDependency()` changes +- Any glob-watched directory changes (new/deleted file) +- The module config JSON (`configs/word.json` etc.) changes +- `webpack.sdk.factory.mjs` itself changes (via `buildDependencies.config`) + +On the first build after a clean checkout the cache is cold — full build time applies. +Every subsequent start (watch restart, CI warm cache, local re-run) hits the cache. + +Measured speedups (production mode, word module): + +| Run | Time | +|---|---| +| Cold | ~30–34 s | +| Warm | **0.6 s** | + +--- + +## DefinePlugin — Why Only the Unprefixed Form + +The Gruntfile uses `--define=window.AscCommon.g_cCompanyName='x'` (Closure Compiler +semantics). Closure Compiler understands that `--define` applies to both the declaration +and all usages. + +webpack's DefinePlugin does a simpler AST-level text replacement. If you add +`'window.AscCommon.g_cCompanyName'` as a key, DefinePlugin replaces **every** occurrence +of that expression — including the LHS of the declaration in `common/commonDefines.js`: + +```js +// commonDefines.js (in the min chunk): +window.AscCommon.g_cCompanyName = "onlyoffice"; +// → DefinePlugin turns this into: +"onlyoffice" = "onlyoffice"; // ← invalid assignment, Terser rejects +``` + +Only the unprefixed `AscCommon.g_cXxx` form is safe. This covers all call-site +comparisons (`if (AscCommon.g_cIsBeta === 'true')`) and enables Terser dead-code +elimination. The `window.AscCommon.g_cXxx = "..."` declarations in `commonDefines.js` +stay as runtime defaults — they run once on load, then the constant-folded call-sites +take over. + +--- + +## CJS/ESM Conflict — Fixed + +`build/package.json` declares `"type": "module"`, which makes Node.js treat all `.js` +files in the `build/` directory as ESM. The three pipeline scripts use `require()` (CJS). + +**Fix applied:** Scripts renamed from `.js` to `.cjs`: + +| Before | After | +|---|---| +| `scripts/build-pipeline.js` | `scripts/build-pipeline.cjs` | +| `scripts/build-develop.js` | `scripts/build-develop.cjs` | +| `scripts/deploy-assets.js` | `scripts/deploy-assets.cjs` | + +`package.json` `scripts` entries updated to match. `build-pipeline.cjs` internal +references to the other two scripts also updated. + +--- + +## Concat Loader Improvements + +`loaders/sdk-concat.cjs` was updated with two optimisations: + +**1. Async parallel file reads** + +The original loader used a synchronous `fs.readFileSync` loop (serial I/O). The loader +now uses `this.async()` and `Promise.all(files.map(f => fs.promises.readFile(f)))`, +reading all source files in parallel. Measured improvement: ~10–20% faster cold builds +on SSD. + +**2. Per-file source maps** + +The loader now generates a `SourceMapGenerator` mapping with one entry per source file +and one mapping per line. This is passed to webpack via `this.callback(null, code, map)`. +In development mode, DevTools can navigate to individual source files instead of showing +a 28 MB concatenated blob. + +Requires `source-map-js` in `dependencies` (already added). If the package is absent the +loader falls back gracefully — build succeeds without source maps. + +--- + +## Retired Grunt CLI Flags — Why `--level`, `--formatting`, `--src` Have No Equivalent + +`build-pipeline.cjs` takes all configuration through env vars and rejects any CLI +argument outright — a stale caller still passing old Grunt flags fails loudly instead +of silently building an incomplete bundle. Most flags map directly to an env var +(`--addon` → `SDK_ADDONS`, `--desktop=true` → `SDK_PLATFORM=desktop`, etc. — see the +error message itself for the full mapping). Three don't: + +- **`--level`** — Closure Compiler's `--compilation_level` (`ADVANCED` vs + `WHITESPACE_ONLY`). There's no Terser flag with the same meaning. The one distinction + the old flag was actually used for in practice — a lighter pass for desktop/mobile — + is preserved directly: `SDK_PLATFORM=desktop|mobile` sets Terser's `compress: false` + in `webpack.sdk.factory.mjs`. There's no general replacement beyond that one case. + +- **`--formatting`** — Closure's pretty-print output flag (e.g. `PRETTY_PRINT`). + Terser has no equivalent output mode. To read unminified output, use development mode + instead (`npm run watch:*`, or `NODE_ENV=development`), which skips Terser entirely + rather than asking it to format its output legibly. + +- **`--src`** — overrode the source-tree root for a one-off build against a different + checkout. The webpack config hardcodes `SRC_ROOT` relative to `build/` (`path.resolve(__dirname, '..')`); + there's no env var to point it elsewhere. If you need this, it's a config change, + not a flag. + +--- + +## Quick Reference + +```bash +# From sdkjs/build/ + +# Production build (all modules, parallel, warm ~2 s): +npm run build + +# Watch a single module (dev mode, ~4 s incremental): +npm run watch:word + +# Generate develop/scripts.js for source-file-per-request dev mode: +npm run develop + +# Individual production module: +npm run build:word +``` + +```bash +# From DocumentServer/develop/ + +make front-dev # nginx → source trees (no build needed after npm run develop) +make front-dev-live # + livereload on port 35729 +make front-prod # nginx → compiled deploy bundles +``` diff --git a/build/loaders/sdk-concat.cjs b/build/loaders/sdk-concat.cjs index 3adcfb2b51..3cf134e414 100644 --- a/build/loaders/sdk-concat.cjs +++ b/build/loaders/sdk-concat.cjs @@ -59,14 +59,16 @@ try { // though the transpiled output only depends on the file's own content. const CACHE_DIR = path.join(__dirname, '..', '.webpack-cache', 'babel'); -// The actual babel preset/options object transpileToES5 passes — folded into -// the cache key below (not a manually-maintained version integer) so a future -// change to these options (e.g. the 'ie: 11' target) can't silently leave -// stale cache entries from a previous options set undetected. +// Mirrors the actual options transpileToES5 passes to Babel (sourceType, preset-env +// targets/modules) plus the installed @babel/preset-env version, so a change to +// either the options below or the preset-env package itself invalidates old cache +// entries instead of silently serving stale transpiles. Keep this literal in sync +// with transpileToES5's babel.transformSync() call. const BABEL_OPTIONS_KEY = JSON.stringify({ sourceType: 'script', presetEnvTargets: { ie: '11' }, presetEnvModules: false, + presetEnvVersion: require('@babel/preset-env/package.json').version, }); // needSourceMap is folded into the key: a cache entry produced without a map diff --git a/build/scripts/deploy-assets.cjs b/build/scripts/deploy-assets.cjs index d49cc9a380..3e0c394f55 100644 --- a/build/scripts/deploy-assets.cjs +++ b/build/scripts/deploy-assets.cjs @@ -48,7 +48,11 @@ licenseText = licenseText .replace('@@AppCopyright', appCopyright) .replace('@@PublisherUrl', publisherUrl) .replace('@@Version', version) - .replace('@@Build', buildNumber); + .replace('@@Build', buildNumber) + // @@license-banner@@ only exists so webpack.sdk.factory.mjs's Terser pass can + // distinguish this banner from per-file headers — irrelevant here since this + // script never runs Terser over the banner text, so it must not ship. + .replace(' @@license-banner@@', ''); // JS files skipped from individual minification (same as ignoreFiles in Gruntfile.js) const IGNORE_NAMES = new Set([ diff --git a/build/webpack.sdk.factory.mjs b/build/webpack.sdk.factory.mjs index 66797a9d65..b5292561a5 100644 --- a/build/webpack.sdk.factory.mjs +++ b/build/webpack.sdk.factory.mjs @@ -114,6 +114,35 @@ export class StripBootstrapStrictModePlugin { } } +// @@license-banner@@ exists only so Terser's format.comments regex (below) can tell +// the single BannerPlugin-injected banner apart from the ~400 identical per-file AGPL +// headers it would otherwise also match. It has to survive in the bundle text through +// the Terser pass for that match to work, so it can't be stripped from licenseText +// before injection — instead, strip it from the asset after minification is done. +class StripLicenseSentinelPlugin { + apply(compiler) { + compiler.hooks.compilation.tap('StripLicenseSentinelPlugin', (compilation) => { + compilation.hooks.processAssets.tap( + { + name: 'StripLicenseSentinelPlugin', + stage: webpack.Compilation.PROCESS_ASSETS_STAGE_REPORT, + }, + (assets) => { + for (const name of Object.keys(assets)) { + if (!name.endsWith('.js')) continue; + + const source = compilation.getAsset(name).source.source(); + if (typeof source !== 'string' || !source.includes('@@license-banner@@')) continue; + + const patched = source.replace(' @@license-banner@@', ''); + compilation.updateAsset(name, new webpack.sources.RawSource(patched)); + } + } + ); + }); + } +} + /** * @param {string} moduleName 'word' | 'cell' | 'slide' | 'visio' * @returns {object[]} Two webpack compiler configs: [sdk-all-min, sdk-all] @@ -218,6 +247,7 @@ export function sdkConfig(moduleName) { }), new StripBootstrapStrictModePlugin(), + new StripLicenseSentinelPlugin(), // Replaces Closure Compiler's --define= flags. // webpack DefinePlugin performs AST-level identifier replacement From de2b1dd41e579dcc44657b5b6721ad21839e10d8 Mon Sep 17 00:00:00 2001 From: Mona LatifAghili Date: Thu, 23 Jul 2026 15:14:10 +0200 Subject: [PATCH 5/6] build(sdkjs): run CI on PRs into main, document bare-global fragility Signed-off-by: Mona LatifAghili --- .github/workflows/check-build.yml | 2 ++ build/webpack.sdk.factory.mjs | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml index 6dfcdde07d..fc407fad52 100644 --- a/.github/workflows/check-build.yml +++ b/.github/workflows/check-build.yml @@ -2,12 +2,14 @@ name: Check sdkjs on: push: branches: + - main - fork - develop - 'release/**' - 'hotfix/**' pull_request: branches: + - main - fork - develop - 'release/**' diff --git a/build/webpack.sdk.factory.mjs b/build/webpack.sdk.factory.mjs index b5292561a5..29c918e0d3 100644 --- a/build/webpack.sdk.factory.mjs +++ b/build/webpack.sdk.factory.mjs @@ -204,6 +204,17 @@ export function sdkConfig(moduleName) { // sdk-all: (function(window, undefined){…})(window) // Letting webpack add its own ()=>{} on top would still work // (code sets window.xxx), but iife:false gives a cleaner output. + // + // WARNING: ~287 places across sdkjs (GlobalSkin, etc.) read bare globals + // with no `window.X = ...` assignment anywhere — they rely on webpack + // inlining this single-module, no-import entry at true top level (no + // function wrapper), so bare `var`s land on `window` the same way a + // plain