everything-dev 1.16.1 → 1.16.2
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 +127 -0
- package/dist/cli/infra.cjs.map +1 -0
- package/dist/cli/infra.mjs +124 -0
- package/dist/cli/infra.mjs.map +1 -0
- package/dist/cli/init.cjs +9 -0
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.d.cts +2 -1
- package/dist/cli/init.d.cts.map +1 -1
- package/dist/cli/init.d.mts +2 -1
- package/dist/cli/init.d.mts.map +1 -1
- package/dist/cli/init.mjs +9 -1
- package/dist/cli/init.mjs.map +1 -1
- package/dist/cli/sync.cjs +6 -0
- package/dist/cli/sync.cjs.map +1 -1
- package/dist/cli/sync.mjs +6 -0
- package/dist/cli/sync.mjs.map +1 -1
- package/dist/contract.d.cts +2 -2
- package/dist/contract.d.mts +2 -2
- package/dist/near-cli.cjs +1 -1
- package/dist/near-cli.mjs +1 -1
- package/dist/plugin.cjs +30 -27
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +2 -2
- package/dist/plugin.d.cts.map +1 -1
- package/dist/plugin.d.mts +2 -2
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +29 -26
- 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/src/cli/infra.ts +190 -0
- package/src/cli/init.ts +4 -0
- package/src/cli/sync.ts +9 -0
- package/src/plugin.ts +36 -42
package/src/cli/infra.ts
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import * as p from "@clack/prompts";
|
|
5
|
+
import type { RuntimeConfig } from "../types";
|
|
6
|
+
|
|
7
|
+
const POSTGRES_USER = "everythingdev";
|
|
8
|
+
const POSTGRES_PASSWORD = "everythingdev";
|
|
9
|
+
const API_DATABASE_SECRET = "API_DATABASE_URL";
|
|
10
|
+
const AUTH_DATABASE_SECRET = "AUTH_DATABASE_URL";
|
|
11
|
+
const BASE_DATABASE_PORT = 5434;
|
|
12
|
+
|
|
13
|
+
interface DatabaseSecretConfig {
|
|
14
|
+
secret: string;
|
|
15
|
+
slug: string;
|
|
16
|
+
port: number;
|
|
17
|
+
serviceName: string;
|
|
18
|
+
databaseName: string;
|
|
19
|
+
volumeName: string;
|
|
20
|
+
url: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function uniqueSecrets(values: Array<string | undefined>): string[] {
|
|
24
|
+
const secrets: string[] = [];
|
|
25
|
+
const seen = new Set<string>();
|
|
26
|
+
|
|
27
|
+
for (const value of values) {
|
|
28
|
+
if (!value || seen.has(value)) continue;
|
|
29
|
+
seen.add(value);
|
|
30
|
+
secrets.push(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return secrets;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getRuntimeSecrets(runtimeConfig: RuntimeConfig): string[] {
|
|
37
|
+
const pluginSecrets = Object.values(runtimeConfig.plugins ?? {}).flatMap(
|
|
38
|
+
(plugin) => plugin.secrets ?? [],
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
return uniqueSecrets([
|
|
42
|
+
API_DATABASE_SECRET,
|
|
43
|
+
AUTH_DATABASE_SECRET,
|
|
44
|
+
...(runtimeConfig.api.secrets ?? []),
|
|
45
|
+
...(runtimeConfig.auth?.secrets ?? []),
|
|
46
|
+
...pluginSecrets,
|
|
47
|
+
]);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeDatabaseSlug(secret: string): string {
|
|
51
|
+
return secret.replace(/_DATABASE_URL$/, "").toLowerCase();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function buildDatabaseConfigs(secrets: string[]): DatabaseSecretConfig[] {
|
|
55
|
+
const databaseSecrets = uniqueSecrets(
|
|
56
|
+
secrets.filter((secret) => secret.endsWith("_DATABASE_URL")),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const additionalSecrets = databaseSecrets
|
|
60
|
+
.filter((secret) => secret !== API_DATABASE_SECRET && secret !== AUTH_DATABASE_SECRET)
|
|
61
|
+
.sort((a, b) => a.localeCompare(b));
|
|
62
|
+
|
|
63
|
+
const orderedSecrets = [API_DATABASE_SECRET, AUTH_DATABASE_SECRET, ...additionalSecrets];
|
|
64
|
+
|
|
65
|
+
return orderedSecrets.map((secret, index) => {
|
|
66
|
+
const slug = normalizeDatabaseSlug(secret);
|
|
67
|
+
const port =
|
|
68
|
+
secret === API_DATABASE_SECRET
|
|
69
|
+
? 5432
|
|
70
|
+
: secret === AUTH_DATABASE_SECRET
|
|
71
|
+
? 5433
|
|
72
|
+
: BASE_DATABASE_PORT + index - 2;
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
secret,
|
|
76
|
+
slug,
|
|
77
|
+
port,
|
|
78
|
+
serviceName: `postgres-${slug.replace(/_/g, "-")}`,
|
|
79
|
+
databaseName: `${slug}_db`,
|
|
80
|
+
volumeName: `postgres_${slug}_data`,
|
|
81
|
+
url: `postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${port}/${slug}_db`,
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function defaultSecretValue(
|
|
87
|
+
secret: string,
|
|
88
|
+
databases: Map<string, DatabaseSecretConfig>,
|
|
89
|
+
options: { forExample: boolean },
|
|
90
|
+
): string {
|
|
91
|
+
if (secret === "BETTER_AUTH_SECRET") {
|
|
92
|
+
return options.forExample ? "" : randomBytes(32).toString("base64url");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (secret === "CORS_ORIGIN") {
|
|
96
|
+
return "http://localhost:3000";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return databases.get(secret)?.url ?? "";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function renderEnvFile(
|
|
103
|
+
secrets: string[],
|
|
104
|
+
databases: DatabaseSecretConfig[],
|
|
105
|
+
options: { forExample: boolean },
|
|
106
|
+
): string {
|
|
107
|
+
const databaseMap = new Map(databases.map((entry) => [entry.secret, entry]));
|
|
108
|
+
const lines = [
|
|
109
|
+
"# Generated from configured bos secrets",
|
|
110
|
+
"# Update values as needed for your local environment",
|
|
111
|
+
"",
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
for (const secret of secrets) {
|
|
115
|
+
lines.push(`${secret}=${defaultSecretValue(secret, databaseMap, options)}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return `${lines.join("\n")}\n`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function renderDockerCompose(databases: DatabaseSecretConfig[]): string {
|
|
122
|
+
const lines = [
|
|
123
|
+
"x-pg-common: &pg-common",
|
|
124
|
+
" image: postgres:17-alpine",
|
|
125
|
+
" environment: &pg-env",
|
|
126
|
+
` POSTGRES_USER: ${POSTGRES_USER}`,
|
|
127
|
+
` POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}`,
|
|
128
|
+
" healthcheck:",
|
|
129
|
+
' test: ["CMD-SHELL", "pg_isready -U everythingdev"]',
|
|
130
|
+
" interval: 3s",
|
|
131
|
+
" timeout: 3s",
|
|
132
|
+
" retries: 5",
|
|
133
|
+
"",
|
|
134
|
+
"services:",
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
for (const database of databases) {
|
|
138
|
+
lines.push(` ${database.serviceName}:`);
|
|
139
|
+
lines.push(" <<: *pg-common");
|
|
140
|
+
lines.push(" environment:");
|
|
141
|
+
lines.push(" <<: *pg-env");
|
|
142
|
+
lines.push(` POSTGRES_DB: ${database.databaseName}`);
|
|
143
|
+
lines.push(" ports:");
|
|
144
|
+
lines.push(` - "${database.port}:5432"`);
|
|
145
|
+
lines.push(" volumes:");
|
|
146
|
+
lines.push(` - ${database.volumeName}:/var/lib/postgresql/data`);
|
|
147
|
+
lines.push("");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
lines.push("volumes:");
|
|
151
|
+
for (const database of databases) {
|
|
152
|
+
lines.push(` ${database.volumeName}:`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return `${lines.join("\n")}\n`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function writeGeneratedInfra(configDir: string, runtimeConfig: RuntimeConfig): string[] {
|
|
159
|
+
const secrets = getRuntimeSecrets(runtimeConfig);
|
|
160
|
+
const databases = buildDatabaseConfigs(secrets);
|
|
161
|
+
const envExamplePath = join(configDir, ".env.example");
|
|
162
|
+
const dockerComposePath = join(configDir, "docker-compose.yml");
|
|
163
|
+
|
|
164
|
+
writeFileSync(envExamplePath, renderEnvFile(secrets, databases, { forExample: true }));
|
|
165
|
+
writeFileSync(dockerComposePath, renderDockerCompose(databases));
|
|
166
|
+
|
|
167
|
+
return secrets;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function ensureEnvFile(configDir: string): void {
|
|
171
|
+
const envPath = join(configDir, ".env");
|
|
172
|
+
const examplePath = join(configDir, ".env.example");
|
|
173
|
+
|
|
174
|
+
if (existsSync(envPath) || !existsSync(examplePath)) return;
|
|
175
|
+
|
|
176
|
+
const content = readFileSync(examplePath, "utf-8");
|
|
177
|
+
const lines = content.split("\n");
|
|
178
|
+
const secret = randomBytes(32).toString("base64url");
|
|
179
|
+
const updated = lines
|
|
180
|
+
.map((line) => {
|
|
181
|
+
if (/^BETTER_AUTH_SECRET=/.test(line)) {
|
|
182
|
+
return `BETTER_AUTH_SECRET=${secret}`;
|
|
183
|
+
}
|
|
184
|
+
return line;
|
|
185
|
+
})
|
|
186
|
+
.join("\n");
|
|
187
|
+
|
|
188
|
+
writeFileSync(envPath, updated);
|
|
189
|
+
p.log.info("Created .env from generated .env.example with generated BETTER_AUTH_SECRET");
|
|
190
|
+
}
|
package/src/cli/init.ts
CHANGED
|
@@ -609,6 +609,10 @@ export async function runTypesGen(destination: string): Promise<void> {
|
|
|
609
609
|
await execCommand("node_modules/.bin/bos", ["types", "gen"], destination);
|
|
610
610
|
}
|
|
611
611
|
|
|
612
|
+
export async function runDockerComposeUp(destination: string): Promise<void> {
|
|
613
|
+
await execCommand("docker", ["compose", "up", "-d", "--wait"], destination);
|
|
614
|
+
}
|
|
615
|
+
|
|
612
616
|
const WORKSPACE_LOCAL_PATHS: Record<string, string> = {
|
|
613
617
|
"everything-dev": "packages/everything-dev",
|
|
614
618
|
"every-plugin": "packages/every-plugin",
|
package/src/cli/sync.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "node:fs";
|
|
10
10
|
import { dirname, join } from "node:path";
|
|
11
11
|
import { glob } from "glob";
|
|
12
|
+
import { loadConfig } from "../config";
|
|
12
13
|
import type { SyncOptions, SyncResult } from "../contract";
|
|
13
14
|
import {
|
|
14
15
|
isPlainObject as isPlainObjectFromMerge,
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
} from "../merge";
|
|
18
19
|
import type { BosPluginRef } from "../types";
|
|
19
20
|
import { isPathExcluded } from "../utils/path-match";
|
|
21
|
+
import { writeGeneratedInfra } from "./infra";
|
|
20
22
|
import {
|
|
21
23
|
personalizeConfig,
|
|
22
24
|
readTemplatekeep,
|
|
@@ -27,10 +29,12 @@ import {
|
|
|
27
29
|
import { readSnapshot, writeSnapshot } from "./snapshot";
|
|
28
30
|
|
|
29
31
|
const FRAMEWORK_OWNED_SYNC_FILES = new Set([
|
|
32
|
+
".env.example",
|
|
30
33
|
".gitignore",
|
|
31
34
|
"biome.json",
|
|
32
35
|
"bos.config.json",
|
|
33
36
|
"package.json",
|
|
37
|
+
"docker-compose.yml",
|
|
34
38
|
".github/renovate.json",
|
|
35
39
|
".github/workflows/ci.yml",
|
|
36
40
|
".github/workflows/release-sync.yml",
|
|
@@ -494,6 +498,11 @@ export async function syncTemplate(projectDir: string, options: SyncOptions): Pr
|
|
|
494
498
|
mode: "sync",
|
|
495
499
|
});
|
|
496
500
|
|
|
501
|
+
const syncedConfig = await loadConfig({ cwd: projectDir });
|
|
502
|
+
if (syncedConfig?.runtime) {
|
|
503
|
+
writeGeneratedInfra(projectDir, syncedConfig.runtime);
|
|
504
|
+
}
|
|
505
|
+
|
|
497
506
|
if (!options.noInstall) {
|
|
498
507
|
await runBunInstall(projectDir);
|
|
499
508
|
await runTypesGen(projectDir);
|
package/src/plugin.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { randomBytes } from "node:crypto";
|
|
2
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
2
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
3
|
import * as p from "@clack/prompts";
|
|
5
4
|
import { Effect } from "effect";
|
|
6
5
|
import { syncApiContractBridge } from "./api-contract";
|
|
7
6
|
import { buildRuntimeConfig, detectLocalPackages, prepareDevelopmentRuntimeConfig } from "./app";
|
|
7
|
+
import { ensureEnvFile, writeGeneratedInfra } from "./cli/infra";
|
|
8
8
|
import {
|
|
9
9
|
copyFilteredFiles,
|
|
10
10
|
fetchParentConfig,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
readTemplatekeep,
|
|
14
14
|
resolveSourceDir,
|
|
15
15
|
runBunInstall,
|
|
16
|
+
runDockerComposeUp,
|
|
16
17
|
runTypesGen,
|
|
17
18
|
writeInitSnapshot,
|
|
18
19
|
} from "./cli/init";
|
|
@@ -57,38 +58,6 @@ import { run } from "./utils/run";
|
|
|
57
58
|
import { saveBosConfig } from "./utils/save-config";
|
|
58
59
|
import { colors } from "./utils/theme";
|
|
59
60
|
|
|
60
|
-
function ensureEnvFile(configDir: string, opts?: { domain?: string }): void {
|
|
61
|
-
const envPath = join(configDir, ".env");
|
|
62
|
-
const examplePath = join(configDir, ".env.example");
|
|
63
|
-
|
|
64
|
-
if (existsSync(envPath)) return;
|
|
65
|
-
|
|
66
|
-
if (!existsSync(examplePath)) return;
|
|
67
|
-
|
|
68
|
-
const content = readFileSync(examplePath, "utf-8");
|
|
69
|
-
const lines = content.split("\n");
|
|
70
|
-
|
|
71
|
-
const secret = randomBytes(32).toString("base64url");
|
|
72
|
-
const corsOrigin = opts?.domain
|
|
73
|
-
? `http://localhost:3000,https://${opts.domain}`
|
|
74
|
-
: "http://localhost:3000";
|
|
75
|
-
|
|
76
|
-
const updated = lines
|
|
77
|
-
.map((line) => {
|
|
78
|
-
if (/^BETTER_AUTH_SECRET=/.test(line)) {
|
|
79
|
-
return `BETTER_AUTH_SECRET=${secret}`;
|
|
80
|
-
}
|
|
81
|
-
if (/^CORS_ORIGIN=/.test(line)) {
|
|
82
|
-
return `CORS_ORIGIN=${corsOrigin}`;
|
|
83
|
-
}
|
|
84
|
-
return line;
|
|
85
|
-
})
|
|
86
|
-
.join("\n");
|
|
87
|
-
|
|
88
|
-
writeFileSync(envPath, updated);
|
|
89
|
-
p.log.info(`Created .env from .env.example with generated BETTER_AUTH_SECRET`);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
61
|
const buildCommands: Record<string, { cmd: string; args: string[] }> = {
|
|
93
62
|
host: { cmd: "bun", args: ["run", "build"] },
|
|
94
63
|
ui: { cmd: "bun", args: ["run", "build"] },
|
|
@@ -1342,6 +1311,7 @@ export default createPlugin({
|
|
|
1342
1311
|
}
|
|
1343
1312
|
|
|
1344
1313
|
directory = directory || domain || extendsGateway;
|
|
1314
|
+
const targetDir = resolve(directory);
|
|
1345
1315
|
plugins = plugins ?? [];
|
|
1346
1316
|
|
|
1347
1317
|
if (!parentConfig) {
|
|
@@ -1405,13 +1375,13 @@ export default createPlugin({
|
|
|
1405
1375
|
const s = p.spinner();
|
|
1406
1376
|
s.start("Setting up project");
|
|
1407
1377
|
|
|
1408
|
-
const filesCopied = await copyFilteredFiles(sourceDir,
|
|
1378
|
+
const filesCopied = await copyFilteredFiles(sourceDir, targetDir, patterns, {
|
|
1409
1379
|
withHost,
|
|
1410
1380
|
plugins,
|
|
1411
1381
|
pluginRoutes,
|
|
1412
1382
|
});
|
|
1413
1383
|
|
|
1414
|
-
await personalizeConfig(
|
|
1384
|
+
await personalizeConfig(targetDir, {
|
|
1415
1385
|
extendsAccount,
|
|
1416
1386
|
extendsGateway,
|
|
1417
1387
|
account: account || extendsAccount,
|
|
@@ -1422,27 +1392,51 @@ export default createPlugin({
|
|
|
1422
1392
|
withHost,
|
|
1423
1393
|
});
|
|
1424
1394
|
|
|
1425
|
-
await writeInitSnapshot(
|
|
1395
|
+
await writeInitSnapshot(targetDir, extendsAccount, extendsGateway, sourceDir, patterns, {
|
|
1426
1396
|
withHost,
|
|
1427
1397
|
plugins,
|
|
1428
1398
|
pluginRoutes,
|
|
1429
1399
|
});
|
|
1430
1400
|
|
|
1431
|
-
|
|
1401
|
+
const initConfig = await loadConfig({ cwd: targetDir });
|
|
1402
|
+
if (initConfig?.runtime) {
|
|
1403
|
+
writeGeneratedInfra(targetDir, initConfig.runtime);
|
|
1404
|
+
}
|
|
1405
|
+
ensureEnvFile(targetDir);
|
|
1432
1406
|
|
|
1433
1407
|
if (!input.noInstall) {
|
|
1434
|
-
await runBunInstall(
|
|
1435
|
-
await runTypesGen(
|
|
1436
|
-
await generateDatabaseMigrations(
|
|
1408
|
+
await runBunInstall(targetDir);
|
|
1409
|
+
await runTypesGen(targetDir);
|
|
1410
|
+
await generateDatabaseMigrations(targetDir);
|
|
1437
1411
|
}
|
|
1438
1412
|
|
|
1439
|
-
const initConfig = await loadConfig({ cwd: directory });
|
|
1440
1413
|
if (initConfig?.config) {
|
|
1441
|
-
await generateCodeArtifacts(
|
|
1414
|
+
await generateCodeArtifacts(targetDir, initConfig.config);
|
|
1442
1415
|
}
|
|
1443
1416
|
|
|
1444
1417
|
s.stop("Project initialized");
|
|
1445
1418
|
|
|
1419
|
+
if (!input.noInteractive) {
|
|
1420
|
+
const shouldStartDocker = await p.confirm({
|
|
1421
|
+
message: "Run docker compose up -d --wait?",
|
|
1422
|
+
initialValue: true,
|
|
1423
|
+
});
|
|
1424
|
+
|
|
1425
|
+
if (shouldStartDocker === true) {
|
|
1426
|
+
const dockerSpinner = p.spinner();
|
|
1427
|
+
dockerSpinner.start("Starting Docker services");
|
|
1428
|
+
try {
|
|
1429
|
+
await runDockerComposeUp(targetDir);
|
|
1430
|
+
dockerSpinner.stop("Docker services ready");
|
|
1431
|
+
} catch (error) {
|
|
1432
|
+
dockerSpinner.stop("Docker services not started");
|
|
1433
|
+
p.log.warn(
|
|
1434
|
+
`docker compose up -d --wait failed: ${error instanceof Error ? error.message : error}`,
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1446
1440
|
return {
|
|
1447
1441
|
status: "initialized" as const,
|
|
1448
1442
|
directory,
|