@basaltbytes/odoo-agentic-dev 0.1.0-beta.1

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 (134) hide show
  1. package/README.md +464 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +73 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/commands/compose.d.ts +19 -0
  6. package/dist/commands/compose.js +29 -0
  7. package/dist/commands/compose.js.map +1 -0
  8. package/dist/commands/doctor.d.ts +35 -0
  9. package/dist/commands/doctor.js +209 -0
  10. package/dist/commands/doctor.js.map +1 -0
  11. package/dist/commands/down.d.ts +23 -0
  12. package/dist/commands/down.js +46 -0
  13. package/dist/commands/down.js.map +1 -0
  14. package/dist/commands/info.d.ts +10 -0
  15. package/dist/commands/info.js +42 -0
  16. package/dist/commands/info.js.map +1 -0
  17. package/dist/commands/json-report.d.ts +45 -0
  18. package/dist/commands/json-report.js +55 -0
  19. package/dist/commands/json-report.js.map +1 -0
  20. package/dist/commands/link-source.d.ts +33 -0
  21. package/dist/commands/link-source.js +116 -0
  22. package/dist/commands/link-source.js.map +1 -0
  23. package/dist/commands/list.d.ts +29 -0
  24. package/dist/commands/list.js +117 -0
  25. package/dist/commands/list.js.map +1 -0
  26. package/dist/commands/logs.d.ts +17 -0
  27. package/dist/commands/logs.js +28 -0
  28. package/dist/commands/logs.js.map +1 -0
  29. package/dist/commands/prune.d.ts +54 -0
  30. package/dist/commands/prune.js +118 -0
  31. package/dist/commands/prune.js.map +1 -0
  32. package/dist/commands/psql.d.ts +8 -0
  33. package/dist/commands/psql.js +24 -0
  34. package/dist/commands/psql.js.map +1 -0
  35. package/dist/commands/reset-db.d.ts +34 -0
  36. package/dist/commands/reset-db.js +86 -0
  37. package/dist/commands/reset-db.js.map +1 -0
  38. package/dist/commands/resolve-context.d.ts +17 -0
  39. package/dist/commands/resolve-context.js +30 -0
  40. package/dist/commands/resolve-context.js.map +1 -0
  41. package/dist/commands/run.d.ts +31 -0
  42. package/dist/commands/run.js +83 -0
  43. package/dist/commands/run.js.map +1 -0
  44. package/dist/commands/setup.d.ts +46 -0
  45. package/dist/commands/setup.js +93 -0
  46. package/dist/commands/setup.js.map +1 -0
  47. package/dist/commands/shell.d.ts +20 -0
  48. package/dist/commands/shell.js +46 -0
  49. package/dist/commands/shell.js.map +1 -0
  50. package/dist/commands/state-hooks.d.ts +28 -0
  51. package/dist/commands/state-hooks.js +86 -0
  52. package/dist/commands/state-hooks.js.map +1 -0
  53. package/dist/commands/test.d.ts +24 -0
  54. package/dist/commands/test.js +70 -0
  55. package/dist/commands/test.js.map +1 -0
  56. package/dist/commands/trailing-args.d.ts +10 -0
  57. package/dist/commands/trailing-args.js +14 -0
  58. package/dist/commands/trailing-args.js.map +1 -0
  59. package/dist/commands/up.d.ts +23 -0
  60. package/dist/commands/up.js +62 -0
  61. package/dist/commands/up.js.map +1 -0
  62. package/dist/commands/update.d.ts +7 -0
  63. package/dist/commands/update.js +31 -0
  64. package/dist/commands/update.js.map +1 -0
  65. package/dist/commands/worktree.d.ts +97 -0
  66. package/dist/commands/worktree.js +355 -0
  67. package/dist/commands/worktree.js.map +1 -0
  68. package/dist/config/load-recipe.d.ts +14 -0
  69. package/dist/config/load-recipe.js +75 -0
  70. package/dist/config/load-recipe.js.map +1 -0
  71. package/dist/config/schema.d.ts +9 -0
  72. package/dist/config/schema.js +190 -0
  73. package/dist/config/schema.js.map +1 -0
  74. package/dist/core/command-plan.d.ts +34 -0
  75. package/dist/core/command-plan.js +112 -0
  76. package/dist/core/command-plan.js.map +1 -0
  77. package/dist/core/compose-model.d.ts +18 -0
  78. package/dist/core/compose-model.js +80 -0
  79. package/dist/core/compose-model.js.map +1 -0
  80. package/dist/core/compose-project.d.ts +3 -0
  81. package/dist/core/compose-project.js +15 -0
  82. package/dist/core/compose-project.js.map +1 -0
  83. package/dist/core/database-name.d.ts +15 -0
  84. package/dist/core/database-name.js +59 -0
  85. package/dist/core/database-name.js.map +1 -0
  86. package/dist/core/environment.d.ts +84 -0
  87. package/dist/core/environment.js +78 -0
  88. package/dist/core/environment.js.map +1 -0
  89. package/dist/core/port-allocator.d.ts +30 -0
  90. package/dist/core/port-allocator.js +58 -0
  91. package/dist/core/port-allocator.js.map +1 -0
  92. package/dist/core/project-recipe.d.ts +163 -0
  93. package/dist/core/project-recipe.js +13 -0
  94. package/dist/core/project-recipe.js.map +1 -0
  95. package/dist/core/safety.d.ts +11 -0
  96. package/dist/core/safety.js +16 -0
  97. package/dist/core/safety.js.map +1 -0
  98. package/dist/core/worktree-context.d.ts +32 -0
  99. package/dist/core/worktree-context.js +94 -0
  100. package/dist/core/worktree-context.js.map +1 -0
  101. package/dist/errors/errors.d.ts +124 -0
  102. package/dist/errors/errors.js +129 -0
  103. package/dist/errors/errors.js.map +1 -0
  104. package/dist/index.d.ts +3 -0
  105. package/dist/index.js +2 -0
  106. package/dist/index.js.map +1 -0
  107. package/dist/platform/command-runner.d.ts +30 -0
  108. package/dist/platform/command-runner.js +65 -0
  109. package/dist/platform/command-runner.js.map +1 -0
  110. package/dist/platform/docker-compose.d.ts +69 -0
  111. package/dist/platform/docker-compose.js +239 -0
  112. package/dist/platform/docker-compose.js.map +1 -0
  113. package/dist/platform/git.d.ts +10 -0
  114. package/dist/platform/git.js +35 -0
  115. package/dist/platform/git.js.map +1 -0
  116. package/dist/platform/odoo-lifecycle.d.ts +30 -0
  117. package/dist/platform/odoo-lifecycle.js +109 -0
  118. package/dist/platform/odoo-lifecycle.js.map +1 -0
  119. package/dist/platform/port-probe.d.ts +7 -0
  120. package/dist/platform/port-probe.js +13 -0
  121. package/dist/platform/port-probe.js.map +1 -0
  122. package/dist/platform/process-supervisor.d.ts +16 -0
  123. package/dist/platform/process-supervisor.js +23 -0
  124. package/dist/platform/process-supervisor.js.map +1 -0
  125. package/dist/platform/state-store.d.ts +37 -0
  126. package/dist/platform/state-store.js +122 -0
  127. package/dist/platform/state-store.js.map +1 -0
  128. package/dist/suppress-sqlite-warning.d.ts +1 -0
  129. package/dist/suppress-sqlite-warning.js +18 -0
  130. package/dist/suppress-sqlite-warning.js.map +1 -0
  131. package/dist/testing/fake-adapters.d.ts +34 -0
  132. package/dist/testing/fake-adapters.js +90 -0
  133. package/dist/testing/fake-adapters.js.map +1 -0
  134. package/package.json +78 -0
