everything-dev 1.20.0 → 1.21.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.
Files changed (61) hide show
  1. package/dist/cli/init.cjs +138 -74
  2. package/dist/cli/init.cjs.map +1 -1
  3. package/dist/cli/init.d.cts +9 -5
  4. package/dist/cli/init.d.cts.map +1 -1
  5. package/dist/cli/init.d.mts +9 -5
  6. package/dist/cli/init.d.mts.map +1 -1
  7. package/dist/cli/init.mjs +138 -75
  8. package/dist/cli/init.mjs.map +1 -1
  9. package/dist/cli/parse.cjs +9 -0
  10. package/dist/cli/parse.cjs.map +1 -1
  11. package/dist/cli/parse.mjs +9 -0
  12. package/dist/cli/parse.mjs.map +1 -1
  13. package/dist/cli/prompts.cjs +44 -13
  14. package/dist/cli/prompts.cjs.map +1 -1
  15. package/dist/cli/prompts.mjs +44 -13
  16. package/dist/cli/prompts.mjs.map +1 -1
  17. package/dist/cli/sync.cjs +6 -0
  18. package/dist/cli/sync.cjs.map +1 -1
  19. package/dist/cli/sync.mjs +6 -0
  20. package/dist/cli/sync.mjs.map +1 -1
  21. package/dist/cli.cjs +3 -1
  22. package/dist/cli.cjs.map +1 -1
  23. package/dist/cli.mjs +3 -1
  24. package/dist/cli.mjs.map +1 -1
  25. package/dist/contract.cjs +9 -1
  26. package/dist/contract.cjs.map +1 -1
  27. package/dist/contract.d.cts +37 -8
  28. package/dist/contract.d.cts.map +1 -1
  29. package/dist/contract.d.mts +37 -8
  30. package/dist/contract.d.mts.map +1 -1
  31. package/dist/contract.meta.cjs +2 -2
  32. package/dist/contract.meta.cjs.map +1 -1
  33. package/dist/contract.meta.d.cts +3 -3
  34. package/dist/contract.meta.d.mts +3 -3
  35. package/dist/contract.meta.mjs +2 -2
  36. package/dist/contract.meta.mjs.map +1 -1
  37. package/dist/contract.mjs +9 -2
  38. package/dist/contract.mjs.map +1 -1
  39. package/dist/index.cjs +1 -0
  40. package/dist/index.d.cts +2 -2
  41. package/dist/index.d.mts +2 -2
  42. package/dist/index.mjs +2 -2
  43. package/dist/plugin.cjs +24 -12
  44. package/dist/plugin.cjs.map +1 -1
  45. package/dist/plugin.d.cts +16 -5
  46. package/dist/plugin.d.cts.map +1 -1
  47. package/dist/plugin.d.mts +16 -5
  48. package/dist/plugin.d.mts.map +1 -1
  49. package/dist/plugin.mjs +25 -13
  50. package/dist/plugin.mjs.map +1 -1
  51. package/dist/types.d.cts +2 -2
  52. package/dist/types.d.mts +2 -2
  53. package/package.json +1 -1
  54. package/src/cli/init.ts +215 -89
  55. package/src/cli/parse.ts +17 -0
  56. package/src/cli/prompts.ts +45 -28
  57. package/src/cli/sync.ts +1 -0
  58. package/src/cli.ts +8 -1
  59. package/src/contract.meta.ts +6 -2
  60. package/src/contract.ts +5 -1
  61. package/src/plugin.ts +41 -18
package/src/cli/init.ts CHANGED
@@ -15,6 +15,7 @@ import { dirname, join, resolve } from "node:path";
15
15
  import { pipeline } from "node:stream/promises";
16
16
  import { execa } from "execa";
17
17
  import { glob } from "glob";
18
+ import type { OverrideSection } from "../contract";
18
19
  import { fetchBosConfigFromFastKv } from "../fastkv";
