@crossdelta/infrastructure 0.3.1 → 0.4.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/bin/generate-env.mjs +135 -0
- package/bin/generate-env.ts +12 -5
- package/dist/types/service-names.d.ts +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// bin/generate-env.ts
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
var toEnvKey = (name) => name.toUpperCase().replace(/-/g, "_");
|
|
8
|
+
var resolveNumber = (value, fileContent) => {
|
|
9
|
+
const literal = Number.parseInt(value, 10);
|
|
10
|
+
if (!Number.isNaN(literal))
|
|
11
|
+
return literal;
|
|
12
|
+
const varMatch = fileContent.match(new RegExp(`(?:const|let|var)\\s+${value}\\s*=\\s*(\\d+)`));
|
|
13
|
+
return varMatch?.[1] ? Number.parseInt(varMatch[1], 10) : undefined;
|
|
14
|
+
};
|
|
15
|
+
var extract = {
|
|
16
|
+
string: (content, pattern) => content.match(pattern)?.[1],
|
|
17
|
+
number: (content, pattern) => {
|
|
18
|
+
const match = content.match(pattern)?.[1];
|
|
19
|
+
return match ? resolveNumber(match, content) : undefined;
|
|
20
|
+
},
|
|
21
|
+
numberArray: (content, pattern) => {
|
|
22
|
+
const match = content.match(pattern)?.[1];
|
|
23
|
+
if (!match)
|
|
24
|
+
return;
|
|
25
|
+
const numbers = match.split(",").map((v) => resolveNumber(v.trim(), content)).filter((n) => n !== undefined);
|
|
26
|
+
return numbers.length > 0 ? numbers : undefined;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var LOCK_FILES = ["bun.lock", "bun.lockb", "package-lock.json", "pnpm-lock.yaml", "yarn.lock"];
|
|
30
|
+
var hasLockFile = (dir) => LOCK_FILES.some((file) => existsSync(join(dir, file)));
|
|
31
|
+
var findWorkspaceRoot = () => {
|
|
32
|
+
let dir = process.cwd();
|
|
33
|
+
while (!hasLockFile(dir)) {
|
|
34
|
+
const parent = join(dir, "..");
|
|
35
|
+
if (parent === dir)
|
|
36
|
+
throw new Error("Could not locate workspace root");
|
|
37
|
+
dir = parent;
|
|
38
|
+
}
|
|
39
|
+
return dir;
|
|
40
|
+
};
|
|
41
|
+
var loadPulumiConfig = async (infraDir, stack) => {
|
|
42
|
+
try {
|
|
43
|
+
const stdout = execSync(`pulumi config --show-secrets --json --stack ${stack} --cwd ${infraDir}`, {
|
|
44
|
+
encoding: "utf-8",
|
|
45
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
46
|
+
});
|
|
47
|
+
const config = JSON.parse(stdout);
|
|
48
|
+
return Object.entries(config).filter(([key]) => key.includes(":")).filter(([, entry]) => entry.value !== undefined && entry.value !== null && entry.value !== "undefined").map(([fullKey, entry]) => {
|
|
49
|
+
const [, rawKey] = fullKey.split(":");
|
|
50
|
+
return `${rawKey}=${entry.value}`;
|
|
51
|
+
});
|
|
52
|
+
} catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var discoverServices = (servicesDir) => {
|
|
57
|
+
if (!existsSync(servicesDir))
|
|
58
|
+
return [];
|
|
59
|
+
const files = readdirSync(servicesDir).filter((file) => file.endsWith(".ts") && file !== "index.ts");
|
|
60
|
+
return files.map((file) => {
|
|
61
|
+
const content = readFileSync(join(servicesDir, file), "utf-8");
|
|
62
|
+
const portsApiMatch = content.match(/ports\(\)\.(?:http|https|grpc|primary)\((\d+)\)/) || content.match(/ports\(\)\.add\((\d+)/);
|
|
63
|
+
return {
|
|
64
|
+
name: extract.string(content, /name:\s*['"]([^'"]+)['"]/) ?? file.replace(".ts", ""),
|
|
65
|
+
primaryPort: portsApiMatch?.[1] ? Number.parseInt(portsApiMatch[1], 10) : undefined,
|
|
66
|
+
containerPort: extract.number(content, /containerPort:\s*(\w+)/),
|
|
67
|
+
httpPort: extract.number(content, /httpPort:\s*(\w+)/),
|
|
68
|
+
internalPorts: extract.numberArray(content, /internalPorts:\s*\[([^\]]+)\]/),
|
|
69
|
+
internalUrl: extract.string(content, /internalUrl:\s*[`'"]([^`'"]+)[`'"]/)
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
var getServicePort = (config) => {
|
|
74
|
+
if (config.primaryPort)
|
|
75
|
+
return config.primaryPort;
|
|
76
|
+
if (config.containerPort)
|
|
77
|
+
return config.containerPort;
|
|
78
|
+
if (config.httpPort)
|
|
79
|
+
return config.httpPort;
|
|
80
|
+
if (config.internalPorts?.[0])
|
|
81
|
+
return config.internalPorts[0];
|
|
82
|
+
return;
|
|
83
|
+
};
|
|
84
|
+
var getLocalUrl = (config) => {
|
|
85
|
+
const port = getServicePort(config);
|
|
86
|
+
if (!port)
|
|
87
|
+
return;
|
|
88
|
+
if (config.internalUrl) {
|
|
89
|
+
const protocolMatch = config.internalUrl.match(/^(\w+):\/\//);
|
|
90
|
+
if (protocolMatch) {
|
|
91
|
+
return `${protocolMatch[1]}://localhost:${port}`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return `http://localhost:${port}`;
|
|
95
|
+
};
|
|
96
|
+
var main = async () => {
|
|
97
|
+
const noPulumi = process.argv.includes("--no-pulumi");
|
|
98
|
+
const workspaceRootDir = findWorkspaceRoot();
|
|
99
|
+
const infraDir = join(workspaceRootDir, "infra");
|
|
100
|
+
const servicesDir = join(infraDir, "services");
|
|
101
|
+
const envLines = ["# Generated .env.local"];
|
|
102
|
+
if (!noPulumi && existsSync(join(infraDir, "Pulumi.yaml"))) {
|
|
103
|
+
const pulumiEnvs = await loadPulumiConfig(infraDir, "dev");
|
|
104
|
+
if (pulumiEnvs.length > 0) {
|
|
105
|
+
envLines.push("", "# Pulumi Secrets");
|
|
106
|
+
envLines.push(...pulumiEnvs);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const serviceConfigs = discoverServices(servicesDir);
|
|
110
|
+
if (serviceConfigs.length > 0) {
|
|
111
|
+
envLines.push("", "# Service URLs");
|
|
112
|
+
for (const config of serviceConfigs) {
|
|
113
|
+
const url = getLocalUrl(config);
|
|
114
|
+
if (url) {
|
|
115
|
+
envLines.push(`${toEnvKey(config.name)}_URL=${url}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
envLines.push("", "# Service Ports");
|
|
119
|
+
for (const config of serviceConfigs) {
|
|
120
|
+
const port = getServicePort(config);
|
|
121
|
+
if (port) {
|
|
122
|
+
envLines.push(`${toEnvKey(config.name)}_PORT=${port}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
console.log(`✅ Discovered ${serviceConfigs.length} services`);
|
|
126
|
+
}
|
|
127
|
+
writeFileSync(join(workspaceRootDir, ".env.local"), `${envLines.join(`
|
|
128
|
+
`)}
|
|
129
|
+
`);
|
|
130
|
+
console.log(`✅ .env.local generated at ${workspaceRootDir}`);
|
|
131
|
+
};
|
|
132
|
+
main().catch((err) => {
|
|
133
|
+
console.error("❌", err.message);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
});
|
package/bin/generate-env.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Generate environment variables for local development.
|
|
4
4
|
*
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Usage: generate-env [--no-pulumi]
|
|
12
12
|
*/
|
|
13
|
+
import { execSync } from 'node:child_process'
|
|
13
14
|
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
14
15
|
import { join } from 'node:path'
|
|
15
16
|
|
|
@@ -58,9 +59,13 @@ const extract = {
|
|
|
58
59
|
},
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
const LOCK_FILES = ['bun.lock', 'bun.lockb', 'package-lock.json', 'pnpm-lock.yaml', 'yarn.lock']
|
|
63
|
+
|
|
64
|
+
const hasLockFile = (dir: string): boolean => LOCK_FILES.some((file) => existsSync(join(dir, file)))
|
|
65
|
+
|
|
61
66
|
const findWorkspaceRoot = (): string => {
|
|
62
67
|
let dir = process.cwd()
|
|
63
|
-
while (!
|
|
68
|
+
while (!hasLockFile(dir)) {
|
|
64
69
|
const parent = join(dir, '..')
|
|
65
70
|
if (parent === dir) throw new Error('Could not locate workspace root')
|
|
66
71
|
dir = parent
|
|
@@ -70,10 +75,12 @@ const findWorkspaceRoot = (): string => {
|
|
|
70
75
|
|
|
71
76
|
const loadPulumiConfig = async (infraDir: string, stack: string): Promise<string[]> => {
|
|
72
77
|
try {
|
|
73
|
-
const
|
|
74
|
-
|
|
78
|
+
const stdout = execSync(`pulumi config --show-secrets --json --stack ${stack} --cwd ${infraDir}`, {
|
|
79
|
+
encoding: 'utf-8',
|
|
80
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
81
|
+
})
|
|
75
82
|
|
|
76
|
-
const config = JSON.parse(
|
|
83
|
+
const config = JSON.parse(stdout) as Record<string, PulumiConfigEntry>
|
|
77
84
|
|
|
78
85
|
return Object.entries(config)
|
|
79
86
|
.filter(([key]) => key.includes(':'))
|
|
@@ -6,4 +6,4 @@
|
|
|
6
6
|
* Known service names in the platform.
|
|
7
7
|
* This type is auto-generated from infra/services/*.ts
|
|
8
8
|
*/
|
|
9
|
-
export type ServiceName = '
|
|
9
|
+
export type ServiceName = 'orders' | 'notifications' | 'storefront' | 'api-gateway';
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crossdelta/infrastructure",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
8
8
|
},
|
|
9
9
|
"bin": {
|
|
10
|
-
"generate-env": "./bin/generate-env.
|
|
10
|
+
"generate-env": "./bin/generate-env.mjs"
|
|
11
11
|
},
|
|
12
12
|
"main": "dist/index.cjs",
|
|
13
13
|
"types": "dist/index.d.ts",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"typescript": "^5.9.3"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
|
-
"build": "bun build --target=node --format=esm --outfile dist/index.js lib/index.ts && bun build --target=node --format=cjs --outfile dist/index.cjs lib/index.ts && bun build --target=node --format=esm --outfile dist/env.js lib/env.ts && bun build --target=node --format=cjs --outfile dist/env.cjs lib/env.ts && tsc -p tsconfig.build.json",
|
|
47
|
+
"build": "bun build --target=node --format=esm --outfile dist/index.js lib/index.ts && bun build --target=node --format=cjs --outfile dist/index.cjs lib/index.ts && bun build --target=node --format=esm --outfile dist/env.js lib/env.ts && bun build --target=node --format=cjs --outfile dist/env.cjs lib/env.ts && bun build --target=node --format=esm --outfile bin/generate-env.mjs bin/generate-env.ts && tsc -p tsconfig.build.json",
|
|
48
48
|
"prepublishOnly": "bun run build",
|
|
49
49
|
"generate-env": "bun run cli/commands/generate-env.ts",
|
|
50
50
|
"lint": "biome lint --fix",
|