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,125 @@
1
+ import { z } from 'zod';
2
+ import { PortweaveError, PW_ERROR_CODES } from "../errors.js";
3
+ import { err, ok } from "../result.js";
4
+ const SERVICE_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
5
+ const ENV_VAR_PATTERN = /^[A-Z][A-Z0-9_]*$/;
6
+ const PLACEHOLDER_PATTERN = /\$\{([^}]+)\}/g;
7
+ const PORT_MIN = 1;
8
+ const PORT_MAX = 65535;
9
+ const envVarSchema = z
10
+ .string()
11
+ .regex(ENV_VAR_PATTERN, 'envVar must match /^[A-Z][A-Z0-9_]*$/');
12
+ const discoveryEnvSchema = z.record(envVarSchema, z.string());
13
+ const serviceEntrySchema = z.strictObject({
14
+ discoveryEnv: discoveryEnvSchema.optional(),
15
+ envVar: envVarSchema,
16
+ group: z.string().min(1, 'group must be a non-empty string').optional(),
17
+ preferred: z
18
+ .int('preferred must be an integer')
19
+ .min(PORT_MIN, `preferred must be >= ${String(PORT_MIN)}`)
20
+ .max(PORT_MAX, `preferred must be <= ${String(PORT_MAX)}`)
21
+ .optional(),
22
+ });
23
+ const servicesMapSchema = z
24
+ .record(z.string().regex(SERVICE_NAME_PATTERN, 'service name must be kebab-case'), serviceEntrySchema)
25
+ .refine((value) => Object.keys(value).length > 0, {
26
+ error: 'services must contain at least one entry',
27
+ });
28
+ const configFileSchema = z.strictObject({
29
+ $schema: z.string().optional(),
30
+ services: servicesMapSchema,
31
+ });
32
+ function formatZodIssues(error) {
33
+ return error.issues
34
+ .map((issue) => {
35
+ const path = issue.path.length > 0 ? issue.path.join('.') : '(root)';
36
+ return `${path}: ${issue.message}`;
37
+ })
38
+ .join('\n');
39
+ }
40
+ function collectPlaceholders(value) {
41
+ const found = [];
42
+ for (const match of value.matchAll(PLACEHOLDER_PATTERN)) {
43
+ found.push(match[1]);
44
+ }
45
+ return found;
46
+ }
47
+ function checkCrossFieldRules(raw) {
48
+ const ctx = {
49
+ errors: [],
50
+ seen: new Map(),
51
+ serviceNames: new Set(Object.keys(raw.services)),
52
+ };
53
+ for (const [name, entry] of Object.entries(raw.services)) {
54
+ recordEnvVar(name, entry, ctx);
55
+ checkDiscoveryEnv(name, entry, ctx);
56
+ }
57
+ return ctx.errors;
58
+ }
59
+ function recordEnvVar(name, entry, ctx) {
60
+ const owner = `services.${name}.envVar`;
61
+ const prior = ctx.seen.get(entry.envVar);
62
+ if (prior !== undefined) {
63
+ ctx.errors.push(`${owner}: duplicate identifier "${entry.envVar}" already declared at ${prior}`);
64
+ return;
65
+ }
66
+ ctx.seen.set(entry.envVar, owner);
67
+ }
68
+ function checkDiscoveryEnv(name, entry, ctx) {
69
+ if (entry.discoveryEnv === undefined) {
70
+ return;
71
+ }
72
+ for (const [envKey, template] of Object.entries(entry.discoveryEnv)) {
73
+ const owner = `services.${name}.discoveryEnv.${envKey}`;
74
+ const prior = ctx.seen.get(envKey);
75
+ if (prior !== undefined) {
76
+ ctx.errors.push(`${owner}: duplicate identifier "${envKey}" already declared at ${prior}`);
77
+ }
78
+ else {
79
+ ctx.seen.set(envKey, owner);
80
+ }
81
+ for (const placeholder of collectPlaceholders(template)) {
82
+ if (!ctx.serviceNames.has(placeholder)) {
83
+ ctx.errors.push(`${owner}: template references unknown service "${placeholder}"`);
84
+ }
85
+ }
86
+ }
87
+ }
88
+ // SERVICE_NAME_PATTERN (^[a-z][a-z0-9-]*$) is load-bearing for stable service
89
+ // ordering: V8 reorders purely-numeric keys ahead of other keys, so requiring
90
+ // names to start with [a-z] ensures JSON.parse insertion-order is preserved.
91
+ function normalize(raw, ctx) {
92
+ const services = Object.entries(raw.services).map(([name, entry]) => ({
93
+ discoveryEnv: entry.discoveryEnv ?? {},
94
+ envVar: entry.envVar,
95
+ ...(entry.group !== undefined ? { group: entry.group } : {}),
96
+ name,
97
+ ...(entry.preferred !== undefined ? { preferred: entry.preferred } : {}),
98
+ }));
99
+ const groups = {};
100
+ for (const service of services) {
101
+ if (service.group !== undefined) {
102
+ const bucket = groups[service.group] ?? [];
103
+ bucket.push(service.name);
104
+ groups[service.group] = bucket;
105
+ }
106
+ }
107
+ return {
108
+ groups,
109
+ services,
110
+ source: ctx.source,
111
+ ...(ctx.sourcePath !== undefined ? { sourcePath: ctx.sourcePath } : {}),
112
+ };
113
+ }
114
+ export function validateAndNormalizeConfig(input, ctx) {
115
+ const parsed = configFileSchema.safeParse(input);
116
+ if (!parsed.success) {
117
+ return err(new PortweaveError(PW_ERROR_CODES.CONFIG_INVALID, formatZodIssues(parsed.error)));
118
+ }
119
+ const crossFieldErrors = checkCrossFieldRules(parsed.data);
120
+ if (crossFieldErrors.length > 0) {
121
+ return err(new PortweaveError(PW_ERROR_CODES.CONFIG_INVALID, crossFieldErrors.join('\n')));
122
+ }
123
+ return ok(normalize(parsed.data, ctx));
124
+ }
125
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { Config } from '../config/index.ts';
2
+ import type { Allocation } from '../allocator/allocate.ts';
3
+ export declare function buildEnvMap(allocation: Allocation, config: Config): Record<string, string>;
4
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/env/build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAEhD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAG1D,wBAAgB,WAAW,CACzB,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,MAAM,GACb,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAqBxB"}
@@ -0,0 +1,17 @@
1
+ import { PortweaveError, PW_ERROR_CODES } from "../errors.js";
2
+ import { evaluateTemplate } from "./templates.js";
3
+ export function buildEnvMap(allocation, config) {
4
+ const result = {};
5
+ for (const service of config.services) {
6
+ if (!Object.hasOwn(allocation.ports, service.name)) {
7
+ throw new PortweaveError(PW_ERROR_CODES.ENV_BUILD_INVALID, `service "${service.name}" has no port in the allocation — config/allocation drift detected`);
8
+ }
9
+ const port = allocation.ports[service.name];
10
+ result[service.envVar] = String(port);
11
+ for (const [discoveryKey, template] of Object.entries(service.discoveryEnv)) {
12
+ result[discoveryKey] = evaluateTemplate(template, allocation.ports);
13
+ }
14
+ }
15
+ return result;
16
+ }
17
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/env/build.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAE7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAEjD,MAAM,UAAU,WAAW,CACzB,UAAsB,EACtB,MAAc;IAEd,MAAM,MAAM,GAA2B,EAAE,CAAA;IAEzC,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACtC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,MAAM,IAAI,cAAc,CACtB,cAAc,CAAC,iBAAiB,EAChC,YAAY,OAAO,CAAC,IAAI,oEAAoE,CAC7F,CAAA;QACH,CAAC;QACD,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QAC3C,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;QAErC,KAAK,MAAM,CAAC,YAAY,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CACnD,OAAO,CAAC,YAAY,CACrB,EAAE,CAAC;YACF,MAAM,CAAC,YAAY,CAAC,GAAG,gBAAgB,CAAC,QAAQ,EAAE,UAAU,CAAC,KAAK,CAAC,CAAA;QACrE,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { PortweaveError } from '../errors.ts';
2
+ import { type Result } from '../result.ts';
3
+ export declare function readDotenvFile(path: string): Promise<Result<Record<string, string>, PortweaveError>>;
4
+ export declare function applyDotenvOverrides(computed: Readonly<Record<string, string>>, dotenv: Readonly<Record<string, string>>): Record<string, string>;
5
+ //# sourceMappingURL=dotenv-merge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dotenv-merge.d.ts","sourceRoot":"","sources":["../../src/env/dotenv-merge.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAkB,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,cAAc,CAAA;AAkDnD,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,cAAc,CAAC,CAAC,CA+BzD;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAC1C,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACvC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQxB"}
@@ -0,0 +1,73 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { PortweaveError, PW_ERROR_CODES } from "../errors.js";
3
+ import { err, ok } from "../result.js";
4
+ // Matches KEY=value, KEY="value", KEY='value' — key must start with a letter
5
+ // or underscore and contain only word chars.
6
+ const DOTENV_LINE_PATTERN = /^([A-Za-z_][A-Za-z0-9_]*)=(["']?)(.*)(\2)$/;
7
+ function parseLine(line, lineNumber) {
8
+ const trimmed = line.trim();
9
+ // Skip blank lines and comment lines
10
+ if (trimmed === '' || trimmed.startsWith('#')) {
11
+ return ok({ key: '', value: '' });
12
+ }
13
+ const match = DOTENV_LINE_PATTERN.exec(trimmed);
14
+ if (match === null) {
15
+ return err(new PortweaveError(PW_ERROR_CODES.ENV_DOTENV_PARSE_FAILED, `malformed .env line ${String(lineNumber)}: ${trimmed}`));
16
+ }
17
+ const key = match[1];
18
+ const quote = match[2];
19
+ let value;
20
+ if (quote === '"' || quote === "'") {
21
+ // Strip surrounding quotes (match group 3 is the inner content).
22
+ // `#` inside a quoted value is treated literally per dotenv convention.
23
+ value = match[3];
24
+ }
25
+ else {
26
+ // Unquoted: everything after the =. Strip inline trailing comments
27
+ // (` # ...` to end-of-line) since unquoted values cannot contain a `#`
28
+ // literal without breaking dotenv consumers. Users who need a literal
29
+ // `#` should quote the value.
30
+ const eqIndex = trimmed.indexOf('=');
31
+ const raw = trimmed.slice(eqIndex + 1);
32
+ const commentMatch = /\s+#.*$/.exec(raw);
33
+ value =
34
+ commentMatch === null ? raw : raw.slice(0, commentMatch.index).trimEnd();
35
+ }
36
+ return ok({ key, value });
37
+ }
38
+ export async function readDotenvFile(path) {
39
+ let content;
40
+ try {
41
+ content = await readFile(path, 'utf-8');
42
+ }
43
+ catch (caught) {
44
+ if (typeof caught === 'object' &&
45
+ caught !== null &&
46
+ 'code' in caught &&
47
+ caught.code === 'ENOENT') {
48
+ return ok({});
49
+ }
50
+ throw caught;
51
+ }
52
+ const result = {};
53
+ const lines = content.split('\n');
54
+ for (const [i, line] of lines.entries()) {
55
+ const parsed = parseLine(line, i + 1);
56
+ if (!parsed.ok) {
57
+ return parsed;
58
+ }
59
+ // Blank lines and comments produce key='' — skip them
60
+ if (parsed.value.key !== '') {
61
+ result[parsed.value.key] = parsed.value.value;
62
+ }
63
+ }
64
+ return ok(result);
65
+ }
66
+ export function applyDotenvOverrides(computed, dotenv) {
67
+ const result = {};
68
+ for (const [key, computedValue] of Object.entries(computed)) {
69
+ result[key] = key in dotenv ? dotenv[key] : computedValue;
70
+ }
71
+ return result;
72
+ }
73
+ //# sourceMappingURL=dotenv-merge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dotenv-merge.js","sourceRoot":"","sources":["../../src/env/dotenv-merge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { buildEnvMap } from './build.ts';
2
+ export { type ResolvedEnv, resolveEnv } from './resolve.ts';
3
+ export { evaluateTemplate } from './templates.ts';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/env/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAE,KAAK,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1,4 @@
1
+ export { buildEnvMap } from "./build.js";
2
+ export { resolveEnv } from "./resolve.js";
3
+ export { evaluateTemplate } from "./templates.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/env/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAoB,UAAU,EAAE,MAAM,cAAc,CAAA;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1,11 @@
1
+ import type { Allocation } from '../allocator/allocate.ts';
2
+ import type { Config } from '../config/index.ts';
3
+ import { type PortweaveError as PortweaveErrorType } from '../errors.ts';
4
+ import { type Result } from '../result.ts';
5
+ export interface ResolvedEnv {
6
+ readonly createdPortweaveDir: boolean;
7
+ readonly currentEnvPath: string;
8
+ readonly env: Readonly<Record<string, string>>;
9
+ }
10
+ export declare function resolveEnv(allocation: Allocation, config: Config, projectRoot: string): Promise<Result<ResolvedEnv, PortweaveErrorType>>;
11
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/env/resolve.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAC1D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAEL,KAAK,cAAc,IAAI,kBAAkB,EAC1C,MAAM,cAAc,CAAA;AACrB,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,cAAc,CAAA;AAKnD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAA;IACrC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAC/C;AAED,wBAAsB,UAAU,CAC9B,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,kBAAkB,CAAC,CAAC,CAuBlD"}
@@ -0,0 +1,29 @@
1
+ import { resolve } from 'node:path';
2
+ import { PortweaveError, } from "../errors.js";
3
+ import { err, ok } from "../result.js";
4
+ import { buildEnvMap } from "./build.js";
5
+ import { applyDotenvOverrides, readDotenvFile } from "./dotenv-merge.js";
6
+ import { atomicWriteDotenv, ensurePortweaveDir } from "./writer.js";
7
+ export async function resolveEnv(allocation, config, projectRoot) {
8
+ let computed;
9
+ try {
10
+ computed = buildEnvMap(allocation, config);
11
+ }
12
+ catch (caught) {
13
+ if (caught instanceof PortweaveError) {
14
+ return err(caught);
15
+ }
16
+ throw caught;
17
+ }
18
+ const dotenvPath = resolve(projectRoot, '.env');
19
+ const dotenvResult = await readDotenvFile(dotenvPath);
20
+ if (!dotenvResult.ok) {
21
+ return dotenvResult;
22
+ }
23
+ const final = applyDotenvOverrides(computed, dotenvResult.value);
24
+ const { created } = await ensurePortweaveDir(projectRoot);
25
+ const currentEnvPath = resolve(projectRoot, '.portweave/current.env');
26
+ await atomicWriteDotenv(currentEnvPath, final);
27
+ return ok({ createdPortweaveDir: created, currentEnvPath, env: final });
28
+ }
29
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/env/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAGnC,OAAO,EACL,cAAc,GAEf,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,GAAG,EAAE,EAAE,EAAe,MAAM,cAAc,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACxE,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAQnE,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,UAAsB,EACtB,MAAc,EACd,WAAmB;IAEnB,IAAI,QAAgC,CAAA;IACpC,IAAI,CAAC;QACH,QAAQ,GAAG,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;IAC5C,CAAC;IAAC,OAAO,MAAe,EAAE,CAAC;QACzB,IAAI,MAAM,YAAY,cAAc,EAAE,CAAC;YACrC,OAAO,GAAG,CAAC,MAAM,CAAC,CAAA;QACpB,CAAC;QACD,MAAM,MAAM,CAAA;IACd,CAAC;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,CAAA;IAC/C,MAAM,YAAY,GAAG,MAAM,cAAc,CAAC,UAAU,CAAC,CAAA;IACrD,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC;QACrB,OAAO,YAAY,CAAA;IACrB,CAAC;IAED,MAAM,KAAK,GAAG,oBAAoB,CAAC,QAAQ,EAAE,YAAY,CAAC,KAAK,CAAC,CAAA;IAChE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,kBAAkB,CAAC,WAAW,CAAC,CAAA;IACzD,MAAM,cAAc,GAAG,OAAO,CAAC,WAAW,EAAE,wBAAwB,CAAC,CAAA;IACrE,MAAM,iBAAiB,CAAC,cAAc,EAAE,KAAK,CAAC,CAAA;IAE9C,OAAO,EAAE,CAAC,EAAE,mBAAmB,EAAE,OAAO,EAAE,cAAc,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAA;AACzE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function evaluateTemplate(template: string, ports: Readonly<Record<string, number>>): string;
2
+ //# sourceMappingURL=templates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/env/templates.ts"],"names":[],"mappings":"AAIA,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACtC,MAAM,CAUR"}
@@ -0,0 +1,11 @@
1
+ import { PortweaveError, PW_ERROR_CODES } from "../errors.js";
2
+ const PLACEHOLDER_PATTERN = /\$\{([^}]+)\}/g;
3
+ export function evaluateTemplate(template, ports) {
4
+ return template.replaceAll(PLACEHOLDER_PATTERN, (_, name) => {
5
+ if (!Object.hasOwn(ports, name)) {
6
+ throw new PortweaveError(PW_ERROR_CODES.ENV_BUILD_INVALID, `discoveryEnv template references unknown service "${name}"`);
7
+ }
8
+ return String(ports[name]);
9
+ });
10
+ }
11
+ //# sourceMappingURL=templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/env/templates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAE7D,MAAM,mBAAmB,GAAG,gBAAgB,CAAA;AAE5C,MAAM,UAAU,gBAAgB,CAC9B,QAAgB,EAChB,KAAuC;IAEvC,OAAO,QAAQ,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC,CAAC,EAAE,IAAY,EAAE,EAAE;QAClE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC;YAChC,MAAM,IAAI,cAAc,CACtB,cAAc,CAAC,iBAAiB,EAChC,qDAAqD,IAAI,GAAG,CAC7D,CAAA;QACH,CAAC;QACD,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare function serializeDotenv(env: Readonly<Record<string, string>>): string;
2
+ export declare function ensurePortweaveDir(projectRoot: string): Promise<{
3
+ created: boolean;
4
+ }>;
5
+ export declare function atomicWriteDotenv(path: string, env: Readonly<Record<string, string>>): Promise<void>;
6
+ //# sourceMappingURL=writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../src/env/writer.ts"],"names":[],"mappings":"AAcA,wBAAgB,eAAe,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,CAc7E;AAED,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAuC/B;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACpC,OAAO,CAAC,IAAI,CAAC,CAKf"}
@@ -0,0 +1,68 @@
1
+ import { access, mkdir, rename, writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ const NEEDS_QUOTING = /[\s#$"'\\]/;
4
+ function shouldQuote(value) {
5
+ return NEEDS_QUOTING.test(value);
6
+ }
7
+ function quoteValue(value) {
8
+ const escaped = value.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
9
+ return `"${escaped}"`;
10
+ }
11
+ export function serializeDotenv(env) {
12
+ const keys = Object.keys(env).sort();
13
+ if (keys.length === 0) {
14
+ return '';
15
+ }
16
+ return (keys
17
+ .map((key) => {
18
+ const value = env[key];
19
+ const serializedValue = shouldQuote(value) ? quoteValue(value) : value;
20
+ return `${key}=${serializedValue}`;
21
+ })
22
+ .join('\n') + '\n');
23
+ }
24
+ export async function ensurePortweaveDir(projectRoot) {
25
+ const portweaveDir = join(projectRoot, '.portweave');
26
+ const gitignorePath = join(portweaveDir, '.gitignore');
27
+ // Try to access the directory to check if it already exists
28
+ let dirAlreadyExisted = true;
29
+ try {
30
+ await access(portweaveDir);
31
+ }
32
+ catch {
33
+ // pw-allow-swallow: access throws ENOENT when dir is absent; we handle
34
+ // the "did not exist" case below by setting the flag.
35
+ dirAlreadyExisted = false;
36
+ }
37
+ await mkdir(portweaveDir, { mode: 0o700, recursive: true });
38
+ if (!dirAlreadyExisted) {
39
+ // First creation — write the .gitignore
40
+ await writeFile(gitignorePath, '*\n', { encoding: 'utf-8', flag: 'wx' });
41
+ return { created: true };
42
+ }
43
+ // Directory already existed — ensure .gitignore exists but don't overwrite
44
+ try {
45
+ await access(gitignorePath);
46
+ // .gitignore already present — nothing to do
47
+ }
48
+ catch {
49
+ // pw-allow-swallow: .gitignore absent even though dir existed (e.g. user
50
+ // deleted it manually). Write it now using 'wx' so a concurrent writer
51
+ // doesn't clobber an existing file.
52
+ try {
53
+ await writeFile(gitignorePath, '*\n', { encoding: 'utf-8', flag: 'wx' });
54
+ }
55
+ catch {
56
+ // pw-allow-swallow: concurrent writer may have created it between our
57
+ // access check and writeFile — that outcome is fine for our purposes.
58
+ }
59
+ }
60
+ return { created: false };
61
+ }
62
+ export async function atomicWriteDotenv(path, env) {
63
+ const tempPath = `${path}.tmp.${process.pid.toString()}.${Date.now().toString()}`;
64
+ const contents = serializeDotenv(env);
65
+ await writeFile(tempPath, contents, { encoding: 'utf-8', mode: 0o600 });
66
+ await rename(tempPath, path);
67
+ }
68
+ //# sourceMappingURL=writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writer.js","sourceRoot":"","sources":["../../src/env/writer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ export declare const PW_ERROR_CODES: {
2
+ readonly ALLOCATION_EXHAUSTED: "PW0401";
3
+ readonly CLI_CHILD_SPAWN_FAILED: "PW0602";
4
+ readonly CLI_INVALID_FLAGS: "PW0601";
5
+ readonly CLI_NO_ALLOCATION: "PW0603";
6
+ readonly CONFIG_INVALID: "PW0102";
7
+ readonly CONFIG_MISSING: "PW0101";
8
+ readonly ENV_BUILD_INVALID: "PW0501";
9
+ readonly ENV_DOTENV_PARSE_FAILED: "PW0502";
10
+ readonly NOT_A_GIT_REPO: "PW0201";
11
+ readonly REGISTRY_CORRUPT: "PW0302";
12
+ readonly REGISTRY_LOCKED: "PW0301";
13
+ readonly RUNTIME_CONFIG_NOT_FOUND: "PW0701";
14
+ readonly RUNTIME_NOT_INITIALIZED: "PW0702";
15
+ readonly WORKTREE_OFFSET_INVALID: "PW0202";
16
+ };
17
+ export type PortweaveErrorCode = (typeof PW_ERROR_CODES)[keyof typeof PW_ERROR_CODES];
18
+ export declare class PortweaveError extends Error {
19
+ readonly code: PortweaveErrorCode;
20
+ constructor(code: PortweaveErrorCode, message: string);
21
+ }
22
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;CAiBuB,CAAA;AAElD,MAAM,MAAM,kBAAkB,GAC5B,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,OAAO,cAAc,CAAC,CAAA;AAItD,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAA;gBAErB,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM;CAMtD"}
package/dist/errors.js ADDED
@@ -0,0 +1,32 @@
1
+ // Seed PW error codes. PW#### grouped by component in 100-blocks — see
2
+ // the table in .ai/specs/result-types/result-types.md.
3
+ export const PW_ERROR_CODES = {
4
+ ALLOCATION_EXHAUSTED: 'PW0401',
5
+ CLI_CHILD_SPAWN_FAILED: 'PW0602',
6
+ CLI_INVALID_FLAGS: 'PW0601',
7
+ CLI_NO_ALLOCATION: 'PW0603',
8
+ CONFIG_INVALID: 'PW0102',
9
+ CONFIG_MISSING: 'PW0101',
10
+ ENV_BUILD_INVALID: 'PW0501',
11
+ ENV_DOTENV_PARSE_FAILED: 'PW0502',
12
+ NOT_A_GIT_REPO: 'PW0201',
13
+ REGISTRY_CORRUPT: 'PW0302',
14
+ REGISTRY_LOCKED: 'PW0301',
15
+ // PW07xx — library-runtime block (see .ai/specs/library-runtime)
16
+ RUNTIME_CONFIG_NOT_FOUND: 'PW0701',
17
+ // PW0702 reserved for future cached-state failure; not emitted at v0
18
+ RUNTIME_NOT_INITIALIZED: 'PW0702',
19
+ WORKTREE_OFFSET_INVALID: 'PW0202',
20
+ };
21
+ // Carries a stable PW#### code for cross-module dispatch. The setPrototypeOf
22
+ // call is load-bearing for `instanceof` under transpilation — do not remove.
23
+ export class PortweaveError extends Error {
24
+ code;
25
+ constructor(code, message) {
26
+ super(message);
27
+ this.name = 'PortweaveError';
28
+ this.code = code;
29
+ Object.setPrototypeOf(this, PortweaveError.prototype);
30
+ }
31
+ }
32
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,uDAAuD;AACvD,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,oBAAoB,EAAE,QAAQ;IAC9B,sBAAsB,EAAE,QAAQ;IAChC,iBAAiB,EAAE,QAAQ;IAC3B,iBAAiB,EAAE,QAAQ;IAC3B,cAAc,EAAE,QAAQ;IACxB,cAAc,EAAE,QAAQ;IACxB,iBAAiB,EAAE,QAAQ;IAC3B,uBAAuB,EAAE,QAAQ;IACjC,cAAc,EAAE,QAAQ;IACxB,gBAAgB,EAAE,QAAQ;IAC1B,eAAe,EAAE,QAAQ;IACzB,iEAAiE;IACjE,wBAAwB,EAAE,QAAQ;IAClC,qEAAqE;IACrE,uBAAuB,EAAE,QAAQ;IACjC,uBAAuB,EAAE,QAAQ;CACe,CAAA;AAKlD,6EAA6E;AAC7E,6EAA6E;AAC7E,MAAM,OAAO,cAAe,SAAQ,KAAK;IAC9B,IAAI,CAAoB;IAEjC,YAAY,IAAwB,EAAE,OAAe;QACnD,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAA;QAC5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,cAAc,CAAC,SAAS,CAAC,CAAA;IACvD,CAAC;CACF"}
@@ -0,0 +1,11 @@
1
+ export { allocate, type Allocation, type AllocationResult, MAX_PROBE_RETRIES, orderServicesForAllocation, } from './allocator/allocate.ts';
2
+ export { findFreeBlock, resolvePoolRange } from './allocator/pool.ts';
3
+ export { probeBlock, probePort } from './allocator/probe.ts';
4
+ export { type Config, loadConfig, type LoadConfigOptions, type ServiceSpec, synthesizeAnonymousConfig, } from './config/index.ts';
5
+ export { buildEnvMap, evaluateTemplate, type ResolvedEnv, resolveEnv, } from './env/index.ts';
6
+ export { PortweaveError, type PortweaveErrorCode, PW_ERROR_CODES, } from './errors.ts';
7
+ export { andThen, err, ok, type Result } from './result.ts';
8
+ export { detectGitWorktreeContext, type GitWorktreeContext, } from './worktree/git.ts';
9
+ export { type AllocationKey, resolveAllocationKey } from './worktree/key.ts';
10
+ export { deriveNamespace, MAIN_NAMESPACE, namespaceOverride, parseExplicitOffset, sanitizeNamespace, } from './worktree/namespace.ts';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,iBAAiB,EACjB,0BAA0B,GAC3B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACrE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,EACL,KAAK,MAAM,EACX,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,yBAAyB,GAC1B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,KAAK,WAAW,EAChB,UAAU,GACX,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACL,cAAc,EACd,KAAK,kBAAkB,EACvB,cAAc,GACf,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAA;AAC3D,OAAO,EACL,wBAAwB,EACxB,KAAK,kBAAkB,GACxB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,KAAK,aAAa,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAC5E,OAAO,EACL,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,yBAAyB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ export { allocate, MAX_PROBE_RETRIES, orderServicesForAllocation, } from "./allocator/allocate.js";
2
+ export { findFreeBlock, resolvePoolRange } from "./allocator/pool.js";
3
+ export { probeBlock, probePort } from "./allocator/probe.js";
4
+ export { loadConfig, synthesizeAnonymousConfig, } from "./config/index.js";
5
+ export { buildEnvMap, evaluateTemplate, resolveEnv, } from "./env/index.js";
6
+ export { PortweaveError, PW_ERROR_CODES, } from "./errors.js";
7
+ export { andThen, err, ok } from "./result.js";
8
+ export { detectGitWorktreeContext, } from "./worktree/git.js";
9
+ export { resolveAllocationKey } from "./worktree/key.js";
10
+ export { deriveNamespace, MAIN_NAMESPACE, namespaceOverride, parseExplicitOffset, sanitizeNamespace, } from "./worktree/namespace.js";
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EAGR,iBAAiB,EACjB,0BAA0B,GAC3B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACrE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,EAEL,UAAU,EAGV,yBAAyB,GAC1B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,WAAW,EACX,gBAAgB,EAEhB,UAAU,GACX,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACL,cAAc,EAEd,cAAc,GACf,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAe,MAAM,aAAa,CAAA;AAC3D,OAAO,EACL,wBAAwB,GAEzB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAsB,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAC5E,OAAO,EACL,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,yBAAyB,CAAA"}
@@ -0,0 +1,3 @@
1
+ export declare function atomicWriteRegistry(path: string, contents: string): Promise<void>;
2
+ export declare function pruneStaleTempFiles(path: string): Promise<void>;
3
+ //# sourceMappingURL=atomic-write.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomic-write.d.ts","sourceRoot":"","sources":["../../src/registry/atomic-write.ts"],"names":[],"mappings":"AAKA,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiCrE"}
@@ -0,0 +1,43 @@
1
+ import { readdir, rename, rm, stat, writeFile } from 'node:fs/promises';
2
+ import { basename, dirname, join } from 'node:path';
3
+ const TMP_SIBLING_TTL_MS = 60_000;
4
+ export async function atomicWriteRegistry(path, contents) {
5
+ const tempPath = `${path}.tmp.${process.pid.toString()}.${Date.now().toString()}`;
6
+ await writeFile(tempPath, contents, { encoding: 'utf-8', mode: 0o600 });
7
+ await rename(tempPath, path);
8
+ }
9
+ export async function pruneStaleTempFiles(path) {
10
+ const dir = dirname(path);
11
+ const prefix = `${basename(path)}.tmp.`;
12
+ let entries;
13
+ try {
14
+ entries = await readdir(dir);
15
+ }
16
+ catch (caught) {
17
+ if (typeof caught === 'object' &&
18
+ caught !== null &&
19
+ 'code' in caught &&
20
+ caught.code === 'ENOENT') {
21
+ return;
22
+ }
23
+ throw caught;
24
+ }
25
+ const now = Date.now();
26
+ for (const name of entries) {
27
+ if (!name.startsWith(prefix)) {
28
+ continue;
29
+ }
30
+ const candidate = join(dir, name);
31
+ try {
32
+ const info = await stat(candidate);
33
+ if (now - info.mtimeMs > TMP_SIBLING_TTL_MS) {
34
+ await rm(candidate, { force: true });
35
+ }
36
+ }
37
+ catch {
38
+ // pw-allow-swallow: tempfile may vanish between readdir and stat;
39
+ // a concurrent writer's cleanup is not a failure for our caller.
40
+ }
41
+ }
42
+ }
43
+ //# sourceMappingURL=atomic-write.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomic-write.js","sourceRoot":"","sources":["../../src/registry/atomic-write.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACvE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEnD,MAAM,kBAAkB,GAAG,MAAM,CAAA;AAEjC,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,IAAY,EACZ,QAAgB;IAEhB,MAAM,QAAQ,GAAG,GAAG,IAAI,QAAQ,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAA;IACjF,MAAM,SAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACvE,MAAM,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;AAC9B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAAY;IACpD,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACzB,MAAM,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAA;IACvC,IAAI,OAAiB,CAAA;IACrB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAA;IAC9B,CAAC;IAAC,OAAO,MAAe,EAAE,CAAC;QACzB,IACE,OAAO,MAAM,KAAK,QAAQ;YAC1B,MAAM,KAAK,IAAI;YACf,MAAM,IAAI,MAAM;YAChB,MAAM,CAAC,IAAI,KAAK,QAAQ,EACxB,CAAC;YACD,OAAM;QACR,CAAC;QACD,MAAM,MAAM,CAAA;IACd,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IACtB,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7B,SAAQ;QACV,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QACjC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,CAAA;YAClC,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,GAAG,kBAAkB,EAAE,CAAC;gBAC5C,MAAM,EAAE,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;YACtC,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,kEAAkE;YAClE,iEAAiE;QACnE,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { PortweaveError } from '../errors.ts';
2
+ import { type Result } from '../result.ts';
3
+ export declare function withLock<T>(lockDir: string, fn: () => Promise<T>): Promise<Result<T, PortweaveError>>;
4
+ //# sourceMappingURL=lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../src/registry/lock.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAkB,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,cAAc,CAAA;AA+EnD,wBAAsB,QAAQ,CAAC,CAAC,EAC9B,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAqBpC"}