diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..dbeac67c45 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,181 @@ +#/ +# @license Apache-2.0 +# +# Copyright (c) 2025 The Stdlib Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#/ + +# EditorConfig configuration file (see ). + +# Indicate that this file is a root-level configuration file: +root = true + +# Set properties for all files: +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# Set properties for JavaScript files: +[*.{js,js.txt}] +indent_style = tab + +# Set properties for JavaScript ES module files: +[*.{mjs,mjs.txt}] +indent_style = tab + +# Set properties for JavaScript CommonJS files: +[*.{cjs,cjs.txt}] +indent_style = tab + +# Set properties for JSX files: +[*.{jsx,jsx.txt}] +indent_style = tab + +# Set properties for JSON files: +[*.{json,json.txt}] +indent_style = space +indent_size = 2 + +# Set properties for TypeScript files: +[*.ts] +indent_style = tab + +# Set properties for Python files: +[*.{py,py.txt}] +indent_style = space +indent_size = 4 + +# Set properties for Julia files: +[*.{jl,jl.txt}] +indent_style = tab + +# Set properties for R files: +[*.{R,R.txt}] +indent_style = tab + +# Set properties for C files: +[*.{c,c.txt}] +indent_style = tab + +# Set properties for C header files: +[*.{h,h.txt}] +indent_style = tab + +# Set properties for C++ files: +[*.{cpp,cpp.txt}] +indent_style = tab + +# Set properties for C++ header files: +[*.{hpp,hpp.txt}] +indent_style = tab + +# Set properties for Fortran files: +[*.{f,f.txt}] +indent_style = space +indent_size = 2 +insert_final_newline = true + +# Set properties for shell files: +[*.{sh,sh.txt}] +indent_style = tab + +# Set properties for AWK files: +[*.{awk,awk.txt}] +indent_style = tab + +# Set properties for HTML files: +[*.{html,html.txt}] +indent_style = tab +tab_width = 2 + +# Set properties for XML files: +[*.{xml,xml.txt}] +indent_style = tab +tab_width = 2 + +# Set properties for CSS files: +[*.{css,css.txt}] +indent_style = tab + +# Set properties for Makefiles: +[Makefile] +indent_style = tab + +[*.{mk,mk.txt}] +indent_style = tab + +# Set properties for Markdown files: +[*.{md,md.txt}] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +# Set properties for `usage.txt` files: +[usage.txt] +indent_style = space +indent_size = 2 + +# Set properties for `repl.txt` files: +[repl.txt] +indent_style = space +indent_size = 4 + +# Set properties for `package.json` files: +[package.{json,json.txt}] +indent_style = space +indent_size = 2 + +# Set properties for `datapackage.json` files: +[datapackage.json] +indent_style = space +indent_size = 2 + +# Set properties for `manifest.json` files: +[manifest.json] +indent_style = space +indent_size = 2 + +# Set properties for `tslint.json` files: +[tslint.json] +indent_style = space +indent_size = 2 + +# Set properties for `tsconfig.json` files: +[tsconfig.json] +indent_style = space +indent_size = 2 + +# Set properties for LaTeX files: +[*.{tex,tex.txt}] +indent_style = tab + +# Set properties for LaTeX Bibliography files: +[*.{bib,bib.txt}] +indent_style = tab + +# Set properties for YAML files: +[*.{yml,yml.txt}] +indent_style = space +indent_size = 2 + +# Set properties for GYP files: +[binding.gyp] +indent_style = space +indent_size = 2 + +[*.gypi] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..3616e53dd8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,171 @@ +#/ +# @license Apache-2.0 +# +# Copyright (c) 2025 The Stdlib Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#/ + +# Files # +######### + + +# Directories # +############### +build/ + +# Compiled source # +################### +*.com +*.class +*.dll +*.o +*.so +*.slo +*.lo +*.obj +*.dylib +*.lai +*.la +*.a +*.lib +*.ko +*.elf +*.node + +# Precompiled headers # +####################### +*.gch +*.pch + +# Executables # +############### +*.exe +*.out +*.app + +# Packages # +############ +# it's better to unpack these files and commit the raw source +# git has its own built in compression methods +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +# Logs and databases # +###################### +*.log +*.sql +*.sqlite + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +Icon? +!icons/ +ehthumbs.db +Thumbs.db +Desktop.ini + +# Temporary files # +################### +*~ + +# Node.js # +########### +/node_modules/ +pids +*.pid +*.seed + +# Matlab # +########## +*.asv +*.mex* + +# Fortran # +########### +*.mod + +# R # +##### +.Rhistory +.Rapp.history +.Rproj.user/ + +# Python # +########## +__pycache__/ +*.py[cod] +*$py.class + +# TeX # +####### +*.aux +*.lof +*.log +*.lot +*.fls +*.out +*.toc +*.dvi +*-converted-to.* +*.bbl +*.bcf +*.blg +*-blx.aux +*-blx.bib +*.brf +*.run.xml +*.fdb_latexmk +*.synctex +*.synctex.gz +*.synctex.gz(busy) +*.pdfsync +*.alg +*.loa +acs-*.bib +*.thm +*.nav +*.snm +*.vrb +*.acn +*.acr +*.glg +*.glo +*.gls +*-concordance.tex +*.tikz +*-tikzDictionary +*.idx +*.ilg +*.ind +*.ist + +# Visual Studio # +################# +.vscode/ +jsconfig.json + +# Sublime Text # +################ +*.sublime-workspace diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000..f955402ed6 --- /dev/null +++ b/.npmrc @@ -0,0 +1,28 @@ +#/ +# @license Apache-2.0 +# +# Copyright (c) 2025 The Stdlib Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#/ + +# Configuration for [npm][1]. +# +# [1]: https://docs.npmjs.com/files/npmrc + +# Disable the creation of a lock file: +package-lock = false +shrinkwrap = false + +# Disable automatically "saving" dependencies on install: +save = false diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..ac5edc43cf --- /dev/null +++ b/Makefile @@ -0,0 +1,75 @@ +#/ +# @license Apache-2.0 +# +# Copyright (c) 2025 The Stdlib Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#/ + +# VARIABLES # + +# Determine the filename: +this_file := $(lastword $(MAKEFILE_LIST)) + +# Determine the absolute path of the Makefile (see http://blog.jgc.org/2007/01/what-makefile-am-i-in.html): +this_dir := $(dir $(CURDIR)/$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))) + +# Remove the trailing slash: +this_dir := $(patsubst %/,%,$(this_dir)) + +# Define the root project directory: +ROOT_DIR ?= $(this_dir) + +# Define the root tools directory: +TOOLS_DIR ?= $(ROOT_DIR)/tools + +# Define the directory containing the entry point for Makefile dependencies: +TOOLS_MAKE_DIR ?= $(TOOLS_DIR)/make + +# Define the subdirectory containing Makefile dependencies: +TOOLS_MAKE_LIB_DIR ?= $(TOOLS_MAKE_DIR)/lib + +# Define the root build directory: +BUILD_DIR ?= $(ROOT_DIR)/public + +# Define the root configuration directory: +CONFIG_DIR ?= $(ROOT_DIR)/etc + +# Define the directory for public WWW assets: +WWW_DIR ?= $(ROOT_DIR)/public + +# Define the top-level directory containing node module dependencies: +NODE_MODULES ?= $(ROOT_DIR)/node_modules + +# Define the top-level directory containing node module executables: +BIN_DIR ?= $(NODE_MODULES)/.bin + +# Define the folder name convention for executables: +BIN_FOLDER ?= bin + +# Define the folder name convention for configuration files: +CONFIG_FOLDER ?= etc + +# Define the folder name convention for build artifacts: +BUILD_FOLDER ?= js + +# Define filename extension conventions (keep in alphabetical order): +CSS_FILENAME_EXT ?= css + +# Define Node paths: +NODE_PATH ?= + + +# DEPENDENCIES # + +include $(TOOLS_MAKE_DIR)/Makefile diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000000..cbd3a299d9 --- /dev/null +++ b/NOTICE @@ -0,0 +1 @@ +Copyright (c) 2016-2025 The Stdlib Authors. diff --git a/etc/README.md b/etc/README.md new file mode 100644 index 0000000000..5f88206948 --- /dev/null +++ b/etc/README.md @@ -0,0 +1,53 @@ + + +# etc + +> Configuration files. + + + +
+ +This directory contains configuration files. + +
+ + + + + +
+ +## Notes + +- Configuration files for external tools and environments should be placed in subdirectories. The subdirectory name should correspond to the name of the tool or environment. + +
+ + + + + + + + diff --git a/etc/esbuild/build.js b/etc/esbuild/build.js new file mode 100644 index 0000000000..d7f40113f9 --- /dev/null +++ b/etc/esbuild/build.js @@ -0,0 +1,46 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var esbuild = require( 'esbuild' ); +var config = require( './config.js' ); + + +// MAIN // + +/** +* Builds the website. +* +* @async +* @throws {Error} build failure +* @returns {Promise} promise which resolves upon building the website +*/ +async function main() { + try { + await esbuild.build( config ); + console.log( 'Build completed successfully!' ); + } catch ( error ) { + console.error( 'Build failed:', error ); + process.exit( 1 ); + } +} + +main(); diff --git a/etc/esbuild/config.js b/etc/esbuild/config.js new file mode 100644 index 0000000000..761b59205c --- /dev/null +++ b/etc/esbuild/config.js @@ -0,0 +1,49 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var resolve = require( 'path' ).resolve; + + +// MAIN // + +var config = { + 'entryPoints': [ + resolve( __dirname, '../../src/index.jsx' ) + ], + 'bundle': true, + 'outfile': resolve( __dirname, '../../public/js/bundle.js' ), + 'minify': true, + 'sourcemap': false, + 'target': 'es2015', + 'jsxFactory': 'h', + 'jsxFragment': 'Fragment', + 'jsxImportSource': 'preact', + 'alias': { + 'react': 'preact/compat', + 'react-dom': 'preact/compat' + } +}; + + +// EXPORTS // + +module.exports = config; diff --git a/lib/server/README.md b/lib/server/README.md new file mode 100644 index 0000000000..4db40e2e03 --- /dev/null +++ b/lib/server/README.md @@ -0,0 +1,23 @@ + + +# Server + +> HTTP server for serving test code coverage. diff --git a/lib/server/lib/defaults.json b/lib/server/lib/defaults.json new file mode 100644 index 0000000000..bce21ac4b8 --- /dev/null +++ b/lib/server/lib/defaults.json @@ -0,0 +1,15 @@ +{ + "address": "127.0.0.1", + "logger": false, + "port": 0, + "prefix": "/", + "root": "", + "static": "", + "trustProxy": false, + "ignoreTrailingSlash": true, + "app": { + "title": "Test Code Coverage | stdlib", + "description": "Test code coverage website for stdlib, a standard library for JavaScript and Node.js, with an emphasis on numerical and scientific computing.", + "theme": "" + } +} diff --git a/lib/server/lib/index.js b/lib/server/lib/index.js new file mode 100644 index 0000000000..2de16f0eae --- /dev/null +++ b/lib/server/lib/index.js @@ -0,0 +1,54 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +/** +* Create an HTTP server for serving test code coverage. +* +* @module @stdlib/www-test-coverage-server +* +* @example +* var httpServer = require( '@stdlib/www-test-coverage-server' ); +* var App = require( 'my-app' ); +* +* var opts = { +* 'port': 7331, +* 'address': '0.0.0.0' +* }; +* var createServer = httpServer( opts ); +* +* function done( error, fastify ) { +* if ( error ) { +* return console.error( error.message ); +* } +* console.log( 'Success!' ); +* fastify.close(); +* } +* +* createServer( App, done ); +*/ + +// MODULES // + +var main = require( './main.js' ); + + +// EXPORTS // + +module.exports = main; diff --git a/lib/server/lib/main.js b/lib/server/lib/main.js new file mode 100644 index 0000000000..bfbe743f77 --- /dev/null +++ b/lib/server/lib/main.js @@ -0,0 +1,222 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var resolve = require( 'path' ).resolve; +var fastify = require( 'fastify' ); +var helmet = require( '@fastify/helmet' ); +var cookie = require( '@fastify/cookie' ); +var fastifyStatic = require( '@fastify/static' ); +var isFunction = require( '@stdlib/assert/is-function' ); +var isString = require( '@stdlib/assert/is-string' ); +var cwd = require( '@stdlib/process/cwd' ); +var copy = require( '@stdlib/utils/copy' ); +var format = require( '@stdlib/string/format' ); +var routes = require( './routes' ); +var DEFAULTS = require( './defaults.json' ); +var validate = require( './validate.js' ); + + +// MAIN // + +/** +* Returns a function which creates an HTTP server for serving test code coverage. +* +* @param {Options} [options] - server options +* @param {string} [options.address="127.0.0.1"] - server address +* @param {string} [options.hostname] - server hostname +* @param {(boolean|string)} [options.logger=false] - either a boolean indicating whether to enable logging or a log level +* @param {NonNegativeInteger} [options.port=0] - server port +* @param {(string|StringArray)} [options.prefix="/"] - URL path prefix(es) used to create a virtual mount path(s) for static directories +* @param {string} [options.root] - root directory +* @param {(string|StringArray)} [options.static] - path of the directory (or directories) containing static files to serve +* @param {boolean} [options.trustProxy=false] - boolean indicating whether to trust `X-forwarded-by` headers when the server is sitting behind a proxy +* @param {boolean} [options.ignoreTrailingSlash=true] - boolean indicating whether to ignore trailing slashes in routes +* @throws {TypeError} must provide valid options +* @throws {Error} must provide valid options +* @returns {Function} function which creates an HTTP server +* +* @example +* var App = require( 'my-app' ); +* +* var opts = { +* 'port': 7331, +* 'address': '0.0.0.0' +* }; +* var createServer = httpServer( opts ); +* +* function done( error, fastify ) { +* if ( error ) { +* return console.error( error.message ); +* } +* console.log( 'Success!' ); +* fastify.close(); +* } +* +* createServer( App, done ); +*/ +function httpServer( options ) { + var opts; + var err; + var dir; + var i; + + opts = copy( DEFAULTS ); + if ( arguments.length ) { + err = validate( opts, options ); + if ( err ) { + throw err; + } + } + if ( opts.hostname === void 0 ) { + opts.hostname = opts.address; + } + dir = cwd(); + opts.root = resolve( dir, opts.root ); + if ( opts.static ) { + if ( isString( opts.static ) ) { + if ( !isString( opts.prefix ) ) { + throw new TypeError( format( 'invalid option. Must provide a string `prefix` when `static` is a string. Option: `%s`.', opts.prefix ) ); + } + opts.static = resolve( dir, opts.static ); + } else { + if ( !isString( opts.prefix ) && opts.prefix.length !== opts.static.length ) { // eslint-disable-line max-len + throw new Error( 'invalid option. Number of prefixes must equal the number of static directories.' ); + } + for ( i = 0; i < opts.static.length; i++ ) { + opts.static[ i ] = resolve( dir, opts.static[ i ] ); + } + } + } + return createServer; + + /** + * Creates an HTTP server. + * + * @private + * @param {Callback} done - function to invoke after creating a server + * @throws {TypeError} first argument must be a function + * @throws {Error} unable to load application template + */ + function createServer( done ) { + var f; + + if ( !isFunction( done ) ) { + throw new TypeError( format( 'invalid argument. First argument must be a function. Value: `%s`.', done ) ); + } + + // Create a fastify instance: + f = fastify( opts ); + + // Set basic security headers: + f.register( helmet, { + 'contentSecurityPolicy': false, + 'crossOriginEmbedderPolicy': false, + 'referrerPolicy': { + 'policy': 'origin' + }, + 'hidePoweredBy': true + }); + + // Add support for reading cookies: + f.register( cookie ); + + // Decorate route handler `this` contexts with additional functionality: + f.decorate( 'rootDir', opts.root ); + + // Decorate each `request` object with a `locals` object for passing along intermediate results within middleware handlers: + f.decorateRequest( 'locals', null ); + + // Register a plugin for serving static files: + f.register( fastifyStatic, { + 'root': opts.root, + 'prefix': opts.prefix, + 'wildcard': true + }); + + // Register routes: + f.register( routes, {} ); + + // Add a hook to perform clean-up tasks when the server is closed: + f.addHook( 'onClose', onClose ); + + // Add a hook to configure the `locals` object for each request: + f.addHook( 'onRequest', onRequest ); + + // Start listening for HTTP requests: + f.listen( opts, onListen ); + + /** + * Callback invoked once a server is listening and ready to accept HTTP requests. + * + * @private + * @param {(Error|null)} error - error object + * @param {string} address - server address + * @returns {void} + */ + function onListen( error, address ) { + if ( error ) { + f.log.error( error.message ); + return done( error ); + } + f.log.info( 'HTTP server initialized. Server is listening for requests on %s.', address ); + done( null, f ); + } + + /** + * Callback invoked upon receiving an HTTP request. + * + * @private + * @param {Object} request - request object + * @param {Object} reply - reply object + * @param {Callback} clbk - callback to invoke once finished + */ + function onRequest( request, reply, clbk ) { + request.locals = {}; + clbk(); + } + + /** + * Callback invoked when a server is closed. + * + * @private + * @param {Object} instance - fastify instance + * @param {Callback} clbk - callback to invoke once finished + */ + function onClose( instance, clbk ) { + setTimeout( onEnd, 0 ); + + /** + * Callback invoked upon performing clean-up tasks. + * + * @private + */ + function onEnd() { + clbk(); + } + } + } +} + + +// EXPORTS // + +module.exports = httpServer; diff --git a/lib/server/lib/node_modules/middleware-sequence/index.js b/lib/server/lib/node_modules/middleware-sequence/index.js new file mode 100644 index 0000000000..467e4ecf88 --- /dev/null +++ b/lib/server/lib/node_modules/middleware-sequence/index.js @@ -0,0 +1,88 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var isFunctionArray = require( '@stdlib/assert/is-function-array' ); +var isFunction = require( '@stdlib/assert/is-function' ); +var format = require( '@stdlib/string/format' ); + + +// MAIN // + +/** +* Returns a reusable middleware function. +* +* @param {FunctionArray} fcns - array of middleware functions +* @param {Callback} errback - callback to invoke upon encountering an error +* @throws {TypeError} first argument must be an array of functions +* @throws {TypeError} second argument must be a function +* @returns {Function} middleware function +*/ +function factory( fcns, errback ) { + if ( !isFunctionArray( fcns ) ) { + throw new TypeError( format( 'invalid argument. First argument must be an array of functions. Value: `%s`.', fcns ) ); + } + if ( !isFunction( errback ) ) { + throw new TypeError( format( 'invalid argument. Second argument must be a function. Value: `%s`.', errback ) ); + } + return middleware; + + /** + * Executes middleware functions in order. + * + * @private + * @param {Object} request - request object + * @param {Object} reply - reply object + * @param {Callback} [done] - callback to invoke upon completion + * @returns {void} + */ + function middleware( request, reply, done ) { + var self = this; // eslint-disable-line no-invalid-this + var idx = -1; + return next(); + + /** + * Executes the next middleware function. + * + * @private + * @param {(Error|null)} error - error object + * @returns {void} + */ + function next() { + // Check for an error... + if ( arguments[ 0 ] ) { + return errback( arguments[ 0 ], request, reply, next ); + } + // Update the counter and check if we have run all functions... + idx += 1; + if ( idx >= fcns.length ) { + return ( done ) ? done() : void 0; + } + // Call the next middleware function: + fcns[ idx ].call( self, request, reply, next ); + } + } +} + + +// EXPORTS // + +module.exports = factory; diff --git a/lib/server/lib/node_modules/middleware/error/index.js b/lib/server/lib/node_modules/middleware/error/index.js new file mode 100644 index 0000000000..306c0d3cbf --- /dev/null +++ b/lib/server/lib/node_modules/middleware/error/index.js @@ -0,0 +1,40 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MAIN // + +/** +* Callback invoked upon encountering an error. +* +* @private +* @param {Object} error - error object +* @param {Object} request - request object +* @param {Object} reply - reply object +* @param {Callback} next - callback to invoke upon completion +* @returns {void} +*/ +function onError( error, request, reply ) { + return reply.status( error.statusCode ).send( error ); +} + + +// EXPORTS // + +module.exports = onError; diff --git a/lib/server/lib/routes/home/home.js b/lib/server/lib/routes/home/home.js new file mode 100644 index 0000000000..9c4ae727fb --- /dev/null +++ b/lib/server/lib/routes/home/home.js @@ -0,0 +1,48 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MAIN // + +/** +* Callback invoked upon receiving an HTTP request. +* +* @private +* @param {Object} request - request object +* @param {Object} reply - reply object +* @returns {void} +*/ +function handler( request, reply ) { + var url; + + url = request.url; + if ( url[ url.length-1 ] !== '/' ) { + url += '/'; + } + request.log.info( 'Resolved URL: %s', request.url ); + + // Send the HTML shell to the client: + reply.type( 'text/html' ); + reply.sendFile( 'index.html' ); +} + + +// EXPORTS // + +module.exports = handler; diff --git a/lib/server/lib/routes/home/index.js b/lib/server/lib/routes/home/index.js new file mode 100644 index 0000000000..f5dba8ad1d --- /dev/null +++ b/lib/server/lib/routes/home/index.js @@ -0,0 +1,43 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var schema = require( './schema.json' ); +var handler = require( './main.js' ); + + +// MAIN // + +/** +* Defines a route handler for returning a shell-app for a landing page. +* +* @private +* @returns {Object} route declaration +*/ +function route() { + schema.handler = handler; + return schema; +} + + +// EXPORTS // + +module.exports = route; diff --git a/lib/server/lib/routes/home/main.js b/lib/server/lib/routes/home/main.js new file mode 100644 index 0000000000..750049b41b --- /dev/null +++ b/lib/server/lib/routes/home/main.js @@ -0,0 +1,51 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var middleware = require( 'middleware-sequence' ); +var onError = require( 'middleware/error' ); +var home = require( './home.js' ); + + +// VARIABLES // + +var steps = [ + home +]; + + +// MAIN // + +/** +* Request handler for processing a request. +* +* @private +* @name handler +* @type {Function} +* @param {Object} request - request object +* @param {Object} reply - reply object +*/ +var handler = middleware( steps, onError ); + + +// EXPORTS // + +module.exports = handler; diff --git a/lib/server/lib/routes/home/schema.json b/lib/server/lib/routes/home/schema.json new file mode 100644 index 0000000000..f9fbed1dc3 --- /dev/null +++ b/lib/server/lib/routes/home/schema.json @@ -0,0 +1,12 @@ +{ + "method": "GET", + "url": "/", + "schema": { + "response": { + "200": { + "type": "string" + } + } + }, + "handler": null +} diff --git a/lib/server/lib/routes/index.js b/lib/server/lib/routes/index.js new file mode 100644 index 0000000000..d75f219d51 --- /dev/null +++ b/lib/server/lib/routes/index.js @@ -0,0 +1,46 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var home = require( './home' ); + + +// MAIN // + +/** +* Registers routes on a Fastify instance. +* +* @private +* @param {Object} fastify - Fastify instance +* @param {Object} options - options +* @param {Function} done - callback to invoke upon registering route handlers +*/ +function register( fastify, options, done ) { + // Landing page route: + fastify.route( home() ); + + done(); +} + + +// EXPORTS // + +module.exports = register; diff --git a/lib/server/lib/validate.js b/lib/server/lib/validate.js new file mode 100644 index 0000000000..95f0de29be --- /dev/null +++ b/lib/server/lib/validate.js @@ -0,0 +1,130 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var hasOwnProp = require( '@stdlib/assert/has-own-property' ); +var isObject = require( '@stdlib/assert/is-plain-object' ); +var isNonNegativeInteger = require( '@stdlib/assert/is-nonnegative-integer' ).isPrimitive; +var isString = require( '@stdlib/assert/is-string' ).isPrimitive; +var isStringArray = require( '@stdlib/assert/is-string-array' ).primitives; +var isBoolean = require( '@stdlib/assert/is-boolean' ).isPrimitive; +var format = require( '@stdlib/string/format' ); + + +// MAIN // + +/** +* Validates function options. +* +* @private +* @param {Object} opts - destination object +* @param {Options} options - function options +* @param {string} [options.address] - server address +* @param {string} [options.hostname] - server hostname +* @param {(boolean|string)} [options.logger] - either boolean indicating whether to enable logging or a log level +* @param {NonNegativeInteger} [options.port] - server port +* @param {(string|StringArray)} [options.prefix] - URL path prefix(es) used to create a virtual mount path(s) for static directories +* @param {string} [options.root] - root directory +* @param {(string|StringArray)} [options.static] - path of the directory (or directories) containing static files to serve +* @param {boolean} [options.trustProxy] - boolean indicating whether to trust `X-forwarded-by` headers when the server is sitting behind a proxy +* @param {boolean} [options.ignoreTrailingSlash] - boolean indicating whether to ignore trailing slashes in routes +* @returns {(Error|null)} error or null +* +* @example +* var options = { +* 'port': 7331, +* 'address': '127.0.0.1' +* }; +* var opts = {}; +* var err = validate( opts, options ); +* if ( err ) { +* throw err; +* } +*/ +function validate( opts, options ) { + if ( !isObject( options ) ) { + return new TypeError( format( 'invalid argument. Options argument must be an object. Value: `%s.`', options ) ); + } + if ( hasOwnProp( options, 'address' ) ) { + opts.address = options.address; + if ( !isString( opts.address ) ) { + return new TypeError( format( 'invalid option. `%s` option must be a string. Option: `%s`.', 'address', opts.address ) ); + } + } + if ( hasOwnProp( options, 'hostname' ) ) { + opts.hostname = options.hostname; + if ( !isString( opts.hostname ) ) { + return new TypeError( format( 'invalid option. `%s` option must be a string. Option: `%s`.', 'hostname', opts.hostname ) ); + } + } + if ( hasOwnProp( options, 'logger' ) ) { + opts.logger = options.logger; + if ( isString( opts.logger ) ) { + opts.logger = { + 'level': opts.logger + }; + } else if ( !isBoolean( opts.logger ) ) { + return new TypeError( format( 'invalid option. `%s` option must be either a boolean or a string. Option: `%s`.', 'logger', opts.logger ) ); + } + } + if ( hasOwnProp( options, 'port' ) ) { + opts.port = options.port; + if ( !isNonNegativeInteger( opts.port ) ) { + return new TypeError( format( 'invalid option. `%s` must be a nonnegative integer. Option: `%s`.', 'port', opts.port ) ); + } + } + if ( hasOwnProp( options, 'prefix' ) ) { + opts.prefix = options.prefix; + if ( !isString( opts.prefix ) && !isStringArray( opts.prefix ) ) { + return new TypeError( format( 'invalid option. `%s` option must be either a string or an array of strings. Option: `%s`.', 'prefix', opts.prefix ) ); + } + } + if ( hasOwnProp( options, 'root' ) ) { + opts.root = options.root; + if ( !isString( opts.root ) ) { + return new TypeError( format( 'invalid option. `%s` option must be a string. Option: `%s`.', 'root', opts.root ) ); + } + } + if ( hasOwnProp( options, 'static' ) ) { + opts.static = options.static; + if ( !isString( opts.static ) && !isStringArray( opts.static ) ) { + return new TypeError( format( 'invalid option. `%s` option must be either a string or an array of strings. Option: `%s`.', 'static', opts.static ) ); + } + } + if ( hasOwnProp( options, 'trustProxy' ) ) { + opts.trustProxy = options.trustProxy; + if ( !isBoolean( opts.trustProxy ) ) { + return new TypeError( format( 'invalid option. `%s` option must be a boolean. Option: `%s`.', 'trustProxy', opts.trustProxy ) ); + } + } + if ( hasOwnProp( options, 'ignoreTrailingSlash' ) ) { + opts.ignoreTrailingSlash = options.ignoreTrailingSlash; + if ( !isBoolean( opts.ignoreTrailingSlash ) ) { + return new TypeError( format( 'invalid option. `%s` option must be a boolean. Option: `%s`.', 'ignoreTrailingSlash', opts.ignoreTrailingSlash ) ); + } + } + return null; +} + + +// EXPORTS // + +module.exports = validate; diff --git a/lib/server/package.json b/lib/server/package.json new file mode 100644 index 0000000000..defef6202e --- /dev/null +++ b/lib/server/package.json @@ -0,0 +1,55 @@ +{ + "private": true, + "name": "@stdlib/www-test-coverage-server", + "version": "0.0.0", + "description": "Create an HTTP server for serving test code coverage.", + "license": "Apache-2.0", + "author": { + "name": "The Stdlib Authors", + "url": "https://github.com/stdlib-js/www-test-code-coverage/graphs/contributors" + }, + "contributors": [ + { + "name": "The Stdlib Authors", + "url": "https://github.com/stdlib-js/www-test-code-coverage/graphs/contributors" + } + ], + "main": "./lib", + "directories": { + "lib": "./lib" + }, + "scripts": {}, + "homepage": "https://github.com/stdlib-js/www-test-code-coverage", + "repository": { + "type": "git", + "url": "git://github.com/stdlib-js/www-test-code-coverage.git" + }, + "bugs": { + "url": "https://github.com/stdlib-js/www-test-code-coverage/issues" + }, + "dependencies": {}, + "devDependencies": {}, + "engines": { + "node": ">=0.10.0", + "npm": ">2.7.0" + }, + "os": [ + "aix", + "darwin", + "freebsd", + "linux", + "macos", + "openbsd", + "sunos", + "win32", + "windows" + ], + "keywords": [ + "stdlib", + "tools", + "tool", + "test", + "server", + "api" + ] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000000..55215639cd --- /dev/null +++ b/package.json @@ -0,0 +1,48 @@ +{ + "private": true, + "name": "www-test-code-coverage", + "version": "0.0.0", + "description": "Website for stdlib project code coverage.", + "main": "", + "license": "Apache-2.0", + "author": { + "name": "The Stdlib Authors", + "url": "https://github.com/stdlib-js/www-test-code-coverage/graphs/contributors" + }, + "contributors": [ + { + "name": "The Stdlib Authors", + "url": "https://github.com/stdlib-js/www-test-code-coverage/graphs/contributors" + } + ], + "scripts": { + "build": "make build", + "clean": "make clean", + "install:prod": "make install", + "start": "NODE_ENV=production node ./server/index.js", + "start-dev": "NODE_ENV=development node ./server/index.js" + }, + "repository": { + "type": "git", + "url": "git://github.com/stdlib-js/www-test-code-coverage.git" + }, + "homepage": "https://github.com/stdlib-js/www-test-code-coverage", + "keywords": [], + "bugs": { + "url": "https://github.com/stdlib-js/www-test-code-coverage/issues" + }, + "dependencies": { + "@fastify/cookie": "^11.0.2", + "@fastify/helmet": "^13.0.1", + "@fastify/static": "^8.0.3", + "@preact/compat": "^18.3.1", + "@stdlib/stdlib": "^0.3.2", + "fastify": "^5.2.1", + "preact": "^10.25.4", + "preact-iso": "^2.8.1" + }, + "devDependencies": { + "esbuild": "^0.24.2", + "clean-css": "^5.3.3" + } +} diff --git a/public/css/main/bundle.json b/public/css/main/bundle.json new file mode 100644 index 0000000000..a4ac5ac68e --- /dev/null +++ b/public/css/main/bundle.json @@ -0,0 +1,3 @@ +[ + "./layout.css" +] diff --git a/public/css/main/bundle.min.css b/public/css/main/bundle.min.css new file mode 100644 index 0000000000..d4fd7bfa55 --- /dev/null +++ b/public/css/main/bundle.min.css @@ -0,0 +1 @@ +*{margin:0;padding:0;box-sizing:border-box} \ No newline at end of file diff --git a/public/css/main/layout.css b/public/css/main/layout.css new file mode 100644 index 0000000000..a7d2e6f140 --- /dev/null +++ b/public/css/main/layout.css @@ -0,0 +1,44 @@ +/* +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +/* +* Layout stylesheet. +* +* 1. General rules. +* 2. Clearfix. +* 3. Positioning. +* 4. Grid. +* 5. Main. +* 6. Lists. +* 7. Definition lists. +* 8. Blockquotes. +* 9. Horizontal rules. +* 10. Tables. +* 11. Code. +* 12. Classes. +*/ + +/* +* General top-level rules. +*/ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000000..fec5b8f4b1 --- /dev/null +++ b/public/index.html @@ -0,0 +1,15 @@ + + + + + + Test Code Coverage | stdlib + + + + + +
+ + + diff --git a/public/js/bundle.js b/public/js/bundle.js new file mode 100644 index 0000000000..742a9e188c --- /dev/null +++ b/public/js/bundle.js @@ -0,0 +1,18 @@ +(()=>{var Ze=Object.defineProperty,Ve=Object.defineProperties;var et=Object.getOwnPropertyDescriptors;var ke=Object.getOwnPropertySymbols;var tt=Object.prototype.hasOwnProperty,nt=Object.prototype.propertyIsEnumerable;var be=(t,e,n)=>e in t?Ze(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,xe=(t,e)=>{for(var n in e||(e={}))tt.call(e,n)&&be(t,n,e[n]);if(ke)for(var n of ke(e))nt.call(e,n)&&be(t,n,e[n]);return t},Ce=(t,e)=>Ve(t,et(e));var _t=(t,e)=>()=>(t&&(e=t(t=0)),e);function R(t,e){for(var n in e)t[n]=e[n];return t}function ie(t){t&&t.parentNode&&t.parentNode.removeChild(t)}function E(t,e,n){var _,o,r,u={};for(r in e)r=="key"?_=e[r]:r=="ref"?o=e[r]:u[r]=e[r];if(arguments.length>2&&(u.children=arguments.length>3?q.call(arguments,2):n),typeof t=="function"&&t.defaultProps!=null)for(r in t.defaultProps)u[r]===void 0&&(u[r]=t.defaultProps[r]);return B(t,u,_,o,null)}function B(t,e,n,_,o){var r={type:t,props:e,key:n,ref:_,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:o==null?++Se:o,__i:-1,__u:0};return o==null&&m.vnode!=null&&m.vnode(r),r}function Y(t){return t.children}function K(t,e){this.props=t,this.context=e}function F(t,e){if(e==null)return t.__?F(t.__,t.__i+1):null;for(var n;ee&&U.sort(te));G.__r=0}function Te(t,e,n,_,o,r,u,c,l,s,p){var i,f,a,v,x,g,h=_&&_.__k||De,d=e.length;for(l=it(n,e,h,l,d),i=0;i0?B(u.type,u.props,u.key,u.ref?u.ref:null,u.__v):u).__=t,u.__b=t.__b+1,c=null,(s=u.__i=ut(u,n,l,i))!==-1&&(i--,(c=n[s])&&(c.__u|=2)),c==null||c.__v===null?(s==-1&&f--,typeof u.type!="function"&&(u.__u|=4)):s!=l&&(s==l-1?f--:s==l+1?f++:(s>l?f--:f++,u.__u|=4))):t.__k[r]=null;if(i)for(r=0;r(l!=null&&!(2&l.__u)?1:0))for(o=n-1,r=n+1;o>=0||r=0){if((l=e[o])&&!(2&l.__u)&&u==l.key&&c===l.type)return o;o--}if(r2&&(c.children=arguments.length>3?q.call(arguments,2):n),B(t.type,c,_||t.key,o||t.ref,null)}function fe(t,e){var n={__c:e="__cC"+Le++,__:t,Consumer:function(_,o){return _.children(o)},Provider:function(_){var o,r;return this.getChildContext||(o=new Set,(r={})[e]=this,this.getChildContext=function(){return r},this.componentWillUnmount=function(){o=null},this.shouldComponentUpdate=function(u){this.props.value!==u.value&&o.forEach(function(c){c.__e=!0,re(c)})},this.sub=function(u){o.add(u);var c=u.componentWillUnmount;u.componentWillUnmount=function(){o&&o.delete(u),c&&c.call(u)}}),_.children}};return n.Provider.__=n.Consumer.contextType=n}var q,m,Se,rt,U,Ee,He,te,Re,oe,ne,_e,Le,N,De,ot,J,T=_t(()=>{N={},De=[],ot=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,J=Array.isArray;q=De.slice,m={__e:function(t,e,n,_){for(var o,r,u;e=e.__;)if((o=e.__c)&&!o.__)try{if((r=o.constructor)&&r.getDerivedStateFromError!=null&&(o.setState(r.getDerivedStateFromError(t)),u=o.__d),o.componentDidCatch!=null&&(o.componentDidCatch(t,_||{}),u=o.__d),u)return o.__E=o}catch(c){t=c}throw t}},Se=0,rt=function(t){return t!=null&&t.constructor==null},K.prototype.setState=function(t,e){var n;n=this.__s!=null&&this.__s!==this.state?this.__s:this.__s=R({},this.state),typeof t=="function"&&(t=t(R({},n),this.props)),t&&R(n,t),t!=null&&this.__v&&(e&&this._sb.push(e),re(this))},K.prototype.forceUpdate=function(t){this.__v&&(this.__e=!0,t&&this.__h.push(t),re(this))},K.prototype.render=Y,U=[],He=typeof Promise=="function"?Promise.prototype.then.bind(Promise.resolve()):setTimeout,te=function(t,e){return t.__v.__b-e.__v.__b},G.__r=0,Re=/(PointerCapture)$|Capture$/i,oe=0,ne=we(!1),_e=we(!0),Le=0});T();T();T();T();var I,y,pe,Ie,he=0,je=[],b=m,We=b.__b,Be=b.__r,Ne=b.diffed,qe=b.__c,$e=b.unmount,Oe=b.__;function X(t,e){b.__h&&b.__h(y,t,he||e),he=0;var n=y.__H||(y.__H={__:[],__h:[]});return t>=n.__.length&&n.__.push({}),n.__[t]}function me(t,e,n){var _=X(I++,2);if(_.t=t,!_.__c&&(_.__=[n?n(e):ft(void 0,e),function(c){var l=_.__N?_.__N[0]:_.__[0],s=_.t(l,c);l!==s&&(_.__N=[s,_.__[1]],_.__c.setState({}))}],_.__c=y,!y.u)){var o=function(c,l,s){if(!_.__c.__H)return!0;var p=_.__c.__H.__.filter(function(f){return!!f.__c});if(p.every(function(f){return!f.__N}))return!r||r.call(this,c,l,s);var i=_.__c.props!==c;return p.forEach(function(f){if(f.__N){var a=f.__[0];f.__=f.__N,f.__N=void 0,a!==f.__[0]&&(i=!0)}}),r&&r.call(this,c,l,s)||i};y.u=!0;var r=y.shouldComponentUpdate,u=y.componentWillUpdate;y.componentWillUpdate=function(c,l,s){if(this.__e){var p=r;r=void 0,o(c,l,s),r=p}u&&u.call(this,c,l,s)},y.shouldComponentUpdate=o}return _.__N||_.__}function ve(t,e){var n=X(I++,4);!b.__s&&Ke(n.__H,e)&&(n.__=t,n.i=e,y.__h.push(n))}function w(t){return he=5,Z(function(){return{current:t}},[])}function Z(t,e){var n=X(I++,7);return Ke(n.__H,e)&&(n.__=t(),n.__H=e,n.__h=t),n.__}function ye(t){var e=y.context[t.__c],n=X(I++,9);return n.c=t,e?(n.__==null&&(n.__=!0,e.sub(y)),e.props.value):t.__}function lt(){for(var t;t=je.shift();)if(t.__P&&t.__H)try{t.__H.__h.forEach(Q),t.__H.__h.forEach(de),t.__H.__h=[]}catch(e){t.__H.__h=[],b.__e(e,t.__v)}}b.__b=function(t){y=null,We&&We(t)},b.__=function(t,e){t&&e.__k&&e.__k.__m&&(t.__m=e.__k.__m),Oe&&Oe(t,e)},b.__r=function(t){Be&&Be(t),I=0;var e=(y=t.__c).__H;e&&(pe===y?(e.__h=[],y.__h=[],e.__.forEach(function(n){n.__N&&(n.__=n.__N),n.i=n.__N=void 0})):(e.__h.forEach(Q),e.__h.forEach(de),e.__h=[],I=0)),pe=y},b.diffed=function(t){Ne&&Ne(t);var e=t.__c;e&&e.__H&&(e.__H.__h.length&&(je.push(e)!==1&&Ie===b.requestAnimationFrame||((Ie=b.requestAnimationFrame)||at)(lt)),e.__H.__.forEach(function(n){n.i&&(n.__H=n.i),n.i=void 0})),pe=y=null},b.__c=function(t,e){e.some(function(n){try{n.__h.forEach(Q),n.__h=n.__h.filter(function(_){return!_.__||de(_)})}catch(_){e.some(function(o){o.__h&&(o.__h=[])}),e=[],b.__e(_,n.__v)}}),qe&&qe(t,e)},b.unmount=function(t){$e&&$e(t);var e,n=t.__c;n&&n.__H&&(n.__H.__.forEach(function(_){try{Q(_)}catch(o){e=o}}),n.__H=void 0,e&&b.__e(e,n.__v))};var ze=typeof requestAnimationFrame=="function";function at(t){var e,n=function(){clearTimeout(_),ze&&cancelAnimationFrame(e),setTimeout(t)},_=setTimeout(n,100);ze&&(e=requestAnimationFrame(n))}function Q(t){var e=y,n=t.__c;typeof n=="function"&&(t.__c=void 0,n()),y=e}function de(t){var e=y;t.__c=t.__(),y=e}function Ke(t,e){return!t||t.length!==e.length||e.some(function(n,_){return n!==t[_]})}function ft(t,e){return typeof e=="function"?e(t):e}var A,$,pt=(t,e)=>{if(A=void 0,e&&e.type==="click"){if(e.ctrlKey||e.metaKey||e.altKey||e.shiftKey||e.button!==0)return t;let n=e.target.closest("a[href]"),_=n&&n.getAttribute("href");if(!n||n.origin!=location.origin||/^#/.test(_)||!/^(_?self)?$/i.test(n.target)||$&&(typeof $=="string"?!_.startsWith($):!$.test(_)))return t;A=!0,e.preventDefault(),e=n.href.replace(location.origin,"")}else typeof e=="string"?A=!0:e&&e.url?(A=!e.replace,e=e.url):e=location.pathname+location.search;return A===!0?history.pushState(null,"",e):A===!1&&history.replaceState(null,"",e),e},ht=(t,e,n)=>{t=t.split("/").filter(Boolean),e=(e||"").split("/").filter(Boolean);for(let _=0,o,r;_{let r=new URL(e,location.origin),u=r.pathname.replace(/\/+$/g,"")||"/";return{url:e,path:u,query:Object.fromEntries(r.searchParams),route:(c,l)=>n({url:c,replace:l}),wasPush:_}},[e]);return ve(()=>(addEventListener("click",n),addEventListener("popstate",n),()=>{removeEventListener("click",n),removeEventListener("popstate",n)}),[]),E(O.ctx.Provider,{value:o},t.children)}var dt=Promise.resolve();function Je(t){let[e,n]=me(k=>k+1,0),{url:_,query:o,wasPush:r,path:u}=Ye(),{rest:c=u,params:l={}}=ye(Ge),s=w(!1),p=w(u),i=w(0),f=w(),a=w(),v=w(),x=w(!1),g=w();g.current=!1;let h=w(!1),d,L,C;ue(t.children).some(k=>{if(ht(c,k.props.path,C=Ce(xe({},k.props),{path:c,query:o,params:l,rest:""})))return d=ae(k,C);k.props.default&&(L=ae(k,C))});let S=d||L;Z(()=>{a.current=f.current;let k=a.current&&a.current.props.children;!k||!S||S.type!==k.type||S.props.component!==k.props.component?(this.__v&&this.__v.__k&&this.__v.__k.reverse(),i.current++,h.current=!0):h.current=!1},[_]);let D=f.current&&f.current.__u&V&&f.current.__u&ee,z=f.current&&f.current.__h;f.current=E(Ge.Provider,{value:C},S),D?(f.current.__u|=V,f.current.__u|=ee):z&&(f.current.__h=!0);let M=a.current;return a.current=null,this.__c=(k,H)=>{g.current=!0,a.current=M,t.onLoadStart&&t.onLoadStart(_),s.current=!0;let P=i.current;k.then(()=>{P===i.current&&(a.current=null,f.current&&(H.__h&&(f.current.__h=H.__h),H.__u&ee&&(f.current.__u|=ee),H.__u&V&&(f.current.__u|=V)),dt.then(n))})},ve(()=>{let k=this.__v&&this.__v.__e;if(g.current){!x.current&&!v.current&&(v.current=k);return}!x.current&&v.current&&(v.current!==k&&v.current.remove(),v.current=null),x.current=!0,p.current!==u&&(r&&scrollTo(0,0),t.onRouteChange&&t.onRouteChange(_),p.current=u),t.onLoadEnd&&s.current&&t.onLoadEnd(_),s.current=!1},[u,r,e]),h.current?[E(ge,{r:f}),E(ge,{r:a})]:E(ge,{r:f})}var V=32,ee=128,ge=({r:t})=>t.current;Je.Provider=O;O.ctx=fe({});var Ge=fe({});var Ye=()=>ye(O.ctx);T();var Qe=m.__e;m.__e=(t,e,n)=>{if(t&&t.then){let _=e;for(;_=_.__;)if(_.__c&&_.__c.__c)return e.__e==null&&(e.__e=n.__e,e.__k=n.__k),e.__k||(e.__k=[]),_.__c.__c(t,e)}Qe&&Qe(t,e,n)};T();function mt(){return E("div",null,E("h1",null,"Lorem ipsum dolor sit amet, consectetur adipisicing elit."))}var Xe=mt;le(E(Xe,null),document.getElementById("root"));})(); +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000000..c260a1baa1 --- /dev/null +++ b/server/index.js @@ -0,0 +1,21 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +// MAIN // + +require( './server.js' ); \ No newline at end of file diff --git a/server/server.js b/server/server.js new file mode 100644 index 0000000000..4ab4e130e9 --- /dev/null +++ b/server/server.js @@ -0,0 +1,67 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var path = require( 'path' ); +var httpServer = require( './../lib/server' ); + + +// VARIABLES // + +var PUBLIC_DIR = path.resolve( __dirname, '..', 'public' ); +var PORT = 3000; + + +// FUNCTIONS // + +/** +* Callback invoked upon starting a server. +* +* @private +* @param {(Error|null)} error - error object +* @param {Object} fastify - fastify instance +*/ +function done( error ) { + if ( error ) { + throw error; + } + console.log( 'Server is running...' ); +} + + +// MAIN // + +/** +* Main execution sequence. +* +* @private +*/ +function main() { + var opts = { + 'logger': 'info', + 'port': PORT, + 'prefix': '/', + 'root': PUBLIC_DIR + }; + httpServer( opts )( done ); +} + +main(); diff --git a/src/app.jsx b/src/app.jsx new file mode 100644 index 0000000000..472eae3035 --- /dev/null +++ b/src/app.jsx @@ -0,0 +1,45 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +import { h } from 'preact' +import { Router } from 'preact-iso' + + +// MAIN // + +/** +* Main application component. +* +* @returns {PreactComponent} preact component +*/ +function App() { + return( +
+

Lorem ipsum dolor sit amet, consectetur adipisicing elit.

+
+ ) +} + + +// EXPORTS // + +export default App; diff --git a/src/index.jsx b/src/index.jsx new file mode 100644 index 0000000000..ba3acf112a --- /dev/null +++ b/src/index.jsx @@ -0,0 +1,29 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +import { render } from 'preact'; +import App from './app.jsx'; + + +// MAIN // + +render( , document.getElementById( 'root' ) ); diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 0000000000..1dfae75f62 --- /dev/null +++ b/tools/README.md @@ -0,0 +1,49 @@ + + +# Tools + +> Development tools. + + + +
+ +This directory contains development utilities for building and managing the website. + +
+ + + + + +
+ +
+ + + + + + + + diff --git a/tools/make/Makefile b/tools/make/Makefile new file mode 100644 index 0000000000..005d80944c --- /dev/null +++ b/tools/make/Makefile @@ -0,0 +1,65 @@ +#/ +# @license Apache-2.0 +# +# Copyright (c) 2025 The Stdlib Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#/ + +# DEPENDENCIES # + +# Order is important: +include $(TOOLS_MAKE_DIR)/common.mk +include $(TOOLS_MAKE_LIB_DIR)/help/Makefile + +# Please keep sorted in alphabetical order: +include $(TOOLS_MAKE_LIB_DIR)/css/Makefile +include $(TOOLS_MAKE_LIB_DIR)/install/Makefile +include $(TOOLS_MAKE_LIB_DIR)/js/Makefile + + +# RULES # + +#/ +# Default target. +# +# @example +# make +# +# @example +# make all +#/ +all: help + +.PHONY: all + +#/ +# Runs the project's cleanup sequence. +# +# @example +# make clean +#/ +clean: clean-node clean-js-bundle + $(QUIET) $(DELETE) $(DELETE_FLAGS) + +.PHONY: clean + +#/ +# Runs the project's build sequence. +# +# @example +# make build +#/ +build: $(NODE_MODULES) build-js css-minify + +.PHONY: build diff --git a/tools/make/README.md b/tools/make/README.md new file mode 100644 index 0000000000..b0fa75f1c0 --- /dev/null +++ b/tools/make/README.md @@ -0,0 +1,43 @@ + + +# Makefile + +> Development utility. + +This project uses [`make`][make] as its development utility. For an overview of `make`, see the `make` [manual][make]. + +## Usage + +#### Help + +To view a list of available `Makefile` targets, + +```bash +$ make help +``` + + + + diff --git a/tools/make/common.mk b/tools/make/common.mk new file mode 100644 index 0000000000..95b0c839e6 --- /dev/null +++ b/tools/make/common.mk @@ -0,0 +1,127 @@ +#/ +# @license Apache-2.0 +# +# Copyright (c) 2025 The Stdlib Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#/ + +# VERBOSITY # + +ifndef VERBOSE + QUIET := @ +endif + + +# ENVIRONMENTS # + +# Determine the OS: +# +# [1]: https://en.wikipedia.org/wiki/Uname#Examples +# [2]: http://stackoverflow.com/a/27776822/2225624 +OS ?= $(shell uname) +ifneq (, $(findstring MINGW,$(OS))) + OS := WINNT +else +ifneq (, $(findstring MSYS,$(OS))) + OS := WINNT +else +ifneq (, $(findstring CYGWIN,$(OS))) + OS := WINNT +else +ifneq (, $(findstring Windows_NT,$(OS))) + OS := WINNT +endif +endif +endif +endif + +# Define whether the make commands are running on a hosted continuous integration service: +TRAVIS ?= +APPVEYOR ?= +CIRCLECI ?= +GITHUB ?= +ifeq ($(TRAVIS), true) + CI_SERVICE ?= travis +else +ifeq ($(APPVEYOR), true) + CI_SERVICE ?= appveyor +else +ifeq ($(CIRCLECI), true) + CI_SERVICE ?= circle +else +ifeq ($(GITHUB), true) + CI_SERVICE ?= github +else + CI_SERVICE ?= none +endif +endif +endif +endif + + +# COMMANDS # + +# Define whether delete operations should be safe (i.e., deleted items are sent to trash, rather than permanently deleted): +SAFE_DELETE ?= false + +# Define the delete command: +ifeq ($(SAFE_DELETE), true) + # FIXME: -rm -rf + DELETE := -rm + DELETE_FLAGS := -rf +else + DELETE ?= -rm + DELETE_FLAGS ?= -rf +endif + +# Define the command for setting executable permissions: +MAKE_EXECUTABLE ?= chmod +x + +# Define the command for recursively creating directories (WARNING: portability issues on some systems!): +MKDIR_RECURSIVE ?= mkdir -p + +# Define the command for extracting tarfiles: +TAR ?= tar + +# Define the command to `cat` a file: +CAT ?= cat + +# Define the command to copy files: +CP ?= cp + +# Define the command to recursively sync directories: +RSYNC_RECURSIVE ?= rsync -r + +# Define the `git` command: +GIT ?= git + +# Define the command for staging files: +GIT_ADD ?= $(GIT) add + +# Define the command for committing files: +GIT_COMMIT ?= $(GIT) commit + +# Determine the `open` command: +ifeq ($(OS), Darwin) + OPEN ?= open +else + OPEN ?= xdg-open +endif +# TODO: add Windows command + +# Define the command for `node`: +NODE ?= node + +# Define the command for `npm`: +NPM ?= npm diff --git a/tools/make/lib/css/Makefile b/tools/make/lib/css/Makefile new file mode 100644 index 0000000000..c2b9cb20cf --- /dev/null +++ b/tools/make/lib/css/Makefile @@ -0,0 +1,36 @@ +#/ +# @license Apache-2.0 +# +# Copyright (c) 2025 The Stdlib Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#/ + +# VARIABLES # + +# Define the path to the script for generating minified CSS bundles: +css_minifier ?= $(TOOLS_DIR)/scripts/minify_css.js + + +# RULES # + +#/ +# Minifies CSS source files. +# +# @example +# make css-minify +#/ +css-minify: $(NODE_MODULES) $(css_minifier) + $(QUIET) NODE_PATH="$(NODE_PATH)" $(NODE) "$(css_minifier)" + +.PHONY: css-minify diff --git a/tools/make/lib/css/README.md b/tools/make/lib/css/README.md new file mode 100644 index 0000000000..ce14bd217b --- /dev/null +++ b/tools/make/lib/css/README.md @@ -0,0 +1,51 @@ + + +# CSS + +> CSS recipes. + + + +
+ +This directory contains [`make`][make] recipes for managing CSS files. + +
+ + + + + +
+ +
+ + + + + + + + diff --git a/tools/make/lib/help/Makefile b/tools/make/lib/help/Makefile new file mode 100644 index 0000000000..e06e026275 --- /dev/null +++ b/tools/make/lib/help/Makefile @@ -0,0 +1,60 @@ +#/ +# @license Apache-2.0 +# +# Copyright (c) 2025 The Stdlib Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#/ + +# VARIABLES # + +# Define the path to the Makefile usage text file for displaying help information: +MAKE_USAGE ?= $(TOOLS_MAKE_DIR)/usage.txt + +# Define command flags: +FIND_MAKEFILES_FLAGS ?= \ + -name 'Makefile' \ + -o \ + -name '*.mk' + + +# RULES # + +#/ +# Prints a help message and lists available targets. +# +# @example +# make help +#/ +help: + $(QUIET) $(CAT) $(MAKE_USAGE) + +.PHONY: help + +#/ +# Lists all Makefile targets. +# +# ## Notes +# +# - The list of targets is NOT exhaustive, as it does not include targets which have not been explicitly declared PHONY targets and does not include targets declared via variables. These targets could be included by dumping the Makefile database `make -qp`, but not seen as necessary due to predominant use of PHONY. +# +# @example +# make list-targets +#/ +list-targets: + $(QUIET) find $(TOOLS_MAKE_DIR) $(FIND_MAKEFILES_FLAGS) \ + | xargs grep '^.PHONY: ' \ + | awk '{print $$2}' \ + | sort + +.PHONY: list-targets diff --git a/tools/make/lib/help/README.md b/tools/make/lib/help/README.md new file mode 100644 index 0000000000..f272ba0650 --- /dev/null +++ b/tools/make/lib/help/README.md @@ -0,0 +1,51 @@ + + +# Help + +> Help recipes. + + + +
+ +This directory contains [`make`][make] recipes for printing help information when using [`make`][make], such as the project [`make`][make] usage text, targets, and more. + +
+ + + + + +
+ +
+ + + + + + + + diff --git a/tools/make/lib/install/Makefile b/tools/make/lib/install/Makefile new file mode 100644 index 0000000000..cd8ceda0a3 --- /dev/null +++ b/tools/make/lib/install/Makefile @@ -0,0 +1,34 @@ +#/ +# @license Apache-2.0 +# +# Copyright (c) 2025 The Stdlib Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#/ + +# DEPENDENCIES # + +include $(TOOLS_MAKE_LIB_DIR)/install/node.mk + + +# RULES # + +#/ +# Runs the project's install process. +# +# @example +# make install +#/ +install: install-node + +.PHONY: install diff --git a/tools/make/lib/install/README.md b/tools/make/lib/install/README.md new file mode 100644 index 0000000000..3088c575fb --- /dev/null +++ b/tools/make/lib/install/README.md @@ -0,0 +1,51 @@ + + +# Install + +> Install recipes. + + + +
+ +This directory contains [`make`][make] recipes for running the project's installation process. + +
+ + + + + +
+ +
+ + + + + + + + diff --git a/tools/make/lib/install/node.mk b/tools/make/lib/install/node.mk new file mode 100644 index 0000000000..0aecf81470 --- /dev/null +++ b/tools/make/lib/install/node.mk @@ -0,0 +1,53 @@ +#/ +# @license Apache-2.0 +# +# Copyright (c) 2025 The Stdlib Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#/ + +# VARIABLES # + +# Define the path to the root `package.json`: +ROOT_PACKAGE_JSON ?= $(ROOT_DIR)/package.json + + +# VARIABLES # + +#/ +# Installs package dependencies by executing [`npm install`][1]. +# +# ## Notes +# +# - Packages will be installed in a local `node_modules` directory relative to the project's `package.json` file. +# +# [1]: https://docs.npmjs.com/cli/install +# +# @example +# make install-node +#/ +install-node: $(ROOT_PACKAGE_JSON) + $(QUIET) $(NPM) install + +.PHONY: install-node + +#/ +# Cleans the `node_modules` directory by removing it entirely. +# +# @example +# make clean-node +#/ +clean-node: + $(QUIET) $(DELETE) $(DELETE_FLAGS) $(NODE_MODULES) + +.PHONY: clean-node diff --git a/tools/make/lib/js/Makefile b/tools/make/lib/js/Makefile new file mode 100644 index 0000000000..f8a6c428c0 --- /dev/null +++ b/tools/make/lib/js/Makefile @@ -0,0 +1,43 @@ +#/ +# @license Apache-2.0 +# +# Copyright (c) 2025 The Stdlib Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#/ + + +# RULES # + +#/ +# Builds JavaScript. +# +# @example +# make build-js +#/ +build-js: $(NODE_MODULES) + $(QUIET) NODE_PATH="$(NODE_PATH)" $(NODE) "$(ROOT_DIR)/etc/esbuild/build.js" + +.PHONY: build-js + +#/ +# Removes JavaScript bundle. +# +# @example +# make clean-js +#/ +clean-js-bundle: + $(QUIET) $(DELETE) $(BUILD_DIR)/js/bundle.js + +.PHONY: clean-js-bundle + diff --git a/tools/make/usage.txt b/tools/make/usage.txt new file mode 100644 index 0000000000..aecad7e54a --- /dev/null +++ b/tools/make/usage.txt @@ -0,0 +1,11 @@ + +Usage: make + + make help Print this message. + make notes Search for code annotations. + make list-files List files. + make install Run install sequence. + make clean Run all cleanup tasks. + make clean-node Remove Node dependencies. + make inspect.VARIABLE Print the runtime value of a VARIABLE. + make assert.VARIABLE Assert that a VARIABLE is set. diff --git a/tools/scripts/README.md b/tools/scripts/README.md new file mode 100644 index 0000000000..2d81c25553 --- /dev/null +++ b/tools/scripts/README.md @@ -0,0 +1,49 @@ + + +# Scripts + +> Generic scripts. + + + +
+ +This directory contains generic project scripts. + +
+ + + + + +
+ +
+ + + + + + + + \ No newline at end of file diff --git a/tools/scripts/minify_css.js b/tools/scripts/minify_css.js new file mode 100644 index 0000000000..c851389b0e --- /dev/null +++ b/tools/scripts/minify_css.js @@ -0,0 +1,126 @@ +#!/usr/bin/env node + +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var fs = require( 'fs' ); +var path = require( 'path' ); +var CleanCSS = require( 'clean-css' ); + + +// VARIABLES // + +// Parent directory containing source CSS files: +var dirpath = path.join( __dirname, '../../public/css' ); + +// Child directories containing source CSS files to bundle and minify: +var dirs = [ + path.join( dirpath, 'main' ) +]; + +// CSS processor options: +var copts = { + 'returnPromise': false, + 'compatibility': 'ie9', + 'level': 1 +}; + + +// FUNCTIONS // + +/** +* Synchronously reads a list of CSS files. +* +* @private +* @param {Array} list - list of files +* @returns {Array} array of file contents +*/ +function readFiles( list ) { + var opts; + var out; + var i; + + opts = { + 'encoding': 'utf8' + }; + out = []; + for ( i = 0; i < list.length; i++ ) { + out.push( fs.readFileSync( list[ i ], opts ) ); + } + return out; +} + + +// MAIN // + +/** +* Main execution sequence. +* +* @private +*/ +function main() { + var minifier; + var fpath; + var fopts; + var tmp; + var i; + var j; + + minifier = new CleanCSS( copts ); + fopts = { + 'encoding': 'utf8' + }; + + // Process each CSS directory... + for ( i = 0; i < dirs.length; i++ ) { + // Read the bundle list: + tmp = require( path.join( dirs[ i ], 'bundle.json' ) ); + + // Resolve each file in the list to an absolute path: + for ( j = 0; j < tmp.length; j++ ) { + tmp[ j ] = path.resolve( dirs[ i ], tmp[ j ] ); + } + // Read each CSS file: + tmp = readFiles( tmp ); + + // Concatenate file contents into a single string: + tmp = tmp.join( '\n' ); + + // Minify the CSS: + tmp = minifier.minify( tmp ); + if ( tmp.errors.length ) { + console.error( 'Directory: %s\n', dirs[ i ] ); + console.error( 'Errors:\n' ); + console.error( tmp.errors.join( '\n' ) ); + } + if ( tmp.warnings.length ) { + console.error( 'Directory: %s\n', dirs[ i ] ); + console.error( 'Warnings:\n' ); + console.error( tmp.warnings.join( '\n' ) ); + } + // Write the minified CSS to file: + fpath = path.join( dirs[ i ], 'bundle.min.css' ); + fs.writeFileSync( fpath, tmp.styles, fopts ); + } +} + +main();