19
20
  import {
20
21
  loadManifestNormalizationSpec,
@@ -27,6 +28,15 @@ import { writeSnapshot } from "./snapshot";
27
28
 
28
29
  const require = createRequire(import.meta.url);
29
30
 
31
+ const _DEFAULT_OVERRIDES: OverrideSection[] = ["ui", "api"];
32
+
33
+ const OVERRIDE_WORKSPACE_MAP: Record<OverrideSection, string[]> = {
34
+ ui: ["ui"],
35
+ api: ["api"],
36
+ host: ["host"],
37
+ plugins: [],
38
+ };
39
+
30
40
  interface SourceResult {
31
41
  sourceDir: string;
32
42
  parentConfig: BosConfig;
@@ -124,6 +134,32 @@ export async function resolveRepositoryViaExtendsChain(
124
134
  }
125
135
  }
126
136
 
137
+ export async function detectGitRemoteUrl(directory: string): Promise<string | undefined> {
138
+ try {
139
+ const { stdout } = await execa("git", ["remote", "get-url", "origin"], {
140
+ cwd: directory,
141
+ stdio: "pipe",
142
+ });
143
+ const url = stdout.trim();
144
+ if (!url) return undefined;
145
+ return normalizeGitUrl(url);
146
+ } catch {
147
+ return undefined;
148
+ }
149
+ }
150
+
151
+ function normalizeGitUrl(url: string): string | undefined {
152
+ const sshMatch = url.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
153
+ if (sshMatch) {
154
+ return `https://github.com/${sshMatch[1]}/${sshMatch[2]}`;
155
+ }
156
+ const httpsMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/.*)?$/);
157
+ if (httpsMatch) {
158
+ return `https://github.com/${httpsMatch[1]}/${httpsMatch[2]}`;
159
+ }
160
+ return url.endsWith(".git") ? url.slice(0, -4) : url;
161
+ }
162
+
127
163
  export async function downloadTarball(
128
164
  repoUrl: string,
129
165
  ): Promise<{ dir: string; cleanup: () => Promise<void> }> {
@@ -191,19 +227,51 @@ function parseGitHubUrl(url: string): { owner: string; repo: string; branch: str
191
227
  return null;
192
228
  }
193
229
 
230
+ function filterPatternsByOverrides(
231
+ patterns: string[],
232
+ overrides: OverrideSection[],
233
+ _plugins?: string[],
234
+ ): string[] {
235
+ const has = (section: OverrideSection) => overrides.includes(section);
236
+ let filtered = [...patterns];
237
+
238
+ if (!has("host")) {
239
+ filtered = filtered.filter((p) => !p.startsWith("host/") && p !== "host/**");
240
+ }
241
+ if (!has("ui")) {
242
+ filtered = filtered.filter((p) => !p.startsWith("ui/") && p !== "ui/**");
243
+ }
244
+ if (!has("api")) {
245
+ filtered = filtered.filter((p) => !p.startsWith("api/") && p !== "api/**");
246
+ }
247
+ if (!has("plugins")) {
248
+ filtered = filtered.filter((p) => !p.startsWith("plugins/") && p !== "plugins/**");
249
+ }
250
+
251
+ return filtered;
252
+ }
253
+
194
254
  export async function copyFilteredFiles(
195
255
  sourceDir: string,
196
256
  destination: string,
197
257
  patterns: string[],
198
- options: { withHost: boolean; plugins?: string[]; pluginRoutes?: Record<string, string[]> },
258
+ options: {
259
+ overrides: OverrideSection[];
260
+ plugins?: string[];
261
+ pluginRoutes?: Record<string, string[]>;
262
+ },
199
263
  ): Promise<number> {
200
264
  if (patterns.length === 0) {
201
265
  return 0;
202
266
  }
203
267
 
204
- const effectivePatterns = options.withHost
205
- ? [...patterns, "host/**"]
206
- : patterns.filter((p) => !p.startsWith("host/") && p !== "host/**");
268
+ const has = (section: OverrideSection) => options.overrides.includes(section);
269
+
270
+ const effectivePatterns = filterPatternsByOverrides(patterns, options.overrides, options.plugins);
271
+
272
+ if (has("host") && !effectivePatterns.some((p) => p.startsWith("host/") || p === "host/**")) {
273
+ effectivePatterns.push("host/**");
274
+ }
207
275
 
208
276
  const excludedRoutePatterns: string[] = [];
209
277
  if (options.pluginRoutes) {
@@ -276,6 +344,13 @@ export async function copyFilteredFiles(
276
344
  return count;
277
345
  }
278
346
 
347
+ function stripProductionFields(entry: Record<string, unknown>): void {
348
+ delete entry.production;
349
+ delete entry.integrity;
350
+ delete entry.ssr;
351
+ delete entry.ssrIntegrity;
352
+ }
353
+
279
354
  export async function personalizeConfig(
280
355
  destination: string,
281
356
  opts: {
@@ -284,13 +359,16 @@ export async function personalizeConfig(
284
359
  account?: string;
285
360
  domain?: string;
286
361
  plugins?: string[];
362
+ overrides: OverrideSection[];
287
363
  pluginRoutes?: Record<string, string[]>;
288
364
  workspaceOpts?: { localOverrides?: boolean; sourceDir?: string };
289
365
  mode?: "init" | "sync";
290
- withHost?: boolean;
366
+ repository?: string;
291
367
  },
292
368
  ): Promise<void> {
293
369
  const isInit = opts.mode !== "sync";
370
+ const has = (section: OverrideSection) => opts.overrides.includes(section);
371
+
294
372
  const configPath = join(destination, "bos.config.json");
295
373
  if (existsSync(configPath)) {
296
374
  const config = JSON.parse(readFileSync(configPath, "utf-8")) as Record<string, unknown>;
@@ -303,53 +381,62 @@ export async function personalizeConfig(
303
381
  if (opts.domain) {
304
382
  config.domain = opts.domain;
305
383
  }
384
+ if (opts.repository) {
385
+ config.repository = opts.repository;
386
+ }
306
387
 
307
388
  if (isInit && config.app && typeof config.app === "object") {
308
389
  const app = config.app as Record<string, unknown>;
309
390
 
310
391
  for (const entryKey of Object.keys(app)) {
392
+ if (
393
+ !has(entryKey as OverrideSection) &&
394
+ (entryKey === "host" || entryKey === "ui" || entryKey === "api" || entryKey === "auth")
395
+ ) {
396
+ delete app[entryKey];
397
+ continue;
398
+ }
311
399
  const entry = app[entryKey];
312
400
  if (entry && typeof entry === "object") {
313
- const e = entry as Record<string, unknown>;
314
- delete e.production;
315
- delete e.integrity;
316
- delete e.ssr;
317
- delete e.ssrIntegrity;
401
+ stripProductionFields(entry as Record<string, unknown>);
318
402
  }
319
403
  }
320
404
  }
321
405
 
322
- if (config.plugins && typeof config.plugins === "object") {
323
- const plugins = config.plugins as Record<string, unknown>;
406
+ if (has("plugins")) {
407
+ if (config.plugins && typeof config.plugins === "object") {
408
+ const plugins = config.plugins as Record<string, unknown>;
324
409
 
325
- if (opts.plugins !== undefined) {
326
- for (const pluginKey of Object.keys(plugins)) {
327
- if (!opts.plugins.includes(pluginKey)) {
328
- delete plugins[pluginKey];
410
+ if (opts.plugins !== undefined) {
411
+ for (const pluginKey of Object.keys(plugins)) {
412
+ if (!opts.plugins.includes(pluginKey)) {
413
+ delete plugins[pluginKey];
414
+ }
329
415
  }
330
416
  }
331
- }
332
417
 
333
- for (const pluginKey of Object.keys(plugins)) {
334
- const plugin = plugins[pluginKey];
335
- let pluginObj: Record<string, unknown>;
418
+ for (const pluginKey of Object.keys(plugins)) {
419
+ const plugin = plugins[pluginKey];
420
+ let pluginObj: Record<string, unknown>;
421
+
422
+ if (typeof plugin === "string") {
423
+ pluginObj = { extends: plugin };
424
+ plugins[pluginKey] = pluginObj;
425
+ } else if (plugin && typeof plugin === "object") {
426
+ pluginObj = { ...(plugin as Record<string, unknown>) };
427
+ } else {
428
+ continue;
429
+ }
336
430
 
337
- if (typeof plugin === "string") {
338
- pluginObj = { extends: plugin };
339
- plugins[pluginKey] = pluginObj;
340
- } else if (plugin && typeof plugin === "object") {
341
- pluginObj = { ...(plugin as Record<string, unknown>) };
342
- } else {
343
- continue;
431
+ stripProductionFields(pluginObj);
344
432
  }
345
433
 
346
- delete pluginObj.production;
347
- delete pluginObj.integrity;
348
- }
349
-
350
- if (Object.keys(plugins).length === 0) {
351
- config.plugins = {};
434
+ if (Object.keys(plugins).length === 0) {
435
+ config.plugins = {};
436
+ }
352
437
  }
438
+ } else {
439
+ delete config.plugins;
353
440
  }
354
441
 
355
442
  await saveBosConfig(destination, config);
@@ -364,10 +451,11 @@ export async function personalizeConfig(
364
451
  if (Array.isArray(ws.packages)) {
365
452
  ws.packages = ws.packages.filter((p: string) => {
366
453
  if (p.startsWith("packages/")) return false;
367
- if (p === "host") return opts.withHost ?? false;
368
- if (p === "plugins/*") return (opts.plugins?.length ?? 0) > 0;
454
+ if (p === "host") return has("host");
455
+ if (p === "plugins/*") return has("plugins") && (opts.plugins?.length ?? 0) > 0;
369
456
  const pluginMatch = p.match(/^plugins\/([^/]+)/);
370
- if (pluginMatch) return opts.plugins?.includes(pluginMatch[1]) ?? true;
457
+ if (pluginMatch)
458
+ return has("plugins") && (opts.plugins?.includes(pluginMatch[1]) ?? true);
371
459
  return true;
372
460
  });
373
461
  }
@@ -395,7 +483,7 @@ export async function personalizeConfig(
395
483
  scripts.typecheck = scripts.typecheck
396
484
  .replace("bun run types:gen && ", "")
397
485
  .replace(/bun run --cwd packages\/everything-dev typecheck & ?/, "");
398
- if (!opts.withHost) {
486
+ if (!has("host")) {
399
487
  scripts.typecheck = scripts.typecheck.replace(/bun run --cwd host tsc --noEmit & ?/, "");
400
488
  }
401
489
  }
@@ -451,27 +539,34 @@ export async function personalizeConfig(
451
539
 
452
540
  await resolveWorkspaceRefs(destination, opts.workspaceOpts);
453
541
 
454
- const genContractPath = join(destination, "ui", "src", "lib", "api-types.gen.ts");
455
- if (!existsSync(genContractPath)) {
456
- mkdirSync(dirname(genContractPath), { recursive: true });
457
- writeFileSync(genContractPath, `export type ApiContract = Record<string, never>;\n`);
542
+ if (has("ui")) {
543
+ const genContractPath = join(destination, "ui", "src", "lib", "api-types.gen.ts");
544
+ if (!existsSync(genContractPath)) {
545
+ mkdirSync(dirname(genContractPath), { recursive: true });
546
+ writeFileSync(genContractPath, `export type ApiContract = Record<string, never>;\n`);
547
+ }
458
548
  }
459
549
 
460
- const pluginsClientGenPath = join(destination, "api", "src", "lib", "plugins-types.gen.ts");
461
- if (!existsSync(pluginsClientGenPath)) {
462
- mkdirSync(dirname(pluginsClientGenPath), { recursive: true });
463
- writeFileSync(
464
- pluginsClientGenPath,
465
- `import type { ContractRouterClient, AnyContractRouter } from "@orpc/contract";\ntype ClientFactory<C extends AnyContractRouter> = (context?: Record<string, unknown>) => ContractRouterClient<C>;\nexport type PluginsClient = Record<string, never>;\n`,
466
- );
550
+ if (has("api")) {
551
+ const pluginsClientGenPath = join(destination, "api", "src", "lib", "plugins-types.gen.ts");
552
+ if (!existsSync(pluginsClientGenPath)) {
553
+ mkdirSync(dirname(pluginsClientGenPath), { recursive: true });
554
+ writeFileSync(
555
+ pluginsClientGenPath,
556
+ `import type { ContractRouterClient, AnyContractRouter } from "@orpc/contract";\ntype ClientFactory<C extends AnyContractRouter> = (context?: Record<string, unknown>) => ContractRouterClient<C>;\nexport type PluginsClient = Record<string, never>;\n`,
557
+ );
558
+ }
467
559
  }
468
560
 
469
561
  const authTypesContent = generateAuthTypesTemplate();
470
- const authTypesPaths = [
471
- join(destination, "ui", "src", "lib", "auth-types.gen.ts"),
472
- join(destination, "api", "src", "lib", "auth-types.gen.ts"),
473
- ];
474
- if (existsSync(join(destination, "host", "src"))) {
562
+ const authTypesPaths: string[] = [];
563
+ if (has("ui")) {
564
+ authTypesPaths.push(join(destination, "ui", "src", "lib", "auth-types.gen.ts"));
565
+ }
566
+ if (has("api")) {
567
+ authTypesPaths.push(join(destination, "api", "src", "lib", "auth-types.gen.ts"));
568
+ }
569
+ if (has("host") && existsSync(join(destination, "host", "src"))) {
475
570
  authTypesPaths.push(join(destination, "host", "src", "lib", "auth-types.gen.ts"));
476
571
  }
477
572
  for (const authTypesGenPath of authTypesPaths) {
@@ -559,55 +654,51 @@ export async function scaffoldMinimalProject(
559
654
  account?: string;
560
655
  domain?: string;
561
656
  plugins?: string[];
562
- withHost?: boolean;
657
+ overrides: OverrideSection[];
658
+ repository?: string;
563
659
  },
564
660
  ): Promise<number> {
565
661
  mkdirSync(destination, { recursive: true });
566
662
 
663
+ const has = (section: OverrideSection) => opts.overrides.includes(section);
664
+
567
665
  const config: Record<string, unknown> = {
568
666
  extends: `bos://${opts.extendsAccount}/${opts.extendsGateway}`,
569
667
  account: opts.account || opts.extendsAccount,
570
668
  ...(opts.domain ? { domain: opts.domain } : {}),
669
+ ...(opts.repository ? { repository: opts.repository } : {}),
571
670
  };
572
671
 
573
672
  if (parentConfig.app && typeof parentConfig.app === "object") {
574
673
  const app: Record<string, unknown> = {};
575
674
  const parentApp = parentConfig.app as Record<string, Record<string, unknown>>;
576
675
 
577
- if (parentApp.host) {
676
+ if (has("host") && parentApp.host) {
578
677
  app.host = { ...parentApp.host };
579
- const host = app.host as Record<string, unknown>;
580
- delete host.production;
581
- delete host.integrity;
678
+ stripProductionFields(app.host as Record<string, unknown>);
582
679
  }
583
680
 
584
- if (parentApp.ui) {
681
+ if (has("ui") && parentApp.ui) {
585
682
  app.ui = { ...parentApp.ui };
586
- const ui = app.ui as Record<string, unknown>;
587
- delete ui.production;
588
- delete ui.integrity;
589
- delete ui.ssr;
590
- delete ui.ssrIntegrity;
683
+ stripProductionFields(app.ui as Record<string, unknown>);
591
684
  }
592
685
 
593
- if (parentApp.api) {
686
+ if (has("api") && parentApp.api) {
594
687
  app.api = { ...parentApp.api };
595
- const api = app.api as Record<string, unknown>;
596
- delete api.production;
597
- delete api.integrity;
688
+ stripProductionFields(app.api as Record<string, unknown>);
598
689
  }
599
690
 
600
- if (parentApp.auth) {
691
+ if (has("plugins") && parentApp.auth) {
601
692
  app.auth = { ...parentApp.auth };
602
- const auth = app.auth as Record<string, unknown>;
603
- delete auth.production;
604
- delete auth.integrity;
693
+ stripProductionFields(app.auth as Record<string, unknown>);
605
694
  }
606
695
 
607
- config.app = app;
696
+ if (Object.keys(app).length > 0) {
697
+ config.app = app;
698
+ }
608
699
  }
609
700
 
610
- if (opts.plugins && opts.plugins.length > 0 && parentConfig.plugins) {
701
+ if (has("plugins") && opts.plugins && opts.plugins.length > 0 && parentConfig.plugins) {
611
702
  const plugins: Record<string, unknown> = {};
612
703
  for (const key of opts.plugins) {
613
704
  const parentPlugin = (parentConfig.plugins as Record<string, unknown>)?.[key];
@@ -616,8 +707,7 @@ export async function scaffoldMinimalProject(
616
707
  plugins[key] = { extends: parentPlugin };
617
708
  } else {
618
709
  const pluginCopy = { ...(parentPlugin as Record<string, unknown>) };
619
- delete pluginCopy.production;
620
- delete pluginCopy.integrity;
710
+ stripProductionFields(pluginCopy);
621
711
  plugins[key] = pluginCopy;
622
712
  }
623
713
  }
@@ -627,6 +717,16 @@ export async function scaffoldMinimalProject(
627
717
 
628
718
  await saveBosConfig(destination, config);
629
719
 
720
+ const workspacePackages: string[] = [];
721
+ for (const section of opts.overrides) {
722
+ workspacePackages.push(...OVERRIDE_WORKSPACE_MAP[section]);
723
+ }
724
+ if (has("plugins") && opts.plugins) {
725
+ for (const plugin of opts.plugins) {
726
+ workspacePackages.push(`plugins/${plugin}`);
727
+ }
728
+ }
729
+
630
730
  const pkg: Record<string, unknown> = {
631
731
  name: opts.domain || opts.extendsGateway,
632
732
  private: true,
@@ -649,13 +749,13 @@ export async function scaffoldMinimalProject(
649
749
  },
650
750
  devDependencies: {},
651
751
  workspaces: {
652
- packages: [],
752
+ packages: workspacePackages,
653
753
  catalog: {},
654
754
  },
655
755
  };
656
756
  writeFileSync(join(destination, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`);
657
757
 
658
- const envExample = generateEnvExample(parentConfig);
758
+ const envExample = generateEnvExample(parentConfig, opts.overrides);
659
759
  if (envExample) {
660
760
  writeFileSync(join(destination, ".env.example"), envExample);
661
761
  }
@@ -714,11 +814,18 @@ export async function writeInitSnapshot(
714
814
  extendsGateway: string,
715
815
  sourceDir: string,
716
816
  patterns: string[],
717
- options: { withHost: boolean; plugins?: string[]; pluginRoutes?: Record<string, string[]> },
817
+ options: {
818
+ overrides: OverrideSection[];
819
+ plugins?: string[];
820
+ pluginRoutes?: Record<string, string[]>;
821
+ },
718
822
  ): Promise<void> {
719
- const effectivePatterns = options.withHost
720
- ? [...patterns, "host/**"]
721
- : patterns.filter((p) => !p.startsWith("host/") && p !== "host/**");
823
+ const effectivePatterns = filterPatternsByOverrides(patterns, options.overrides, options.plugins);
824
+
825
+ const has = (section: OverrideSection) => options.overrides.includes(section);
826
+ if (has("host") && !effectivePatterns.some((p) => p.startsWith("host/") || p === "host/**")) {
827
+ effectivePatterns.push("host/**");
828
+ }
722
829
 
723
830
  const excludedRoutePatterns: string[] = [];
724
831
  if (options.pluginRoutes) {
@@ -817,10 +924,17 @@ export async function execCommand(command: string, args: string[], cwd?: string)
817
924
  await execa(command, args, { cwd, stdio: "pipe" });
818
925
  }
819
926
 
820
- function generateEnvExample(config: BosConfigInput): string {
927
+ function generateEnvExample(config: BosConfigInput, overrides: OverrideSection[]): string {
928
+ const has = (section: OverrideSection) => overrides.includes(section);
929
+
821
930
  const lines: string[] = ["# Environment variables"];
822
- const collectSecrets = (obj: Record<string, unknown>, prefix = ""): void => {
931
+ const collectSecrets = (
932
+ obj: Record<string, unknown>,
933
+ includeSection: boolean,
934
+ prefix = "",
935
+ ): void => {
823
936
  for (const [key, value] of Object.entries(obj)) {
937
+ if (!includeSection) continue;
824
938
  if (key === "secrets" && Array.isArray(value)) {
825
939
  for (const secret of value) {
826
940
  if (typeof secret === "string") {
@@ -834,16 +948,28 @@ function generateEnvExample(config: BosConfigInput): string {
834
948
  }
835
949
  }
836
950
  } else if (isPlainObject(value) && key !== "extends") {
837
- collectSecrets(value as Record<string, unknown>, `${prefix}${key}.`);
951
+ collectSecrets(value as Record<string, unknown>, includeSection, `${prefix}${key}.`);
838
952
  }
839
953
  }
840
954
  };
841
955
 
842
956
  if (config.app && typeof config.app === "object") {
843
- collectSecrets(config.app as Record<string, unknown>);
844
- }
845
- if (config.plugins && typeof config.plugins === "object") {
846
- collectSecrets(config.plugins as Record<string, unknown>);
957
+ const app = config.app as Record<string, unknown>;
958
+ collectSecrets(app, has("host"), "host.");
959
+ collectSecrets(app, has("ui"), "ui.");
960
+ collectSecrets(app, has("api"), "api.");
961
+ collectSecrets(app, has("plugins"), "auth.");
962
+ }
963
+ if (has("plugins") && config.plugins && typeof config.plugins === "object") {
964
+ for (const [pluginKey, pluginVal] of Object.entries(
965
+ config.plugins as Record<string, unknown>,
966
+ )) {
967
+ if (isPlainObject(pluginVal)) {
968
+ collectSecrets(pluginVal as Record<string, unknown>, true);
969
+ } else if (typeof pluginVal === "string") {
970
+ lines.push(`# Plugin '${pluginKey}' extends ${pluginVal}`);
971
+ }
972
+ }
847
973
  }
848
974
 
849
975
  lines.push("BETTER_AUTH_SECRET=generate-a-secret-here");
package/src/cli/parse.ts CHANGED
@@ -29,6 +29,10 @@ function isBooleanSchema(schema: SchemaLike): boolean {
29
29
  return unwrap(schema)._def?.type === "boolean";
30
30
  }
31
31
 
32
+ function isArraySchema(schema: SchemaLike): boolean {
33
+ return unwrap(schema)._def?.type === "array";
34
+ }
35
+
32
36
  function coerceValue(raw: string, schema: SchemaLike): unknown {
33
37
  const inner = unwrap(schema);
34
38
  switch (inner._def?.type) {
@@ -100,6 +104,19 @@ export function parseCommandInput(descriptor: CommandDescriptor, argv: string[])
100
104
  }
101
105
 
102
106
  const next = inline ?? argv[i + 1];
107
+
108
+ if (isArraySchema(fieldSchema)) {
109
+ if (next === undefined || next.startsWith("--")) {
110
+ throw new Error(`Missing value for ${flag}`);
111
+ }
112
+ input[fieldName] = next
113
+ .split(",")
114
+ .map((s: string) => s.trim())
115
+ .filter(Boolean);
116
+ if (!inline) i += 1;
117
+ continue;
118
+ }
119
+
103
120
  if (next === undefined || next.startsWith("--")) {
104
121
  throw new Error(`Missing value for ${flag}`);
105
122
  }
@@ -1,5 +1,6 @@
1
1
  import process from "node:process";
2
2
  import * as p from "@clack/prompts";
3
+ import type { OverrideSection } from "../contract";
3
4
 
4
5
  function parseExtendsRef(ref: string): { account: string; gateway: string } | null {
5
6
  const normalized = ref.startsWith("bos://") ? ref : `bos://${ref}`;
@@ -17,13 +18,20 @@ function deriveAccountFromExtends(domain: string, extendsAccount: string): strin
17
18
  return `${firstSegment}.${suffix}`;
18
19
  }
19
20
 
21
+ const OVERRIDE_OPTIONS: { value: OverrideSection; label: string; hint: string }[] = [
22
+ { value: "ui", label: "ui", hint: "Override UI with local source" },
23
+ { value: "api", label: "api", hint: "Override API with local source" },
24
+ { value: "host", label: "host", hint: "Override host with local source" },
25
+ { value: "plugins", label: "plugins", hint: "Override selected plugins with local source" },
26
+ ];
27
+
20
28
  export async function promptInitOptions(input: {
21
29
  extends?: string;
22
30
  directory?: string;
23
31
  account?: string;
24
32
  domain?: string;
25
33
  plugins?: string[];
26
- withHost?: boolean;
34
+ overrides?: OverrideSection[];
27
35
  parentPluginKeys?: string[];
28
36
  }): Promise<{
29
37
  extendsAccount: string;
@@ -32,7 +40,7 @@ export async function promptInitOptions(input: {
32
40
  account?: string;
33
41
  domain?: string;
34
42
  plugins: string[];
35
- withHost: boolean;
43
+ overrides: OverrideSection[];
36
44
  }> {
37
45
  p.intro("Let's build an app...");
38
46
 
@@ -78,34 +86,43 @@ export async function promptInitOptions(input: {
78
86
 
79
87
  const directory = input.directory || domain || extendsGateway;
80
88
 
81
- const parentPlugins = input.parentPluginKeys ?? [];
82
- const pluginOptions =
83
- parentPlugins.length > 0 ? parentPlugins.map((key) => ({ value: key, label: key })) : [];
84
-
85
- const plugins =
86
- input.plugins ??
87
- (pluginOptions.length > 0
88
- ? ((await p.multiselect({
89
- message: "Select plugins:",
90
- options: pluginOptions,
91
- required: false,
92
- })) as string[])
93
- : []);
94
-
95
- if (p.isCancel(plugins)) process.exit(0);
96
-
97
- const go =
98
- input.withHost !== undefined
99
- ? true
100
- : await p.confirm({
101
- message: "GO!",
102
- initialValue: true,
103
- });
89
+ const overrides =
90
+ input.overrides ??
91
+ ((await p.multiselect({
92
+ message: "Which sections to override locally?",
93
+ options: OVERRIDE_OPTIONS,
94
+ initialValues: ["ui", "api"] as OverrideSection[],
95
+ required: false,
96
+ })) as OverrideSection[]);
97
+
98
+ if (p.isCancel(overrides)) process.exit(0);
99
+
100
+ let plugins: string[] = [];
101
+ if (overrides.includes("plugins")) {
102
+ const parentPlugins = input.parentPluginKeys ?? [];
103
+ const pluginOptions =
104
+ parentPlugins.length > 0 ? parentPlugins.map((key) => ({ value: key, label: key })) : [];
105
+
106
+ plugins =
107
+ input.plugins ??
108
+ (pluginOptions.length > 0
109
+ ? ((await p.multiselect({
110
+ message: "Select plugins to include:",
111
+ options: pluginOptions,
112
+ required: false,
113
+ })) as string[])
114
+ : []);
115
+
116
+ if (p.isCancel(plugins)) process.exit(0);
117
+ }
118
+
119
+ const go = await p.confirm({
120
+ message: "GO!",
121
+ initialValue: true,
122
+ });
104
123
 
105
124
  if (p.isCancel(go) || !go) process.exit(0);
106
125
 
107
- const withHost = input.withHost ?? false;
108
-
109
126
  return {
110
127
  extendsAccount,
111
128
  extendsGateway,
@@ -113,6 +130,6 @@ export async function promptInitOptions(input: {
113
130
  account: account || undefined,
114
131
  domain: domain || undefined,
115
132
  plugins,
116
- withHost,
133
+ overrides,
117
134
  };
118
135
  }
package/src/cli/sync.ts CHANGED
@@ -491,6 +491,7 @@ export async function syncTemplate(projectDir: string, options: SyncOptions): Pr
491
491
  domain,
492
492
  plugins: childPlugins,
493
493
  pluginRoutes,
494
+ overrides: ["ui", "api", "host", "plugins"],
494
495
  workspaceOpts: { sourceDir },
495
496
  mode: "sync",
496
497
  });
package/src/cli.ts CHANGED
@@ -70,9 +70,14 @@ async function warnIfOutdated(client: any, command: string): Promise<void> {
70
70
  const status = await client.status();
71
71
  if (status.status === "error" || !status.packages) return;
72
72
 
73
+ const frameworkPackages = ["everything-dev", "every-plugin"];
74
+
73
75
  const outdated = status.packages.filter(
74
76
  (p: { name: string; installed?: string; latest?: string }) =>
75
- p.installed && p.latest && normalizeVersion(p.installed) !== normalizeVersion(p.latest),
77
+ p.installed &&
78
+ p.latest &&
79
+ normalizeVersion(p.installed) !== normalizeVersion(p.latest) &&
80
+ frameworkPackages.includes(p.name),
76
81
  );
77
82
 
78
83
  if (outdated.length === 0) return;
@@ -162,6 +167,8 @@ async function main() {
162
167
  console.log(` ${colors.dim("Directory:")} ${result.directory}`);
163
168
  if (result.account) console.log(` ${colors.dim("Account:")} ${result.account}`);
164
169
  if (result.domain) console.log(` ${colors.dim("Domain:")} ${result.domain}`);
170
+ if (result.overrides && result.overrides.length > 0)
171
+ console.log(` ${colors.dim("Overrides:")} ${result.overrides.join(", ")}`);
165
172
  if (result.plugins && result.plugins.length > 0)
166
173
  console.log(` ${colors.dim("Plugins:")} ${result.plugins.join(", ")}`);
167
174
  console.log(` ${colors.dim("Files copied:")} ${result.filesCopied}`);