fukuoka v0.0.8
fukuoka 3.0 (beta)
- NodeJS 14 (Minimum)
NPX
> npm i fukuoka
> cp node_modules/fukuoka/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]
Typically you will be using squared 4.0 with fukuoka/squared-express although it can independently be used for server or debugging purposes. It is recommended to copy the latest release into your squared project folder when there are patches.
Version Compatibility
* v1.0 - 04-29-21 - squared-functions 1.0 (squared 3.0 + NodeJS 10)
* v1.1 - 06-16-21 - squared-functions 1.1 (squared 3.1)
* v1.2 - 08-03-21 - squared-functions 1.2 (HTTP/2)
* v1.3 - 09-15-21 - squared-functions 2.0 (squared 3.2)
* v1.4 - 10-18-21 - squared-functions 3.0 (squared 3.3)
* v1.5 - 12-07-21 - squared-functions 3.1
* v1.7 - 12-23-21 - squared-functions 3.2 (squared 3.4)
* v1.8 - 01-07-22 - squared-functions 3.3 (squared 3.5)
* v1.9 - 02-03-22 - squared-functions 3.5
* v2.0 - 02-22-22 - squared-functions 4.0 (squared 3.6 + NodeJS 12)
* v2.1 - 04-29-22 - squared-functions 4.1 (squared 3.7)
* v2.2 - 06-16-22 - squared-functions 4.2 (squared 4.0)
* v2.3 - 08-06-22 - squared-functions 4.3 (squared 4.1)
* v2.4 - 09-21-22 - squared-functions 4.4 (squared 4.2)
* v2.5 - 10-02-22 - squared-functions 4.5 (squared 4.2.2)
* v2.6 - 10-13-22 - squared-functions 4.6 (squared 4.2.3)
* v2.7 - 10-28-22 - squared-functions 4.7 (squared 4.3)
* v2.8 - 11-05-22 - squared-functions 4.8 (squared 4.3.1)
* v2.9 - 12-31-22 - squared-functions 4.9 (squared 4.4)
* v2.10 - ??-??-23 - squared-functions 4.10 (squared 4.5)
* v3.0 - ??-??-23 - sapporo 5.0 (squared 4.x + NodeJS 14) (tenative)
Minor releases are sometimes released in conjunction with squared patch releases. Using fukuoka/squared-express with an older version of sapporo/squared-functions 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)
id: "111-111-111" | location.href, // Overwrite previous request with same ID (optional)
expires: "Jan 01 2021 12:00:00" | "1w 1d 1h 1m 1s 1ms" // Empty is never (optional)
},
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
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
callback: (result) => console.log(result), // JSON response from API service (deprecated - Promise.then)
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
cache: true // Use when config file is unchanged
},
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" | true,
key: "111-111-111" // Key inside map
},
// Settings overrides
cache: {
request: false | 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?: NumString, 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
},
ignoreExtensions: false | true | string[], // 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)
},
requestTimeout: 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.defaultConfig.applicationId (app/build.gradle)
javaVersion: 1.8 | 11, // JavaVersion.VERSION_1_8 | JavaVersion.VERSION_11 (outputDocumentEditing: true)
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"
versionName: "1.0",
versionCode: 1,
profileable: false | true, // <profileable android:enabled="[false|true]" />
profileable: "release", // buildTypes.signingConfig signingConfigs.release
profileable: "--warn-manifest-validation", // aaptOptions.additionalParameters (single --arg)
profileable: ["release", "--warn-manifest-validation", "--no-version-vectors"], // buildTypes.signingConfig + aaptOptions.additionalParameters (multiple array)
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: node-zopfli
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)
Routing
Simple routing and middleware can be loaded using locally evaluated functions in case you need additional functionality. It is not recommended to use this package 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, "index": true | { "filter": "function (filename, index, files, dir) { return filename.endsWith('.png'); }", "stylesheet": "common/directory.css" } } // "static" is false will not serve directory contents
],
"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"); // npm i 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": "@squared-functions/image/jimp" } // NPM packages only with ImageConstructor or ImageV3Constructor interface (GET)
/* OR */
{ "path": "/form-data/transform/image", "image": "@squared-functions/image/jimp" } // Same (POST)
]
},
"document": {
/* squared.document.[json|yml|cjs] */
"chrome": {
"handler": "@squared-functions/document/chrome",
"eval_function": true,
"settings": {
/* squared.document.chrome.settings.[json|yml|cjs] */
"transform": {
"js": {
/* Built-in transformer */
"rollup": {
"typescript": { // Query param: "format"
"output": {
"format": "iife"
},
"plugins": [
["@rollup/plugin-typescript", { lib: ["es5", "es6", "dom"], target: "es5" }], // npm i @rollup/plugin-typescript
"rollup-plugin-terser" // npm i rollup-plugin-terser
]
},
"bundle": {
"plugins": ["rollup-plugin-sourcemaps"], // npm i rollup-plugin-sourcemaps
"output": {
"format": "iife",
"sourcemap": true
}
},
"bundle-es6": {
"plugins": ["rollup-plugin-sourcemaps"],
"output": {
"format": "es",
"sourcemap": "inline" // Inline source maps are more reliable
}
}
}
},
"css": {
/* Built-in transformer */
"postcss": {
"demo-css": { // Format names are unique per "type"
"plugins": ["autoprefixer", "cssnano"] // npm i autoprefixer cssnano
}
},
/* Inline transformer */
"sass": { // npm i sass
"demo": "function (sass, value, options, resolve, require) { resolve(sass.renderSync({ ...options.baseConfig, data: value }, functions: {}).css); }" // Uses Promise callback "resolve"
"demo-async": "async function (sass, value, options, require) { const result = await sass.render({ ...options.baseConfig, data: value }, functions: {}); return result.css; }" // Document module is "context"
"demo-output": { // options.baseConfig (optional)
"indentedSyntax": true,
"outputStyle": "compressed"
}
}
}
}
}
}
}
}
Most NPM plugins require manual installation and a custom written transfomer. There are a few built-in example transformers as part of squared-functions.
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>
NOTE: 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": "@squared-functions/document/android",
"extensions": [
"@squared-functions/document/android/extensions/app/manifest",
"@squared-functions/document/android/extensions/gradle/settings",
"@squared-functions/document/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 */
};
NOTE: Asynchronous functions are supported.
HTTP request: Proxy
Connecting to a proxy server is possible for most applications. Some authorization schemes are supported when using HTTP headers.
- npm i http-proxy-agent https-proxy-agent
{
"request": {
"proxy": {
"address": "https://192.168.0.1", // Required
"port": "443", // Required
"include": ["https://www.googleapis.com"], // Takes precedence (ignores "exclude")
"exclude": ["http://localhost:3000" /* Same as both being empty */] // Matches using startsWith
},
"headers": { // Optional
"https://www.googleapis.com/v1/": { "authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" }
"https://www.googleapis.com/v2/": { "authorization": "Bearer YOUR_ACCESS_TOKEN" }
}
}
}
HTTP response: Proxy
Proxy routes can be defined for additional functionality if you are using this server for something other than development.
- npm i http-proxy-middleware
- https://github.com/chimurai/http-proxy-middleware#http-proxy-middleware-options
{
"routing": {
"production": [
{ "mount": "http://www.example1.com", "path": "/target/example1" }, // mount - destination | path - source (Express)
/* OR */
{
"mount": "http://www.example2.com",
"path": "/target/example2",
"options": {
"onOpen": "function onOpen(err, req, res, target) {}", // this = NodeJS.process
"onClose": "./local-close-handler.js",
"onError": "./local-error-handler.cjs", // OR: squared.cjs (agent + ssl + buffer)
"logProvider": "npm:external-log-provider", // NPM package
"onProxyReq": "fixRequestBody" // Internal (http-proxy-middleware)
}
}
]
}
}
NOTE: Proxied responses from a secure website requires an equivalent secure HTTPS host server.
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)
NOTE: File extension ".cjs" can also be used which gives you full access to the NodeJS API.
// squared.cjs
module.exports = {
apiVersion: "1.3.0", // Optional
document: {
chrome: {
handler: "@squared-functions/document/chrome",
settings: {
transform: {
js: {
terser: {
minify: async function (terser, value, options) {
return await terser.minify(value, options.outputConfig).code; // npm i terser
}
}
}
}
}
}
}
};
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 (v2.0)
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]" // v2.2
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]" // v2.7
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)
LICENSE
BSD 3-Clause