procdeck 0.1.0 → 0.3.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.
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The package's public entry point — everything a `procdeck.config.ts` needs.
3
+ *
4
+ * The types are written out structurally instead of being derived from the
5
+ * Effect schema in `config.ts`, because a derived type makes the emitted `.d.ts`
6
+ * import `effect`, and effect is bundled into the CLI rather than installed:
7
+ * consumers would be typechecking against a package they don't have.
8
+ *
9
+ * `tests/public.test.ts` asserts at the type level that this mirrors the schema
10
+ * exactly, so the two cannot drift apart silently.
11
+ */
12
+ /** A check that must pass before the proc spawns; failure parks it in `blocked`. */
13
+ export type Preflight = {
14
+ /** Shell command; exit 0 lets the proc spawn. */
15
+ readonly shell: string;
16
+ /** Regex the check's output must also match, for checks that exit 0 either way. */
17
+ readonly expect?: string;
18
+ /** What the human should do when the check fails. */
19
+ readonly hint?: string;
20
+ };
21
+ /** A regex watched in the pane output; a match raises a badge in the UI. */
22
+ export type Alert = {
23
+ readonly pattern: string;
24
+ /** Badge text. */
25
+ readonly label: string;
26
+ };
27
+ /** A single supervised process. Exactly one of `shell` / `cmd` is used. */
28
+ export type ProcSpec = {
29
+ /** Pane label and proxy subdomain (`api` → http://api.localhost:4820). */
30
+ readonly id: string;
31
+ /** Command line handed to `$SHELL -c`. */
32
+ readonly shell?: string;
33
+ /** argv, executed without a shell. */
34
+ readonly cmd?: ReadonlyArray<string>;
35
+ /** Working directory; defaults to the config file's directory. */
36
+ readonly cwd?: string;
37
+ /** Extra environment; values may use `${port}` / `${port:other}`. */
38
+ readonly env?: {
39
+ readonly [key: string]: string;
40
+ };
41
+ /** Spawn on startup. Defaults to true. */
42
+ readonly autostart?: boolean;
43
+ /** URL this process serves, shown in the UI as a link. */
44
+ readonly url?: string;
45
+ /** Ids of procs that must be ready before this one spawns. */
46
+ readonly needs?: ReadonlyArray<string>;
47
+ /** What "ready" means for dependents. Defaults to "listening". */
48
+ readonly readyWhen?: "listening" | "started";
49
+ readonly preflight?: Preflight;
50
+ readonly alerts?: ReadonlyArray<Alert>;
51
+ };
52
+ /** A procdeck deck: the web UI's settings and the processes it supervises. */
53
+ export type ProcdeckConfig = {
54
+ /** Only meaningful in a JSON config; accepted here for symmetry. */
55
+ readonly $schema?: string;
56
+ /** Deck name; defaults to the config directory's basename. */
57
+ readonly name?: string;
58
+ /** Port for the web UI and the `*.localhost` proxy. Defaults to 4820. */
59
+ readonly port?: number;
60
+ readonly procs: ReadonlyArray<ProcSpec>;
61
+ };
62
+ /** Identity helper that gives config files their types. */
63
+ export declare const defineConfig: (config: ProcdeckConfig) => ProcdeckConfig;
package/dist/public.js ADDED
@@ -0,0 +1,7 @@
1
+ import "./chunk-MLKGABMK.js";
2
+
3
+ // src/public.ts
4
+ var defineConfig = (config) => config;
5
+ export {
6
+ defineConfig
7
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "procdeck",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Interactive dev-process multiplexer with a web UI: terminal panes in a browser tab, declarative dependencies, assigned ports and a *.localhost reverse proxy.",
5
5
  "keywords": [
6
6
  "cli",
@@ -26,10 +26,11 @@
26
26
  },
27
27
  "type": "module",
28
28
  "engines": {
29
- "node": ">=22.18"
29
+ "node": ">=22"
30
30
  },
31
31
  "files": [
32
32
  "dist",
33
+ "schema.json",
33
34
  "ui/dist"
34
35
  ],
35
36
  "bin": {
@@ -37,19 +38,19 @@
37
38
  },
38
39
  "exports": {
39
40
  ".": {
40
- "types": "./dist/config.d.ts",
41
- "import": "./dist/config.js"
41
+ "types": "./dist/public.d.ts",
42
+ "import": "./dist/public.js"
42
43
  }
43
44
  },
44
45
  "publishConfig": {
45
46
  "access": "public"
46
47
  },
47
48
  "dependencies": {
48
- "effect": "4.0.0-beta.106",
49
- "node-pty": "^1.1.0"
49
+ "@lydell/node-pty": "1.2.0-beta.15"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@effect/platform-browser": "4.0.0-beta.106",
53
+ "effect": "4.0.0-beta.106",
53
54
  "@types/node": "^22.10.1",
54
55
  "@xterm/addon-fit": "^0.11.0",
55
56
  "@xterm/addon-search": "^0.16.0",
@@ -62,8 +63,9 @@
62
63
  },
63
64
  "scripts": {
64
65
  "ui:dev": "vite ui",
65
- "build": "pnpm run build:meta && pnpm run build:ui && pnpm run build:js && pnpm run build:types",
66
+ "build": "pnpm run build:meta && pnpm run build:schema && pnpm run build:ui && pnpm run build:js && pnpm run build:types",
66
67
  "build:meta": "cp ../../readme.md ../../license .",
68
+ "build:schema": "node scripts/gen-schema.ts",
67
69
  "build:ui": "vite build ui",
68
70
  "build:js": "tsup",
69
71
  "build:types": "tsc -p tsconfig.build.json",
package/readme.md CHANGED
@@ -56,22 +56,22 @@ which port is which**.
56
56
 
57
57
  ## Install
58
58
 
59
+ Nothing to install, if you don't want to — write a `procdeck.config.json` (see
60
+ [Config](#config)) and run:
61
+
59
62
  ```sh
60
- pnpm add -D procdeck # npm i -D procdeck · bun add -d procdeck
63
+ npx procdeck # picks up ./procdeck.config.json
64
+ npx procdeck decks/backend.json # or point it at any config file
61
65
  ```
62
66
 
63
- Requires **Node ≥ 22.18**: `procdeck.config.ts` is a TypeScript file, loaded through
64
- Node's built-in type stripping.
67
+ Open <http://localhost:4820> for the panes. Requires **Node ≥ 22**.
65
68
 
66
- Write a `procdeck.config.ts` next to your `package.json` (see [Config](#config)), then:
69
+ Or add it to the project, which is what you want if you keep the config in TypeScript:
67
70
 
68
71
  ```sh
69
- npx procdeck # defaults to ./procdeck.config.ts
70
- npx procdeck decks/backend.ts # or point it at any config file
72
+ pnpm add -D procdeck # npm i -D procdeck · bun add -d procdeck
71
73
  ```
72
74
 
73
- Open <http://localhost:4820> for the panes.
74
-
75
75
  ## Try the example
76
76
 
77
77
  In a clone of this repo:
@@ -81,14 +81,12 @@ pnpm install
81
81
  pnpm dev # builds the UI, starts the example stack
82
82
  ```
83
83
 
84
- > **Native dependency note.** procdeck runs panes through [node-pty](https://github.com/microsoft/node-pty),
85
- > which ships a native prebuild. npm and yarn set it up automatically. pnpm ≥ 10 and Bun
86
- > block dependency install scripts by default — in your own project run
87
- > `pnpm approve-builds` and allow `node-pty` (Bun: add it to `trustedDependencies`).
88
- > procdeck also repairs the most common fallout by itself at startup (the darwin
89
- > `spawn-helper` prebuild arriving without its execute bit), so approving is only
90
- > strictly required when node-pty must compile from source (no prebuild for your
91
- > platform).
84
+ > **Install footprint.** ~1.8 MB, one dependency. The server is shipped as a bundle, so
85
+ > nothing but the PTY bindings is installed — and those are
86
+ > [`@lydell/node-pty`](https://github.com/lydell/node-pty): the same sources as
87
+ > [node-pty](https://github.com/microsoft/node-pty), distributed as per-platform packages.
88
+ > Nothing compiles and no install script runs, so there is no `pnpm approve-builds` /
89
+ > `trustedDependencies` detour, and no compiler is needed on Linux.
92
90
 
93
91
  Open <http://localhost:4820> for the panes, then <http://web.localhost:4820> and
94
92
  <http://api.localhost:4820> — the example's two servers found each other (and their own
@@ -96,7 +94,31 @@ ports) entirely through `${port}` templates. See [`example/`](example/).
96
94
 
97
95
  ## Config
98
96
 
99
- `procdeck.config.ts` — a TypeScript file, validated by an Effect schema:
97
+ Two formats, one schema. **JSON** needs nothing from your toolchain — no TypeScript, and
98
+ procdeck itself doesn't have to be a dependency. Point `$schema` at the published schema
99
+ and the editor completes and validates every field:
100
+
101
+ ```jsonc
102
+ {
103
+ "$schema": "https://unpkg.com/procdeck/schema.json",
104
+ "port": 4820,
105
+ "procs": [
106
+ { "id": "api", "shell": "pnpm --filter api dev", "env": { "PORT": "${port}" } },
107
+ {
108
+ "id": "web",
109
+ "shell": "pnpm --filter web dev",
110
+ "env": { "PORT": "${port}", "API_URL": "http://localhost:${port:api}" },
111
+ "needs": ["api"]
112
+ }
113
+ ]
114
+ }
115
+ ```
116
+
117
+ (Installed locally, `"./node_modules/procdeck/schema.json"` works too and needs no
118
+ network.)
119
+
120
+ **TypeScript** buys comments and computed configs, at the price of Node ≥ 22.18 (native
121
+ type stripping) and procdeck in your dependencies:
100
122
 
101
123
  ```ts
102
124
  import { defineConfig } from "procdeck"
@@ -131,7 +153,9 @@ export default defineConfig({
131
153
  ```
132
154
 
133
155
  Full field reference: [`packages/procdeck/src/config.ts`](packages/procdeck/src/config.ts)
134
- (the schema is the documentation).
156
+ (the schema is the documentation — `schema.json` is generated from it at build time, so
157
+ the two formats validate identically). Config files are looked up in this order when no
158
+ path is given: `procdeck.config.json`, `.ts`, `.js`, `.mjs`.
135
159
 
136
160
  Tips:
137
161
 
package/schema.json ADDED
@@ -0,0 +1,212 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://unpkg.com/procdeck/schema.json",
4
+ "title": "procdeck config",
5
+ "type": "object",
6
+ "properties": {
7
+ "$schema": {
8
+ "type": "string",
9
+ "description": "URL of the procdeck JSON schema, for editor completion and validation.",
10
+ "examples": [
11
+ "https://unpkg.com/procdeck/schema.json"
12
+ ]
13
+ },
14
+ "name": {
15
+ "type": "string",
16
+ "description": "Deck name — the tab title and the name of the installed web app. Defaults to the config directory's basename.",
17
+ "examples": [
18
+ "my-app"
19
+ ]
20
+ },
21
+ "port": {
22
+ "description": "Port for the web UI and the `*.localhost` proxy. Defaults to 4820.",
23
+ "examples": [
24
+ 4820
25
+ ],
26
+ "type": "integer",
27
+ "allOf": [
28
+ {
29
+ "minimum": 1,
30
+ "maximum": 65535
31
+ }
32
+ ]
33
+ },
34
+ "procs": {
35
+ "type": "array",
36
+ "items": {
37
+ "type": "object",
38
+ "properties": {
39
+ "id": {
40
+ "type": "string",
41
+ "description": "Stable identifier: the pane label, and the proxy subdomain (`api` → http://api.localhost:4820).",
42
+ "examples": [
43
+ "api",
44
+ "web"
45
+ ],
46
+ "allOf": [
47
+ {
48
+ "minLength": 1
49
+ }
50
+ ]
51
+ },
52
+ "shell": {
53
+ "type": "string",
54
+ "description": "Command line handed to `$SHELL -c` — pipes, `&&` and globs work. Exactly one of `shell` or `cmd` must be set.",
55
+ "examples": [
56
+ "pnpm --filter api dev"
57
+ ]
58
+ },
59
+ "cmd": {
60
+ "type": "array",
61
+ "items": {
62
+ "type": "string"
63
+ },
64
+ "description": "argv, executed directly without a shell. Exactly one of `shell` or `cmd` must be set.",
65
+ "examples": [
66
+ [
67
+ "node",
68
+ "server.js"
69
+ ]
70
+ ],
71
+ "allOf": [
72
+ {
73
+ "minItems": 1
74
+ }
75
+ ]
76
+ },
77
+ "cwd": {
78
+ "type": "string",
79
+ "description": "Working directory; defaults to the config file's directory.",
80
+ "examples": [
81
+ "packages/api"
82
+ ]
83
+ },
84
+ "env": {
85
+ "type": "object",
86
+ "additionalProperties": {
87
+ "type": "string"
88
+ },
89
+ "description": "Extra environment on top of the inherited one. Values may use `${port}` / `${port:other}` templates.",
90
+ "examples": [
91
+ {
92
+ "PORT": "${port}",
93
+ "API_URL": "http://localhost:${port:api}"
94
+ }
95
+ ]
96
+ },
97
+ "autostart": {
98
+ "type": "boolean",
99
+ "description": "Spawn on startup. Defaults to true; false leaves the pane idle until Start."
100
+ },
101
+ "url": {
102
+ "type": "string",
103
+ "description": "URL this process serves, shown in the UI as a link. Also pins readiness to that URL's port.",
104
+ "examples": [
105
+ "http://localhost:${port}"
106
+ ]
107
+ },
108
+ "needs": {
109
+ "type": "array",
110
+ "items": {
111
+ "type": "string"
112
+ },
113
+ "description": "Ids of procs that must be ready before this one spawns. Declarative: readiness is auto-detected, no port numbers in the config.",
114
+ "examples": [
115
+ [
116
+ "api"
117
+ ]
118
+ ]
119
+ },
120
+ "readyWhen": {
121
+ "type": "string",
122
+ "enum": [
123
+ "listening",
124
+ "started"
125
+ ],
126
+ "description": "What \"ready\" means for dependents: \"listening\" (default) — the process tree opened a TCP port; \"started\" — it merely spawned, for procs that never listen."
127
+ },
128
+ "preflight": {
129
+ "type": "object",
130
+ "properties": {
131
+ "shell": {
132
+ "type": "string",
133
+ "description": "Shell command; exit 0 lets the proc spawn.",
134
+ "examples": [
135
+ "wrangler whoami"
136
+ ],
137
+ "allOf": [
138
+ {
139
+ "minLength": 1
140
+ }
141
+ ]
142
+ },
143
+ "expect": {
144
+ "type": "string",
145
+ "description": "Regex the check's output must also match. For checks that exit 0 either way — `wrangler whoami` reports \"not authenticated\" with exit 0 — so the config needs no `| grep` pipelines.",
146
+ "examples": [
147
+ "You are logged in"
148
+ ]
149
+ },
150
+ "hint": {
151
+ "type": "string",
152
+ "description": "What the human should do when the check fails; shown on the blocked pane.",
153
+ "examples": [
154
+ "run `wrangler login`, then Start"
155
+ ]
156
+ }
157
+ },
158
+ "required": [
159
+ "shell"
160
+ ],
161
+ "additionalProperties": false,
162
+ "description": "A gate that must pass before the process spawns — e.g. `wrangler whoami` before a proc whose tree needs a Cloudflare token. Keeps interactive auth flows out of supervised panes, where a restart would kill their callback servers mid-handshake."
163
+ },
164
+ "alerts": {
165
+ "type": "array",
166
+ "items": {
167
+ "type": "object",
168
+ "properties": {
169
+ "pattern": {
170
+ "type": "string",
171
+ "description": "Regex matched against a rolling tail of the pane's output.",
172
+ "examples": [
173
+ "ERROR",
174
+ "not authenticated"
175
+ ]
176
+ },
177
+ "label": {
178
+ "type": "string",
179
+ "description": "Badge text shown in the UI when the pattern matches.",
180
+ "examples": [
181
+ "needs login"
182
+ ],
183
+ "allOf": [
184
+ {
185
+ "minLength": 1
186
+ }
187
+ ]
188
+ }
189
+ },
190
+ "required": [
191
+ "pattern",
192
+ "label"
193
+ ],
194
+ "additionalProperties": false,
195
+ "description": "A regex watched in the pane output; a match raises a badge in the UI."
196
+ },
197
+ "description": "Output patterns that raise a badge when they appear in the pane."
198
+ }
199
+ },
200
+ "required": [
201
+ "id"
202
+ ],
203
+ "additionalProperties": false
204
+ },
205
+ "description": "The processes this deck supervises."
206
+ }
207
+ },
208
+ "required": [
209
+ "procs"
210
+ ],
211
+ "additionalProperties": false
212
+ }
@@ -1,187 +0,0 @@
1
- // src/config.ts
2
- import { pathToFileURL } from "url";
3
- import * as path from "path";
4
- import { Effect, Schema } from "effect";
5
- var regexSource = Schema.String.pipe(
6
- Schema.check(
7
- Schema.makeFilter((pattern) => {
8
- try {
9
- new RegExp(pattern);
10
- return void 0;
11
- } catch {
12
- return "expected a valid regular expression";
13
- }
14
- })
15
- )
16
- );
17
- var PreflightSchema = Schema.Struct({
18
- /** Shell command; exit 0 lets the proc spawn. */
19
- shell: Schema.String.pipe(Schema.check(Schema.isMinLength(1))),
20
- /**
21
- * Regex that must also match the check's output. For checks that exit 0
22
- * either way — `wrangler whoami` reports "not authenticated" with exit 0 —
23
- * so the config doesn't need `| grep` pipelines.
24
- */
25
- expect: Schema.optionalKey(regexSource),
26
- /** What the human should do when the check fails. */
27
- hint: Schema.optionalKey(Schema.String)
28
- });
29
- var AlertSchema = Schema.Struct({
30
- pattern: regexSource,
31
- /** Badge text. */
32
- label: Schema.String.pipe(Schema.check(Schema.isMinLength(1)))
33
- });
34
- var ProcSpecSchema = Schema.Struct({
35
- /** Stable identifier, also the tab label. */
36
- id: Schema.String.pipe(Schema.check(Schema.isMinLength(1))),
37
- /** Command line handed to `$SHELL -c`. */
38
- shell: Schema.optionalKey(Schema.String),
39
- /** argv, executed without a shell. */
40
- cmd: Schema.optionalKey(Schema.Array(Schema.String).pipe(Schema.check(Schema.isMinLength(1)))),
41
- /** Working directory; defaults to the config file's directory. */
42
- cwd: Schema.optionalKey(Schema.String),
43
- /** Extra environment on top of the inherited one. */
44
- env: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)),
45
- /** Spawn on startup. Defaults to true. */
46
- autostart: Schema.optionalKey(Schema.Boolean),
47
- /** URL this process serves, shown in the UI as a link. */
48
- url: Schema.optionalKey(Schema.String),
49
- /**
50
- * Names of procs that must be ready before this one spawns. Declarative:
51
- * readiness is auto-detected, no port numbers in the config.
52
- */
53
- needs: Schema.optionalKey(Schema.Array(Schema.String)),
54
- /**
55
- * What "ready" means for dependents: "listening" (default) — the process
56
- * tree opened a TCP port; "started" — it merely spawned (for procs that
57
- * never listen).
58
- */
59
- readyWhen: Schema.optionalKey(Schema.Literals(["listening", "started"])),
60
- /** Check that must pass before this proc spawns; failure shows as "blocked". */
61
- preflight: Schema.optionalKey(PreflightSchema),
62
- /** Output patterns that raise a badge when they appear in the pane. */
63
- alerts: Schema.optionalKey(Schema.Array(AlertSchema))
64
- }).pipe(
65
- Schema.check(
66
- Schema.makeFilter(
67
- (spec) => spec.shell === void 0 === (spec.cmd === void 0) ? "expected exactly one of `shell` or `cmd`" : void 0
68
- )
69
- )
70
- );
71
- var DEFAULT_UI_PORT = 4820;
72
- var PORT_TEMPLATE = /\$\{port(?::([^}]+))?\}/g;
73
- var templatedStrings = (spec) => [spec.shell, ...spec.cmd ?? [], ...Object.values(spec.env ?? {}), spec.url].filter(
74
- (text) => text !== void 0
75
- );
76
- var usesOwnPort = (spec) => templatedStrings(spec).some(
77
- (text) => [...text.matchAll(PORT_TEMPLATE)].some((match) => match[1] === void 0)
78
- );
79
- var portRefs = (spec) => templatedStrings(spec).flatMap(
80
- (text) => [...text.matchAll(PORT_TEMPLATE)].flatMap((match) => match[1] === void 0 ? [] : [match[1]])
81
- );
82
- var resolveSpec = (spec, assigned) => {
83
- const sub = (text) => text.replace(PORT_TEMPLATE, (whole, ref) => {
84
- const port = assigned.get(ref ?? spec.id);
85
- return port === void 0 ? whole : String(port);
86
- });
87
- return {
88
- ...spec,
89
- ...spec.shell === void 0 ? {} : { shell: sub(spec.shell) },
90
- ...spec.cmd === void 0 ? {} : { cmd: spec.cmd.map(sub) },
91
- ...spec.env === void 0 ? {} : { env: Object.fromEntries(Object.entries(spec.env).map(([key, value]) => [key, sub(value)])) },
92
- ...spec.url === void 0 ? {} : { url: sub(spec.url) }
93
- };
94
- };
95
- var validateGraph = (procs) => {
96
- const ids = new Set(procs.map((proc) => proc.id));
97
- if (ids.size !== procs.length) return "duplicate proc ids";
98
- for (const proc of procs) {
99
- for (const need of proc.needs ?? []) {
100
- if (!ids.has(need)) return `proc "${proc.id}" needs unknown proc "${need}"`;
101
- }
102
- }
103
- const needsOf = new Map(procs.map((proc) => [proc.id, proc.needs ?? []]));
104
- const done = /* @__PURE__ */ new Set();
105
- const onPath = /* @__PURE__ */ new Set();
106
- const visit = (id) => {
107
- if (done.has(id)) return void 0;
108
- if (onPath.has(id)) return `dependency cycle through "${id}"`;
109
- onPath.add(id);
110
- for (const need of needsOf.get(id) ?? []) {
111
- const cycle = visit(need);
112
- if (cycle !== void 0) return cycle;
113
- }
114
- onPath.delete(id);
115
- done.add(id);
116
- return void 0;
117
- };
118
- for (const proc of procs) {
119
- const cycle = visit(proc.id);
120
- if (cycle !== void 0) return cycle;
121
- }
122
- return void 0;
123
- };
124
- var validatePortTemplates = (procs) => {
125
- const byId = new Map(procs.map((proc) => [proc.id, proc]));
126
- for (const proc of procs) {
127
- for (const ref of portRefs(proc)) {
128
- const target = byId.get(ref);
129
- if (target === void 0) {
130
- return `proc "${proc.id}" references \${port:${ref}} but there is no proc "${ref}"`;
131
- }
132
- if (!usesOwnPort(target)) {
133
- return `proc "${proc.id}" references \${port:${ref}} but "${ref}" does not use \${port} itself`;
134
- }
135
- }
136
- }
137
- return void 0;
138
- };
139
- var ProcdeckConfigSchema = Schema.Struct({
140
- /**
141
- * Deck name — the tab title and the name of the installed web app.
142
- * Defaults to the config directory's basename.
143
- */
144
- name: Schema.optionalKey(Schema.String),
145
- /** Port for the web UI. Defaults to 4820. */
146
- port: Schema.optionalKey(Schema.Number),
147
- procs: Schema.Array(ProcSpecSchema)
148
- }).pipe(
149
- Schema.check(
150
- Schema.makeFilter(
151
- (config) => validateGraph(config.procs) ?? validatePortTemplates(config.procs)
152
- )
153
- )
154
- );
155
- var defineConfig = (config) => config;
156
- var ConfigError = class extends Schema.TaggedError()("ConfigError", {
157
- file: Schema.String,
158
- message: Schema.String
159
- }) {
160
- };
161
- var loadConfig = Effect.fn("procdeck.loadConfig")(function* (file) {
162
- const absolute = path.resolve(file);
163
- const module = yield* Effect.tryPromise({
164
- try: () => import(pathToFileURL(absolute).href),
165
- catch: (cause) => new ConfigError({ file: absolute, message: String(cause) })
166
- });
167
- const config = yield* Effect.try({
168
- try: () => Schema.decodeUnknownSync(ProcdeckConfigSchema)(module.default),
169
- catch: (cause) => new ConfigError({ file: absolute, message: String(cause) })
170
- });
171
- const root = path.dirname(absolute);
172
- const loaded = { config, root, name: config.name ?? path.basename(root) };
173
- return loaded;
174
- });
175
-
176
- export {
177
- PreflightSchema,
178
- AlertSchema,
179
- ProcSpecSchema,
180
- DEFAULT_UI_PORT,
181
- usesOwnPort,
182
- resolveSpec,
183
- ProcdeckConfigSchema,
184
- defineConfig,
185
- ConfigError,
186
- loadConfig
187
- };