@pristine-ts/cli 1.0.440 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/pristine.cjs +7 -0
- package/dist/lib/cjs/bin.js +15 -1
- package/dist/lib/cjs/bin.js.map +1 -1
- package/dist/lib/cjs/bootstrap/app-module-loader.js +321 -0
- package/dist/lib/cjs/bootstrap/app-module-loader.js.map +1 -0
- package/dist/lib/cjs/bootstrap/bootstrap.js +31 -0
- package/dist/lib/cjs/bootstrap/bootstrap.js.map +1 -0
- package/dist/lib/cjs/bootstrap/build-manifest-checker.js +67 -0
- package/dist/lib/cjs/bootstrap/build-manifest-checker.js.map +1 -0
- package/dist/lib/cjs/bootstrap/build-manifest-reader.js +44 -0
- package/dist/lib/cjs/bootstrap/build-manifest-reader.js.map +1 -0
- package/dist/lib/cjs/bootstrap/build-manifest-staleness.enum.js +23 -0
- package/dist/lib/cjs/bootstrap/build-manifest-staleness.enum.js.map +1 -0
- package/dist/lib/cjs/bootstrap/build-manifest-writer.js +55 -0
- package/dist/lib/cjs/bootstrap/build-manifest-writer.js.map +1 -0
- package/dist/lib/cjs/bootstrap/build-manifest.js +31 -0
- package/dist/lib/cjs/bootstrap/build-manifest.js.map +1 -0
- package/dist/lib/cjs/bootstrap/build-runner.js +44 -0
- package/dist/lib/cjs/bootstrap/build-runner.js.map +1 -0
- package/dist/lib/cjs/bootstrap/build-staleness-prompt.js +90 -0
- package/dist/lib/cjs/bootstrap/build-staleness-prompt.js.map +1 -0
- package/dist/lib/cjs/bootstrap/dynamic-importer.js +43 -0
- package/dist/lib/cjs/bootstrap/dynamic-importer.js.map +1 -0
- package/dist/lib/cjs/bootstrap/init-prompt.js +127 -0
- package/dist/lib/cjs/bootstrap/init-prompt.js.map +1 -0
- package/dist/lib/cjs/bootstrap/loaded-app-module.js +29 -0
- package/dist/lib/cjs/bootstrap/loaded-app-module.js.map +1 -0
- package/dist/lib/cjs/bootstrap/loaded-plugin.js +22 -0
- package/dist/lib/cjs/bootstrap/loaded-plugin.js.map +1 -0
- package/dist/lib/cjs/bootstrap/plugin-loader.js +149 -0
- package/dist/lib/cjs/bootstrap/plugin-loader.js.map +1 -0
- package/dist/lib/cjs/bootstrap/source-hasher.js +41 -0
- package/dist/lib/cjs/bootstrap/source-hasher.js.map +1 -0
- package/dist/lib/cjs/cli.js +94 -111
- package/dist/lib/cjs/cli.js.map +1 -1
- package/dist/lib/cjs/cli.module.js +9 -1
- package/dist/lib/cjs/cli.module.js.map +1 -1
- package/dist/lib/cjs/commands/build-alias.command.js +50 -0
- package/dist/lib/cjs/commands/build-alias.command.js.map +1 -0
- package/dist/lib/cjs/commands/build.command.js +173 -0
- package/dist/lib/cjs/commands/build.command.js.map +1 -0
- package/dist/lib/cjs/commands/commands.js +15 -0
- package/dist/lib/cjs/commands/commands.js.map +1 -1
- package/dist/lib/cjs/commands/config-print.command.js +75 -0
- package/dist/lib/cjs/commands/config-print.command.js.map +1 -0
- package/dist/lib/cjs/commands/help-alias.command.js +52 -0
- package/dist/lib/cjs/commands/help-alias.command.js.map +1 -0
- package/dist/lib/cjs/commands/help.command.js +44 -10
- package/dist/lib/cjs/commands/help.command.js.map +1 -1
- package/dist/lib/cjs/commands/info-alias.command.js +50 -0
- package/dist/lib/cjs/commands/info-alias.command.js.map +1 -0
- package/dist/lib/cjs/commands/info.command.js +162 -0
- package/dist/lib/cjs/commands/info.command.js.map +1 -0
- package/dist/lib/cjs/commands/init-alias.command.js +51 -0
- package/dist/lib/cjs/commands/init-alias.command.js.map +1 -0
- package/dist/lib/cjs/commands/init.command-options.js +53 -0
- package/dist/lib/cjs/commands/init.command-options.js.map +1 -0
- package/dist/lib/cjs/commands/init.command.js +249 -0
- package/dist/lib/cjs/commands/init.command.js.map +1 -0
- package/dist/lib/cjs/commands/list-alias.command.js +50 -0
- package/dist/lib/cjs/commands/list-alias.command.js.map +1 -0
- package/dist/lib/cjs/commands/list.command.js +7 -1
- package/dist/lib/cjs/commands/list.command.js.map +1 -1
- package/dist/lib/cjs/commands/start-alias.command.js +51 -0
- package/dist/lib/cjs/commands/start-alias.command.js.map +1 -0
- package/dist/lib/cjs/commands/start.command-options.js +34 -0
- package/dist/lib/cjs/commands/start.command-options.js.map +1 -0
- package/dist/lib/cjs/commands/start.command.js +169 -0
- package/dist/lib/cjs/commands/start.command.js.map +1 -0
- package/dist/lib/cjs/commands/verify-alias.command.js +50 -0
- package/dist/lib/cjs/commands/verify-alias.command.js.map +1 -0
- package/dist/lib/cjs/commands/verify.command.js +71 -0
- package/dist/lib/cjs/commands/verify.command.js.map +1 -0
- package/dist/lib/cjs/config/config-loader.js +136 -0
- package/dist/lib/cjs/config/config-loader.js.map +1 -0
- package/dist/lib/cjs/config/config-provenance.enum.js +15 -0
- package/dist/lib/cjs/config/config-provenance.enum.js.map +1 -0
- package/dist/lib/cjs/config/config.js +22 -0
- package/dist/lib/cjs/config/config.js.map +1 -0
- package/dist/lib/cjs/config/define-config.js +18 -0
- package/dist/lib/cjs/config/define-config.js.map +1 -0
- package/dist/lib/cjs/config/pristine-config.interface.js +3 -0
- package/dist/lib/cjs/config/pristine-config.interface.js.map +1 -0
- package/dist/lib/cjs/config/resolved-pristine-config.js +25 -0
- package/dist/lib/cjs/config/resolved-pristine-config.js.map +1 -0
- package/dist/lib/cjs/event-handlers/cli.event-handler.js +69 -48
- package/dist/lib/cjs/event-handlers/cli.event-handler.js.map +1 -1
- package/dist/lib/cjs/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/lib/esm/bin.js +15 -1
- package/dist/lib/esm/bin.js.map +1 -1
- package/dist/lib/esm/bootstrap/app-module-loader.js +315 -0
- package/dist/lib/esm/bootstrap/app-module-loader.js.map +1 -0
- package/dist/lib/esm/bootstrap/bootstrap.js +15 -0
- package/dist/lib/esm/bootstrap/bootstrap.js.map +1 -0
- package/dist/lib/esm/bootstrap/build-manifest-checker.js +61 -0
- package/dist/lib/esm/bootstrap/build-manifest-checker.js.map +1 -0
- package/dist/lib/esm/bootstrap/build-manifest-reader.js +38 -0
- package/dist/lib/esm/bootstrap/build-manifest-reader.js.map +1 -0
- package/dist/lib/esm/bootstrap/build-manifest-staleness.enum.js +20 -0
- package/dist/lib/esm/bootstrap/build-manifest-staleness.enum.js.map +1 -0
- package/dist/lib/esm/bootstrap/build-manifest-writer.js +49 -0
- package/dist/lib/esm/bootstrap/build-manifest-writer.js.map +1 -0
- package/dist/lib/esm/bootstrap/build-manifest.js +27 -0
- package/dist/lib/esm/bootstrap/build-manifest.js.map +1 -0
- package/dist/lib/esm/bootstrap/build-runner.js +41 -0
- package/dist/lib/esm/bootstrap/build-runner.js.map +1 -0
- package/dist/lib/esm/bootstrap/build-staleness-prompt.js +87 -0
- package/dist/lib/esm/bootstrap/build-staleness-prompt.js.map +1 -0
- package/dist/lib/esm/bootstrap/dynamic-importer.js +40 -0
- package/dist/lib/esm/bootstrap/dynamic-importer.js.map +1 -0
- package/dist/lib/esm/bootstrap/init-prompt.js +124 -0
- package/dist/lib/esm/bootstrap/init-prompt.js.map +1 -0
- package/dist/lib/esm/bootstrap/loaded-app-module.js +25 -0
- package/dist/lib/esm/bootstrap/loaded-app-module.js.map +1 -0
- package/dist/lib/esm/bootstrap/loaded-plugin.js +18 -0
- package/dist/lib/esm/bootstrap/loaded-plugin.js.map +1 -0
- package/dist/lib/esm/bootstrap/plugin-loader.js +143 -0
- package/dist/lib/esm/bootstrap/plugin-loader.js.map +1 -0
- package/dist/lib/esm/bootstrap/source-hasher.js +35 -0
- package/dist/lib/esm/bootstrap/source-hasher.js.map +1 -0
- package/dist/lib/esm/cli.js +109 -0
- package/dist/lib/esm/cli.js.map +1 -0
- package/dist/lib/esm/cli.module.js +7 -0
- package/dist/lib/esm/cli.module.js.map +1 -1
- package/dist/lib/esm/commands/build-alias.command.js +47 -0
- package/dist/lib/esm/commands/build-alias.command.js.map +1 -0
- package/dist/lib/esm/commands/build.command.js +167 -0
- package/dist/lib/esm/commands/build.command.js.map +1 -0
- package/dist/lib/esm/commands/commands.js +15 -0
- package/dist/lib/esm/commands/commands.js.map +1 -1
- package/dist/lib/esm/commands/config-print.command.js +69 -0
- package/dist/lib/esm/commands/config-print.command.js.map +1 -0
- package/dist/lib/esm/commands/help-alias.command.js +49 -0
- package/dist/lib/esm/commands/help-alias.command.js.map +1 -0
- package/dist/lib/esm/commands/help.command.js +44 -10
- package/dist/lib/esm/commands/help.command.js.map +1 -1
- package/dist/lib/esm/commands/info-alias.command.js +47 -0
- package/dist/lib/esm/commands/info-alias.command.js.map +1 -0
- package/dist/lib/esm/commands/info.command.js +156 -0
- package/dist/lib/esm/commands/info.command.js.map +1 -0
- package/dist/lib/esm/commands/init-alias.command.js +48 -0
- package/dist/lib/esm/commands/init-alias.command.js.map +1 -0
- package/dist/lib/esm/commands/init.command-options.js +49 -0
- package/dist/lib/esm/commands/init.command-options.js.map +1 -0
- package/dist/lib/esm/commands/init.command.js +243 -0
- package/dist/lib/esm/commands/init.command.js.map +1 -0
- package/dist/lib/esm/commands/list-alias.command.js +47 -0
- package/dist/lib/esm/commands/list-alias.command.js.map +1 -0
- package/dist/lib/esm/commands/list.command.js +8 -2
- package/dist/lib/esm/commands/list.command.js.map +1 -1
- package/dist/lib/esm/commands/start-alias.command.js +48 -0
- package/dist/lib/esm/commands/start-alias.command.js.map +1 -0
- package/dist/lib/esm/commands/start.command-options.js +30 -0
- package/dist/lib/esm/commands/start.command-options.js.map +1 -0
- package/dist/lib/esm/commands/start.command.js +166 -0
- package/dist/lib/esm/commands/start.command.js.map +1 -0
- package/dist/lib/esm/commands/verify-alias.command.js +47 -0
- package/dist/lib/esm/commands/verify-alias.command.js.map +1 -0
- package/dist/lib/esm/commands/verify.command.js +68 -0
- package/dist/lib/esm/commands/verify.command.js.map +1 -0
- package/dist/lib/esm/config/config-loader.js +130 -0
- package/dist/lib/esm/config/config-loader.js.map +1 -0
- package/dist/lib/esm/config/config-provenance.enum.js +12 -0
- package/dist/lib/esm/config/config-provenance.enum.js.map +1 -0
- package/dist/lib/esm/config/config.js +6 -0
- package/dist/lib/esm/config/config.js.map +1 -0
- package/dist/lib/esm/config/define-config.js +14 -0
- package/dist/lib/esm/config/define-config.js.map +1 -0
- package/dist/lib/esm/config/pristine-config.interface.js +2 -0
- package/dist/lib/esm/config/pristine-config.interface.js.map +1 -0
- package/dist/lib/esm/config/resolved-pristine-config.js +21 -0
- package/dist/lib/esm/config/resolved-pristine-config.js.map +1 -0
- package/dist/lib/esm/event-handlers/cli.event-handler.js +69 -48
- package/dist/lib/esm/event-handlers/cli.event-handler.js.map +1 -1
- package/dist/lib/esm/tsconfig.tsbuildinfo +1 -0
- package/dist/types/bootstrap/app-module-loader.d.ts +83 -0
- package/dist/types/bootstrap/bootstrap.d.ts +14 -0
- package/dist/types/bootstrap/build-manifest-checker.d.ts +19 -0
- package/dist/types/bootstrap/build-manifest-reader.d.ts +14 -0
- package/dist/types/bootstrap/build-manifest-staleness.enum.d.ts +18 -0
- package/dist/types/bootstrap/build-manifest-writer.d.ts +19 -0
- package/dist/types/bootstrap/build-manifest.d.ts +29 -0
- package/dist/types/bootstrap/build-runner.d.ts +16 -0
- package/dist/types/bootstrap/build-staleness-prompt.d.ts +27 -0
- package/dist/types/bootstrap/dynamic-importer.d.ts +13 -0
- package/dist/types/bootstrap/init-prompt.d.ts +38 -0
- package/dist/types/bootstrap/loaded-app-module.d.ts +40 -0
- package/dist/types/bootstrap/loaded-plugin.d.ts +20 -0
- package/dist/types/bootstrap/plugin-loader.d.ts +35 -0
- package/dist/types/bootstrap/source-hasher.d.ts +15 -0
- package/dist/types/cli.d.ts +12 -0
- package/dist/types/cli.module.d.ts +3 -0
- package/dist/types/commands/build-alias.command.d.ts +15 -0
- package/dist/types/commands/build.command.d.ts +47 -0
- package/dist/types/commands/commands.d.ts +15 -0
- package/dist/types/commands/config-print.command.d.ts +18 -0
- package/dist/types/commands/help-alias.command.d.ts +17 -0
- package/dist/types/commands/help.command.d.ts +19 -1
- package/dist/types/commands/info-alias.command.d.ts +15 -0
- package/dist/types/commands/info.command.d.ts +41 -0
- package/dist/types/commands/init-alias.command.d.ts +16 -0
- package/dist/types/commands/init.command-options.d.ts +22 -0
- package/dist/types/commands/init.command.d.ts +60 -0
- package/dist/types/commands/list-alias.command.d.ts +15 -0
- package/dist/types/commands/list.command.d.ts +6 -0
- package/dist/types/commands/start-alias.command.d.ts +16 -0
- package/dist/types/commands/start.command-options.d.ts +11 -0
- package/dist/types/commands/start.command.d.ts +47 -0
- package/dist/types/commands/verify-alias.command.d.ts +15 -0
- package/dist/types/commands/verify.command.d.ts +24 -0
- package/dist/types/config/config-loader.d.ts +40 -0
- package/dist/types/config/config-provenance.enum.d.ts +10 -0
- package/dist/types/config/config.d.ts +5 -0
- package/dist/types/config/define-config.d.ts +14 -0
- package/dist/types/config/pristine-config.interface.d.ts +67 -0
- package/dist/types/config/resolved-pristine-config.d.ts +28 -0
- package/dist/types/event-handlers/cli.event-handler.d.ts +30 -3
- package/dist/types/interfaces/command.interface.d.ts +31 -3
- package/package.json +16 -13
- package/readme.md +1023 -17
package/readme.md
CHANGED
|
@@ -1,33 +1,1039 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@pristine-ts/cli`
|
|
2
2
|
|
|
3
|
-
The CLI
|
|
3
|
+
The Pristine CLI — a `pristine` binary for your project, plus everything you need to add
|
|
4
|
+
your own commands, build and start your app, and verify that your AppModule is healthy.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
If you've used `ng`, `nest`, or `vite`, this is the equivalent for Pristine apps.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
`class-validator` library.
|
|
8
|
+
---
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
## Table of contents
|
|
11
11
|
|
|
12
|
+
- [Install](#install)
|
|
13
|
+
- [A 5-minute tour](#a-5-minute-tour)
|
|
14
|
+
- [Recipes](#recipes)
|
|
15
|
+
- [Add a command to your app](#recipe-add-a-command-to-your-app)
|
|
16
|
+
- [Build your TypeScript](#recipe-build-your-typescript)
|
|
17
|
+
- [Start your app in production](#recipe-start-your-app-in-production)
|
|
18
|
+
- [Host an HTTP (or HTTPS) server](#recipe-host-an-http-or-https-server)
|
|
19
|
+
- [Verify your AppModule on every CI run](#recipe-verify-your-appmodule-on-every-ci-run)
|
|
20
|
+
- [Pull commands in from a separate package (plugins)](#recipe-pull-commands-in-from-a-separate-package-plugins)
|
|
21
|
+
- [Configuration reference](#configuration-reference)
|
|
22
|
+
- [How `pristine` finds your AppModule](#how-pristine-finds-your-appmodule)
|
|
23
|
+
- [Built-in commands](#built-in-commands)
|
|
24
|
+
- [Production deployment](#production-deployment)
|
|
25
|
+
- [Architecture & design notes](#architecture--design-notes)
|
|
26
|
+
- [Migrating from older versions](#migrating-from-older-versions)
|
|
27
|
+
- [What changed](#what-changed-versus-pre-10440)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
You have two good options. Pick the one that matches how you'll invoke the CLI.
|
|
34
|
+
|
|
35
|
+
### Option A — Local install (recommended for project-bound usage)
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
npm install --save-dev @pristine-ts/cli
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Use it from your `package.json` scripts — no `npx` needed because npm puts
|
|
42
|
+
`node_modules/.bin/` on PATH for `npm run` invocations:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "pristine build",
|
|
48
|
+
"start": "pristine start",
|
|
49
|
+
"verify": "pristine verify"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
npm run build
|
|
56
|
+
npm run start
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For one-off invocations from the terminal, use `npx pristine ...`.
|
|
60
|
+
|
|
61
|
+
### Option B — Global install (if you want bare `pristine` in any terminal)
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
npm install -g @pristine-ts/cli
|
|
65
|
+
pristine list # works in any directory
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The bin is self-contained, so the global install pulls everything it needs and `pristine`
|
|
69
|
+
becomes available everywhere. This is the Angular/Nest/Vue CLI pattern.
|
|
70
|
+
|
|
71
|
+
> **Tip**: if you want bare `pristine` in your terminal **without** a global install, add
|
|
72
|
+
> `./node_modules/.bin` to your shell PATH per-project. `direnv` works well:
|
|
73
|
+
> create a `.envrc` with `PATH_add node_modules/.bin`, run `direnv allow`, done.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## A 5-minute tour
|
|
78
|
+
|
|
79
|
+
Let's walk through what the CLI looks like in practice. Assume you have a brand-new
|
|
80
|
+
project with just `package.json` and `tsconfig.json`.
|
|
81
|
+
|
|
82
|
+
### 1. Install the CLI
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
npm install --save-dev @pristine-ts/cli
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 2. Run `pristine init`
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
npx pristine init
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`pristine init` is the canonical setup command. It interactively (or via flags in CI):
|
|
95
|
+
|
|
96
|
+
- Asks where your AppModule source file lives (default: `src/app.module.ts`)
|
|
97
|
+
- Asks where the compiled output should land (default: `dist/app.module.js`)
|
|
98
|
+
- Asks which tsconfig to use (default: `tsconfig.json`) and build format (default: `esm`)
|
|
99
|
+
- Writes `pristine.config.ts` with both `sourcePath` AND `outputPath` populated
|
|
100
|
+
- Optionally scaffolds a starter AppModule at the source path (only if it doesn't exist)
|
|
101
|
+
- Optionally adds `build`/`start`/`verify` scripts to your `package.json` (only ones that
|
|
102
|
+
don't already exist — never overwritten)
|
|
103
|
+
- Adds `.pristine/` to your `.gitignore` if one is present
|
|
104
|
+
|
|
105
|
+
The generated config:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import {defineConfig} from "@pristine-ts/cli";
|
|
109
|
+
|
|
110
|
+
export default defineConfig({
|
|
111
|
+
appModule: {
|
|
112
|
+
sourcePath: "src/app.module.ts",
|
|
113
|
+
outputPath: "dist/app.module.js",
|
|
114
|
+
},
|
|
115
|
+
build: {
|
|
116
|
+
tsconfig: "tsconfig.json",
|
|
117
|
+
format: "esm",
|
|
118
|
+
},
|
|
119
|
+
});
|
|
12
120
|
```
|
|
13
121
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
122
|
+
For non-interactive use:
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
pristine init \
|
|
126
|
+
--source-path=src/app.module.ts \
|
|
127
|
+
--output-path=dist/app.module.js \
|
|
128
|
+
--tsconfig=tsconfig.json \
|
|
129
|
+
--format=esm \
|
|
130
|
+
--scaffold \
|
|
131
|
+
--scripts
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 3. Build your project
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
npx pristine build
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`pristine build` runs `tsc` for you and produces `dist/`.
|
|
141
|
+
|
|
142
|
+
### 4. See what's loaded
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
npx pristine info
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Output looks like:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
Pristine CLI
|
|
152
|
+
Version: 1.0.440
|
|
153
|
+
Node: v22.18.0
|
|
154
|
+
Platform: darwin arm64 (24.6.0)
|
|
155
|
+
CWD: /Users/you/projects/my-app
|
|
156
|
+
|
|
157
|
+
Configuration
|
|
158
|
+
Config file: /Users/you/projects/my-app/pristine.config.ts
|
|
159
|
+
AppModule path: dist/app.module.js (from config file)
|
|
160
|
+
|
|
161
|
+
AppModule: my-app
|
|
162
|
+
Imported modules (5):
|
|
163
|
+
- my-app
|
|
164
|
+
- pristine.cli
|
|
165
|
+
- pristine.common
|
|
166
|
+
- pristine.core
|
|
167
|
+
- pristine.logging
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 5. Verify your AppModule boots cleanly
|
|
171
|
+
|
|
172
|
+
```sh
|
|
173
|
+
npx pristine verify
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
This actually starts a kernel from your AppModule, captures every phase outcome (module
|
|
177
|
+
registration, config load, after-init, etc.), runs every registered `InstantiationTest`,
|
|
178
|
+
and exits non-zero if anything fails. Drop it in CI to catch boot regressions before they
|
|
179
|
+
ship.
|
|
180
|
+
|
|
181
|
+
### 6. Run your app
|
|
182
|
+
|
|
183
|
+
```sh
|
|
184
|
+
npx pristine start
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Boots your AppModule, registers SIGTERM/SIGINT handlers, keeps the process alive. If your
|
|
188
|
+
AppModule imports `@pristine-ts/http`, `pristine start` automatically launches an HTTP
|
|
189
|
+
server on `0.0.0.0:3000` (configurable). Send `SIGTERM` and watch graceful shutdown happen.
|
|
190
|
+
|
|
191
|
+
That's the whole loop. The rest of the README is recipes for specific things you'll want
|
|
192
|
+
to do, plus reference material when you need to look something up.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Recipes
|
|
197
|
+
|
|
198
|
+
### Recipe: Add a command to your app
|
|
199
|
+
|
|
200
|
+
You want to type `pristine sync-products` and have your own code run.
|
|
201
|
+
|
|
202
|
+
**1. Write the command class.** It's an injectable class implementing `CommandInterface`,
|
|
203
|
+
decorated with `@tag(ServiceDefinitionTagEnum.Command)`:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
// src/commands/sync-products.command.ts
|
|
207
|
+
import {moduleScoped, ServiceDefinitionTagEnum, tag} from "@pristine-ts/common";
|
|
208
|
+
import {injectable} from "tsyringe";
|
|
209
|
+
import {CommandInterface, ConsoleManager, ExitCodeEnum} from "@pristine-ts/cli";
|
|
210
|
+
import {AppModuleKeyname} from "../app.module.keyname";
|
|
211
|
+
import {ProductService} from "../services/product.service";
|
|
212
|
+
|
|
213
|
+
@tag(ServiceDefinitionTagEnum.Command)
|
|
214
|
+
@moduleScoped(AppModuleKeyname)
|
|
215
|
+
@injectable()
|
|
216
|
+
export class SyncProductsCommand implements CommandInterface<null> {
|
|
217
|
+
optionsType = null;
|
|
218
|
+
name = "sync-products";
|
|
219
|
+
description = "Re-sync the local product cache from upstream.";
|
|
220
|
+
|
|
221
|
+
constructor(
|
|
222
|
+
private readonly consoleManager: ConsoleManager,
|
|
223
|
+
private readonly productService: ProductService,
|
|
224
|
+
) {}
|
|
225
|
+
|
|
226
|
+
async run(): Promise<ExitCodeEnum | number> {
|
|
227
|
+
const count = await this.productService.syncAll();
|
|
228
|
+
this.consoleManager.writeSuccess(`Synced ${count} products.`);
|
|
229
|
+
return ExitCodeEnum.Success;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**2. Make sure your AppModule imports it.** The simplest way: include it in your AppModule's
|
|
235
|
+
`importServices`:
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
// src/app.module.ts
|
|
239
|
+
import {AppModuleInterface} from "@pristine-ts/common";
|
|
240
|
+
import {CliModule} from "@pristine-ts/cli";
|
|
241
|
+
import {CoreModule} from "@pristine-ts/core";
|
|
242
|
+
import {SyncProductsCommand} from "./commands/sync-products.command";
|
|
243
|
+
|
|
244
|
+
export const AppModule: AppModuleInterface = {
|
|
245
|
+
keyname: "my-app",
|
|
246
|
+
importModules: [CoreModule, CliModule],
|
|
247
|
+
importServices: [SyncProductsCommand],
|
|
248
|
+
};
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**3. Build and run.**
|
|
252
|
+
|
|
253
|
+
```sh
|
|
254
|
+
npx pristine build
|
|
255
|
+
npx pristine sync-products
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Output:
|
|
259
|
+
|
|
260
|
+
```
|
|
261
|
+
✔ Success: Synced 142 products.
|
|
262
|
+
[status:'Success', code:'0'] - Command 'sync-products' exited.
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
#### With typed CLI flags
|
|
266
|
+
|
|
267
|
+
Need `--limit=100 --dry-run`? Define an options class with `class-validator` decorators:
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
// src/commands/sync-products.command-options.ts
|
|
271
|
+
import "reflect-metadata";
|
|
272
|
+
import {IsBoolean, IsNumber, IsOptional} from "@pristine-ts/class-validator";
|
|
273
|
+
|
|
274
|
+
export class SyncProductsOptions {
|
|
275
|
+
@IsOptional() @IsNumber() limit?: number;
|
|
276
|
+
@IsOptional() @IsBoolean() "dry-run"?: boolean;
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Then in the command, set `optionsType` to a fresh instance and read `args` in `run`:
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
@injectable()
|
|
284
|
+
export class SyncProductsCommand implements CommandInterface<SyncProductsOptions> {
|
|
285
|
+
optionsType = new SyncProductsOptions();
|
|
286
|
+
name = "sync-products";
|
|
287
|
+
description = "Re-sync the local product cache from upstream.";
|
|
288
|
+
|
|
289
|
+
async run(args: SyncProductsOptions): Promise<ExitCodeEnum | number> {
|
|
290
|
+
const limit = args.limit ?? Infinity;
|
|
291
|
+
const dryRun = args["dry-run"] === true;
|
|
292
|
+
// ...
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
`npx pristine sync-products --limit=50 --dry-run` parses, validates, and passes the typed
|
|
298
|
+
options into `run`. Validation failures exit non-zero and print the constraint errors.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
### Recipe: Build your TypeScript
|
|
303
|
+
|
|
304
|
+
`pristine build` is a `tsc` wrapper that ALSO writes a build manifest at
|
|
305
|
+
`.pristine/build-manifest.json` so downstream commands can detect when the build is
|
|
306
|
+
stale (source edited, output deleted, paths reconfigured).
|
|
307
|
+
|
|
308
|
+
For most projects, the only thing you need is the default config produced by `pristine init`:
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
// pristine.config.ts
|
|
312
|
+
import {defineConfig} from "@pristine-ts/cli";
|
|
313
|
+
|
|
314
|
+
export default defineConfig({
|
|
315
|
+
appModule: {path: "dist/app.module.js"},
|
|
316
|
+
build: {
|
|
317
|
+
outDir: "dist",
|
|
318
|
+
tsconfig: "tsconfig.json",
|
|
319
|
+
format: "esm", // "esm" | "cjs" | "both"
|
|
320
|
+
clean: true, // wipe outDir before each build
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
```sh
|
|
326
|
+
npx pristine build
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
#### Building both ESM and CJS
|
|
330
|
+
|
|
331
|
+
If you publish a library that needs both:
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
build: {
|
|
335
|
+
format: "both", // runs tsconfig.json then tsconfig.cjs.json sequentially
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
You need a `tsconfig.cjs.json` sibling that targets CommonJS. The CLI looks for it
|
|
340
|
+
automatically when format is `"both"` or `"cjs"`.
|
|
341
|
+
|
|
342
|
+
#### Custom tsconfig path
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
build: {
|
|
346
|
+
tsconfig: "tsconfig.build.json",
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
#### The build manifest
|
|
351
|
+
|
|
352
|
+
After a successful build, `pristine build` writes
|
|
353
|
+
`<project>/.pristine/build-manifest.json`:
|
|
354
|
+
|
|
355
|
+
```json
|
|
356
|
+
{
|
|
357
|
+
"appModuleSourcePath": "/abs/path/src/app.module.ts",
|
|
358
|
+
"appModuleOutputPath": "/abs/path/dist/app.module.js",
|
|
359
|
+
"sourceHash": "sha256:...",
|
|
360
|
+
"builtAt": "2026-05-11T00:00:00.000Z"
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Every command that loads your AppModule (`pristine start`, `pristine verify`, etc.) reads
|
|
365
|
+
this file to confirm the compiled output matches your current source. If the manifest is
|
|
366
|
+
**stale** (source edited since last build, output deleted, paths reconfigured), the CLI:
|
|
367
|
+
|
|
368
|
+
- **In a TTY**: prints what's stale and prompts: "Run `pristine build` now to refresh? [Y/n]".
|
|
369
|
+
On Yes, runs the build inline and continues. On No, exits.
|
|
370
|
+
- **Non-TTY** (CI, Docker): prints the same explanation and exits non-zero. CI never
|
|
371
|
+
auto-rebuilds — that hides bugs.
|
|
372
|
+
|
|
373
|
+
Examples of stale states and what they mean:
|
|
374
|
+
|
|
375
|
+
| Reason | What happened |
|
|
376
|
+
|--------|---------------|
|
|
377
|
+
| `Missing` | No manifest yet. Run `pristine build`. |
|
|
378
|
+
| `SourcePathChanged` | You edited `appModule.sourcePath` in the config. Rebuild. |
|
|
379
|
+
| `OutputPathChanged` | You edited `appModule.outputPath` in the config. Rebuild. |
|
|
380
|
+
| `SourceContentChanged` | The source file's bytes don't match the hash from the last build. Rebuild. |
|
|
381
|
+
| `OutputMissing` | The compiled file referenced by the manifest is no longer on disk. Rebuild. |
|
|
382
|
+
|
|
383
|
+
The manifest only ships when both `appModule.sourcePath` and `appModule.outputPath` are
|
|
384
|
+
configured (which `pristine init` does for you). Without them, `pristine build` still works
|
|
385
|
+
as a thin `tsc` wrapper but doesn't produce a manifest, and downstream commands skip the
|
|
386
|
+
staleness check.
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
### Recipe: Start your app in production
|
|
391
|
+
|
|
392
|
+
`pristine start` is a real production entry point — boots your AppModule, runs every
|
|
393
|
+
registered `RuntimeServer` (HTTP, etc.), handles SIGTERM/SIGINT with graceful shutdown.
|
|
394
|
+
|
|
395
|
+
#### The simplest case
|
|
396
|
+
|
|
397
|
+
```sh
|
|
398
|
+
npx pristine start
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
If your AppModule has no HTTP/queue/etc. modules imported, this just boots the kernel and
|
|
402
|
+
waits for a signal. Useful as a worker-style entry that consumes events through other
|
|
403
|
+
mechanisms (cron, CLI args, etc.).
|
|
404
|
+
|
|
405
|
+
#### With graceful shutdown
|
|
406
|
+
|
|
407
|
+
Add an `onShutdown` hook to your modules to release resources cleanly when the process
|
|
408
|
+
gets SIGTERM:
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
import {ModuleInterface} from "@pristine-ts/common";
|
|
412
|
+
|
|
413
|
+
export const DatabaseModule: ModuleInterface = {
|
|
414
|
+
keyname: "my-app.database",
|
|
415
|
+
// ... onInit, providerRegistrations, etc.
|
|
416
|
+
onShutdown: async (container) => {
|
|
417
|
+
const pool = container.resolve(DatabaseConnectionPool);
|
|
418
|
+
await pool.drain(); // wait for in-flight queries
|
|
419
|
+
await pool.close(); // release sockets
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
When `pristine start` receives SIGTERM:
|
|
425
|
+
1. Signal handler fires.
|
|
426
|
+
2. `Kernel.stop()` walks every imported module's `onShutdown` in outer-to-inner order
|
|
427
|
+
(your AppModule first, leaf dependencies last) so a higher-level module can still call
|
|
428
|
+
into its dependencies during teardown.
|
|
429
|
+
3. Each hook gets a 10-second timeout. Misbehaving hooks log a warning and shutdown
|
|
430
|
+
continues.
|
|
431
|
+
4. After all hooks complete, the process exits 0.
|
|
432
|
+
5. If shutdown takes longer than 30 seconds total, the process is force-killed (so
|
|
433
|
+
Kubernetes / ECS / systemd are never stuck waiting).
|
|
434
|
+
|
|
435
|
+
A second SIGTERM/SIGINT during shutdown bypasses the rest of the wait and exits
|
|
436
|
+
immediately with code 130.
|
|
437
|
+
|
|
438
|
+
#### In a Dockerfile
|
|
439
|
+
|
|
440
|
+
```dockerfile
|
|
441
|
+
FROM node:22-slim
|
|
442
|
+
WORKDIR /app
|
|
443
|
+
COPY package*.json ./
|
|
444
|
+
RUN npm ci --omit=dev
|
|
445
|
+
RUN npm install -g @pristine-ts/cli
|
|
446
|
+
COPY dist/ ./dist/
|
|
447
|
+
COPY pristine.config.js ./ # if your config file is .ts, compile it or use .js
|
|
448
|
+
CMD ["pristine", "start"]
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
`pristine start` is the canonical container entry. If you'd rather use bare Node, that
|
|
452
|
+
also works: `CMD ["node", "dist/main.js"]`. Both are supported; `pristine start` adds
|
|
453
|
+
graceful-shutdown wiring you'd otherwise have to write yourself.
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
### Recipe: Host an HTTP (or HTTPS) server
|
|
458
|
+
|
|
459
|
+
If your AppModule imports `@pristine-ts/http`, `pristine start` automatically launches the
|
|
460
|
+
built-in `KernelHttpServer`. Every incoming request goes through `kernel.handle()` →
|
|
461
|
+
the `@pristine-ts/networking` `Router` → your controllers. No glue code needed.
|
|
462
|
+
|
|
463
|
+
**1. Set up the AppModule.**
|
|
464
|
+
|
|
465
|
+
```ts
|
|
466
|
+
// src/app.module.ts
|
|
467
|
+
import {AppModuleInterface} from "@pristine-ts/common";
|
|
468
|
+
import {CoreModule} from "@pristine-ts/core";
|
|
469
|
+
import {HttpModule} from "@pristine-ts/http";
|
|
470
|
+
import {NetworkingModule} from "@pristine-ts/networking";
|
|
471
|
+
import {DogsController} from "./controllers/dogs.controller";
|
|
472
|
+
|
|
473
|
+
export const AppModule: AppModuleInterface = {
|
|
474
|
+
keyname: "my-app",
|
|
475
|
+
importModules: [CoreModule, HttpModule, NetworkingModule],
|
|
476
|
+
importServices: [DogsController],
|
|
477
|
+
};
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**2. Write a controller.** Standard Pristine — nothing CLI-specific:
|
|
481
|
+
|
|
482
|
+
```ts
|
|
483
|
+
// src/controllers/dogs.controller.ts
|
|
484
|
+
import {injectable} from "tsyringe";
|
|
485
|
+
import {controller, HttpMethod, route} from "@pristine-ts/networking";
|
|
486
|
+
|
|
487
|
+
@injectable()
|
|
488
|
+
@controller("/dogs")
|
|
489
|
+
export class DogsController {
|
|
490
|
+
@route(HttpMethod.Get, "")
|
|
491
|
+
list() {
|
|
492
|
+
return [{name: "Peach"}, {name: "Banjo"}];
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
**3. Build and start.**
|
|
498
|
+
|
|
499
|
+
```sh
|
|
500
|
+
npx pristine build
|
|
501
|
+
npx pristine start
|
|
502
|
+
# Pristine app running with 1 server(s): http. Send SIGTERM (or Ctrl+C) to stop.
|
|
503
|
+
|
|
504
|
+
curl http://localhost:3000/dogs
|
|
505
|
+
# [{"name":"Peach"},{"name":"Banjo"}]
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
#### Customizing port and address
|
|
509
|
+
|
|
510
|
+
Three layers, highest priority first. Use whichever fits.
|
|
511
|
+
|
|
512
|
+
```sh
|
|
513
|
+
# CLI flag — one-off override
|
|
514
|
+
npx pristine start --port=4000 --address=127.0.0.1
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
```ts
|
|
518
|
+
// Config file — project-level default
|
|
519
|
+
export default defineConfig({
|
|
520
|
+
appModule: {path: "dist/app.module.js"},
|
|
521
|
+
kernelConfiguration: {
|
|
522
|
+
"pristine.http.kernel-server.port": 4000,
|
|
523
|
+
"pristine.http.kernel-server.address": "127.0.0.1",
|
|
524
|
+
},
|
|
525
|
+
});
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
```sh
|
|
529
|
+
# Environment variable — deploy-time override
|
|
530
|
+
PRISTINE_HTTP_KERNEL_SERVER_PORT=4000 \
|
|
531
|
+
PRISTINE_HTTP_KERNEL_SERVER_ADDRESS=0.0.0.0 \
|
|
532
|
+
pristine start
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
Defaults: `0.0.0.0:3000`.
|
|
536
|
+
|
|
537
|
+
#### Switching to HTTPS
|
|
538
|
+
|
|
539
|
+
Set the TLS key and cert paths and the server flips from `http.Server` to `https.Server`
|
|
540
|
+
automatically:
|
|
541
|
+
|
|
542
|
+
```sh
|
|
543
|
+
PRISTINE_HTTP_KERNEL_SERVER_TLS_KEY_PATH=/etc/ssl/key.pem \
|
|
544
|
+
PRISTINE_HTTP_KERNEL_SERVER_TLS_CERT_PATH=/etc/ssl/cert.pem \
|
|
545
|
+
pristine start
|
|
546
|
+
# KernelHttpServer: listening on https://0.0.0.0:3000
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
Or via config:
|
|
550
|
+
|
|
551
|
+
```ts
|
|
552
|
+
kernelConfiguration: {
|
|
553
|
+
"pristine.http.kernel-server.tls.key-path": "/etc/ssl/key.pem",
|
|
554
|
+
"pristine.http.kernel-server.tls.cert-path": "/etc/ssl/cert.pem",
|
|
555
|
+
}
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
When both paths are set to non-empty values, the server reads the PEM files at boot and
|
|
559
|
+
serves HTTPS. The `name` it reports flips from `"http"` to `"https"`.
|
|
560
|
+
|
|
561
|
+
#### Graceful drain
|
|
562
|
+
|
|
563
|
+
When SIGTERM hits, `KernelHttpServer.stop()` is called via `HttpModule.onShutdown` — it
|
|
564
|
+
calls `server.close()` (refuses new connections, lets in-flight requests finish), waits up
|
|
565
|
+
to 10 seconds for connection drain, then force-closes any remaining sockets. Matches what
|
|
566
|
+
container orchestrators expect during a rolling deploy.
|
|
567
|
+
|
|
568
|
+
#### Adding more server types
|
|
569
|
+
|
|
570
|
+
Any module can register a long-running server by implementing `RuntimeServerInterface`
|
|
571
|
+
and tagging it. `pristine start` discovers and launches it alongside the HTTP server:
|
|
572
|
+
|
|
573
|
+
```ts
|
|
574
|
+
@tag(ServiceDefinitionTagEnum.RuntimeServer)
|
|
575
|
+
@moduleScoped(MyModuleKeyname)
|
|
576
|
+
@injectable()
|
|
577
|
+
export class GrpcServer implements RuntimeServerInterface {
|
|
578
|
+
name = "grpc";
|
|
579
|
+
async start(overrides) { /* ... */ }
|
|
580
|
+
async stop() { /* ... */ }
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
This is how future gRPC, websocket, or queue-listener modules will plug into `pristine
|
|
585
|
+
start` — no `@pristine-ts/cli` changes required.
|
|
586
|
+
|
|
587
|
+
---
|
|
588
|
+
|
|
589
|
+
### Recipe: Verify your AppModule on every CI run
|
|
590
|
+
|
|
591
|
+
`pristine verify` runs a fresh kernel boot of your AppModule on a throw-away kernel,
|
|
592
|
+
captures per-phase outcomes (module registration, config check/load, after-init, etc.),
|
|
593
|
+
and runs every registered `InstantiationTestInterface`. Returns non-zero if anything
|
|
594
|
+
fails. Perfect for CI.
|
|
595
|
+
|
|
596
|
+
#### In your CI pipeline
|
|
597
|
+
|
|
598
|
+
```yaml
|
|
599
|
+
# .github/workflows/ci.yml
|
|
600
|
+
- run: npm ci
|
|
601
|
+
- run: npm run build
|
|
602
|
+
- run: npx pristine verify
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
#### Adding your own boot-time health checks
|
|
606
|
+
|
|
607
|
+
Want CI to fail if your DB credentials are wrong? Implement `InstantiationTestInterface`:
|
|
608
|
+
|
|
609
|
+
```ts
|
|
610
|
+
// src/health/database-connectivity.test.ts
|
|
611
|
+
import {tag, ServiceDefinitionTagEnum} from "@pristine-ts/common";
|
|
612
|
+
import {injectable, DependencyContainer} from "tsyringe";
|
|
613
|
+
import {InstantiationTestInterface, InstantiationTestResultInterface} from "@pristine-ts/core";
|
|
614
|
+
import {DatabaseClient} from "../database/database.client";
|
|
615
|
+
|
|
616
|
+
@tag(ServiceDefinitionTagEnum.InstantiationTest)
|
|
617
|
+
@injectable()
|
|
618
|
+
export class DatabaseConnectivityTest implements InstantiationTestInterface {
|
|
619
|
+
name = "database connectivity";
|
|
620
|
+
description = "Pings the database to confirm credentials and network reachability.";
|
|
621
|
+
|
|
622
|
+
async run(container: DependencyContainer): Promise<InstantiationTestResultInterface> {
|
|
623
|
+
try {
|
|
624
|
+
await container.resolve(DatabaseClient).ping();
|
|
625
|
+
return {passed: true};
|
|
626
|
+
} catch (e) {
|
|
627
|
+
return {passed: false, message: (e as Error).message};
|
|
18
628
|
}
|
|
629
|
+
}
|
|
19
630
|
}
|
|
20
|
-
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
Register it the same way you would any service (via `importServices` or by `@tag`
|
|
634
|
+
self-registration). `pristine verify` will discover and run it automatically.
|
|
635
|
+
|
|
636
|
+
To skip the health-test phase (and only verify the boot phases):
|
|
21
637
|
|
|
638
|
+
```sh
|
|
639
|
+
npx pristine verify --skip-tests
|
|
22
640
|
```
|
|
23
641
|
|
|
24
|
-
|
|
642
|
+
---
|
|
643
|
+
|
|
644
|
+
### Recipe: Pull commands in from a separate package (plugins)
|
|
645
|
+
|
|
646
|
+
Custom commands can live in their own npm package. Useful for tooling-only commands
|
|
647
|
+
(generators, codemods, linters) you don't want loaded into your runtime AppModule.
|
|
25
648
|
|
|
649
|
+
**Plugin author** publishes a package that exports one or more `*Module` symbols:
|
|
650
|
+
|
|
651
|
+
```ts
|
|
652
|
+
// my-plugin/src/index.ts
|
|
653
|
+
import {ModuleInterface} from "@pristine-ts/common";
|
|
654
|
+
import {moduleScoped, ServiceDefinitionTagEnum, tag} from "@pristine-ts/common";
|
|
655
|
+
import {injectable} from "tsyringe";
|
|
656
|
+
import {CommandInterface, ConsoleManager, ExitCodeEnum} from "@pristine-ts/cli";
|
|
657
|
+
|
|
658
|
+
@tag(ServiceDefinitionTagEnum.Command)
|
|
659
|
+
@moduleScoped("my-plugin")
|
|
660
|
+
@injectable()
|
|
661
|
+
export class HelloCommand implements CommandInterface<null> {
|
|
662
|
+
optionsType = null;
|
|
663
|
+
name = "my-plugin:hello";
|
|
664
|
+
description = "Says hello.";
|
|
665
|
+
constructor(private readonly consoleManager: ConsoleManager) {}
|
|
666
|
+
async run() { this.consoleManager.writeLine("Hello!"); return ExitCodeEnum.Success; }
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
export const MyPluginModule: ModuleInterface = {
|
|
670
|
+
keyname: "my-plugin",
|
|
671
|
+
// No providerRegistrations needed — the @tag decorator self-registers HelloCommand
|
|
672
|
+
// as soon as the file is imported (which happens when this module is loaded).
|
|
673
|
+
};
|
|
26
674
|
```
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
675
|
+
|
|
676
|
+
**Consumer** installs and declares it:
|
|
677
|
+
|
|
678
|
+
```sh
|
|
679
|
+
npm install --save-dev my-plugin
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
```ts
|
|
683
|
+
// pristine.config.ts
|
|
684
|
+
import {defineConfig} from "@pristine-ts/cli";
|
|
685
|
+
|
|
686
|
+
export default defineConfig({
|
|
687
|
+
appModule: {path: "dist/app.module.js"},
|
|
688
|
+
plugins: [
|
|
689
|
+
"my-plugin",
|
|
690
|
+
// Or: {name: "@my-org/codegen", options: {/* reserved for future use */}},
|
|
691
|
+
],
|
|
692
|
+
});
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
```sh
|
|
696
|
+
npx pristine my-plugin:hello
|
|
697
|
+
# Hello!
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
Plugins are resolved from the **consumer's** `node_modules` (via `createRequire` anchored
|
|
701
|
+
at the config file's location), so monorepos with hoisted deps work out of the box.
|
|
702
|
+
|
|
703
|
+
#### Failure modes
|
|
704
|
+
|
|
705
|
+
- Missing plugin → clear stderr error, CLI continues without it (built-in commands like
|
|
706
|
+
`pristine p:config:print` still work for debugging).
|
|
707
|
+
- Plugin exports no `*Module` symbols → loud error (silent loading is a footgun).
|
|
708
|
+
- Two commands collide on `name` → stderr warning at boot listing the count, first
|
|
709
|
+
registered match dispatches. Rename one to fix.
|
|
710
|
+
|
|
711
|
+
#### Diagnostics
|
|
712
|
+
|
|
713
|
+
`pristine info` lists every loaded plugin under a dedicated `Plugins (N)` section.
|
|
714
|
+
|
|
715
|
+
---
|
|
716
|
+
|
|
717
|
+
## Configuration reference
|
|
718
|
+
|
|
719
|
+
The canonical config file is **`pristine.config.ts`** at your project root.
|
|
720
|
+
|
|
721
|
+
```ts
|
|
722
|
+
import {defineConfig} from "@pristine-ts/cli";
|
|
723
|
+
|
|
724
|
+
export default defineConfig({
|
|
725
|
+
appModule: {
|
|
726
|
+
path: "dist/app.module.js", // required for non-trivial setups
|
|
727
|
+
export: "AppModule", // default; override only for unusual setups
|
|
728
|
+
},
|
|
729
|
+
|
|
730
|
+
build: {
|
|
731
|
+
outDir: "dist", // tsc's outDir is what actually controls output
|
|
732
|
+
tsconfig: "tsconfig.json",
|
|
733
|
+
format: "esm", // "esm" | "cjs" | "both"
|
|
734
|
+
clean: false, // wipe outDir before each build
|
|
31
735
|
},
|
|
32
736
|
|
|
33
|
-
|
|
737
|
+
start: {
|
|
738
|
+
// Reserved for upcoming features (entry, watch, nodeArgs).
|
|
739
|
+
},
|
|
740
|
+
|
|
741
|
+
plugins: [
|
|
742
|
+
"my-plugin",
|
|
743
|
+
{name: "@my-org/codegen"},
|
|
744
|
+
],
|
|
745
|
+
|
|
746
|
+
kernelConfiguration: {
|
|
747
|
+
// Any configuration value your modules expect — these are passed through to
|
|
748
|
+
// `kernel.start(appModule, kernelConfiguration)` so they take effect during boot.
|
|
749
|
+
"pristine.http.kernel-server.port": 4000,
|
|
750
|
+
"pristine.logging.logSeverityLevelConfiguration": 1,
|
|
751
|
+
},
|
|
752
|
+
});
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
All fields are optional. The CLI applies sensible defaults wherever a field is absent.
|
|
756
|
+
|
|
757
|
+
### Supported file formats
|
|
758
|
+
|
|
759
|
+
The CLI looks for these names in order, walking **up** from `process.cwd()` until it
|
|
760
|
+
finds a match (so a CLI invocation from `packages/foo/` in a monorepo finds the root
|
|
761
|
+
config):
|
|
762
|
+
|
|
763
|
+
1. `pristine.config.ts` — recommended; full IDE autocomplete via `defineConfig`
|
|
764
|
+
2. `pristine.config.mts`
|
|
765
|
+
3. `pristine.config.cts`
|
|
766
|
+
4. `pristine.config.js`
|
|
767
|
+
5. `pristine.config.mjs`
|
|
768
|
+
6. `pristine.config.cjs`
|
|
769
|
+
|
|
770
|
+
`.ts` configs load at runtime via `jiti` — no separate compile step needed.
|
|
771
|
+
|
|
772
|
+
### Inspecting the resolved config
|
|
773
|
+
|
|
774
|
+
```sh
|
|
775
|
+
npx pristine p:config:print
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
Prints the loaded config as JSON, plus the file path it came from and per-field
|
|
779
|
+
provenance markers. Use this when discovery is doing something unexpected.
|
|
780
|
+
|
|
781
|
+
---
|
|
782
|
+
|
|
783
|
+
## How `pristine` finds your AppModule
|
|
784
|
+
|
|
785
|
+
When the CLI starts, it walks this cascade. The first match wins.
|
|
786
|
+
|
|
787
|
+
```
|
|
788
|
+
1. pristine.config.ts → appModule.path
|
|
789
|
+
↓ (not set?)
|
|
790
|
+
2. package.json → pristine.appModule.path
|
|
791
|
+
↓ (deprecated alias: pristine.appModule.cjsPath, prints warning)
|
|
792
|
+
↓ (not set?)
|
|
793
|
+
3. .pristine/last-app-module ← cached selection from a previous TTY prompt
|
|
794
|
+
↓ (not set?)
|
|
795
|
+
4. Convention scan: dist/, dist/lib/cjs/, dist/lib/esm/, build/, .
|
|
796
|
+
for *.module.{js,mjs,cjs}
|
|
797
|
+
├── named app.module.* → score 0
|
|
798
|
+
└── exports an AppModule symbol → score 10
|
|
799
|
+
── one match? → use it
|
|
800
|
+
── multiple equally-ranked + TTY? → prompt
|
|
801
|
+
── multiple equally-ranked + no TTY? → exit with actionable error
|
|
802
|
+
↓ (no candidates?)
|
|
803
|
+
5. Legacy node_modules/@pristine-ts/* scan (synthetic AppModule)
|
|
804
|
+
↓ (still nothing?)
|
|
805
|
+
6. Built-in CliModule fallback (so p:help etc. always work)
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
If a configured AppModule path can't be loaded (file missing, import error), the CLI
|
|
809
|
+
warns to stderr and falls back to the CliModule fallback. Built-in commands like
|
|
810
|
+
`pristine p:config:print` still work so you can debug.
|
|
811
|
+
|
|
812
|
+
### Module formats
|
|
813
|
+
|
|
814
|
+
The loader accepts:
|
|
815
|
+
|
|
816
|
+
| Extension | Loaded as | Example |
|
|
817
|
+
|-----------|-----------|---------|
|
|
818
|
+
| `.js` (CJS) | CommonJS | tsc's default output |
|
|
819
|
+
| `.cjs` | CommonJS (explicit) | |
|
|
820
|
+
| `.mjs` | ESM | |
|
|
821
|
+
| `.js` in a `"type": "module"` package | ESM (via package context) | |
|
|
822
|
+
|
|
823
|
+
All loaded via Node's real dynamic `import()` (with `pathToFileURL` for absolute paths).
|
|
824
|
+
|
|
825
|
+
---
|
|
826
|
+
|
|
827
|
+
## Built-in commands
|
|
828
|
+
|
|
829
|
+
Every framework-reserved command has a canonical `p:`-prefixed name and a top-level
|
|
830
|
+
alias. Use whichever you prefer.
|
|
831
|
+
|
|
832
|
+
| Command | Alias | What it does |
|
|
833
|
+
|---------|-------|--------------|
|
|
834
|
+
| `pristine p:init` | `init` | Scaffold a new project setup interactively (or via flags). Writes `pristine.config.ts`, optional starter AppModule, optional npm scripts. Refuses to overwrite an existing config. |
|
|
835
|
+
| `pristine p:help` | `help` | Print usage and list every registered command (built-in + custom) with descriptions. |
|
|
836
|
+
| `pristine p:list` | `list` | Print every registered command name (compact form). |
|
|
837
|
+
| `pristine p:info` | `info` | Print framework version, Node, OS, resolved config path, AppModule location, imported module list. Useful for support tickets. |
|
|
838
|
+
| `pristine p:build` | `build` | Compile your TypeScript via `tsc` and write the build manifest. Reads `build.{outDir,tsconfig,format,clean}` and `appModule.{sourcePath,outputPath}` from config. |
|
|
839
|
+
| `pristine p:start` | `start` | Boot the AppModule and run until SIGTERM/SIGINT. Auto-starts every registered `RuntimeServer` (HTTP, etc.). Production-grade. Supports `--port` / `--address`. Prompts to rebuild if the manifest is stale. |
|
|
840
|
+
| `pristine p:verify` | `verify` | Boot a fresh kernel of your AppModule, run all registered `InstantiationTest`s. Exits non-zero on failure. `--skip-tests` skips the test phase. |
|
|
841
|
+
| `pristine p:config:init` | — | Legacy helper that migrates a `pristine.appModule.{path,cjsPath}` field from `package.json` to a minimal config file. Prefer `pristine init` for new projects. |
|
|
842
|
+
| `pristine p:config:print` | — | Print the resolved config + file path it loaded from + per-field provenance. |
|
|
843
|
+
|
|
844
|
+
`config:*` commands intentionally don't have top-level aliases — they're sub-commands by
|
|
845
|
+
design.
|
|
846
|
+
|
|
847
|
+
---
|
|
848
|
+
|
|
849
|
+
## Production deployment
|
|
850
|
+
|
|
851
|
+
You have two equally supported entry points.
|
|
852
|
+
|
|
853
|
+
### Option A — `node dist/main.js`
|
|
854
|
+
|
|
855
|
+
Traditional. Your `dist/main.js` is the entry. No `@pristine-ts/cli` needed in the deploy
|
|
856
|
+
unit. Lifecycle, signal handling, and graceful shutdown are your responsibility.
|
|
857
|
+
|
|
858
|
+
### Option B — `pristine start`
|
|
859
|
+
|
|
860
|
+
`pristine start` is itself a production-grade entry. It boots your AppModule, starts every
|
|
861
|
+
registered `RuntimeServer` (HTTP server, etc.), handles SIGTERM/SIGINT with graceful
|
|
862
|
+
shutdown, enforces a hard-exit timeout, and keeps the event loop alive on its own.
|
|
863
|
+
|
|
864
|
+
Install once on the host:
|
|
865
|
+
|
|
866
|
+
```sh
|
|
867
|
+
npm install -g @pristine-ts/cli
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
Then in your Dockerfile / systemd unit / process manager:
|
|
871
|
+
|
|
872
|
+
```sh
|
|
873
|
+
pristine start
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
If `@pristine-ts/http` is in your AppModule, an HTTP server is launched automatically.
|
|
877
|
+
Configure port/address/TLS via env vars (see [HTTP recipe](#recipe-host-an-http-or-https-server)).
|
|
878
|
+
|
|
879
|
+
---
|
|
880
|
+
|
|
881
|
+
## Architecture & design notes
|
|
882
|
+
|
|
883
|
+
The `pristine` bin file is a thin shim:
|
|
884
|
+
|
|
885
|
+
```js
|
|
886
|
+
require("reflect-metadata");
|
|
887
|
+
require("@pristine-ts/cli").bootstrap();
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
This is deliberate. An earlier design bundled the entire CLI into the bin file; this
|
|
891
|
+
caused a "TypeInfo not known for X" error in real consumer projects because tsyringe's
|
|
892
|
+
decorator metadata is keyed by class identity, and bundling produced a *second* set of
|
|
893
|
+
class identities (the bundled copy) that didn't share metadata with the consumer's
|
|
894
|
+
`node_modules`-loaded copy.
|
|
895
|
+
|
|
896
|
+
The current design loads `@pristine-ts/cli` from the consumer's `node_modules`. This
|
|
897
|
+
guarantees that whichever `@pristine-ts/cli` class your AppModule imports is the same
|
|
898
|
+
physical class the bin reaches for — single identity, single decorator metadata
|
|
899
|
+
registration, no mismatch.
|
|
900
|
+
|
|
901
|
+
Side effect: `reflect-metadata`, `tsyringe`, and `class-transformer` are NOT declared as
|
|
902
|
+
direct dependencies of `@pristine-ts/cli`. They come transitively through
|
|
903
|
+
`@pristine-ts/common` (which every Pristine package depends on). Declaring them directly
|
|
904
|
+
would cause npm to install duplicate copies in `packages/cli/node_modules/`, and
|
|
905
|
+
`reflect-metadata` in particular keeps its decorator WeakMap inside the module closure —
|
|
906
|
+
two copies = two WeakMaps = silently lost metadata.
|
|
907
|
+
|
|
908
|
+
The bin itself is bundled with `esbuild` for fast startup, but with all `@pristine-ts/*`
|
|
909
|
+
packages marked external so the cross-realm trap doesn't reappear.
|
|
910
|
+
|
|
911
|
+
---
|
|
912
|
+
|
|
913
|
+
## Migrating from older versions
|
|
914
|
+
|
|
915
|
+
### From the `package.json` `pristine.appModule.cjsPath` field
|
|
916
|
+
|
|
917
|
+
The old setup required:
|
|
918
|
+
|
|
919
|
+
```json
|
|
920
|
+
{
|
|
921
|
+
"pristine": {
|
|
922
|
+
"appModule": { "cjsPath": "dist/lib/cjs/app.module.js" }
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
Both `pristine.appModule.cjsPath` (deprecated, prints warning) and `pristine.appModule.path`
|
|
928
|
+
(new, format-agnostic) still work for one minor version cycle. To migrate cleanly:
|
|
929
|
+
|
|
930
|
+
```sh
|
|
931
|
+
npx pristine p:config:init
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
The command detects the existing `pristine.appModule.{path,cjsPath}` field, generates a
|
|
935
|
+
`pristine.config.ts` with the path migrated, and tells you to delete the `pristine` field
|
|
936
|
+
from `package.json`.
|
|
937
|
+
|
|
938
|
+
### From manual bootstrap in `main.ts`
|
|
939
|
+
|
|
940
|
+
If your old setup looked like:
|
|
941
|
+
|
|
942
|
+
```ts
|
|
943
|
+
// main.ts (old)
|
|
944
|
+
import {Kernel} from "@pristine-ts/core";
|
|
945
|
+
import http from "http";
|
|
946
|
+
import {AppModule} from "./app.module";
|
|
947
|
+
|
|
948
|
+
const kernel = new Kernel();
|
|
949
|
+
await kernel.start(AppModule);
|
|
950
|
+
|
|
951
|
+
http.createServer(async (req, res) => {
|
|
952
|
+
// ... wire req → kernel.handle → res
|
|
953
|
+
}).listen(3000);
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
You can drop all of that and just use `pristine start`. Make sure `@pristine-ts/http` is
|
|
957
|
+
in your AppModule's `importModules` and the server starts automatically with the same
|
|
958
|
+
routing pipeline (no behavior changes).
|
|
959
|
+
|
|
960
|
+
---
|
|
961
|
+
|
|
962
|
+
## What changed (versus pre-1.0.440)
|
|
963
|
+
|
|
964
|
+
**Phase 7 (this release):**
|
|
965
|
+
|
|
966
|
+
- **`pristine init` command.** Interactive (or flag-driven) scaffold: writes
|
|
967
|
+
`pristine.config.ts` with both `sourcePath` and `outputPath`, optionally creates a starter
|
|
968
|
+
AppModule, optionally adds `build`/`start`/`verify` scripts to `package.json`, optionally
|
|
969
|
+
adds `.pristine/` to `.gitignore`. Never overwrites existing files.
|
|
970
|
+
- **Explicit source + output paths in config.** `appModule.path` deprecated;
|
|
971
|
+
`appModule.sourcePath` (what `pristine build` compiles) and `appModule.outputPath`
|
|
972
|
+
(what runtime commands load) replace it. Old `path` field still works for one minor
|
|
973
|
+
cycle with a warning.
|
|
974
|
+
- **Build manifest at `.pristine/build-manifest.json`.** Written atomically by
|
|
975
|
+
`pristine build` after successful compile. Records source path, output path, source
|
|
976
|
+
content hash, build timestamp.
|
|
977
|
+
- **Staleness detection.** `pristine start`/`verify`/etc. read the manifest before loading
|
|
978
|
+
the AppModule. Stale manifests (source edited, output missing, paths reconfigured) are
|
|
979
|
+
detected and surfaced with a specific reason. In a TTY, the user is prompted to rebuild
|
|
980
|
+
inline; in CI, the bin exits non-zero with the explanation.
|
|
981
|
+
- **Legacy `path` field still works.** With a deprecation warning pointing users at
|
|
982
|
+
`pristine init` for migration.
|
|
983
|
+
|
|
984
|
+
**Phase 6:**
|
|
985
|
+
|
|
986
|
+
- **End-to-end smoke tests for the bin.** `tests/cli` exercises every command via the
|
|
987
|
+
actual built `pristine` binary spawned by jest.
|
|
988
|
+
- **`pristine.config.ts` migration in `tests/cli`.** The old `package.json`
|
|
989
|
+
`pristine.appModule.cjsPath` field was removed in favor of a real `pristine.config.ts`.
|
|
990
|
+
- **CI runs the e2e suite.** `npm run e2e` invokes `tests/cli`'s suite alongside
|
|
991
|
+
`tests/e2e`.
|
|
992
|
+
|
|
993
|
+
**Phase 5:**
|
|
994
|
+
|
|
995
|
+
- **Plugin discovery via `pristine.config.ts`'s `plugins` array.** Tooling-only command
|
|
996
|
+
packages can be opted into without polluting the runtime AppModule.
|
|
997
|
+
- **Plugin failure is non-fatal.** A missing or broken plugin warns to stderr and the CLI
|
|
998
|
+
continues with built-in commands intact.
|
|
999
|
+
- **`pristine info` lists loaded plugins.**
|
|
1000
|
+
- **Command-name collisions warn loudly** at boot.
|
|
1001
|
+
|
|
1002
|
+
**Phase 4:**
|
|
1003
|
+
|
|
1004
|
+
- **`pristine start` hosts HTTP servers automatically** when `@pristine-ts/http` is
|
|
1005
|
+
imported. HTTPS support via TLS file paths. CLI flag overrides for `--port` / `--address`.
|
|
1006
|
+
- **`pristine start` is a real production entry point.** SIGTERM/SIGINT → graceful
|
|
1007
|
+
`Kernel.stop()` → `onShutdown` hooks → exit. Hard-exit timeout protection.
|
|
1008
|
+
- **`RuntimeServerInterface`** added so any module can plug a long-running server into
|
|
1009
|
+
`pristine start`.
|
|
1010
|
+
- **`pristine info`** prints framework + runtime metadata + the imported module graph.
|
|
1011
|
+
- **`pristine build`** wraps `tsc`. Format `"both"` runs ESM and CJS sequentially.
|
|
1012
|
+
- **`pristine help`** is generated from the live command registry.
|
|
1013
|
+
- **Top-level aliases** (`pristine help`, `list`, `verify`, `info`, `build`, `start`).
|
|
1014
|
+
- **`CommandInterface.description`** added (optional one-line summary).
|
|
1015
|
+
|
|
1016
|
+
**Phase 3:**
|
|
1017
|
+
|
|
1018
|
+
- **`pristine.config.ts` is the canonical config location.** Loaded via `jiti` — no
|
|
1019
|
+
separate compile step.
|
|
1020
|
+
- **`p:config:init`** generates a starter config and migrates from `package.json`.
|
|
1021
|
+
- **`p:config:print`** prints the resolved config with provenance markers.
|
|
1022
|
+
- **Deprecation warning** on `pristine.appModule.cjsPath` in `package.json`.
|
|
1023
|
+
|
|
1024
|
+
**Phase 2:**
|
|
1025
|
+
|
|
1026
|
+
- **ESM (`.mjs`) AppModules supported.** Path resolved through `pathToFileURL` so Windows
|
|
1027
|
+
paths and ESM resolution are correct.
|
|
1028
|
+
- **Convention-based AppModule discovery.** Greenfield projects with `dist/app.module.js`
|
|
1029
|
+
need zero configuration.
|
|
1030
|
+
- **Multi-candidate detection with TTY prompt / non-TTY error.** Selections are cached so
|
|
1031
|
+
re-runs skip the prompt.
|
|
1032
|
+
- **Resilient bootstrap.** A broken AppModule path no longer prevents built-in commands
|
|
1033
|
+
from running.
|
|
1034
|
+
|
|
1035
|
+
**Phase 1:**
|
|
1036
|
+
|
|
1037
|
+
- **The bin is bundled** (esbuild, single file) but with all `@pristine-ts/*` packages
|
|
1038
|
+
marked external (cross-realm safety — see [Architecture](#architecture--design-notes)).
|
|
1039
|
+
- **Bundled bin is published with the executable bit set.**
|