@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.
- package/README.md +464 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +73 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/compose.d.ts +19 -0
- package/dist/commands/compose.js +29 -0
- package/dist/commands/compose.js.map +1 -0
- package/dist/commands/doctor.d.ts +35 -0
- package/dist/commands/doctor.js +209 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/down.d.ts +23 -0
- package/dist/commands/down.js +46 -0
- package/dist/commands/down.js.map +1 -0
- package/dist/commands/info.d.ts +10 -0
- package/dist/commands/info.js +42 -0
- package/dist/commands/info.js.map +1 -0
- package/dist/commands/json-report.d.ts +45 -0
- package/dist/commands/json-report.js +55 -0
- package/dist/commands/json-report.js.map +1 -0
- package/dist/commands/link-source.d.ts +33 -0
- package/dist/commands/link-source.js +116 -0
- package/dist/commands/link-source.js.map +1 -0
- package/dist/commands/list.d.ts +29 -0
- package/dist/commands/list.js +117 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/logs.d.ts +17 -0
- package/dist/commands/logs.js +28 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/prune.d.ts +54 -0
- package/dist/commands/prune.js +118 -0
- package/dist/commands/prune.js.map +1 -0
- package/dist/commands/psql.d.ts +8 -0
- package/dist/commands/psql.js +24 -0
- package/dist/commands/psql.js.map +1 -0
- package/dist/commands/reset-db.d.ts +34 -0
- package/dist/commands/reset-db.js +86 -0
- package/dist/commands/reset-db.js.map +1 -0
- package/dist/commands/resolve-context.d.ts +17 -0
- package/dist/commands/resolve-context.js +30 -0
- package/dist/commands/resolve-context.js.map +1 -0
- package/dist/commands/run.d.ts +31 -0
- package/dist/commands/run.js +83 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/setup.d.ts +46 -0
- package/dist/commands/setup.js +93 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/shell.d.ts +20 -0
- package/dist/commands/shell.js +46 -0
- package/dist/commands/shell.js.map +1 -0
- package/dist/commands/state-hooks.d.ts +28 -0
- package/dist/commands/state-hooks.js +86 -0
- package/dist/commands/state-hooks.js.map +1 -0
- package/dist/commands/test.d.ts +24 -0
- package/dist/commands/test.js +70 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/trailing-args.d.ts +10 -0
- package/dist/commands/trailing-args.js +14 -0
- package/dist/commands/trailing-args.js.map +1 -0
- package/dist/commands/up.d.ts +23 -0
- package/dist/commands/up.js +62 -0
- package/dist/commands/up.js.map +1 -0
- package/dist/commands/update.d.ts +7 -0
- package/dist/commands/update.js +31 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/worktree.d.ts +97 -0
- package/dist/commands/worktree.js +355 -0
- package/dist/commands/worktree.js.map +1 -0
- package/dist/config/load-recipe.d.ts +14 -0
- package/dist/config/load-recipe.js +75 -0
- package/dist/config/load-recipe.js.map +1 -0
- package/dist/config/schema.d.ts +9 -0
- package/dist/config/schema.js +190 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/core/command-plan.d.ts +34 -0
- package/dist/core/command-plan.js +112 -0
- package/dist/core/command-plan.js.map +1 -0
- package/dist/core/compose-model.d.ts +18 -0
- package/dist/core/compose-model.js +80 -0
- package/dist/core/compose-model.js.map +1 -0
- package/dist/core/compose-project.d.ts +3 -0
- package/dist/core/compose-project.js +15 -0
- package/dist/core/compose-project.js.map +1 -0
- package/dist/core/database-name.d.ts +15 -0
- package/dist/core/database-name.js +59 -0
- package/dist/core/database-name.js.map +1 -0
- package/dist/core/environment.d.ts +84 -0
- package/dist/core/environment.js +78 -0
- package/dist/core/environment.js.map +1 -0
- package/dist/core/port-allocator.d.ts +30 -0
- package/dist/core/port-allocator.js +58 -0
- package/dist/core/port-allocator.js.map +1 -0
- package/dist/core/project-recipe.d.ts +163 -0
- package/dist/core/project-recipe.js +13 -0
- package/dist/core/project-recipe.js.map +1 -0
- package/dist/core/safety.d.ts +11 -0
- package/dist/core/safety.js +16 -0
- package/dist/core/safety.js.map +1 -0
- package/dist/core/worktree-context.d.ts +32 -0
- package/dist/core/worktree-context.js +94 -0
- package/dist/core/worktree-context.js.map +1 -0
- package/dist/errors/errors.d.ts +124 -0
- package/dist/errors/errors.js +129 -0
- package/dist/errors/errors.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/platform/command-runner.d.ts +30 -0
- package/dist/platform/command-runner.js +65 -0
- package/dist/platform/command-runner.js.map +1 -0
- package/dist/platform/docker-compose.d.ts +69 -0
- package/dist/platform/docker-compose.js +239 -0
- package/dist/platform/docker-compose.js.map +1 -0
- package/dist/platform/git.d.ts +10 -0
- package/dist/platform/git.js +35 -0
- package/dist/platform/git.js.map +1 -0
- package/dist/platform/odoo-lifecycle.d.ts +30 -0
- package/dist/platform/odoo-lifecycle.js +109 -0
- package/dist/platform/odoo-lifecycle.js.map +1 -0
- package/dist/platform/port-probe.d.ts +7 -0
- package/dist/platform/port-probe.js +13 -0
- package/dist/platform/port-probe.js.map +1 -0
- package/dist/platform/process-supervisor.d.ts +16 -0
- package/dist/platform/process-supervisor.js +23 -0
- package/dist/platform/process-supervisor.js.map +1 -0
- package/dist/platform/state-store.d.ts +37 -0
- package/dist/platform/state-store.js +122 -0
- package/dist/platform/state-store.js.map +1 -0
- package/dist/suppress-sqlite-warning.d.ts +1 -0
- package/dist/suppress-sqlite-warning.js +18 -0
- package/dist/suppress-sqlite-warning.js.map +1 -0
- package/dist/testing/fake-adapters.d.ts +34 -0
- package/dist/testing/fake-adapters.js +90 -0
- package/dist/testing/fake-adapters.js.map +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { UnsafeDatabaseNameError } from "../errors/errors.js";
|
|
4
|
+
export const DB_NAME_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
5
|
+
const MAX_DB_NAME_LENGTH = 63;
|
|
6
|
+
/**
|
|
7
|
+
* Derived names are capped below PostgreSQL's 63 so the 5-char `__tpl`
|
|
8
|
+
* template suffix still fits. Explicit env overrides keep the full 63 budget
|
|
9
|
+
* (template snapshots are skipped for names longer than this — see
|
|
10
|
+
* decideResetPath in core/environment.ts).
|
|
11
|
+
*/
|
|
12
|
+
const DERIVED_NAME_BUDGET = 58;
|
|
13
|
+
/** Default leading branch type segments dropped (once) before deriving a name. */
|
|
14
|
+
export const DEFAULT_STRIP_BRANCH_PREFIXES = [
|
|
15
|
+
"feature",
|
|
16
|
+
"feat",
|
|
17
|
+
"bugfix",
|
|
18
|
+
"bug",
|
|
19
|
+
"hotfix",
|
|
20
|
+
"fix",
|
|
21
|
+
"chore",
|
|
22
|
+
"task",
|
|
23
|
+
];
|
|
24
|
+
export const sanitizeNamePart = (raw) => raw
|
|
25
|
+
.toLowerCase()
|
|
26
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
27
|
+
.replace(/^_+|_+$/g, "");
|
|
28
|
+
const sha8 = (input) => createHash("sha256").update(input).digest("hex").slice(0, 8);
|
|
29
|
+
const truncate = (name) => name.length <= DERIVED_NAME_BUDGET ? name : `${name.slice(0, 49)}_${sha8(name)}`;
|
|
30
|
+
const assertSafeDatabaseName = (name) => name.length === 0 || name.length > MAX_DB_NAME_LENGTH || !DB_NAME_PATTERN.test(name)
|
|
31
|
+
? Effect.fail(new UnsafeDatabaseNameError({ name }))
|
|
32
|
+
: Effect.succeed(name);
|
|
33
|
+
export const deriveDatabaseName = (options) => {
|
|
34
|
+
if (options.envDatabase !== undefined)
|
|
35
|
+
return assertSafeDatabaseName(options.envDatabase);
|
|
36
|
+
if (options.branch !== undefined &&
|
|
37
|
+
options.sharedDatabase !== null &&
|
|
38
|
+
options.sharedBranches.includes(options.branch)) {
|
|
39
|
+
return assertSafeDatabaseName(options.sharedDatabase);
|
|
40
|
+
}
|
|
41
|
+
const seed = options.branch ?? options.worktreeName;
|
|
42
|
+
const segments = seed.split("/");
|
|
43
|
+
// strip AT MOST one leading type segment — the bash `case feature/*|fix/*)`
|
|
44
|
+
// this reproduces matches a single prefix, so feature/fix/x keeps "fix"
|
|
45
|
+
const prefixes = options.stripBranchPrefixes.map((prefix) => prefix.toLowerCase());
|
|
46
|
+
if (segments.length > 1 && prefixes.includes(segments[0].toLowerCase()))
|
|
47
|
+
segments.shift();
|
|
48
|
+
let body = sanitizeNamePart(segments.join("/"));
|
|
49
|
+
if (body.length === 0)
|
|
50
|
+
body = sanitizeNamePart(options.worktreeName);
|
|
51
|
+
// bash parity: a name that sanitizes to nothing becomes "worktree", never "<prefix>_"
|
|
52
|
+
if (body.length === 0)
|
|
53
|
+
body = "worktree";
|
|
54
|
+
const prefixed = body === options.dbPrefix || body.startsWith(`${options.dbPrefix}_`)
|
|
55
|
+
? body
|
|
56
|
+
: `${options.dbPrefix}_${body}`;
|
|
57
|
+
return assertSafeDatabaseName(truncate(prefixed));
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=database-name.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database-name.js","sourceRoot":"","sources":["../../src/core/database-name.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { OdooAgenticDevConfig } from "./project-recipe.js";
|
|
2
|
+
/**
|
|
3
|
+
* One row of the global state registry's `environments` table (camelCase
|
|
4
|
+
* mirror of the SQL schema). Nullable columns are `T | null`, never optional,
|
|
5
|
+
* so rows survive `exactOptionalPropertyTypes` round-trips through SQLite.
|
|
6
|
+
*/
|
|
7
|
+
export type EnvironmentRow = {
|
|
8
|
+
readonly composeProject: string;
|
|
9
|
+
readonly projectId: string;
|
|
10
|
+
readonly databaseName: string;
|
|
11
|
+
readonly rootDir: string;
|
|
12
|
+
readonly worktreeName: string;
|
|
13
|
+
/** null when detached / not a repo */
|
|
14
|
+
readonly branch: string | null;
|
|
15
|
+
readonly odooHttpPort: number;
|
|
16
|
+
readonly shared: boolean;
|
|
17
|
+
/** ISO 8601 UTC */
|
|
18
|
+
readonly createdAt: string;
|
|
19
|
+
/** ISO 8601 UTC */
|
|
20
|
+
readonly lastUsedAt: string;
|
|
21
|
+
/** e.g. kl_feature_x__tpl */
|
|
22
|
+
readonly templateDb: string | null;
|
|
23
|
+
/** hash of (modules, withoutDemo, odoo.version, postInit) */
|
|
24
|
+
readonly templateKey: string | null;
|
|
25
|
+
};
|
|
26
|
+
export declare const TEMPLATE_SUFFIX = "__tpl";
|
|
27
|
+
/**
|
|
28
|
+
* Longest database name that still fits `TEMPLATE_SUFFIX` within PostgreSQL's
|
|
29
|
+
* 63-char identifier limit. Derived names are truncated to this budget;
|
|
30
|
+
* explicit overrides may exceed it and then simply skip template snapshots.
|
|
31
|
+
*/
|
|
32
|
+
export declare const TEMPLATE_BASE_NAME_BUDGET = 58;
|
|
33
|
+
export declare const templateDbName: (db: string) => string;
|
|
34
|
+
/**
|
|
35
|
+
* Identity of a template snapshot: everything baked into the database by a
|
|
36
|
+
* full init (modules, demo data, Odoo version, post-init hooks). Any change
|
|
37
|
+
* here must invalidate existing snapshots.
|
|
38
|
+
*/
|
|
39
|
+
export declare const computeTemplateKey: (recipe: OdooAgenticDevConfig) => string;
|
|
40
|
+
export type EnvStatus = "running" | "stopped" | "vanished";
|
|
41
|
+
export type PruneReason = "keep" | "gone-branch" | "gone-rootdir" | "vanished" | "stale" | "shared-skipped";
|
|
42
|
+
/** Filesystem/git facts about a row, gathered by the caller (IO stays there). */
|
|
43
|
+
export type EnvironmentProbe = {
|
|
44
|
+
readonly rootDirExists: boolean;
|
|
45
|
+
/** null = not a repo / no branch recorded — the branch rule does not apply */
|
|
46
|
+
readonly branchExists: boolean | null;
|
|
47
|
+
};
|
|
48
|
+
export type ClassifiedEnvironment = {
|
|
49
|
+
readonly row: EnvironmentRow;
|
|
50
|
+
readonly status: EnvStatus;
|
|
51
|
+
readonly reason: PruneReason;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Pure prune/list decision. Docker is the truth for status; prune reasons are
|
|
55
|
+
* evaluated in strict priority order: shared shield, vanished stack, deleted
|
|
56
|
+
* root dir, deleted branch, staleness (only when `olderThanDays` is set).
|
|
57
|
+
* Rows with no probe entry are conservatively kept.
|
|
58
|
+
*/
|
|
59
|
+
export declare const classifyEnvironments: (input: {
|
|
60
|
+
readonly rows: ReadonlyArray<EnvironmentRow>;
|
|
61
|
+
readonly dockerProjects: ReadonlyArray<{
|
|
62
|
+
readonly name: string;
|
|
63
|
+
readonly running: boolean;
|
|
64
|
+
}>;
|
|
65
|
+
readonly probes: ReadonlyMap<string, EnvironmentProbe>;
|
|
66
|
+
readonly olderThanDays: number | null;
|
|
67
|
+
readonly allowShared: boolean;
|
|
68
|
+
/** ISO 8601 UTC */
|
|
69
|
+
readonly now: string;
|
|
70
|
+
}) => ReadonlyArray<ClassifiedEnvironment>;
|
|
71
|
+
export type ResetPath = "restore" | "full" | "full-then-snapshot";
|
|
72
|
+
/**
|
|
73
|
+
* Pure reset-path decision for `reset-db`/`setup`. Snapshots are only ever
|
|
74
|
+
* planned for names within `TEMPLATE_BASE_NAME_BUDGET` — over-budget names
|
|
75
|
+
* (explicit overrides) degrade every snapshot outcome to a plain `full`.
|
|
76
|
+
*/
|
|
77
|
+
export declare const decideResetPath: (input: {
|
|
78
|
+
readonly row: EnvironmentRow | undefined;
|
|
79
|
+
readonly expectedKey: string;
|
|
80
|
+
readonly databaseName: string;
|
|
81
|
+
readonly noTemplate: boolean;
|
|
82
|
+
readonly refreshTemplate: boolean;
|
|
83
|
+
readonly hasOverrides: boolean;
|
|
84
|
+
}) => ResetPath;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
export const TEMPLATE_SUFFIX = "__tpl";
|
|
3
|
+
/**
|
|
4
|
+
* Longest database name that still fits `TEMPLATE_SUFFIX` within PostgreSQL's
|
|
5
|
+
* 63-char identifier limit. Derived names are truncated to this budget;
|
|
6
|
+
* explicit overrides may exceed it and then simply skip template snapshots.
|
|
7
|
+
*/
|
|
8
|
+
export const TEMPLATE_BASE_NAME_BUDGET = 58;
|
|
9
|
+
export const templateDbName = (db) => `${db}${TEMPLATE_SUFFIX}`;
|
|
10
|
+
/**
|
|
11
|
+
* Identity of a template snapshot: everything baked into the database by a
|
|
12
|
+
* full init (modules, demo data, Odoo version, post-init hooks). Any change
|
|
13
|
+
* here must invalidate existing snapshots.
|
|
14
|
+
*/
|
|
15
|
+
export const computeTemplateKey = (recipe) => createHash("sha256")
|
|
16
|
+
.update(JSON.stringify([
|
|
17
|
+
recipe.database.initialModules,
|
|
18
|
+
recipe.database.withoutDemo,
|
|
19
|
+
recipe.odoo.version,
|
|
20
|
+
recipe.database.postInit,
|
|
21
|
+
]))
|
|
22
|
+
.digest("hex")
|
|
23
|
+
.slice(0, 8);
|
|
24
|
+
const DAY_MS = 86_400_000;
|
|
25
|
+
/**
|
|
26
|
+
* Pure prune/list decision. Docker is the truth for status; prune reasons are
|
|
27
|
+
* evaluated in strict priority order: shared shield, vanished stack, deleted
|
|
28
|
+
* root dir, deleted branch, staleness (only when `olderThanDays` is set).
|
|
29
|
+
* Rows with no probe entry are conservatively kept.
|
|
30
|
+
*/
|
|
31
|
+
export const classifyEnvironments = (input) => {
|
|
32
|
+
const dockerByName = new Map(input.dockerProjects.map((p) => [p.name, p.running]));
|
|
33
|
+
const nowMs = Date.parse(input.now);
|
|
34
|
+
return input.rows.map((row) => {
|
|
35
|
+
const running = dockerByName.get(row.composeProject);
|
|
36
|
+
const status = running === undefined ? "vanished" : running ? "running" : "stopped";
|
|
37
|
+
const probe = input.probes.get(row.composeProject) ?? {
|
|
38
|
+
rootDirExists: true,
|
|
39
|
+
branchExists: null,
|
|
40
|
+
};
|
|
41
|
+
const stale = input.olderThanDays !== null &&
|
|
42
|
+
nowMs - Date.parse(row.lastUsedAt) > input.olderThanDays * DAY_MS;
|
|
43
|
+
const reason = row.shared && !input.allowShared
|
|
44
|
+
? "shared-skipped"
|
|
45
|
+
: status === "vanished"
|
|
46
|
+
? "vanished"
|
|
47
|
+
: !probe.rootDirExists
|
|
48
|
+
? "gone-rootdir"
|
|
49
|
+
: probe.branchExists === false
|
|
50
|
+
? "gone-branch"
|
|
51
|
+
: stale
|
|
52
|
+
? "stale"
|
|
53
|
+
: "keep";
|
|
54
|
+
return { row, status, reason };
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Pure reset-path decision for `reset-db`/`setup`. Snapshots are only ever
|
|
59
|
+
* planned for names within `TEMPLATE_BASE_NAME_BUDGET` — over-budget names
|
|
60
|
+
* (explicit overrides) degrade every snapshot outcome to a plain `full`.
|
|
61
|
+
*/
|
|
62
|
+
export const decideResetPath = (input) => {
|
|
63
|
+
const snapshotFits = input.databaseName.length <= TEMPLATE_BASE_NAME_BUDGET;
|
|
64
|
+
if (input.hasOverrides)
|
|
65
|
+
return "full";
|
|
66
|
+
if (input.refreshTemplate)
|
|
67
|
+
return snapshotFits ? "full-then-snapshot" : "full";
|
|
68
|
+
if (input.noTemplate)
|
|
69
|
+
return "full";
|
|
70
|
+
if (input.row !== undefined &&
|
|
71
|
+
input.row.templateDb !== null &&
|
|
72
|
+
input.row.templateKey === input.expectedKey &&
|
|
73
|
+
snapshotFits) {
|
|
74
|
+
return "restore";
|
|
75
|
+
}
|
|
76
|
+
return snapshotFits ? "full-then-snapshot" : "full";
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=environment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"environment.js","sourceRoot":"","sources":["../../src/core/environment.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { ConfigValidationError } from "../errors/errors.js";
|
|
3
|
+
export type PortHashAlgorithm = "fnv1a32" | "posix-cksum";
|
|
4
|
+
/** FNV-1a 32-bit hash; stable basis for port offsets. */
|
|
5
|
+
export declare const fnv1a32: (input: string) => number;
|
|
6
|
+
/**
|
|
7
|
+
* POSIX 1003.2 `cksum` CRC: polynomial 0x04C11DB7 fed MSB-first with initial
|
|
8
|
+
* value 0 over the UTF-8 message bytes, then over the message length encoded
|
|
9
|
+
* least-significant byte first with leading zero bytes stripped (zero length
|
|
10
|
+
* contributes no bytes), finally ones-complemented. Bit-for-bit identical to
|
|
11
|
+
* the coreutils/BSD `cksum` binary — this is what reproduces the bash
|
|
12
|
+
* `cksum <<< "$db" | awk '{print $1}'` port derivation.
|
|
13
|
+
*/
|
|
14
|
+
export declare const posixCksum: (input: string) => number;
|
|
15
|
+
export declare const derivePorts: (options: {
|
|
16
|
+
readonly databaseName: string;
|
|
17
|
+
readonly ports: {
|
|
18
|
+
readonly odooBase: number;
|
|
19
|
+
readonly companionBase: number;
|
|
20
|
+
readonly range: number;
|
|
21
|
+
readonly hashAlgorithm: PortHashAlgorithm;
|
|
22
|
+
};
|
|
23
|
+
readonly companionApps: ReadonlyArray<{
|
|
24
|
+
readonly name: string;
|
|
25
|
+
}>;
|
|
26
|
+
readonly envHttpPort: string | undefined;
|
|
27
|
+
}) => Effect.Effect<{
|
|
28
|
+
readonly odooHttpPort: number;
|
|
29
|
+
readonly companionPorts: ReadonlyMap<string, number>;
|
|
30
|
+
}, ConfigValidationError>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { ConfigValidationError } from "../errors/errors.js";
|
|
3
|
+
/** FNV-1a 32-bit hash; stable basis for port offsets. */
|
|
4
|
+
export const fnv1a32 = (input) => {
|
|
5
|
+
let hash = 0x811c9dc5;
|
|
6
|
+
for (let i = 0; i < input.length; i++) {
|
|
7
|
+
hash ^= input.charCodeAt(i);
|
|
8
|
+
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
9
|
+
}
|
|
10
|
+
return hash >>> 0;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* POSIX 1003.2 `cksum` CRC: polynomial 0x04C11DB7 fed MSB-first with initial
|
|
14
|
+
* value 0 over the UTF-8 message bytes, then over the message length encoded
|
|
15
|
+
* least-significant byte first with leading zero bytes stripped (zero length
|
|
16
|
+
* contributes no bytes), finally ones-complemented. Bit-for-bit identical to
|
|
17
|
+
* the coreutils/BSD `cksum` binary — this is what reproduces the bash
|
|
18
|
+
* `cksum <<< "$db" | awk '{print $1}'` port derivation.
|
|
19
|
+
*/
|
|
20
|
+
export const posixCksum = (input) => {
|
|
21
|
+
let crc = 0;
|
|
22
|
+
const feed = (byte) => {
|
|
23
|
+
crc = (crc ^ (byte << 24)) >>> 0;
|
|
24
|
+
for (let bit = 0; bit < 8; bit++) {
|
|
25
|
+
crc = ((crc & 0x80000000) !== 0 ? (crc << 1) ^ 0x04c11db7 : crc << 1) >>> 0;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const bytes = new TextEncoder().encode(input);
|
|
29
|
+
for (const byte of bytes)
|
|
30
|
+
feed(byte);
|
|
31
|
+
for (let length = bytes.length; length > 0; length = Math.floor(length / 256)) {
|
|
32
|
+
feed(length & 0xff);
|
|
33
|
+
}
|
|
34
|
+
return ~crc >>> 0;
|
|
35
|
+
};
|
|
36
|
+
const hashFor = (algorithm) => algorithm === "posix-cksum" ? posixCksum : fnv1a32;
|
|
37
|
+
export const derivePorts = (options) => {
|
|
38
|
+
const offset = hashFor(options.ports.hashAlgorithm)(options.databaseName) % options.ports.range;
|
|
39
|
+
let odooHttpPort = options.ports.odooBase + offset;
|
|
40
|
+
if (options.envHttpPort !== undefined) {
|
|
41
|
+
const parsed = Number.parseInt(options.envHttpPort, 10);
|
|
42
|
+
if (!Number.isInteger(parsed) ||
|
|
43
|
+
String(parsed) !== options.envHttpPort.trim() ||
|
|
44
|
+
parsed < 1 ||
|
|
45
|
+
parsed > 65535) {
|
|
46
|
+
return Effect.fail(new ConfigValidationError({
|
|
47
|
+
issues: [`ODOO_HTTP_PORT must be an integer port, got "${options.envHttpPort}"`],
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
odooHttpPort = parsed;
|
|
51
|
+
}
|
|
52
|
+
const companionPorts = new Map();
|
|
53
|
+
options.companionApps.forEach((app, index) => {
|
|
54
|
+
companionPorts.set(app.name, options.ports.companionBase + offset + index);
|
|
55
|
+
});
|
|
56
|
+
return Effect.succeed({ odooHttpPort, companionPorts });
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=port-allocator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"port-allocator.js","sourceRoot":"","sources":["../../src/core/port-allocator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import type { PortHashAlgorithm } from "./port-allocator.js";
|
|
2
|
+
export declare const CANONICAL_ENV_VARS: readonly ["ODOO_DATABASE", "E2E_ODOO_DB", "ODOO_BASE_URL", "ODOO_HTTP_PORT", "ODOO_COMPOSE_PROJECT_NAME"];
|
|
3
|
+
export type CanonicalEnvVar = (typeof CANONICAL_ENV_VARS)[number];
|
|
4
|
+
export type OdooAddonMount = {
|
|
5
|
+
readonly host: string;
|
|
6
|
+
readonly container: string;
|
|
7
|
+
/** allow host paths outside the project root (default false) */
|
|
8
|
+
readonly allowOutsideRepo?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export type PostInitHook = {
|
|
11
|
+
readonly type: "odoo-shell-file";
|
|
12
|
+
readonly file: string;
|
|
13
|
+
} | {
|
|
14
|
+
readonly type: "odoo-shell-inline";
|
|
15
|
+
readonly code: string;
|
|
16
|
+
} | {
|
|
17
|
+
readonly type: "set-ir-config-parameter";
|
|
18
|
+
readonly key: string;
|
|
19
|
+
readonly value: string;
|
|
20
|
+
} | {
|
|
21
|
+
readonly type: "command";
|
|
22
|
+
readonly command: string;
|
|
23
|
+
readonly args: ReadonlyArray<string>;
|
|
24
|
+
readonly cwd?: string;
|
|
25
|
+
};
|
|
26
|
+
export type CompanionAppConfig = {
|
|
27
|
+
readonly name: string;
|
|
28
|
+
readonly cwd: string;
|
|
29
|
+
readonly command: string;
|
|
30
|
+
readonly args: ReadonlyArray<string>;
|
|
31
|
+
/** env var name that receives the allocated port */
|
|
32
|
+
readonly portEnv?: string;
|
|
33
|
+
/** env var name that receives the app's http://localhost:<port> URL */
|
|
34
|
+
readonly urlEnv?: string;
|
|
35
|
+
/** extra env; values may reference canonical vars as "$ODOO_DATABASE" etc. */
|
|
36
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
37
|
+
};
|
|
38
|
+
export type PackageManagerStep = {
|
|
39
|
+
readonly cwd: string;
|
|
40
|
+
readonly command: string;
|
|
41
|
+
readonly args: ReadonlyArray<string>;
|
|
42
|
+
};
|
|
43
|
+
export type OdooProjectConfig = {
|
|
44
|
+
readonly id: string;
|
|
45
|
+
readonly dbPrefix: string;
|
|
46
|
+
readonly sharedDatabase?: string;
|
|
47
|
+
readonly sharedBranches?: ReadonlyArray<string>;
|
|
48
|
+
/** leading branch type segments stripped (at most one) before deriving the database name */
|
|
49
|
+
readonly stripBranchPrefixes?: ReadonlyArray<string>;
|
|
50
|
+
};
|
|
51
|
+
export type OdooRuntimeConfig = {
|
|
52
|
+
readonly version: string;
|
|
53
|
+
readonly serviceName?: string;
|
|
54
|
+
readonly databaseServiceName?: string;
|
|
55
|
+
readonly postgresImage?: string;
|
|
56
|
+
readonly configFile?: string;
|
|
57
|
+
readonly dockerfile?: string;
|
|
58
|
+
readonly imageName?: string;
|
|
59
|
+
readonly addons: ReadonlyArray<OdooAddonMount>;
|
|
60
|
+
/** path to a local Odoo source checkout, or "docker-only" */
|
|
61
|
+
readonly source?: string;
|
|
62
|
+
};
|
|
63
|
+
export type OdooDatabaseConfig = {
|
|
64
|
+
readonly initialModules?: ReadonlyArray<string>;
|
|
65
|
+
/** Odoo --without-demo value; false disables the flag entirely */
|
|
66
|
+
readonly withoutDemo?: string | false;
|
|
67
|
+
readonly postInit?: ReadonlyArray<PostInitHook>;
|
|
68
|
+
};
|
|
69
|
+
export type OdooAgenticDevConfigInput = {
|
|
70
|
+
readonly project: OdooProjectConfig;
|
|
71
|
+
readonly ports?: {
|
|
72
|
+
readonly odooBase?: number;
|
|
73
|
+
readonly companionBase?: number;
|
|
74
|
+
readonly range?: number;
|
|
75
|
+
/** offset hash over the database name; "posix-cksum" reproduces bash `cksum` tooling */
|
|
76
|
+
readonly hashAlgorithm?: PortHashAlgorithm;
|
|
77
|
+
};
|
|
78
|
+
readonly odoo: OdooRuntimeConfig;
|
|
79
|
+
readonly database?: OdooDatabaseConfig;
|
|
80
|
+
readonly setup?: {
|
|
81
|
+
readonly submodules?: boolean;
|
|
82
|
+
readonly packageManagers?: ReadonlyArray<PackageManagerStep>;
|
|
83
|
+
};
|
|
84
|
+
readonly compose?: {
|
|
85
|
+
/** escape hatch: project-supplied compose file instead of the generated one */
|
|
86
|
+
readonly file?: string;
|
|
87
|
+
};
|
|
88
|
+
readonly worktree?: {
|
|
89
|
+
/** project-root files copied into a fresh worktree when they exist (e.g. ".env.e2e") */
|
|
90
|
+
readonly copyFiles?: ReadonlyArray<string>;
|
|
91
|
+
/** prefix for the branch `worktree create` makes (default "worktree-") */
|
|
92
|
+
readonly branchPrefix?: string;
|
|
93
|
+
};
|
|
94
|
+
readonly test?: {
|
|
95
|
+
/** profile name → extra odoo CLI args, e.g. { payment: ["--test-tags", "payment"] } */
|
|
96
|
+
readonly profiles?: Readonly<Record<string, ReadonlyArray<string>>>;
|
|
97
|
+
};
|
|
98
|
+
/** compatibility aliases: alias env var name → any assembled env key (canonical or companion portEnv/urlEnv) */
|
|
99
|
+
readonly envAliases?: Readonly<Record<string, string>>;
|
|
100
|
+
readonly companionApps?: ReadonlyArray<CompanionAppConfig>;
|
|
101
|
+
/** stale-environment cleanup: warn by default, prune automatically when auto */
|
|
102
|
+
readonly cleanup?: {
|
|
103
|
+
readonly maxAgeDays?: number;
|
|
104
|
+
readonly auto?: boolean;
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
/** Normalized config: every default applied, optionals resolved. */
|
|
108
|
+
export type OdooAgenticDevConfig = {
|
|
109
|
+
readonly project: {
|
|
110
|
+
readonly id: string;
|
|
111
|
+
readonly dbPrefix: string;
|
|
112
|
+
readonly sharedDatabase: string | null;
|
|
113
|
+
readonly sharedBranches: ReadonlyArray<string>;
|
|
114
|
+
readonly stripBranchPrefixes: ReadonlyArray<string>;
|
|
115
|
+
};
|
|
116
|
+
readonly ports: {
|
|
117
|
+
readonly odooBase: number;
|
|
118
|
+
readonly companionBase: number;
|
|
119
|
+
readonly range: number;
|
|
120
|
+
readonly hashAlgorithm: PortHashAlgorithm;
|
|
121
|
+
};
|
|
122
|
+
readonly odoo: {
|
|
123
|
+
readonly version: string;
|
|
124
|
+
readonly serviceName: string;
|
|
125
|
+
readonly databaseServiceName: string;
|
|
126
|
+
readonly postgresImage: string;
|
|
127
|
+
readonly configFile: string | null;
|
|
128
|
+
readonly dockerfile: string | null;
|
|
129
|
+
readonly imageName: string | null;
|
|
130
|
+
readonly addons: ReadonlyArray<OdooAddonMount>;
|
|
131
|
+
readonly source: string | null;
|
|
132
|
+
};
|
|
133
|
+
readonly database: {
|
|
134
|
+
readonly initialModules: ReadonlyArray<string>;
|
|
135
|
+
readonly withoutDemo: string | false;
|
|
136
|
+
readonly postInit: ReadonlyArray<PostInitHook>;
|
|
137
|
+
};
|
|
138
|
+
readonly setup: {
|
|
139
|
+
readonly submodules: boolean;
|
|
140
|
+
readonly packageManagers: ReadonlyArray<PackageManagerStep>;
|
|
141
|
+
};
|
|
142
|
+
readonly compose: {
|
|
143
|
+
readonly file: string | null;
|
|
144
|
+
};
|
|
145
|
+
readonly worktree: {
|
|
146
|
+
readonly copyFiles: ReadonlyArray<string>;
|
|
147
|
+
readonly branchPrefix: string;
|
|
148
|
+
};
|
|
149
|
+
readonly test: {
|
|
150
|
+
readonly profiles: Readonly<Record<string, ReadonlyArray<string>>>;
|
|
151
|
+
};
|
|
152
|
+
readonly envAliases: Readonly<Record<string, string>>;
|
|
153
|
+
readonly companionApps: ReadonlyArray<CompanionAppConfig>;
|
|
154
|
+
readonly cleanup: {
|
|
155
|
+
readonly maxAgeDays: number;
|
|
156
|
+
readonly auto: boolean;
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* Identity helper preserving literal types. Runtime validation happens when the
|
|
161
|
+
* CLI loads the file (config/schema.ts), so plain JS configs are covered too.
|
|
162
|
+
*/
|
|
163
|
+
export declare const defineConfig: (config: OdooAgenticDevConfigInput) => OdooAgenticDevConfigInput;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const CANONICAL_ENV_VARS = [
|
|
2
|
+
"ODOO_DATABASE",
|
|
3
|
+
"E2E_ODOO_DB",
|
|
4
|
+
"ODOO_BASE_URL",
|
|
5
|
+
"ODOO_HTTP_PORT",
|
|
6
|
+
"ODOO_COMPOSE_PROJECT_NAME",
|
|
7
|
+
];
|
|
8
|
+
/**
|
|
9
|
+
* Identity helper preserving literal types. Runtime validation happens when the
|
|
10
|
+
* CLI loads the file (config/schema.ts), so plain JS configs are covered too.
|
|
11
|
+
*/
|
|
12
|
+
export const defineConfig = (config) => config;
|
|
13
|
+
//# sourceMappingURL=project-recipe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-recipe.js","sourceRoot":"","sources":["../../src/core/project-recipe.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,eAAe;IACf,aAAa;IACb,eAAe;IACf,gBAAgB;IAChB,2BAA2B;CACnB,CAAC;AA0JX;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAiC,EAA6B,EAAE,CAC3F,MAAM,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { ConfigValidationError, SharedDatabaseProtectionError } from "../errors/errors.js";
|
|
3
|
+
export declare const isSharedDatabase: (databaseName: string, sharedDatabase: string | null) => boolean;
|
|
4
|
+
export declare const assertSharedDatabaseAllowed: (options: {
|
|
5
|
+
readonly databaseName: string;
|
|
6
|
+
readonly sharedDatabase: string | null;
|
|
7
|
+
readonly allowShared: boolean;
|
|
8
|
+
/** command name for the error message, e.g. "reset-db" */
|
|
9
|
+
readonly action: string;
|
|
10
|
+
}) => Effect.Effect<void, SharedDatabaseProtectionError>;
|
|
11
|
+
export declare const assertComposeProjectName: (name: string) => Effect.Effect<string, ConfigValidationError>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { ConfigValidationError, SharedDatabaseProtectionError } from "../errors/errors.js";
|
|
3
|
+
const COMPOSE_PROJECT_PATTERN = /^[a-z0-9][a-z0-9_-]*$/;
|
|
4
|
+
export const isSharedDatabase = (databaseName, sharedDatabase) => sharedDatabase !== null && databaseName === sharedDatabase;
|
|
5
|
+
export const assertSharedDatabaseAllowed = (options) => isSharedDatabase(options.databaseName, options.sharedDatabase) && !options.allowShared
|
|
6
|
+
? Effect.fail(new SharedDatabaseProtectionError({
|
|
7
|
+
database: options.databaseName,
|
|
8
|
+
action: options.action,
|
|
9
|
+
}))
|
|
10
|
+
: Effect.void;
|
|
11
|
+
export const assertComposeProjectName = (name) => COMPOSE_PROJECT_PATTERN.test(name)
|
|
12
|
+
? Effect.succeed(name)
|
|
13
|
+
: Effect.fail(new ConfigValidationError({
|
|
14
|
+
issues: [`compose project name "${name}" is empty or unsafe`],
|
|
15
|
+
}));
|
|
16
|
+
//# sourceMappingURL=safety.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safety.js","sourceRoot":"","sources":["../../src/core/safety.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,qBAAqB,EAAE,6BAA6B,EAAE,MAAM,qBAAqB,CAAC;AAE3F,MAAM,uBAAuB,GAAG,uBAAuB,CAAC;AAExD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,YAAoB,EAAE,cAA6B,EAAW,EAAE,CAC/F,cAAc,KAAK,IAAI,IAAI,YAAY,KAAK,cAAc,CAAC;AAE7D,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,OAM3C,EAAsD,EAAE,CACvD,gBAAgB,CAAC,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW;IACpF,CAAC,CAAC,MAAM,CAAC,IAAI,CACT,IAAI,6BAA6B,CAAC;QAChC,QAAQ,EAAE,OAAO,CAAC,YAAY;QAC9B,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CACH;IACH,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;AAElB,MAAM,CAAC,MAAM,wBAAwB,GAAG,CACtC,IAAY,EACkC,EAAE,CAChD,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC;IAChC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;IACtB,CAAC,CAAC,MAAM,CAAC,IAAI,CACT,IAAI,qBAAqB,CAAC;QACxB,MAAM,EAAE,CAAC,yBAAyB,IAAI,sBAAsB,CAAC;KAC9D,CAAC,CACH,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { ConfigValidationError } from "../errors/errors.js";
|
|
3
|
+
import type { UnsafeDatabaseNameError } from "../errors/errors.js";
|
|
4
|
+
import type { OdooAgenticDevConfig } from "./project-recipe.js";
|
|
5
|
+
export type GitState = {
|
|
6
|
+
readonly _tag: "Branch";
|
|
7
|
+
readonly branch: string;
|
|
8
|
+
} | {
|
|
9
|
+
readonly _tag: "Detached";
|
|
10
|
+
} | {
|
|
11
|
+
readonly _tag: "NotARepo";
|
|
12
|
+
};
|
|
13
|
+
export type WorktreeContext = {
|
|
14
|
+
readonly rootDir: string;
|
|
15
|
+
readonly worktreeName: string;
|
|
16
|
+
/** the real git branch; null when detached / not a repo (name overrides don't count) */
|
|
17
|
+
readonly branch: string | null;
|
|
18
|
+
readonly databaseName: string;
|
|
19
|
+
readonly composeProjectName: string;
|
|
20
|
+
readonly odooHttpPort: number;
|
|
21
|
+
readonly odooBaseUrl: string;
|
|
22
|
+
readonly companionPorts: ReadonlyMap<string, number>;
|
|
23
|
+
readonly env: Record<string, string>;
|
|
24
|
+
};
|
|
25
|
+
export declare const buildWorktreeContext: (options: {
|
|
26
|
+
readonly rootDir: string;
|
|
27
|
+
readonly recipe: OdooAgenticDevConfig;
|
|
28
|
+
readonly env: Record<string, string | undefined>;
|
|
29
|
+
readonly git: GitState;
|
|
30
|
+
}) => Effect.Effect<WorktreeContext, ConfigValidationError | UnsafeDatabaseNameError>;
|
|
31
|
+
/** Replace $NAME tokens with values from `env`; unknown tokens are left intact. */
|
|
32
|
+
export declare const substituteEnvTokens: (value: string, env: Record<string, string>) => string;
|