@xova/matrix 0.1.0-rc.0
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/LICENSE +21 -0
- package/README.md +198 -0
- package/README.zh-CN.md +198 -0
- package/bin/matrix.mjs +10 -0
- package/dist/archive.js +1 -0
- package/dist/cli-args.js +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +2 -0
- package/dist/config.d.ts +65 -0
- package/dist/config.js +1 -0
- package/dist/defaults.d.ts +30 -0
- package/dist/defaults.js +1 -0
- package/dist/exec.js +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1 -0
- package/dist/plan.d.ts +12 -0
- package/dist/plan.js +1 -0
- package/dist/schema.d.ts +89 -0
- package/dist/schema.js +1 -0
- package/dist/types.d.ts +238 -0
- package/dist/types.js +1 -0
- package/package.json +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Xova
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# @xova/matrix
|
|
2
|
+
|
|
3
|
+
Configuration-driven CLI for running and packaging multi-app workspaces.
|
|
4
|
+
|
|
5
|
+
English · [简体中文](README.zh-CN.md)
|
|
6
|
+
|
|
7
|
+
Define projects once, then run development, builds, previews, and custom commands by product and environment.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Typed configuration for projects, products, variants, and targets
|
|
12
|
+
- Built-in `dev`, `build`, and `preview` targets
|
|
13
|
+
- Target dependencies with `ready` and `completed` conditions
|
|
14
|
+
- `development`, `staging`, and `production` environments
|
|
15
|
+
- Custom environment names such as `qa` and `uat`
|
|
16
|
+
- Layered environment variables with dotenv and shell overrides
|
|
17
|
+
- Interactive product and environment selection
|
|
18
|
+
- Optional build archives under `artifacts`
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
Requires Node.js `>=22.18.0`.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pnpm add -D @xova/matrix
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
Create `matrix.config.ts` in the workspace root:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { defineMatrixConfig, defineMatrixEnv } from '@xova/matrix'
|
|
34
|
+
|
|
35
|
+
export default defineMatrixConfig({
|
|
36
|
+
projects: {
|
|
37
|
+
web: {
|
|
38
|
+
root: './apps/web',
|
|
39
|
+
targets: {
|
|
40
|
+
dev: 'vite',
|
|
41
|
+
build: { command: 'vite build', archive: true },
|
|
42
|
+
preview: 'vite preview',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
products: {
|
|
47
|
+
app: {
|
|
48
|
+
variants: { web: 'web' },
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Run it from the directory containing the config file:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
matrix dev app
|
|
58
|
+
matrix build app --env staging
|
|
59
|
+
matrix plan app --target preview --env production
|
|
60
|
+
matrix doctor
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
When `product`, target, or `--env` is omitted in an interactive terminal, Matrix prompts for a selection. Interactive selection follows Product → Variant → Target → Environment, and one run selects a single Product. Use `--product` and `--target` to provide explicit selections without relying on positional argument order.
|
|
64
|
+
|
|
65
|
+
See [`examples/basic`](examples/basic/README.md) for a self-contained example that runs without a framework dependency.
|
|
66
|
+
|
|
67
|
+
## Configuration
|
|
68
|
+
|
|
69
|
+
| Concept | Purpose |
|
|
70
|
+
| ------- | --------------------------------------------------------------- |
|
|
71
|
+
| Project | An application directory and its commands |
|
|
72
|
+
| Product | A runnable deliverable composed of variants |
|
|
73
|
+
| Variant | A product entry bound to a project |
|
|
74
|
+
| Target | A command such as `dev`, `build`, `preview`, or a custom target |
|
|
75
|
+
|
|
76
|
+
### Multiple variants
|
|
77
|
+
|
|
78
|
+
A product can run more than one project and express dependencies per target:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
export default {
|
|
82
|
+
products: {
|
|
83
|
+
app: {
|
|
84
|
+
variants: {
|
|
85
|
+
web: 'web',
|
|
86
|
+
desktop: {
|
|
87
|
+
project: 'desktop',
|
|
88
|
+
targets: {
|
|
89
|
+
dev: { dependsOn: [{ variant: 'web', condition: 'ready' }] },
|
|
90
|
+
build: { dependsOn: [{ variant: 'web', condition: 'completed' }] },
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`ready` is useful for continuous targets such as `dev`; `completed` is useful for one-shot targets such as `build`.
|
|
100
|
+
|
|
101
|
+
### Environments
|
|
102
|
+
|
|
103
|
+
Use top-level `env` and `$env` for values shared by all products. Put product-specific values under the product when different products assemble the same projects with different backends:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
export default defineMatrixConfig({
|
|
107
|
+
projects: {},
|
|
108
|
+
products: {
|
|
109
|
+
app: {
|
|
110
|
+
appId: 'com.example.app',
|
|
111
|
+
env: { VITE_API_BASE: 'http://localhost:3000' },
|
|
112
|
+
$env: defineMatrixEnv({
|
|
113
|
+
staging: { VITE_API_BASE: 'https://staging-api.example.com' },
|
|
114
|
+
production: { VITE_API_BASE: 'https://api.example.com' },
|
|
115
|
+
}),
|
|
116
|
+
variants: {},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`defineMatrixEnv()` is syntax sugar for the c12-compatible `$env.<environment>.env` shape. The raw shape remains supported.
|
|
123
|
+
|
|
124
|
+
Values are merged from low to high precedence:
|
|
125
|
+
|
|
126
|
+
```text
|
|
127
|
+
global env/$env < product env/$env < .env layers < process.env < MATRIX_*
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Product-level environment values are resolved independently for each product. This lets two products reuse the same Desktop project while connecting it to different Web variants or services.
|
|
131
|
+
|
|
132
|
+
Custom environment names are supported. Define them with the same helper and pass the name explicitly to the CLI:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
export default defineMatrixConfig({
|
|
136
|
+
$env: defineMatrixEnv({
|
|
137
|
+
qa: {
|
|
138
|
+
VITE_API_BASE: 'https://qa-api.example.com',
|
|
139
|
+
},
|
|
140
|
+
}),
|
|
141
|
+
projects: {},
|
|
142
|
+
products: {},
|
|
143
|
+
})
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
matrix build app --env qa
|
|
148
|
+
matrix plan app --target preview --env qa
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Custom environments are available through `--env`. When running interactively, Matrix adds names found in the top-level and product `$env` configuration to the selector.
|
|
152
|
+
|
|
153
|
+
Dotenv files are loaded as `.env`, `.env.local`, `.env.<environment>`, and `.env.<environment>.local`. Matrix passes variables such as `VITE_*` and `NUXT_*` to child processes; application frameworks keep ownership of their own runtime configuration.
|
|
154
|
+
|
|
155
|
+
## Targets and defaults
|
|
156
|
+
|
|
157
|
+
The built-in targets use these defaults:
|
|
158
|
+
|
|
159
|
+
| Target | Environment | Continuous |
|
|
160
|
+
| --------- | ------------- | ---------- |
|
|
161
|
+
| `dev` | `development` | Yes |
|
|
162
|
+
| `build` | `production` | No |
|
|
163
|
+
| `preview` | `production` | Yes |
|
|
164
|
+
|
|
165
|
+
Custom targets are non-continuous by default and use `development` unless `--env` is provided. Project roots default to `.`, target output directories default to `dist`, and archive output defaults to `artifacts`. Archives are disabled by default, are only valid for the build target, and use zip when enabled. A configured archive fails the build when its output directory does not exist.
|
|
166
|
+
|
|
167
|
+
Interactive Variant selection comes before Target selection. Target options are derived from the common targets available to the selected Variants; use `--variant` to provide the scope in non-interactive runs.
|
|
168
|
+
|
|
169
|
+
Any configured target can be invoked from the CLI. `test`, `lint`, and `e2e` are common custom targets.
|
|
170
|
+
|
|
171
|
+
## Commands
|
|
172
|
+
|
|
173
|
+
```text
|
|
174
|
+
matrix [target] [product] [--variant name] [--env <environment>]
|
|
175
|
+
matrix --product <product> [--target <target>] [--variant name] [--env <environment>]
|
|
176
|
+
matrix dev [product]
|
|
177
|
+
matrix build [product] [--env <environment>] [--archive]
|
|
178
|
+
matrix preview [product] [--env <environment>]
|
|
179
|
+
matrix plan [product] [--target <target>] [--env <environment>]
|
|
180
|
+
matrix doctor
|
|
181
|
+
matrix <custom-target> [product] [--env <environment>]
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Development
|
|
185
|
+
|
|
186
|
+
Dependency versions are maintained in the pnpm catalog in `pnpm-workspace.yaml`.
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
pnpm install
|
|
190
|
+
pnpm check
|
|
191
|
+
pnpm lint:fix
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`pnpm lint:fix` formats JavaScript, TypeScript, and Markdown through ESLint. `pnpm check` runs lint, typecheck, tests, and the production build.
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
[MIT](LICENSE)
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# @xova/matrix
|
|
2
|
+
|
|
3
|
+
面向多应用工作区的配置驱动 CLI。
|
|
4
|
+
|
|
5
|
+
[English](README.md) · 简体中文
|
|
6
|
+
|
|
7
|
+
只需定义一次项目,就可以按产品和环境运行开发服务、构建、预览和自定义命令。
|
|
8
|
+
|
|
9
|
+
## 特性
|
|
10
|
+
|
|
11
|
+
- 使用类型安全的配置描述项目、产品、变体和目标
|
|
12
|
+
- 内置 `dev`、`build` 和 `preview` 目标
|
|
13
|
+
- 支持带有 `ready` 和 `completed` 条件的目标依赖
|
|
14
|
+
- 内置 `development`、`staging` 和 `production` 环境
|
|
15
|
+
- 支持 `qa`、`uat` 等自定义环境名称
|
|
16
|
+
- 支持 dotenv 和 Shell 覆盖的分层环境变量
|
|
17
|
+
- 支持交互式选择产品和环境
|
|
18
|
+
- 支持将构建结果归档到 `artifacts`
|
|
19
|
+
|
|
20
|
+
## 安装
|
|
21
|
+
|
|
22
|
+
需要 Node.js `>=22.18.0`。
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pnpm add -D @xova/matrix
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 快速开始
|
|
29
|
+
|
|
30
|
+
在工作区根目录创建 `matrix.config.ts`:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { defineMatrixConfig, defineMatrixEnv } from '@xova/matrix'
|
|
34
|
+
|
|
35
|
+
export default defineMatrixConfig({
|
|
36
|
+
projects: {
|
|
37
|
+
web: {
|
|
38
|
+
root: './apps/web',
|
|
39
|
+
targets: {
|
|
40
|
+
dev: 'vite',
|
|
41
|
+
build: { command: 'vite build', archive: true },
|
|
42
|
+
preview: 'vite preview',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
products: {
|
|
47
|
+
app: {
|
|
48
|
+
variants: { web: 'web' },
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
在包含配置文件的目录中运行:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
matrix dev app
|
|
58
|
+
matrix build app --env staging
|
|
59
|
+
matrix plan app --target preview --env production
|
|
60
|
+
matrix doctor
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
在交互式终端中,如果省略产品、目标或 `--env`,Matrix 会提示选择。交互式选择顺序为 Product → Variant → Target → Environment,一次运行只选择一个 Product。可以使用 `--product` 和 `--target` 显式指定选择,避免依赖位置参数顺序。
|
|
64
|
+
|
|
65
|
+
完整的可运行示例见 [`examples/basic`](examples/basic/README.md),它不依赖具体前端框架。
|
|
66
|
+
|
|
67
|
+
## 配置
|
|
68
|
+
|
|
69
|
+
| 概念 | 作用 |
|
|
70
|
+
| ------- | -------------------------------------- |
|
|
71
|
+
| Project | 应用目录及其命令 |
|
|
72
|
+
| Product | 由多个变体组成的可运行交付物 |
|
|
73
|
+
| Variant | 绑定到项目的产品条目 |
|
|
74
|
+
| Target | `dev`、`build`、`preview` 或自定义目标 |
|
|
75
|
+
|
|
76
|
+
### 多变体
|
|
77
|
+
|
|
78
|
+
一个产品可以运行多个项目,并为不同目标声明依赖:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
export default {
|
|
82
|
+
products: {
|
|
83
|
+
app: {
|
|
84
|
+
variants: {
|
|
85
|
+
web: 'web',
|
|
86
|
+
desktop: {
|
|
87
|
+
project: 'desktop',
|
|
88
|
+
targets: {
|
|
89
|
+
dev: { dependsOn: [{ variant: 'web', condition: 'ready' }] },
|
|
90
|
+
build: { dependsOn: [{ variant: 'web', condition: 'completed' }] },
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
持续运行的 `dev` 适合使用 `ready`,一次性执行的 `build` 适合使用 `completed`。
|
|
100
|
+
|
|
101
|
+
### 环境
|
|
102
|
+
|
|
103
|
+
所有产品共享的变量放在顶层 `env` 和 `$env`。如果不同产品复用相同项目但连接不同后端,则将产品专属变量放在产品内部:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
export default defineMatrixConfig({
|
|
107
|
+
projects: {},
|
|
108
|
+
products: {
|
|
109
|
+
app: {
|
|
110
|
+
appId: 'com.example.app',
|
|
111
|
+
env: { VITE_API_BASE: 'http://localhost:3000' },
|
|
112
|
+
$env: defineMatrixEnv({
|
|
113
|
+
staging: { VITE_API_BASE: 'https://staging-api.example.com' },
|
|
114
|
+
production: { VITE_API_BASE: 'https://api.example.com' },
|
|
115
|
+
}),
|
|
116
|
+
variants: {},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`defineMatrixEnv()` 是 c12 兼容的 `$env.<environment>.env` 写法的语法糖,底层结构仍然保持不变,也继续支持直接使用原始写法。
|
|
123
|
+
|
|
124
|
+
变量覆盖优先级从低到高为:
|
|
125
|
+
|
|
126
|
+
```text
|
|
127
|
+
global env/$env < product env/$env < .env layers < process.env < MATRIX_*
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
产品级环境变量会为每个产品独立解析。这样多个产品可以复用同一个 Desktop 项目,同时连接不同的 Web 变体或服务。
|
|
131
|
+
|
|
132
|
+
支持自定义环境名称。使用相同的 helper 定义,并在 CLI 中显式传入环境名:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
export default defineMatrixConfig({
|
|
136
|
+
$env: defineMatrixEnv({
|
|
137
|
+
qa: {
|
|
138
|
+
VITE_API_BASE: 'https://qa-api.example.com',
|
|
139
|
+
},
|
|
140
|
+
}),
|
|
141
|
+
projects: {},
|
|
142
|
+
products: {},
|
|
143
|
+
})
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
matrix build app --env qa
|
|
148
|
+
matrix plan app --target preview --env qa
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
自定义环境可以通过 `--env` 使用。交互式运行时,Matrix 会将顶层和产品级 `$env` 中声明的环境名加入选择器。
|
|
152
|
+
|
|
153
|
+
dotenv 文件按 `.env`、`.env.local`、`.env.<environment>` 和 `.env.<environment>.local` 加载。Matrix 会将 `VITE_*`、`NUXT_*` 等变量传递给子进程,应用框架继续负责自己的运行时配置。
|
|
154
|
+
|
|
155
|
+
## 目标和默认值
|
|
156
|
+
|
|
157
|
+
内置目标的默认值如下:
|
|
158
|
+
|
|
159
|
+
| 目标 | 环境 | 是否持续运行 |
|
|
160
|
+
| --------- | ------------- | ------------ |
|
|
161
|
+
| `dev` | `development` | 是 |
|
|
162
|
+
| `build` | `production` | 否 |
|
|
163
|
+
| `preview` | `production` | 是 |
|
|
164
|
+
|
|
165
|
+
自定义目标默认不会持续运行,未指定 `--env` 时使用 `development`。项目默认使用当前目录,目标输出目录默认为 `dist`,归档输出目录默认为 `artifacts`。归档默认关闭,只允许配置在 build 目标上,启用后默认使用 zip 格式。配置了归档但输出目录不存在时,构建会失败。
|
|
166
|
+
|
|
167
|
+
交互式运行会先选择 Variant,再选择 Target。Target 选项来自已选 Variant 共同支持的目标;非交互式运行可以使用 `--variant` 提前指定执行范围。
|
|
168
|
+
|
|
169
|
+
配置中的任意目标都可以通过 CLI 调用。常见的自定义目标包括 `test`、`lint` 和 `e2e`。
|
|
170
|
+
|
|
171
|
+
## 命令
|
|
172
|
+
|
|
173
|
+
```text
|
|
174
|
+
matrix [target] [product] [--variant name] [--env <environment>]
|
|
175
|
+
matrix --product <product> [--target <target>] [--variant name] [--env <environment>]
|
|
176
|
+
matrix dev [product]
|
|
177
|
+
matrix build [product] [--env <environment>] [--archive]
|
|
178
|
+
matrix preview [product] [--env <environment>]
|
|
179
|
+
matrix plan [product] [--target <target>] [--env <environment>]
|
|
180
|
+
matrix doctor
|
|
181
|
+
matrix <custom-target> [product] [--env <environment>]
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## 开发
|
|
185
|
+
|
|
186
|
+
依赖版本统一维护在 `pnpm-workspace.yaml` 的 pnpm catalog 中。
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
pnpm install
|
|
190
|
+
pnpm check
|
|
191
|
+
pnpm lint:fix
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`pnpm lint:fix` 通过 ESLint 格式化 JavaScript、TypeScript 和 Markdown。`pnpm check` 会依次执行 lint、类型检查、测试和生产构建。
|
|
195
|
+
|
|
196
|
+
## 许可证
|
|
197
|
+
|
|
198
|
+
[MIT](LICENSE)
|
package/bin/matrix.mjs
ADDED
package/dist/archive.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import e from"node:path";import{randomUUID as t}from"node:crypto";import{createWriteStream as n}from"node:fs";import{mkdir as r,rename as i,stat as a,unlink as o}from"node:fs/promises";import{pipeline as s}from"node:stream/promises";import c from"archiver";async function l(l,u,d){let f;try{f=await a(l)}catch(e){let t=e.code;throw t===`ENOENT`||t===`ENOTDIR`?Error(`Archive source directory does not exist: ${l}`,{cause:e}):e}if(!f.isDirectory())throw Error(`Archive source must be a directory: ${l}`);let p=e.relative(e.resolve(l),e.resolve(u));if(!p||!p.startsWith(`..`)&&!e.isAbsolute(p))throw Error(`Archive destination must be outside source directory: ${u}`);let m=e.dirname(u),h=e.join(m,`.${e.basename(u)}.${t()}.tmp`);await r(m,{recursive:!0});let g;try{let e=n(h),t=c(d===`zip`?`zip`:`tar`,d===`tar.gz`?{gzip:!0,gzipOptions:{level:9}}:void 0);g=s(t,e),t.directory(l,!1),await t.finalize(),await g,await i(h,u)}finally{await g?.catch(()=>void 0),await o(h).catch(e=>{if(e.code!==`ENOENT`)throw e})}}export{l as archiveDirectory};
|
package/dist/cli-args.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=`matrix [target] [product] [--product name] [--variant name] [--env name] [--target name] [--archive|--no-archive] [-h|--help]`;function t(e,t,n){let r=e[t+1];if(!r||r.startsWith(`-`))throw Error(`Option ${n} requires a value`);return r}function n(e,t){let n=e.split(`,`).map(e=>e.trim());if(n.some(e=>!e))throw Error(`Option ${t} contains an empty variant name`);return n}function r(e){let r={variants:[]},i=[];for(let a=0;a<e.length;a++){let o=e[a];if(!o)throw Error(`Unexpected empty argument`);if(o===`--help`||o===`-h`){r.help=!0;continue}if(o===`--archive`||o===`--no-archive`){let e=o===`--archive`;if(r.archive!==void 0&&r.archive!==e)throw Error(`Options --archive and --no-archive cannot be used together`);if(r.archive===e)throw Error(`Duplicate option: ${o}`);r.archive=e;continue}if(o===`--variant`||o===`-v`){let i=t(e,a,o);r.variants.push(...n(i,o)),a++;continue}if(o===`--product`||o===`--env`||o===`--mode`||o===`--target`){let n=t(e,a,o);if(o===`--product`){if(r.product!==void 0)throw Error(`Duplicate option: --product`);r.product=n}else if(o===`--target`){if(r.target!==void 0)throw Error(`Duplicate option: --target`);r.target=n}else{if(r.env!==void 0)throw Error(`Duplicate option: --env`);r.env=n}a++;continue}if(o.startsWith(`-`))throw Error(`Unknown option: ${o}`);if(i.push(o),i.length>2)throw Error(`Unexpected argument: ${o}`)}if(i[0]&&(r.command=i[0]),i[1]){if(r.product!==void 0)throw Error(`Product was provided more than once`);r.product=i[1]}return r}function i(e){let t=e.product!==void 0||e.target!==void 0||e.env!==void 0||e.variants.length>0||e.archive!==void 0;if(e.help||e.command===`help`){if(t||e.command!==void 0&&e.command!==`help`)throw Error(`Help does not accept execution options`);return}if(e.command===`doctor`){if(e.product!==void 0||e.target!==void 0||e.variants.length>0||e.archive!==void 0)throw Error(`doctor only accepts --env`);return}if(e.command&&e.command!==`plan`&&e.target!==void 0)throw Error(`Target is already selected by command ${e.command}; use --target only with plan or --product`)}export{e as CLI_HELP,r as parseArgs,i as validateCliArgs};
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{MATRIX_DEFAULTS as e,defaultEnvironmentForTarget as t}from"./defaults.js";import{listMatrixEnvironments as n,loadMatrixConfig as r}from"./config.js";import{createExecutionPlan as i,validateExecutionGraph as a}from"./plan.js";import{CLI_HELP as o,parseArgs as s,validateCliArgs as c}from"./cli-args.js";import{runExecutionPlan as l}from"./exec.js";import u from"node:process";import{isCancel as d,multiselect as f,outro as p,select as m}from"@clack/prompts";import h from"consola";const g=/token|secret|password|passwd|authorization|cookie|api[_-]?key|private[_-]?key/i;function _(e,t){return{...e,tasks:e.tasks.map(e=>({...e,env:Object.fromEntries(Object.entries(e.env).filter(([e])=>t.has(e)||e.startsWith(`MATRIX_`)).map(([e,t])=>[e,g.test(e)?`***`:t]))}))}}function v(e,t=[]){let n=(t.length?t.map(t=>e.variants[t]).filter(e=>e!==void 0):Object.values(e.variants)).map(e=>new Set(Object.keys(e.targets)));return[...n[0]??/* @__PURE__ */ new Set].filter(e=>n.every(t=>t.has(e))).sort()}function y(e,t){for(let n of t)if(!e.variants[n])throw Error(`Unknown variant for product ${e.key}: ${n}`)}function b(e,t,n=[]){let r=v(t,n),i=r.length?r.join(`, `):`none`;if(!r.includes(e))throw Error(`Unknown command or target for product ${t.key}: ${e}. Available targets: ${i}`)}async function x(e,t){if(!e.product){if(!u.stdin.isTTY||!u.stdout.isTTY)throw Error(`Product is required in non-interactive mode. Try: matrix <target> <product> or matrix --product <product>`);let n=await m({message:`Select product`,options:Object.keys(t).map(e=>({value:e,label:e}))});if(d(n))return null;e.product=n}if(e.product.includes(`,`))throw Error(`Only one product can be selected per run`);if(!t[e.product])throw Error(`Unknown product: ${e.product}`);return e}async function S(t,n){let r=t.target??(t.command&&t.command!==`plan`?t.command:void 0);if(r)return t.target=r,t;let i=v(n,t.variants);if(!i.length)throw Error(`Product ${n.key} has no common targets`);if(!u.stdin.isTTY||!u.stdout.isTTY)return t.target=i.includes(e.target)?e.target:i[0],t;let a=await m({message:`Select target`,initialValue:i.includes(e.target)?e.target:i[0],options:i.map(t=>({value:t,label:t,...t===e.target?{hint:`default`}:{}}))});return d(a)?null:(t.target=a,t)}async function C(e,t){let n=!e.command&&u.stdin.isTTY&&u.stdout.isTTY;if(!e.variants.length&&n&&Object.keys(t.variants).length>1){let n=Object.entries(t.variants),r=await f({message:`Select variants (leave empty to select all)`,options:n.map(([e])=>({value:e,label:e}))});if(d(r))return null;e.variants=r}return e}async function w(e,n,r){if(e.env)return e;let i=t(n);if(u.stdin.isTTY&&u.stdout.isTTY){let t=await m({message:`Select environment`,initialValue:i,options:r.map(e=>({value:e,label:e,...e===i?{hint:`default`}:{}}))});if(d(t))return null;e.env=t}else e.env=i;return e}async function T(d=u.argv.slice(2)){let f=s(d);if(c(f),f.command===`help`||f.help){console.log(o);return}if(f.command===`doctor`){let e=await r({...f.env?{envName:f.env}:{}});a({config:e.config,projects:e.projects,products:e.products,externalEnv:e.externalEnv,cwd:e.cwd,envName:e.envName}),h.success(`Configuration is valid: ${e.configFile??`matrix.config.ts`}`);return}let m=f.target??(f.command&&f.command!==`plan`?f.command:e.target),g=f.env??t(m),v=await r({envName:g}),T=await x(f,v.products);if(!T)return p(`Cancelled`);let E=v.products[T.product];if(!E)throw Error(`Unknown product: ${T.product}`);y(E,T.variants);let D=await C(T,E);if(!D)return p(`Cancelled`);y(E,D.variants);let O=await S(D,E);if(!O)return p(`Cancelled`);let k=O.target;if(O.archive!==void 0&&k!==`build`)throw Error(`Archive options are only valid for the build target: ${k}`);b(k,E,O.variants);let A=await w(O,k,!O.env&&u.stdin.isTTY&&u.stdout.isTTY?await n({productName:O.product}):[]);if(!A)return p(`Cancelled`);let j=A.env===g?v:await r({envName:A.env}),M=j.products[A.product];if(!M)throw Error(`Unknown product: ${A.product}`);y(M,A.variants),b(k,M,A.variants);let N=A.command??k,P=[A.product],F={config:j.config,projects:j.projects,products:j.products,externalEnv:j.externalEnv,cwd:j.cwd,productNames:P,target:k,envName:j.envName},I=A.variants.length?i({...F,variantNames:A.variants}):i(F);if(A.archive!==void 0)for(let e of I.tasks)e.target===`build`&&(e.archive.enabled=A.archive);if(N===`plan`){let e=new Set(Object.keys(j.config.env??{}));for(let t of P)for(let n of Object.keys(j.config.products[t]?.env??{}))e.add(n);console.log(JSON.stringify(_(I,e),null,2));return}h.info(`Plan: ${P[0]} / ${A.variants.length?A.variants.join(`, `):`all variants`} / ${k} / ${j.envName}`);let L=A.variants.length?A.variants:Object.keys(M.variants),R=new Set(L.map(e=>`${P[0]}:${e}:${k}`)),z=I.tasks.filter(e=>!R.has(e.id));z.length&&h.info(`Including dependencies: ${z.map(e=>e.id).join(`, `)}`),await l(I)}export{T as runCli};
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { EnvMap, MatrixConfig, NormalizedProduct, NormalizedProject } from "./types.js";
|
|
2
|
+
import { MATRIX_DEFAULTS, defaultEnvironmentForTarget } from "./defaults.js";
|
|
3
|
+
//#region src/config.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Provides type inference for a Matrix configuration file.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* export default defineMatrixConfig({
|
|
10
|
+
* projects: { web: { targets: { build: 'pnpm build' } } },
|
|
11
|
+
* products: { app: { variants: { web: 'web' } } },
|
|
12
|
+
* })
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function defineMatrixConfig<T extends MatrixConfig>(config: T): T;
|
|
16
|
+
/**
|
|
17
|
+
* Converts a compact environment map into c12's `$env` configuration shape.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* $env: defineMatrixEnv({
|
|
22
|
+
* qa: { API_BASE_URL: 'https://qa.example.test' },
|
|
23
|
+
* })
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function defineMatrixEnv<T extends Record<string, EnvMap>>(environments: T): { [K in keyof T]: {
|
|
27
|
+
env: T[K];
|
|
28
|
+
}; };
|
|
29
|
+
/**
|
|
30
|
+
* Lists built-in and configured environment names without applying dotenv or process overrides.
|
|
31
|
+
*
|
|
32
|
+
* When `productName` is provided, product-scoped environments are included only for that
|
|
33
|
+
* product. This is used by the interactive CLI after product selection.
|
|
34
|
+
*/
|
|
35
|
+
export declare function listMatrixEnvironments(options?: {
|
|
36
|
+
cwd?: string;
|
|
37
|
+
configFile?: string;
|
|
38
|
+
productName?: string;
|
|
39
|
+
}): Promise<string[]>;
|
|
40
|
+
/** Resolves target, identity, and project defaults into execution-ready structures. */
|
|
41
|
+
export declare function normalizeMatrixConfig(raw: MatrixConfig): {
|
|
42
|
+
config: MatrixConfig;
|
|
43
|
+
projects: Record<string, NormalizedProject>;
|
|
44
|
+
products: Record<string, NormalizedProduct>;
|
|
45
|
+
};
|
|
46
|
+
type LoadedMatrixConfig = ReturnType<typeof normalizeMatrixConfig> & {
|
|
47
|
+
configFile: string | undefined;
|
|
48
|
+
layers: unknown[] | undefined;
|
|
49
|
+
cwd: string;
|
|
50
|
+
envName: string;
|
|
51
|
+
externalEnv: EnvMap;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Loads and validates Matrix configuration through c12.
|
|
55
|
+
*
|
|
56
|
+
* The selected environment applies c12 `$env` layers and the dotenv files `.env`,
|
|
57
|
+
* `.env.local`, `.env.<environment>`, and `.env.<environment>.local`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function loadMatrixConfig(options?: {
|
|
60
|
+
cwd?: string;
|
|
61
|
+
envName?: string;
|
|
62
|
+
configFile?: string;
|
|
63
|
+
}): Promise<LoadedMatrixConfig>;
|
|
64
|
+
//#endregion
|
|
65
|
+
export { MATRIX_DEFAULTS, defaultEnvironmentForTarget };
|
package/dist/config.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{MATRIX_DEFAULTS as e,defaultEnvironmentForTarget as t}from"./defaults.js";import{assertMatrixConfig as n}from"./schema.js";import{MATRIX_ENVIRONMENTS as r}from"./types.js";import i from"node:path";import a from"node:process";import{loadConfig as o}from"c12";function s(e){return e}function c(e){return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,{env:t}]))}async function l(e={}){let t=i.resolve(e.cwd??a.cwd()),n=(await o({name:`matrix`,cwd:t,...e.configFile?{configFile:e.configFile}:{},envName:!1,dotenv:!1,omit$Keys:!1,rcFile:!1,packageJson:!1})).config,s=new Set(r);for(let e of Object.keys(n.$env??{}))s.add(e);let c=e.productName?n.products?.[e.productName]:void 0,l=e.productName?c?[c]:[]:Object.values(n.products??{});for(let e of l)for(let t of Object.keys(e.$env??{}))s.add(t);return[...s]}function u(t,n){let r=typeof n==`string`?{command:n}:{...n};if(!r.command)throw Error(`Target ${t} must define a command`);let i=typeof r.archive==`boolean`?{enabled:r.archive,format:e.archive.format}:{enabled:r.archive?.enabled??e.archive.enabled,format:r.archive?.format??e.archive.format},a=e.targets[t];return{...r,name:t,continuous:r.continuous??a?.continuous??!1,outputDir:r.outputDir??e.outputDir,archive:i,dependsOn:(r.dependsOn??[]).map(e=>typeof e==`string`?{variant:e}:e)}}function d(e,t){if(e!==`build`&&typeof t!=`string`&&t.archive!==void 0)throw Error(`Archive is only supported for build targets: ${e}`)}function f(e,t){if(typeof t==`string`)return t;let n=t.readyWhen===void 0?e.readyWhen:{...e.readyWhen,...t.readyWhen},r=typeof e.archive==`object`?e.archive:{enabled:e.archive},i=typeof t.archive==`object`&&t.archive!==null?{...r,...t.archive}:t.archive??r;return{...e,...t,archive:i,...n?{readyWhen:n}:{}}}function p(...e){return Object.assign({},...e.filter(Boolean))}function m(e,t){let n=Object.fromEntries(Object.entries(e.products).map(([e,n])=>{let{$env:r,...i}=n,a=p(n.env,r?.[t]?.env);return[e,{...i,...Object.keys(a).length?{env:a}:{}}]}));return{...e,products:n}}function h(e){let t=Object.fromEntries(Object.entries(e.projects).map(([e,t])=>[e,{...t,id:e,targets:Object.fromEntries(Object.entries(t.targets).map(([e,t])=>(d(e,t),[e,u(e,t)])))}]));return{config:e,projects:t,products:Object.fromEntries(Object.entries(e.products).map(([e,n])=>{let r=Object.fromEntries(Object.entries(n.variants).map(([n,r])=>{let i=typeof r==`string`?{project:r}:r,a=t[i.project];if(!a)throw Error(`Product ${e} variant ${n} references unknown project ${i.project}`);let o=Object.fromEntries(Object.entries(a.targets).map(([e,t])=>{let n=i.targets?.[e];return n!==void 0&&d(e,n),[e,u(e,n===void 0?t:f(t,n))]}));for(let[t,r]of Object.entries(i.targets??{}))if(!(t in o)){if(d(t,r),typeof r==`string`)o[t]=u(t,r);else if(r.command)o[t]=u(t,r);else throw Error(`Variant ${e}/${n} target ${t} must define a command`)}return[n,{...i,id:n,targets:o}]}));return[e,{...n,id:n.id??e,name:n.name??e,slug:n.slug??e,key:e,variants:r}]}))}}async function g(t={}){let r=i.resolve(t.cwd??a.cwd()),s=t.envName??e.environment,c={name:`matrix`,cwd:r,...t.configFile?{configFile:t.configFile}:{},envName:s,dotenv:{fileName:[`.env`,`.env.local`,`.env.${s}`,`.env.${s}.local`]},omit$Keys:!0,rcFile:!1,packageJson:!1},l=await o(c),u=m(n(l.config),s),d=Object.fromEntries(Object.entries(a.env).filter(e=>e[1]!==void 0));return{...h(u),configFile:l.configFile,layers:l.layers,cwd:r,envName:s,externalEnv:d}}export{e as MATRIX_DEFAULTS,t as defaultEnvironmentForTarget,s as defineMatrixConfig,c as defineMatrixEnv,l as listMatrixEnvironments,g as loadMatrixConfig,h as normalizeMatrixConfig};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/defaults.d.ts
|
|
2
|
+
/** Default values applied when a Matrix configuration omits optional fields. */
|
|
3
|
+
export declare const MATRIX_DEFAULTS: {
|
|
4
|
+
readonly target: "dev";
|
|
5
|
+
readonly environment: "development";
|
|
6
|
+
readonly projectRoot: ".";
|
|
7
|
+
readonly outputDir: "dist";
|
|
8
|
+
readonly artifactsRoot: "artifacts";
|
|
9
|
+
readonly archive: {
|
|
10
|
+
readonly enabled: false;
|
|
11
|
+
readonly format: "zip";
|
|
12
|
+
};
|
|
13
|
+
readonly targets: {
|
|
14
|
+
readonly dev: {
|
|
15
|
+
readonly environment: "development";
|
|
16
|
+
readonly continuous: true;
|
|
17
|
+
};
|
|
18
|
+
readonly build: {
|
|
19
|
+
readonly environment: "production";
|
|
20
|
+
readonly continuous: false;
|
|
21
|
+
};
|
|
22
|
+
readonly preview: {
|
|
23
|
+
readonly environment: "production";
|
|
24
|
+
readonly continuous: true;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
/** Returns the default environment associated with a target name. */
|
|
29
|
+
export declare function defaultEnvironmentForTarget(target: string): string;
|
|
30
|
+
//#endregion
|
package/dist/defaults.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e={target:`dev`,environment:`development`,projectRoot:`.`,outputDir:`dist`,artifactsRoot:`artifacts`,archive:{enabled:!1,format:`zip`},targets:{dev:{environment:`development`,continuous:!0},build:{environment:`production`,continuous:!1},preview:{environment:`production`,continuous:!0}}};function t(t){return e.targets[t]?.environment??e.environment}export{e as MATRIX_DEFAULTS,t as defaultEnvironmentForTarget};
|
package/dist/exec.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{archiveDirectory as e}from"./archive.js";import t from"node:path";import n from"node:process";import r from"consola";import i from"node:net";import{execaCommand as a}from"execa";const o=new Promise(()=>void 0);function s(e){return new Promise(t=>setTimeout(t,e))}function c(e,t){return t.size?Promise.race([e,...t]):e}function l(e,t,n){return new Promise(r=>{let a=i.createConnection({host:e,port:t}),o=!1,s=e=>{o||(o=!0,a.destroy(),r(e))};a.once(`connect`,()=>s(!0)),a.once(`error`,()=>s(!1)),a.setTimeout(n,()=>s(!1))})}async function u(e,t,n){let r=t.readyWhen;if(!r)return;let i=r.timeout??3e4,a=Date.now()+i;for(;;){let i=a-Date.now();if(i<=0)break;if(await c(l(r.host??`127.0.0.1`,r.port,Math.min(1e3,i)),n))return;if(await c(Promise.race([e.then(e=>e),new Promise(e=>setTimeout(e,Math.min(200,Math.max(1,a-Date.now()))))]),n)!==void 0)throw Error(`${t.id} exited before becoming ready`)}throw Error(`Timed out waiting for ${t.id} on port ${r.port}`)}async function d(e,t,n){for(let[n,r]of e)t.has(n)||(t.add(n),r.kill(`SIGTERM`));await Promise.race([Promise.allSettled([...e.values()]),s(5e3)]);for(let[r,i]of e)n.has(r)||(t.add(r),i.kill(`SIGKILL`));await Promise.allSettled([...e.values()])}async function f(i){let s=/* @__PURE__ */ new Map,l=/* @__PURE__ */ new Set,f=/* @__PURE__ */ new Set,p=/* @__PURE__ */ new Set,m=()=>{for(let[e,t]of s)l.has(e)||(l.add(e),t.kill(`SIGTERM`))};n.on(`SIGINT`,m),n.on(`SIGTERM`,m);try{for(let d of i.tasks){for(let e of d.dependsOn){let t=s.get(e.id);if(!t)throw Error(`Dependency ${e.id} was not started`);if(e.condition===`completed`){let n=await c(t,p);if(n.exitCode!==0)throw Error(`${e.id} exited with code ${n.exitCode}`)}else{let n=i.tasks.find(t=>t.id===e.id);if(!n)throw Error(`Dependency task ${e.id} is missing`);await u(t,n,p)}}r.info(`${d.id} → ${d.command}`);let m=a(d.command,{cwd:d.cwd,env:{...n.env,...Object.fromEntries(Object.entries(d.env).map(([e,t])=>[e,String(t)]))},stdio:`inherit`,reject:!1,killDescendants:!0});if(s.set(d.id,m),m.then(()=>f.add(d.id),()=>f.add(d.id)),d.continuous){let e=m.then(e=>{if(l.size||e.exitCode===0)return o;throw Error(`${d.id} exited with code ${e.exitCode}`)});p.add(e),e.catch(()=>void 0)}if(!d.continuous){let n=await c(m,p);if(n.exitCode!==0)throw Error(`${d.id} exited with code ${n.exitCode}`);if(d.target===`build`&&d.archive.enabled){let n=d.archive.format===`zip`?`zip`:`tar.gz`,r=t.join(i.artifactsRoot,d.product,i.envName,`${d.variant}.${n}`);await e(d.outputDir,r,d.archive.format)}}}let d=i.tasks.filter(e=>e.continuous).map(e=>s.get(e.id));return d.length&&await c(Promise.race(d.map(async e=>{let t=await e;if(!l.size&&t.exitCode!==0)throw Error(`Service exited with code ${t.exitCode}`)})),p),{children:s}}finally{i.tasks.some(e=>e.continuous)?await d(s,l,f):await Promise.allSettled([...s.values()]),n.removeListener(`SIGINT`,m),n.removeListener(`SIGTERM`,m)}}export{f as runExecutionPlan};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { CommandTarget, CreateExecutionPlanInput, EnvMap, EnvironmentConfig, ExecutionPlan, ExecutionTask, MATRIX_ENVIRONMENTS, MatrixConfig, MatrixEnvironment, NormalizedProduct, NormalizedProject, NormalizedTarget, NormalizedVariant, ProductConfig, ProjectConfig, Scalar, SuffixConfig, TargetConfig, TargetDependency, TargetOverride, VariantConfig } from "./types.js";
|
|
2
|
+
import { MATRIX_DEFAULTS, defaultEnvironmentForTarget } from "./defaults.js";
|
|
3
|
+
import { defineMatrixConfig, defineMatrixEnv, listMatrixEnvironments, loadMatrixConfig, normalizeMatrixConfig } from "./config.js";
|
|
4
|
+
import { createExecutionPlan } from "./plan.js";
|
|
5
|
+
import { assertMatrixConfig, matrixConfigSchema } from "./schema.js";
|
|
6
|
+
export { type CommandTarget, type CreateExecutionPlanInput, type EnvMap, type EnvironmentConfig, type ExecutionPlan, type ExecutionTask, MATRIX_DEFAULTS, MATRIX_ENVIRONMENTS, type MatrixConfig, type MatrixEnvironment, type NormalizedProduct, type NormalizedProject, type NormalizedTarget, type NormalizedVariant, type ProductConfig, type ProjectConfig, type Scalar, type SuffixConfig, type TargetConfig, type TargetDependency, type TargetOverride, type VariantConfig, assertMatrixConfig, createExecutionPlan, defaultEnvironmentForTarget, defineMatrixConfig, defineMatrixEnv, listMatrixEnvironments, loadMatrixConfig, matrixConfigSchema, normalizeMatrixConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{MATRIX_DEFAULTS as e,defaultEnvironmentForTarget as t}from"./defaults.js";import{assertMatrixConfig as n,matrixConfigSchema as r}from"./schema.js";import{MATRIX_ENVIRONMENTS as i}from"./types.js";import{defineMatrixConfig as a,defineMatrixEnv as o,listMatrixEnvironments as s,loadMatrixConfig as c,normalizeMatrixConfig as l}from"./config.js";import{createExecutionPlan as u}from"./plan.js";export{e as MATRIX_DEFAULTS,i as MATRIX_ENVIRONMENTS,n as assertMatrixConfig,u as createExecutionPlan,t as defaultEnvironmentForTarget,a as defineMatrixConfig,o as defineMatrixEnv,s as listMatrixEnvironments,c as loadMatrixConfig,r as matrixConfigSchema,l as normalizeMatrixConfig};
|
package/dist/plan.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CreateExecutionPlanInput, ExecutionPlan } from "./types.js";
|
|
2
|
+
//#region src/plan.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Creates an ordered execution plan for one target across selected products or variants.
|
|
5
|
+
*
|
|
6
|
+
* Dependencies are expanded recursively and cycles are rejected. Environment values are
|
|
7
|
+
* merged into each task before the plan is returned.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createExecutionPlan(input: CreateExecutionPlanInput): ExecutionPlan;
|
|
10
|
+
/** Validates every configured product, variant, and target without starting any process. */
|
|
11
|
+
export declare function validateExecutionGraph(input: Omit<CreateExecutionPlanInput, 'productNames' | 'variantNames' | 'target'>): void;
|
|
12
|
+
//#endregion
|
package/dist/plan.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{MATRIX_DEFAULTS as e}from"./defaults.js";import t from"node:path";import n from"node:process";function r(...e){return Object.assign({},...e.filter(Boolean))}function i(){return Object.fromEntries(Object.entries(n.env).filter(e=>e[1]!==void 0))}function a(e,t){return e===void 0||t===void 0?e:`${e}${t}`}function o(...e){let t={};for(let n of e)for(let[e,r]of Object.entries(n??{}))t[e]={...t[e],...r};return t}function s(e,t,n){let r={...e.suffixes?.[n]??{},...t.suffixes?.[n]??{}};return{id:e.id,name:a(t.name??e.name,r.name),slug:a(t.slug??e.slug,r.slug),appId:a(t.appId??e.appId,r.appId)}}function c(e,t){return e.condition??(t.continuous?`ready`:`completed`)}function l(n){let a=/* @__PURE__ */ new Map,l=/* @__PURE__ */ new Set,u=[],d=(f,p,m)=>{let h=`${f}:${p}:${m}`,g=a.get(h);if(g)return g;if(l.has(h))throw Error(`Dependency cycle detected at ${h}`);let _=n.products[f];if(!_)throw Error(`Unknown product: ${f}`);let v=_.variants[p];if(!v)throw Error(`Unknown variant ${p} for product ${f}`);let y=n.projects[v.project];if(!y)throw Error(`Variant ${f}/${p} references unknown project ${v.project}`);let b=v.targets[m];if(!b)throw Error(`Variant ${f}/${p} has no target ${m}`);l.add(h);let x=b.dependsOn.map(e=>{let t=e.target??m,n=_.variants[e.variant];if(!n)throw Error(`Unknown dependency variant ${f}/${e.variant}`);let r=n.targets[t];if(!r)throw Error(`Variant ${f}/${e.variant} has no target ${t}`);return{id:d(f,e.variant,t).id,condition:c(e,r)}});l.delete(h);let S=s({..._,suffixes:o(n.config.suffixes,_.suffixes)},v,n.envName),C=t.resolve(n.cwd,y.root??e.projectRoot),w={id:h,product:f,variant:p,project:v.project,projectRoot:C,target:m,name:S.name,slug:S.slug,...S.appId?{appId:S.appId}:{},command:b.command,cwd:C,env:r(n.config.env,_.env,n.externalEnv??i(),{MATRIX_ENV_NAME:n.envName,MATRIX_PRODUCT_ID:S.id,MATRIX_PRODUCT_NAME:S.name,MATRIX_PRODUCT_SLUG:S.slug}),continuous:b.continuous,archive:b.archive,...b.readyWhen?{readyWhen:b.readyWhen}:{},outputDir:t.resolve(C,b.outputDir),dependsOn:x};return a.set(h,w),u.push(w),w};for(let e of n.productNames){let t=n.products[e];if(!t)throw Error(`Unknown product: ${e}`);let r=n.variantNames?.length?n.variantNames:Object.keys(t.variants);for(let t of r)d(e,t,n.target)}return{envName:n.envName,tasks:u,artifactsRoot:t.resolve(n.cwd,n.config.artifacts?.root??e.artifactsRoot)}}function u(e){for(let[t,n]of Object.entries(e.products))for(let[r,i]of Object.entries(n.variants))for(let n of Object.keys(i.targets))l({...e,productNames:[t],variantNames:[r],target:n})}export{l as createExecutionPlan,u as validateExecutionGraph};
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
//#region src/schema.d.ts
|
|
3
|
+
/** Runtime schema used to validate a loaded Matrix configuration. */
|
|
4
|
+
export declare const matrixConfigSchema: v.ObjectSchema<{
|
|
5
|
+
readonly suffixes: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.ObjectSchema<{
|
|
6
|
+
readonly name: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
7
|
+
readonly slug: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
8
|
+
readonly appId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
9
|
+
}, undefined>, undefined>, undefined>;
|
|
10
|
+
readonly env: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.UnionSchema<[v.StringSchema<undefined>, v.NumberSchema<undefined>, v.BooleanSchema<undefined>], undefined>, undefined>, undefined>;
|
|
11
|
+
readonly $env: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.ObjectSchema<{
|
|
12
|
+
readonly env: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.UnionSchema<[v.StringSchema<undefined>, v.NumberSchema<undefined>, v.BooleanSchema<undefined>], undefined>, undefined>, undefined>;
|
|
13
|
+
}, undefined>, undefined>, undefined>;
|
|
14
|
+
readonly projects: v.RecordSchema<v.StringSchema<undefined>, v.ObjectSchema<{
|
|
15
|
+
readonly root: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
16
|
+
readonly targets: v.RecordSchema<v.StringSchema<undefined>, v.UnionSchema<[v.StringSchema<undefined>, v.ObjectSchema<{
|
|
17
|
+
readonly command: v.StringSchema<undefined>;
|
|
18
|
+
readonly continuous: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
19
|
+
readonly readyWhen: v.OptionalSchema<v.ObjectSchema<{
|
|
20
|
+
readonly type: v.LiteralSchema<"port", undefined>;
|
|
21
|
+
readonly host: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
22
|
+
readonly port: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>, v.MaxValueAction<number, 65535, undefined>]>;
|
|
23
|
+
readonly timeout: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>, undefined>;
|
|
24
|
+
}, undefined>, undefined>;
|
|
25
|
+
readonly outputDir: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
26
|
+
readonly archive: v.OptionalSchema<v.UnionSchema<[v.BooleanSchema<undefined>, v.ObjectSchema<{
|
|
27
|
+
readonly enabled: v.BooleanSchema<undefined>;
|
|
28
|
+
readonly format: v.OptionalSchema<v.PicklistSchema<["zip", "tar.gz"], undefined>, undefined>;
|
|
29
|
+
}, undefined>], undefined>, undefined>;
|
|
30
|
+
readonly dependsOn: v.OptionalSchema<v.ArraySchema<v.UnionSchema<[v.StringSchema<undefined>, v.ObjectSchema<{
|
|
31
|
+
readonly variant: v.StringSchema<undefined>;
|
|
32
|
+
readonly target: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
33
|
+
readonly condition: v.OptionalSchema<v.PicklistSchema<["completed", "ready"], undefined>, undefined>;
|
|
34
|
+
}, undefined>], undefined>, undefined>, undefined>;
|
|
35
|
+
}, undefined>], undefined>, undefined>;
|
|
36
|
+
}, undefined>, undefined>;
|
|
37
|
+
readonly products: v.RecordSchema<v.StringSchema<undefined>, v.ObjectSchema<{
|
|
38
|
+
readonly id: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
39
|
+
readonly name: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
40
|
+
readonly slug: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
41
|
+
readonly appId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
42
|
+
readonly env: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.UnionSchema<[v.StringSchema<undefined>, v.NumberSchema<undefined>, v.BooleanSchema<undefined>], undefined>, undefined>, undefined>;
|
|
43
|
+
readonly $env: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.ObjectSchema<{
|
|
44
|
+
readonly env: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.UnionSchema<[v.StringSchema<undefined>, v.NumberSchema<undefined>, v.BooleanSchema<undefined>], undefined>, undefined>, undefined>;
|
|
45
|
+
}, undefined>, undefined>, undefined>;
|
|
46
|
+
readonly suffixes: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.ObjectSchema<{
|
|
47
|
+
readonly name: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
48
|
+
readonly slug: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
49
|
+
readonly appId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
50
|
+
}, undefined>, undefined>, undefined>;
|
|
51
|
+
readonly variants: v.RecordSchema<v.StringSchema<undefined>, v.UnionSchema<[v.StringSchema<undefined>, v.ObjectSchema<{
|
|
52
|
+
readonly project: v.StringSchema<undefined>;
|
|
53
|
+
readonly name: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
54
|
+
readonly slug: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
55
|
+
readonly appId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
56
|
+
readonly suffixes: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.ObjectSchema<{
|
|
57
|
+
readonly name: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
58
|
+
readonly slug: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
59
|
+
readonly appId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
60
|
+
}, undefined>, undefined>, undefined>;
|
|
61
|
+
readonly targets: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.UnionSchema<[v.StringSchema<undefined>, v.ObjectSchema<{
|
|
62
|
+
readonly command: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
63
|
+
readonly continuous: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
64
|
+
readonly readyWhen: v.OptionalSchema<v.ObjectSchema<{
|
|
65
|
+
readonly type: v.LiteralSchema<"port", undefined>;
|
|
66
|
+
readonly host: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
67
|
+
readonly port: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>, v.MaxValueAction<number, 65535, undefined>]>;
|
|
68
|
+
readonly timeout: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>, undefined>;
|
|
69
|
+
}, undefined>, undefined>;
|
|
70
|
+
readonly outputDir: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
71
|
+
readonly archive: v.OptionalSchema<v.UnionSchema<[v.BooleanSchema<undefined>, v.ObjectSchema<{
|
|
72
|
+
readonly enabled: v.BooleanSchema<undefined>;
|
|
73
|
+
readonly format: v.OptionalSchema<v.PicklistSchema<["zip", "tar.gz"], undefined>, undefined>;
|
|
74
|
+
}, undefined>], undefined>, undefined>;
|
|
75
|
+
readonly dependsOn: v.OptionalSchema<v.ArraySchema<v.UnionSchema<[v.StringSchema<undefined>, v.ObjectSchema<{
|
|
76
|
+
readonly variant: v.StringSchema<undefined>;
|
|
77
|
+
readonly target: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
78
|
+
readonly condition: v.OptionalSchema<v.PicklistSchema<["completed", "ready"], undefined>, undefined>;
|
|
79
|
+
}, undefined>], undefined>, undefined>, undefined>;
|
|
80
|
+
}, undefined>], undefined>, undefined>, undefined>;
|
|
81
|
+
}, undefined>], undefined>, undefined>;
|
|
82
|
+
}, undefined>, undefined>;
|
|
83
|
+
readonly artifacts: v.OptionalSchema<v.ObjectSchema<{
|
|
84
|
+
readonly root: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
85
|
+
}, undefined>, undefined>;
|
|
86
|
+
}, undefined>;
|
|
87
|
+
/** Validates a configuration and throws a Valibot error when it is malformed. */
|
|
88
|
+
export declare function assertMatrixConfig(value: unknown): v.InferOutput<typeof matrixConfigSchema>;
|
|
89
|
+
//#endregion
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import*as e from"valibot";const t=e.union([e.string(),e.number(),e.boolean()]),n=e.optional(e.record(e.string(),t)),r=e.object({name:e.optional(e.string()),slug:e.optional(e.string()),appId:e.optional(e.string())}),i=e.object({variant:e.string(),target:e.optional(e.string()),condition:e.optional(e.picklist([`completed`,`ready`]))}),a=e.union([e.string(),i]),o=e.pipe(e.number(),e.integer(),e.minValue(1),e.maxValue(65535)),s=e.pipe(e.number(),e.integer(),e.minValue(1)),c=e.union([e.string(),e.object({command:e.string(),continuous:e.optional(e.boolean()),readyWhen:e.optional(e.object({type:e.literal(`port`),host:e.optional(e.string()),port:o,timeout:e.optional(s)})),outputDir:e.optional(e.string()),archive:e.optional(e.union([e.boolean(),e.object({enabled:e.boolean(),format:e.optional(e.picklist([`zip`,`tar.gz`]))})])),dependsOn:e.optional(e.array(a))})]),l=e.union([e.string(),e.object({command:e.optional(e.string()),continuous:e.optional(e.boolean()),readyWhen:e.optional(e.object({type:e.literal(`port`),host:e.optional(e.string()),port:o,timeout:e.optional(s)})),outputDir:e.optional(e.string()),archive:e.optional(e.union([e.boolean(),e.object({enabled:e.boolean(),format:e.optional(e.picklist([`zip`,`tar.gz`]))})])),dependsOn:e.optional(e.array(a))})]),u=e.object({root:e.optional(e.string()),targets:e.record(e.string(),c)}),d=e.object({env:n}),f=e.union([e.string(),e.object({project:e.string(),name:e.optional(e.string()),slug:e.optional(e.string()),appId:e.optional(e.string()),suffixes:e.optional(e.record(e.string(),r)),targets:e.optional(e.record(e.string(),l))})]),p=e.object({id:e.optional(e.string()),name:e.optional(e.string()),slug:e.optional(e.string()),appId:e.optional(e.string()),env:n,$env:e.optional(e.record(e.string(),d)),suffixes:e.optional(e.record(e.string(),r)),variants:e.record(e.string(),f)}),m=e.object({suffixes:e.optional(e.record(e.string(),r)),env:n,$env:e.optional(e.record(e.string(),d)),projects:e.record(e.string(),u),products:e.record(e.string(),p),artifacts:e.optional(e.object({root:e.optional(e.string())}))});function h(t){return e.parse(m,t)}export{h as assertMatrixConfig,m as matrixConfigSchema};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
/** Values that can be passed to a process as environment variables. */
|
|
3
|
+
export type Scalar = string | number | boolean;
|
|
4
|
+
/** A flat environment variable map. */
|
|
5
|
+
export type EnvMap = Record<string, Scalar>;
|
|
6
|
+
/** Configuration for a project command such as `dev`, `build`, or `preview`. */
|
|
7
|
+
export interface CommandTarget {
|
|
8
|
+
/** Command to execute from the project's root directory. */
|
|
9
|
+
command: string;
|
|
10
|
+
/** Whether the command keeps running after it starts. Defaults from the target name. */
|
|
11
|
+
continuous?: boolean;
|
|
12
|
+
/** Readiness probe used by dependent continuous targets. */
|
|
13
|
+
readyWhen?: {
|
|
14
|
+
/** Probe type. Port probing is currently supported. */
|
|
15
|
+
type: 'port';
|
|
16
|
+
/** Host to probe. Defaults to the local host used by the probe. */
|
|
17
|
+
host?: string;
|
|
18
|
+
/** TCP port that must accept a connection. */
|
|
19
|
+
port: number;
|
|
20
|
+
/** Maximum time to wait for readiness, in milliseconds. */
|
|
21
|
+
timeout?: number;
|
|
22
|
+
};
|
|
23
|
+
/** Directory to archive, relative to the project root. Defaults to `dist`. */
|
|
24
|
+
outputDir?: string;
|
|
25
|
+
/** Whether to archive build output and, optionally, which archive format to use. Only valid on the build target. */
|
|
26
|
+
archive?: boolean | {
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
format?: 'zip' | 'tar.gz';
|
|
29
|
+
};
|
|
30
|
+
/** Other variants that must run before this target. */
|
|
31
|
+
dependsOn?: Array<string | TargetDependency>;
|
|
32
|
+
}
|
|
33
|
+
/** Short target syntax containing only the command. */
|
|
34
|
+
export type TargetConfig = string | CommandTarget;
|
|
35
|
+
/** Partial target configuration used to override a project's target on a variant. */
|
|
36
|
+
export type TargetOverride = string | Partial<CommandTarget>;
|
|
37
|
+
/** Dependency on another variant of the same product. */
|
|
38
|
+
export interface TargetDependency {
|
|
39
|
+
/** Variant that must be executed first. */
|
|
40
|
+
variant: string;
|
|
41
|
+
/** Target to execute on the dependency. Defaults to the current target. */
|
|
42
|
+
target?: string;
|
|
43
|
+
/** Dependency condition. Defaults to `ready` for continuous targets and `completed` otherwise. */
|
|
44
|
+
condition?: 'completed' | 'ready';
|
|
45
|
+
}
|
|
46
|
+
/** A runnable project and the targets it exposes. */
|
|
47
|
+
export interface ProjectConfig {
|
|
48
|
+
/** Project directory, relative to the Matrix configuration directory. Defaults to `.`. */
|
|
49
|
+
root?: string;
|
|
50
|
+
/** Named commands exposed by this project. */
|
|
51
|
+
targets: Record<string, TargetConfig>;
|
|
52
|
+
}
|
|
53
|
+
/** Configuration for a product variant, or a project name in short form. */
|
|
54
|
+
export type VariantConfig = string | {
|
|
55
|
+
/** Project used to implement this variant. */
|
|
56
|
+
project: string;
|
|
57
|
+
/** Display name used in generated task metadata. */
|
|
58
|
+
name?: string;
|
|
59
|
+
/** Slug used in generated task metadata. */
|
|
60
|
+
slug?: string;
|
|
61
|
+
/** Application identifier used in generated task metadata. */
|
|
62
|
+
appId?: string;
|
|
63
|
+
/** Environment-specific identity suffixes for this variant. */
|
|
64
|
+
suffixes?: Record<string, SuffixConfig>;
|
|
65
|
+
/** Target overrides or additional targets for this variant. */
|
|
66
|
+
targets?: Record<string, TargetOverride>;
|
|
67
|
+
};
|
|
68
|
+
/** Environment-specific suffixes for a product or variant identity. */
|
|
69
|
+
export interface SuffixConfig {
|
|
70
|
+
/** Text appended to the display name. */
|
|
71
|
+
name?: string;
|
|
72
|
+
/** Text appended to the slug. */
|
|
73
|
+
slug?: string;
|
|
74
|
+
/** Text appended to the application identifier. */
|
|
75
|
+
appId?: string;
|
|
76
|
+
}
|
|
77
|
+
/** A product groups variants that are selected and executed together. */
|
|
78
|
+
export interface ProductConfig {
|
|
79
|
+
/** Stable product identifier. Defaults to the product key. */
|
|
80
|
+
id?: string;
|
|
81
|
+
/** Display name. Defaults to the product key. */
|
|
82
|
+
name?: string;
|
|
83
|
+
/** Slug. Defaults to the product key. */
|
|
84
|
+
slug?: string;
|
|
85
|
+
/** Base application identifier. */
|
|
86
|
+
appId?: string;
|
|
87
|
+
/** Base environment variables for every environment. */
|
|
88
|
+
env?: EnvMap;
|
|
89
|
+
/** Product-scoped environment overrides loaded by c12's `$env` mechanism. */
|
|
90
|
+
$env?: Record<string, EnvironmentConfig>;
|
|
91
|
+
/** Environment-specific identity suffixes. */
|
|
92
|
+
suffixes?: Record<string, SuffixConfig>;
|
|
93
|
+
/** Product variants. */
|
|
94
|
+
variants: Record<string, VariantConfig>;
|
|
95
|
+
}
|
|
96
|
+
/** Configuration for one named environment. */
|
|
97
|
+
export interface EnvironmentConfig {
|
|
98
|
+
/** Environment variables merged over the parent scope. */
|
|
99
|
+
env?: EnvMap;
|
|
100
|
+
}
|
|
101
|
+
/** Root Matrix configuration. */
|
|
102
|
+
export interface MatrixConfig {
|
|
103
|
+
/** Global environment-specific identity suffixes. */
|
|
104
|
+
suffixes?: Record<string, SuffixConfig>;
|
|
105
|
+
/** Base environment variables shared by all products. */
|
|
106
|
+
env?: EnvMap;
|
|
107
|
+
/** Global environment overrides loaded by c12's `$env` mechanism. */
|
|
108
|
+
$env?: Record<string, EnvironmentConfig>;
|
|
109
|
+
/** Reusable projects. */
|
|
110
|
+
projects: Record<string, ProjectConfig>;
|
|
111
|
+
/** Products composed from the projects above. */
|
|
112
|
+
products: Record<string, ProductConfig>;
|
|
113
|
+
/** Output artifact configuration. */
|
|
114
|
+
artifacts?: {
|
|
115
|
+
root?: string;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/** Built-in environment names offered by Matrix. Custom names can be added with `$env`. */
|
|
119
|
+
export declare const MATRIX_ENVIRONMENTS: readonly ["development", "staging", "production"];
|
|
120
|
+
/** A built-in Matrix environment name. */
|
|
121
|
+
export type MatrixEnvironment = typeof MATRIX_ENVIRONMENTS[number];
|
|
122
|
+
/** Target after defaults and variant overrides have been resolved. */
|
|
123
|
+
export type NormalizedTarget = Omit<CommandTarget, 'outputDir' | 'dependsOn'> & {
|
|
124
|
+
name: string;
|
|
125
|
+
continuous: boolean;
|
|
126
|
+
outputDir: string;
|
|
127
|
+
archive: {
|
|
128
|
+
enabled: boolean;
|
|
129
|
+
format: 'zip' | 'tar.gz';
|
|
130
|
+
};
|
|
131
|
+
dependsOn: TargetDependency[];
|
|
132
|
+
};
|
|
133
|
+
/** Project after its target definitions have been normalized. */
|
|
134
|
+
export type NormalizedProject = Omit<ProjectConfig, 'targets'> & {
|
|
135
|
+
id: string;
|
|
136
|
+
targets: Record<string, NormalizedTarget>;
|
|
137
|
+
};
|
|
138
|
+
/** Variant after its project targets and overrides have been normalized. */
|
|
139
|
+
export type NormalizedVariant = Omit<Exclude<VariantConfig, string>, 'targets'> & {
|
|
140
|
+
id: string;
|
|
141
|
+
project: string;
|
|
142
|
+
targets: Record<string, NormalizedTarget>;
|
|
143
|
+
};
|
|
144
|
+
/** Product after identity defaults and variant definitions have been normalized. */
|
|
145
|
+
export type NormalizedProduct = Omit<ProductConfig, 'variants' | 'id' | 'name' | 'slug'> & {
|
|
146
|
+
id: string;
|
|
147
|
+
name: string;
|
|
148
|
+
slug: string;
|
|
149
|
+
key: string;
|
|
150
|
+
variants: Record<string, NormalizedVariant>;
|
|
151
|
+
};
|
|
152
|
+
/** One executable task in an {@link ExecutionPlan}. */
|
|
153
|
+
export interface ExecutionTask {
|
|
154
|
+
/** Stable task identifier. */
|
|
155
|
+
id: string;
|
|
156
|
+
/** Product key that owns the task. */
|
|
157
|
+
product: string;
|
|
158
|
+
/** Variant key that owns the task. */
|
|
159
|
+
variant: string;
|
|
160
|
+
/** Referenced project key. */
|
|
161
|
+
project: string;
|
|
162
|
+
/** Absolute project directory. */
|
|
163
|
+
projectRoot: string;
|
|
164
|
+
/** Target name. */
|
|
165
|
+
target: string;
|
|
166
|
+
/** Resolved display name. */
|
|
167
|
+
name: string;
|
|
168
|
+
/** Resolved slug. */
|
|
169
|
+
slug: string;
|
|
170
|
+
/** Resolved application identifier, when configured. */
|
|
171
|
+
appId?: string;
|
|
172
|
+
/** Command to execute. */
|
|
173
|
+
command: string;
|
|
174
|
+
/** Working directory for the command. */
|
|
175
|
+
cwd: string;
|
|
176
|
+
/** Environment passed to the command. */
|
|
177
|
+
env: EnvMap;
|
|
178
|
+
/** Whether the command is expected to remain running. */
|
|
179
|
+
continuous: boolean;
|
|
180
|
+
/** Resolved archive settings. */
|
|
181
|
+
archive: {
|
|
182
|
+
enabled: boolean;
|
|
183
|
+
format: 'zip' | 'tar.gz';
|
|
184
|
+
};
|
|
185
|
+
/** Readiness probe, when configured. */
|
|
186
|
+
readyWhen?: {
|
|
187
|
+
type: 'port';
|
|
188
|
+
host?: string;
|
|
189
|
+
port: number;
|
|
190
|
+
timeout?: number;
|
|
191
|
+
};
|
|
192
|
+
/** Absolute output directory. */
|
|
193
|
+
outputDir: string;
|
|
194
|
+
/** Resolved task dependencies and their completion conditions. */
|
|
195
|
+
dependsOn: Array<{
|
|
196
|
+
id: string;
|
|
197
|
+
condition: 'completed' | 'ready';
|
|
198
|
+
}>;
|
|
199
|
+
}
|
|
200
|
+
/** Ordered executable tasks and their shared artifact destination. */
|
|
201
|
+
export interface ExecutionPlan {
|
|
202
|
+
/** Environment selected for this plan. */
|
|
203
|
+
envName: string;
|
|
204
|
+
/** Tasks in dependency-safe execution order. */
|
|
205
|
+
tasks: ExecutionTask[];
|
|
206
|
+
/** Absolute root directory for generated archives. */
|
|
207
|
+
artifactsRoot: string;
|
|
208
|
+
}
|
|
209
|
+
/** Input accepted by {@link createExecutionPlan}. */
|
|
210
|
+
export interface CreateExecutionPlanInput {
|
|
211
|
+
/** Resolved root-level configuration values used by planning. */
|
|
212
|
+
config: {
|
|
213
|
+
artifacts?: {
|
|
214
|
+
root?: string;
|
|
215
|
+
};
|
|
216
|
+
env?: EnvMap;
|
|
217
|
+
suffixes?: Record<string, SuffixConfig>;
|
|
218
|
+
};
|
|
219
|
+
/** Normalized projects keyed by project name. */
|
|
220
|
+
projects: Record<string, {
|
|
221
|
+
root?: string;
|
|
222
|
+
}>;
|
|
223
|
+
/** Normalized products keyed by product name. */
|
|
224
|
+
products: Record<string, NormalizedProduct>;
|
|
225
|
+
/** External process environment to merge into each task. */
|
|
226
|
+
externalEnv?: EnvMap;
|
|
227
|
+
/** Directory containing the Matrix configuration. */
|
|
228
|
+
cwd: string;
|
|
229
|
+
/** Products to include in the plan. */
|
|
230
|
+
productNames: string[];
|
|
231
|
+
/** Optional variant filter. */
|
|
232
|
+
variantNames?: string[];
|
|
233
|
+
/** Target to execute for each selected variant. */
|
|
234
|
+
target: string;
|
|
235
|
+
/** Environment used to resolve identity suffixes. */
|
|
236
|
+
envName: string;
|
|
237
|
+
}
|
|
238
|
+
//#endregion
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=[`development`,`staging`,`production`];export{e as MATRIX_ENVIRONMENTS};
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xova/matrix",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0-rc.0",
|
|
5
|
+
"description": "A configuration-driven multi-app workspace orchestration engine and CLI.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/xova-dev/matrix#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/xova-dev/matrix.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/xova-dev/matrix/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"workspace",
|
|
17
|
+
"monorepo",
|
|
18
|
+
"cli",
|
|
19
|
+
"build",
|
|
20
|
+
"orchestration"
|
|
21
|
+
],
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./config": {
|
|
28
|
+
"types": "./dist/config.d.ts",
|
|
29
|
+
"import": "./dist/config.js"
|
|
30
|
+
},
|
|
31
|
+
"./plan": {
|
|
32
|
+
"types": "./dist/plan.d.ts",
|
|
33
|
+
"import": "./dist/plan.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"bin": {
|
|
37
|
+
"matrix": "./bin/matrix.mjs"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"bin",
|
|
41
|
+
"dist"
|
|
42
|
+
],
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=22.18.0"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@clack/prompts": "^0.11.0",
|
|
48
|
+
"archiver": "^7.0.1",
|
|
49
|
+
"c12": "^3.3.0",
|
|
50
|
+
"consola": "^3.4.2",
|
|
51
|
+
"execa": "^9.6.0",
|
|
52
|
+
"valibot": "^1.1.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@antfu/eslint-config": "^9.5.1",
|
|
56
|
+
"@types/archiver": "^6.0.3",
|
|
57
|
+
"@types/node": "^24.9.2",
|
|
58
|
+
"eslint": "^10.10.0",
|
|
59
|
+
"eslint-plugin-format": "^2.0.1",
|
|
60
|
+
"prettier": "^3.9.8",
|
|
61
|
+
"publint": "^0.3.24",
|
|
62
|
+
"tsdown": "^0.23.0",
|
|
63
|
+
"typescript": "^5.9.2",
|
|
64
|
+
"vitest": "^3.2.4"
|
|
65
|
+
},
|
|
66
|
+
"scripts": {
|
|
67
|
+
"build": "tsdown",
|
|
68
|
+
"test:pack": "node scripts/pack-smoke.mjs",
|
|
69
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
70
|
+
"test": "vitest run",
|
|
71
|
+
"lint": "eslint .",
|
|
72
|
+
"lint:fix": "eslint . --fix",
|
|
73
|
+
"check": "pnpm lint && pnpm typecheck && pnpm test && pnpm build"
|
|
74
|
+
}
|
|
75
|
+
}
|