everything-dev 1.28.8 → 1.28.10
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 +53 -16
- package/dist/cli/infra.cjs.map +1 -1
- package/dist/cli/infra.mjs +53 -17
- package/dist/cli/infra.mjs.map +1 -1
- package/dist/cli/init.cjs +12 -33
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.d.cts.map +1 -1
- package/dist/cli/init.d.mts.map +1 -1
- package/dist/cli/init.mjs +12 -33
- package/dist/cli/init.mjs.map +1 -1
- package/dist/cli/sync.cjs +1 -1
- package/dist/cli/sync.cjs.map +1 -1
- package/dist/cli/sync.mjs +1 -1
- package/dist/cli/sync.mjs.map +1 -1
- package/dist/cli/upgrade.cjs +3 -26
- package/dist/cli/upgrade.cjs.map +1 -1
- package/dist/cli/upgrade.mjs +3 -26
- package/dist/cli/upgrade.mjs.map +1 -1
- package/dist/contract.d.cts +18 -18
- package/dist/contract.d.mts +18 -18
- package/dist/plugin.cjs +10 -0
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +8 -8
- package/dist/plugin.d.cts.map +1 -1
- package/dist/plugin.d.mts +8 -8
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +11 -1
- 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/dist/cli/infra.cjs
CHANGED
|
@@ -11,6 +11,7 @@ const POSTGRES_USER = "everythingdev";
|
|
|
11
11
|
const POSTGRES_PASSWORD = "everythingdev";
|
|
12
12
|
const API_DATABASE_SECRET = "API_DATABASE_URL";
|
|
13
13
|
const AUTH_DATABASE_SECRET = "AUTH_DATABASE_URL";
|
|
14
|
+
const HOST_SECRET = "CORS_ORIGIN";
|
|
14
15
|
const BASE_DATABASE_PORT = 5434;
|
|
15
16
|
function uniqueSecrets(values) {
|
|
16
17
|
const secrets = [];
|
|
@@ -22,15 +23,34 @@ function uniqueSecrets(values) {
|
|
|
22
23
|
}
|
|
23
24
|
return secrets;
|
|
24
25
|
}
|
|
25
|
-
function
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
function getSecretGroups(runtimeConfig) {
|
|
27
|
+
const groups = [];
|
|
28
|
+
const seen = /* @__PURE__ */ new Set();
|
|
29
|
+
const addGroup = (section, secrets) => {
|
|
30
|
+
const filtered = secrets.filter((s) => {
|
|
31
|
+
if (seen.has(s)) return false;
|
|
32
|
+
seen.add(s);
|
|
33
|
+
return true;
|
|
34
|
+
});
|
|
35
|
+
if (filtered.length > 0) groups.push({
|
|
36
|
+
section,
|
|
37
|
+
secrets: filtered
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
addGroup("app.host", uniqueSecrets([...runtimeConfig.host.secrets ?? [], HOST_SECRET]));
|
|
41
|
+
addGroup("app.api", uniqueSecrets(runtimeConfig.api.secrets ?? []));
|
|
42
|
+
if (runtimeConfig.auth) addGroup("app.auth", uniqueSecrets(runtimeConfig.auth.secrets ?? []));
|
|
43
|
+
if (runtimeConfig.plugins) {
|
|
44
|
+
for (const [pluginKey, plugin] of Object.entries(runtimeConfig.plugins)) if (plugin.secrets && plugin.secrets.length > 0) addGroup(`plugins.${pluginKey}`, plugin.secrets);
|
|
45
|
+
}
|
|
46
|
+
return groups;
|
|
47
|
+
}
|
|
48
|
+
function buildGeneratedInfraSpec(runtimeConfig) {
|
|
49
|
+
const groups = getSecretGroups(runtimeConfig);
|
|
50
|
+
return {
|
|
51
|
+
groups,
|
|
52
|
+
databases: buildDatabaseConfigs(groups.flatMap((group) => group.secrets))
|
|
53
|
+
};
|
|
34
54
|
}
|
|
35
55
|
function normalizeDatabaseSlug(secret) {
|
|
36
56
|
return secret.replace(/_DATABASE_URL$/, "").toLowerCase();
|
|
@@ -59,14 +79,18 @@ function defaultSecretValue(secret, databases, options) {
|
|
|
59
79
|
if (secret === "CORS_ORIGIN") return "http://localhost:3000";
|
|
60
80
|
return databases.get(secret)?.url ?? "";
|
|
61
81
|
}
|
|
62
|
-
function renderEnvFile(
|
|
82
|
+
function renderEnvFile(groups, databases, options) {
|
|
63
83
|
const databaseMap = new Map(databases.map((entry) => [entry.secret, entry]));
|
|
64
84
|
const lines = [
|
|
65
85
|
"# Generated from configured bos secrets",
|
|
66
86
|
"# Update values as needed for your local environment",
|
|
67
87
|
""
|
|
68
88
|
];
|
|
69
|
-
for (const
|
|
89
|
+
for (const group of groups) {
|
|
90
|
+
lines.push(`# ${group.section}`);
|
|
91
|
+
for (const secret of group.secrets) lines.push(`${secret}=${defaultSecretValue(secret, databaseMap, options)}`);
|
|
92
|
+
lines.push("");
|
|
93
|
+
}
|
|
70
94
|
return `${lines.join("\n")}\n`;
|
|
71
95
|
}
|
|
72
96
|
function renderDockerCompose(databases) {
|
|
@@ -100,14 +124,26 @@ function renderDockerCompose(databases) {
|
|
|
100
124
|
for (const database of databases) lines.push(` ${database.volumeName}:`);
|
|
101
125
|
return `${lines.join("\n")}\n`;
|
|
102
126
|
}
|
|
127
|
+
function syncTextFile(filePath, nextContent) {
|
|
128
|
+
if ((0, node_fs.existsSync)(filePath) && (0, node_fs.readFileSync)(filePath, "utf-8") === nextContent) return false;
|
|
129
|
+
(0, node_fs.writeFileSync)(filePath, nextContent);
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
103
132
|
function writeGeneratedInfra(configDir, runtimeConfig) {
|
|
104
|
-
|
|
105
|
-
|
|
133
|
+
return syncGeneratedInfra(configDir, runtimeConfig).secrets;
|
|
134
|
+
}
|
|
135
|
+
function syncGeneratedInfra(configDir, runtimeConfig) {
|
|
136
|
+
const spec = buildGeneratedInfraSpec(runtimeConfig);
|
|
137
|
+
const secrets = spec.groups.flatMap((group) => group.secrets);
|
|
138
|
+
const newEnvContent = renderEnvFile(spec.groups, spec.databases, { forExample: true });
|
|
139
|
+
const newDockerContent = renderDockerCompose(spec.databases);
|
|
106
140
|
const envExamplePath = (0, node_path.join)(configDir, ".env.example");
|
|
107
141
|
const dockerComposePath = (0, node_path.join)(configDir, "docker-compose.yml");
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
142
|
+
return {
|
|
143
|
+
secrets,
|
|
144
|
+
envExampleChanged: syncTextFile(envExamplePath, newEnvContent),
|
|
145
|
+
dockerComposeChanged: syncTextFile(dockerComposePath, newDockerContent)
|
|
146
|
+
};
|
|
111
147
|
}
|
|
112
148
|
function ensureEnvFile(configDir) {
|
|
113
149
|
const envPath = (0, node_path.join)(configDir, ".env");
|
|
@@ -134,5 +170,6 @@ function loadProjectEnv(configDir) {
|
|
|
134
170
|
//#endregion
|
|
135
171
|
exports.ensureEnvFile = ensureEnvFile;
|
|
136
172
|
exports.loadProjectEnv = loadProjectEnv;
|
|
173
|
+
exports.syncGeneratedInfra = syncGeneratedInfra;
|
|
137
174
|
exports.writeGeneratedInfra = writeGeneratedInfra;
|
|
138
175
|
//# sourceMappingURL=infra.cjs.map
|
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 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\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 getRuntimeSecrets(runtimeConfig: RuntimeConfig): string[] {\n const pluginSecrets = Object.values(runtimeConfig.plugins ?? {}).flatMap(\n (plugin) => plugin.secrets ?? [],\n );\n\n return uniqueSecrets([\n API_DATABASE_SECRET,\n AUTH_DATABASE_SECRET,\n ...(runtimeConfig.api.secrets ?? []),\n ...(runtimeConfig.auth?.secrets ?? []),\n ...pluginSecrets,\n ]);\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 secrets: string[],\n databases: DatabaseSecretConfig[],\n options: { forExample: boolean },\n): string {\n const databaseMap = new Map(databases.map((entry) => [entry.secret, entry]));\n const lines = [\n \"# Generated from configured bos secrets\",\n \"# Update values as needed for your local environment\",\n \"\",\n ];\n\n for (const secret of secrets) {\n lines.push(`${secret}=${defaultSecretValue(secret, databaseMap, options)}`);\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\nexport function writeGeneratedInfra(configDir: string, runtimeConfig: RuntimeConfig): string[] {\n const secrets = getRuntimeSecrets(runtimeConfig);\n const databases = buildDatabaseConfigs(secrets);\n const envExamplePath = join(configDir, \".env.example\");\n const dockerComposePath = join(configDir, \"docker-compose.yml\");\n\n writeFileSync(envExamplePath, renderEnvFile(secrets, databases, { forExample: true }));\n writeFileSync(dockerComposePath, renderDockerCompose(databases));\n\n return secrets;\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 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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"}
|
package/dist/cli/infra.mjs
CHANGED
|
@@ -9,6 +9,7 @@ const POSTGRES_USER = "everythingdev";
|
|
|
9
9
|
const POSTGRES_PASSWORD = "everythingdev";
|
|
10
10
|
const API_DATABASE_SECRET = "API_DATABASE_URL";
|
|
11
11
|
const AUTH_DATABASE_SECRET = "AUTH_DATABASE_URL";
|
|
12
|
+
const HOST_SECRET = "CORS_ORIGIN";
|
|
12
13
|
const BASE_DATABASE_PORT = 5434;
|
|
13
14
|
function uniqueSecrets(values) {
|
|
14
15
|
const secrets = [];
|
|
@@ -20,15 +21,34 @@ function uniqueSecrets(values) {
|
|
|
20
21
|
}
|
|
21
22
|
return secrets;
|
|
22
23
|
}
|
|
23
|
-
function
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
function getSecretGroups(runtimeConfig) {
|
|
25
|
+
const groups = [];
|
|
26
|
+
const seen = /* @__PURE__ */ new Set();
|
|
27
|
+
const addGroup = (section, secrets) => {
|
|
28
|
+
const filtered = secrets.filter((s) => {
|
|
29
|
+
if (seen.has(s)) return false;
|
|
30
|
+
seen.add(s);
|
|
31
|
+
return true;
|
|
32
|
+
});
|
|
33
|
+
if (filtered.length > 0) groups.push({
|
|
34
|
+
section,
|
|
35
|
+
secrets: filtered
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
addGroup("app.host", uniqueSecrets([...runtimeConfig.host.secrets ?? [], HOST_SECRET]));
|
|
39
|
+
addGroup("app.api", uniqueSecrets(runtimeConfig.api.secrets ?? []));
|
|
40
|
+
if (runtimeConfig.auth) addGroup("app.auth", uniqueSecrets(runtimeConfig.auth.secrets ?? []));
|
|
41
|
+
if (runtimeConfig.plugins) {
|
|
42
|
+
for (const [pluginKey, plugin] of Object.entries(runtimeConfig.plugins)) if (plugin.secrets && plugin.secrets.length > 0) addGroup(`plugins.${pluginKey}`, plugin.secrets);
|
|
43
|
+
}
|
|
44
|
+
return groups;
|
|
45
|
+
}
|
|
46
|
+
function buildGeneratedInfraSpec(runtimeConfig) {
|
|
47
|
+
const groups = getSecretGroups(runtimeConfig);
|
|
48
|
+
return {
|
|
49
|
+
groups,
|
|
50
|
+
databases: buildDatabaseConfigs(groups.flatMap((group) => group.secrets))
|
|
51
|
+
};
|
|
32
52
|
}
|
|
33
53
|
function normalizeDatabaseSlug(secret) {
|
|
34
54
|
return secret.replace(/_DATABASE_URL$/, "").toLowerCase();
|
|
@@ -57,14 +77,18 @@ function defaultSecretValue(secret, databases, options) {
|
|
|
57
77
|
if (secret === "CORS_ORIGIN") return "http://localhost:3000";
|
|
58
78
|
return databases.get(secret)?.url ?? "";
|
|
59
79
|
}
|
|
60
|
-
function renderEnvFile(
|
|
80
|
+
function renderEnvFile(groups, databases, options) {
|
|
61
81
|
const databaseMap = new Map(databases.map((entry) => [entry.secret, entry]));
|
|
62
82
|
const lines = [
|
|
63
83
|
"# Generated from configured bos secrets",
|
|
64
84
|
"# Update values as needed for your local environment",
|
|
65
85
|
""
|
|
66
86
|
];
|
|
67
|
-
for (const
|
|
87
|
+
for (const group of groups) {
|
|
88
|
+
lines.push(`# ${group.section}`);
|
|
89
|
+
for (const secret of group.secrets) lines.push(`${secret}=${defaultSecretValue(secret, databaseMap, options)}`);
|
|
90
|
+
lines.push("");
|
|
91
|
+
}
|
|
68
92
|
return `${lines.join("\n")}\n`;
|
|
69
93
|
}
|
|
70
94
|
function renderDockerCompose(databases) {
|
|
@@ -98,14 +122,26 @@ function renderDockerCompose(databases) {
|
|
|
98
122
|
for (const database of databases) lines.push(` ${database.volumeName}:`);
|
|
99
123
|
return `${lines.join("\n")}\n`;
|
|
100
124
|
}
|
|
125
|
+
function syncTextFile(filePath, nextContent) {
|
|
126
|
+
if (existsSync(filePath) && readFileSync(filePath, "utf-8") === nextContent) return false;
|
|
127
|
+
writeFileSync(filePath, nextContent);
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
101
130
|
function writeGeneratedInfra(configDir, runtimeConfig) {
|
|
102
|
-
|
|
103
|
-
|
|
131
|
+
return syncGeneratedInfra(configDir, runtimeConfig).secrets;
|
|
132
|
+
}
|
|
133
|
+
function syncGeneratedInfra(configDir, runtimeConfig) {
|
|
134
|
+
const spec = buildGeneratedInfraSpec(runtimeConfig);
|
|
135
|
+
const secrets = spec.groups.flatMap((group) => group.secrets);
|
|
136
|
+
const newEnvContent = renderEnvFile(spec.groups, spec.databases, { forExample: true });
|
|
137
|
+
const newDockerContent = renderDockerCompose(spec.databases);
|
|
104
138
|
const envExamplePath = join(configDir, ".env.example");
|
|
105
139
|
const dockerComposePath = join(configDir, "docker-compose.yml");
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
140
|
+
return {
|
|
141
|
+
secrets,
|
|
142
|
+
envExampleChanged: syncTextFile(envExamplePath, newEnvContent),
|
|
143
|
+
dockerComposeChanged: syncTextFile(dockerComposePath, newDockerContent)
|
|
144
|
+
};
|
|
109
145
|
}
|
|
110
146
|
function ensureEnvFile(configDir) {
|
|
111
147
|
const envPath = join(configDir, ".env");
|
|
@@ -130,5 +166,5 @@ function loadProjectEnv(configDir) {
|
|
|
130
166
|
}
|
|
131
167
|
|
|
132
168
|
//#endregion
|
|
133
|
-
export { ensureEnvFile, loadProjectEnv, writeGeneratedInfra };
|
|
169
|
+
export { ensureEnvFile, loadProjectEnv, syncGeneratedInfra, writeGeneratedInfra };
|
|
134
170
|
//# sourceMappingURL=infra.mjs.map
|
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 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\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 getRuntimeSecrets(runtimeConfig: RuntimeConfig): string[] {\n const pluginSecrets = Object.values(runtimeConfig.plugins ?? {}).flatMap(\n (plugin) => plugin.secrets ?? [],\n );\n\n return uniqueSecrets([\n API_DATABASE_SECRET,\n AUTH_DATABASE_SECRET,\n ...(runtimeConfig.api.secrets ?? []),\n ...(runtimeConfig.auth?.secrets ?? []),\n ...pluginSecrets,\n ]);\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 secrets: string[],\n databases: DatabaseSecretConfig[],\n options: { forExample: boolean },\n): string {\n const databaseMap = new Map(databases.map((entry) => [entry.secret, entry]));\n const lines = [\n \"# Generated from configured bos secrets\",\n \"# Update values as needed for your local environment\",\n \"\",\n ];\n\n for (const secret of secrets) {\n lines.push(`${secret}=${defaultSecretValue(secret, databaseMap, options)}`);\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\nexport function writeGeneratedInfra(configDir: string, runtimeConfig: RuntimeConfig): string[] {\n const secrets = getRuntimeSecrets(runtimeConfig);\n const databases = buildDatabaseConfigs(secrets);\n const envExamplePath = join(configDir, \".env.example\");\n const dockerComposePath = join(configDir, \"docker-compose.yml\");\n\n writeFileSync(envExamplePath, renderEnvFile(secrets, databases, { forExample: true }));\n writeFileSync(dockerComposePath, renderDockerCompose(databases));\n\n return secrets;\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 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":";;;;;;;AAOA,MAAM,gBAAgB;AACtB,MAAM,oBAAoB;AAC1B,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB;AAC7B,MAAM,cAAc;AACpB,MAAM,qBAAqB;AA4B3B,SAAS,cAAc,QAA6C;CAClE,MAAM,UAAoB,EAAE;CAC5B,MAAM,uBAAO,IAAI,KAAa;AAE9B,MAAK,MAAM,SAAS,QAAQ;AAC1B,MAAI,CAAC,SAAS,KAAK,IAAI,MAAM,CAAE;AAC/B,OAAK,IAAI,MAAM;AACf,UAAQ,KAAK,MAAM;;AAGrB,QAAO;;AAGT,SAAS,gBAAgB,eAA6C;CACpE,MAAM,SAAwB,EAAE;CAChC,MAAM,uBAAO,IAAI,KAAa;CAE9B,MAAM,YAAY,SAAiB,YAAsB;EACvD,MAAM,WAAW,QAAQ,QAAQ,MAAM;AACrC,OAAI,KAAK,IAAI,EAAE,CAAE,QAAO;AACxB,QAAK,IAAI,EAAE;AACX,UAAO;IACP;AACF,MAAI,SAAS,SAAS,EACpB,QAAO,KAAK;GAAE;GAAS,SAAS;GAAU,CAAC;;AAI/C,UAAS,YAAY,cAAc,CAAC,GAAI,cAAc,KAAK,WAAW,EAAE,EAAG,YAAY,CAAC,CAAC;AAEzF,UAAS,WAAW,cAAc,cAAc,IAAI,WAAW,EAAE,CAAC,CAAC;AAEnE,KAAI,cAAc,KAChB,UAAS,YAAY,cAAc,cAAc,KAAK,WAAW,EAAE,CAAC,CAAC;AAGvE,KAAI,cAAc,SAChB;OAAK,MAAM,CAAC,WAAW,WAAW,OAAO,QAAQ,cAAc,QAAQ,CACrE,KAAI,OAAO,WAAW,OAAO,QAAQ,SAAS,EAC5C,UAAS,WAAW,aAAa,OAAO,QAAQ;;AAKtD,QAAO;;AAGT,SAAS,wBAAwB,eAAkD;CACjF,MAAM,SAAS,gBAAgB,cAAc;AAE7C,QAAO;EAAE;EAAQ,WADC,qBAAqB,OAAO,SAAS,UAAU,MAAM,QAAQ,CACrD;EAAE;;AAG9B,SAAS,sBAAsB,QAAwB;AACrD,QAAO,OAAO,QAAQ,kBAAkB,GAAG,CAAC,aAAa;;AAG3D,SAAS,qBAAqB,SAA2C;AAWvE,QAAO;EAFiB;EAAqB;EAAsB,GAR3C,cACtB,QAAQ,QAAQ,WAAW,OAAO,SAAS,gBAAgB,CAAC,CAGrB,CACtC,QAAQ,WAAW,WAAW,uBAAuB,WAAW,qBAAqB,CACrF,MAAM,GAAG,MAAM,EAAE,cAAc,EAAE,CAEmD;EAElE,CAAC,KAAK,QAAQ,UAAU;EAC3C,MAAM,OAAO,sBAAsB,OAAO;EAC1C,MAAM,OACJ,WAAW,sBACP,OACA,WAAW,uBACT,OACA,qBAAqB,QAAQ;AAErC,SAAO;GACL;GACA;GACA;GACA,aAAa,YAAY,KAAK,QAAQ,MAAM,IAAI;GAChD,cAAc,GAAG,KAAK;GACtB,YAAY,YAAY,KAAK;GAC7B,KAAK,cAAc,cAAc,GAAG,kBAAkB,aAAa,KAAK,GAAG,KAAK;GACjF;GACD;;AAGJ,SAAS,mBACP,QACA,WACA,SACQ;AACR,KAAI,WAAW,qBACb,QAAO,QAAQ,aAAa,KAAK,YAAY,GAAG,CAAC,SAAS,YAAY;AAGxE,KAAI,WAAW,cACb,QAAO;AAGT,QAAO,UAAU,IAAI,OAAO,EAAE,OAAO;;AAGvC,SAAS,cACP,QACA,WACA,SACQ;CACR,MAAM,cAAc,IAAI,IAAI,UAAU,KAAK,UAAU,CAAC,MAAM,QAAQ,MAAM,CAAC,CAAC;CAC5E,MAAM,QAAkB;EACtB;EACA;EACA;EACD;AAED,MAAK,MAAM,SAAS,QAAQ;AAC1B,QAAM,KAAK,KAAK,MAAM,UAAU;AAChC,OAAK,MAAM,UAAU,MAAM,QACzB,OAAM,KAAK,GAAG,OAAO,GAAG,mBAAmB,QAAQ,aAAa,QAAQ,GAAG;AAE7E,QAAM,KAAK,GAAG;;AAGhB,QAAO,GAAG,MAAM,KAAK,KAAK,CAAC;;AAG7B,SAAS,oBAAoB,WAA2C;CACtE,MAAM,QAAQ;EACZ;EACA;EACA;EACA,sBAAsB;EACtB,0BAA0B;EAC1B;EACA;EACA;EACA;EACA;EACA;EACA;EACD;AAED,MAAK,MAAM,YAAY,WAAW;AAChC,QAAM,KAAK,KAAK,SAAS,YAAY,GAAG;AACxC,QAAM,KAAK,qBAAqB;AAChC,QAAM,KAAK,mBAAmB;AAC9B,QAAM,KAAK,oBAAoB;AAC/B,QAAM,KAAK,sBAAsB,SAAS,eAAe;AACzD,QAAM,KAAK,aAAa;AACxB,QAAM,KAAK,YAAY,SAAS,KAAK,QAAQ;AAC7C,QAAM,KAAK,eAAe;AAC1B,QAAM,KAAK,WAAW,SAAS,WAAW,2BAA2B;AACrE,QAAM,KAAK,GAAG;;AAGhB,OAAM,KAAK,WAAW;AACtB,MAAK,MAAM,YAAY,UACrB,OAAM,KAAK,KAAK,SAAS,WAAW,GAAG;AAGzC,QAAO,GAAG,MAAM,KAAK,KAAK,CAAC;;AAG7B,SAAS,aAAa,UAAkB,aAA8B;AACpE,KAAI,WAAW,SAAS,IAAI,aAAa,UAAU,QAAQ,KAAK,YAC9D,QAAO;AAGT,eAAc,UAAU,YAAY;AACpC,QAAO;;AAGT,SAAgB,oBAAoB,WAAmB,eAAwC;AAC7F,QAAO,mBAAmB,WAAW,cAAc,CAAC;;AAGtD,SAAgB,mBACd,WACA,eAC0B;CAC1B,MAAM,OAAO,wBAAwB,cAAc;CACnD,MAAM,UAAU,KAAK,OAAO,SAAS,UAAU,MAAM,QAAQ;CAC7D,MAAM,gBAAgB,cAAc,KAAK,QAAQ,KAAK,WAAW,EAAE,YAAY,MAAM,CAAC;CACtF,MAAM,mBAAmB,oBAAoB,KAAK,UAAU;CAE5D,MAAM,iBAAiB,KAAK,WAAW,eAAe;CACtD,MAAM,oBAAoB,KAAK,WAAW,qBAAqB;AAE/D,QAAO;EACL;EACA,mBAAmB,aAAa,gBAAgB,cAAc;EAC9D,sBAAsB,aAAa,mBAAmB,iBAAiB;EACxE;;AAGH,SAAgB,cAAc,WAAyB;CACrD,MAAM,UAAU,KAAK,WAAW,OAAO;CACvC,MAAM,cAAc,KAAK,WAAW,eAAe;AAEnD,KAAI,WAAW,QAAQ,IAAI,CAAC,WAAW,YAAY,CAAE;CAGrD,MAAM,QADU,aAAa,aAAa,QACrB,CAAC,MAAM,KAAK;CACjC,MAAM,SAAS,YAAY,GAAG,CAAC,SAAS,YAAY;AAUpD,eAAc,SATE,MACb,KAAK,SAAS;AACb,MAAI,uBAAuB,KAAK,KAAK,CACnC,QAAO,sBAAsB;AAE/B,SAAO;GACP,CACD,KAAK,KAEsB,CAAC;AAC/B,GAAE,IAAI,KAAK,6EAA6E;;AAG1F,SAAgB,eAAe,WAAyB;CACtD,MAAM,UAAU,KAAK,WAAW,OAAO;AACvC,KAAI,CAAC,WAAW,QAAQ,CAAE;AAE1B,QAAW;EAAE,MAAM;EAAS,YAAY,QAAQ;EAAK,OAAO;EAAM,CAAC"}
|
package/dist/cli/init.cjs
CHANGED
|
@@ -271,7 +271,18 @@ function buildChildRootScripts(sections) {
|
|
|
271
271
|
release: "echo 'Packages versioned - app release handled by workflow'",
|
|
272
272
|
postinstall: "node_modules/.bin/bos types gen || true",
|
|
273
273
|
"types:gen": "node_modules/.bin/bos types gen",
|
|
274
|
-
bos: "node_modules/.bin/bos"
|
|
274
|
+
bos: "node_modules/.bin/bos",
|
|
275
|
+
"db:push": "bun run --cwd api drizzle-kit push",
|
|
276
|
+
"db:studio": "bun run --cwd api drizzle-kit studio",
|
|
277
|
+
"db:generate": "bun run --cwd api drizzle-kit generate",
|
|
278
|
+
"db:migrate": "bun run --cwd api drizzle-kit migrate",
|
|
279
|
+
test: "bun run test:api && bun run test:e2e",
|
|
280
|
+
"test:api": "cd api && bun run test tests/integration/ tests/unit/",
|
|
281
|
+
"test:integration": "cd api && bun run test tests/integration/",
|
|
282
|
+
"test:e2e": "bun run --cwd host test:e2e",
|
|
283
|
+
"dev:postgres": "docker compose up -d --wait && bun run dev",
|
|
284
|
+
"dev:postgres:down": "docker compose down",
|
|
285
|
+
"dev:postgres:reset": "docker compose down -v && docker compose up -d --wait"
|
|
275
286
|
};
|
|
276
287
|
if (sections.ui) scripts["dev:ui"] = "node_modules/.bin/bos dev --ui local --api remote";
|
|
277
288
|
if (sections.api) scripts["dev:api"] = "node_modules/.bin/bos dev --ui remote --api local";
|
|
@@ -701,8 +712,6 @@ async function scaffoldMinimalProject(destination, parentConfig, opts) {
|
|
|
701
712
|
}
|
|
702
713
|
};
|
|
703
714
|
(0, node_fs.writeFileSync)((0, node_path.join)(destination, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`);
|
|
704
|
-
const envExample = generateEnvExample(parentConfig, opts.overrides);
|
|
705
|
-
if (envExample) (0, node_fs.writeFileSync)((0, node_path.join)(destination, ".env.example"), envExample);
|
|
706
715
|
(0, node_fs.writeFileSync)((0, node_path.join)(destination, ".gitignore"), generateGitignore());
|
|
707
716
|
return 4;
|
|
708
717
|
}
|
|
@@ -781,36 +790,6 @@ async function execCommand(command, args, cwd, options) {
|
|
|
781
790
|
timeout
|
|
782
791
|
});
|
|
783
792
|
}
|
|
784
|
-
function generateEnvExample(config, overrides) {
|
|
785
|
-
const has = (section) => overrides.includes(section);
|
|
786
|
-
const lines = ["# Environment variables"];
|
|
787
|
-
const collectSecrets = (obj, includeSection, prefix = "") => {
|
|
788
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
789
|
-
if (!includeSection) continue;
|
|
790
|
-
if (key === "secrets" && Array.isArray(value)) {
|
|
791
|
-
for (const secret of value) if (typeof secret === "string") lines.push(`${secret}=`);
|
|
792
|
-
} else if (key === "variables" && isPlainObject(value)) {
|
|
793
|
-
for (const [varKey, varVal] of Object.entries(value)) if (typeof varVal === "string") lines.push(`${varKey}=${varVal}`);
|
|
794
|
-
} else if (isPlainObject(value) && key !== "extends") collectSecrets(value, includeSection, `${prefix}${key}.`);
|
|
795
|
-
}
|
|
796
|
-
};
|
|
797
|
-
if (config.app && typeof config.app === "object") {
|
|
798
|
-
const app = config.app;
|
|
799
|
-
collectSecrets(app, has("host"), "host.");
|
|
800
|
-
collectSecrets(app, has("ui"), "ui.");
|
|
801
|
-
collectSecrets(app, has("api"), "api.");
|
|
802
|
-
collectSecrets(app, has("plugins"), "auth.");
|
|
803
|
-
}
|
|
804
|
-
if (has("plugins") && config.plugins && typeof config.plugins === "object") {
|
|
805
|
-
for (const [pluginKey, pluginVal] of Object.entries(config.plugins)) if (isPlainObject(pluginVal)) collectSecrets(pluginVal, true);
|
|
806
|
-
else if (typeof pluginVal === "string") lines.push(`# Plugin '${pluginKey}' extends ${pluginVal}`);
|
|
807
|
-
}
|
|
808
|
-
lines.push("BETTER_AUTH_SECRET=generate-a-secret-here");
|
|
809
|
-
return `${lines.join("\n")}\n`;
|
|
810
|
-
}
|
|
811
|
-
function isPlainObject(value) {
|
|
812
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
813
|
-
}
|
|
814
793
|
function generateGitignore() {
|
|
815
794
|
return `node_modules/
|
|
816
795
|
dist/
|