everything-dev 1.16.0 → 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/cli/upgrade.cjs +75 -7
- package/dist/cli/upgrade.cjs.map +1 -1
- package/dist/cli/upgrade.mjs +74 -8
- package/dist/cli/upgrade.mjs.map +1 -1
- package/dist/cli.cjs +2 -0
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +2 -0
- package/dist/cli.mjs.map +1 -1
- package/dist/config.cjs +2 -24
- package/dist/config.cjs.map +1 -1
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +3 -25
- package/dist/config.mjs.map +1 -1
- package/dist/contract.cjs +2 -0
- package/dist/contract.cjs.map +1 -1
- package/dist/contract.d.cts +6 -2
- package/dist/contract.d.cts.map +1 -1
- package/dist/contract.d.mts +6 -2
- package/dist/contract.d.mts.map +1 -1
- package/dist/contract.meta.cjs +2 -2
- package/dist/contract.meta.cjs.map +1 -1
- package/dist/contract.meta.d.cts +2 -2
- package/dist/contract.meta.d.mts +2 -2
- package/dist/contract.meta.mjs +2 -2
- package/dist/contract.meta.mjs.map +1 -1
- package/dist/contract.mjs +2 -0
- package/dist/contract.mjs.map +1 -1
- package/dist/fastkv.cjs +0 -45
- package/dist/fastkv.cjs.map +1 -1
- package/dist/fastkv.d.cts +1 -19
- package/dist/fastkv.d.cts.map +1 -1
- package/dist/fastkv.d.mts +1 -19
- package/dist/fastkv.d.mts.map +1 -1
- package/dist/fastkv.mjs +1 -44
- package/dist/fastkv.mjs.map +1 -1
- package/dist/index.cjs +0 -2
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/merge.cjs +4 -11
- package/dist/merge.cjs.map +1 -1
- package/dist/merge.d.cts.map +1 -1
- package/dist/merge.d.mts.map +1 -1
- package/dist/merge.mjs +4 -11
- package/dist/merge.mjs.map +1 -1
- package/dist/near-cli.cjs +1 -1
- package/dist/near-cli.mjs +1 -1
- package/dist/plugin.cjs +51 -58
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +4 -2
- package/dist/plugin.d.cts.map +1 -1
- package/dist/plugin.d.mts +4 -2
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +50 -57
- 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/cli/upgrade.ts +117 -6
- package/src/cli.ts +6 -0
- package/src/config.ts +5 -34
- package/src/contract.meta.ts +2 -2
- package/src/contract.ts +2 -0
- package/src/fastkv.ts +0 -72
- package/src/merge.ts +6 -19
- package/src/plugin.ts +51 -70
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/cli/upgrade.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { existsSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import * as p from "@clack/prompts";
|
|
3
5
|
import { glob } from "glob";
|
|
4
6
|
import type { UpgradeOptions, UpgradeResult } from "../contract";
|
|
7
|
+
import { resolveExtendsRef } from "../merge";
|
|
8
|
+
import { saveBosConfig } from "../utils/save-config";
|
|
5
9
|
import { readInstalledFrameworkVersion } from "./framework-version";
|
|
6
|
-
import { runBunInstall, runTypesGen } from "./init";
|
|
10
|
+
import { fetchParentConfig, runBunInstall, runTypesGen } from "./init";
|
|
7
11
|
import { syncTemplate } from "./sync";
|
|
8
12
|
|
|
9
13
|
const FRAMEWORK_PACKAGES = ["everything-dev", "every-plugin"];
|
|
@@ -47,6 +51,105 @@ interface NpmPackageInfo {
|
|
|
47
51
|
version: string;
|
|
48
52
|
}
|
|
49
53
|
|
|
54
|
+
function getExtendsRef(config: Record<string, unknown>): string | undefined {
|
|
55
|
+
if (typeof config.extends === "string") {
|
|
56
|
+
return config.extends;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (config.extends && typeof config.extends === "object") {
|
|
60
|
+
return resolveExtendsRef(config.extends as Record<string, string>, "production");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function parseBosRef(ref: string): { account: string; gateway: string } | null {
|
|
67
|
+
const match = ref.match(/^bos:\/\/([^/]+)\/(.+)$/);
|
|
68
|
+
if (!match?.[1] || !match[2]) return null;
|
|
69
|
+
return { account: match[1], gateway: match[2] };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function loadParentPluginOptions(projectDir: string): Promise<{
|
|
73
|
+
localConfig: Record<string, unknown>;
|
|
74
|
+
parentPlugins: Record<string, unknown>;
|
|
75
|
+
newPluginKeys: string[];
|
|
76
|
+
} | null> {
|
|
77
|
+
const configPath = join(projectDir, "bos.config.json");
|
|
78
|
+
if (!existsSync(configPath)) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const localConfig = JSON.parse(readFileSync(configPath, "utf-8")) as Record<string, unknown>;
|
|
83
|
+
const extendsRef = getExtendsRef(localConfig);
|
|
84
|
+
if (!extendsRef?.startsWith("bos://")) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const parsed = parseBosRef(extendsRef);
|
|
89
|
+
if (!parsed) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let parentConfig: Record<string, unknown>;
|
|
94
|
+
try {
|
|
95
|
+
parentConfig = await fetchParentConfig(parsed.account, parsed.gateway);
|
|
96
|
+
} catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const parentPlugins =
|
|
101
|
+
parentConfig.plugins && typeof parentConfig.plugins === "object"
|
|
102
|
+
? (parentConfig.plugins as Record<string, unknown>)
|
|
103
|
+
: {};
|
|
104
|
+
const localPlugins =
|
|
105
|
+
localConfig.plugins && typeof localConfig.plugins === "object"
|
|
106
|
+
? (localConfig.plugins as Record<string, unknown>)
|
|
107
|
+
: {};
|
|
108
|
+
|
|
109
|
+
const newPluginKeys = Object.keys(parentPlugins).filter((key) => !(key in localPlugins));
|
|
110
|
+
return { localConfig, parentPlugins, newPluginKeys };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function addSelectedParentPlugins(projectDir: string): Promise<string[]> {
|
|
114
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const pluginOptions = await loadParentPluginOptions(projectDir);
|
|
119
|
+
if (!pluginOptions || pluginOptions.newPluginKeys.length === 0) {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const selectedValue = await p.multiselect({
|
|
124
|
+
message: "Select new plugins from parent:",
|
|
125
|
+
options: pluginOptions.newPluginKeys.map((key) => ({ value: key, label: key })),
|
|
126
|
+
required: false,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (p.isCancel(selectedValue)) {
|
|
130
|
+
process.exit(0);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const selected = selectedValue as string[];
|
|
134
|
+
if (selected.length === 0) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const localPlugins =
|
|
139
|
+
pluginOptions.localConfig.plugins && typeof pluginOptions.localConfig.plugins === "object"
|
|
140
|
+
? (pluginOptions.localConfig.plugins as Record<string, unknown>)
|
|
141
|
+
: {};
|
|
142
|
+
const nextPlugins = { ...localPlugins };
|
|
143
|
+
for (const key of selected) {
|
|
144
|
+
nextPlugins[key] = pluginOptions.parentPlugins[key];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
pluginOptions.localConfig.plugins = nextPlugins;
|
|
148
|
+
await saveBosConfig(projectDir, pluginOptions.localConfig);
|
|
149
|
+
|
|
150
|
+
return selected;
|
|
151
|
+
}
|
|
152
|
+
|
|
50
153
|
async function fetchLatestNpmVersion(packageName: string): Promise<string | null> {
|
|
51
154
|
try {
|
|
52
155
|
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
|
|
@@ -269,6 +372,7 @@ export async function upgradeTemplate(
|
|
|
269
372
|
|
|
270
373
|
if (options.dryRun) {
|
|
271
374
|
let changelogUrl: string | undefined;
|
|
375
|
+
const pluginOptions = options.noSync ? null : await loadParentPluginOptions(projectDir);
|
|
272
376
|
if (hasUpdates) {
|
|
273
377
|
const configPath = join(projectDir, "bos.config.json");
|
|
274
378
|
let parentConfig: Record<string, unknown> | null = null;
|
|
@@ -289,6 +393,7 @@ export async function upgradeTemplate(
|
|
|
289
393
|
...packages,
|
|
290
394
|
...catalogVersionUpdates.map((u) => ({ name: u.name, from: u.from, to: u.to })),
|
|
291
395
|
],
|
|
396
|
+
availablePlugins: pluginOptions?.newPluginKeys,
|
|
292
397
|
changelogUrl,
|
|
293
398
|
};
|
|
294
399
|
}
|
|
@@ -315,13 +420,13 @@ export async function upgradeTemplate(
|
|
|
315
420
|
}
|
|
316
421
|
}
|
|
317
422
|
|
|
318
|
-
if (hasUpdates && !options.noInstall) {
|
|
319
|
-
await runBunInstall(projectDir);
|
|
320
|
-
await runTypesGen(projectDir);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
423
|
let syncResult: UpgradeResult["sync"];
|
|
424
|
+
let addedPlugins: string[] = [];
|
|
324
425
|
if (!options.noSync) {
|
|
426
|
+
if (!options.dryRun) {
|
|
427
|
+
addedPlugins = await addSelectedParentPlugins(projectDir);
|
|
428
|
+
}
|
|
429
|
+
|
|
325
430
|
syncResult = await syncTemplate(projectDir, {
|
|
326
431
|
dryRun: false,
|
|
327
432
|
force: options.force,
|
|
@@ -329,6 +434,11 @@ export async function upgradeTemplate(
|
|
|
329
434
|
});
|
|
330
435
|
}
|
|
331
436
|
|
|
437
|
+
if ((hasUpdates || addedPlugins.length > 0) && !options.noInstall) {
|
|
438
|
+
await runBunInstall(projectDir);
|
|
439
|
+
await runTypesGen(projectDir);
|
|
440
|
+
}
|
|
441
|
+
|
|
332
442
|
const migratedFiles = await rewriteLegacyUiImports(projectDir);
|
|
333
443
|
for (const file of OBSOLETE_FILES) {
|
|
334
444
|
const filePath = join(projectDir, file);
|
|
@@ -359,6 +469,7 @@ export async function upgradeTemplate(
|
|
|
359
469
|
],
|
|
360
470
|
sync: syncResult,
|
|
361
471
|
migrated: migratedFiles.length > 0 ? migratedFiles : undefined,
|
|
472
|
+
selectedPlugins: addedPlugins.length > 0 ? addedPlugins : undefined,
|
|
362
473
|
changelogUrl,
|
|
363
474
|
};
|
|
364
475
|
}
|
package/src/cli.ts
CHANGED
|
@@ -240,6 +240,12 @@ async function main() {
|
|
|
240
240
|
if (result.changelogUrl) {
|
|
241
241
|
console.log(` ${colors.dim("Changelog:")} ${result.changelogUrl}`);
|
|
242
242
|
}
|
|
243
|
+
if (result.availablePlugins && result.availablePlugins.length > 0) {
|
|
244
|
+
console.log(` ${colors.dim("New parent plugins:")} ${result.availablePlugins.join(", ")}`);
|
|
245
|
+
}
|
|
246
|
+
if (result.selectedPlugins && result.selectedPlugins.length > 0) {
|
|
247
|
+
console.log(` ${colors.dim("Added plugins:")} ${result.selectedPlugins.join(", ")}`);
|
|
248
|
+
}
|
|
243
249
|
if (result.sync) {
|
|
244
250
|
const sync = result.sync;
|
|
245
251
|
if (sync.updated.length > 0) {
|
package/src/config.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
-
import { fetchBosConfigFromFastKv
|
|
3
|
+
import { fetchBosConfigFromFastKv } from "./fastkv";
|
|
4
4
|
import {
|
|
5
5
|
type BosEnv,
|
|
6
6
|
isPlainObject,
|
|
@@ -546,31 +546,6 @@ async function resolveRemotePluginRuntimeName(baseUrl: string, fallback: string)
|
|
|
546
546
|
}
|
|
547
547
|
}
|
|
548
548
|
|
|
549
|
-
interface ResolvedBosPlugin {
|
|
550
|
-
url: string;
|
|
551
|
-
integrity?: string;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
async function resolveBosPluginUrl(bosUrl: string): Promise<ResolvedBosPlugin | null> {
|
|
555
|
-
const parsed = parsePluginBosUrl(bosUrl);
|
|
556
|
-
if (!parsed) return null;
|
|
557
|
-
|
|
558
|
-
try {
|
|
559
|
-
const entry = await fetchPluginFromRegistry(parsed.accountId, parsed.pluginName);
|
|
560
|
-
if (!entry) return null;
|
|
561
|
-
|
|
562
|
-
const cdnUrl = entry.metadata.cdnUrl;
|
|
563
|
-
if (!cdnUrl) return null;
|
|
564
|
-
|
|
565
|
-
return {
|
|
566
|
-
url: cdnUrl,
|
|
567
|
-
integrity: entry.metadata.integrity ?? undefined,
|
|
568
|
-
};
|
|
569
|
-
} catch {
|
|
570
|
-
return null;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
549
|
async function buildRuntimePluginConfig(
|
|
575
550
|
pluginId: string,
|
|
576
551
|
config: BosConfigInput,
|
|
@@ -586,16 +561,12 @@ async function buildRuntimePluginConfig(
|
|
|
586
561
|
const sourceProduction = typeof source.production === "string" ? source.production : undefined;
|
|
587
562
|
const proxy = typeof apiConfig.proxy === "string" ? apiConfig.proxy : undefined;
|
|
588
563
|
const development = apiDevelopment ?? sourceDevelopment;
|
|
589
|
-
|
|
564
|
+
const production = apiProduction ?? sourceProduction;
|
|
590
565
|
|
|
591
566
|
if (production?.startsWith("bos://")) {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
if (resolved.integrity && env === "production") {
|
|
596
|
-
source.integrity = resolved.integrity;
|
|
597
|
-
}
|
|
598
|
-
}
|
|
567
|
+
throw new Error(
|
|
568
|
+
`Plugin "${pluginId}" has unsupported production target "${production}". Use extends: "bos://account/domain" for plugin configs or a CDN URL for production.`,
|
|
569
|
+
);
|
|
599
570
|
}
|
|
600
571
|
|
|
601
572
|
const runtimeTarget =
|
package/src/contract.meta.ts
CHANGED
|
@@ -44,7 +44,7 @@ export const cliCommandMeta = {
|
|
|
44
44
|
fields: {
|
|
45
45
|
source: {
|
|
46
46
|
positional: true,
|
|
47
|
-
description: "Plugin source (local:path, bos://account/
|
|
47
|
+
description: "Plugin source (local:path, bos://account/domain, or URL)",
|
|
48
48
|
},
|
|
49
49
|
as: { description: "Plugin alias" },
|
|
50
50
|
production: { description: "Production URL override" },
|
|
@@ -113,7 +113,7 @@ export const cliCommandMeta = {
|
|
|
113
113
|
upgrade: {
|
|
114
114
|
commandPath: ["upgrade"],
|
|
115
115
|
summary: "Upgrade framework packages and sync template files",
|
|
116
|
-
interactive:
|
|
116
|
+
interactive: true,
|
|
117
117
|
fields: {
|
|
118
118
|
dryRun: { description: "Preview changes without writing" },
|
|
119
119
|
force: { description: "Overwrite user-modified files during sync" },
|
package/src/contract.ts
CHANGED
|
@@ -201,6 +201,8 @@ export const UpgradeResultSchema = z.object({
|
|
|
201
201
|
),
|
|
202
202
|
sync: SyncResultSchema.optional(),
|
|
203
203
|
migrated: z.array(z.string()).optional(),
|
|
204
|
+
availablePlugins: z.array(z.string()).optional(),
|
|
205
|
+
selectedPlugins: z.array(z.string()).optional(),
|
|
204
206
|
changelogUrl: z.string().optional(),
|
|
205
207
|
error: z.string().optional(),
|
|
206
208
|
});
|
package/src/fastkv.ts
CHANGED
|
@@ -139,78 +139,6 @@ export interface PluginManifest {
|
|
|
139
139
|
additionalExports?: Array<{ path: string; exports: string[]; sha256: string }>;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
export interface PluginMetadata {
|
|
143
|
-
title: string | null;
|
|
144
|
-
description: string | null;
|
|
145
|
-
repoUrl: string | null;
|
|
146
|
-
version: string;
|
|
147
|
-
publishedAt: string;
|
|
148
|
-
cdnUrl: string;
|
|
149
|
-
integrity: string | null;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export interface PluginRegistryEntry {
|
|
153
|
-
manifest: PluginManifest;
|
|
154
|
-
metadata: PluginMetadata;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export function parsePluginBosUrl(
|
|
158
|
-
source: string,
|
|
159
|
-
): { accountId: string; pluginName: string } | null {
|
|
160
|
-
if (!source.startsWith("bos://")) return null;
|
|
161
|
-
const match = source.match(/^bos:\/\/([^/]+)\/plugins\/([^/]+)$/);
|
|
162
|
-
if (!match?.[1] || !match[2]) return null;
|
|
163
|
-
return { accountId: match[1], pluginName: match[2] };
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async function fetchKvValue(accountId: string, key: string): Promise<unknown | null> {
|
|
167
|
-
const payload = await fetchJson<FastKvListResponse>(
|
|
168
|
-
`${getFastKvBaseUrlForAccount(accountId)}/v0/latest/${encodeURIComponent(getRegistryNamespaceForAccount(accountId))}/${encodeURIComponent(accountId)}`,
|
|
169
|
-
{
|
|
170
|
-
method: "POST",
|
|
171
|
-
body: JSON.stringify({ key, limit: 1 }),
|
|
172
|
-
},
|
|
173
|
-
);
|
|
174
|
-
const value = payload?.entries?.find(Boolean)?.value;
|
|
175
|
-
if (value == null) return null;
|
|
176
|
-
if (typeof value === "string") {
|
|
177
|
-
try {
|
|
178
|
-
return JSON.parse(value);
|
|
179
|
-
} catch {
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return value;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export async function fetchPluginFromRegistry(
|
|
187
|
-
accountId: string,
|
|
188
|
-
pluginName: string,
|
|
189
|
-
): Promise<PluginRegistryEntry | null> {
|
|
190
|
-
const manifestKey = `plugins/${accountId}/${pluginName}/manifest.json`;
|
|
191
|
-
const metadataKey = `plugins/${accountId}/${pluginName}/metadata`;
|
|
192
|
-
|
|
193
|
-
const [rawManifest, rawMetadata] = await Promise.all([
|
|
194
|
-
fetchKvValue(accountId, manifestKey),
|
|
195
|
-
fetchKvValue(accountId, metadataKey),
|
|
196
|
-
]);
|
|
197
|
-
|
|
198
|
-
if (!rawManifest || typeof rawManifest !== "object") return null;
|
|
199
|
-
|
|
200
|
-
return {
|
|
201
|
-
manifest: rawManifest as PluginManifest,
|
|
202
|
-
metadata: (rawMetadata ?? {
|
|
203
|
-
title: null,
|
|
204
|
-
description: null,
|
|
205
|
-
repoUrl: null,
|
|
206
|
-
version: "",
|
|
207
|
-
publishedAt: "",
|
|
208
|
-
cdnUrl: "",
|
|
209
|
-
integrity: null,
|
|
210
|
-
}) as PluginMetadata,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
142
|
export async function fetchRemotePluginManifest(cdnUrl: string): Promise<PluginManifest | null> {
|
|
215
143
|
try {
|
|
216
144
|
const baseUrl = cdnUrl.replace(/\/$/, "");
|
package/src/merge.ts
CHANGED
|
@@ -95,28 +95,15 @@ export function mergeBosConfigWithExtends(
|
|
|
95
95
|
parent: BosConfigInput,
|
|
96
96
|
child: BosConfigInput,
|
|
97
97
|
): BosConfigInput {
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
for (const [key, rawValue] of Object.entries(child.plugins)) {
|
|
103
|
-
const value = rawValue as unknown;
|
|
104
|
-
if (value === null || value === false) {
|
|
105
|
-
delete plugins[key];
|
|
106
|
-
} else if (isPlainObject(plugins[key]) && isPlainObject(value)) {
|
|
107
|
-
plugins[key] = bosConfigMerger(
|
|
108
|
-
value as Record<string, unknown>,
|
|
109
|
-
plugins[key] as Record<string, unknown>,
|
|
110
|
-
);
|
|
111
|
-
} else {
|
|
112
|
-
plugins[key] = value;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
(merged as Record<string, unknown>).plugins = plugins;
|
|
116
|
-
} else if (child.plugins !== undefined) {
|
|
98
|
+
const { plugins: _ignoredParentPlugins, ...parentWithoutPlugins } = parent;
|
|
99
|
+
const merged = bosConfigMerger(child, parentWithoutPlugins) as BosConfigInput;
|
|
100
|
+
|
|
101
|
+
if (child.plugins !== undefined && isPlainObject(child.plugins)) {
|
|
117
102
|
(merged as Record<string, unknown>).plugins = cleanNullSentinels(
|
|
118
103
|
child.plugins as Record<string, unknown>,
|
|
119
104
|
);
|
|
105
|
+
} else {
|
|
106
|
+
delete (merged as Record<string, unknown>).plugins;
|
|
120
107
|
}
|
|
121
108
|
|
|
122
109
|
const mergedRecord = merged as Record<string, unknown>;
|