everything-dev 1.4.1 → 1.5.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 +84 -18
- package/dist/cli/sync.cjs.map +1 -1
- package/dist/cli/sync.mjs +84 -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 +10 -5
- package/dist/plugin.d.mts +10 -5
- 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 +6 -4
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.mts +6 -4
- package/dist/types.d.mts.map +1 -1
- package/dist/types.mjs +2 -1
- package/dist/types.mjs.map +1 -1
- package/package.json +1 -1
- package/src/cli/init.ts +122 -7
- package/src/cli/prompts.ts +84 -10
- package/src/cli/sync.ts +137 -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,71 @@ 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 ["dependencies", "devDependencies", "peerDependencies"] as const) {
|
|
84
|
+
const localDeps = local[depField] as Record<string, string> | undefined;
|
|
85
|
+
const templateDeps = template[depField] as Record<string, string> | undefined;
|
|
86
|
+
|
|
87
|
+
if (!localDeps && !templateDeps) continue;
|
|
88
|
+
|
|
89
|
+
const mergedDeps: Record<string, string> = { ...(templateDeps ?? {}) };
|
|
90
|
+
|
|
91
|
+
if (localDeps) {
|
|
92
|
+
for (const [name, version] of Object.entries(localDeps)) {
|
|
93
|
+
if (!(name in mergedDeps)) {
|
|
94
|
+
mergedDeps[name] = version;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (Object.keys(mergedDeps).length > 0) {
|
|
100
|
+
merged[depField] = mergedDeps;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (local.scripts && typeof local.scripts === "object") {
|
|
105
|
+
merged.scripts = {
|
|
106
|
+
...((template.scripts as Record<string, string>) ?? {}),
|
|
107
|
+
...(local.scripts as Record<string, string>),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return merged;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function toDestPath(filePath: string): string {
|
|
115
|
+
return filePath.startsWith(".templates/") ? filePath.slice(".templates/".length) : filePath;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function writeSyncedFile(sourceDir: string, projectDir: string, filePath: string): void {
|
|
119
|
+
const src = join(sourceDir, filePath);
|
|
120
|
+
const destPath = filePath.startsWith(".templates/")
|
|
121
|
+
? filePath.slice(".templates/".length)
|
|
122
|
+
: filePath;
|
|
123
|
+
const dest = join(projectDir, destPath);
|
|
124
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
125
|
+
|
|
126
|
+
if (filePath.endsWith("package.json")) {
|
|
127
|
+
const localContent = existsSync(dest) ? readFileSync(dest, "utf-8") : null;
|
|
128
|
+
const templateContent = readFileSync(src, "utf-8");
|
|
129
|
+
|
|
130
|
+
if (localContent) {
|
|
131
|
+
const local = JSON.parse(localContent) as Record<string, unknown>;
|
|
132
|
+
const template = JSON.parse(templateContent) as Record<string, unknown>;
|
|
133
|
+
const merged = mergePackageJson(local, template);
|
|
134
|
+
writeFileSync(dest, `${JSON.stringify(merged, null, 2)}\n`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
writeFileSync(dest, readFileSync(src));
|
|
140
|
+
}
|
|
141
|
+
|
|
77
142
|
export async function syncTemplate(projectDir: string, options: SyncOptions): Promise<SyncResult> {
|
|
78
143
|
const localConfig = JSON.parse(
|
|
79
144
|
readFileSync(join(projectDir, "bos.config.json"), "utf-8"),
|
|
@@ -104,7 +169,10 @@ export async function syncTemplate(projectDir: string, options: SyncOptions): Pr
|
|
|
104
169
|
const extendsAccount = extendsMatch[1];
|
|
105
170
|
const extendsGateway = extendsMatch[2];
|
|
106
171
|
|
|
107
|
-
const { sourceDir, cleanup } = await resolveSourceDir({
|
|
172
|
+
const { sourceDir, parentConfig, cleanup } = await resolveSourceDir({
|
|
173
|
+
extendsAccount,
|
|
174
|
+
extendsGateway,
|
|
175
|
+
});
|
|
108
176
|
|
|
109
177
|
try {
|
|
110
178
|
const patterns = await readTemplatekeep(sourceDir);
|
|
@@ -135,40 +203,87 @@ export async function syncTemplate(projectDir: string, options: SyncOptions): Pr
|
|
|
135
203
|
}
|
|
136
204
|
}
|
|
137
205
|
|
|
206
|
+
const childPlugins =
|
|
207
|
+
localConfig.plugins && typeof localConfig.plugins === "object"
|
|
208
|
+
? Object.keys(localConfig.plugins as Record<string, unknown>)
|
|
209
|
+
: [];
|
|
210
|
+
|
|
211
|
+
const pluginRoutes: Record<string, string[]> = {};
|
|
212
|
+
if (parentConfig.plugins) {
|
|
213
|
+
for (const [key, ref] of Object.entries(parentConfig.plugins)) {
|
|
214
|
+
if (ref.routes && ref.routes.length > 0) {
|
|
215
|
+
pluginRoutes[key] = ref.routes;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const excludedRoutePatterns: string[] = [];
|
|
221
|
+
for (const [pluginKey, routePatterns] of Object.entries(pluginRoutes)) {
|
|
222
|
+
if (!childPlugins.includes(pluginKey)) {
|
|
223
|
+
excludedRoutePatterns.push(...routePatterns);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const filteredFiles = new Set<string>();
|
|
228
|
+
for (const filePath of allTemplateFiles) {
|
|
229
|
+
const pluginMatch = filePath.match(/^plugins\/([^/]+)/);
|
|
230
|
+
if (pluginMatch && !childPlugins.includes(pluginMatch[1])) continue;
|
|
231
|
+
if (isExcluded(filePath, excludedRoutePatterns)) continue;
|
|
232
|
+
filteredFiles.add(filePath);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
for (const [pluginKey, routePatterns] of Object.entries(pluginRoutes)) {
|
|
236
|
+
if (!childPlugins.includes(pluginKey)) continue;
|
|
237
|
+
for (const rp of routePatterns) {
|
|
238
|
+
const matches = await glob(rp, {
|
|
239
|
+
cwd: sourceDir,
|
|
240
|
+
nodir: true,
|
|
241
|
+
dot: true,
|
|
242
|
+
absolute: false,
|
|
243
|
+
});
|
|
244
|
+
for (const match of matches) {
|
|
245
|
+
if (!isExcluded(match, excludedRoutePatterns)) {
|
|
246
|
+
filteredFiles.add(match);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
138
252
|
const snapshot = await readSnapshot(projectDir);
|
|
139
253
|
|
|
140
254
|
const updated: string[] = [];
|
|
141
255
|
const skipped: string[] = [];
|
|
142
256
|
const added: string[] = [];
|
|
143
257
|
|
|
144
|
-
for (const filePath of
|
|
145
|
-
|
|
258
|
+
for (const filePath of filteredFiles) {
|
|
259
|
+
const destPath = toDestPath(filePath);
|
|
260
|
+
if (isExcluded(destPath, excludePatterns)) continue;
|
|
146
261
|
|
|
147
|
-
const localHash = computeLocalHash(projectDir,
|
|
262
|
+
const localHash = computeLocalHash(projectDir, destPath);
|
|
148
263
|
const sourceContent = readFileSync(join(sourceDir, filePath));
|
|
149
264
|
const sourceHash = createHash("sha256").update(sourceContent).digest("hex").substring(0, 16);
|
|
150
265
|
|
|
151
266
|
if (localHash === null) {
|
|
152
|
-
added.push(
|
|
267
|
+
added.push(destPath);
|
|
153
268
|
continue;
|
|
154
269
|
}
|
|
155
270
|
|
|
156
271
|
if (localHash === sourceHash) continue;
|
|
157
272
|
|
|
158
|
-
const snapshotHash = snapshot?.files[
|
|
273
|
+
const snapshotHash = snapshot?.files[destPath];
|
|
159
274
|
|
|
160
275
|
if (snapshotHash === undefined) {
|
|
161
|
-
updated.push(
|
|
276
|
+
updated.push(destPath);
|
|
162
277
|
continue;
|
|
163
278
|
}
|
|
164
279
|
|
|
165
280
|
if (localHash === snapshotHash) {
|
|
166
|
-
updated.push(
|
|
281
|
+
updated.push(destPath);
|
|
167
282
|
} else {
|
|
168
283
|
if (options.force) {
|
|
169
|
-
updated.push(
|
|
284
|
+
updated.push(destPath);
|
|
170
285
|
} else {
|
|
171
|
-
skipped.push(
|
|
286
|
+
skipped.push(destPath);
|
|
172
287
|
}
|
|
173
288
|
}
|
|
174
289
|
}
|
|
@@ -184,24 +299,27 @@ export async function syncTemplate(projectDir: string, options: SyncOptions): Pr
|
|
|
184
299
|
|
|
185
300
|
const filesToWrite = [...updated, ...added].filter((f) => !isExcluded(f, excludePatterns));
|
|
186
301
|
|
|
302
|
+
const destToSource = new Map<string, string>();
|
|
303
|
+
for (const filePath of filteredFiles) {
|
|
304
|
+
destToSource.set(toDestPath(filePath), filePath);
|
|
305
|
+
}
|
|
306
|
+
|
|
187
307
|
if (filesToWrite.length > 0) {
|
|
188
308
|
backupFiles(projectDir, filesToWrite);
|
|
189
309
|
|
|
190
|
-
for (const
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
mkdirSync(dirname(dest), { recursive: true });
|
|
194
|
-
writeFileSync(dest, readFileSync(src));
|
|
310
|
+
for (const destPath of filesToWrite) {
|
|
311
|
+
const sourcePath = destToSource.get(destPath) ?? destPath;
|
|
312
|
+
writeSyncedFile(sourceDir, projectDir, sourcePath);
|
|
195
313
|
}
|
|
196
314
|
}
|
|
197
315
|
|
|
198
316
|
const newSnapshotFiles: Record<string, string> = {};
|
|
199
|
-
for (const filePath of
|
|
317
|
+
for (const filePath of filteredFiles) {
|
|
200
318
|
const src = join(sourceDir, filePath);
|
|
201
319
|
const stat = lstatSync(src);
|
|
202
320
|
if (!stat.isFile()) continue;
|
|
203
321
|
const content = readFileSync(src);
|
|
204
|
-
newSnapshotFiles[filePath] = createHash("sha256")
|
|
322
|
+
newSnapshotFiles[toDestPath(filePath)] = createHash("sha256")
|
|
205
323
|
.update(content)
|
|
206
324
|
.digest("hex")
|
|
207
325
|
.substring(0, 16);
|
|
@@ -220,6 +338,8 @@ export async function syncTemplate(projectDir: string, options: SyncOptions): Pr
|
|
|
220
338
|
extendsGateway,
|
|
221
339
|
account,
|
|
222
340
|
domain,
|
|
341
|
+
plugins: childPlugins,
|
|
342
|
+
pluginRoutes,
|
|
223
343
|
workspaceOpts: { sourceDir },
|
|
224
344
|
});
|
|
225
345
|
|
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();
|