portweave 0.1.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.
Files changed (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +535 -0
  3. package/dist/allocator/allocate.concurrent.d.ts +3 -0
  4. package/dist/allocator/allocate.concurrent.d.ts.map +1 -0
  5. package/dist/allocator/allocate.concurrent.js +10 -0
  6. package/dist/allocator/allocate.concurrent.js.map +1 -0
  7. package/dist/allocator/allocate.d.ts +20 -0
  8. package/dist/allocator/allocate.d.ts.map +1 -0
  9. package/dist/allocator/allocate.js +106 -0
  10. package/dist/allocator/allocate.js.map +1 -0
  11. package/dist/allocator/cross-project.d.ts +2 -0
  12. package/dist/allocator/cross-project.d.ts.map +1 -0
  13. package/dist/allocator/cross-project.js +9 -0
  14. package/dist/allocator/cross-project.js.map +1 -0
  15. package/dist/allocator/order.d.ts +2 -0
  16. package/dist/allocator/order.d.ts.map +1 -0
  17. package/dist/allocator/order.js +8 -0
  18. package/dist/allocator/order.js.map +1 -0
  19. package/dist/allocator/pool.d.ts +35 -0
  20. package/dist/allocator/pool.d.ts.map +1 -0
  21. package/dist/allocator/pool.js +74 -0
  22. package/dist/allocator/pool.js.map +1 -0
  23. package/dist/allocator/probe.d.ts +22 -0
  24. package/dist/allocator/probe.d.ts.map +1 -0
  25. package/dist/allocator/probe.js +40 -0
  26. package/dist/allocator/probe.js.map +1 -0
  27. package/dist/cli/banner.d.ts +25 -0
  28. package/dist/cli/banner.d.ts.map +1 -0
  29. package/dist/cli/banner.js +61 -0
  30. package/dist/cli/banner.js.map +1 -0
  31. package/dist/cli/run.d.ts +15 -0
  32. package/dist/cli/run.d.ts.map +1 -0
  33. package/dist/cli/run.js +156 -0
  34. package/dist/cli/run.js.map +1 -0
  35. package/dist/cli/show.d.ts +21 -0
  36. package/dist/cli/show.d.ts.map +1 -0
  37. package/dist/cli/show.js +141 -0
  38. package/dist/cli/show.js.map +1 -0
  39. package/dist/cli/spawn.d.ts +30 -0
  40. package/dist/cli/spawn.d.ts.map +1 -0
  41. package/dist/cli/spawn.js +85 -0
  42. package/dist/cli/spawn.js.map +1 -0
  43. package/dist/cli.d.ts +5 -0
  44. package/dist/cli.d.ts.map +1 -0
  45. package/dist/cli.js +52 -0
  46. package/dist/cli.js.map +1 -0
  47. package/dist/config/anonymous.d.ts +5 -0
  48. package/dist/config/anonymous.d.ts.map +1 -0
  49. package/dist/config/anonymous.js +21 -0
  50. package/dist/config/anonymous.js.map +1 -0
  51. package/dist/config/index.d.ts +4 -0
  52. package/dist/config/index.d.ts.map +1 -0
  53. package/dist/config/index.js +4 -0
  54. package/dist/config/index.js.map +1 -0
  55. package/dist/config/loader.d.ts +8 -0
  56. package/dist/config/loader.d.ts.map +1 -0
  57. package/dist/config/loader.js +59 -0
  58. package/dist/config/loader.js.map +1 -0
  59. package/dist/config/schema.d.ts +21 -0
  60. package/dist/config/schema.d.ts.map +1 -0
  61. package/dist/config/schema.js +125 -0
  62. package/dist/config/schema.js.map +1 -0
  63. package/dist/env/build.d.ts +4 -0
  64. package/dist/env/build.d.ts.map +1 -0
  65. package/dist/env/build.js +17 -0
  66. package/dist/env/build.js.map +1 -0
  67. package/dist/env/dotenv-merge.d.ts +5 -0
  68. package/dist/env/dotenv-merge.d.ts.map +1 -0
  69. package/dist/env/dotenv-merge.js +73 -0
  70. package/dist/env/dotenv-merge.js.map +1 -0
  71. package/dist/env/index.d.ts +4 -0
  72. package/dist/env/index.d.ts.map +1 -0
  73. package/dist/env/index.js +4 -0
  74. package/dist/env/index.js.map +1 -0
  75. package/dist/env/resolve.d.ts +11 -0
  76. package/dist/env/resolve.d.ts.map +1 -0
  77. package/dist/env/resolve.js +29 -0
  78. package/dist/env/resolve.js.map +1 -0
  79. package/dist/env/templates.d.ts +2 -0
  80. package/dist/env/templates.d.ts.map +1 -0
  81. package/dist/env/templates.js +11 -0
  82. package/dist/env/templates.js.map +1 -0
  83. package/dist/env/writer.d.ts +6 -0
  84. package/dist/env/writer.d.ts.map +1 -0
  85. package/dist/env/writer.js +68 -0
  86. package/dist/env/writer.js.map +1 -0
  87. package/dist/errors.d.ts +22 -0
  88. package/dist/errors.d.ts.map +1 -0
  89. package/dist/errors.js +32 -0
  90. package/dist/errors.js.map +1 -0
  91. package/dist/index.d.ts +11 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +11 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/registry/atomic-write.d.ts +3 -0
  96. package/dist/registry/atomic-write.d.ts.map +1 -0
  97. package/dist/registry/atomic-write.js +43 -0
  98. package/dist/registry/atomic-write.js.map +1 -0
  99. package/dist/registry/lock.d.ts +4 -0
  100. package/dist/registry/lock.d.ts.map +1 -0
  101. package/dist/registry/lock.js +86 -0
  102. package/dist/registry/lock.js.map +1 -0
  103. package/dist/registry/paths.d.ts +7 -0
  104. package/dist/registry/paths.d.ts.map +1 -0
  105. package/dist/registry/paths.js +13 -0
  106. package/dist/registry/paths.js.map +1 -0
  107. package/dist/registry/prune.d.ts +3 -0
  108. package/dist/registry/prune.d.ts.map +1 -0
  109. package/dist/registry/prune.js +24 -0
  110. package/dist/registry/prune.js.map +1 -0
  111. package/dist/registry/serialize.d.ts +6 -0
  112. package/dist/registry/serialize.d.ts.map +1 -0
  113. package/dist/registry/serialize.js +134 -0
  114. package/dist/registry/serialize.js.map +1 -0
  115. package/dist/registry/storage.concurrent.d.ts +3 -0
  116. package/dist/registry/storage.concurrent.d.ts.map +1 -0
  117. package/dist/registry/storage.concurrent.js +10 -0
  118. package/dist/registry/storage.concurrent.js.map +1 -0
  119. package/dist/registry/storage.d.ts +11 -0
  120. package/dist/registry/storage.d.ts.map +1 -0
  121. package/dist/registry/storage.js +101 -0
  122. package/dist/registry/storage.js.map +1 -0
  123. package/dist/registry/types.d.ts +14 -0
  124. package/dist/registry/types.d.ts.map +1 -0
  125. package/dist/registry/types.js +2 -0
  126. package/dist/registry/types.js.map +1 -0
  127. package/dist/result.d.ts +11 -0
  128. package/dist/result.d.ts.map +1 -0
  129. package/dist/result.js +6 -0
  130. package/dist/result.js.map +1 -0
  131. package/dist/runtime/error-passthrough.d.ts +20 -0
  132. package/dist/runtime/error-passthrough.d.ts.map +1 -0
  133. package/dist/runtime/error-passthrough.js +20 -0
  134. package/dist/runtime/error-passthrough.js.map +1 -0
  135. package/dist/runtime/exports-smoke.d.ts +4 -0
  136. package/dist/runtime/exports-smoke.d.ts.map +1 -0
  137. package/dist/runtime/exports-smoke.js +36 -0
  138. package/dist/runtime/exports-smoke.js.map +1 -0
  139. package/dist/runtime/index.d.ts +15 -0
  140. package/dist/runtime/index.d.ts.map +1 -0
  141. package/dist/runtime/index.js +99 -0
  142. package/dist/runtime/index.js.map +1 -0
  143. package/dist/runtime/upward-walk.d.ts +14 -0
  144. package/dist/runtime/upward-walk.d.ts.map +1 -0
  145. package/dist/runtime/upward-walk.js +39 -0
  146. package/dist/runtime/upward-walk.js.map +1 -0
  147. package/dist/worktree/git.d.ts +13 -0
  148. package/dist/worktree/git.d.ts.map +1 -0
  149. package/dist/worktree/git.js +66 -0
  150. package/dist/worktree/git.js.map +1 -0
  151. package/dist/worktree/key.d.ts +10 -0
  152. package/dist/worktree/key.d.ts.map +1 -0
  153. package/dist/worktree/key.js +38 -0
  154. package/dist/worktree/key.js.map +1 -0
  155. package/dist/worktree/namespace.d.ts +8 -0
  156. package/dist/worktree/namespace.d.ts.map +1 -0
  157. package/dist/worktree/namespace.js +58 -0
  158. package/dist/worktree/namespace.js.map +1 -0
  159. package/package.json +109 -0
@@ -0,0 +1,21 @@
1
+ import type { Command } from 'commander';
2
+ import type { PortweaveError } from '../errors.ts';
3
+ import { type Result } from '../result.ts';
4
+ export interface ShowOptions {
5
+ cwd?: string;
6
+ /**
7
+ * Test-injection override for process.env. Scoped XDG_CONFIG_HOME and
8
+ * other env vars flow through to `withRegistry`. Defaults to `process.env`
9
+ * in production. See `src/cli/__tests__/show.test.ts` for usage.
10
+ */
11
+ env?: NodeJS.ProcessEnv;
12
+ json?: boolean;
13
+ stderr?: NodeJS.WritableStream;
14
+ stdout?: NodeJS.WritableStream;
15
+ }
16
+ export interface ShowOutcome {
17
+ readonly exitCode: number;
18
+ }
19
+ export declare function runShow(options: ShowOptions): Promise<Result<ShowOutcome, PortweaveError>>;
20
+ export declare function registerShowCommand(program: Command): void;
21
+ //# sourceMappingURL=show.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"show.d.ts","sourceRoot":"","sources":["../../src/cli/show.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAExC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAIlD,OAAO,EAAM,KAAK,MAAM,EAAE,MAAM,cAAc,CAAA;AAM9C,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IACvB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAA;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAA;CAC/B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAoHD,wBAAsB,OAAO,CAC3B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC,CAoC9C;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAa1D"}
@@ -0,0 +1,141 @@
1
+ import { buildEnvMap } from "../env/index.js";
2
+ import { ok } from "../result.js";
3
+ import { withRegistry } from "../registry/storage.js";
4
+ import { loadConfig } from "../config/index.js";
5
+ import { resolveAllocationKey } from "../worktree/key.js";
6
+ import { formatAllocationBanner } from "./banner.js";
7
+ const NO_ALLOCATION_JSON = '{"error":"no-allocation"}\n';
8
+ const NO_ALLOCATION_MSG = '[portweave] no allocation for this worktree — run "portweave run" first\n';
9
+ // Checks equality by the three key fields withRegistry uses internally.
10
+ function keysEqual(a, b) {
11
+ return (a.gitCommonDir === b.gitCommonDir &&
12
+ a.namespace === b.namespace &&
13
+ a.worktreeRoot === b.worktreeRoot);
14
+ }
15
+ function writeOut(stream, text) {
16
+ return new Promise((resolve, reject) => {
17
+ stream.write(text, (writeErr) => {
18
+ if (writeErr) {
19
+ reject(writeErr);
20
+ }
21
+ else {
22
+ resolve();
23
+ }
24
+ });
25
+ });
26
+ }
27
+ function buildJsonPayload(entry, envMap) {
28
+ // Top-level keys alphabetically sorted per spec.
29
+ const payload = {
30
+ env: sortedObject(envMap),
31
+ namespace: entry.namespace,
32
+ ports: sortedObject(entry.ports),
33
+ worktreeRoot: entry.key.worktreeRoot,
34
+ };
35
+ return JSON.stringify(payload, null, 2) + '\n';
36
+ }
37
+ // T is constrained to the JSON-leaf types we actually use (string for env,
38
+ // number for ports). The constraint documents valid usage and lets the
39
+ // type narrowing through index access stay sound under future strictness.
40
+ function sortedObject(obj) {
41
+ const sorted = {};
42
+ for (const key of Object.keys(obj).sort()) {
43
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
44
+ sorted[key] = obj[key];
45
+ }
46
+ return sorted;
47
+ }
48
+ function lookupEntry(key, processEnv) {
49
+ return withRegistry((handle) => {
50
+ const found = handle.entries.find((e) => keysEqual(e.key, key));
51
+ if (found === undefined) {
52
+ return null;
53
+ }
54
+ handle.touch(key);
55
+ // Return the post-touch entry
56
+ const touched = handle.entries.find((e) => keysEqual(e.key, key));
57
+ return touched ?? found;
58
+ }, processEnv);
59
+ }
60
+ async function emitOutput(args) {
61
+ const { config, entry, json, stderr, stdout } = args;
62
+ if (!json) {
63
+ const banner = formatAllocationBanner({
64
+ allocation: entry,
65
+ config,
66
+ reused: true,
67
+ });
68
+ await writeOut(stdout, banner);
69
+ return 0;
70
+ }
71
+ let envMap;
72
+ try {
73
+ envMap = buildEnvMap(entry, config);
74
+ }
75
+ catch (caught) {
76
+ const msg = caught instanceof Error ? caught.message : String(caught);
77
+ await writeOut(stderr, `[portweave] ${msg}\n`);
78
+ return 1;
79
+ }
80
+ await writeOut(stdout, buildJsonPayload(entry, envMap));
81
+ return 0;
82
+ }
83
+ async function resolveInputs(cwd) {
84
+ const keyResult = resolveAllocationKey(cwd);
85
+ if (!keyResult.ok) {
86
+ return keyResult;
87
+ }
88
+ const configResult = await loadConfig(keyResult.value.worktreeRoot);
89
+ if (!configResult.ok) {
90
+ return configResult;
91
+ }
92
+ return ok({ config: configResult.value, key: keyResult.value });
93
+ }
94
+ export async function runShow(options) {
95
+ const cwd = options.cwd ?? process.cwd();
96
+ const processEnv = options.env ?? process.env;
97
+ const stdout = options.stdout ?? process.stdout;
98
+ const stderr = options.stderr ?? process.stderr;
99
+ const json = options.json === true;
100
+ const inputsResult = await resolveInputs(cwd);
101
+ if (!inputsResult.ok) {
102
+ await writeOut(stderr, `[portweave] ${inputsResult.error.message}\n`);
103
+ return ok({ exitCode: 1 });
104
+ }
105
+ const { config, key } = inputsResult.value;
106
+ const registryResult = await lookupEntry(key, processEnv);
107
+ if (!registryResult.ok) {
108
+ await writeOut(stderr, `[portweave] ${registryResult.error.message}\n`);
109
+ return ok({ exitCode: 1 });
110
+ }
111
+ if (registryResult.value === null) {
112
+ await writeOut(stdout, json ? NO_ALLOCATION_JSON : '');
113
+ if (!json) {
114
+ await writeOut(stderr, NO_ALLOCATION_MSG);
115
+ }
116
+ return ok({ exitCode: 1 });
117
+ }
118
+ const exitCode = await emitOutput({
119
+ config,
120
+ entry: registryResult.value,
121
+ json,
122
+ stderr,
123
+ stdout,
124
+ });
125
+ return ok({ exitCode });
126
+ }
127
+ export function registerShowCommand(program) {
128
+ program
129
+ .command('show')
130
+ .description('Print the port allocation for the current worktree')
131
+ .option('--json', 'Output as JSON')
132
+ .action(async (opts) => {
133
+ const result = await runShow({ json: opts.json });
134
+ if (!result.ok) {
135
+ process.stderr.write(`[portweave] ${result.error.message}\n`);
136
+ process.exit(1);
137
+ }
138
+ process.exit(result.value.exitCode);
139
+ });
140
+ }
141
+ //# sourceMappingURL=show.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"show.js","sourceRoot":"","sources":["../../src/cli/show.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { PortweaveError } from '../errors.ts';
2
+ import { type Result } from '../result.ts';
3
+ import type { RunIo } from './run.ts';
4
+ export interface SpawnChildOptions {
5
+ env: NodeJS.ProcessEnv;
6
+ io: Pick<RunIo, 'stderr' | 'stdout'>;
7
+ signal?: AbortSignal;
8
+ }
9
+ export interface SpawnChildResult {
10
+ exitCode: null | number;
11
+ signal: NodeJS.Signals | null;
12
+ }
13
+ /**
14
+ * Spawns a child process with `stdio: 'inherit'`, forwards SIGINT and SIGTERM,
15
+ * and resolves with the child's exit status.
16
+ *
17
+ * - Resolves with `ok({ exitCode, signal })` on any child exit (including non-zero).
18
+ * - Returns `err(PortweaveError(CLI_CHILD_SPAWN_FAILED))` if the child fails to start.
19
+ * - If `signal` is provided, aborts by sending SIGTERM to the child when the signal fires.
20
+ */
21
+ export declare function spawnChild(argv: readonly string[], options: SpawnChildOptions): Promise<Result<SpawnChildResult, PortweaveError>>;
22
+ /**
23
+ * Converts a `SpawnChildResult` into a numeric exit code following POSIX
24
+ * shell conventions:
25
+ * - exitCode is returned as-is when not null
26
+ * - signal termination → 128 + signal number
27
+ * - unknown termination → 1
28
+ */
29
+ export declare function resolveExitCode(result: SpawnChildResult): number;
30
+ //# sourceMappingURL=spawn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn.d.ts","sourceRoot":"","sources":["../../src/cli/spawn.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAkB,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AAErC,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAA;IAMtB,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,GAAG,QAAQ,CAAC,CAAA;IACpC,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,IAAI,GAAG,MAAM,CAAA;IACvB,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAA;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC,CAgEnD;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAUhE"}
@@ -0,0 +1,85 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { PortweaveError, PW_ERROR_CODES } from "../errors.js";
3
+ import { err, ok } from "../result.js";
4
+ /**
5
+ * Spawns a child process with `stdio: 'inherit'`, forwards SIGINT and SIGTERM,
6
+ * and resolves with the child's exit status.
7
+ *
8
+ * - Resolves with `ok({ exitCode, signal })` on any child exit (including non-zero).
9
+ * - Returns `err(PortweaveError(CLI_CHILD_SPAWN_FAILED))` if the child fails to start.
10
+ * - If `signal` is provided, aborts by sending SIGTERM to the child when the signal fires.
11
+ */
12
+ export function spawnChild(argv, options) {
13
+ if (argv.length === 0) {
14
+ return Promise.resolve(err(new PortweaveError(PW_ERROR_CODES.CLI_CHILD_SPAWN_FAILED, 'No command provided to spawnChild')));
15
+ }
16
+ const [cmd, ...args] = argv;
17
+ return new Promise((resolve) => {
18
+ // spawn (not exec) is used intentionally: no shell is involved, so shell
19
+ // metacharacters in argv are inert. Relative-path commands (e.g. ./my-script)
20
+ // are allowed — that is user-intent behavior, not a vulnerability, since the
21
+ // caller explicitly passed the command string.
22
+ const child = spawn(cmd, args, { env: options.env, stdio: 'inherit' });
23
+ const sigintHandler = () => {
24
+ child.kill('SIGINT');
25
+ };
26
+ const sigtermHandler = () => {
27
+ child.kill('SIGTERM');
28
+ };
29
+ process.on('SIGINT', sigintHandler);
30
+ process.on('SIGTERM', sigtermHandler);
31
+ let abortHandler;
32
+ if (options.signal !== undefined) {
33
+ abortHandler = () => {
34
+ child.kill('SIGTERM');
35
+ };
36
+ options.signal.addEventListener('abort', abortHandler);
37
+ }
38
+ function teardown() {
39
+ process.off('SIGINT', sigintHandler);
40
+ process.off('SIGTERM', sigtermHandler);
41
+ if (options.signal !== undefined && abortHandler !== undefined) {
42
+ options.signal.removeEventListener('abort', abortHandler);
43
+ }
44
+ }
45
+ child.on('error', (error) => {
46
+ teardown();
47
+ resolve(err(new PortweaveError(PW_ERROR_CODES.CLI_CHILD_SPAWN_FAILED, `failed to spawn "${cmd}": ${error.message}`)));
48
+ });
49
+ child.on('exit', (code, signal) => {
50
+ teardown();
51
+ resolve(ok({ exitCode: code, signal }));
52
+ });
53
+ });
54
+ }
55
+ /**
56
+ * Converts a `SpawnChildResult` into a numeric exit code following POSIX
57
+ * shell conventions:
58
+ * - exitCode is returned as-is when not null
59
+ * - signal termination → 128 + signal number
60
+ * - unknown termination → 1
61
+ */
62
+ export function resolveExitCode(result) {
63
+ if (result.exitCode !== null) {
64
+ return result.exitCode;
65
+ }
66
+ if (result.signal !== null) {
67
+ return 128 + signalNumber(result.signal);
68
+ }
69
+ // Both exitCode and signal are null: child was killed externally before the
70
+ // exit event fired with meaningful values. Return 1 as a safe non-zero sentinel.
71
+ return 1;
72
+ }
73
+ const SIGNAL_NUMBERS = {
74
+ SIGABRT: 6,
75
+ SIGHUP: 1,
76
+ SIGINT: 2,
77
+ SIGKILL: 9,
78
+ SIGQUIT: 3,
79
+ SIGSTOP: 19,
80
+ SIGTERM: 15,
81
+ };
82
+ function signalNumber(signal) {
83
+ return SIGNAL_NUMBERS[signal] ?? 0;
84
+ }
85
+ //# sourceMappingURL=spawn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn.js","sourceRoot":"","sources":["../../src/cli/spawn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAC1C,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAE,GAAG,EAAE,EAAE,EAAe,MAAM,cAAc,CAAA;AAmBnD;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CACxB,IAAuB,EACvB,OAA0B;IAE1B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,OAAO,CAAC,OAAO,CACpB,GAAG,CACD,IAAI,cAAc,CAChB,cAAc,CAAC,sBAAsB,EACrC,mCAAmC,CACpC,CACF,CACF,CAAA;IACH,CAAC;IAED,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAA;IAE3B,OAAO,IAAI,OAAO,CAA2C,CAAC,OAAO,EAAE,EAAE;QACvE,yEAAyE;QACzE,8EAA8E;QAC9E,6EAA6E;QAC7E,+CAA+C;QAC/C,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAA;QAEtE,MAAM,aAAa,GAAG,GAAG,EAAE;YACzB,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACtB,CAAC,CAAA;QACD,MAAM,cAAc,GAAG,GAAG,EAAE;YAC1B,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACvB,CAAC,CAAA;QAED,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAA;QACnC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,cAAc,CAAC,CAAA;QAErC,IAAI,YAAsC,CAAA;QAC1C,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YACjC,YAAY,GAAG,GAAG,EAAE;gBAClB,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;YACvB,CAAC,CAAA;YACD,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAA;QACxD,CAAC;QAED,SAAS,QAAQ;YACf,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAA;YACpC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,cAAc,CAAC,CAAA;YACtC,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;gBAC/D,OAAO,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAA;YAC3D,CAAC;QACH,CAAC;QAED,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAY,EAAE,EAAE;YACjC,QAAQ,EAAE,CAAA;YACV,OAAO,CACL,GAAG,CACD,IAAI,cAAc,CAChB,cAAc,CAAC,sBAAsB,EACrC,oBAAoB,GAAG,MAAM,KAAK,CAAC,OAAO,EAAE,CAC7C,CACF,CACF,CAAA;QACH,CAAC,CAAC,CAAA;QAEF,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAmB,EAAE,MAA6B,EAAE,EAAE;YACtE,QAAQ,EAAE,CAAA;YACV,OAAO,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;QACzC,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,MAAwB;IACtD,IAAI,MAAM,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;QAC7B,OAAO,MAAM,CAAC,QAAQ,CAAA;IACxB,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;QAC3B,OAAO,GAAG,GAAG,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IAC1C,CAAC;IACD,4EAA4E;IAC5E,iFAAiF;IACjF,OAAO,CAAC,CAAA;AACV,CAAC;AAED,MAAM,cAAc,GAAqC;IACvD,OAAO,EAAE,CAAC;IACV,MAAM,EAAE,CAAC;IACT,MAAM,EAAE,CAAC;IACT,OAAO,EAAE,CAAC;IACV,OAAO,EAAE,CAAC;IACV,OAAO,EAAE,EAAE;IACX,OAAO,EAAE,EAAE;CACZ,CAAA;AAED,SAAS,YAAY,CAAC,MAAsB;IAC1C,OAAO,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;AACpC,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ export declare function buildCli(): Command;
4
+ export declare function main(argv?: readonly string[]): Promise<number>;
5
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAQnC,wBAAgB,QAAQ,IAAI,OAAO,CAiBlC;AAED,wBAAsB,IAAI,CACxB,IAAI,GAAE,SAAS,MAAM,EAAiB,GACrC,OAAO,CAAC,MAAM,CAAC,CAuBjB"}
package/dist/cli.js ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { Command } from 'commander';
4
+ import { PortweaveError } from "./errors.js";
5
+ import { registerRunCommand } from "./cli/run.js";
6
+ import { registerShowCommand } from "./cli/show.js";
7
+ const require = createRequire(import.meta.url);
8
+ const pkg = require('../package.json');
9
+ export function buildCli() {
10
+ const program = new Command('portweave');
11
+ program
12
+ .version(pkg.version, '-V, --version', 'output the version number')
13
+ .description('Zero-thought, conflict-free local-dev port allocation across projects and git worktrees.')
14
+ .enablePositionalOptions()
15
+ .option('--config <path>', 'path to portweave config file')
16
+ .option('--count <n>', 'number of ports to allocate (anonymous mode)')
17
+ .option('--verbose', 'print additional diagnostic output');
18
+ registerRunCommand(program);
19
+ registerShowCommand(program);
20
+ return program;
21
+ }
22
+ export async function main(argv = process.argv) {
23
+ const program = buildCli();
24
+ try {
25
+ await program.parseAsync(argv);
26
+ const ec = process.exitCode;
27
+ return typeof ec === 'number' ? ec : 0;
28
+ }
29
+ catch (caught) {
30
+ if (caught instanceof PortweaveError) {
31
+ process.stderr.write(`[portweave] error: ${caught.message} (${caught.code})\n`);
32
+ }
33
+ else if (caught instanceof Error) {
34
+ process.stderr.write(`[portweave] error: ${caught.message}\n`);
35
+ const verbose = argv.includes('--verbose');
36
+ if (verbose && caught.stack !== undefined) {
37
+ process.stderr.write(caught.stack + '\n');
38
+ }
39
+ }
40
+ else {
41
+ process.stderr.write(`[portweave] error: unknown error\n`);
42
+ }
43
+ return 1;
44
+ }
45
+ }
46
+ // Entry point when run as a script
47
+ if (import.meta.url === `file://${process.argv[1]}`) {
48
+ void main().then((code) => {
49
+ process.exit(code);
50
+ });
51
+ }
52
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAEnD,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAC9C,MAAM,GAAG,GAAG,OAAO,CAAC,iBAAiB,CAAwB,CAAA;AAE7D,MAAM,UAAU,QAAQ;IACtB,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,WAAW,CAAC,CAAA;IAExC,OAAO;SACJ,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,eAAe,EAAE,2BAA2B,CAAC;SAClE,WAAW,CACV,0FAA0F,CAC3F;SACA,uBAAuB,EAAE;SACzB,MAAM,CAAC,iBAAiB,EAAE,+BAA+B,CAAC;SAC1D,MAAM,CAAC,aAAa,EAAE,8CAA8C,CAAC;SACrE,MAAM,CAAC,WAAW,EAAE,oCAAoC,CAAC,CAAA;IAE5D,kBAAkB,CAAC,OAAO,CAAC,CAAA;IAC3B,mBAAmB,CAAC,OAAO,CAAC,CAAA;IAE5B,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CACxB,OAA0B,OAAO,CAAC,IAAI;IAEtC,MAAM,OAAO,GAAG,QAAQ,EAAE,CAAA;IAE1B,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QAC9B,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAA;QAC3B,OAAO,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;IACxC,CAAC;IAAC,OAAO,MAAe,EAAE,CAAC;QACzB,IAAI,MAAM,YAAY,cAAc,EAAE,CAAC;YACrC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,sBAAsB,MAAM,CAAC,OAAO,KAAK,MAAM,CAAC,IAAI,KAAK,CAC1D,CAAA;QACH,CAAC;aAAM,IAAI,MAAM,YAAY,KAAK,EAAE,CAAC;YACnC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sBAAsB,MAAM,CAAC,OAAO,IAAI,CAAC,CAAA;YAC9D,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;YAC1C,IAAI,OAAO,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,CAAA;YAC3C,CAAC;QACH,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAA;QAC5D,CAAC;QACD,OAAO,CAAC,CAAA;IACV,CAAC;AACH,CAAC;AAED,mCAAmC;AACnC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IACpD,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;QACxB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACpB,CAAC,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { PortweaveError } from '../errors.ts';
2
+ import { type Result } from '../result.ts';
3
+ import type { Config } from './schema.ts';
4
+ export declare function synthesizeAnonymousConfig(count: number): Result<Config, PortweaveError>;
5
+ //# sourceMappingURL=anonymous.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anonymous.d.ts","sourceRoot":"","sources":["../../src/config/anonymous.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAkB,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,aAAa,CAAA;AAKtD,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,MAAM,GACZ,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAsBhC"}
@@ -0,0 +1,21 @@
1
+ import { PortweaveError, PW_ERROR_CODES } from "../errors.js";
2
+ import { err, ok } from "../result.js";
3
+ const ANONYMOUS_COUNT_MIN = 1;
4
+ const ANONYMOUS_COUNT_MAX = 100;
5
+ export function synthesizeAnonymousConfig(count) {
6
+ if (!Number.isInteger(count) ||
7
+ count < ANONYMOUS_COUNT_MIN ||
8
+ count > ANONYMOUS_COUNT_MAX) {
9
+ return err(new PortweaveError(PW_ERROR_CODES.CONFIG_INVALID, `count must be an integer in [${String(ANONYMOUS_COUNT_MIN)}, ${String(ANONYMOUS_COUNT_MAX)}], received ${String(count)}`));
10
+ }
11
+ const services = [];
12
+ for (let i = 1; i <= count; i += 1) {
13
+ services.push({
14
+ discoveryEnv: {},
15
+ envVar: `PORT_${String(i)}`,
16
+ name: `port-${String(i)}`,
17
+ });
18
+ }
19
+ return ok({ groups: {}, services, source: 'anonymous' });
20
+ }
21
+ //# sourceMappingURL=anonymous.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anonymous.js","sourceRoot":"","sources":["../../src/config/anonymous.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAE,GAAG,EAAE,EAAE,EAAe,MAAM,cAAc,CAAA;AAGnD,MAAM,mBAAmB,GAAG,CAAC,CAAA;AAC7B,MAAM,mBAAmB,GAAG,GAAG,CAAA;AAE/B,MAAM,UAAU,yBAAyB,CACvC,KAAa;IAEb,IACE,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC;QACxB,KAAK,GAAG,mBAAmB;QAC3B,KAAK,GAAG,mBAAmB,EAC3B,CAAC;QACD,OAAO,GAAG,CACR,IAAI,cAAc,CAChB,cAAc,CAAC,cAAc,EAC7B,gCAAgC,MAAM,CAAC,mBAAmB,CAAC,KAAK,MAAM,CAAC,mBAAmB,CAAC,eAAe,MAAM,CAAC,KAAK,CAAC,EAAE,CAC1H,CACF,CAAA;IACH,CAAC;IACD,MAAM,QAAQ,GAAkB,EAAE,CAAA;IAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,QAAQ,CAAC,IAAI,CAAC;YACZ,YAAY,EAAE,EAAE;YAChB,MAAM,EAAE,QAAQ,MAAM,CAAC,CAAC,CAAC,EAAE;YAC3B,IAAI,EAAE,QAAQ,MAAM,CAAC,CAAC,CAAC,EAAE;SAC1B,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAA;AAC1D,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { synthesizeAnonymousConfig } from './anonymous.ts';
2
+ export { loadConfig, type LoadConfigOptions } from './loader.ts';
3
+ export { type Config, type ServiceSpec } from './schema.ts';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAChE,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,4 @@
1
+ export { synthesizeAnonymousConfig } from "./anonymous.js";
2
+ export { loadConfig } from "./loader.js";
3
+ export {} from "./schema.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAAE,UAAU,EAA0B,MAAM,aAAa,CAAA;AAChE,OAAO,EAAiC,MAAM,aAAa,CAAA"}
@@ -0,0 +1,8 @@
1
+ import { PortweaveError } from '../errors.ts';
2
+ import { type Result } from '../result.ts';
3
+ import { type Config } from './schema.ts';
4
+ export interface LoadConfigOptions {
5
+ configPath?: string;
6
+ }
7
+ export declare function loadConfig(cwd: string, opts?: LoadConfigOptions): Promise<Result<Config, PortweaveError>>;
8
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAkB,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,EAAE,KAAK,MAAM,EAA8B,MAAM,aAAa,CAAA;AAIrE,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AA2DD,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,iBAAiB,GACvB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAsBzC"}
@@ -0,0 +1,59 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { isAbsolute, resolve } from 'node:path';
3
+ import { PortweaveError, PW_ERROR_CODES } from "../errors.js";
4
+ import { err, ok } from "../result.js";
5
+ import { validateAndNormalizeConfig } from "./schema.js";
6
+ const DEFAULT_CONFIG_FILENAME = 'portweave.config.json';
7
+ function resolveConfigPath(cwd, configPath) {
8
+ if (configPath === undefined) {
9
+ return resolve(cwd, DEFAULT_CONFIG_FILENAME);
10
+ }
11
+ return isAbsolute(configPath) ? configPath : resolve(cwd, configPath);
12
+ }
13
+ function describe(caught) {
14
+ return caught instanceof Error ? caught.message : String(caught);
15
+ }
16
+ async function readConfigFile(absolutePath) {
17
+ try {
18
+ const contents = await readFile(absolutePath, 'utf8');
19
+ return { contents, tag: 'ok' };
20
+ }
21
+ catch (caught) {
22
+ if (caught instanceof Error &&
23
+ 'code' in caught &&
24
+ caught.code === 'ENOENT') {
25
+ return { tag: 'missing' };
26
+ }
27
+ return {
28
+ problem: new PortweaveError(PW_ERROR_CODES.CONFIG_INVALID, `failed to read ${absolutePath}: ${describe(caught)}`),
29
+ tag: 'error',
30
+ };
31
+ }
32
+ }
33
+ function parseJson(contents, absolutePath) {
34
+ try {
35
+ return ok(JSON.parse(contents));
36
+ }
37
+ catch (caught) {
38
+ return err(new PortweaveError(PW_ERROR_CODES.CONFIG_INVALID, `failed to parse JSON at ${absolutePath}: ${describe(caught)}`));
39
+ }
40
+ }
41
+ export async function loadConfig(cwd, opts) {
42
+ const absolutePath = resolveConfigPath(cwd, opts?.configPath);
43
+ const outcome = await readConfigFile(absolutePath);
44
+ if (outcome.tag === 'missing') {
45
+ return err(new PortweaveError(PW_ERROR_CODES.CONFIG_MISSING, `no portweave.config.json at ${absolutePath}`));
46
+ }
47
+ if (outcome.tag === 'error') {
48
+ return err(outcome.problem);
49
+ }
50
+ const parsed = parseJson(outcome.contents, absolutePath);
51
+ if (!parsed.ok) {
52
+ return parsed;
53
+ }
54
+ return validateAndNormalizeConfig(parsed.value, {
55
+ source: 'file',
56
+ sourcePath: absolutePath,
57
+ });
58
+ }
59
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAE,GAAG,EAAE,EAAE,EAAe,MAAM,cAAc,CAAA;AACnD,OAAO,EAAe,0BAA0B,EAAE,MAAM,aAAa,CAAA;AAErE,MAAM,uBAAuB,GAAG,uBAAuB,CAAA;AAMvD,SAAS,iBAAiB,CACxB,GAAW,EACX,UAA8B;IAE9B,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,OAAO,CAAC,GAAG,EAAE,uBAAuB,CAAC,CAAA;IAC9C,CAAC;IACD,OAAO,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,CAAA;AACvE,CAAC;AAED,SAAS,QAAQ,CAAC,MAAe;IAC/B,OAAO,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;AAClE,CAAC;AAOD,KAAK,UAAU,cAAc,CAAC,YAAoB;IAChD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAA;QACrD,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,CAAA;IAChC,CAAC;IAAC,OAAO,MAAe,EAAE,CAAC;QACzB,IACE,MAAM,YAAY,KAAK;YACvB,MAAM,IAAI,MAAM;YACf,MAA4B,CAAC,IAAI,KAAK,QAAQ,EAC/C,CAAC;YACD,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,CAAA;QAC3B,CAAC;QACD,OAAO;YACL,OAAO,EAAE,IAAI,cAAc,CACzB,cAAc,CAAC,cAAc,EAC7B,kBAAkB,YAAY,KAAK,QAAQ,CAAC,MAAM,CAAC,EAAE,CACtD;YACD,GAAG,EAAE,OAAO;SACb,CAAA;IACH,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAChB,QAAgB,EAChB,YAAoB;IAEpB,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAA;IACjC,CAAC;IAAC,OAAO,MAAe,EAAE,CAAC;QACzB,OAAO,GAAG,CACR,IAAI,cAAc,CAChB,cAAc,CAAC,cAAc,EAC7B,2BAA2B,YAAY,KAAK,QAAQ,CAAC,MAAM,CAAC,EAAE,CAC/D,CACF,CAAA;IACH,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,GAAW,EACX,IAAwB;IAExB,MAAM,YAAY,GAAG,iBAAiB,CAAC,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC,CAAA;IAC7D,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,YAAY,CAAC,CAAA;IAClD,IAAI,OAAO,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,GAAG,CACR,IAAI,cAAc,CAChB,cAAc,CAAC,cAAc,EAC7B,+BAA+B,YAAY,EAAE,CAC9C,CACF,CAAA;IACH,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC;QAC5B,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IAC7B,CAAC;IACD,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAA;IACxD,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,OAAO,MAAM,CAAA;IACf,CAAC;IACD,OAAO,0BAA0B,CAAC,MAAM,CAAC,KAAK,EAAE;QAC9C,MAAM,EAAE,MAAM;QACd,UAAU,EAAE,YAAY;KACzB,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { PortweaveError } from '../errors.ts';
2
+ import { type Result } from '../result.ts';
3
+ export interface ServiceSpec {
4
+ discoveryEnv: Record<string, string>;
5
+ envVar: string;
6
+ group?: string;
7
+ name: string;
8
+ preferred?: number;
9
+ }
10
+ export interface Config {
11
+ groups: Record<string, string[]>;
12
+ services: ServiceSpec[];
13
+ source: 'anonymous' | 'file';
14
+ sourcePath?: string;
15
+ }
16
+ export interface NormalizationContext {
17
+ source: 'anonymous' | 'file';
18
+ sourcePath?: string;
19
+ }
20
+ export declare function validateAndNormalizeConfig(input: unknown, ctx: NormalizationContext): Result<Config, PortweaveError>;
21
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAkB,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,cAAc,CAAA;AA0CnD,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACpC,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IAChC,QAAQ,EAAE,WAAW,EAAE,CAAA;IACvB,MAAM,EAAE,WAAW,GAAG,MAAM,CAAA;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,GAAG,MAAM,CAAA;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAiHD,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,oBAAoB,GACxB,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAoBhC"}