everything-dev 1.12.3 → 1.13.1

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.
Files changed (124) hide show
  1. package/cli.js +1 -1
  2. package/dist/app.cjs +24 -101
  3. package/dist/app.cjs.map +1 -1
  4. package/dist/app.mjs +25 -102
  5. package/dist/app.mjs.map +1 -1
  6. package/dist/cli/init.cjs +143 -66
  7. package/dist/cli/init.cjs.map +1 -1
  8. package/dist/cli/init.d.cts +1 -1
  9. package/dist/cli/init.d.cts.map +1 -1
  10. package/dist/cli/init.d.mts +1 -1
  11. package/dist/cli/init.d.mts.map +1 -1
  12. package/dist/cli/init.mjs +144 -67
  13. package/dist/cli/init.mjs.map +1 -1
  14. package/dist/cli/prompts.cjs +3 -3
  15. package/dist/cli/prompts.cjs.map +1 -1
  16. package/dist/cli/prompts.mjs +3 -3
  17. package/dist/cli/prompts.mjs.map +1 -1
  18. package/dist/cli/sync.cjs +15 -56
  19. package/dist/cli/sync.cjs.map +1 -1
  20. package/dist/cli/sync.mjs +15 -56
  21. package/dist/cli/sync.mjs.map +1 -1
  22. package/dist/cli/upgrade.cjs +3 -1
  23. package/dist/cli/upgrade.cjs.map +1 -1
  24. package/dist/cli/upgrade.mjs +3 -1
  25. package/dist/cli/upgrade.mjs.map +1 -1
  26. package/dist/config.cjs +223 -81
  27. package/dist/config.cjs.map +1 -1
  28. package/dist/config.d.cts +21 -5
  29. package/dist/config.d.cts.map +1 -1
  30. package/dist/config.d.mts +21 -5
  31. package/dist/config.d.mts.map +1 -1
  32. package/dist/config.mjs +217 -83
  33. package/dist/config.mjs.map +1 -1
  34. package/dist/contract.d.cts +104 -8
  35. package/dist/contract.d.cts.map +1 -1
  36. package/dist/contract.d.mts +104 -8
  37. package/dist/contract.d.mts.map +1 -1
  38. package/dist/host.cjs +34 -1
  39. package/dist/host.cjs.map +1 -1
  40. package/dist/host.d.cts.map +1 -1
  41. package/dist/host.d.mts.map +1 -1
  42. package/dist/host.mjs +34 -1
  43. package/dist/host.mjs.map +1 -1
  44. package/dist/index.cjs +17 -0
  45. package/dist/index.d.cts +5 -3
  46. package/dist/index.d.mts +5 -3
  47. package/dist/index.mjs +5 -3
  48. package/dist/merge.cjs +113 -0
  49. package/dist/merge.cjs.map +1 -0
  50. package/dist/merge.d.cts +7 -0
  51. package/dist/merge.d.cts.map +1 -0
  52. package/dist/merge.d.mts +7 -0
  53. package/dist/merge.d.mts.map +1 -0
  54. package/dist/merge.mjs +107 -0
  55. package/dist/merge.mjs.map +1 -0
  56. package/dist/plugin.cjs +117 -105
  57. package/dist/plugin.cjs.map +1 -1
  58. package/dist/plugin.d.cts +114 -8
  59. package/dist/plugin.d.cts.map +1 -1
  60. package/dist/plugin.d.mts +114 -8
  61. package/dist/plugin.d.mts.map +1 -1
  62. package/dist/plugin.mjs +117 -105
  63. package/dist/plugin.mjs.map +1 -1
  64. package/dist/service-descriptor.cjs +21 -0
  65. package/dist/service-descriptor.cjs.map +1 -1
  66. package/dist/service-descriptor.d.cts +23 -1
  67. package/dist/service-descriptor.d.cts.map +1 -1
  68. package/dist/service-descriptor.d.mts +23 -1
  69. package/dist/service-descriptor.d.mts.map +1 -1
  70. package/dist/service-descriptor.mjs +21 -0
  71. package/dist/service-descriptor.mjs.map +1 -1
  72. package/dist/shared.cjs +24 -2
  73. package/dist/shared.cjs.map +1 -1
  74. package/dist/shared.d.cts +3 -0
  75. package/dist/shared.d.cts.map +1 -1
  76. package/dist/shared.d.mts +3 -0
  77. package/dist/shared.d.mts.map +1 -1
  78. package/dist/shared.mjs +25 -3
  79. package/dist/shared.mjs.map +1 -1
  80. package/dist/sidebar.cjs +124 -0
  81. package/dist/sidebar.cjs.map +1 -0
  82. package/dist/sidebar.d.cts +8 -0
  83. package/dist/sidebar.d.cts.map +1 -0
  84. package/dist/sidebar.d.mts +8 -0
  85. package/dist/sidebar.d.mts.map +1 -0
  86. package/dist/sidebar.mjs +122 -0
  87. package/dist/sidebar.mjs.map +1 -0
  88. package/dist/types.cjs +104 -10
  89. package/dist/types.cjs.map +1 -1
  90. package/dist/types.d.cts +256 -29
  91. package/dist/types.d.cts.map +1 -1
  92. package/dist/types.d.mts +256 -29
  93. package/dist/types.d.mts.map +1 -1
  94. package/dist/types.mjs +100 -11
  95. package/dist/types.mjs.map +1 -1
  96. package/dist/utils/path-match.cjs +18 -0
  97. package/dist/utils/path-match.cjs.map +1 -0
  98. package/dist/utils/path-match.mjs +17 -0
  99. package/dist/utils/path-match.mjs.map +1 -0
  100. package/dist/utils/save-config.cjs +19 -0
  101. package/dist/utils/save-config.cjs.map +1 -0
  102. package/dist/utils/save-config.mjs +18 -0
  103. package/dist/utils/save-config.mjs.map +1 -0
  104. package/package.json +3 -2
  105. package/skills/dev-workflow/SKILL.md +8 -0
  106. package/skills/extends-config/SKILL.md +132 -0
  107. package/skills/init-upgrade/SKILL.md +128 -0
  108. package/skills/publish-sync/SKILL.md +30 -0
  109. package/src/app.ts +23 -118
  110. package/src/cli/init.ts +199 -100
  111. package/src/cli/prompts.ts +2 -2
  112. package/src/cli/sync.ts +27 -96
  113. package/src/cli/upgrade.ts +2 -0
  114. package/src/config.ts +356 -132
  115. package/src/host.ts +45 -0
  116. package/src/index.ts +1 -0
  117. package/src/merge.ts +198 -0
  118. package/src/plugin.ts +340 -318
  119. package/src/service-descriptor.ts +23 -0
  120. package/src/shared.ts +48 -5
  121. package/src/sidebar.ts +162 -0
  122. package/src/types.ts +134 -28
  123. package/src/utils/path-match.ts +16 -0
  124. package/src/utils/save-config.ts +20 -0
