everything-dev 1.4.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/init.cjs +78 -8
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.d.cts +6 -0
- package/dist/cli/init.d.cts.map +1 -1
- package/dist/cli/init.d.mts +6 -0
- package/dist/cli/init.d.mts.map +1 -1
- package/dist/cli/init.mjs +78 -8
- package/dist/cli/init.mjs.map +1 -1
- package/dist/cli/prompts.cjs +64 -6
- package/dist/cli/prompts.cjs.map +1 -1
- package/dist/cli/prompts.mjs +64 -6
- package/dist/cli/prompts.mjs.map +1 -1
- package/dist/cli/sync.cjs +85 -18
- package/dist/cli/sync.cjs.map +1 -1
- package/dist/cli/sync.mjs +85 -18
- package/dist/cli/sync.mjs.map +1 -1
- package/dist/cli.cjs +41 -4
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +41 -4
- package/dist/cli.mjs.map +1 -1
- package/dist/contract.cjs +3 -0
- package/dist/contract.cjs.map +1 -1
- package/dist/contract.d.cts +14 -6
- package/dist/contract.d.cts.map +1 -1
- package/dist/contract.d.mts +14 -6
- package/dist/contract.d.mts.map +1 -1
- package/dist/contract.mjs +3 -0
- package/dist/contract.mjs.map +1 -1
- package/dist/plugin.cjs +48 -17
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +9 -4
- package/dist/plugin.d.mts +9 -4
- package/dist/plugin.mjs +48 -17
- package/dist/plugin.mjs.map +1 -1
- package/dist/types.cjs +2 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +4 -2
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.mts +4 -2
- package/dist/types.d.mts.map +1 -1
- package/dist/types.mjs +2 -1
- package/dist/types.mjs.map +1 -1
- package/package.json +2 -2
- package/src/cli/init.ts +122 -7
- package/src/cli/prompts.ts +84 -10
- package/src/cli/sync.ts +142 -17
- package/src/cli.ts +55 -4
- package/src/contract.ts +3 -0
- package/src/plugin.ts +51 -18
- package/src/types.ts +1 -0
package/src/cli/init.ts
CHANGED
|
@@ -148,7 +148,7 @@ export async function copyFilteredFiles(
|
|
|
148
148
|
sourceDir: string,
|
|
149
149
|
destination: string,
|
|
150
150
|
patterns: string[],
|
|
151
|
-
options: { withHost: boolean },
|
|
151
|
+
options: { withHost: boolean; plugins?: string[]; pluginRoutes?: Record<string, string[]> },
|
|
152
152
|
): Promise<number> {
|
|
153
153
|
if (patterns.length === 0) {
|
|
154
154
|
return 0;
|
|
@@ -158,8 +158,24 @@ export async function copyFilteredFiles(
|
|
|
158
158
|
? [...patterns, "host/**"]
|
|
159
159
|
: patterns.filter((p) => !p.startsWith("host/") && p !== "host/**");
|
|
160
160
|
|
|
161
|
+
const filteredPatterns = effectivePatterns.filter((p) => {
|
|
162
|
+
const pluginMatch = p.match(/^plugins\/([^/]+)/);
|
|
163
|
+
if (!pluginMatch) return true;
|
|
164
|
+
const pluginName = pluginMatch[1];
|
|
165
|
+
return options.plugins?.includes(pluginName) ?? true;
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const excludedRoutePatterns: string[] = [];
|
|
169
|
+
if (options.pluginRoutes) {
|
|
170
|
+
for (const [pluginKey, routePatterns] of Object.entries(options.pluginRoutes)) {
|
|
171
|
+
if (!(options.plugins?.includes(pluginKey) ?? true)) {
|
|
172
|
+
excludedRoutePatterns.push(...routePatterns);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
161
177
|
const allFiles = new Set<string>();
|
|
162
|
-
for (const pattern of
|
|
178
|
+
for (const pattern of filteredPatterns) {
|
|
163
179
|
const matches = await glob(pattern, {
|
|
164
180
|
cwd: sourceDir,
|
|
165
181
|
nodir: true,
|
|
@@ -167,10 +183,38 @@ export async function copyFilteredFiles(
|
|
|
167
183
|
absolute: false,
|
|
168
184
|
});
|
|
169
185
|
for (const match of matches) {
|
|
186
|
+
const pluginMatch = match.match(/^plugins\/([^/]+)/);
|
|
187
|
+
if (pluginMatch) {
|
|
188
|
+
const pluginName = pluginMatch[1];
|
|
189
|
+
if (!(options.plugins?.includes(pluginName) ?? true)) continue;
|
|
190
|
+
}
|
|
191
|
+
if (isRouteExcluded(match, excludedRoutePatterns)) continue;
|
|
170
192
|
allFiles.add(match);
|
|
171
193
|
}
|
|
172
194
|
}
|
|
173
195
|
|
|
196
|
+
const routeFiles = new Set<string>();
|
|
197
|
+
if (options.pluginRoutes) {
|
|
198
|
+
for (const [pluginKey, routePatterns] of Object.entries(options.pluginRoutes)) {
|
|
199
|
+
if (!(options.plugins?.includes(pluginKey) ?? true)) continue;
|
|
200
|
+
for (const rp of routePatterns) {
|
|
201
|
+
const matches = await glob(rp, {
|
|
202
|
+
cwd: sourceDir,
|
|
203
|
+
nodir: true,
|
|
204
|
+
dot: true,
|
|
205
|
+
absolute: false,
|
|
206
|
+
});
|
|
207
|
+
for (const match of matches) {
|
|
208
|
+
if (!isRouteExcluded(match, excludedRoutePatterns)) {
|
|
209
|
+
routeFiles.add(match);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
for (const f of routeFiles) allFiles.add(f);
|
|
217
|
+
|
|
174
218
|
mkdirSync(destination, { recursive: true });
|
|
175
219
|
|
|
176
220
|
let count = 0;
|
|
@@ -179,7 +223,10 @@ export async function copyFilteredFiles(
|
|
|
179
223
|
const stat = lstatSync(src);
|
|
180
224
|
if (!stat.isFile()) continue;
|
|
181
225
|
|
|
182
|
-
const
|
|
226
|
+
const destPath = filePath.startsWith(".templates/")
|
|
227
|
+
? filePath.slice(".templates/".length)
|
|
228
|
+
: filePath;
|
|
229
|
+
const dest = join(destination, destPath);
|
|
183
230
|
mkdirSync(dirname(dest), { recursive: true });
|
|
184
231
|
const content = readFileSync(src);
|
|
185
232
|
writeFileSync(dest, content);
|
|
@@ -189,6 +236,19 @@ export async function copyFilteredFiles(
|
|
|
189
236
|
return count;
|
|
190
237
|
}
|
|
191
238
|
|
|
239
|
+
function isRouteExcluded(filePath: string, excludedPatterns: string[]): boolean {
|
|
240
|
+
if (excludedPatterns.length === 0) return false;
|
|
241
|
+
for (const pattern of excludedPatterns) {
|
|
242
|
+
if (pattern.endsWith("/**")) {
|
|
243
|
+
const prefix = pattern.slice(0, -3);
|
|
244
|
+
if (filePath.startsWith(`${prefix}/`) || filePath === prefix) return true;
|
|
245
|
+
} else if (filePath === pattern || filePath.startsWith(`${pattern}/`)) {
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
|
|
192
252
|
export async function personalizeConfig(
|
|
193
253
|
destination: string,
|
|
194
254
|
opts: {
|
|
@@ -196,6 +256,8 @@ export async function personalizeConfig(
|
|
|
196
256
|
extendsGateway: string;
|
|
197
257
|
account?: string;
|
|
198
258
|
domain?: string;
|
|
259
|
+
plugins?: string[];
|
|
260
|
+
pluginRoutes?: Record<string, string[]>;
|
|
199
261
|
workspaceOpts?: { localOverrides?: boolean; sourceDir?: string };
|
|
200
262
|
},
|
|
201
263
|
): Promise<void> {
|
|
@@ -228,6 +290,15 @@ export async function personalizeConfig(
|
|
|
228
290
|
|
|
229
291
|
if (config.plugins && typeof config.plugins === "object") {
|
|
230
292
|
const plugins = config.plugins as Record<string, unknown>;
|
|
293
|
+
|
|
294
|
+
if (opts.plugins && opts.plugins.length > 0) {
|
|
295
|
+
for (const pluginKey of Object.keys(plugins)) {
|
|
296
|
+
if (!opts.plugins.includes(pluginKey)) {
|
|
297
|
+
delete plugins[pluginKey];
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
231
302
|
for (const pluginKey of Object.keys(plugins)) {
|
|
232
303
|
const plugin = plugins[pluginKey];
|
|
233
304
|
if (plugin && typeof plugin === "object") {
|
|
@@ -236,6 +307,10 @@ export async function personalizeConfig(
|
|
|
236
307
|
delete p.productionIntegrity;
|
|
237
308
|
}
|
|
238
309
|
}
|
|
310
|
+
|
|
311
|
+
if (Object.keys(plugins).length === 0) {
|
|
312
|
+
delete config.plugins;
|
|
313
|
+
}
|
|
239
314
|
}
|
|
240
315
|
|
|
241
316
|
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
@@ -248,7 +323,13 @@ export async function personalizeConfig(
|
|
|
248
323
|
if (pkg.workspaces && typeof pkg.workspaces === "object") {
|
|
249
324
|
const ws = pkg.workspaces as { packages?: string[] };
|
|
250
325
|
if (Array.isArray(ws.packages)) {
|
|
251
|
-
ws.packages = ws.packages
|
|
326
|
+
ws.packages = ws.packages
|
|
327
|
+
.filter((p: string) => p !== "host" && !p.startsWith("packages/"))
|
|
328
|
+
.filter((p: string) => {
|
|
329
|
+
const pluginMatch = p.match(/^plugins\/([^/]+)/);
|
|
330
|
+
if (!pluginMatch) return true;
|
|
331
|
+
return opts.plugins?.includes(pluginMatch[1]) ?? true;
|
|
332
|
+
});
|
|
252
333
|
}
|
|
253
334
|
}
|
|
254
335
|
|
|
@@ -322,7 +403,7 @@ async function resolveWorkspaceRefs(
|
|
|
322
403
|
await normalizePackageManifestsInTree({
|
|
323
404
|
sourceRootDir: options?.sourceDir ?? destination,
|
|
324
405
|
targetDir: destination,
|
|
325
|
-
resolveCatalogRefs:
|
|
406
|
+
resolveCatalogRefs: true,
|
|
326
407
|
removeWorkspaceDeps: ["host"],
|
|
327
408
|
});
|
|
328
409
|
|
|
@@ -363,12 +444,21 @@ export async function writeInitSnapshot(
|
|
|
363
444
|
extendsGateway: string,
|
|
364
445
|
sourceDir: string,
|
|
365
446
|
patterns: string[],
|
|
366
|
-
options: { withHost: boolean },
|
|
447
|
+
options: { withHost: boolean; plugins?: string[]; pluginRoutes?: Record<string, string[]> },
|
|
367
448
|
): Promise<void> {
|
|
368
449
|
const effectivePatterns = options.withHost
|
|
369
450
|
? [...patterns, "host/**"]
|
|
370
451
|
: patterns.filter((p) => !p.startsWith("host/") && p !== "host/**");
|
|
371
452
|
|
|
453
|
+
const excludedRoutePatterns: string[] = [];
|
|
454
|
+
if (options.pluginRoutes) {
|
|
455
|
+
for (const [pluginKey, routePatterns] of Object.entries(options.pluginRoutes)) {
|
|
456
|
+
if (!(options.plugins?.includes(pluginKey) ?? true)) {
|
|
457
|
+
excludedRoutePatterns.push(...routePatterns);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
372
462
|
const allFiles = new Set<string>();
|
|
373
463
|
for (const pattern of effectivePatterns) {
|
|
374
464
|
const matches = await glob(pattern, {
|
|
@@ -378,17 +468,42 @@ export async function writeInitSnapshot(
|
|
|
378
468
|
absolute: false,
|
|
379
469
|
});
|
|
380
470
|
for (const match of matches) {
|
|
471
|
+
const pluginMatch = match.match(/^plugins\/([^/]+)/);
|
|
472
|
+
if (pluginMatch && !(options.plugins?.includes(pluginMatch[1]) ?? true)) continue;
|
|
473
|
+
if (isRouteExcluded(match, excludedRoutePatterns)) continue;
|
|
381
474
|
allFiles.add(match);
|
|
382
475
|
}
|
|
383
476
|
}
|
|
384
477
|
|
|
478
|
+
if (options.pluginRoutes) {
|
|
479
|
+
for (const [pluginKey, routePatterns] of Object.entries(options.pluginRoutes)) {
|
|
480
|
+
if (!(options.plugins?.includes(pluginKey) ?? true)) continue;
|
|
481
|
+
for (const rp of routePatterns) {
|
|
482
|
+
const matches = await glob(rp, {
|
|
483
|
+
cwd: sourceDir,
|
|
484
|
+
nodir: true,
|
|
485
|
+
dot: true,
|
|
486
|
+
absolute: false,
|
|
487
|
+
});
|
|
488
|
+
for (const match of matches) {
|
|
489
|
+
if (!isRouteExcluded(match, excludedRoutePatterns)) {
|
|
490
|
+
allFiles.add(match);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
385
497
|
const fileHashes: Record<string, string> = {};
|
|
386
498
|
for (const filePath of allFiles) {
|
|
387
499
|
const src = join(sourceDir, filePath);
|
|
388
500
|
const stat = lstatSync(src);
|
|
389
501
|
if (!stat.isFile()) continue;
|
|
390
502
|
const content = readFileSync(src);
|
|
391
|
-
|
|
503
|
+
const destPath = filePath.startsWith(".templates/")
|
|
504
|
+
? filePath.slice(".templates/".length)
|
|
505
|
+
: filePath;
|
|
506
|
+
fileHashes[destPath] = computeHash(content);
|
|
392
507
|
}
|
|
393
508
|
|
|
394
509
|
await writeSnapshot(destination, {
|
package/src/cli/prompts.ts
CHANGED
|
@@ -25,9 +25,10 @@ export async function promptYesNo(question: string, defaultVal = false): Promise
|
|
|
25
25
|
return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
function
|
|
29
|
-
const
|
|
30
|
-
|
|
28
|
+
function parseExtendsRef(ref: string): { account: string; gateway: string } | null {
|
|
29
|
+
const match = ref.match(/^(?:bos:\/\/)?([^/]+)\/(.+)$/);
|
|
30
|
+
if (!match) return null;
|
|
31
|
+
return { account: match[1], gateway: match[2] };
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
function deriveAccountFromDomain(domain: string, extendsAccount: string): string {
|
|
@@ -39,12 +40,73 @@ function deriveAccountFromDomain(domain: string, extendsAccount: string): string
|
|
|
39
40
|
return `${firstSegment}.${suffix}`;
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
const AVAILABLE_PLUGINS = [
|
|
44
|
+
{
|
|
45
|
+
key: "_template",
|
|
46
|
+
label: "template",
|
|
47
|
+
description: "Plugin scaffold and boilerplate",
|
|
48
|
+
default: true,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
key: "registry",
|
|
52
|
+
label: "registry",
|
|
53
|
+
description: "FastKV app discovery and metadata",
|
|
54
|
+
default: false,
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
async function promptPluginSelect(): Promise<string[]> {
|
|
59
|
+
const selected = new Set<string>(AVAILABLE_PLUGINS.filter((p) => p.default).map((p) => p.key));
|
|
60
|
+
|
|
61
|
+
console.log();
|
|
62
|
+
console.log(" Select plugins (enter number to toggle, enter to confirm):");
|
|
63
|
+
for (let i = 0; i < AVAILABLE_PLUGINS.length; i++) {
|
|
64
|
+
const p = AVAILABLE_PLUGINS[i];
|
|
65
|
+
const marker = selected.has(p.key) ? "●" : "○";
|
|
66
|
+
console.log(` ${marker} ${i + 1}. ${p.label} — ${p.description}`);
|
|
67
|
+
}
|
|
68
|
+
console.log();
|
|
69
|
+
|
|
70
|
+
while (true) {
|
|
71
|
+
const answer = await prompt(
|
|
72
|
+
" Plugins",
|
|
73
|
+
selected.size > 0 ? Array.from(selected).join(",") : "",
|
|
74
|
+
);
|
|
75
|
+
if (!answer) break;
|
|
76
|
+
|
|
77
|
+
const num = Number.parseInt(answer, 10);
|
|
78
|
+
if (num >= 1 && num <= AVAILABLE_PLUGINS.length) {
|
|
79
|
+
const plugin = AVAILABLE_PLUGINS[num - 1];
|
|
80
|
+
if (selected.has(plugin.key)) {
|
|
81
|
+
selected.delete(plugin.key);
|
|
82
|
+
} else {
|
|
83
|
+
selected.add(plugin.key);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log(" Current selection:");
|
|
87
|
+
for (let i = 0; i < AVAILABLE_PLUGINS.length; i++) {
|
|
88
|
+
const p = AVAILABLE_PLUGINS[i];
|
|
89
|
+
const marker = selected.has(p.key) ? "●" : "○";
|
|
90
|
+
console.log(` ${marker} ${i + 1}. ${p.label}`);
|
|
91
|
+
}
|
|
92
|
+
console.log();
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return Array.from(selected);
|
|
100
|
+
}
|
|
101
|
+
|
|
42
102
|
export async function promptInitOptions(input: {
|
|
43
103
|
extendsAccount?: string;
|
|
44
104
|
extendsGateway?: string;
|
|
105
|
+
extends?: string;
|
|
45
106
|
directory?: string;
|
|
46
107
|
account?: string;
|
|
47
108
|
domain?: string;
|
|
109
|
+
plugins?: string[];
|
|
48
110
|
withHost?: boolean;
|
|
49
111
|
}): Promise<{
|
|
50
112
|
extendsAccount: string;
|
|
@@ -52,21 +114,32 @@ export async function promptInitOptions(input: {
|
|
|
52
114
|
directory: string;
|
|
53
115
|
account?: string;
|
|
54
116
|
domain?: string;
|
|
117
|
+
plugins: string[];
|
|
55
118
|
withHost: boolean;
|
|
56
119
|
}> {
|
|
57
|
-
const
|
|
58
|
-
input.extendsAccount || (await prompt("Extends account", "dev.everything.near"));
|
|
120
|
+
const domain = input.domain || (await prompt("Project domain"));
|
|
59
121
|
|
|
60
|
-
const
|
|
61
|
-
|
|
122
|
+
const extendsInput = input.extends || (await prompt("Extend from", ""));
|
|
123
|
+
let extendsAccount = input.extendsAccount || "";
|
|
124
|
+
let extendsGateway = input.extendsGateway || "";
|
|
62
125
|
|
|
63
|
-
|
|
126
|
+
if (extendsInput) {
|
|
127
|
+
const parsed = parseExtendsRef(extendsInput);
|
|
128
|
+
if (parsed) {
|
|
129
|
+
extendsAccount = extendsAccount || parsed.account;
|
|
130
|
+
extendsGateway = extendsGateway || parsed.gateway;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
extendsAccount = extendsAccount || "dev.everything.near";
|
|
135
|
+
extendsGateway = extendsGateway || "everything.dev";
|
|
64
136
|
|
|
65
137
|
const accountDefault = domain ? deriveAccountFromDomain(domain, extendsAccount) : "";
|
|
66
138
|
const account = input.account || (await prompt("Project NEAR account", accountDefault));
|
|
67
139
|
|
|
68
|
-
const
|
|
69
|
-
|
|
140
|
+
const directory = input.directory || domain || extendsGateway;
|
|
141
|
+
|
|
142
|
+
const plugins = input.plugins || (await promptPluginSelect());
|
|
70
143
|
|
|
71
144
|
const withHost =
|
|
72
145
|
input.withHost !== undefined ? input.withHost : await promptYesNo("Include host?", false);
|
|
@@ -77,6 +150,7 @@ export async function promptInitOptions(input: {
|
|
|
77
150
|
directory,
|
|
78
151
|
account: account || undefined,
|
|
79
152
|
domain: domain || undefined,
|
|
153
|
+
plugins,
|
|
80
154
|
withHost,
|
|
81
155
|
};
|
|
82
156
|
}
|
package/src/cli/sync.ts
CHANGED
|
@@ -74,6 +74,76 @@ function backupFiles(projectDir: string, filePaths: string[]): string | null {
|
|
|
74
74
|
return backupDir;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
function mergePackageJson(
|
|
78
|
+
local: Record<string, unknown>,
|
|
79
|
+
template: Record<string, unknown>,
|
|
80
|
+
): Record<string, unknown> {
|
|
81
|
+
const merged = { ...template };
|
|
82
|
+
|
|
83
|
+
for (const depField of [
|
|
84
|
+
"dependencies",
|
|
85
|
+
"devDependencies",
|
|
86
|
+
"peerDependencies",
|
|
87
|
+
"overrides",
|
|
88
|
+
] as const) {
|
|
89
|
+
const localDeps = local[depField] as Record<string, string> | undefined;
|
|
90
|
+
const templateDeps = template[depField] as Record<string, string> | undefined;
|
|
91
|
+
|
|
92
|
+
if (!localDeps && !templateDeps) continue;
|
|
93
|
+
|
|
94
|
+
const mergedDeps: Record<string, string> = { ...(templateDeps ?? {}) };
|
|
95
|
+
|
|
96
|
+
if (localDeps) {
|
|
97
|
+
for (const [name, version] of Object.entries(localDeps)) {
|
|
98
|
+
if (!(name in mergedDeps)) {
|
|
99
|
+
mergedDeps[name] = version;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (Object.keys(mergedDeps).length > 0) {
|
|
105
|
+
merged[depField] = mergedDeps;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (local.scripts && typeof local.scripts === "object") {
|
|
110
|
+
merged.scripts = {
|
|
111
|
+
...((template.scripts as Record<string, string>) ?? {}),
|
|
112
|
+
...(local.scripts as Record<string, string>),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return merged;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function toDestPath(filePath: string): string {
|
|
120
|
+
return filePath.startsWith(".templates/") ? filePath.slice(".templates/".length) : filePath;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function writeSyncedFile(sourceDir: string, projectDir: string, filePath: string): void {
|
|
124
|
+
const src = join(sourceDir, filePath);
|
|
125
|
+
const destPath = filePath.startsWith(".templates/")
|
|
126
|
+
? filePath.slice(".templates/".length)
|
|
127
|
+
: filePath;
|
|
128
|
+
const dest = join(projectDir, destPath);
|
|
129
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
130
|
+
|
|
131
|
+
if (filePath.endsWith("package.json")) {
|
|
132
|
+
const localContent = existsSync(dest) ? readFileSync(dest, "utf-8") : null;
|
|
133
|
+
const templateContent = readFileSync(src, "utf-8");
|
|
134
|
+
|
|
135
|
+
if (localContent) {
|
|
136
|
+
const local = JSON.parse(localContent) as Record<string, unknown>;
|
|
137
|
+
const template = JSON.parse(templateContent) as Record<string, unknown>;
|
|
138
|
+
const merged = mergePackageJson(local, template);
|
|
139
|
+
writeFileSync(dest, `${JSON.stringify(merged, null, 2)}\n`);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
writeFileSync(dest, readFileSync(src));
|
|
145
|
+
}
|
|
146
|
+
|
|
77
147
|
export async function syncTemplate(projectDir: string, options: SyncOptions): Promise<SyncResult> {
|
|
78
148
|
const localConfig = JSON.parse(
|
|
79
149
|
readFileSync(join(projectDir, "bos.config.json"), "utf-8"),
|
|
@@ -104,7 +174,10 @@ export async function syncTemplate(projectDir: string, options: SyncOptions): Pr
|
|
|
104
174
|
const extendsAccount = extendsMatch[1];
|
|
105
175
|
const extendsGateway = extendsMatch[2];
|
|
106
176
|
|
|
107
|
-
const { sourceDir, cleanup } = await resolveSourceDir({
|
|
177
|
+
const { sourceDir, parentConfig, cleanup } = await resolveSourceDir({
|
|
178
|
+
extendsAccount,
|
|
179
|
+
extendsGateway,
|
|
180
|
+
});
|
|
108
181
|
|
|
109
182
|
try {
|
|
110
183
|
const patterns = await readTemplatekeep(sourceDir);
|
|
@@ -135,40 +208,87 @@ export async function syncTemplate(projectDir: string, options: SyncOptions): Pr
|
|
|
135
208
|
}
|
|
136
209
|
}
|
|
137
210
|
|
|
211
|
+
const childPlugins =
|
|
212
|
+
localConfig.plugins && typeof localConfig.plugins === "object"
|
|
213
|
+
? Object.keys(localConfig.plugins as Record<string, unknown>)
|
|
214
|
+
: [];
|
|
215
|
+
|
|
216
|
+
const pluginRoutes: Record<string, string[]> = {};
|
|
217
|
+
if (parentConfig.plugins) {
|
|
218
|
+
for (const [key, ref] of Object.entries(parentConfig.plugins)) {
|
|
219
|
+
if (ref.routes && ref.routes.length > 0) {
|
|
220
|
+
pluginRoutes[key] = ref.routes;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const excludedRoutePatterns: string[] = [];
|
|
226
|
+
for (const [pluginKey, routePatterns] of Object.entries(pluginRoutes)) {
|
|
227
|
+
if (!childPlugins.includes(pluginKey)) {
|
|
228
|
+
excludedRoutePatterns.push(...routePatterns);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const filteredFiles = new Set<string>();
|
|
233
|
+
for (const filePath of allTemplateFiles) {
|
|
234
|
+
const pluginMatch = filePath.match(/^plugins\/([^/]+)/);
|
|
235
|
+
if (pluginMatch && !childPlugins.includes(pluginMatch[1])) continue;
|
|
236
|
+
if (isExcluded(filePath, excludedRoutePatterns)) continue;
|
|
237
|
+
filteredFiles.add(filePath);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
for (const [pluginKey, routePatterns] of Object.entries(pluginRoutes)) {
|
|
241
|
+
if (!childPlugins.includes(pluginKey)) continue;
|
|
242
|
+
for (const rp of routePatterns) {
|
|
243
|
+
const matches = await glob(rp, {
|
|
244
|
+
cwd: sourceDir,
|
|
245
|
+
nodir: true,
|
|
246
|
+
dot: true,
|
|
247
|
+
absolute: false,
|
|
248
|
+
});
|
|
249
|
+
for (const match of matches) {
|
|
250
|
+
if (!isExcluded(match, excludedRoutePatterns)) {
|
|
251
|
+
filteredFiles.add(match);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
138
257
|
const snapshot = await readSnapshot(projectDir);
|
|
139
258
|
|
|
140
259
|
const updated: string[] = [];
|
|
141
260
|
const skipped: string[] = [];
|
|
142
261
|
const added: string[] = [];
|
|
143
262
|
|
|
144
|
-
for (const filePath of
|
|
145
|
-
|
|
263
|
+
for (const filePath of filteredFiles) {
|
|
264
|
+
const destPath = toDestPath(filePath);
|
|
265
|
+
if (isExcluded(destPath, excludePatterns)) continue;
|
|
146
266
|
|
|
147
|
-
const localHash = computeLocalHash(projectDir,
|
|
267
|
+
const localHash = computeLocalHash(projectDir, destPath);
|
|
148
268
|
const sourceContent = readFileSync(join(sourceDir, filePath));
|
|
149
269
|
const sourceHash = createHash("sha256").update(sourceContent).digest("hex").substring(0, 16);
|
|
150
270
|
|
|
151
271
|
if (localHash === null) {
|
|
152
|
-
added.push(
|
|
272
|
+
added.push(destPath);
|
|
153
273
|
continue;
|
|
154
274
|
}
|
|
155
275
|
|
|
156
276
|
if (localHash === sourceHash) continue;
|
|
157
277
|
|
|
158
|
-
const snapshotHash = snapshot?.files[
|
|
278
|
+
const snapshotHash = snapshot?.files[destPath];
|
|
159
279
|
|
|
160
280
|
if (snapshotHash === undefined) {
|
|
161
|
-
updated.push(
|
|
281
|
+
updated.push(destPath);
|
|
162
282
|
continue;
|
|
163
283
|
}
|
|
164
284
|
|
|
165
285
|
if (localHash === snapshotHash) {
|
|
166
|
-
updated.push(
|
|
286
|
+
updated.push(destPath);
|
|
167
287
|
} else {
|
|
168
288
|
if (options.force) {
|
|
169
|
-
updated.push(
|
|
289
|
+
updated.push(destPath);
|
|
170
290
|
} else {
|
|
171
|
-
skipped.push(
|
|
291
|
+
skipped.push(destPath);
|
|
172
292
|
}
|
|
173
293
|
}
|
|
174
294
|
}
|
|
@@ -184,24 +304,27 @@ export async function syncTemplate(projectDir: string, options: SyncOptions): Pr
|
|
|
184
304
|
|
|
185
305
|
const filesToWrite = [...updated, ...added].filter((f) => !isExcluded(f, excludePatterns));
|
|
186
306
|
|
|
307
|
+
const destToSource = new Map<string, string>();
|
|
308
|
+
for (const filePath of filteredFiles) {
|
|
309
|
+
destToSource.set(toDestPath(filePath), filePath);
|
|
310
|
+
}
|
|
311
|
+
|
|
187
312
|
if (filesToWrite.length > 0) {
|
|
188
313
|
backupFiles(projectDir, filesToWrite);
|
|
189
314
|
|
|
190
|
-
for (const
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
mkdirSync(dirname(dest), { recursive: true });
|
|
194
|
-
writeFileSync(dest, readFileSync(src));
|
|
315
|
+
for (const destPath of filesToWrite) {
|
|
316
|
+
const sourcePath = destToSource.get(destPath) ?? destPath;
|
|
317
|
+
writeSyncedFile(sourceDir, projectDir, sourcePath);
|
|
195
318
|
}
|
|
196
319
|
}
|
|
197
320
|
|
|
198
321
|
const newSnapshotFiles: Record<string, string> = {};
|
|
199
|
-
for (const filePath of
|
|
322
|
+
for (const filePath of filteredFiles) {
|
|
200
323
|
const src = join(sourceDir, filePath);
|
|
201
324
|
const stat = lstatSync(src);
|
|
202
325
|
if (!stat.isFile()) continue;
|
|
203
326
|
const content = readFileSync(src);
|
|
204
|
-
newSnapshotFiles[filePath] = createHash("sha256")
|
|
327
|
+
newSnapshotFiles[toDestPath(filePath)] = createHash("sha256")
|
|
205
328
|
.update(content)
|
|
206
329
|
.digest("hex")
|
|
207
330
|
.substring(0, 16);
|
|
@@ -220,6 +343,8 @@ export async function syncTemplate(projectDir: string, options: SyncOptions): Pr
|
|
|
220
343
|
extendsGateway,
|
|
221
344
|
account,
|
|
222
345
|
domain,
|
|
346
|
+
plugins: childPlugins,
|
|
347
|
+
pluginRoutes,
|
|
223
348
|
workspaceOpts: { sourceDir },
|
|
224
349
|
});
|
|
225
350
|
|
package/src/cli.ts
CHANGED
|
@@ -113,6 +113,8 @@ async function main() {
|
|
|
113
113
|
console.log(` ${colors.dim("Directory:")} ${result.directory}`);
|
|
114
114
|
if (result.account) console.log(` ${colors.dim("Account:")} ${result.account}`);
|
|
115
115
|
if (result.domain) console.log(` ${colors.dim("Domain:")} ${result.domain}`);
|
|
116
|
+
if (result.plugins && result.plugins.length > 0)
|
|
117
|
+
console.log(` ${colors.dim("Plugins:")} ${result.plugins.join(", ")}`);
|
|
116
118
|
console.log(` ${colors.dim("Files copied:")} ${result.filesCopied}`);
|
|
117
119
|
console.log();
|
|
118
120
|
console.log(colors.dim(" Next steps:"));
|
|
@@ -156,6 +158,24 @@ async function main() {
|
|
|
156
158
|
if (result.updated.length === 0 && result.added.length === 0 && result.skipped.length === 0) {
|
|
157
159
|
console.log(` ${colors.dim("Already up to date")}`);
|
|
158
160
|
}
|
|
161
|
+
if (result.status !== "dry-run" && result.updated.length > 0) {
|
|
162
|
+
console.log();
|
|
163
|
+
console.log(colors.dim(" Review changes — your customizations take priority:"));
|
|
164
|
+
console.log(
|
|
165
|
+
colors.dim(
|
|
166
|
+
" • api/src/contract.ts, api/src/index.ts, api/src/db/schema.ts — never overwritten",
|
|
167
|
+
),
|
|
168
|
+
);
|
|
169
|
+
console.log(
|
|
170
|
+
colors.dim(" • ui/src/components/**, ui/src/styles.css — never overwritten"),
|
|
171
|
+
);
|
|
172
|
+
console.log(
|
|
173
|
+
colors.dim(
|
|
174
|
+
" • Other updated files — accept framework improvements, then restore your changes",
|
|
175
|
+
),
|
|
176
|
+
);
|
|
177
|
+
console.log(colors.dim(" • Skipped files — yours already, only update with --force"));
|
|
178
|
+
}
|
|
159
179
|
console.log();
|
|
160
180
|
return;
|
|
161
181
|
}
|
|
@@ -169,7 +189,7 @@ async function main() {
|
|
|
169
189
|
if (result.status === "dry-run") {
|
|
170
190
|
console.log(colors.cyan(`${icons.ok} Dry run — no changes applied`));
|
|
171
191
|
} else {
|
|
172
|
-
console.log(colors.green(`${icons.ok}
|
|
192
|
+
console.log(colors.green(`${icons.ok} Upgrade successful`));
|
|
173
193
|
}
|
|
174
194
|
for (const pkg of result.packages) {
|
|
175
195
|
if (pkg.from && pkg.from !== pkg.to) {
|
|
@@ -186,15 +206,46 @@ async function main() {
|
|
|
186
206
|
if (result.sync) {
|
|
187
207
|
const sync = result.sync;
|
|
188
208
|
if (sync.updated.length > 0) {
|
|
189
|
-
console.log(` ${colors.dim("
|
|
209
|
+
console.log(` ${colors.dim("Updated:")} ${sync.updated.length} file(s)`);
|
|
210
|
+
for (const f of sync.updated) console.log(` ${colors.dim(f)}`);
|
|
190
211
|
}
|
|
191
212
|
if (sync.added.length > 0) {
|
|
192
|
-
console.log(` ${colors.dim("
|
|
213
|
+
console.log(` ${colors.dim("Added:")} ${sync.added.length} file(s)`);
|
|
214
|
+
for (const f of sync.added) console.log(` ${colors.dim(f)}`);
|
|
193
215
|
}
|
|
194
216
|
if (sync.skipped.length > 0) {
|
|
195
217
|
console.log(
|
|
196
|
-
` ${colors.yellow("
|
|
218
|
+
` ${colors.yellow("Skipped:")} ${sync.skipped.length} file(s) (locally modified, use --force to overwrite)`,
|
|
219
|
+
);
|
|
220
|
+
for (const f of sync.skipped) console.log(` ${colors.dim(f)}`);
|
|
221
|
+
}
|
|
222
|
+
if (
|
|
223
|
+
result.status !== "dry-run" &&
|
|
224
|
+
(sync.updated.length > 0 || sync.added.length > 0 || sync.skipped.length > 0)
|
|
225
|
+
) {
|
|
226
|
+
console.log();
|
|
227
|
+
console.log(colors.dim(" Resolve differences — your code takes priority:"));
|
|
228
|
+
console.log();
|
|
229
|
+
console.log(colors.dim(" Never overwritten (safe):"));
|
|
230
|
+
console.log(
|
|
231
|
+
colors.dim(" • api/src/contract.ts, api/src/index.ts, api/src/db/schema.ts"),
|
|
232
|
+
);
|
|
233
|
+
console.log(colors.dim(" • ui/src/components/**, ui/src/styles.css"));
|
|
234
|
+
console.log();
|
|
235
|
+
console.log(colors.dim(" Replaced — review and keep your changes:"));
|
|
236
|
+
console.log(
|
|
237
|
+
colors.dim(
|
|
238
|
+
" • api/drizzle.config.ts, api/tsconfig.json, api/tsconfig.contract.json",
|
|
239
|
+
),
|
|
197
240
|
);
|
|
241
|
+
console.log(colors.dim(" • api/plugin.dev.ts, api/rspack.config.js"));
|
|
242
|
+
console.log(colors.dim(" • ui/src/routes/* (core routes only)"));
|
|
243
|
+
console.log();
|
|
244
|
+
console.log(colors.dim(" Merged — your deps preserved:"));
|
|
245
|
+
console.log(colors.dim(" • package.json, api/package.json, ui/package.json"));
|
|
246
|
+
console.log();
|
|
247
|
+
console.log(colors.dim(" Skipped — already yours:"));
|
|
248
|
+
console.log(colors.dim(" • Use --force only if you want framework updates"));
|
|
198
249
|
}
|
|
199
250
|
}
|
|
200
251
|
console.log();
|