everything-dev 1.20.0 → 1.22.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 (64) hide show
  1. package/dist/cli/init.cjs +163 -80
  2. package/dist/cli/init.cjs.map +1 -1
  3. package/dist/cli/init.d.cts +12 -6
  4. package/dist/cli/init.d.cts.map +1 -1
  5. package/dist/cli/init.d.mts +12 -6
  6. package/dist/cli/init.d.mts.map +1 -1
  7. package/dist/cli/init.mjs +163 -81
  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 +33 -4
  28. package/dist/contract.d.cts.map +1 -1
  29. package/dist/contract.d.mts +33 -4
  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/fastkv.cjs +4 -1
  40. package/dist/fastkv.cjs.map +1 -1
  41. package/dist/fastkv.mjs +4 -1
  42. package/dist/fastkv.mjs.map +1 -1
  43. package/dist/index.cjs +1 -0
  44. package/dist/index.d.cts +2 -2
  45. package/dist/index.d.mts +2 -2
  46. package/dist/index.mjs +2 -2
  47. package/dist/plugin.cjs +43 -20
  48. package/dist/plugin.cjs.map +1 -1
  49. package/dist/plugin.d.cts +13 -2
  50. package/dist/plugin.d.cts.map +1 -1
  51. package/dist/plugin.d.mts +13 -2
  52. package/dist/plugin.d.mts.map +1 -1
  53. package/dist/plugin.mjs +44 -21
  54. package/dist/plugin.mjs.map +1 -1
  55. package/package.json +1 -1
  56. package/src/cli/init.ts +252 -95
  57. package/src/cli/parse.ts +17 -0
  58. package/src/cli/prompts.ts +45 -28
  59. package/src/cli/sync.ts +1 -0
  60. package/src/cli.ts +8 -1
  61. package/src/contract.meta.ts +6 -2
  62. package/src/contract.ts +5 -1
  63. package/src/fastkv.ts +6 -1
  64. package/src/plugin.ts +58 -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,17 @@ import { writeSnapshot } from "./snapshot";
27
28
 
28
29
  const require = createRequire(import.meta.url);
29
30
 
31
+ const FRAMEWORK_PACKAGES = ["every-plugin", "everything-dev"] as const;
32
+
33
+ const _DEFAULT_OVERRIDES: OverrideSection[] = ["ui", "api"];
34
+
35
+ const OVERRIDE_WORKSPACE_MAP: Record<OverrideSection, string[]> = {
36
+ ui: ["ui"],
37
+ api: ["api"],
38
+ host: ["host"],
39
+ plugins: [],
40
+ };
41
+
30
42
  interface SourceResult {
31
43
  sourceDir: string;
32
44
  parentConfig: BosConfig;
@@ -124,6 +136,32 @@ export async function resolveRepositoryViaExtendsChain(
124
136
  }
125
137
  }
126
138
 
