everything-dev 1.35.2 → 1.35.4
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/dist/cli/infra.cjs +45 -8
- package/dist/cli/infra.cjs.map +1 -1
- package/dist/cli/infra.mjs +45 -8
- package/dist/cli/infra.mjs.map +1 -1
- package/dist/cli/init.cjs +2 -1
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.mjs +2 -1
- package/dist/cli/init.mjs.map +1 -1
- package/dist/contract.d.cts +18 -18
- package/dist/contract.d.mts +18 -18
- package/dist/plugin.cjs +9 -11
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +10 -10
- package/dist/plugin.d.cts.map +1 -1
- package/dist/plugin.d.mts +10 -10
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +9 -11
- package/dist/plugin.mjs.map +1 -1
- package/dist/types.d.cts +2 -2
- package/dist/types.d.mts +2 -2
- package/package.json +1 -1
- package/skills/dev-workflow/SKILL.md +3 -3
- package/skills/publish-sync/SKILL.md +1 -1
- package/skills/super-app/SKILL.md +2 -2
package/dist/cli/infra.cjs
CHANGED
|
@@ -45,31 +45,62 @@ function getSecretGroups(runtimeConfig) {
|
|
|
45
45
|
}
|
|
46
46
|
return groups;
|
|
47
47
|
}
|
|
48
|
-
function buildGeneratedInfraSpec(runtimeConfig) {
|
|
48
|
+
function buildGeneratedInfraSpec(runtimeConfig, configDir) {
|
|
49
49
|
const groups = getSecretGroups(runtimeConfig);
|
|
50
|
+
const originMap = configDir ? buildOriginMap(configDir, runtimeConfig) : /* @__PURE__ */ new Map();
|
|
50
51
|
return {
|
|
51
52
|
groups,
|
|
52
|
-
databases: buildDatabaseConfigs(groups.flatMap((group) => group.secrets))
|
|
53
|
+
databases: buildDatabaseConfigs(groups.flatMap((group) => group.secrets), originMap)
|
|
53
54
|
};
|
|
54
55
|
}
|
|
55
56
|
function normalizeDatabaseSlug(secret) {
|
|
56
57
|
return secret.replace(/_DATABASE_URL$/, "").toLowerCase();
|
|
57
58
|
}
|
|
58
|
-
function
|
|
59
|
+
function buildOriginMap(configDir, runtimeConfig) {
|
|
60
|
+
const configPath = (0, node_path.join)(configDir, "bos.config.json");
|
|
61
|
+
const originMap = /* @__PURE__ */ new Map();
|
|
62
|
+
const account = runtimeConfig.account;
|
|
63
|
+
const resolveOrigin = (extendsRef) => {
|
|
64
|
+
if (typeof extendsRef === "string") return extendsRef.match(/^bos:\/\/([^/]+)\//)?.[1] ?? null;
|
|
65
|
+
return null;
|
|
66
|
+
};
|
|
67
|
+
const rawConfig = (0, node_fs.existsSync)(configPath) ? JSON.parse((0, node_fs.readFileSync)(configPath, "utf-8")) : null;
|
|
68
|
+
const rawPlugins = rawConfig?.plugins;
|
|
69
|
+
for (const secret of runtimeConfig.api.secrets ?? []) if (!originMap.has(secret)) originMap.set(secret, account);
|
|
70
|
+
const authExtends = ((rawConfig?.app)?.auth)?.extends;
|
|
71
|
+
const authOrigin = resolveOrigin(authExtends) ?? account;
|
|
72
|
+
for (const secret of runtimeConfig.auth?.secrets ?? []) if (!originMap.has(secret)) originMap.set(secret, authOrigin);
|
|
73
|
+
for (const [pluginKey, pluginEntry] of Object.entries(runtimeConfig.plugins ?? {})) {
|
|
74
|
+
const rawPlugin = rawPlugins?.[pluginKey];
|
|
75
|
+
let pluginOrigin;
|
|
76
|
+
if (typeof rawPlugin === "string") pluginOrigin = resolveOrigin(rawPlugin) ?? account;
|
|
77
|
+
else if (rawPlugin && typeof rawPlugin === "object") pluginOrigin = resolveOrigin(rawPlugin.extends) ?? account;
|
|
78
|
+
else pluginOrigin = account;
|
|
79
|
+
for (const secret of pluginEntry.secrets ?? []) if (!originMap.has(secret)) originMap.set(secret, pluginOrigin);
|
|
80
|
+
}
|
|
81
|
+
for (const secret of runtimeConfig.host.secrets ?? []) if (!originMap.has(secret)) originMap.set(secret, account);
|
|
82
|
+
return originMap;
|
|
83
|
+
}
|
|
84
|
+
function buildDatabaseConfigs(secrets, originMap) {
|
|
59
85
|
return [
|
|
60
86
|
API_DATABASE_SECRET,
|
|
61
87
|
AUTH_DATABASE_SECRET,
|
|
62
88
|
...uniqueSecrets(secrets.filter((secret) => secret.endsWith("_DATABASE_URL"))).filter((secret) => secret !== API_DATABASE_SECRET && secret !== AUTH_DATABASE_SECRET).sort((a, b) => a.localeCompare(b))
|
|
63
89
|
].map((secret, index) => {
|
|
64
90
|
const slug = normalizeDatabaseSlug(secret);
|
|
91
|
+
const fromKey = originMap.get(secret) ?? "";
|
|
65
92
|
const port = secret === API_DATABASE_SECRET ? 5432 : secret === AUTH_DATABASE_SECRET ? 5433 : BASE_DATABASE_PORT + index - 2;
|
|
93
|
+
const volumeName = fromKey ? `${fromKey.replace(/\./g, "_")}_postgres_${slug}_data` : `postgres_${slug}_data`;
|
|
94
|
+
const containerName = fromKey ? `${fromKey}-postgres-${slug}` : `postgres-${slug}`;
|
|
66
95
|
return {
|
|
67
96
|
secret,
|
|
68
97
|
slug,
|
|
98
|
+
fromKey,
|
|
69
99
|
port,
|
|
70
100
|
serviceName: `postgres-${slug.replace(/_/g, "-")}`,
|
|
101
|
+
containerName,
|
|
71
102
|
databaseName: `${slug}_db`,
|
|
72
|
-
volumeName
|
|
103
|
+
volumeName,
|
|
73
104
|
url: `postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${port}/${slug}_db`
|
|
74
105
|
};
|
|
75
106
|
});
|
|
@@ -93,8 +124,10 @@ function renderEnvFile(groups, databases, options) {
|
|
|
93
124
|
}
|
|
94
125
|
return `${lines.join("\n")}\n`;
|
|
95
126
|
}
|
|
96
|
-
function renderDockerCompose(databases) {
|
|
127
|
+
function renderDockerCompose(databases, projectName) {
|
|
97
128
|
const lines = [
|
|
129
|
+
`name: ${projectName}`,
|
|
130
|
+
"",
|
|
98
131
|
"x-pg-common: &pg-common",
|
|
99
132
|
" image: postgres:17-alpine",
|
|
100
133
|
" environment: &pg-env",
|
|
@@ -111,6 +144,7 @@ function renderDockerCompose(databases) {
|
|
|
111
144
|
for (const database of databases) {
|
|
112
145
|
lines.push(` ${database.serviceName}:`);
|
|
113
146
|
lines.push(" <<: *pg-common");
|
|
147
|
+
lines.push(` container_name: ${database.containerName}`);
|
|
114
148
|
lines.push(" environment:");
|
|
115
149
|
lines.push(" <<: *pg-env");
|
|
116
150
|
lines.push(` POSTGRES_DB: ${database.databaseName}`);
|
|
@@ -121,7 +155,10 @@ function renderDockerCompose(databases) {
|
|
|
121
155
|
lines.push("");
|
|
122
156
|
}
|
|
123
157
|
lines.push("volumes:");
|
|
124
|
-
for (const database of databases)
|
|
158
|
+
for (const database of databases) {
|
|
159
|
+
lines.push(` ${database.volumeName}:`);
|
|
160
|
+
lines.push(` name: ${database.volumeName}`);
|
|
161
|
+
}
|
|
125
162
|
return `${lines.join("\n")}\n`;
|
|
126
163
|
}
|
|
127
164
|
function syncTextFile(filePath, nextContent) {
|
|
@@ -133,10 +170,10 @@ function writeGeneratedInfra(configDir, runtimeConfig) {
|
|
|
133
170
|
return syncGeneratedInfra(configDir, runtimeConfig).secrets;
|
|
134
171
|
}
|
|
135
172
|
function syncGeneratedInfra(configDir, runtimeConfig) {
|
|
136
|
-
const spec = buildGeneratedInfraSpec(runtimeConfig);
|
|
173
|
+
const spec = buildGeneratedInfraSpec(runtimeConfig, configDir);
|
|
137
174
|
const secrets = spec.groups.flatMap((group) => group.secrets);
|
|
138
175
|
const newEnvContent = renderEnvFile(spec.groups, spec.databases, { forExample: true });
|
|
139
|
-
const newDockerContent = renderDockerCompose(spec.databases);
|
|
176
|
+
const newDockerContent = renderDockerCompose(spec.databases, runtimeConfig.account);
|
|
140
177
|
const envExamplePath = (0, node_path.join)(configDir, ".env.example");
|
|
141
178
|
const dockerComposePath = (0, node_path.join)(configDir, "docker-compose.yml");
|
|
142
179
|
return {
|
package/dist/cli/infra.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"infra.cjs","names":[],"sources":["../../src/cli/infra.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport * as p from \"@clack/prompts\";\nimport { config as loadDotenv } from \"dotenv\";\nimport type { RuntimeConfig } from \"../types\";\n\nconst POSTGRES_USER = \"everythingdev\";\nconst POSTGRES_PASSWORD = \"everythingdev\";\nconst API_DATABASE_SECRET = \"API_DATABASE_URL\";\nconst AUTH_DATABASE_SECRET = \"AUTH_DATABASE_URL\";\nconst HOST_SECRET = \"CORS_ORIGIN\";\nconst BASE_DATABASE_PORT = 5434;\n\ninterface DatabaseSecretConfig {\n secret: string;\n slug: string;\n port: number;\n serviceName: string;\n databaseName: string;\n volumeName: string;\n url: string;\n}\n\ninterface SecretGroup {\n section: string;\n secrets: string[];\n}\n\ninterface GeneratedInfraSpec {\n groups: SecretGroup[];\n databases: DatabaseSecretConfig[];\n}\n\ninterface SyncGeneratedInfraResult {\n secrets: string[];\n envExampleChanged: boolean;\n dockerComposeChanged: boolean;\n}\n\nfunction uniqueSecrets(values: Array<string | undefined>): string[] {\n const secrets: string[] = [];\n const seen = new Set<string>();\n\n for (const value of values) {\n if (!value || seen.has(value)) continue;\n seen.add(value);\n secrets.push(value);\n }\n\n return secrets;\n}\n\nfunction getSecretGroups(runtimeConfig: RuntimeConfig): SecretGroup[] {\n const groups: SecretGroup[] = [];\n const seen = new Set<string>();\n\n const addGroup = (section: string, secrets: string[]) => {\n const filtered = secrets.filter((s) => {\n if (seen.has(s)) return false;\n seen.add(s);\n return true;\n });\n if (filtered.length > 0) {\n groups.push({ section, secrets: filtered });\n }\n };\n\n addGroup(\"app.host\", uniqueSecrets([...(runtimeConfig.host.secrets ?? []), HOST_SECRET]));\n\n addGroup(\"app.api\", uniqueSecrets(runtimeConfig.api.secrets ?? []));\n\n if (runtimeConfig.auth) {\n addGroup(\"app.auth\", uniqueSecrets(runtimeConfig.auth.secrets ?? []));\n }\n\n if (runtimeConfig.plugins) {\n for (const [pluginKey, plugin] of Object.entries(runtimeConfig.plugins)) {\n if (plugin.secrets && plugin.secrets.length > 0) {\n addGroup(`plugins.${pluginKey}`, plugin.secrets);\n }\n }\n }\n\n return groups;\n}\n\nfunction buildGeneratedInfraSpec(runtimeConfig: RuntimeConfig): GeneratedInfraSpec {\n const groups = getSecretGroups(runtimeConfig);\n const databases = buildDatabaseConfigs(groups.flatMap((group) => group.secrets));\n return { groups, databases };\n}\n\nfunction normalizeDatabaseSlug(secret: string): string {\n return secret.replace(/_DATABASE_URL$/, \"\").toLowerCase();\n}\n\nfunction buildDatabaseConfigs(secrets: string[]): DatabaseSecretConfig[] {\n const databaseSecrets = uniqueSecrets(\n secrets.filter((secret) => secret.endsWith(\"_DATABASE_URL\")),\n );\n\n const additionalSecrets = databaseSecrets\n .filter((secret) => secret !== API_DATABASE_SECRET && secret !== AUTH_DATABASE_SECRET)\n .sort((a, b) => a.localeCompare(b));\n\n const orderedSecrets = [API_DATABASE_SECRET, AUTH_DATABASE_SECRET, ...additionalSecrets];\n\n return orderedSecrets.map((secret, index) => {\n const slug = normalizeDatabaseSlug(secret);\n const port =\n secret === API_DATABASE_SECRET\n ? 5432\n : secret === AUTH_DATABASE_SECRET\n ? 5433\n : BASE_DATABASE_PORT + index - 2;\n\n return {\n secret,\n slug,\n port,\n serviceName: `postgres-${slug.replace(/_/g, \"-\")}`,\n databaseName: `${slug}_db`,\n volumeName: `postgres_${slug}_data`,\n url: `postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${port}/${slug}_db`,\n };\n });\n}\n\nfunction defaultSecretValue(\n secret: string,\n databases: Map<string, DatabaseSecretConfig>,\n options: { forExample: boolean },\n): string {\n if (secret === \"BETTER_AUTH_SECRET\") {\n return options.forExample ? \"\" : randomBytes(32).toString(\"base64url\");\n }\n\n if (secret === \"CORS_ORIGIN\") {\n return \"http://localhost:3000\";\n }\n\n return databases.get(secret)?.url ?? \"\";\n}\n\nfunction renderEnvFile(\n groups: SecretGroup[],\n databases: DatabaseSecretConfig[],\n options: { forExample: boolean },\n): string {\n const databaseMap = new Map(databases.map((entry) => [entry.secret, entry]));\n const lines: string[] = [\n \"# Generated from configured bos secrets\",\n \"# Update values as needed for your local environment\",\n \"\",\n ];\n\n for (const group of groups) {\n lines.push(`# ${group.section}`);\n for (const secret of group.secrets) {\n lines.push(`${secret}=${defaultSecretValue(secret, databaseMap, options)}`);\n }\n lines.push(\"\");\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction renderDockerCompose(databases: DatabaseSecretConfig[]): string {\n const lines = [\n \"x-pg-common: &pg-common\",\n \" image: postgres:17-alpine\",\n \" environment: &pg-env\",\n ` POSTGRES_USER: ${POSTGRES_USER}`,\n ` POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}`,\n \" healthcheck:\",\n ' test: [\"CMD-SHELL\", \"pg_isready -U everythingdev\"]',\n \" interval: 3s\",\n \" timeout: 3s\",\n \" retries: 5\",\n \"\",\n \"services:\",\n ];\n\n for (const database of databases) {\n lines.push(` ${database.serviceName}:`);\n lines.push(\" <<: *pg-common\");\n lines.push(\" environment:\");\n lines.push(\" <<: *pg-env\");\n lines.push(` POSTGRES_DB: ${database.databaseName}`);\n lines.push(\" ports:\");\n lines.push(` - \"${database.port}:5432\"`);\n lines.push(\" volumes:\");\n lines.push(` - ${database.volumeName}:/var/lib/postgresql/data`);\n lines.push(\"\");\n }\n\n lines.push(\"volumes:\");\n for (const database of databases) {\n lines.push(` ${database.volumeName}:`);\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction syncTextFile(filePath: string, nextContent: string): boolean {\n if (existsSync(filePath) && readFileSync(filePath, \"utf-8\") === nextContent) {\n return false;\n }\n\n writeFileSync(filePath, nextContent);\n return true;\n}\n\nexport function writeGeneratedInfra(configDir: string, runtimeConfig: RuntimeConfig): string[] {\n return syncGeneratedInfra(configDir, runtimeConfig).secrets;\n}\n\nexport function syncGeneratedInfra(\n configDir: string,\n runtimeConfig: RuntimeConfig,\n): SyncGeneratedInfraResult {\n const spec = buildGeneratedInfraSpec(runtimeConfig);\n const secrets = spec.groups.flatMap((group) => group.secrets);\n const newEnvContent = renderEnvFile(spec.groups, spec.databases, { forExample: true });\n const newDockerContent = renderDockerCompose(spec.databases);\n\n const envExamplePath = join(configDir, \".env.example\");\n const dockerComposePath = join(configDir, \"docker-compose.yml\");\n\n return {\n secrets,\n envExampleChanged: syncTextFile(envExamplePath, newEnvContent),\n dockerComposeChanged: syncTextFile(dockerComposePath, newDockerContent),\n };\n}\n\nexport function ensureEnvFile(configDir: string): void {\n const envPath = join(configDir, \".env\");\n const examplePath = join(configDir, \".env.example\");\n\n if (existsSync(envPath) || !existsSync(examplePath)) return;\n\n const content = readFileSync(examplePath, \"utf-8\");\n const lines = content.split(\"\\n\");\n const secret = randomBytes(32).toString(\"base64url\");\n const updated = lines\n .map((line) => {\n if (/^BETTER_AUTH_SECRET=/.test(line)) {\n return `BETTER_AUTH_SECRET=${secret}`;\n }\n return line;\n })\n .join(\"\\n\");\n\n writeFileSync(envPath, updated);\n p.log.info(\"Created .env from generated .env.example with generated BETTER_AUTH_SECRET\");\n}\n\nexport function loadProjectEnv(configDir: string): void {\n const envPath = join(configDir, \".env\");\n if (!existsSync(envPath)) return;\n\n loadDotenv({ path: envPath, processEnv: process.env, quiet: true });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"infra.cjs","names":[],"sources":["../../src/cli/infra.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport * as p from \"@clack/prompts\";\nimport { config as loadDotenv } from \"dotenv\";\nimport type { RuntimeConfig } from \"../types\";\n\nconst POSTGRES_USER = \"everythingdev\";\nconst POSTGRES_PASSWORD = \"everythingdev\";\nconst API_DATABASE_SECRET = \"API_DATABASE_URL\";\nconst AUTH_DATABASE_SECRET = \"AUTH_DATABASE_URL\";\nconst HOST_SECRET = \"CORS_ORIGIN\";\nconst BASE_DATABASE_PORT = 5434;\n\ninterface DatabaseSecretConfig {\n secret: string;\n slug: string;\n fromKey: string;\n port: number;\n serviceName: string;\n containerName: string;\n databaseName: string;\n volumeName: string;\n url: string;\n}\n\ninterface SecretGroup {\n section: string;\n secrets: string[];\n}\n\ninterface GeneratedInfraSpec {\n groups: SecretGroup[];\n databases: DatabaseSecretConfig[];\n}\n\ninterface SyncGeneratedInfraResult {\n secrets: string[];\n envExampleChanged: boolean;\n dockerComposeChanged: boolean;\n}\n\nfunction uniqueSecrets(values: Array<string | undefined>): string[] {\n const secrets: string[] = [];\n const seen = new Set<string>();\n\n for (const value of values) {\n if (!value || seen.has(value)) continue;\n seen.add(value);\n secrets.push(value);\n }\n\n return secrets;\n}\n\nfunction getSecretGroups(runtimeConfig: RuntimeConfig): SecretGroup[] {\n const groups: SecretGroup[] = [];\n const seen = new Set<string>();\n\n const addGroup = (section: string, secrets: string[]) => {\n const filtered = secrets.filter((s) => {\n if (seen.has(s)) return false;\n seen.add(s);\n return true;\n });\n if (filtered.length > 0) {\n groups.push({ section, secrets: filtered });\n }\n };\n\n addGroup(\"app.host\", uniqueSecrets([...(runtimeConfig.host.secrets ?? []), HOST_SECRET]));\n\n addGroup(\"app.api\", uniqueSecrets(runtimeConfig.api.secrets ?? []));\n\n if (runtimeConfig.auth) {\n addGroup(\"app.auth\", uniqueSecrets(runtimeConfig.auth.secrets ?? []));\n }\n\n if (runtimeConfig.plugins) {\n for (const [pluginKey, plugin] of Object.entries(runtimeConfig.plugins)) {\n if (plugin.secrets && plugin.secrets.length > 0) {\n addGroup(`plugins.${pluginKey}`, plugin.secrets);\n }\n }\n }\n\n return groups;\n}\n\nfunction buildGeneratedInfraSpec(\n runtimeConfig: RuntimeConfig,\n configDir?: string,\n): GeneratedInfraSpec {\n const groups = getSecretGroups(runtimeConfig);\n const originMap = configDir ? buildOriginMap(configDir, runtimeConfig) : new Map();\n const databases = buildDatabaseConfigs(\n groups.flatMap((group) => group.secrets),\n originMap,\n );\n return { groups, databases };\n}\n\nfunction normalizeDatabaseSlug(secret: string): string {\n return secret.replace(/_DATABASE_URL$/, \"\").toLowerCase();\n}\n\nfunction buildOriginMap(configDir: string, runtimeConfig: RuntimeConfig): Map<string, string> {\n const configPath = join(configDir, \"bos.config.json\");\n\n const originMap = new Map<string, string>();\n const account = runtimeConfig.account;\n\n const resolveOrigin = (extendsRef: unknown): string | null => {\n if (typeof extendsRef === \"string\") {\n const match = extendsRef.match(/^bos:\\/\\/([^/]+)\\//);\n return match?.[1] ?? null;\n }\n return null;\n };\n\n const rawConfig = existsSync(configPath)\n ? (JSON.parse(readFileSync(configPath, \"utf-8\")) as Record<string, unknown>)\n : null;\n const rawPlugins = rawConfig?.plugins as Record<string, unknown> | undefined;\n\n for (const secret of runtimeConfig.api.secrets ?? []) {\n if (!originMap.has(secret)) originMap.set(secret, account);\n }\n\n const rawApp = rawConfig?.app as Record<string, unknown> | undefined;\n const authExtends = (rawApp?.auth as Record<string, unknown> | undefined)?.extends;\n const authOrigin = resolveOrigin(authExtends) ?? account;\n for (const secret of runtimeConfig.auth?.secrets ?? []) {\n if (!originMap.has(secret)) originMap.set(secret, authOrigin);\n }\n\n for (const [pluginKey, pluginEntry] of Object.entries(runtimeConfig.plugins ?? {})) {\n const rawPlugin = rawPlugins?.[pluginKey];\n let pluginOrigin: string;\n if (typeof rawPlugin === \"string\") {\n pluginOrigin = resolveOrigin(rawPlugin) ?? account;\n } else if (rawPlugin && typeof rawPlugin === \"object\") {\n pluginOrigin = resolveOrigin((rawPlugin as Record<string, unknown>).extends) ?? account;\n } else {\n pluginOrigin = account;\n }\n for (const secret of pluginEntry.secrets ?? []) {\n if (!originMap.has(secret)) originMap.set(secret, pluginOrigin);\n }\n }\n\n for (const secret of runtimeConfig.host.secrets ?? []) {\n if (!originMap.has(secret)) originMap.set(secret, account);\n }\n\n return originMap;\n}\n\nfunction buildDatabaseConfigs(\n secrets: string[],\n originMap: Map<string, string>,\n): DatabaseSecretConfig[] {\n const databaseSecrets = uniqueSecrets(\n secrets.filter((secret) => secret.endsWith(\"_DATABASE_URL\")),\n );\n\n const additionalSecrets = databaseSecrets\n .filter((secret) => secret !== API_DATABASE_SECRET && secret !== AUTH_DATABASE_SECRET)\n .sort((a, b) => a.localeCompare(b));\n\n const orderedSecrets = [API_DATABASE_SECRET, AUTH_DATABASE_SECRET, ...additionalSecrets];\n\n return orderedSecrets.map((secret, index) => {\n const slug = normalizeDatabaseSlug(secret);\n const fromKey = originMap.get(secret) ?? \"\";\n const port =\n secret === API_DATABASE_SECRET\n ? 5432\n : secret === AUTH_DATABASE_SECRET\n ? 5433\n : BASE_DATABASE_PORT + index - 2;\n\n const volumeName = fromKey\n ? `${fromKey.replace(/\\./g, \"_\")}_postgres_${slug}_data`\n : `postgres_${slug}_data`;\n\n const containerName = fromKey ? `${fromKey}-postgres-${slug}` : `postgres-${slug}`;\n\n return {\n secret,\n slug,\n fromKey,\n port,\n serviceName: `postgres-${slug.replace(/_/g, \"-\")}`,\n containerName,\n databaseName: `${slug}_db`,\n volumeName,\n url: `postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${port}/${slug}_db`,\n };\n });\n}\n\nfunction defaultSecretValue(\n secret: string,\n databases: Map<string, DatabaseSecretConfig>,\n options: { forExample: boolean },\n): string {\n if (secret === \"BETTER_AUTH_SECRET\") {\n return options.forExample ? \"\" : randomBytes(32).toString(\"base64url\");\n }\n\n if (secret === \"CORS_ORIGIN\") {\n return \"http://localhost:3000\";\n }\n\n return databases.get(secret)?.url ?? \"\";\n}\n\nfunction renderEnvFile(\n groups: SecretGroup[],\n databases: DatabaseSecretConfig[],\n options: { forExample: boolean },\n): string {\n const databaseMap = new Map(databases.map((entry) => [entry.secret, entry]));\n const lines: string[] = [\n \"# Generated from configured bos secrets\",\n \"# Update values as needed for your local environment\",\n \"\",\n ];\n\n for (const group of groups) {\n lines.push(`# ${group.section}`);\n for (const secret of group.secrets) {\n lines.push(`${secret}=${defaultSecretValue(secret, databaseMap, options)}`);\n }\n lines.push(\"\");\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction renderDockerCompose(databases: DatabaseSecretConfig[], projectName: string): string {\n const lines = [\n `name: ${projectName}`,\n \"\",\n \"x-pg-common: &pg-common\",\n \" image: postgres:17-alpine\",\n \" environment: &pg-env\",\n ` POSTGRES_USER: ${POSTGRES_USER}`,\n ` POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}`,\n \" healthcheck:\",\n ' test: [\"CMD-SHELL\", \"pg_isready -U everythingdev\"]',\n \" interval: 3s\",\n \" timeout: 3s\",\n \" retries: 5\",\n \"\",\n \"services:\",\n ];\n\n for (const database of databases) {\n lines.push(` ${database.serviceName}:`);\n lines.push(\" <<: *pg-common\");\n lines.push(` container_name: ${database.containerName}`);\n lines.push(\" environment:\");\n lines.push(\" <<: *pg-env\");\n lines.push(` POSTGRES_DB: ${database.databaseName}`);\n lines.push(\" ports:\");\n lines.push(` - \"${database.port}:5432\"`);\n lines.push(\" volumes:\");\n lines.push(` - ${database.volumeName}:/var/lib/postgresql/data`);\n lines.push(\"\");\n }\n\n lines.push(\"volumes:\");\n for (const database of databases) {\n lines.push(` ${database.volumeName}:`);\n lines.push(` name: ${database.volumeName}`);\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction syncTextFile(filePath: string, nextContent: string): boolean {\n if (existsSync(filePath) && readFileSync(filePath, \"utf-8\") === nextContent) {\n return false;\n }\n\n writeFileSync(filePath, nextContent);\n return true;\n}\n\nexport function writeGeneratedInfra(configDir: string, runtimeConfig: RuntimeConfig): string[] {\n return syncGeneratedInfra(configDir, runtimeConfig).secrets;\n}\n\nexport function syncGeneratedInfra(\n configDir: string,\n runtimeConfig: RuntimeConfig,\n): SyncGeneratedInfraResult {\n const spec = buildGeneratedInfraSpec(runtimeConfig, configDir);\n const secrets = spec.groups.flatMap((group) => group.secrets);\n const newEnvContent = renderEnvFile(spec.groups, spec.databases, { forExample: true });\n const newDockerContent = renderDockerCompose(spec.databases, runtimeConfig.account);\n\n const envExamplePath = join(configDir, \".env.example\");\n const dockerComposePath = join(configDir, \"docker-compose.yml\");\n\n return {\n secrets,\n envExampleChanged: syncTextFile(envExamplePath, newEnvContent),\n dockerComposeChanged: syncTextFile(dockerComposePath, newDockerContent),\n };\n}\n\nexport function ensureEnvFile(configDir: string): void {\n const envPath = join(configDir, \".env\");\n const examplePath = join(configDir, \".env.example\");\n\n if (existsSync(envPath) || !existsSync(examplePath)) return;\n\n const content = readFileSync(examplePath, \"utf-8\");\n const lines = content.split(\"\\n\");\n const secret = randomBytes(32).toString(\"base64url\");\n const updated = lines\n .map((line) => {\n if (/^BETTER_AUTH_SECRET=/.test(line)) {\n return `BETTER_AUTH_SECRET=${secret}`;\n }\n return line;\n })\n .join(\"\\n\");\n\n writeFileSync(envPath, updated);\n p.log.info(\"Created .env from generated .env.example with generated BETTER_AUTH_SECRET\");\n}\n\nexport function loadProjectEnv(configDir: string): void {\n const envPath = join(configDir, \".env\");\n if (!existsSync(envPath)) return;\n\n loadDotenv({ path: envPath, processEnv: process.env, quiet: true });\n}\n"],"mappings":"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"}
|
package/dist/cli/infra.mjs
CHANGED
|
@@ -43,31 +43,62 @@ function getSecretGroups(runtimeConfig) {
|
|
|
43
43
|
}
|
|
44
44
|
return groups;
|
|
45
45
|
}
|
|
46
|
-
function buildGeneratedInfraSpec(runtimeConfig) {
|
|
46
|
+
function buildGeneratedInfraSpec(runtimeConfig, configDir) {
|
|
47
47
|
const groups = getSecretGroups(runtimeConfig);
|
|
48
|
+
const originMap = configDir ? buildOriginMap(configDir, runtimeConfig) : /* @__PURE__ */ new Map();
|
|
48
49
|
return {
|
|
49
50
|
groups,
|
|
50
|
-
databases: buildDatabaseConfigs(groups.flatMap((group) => group.secrets))
|
|
51
|
+
databases: buildDatabaseConfigs(groups.flatMap((group) => group.secrets), originMap)
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
54
|
function normalizeDatabaseSlug(secret) {
|
|
54
55
|
return secret.replace(/_DATABASE_URL$/, "").toLowerCase();
|
|
55
56
|
}
|
|
56
|
-
function
|
|
57
|
+
function buildOriginMap(configDir, runtimeConfig) {
|
|
58
|
+
const configPath = join(configDir, "bos.config.json");
|
|
59
|
+
const originMap = /* @__PURE__ */ new Map();
|
|
60
|
+
const account = runtimeConfig.account;
|
|
61
|
+
const resolveOrigin = (extendsRef) => {
|
|
62
|
+
if (typeof extendsRef === "string") return extendsRef.match(/^bos:\/\/([^/]+)\//)?.[1] ?? null;
|
|
63
|
+
return null;
|
|
64
|
+
};
|
|
65
|
+
const rawConfig = existsSync(configPath) ? JSON.parse(readFileSync(configPath, "utf-8")) : null;
|
|
66
|
+
const rawPlugins = rawConfig?.plugins;
|
|
67
|
+
for (const secret of runtimeConfig.api.secrets ?? []) if (!originMap.has(secret)) originMap.set(secret, account);
|
|
68
|
+
const authExtends = ((rawConfig?.app)?.auth)?.extends;
|
|
69
|
+
const authOrigin = resolveOrigin(authExtends) ?? account;
|
|
70
|
+
for (const secret of runtimeConfig.auth?.secrets ?? []) if (!originMap.has(secret)) originMap.set(secret, authOrigin);
|
|
71
|
+
for (const [pluginKey, pluginEntry] of Object.entries(runtimeConfig.plugins ?? {})) {
|
|
72
|
+
const rawPlugin = rawPlugins?.[pluginKey];
|
|
73
|
+
let pluginOrigin;
|
|
74
|
+
if (typeof rawPlugin === "string") pluginOrigin = resolveOrigin(rawPlugin) ?? account;
|
|
75
|
+
else if (rawPlugin && typeof rawPlugin === "object") pluginOrigin = resolveOrigin(rawPlugin.extends) ?? account;
|
|
76
|
+
else pluginOrigin = account;
|
|
77
|
+
for (const secret of pluginEntry.secrets ?? []) if (!originMap.has(secret)) originMap.set(secret, pluginOrigin);
|
|
78
|
+
}
|
|
79
|
+
for (const secret of runtimeConfig.host.secrets ?? []) if (!originMap.has(secret)) originMap.set(secret, account);
|
|
80
|
+
return originMap;
|
|
81
|
+
}
|
|
82
|
+
function buildDatabaseConfigs(secrets, originMap) {
|
|
57
83
|
return [
|
|
58
84
|
API_DATABASE_SECRET,
|
|
59
85
|
AUTH_DATABASE_SECRET,
|
|
60
86
|
...uniqueSecrets(secrets.filter((secret) => secret.endsWith("_DATABASE_URL"))).filter((secret) => secret !== API_DATABASE_SECRET && secret !== AUTH_DATABASE_SECRET).sort((a, b) => a.localeCompare(b))
|
|
61
87
|
].map((secret, index) => {
|
|
62
88
|
const slug = normalizeDatabaseSlug(secret);
|
|
89
|
+
const fromKey = originMap.get(secret) ?? "";
|
|
63
90
|
const port = secret === API_DATABASE_SECRET ? 5432 : secret === AUTH_DATABASE_SECRET ? 5433 : BASE_DATABASE_PORT + index - 2;
|
|
91
|
+
const volumeName = fromKey ? `${fromKey.replace(/\./g, "_")}_postgres_${slug}_data` : `postgres_${slug}_data`;
|
|
92
|
+
const containerName = fromKey ? `${fromKey}-postgres-${slug}` : `postgres-${slug}`;
|
|
64
93
|
return {
|
|
65
94
|
secret,
|
|
66
95
|
slug,
|
|
96
|
+
fromKey,
|
|
67
97
|
port,
|
|
68
98
|
serviceName: `postgres-${slug.replace(/_/g, "-")}`,
|
|
99
|
+
containerName,
|
|
69
100
|
databaseName: `${slug}_db`,
|
|
70
|
-
volumeName
|
|
101
|
+
volumeName,
|
|
71
102
|
url: `postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${port}/${slug}_db`
|
|
72
103
|
};
|
|
73
104
|
});
|
|
@@ -91,8 +122,10 @@ function renderEnvFile(groups, databases, options) {
|
|
|
91
122
|
}
|
|
92
123
|
return `${lines.join("\n")}\n`;
|
|
93
124
|
}
|
|
94
|
-
function renderDockerCompose(databases) {
|
|
125
|
+
function renderDockerCompose(databases, projectName) {
|
|
95
126
|
const lines = [
|
|
127
|
+
`name: ${projectName}`,
|
|
128
|
+
"",
|
|
96
129
|
"x-pg-common: &pg-common",
|
|
97
130
|
" image: postgres:17-alpine",
|
|
98
131
|
" environment: &pg-env",
|
|
@@ -109,6 +142,7 @@ function renderDockerCompose(databases) {
|
|
|
109
142
|
for (const database of databases) {
|
|
110
143
|
lines.push(` ${database.serviceName}:`);
|
|
111
144
|
lines.push(" <<: *pg-common");
|
|
145
|
+
lines.push(` container_name: ${database.containerName}`);
|
|
112
146
|
lines.push(" environment:");
|
|
113
147
|
lines.push(" <<: *pg-env");
|
|
114
148
|
lines.push(` POSTGRES_DB: ${database.databaseName}`);
|
|
@@ -119,7 +153,10 @@ function renderDockerCompose(databases) {
|
|
|
119
153
|
lines.push("");
|
|
120
154
|
}
|
|
121
155
|
lines.push("volumes:");
|
|
122
|
-
for (const database of databases)
|
|
156
|
+
for (const database of databases) {
|
|
157
|
+
lines.push(` ${database.volumeName}:`);
|
|
158
|
+
lines.push(` name: ${database.volumeName}`);
|
|
159
|
+
}
|
|
123
160
|
return `${lines.join("\n")}\n`;
|
|
124
161
|
}
|
|
125
162
|
function syncTextFile(filePath, nextContent) {
|
|
@@ -131,10 +168,10 @@ function writeGeneratedInfra(configDir, runtimeConfig) {
|
|
|
131
168
|
return syncGeneratedInfra(configDir, runtimeConfig).secrets;
|
|
132
169
|
}
|
|
133
170
|
function syncGeneratedInfra(configDir, runtimeConfig) {
|
|
134
|
-
const spec = buildGeneratedInfraSpec(runtimeConfig);
|
|
171
|
+
const spec = buildGeneratedInfraSpec(runtimeConfig, configDir);
|
|
135
172
|
const secrets = spec.groups.flatMap((group) => group.secrets);
|
|
136
173
|
const newEnvContent = renderEnvFile(spec.groups, spec.databases, { forExample: true });
|
|
137
|
-
const newDockerContent = renderDockerCompose(spec.databases);
|
|
174
|
+
const newDockerContent = renderDockerCompose(spec.databases, runtimeConfig.account);
|
|
138
175
|
const envExamplePath = join(configDir, ".env.example");
|
|
139
176
|
const dockerComposePath = join(configDir, "docker-compose.yml");
|
|
140
177
|
return {
|
package/dist/cli/infra.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"infra.mjs","names":[],"sources":["../../src/cli/infra.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport * as p from \"@clack/prompts\";\nimport { config as loadDotenv } from \"dotenv\";\nimport type { RuntimeConfig } from \"../types\";\n\nconst POSTGRES_USER = \"everythingdev\";\nconst POSTGRES_PASSWORD = \"everythingdev\";\nconst API_DATABASE_SECRET = \"API_DATABASE_URL\";\nconst AUTH_DATABASE_SECRET = \"AUTH_DATABASE_URL\";\nconst HOST_SECRET = \"CORS_ORIGIN\";\nconst BASE_DATABASE_PORT = 5434;\n\ninterface DatabaseSecretConfig {\n secret: string;\n slug: string;\n port: number;\n serviceName: string;\n databaseName: string;\n volumeName: string;\n url: string;\n}\n\ninterface SecretGroup {\n section: string;\n secrets: string[];\n}\n\ninterface GeneratedInfraSpec {\n groups: SecretGroup[];\n databases: DatabaseSecretConfig[];\n}\n\ninterface SyncGeneratedInfraResult {\n secrets: string[];\n envExampleChanged: boolean;\n dockerComposeChanged: boolean;\n}\n\nfunction uniqueSecrets(values: Array<string | undefined>): string[] {\n const secrets: string[] = [];\n const seen = new Set<string>();\n\n for (const value of values) {\n if (!value || seen.has(value)) continue;\n seen.add(value);\n secrets.push(value);\n }\n\n return secrets;\n}\n\nfunction getSecretGroups(runtimeConfig: RuntimeConfig): SecretGroup[] {\n const groups: SecretGroup[] = [];\n const seen = new Set<string>();\n\n const addGroup = (section: string, secrets: string[]) => {\n const filtered = secrets.filter((s) => {\n if (seen.has(s)) return false;\n seen.add(s);\n return true;\n });\n if (filtered.length > 0) {\n groups.push({ section, secrets: filtered });\n }\n };\n\n addGroup(\"app.host\", uniqueSecrets([...(runtimeConfig.host.secrets ?? []), HOST_SECRET]));\n\n addGroup(\"app.api\", uniqueSecrets(runtimeConfig.api.secrets ?? []));\n\n if (runtimeConfig.auth) {\n addGroup(\"app.auth\", uniqueSecrets(runtimeConfig.auth.secrets ?? []));\n }\n\n if (runtimeConfig.plugins) {\n for (const [pluginKey, plugin] of Object.entries(runtimeConfig.plugins)) {\n if (plugin.secrets && plugin.secrets.length > 0) {\n addGroup(`plugins.${pluginKey}`, plugin.secrets);\n }\n }\n }\n\n return groups;\n}\n\nfunction buildGeneratedInfraSpec(runtimeConfig: RuntimeConfig): GeneratedInfraSpec {\n const groups = getSecretGroups(runtimeConfig);\n const databases = buildDatabaseConfigs(groups.flatMap((group) => group.secrets));\n return { groups, databases };\n}\n\nfunction normalizeDatabaseSlug(secret: string): string {\n return secret.replace(/_DATABASE_URL$/, \"\").toLowerCase();\n}\n\nfunction buildDatabaseConfigs(secrets: string[]): DatabaseSecretConfig[] {\n const databaseSecrets = uniqueSecrets(\n secrets.filter((secret) => secret.endsWith(\"_DATABASE_URL\")),\n );\n\n const additionalSecrets = databaseSecrets\n .filter((secret) => secret !== API_DATABASE_SECRET && secret !== AUTH_DATABASE_SECRET)\n .sort((a, b) => a.localeCompare(b));\n\n const orderedSecrets = [API_DATABASE_SECRET, AUTH_DATABASE_SECRET, ...additionalSecrets];\n\n return orderedSecrets.map((secret, index) => {\n const slug = normalizeDatabaseSlug(secret);\n const port =\n secret === API_DATABASE_SECRET\n ? 5432\n : secret === AUTH_DATABASE_SECRET\n ? 5433\n : BASE_DATABASE_PORT + index - 2;\n\n return {\n secret,\n slug,\n port,\n serviceName: `postgres-${slug.replace(/_/g, \"-\")}`,\n databaseName: `${slug}_db`,\n volumeName: `postgres_${slug}_data`,\n url: `postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${port}/${slug}_db`,\n };\n });\n}\n\nfunction defaultSecretValue(\n secret: string,\n databases: Map<string, DatabaseSecretConfig>,\n options: { forExample: boolean },\n): string {\n if (secret === \"BETTER_AUTH_SECRET\") {\n return options.forExample ? \"\" : randomBytes(32).toString(\"base64url\");\n }\n\n if (secret === \"CORS_ORIGIN\") {\n return \"http://localhost:3000\";\n }\n\n return databases.get(secret)?.url ?? \"\";\n}\n\nfunction renderEnvFile(\n groups: SecretGroup[],\n databases: DatabaseSecretConfig[],\n options: { forExample: boolean },\n): string {\n const databaseMap = new Map(databases.map((entry) => [entry.secret, entry]));\n const lines: string[] = [\n \"# Generated from configured bos secrets\",\n \"# Update values as needed for your local environment\",\n \"\",\n ];\n\n for (const group of groups) {\n lines.push(`# ${group.section}`);\n for (const secret of group.secrets) {\n lines.push(`${secret}=${defaultSecretValue(secret, databaseMap, options)}`);\n }\n lines.push(\"\");\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction renderDockerCompose(databases: DatabaseSecretConfig[]): string {\n const lines = [\n \"x-pg-common: &pg-common\",\n \" image: postgres:17-alpine\",\n \" environment: &pg-env\",\n ` POSTGRES_USER: ${POSTGRES_USER}`,\n ` POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}`,\n \" healthcheck:\",\n ' test: [\"CMD-SHELL\", \"pg_isready -U everythingdev\"]',\n \" interval: 3s\",\n \" timeout: 3s\",\n \" retries: 5\",\n \"\",\n \"services:\",\n ];\n\n for (const database of databases) {\n lines.push(` ${database.serviceName}:`);\n lines.push(\" <<: *pg-common\");\n lines.push(\" environment:\");\n lines.push(\" <<: *pg-env\");\n lines.push(` POSTGRES_DB: ${database.databaseName}`);\n lines.push(\" ports:\");\n lines.push(` - \"${database.port}:5432\"`);\n lines.push(\" volumes:\");\n lines.push(` - ${database.volumeName}:/var/lib/postgresql/data`);\n lines.push(\"\");\n }\n\n lines.push(\"volumes:\");\n for (const database of databases) {\n lines.push(` ${database.volumeName}:`);\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction syncTextFile(filePath: string, nextContent: string): boolean {\n if (existsSync(filePath) && readFileSync(filePath, \"utf-8\") === nextContent) {\n return false;\n }\n\n writeFileSync(filePath, nextContent);\n return true;\n}\n\nexport function writeGeneratedInfra(configDir: string, runtimeConfig: RuntimeConfig): string[] {\n return syncGeneratedInfra(configDir, runtimeConfig).secrets;\n}\n\nexport function syncGeneratedInfra(\n configDir: string,\n runtimeConfig: RuntimeConfig,\n): SyncGeneratedInfraResult {\n const spec = buildGeneratedInfraSpec(runtimeConfig);\n const secrets = spec.groups.flatMap((group) => group.secrets);\n const newEnvContent = renderEnvFile(spec.groups, spec.databases, { forExample: true });\n const newDockerContent = renderDockerCompose(spec.databases);\n\n const envExamplePath = join(configDir, \".env.example\");\n const dockerComposePath = join(configDir, \"docker-compose.yml\");\n\n return {\n secrets,\n envExampleChanged: syncTextFile(envExamplePath, newEnvContent),\n dockerComposeChanged: syncTextFile(dockerComposePath, newDockerContent),\n };\n}\n\nexport function ensureEnvFile(configDir: string): void {\n const envPath = join(configDir, \".env\");\n const examplePath = join(configDir, \".env.example\");\n\n if (existsSync(envPath) || !existsSync(examplePath)) return;\n\n const content = readFileSync(examplePath, \"utf-8\");\n const lines = content.split(\"\\n\");\n const secret = randomBytes(32).toString(\"base64url\");\n const updated = lines\n .map((line) => {\n if (/^BETTER_AUTH_SECRET=/.test(line)) {\n return `BETTER_AUTH_SECRET=${secret}`;\n }\n return line;\n })\n .join(\"\\n\");\n\n writeFileSync(envPath, updated);\n p.log.info(\"Created .env from generated .env.example with generated BETTER_AUTH_SECRET\");\n}\n\nexport function loadProjectEnv(configDir: string): void {\n const envPath = join(configDir, \".env\");\n if (!existsSync(envPath)) return;\n\n loadDotenv({ path: envPath, processEnv: process.env, quiet: true });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"infra.mjs","names":[],"sources":["../../src/cli/infra.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport * as p from \"@clack/prompts\";\nimport { config as loadDotenv } from \"dotenv\";\nimport type { RuntimeConfig } from \"../types\";\n\nconst POSTGRES_USER = \"everythingdev\";\nconst POSTGRES_PASSWORD = \"everythingdev\";\nconst API_DATABASE_SECRET = \"API_DATABASE_URL\";\nconst AUTH_DATABASE_SECRET = \"AUTH_DATABASE_URL\";\nconst HOST_SECRET = \"CORS_ORIGIN\";\nconst BASE_DATABASE_PORT = 5434;\n\ninterface DatabaseSecretConfig {\n secret: string;\n slug: string;\n fromKey: string;\n port: number;\n serviceName: string;\n containerName: string;\n databaseName: string;\n volumeName: string;\n url: string;\n}\n\ninterface SecretGroup {\n section: string;\n secrets: string[];\n}\n\ninterface GeneratedInfraSpec {\n groups: SecretGroup[];\n databases: DatabaseSecretConfig[];\n}\n\ninterface SyncGeneratedInfraResult {\n secrets: string[];\n envExampleChanged: boolean;\n dockerComposeChanged: boolean;\n}\n\nfunction uniqueSecrets(values: Array<string | undefined>): string[] {\n const secrets: string[] = [];\n const seen = new Set<string>();\n\n for (const value of values) {\n if (!value || seen.has(value)) continue;\n seen.add(value);\n secrets.push(value);\n }\n\n return secrets;\n}\n\nfunction getSecretGroups(runtimeConfig: RuntimeConfig): SecretGroup[] {\n const groups: SecretGroup[] = [];\n const seen = new Set<string>();\n\n const addGroup = (section: string, secrets: string[]) => {\n const filtered = secrets.filter((s) => {\n if (seen.has(s)) return false;\n seen.add(s);\n return true;\n });\n if (filtered.length > 0) {\n groups.push({ section, secrets: filtered });\n }\n };\n\n addGroup(\"app.host\", uniqueSecrets([...(runtimeConfig.host.secrets ?? []), HOST_SECRET]));\n\n addGroup(\"app.api\", uniqueSecrets(runtimeConfig.api.secrets ?? []));\n\n if (runtimeConfig.auth) {\n addGroup(\"app.auth\", uniqueSecrets(runtimeConfig.auth.secrets ?? []));\n }\n\n if (runtimeConfig.plugins) {\n for (const [pluginKey, plugin] of Object.entries(runtimeConfig.plugins)) {\n if (plugin.secrets && plugin.secrets.length > 0) {\n addGroup(`plugins.${pluginKey}`, plugin.secrets);\n }\n }\n }\n\n return groups;\n}\n\nfunction buildGeneratedInfraSpec(\n runtimeConfig: RuntimeConfig,\n configDir?: string,\n): GeneratedInfraSpec {\n const groups = getSecretGroups(runtimeConfig);\n const originMap = configDir ? buildOriginMap(configDir, runtimeConfig) : new Map();\n const databases = buildDatabaseConfigs(\n groups.flatMap((group) => group.secrets),\n originMap,\n );\n return { groups, databases };\n}\n\nfunction normalizeDatabaseSlug(secret: string): string {\n return secret.replace(/_DATABASE_URL$/, \"\").toLowerCase();\n}\n\nfunction buildOriginMap(configDir: string, runtimeConfig: RuntimeConfig): Map<string, string> {\n const configPath = join(configDir, \"bos.config.json\");\n\n const originMap = new Map<string, string>();\n const account = runtimeConfig.account;\n\n const resolveOrigin = (extendsRef: unknown): string | null => {\n if (typeof extendsRef === \"string\") {\n const match = extendsRef.match(/^bos:\\/\\/([^/]+)\\//);\n return match?.[1] ?? null;\n }\n return null;\n };\n\n const rawConfig = existsSync(configPath)\n ? (JSON.parse(readFileSync(configPath, \"utf-8\")) as Record<string, unknown>)\n : null;\n const rawPlugins = rawConfig?.plugins as Record<string, unknown> | undefined;\n\n for (const secret of runtimeConfig.api.secrets ?? []) {\n if (!originMap.has(secret)) originMap.set(secret, account);\n }\n\n const rawApp = rawConfig?.app as Record<string, unknown> | undefined;\n const authExtends = (rawApp?.auth as Record<string, unknown> | undefined)?.extends;\n const authOrigin = resolveOrigin(authExtends) ?? account;\n for (const secret of runtimeConfig.auth?.secrets ?? []) {\n if (!originMap.has(secret)) originMap.set(secret, authOrigin);\n }\n\n for (const [pluginKey, pluginEntry] of Object.entries(runtimeConfig.plugins ?? {})) {\n const rawPlugin = rawPlugins?.[pluginKey];\n let pluginOrigin: string;\n if (typeof rawPlugin === \"string\") {\n pluginOrigin = resolveOrigin(rawPlugin) ?? account;\n } else if (rawPlugin && typeof rawPlugin === \"object\") {\n pluginOrigin = resolveOrigin((rawPlugin as Record<string, unknown>).extends) ?? account;\n } else {\n pluginOrigin = account;\n }\n for (const secret of pluginEntry.secrets ?? []) {\n if (!originMap.has(secret)) originMap.set(secret, pluginOrigin);\n }\n }\n\n for (const secret of runtimeConfig.host.secrets ?? []) {\n if (!originMap.has(secret)) originMap.set(secret, account);\n }\n\n return originMap;\n}\n\nfunction buildDatabaseConfigs(\n secrets: string[],\n originMap: Map<string, string>,\n): DatabaseSecretConfig[] {\n const databaseSecrets = uniqueSecrets(\n secrets.filter((secret) => secret.endsWith(\"_DATABASE_URL\")),\n );\n\n const additionalSecrets = databaseSecrets\n .filter((secret) => secret !== API_DATABASE_SECRET && secret !== AUTH_DATABASE_SECRET)\n .sort((a, b) => a.localeCompare(b));\n\n const orderedSecrets = [API_DATABASE_SECRET, AUTH_DATABASE_SECRET, ...additionalSecrets];\n\n return orderedSecrets.map((secret, index) => {\n const slug = normalizeDatabaseSlug(secret);\n const fromKey = originMap.get(secret) ?? \"\";\n const port =\n secret === API_DATABASE_SECRET\n ? 5432\n : secret === AUTH_DATABASE_SECRET\n ? 5433\n : BASE_DATABASE_PORT + index - 2;\n\n const volumeName = fromKey\n ? `${fromKey.replace(/\\./g, \"_\")}_postgres_${slug}_data`\n : `postgres_${slug}_data`;\n\n const containerName = fromKey ? `${fromKey}-postgres-${slug}` : `postgres-${slug}`;\n\n return {\n secret,\n slug,\n fromKey,\n port,\n serviceName: `postgres-${slug.replace(/_/g, \"-\")}`,\n containerName,\n databaseName: `${slug}_db`,\n volumeName,\n url: `postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${port}/${slug}_db`,\n };\n });\n}\n\nfunction defaultSecretValue(\n secret: string,\n databases: Map<string, DatabaseSecretConfig>,\n options: { forExample: boolean },\n): string {\n if (secret === \"BETTER_AUTH_SECRET\") {\n return options.forExample ? \"\" : randomBytes(32).toString(\"base64url\");\n }\n\n if (secret === \"CORS_ORIGIN\") {\n return \"http://localhost:3000\";\n }\n\n return databases.get(secret)?.url ?? \"\";\n}\n\nfunction renderEnvFile(\n groups: SecretGroup[],\n databases: DatabaseSecretConfig[],\n options: { forExample: boolean },\n): string {\n const databaseMap = new Map(databases.map((entry) => [entry.secret, entry]));\n const lines: string[] = [\n \"# Generated from configured bos secrets\",\n \"# Update values as needed for your local environment\",\n \"\",\n ];\n\n for (const group of groups) {\n lines.push(`# ${group.section}`);\n for (const secret of group.secrets) {\n lines.push(`${secret}=${defaultSecretValue(secret, databaseMap, options)}`);\n }\n lines.push(\"\");\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction renderDockerCompose(databases: DatabaseSecretConfig[], projectName: string): string {\n const lines = [\n `name: ${projectName}`,\n \"\",\n \"x-pg-common: &pg-common\",\n \" image: postgres:17-alpine\",\n \" environment: &pg-env\",\n ` POSTGRES_USER: ${POSTGRES_USER}`,\n ` POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}`,\n \" healthcheck:\",\n ' test: [\"CMD-SHELL\", \"pg_isready -U everythingdev\"]',\n \" interval: 3s\",\n \" timeout: 3s\",\n \" retries: 5\",\n \"\",\n \"services:\",\n ];\n\n for (const database of databases) {\n lines.push(` ${database.serviceName}:`);\n lines.push(\" <<: *pg-common\");\n lines.push(` container_name: ${database.containerName}`);\n lines.push(\" environment:\");\n lines.push(\" <<: *pg-env\");\n lines.push(` POSTGRES_DB: ${database.databaseName}`);\n lines.push(\" ports:\");\n lines.push(` - \"${database.port}:5432\"`);\n lines.push(\" volumes:\");\n lines.push(` - ${database.volumeName}:/var/lib/postgresql/data`);\n lines.push(\"\");\n }\n\n lines.push(\"volumes:\");\n for (const database of databases) {\n lines.push(` ${database.volumeName}:`);\n lines.push(` name: ${database.volumeName}`);\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction syncTextFile(filePath: string, nextContent: string): boolean {\n if (existsSync(filePath) && readFileSync(filePath, \"utf-8\") === nextContent) {\n return false;\n }\n\n writeFileSync(filePath, nextContent);\n return true;\n}\n\nexport function writeGeneratedInfra(configDir: string, runtimeConfig: RuntimeConfig): string[] {\n return syncGeneratedInfra(configDir, runtimeConfig).secrets;\n}\n\nexport function syncGeneratedInfra(\n configDir: string,\n runtimeConfig: RuntimeConfig,\n): SyncGeneratedInfraResult {\n const spec = buildGeneratedInfraSpec(runtimeConfig, configDir);\n const secrets = spec.groups.flatMap((group) => group.secrets);\n const newEnvContent = renderEnvFile(spec.groups, spec.databases, { forExample: true });\n const newDockerContent = renderDockerCompose(spec.databases, runtimeConfig.account);\n\n const envExamplePath = join(configDir, \".env.example\");\n const dockerComposePath = join(configDir, \"docker-compose.yml\");\n\n return {\n secrets,\n envExampleChanged: syncTextFile(envExamplePath, newEnvContent),\n dockerComposeChanged: syncTextFile(dockerComposePath, newDockerContent),\n };\n}\n\nexport function ensureEnvFile(configDir: string): void {\n const envPath = join(configDir, \".env\");\n const examplePath = join(configDir, \".env.example\");\n\n if (existsSync(envPath) || !existsSync(examplePath)) return;\n\n const content = readFileSync(examplePath, \"utf-8\");\n const lines = content.split(\"\\n\");\n const secret = randomBytes(32).toString(\"base64url\");\n const updated = lines\n .map((line) => {\n if (/^BETTER_AUTH_SECRET=/.test(line)) {\n return `BETTER_AUTH_SECRET=${secret}`;\n }\n return line;\n })\n .join(\"\\n\");\n\n writeFileSync(envPath, updated);\n p.log.info(\"Created .env from generated .env.example with generated BETTER_AUTH_SECRET\");\n}\n\nexport function loadProjectEnv(configDir: string): void {\n const envPath = join(configDir, \".env\");\n if (!existsSync(envPath)) return;\n\n loadDotenv({ path: envPath, processEnv: process.env, quiet: true });\n}\n"],"mappings":"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"}
|
package/dist/cli/init.cjs
CHANGED
|
@@ -322,7 +322,7 @@ function buildRootTypecheckScript(sections) {
|
|
|
322
322
|
}
|
|
323
323
|
function buildChildRootScripts(sections) {
|
|
324
324
|
const scripts = {
|
|
325
|
-
dev: "node_modules/.bin/bos dev
|
|
325
|
+
dev: "node_modules/.bin/bos dev",
|
|
326
326
|
"dev:proxy": "node_modules/.bin/bos dev --proxy",
|
|
327
327
|
build: "node_modules/.bin/bos build",
|
|
328
328
|
deploy: "node_modules/.bin/bos build --deploy",
|
|
@@ -864,6 +864,7 @@ function generateGitignore() {
|
|
|
864
864
|
dist/
|
|
865
865
|
.env
|
|
866
866
|
.bos/
|
|
867
|
+
docker-compose.yml
|
|
867
868
|
*.gen.ts
|
|
868
869
|
*.gen.tsx
|
|
869
870
|
`;
|