squared-express
v3.3.7
Published
Express server for use with squared.
Downloads
310
Maintainers
Readme
squared-express 3.3
General Usage
NPX
> npm i squared-express
> cp node_modules/squared-express/config/json/* .
> squared.[json|yml|cjs] # configure
> npx serve [--help]
> http://localhost:3000
Local Development
# NOTE: cd ./dist
> squared.[json|yml|cjs] # configure
> node serve.js [--help]
# OR - Unix
> chmod +x serve.js # once
> ./serve.js [--help]
[!NOTE] The file extension ".cjs" is required with projects using
ES
modules.
Typically you will be using squared 5
although it can be used independently for typical server configuration or debugging purposes. It is recommended to copy the latest release into your squared project folder when there are patches.
Version Compatibility
* v1.0.0 - 04-29-21 - squared-functions 1.0 (squared 3.0 + NodeJS 10)
* v1.1.0 - 06-16-21 - squared-functions 1.1 (squared 3.1)
* v1.2.0 - 08-03-21 - squared-functions 1.2
* v1.3.0 - 09-15-21 - squared-functions 2.0 (squared 3.2)
* v1.4.0 - 10-18-21 - squared-functions 3.0 (squared 3.3)
* v1.5.0 - 12-07-21 - squared-functions 3.1
* v1.7.0 - 12-23-21 - squared-functions 3.2 (squared 3.4)
* v1.8.0 - 01-07-22 - squared-functions 3.3 (squared 3.5)
* v1.9.0 - 02-03-22 - squared-functions 3.5
* v2.0.0 - 02-22-22 - squared-functions 4.0 (squared 3.6 + NodeJS 12)
* v2.1.0 - 04-29-22 - squared-functions 4.1 (squared 3.7)
* v2.2.0 - 06-16-22 - squared-functions 4.2 (squared 4.0)
* v2.3.0 - 08-06-22 - squared-functions 4.3 (squared 4.1)
* v2.4.0 - 09-21-22 - squared-functions 4.4 (squared 4.2)
* v2.5.0 - 10-02-22 - squared-functions 4.5 (squared 4.2.2)
* v2.6.0 - 10-13-22 - squared-functions 4.6 (squared 4.2.3)
* v2.7.0 - 10-28-22 - squared-functions 4.7 (squared 4.3)
* v2.8.0 - 11-05-22 - squared-functions 4.8 (squared 4.3.1)
* v2.9.0 - 12-31-22 - squared-functions 4.9 (squared 4.4)
* v2.9.5 - 02-13-23 - squared-functions 4.9.5 (squared 4.5)
* v3.0.0 - 04-29-23 - E-mc 0.5.2 (squared 5.0 + NodeJS 14)
* v3.1.0 - 08-09-23 - E-mc 0.6.0 (squared 5.1)
* v3.1.1 - 12-07-23 - E-mc 0.7.0 (squared 5.1.1)
* v3.1.2 - 12-23-23 - E-mc 0.8.0 (squared 5.1.2)
* v3.2.0 - 04-29-24 - E-mc 0.9.0 (squared 5.2.0 + NodeJS 14/16)
* v3.3.0 - 08-06-24 - E-mc 0.10.0 (squared 5.3.0)
[!NOTE] Using squared-express with an older version of
E-mc
is not recommended.
Request Options
// All attributes are optional
const data = {
// Archive
filename: "archive1",
format: "zip", // zip | tar | gz/tgz + [ 7z | wbn | zopfli(gz) ]
copyTo: "/path/project", // zip from directory
// Copy
emptyDir: false, // Empty target directory
watch: false, // Enable file watching
update: { // Reload assets and data sources
interval: 10 * 60, // 10m (by second) (required)
interval: "1d",
id: "111-111-111" | location.href, // Overwrite previous request with same ID (optional)
start: "Jan 01 2023 12:00:00" | "1w 1d 1h 1m 1s 1ms", // Empty is immediate (optional)
expires: "Jan 01 2023 12:00:00" | "1w 1d 1h 1m 1s 1ms" // Empty is never (optional)
},
update: true, // Uses URL and glob values in "node.tasks.copy:update"
incremental: false, // Explicit "false" to disable
incremental: "exists", // Will bypass files already located at destination
incremental: "etag", // Same as "exists" except HTTP downloads will verify ETag
incrementalMap: {
pathname: {
"images/": "exists", // Not recursive
"js/**/*": "etag" // Glob is supported
},
extension: {
"js": "etag",
"mjs": false
},
mime: {
"image/png": "exists", // First match will quit search
"image/*": "etag"
},
overwrite: false // Only when undefined
},
checksum: true, // sha256 + recursive
checksum: "sha512", // checksum.sha512
checksum: "filename.sha384", // sha384
checksum: {
algorithm: "md5", // Default is "sha256"
digest: "base64", // Default is "hex"
filename: "checksum.crc", // Default is "checksum" + algorithm
recursive: true, // Default is "false"
recursive: 1, // Ignore nested checksum files
include: "**/*.png", // Has precedence
exclude: ["**/*.js", "**/*.css"]
},
assets: [ // Undetected resources used in the application (e.g. imports inside custom elements or SVG)
{
pathname: "app/src/main/res/drawable",
filename: "ic_launcher_background.xml",
uri: "http://localhost:3000/common/images/ic_launcher_background.xml"
}
],
filter: (asset, index) => asset.mimeType === "text/html" || asset.filename.endsWith(".xml"), // Include only "text/html" and XML files
exclusions: {
glob: ["**/*.zip"],
pathname: ["app/build", "app/libs"],
filename: ["ic_launcher_foreground.xml"],
extension: ["iml", "pro"], // Not case-sensitive
pattern: ["output", /grad.+?\./i, "\\.git"]
},
exclusions: ["**/*.zip", /\.zip$/], // glob + pattern (uses glob for strings)
modules: ["db", "cloud"], // Attempt to install undetected modules
config: {
uri: "http://localhost:3000/example.yml" // Auto-detect "yml" extension
},
config: {
mimeType: "json" // http://hostname/example.html -> http://hostname/example.html.json
},
config: {
uri: "http://hostname/example.config",
mimeType: "text/javascript", // json
encoding: "utf-8", // Optional
cache: true // Use when config file is unchanged
},
config: {
uri: "/path/to/example.config", // Will always check user "host" permission (local path)
mimeType: "application/jwt", // squared.auth(...) (required)
mimeType: "application/jwt; text/yaml",
document: "chrome" // Permission to read module directories (optional)
},
config: "http://hostname/example.yml",
config: "json", // json | yml | yaml
config: true, // Uses sqd.config in base directory
config: {
uri: true, // sqd.config
inherit: true // Uses glob matching for multiple blocks
},
config: {
uri: "http://hostname/example.json",
key: "111-111-111" // Key inside map
},
config: { // v3.3
inherit: {
append: true, // Arrays are concatenated
preserve: true, // Every object property in nested objects are merged
depth: 1 // Nested levels of arrays used with "append"
},
dataSource: [{
source: "redis", // Cloud is supported
uri: "redis://redis-6379.redis-cloud.com:6379",
username: "demo",
password: "********",
key: "config:1",
format: "JSON"
},
{
source: "redis", // Can be from any DB provider
uri: "redis://redis-6379.redis-cloud.com:6379",
username: "demo",
password: "********",
key: "config:2",
format: "JSON"
}]
},
// Settings overrides
cache: {
request: true | ["http://localhost:3000"] // true & exclude (request.cache)
},
error: { // error.abort
abort: ["filemanager", "watch", "cloud", "jimp", "gulp", "android", "chrome"], // By module name (customizable)
abort: ["(http)", "(process)", "(image)", "(compress)", "(cloud)", "(watch)", "(system)", "(node)", "(file)", "(permission)", "(exec)", "(timeout)"], // By error type (module takes precedence)
abort: ["chrome", "cloud"], // Only "chrome" or "cloud" module errors are aborted
abort: ["filemanager", "(http)", "chrome"], // "http" and "chrome" errors will bubble up to "filemanager" and abort entire transaction (except watch)
abort: ["filemanager", "(exec)", "chrome"], // Required for NPM auto-install (use with "filemanager" for auto-restart)
abort: "filemanager", // Only for permissions
abort: "(unknown)", // Abort all errors
fatal: true, // Abort all thrown errors
fatal: false // Overrides system settings
},
broadcast: (result: { value: string, type?: number | string, timeStamp?: number }) => void,
broadcast: {
socketId: "111-111-111" | ["111-111-111", "222-222-222"],
callback: function(result) { console.log(result.value); },
port: 3443, // Optional
secure: true
},
broadcastId: "111-111-111", // Reuse socket for simultaneous request or redirect messages to another destination
log: false, // enabled (default is "true")
log: "chrome" | ["cloud", "gulp"], // exclude
log: {
enabled: false,
level: 0, // Modules will see all logs except when specified by user
level: "debug", // fatal = 1, error = 2, warn = 3, info = 4, debug = 5, assert = 6, trace = 7
exclude: ["cloud", "gulp"],
useColor: true, // Includes broadcast messages
showSize: false, // File size (default is "true")
useNumeric: true,
showProgress: false, // Will lock screen to one instance
showDiff: [ // Files about to be overwritten (v3.3)
"text/css",
"javascript", // MIME subtype
"**/assets/*.js" // Glob
]
},
ignoreExtensions: true | ["gulp", "jimp"], // Module name | NPM package
// Auth
outgoing: {
authorization: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InNxdWFyZWQiLCJwYXNzd29yZCI6Imp3dDEyMyIsImlhdCI6MTY0NTQxMTA1NX0.vK1VMoJNEirWhVjAH4V5VN21gebUtylqMi63gBKmRZM" // JSON web token (https://jwt.io)
},
// FileManager
timeout: {
filemanager: "1m 1s 1ms", // processTimeout
jimp: "5s" // document + image + task (moduleName)
},
readTimeout: 10, // request.read_timeout (seconds)
headers: {
"https://www.googleapis.com/v1/": { "authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" }, // request.headers (merged)
"https://www.googleapis.com/v2/": { "authorization": "Bearer YOUR_ACCESS_TOKEN" } // URLs are matched using length comparison
},
httpVersion: 1 | 2, // request.use.http_version (override)
ipVersion: 0 | 4 | 6, // request.dns.family
// Android
manifest: {}, // ManifestData (types/android/resource.d.ts)
projectName: "com.example.sqd", // rootProject.name (settings.gradle)
namespace: "sqd1", // android.namespace (app/build.gradle)
applicationId: "sqd1.layout.main", // android.defaultConfig.applicationId (app/build.gradle)
javaVersion: 1.8 | 11, // JavaVersion.VERSION_1_8 | JavaVersion.VERSION_11 (outputDocumentEditing: true)
jvmToolchain: 17, // v3.3
versionCatalog: false, // v3.3
targetAPI: 32 | "Tiramisu", // Override targetAPI (outputDocumentEditing: true)
mainParentDir: "app", // Override "outputDirectory" (outputDocumentEditing: true)
mainSrcDir: "src/main",
mainActivityFile: "MainActivity.java", // "MainActivity.*" | "/user/project/path_to/MainActivity.java" | "app/path_to/MainActivity.java"
dependencyScopes: true,
dependencyScopes: "compile" | "provided" | "runtime" | "test", // implementation | compileOnly | runtimeOnly | testImplementation
dependencyScopes: "snapshot", // Use latest published release
dependencyScopes: 1, // "snapshot" + true
dependencyScopes: ["snapshot", "compile"],
versionName: "1.0",
versionCode: 1,
profileable: true, // <profileable android:enabled="[false|true]" />
profileable: "debug", // android.buildTypes.release.signingConfig = signingConfigs.debug
profileable: "--warn-manifest-validation", // aaptOptions.additionalParameters (--prefix)
profileable: ["release", "--warn-manifest-validation", "--no-version-vectors"], // signingConfig + additionalParameters (multiple --args)
commands: "build" | ["test", "deploy"] | ["lint", ["test", "--rerun-tasks"]], // gradlew build | gradlew test deploy | gradlew lint && gradlew test --rerun-tasks
extensionData: {}, // Transferred into AndroidDocument.extensionData
updateXmlOnly: false,
// Chrome
cache: {
transform: false, // Not recommended when using watch
transform: true, // "etag" (not bundled) + string comparison by URL (single page)
transform: "etag", // request.cache OR request.buffer.expires (required)
transform: "md5" | "sha1" | "sha224" | "sha256" | "sha384" | "sha512", // Multi-[user|page] + Inline content (includes "etag")
transform: { expires: "2h" }, // Expires in 2 hrs since creation
transform: { expires: "1h", renew: true }, // Expires from 1 hr of last time accessed
transform: { algorithm: "md5" /* etag */, expires: "2h", limit: "5mb" }, // Set expiration and content size limit
transform: { exclude: { html: "*", js: ["bundle-es6"] } }, // Format names per type
transform: { include: { css: "*", js: ["bundle"] } }
},
imports: {
"http://localhost:3000/build/": "./build", // Starts with "http"
"http://localhost:3000/dist/chrome.framework.js": "/path/project/build/framework/chrome/src/main.js" // Full file path
},
excluding: [document.getElementId("img")], // Elements to remove from HTML
downloadOnly: true, // Do not transform HTML and CSS files
webBundle: {
baseUrl: "http://hostname/dir/", // Resolves to current host and directory
rewriteHtmlPage: true | "index.html", // Hide or rename main page
excludeHtmlPage: true, // Exclude HTML page from WBN archive
excludeTransforms: true, // Exclude transformed files not used in HTML page
includeScopes: ["**/*.css"], // http://localhost:3000/dir/**/*.css (hides "excludeTransforms" + "excludeScopes")
excludeScopes: ["/**/*.js"], // http://localhost:3000/**/*.js
copyTo: "/path/project", // Copy archive (absolute + permission)
rootDirAlias: "__serverroot__" // Internal value
},
baseHref: "http://hostname/prod/example.html" // Additional hostname to use for parsing (URL | string)
};
// Project based - Android
squared.save(); // Uses defaults from settings
squared.saveAs("archive1.zip", data); // "data" (optional)
squared.appendTo("/path/project.zip");
squared.copyTo("/path/project");
squared.copyTo(["/path/project1", "/path/project2"]);
// File based - Chrome
squared.saveFiles("archive.7z", data); // "data" (required)
squared.appendFiles("http://hostname/project.zip", data);
squared.copyFiles("/path/www", data);
squared.copyFiles(["/server1/www", "/server2/www"], data);
You can also store your entire configuration in a single JSON/YAML file.
// http://hostname/example.html.json
{
"emptyDir": true,
"watch": true,
"elements": [
{
"selector": "html",
"type": "html",
"filename": "index.html",
"attributes": {
"lang": "en"
}
}
]
}
squared.copyTo("/path/project", { config: "http://hostname/example.html.json" });
squared.copyTo("/path/project", { config: "json" }); // http://hostname/example.html
Archiving
Supported formats:
- zip
- tar
- gz/tgz
Optional formats:
- 7z
- npm: node-7z + 7zip-bin
- windows: https://www.7-zip.org/download.html
- macos: https://formulae.brew.sh/formula/p7zip
- linux: p7zip
- wbn
- npm: wbn
- zopfli
- npm: @gfx/zopfli (wasm)
- npm: node-zopfli (python + gcc)
You can use a locally installed 7zip-bin
by providing the full location of the binary (7za or 7z.exe) or using the value "detect" in settings. (compress.7z.bin)
Performance
- fflate (zip)
- npm: fflate
- libarchive (decompress: zip + tar + 7-zip + rar)
- npm: archive-wasm
- npm: libarchive-wasm
Routing
Simple routing and middleware can be loaded using locally evaluated functions in case you need additional functionality. It is not recommended to use in production environments when custom routes are defined.
- https://expressjs.com/en/guide/routing.html
// squared.[json|yml|cjs]
{
"apiVersion": false | "0.0.0", // Disable API routes
"routing": {
"common": [
{ "mount": "html", "path": "/", "options": { "setHeaders": "function (res, path, stat) { if (path.endsWith('.wbn')) res.set('X-Content-Type-Options', 'nosniff'); }" } }, // Static routes only - ServeStaticOptions
{ "mount": "dist", "path": "/dist" },
{ "get": "/index.html", "handler": "./index-html.js" }, // Handlers can be absolute paths
{ "all": "/route/pathname", "handler": ["./handler-1.js", "./handler-2.js", "npm:custom-package"] }, // "npm:" is recommended
{ "handler": "./middleware.cjs" }, // ".cjs" uses module.exports and is debuggable
/* Middleware - higher-order functions */
{ "handler": "npm:cookie-parser", "apply": ["secret", { "encode": true }] }, // "apply" is always an array (required)
{ "get": "/session/log", "handler": ["npm:cookie-parser", "./handler.js", "npm:morgan"], "apply": { "npm:cookie-parser": [], "npm:morgan": ["combined"] }, // "./handler.js" is used directly
/* List directory - https://github.com/expressjs/serve-index#options - npm i serve-index */
{
"mount": "public/images",
"path": "/images",
"static": true, // "false" will not serve directory contents
"index": true,
/* OR */
"index": { "filter": "function (filename, index, files, dir) { return filename.endsWith('.png'); }", "stylesheet": "common/directory.css" }
}
],
"router": [
{ "router": true, "path": "/assets", "options": { "caseSensitive": true }, "handler": ["npm:cookie-parser", "npm:morgan"] }, // Middleware used with all requests under "/assets" (first) (required)
{ "router": "/assets", "path": "/js", "mount": "public/js", "handler": ["./handler-1.js", "./handler-2.js"], "index": true }, // Same as "common"
{ "router": "/assets", "path": "/css", "mount": "public/css", "handler": ["./handler-2.js", "./handler-3.js"] } // Path is "/assets/css"
],
"production": [
{ "post": "/data/:userId", "handler": "function (req, res) { res.send(req.params); }" } // Inline handlers always start with "function" or "async function"
]
},
"error": {
"handler": "function (err, data, require) { console.error(err); }", // Uncaught exceptions
"out": "function (err, data, require) { require('fs').appendFileSync(require('path').resolve('./fail.log'), data.sessionId + ' ' + new Date(data.timeStamp).toISOString() + ': ' + err.stack + '\\n', 'utf-8'); }" // Caught exceptions + Global
}
}
// index-html.js
function (req, res) {
res.send("<html><body><!-- content --></body></html>");
}
// handler-1.js
function (req, res, next) {
/* synchronous code */
next();
}
// handler-2.js
async function (req, res, next) {
/* await code */
next(); // Express is not asynchronous
}
// npm i custom-package
const cookieParser = require("cookie-parser");
module.exports = cookieParser(); // function (req, res, next) {}
// middleware.cjs
function (req, res, next) {
const path = require("path");
}
// middleware.js
function (req, res, next, require) {
/* Environment variables not accessible */
}
// serve.routes.js
module.exports = function(app: Express, settings: ExpressSettings, auth: IJwtAuth) {
app.get("/path/url/1", (req, res) => {
res.send("1");
});
app.get("/path/url/2", (req, res) => {
res.send("2");
});
};
Workspaces
Text based documents which require a preprocessor before being rendered can have the working live document precompiled. Images with transformations can similarly be served into the browser for immediate viewing during the drafting phase. It is not recommended for use in production deployments.
// squared.[json|yml|cjs]
{
"routing": {
"development": [
// squared.setEndpoint("ASSETS_COPY", "/render")
{ "path": "/render", "document": "chrome", "type": "text/html" }, // Unsupported: UUID + cloud URL + inline + blob
/* OR */
{ "path": "/render", "document": "chrome", "type": "function (asset, index, array) { return asset.bundleId > 0 && asset.mimeType?.endsWith('text/css'); }" }, // Array.filter
{ "mount": "../local/src", "path": "/workspace-1", "document": "chrome", "static": true }, // "static" enables loading external resources in same directory (e.g. source maps)
{ "mount": "../local/html", "path": "/workspace-2", "document": "chrome" }, // Without "document" it is treated as an ordinary static mount
{ "mount": "../local/html/common/images", "path": "/common/images", "image": "@pi-r/jimp" }, // NPM packages only with ImageConstructor or ImageV3Constructor interface (GET)
/* OR */
{ "path": "/form-data/transform/image", "image": "@pi-r/jimp" } // Same (POST)
]
}
}
[!NOTE] Script files with ".cjs" extension will be parsed with
require
.
<html>
<head>
<!-- predeclare ESM globals -->
<script>var android = null;</script>
<!-- ../local/build/main.js -->
<script type="text/javascript" src="/workspace-1/build/main.js?format=bundle&type=js&{name}=app"></script> <!-- query params with (!name | {name}) are sent as external properties to transformer -->
<!-- ../local/src/util.ts -->
<script type="text/javascript" src="/workspace-1/src/util.ts?format=typescript&type=js&cache=1&{compilerOptions.target}=es2017"></script> <!-- nested query params use "qs" parsing library -->
<!-- ../local/html/template-1.sass -->
<link rel="stylesheet" type="text/css" href="/workspace-2/template-1.sass?format=demo&type=css&mime=text/css" /> <!-- "mime" might be required for non-standard file extensions -->
<!-- ../local/html/css/template-2.sass -->
<link rel="stylesheet" type="text/css" href="/workspace-2/css/template-2.sass?format=demo%2Bdemo-2&type=css&encoding=utf16le" /> <!-- "+" chain symbol (demo+demo-2) is URL encoded as "%2B" -->
<!-- ../local/build/framework/android/src/main.js -->
<script type="module">
import appBase from "/build/framework/android/src/main.js?format=bundle-es6&type=js"; // Using cache not recommended when auxiliary files are modified
android = appBase;
</script>
</head>
<body>
<img src="/common/images/android.png?command=webp(480x800)%7B90%7D" /> <!-- URL encoded: webp(480x800){90} (Only one rotation is supported) -->
<!-- OR -->
<img src="/common/images/android.png?command=webp(480x800)%7B90%7D&cache=1" /> <!-- uses disk storage (e.g. tmp/jimp) -->
</body>
</html>
[!TIP] The optional
mime
parameter can be used when the server incorrectly detects the file content type.
If any of the required query parameters are missing then the request will be sent to the next Express static handler.
- Document: format + type + encoding? + cache?
- Image: [command | cmd | q] + cache?
You can debug TypeScript files directly with Visual Code using tsc --[sourceMap|inlineSourceMap] --outDir <workspace>
. It is also more efficient to use --watch
in conjunction with js
output files for recompilation.
[!NOTE] External properties are parsed with the same qs 6.11 library that is bundled with Express.
Document: Extensions
Assets can be modified externally with custom made NPM packages at the end of the finalization process. These are configurable only in Express settings and not through a RequestData
object.
{
"document": {
"android": {
"handler": "@pi-r/android",
"extensions": [
"@pi-r/android/extensions/app/manifest",
"@pi-r/android/extensions/gradle/settings",
"@pi-r/android/extensions/gradle/dependencies",
"custom-android-extension"
]
}
}
}
// npm i custom-android-extension
module.exports = function(instance, documentDir) { // this = FileManager
const mainSrcDir = path.join(this.baseDirectory, instance.mainParentDir, instance.mainSrcDir);
/* Modify instance.assets */
};
[!TIP] Asynchronous functions are supported.
Permissions
Copying files to a destination folder requires enabling write privileges. These folders can be restricted using glob patterns which are only configurable in settings or overidden using the CLI.
{
"disk_read": false,
"disk_write": true, // Inherited
"unc_read": false,
"unc_write": false,
"node": {
"modules": {
"exclude": ["rollup", "terser"] // Packages that do not install correctly without a server restart
}
},
"permission": {
"disk_read": ["**"],
"disk_write": ["/var/www/**", "/user/workspace/**"],
"unc_read": "**",
"unc_write": [/* none */], // Default is "**"
"home_read": true, // boolean only
"home_write": false,
"process_exec": ["npm"] // Required for auto-install (node.modules.exclude)
},
"auth": {
"algorithm": {
"HS": {
"enabled": true, // HS256 | HS384 | HS512
"key": "secret123"
},
"PS": {
"enabled": false,
"cert": "/path/cert.pem" // PEM format
}
},
"client": {
"username": { // Any string
"cipher": { // Optional
"algorithm": "AES",
"key": "key123"
},
"password": "password123", // When using hash or cipher copy password from JSON response
"permission": {
"host": {
"inherit": true, // Inherits from "permission"
"disk_write": ["**"]
},
"chrome": { // Module name
"inherit": false, // Explicit "false" to disable
"disk_write": ["/project/chrome/**"]
}
}
}
}
},
"document": {
"chrome": {
"permission": {
"inherit": true,
"disk_write": ["/var/www/**"]
}
}
}
}
Directory: sqd.config
HTML page directories can contain a sqd.config
file that can be used to store request data for multiple pages in either JSON or YAML format. Name resolution order goes by full path and then filename. Search parameters are also interpreted when the page is served.
<!-- http://localhost:3000/project/bundle.html -->
<script>
squared.copyTo("/path/output", { config: true }); // Same as http://localhost:3000/project/sqd.config
/* OR */
squared.copyTo("/path/output", { config: { uri: true, key: "111-111-111" } });
</script>
// sqd.config
{
"/project/bundle.html?id=1": {
"ordinal": 1, // Used with config.inherit
"useOriginalHtmlPage": true
"elements": [{ "selector": "html", "type": "html" }]
},
"111-111-111": [{ "selector": "html", "type": "html" }], // key
"/project/bundle.html": [{ "selector": "html", "type": "html" }],
"bundle.html?id=1": [{ "selector": "html", "type": "html" }],
"bundle.html": [{ "selector": "html", "type": "html" }],
"**/*.html": [{ "selector": "html", "type": "html" }], // Glob patterns
"**/*.html\\?id=1": [{ "selector": "html", "type": "html" }] // Escaping "?" is required (RegExp special characters)
}
Settings
squared.json
(v3) is no longer backwards compatible with v1 and v2. Warnings will be given due to the use of unsafe migration routines from v2 to v3. Check the release notes (v3.0.0) for affected properties.
- squared.json
- squared.config.json (suffix)
- squared.settings.json (prefix)
[!TIP] File extension ".cjs" can also be used which gives you full access to the NodeJS API.
// squared.cjs
module.exports = {
document: {
chrome: {
handler: "@pi-r/chrome",
settings: {
transform: {
js: {
terser: {
minify: async function (terser, value, options) {
return await terser.minify(value, options.outputConfig).code;
}
}
}
}
}
}
}
};
API Routes
v1.0.0
// NOTE: {required} [optional]
POST: "/api/v1/assets/archive?format={zip|tar|7z|gz}&filename=[no_ext]&to=[disk_uri]&append_to=[archive_uri]"
POST: "/api/v1/assets/copy?to={disk_uri}&empty=[0|1|2]" // Target directory (1=sub|2=base)
GET: "/api/v1/loader/data/json?key={id}&cache=[0|1]"
GET: "/api/v1/loader/data/json?key={id}&cache=[0|1]&mime=[json|yaml|toml]"
GET: "/api/v1/loader/data/blob?key={id}&cache=[0|1]"
GET: "/api/v1/loader/data/text?key={id}&cache=[0|1]"
GET: "/api/v1/loader/data/document?key={id}&cache=[0|1]"
GET: "/api/v1/loader/data/arraybuffer?key={id}&cache=[0|1]"
v1.1.0
POST: "/api/v1/websocket/observe?pathname={spec_URL}"
v1.2.0
GET: "/api/v1/auth/jwt/sign?alg={HS256|HS384|HS512|RS256|RS384|RS512|PS256|PS384|PS512|ES256|ES384|ES512}&username={string}&password={string}&aud=[string|string[]]&iss=[string|string[]]&sub=[string]&jti=[string]&nbf=[1y 1d 1h 1m]&expires=[1y 1d 1h 1m]" // env=development
GET: "/api/v1/auth/jwt/verify?token={string}"
v1.2.1
GET: "/api/v1/auth/jwt/sign?alg={string}&username={string}&password={string}&nonce=[0|1]"
GET: "/api/v1/auth/jwt/sign?hash=[MD5|SHA1|SHA3|SHA224|SHA256|SHA384|SHA512|RIPEMD160]&passphrase=[string]" // passphrase (optional)
GET: "/api/v1/auth/jwt/sign?cipher=[AES|DES|TripleDES|Rabbit|RC4|RC4Drop]&key=[string]" // key (required)
v1.3.0
GET: "/api/v1/threads/count?as=[text]"
GET: "/api/v1/threads/stat"
POST: "/api/v1/threads/kill?pid={uuid}" // Internal (squared)
POST: "/api/v1/admin/threads/stat" // JWT auth header (required)
POST: "/api/v1/admin/threads/kill?pid=[number]&all=[0|1]&as=[text]" // Multiple supported - ?pid=1&pid=2
v1.3.1
POST: "/api/v1/admin/auth/jwt/sign" // Same as GET params (auth.settings.admin.users)
v1.4.0
GET: "/api/v1/loader/data/json?mime=[...|json5]&encoding=[utf8|utf16|...]" // https://nodejs.org/api/buffer.html#buffers-and-character-encodings
POST: "/api/v1/loader/data/json?key={id}&document=[text]&mime=[json|yaml|toml|json5]&encoding=[utf8|utf16|...]" // JWT auth
v1.5.0
POST: "/api/v1/loader/data/cloud?flat=[0|1]&merge=[0|1|2|3]&depth=[int]&encoding=[utf8|utf16|...]" // v3.3
POST: "/api/v1/loader/data/db?document={text}&flat=[0|1]&merge=[0|1|2|3]&depth=[int]&encoding=[utf8|utf16|...]"
LICENSE
BSD 3-Clause