package/src/cli/init.ts CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  existsSync,
6
6
  lstatSync,
7
7
  mkdirSync,
8
+ mkdtempSync,
8
9
  readFileSync,
9
10
  rmSync,
10
11
  writeFileSync,
@@ -20,40 +21,12 @@ import {
20
21
  normalizePackageManifestsInTree,
21
22
  } from "../internal/manifest-normalizer";
22
23
  import type { BosConfig } from "../types";
24
+ import { isPathExcluded } from "../utils/path-match";
25
+ import { saveBosConfig } from "../utils/save-config";
23
26
  import { writeSnapshot } from "./snapshot";
24
27
 
25
28
  const require = createRequire(import.meta.url);
26
29
 
27
- const BOS_CONFIG_ORDER = [
28
- "extends",
29
- "account",
30
- "domain",
31
- "testnet",
32
- "staging",
33
- "repository",
34
- "app",
35
- "plugins",
36
- "shared",
37
- ];
38
-
39
- function rebuildOrderedConfig(config: Record<string, unknown>): Record<string, unknown> {
40
- const ordered: Record<string, unknown> = {};
41
-
42
- for (const key of BOS_CONFIG_ORDER) {
43
- if (key in config) {
44
- ordered[key] = config[key];
45
- }
46
- }
47
-
48
- for (const key of Object.keys(config)) {
49
- if (!BOS_CONFIG_ORDER.includes(key)) {
50
- ordered[key] = config[key];
51
- }
52
- }
53
-
54
- return ordered;
55
- }
56
-
57
30
  interface SourceResult {
58
31
  sourceDir: string;
59
32
  parentConfig: BosConfig;
@@ -86,6 +59,19 @@ export async function resolveSourceDir(opts: {
86
59
  return { sourceDir, parentConfig, cleanup };
87
60
  }
88
61
 
62
+ export async function readTemplatekeep(sourceDir: string): Promise<string[]> {
63
+ const keepFile = join(sourceDir, ".templatekeep");
64
+ if (!existsSync(keepFile)) {
65
+ return [];
66
+ }
67
+
68
+ const content = readFileSync(keepFile, "utf-8");
69
+ return content
70
+ .split("\n")
71
+ .map((line) => line.trim())
72
+ .filter((line) => line.length > 0 && !line.startsWith("#"));
73
+ }
74
+
89
75
  export async function fetchParentConfig(
90
76
  extendsAccount: string,
91
77
  extendsGateway: string,
@@ -161,19 +147,6 @@ function parseGitHubUrl(url: string): { owner: string; repo: string; branch: str
161
147
  return null;
162
148
  }
163
149
 
164
- export async function readTemplatekeep(sourceDir: string): Promise<string[]> {
165
- const keepFile = join(sourceDir, ".templatekeep");
166
- if (!existsSync(keepFile)) {
167
- return [];
168
- }
169
-
170
- const content = readFileSync(keepFile, "utf-8");
171
- return content
172
- .split("\n")
173
- .map((line) => line.trim())
174
- .filter((line) => line.length > 0 && !line.startsWith("#"));
175
- }
176
-
177
150
  export async function copyFilteredFiles(
178
151
  sourceDir: string,
179
152
  destination: string,
@@ -218,7 +191,7 @@ export async function copyFilteredFiles(
218
191
  const pluginName = pluginMatch[1];
219
192
  if (!(options.plugins?.includes(pluginName) ?? true)) continue;
220
193
  }
221
- if (isRouteExcluded(match, excludedRoutePatterns)) continue;
194
+ if (isPathExcluded(match, excludedRoutePatterns)) continue;
222
195
  allFiles.add(match);
223
196
  }
224
197
  }
@@ -235,7 +208,7 @@ export async function copyFilteredFiles(
235
208
  absolute: false,
236
209
  });
237
210
  for (const match of matches) {
238
- if (!isRouteExcluded(match, excludedRoutePatterns)) {
211
+ if (!isPathExcluded(match, excludedRoutePatterns)) {
239
212
  routeFiles.add(match);
240
213
  }
241
214
  }
@@ -266,19 +239,6 @@ export async function copyFilteredFiles(
266
239
  return count;
267
240
  }
268
241
 
269
- function isRouteExcluded(filePath: string, excludedPatterns: string[]): boolean {
270
- if (excludedPatterns.length === 0) return false;
271
- for (const pattern of excludedPatterns) {
272
- if (pattern.endsWith("/**")) {
273
- const prefix = pattern.slice(0, -3);
274
- if (filePath.startsWith(`${prefix}/`) || filePath === prefix) return true;
275
- } else if (filePath === pattern || filePath.startsWith(`${pattern}/`)) {
276
- return true;
277
- }
278
- }
279
- return false;
280
- }
281
-
282
242
  export async function personalizeConfig(
283
243
  destination: string,
284
244
  opts: {
@@ -334,13 +294,124 @@ export async function personalizeConfig(
334
294
  }
335
295
 
336
296
  if (isInit) {
297
+ const parentDomain = opts.extendsGateway;
298
+
337
299
  for (const pluginKey of Object.keys(plugins)) {
338
300
  const plugin = plugins[pluginKey];
339
- if (plugin && typeof plugin === "object") {
340
- const p = plugin as Record<string, unknown>;
341
- delete p.production;
342
- delete p.integrity;
301
+ let pluginObj: Record<string, unknown>;
302
+
303
+ if (typeof plugin === "string") {
304
+ pluginObj = { extends: plugin };
305
+ plugins[pluginKey] = pluginObj;
306
+ } else if (plugin && typeof plugin === "object") {
307
+ pluginObj = { ...(plugin as Record<string, unknown>) };
308
+ } else {
309
+ continue;
343
310
  }
311
+
312
+ if (
313
+ pluginObj.development &&
314
+ typeof pluginObj.development === "string" &&
315
+ pluginObj.development.startsWith("local:")
316
+ ) {
317
+ const pluginDir = join(destination, pluginObj.development.slice("local:".length));
318
+ const pluginConfigPath = join(pluginDir, "bos.config.json");
319
+
320
+ if (existsSync(pluginConfigPath)) {
321
+ try {
322
+ const pluginConfig = JSON.parse(readFileSync(pluginConfigPath, "utf-8")) as Record<
323
+ string,
324
+ unknown
325
+ >;
326
+ delete pluginConfig.extends;
327
+ if (pluginConfig.app && typeof pluginConfig.app === "object") {
328
+ const app = pluginConfig.app as Record<string, unknown>;
329
+ for (const entryKey of Object.keys(app)) {
330
+ const entry = app[entryKey];
331
+ if (entry && typeof entry === "object") {
332
+ const e = entry as Record<string, unknown>;
333
+ delete e.production;
334
+ delete e.integrity;
335
+ }
336
+ }
337
+ }
338
+ writeFileSync(pluginConfigPath, `${JSON.stringify(pluginConfig, null, 2)}\n`);
339
+ } catch {}
340
+ } else if (existsSync(pluginDir)) {
341
+ const pluginConfig: Record<string, unknown> = {};
342
+ pluginConfig.domain = `${pluginKey}.${opts.domain ?? parentDomain}`;
343
+ pluginConfig.app = { api: { development: "local:." } };
344
+
345
+ if (opts.pluginRoutes?.[pluginKey]) {
346
+ pluginConfig.routes = opts.pluginRoutes[pluginKey];
347
+ }
348
+ if (pluginObj.sidebar) {
349
+ pluginConfig.sidebar = pluginObj.sidebar;
350
+ }
351
+
352
+ mkdirSync(pluginDir, { recursive: true });
353
+ writeFileSync(pluginConfigPath, `${JSON.stringify(pluginConfig, null, 2)}\n`);
354
+ }
355
+
356
+ const cleanEntry: Record<string, unknown> = { development: pluginObj.development };
357
+ if (pluginObj.extends) {
358
+ cleanEntry.extends = pluginObj.extends;
359
+ }
360
+ if (pluginObj.secrets) {
361
+ cleanEntry.secrets = pluginObj.secrets;
362
+ }
363
+ if (pluginObj.variables) {
364
+ cleanEntry.variables = pluginObj.variables;
365
+ }
366
+ plugins[pluginKey] = cleanEntry;
367
+ } else {
368
+ delete pluginObj.production;
369
+ delete pluginObj.integrity;
370
+ delete pluginObj.sidebar;
371
+ delete pluginObj.routes;
372
+ }
373
+ }
374
+ } else {
375
+ for (const pluginKey of Object.keys(plugins)) {
376
+ const pluginDir = resolve(
377
+ destination,
378
+ (plugins[pluginKey] as Record<string, unknown>)?.development
379
+ ?.toString()
380
+ ?.slice("local:".length) ?? "",
381
+ );
382
+ const pluginConfigPath = join(pluginDir, "bos.config.json");
383
+ if (!existsSync(pluginConfigPath)) continue;
384
+
385
+ try {
386
+ const pluginConfig = JSON.parse(readFileSync(pluginConfigPath, "utf-8")) as Record<
387
+ string,
388
+ unknown
389
+ >;
390
+ let changed = false;
391
+
392
+ if ("extends" in pluginConfig) {
393
+ delete pluginConfig.extends;
394
+ changed = true;
395
+ }
396
+ if (pluginConfig.app && typeof pluginConfig.app === "object") {
397
+ const app = pluginConfig.app as Record<string, unknown>;
398
+ for (const entryKey of Object.keys(app)) {
399
+ const entry = app[entryKey];
400
+ if (entry && typeof entry === "object") {
401
+ const e = entry as Record<string, unknown>;
402
+ if ("production" in e || "integrity" in e) {
403
+ delete e.production;
404
+ delete e.integrity;
405
+ changed = true;
406
+ }
407
+ }
408
+ }
409
+ }
410
+
411
+ if (changed) {
412
+ writeFileSync(pluginConfigPath, `${JSON.stringify(pluginConfig, null, 2)}\n`);
413
+ }
414
+ } catch {}
344
415
  }
345
416
  }
346
417
 
@@ -349,7 +420,7 @@ export async function personalizeConfig(
349
420
  }
350
421
  }
351
422
 
352
- writeFileSync(configPath, `${JSON.stringify(rebuildOrderedConfig(config), null, 2)}\n`);
423
+ await saveBosConfig(destination, config);
353
424
  }
354
425
 
355
426
  const pkgPath = join(destination, "package.json");
@@ -463,32 +534,71 @@ export async function personalizeConfig(
463
534
  );
464
535
  }
465
536
 
466
- const authTypesGenPath = join(destination, "ui", "src", "lib", "auth-types.gen.ts");
467
- if (!existsSync(authTypesGenPath)) {
468
- mkdirSync(dirname(authTypesGenPath), { recursive: true });
469
- writeFileSync(
470
- authTypesGenPath,
471
- `import type { Auth } from "better-auth";\nexport type { Auth } from "better-auth";\nexport type AuthSessionUser = NonNullable<Auth["$Infer"]["Session"]["user"]> & {\n role?: string | null;\n isAnonymous?: boolean | null;\n walletAddress?: string | null;\n banned?: boolean | null;\n};\nexport type AuthSessionData = NonNullable<Auth["$Infer"]["Session"]["session"]> & {\n activeOrganizationId?: string | null;\n};\nexport type AuthSession = {\n user: AuthSessionUser | null;\n session: AuthSessionData | null;\n};\nexport interface AuthOrganizationContext {\n activeOrganizationId: string | null;\n organization: { id: string; name: string; slug: string; logo?: string | null; metadata?: Record<string, unknown> } | null;\n member: { id: string; role: string } | null;\n isPersonal: boolean;\n hasOrganization: boolean;\n}\nexport interface AuthRequestContext {\n user: AuthSessionUser | null;\n userId: string | null;\n isAuthenticated: boolean;\n authMethod: "session" | "apiKey" | "anonymous" | "none";\n near: {\n primaryAccountId: string | null;\n linkedAccounts: Array<{ accountId: string; network: string; publicKey: string; isPrimary: boolean }>;\n hasNearAccount: boolean;\n };\n organization: AuthOrganizationContext;\n organizations?: Array<{ id: string; role: string; name?: string; slug?: string }>;\n}\nexport type AuthActiveMember = { id: string | null; role: string | null; organizationId: string | null };\nexport type AuthOrganization = NonNullable<AuthOrganizationContext["organization"]>;\nexport type AuthOrganizationMember = NonNullable<AuthOrganizationContext["member"]>;\nexport type AuthOrganizationSummary = NonNullable<AuthRequestContext["organizations"]>[number];\nexport type AuthBaseSession = Auth["$Infer"]["Session"];\nexport type createAuthInstance = never;\nexport interface AuthServices {\n auth: Auth;\n db: unknown;\n driver: { close(): Promise<void> };\n handler: (req: Request) => Promise<Response>;\n}\n`,
472
- );
537
+ const authTypesContent = generateAuthTypesTemplate();
538
+ const authTypesPaths = [
539
+ join(destination, "ui", "src", "lib", "auth-types.gen.ts"),
540
+ join(destination, "api", "src", "lib", "auth-types.gen.ts"),
541
+ ];
542
+ if (existsSync(join(destination, "host", "src"))) {
543
+ authTypesPaths.push(join(destination, "host", "src", "lib", "auth-types.gen.ts"));
473
544
  }
474
-
475
- const apiAuthTypesGenPath = join(destination, "api", "src", "lib", "auth-types.gen.ts");
476
- if (!existsSync(apiAuthTypesGenPath)) {
477
- mkdirSync(dirname(apiAuthTypesGenPath), { recursive: true });
478
- writeFileSync(
479
- apiAuthTypesGenPath,
480
- `import type { Auth } from "better-auth";\nexport type { Auth } from "better-auth";\nexport type AuthSessionUser = NonNullable<Auth["$Infer"]["Session"]["user"]> & {\n role?: string | null;\n isAnonymous?: boolean | null;\n walletAddress?: string | null;\n banned?: boolean | null;\n};\nexport type AuthSessionData = NonNullable<Auth["$Infer"]["Session"]["session"]> & {\n activeOrganizationId?: string | null;\n};\nexport type AuthSession = {\n user: AuthSessionUser | null;\n session: AuthSessionData | null;\n};\nexport interface AuthOrganizationContext {\n activeOrganizationId: string | null;\n organization: { id: string; name: string; slug: string; logo?: string | null; metadata?: Record<string, unknown> } | null;\n member: { id: string; role: string } | null;\n isPersonal: boolean;\n hasOrganization: boolean;\n}\nexport interface AuthRequestContext {\n user: AuthSessionUser | null;\n userId: string | null;\n isAuthenticated: boolean;\n authMethod: "session" | "apiKey" | "anonymous" | "none";\n near: {\n primaryAccountId: string | null;\n linkedAccounts: Array<{ accountId: string; network: string; publicKey: string; isPrimary: boolean }>;\n hasNearAccount: boolean;\n };\n organization: AuthOrganizationContext;\n organizations?: Array<{ id: string; role: string; name?: string; slug?: string }>;\n}\nexport type AuthActiveMember = { id: string | null; role: string | null; organizationId: string | null };\nexport type AuthOrganization = NonNullable<AuthOrganizationContext["organization"]>;\nexport type AuthOrganizationMember = NonNullable<AuthOrganizationContext["member"]>;\nexport type AuthOrganizationSummary = NonNullable<AuthRequestContext["organizations"]>[number];\nexport type AuthBaseSession = Auth["$Infer"]["Session"];\nexport type createAuthInstance = never;\nexport interface AuthServices {\n auth: Auth;\n db: unknown;\n driver: { close(): Promise<void> };\n handler: (req: Request) => Promise<Response>;\n}\n`,
481
- );
545
+ for (const authTypesGenPath of authTypesPaths) {
546
+ if (!existsSync(authTypesGenPath)) {
547
+ mkdirSync(dirname(authTypesGenPath), { recursive: true });
548
+ writeFileSync(authTypesGenPath, authTypesContent);
549
+ }
482
550
  }
551
+ }
483
552
 
484
- const hostAuthTypesGenPath = join(destination, "host", "src", "lib", "auth-types.gen.ts");
485
- if (existsSync(join(destination, "host", "src")) && !existsSync(hostAuthTypesGenPath)) {
486
- mkdirSync(dirname(hostAuthTypesGenPath), { recursive: true });
487
- writeFileSync(
488
- hostAuthTypesGenPath,
489
- `import type { Auth } from "better-auth";\nexport type { Auth } from "better-auth";\nexport type AuthSessionUser = NonNullable<Auth["$Infer"]["Session"]["user"]> & {\n role?: string | null;\n isAnonymous?: boolean | null;\n walletAddress?: string | null;\n banned?: boolean | null;\n};\nexport type AuthSessionData = NonNullable<Auth["$Infer"]["Session"]["session"]> & {\n activeOrganizationId?: string | null;\n};\nexport type AuthSession = {\n user: AuthSessionUser | null;\n session: AuthSessionData | null;\n};\nexport interface AuthOrganizationContext {\n activeOrganizationId: string | null;\n organization: { id: string; name: string; slug: string; logo?: string | null; metadata?: Record<string, unknown> } | null;\n member: { id: string; role: string } | null;\n isPersonal: boolean;\n hasOrganization: boolean;\n}\nexport interface AuthRequestContext {\n user: AuthSessionUser | null;\n userId: string | null;\n isAuthenticated: boolean;\n authMethod: "session" | "apiKey" | "anonymous" | "none";\n near: {\n primaryAccountId: string | null;\n linkedAccounts: Array<{ accountId: string; network: string; publicKey: string; isPrimary: boolean }>;\n hasNearAccount: boolean;\n };\n organization: AuthOrganizationContext;\n organizations?: Array<{ id: string; role: string; name?: string; slug?: string }>;\n}\nexport type AuthActiveMember = { id: string | null; role: string | null; organizationId: string | null };\nexport type AuthOrganization = NonNullable<AuthOrganizationContext["organization"]>;\nexport type AuthOrganizationMember = NonNullable<AuthOrganizationContext["member"]>;\nexport type AuthOrganizationSummary = NonNullable<AuthRequestContext["organizations"]>[number];\nexport type AuthBaseSession = Auth["$Infer"]["Session"];\nexport type createAuthInstance = never;\nexport interface AuthServices {\n auth: Auth;\n db: unknown;\n driver: { close(): Promise<void> };\n handler: (req: Request) => Promise<Response>;\n}\n`,
490
- );
491
- }
553
+ function generateAuthTypesTemplate(): string {
554
+ return `import type { Auth } from "better-auth";
555
+ export type { Auth } from "better-auth";
556
+ export type AuthSessionUser = NonNullable<Auth["$Infer"]["Session"]["user"]> & {
557
+ role?: string | null;
558
+ isAnonymous?: boolean | null;
559
+ walletAddress?: string | null;
560
+ banned?: boolean | null;
561
+ };
562
+ export type AuthSessionData = NonNullable<Auth["$Infer"]["Session"]["session"]> & {
563
+ activeOrganizationId?: string | null;
564
+ };
565
+ export type AuthSession = {
566
+ user: AuthSessionUser | null;
567
+ session: AuthSessionData | null;
568
+ };
569
+ export interface AuthOrganizationContext {
570
+ activeOrganizationId: string | null;
571
+ organization: { id: string; name: string; slug: string; logo?: string | null; metadata?: Record<string, unknown> } | null;
572
+ member: { id: string; role: string } | null;
573
+ isPersonal: boolean;
574
+ hasOrganization: boolean;
575
+ }
576
+ export interface AuthRequestContext {
577
+ user: AuthSessionUser | null;
578
+ userId: string | null;
579
+ isAuthenticated: boolean;
580
+ authMethod: "session" | "apiKey" | "anonymous" | "none";
581
+ near: {
582
+ primaryAccountId: string | null;
583
+ linkedAccounts: Array<{ accountId: string; network: string; publicKey: string; isPrimary: boolean }>;
584
+ hasNearAccount: boolean;
585
+ };
586
+ organization: AuthOrganizationContext;
587
+ organizations?: Array<{ id: string; role: string; name?: string; slug?: string }>;
588
+ }
589
+ export type AuthActiveMember = { id: string | null; role: string | null; organizationId: string | null };
590
+ export type AuthOrganization = NonNullable<AuthOrganizationContext["organization"]>;
591
+ export type AuthOrganizationMember = NonNullable<AuthOrganizationContext["member"]>;
592
+ export type AuthOrganizationSummary = NonNullable<AuthRequestContext["organizations"]>[number];
593
+ export type AuthBaseSession = Auth["$Infer"]["Session"];
594
+ export type createAuthInstance = never;
595
+ export interface AuthServices {
596
+ auth: Auth;
597
+ db: unknown;
598
+ driver: { close(): Promise<void> };
599
+ handler: (req: Request) => Promise<Response>;
600
+ }
601
+ `;
492
602
  }
493
603
 
494
604
  export async function runBunInstall(destination: string): Promise<void> {
@@ -579,7 +689,7 @@ export async function writeInitSnapshot(
579
689
  for (const match of matches) {
580
690
  const pluginMatch = match.match(/^plugins\/([^/]+)/);
581
691
  if (pluginMatch && !(options.plugins?.includes(pluginMatch[1]) ?? true)) continue;
582
- if (isRouteExcluded(match, excludedRoutePatterns)) continue;
692
+ if (isPathExcluded(match, excludedRoutePatterns)) continue;
583
693
  allFiles.add(match);
584
694
  }
585
695
  }
@@ -595,7 +705,7 @@ export async function writeInitSnapshot(
595
705
  absolute: false,
596
706
  });
597
707
  for (const match of matches) {
598
- if (!isRouteExcluded(match, excludedRoutePatterns)) {
708
+ if (!isPathExcluded(match, excludedRoutePatterns)) {
599
709
  allFiles.add(match);
600
710
  }
601
711
  }
@@ -626,18 +736,7 @@ function computeHash(data: Uint8Array): string {
626
736
  }
627
737
 
628
738
  function mkTmpDir(prefix: string): string {
629
- const base = join(tmpdir(), prefix);
630
- let attempt = 0;
631
- while (true) {
632
- const dir = `${base}-${Date.now()}-${attempt}`;
633
- try {
634
- mkdirSync(dir, { recursive: true });
635
- return dir;
636
- } catch {
637
- attempt++;
638
- if (attempt > 10) throw new Error("Failed to create temp directory");
639
- }
640
- }
739
+ return mkdtempSync(join(tmpdir(), `${prefix}-`));
641
740
  }
642
741
 
643
742
  export async function generateDatabaseMigrations(destination: string): Promise<void> {
@@ -16,7 +16,7 @@ function deriveAccountFromDomain(domain: string, extendsAccount: string): string
16
16
  return `${firstSegment}.${suffix}`;
17
17
  }
18
18
 
19
- const AVAILABLE_PLUGINS = [{ value: "_template", label: "template" }];
19
+ const AVAILABLE_PLUGINS = [{ value: "settings", label: "settings" }];
20
20
 
21
21
  export async function promptInitOptions(input: {
22
22
  extendsAccount?: string;
@@ -89,7 +89,7 @@ export async function promptInitOptions(input: {
89
89
  ((await p.multiselect({
90
90
  message: "Select plugins:",
91
91
  options: AVAILABLE_PLUGINS,
92
- initialValues: ["_template"],
92
+ initialValues: ["settings"],
93
93
  required: false,
94
94
  })) as string[]);
95
95
 
package/src/cli/sync.ts CHANGED
@@ -10,6 +10,13 @@ import {
10
10
  import { dirname, join } from "node:path";
11
11
  import { glob } from "glob";
12
12
  import type { SyncOptions, SyncResult } from "../contract";
13
+ import {
14
+ isPlainObject as isPlainObjectFromMerge,
15
+ mergeBosConfigWithTemplate,
16
+ resolveExtendsRef,
17
+ } from "../merge";
18
+ import type { BosPluginRef } from "../types";
19
+ import { isPathExcluded } from "../utils/path-match";
13
20
  import {
14
21
  personalizeConfig,
15
22
  readTemplatekeep,
@@ -24,6 +31,8 @@ const FRAMEWORK_OWNED_SYNC_FILES = new Set([
24
31
  "biome.json",
25
32
  "bos.config.json",
26
33
  "package.json",
34
+ ".github/renovate.json",
35
+ ".github/workflows/ci.yml",
27
36
  ".github/workflows/release-sync.yml",
28
37
  "ui/package.json",
29
38
  "ui/postcss.config.mjs",
@@ -67,22 +76,6 @@ export function readLocalSyncExcludes(projectDir: string): string[] {
67
76
  return readExcludeFile(join(projectDir, ".bos", "sync-local-exclude"));
68
77
  }
69
78
 
70
- function isExcluded(filePath: string, excludePatterns: string[]): boolean {
71
- for (const pattern of excludePatterns) {
72
- if (pattern.endsWith("/**")) {
73
- const prefix = pattern.slice(0, -3);
74
- if (filePath.startsWith(`${prefix}/`) || filePath === prefix) return true;
75
- } else if (pattern.endsWith("/*")) {
76
- const prefix = pattern.slice(0, -2);
77
- const slashIdx = filePath.indexOf("/", prefix.length + 1);
78
- if (filePath.startsWith(`${prefix}/`) && slashIdx === -1) return true;
79
- } else if (filePath === pattern || filePath.startsWith(`${pattern}/`)) {
80
- return true;
81
- }
82
- }
83
- return false;
84
- }
85
-
86
79
  function computeLocalHash(projectDir: string, filePath: string): string | null {
87
80
  const fullPath = join(projectDir, filePath);
88
81
  if (!existsSync(fullPath)) return null;
@@ -111,78 +104,6 @@ function backupFiles(projectDir: string, filePaths: string[]): string | null {
111
104
  return backupDir;
112
105
  }
113
106
 
114
- function isPlainObject(value: unknown): value is Record<string, unknown> {
115
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
116
- }
117
-
118
- function mergeJsonValues(local: unknown, template: unknown): unknown {
119
- if (isPlainObject(local) && isPlainObject(template)) {
120
- const merged: Record<string, unknown> = {};
121
- // Preserve local key order first
122
- for (const key of Object.keys(local)) {
123
- merged[key] = mergeJsonValues(local[key], template[key]);
124
- }
125
- // Append any new template keys at the end
126
- for (const key of Object.keys(template)) {
127
- if (!(key in merged)) {
128
- merged[key] = template[key];
129
- }
130
- }
131
- return merged;
132
- }
133
- // For arrays and primitives, local always wins
134
- return local ?? template;
135
- }
136
-
137
- function mergeBosConfig(
138
- local: Record<string, unknown>,
139
- template: Record<string, unknown>,
140
- ): Record<string, unknown> {
141
- const merged: Record<string, unknown> = {};
142
-
143
- // 1. extends always first
144
- if (local.extends !== undefined) merged.extends = local.extends;
145
- else if (template.extends !== undefined) merged.extends = template.extends;
146
-
147
- // 2. Fixed trailing group: app, plugins, shared
148
- const TRAIL_GROUP = ["app", "plugins", "shared"];
149
-
150
- const localKeys = Object.keys(local).filter((k) => k !== "extends");
151
- const templateKeys = Object.keys(template).filter(
152
- (k) => k !== "extends" && !localKeys.includes(k),
153
- );
154
-
155
- // Find the first trailing-group key present locally ("app" comes first in the group)
156
- const firstTrailIndex = localKeys.findIndex((k) => TRAIL_GROUP.includes(k));
157
-
158
- const orderedKeys: string[] = [];
159
-
160
- if (firstTrailIndex >= 0) {
161
- // Keys before the trail group stay in local order
162
- orderedKeys.push(...localKeys.slice(0, firstTrailIndex));
163
- // New template keys inserted right before the trail group
164
- orderedKeys.push(...templateKeys);
165
- // Trail group keys (app, plugins, shared) in canonical order, preserving local if present
166
- for (const trailKey of TRAIL_GROUP) {
167
- if (localKeys.includes(trailKey)) orderedKeys.push(trailKey);
168
- }
169
- } else {
170
- // No trail group found locally — keep local order then append new keys and trail group
171
- orderedKeys.push(...localKeys);
172
- orderedKeys.push(...templateKeys);
173
- for (const trailKey of TRAIL_GROUP) {
174
- if (templateKeys.includes(trailKey)) orderedKeys.push(trailKey);
175
- }
176
- }
177
-
178
- // 3. Merge values for each key
179
- for (const key of orderedKeys) {
180
- merged[key] = mergeJsonValues(local[key], template[key]);
181
- }
182
-
183
- return merged;
184
- }
185
-
186
107
  function mergeStringMaps(
187
108
  local: Record<string, string> | undefined,
188
109
  template: Record<string, string> | undefined,
@@ -328,7 +249,7 @@ function writeSyncedFile(sourceDir: string, projectDir: string, filePath: string
328
249
  if (localContent) {
329
250
  const local = JSON.parse(localContent) as Record<string, unknown>;
330
251
  const template = JSON.parse(templateContent) as Record<string, unknown>;
331
- const merged = mergeBosConfig(local, template);
252
+ const merged = mergeBosConfigWithTemplate(local, template);
332
253
  writeFileSync(dest, `${JSON.stringify(merged, null, 2)}\n`);
333
254
  return;
334
255
  }
@@ -351,11 +272,20 @@ function writeSyncedFile(sourceDir: string, projectDir: string, filePath: string
351
272
  }
352
273
 
353
274
  export async function syncTemplate(projectDir: string, options: SyncOptions): Promise<SyncResult> {
275
+ // Sync reads the raw bos.config.json (not the resolved config) because it needs
276
+ // the user's explicit local settings: their extends ref, selected plugins, etc.
277
+ // The resolved config is the merged result and would include inherited parent
278
+ // values that the user didn't explicitly choose, which would break sync filtering.
354
279
  const localConfig = JSON.parse(
355
280
  readFileSync(join(projectDir, "bos.config.json"), "utf-8"),
356
281
  ) as Record<string, unknown>;
357
282
 
358
- const extendsRef = localConfig.extends as string | undefined;
283
+ let extendsRef: string | undefined;
284
+ if (typeof localConfig.extends === "string") {
285
+ extendsRef = localConfig.extends;
286
+ } else if (isPlainObjectFromMerge(localConfig.extends)) {
287
+ extendsRef = resolveExtendsRef(localConfig.extends as Record<string, string>, "production");
288
+ }
359
289
  if (!extendsRef?.startsWith("bos://")) {
360
290
  return {
361
291
  status: "error",
@@ -421,8 +351,9 @@ export async function syncTemplate(projectDir: string, options: SyncOptions): Pr
421
351
 
422
352
  const pluginRoutes: Record<string, string[]> = {};
423
353
  if (parentConfig.plugins) {
424
- for (const [key, ref] of Object.entries(parentConfig.plugins)) {
425
- if (ref.routes && ref.routes.length > 0) {
354
+ for (const [key, entry] of Object.entries(parentConfig.plugins)) {
355
+ const ref: BosPluginRef | null = entry && typeof entry !== "string" ? entry : null;
356
+ if (ref?.routes && ref.routes.length > 0) {
426
357
  pluginRoutes[key] = ref.routes;
427
358
  }
428
359
  }
@@ -439,7 +370,7 @@ export async function syncTemplate(projectDir: string, options: SyncOptions): Pr
439
370
  for (const filePath of allTemplateFiles) {
440
371
  const pluginMatch = filePath.match(/^plugins\/([^/]+)/);
441
372
  if (pluginMatch && !childPlugins.includes(pluginMatch[1])) continue;
442
- if (isExcluded(filePath, excludedRoutePatterns)) continue;
373
+ if (isPathExcluded(filePath, excludedRoutePatterns)) continue;
443
374
  filteredFiles.add(filePath);
444
375
  }
445
376
 
@@ -453,7 +384,7 @@ export async function syncTemplate(projectDir: string, options: SyncOptions): Pr
453
384
  absolute: false,
454
385
  });
455
386
  for (const match of matches) {
456
- if (!isExcluded(match, excludedRoutePatterns)) {
387
+ if (!isPathExcluded(match, excludedRoutePatterns)) {
457
388
  filteredFiles.add(match);
458
389
  }
459
390
  }
@@ -469,7 +400,7 @@ export async function syncTemplate(projectDir: string, options: SyncOptions): Pr
469
400
  for (const filePath of filteredFiles) {
470
401
  const destPath = toDestPath(filePath);
471
402
  const frameworkOwned = isFrameworkOwnedSyncFile(destPath);
472
- if (isExcluded(destPath, excludePatterns) && !frameworkOwned) continue;
403
+ if (isPathExcluded(destPath, excludePatterns) && !frameworkOwned) continue;
473
404
 
474
405
  const localHash = computeLocalHash(projectDir, destPath);
475
406
  const sourceContent = readFileSync(join(sourceDir, filePath));
@@ -515,7 +446,7 @@ export async function syncTemplate(projectDir: string, options: SyncOptions): Pr
515
446
  }
516
447
 
517
448
  const filesToWrite = [...updated, ...added].filter(
518
- (f) => isFrameworkOwnedSyncFile(f) || !isExcluded(f, excludePatterns),
449
+ (f) => isFrameworkOwnedSyncFile(f) || !isPathExcluded(f, excludePatterns),
519
450
  );
520
451
 
521
452
  const destToSource = new Map<string, string>();
@@ -25,6 +25,8 @@ const OBSOLETE_FILES = [
25
25
  "ui/src/lib/auth-client.ts",
26
26
  "ui/src/lib/session.ts",
27
27
  "ui/scripts/generate-metadata.ts",
28
+ ".github/dependabot.yml",
29
+ ".github/templates/dependabot.yml",
28
30
  ];
29
31
 
30
32
  interface NpmPackageInfo {