diff --git a/components/bin/build b/components/bin/build index 68360eac4..ef46205c7 100755 --- a/components/bin/build +++ b/components/bin/build @@ -61,6 +61,9 @@ process.chdir(path.dirname(json)); const mjPath = path.relative(process.cwd(), path.resolve(__dirname, '..', '..', target)); const mjGlobal = path.join('..', mjPath, 'components', 'global.js'); +/** + * Determine the module type + */ function getType() { const component = config.component || 'part'; if (component.match(/\/(svg|chtml|common)\/fonts\//)) return RegExp.$1 + '-font'; @@ -69,6 +72,13 @@ function getType() { return component; } +/** + * Convert Windows paths to unix paths + */ +const normalize = process.platform === 'win32' + ? (file) => file.replace(/\\/g, '/') + : (file) => file; + /** * Extract the configuration values */ @@ -100,7 +110,7 @@ let PACKAGE = []; */ function processList(base, dir, list, top = true) { for (const item of list) { - const file = path.join(dir, item); + const file = normalize(path.join(dir, item)); if (!EXCLUDE.has(file)) { const stat = fs.statSync(path.resolve(base, file)); if (stat.isDirectory()) { @@ -271,12 +281,12 @@ function getExtraDirectories() { function processGlobal() { console.info(' ' + COMPONENT + '.ts'); const lines = (target === 'cjs' ? [ - `const {combineWithMathJax} = require('${GLOBAL}')`, - `const {VERSION} = require('${VERSION}');`, + `const {combineWithMathJax} = require('${normalize(GLOBAL)}')`, + `const {VERSION} = require('${normalize(VERSION)}');`, '', ] : [ - `import {combineWithMathJax} from '${GLOBAL}';`, - `import {VERSION} from '${VERSION}';`, + `import {combineWithMathJax} from '${normalize(GLOBAL)}';`, + `import {VERSION} from '${normalize(VERSION)}';`, '', ]); const [prefix, indent, postfix] = getExtraDirectories(); @@ -337,7 +347,7 @@ function processPackage(lines, space, dir) { if (path.dirname(PACKAGE[0]) === dir) { const file = PACKAGE.shift(); const name = path.basename(file); - let relativefile = path.join('..', JS, dir, name).replace(/\.ts$/, '.js') + const relativefile = normalize(path.join('..', JS, dir, name).replace(/\.ts$/, '.js')); const component = 'module' + (++importCount); lines.push( target === 'cjs' ? diff --git a/components/bin/makeAll b/components/bin/makeAll index b661d3696..8eafa678c 100755 --- a/components/bin/makeAll +++ b/components/bin/makeAll @@ -124,6 +124,13 @@ function fileRegExp(name) { return new RegExp(name.replace(/([\\.{}[\]()?*^$])/g, '\\$1'), 'g'); } +/** + * Options for the execSync() function + */ +const execOptions = process.platform === 'win32' + ? { shell: `${process.env.ProgramFiles}\\Git\\bin\\bash.exe` } + : {}; + /** * Get the current working directory */ @@ -196,7 +203,7 @@ function processSubdirs(dir, action, config) { * Run a command on a given directory */ function run(cmd, dir) { - return execSync(cmd + ` '${path.relative('.', dir).replace(/'/g, '\\\'')}'`); + return execSync(cmd + ` '${path.relative('.', dir).replace(/'/g, '\\\'')}'`, execOptions); } /** diff --git a/components/bin/pack b/components/bin/pack index ebcf8a214..4a91ab5de 100755 --- a/components/bin/pack +++ b/components/bin/pack @@ -25,7 +25,7 @@ const fs = require('fs'); const path = require('path'); -const {spawn, execSync} = require('child_process'); +const {spawn} = require('child_process'); /** * The module type to use ('cjs' or 'mjs') @@ -64,14 +64,7 @@ const rootRE = fileRegExp(path.dirname(jsPath)); const nodeRE = /^.*\/node_modules/; const fontRE = new RegExp('^.*\\/(mathjax-[^\/-]*)(?:-font)?\/(build|[cm]js)'); -/** - * Find the directory where npx runs (so we know where "npx webpack" will run) - * (We use npx rather than pnpm here as it seems that pnpm doesn't - * find the executable from a node_modules directory higher than the - * first package.json, and extensions and fonts can have their own - * package.json.) - */ -const packDir = String(execSync('npx node -e "console.log(process.cwd())"')); +const packDir = process.cwd(); /** * @param {string} dir The directory to pack @@ -80,11 +73,20 @@ const packDir = String(execSync('npx node -e "console.log(process.cwd())"')); async function readJSON(dir) { return new Promise((ok, fail) => { const buffer = []; - const child = spawn('npx', [ - 'webpack', '--env', `dir=${path.relative(packDir, path.resolve(dir))}`, - '--env', `bundle=${bundle}`, '--json', - '-c', path.relative(packDir, path.join(compPath, 'webpack.config.' + target)) - ]); + const child = spawn( + 'npx', + [ + 'webpack', + '--env', `dir=${path.relative(packDir, path.resolve(dir))}`, + '--env', `bundle=${bundle}`, + '--json', + '-c', path.relative(packDir, path.join(compPath, 'webpack.config.' + target)) + ], + { + cwd: packDir, + shell: true, + } + ); child.stdout.on('data', (data) => buffer.push(String(data))); child.stderr.on('data', (data) => console.error(String(data))); child.on('close', (code) => { diff --git a/components/mjs/a11y/speech/speech.js b/components/mjs/a11y/speech/speech.js index a4c474b4d..4f6aae494 100644 --- a/components/mjs/a11y/speech/speech.js +++ b/components/mjs/a11y/speech/speech.js @@ -2,20 +2,20 @@ import './lib/speech.js'; import {combineDefaults} from '#js/components/global.js'; import {Package} from '#js/components/package.js'; -import {hasWindow} from '#js/util/context.js'; +import {context} from '#js/util/context.js'; import {SpeechHandler} from '#js/a11y/speech.js'; if (MathJax.loader) { let path = Package.resolvePath('[sre]', false); let maps = Package.resolvePath('[mathmaps]', false); - if (hasWindow) { + if (context.window) { path = new URL(path, location).href; maps = new URL(maps, location).href; } else { const REQUIRE = typeof require !== 'undefined' ? require : MathJax.config.loader.require; if (REQUIRE?.resolve) { - path = REQUIRE.resolve(`${path}/require.mjs`).replace(/\/[^\/]*$/, ''); - maps = REQUIRE.resolve(`${maps}/base.json`).replace(/\/[^\/]*$/, ''); + path = context.path(REQUIRE.resolve(`${path}/require.mjs`)).replace(/\/[^\/]*$/, ''); + maps = context.path(REQUIRE.resolve(`${maps}/base.json`)).replace(/\/[^\/]*$/, ''); } else { path = maps = ''; } diff --git a/components/mjs/node-main/node-main-setup.mjs b/components/mjs/node-main/node-main-setup.mjs index f143f4233..793a66e4e 100644 --- a/components/mjs/node-main/node-main-setup.mjs +++ b/components/mjs/node-main/node-main-setup.mjs @@ -4,4 +4,4 @@ global.require = createRequire(import.meta.url); const path = require("path"); if (!global.MathJax) global.MathJax = {}; -global.MathJax.__dirname = path.dirname(new URL(import.meta.url).pathname); +global.MathJax.__dirname = path.dirname(new URL(import.meta.url).pathname); diff --git a/components/mjs/node-main/node-main.js b/components/mjs/node-main/node-main.js index 13b635173..e09f18270 100644 --- a/components/mjs/node-main/node-main.js +++ b/components/mjs/node-main/node-main.js @@ -23,6 +23,7 @@ import '../startup/init.js'; import {Loader, CONFIG} from '#js/components/loader.js'; import {Package} from '#js/components/package.js'; import {combineDefaults, combineConfig} from '#js/components/global.js'; +import {context} from '#js/util/context.js'; import '../core/core.js'; import '../adaptors/liteDOM/liteDOM.js'; import {source} from '../source.js'; @@ -30,7 +31,7 @@ import {source} from '../source.js'; const MathJax = global.MathJax; const path = eval('require("path")'); // get path from node, not webpack -const dir = MathJax.config.__dirname; // set up by node-main.mjs or node-main.cjs +const dir = context.path(MathJax.config.__dirname); // set up by node-main.mjs or node-main.cjs /* * Set up the initial configuration diff --git a/components/mjs/source-lab.js b/components/mjs/source-lab.js index 708290300..d7455ab4a 100644 --- a/components/mjs/source-lab.js +++ b/components/mjs/source-lab.js @@ -15,4 +15,4 @@ * limitations under the License. */ -export const src = String(new URL('.', import.meta.url)).replace(/\/$/, ''); +export const dirname = String(new URL('.', import.meta.url)).replace(/\/$/, ''); diff --git a/components/mjs/source.cjs b/components/mjs/source.cjs index db7fddf94..bd1f9dd24 100644 --- a/components/mjs/source.cjs +++ b/components/mjs/source.cjs @@ -15,4 +15,4 @@ * limitations under the License. */ -module.exports.src = __dirname; +module.exports.dirname = __dirname; diff --git a/components/mjs/source.d.cts b/components/mjs/source.d.cts index 93e9f7a7c..363367412 100644 --- a/components/mjs/source.d.cts +++ b/components/mjs/source.d.cts @@ -1 +1 @@ -export declare const src: string; +export declare const dirname: string; diff --git a/components/mjs/source.js b/components/mjs/source.js index e632cbf88..ab4e056da 100644 --- a/components/mjs/source.js +++ b/components/mjs/source.js @@ -15,7 +15,9 @@ * limitations under the License. */ -import {src} from '#source/source.cjs'; +import {dirname} from '#source/source.cjs'; +import {context} from '#js/util/context.js'; +const src = context.path(dirname); export const source = { 'core': `${src}/core/core.js`, diff --git a/package.json b/package.json index ef2dece39..ce8ce4d8a 100644 --- a/package.json +++ b/package.json @@ -86,9 +86,9 @@ "copy:mml3": "copy() { pnpm -s log:single 'Copying legacy code MathML3'; pnpm copyfiles -u 1 ts/input/mathml/mml3/mml3.sef.json $1; }; copy", "copy:pkg": "copy() { pnpm -s log:single \"Copying package.json to $1\"; pnpm copyfiles -u 2 components/bin/package.json $1; }; copy", "=============================================================================== log": "", - "log:comp": "log() { echo \\\\033[32m$1\\\\033[0m; }; log", - "log:header": "log() { echo '============='; echo $1; echo '============='; }; log", - "log:single": "log() { echo \\\\033[34m--$1\\\\033[0m; }; log", + "log:comp": "log() { echo \u001B[32m$1\u001B[0m; }; log", + "log:header": "log() { echo '\u001B[1m============='; echo $1; echo '=============\u001B[0m'; }; log", + "log:single": "log() { echo \u001B[94m--$1\u001B[0m; }; log", "=============================================================================== cjs": "", "cjs:build": "pnpm -s log:header 'Building cjs'; pnpm -s cjs:src:build && pnpm -s cjs:components:build", "cjs:bundle:clean": "pnpm clean:dir bundle-cjs", diff --git a/testsuite/tests/util/Context-android.test.ts b/testsuite/tests/util/Context-android.test.ts index 4b9c88db1..d95322bde 100644 --- a/testsuite/tests/util/Context-android.test.ts +++ b/testsuite/tests/util/Context-android.test.ts @@ -7,6 +7,8 @@ describe('context object', () => { test('context', async () => { let {context, hasWindow} = await import("#js/util/context.js"); + expect(context.path('C:\\test.js')).toBe('C:\\test.js'); + delete context.path; expect(context).toEqual({window: window, document: window.document, os: 'Unix'}); expect(hasWindow).toBe(true); }); diff --git a/testsuite/tests/util/Context-browser.test.ts b/testsuite/tests/util/Context-browser.test.ts index cb5d959cf..56e939fec 100644 --- a/testsuite/tests/util/Context-browser.test.ts +++ b/testsuite/tests/util/Context-browser.test.ts @@ -7,6 +7,8 @@ describe('context object', () => { test('context', async () => { let {context, hasWindow} = await import("#js/util/context.js"); + expect(context.path('C:\\test.js')).toBe('C:\\test.js'); + delete context.path; expect(context).toEqual({window: window, document: window.document, os: 'Unix'}); expect(hasWindow).toBe(true); }); diff --git a/testsuite/tests/util/Context-node-unknown.test.ts b/testsuite/tests/util/Context-node-unknown.test.ts new file mode 100644 index 000000000..094e5514f --- /dev/null +++ b/testsuite/tests/util/Context-node-unknown.test.ts @@ -0,0 +1,15 @@ +import { describe, test, expect } from '@jest/globals'; + +global.process = {...process, platform: 'test'} as any; + +describe('context object', () => { + + test('context', async () => { + let {context, hasWindow} = await import("#js/util/context.js"); + expect(context.path('C:\\test.js')).toBe('C:\\test.js'); + delete context.path; + expect(context).toEqual({window: null, document: null, os: 'test'}); + expect(hasWindow).toBe(false); + }); + +}); diff --git a/testsuite/tests/util/Context-node.test.ts b/testsuite/tests/util/Context-node.test.ts index 8df0b38fe..fc589c87a 100644 --- a/testsuite/tests/util/Context-node.test.ts +++ b/testsuite/tests/util/Context-node.test.ts @@ -1,10 +1,30 @@ import { describe, test, expect } from '@jest/globals'; import { context, hasWindow } from '#js/util/context.js'; +const OS = { + 'linux': 'Unix', + 'android': 'Unix', + 'aix': 'Unix', + 'freebsd': 'Unix', + 'netbsd': 'Unix', + 'openbsd': 'Unix', + 'sunos': 'Unix', + 'darwin': 'MacOS', + 'win32': 'Windows', + 'cygwin': 'Windows', + 'haiku': 'unknown', +}[process.platform] || process.platform; + describe('context object', () => { test('context', () => { - expect(context).toEqual({window: null, document: null, os: 'unknown'}); + if (process.platform as string === 'Windows') { + expect(context.path('C:\\test.js')).toBe('C:/test.js'); + } else { + expect(context.path('C:\\test.js')).toBe('C:\\test.js'); + } + delete context.path; + expect(context).toEqual({window: null, document: null, os: OS}); expect(hasWindow).toBe(false); }); diff --git a/testsuite/tests/util/Context-unknown.test.ts b/testsuite/tests/util/Context-unknown.test.ts new file mode 100644 index 000000000..cc7ed9f63 --- /dev/null +++ b/testsuite/tests/util/Context-unknown.test.ts @@ -0,0 +1,16 @@ +import { describe, test, expect } from '@jest/globals'; + +const window = {document: {}, navigator: {userAgent: '', appVersion: ''}}; +(global as any).window = window; + +describe('context object', () => { + + test('context', async () => { + let {context, hasWindow} = await import("#js/util/context.js"); + expect(context.path('C:\\test.js')).toBe('C:\\test.js'); + delete context.path; + expect(context).toEqual({window: window, document: window.document, os: 'unknown'}); + expect(hasWindow).toBe(true); + }); + +}); diff --git a/testsuite/tests/util/Context-windows.test.ts b/testsuite/tests/util/Context-windows.test.ts new file mode 100644 index 000000000..8b8550e40 --- /dev/null +++ b/testsuite/tests/util/Context-windows.test.ts @@ -0,0 +1,18 @@ +import { describe, test, expect } from '@jest/globals'; + +const window = {document: {}, navigator: {appVersion: 'Win'}}; +(global as any).window = window; + +describe('context object', () => { + + test('context', async () => { + let {context, hasWindow} = await import("#js/util/context.js"); + expect(context.path('C:\\test.js')).toBe('C:/test.js'); + expect(context.path('/C:/test.js')).toBe('C:/test.js'); + expect(context.path('/test.js')).toBe('/test.js'); + delete context.path; + expect(context).toEqual({window: window, document: window.document, os: 'Windows'}); + expect(hasWindow).toBe(true); + }); + +}); diff --git a/ts/components/cjs/root.ts b/ts/components/cjs/root.ts index 137c4a5a2..e22a9cc6f 100644 --- a/ts/components/cjs/root.ts +++ b/ts/components/cjs/root.ts @@ -25,10 +25,13 @@ * The location of this file */ declare const __dirname: string; +declare const require: (file: string) => any; /** * @return {string} The MathJax component root directory */ export function mjxRoot(): string { - return __dirname.replace(/[cm]js\/components\/[cm]js$/, 'bundle'); + return require('../../util/context.js') + .context.path(__dirname) + .replace(/[cm]js\/components\/[cm]js$/, 'bundle'); } diff --git a/ts/components/cjs/sre-root.ts b/ts/components/cjs/sre-root.ts index bb8cff7e9..867c41493 100644 --- a/ts/components/cjs/sre-root.ts +++ b/ts/components/cjs/sre-root.ts @@ -25,10 +25,13 @@ * The location of this file */ declare const __dirname: string; +declare const require: (file: string) => any; /** - * @return {string} The MathJax mjs SRE root directory + * @return {string} The MathJax cjs SRE root directory */ export function sreRoot(): string { - return __dirname.replace(/components\/[cm]js$/, 'a11y/sre'); + return require('../../util/context.js') + .context.path(__dirname) + .replace(/components\/[cm]js$/, 'a11y/sre'); } diff --git a/ts/components/loader.ts b/ts/components/loader.ts index 34c2e2e56..c3b476238 100644 --- a/ts/components/loader.ts +++ b/ts/components/loader.ts @@ -120,7 +120,7 @@ export const PathFilters: { [name: string]: PathFilterFunction } = { */ normalize: (data) => { const name = data.name; - if (!name.match(/^(?:[a-z]+:\/)?\/|[a-z]:\\|\[/i)) { + if (!name.match(/^(?:[a-z]+:\/)?\/|[a-z]:[/\\]|\[/i)) { data.name = '[mathjax]/' + name.replace(/^\.\//, ''); } return true; diff --git a/ts/components/mjs/root.ts b/ts/components/mjs/root.ts index d8e328158..9fae578fa 100644 --- a/ts/components/mjs/root.ts +++ b/ts/components/mjs/root.ts @@ -21,12 +21,13 @@ * @author dpvc@mathjax.org (Davide Cervone) */ +import { context } from '../../util/context.js'; + /** * @returns {string} The MathJax component root directory */ export function mjxRoot(): string { - return new URL(import.meta.url).pathname.replace( - /[cm]js\/components\/[cm]js\/root.js$/, - 'bundle' - ); + return context + .path(new URL(import.meta.url).pathname) + .replace(/[cm]js\/components\/[cm]js\/root.js$/, 'bundle'); } diff --git a/ts/components/mjs/sre-root.ts b/ts/components/mjs/sre-root.ts index 99c44f78f..103bdf2a9 100644 --- a/ts/components/mjs/sre-root.ts +++ b/ts/components/mjs/sre-root.ts @@ -21,12 +21,13 @@ * @author dpvc@mathjax.org (Davide Cervone) */ +import { context } from '../../util/context.js'; + /** * @returns {string} The MathJax mjs SRE root directory */ export function sreRoot(): string { - return new URL(import.meta.url).pathname.replace( - /components\/[cm]js\/sre-root.js$/, - 'a11y/sre' - ); + return context + .path(new URL(import.meta.url).pathname) + .replace(/components\/[cm]js\/sre-root.js$/, 'a11y/sre'); } diff --git a/ts/util/asyncLoad/node-import.cjs b/ts/util/asyncLoad/node-import.cjs index 6759ea124..b5ed1d464 100644 --- a/ts/util/asyncLoad/node-import.cjs +++ b/ts/util/asyncLoad/node-import.cjs @@ -23,9 +23,9 @@ const { mathjax } = require('../../mathjax.js'); const path = require('path'); -const { src } = require('#source/source.cjs'); +const { dirname } = require('#source/source.cjs'); -let root = path.resolve(src, '..', '..', 'cjs'); +let root = path.resolve(dirname, '..', '..', 'cjs'); if (!mathjax.asyncLoad) { mathjax.asyncLoad = async (name) => { diff --git a/ts/util/asyncLoad/node.ts b/ts/util/asyncLoad/node.ts index 5189a7e11..5ba06e0c7 100644 --- a/ts/util/asyncLoad/node.ts +++ b/ts/util/asyncLoad/node.ts @@ -23,11 +23,11 @@ import { mathjax } from '../../mathjax.js'; import * as path from 'path'; -import { src } from '#source/source.cjs'; +import { dirname } from '#source/source.cjs'; declare const require: (name: string) => any; -let root = path.resolve(src, '..', '..', 'cjs'); +let root = path.resolve(dirname, '..', '..', 'cjs'); if (!mathjax.asyncLoad && typeof require !== 'undefined') { mathjax.asyncLoad = (name: string) => { diff --git a/ts/util/context.ts b/ts/util/context.ts index 568a7c6fa..4f5b216b3 100644 --- a/ts/util/context.ts +++ b/ts/util/context.ts @@ -21,6 +21,8 @@ * @author dpvc@mathjax.org (Davide Cervone) */ +declare const process: { platform: string }; + /** * True if there is a window object */ @@ -49,7 +51,30 @@ export const context = { if (window.navigator.userAgent.includes('Android')) { return 'Unix'; } + } else if (typeof process !== 'undefined') { + return ( + { + linux: 'Unix', + android: 'Unix', + aix: 'Unix', + freebsd: 'Unix', + netbsd: 'Unix', + openbsd: 'Unix', + sunos: 'Unix', + darwin: 'MacOS', + win32: 'Windows', + cygwin: 'Windows', + }[process.platform] || process.platform + ); } return 'unknown'; })(), + path: (file: string) => file, }; + +if (context.os === 'Windows') { + context.path = (file: string) => + file.match(/^[/\\]?[a-zA-Z]:[/\\]/) + ? file.replace(/\\/g, '/').replace(/^\//, '') + : file; +}