otavia 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.
- package/bun.lock +589 -0
- package/package.json +35 -0
- package/src/cli.ts +153 -0
- package/src/commands/__tests__/aws-auth.test.ts +32 -0
- package/src/commands/__tests__/cell.test.ts +44 -0
- package/src/commands/__tests__/dev.test.ts +49 -0
- package/src/commands/__tests__/init.test.ts +47 -0
- package/src/commands/__tests__/setup.test.ts +263 -0
- package/src/commands/aws-auth.ts +32 -0
- package/src/commands/aws.ts +59 -0
- package/src/commands/cell.ts +33 -0
- package/src/commands/clean.ts +32 -0
- package/src/commands/deploy.ts +508 -0
- package/src/commands/dev/__tests__/fixtures/gateway-cell/cell.yaml +8 -0
- package/src/commands/dev/__tests__/gateway-backend-routes.test.ts +13 -0
- package/src/commands/dev/__tests__/gateway-forward-url.test.ts +20 -0
- package/src/commands/dev/__tests__/gateway-sso-base-url.test.ts +93 -0
- package/src/commands/dev/__tests__/tunnel.test.ts +93 -0
- package/src/commands/dev/__tests__/vite-dev-proxy-rules.test.ts +220 -0
- package/src/commands/dev/__tests__/well-known.test.ts +88 -0
- package/src/commands/dev/forward-url.ts +7 -0
- package/src/commands/dev/gateway.ts +421 -0
- package/src/commands/dev/main-frontend-runtime/main-entry.ts +35 -0
- package/src/commands/dev/main-frontend-runtime/vite-config.ts +210 -0
- package/src/commands/dev/mount-selection.ts +9 -0
- package/src/commands/dev/tunnel.ts +176 -0
- package/src/commands/dev/vite-dev.ts +382 -0
- package/src/commands/dev/well-known.ts +76 -0
- package/src/commands/dev.ts +107 -0
- package/src/commands/init.ts +69 -0
- package/src/commands/lint.ts +49 -0
- package/src/commands/setup.ts +887 -0
- package/src/commands/test.ts +331 -0
- package/src/commands/typecheck.ts +36 -0
- package/src/config/__tests__/load-cell-yaml.test.ts +248 -0
- package/src/config/__tests__/load-otavia-yaml.test.ts +492 -0
- package/src/config/__tests__/ports.test.ts +48 -0
- package/src/config/__tests__/resolve-cell-dir.test.ts +60 -0
- package/src/config/__tests__/resolve-params.test.ts +137 -0
- package/src/config/__tests__/resource-names.test.ts +62 -0
- package/src/config/cell-yaml-schema.ts +115 -0
- package/src/config/load-cell-yaml.ts +87 -0
- package/src/config/load-otavia-yaml.ts +256 -0
- package/src/config/otavia-yaml-schema.ts +49 -0
- package/src/config/ports.ts +57 -0
- package/src/config/resolve-cell-dir.ts +55 -0
- package/src/config/resolve-params.ts +160 -0
- package/src/config/resource-names.ts +60 -0
- package/src/deploy/__tests__/template.test.ts +137 -0
- package/src/deploy/api-gateway.ts +96 -0
- package/src/deploy/cloudflare-dns.ts +261 -0
- package/src/deploy/cloudfront.ts +228 -0
- package/src/deploy/dynamodb.ts +68 -0
- package/src/deploy/lambda.ts +121 -0
- package/src/deploy/s3.ts +57 -0
- package/src/deploy/template.ts +264 -0
- package/src/deploy/types.ts +16 -0
- package/src/local/docker.ts +175 -0
- package/src/local/dynamodb-local.ts +124 -0
- package/src/local/minio-local.ts +44 -0
- package/src/utils/env.test.ts +74 -0
- package/src/utils/env.ts +79 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { describe, expect, test } from "bun:test";
|
|
5
|
+
import { loadEnvForCell } from "./env.js";
|
|
6
|
+
|
|
7
|
+
function write(path: string, content: string): void {
|
|
8
|
+
writeFileSync(path, content.trim() + "\n", "utf-8");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe("loadEnvForCell", () => {
|
|
12
|
+
test("deploy stage uses .env + .env.deploy and skips .env.local", () => {
|
|
13
|
+
const root = mkdtempSync(join(tmpdir(), "otavia-env-deploy-"));
|
|
14
|
+
try {
|
|
15
|
+
const cellDir = join(root, "apps", "demo");
|
|
16
|
+
mkdirSync(cellDir, { recursive: true });
|
|
17
|
+
write(join(root, ".env"), "SSO_BASE_URL=http://localhost:7100/sso\nSHARED=base");
|
|
18
|
+
write(join(root, ".env.deploy"), "SSO_BASE_URL=https://beta.example.com/sso");
|
|
19
|
+
write(join(root, ".env.local"), "SSO_BASE_URL=http://local-override.invalid/sso");
|
|
20
|
+
write(join(cellDir, ".env"), "CELL_ONLY=1");
|
|
21
|
+
write(join(cellDir, ".env.deploy"), "DEPLOY_ONLY=1");
|
|
22
|
+
write(join(cellDir, ".env.local"), "CELL_LOCAL_ONLY=1");
|
|
23
|
+
|
|
24
|
+
const env = loadEnvForCell(root, "demo", { stage: "deploy" });
|
|
25
|
+
expect(env.SSO_BASE_URL).toBe("https://beta.example.com/sso");
|
|
26
|
+
expect(env.SHARED).toBe("base");
|
|
27
|
+
expect(env.CELL_ONLY).toBe("1");
|
|
28
|
+
expect(env.DEPLOY_ONLY).toBe("1");
|
|
29
|
+
expect(env.CELL_LOCAL_ONLY).toBeUndefined();
|
|
30
|
+
} finally {
|
|
31
|
+
rmSync(root, { recursive: true, force: true });
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("dev stage applies .env.dev and .env.local overrides", () => {
|
|
36
|
+
const root = mkdtempSync(join(tmpdir(), "otavia-env-dev-"));
|
|
37
|
+
try {
|
|
38
|
+
const cellDir = join(root, "apps", "demo");
|
|
39
|
+
mkdirSync(cellDir, { recursive: true });
|
|
40
|
+
write(join(root, ".env"), "SSO_BASE_URL=http://localhost:7100/sso\nLOG_LEVEL=info");
|
|
41
|
+
write(join(root, ".env.dev"), "LOG_LEVEL=debug");
|
|
42
|
+
write(join(root, ".env.local"), "LOG_LEVEL=trace");
|
|
43
|
+
write(join(cellDir, ".env"), "LOG_LEVEL=warn");
|
|
44
|
+
write(join(cellDir, ".env.dev"), "LOG_LEVEL=error");
|
|
45
|
+
write(join(cellDir, ".env.local"), "LOG_LEVEL=fatal");
|
|
46
|
+
|
|
47
|
+
const env = loadEnvForCell(root, cellDir, { stage: "dev" });
|
|
48
|
+
expect(env.SSO_BASE_URL).toBe("http://localhost:7100/sso");
|
|
49
|
+
expect(env.LOG_LEVEL).toBe("fatal");
|
|
50
|
+
} finally {
|
|
51
|
+
rmSync(root, { recursive: true, force: true });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("test stage applies .env.test and skips .env.local", () => {
|
|
56
|
+
const root = mkdtempSync(join(tmpdir(), "otavia-env-test-"));
|
|
57
|
+
try {
|
|
58
|
+
const cellDir = join(root, "apps", "demo");
|
|
59
|
+
mkdirSync(cellDir, { recursive: true });
|
|
60
|
+
write(join(root, ".env"), "A=base");
|
|
61
|
+
write(join(root, ".env.test"), "A=test");
|
|
62
|
+
write(join(root, ".env.local"), "A=local");
|
|
63
|
+
write(join(cellDir, ".env"), "B=cell");
|
|
64
|
+
write(join(cellDir, ".env.test"), "B=cell-test");
|
|
65
|
+
write(join(cellDir, ".env.local"), "B=cell-local");
|
|
66
|
+
|
|
67
|
+
const env = loadEnvForCell(root, "demo", { stage: "test" });
|
|
68
|
+
expect(env.A).toBe("test");
|
|
69
|
+
expect(env.B).toBe("cell-test");
|
|
70
|
+
} finally {
|
|
71
|
+
rmSync(root, { recursive: true, force: true });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
package/src/utils/env.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
/** Parse .env file content into a key-value map. */
|
|
5
|
+
export function parseEnvFile(content: string): Record<string, string> {
|
|
6
|
+
const result: Record<string, string> = {};
|
|
7
|
+
for (const line of content.split("\n")) {
|
|
8
|
+
const trimmed = line.trim();
|
|
9
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
10
|
+
const eqIdx = trimmed.indexOf("=");
|
|
11
|
+
if (eqIdx === -1) continue;
|
|
12
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
13
|
+
let value = trimmed.slice(eqIdx + 1).trim();
|
|
14
|
+
if (
|
|
15
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
16
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
17
|
+
) {
|
|
18
|
+
value = value.slice(1, -1);
|
|
19
|
+
}
|
|
20
|
+
result[key] = value;
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type EnvStage = "dev" | "test" | "deploy";
|
|
26
|
+
|
|
27
|
+
function normalizeStage(stage?: string): EnvStage | undefined {
|
|
28
|
+
if (!stage) return undefined;
|
|
29
|
+
if (stage === "cloud") return "deploy";
|
|
30
|
+
if (stage === "dev" || stage === "test" || stage === "deploy") return stage;
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function resolveCellDirForEnv(rootDir: string, cellIdOrDir: string): string {
|
|
35
|
+
if (isAbsolute(cellIdOrDir)) return cellIdOrDir;
|
|
36
|
+
const cellsPath = resolve(rootDir, "cells", cellIdOrDir);
|
|
37
|
+
const appsPath = resolve(rootDir, "apps", cellIdOrDir);
|
|
38
|
+
if (existsSync(cellsPath)) return cellsPath;
|
|
39
|
+
if (existsSync(appsPath)) return appsPath;
|
|
40
|
+
return cellsPath;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function loadEnvFile(path: string, target: Record<string, string>): void {
|
|
44
|
+
if (!existsSync(path)) return;
|
|
45
|
+
Object.assign(target, parseEnvFile(readFileSync(path, "utf-8")));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Load layered env files with stage overrides.
|
|
50
|
+
* Order (later overrides earlier):
|
|
51
|
+
* 1) root: .env, .env.<stage>, .env.local (dev/default only)
|
|
52
|
+
* 2) cell: .env, .env.<stage>, .env.local (dev/default only)
|
|
53
|
+
*/
|
|
54
|
+
export function loadEnvForCell(
|
|
55
|
+
rootDir: string,
|
|
56
|
+
cellIdOrDir: string,
|
|
57
|
+
options?: { stage?: string }
|
|
58
|
+
): Record<string, string> {
|
|
59
|
+
const merged: Record<string, string> = {};
|
|
60
|
+
const stage = normalizeStage(options?.stage);
|
|
61
|
+
const useLocalOverrides = stage !== "deploy" && stage !== "test";
|
|
62
|
+
const rootEnv = resolve(rootDir, ".env");
|
|
63
|
+
const rootStageEnv = stage ? resolve(rootDir, `.env.${stage}`) : "";
|
|
64
|
+
const rootEnvLocal = resolve(rootDir, ".env.local");
|
|
65
|
+
const cellDir = resolveCellDirForEnv(rootDir, cellIdOrDir);
|
|
66
|
+
const cellEnv = resolve(cellDir, ".env");
|
|
67
|
+
const cellStageEnv = stage ? resolve(cellDir, `.env.${stage}`) : "";
|
|
68
|
+
const cellEnvLocal = resolve(cellDir, ".env.local");
|
|
69
|
+
|
|
70
|
+
loadEnvFile(rootEnv, merged);
|
|
71
|
+
if (rootStageEnv) loadEnvFile(rootStageEnv, merged);
|
|
72
|
+
if (useLocalOverrides) loadEnvFile(rootEnvLocal, merged);
|
|
73
|
+
|
|
74
|
+
loadEnvFile(cellEnv, merged);
|
|
75
|
+
if (cellStageEnv) loadEnvFile(cellStageEnv, merged);
|
|
76
|
+
if (useLocalOverrides) loadEnvFile(cellEnvLocal, merged);
|
|
77
|
+
|
|
78
|
+
return merged;
|
|
79
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "ESNext",
|
|
4
|
+
"moduleResolution": "bundler",
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"lib": ["ESNext", "DOM"],
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"rootDir": "src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"types": ["bun", "node"]
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"]
|
|
14
|
+
}
|