Commit b1a996a

Anton Golub <antongolub@antongolub.com>
2025-07-29 12:41:57
docs: add architecture notes (#1290) tag: 8.7.2
1 parent c06d3c6
docs/.vitepress/config.mts
@@ -68,6 +68,7 @@ export default defineConfig({
             { text: 'Process Promise', link: '/process-promise' },
             { text: 'Process Output', link: '/process-output' },
             { text: 'Contribution Guide', link: '/contribution' },
+            { text: 'Architecture', link: '/architecture' },
             { text: 'Migration from v7', link: '/migration-from-v7' },
             { text: '⚡ zx@lite', link: '/lite' },
           ],
docs/api.md
@@ -244,8 +244,8 @@ syncProcessCwd(false) // pass false to disable the hook
 
 ## `retry()`
 
-Retries a callback for a few times. Will return after the first
-successful attempt, or will throw after specifies attempts count.
+Retries a callback for a few times. Will return the first
+successful result, or will throw after the specified attempts count.
 
 ```js
 const p = await retry(10, () => $`curl https://medv.io`)
docs/architecture.md
@@ -0,0 +1,95 @@
+# The zx architecture
+This section helps to better understand the `zx` concepts and logic, and will be useful for those who want to become a project contributor, make tools based on it, or create something similar from scratch.
+
+## High-level modules
+| Module                                                                  | Description                                                         |
+|-------------------------------------------------------------------------|---------------------------------------------------------------------|
+| [zurk](https://github.com/webpod/zurk)                                  | Execution engine for spawning and managing child processes.         |
+| [./src/core.ts](https://github.com/google/zx/blob/main/src/core.ts)     | `$` factory, presets, utilities, high-level APIs.                   |
+| [./src/goods.ts](https://github.com/google/zx/blob/main/src/goods.ts)   | Utilities for common tasks like fs ops, glob search, fetching, etc. |
+| [./src/cli.ts](https://github.com/google/zx/blob/main/src/cli.ts)       | CLI interface and scripts pre-processors.                           |
+| [./src/deps.ts](https://github.com/google/zx/blob/main/src/deps.ts)     | Dependency analyzing and installation.                              |
+| [./src/vendor.ts](https://github.com/google/zx/blob/main/src/vendor.ts) | Third-party libraries.                                              |
+| [./src/utils.ts](https://github.com/google/zx/blob/main/src/utils.ts)   | Generic helpers.                                                    |
+| [./src/md.ts](https://github.com/google/zx/blob/main/src/md.ts)         | Markdown scripts extractor.                                         |
+| [./src/error.ts](https://github.com/google/zx/blob/main/src/error.ts)   | Error handling and formatting.                                      |
+| [./src/global.ts](https://github.com/google/zx/blob/main/src/global.ts) | Global injectors.                                                   |
+
+
+## Core design
+
+### `Options`
+A set of options for `$` and `ProcessPromise` configuration. `defaults` holds the initial library preset. `Snapshot` captures the current `Options `context and attaches isolated subparts.
+
+### `$`
+A piece of template literal magic.
+```ts
+interface Shell<
+  S = false,
+  R = S extends true ? ProcessOutput : ProcessPromise,
+> {
+  (pieces: TemplateStringsArray, ...args: any[]): R
+  <O extends Partial<Options> = Partial<Options>, R = O extends { sync: true } ? Shell<true> : Shell>(opts: O): R
+  sync: {
+    (pieces: TemplateStringsArray, ...args: any[]): ProcessOutput
+    (opts: Partial<Omit<Options, 'sync'>>): Shell<true>
+  }
+}
+
+$`cmd ${arg}`             // ProcessPromise
+$(opts)`cmd ${arg}`       // ProcessPromise
+$.sync`cmd ${arg}`        // ProcessOutput
+$.sync(opts)`cmd ${arg}`  // ProcessOutput
+```
+
+The `$` factory creates `ProcessPromise` instances and bounds with snapshot-context via `Proxy` and `AsyncLocalStorage`. The trick:
+```ts
+const storage = new AsyncLocalStorage<Options>()
+
+const getStore = () => storage.getStore() || defaults
+
+function within<R>(callback: () => R): R {
+  return storage.run({ ...getStore() }, callback)
+}
+// Inside $ factory ...
+const opts = getStore()
+if (!Array.isArray(pieces)) {
+  return function (this: any, ...args: any) {
+    return within(() => Object.assign($, opts, pieces).apply(this, args))
+  }
+}
+```
+
+### `ProcessPromise` 
+A promise-inherited class represents and operates a child process, provides methods for piping, killing, response formatting.
+
+#### Lifecycle
+| Stage        | Description            |
+|--------------|------------------------|
+| `initial`    | Blank instance         |
+| `halted`     | Awaits running         |
+| `running`    | Process in action      |
+| `fulfilled`  | Successfully completed |
+| `rejected`   | Failed                 |
+
+| Gear         | Description                                                                                 |
+|--------------|---------------------------------------------------------------------------------------------|
+| `build()`    | Produces `cmd` from template and context, applies `quote` to arguments                      |
+| `run()`      | Spawns the process and captures its data via `zurk` events listeners                        |
+| `finalize()` | Assigns the result to the instance: analyzes status code, invokes `_resolve()`, `_reject()` |
+
+### `ProcessOutput`
+A class that represents the output of a `ProcessPromise`. It provides methods to access the process's stdout, stderr, exit code and extra methods for formatting the output and checking the process's success.
+
+### `Fail`
+Consolidates error handling functionality across the zx library: errors codes mapping, formatting, stack parsing.
+
+## Building
+In the early stages of the project, we [had some difficulties](https://dev.to/antongolub/how-and-why-do-we-bundle-zx-1ca6) with zx packaging. We couldn't find a suitable tool for assembly, so we made our own toolkit based on [esbuild](https://github.com/evanw/esbuild) and [dts-bundle-generator](https://github.com/timocov/dts-bundle-generator). This process is divided into several scripts.
+
+| Script                                                                                       | Description                                                            |
+|----------------------------------------------------------------------------------------------|------------------------------------------------------------------------|
+| [`./scripts/build-dts.mjs`](https://github.com/google/zx/blob/main/scripts/build-dts.mjs)    | Extracts and merges 3rd-party types, generates `dts` files.            |
+| [`./scripts/build-js.mjs`](https://github.com/google/zx/blob/main/scripts/build-js.mjs)      | Produces [hybrid bundles](./setup#hybrid) for each package entry point |
+| [`./scripts/build-jsr.mjs`](https://github.com/google/zx/blob/main/scripts/build-jsr.mjs)    | Builds extra assets for [JSR](https://jsr.io/@webpod/zx) publishing    |
+| [`./scripts/build-tests.mjs`](https://github.com/google/zx/blob/main/scripts/build-test.mjs) | Generates autotests to verify exports consistency                      |
docs/contribution.md
@@ -28,7 +28,7 @@ You generally only need to submit a CLA once, so if you've already submitted one
 again.
 
 ## How to Contribute
-Before proposing changes, look for similar ones in the project's [issues](https://github.com/google/zx/issues) and [pull requests](https://github.com/google/zx/pulls). If you can't decide, create a new [discussion](https://github.com/google/zx/discussions) topic, and we will help you figure it out. When ready to move on:
+Before proposing changes, look for similar ones in the project's [issues](https://github.com/google/zx/issues) and [pull requests](https://github.com/google/zx/pulls). If you can't decide, create a new [discussion](https://github.com/google/zx/discussions) topic, and we will help you figure it out. Dive also into [architecture notes](/architecture) to observe design concepts. When ready to move on:
 
 * Prepare your development environment.
   * Switch to the recommended version of Node.js