139
+ export async function detectGitRemoteUrl(directory: string): Promise<string | undefined> {
140
+ try {
141
+ const { stdout } = await execa("git", ["remote", "get-url", "origin"], {
142
+ cwd: directory,
143
+ stdio: "pipe",
144
+ });
145
+ const url = stdout.trim();
146
+ if (!url) return undefined;
147
+ return normalizeGitUrl(url);
148
+ } catch {
149
+ return undefined;
150
+ }
151
+ }
152
+
153
+ function normalizeGitUrl(url: string): string | undefined {
154
+ const sshMatch = url.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
155
+ if (sshMatch) {
156
+ return `https://github.com/${sshMatch[1]}/${sshMatch[2]}`;
157
+ }
158
+ const httpsMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/.*)?$/);
159
+ if (httpsMatch) {
160
+ return `https://github.com/${httpsMatch[1]}/${httpsMatch[2]}`;
161
+ }
162
+ return url.endsWith(".git") ? url.slice(0, -4) : url;
163
+ }
164
+
127
165
  export async function downloadTarball(
128
166
  repoUrl: string,
129
167
  ): Promise<{ dir: string; cleanup: () => Promise<void> }> {
@@ -191,19 +229,51 @@ function parseGitHubUrl(url: string): { owner: string; repo: string; branch: str
191
229
  return null;
192
230
  }
193
231
 
232
+ function filterPatternsByOverrides(
233
+ patterns: string[],
234
+ overrides: OverrideSection[],
235
+ _plugins?: string[],
236
+ ): string[] {
237
+ const has = (section: OverrideSection) => overrides.includes(section);
238
+ let filtered = [...patterns];
239
+
240
+ if (!has("host")) {
241
+ filtered = filtered.filter((p) => !p.startsWith("host/") && p !== "host/**");
242
+ }
243
+ if (!has("ui")) {
244
+ filtered = filtered.filter((p) => !p.startsWith("ui/") && p !== "ui/**");
245
+ }
246
+ if (!has("api")) {
247
+ filtered = filtered.filter((p) => !p.startsWith("api/") && p !== "api/**");
248
+ }
249
+ if (!has("plugins")) {
250
+ filtered = filtered.filter((p) => !p.startsWith("plugins/") && p !== "plugins/**");
251
+ }
252
+
253
+ return filtered;
254
+ }
255
+
194
256
  export async function copyFilteredFiles(
195
257
  sourceDir: string,
196
258
  destination: string,
197
259
  patterns: string[],
198
- options: { withHost: boolean; plugins?: string[]; pluginRoutes?: Record<string, string[]> },
260
+ options: {
261
+ overrides: OverrideSection[];
262
+ plugins?: string[];
263
+ pluginRoutes?: Record<string, string[]>;
264
+ },
199
265
  ): Promise<number> {
200
266
  if (patterns.length === 0) {
201
267
  return 0;
202
268
  }
203
269
 
204
- const effectivePatterns = options.withHost
205
- ? [...patterns, "host/**"]
206
- : patterns.filter((p) => !p.startsWith("host/") && p !== "host/**");
270
+ const has = (section: OverrideSection) => options.overrides.includes(section);
271
+
272
+ const effectivePatterns = filterPatternsByOverrides(patterns, options.overrides, options.plugins);
273
+
274
+ if (has("host") && !effectivePatterns.some((p) => p.startsWith("host/") || p === "host/**")) {
275
+ effectivePatterns.push("host/**");
276
+ }
207
277
 
208
278
  const excludedRoutePatterns: string[] = [];
209
279
  if (options.pluginRoutes) {
@@ -276,6 +346,13 @@ export async function copyFilteredFiles(
276
346
  return count;
277
347
  }
278
348
 
349
+ function stripProductionFields(entry: Record<string, unknown>): void {
350
+ delete entry.production;
351
+ delete entry.integrity;
352
+ delete entry.ssr;
353
+ delete entry.ssrIntegrity;
354
+ }
355
+
279
356
  export async function personalizeConfig(
280
357
  destination: string,
281
358
  opts: {
@@ -284,13 +361,16 @@ export async function personalizeConfig(
284
361
  account?: string;
285
362
  domain?: string;
286
363
  plugins?: string[];
364
+ overrides: OverrideSection[];
287
365
  pluginRoutes?: Record<string, string[]>;
288
366
  workspaceOpts?: { localOverrides?: boolean; sourceDir?: string };
289
367
  mode?: "init" | "sync";
290
- withHost?: boolean;
368
+ repository?: string;
291
369
  },
292
370
  ): Promise<void> {
293
371
  const isInit = opts.mode !== "sync";
372
+ const has = (section: OverrideSection) => opts.overrides.includes(section);
373
+
294
374
  const configPath = join(destination, "bos.config.json");
295
375
  if (existsSync(configPath)) {
296
376
  const config = JSON.parse(readFileSync(configPath, "utf-8")) as Record<string, unknown>;
@@ -303,53 +383,62 @@ export async function personalizeConfig(
303
383
  if (opts.domain) {
304
384
  config.domain = opts.domain;
305
385
  }
386
+ if (opts.repository) {
387
+ config.repository = opts.repository;
388
+ }
306
389
 
307
390
  if (isInit && config.app && typeof config.app === "object") {
308
391
  const app = config.app as Record<string, unknown>;
309
392
 
310
393
  for (const entryKey of Object.keys(app)) {
394
+ if (
395
+ !has(entryKey as OverrideSection) &&
396
+ (entryKey === "host" || entryKey === "ui" || entryKey === "api" || entryKey === "auth")
397
+ ) {
398
+ delete app[entryKey];
399
+ continue;
400
+ }
311
401
  const entry = app[entryKey];
312
402
  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;
403
+ stripProductionFields(entry as Record<string, unknown>);
318
404
  }
319
405
  }
320
406
  }
321
407
 
322
- if (config.plugins && typeof config.plugins === "object") {
323
- const plugins = config.plugins as Record<string, unknown>;
408
+ if (has("plugins")) {
409
+ if (config.plugins && typeof config.plugins === "object") {
410
+ const plugins = config.plugins as Record<string, unknown>;
324
411
 
325
- if (opts.plugins !== undefined) {
326
- for (const pluginKey of Object.keys(plugins)) {
327
- if (!opts.plugins.includes(pluginKey)) {
328
- delete plugins[pluginKey];
412
+ if (opts.plugins !== undefined) {
413
+ for (const pluginKey of Object.keys(plugins)) {
414
+ if (!opts.plugins.includes(pluginKey)) {
415
+ delete plugins[pluginKey];
416
+ }
329
417
  }
330
418
  }
331
- }
332
419
 
333
- for (const pluginKey of Object.keys(plugins)) {
334
- const plugin = plugins[pluginKey];
335
- let pluginObj: Record<string, unknown>;
420
+ for (const pluginKey of Object.keys(plugins)) {
421
+ const plugin = plugins[pluginKey];
422
+ let pluginObj: Record<string, unknown>;
423
+
424
+ if (typeof plugin === "string") {
425
+ pluginObj = { extends: plugin };
426
+ plugins[pluginKey] = pluginObj;
427
+ } else if (plugin && typeof plugin === "object") {
428
+ pluginObj = { ...(plugin as Record<string, unknown>) };
429
+ } else {
430
+ continue;
431
+ }
336
432
 
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;
433
+ stripProductionFields(pluginObj);
344
434
  }
345
435
 
346
- delete pluginObj.production;
347
- delete pluginObj.integrity;
348
- }
349
-
350
- if (Object.keys(plugins).length === 0) {
351
- config.plugins = {};
436
+ if (Object.keys(plugins).length === 0) {
437
+ config.plugins = {};
438
+ }
352
439
  }
440
+ } else {
441
+ delete config.plugins;
353
442
  }
354
443
 
355
444
  await saveBosConfig(destination, config);
@@ -364,10 +453,11 @@ export async function personalizeConfig(
364
453
  if (Array.isArray(ws.packages)) {
365
454
  ws.packages = ws.packages.filter((p: string) => {
366
455
  if (p.startsWith("packages/")) return false;
367
- if (p === "host") return opts.withHost ?? false;
368
- if (p === "plugins/*") return (opts.plugins?.length ?? 0) > 0;
456
+ if (p === "host") return has("host");
457
+ if (p === "plugins/*") return has("plugins") && (opts.plugins?.length ?? 0) > 0;
369
458
  const pluginMatch = p.match(/^plugins\/([^/]+)/);
370
- if (pluginMatch) return opts.plugins?.includes(pluginMatch[1]) ?? true;
459
+ if (pluginMatch)
460
+ return has("plugins") && (opts.plugins?.includes(pluginMatch[1]) ?? true);
371
461
  return true;
372
462
  });
373
463
  }
@@ -395,7 +485,7 @@ export async function personalizeConfig(
395
485
  scripts.typecheck = scripts.typecheck
396
486
  .replace("bun run types:gen && ", "")
397
487
  .replace(/bun run --cwd packages\/everything-dev typecheck & ?/, "");
398
- if (!opts.withHost) {
488
+ if (!has("host")) {
399
489
  scripts.typecheck = scripts.typecheck.replace(/bun run --cwd host tsc --noEmit & ?/, "");
400
490
  }
401
491
  }
@@ -451,27 +541,34 @@ export async function personalizeConfig(
451
541
 
452
542
  await resolveWorkspaceRefs(destination, opts.workspaceOpts);
453
543
 
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`);
544
+ if (has("ui")) {
545
+ const genContractPath = join(destination, "ui", "src", "lib", "api-types.gen.ts");
546
+ if (!existsSync(genContractPath)) {
547
+ mkdirSync(dirname(genContractPath), { recursive: true });
548
+ writeFileSync(genContractPath, `export type ApiContract = Record<string, never>;\n`);
549
+ }
458
550
  }
459
551
 
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
- );
552
+ if (has("api")) {
553
+ const pluginsClientGenPath = join(destination, "api", "src", "lib", "plugins-types.gen.ts");
554
+ if (!existsSync(pluginsClientGenPath)) {
555
+ mkdirSync(dirname(pluginsClientGenPath), { recursive: true });
556
+ writeFileSync(
557
+ pluginsClientGenPath,
558
+ `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`,
559
+ );
560
+ }
467
561
  }
468
562
 
469
563
  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"))) {
564
+ const authTypesPaths: string[] = [];
565
+ if (has("ui")) {
566
+ authTypesPaths.push(join(destination, "ui", "src", "lib", "auth-types.gen.ts"));
567
+ }
568
+ if (has("api")) {
569
+ authTypesPaths.push(join(destination, "api", "src", "lib", "auth-types.gen.ts"));
570
+ }
571
+ if (has("host") && existsSync(join(destination, "host", "src"))) {
475
572
  authTypesPaths.push(join(destination, "host", "src", "lib", "auth-types.gen.ts"));
476
573
  }
477
574
  for (const authTypesGenPath of authTypesPaths) {
@@ -534,15 +631,15 @@ export interface AuthServices {
534
631
  }
535
632
 
536
633
  export async function runBunInstall(destination: string): Promise<void> {
537
- await execCommand("bun", ["install", "--ignore-scripts"], destination);
634
+ await execCommand("bun", ["install", "--ignore-scripts"], destination, { stdio: "inherit" });
538
635
  }
539
636
 
540
637
  export async function runTypesGen(destination: string): Promise<void> {
541
- await execCommand("node_modules/.bin/bos", ["types", "gen"], destination);
638
+ await execCommand("node_modules/.bin/bos", ["types", "gen"], destination, { stdio: "inherit" });
542
639
  }
543
640
 
544
641
  export async function runDockerComposeUp(destination: string): Promise<void> {
545
- await execCommand("docker", ["compose", "up", "-d", "--wait"], destination);
642
+ await execCommand("docker", ["compose", "up", "-d", "--wait"], destination, { stdio: "inherit" });
546
643
  }
547
644
 
548
645
  const WORKSPACE_LOCAL_PATHS: Record<string, string> = {
@@ -550,6 +647,20 @@ const WORKSPACE_LOCAL_PATHS: Record<string, string> = {
550
647
  "every-plugin": "packages/every-plugin",
551
648
  };
552
649
 
650
+ function resolveFrameworkCatalog(): Record<string, string> {
651
+ const catalog: Record<string, string> = {};
652
+ for (const packageName of FRAMEWORK_PACKAGES) {
653
+ try {
654
+ const resolved = require.resolve(`${packageName}/package.json`);
655
+ const pkg = JSON.parse(readFileSync(resolved, "utf-8")) as { version?: string };
656
+ if (pkg.version) {
657
+ catalog[packageName] = `^${pkg.version}`;
658
+ }
659
+ } catch {}
660
+ }
661
+ return catalog;
662
+ }
663
+
553
664
  export async function scaffoldMinimalProject(
554
665
  destination: string,
555
666
  parentConfig: BosConfigInput,
@@ -559,55 +670,51 @@ export async function scaffoldMinimalProject(
559
670
  account?: string;
560
671
  domain?: string;
561
672
  plugins?: string[];
562
- withHost?: boolean;
673
+ overrides: OverrideSection[];
674
+ repository?: string;
563
675
  },
564
676
  ): Promise<number> {
565
677
  mkdirSync(destination, { recursive: true });
566
678
 
679
+ const has = (section: OverrideSection) => opts.overrides.includes(section);
680
+
567
681
  const config: Record<string, unknown> = {
568
682
  extends: `bos://${opts.extendsAccount}/${opts.extendsGateway}`,
569
683
  account: opts.account || opts.extendsAccount,
570
684
  ...(opts.domain ? { domain: opts.domain } : {}),
685
+ ...(opts.repository ? { repository: opts.repository } : {}),
571
686
  };
572
687
 
573
688
  if (parentConfig.app && typeof parentConfig.app === "object") {
574
689
  const app: Record<string, unknown> = {};
575
690
  const parentApp = parentConfig.app as Record<string, Record<string, unknown>>;
576
691
 
577
- if (parentApp.host) {
692
+ if (has("host") && parentApp.host) {
578
693
  app.host = { ...parentApp.host };
579
- const host = app.host as Record<string, unknown>;
580
- delete host.production;
581
- delete host.integrity;
694
+ stripProductionFields(app.host as Record<string, unknown>);
582
695
  }
583
696
 
584
- if (parentApp.ui) {
697
+ if (has("ui") && parentApp.ui) {
585
698
  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;
699
+ stripProductionFields(app.ui as Record<string, unknown>);
591
700
  }
592
701
 
593
- if (parentApp.api) {
702
+ if (has("api") && parentApp.api) {
594
703
  app.api = { ...parentApp.api };
595
- const api = app.api as Record<string, unknown>;
596
- delete api.production;
597
- delete api.integrity;
704
+ stripProductionFields(app.api as Record<string, unknown>);
598
705
  }
599
706
 
600
- if (parentApp.auth) {
707
+ if (has("plugins") && parentApp.auth) {
601
708
  app.auth = { ...parentApp.auth };
602
- const auth = app.auth as Record<string, unknown>;
603
- delete auth.production;
604
- delete auth.integrity;
709
+ stripProductionFields(app.auth as Record<string, unknown>);
605
710
  }
606
711
 
607
- config.app = app;
712
+ if (Object.keys(app).length > 0) {
713
+ config.app = app;
714
+ }
608
715
  }
609
716
 
610
- if (opts.plugins && opts.plugins.length > 0 && parentConfig.plugins) {
717
+ if (has("plugins") && opts.plugins && opts.plugins.length > 0 && parentConfig.plugins) {
611
718
  const plugins: Record<string, unknown> = {};
612
719
  for (const key of opts.plugins) {
613
720
  const parentPlugin = (parentConfig.plugins as Record<string, unknown>)?.[key];
@@ -616,8 +723,7 @@ export async function scaffoldMinimalProject(
616
723
  plugins[key] = { extends: parentPlugin };
617
724
  } else {
618
725
  const pluginCopy = { ...(parentPlugin as Record<string, unknown>) };
619
- delete pluginCopy.production;
620
- delete pluginCopy.integrity;
726
+ stripProductionFields(pluginCopy);
621
727
  plugins[key] = pluginCopy;
622
728
  }
623
729
  }
@@ -627,6 +733,18 @@ export async function scaffoldMinimalProject(
627
733
 
628
734
  await saveBosConfig(destination, config);
629
735
 
736
+ const workspacePackages: string[] = [];
737
+ for (const section of opts.overrides) {
738
+ workspacePackages.push(...OVERRIDE_WORKSPACE_MAP[section]);
739
+ }
740
+ if (has("plugins") && opts.plugins) {
741
+ for (const plugin of opts.plugins) {
742
+ workspacePackages.push(`plugins/${plugin}`);
743
+ }
744
+ }
745
+
746
+ const catalog = resolveFrameworkCatalog();
747
+
630
748
  const pkg: Record<string, unknown> = {
631
749
  name: opts.domain || opts.extendsGateway,
632
750
  private: true,
@@ -649,13 +767,13 @@ export async function scaffoldMinimalProject(
649
767
  },
650
768
  devDependencies: {},
651
769
  workspaces: {
652
- packages: [],
653
- catalog: {},
770
+ packages: workspacePackages,
771
+ catalog,
654
772
  },
655
773
  };
656
774
  writeFileSync(join(destination, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`);
657
775
 
658
- const envExample = generateEnvExample(parentConfig);
776
+ const envExample = generateEnvExample(parentConfig, opts.overrides);
659
777
  if (envExample) {
660
778
  writeFileSync(join(destination, ".env.example"), envExample);
661
779
  }
@@ -714,11 +832,18 @@ export async function writeInitSnapshot(
714
832
  extendsGateway: string,
715
833
  sourceDir: string,
716
834
  patterns: string[],
717
- options: { withHost: boolean; plugins?: string[]; pluginRoutes?: Record<string, string[]> },
835
+ options: {
836
+ overrides: OverrideSection[];
837
+ plugins?: string[];
838
+ pluginRoutes?: Record<string, string[]>;
839
+ },
718
840
  ): Promise<void> {
719
- const effectivePatterns = options.withHost
720
- ? [...patterns, "host/**"]
721
- : patterns.filter((p) => !p.startsWith("host/") && p !== "host/**");
841
+ const effectivePatterns = filterPatternsByOverrides(patterns, options.overrides, options.plugins);
842
+
843
+ const has = (section: OverrideSection) => options.overrides.includes(section);
844
+ if (has("host") && !effectivePatterns.some((p) => p.startsWith("host/") || p === "host/**")) {
845
+ effectivePatterns.push("host/**");
846
+ }
722
847
 
723
848
  const excludedRoutePatterns: string[] = [];
724
849
  if (options.pluginRoutes) {
@@ -813,14 +938,34 @@ export async function generateDatabaseMigrations(destination: string): Promise<v
813
938
  }
814
939
  }
815
940
 
816
- export async function execCommand(command: string, args: string[], cwd?: string): Promise<void> {
817
- await execa(command, args, { cwd, stdio: "pipe" });
941
+ const COMMAND_TIMEOUTS: Record<string, number> = {
942
+ bun: 5 * 60_000,
943
+ docker: 5 * 60_000,
944
+ node_modules: 2 * 60_000,
945
+ tar: 60_000,
946
+ };
947
+
948
+ export async function execCommand(
949
+ command: string,
950
+ args: string[],
951
+ cwd?: string,
952
+ options?: { stdio?: "pipe" | "inherit" },
953
+ ): Promise<void> {
954
+ const timeout = COMMAND_TIMEOUTS[command] ?? 2 * 60_000;
955
+ await execa(command, args, { cwd, stdio: options?.stdio ?? "pipe", timeout });
818
956
  }
819
957
 
820
- function generateEnvExample(config: BosConfigInput): string {
958
+ function generateEnvExample(config: BosConfigInput, overrides: OverrideSection[]): string {
959
+ const has = (section: OverrideSection) => overrides.includes(section);
960
+
821
961
  const lines: string[] = ["# Environment variables"];
822
- const collectSecrets = (obj: Record<string, unknown>, prefix = ""): void => {
962
+ const collectSecrets = (
963
+ obj: Record<string, unknown>,
964
+ includeSection: boolean,
965
+ prefix = "",
966
+ ): void => {
823
967
  for (const [key, value] of Object.entries(obj)) {
968
+ if (!includeSection) continue;
824
969
  if (key === "secrets" && Array.isArray(value)) {
825
970
  for (const secret of value) {
826
971
  if (typeof secret === "string") {
@@ -834,16 +979,28 @@ function generateEnvExample(config: BosConfigInput): string {
834
979
  }
835
980
  }
836
981
  } else if (isPlainObject(value) && key !== "extends") {
837
- collectSecrets(value as Record<string, unknown>, `${prefix}${key}.`);
982
+ collectSecrets(value as Record<string, unknown>, includeSection, `${prefix}${key}.`);
838
983
  }
839
984
  }
840
985
  };
841
986
 
842
987
  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>);
988
+ const app = config.app as Record<string, unknown>;
989
+ collectSecrets(app, has("host"), "host.");
990
+ collectSecrets(app, has("ui"), "ui.");
991
+ collectSecrets(app, has("api"), "api.");
992
+ collectSecrets(app, has("plugins"), "auth.");
993
+ }
994
+ if (has("plugins") && config.plugins && typeof config.plugins === "object") {
995
+ for (const [pluginKey, pluginVal] of Object.entries(
996
+ config.plugins as Record<string, unknown>,
997
+ )) {
998
+ if (isPlainObject(pluginVal)) {
999
+ collectSecrets(pluginVal as Record<string, unknown>, true);
1000
+ } else if (typeof pluginVal === "string") {
1001
+ lines.push(`# Plugin '${pluginKey}' extends ${pluginVal}`);
1002
+ }
1003
+ }
847
1004
  }
848
1005
 
849
1006
  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
  }