npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

sqd-serve

v0.15.7

Published

CLI action [serve] for squared.

Downloads

171

Readme

sqd-serve 0.15

> npm init

> npm i sqd-serve        # latest stable

> npm i sqd-cli          # recommended
> npx sqd init [--help]
# OR
> cp node_modules/sqd-serve/config/json/* .

> squared.[json|yml|cjs] # configure

> npx serve [--help]

> http://localhost:3000

[!NOTE] The file extension ".cjs" is required with projects using ES modules when serving locally.

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

  • v0.10 - E-mc 0.5.2 (squared 5.0)
  • v0.11 - E-mc 0.6.0 (squared 5.1)
  • v0.12 - E-mc 0.7.0 (squared 5.1.1)
  • v0.13 - E-mc 0.8.0 (squared 5.1.2)
  • v0.14 - E-mc 0.9.0 (squared 5.2.0)
  • v0.15 - E-mc 0.10.0 (squared 5.3.0)

[!NOTE] Using sqd-serve 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: {
      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?: 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,
      showProgress: false, // Will lock screen to one instance
      showDiff: [ // Files about to be overwritten
        "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.defaultConfig.applicationId (app/build.gradle)
    javaVersion: 1.8 | 11, // JavaVersion.VERSION_1_8 | JavaVersion.VERSION_11 (outputDocumentEditing: true)
    jvmToolchain: 17,
    versionCatalog: false,
    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" | "ripemd", // 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 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, // "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.

  1. squared.json
  2. squared.config.json (suffix)
  3. 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|...]"
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