diff --git a/package-lock.json b/package-lock.json index 4a9c2c58..da9fc9b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11255,6 +11255,79 @@ "integrity": "sha512-808EqPQbmUD6/IMpWUXLOZcblCHf9xaiB+un0RYNNE9+6VRjoiw6Be8R32tZ0ips1PX/15tlnA2Ev4UUgg827Q==", "dev": true }, + "ts-loader": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.4.0.tgz", + "integrity": "sha512-6nFY3IZ2//mrPc+ImY3hNWx1vCHyEhl6V+wLmL4CZcm6g1CqX7UKrkc6y0i4FwcfOhxyMPCfaEvh20f4r9GNpw==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "enhanced-resolve": "^4.0.0", + "loader-utils": "^2.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "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==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "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==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", @@ -11319,6 +11392,12 @@ "is-typedarray": "^1.0.0" } }, + "typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "dev": true + }, "unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -11362,7 +11441,7 @@ "unicode-trie": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-0.3.1.tgz", - "integrity": "sha512-WgVuO0M2jDl7hVfbPgXv2LUrD81HM0bQj/bvLGiw6fJ4Zo8nNFnDrA0/hU2Te/wz6pjxCm5cxJwtLjo2eyV51Q==", + "integrity": "sha1-1nHd3YkQGgi6w3tqUWEBBgIFIIU=", "dev": true, "requires": { "pako": "^0.2.5", diff --git a/package.json b/package.json index 24d6bf5a..51cc64d5 100644 --- a/package.json +++ b/package.json @@ -60,11 +60,14 @@ "style-loader": "^2.0.0", "svelte": "^3.44.3", "svelte-loader": "^3.1.2", + "ts-loader": "^8.4.0", + "typescript": "^4.7.4", "url-loader": "^4.1.1", "webpack": "^4.46.0", "webpack-bundle-analyzer": "^4.5.0", "webpack-cli": "^4.9.1", "webpack-dev-server": "^3.11.3", + "webpack-merge": "^5.8.0", "worker-loader": "^2.0.0" } } diff --git a/src/packager/plist.js b/src/packager/plist.js deleted file mode 100644 index 3f03f589..00000000 --- a/src/packager/plist.js +++ /dev/null @@ -1,122 +0,0 @@ -// Parses and generates Apple Info.plist files -// Example file: -/* - - - - - BuildMachineOSBuild - 20F71 - CFBundleDevelopmentRegion - en - CFBundleExecutable - WebView - CFBundleIconFile - AppIcon - CFBundleIconName - AppIcon - CFBundleIdentifier - org.turbowarp.webviews.mac - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - WebView - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSupportedPlatforms - - MacOSX - - CFBundleVersion - 1 - DTCompiler - com.apple.compilers.llvm.clang.1_0 - DTPlatformBuild - 12E507 - DTPlatformName - macosx - DTPlatformVersion - 11.3 - DTSDKBuild - 20E214 - DTSDKName - macosx11.3 - DTXcode - 1251 - DTXcodeBuild - 12E507 - LSApplicationCategoryType - public.app-category.games - LSMinimumSystemVersion - 10.12 - NSMainStoryboardFile - Main - NSPrincipalClass - NSApplication - - -*/ - -const xmlToValue = (node) => { - if (node.tagName === 'dict') { - const result = {}; - for (const child of node.children) { - if (child.tagName === 'key') { - result[child.textContent] = xmlToValue(child.nextElementSibling); - } - } - return result; - } else if (node.tagName === 'array') { - return Array.from(node.children).map(xmlToValue); - } else if (node.tagName === 'string') { - return node.textContent; - } - console.warn('unknown plist xml', node); - return null; -}; - -const valueToXml = (doc, value) => { - if (Array.isArray(value)) { - const node = doc.createElement('array'); - for (const listItem of value) { - node.appendChild(valueToXml(doc, listItem)); - } - return node; - } else if (typeof value === 'object') { - const node = doc.createElement('dict'); - for (const [key, keyValue] of Object.entries(value)) { - const keyNode = doc.createElement('key'); - keyNode.textContent = key; - const valueNode = valueToXml(doc, keyValue); - node.appendChild(keyNode); - node.appendChild(valueNode); - } - return node; - } else if (typeof value === 'string') { - const node = doc.createElement('string'); - node.textContent = value; - return node; - } - console.warn('unknown plist value', value); - return valueToXml(doc, `${value}`); -}; - -export const parsePlist = (string) => { - const xml = new DOMParser().parseFromString(string, 'text/xml'); - const rootNode = xml.children[0]; - const rootDict = rootNode.children[0]; - return xmlToValue(rootDict); -}; - -export const generatePlist = (values) => { - const xml = document.implementation.createDocument(null, "plist"); - const rootNode = xml.documentElement; - rootNode.setAttribute('version', '1.0'); - rootNode.appendChild(valueToXml(xml, values)); - const serialized = new XMLSerializer().serializeToString(xml); - return ` - -${serialized}`; -}; diff --git a/src/packager/plist.ts b/src/packager/plist.ts new file mode 100644 index 00000000..1fa7ad5f --- /dev/null +++ b/src/packager/plist.ts @@ -0,0 +1,134 @@ +// Parses and generates Apple Info.plist files +// Example file: +/* + + + + + BuildMachineOSBuild + 20F71 + CFBundleDevelopmentRegion + en + CFBundleExecutable + WebView + CFBundleIconFile + AppIcon + CFBundleIconName + AppIcon + CFBundleIdentifier + org.turbowarp.webviews.mac + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + WebView + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 12E507 + DTPlatformName + macosx + DTPlatformVersion + 11.3 + DTSDKBuild + 20E214 + DTSDKName + macosx11.3 + DTXcode + 1251 + DTXcodeBuild + 12E507 + LSApplicationCategoryType + public.app-category.games + LSMinimumSystemVersion + 10.12 + NSMainStoryboardFile + Main + NSPrincipalClass + NSApplication + + +*/ + +type PlistValue = string | Plist | PlistValue[]; +interface Plist { + [s: string]: PlistValue; +} + +const xmlToValue = (node: Element): PlistValue => { + if (node.tagName === 'dict') { + const result: Plist = {}; + for (const child of node.children) { + if (child.tagName === 'key') { + const next = child.nextElementSibling; + if (!next) { + throw new Error('Plist dict key is missing value'); + } + result[child.textContent!] = xmlToValue(next); + } + } + return result; + } else if (node.tagName === 'array') { + return Array.from(node.children).map(xmlToValue); + } else if (node.tagName === 'string') { + return node.textContent!; + } + throw new Error('Failed to parse plist value'); +}; + +const valueToXml = (doc: XMLDocument, value: PlistValue): Element => { + if (Array.isArray(value)) { + const node = doc.createElement('array'); + for (const listItem of value) { + node.appendChild(valueToXml(doc, listItem)); + } + return node; + } else if (typeof value === 'object') { + const node = doc.createElement('dict'); + for (const [key, keyValue] of Object.entries(value)) { + const keyNode = doc.createElement('key'); + keyNode.textContent = key; + const valueNode = valueToXml(doc, keyValue); + node.appendChild(keyNode); + node.appendChild(valueNode); + } + return node; + } else if (typeof value === 'string') { + const node = doc.createElement('string'); + node.textContent = value; + return node; + } + console.warn('unknown plist value', value); + return valueToXml(doc, `${value}`); +}; + +export const parsePlist = (string: string): Plist => { + const xml = new DOMParser().parseFromString(string, 'text/xml'); + const rootNode = xml.children[0]; + const rootDict = rootNode.children[0]; + const plist = xmlToValue(rootDict); + if (typeof plist !== 'object' || Array.isArray(plist)) { + throw new Error('Top level object in plist was not a dict'); + } + return plist; +}; + +export const generatePlist = (values: Plist): string => { + const xml = document.implementation.createDocument(null, "plist"); + const rootNode = xml.documentElement; + rootNode.setAttribute('version', '1.0'); + rootNode.appendChild(valueToXml(xml, values)); + const serialized = new XMLSerializer().serializeToString(xml); + return ` + +${serialized}`; +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..4d5ed12d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "outDir": "./dist/", + "target": "es2018", + "module": "es6", + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "strict": true, + "noImplicitAny": true + } +} diff --git a/webpack.config.js b/webpack.config.js index 90f3f52b..8fb057d1 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,15 +3,13 @@ const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const CopyWebpackPlugin = require('copy-webpack-plugin'); +const merge = require('webpack-merge').merge; const AddBuildIDToOutputPlugin = require('./src/build/add-build-id-to-output-plugin'); const GenerateServiceWorkerPlugin = require('./src/build/generate-service-worker-plugin'); const EagerDynamicImportPlugin = require('./src/build/eager-dynamic-import-plugin'); const isProduction = process.env.NODE_ENV === 'production'; const isStandalone = !!process.env.STANDALONE; -const base = { - mode: isProduction ? 'production' : 'development' -}; const dist = path.resolve(__dirname, 'dist'); const buildId = isProduction ? require('./src/build/generate-scaffolding-build-id') : null; @@ -30,8 +28,32 @@ const getVersion = () => { }; const version = getVersion(); -const makeScaffolding = ({full}) => ({ - ...base, +const globalBase = { + mode: isProduction ? 'production' : 'development', + resolve: { + extensions: ['.ts', '.js'] + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/ + } + ] + } +}; + +const frontendBase = { + plugins: [ + new webpack.DefinePlugin({ + 'process.env.SCAFFOLDING_BUILD_ID': buildId ? JSON.stringify(buildId) : 'Math.random().toString()', + 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development') + }) + ] +}; + +const makeScaffolding = ({full}) => merge(globalBase, frontendBase, { devtool: isProduction ? '' : 'source-map', output: { filename: 'scaffolding/[name].js', @@ -121,15 +143,7 @@ const makeScaffolding = ({full}) => ({ ] }); -const commonFrontendPlugins = () => [ - new webpack.DefinePlugin({ - 'process.env.SCAFFOLDING_BUILD_ID': buildId ? JSON.stringify(buildId) : 'Math.random().toString()', - 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development') - }) -]; - -const makeWebsite = () => ({ - ...base, +const makeWebsite = () => merge(globalBase, frontendBase, { devtool: isStandalone ? '' : 'source-map', output: { filename: isProduction ? 'js/[name].[contenthash].js' : 'js/[name].js', @@ -142,7 +156,7 @@ const makeWebsite = () => ({ alias: { svelte: path.resolve('node_modules', 'svelte') }, - extensions: ['.mjs', '.js', '.svelte'], + extensions: ['.svelte'], mainFields: ['svelte', 'browser', 'module', 'main'] }, optimization: { @@ -171,7 +185,6 @@ const makeWebsite = () => ({ ] }, plugins: [ - ...commonFrontendPlugins(), new CopyWebpackPlugin({ patterns: [ { @@ -205,8 +218,7 @@ const makeWebsite = () => ({ }, }); -const makeNode = () => ({ - ...base, +const makeNode = () => merge(globalBase, { devtool: '', target: 'node', output: { @@ -236,7 +248,6 @@ const makeNode = () => ({ ] }, plugins: [ - ...commonFrontendPlugins(), ...(process.env.BUNDLE_ANALYZER === 'node' ? [new BundleAnalyzerPlugin()] : []) ], });