@@ -0,0 +1,239 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { dirname, isAbsolute, join, resolve } from "node:path";
3
+ import { Context, Duration, Effect, Layer } from "effect";
4
+ import { ComposeCommandError, DockerUnavailableError, tail } from "../errors/errors.js";
5
+ import { buildComposeModel, GENERATED_COMPOSE_RELATIVE_PATH, renderComposeYaml, } from "../core/compose-model.js";
6
+ import { CommandRunner } from "./command-runner.js";
7
+ export const composeArgs = (ref, rest) => [
8
+ "compose",
9
+ "-p",
10
+ ref.projectName,
11
+ "-f",
12
+ ref.composeFile,
13
+ "--project-directory",
14
+ ref.projectDir,
15
+ ...rest,
16
+ ];
17
+ export const DockerCompose = Context.Service("odoo-agentic-dev/DockerCompose");
18
+ /**
19
+ * Lenient parse of `docker compose ls -a --format json`: an array of
20
+ * `{ Name, Status, ConfigFiles }` where Status reads like "running(2)" or
21
+ * "exited(2)". Anything unrecognized is skipped rather than fatal.
22
+ */
23
+ export const parseComposeLs = (stdout) => {
24
+ const text = stdout.trim();
25
+ if (text.length === 0)
26
+ return [];
27
+ const parsed = JSON.parse(text);
28
+ if (!Array.isArray(parsed))
29
+ return [];
30
+ const projects = [];
31
+ for (const item of parsed) {
32
+ if (typeof item !== "object" || item === null)
33
+ continue;
34
+ const record = item;
35
+ if (typeof record["Name"] !== "string" || record["Name"].length === 0)
36
+ continue;
37
+ projects.push({
38
+ name: record["Name"],
39
+ running: String(record["Status"] ?? "").startsWith("running"),
40
+ });
41
+ }
42
+ return projects;
43
+ };
44
+ /**
45
+ * Lenient parse of `docker ps --format json` output (NDJSON, one container per
46
+ * line; a top-level array is tolerated). `Labels` is a comma-joined `k=v`
47
+ * string, so label values containing commas are truncated — acceptable for the
48
+ * best-effort adoption this feeds. Unparseable lines are skipped.
49
+ */
50
+ export const parseLabeledPs = (stdout) => {
51
+ const containers = new Map();
52
+ for (const line of stdout.split(/\r?\n/)) {
53
+ const text = line.trim();
54
+ if (text.length === 0)
55
+ continue;
56
+ let parsed;
57
+ try {
58
+ parsed = JSON.parse(text);
59
+ }
60
+ catch {
61
+ continue;
62
+ }
63
+ for (const item of Array.isArray(parsed) ? parsed : [parsed]) {
64
+ if (typeof item !== "object" || item === null)
65
+ continue;
66
+ const labelsRaw = item["Labels"];
67
+ if (typeof labelsRaw !== "string")
68
+ continue;
69
+ const labels = new Map();
70
+ for (const part of labelsRaw.split(",")) {
71
+ const eq = part.indexOf("=");
72
+ if (eq > 0)
73
+ labels.set(part.slice(0, eq), part.slice(eq + 1));
74
+ }
75
+ const composeProject = labels.get("com.docker.compose.project");
76
+ if (composeProject === undefined || containers.has(composeProject))
77
+ continue;
78
+ containers.set(composeProject, {
79
+ composeProject,
80
+ projectId: labels.get("dev.basaltbytes.oad.project-id") ?? null,
81
+ database: labels.get("dev.basaltbytes.oad.database") ?? null,
82
+ rootDir: labels.get("dev.basaltbytes.oad.root-dir") ?? null,
83
+ branch: labels.get("dev.basaltbytes.oad.branch") ?? null,
84
+ });
85
+ }
86
+ }
87
+ return [...containers.values()];
88
+ };
89
+ const splitLines = (stdout) => stdout
90
+ .split(/\r?\n/)
91
+ .map((line) => line.trim())
92
+ .filter((line) => line.length > 0);
93
+ export const DockerComposeLive = Layer.effect(DockerCompose, Effect.gen(function* () {
94
+ const runner = yield* CommandRunner;
95
+ const toComposeError = (args) => (cause) => new ComposeCommandError({
96
+ args,
97
+ exitCode: -1,
98
+ stderrTail: cause.stderrTail ?? String(cause),
99
+ });
100
+ /**
101
+ * Single captured execution path: the caller hands over the fully expanded
102
+ * argv exactly once, and that same argv is shared by the runner spec and
103
+ * the error mapping (and, in `run`, by `failOnNonZero`). Never re-expand
104
+ * `composeArgs` for the same invocation.
105
+ */
106
+ const exec = (ref, argv, stdin) => runner
107
+ .run({ command: "docker", args: argv, cwd: ref.projectDir, env: ref.env, stdin })
108
+ .pipe(Effect.mapError(toComposeError(argv)));
109
+ const tryRun = (ref, args) => exec(ref, composeArgs(ref, args));
110
+ /** Spec rule: never dump huge logs inline — full output goes to .odoo-agentic-dev/logs/, the error keeps a tail + the path. */
111
+ const writeFailureLog = (ref, argv, result) => {
112
+ try {
113
+ const dir = join(ref.projectDir, ".odoo-agentic-dev", "logs");
114
+ mkdirSync(dir, { recursive: true });
115
+ const file = join(dir, `compose-${Date.now()}.log`);
116
+ writeFileSync(file, `$ docker ${argv.join(" ")}\nexit ${result.exitCode}\n\n--- stdout ---\n${result.stdout}\n--- stderr ---\n${result.stderr}\n`);
117
+ return file;
118
+ }
119
+ catch {
120
+ return undefined;
121
+ }
122
+ };
123
+ const failOnNonZero = (ref, argv) => (result) => {
124
+ if (result.exitCode === 0)
125
+ return Effect.succeed(result);
126
+ const logFile = writeFailureLog(ref, argv, result);
127
+ return Effect.fail(new ComposeCommandError({
128
+ args: argv,
129
+ exitCode: result.exitCode,
130
+ stderrTail: tail(result.stderr || result.stdout) +
131
+ (logFile !== undefined ? `\nfull log: ${logFile}` : ""),
132
+ }));
133
+ };
134
+ const run = (ref, args, stdin) => {
135
+ const argv = composeArgs(ref, args);
136
+ return exec(ref, argv, stdin).pipe(Effect.flatMap(failOnNonZero(ref, argv)));
137
+ };
138
+ /** Top-level `docker <argv>` (no compose ref): captured, fails typed on non-zero. */
139
+ const dockerRun = (argv) => runner.run({ command: "docker", args: argv }).pipe(Effect.mapError(toComposeError(argv)), Effect.flatMap((result) => result.exitCode === 0
140
+ ? Effect.succeed(result)
141
+ : Effect.fail(new ComposeCommandError({
142
+ args: argv,
143
+ exitCode: result.exitCode,
144
+ stderrTail: tail(result.stderr || result.stdout),
145
+ }))));
146
+ const parseWith = (argv, parse) => (result) => Effect.try({
147
+ try: () => parse(result.stdout),
148
+ catch: (cause) => new ComposeCommandError({
149
+ args: argv,
150
+ exitCode: 0,
151
+ stderrTail: `unparseable docker output: ${String(cause)}`,
152
+ }),
153
+ });
154
+ const listProjects = () => {
155
+ const argv = ["compose", "ls", "-a", "--format", "json"];
156
+ return dockerRun(argv).pipe(Effect.flatMap(parseWith(argv, parseComposeLs)));
157
+ };
158
+ const listLabeledContainers = () => {
159
+ const argv = ["ps", "-a", "--filter", "label=dev.basaltbytes.oad=1", "--format", "json"];
160
+ return dockerRun(argv).pipe(Effect.flatMap(parseWith(argv, parseLabeledPs)));
161
+ };
162
+ const removeByLabel = (composeProject) => Effect.gen(function* () {
163
+ const filter = `label=com.docker.compose.project=${composeProject}`;
164
+ const ids = splitLines((yield* dockerRun(["ps", "-aq", "--filter", filter])).stdout);
165
+ if (ids.length > 0)
166
+ yield* dockerRun(["rm", "-f", ...ids]);
167
+ const volumes = splitLines((yield* dockerRun(["volume", "ls", "-q", "--filter", filter])).stdout);
168
+ if (volumes.length > 0)
169
+ yield* dockerRun(["volume", "rm", ...volumes]);
170
+ });
171
+ return {
172
+ ensureAvailable: () => runner.run({ command: "docker", args: ["version", "--format", "json"] }).pipe(Effect.mapError((e) => new DockerUnavailableError({ reason: e.stderrTail || "docker CLI not found" })), Effect.flatMap((result) => result.exitCode === 0
173
+ ? Effect.void
174
+ : Effect.fail(new DockerUnavailableError({
175
+ reason: tail(result.stderr) || `docker exited ${result.exitCode}`,
176
+ })))),
177
+ prepareComposeFile: (recipe, ctx) => Effect.gen(function* () {
178
+ if (recipe.compose.file !== null) {
179
+ const file = isAbsolute(recipe.compose.file)
180
+ ? recipe.compose.file
181
+ : resolve(ctx.rootDir, recipe.compose.file);
182
+ return {
183
+ projectName: ctx.composeProjectName,
184
+ composeFile: file,
185
+ projectDir: ctx.rootDir,
186
+ env: ctx.env,
187
+ };
188
+ }
189
+ const file = join(ctx.rootDir, GENERATED_COMPOSE_RELATIVE_PATH);
190
+ yield* Effect.try({
191
+ try: () => {
192
+ mkdirSync(dirname(file), { recursive: true });
193
+ writeFileSync(file, renderComposeYaml(buildComposeModel(recipe, ctx)));
194
+ },
195
+ catch: (cause) => new ComposeCommandError({
196
+ args: ["<prepare>"],
197
+ exitCode: -1,
198
+ stderrTail: String(cause),
199
+ }),
200
+ });
201
+ return {
202
+ projectName: ctx.composeProjectName,
203
+ composeFile: file,
204
+ projectDir: ctx.rootDir,
205
+ env: ctx.env,
206
+ };
207
+ }),
208
+ listProjects,
209
+ listLabeledContainers,
210
+ removeByLabel,
211
+ run,
212
+ tryRun,
213
+ stream: (ref, args) => {
214
+ const argv = composeArgs(ref, args);
215
+ return runner
216
+ .runInherited({ command: "docker", args: argv, cwd: ref.projectDir, env: ref.env })
217
+ .pipe(Effect.mapError(toComposeError(argv)), Effect.flatMap((code) => code === 0
218
+ ? Effect.void
219
+ : Effect.fail(new ComposeCommandError({ args: argv, exitCode: code, stderrTail: "" }))));
220
+ },
221
+ waitForDb: (ref, dbService, options) => {
222
+ const interval = options?.intervalMillis ?? 1000;
223
+ const maxAttempts = options?.maxAttempts ?? 60;
224
+ const args = ["exec", "-T", dbService, "pg_isready", "-U", "odoo", "-d", "postgres"];
225
+ const argv = composeArgs(ref, args);
226
+ const attempt = (n) => exec(ref, argv).pipe(Effect.flatMap((result) => result.exitCode === 0
227
+ ? Effect.void
228
+ : n >= maxAttempts
229
+ ? Effect.fail(new ComposeCommandError({
230
+ args: argv,
231
+ exitCode: result.exitCode,
232
+ stderrTail: `database not ready after ${maxAttempts} attempts`,
233
+ }))
234
+ : Effect.sleep(Duration.millis(interval)).pipe(Effect.flatMap(() => attempt(n + 1)))));
235
+ return attempt(1);
236
+ },
237
+ };
238
+ }));
239
+ //# sourceMappingURL=docker-compose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docker-compose.js","sourceRoot":"","sources":["../../src/platform/docker-compose.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ import { GitError } from "../errors/errors.js";
3
+ import type { GitState } from "../core/worktree-context.js";
4
+ export interface GitApi {
5
+ readonly state: (rootDir: string) => Effect.Effect<GitState, GitError>;
6
+ /** Does `refs/heads/<branch>` exist in the repo at rootDir? exit 0 → true, 1 → false. */
7
+ readonly branchExists: (rootDir: string, branch: string) => Effect.Effect<boolean, GitError>;
8
+ }
9
+ export declare const Git: Context.Service<GitApi, GitApi>;
10
+ export declare const GitLive: Layer.Layer<GitApi, never, import("./command-runner.js").CommandRunnerApi>;
@@ -0,0 +1,35 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ import { GitError } from "../errors/errors.js";
3
+ import { CommandRunner } from "./command-runner.js";
4
+ export const Git = Context.Service("odoo-agentic-dev/Git");
5
+ export const GitLive = Layer.effect(Git, Effect.gen(function* () {
6
+ const runner = yield* CommandRunner;
7
+ return {
8
+ state: (rootDir) => runner
9
+ .run({ command: "git", args: ["rev-parse", "--abbrev-ref", "HEAD"], cwd: rootDir })
10
+ .pipe(Effect.mapError((e) => new GitError({ reason: e.stderrTail || String(e) })), Effect.flatMap((result) => {
11
+ if (result.exitCode === 0) {
12
+ const branch = result.stdout.trim();
13
+ return Effect.succeed(branch === "HEAD" ? { _tag: "Detached" } : { _tag: "Branch", branch });
14
+ }
15
+ if (result.stderr.includes("not a git repository")) {
16
+ return Effect.succeed({ _tag: "NotARepo" });
17
+ }
18
+ return Effect.fail(new GitError({ reason: result.stderr.trim() || `git exited ${result.exitCode}` }));
19
+ })),
20
+ branchExists: (rootDir, branch) => runner
21
+ .run({
22
+ command: "git",
23
+ args: ["-C", rootDir, "rev-parse", "--verify", "--quiet", `refs/heads/${branch}`],
24
+ })
25
+ .pipe(Effect.mapError((e) => new GitError({ reason: e.stderrTail || String(e) })), Effect.flatMap((result) => {
26
+ if (result.exitCode === 0)
27
+ return Effect.succeed(true);
28
+ // --quiet: a missing ref exits 1 with no output — that is the "false" case
29
+ if (result.exitCode === 1)
30
+ return Effect.succeed(false);
31
+ return Effect.fail(new GitError({ reason: result.stderr.trim() || `git exited ${result.exitCode}` }));
32
+ })),
33
+ };
34
+ }));
35
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/platform/git.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ import type { RuntimeError } from "../errors/errors.js";
3
+ import type { OdooAgenticDevConfig } from "../core/project-recipe.js";
4
+ import type { WorktreeContext } from "../core/worktree-context.js";
5
+ import type { OdooTestOptions } from "../core/command-plan.js";
6
+ export interface OdooLifecycleApi {
7
+ readonly resetDatabase: (recipe: OdooAgenticDevConfig, ctx: WorktreeContext, options: {
8
+ readonly modules?: ReadonlyArray<string> | undefined;
9
+ readonly withoutDemo?: string | false | undefined;
10
+ }) => Effect.Effect<void, RuntimeError>;
11
+ readonly runPostInitHooks: (recipe: OdooAgenticDevConfig, ctx: WorktreeContext) => Effect.Effect<void, RuntimeError>;
12
+ readonly updateModules: (recipe: OdooAgenticDevConfig, ctx: WorktreeContext, modules: ReadonlyArray<string>, options: {
13
+ readonly restart: boolean;
14
+ }) => Effect.Effect<void, RuntimeError>;
15
+ /**
16
+ * Snapshot `<db>` into `<db>__tpl` (database + filestore). Docker-only:
17
+ * recording the template in the state registry is the caller's concern.
18
+ */
19
+ readonly snapshotTemplate: (recipe: OdooAgenticDevConfig, ctx: WorktreeContext) => Effect.Effect<void, RuntimeError>;
20
+ /** Recreate `<db>` from `<db>__tpl` (database + filestore). Hooks are baked in. */
21
+ readonly restoreFromTemplate: (recipe: OdooAgenticDevConfig, ctx: WorktreeContext) => Effect.Effect<void, RuntimeError>;
22
+ /** Runs the test suite; reporting the tails is the caller's concern. */
23
+ readonly runTests: (recipe: OdooAgenticDevConfig, ctx: WorktreeContext, options: OdooTestOptions) => Effect.Effect<{
24
+ readonly exitCode: number;
25
+ readonly stdoutTail: string;
26
+ readonly stderrTail: string;
27
+ }, RuntimeError>;
28
+ }
29
+ export declare const OdooLifecycle: Context.Service<OdooLifecycleApi, OdooLifecycleApi>;
30
+ export declare const OdooLifecycleLive: Layer.Layer<OdooLifecycleApi, never, import("./command-runner.js").CommandRunnerApi | import("./docker-compose.js").DockerComposeApi>;
@@ -0,0 +1,109 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { isAbsolute, resolve } from "node:path";
3
+ import { Context, Effect, Layer } from "effect";
4
+ import { ConfigLoadError, OdooCommandError, tail } from "../errors/errors.js";
5
+ import { copyFilestoreArgs, createDatabaseSql, createFromTemplateSql, dropDatabaseSql, expandHook, odooInitArgs, odooShellArgs, odooTestArgs, odooUpdateArgs, psqlArgs, removeFilestoreArgs, terminateSessionsSql, } from "../core/command-plan.js";
6
+ import { templateDbName } from "../core/environment.js";
7
+ import { DockerCompose } from "./docker-compose.js";
8
+ import { CommandRunner, runInheritedOrFail } from "./command-runner.js";
9
+ const toOdooError = (e) => new OdooCommandError({ args: e.args, exitCode: e.exitCode, stderrTail: e.stderrTail });
10
+ export const OdooLifecycle = Context.Service("odoo-agentic-dev/OdooLifecycle");
11
+ export const OdooLifecycleLive = Layer.effect(OdooLifecycle, Effect.gen(function* () {
12
+ const compose = yield* DockerCompose;
13
+ const runner = yield* CommandRunner;
14
+ const ensureDbReady = (recipe, ref) => compose
15
+ .stream(ref, ["up", "-d", recipe.odoo.databaseServiceName])
16
+ .pipe(Effect.andThen(compose.waitForDb(ref, recipe.odoo.databaseServiceName)));
17
+ const runShellCode = (recipe, ctx, ref, code) => compose.run(ref, odooShellArgs(recipe.odoo.serviceName, ctx.databaseName), code);
18
+ return {
19
+ resetDatabase: (recipe, ctx, options) => Effect.gen(function* () {
20
+ const ref = yield* compose.prepareComposeFile(recipe, ctx);
21
+ yield* ensureDbReady(recipe, ref);
22
+ const db = recipe.odoo.databaseServiceName;
23
+ yield* compose.run(ref, psqlArgs(db, terminateSessionsSql(ctx.databaseName)));
24
+ yield* compose.run(ref, psqlArgs(db, dropDatabaseSql(ctx.databaseName)));
25
+ yield* compose.run(ref, psqlArgs(db, createDatabaseSql(ctx.databaseName)));
26
+ yield* compose.run(ref, removeFilestoreArgs(recipe.odoo.serviceName, ctx.databaseName));
27
+ const initArgs = odooInitArgs(recipe.odoo.serviceName, ctx.databaseName, options.modules ?? recipe.database.initialModules, options.withoutDemo ?? recipe.database.withoutDemo);
28
+ yield* compose.stream(ref, initArgs).pipe(Effect.mapError(toOdooError));
29
+ }),
30
+ runPostInitHooks: (recipe, ctx) => Effect.gen(function* () {
31
+ if (recipe.database.postInit.length === 0)
32
+ return;
33
+ const ref = yield* compose.prepareComposeFile(recipe, ctx);
34
+ for (const hook of recipe.database.postInit) {
35
+ const expanded = expandHook(hook, ctx.env);
36
+ switch (expanded.kind) {
37
+ case "odoo-shell": {
38
+ yield* runShellCode(recipe, ctx, ref, expanded.code);
39
+ break;
40
+ }
41
+ case "odoo-shell-file": {
42
+ const path = isAbsolute(expanded.file)
43
+ ? expanded.file
44
+ : resolve(ctx.rootDir, expanded.file);
45
+ const code = yield* Effect.try({
46
+ try: () => readFileSync(path, "utf8"),
47
+ catch: (cause) => new ConfigLoadError({ path, reason: String(cause) }),
48
+ });
49
+ yield* runShellCode(recipe, ctx, ref, code);
50
+ break;
51
+ }
52
+ case "host-command": {
53
+ const cwd = expanded.cwd === undefined ? ctx.rootDir : resolve(ctx.rootDir, expanded.cwd);
54
+ yield* runInheritedOrFail(runner, {
55
+ command: expanded.command,
56
+ args: expanded.args,
57
+ cwd,
58
+ env: ctx.env,
59
+ prefix: `[hook:${expanded.command}] `,
60
+ });
61
+ break;
62
+ }
63
+ }
64
+ }
65
+ }),
66
+ snapshotTemplate: (recipe, ctx) => Effect.gen(function* () {
67
+ const ref = yield* compose.prepareComposeFile(recipe, ctx);
68
+ yield* ensureDbReady(recipe, ref);
69
+ const db = recipe.odoo.databaseServiceName;
70
+ const tpl = templateDbName(ctx.databaseName);
71
+ yield* compose.run(ref, psqlArgs(db, terminateSessionsSql(ctx.databaseName)));
72
+ yield* compose.run(ref, psqlArgs(db, dropDatabaseSql(tpl)));
73
+ yield* compose.run(ref, psqlArgs(db, createFromTemplateSql(tpl, ctx.databaseName)));
74
+ yield* compose.run(ref, copyFilestoreArgs(recipe.odoo.serviceName, ctx.databaseName, tpl));
75
+ }),
76
+ restoreFromTemplate: (recipe, ctx) => Effect.gen(function* () {
77
+ const ref = yield* compose.prepareComposeFile(recipe, ctx);
78
+ yield* ensureDbReady(recipe, ref);
79
+ const db = recipe.odoo.databaseServiceName;
80
+ const tpl = templateDbName(ctx.databaseName);
81
+ yield* compose.run(ref, psqlArgs(db, terminateSessionsSql(ctx.databaseName)));
82
+ yield* compose.run(ref, psqlArgs(db, dropDatabaseSql(ctx.databaseName)));
83
+ yield* compose.run(ref, psqlArgs(db, createFromTemplateSql(ctx.databaseName, tpl)));
84
+ yield* compose.run(ref, copyFilestoreArgs(recipe.odoo.serviceName, tpl, ctx.databaseName));
85
+ }),
86
+ updateModules: (recipe, ctx, modules, options) => Effect.gen(function* () {
87
+ const ref = yield* compose.prepareComposeFile(recipe, ctx);
88
+ yield* ensureDbReady(recipe, ref);
89
+ yield* compose.stream(ref, ["stop", recipe.odoo.serviceName]);
90
+ yield* compose
91
+ .stream(ref, odooUpdateArgs(recipe.odoo.serviceName, ctx.databaseName, modules))
92
+ .pipe(Effect.mapError(toOdooError));
93
+ if (options.restart) {
94
+ yield* compose.stream(ref, ["up", "-d", recipe.odoo.serviceName]);
95
+ }
96
+ }),
97
+ runTests: (recipe, ctx, options) => Effect.gen(function* () {
98
+ const ref = yield* compose.prepareComposeFile(recipe, ctx);
99
+ yield* ensureDbReady(recipe, ref);
100
+ const result = yield* compose.tryRun(ref, odooTestArgs(recipe.odoo.serviceName, ctx.databaseName, options));
101
+ return {
102
+ exitCode: result.exitCode,
103
+ stdoutTail: tail(result.stdout, 200),
104
+ stderrTail: tail(result.stderr, 200),
105
+ };
106
+ }),
107
+ };
108
+ }));
109
+ //# sourceMappingURL=odoo-lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"odoo-lifecycle.js","sourceRoot":"","sources":["../../src/platform/odoo-lifecycle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ export interface PortProbeApi {
3
+ /** Bind test on 127.0.0.1. Never fails: any bind error means "not free". */
4
+ readonly isFree: (port: number) => Effect.Effect<boolean>;
5
+ }
6
+ export declare const PortProbe: Context.Service<PortProbeApi, PortProbeApi>;
7
+ export declare const PortProbeLive: Layer.Layer<PortProbeApi, never, never>;
@@ -0,0 +1,13 @@
1
+ import { createServer } from "node:net";
2
+ import { Context, Effect, Layer } from "effect";
3
+ export const PortProbe = Context.Service("odoo-agentic-dev/PortProbe");
4
+ export const PortProbeLive = Layer.succeed(PortProbe, {
5
+ isFree: (port) => Effect.callback((resume) => {
6
+ const server = createServer();
7
+ server.once("error", () => resume(Effect.succeed(false)));
8
+ server.listen({ port, host: "127.0.0.1", exclusive: true }, () => {
9
+ server.close(() => resume(Effect.succeed(true)));
10
+ });
11
+ }),
12
+ });
13
+ //# sourceMappingURL=port-probe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port-probe.js","sourceRoot":"","sources":["../../src/platform/port-probe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAOhD,MAAM,CAAC,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAe,4BAA4B,CAAC,CAAC;AAErF,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE;IACpD,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CACf,MAAM,CAAC,QAAQ,CAAU,CAAC,MAAM,EAAE,EAAE;QAClC,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC1D,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE;YAC/D,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;CACL,CAAC,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ import { CompanionProcessError } from "../errors/errors.js";
3
+ import type { CommandFailedError } from "../errors/errors.js";
4
+ export type CompanionSpec = {
5
+ readonly name: string;
6
+ readonly cwd: string;
7
+ readonly command: string;
8
+ readonly args: ReadonlyArray<string>;
9
+ readonly env: Record<string, string>;
10
+ };
11
+ export interface ProcessSupervisorApi {
12
+ /** Run all companions concurrently; first failure interrupts the rest. */
13
+ readonly runAll: (specs: ReadonlyArray<CompanionSpec>) => Effect.Effect<void, CompanionProcessError | CommandFailedError>;
14
+ }
15
+ export declare const ProcessSupervisor: Context.Service<ProcessSupervisorApi, ProcessSupervisorApi>;
16
+ export declare const ProcessSupervisorLive: Layer.Layer<ProcessSupervisorApi, never, import("./command-runner.js").CommandRunnerApi>;
@@ -0,0 +1,23 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ import { CompanionProcessError } from "../errors/errors.js";
3
+ import { CommandRunner } from "./command-runner.js";
4
+ export const ProcessSupervisor = Context.Service("odoo-agentic-dev/ProcessSupervisor");
5
+ export const ProcessSupervisorLive = Layer.effect(ProcessSupervisor, Effect.gen(function* () {
6
+ const runner = yield* CommandRunner;
7
+ return {
8
+ runAll: (specs) => specs.length === 0
9
+ ? Effect.void
10
+ : Effect.all(specs.map((spec) => runner
11
+ .runInherited({
12
+ command: spec.command,
13
+ args: spec.args,
14
+ cwd: spec.cwd,
15
+ env: spec.env,
16
+ prefix: `[${spec.name}] `,
17
+ })
18
+ .pipe(Effect.flatMap((code) => code === 0
19
+ ? Effect.void
20
+ : Effect.fail(new CompanionProcessError({ name: spec.name, exitCode: code }))))), { concurrency: "unbounded" }).pipe(Effect.asVoid),
21
+ };
22
+ }));
23
+ //# sourceMappingURL=process-supervisor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-supervisor.js","sourceRoot":"","sources":["../../src/platform/process-supervisor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAiBpD,MAAM,CAAC,MAAM,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAC9C,oCAAoC,CACrC,CAAC;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,CAAC,MAAM,CAC/C,iBAAiB,EACjB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,aAAa,CAAC;IACpC,OAAO;QACL,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAChB,KAAK,CAAC,MAAM,KAAK,CAAC;YAChB,CAAC,CAAC,MAAM,CAAC,IAAI;YACb,CAAC,CAAC,MAAM,CAAC,GAAG,CACR,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CACjB,MAAM;iBACH,YAAY,CAAC;gBACZ,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,MAAM,EAAE,IAAI,IAAI,CAAC,IAAI,IAAI;aAC1B,CAAC;iBACD,IAAI,CACH,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CACtB,IAAI,KAAK,CAAC;gBACR,CAAC,CAAC,MAAM,CAAC,IAAI;gBACb,CAAC,CAAC,MAAM,CAAC,IAAI,CACT,IAAI,qBAAqB,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAC/D,CACN,CACF,CACJ,EACD,EAAE,WAAW,EAAE,WAAW,EAAE,CAC7B,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;KAC5B,CAAC;AACJ,CAAC,CAAC,CACH,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ import type { EnvironmentRow } from "../core/environment.js";
3
+ import { StateError } from "../errors/errors.js";
4
+ /**
5
+ * Upsert payload: the live identity of an environment. Timestamps and template
6
+ * metadata are owned by the store — `created_at`, `template_db` and
7
+ * `template_key` are preserved on existing rows, `last_used_at` is refreshed.
8
+ * `now` exists so tests can inject a fixed clock; callers normally omit it.
9
+ */
10
+ export type EnvironmentUpsert = {
11
+ readonly composeProject: string;
12
+ readonly projectId: string;
13
+ readonly databaseName: string;
14
+ readonly rootDir: string;
15
+ readonly worktreeName: string;
16
+ readonly branch: string | null;
17
+ readonly odooHttpPort: number;
18
+ readonly shared: boolean;
19
+ readonly now?: string | undefined;
20
+ };
21
+ export interface StateStoreApi {
22
+ readonly upsert: (env: EnvironmentUpsert) => Effect.Effect<void, StateError>;
23
+ readonly touch: (composeProject: string) => Effect.Effect<void, StateError>;
24
+ readonly get: (composeProject: string) => Effect.Effect<EnvironmentRow | undefined, StateError>;
25
+ readonly list: (filter: {
26
+ readonly projectId?: string | undefined;
27
+ }) => Effect.Effect<ReadonlyArray<EnvironmentRow>, StateError>;
28
+ readonly remove: (composeProject: string) => Effect.Effect<void, StateError>;
29
+ readonly setTemplate: (composeProject: string, meta: {
30
+ readonly databaseName: string;
31
+ readonly key: string;
32
+ } | null) => Effect.Effect<void, StateError>;
33
+ }
34
+ export declare const StateStore: Context.Service<StateStoreApi, StateStoreApi>;
35
+ /** Global registry location; `ODOO_AGENTIC_DEV_STATE_DB` overrides (tests). */
36
+ export declare const resolveStateDbPath: (env?: Record<string, string | undefined>) => string;
37
+ export declare const StateStoreLive: Layer.Layer<StateStoreApi, never, never>;