everything-dev 1.27.0 → 1.28.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 (114) hide show
  1. package/dist/cli/infra.cjs +1 -1
  2. package/dist/cli/infra.mjs +1 -1
  3. package/dist/cli/init.cjs +34 -9
  4. package/dist/cli/init.cjs.map +1 -1
  5. package/dist/cli/init.d.cts +2 -1
  6. package/dist/cli/init.d.cts.map +1 -1
  7. package/dist/cli/init.d.mts +2 -1
  8. package/dist/cli/init.d.mts.map +1 -1
  9. package/dist/cli/init.mjs +34 -9
  10. package/dist/cli/init.mjs.map +1 -1
  11. package/dist/cli/prompts.cjs +28 -24
  12. package/dist/cli/prompts.cjs.map +1 -1
  13. package/dist/cli/prompts.mjs +27 -24
  14. package/dist/cli/prompts.mjs.map +1 -1
  15. package/dist/cli/sync.cjs +40 -3
  16. package/dist/cli/sync.cjs.map +1 -1
  17. package/dist/cli/sync.mjs +40 -3
  18. package/dist/cli/sync.mjs.map +1 -1
  19. package/dist/cli.cjs +187 -12
  20. package/dist/cli.cjs.map +1 -1
  21. package/dist/cli.mjs +186 -11
  22. package/dist/cli.mjs.map +1 -1
  23. package/dist/config.cjs +1 -0
  24. package/dist/config.cjs.map +1 -1
  25. package/dist/config.d.cts.map +1 -1
  26. package/dist/config.d.mts.map +1 -1
  27. package/dist/config.mjs +1 -0
  28. package/dist/config.mjs.map +1 -1
  29. package/dist/contract.cjs +1 -1
  30. package/dist/contract.cjs.map +1 -1
  31. package/dist/contract.d.cts +38 -34
  32. package/dist/contract.d.cts.map +1 -1
  33. package/dist/contract.d.mts +38 -34
  34. package/dist/contract.d.mts.map +1 -1
  35. package/dist/contract.mjs +1 -0
  36. package/dist/contract.mjs.map +1 -1
  37. package/dist/dev-session.cjs +0 -1
  38. package/dist/dev-session.mjs +1 -1
  39. package/dist/index.cjs +0 -2
  40. package/dist/index.d.cts +2 -2
  41. package/dist/index.d.mts +2 -2
  42. package/dist/index.mjs +0 -1
  43. package/dist/near-cli.cjs +1 -1
  44. package/dist/near-cli.mjs +1 -1
  45. package/dist/orchestrator.cjs +1 -1
  46. package/dist/orchestrator.mjs +1 -1
  47. package/dist/plugin.cjs +183 -151
  48. package/dist/plugin.cjs.map +1 -1
  49. package/dist/plugin.d.cts +67 -34
  50. package/dist/plugin.d.cts.map +1 -1
  51. package/dist/plugin.d.mts +66 -34
  52. package/dist/plugin.d.mts.map +1 -1
  53. package/dist/plugin.mjs +173 -142
  54. package/dist/plugin.mjs.map +1 -1
  55. package/dist/service-descriptor.d.cts +34 -0
  56. package/dist/service-descriptor.d.cts.map +1 -0
  57. package/dist/service-descriptor.d.mts +36 -0
  58. package/dist/service-descriptor.d.mts.map +1 -0
  59. package/dist/types.d.cts +2 -2
  60. package/dist/types.d.mts +2 -2
  61. package/dist/utils/run.cjs +9 -20
  62. package/dist/utils/run.cjs.map +1 -1
  63. package/dist/utils/run.mjs +9 -20
  64. package/dist/utils/run.mjs.map +1 -1
  65. package/package.json +2 -2
  66. package/src/api-contract.ts +0 -623
  67. package/src/app.ts +0 -193
  68. package/src/cli/catalog.ts +0 -49
  69. package/src/cli/framework-version.ts +0 -61
  70. package/src/cli/help.ts +0 -13
  71. package/src/cli/infra.ts +0 -190
  72. package/src/cli/init.ts +0 -1145
  73. package/src/cli/parse.ts +0 -147
  74. package/src/cli/prompts.ts +0 -135
  75. package/src/cli/snapshot.ts +0 -46
  76. package/src/cli/status.ts +0 -99
  77. package/src/cli/sync.ts +0 -429
  78. package/src/cli/timing.ts +0 -63
  79. package/src/cli/upgrade.ts +0 -869
  80. package/src/cli.ts +0 -516
  81. package/src/components/dev-view.tsx +0 -352
  82. package/src/components/streaming-view.ts +0 -177
  83. package/src/config.ts +0 -893
  84. package/src/contract.meta.ts +0 -140
  85. package/src/contract.ts +0 -326
  86. package/src/dev-logs.ts +0 -92
  87. package/src/dev-session.ts +0 -283
  88. package/src/fastkv.ts +0 -181
  89. package/src/index.ts +0 -8
  90. package/src/integrity.ts +0 -138
  91. package/src/internal/manifest-normalizer.ts +0 -290
  92. package/src/merge.ts +0 -187
  93. package/src/mf.ts +0 -147
  94. package/src/near-cli.ts +0 -259
  95. package/src/network.ts +0 -3
  96. package/src/orchestrator.ts +0 -493
  97. package/src/plugin.ts +0 -1799
  98. package/src/sdk.ts +0 -14
  99. package/src/service-descriptor.ts +0 -281
  100. package/src/shared.ts +0 -249
  101. package/src/sidebar.ts +0 -140
  102. package/src/types.ts +0 -330
  103. package/src/ui/head.ts +0 -83
  104. package/src/ui/index.ts +0 -5
  105. package/src/ui/metadata.ts +0 -95
  106. package/src/ui/router.ts +0 -88
  107. package/src/ui/runtime.ts +0 -42
  108. package/src/ui/types.ts +0 -65
  109. package/src/utils/banner.ts +0 -21
  110. package/src/utils/linkify.ts +0 -11
  111. package/src/utils/path-match.ts +0 -16
  112. package/src/utils/run.ts +0 -31
  113. package/src/utils/save-config.ts +0 -20
  114. package/src/utils/theme.ts +0 -39
package/src/cli/init.ts DELETED
@@ -1,1145 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import {
3
- createWriteStream,
4
- existsSync,
5
- lstatSync,
6
- mkdirSync,
7
- mkdtempSync,
8
- readFileSync,
9
- rmSync,
10
- writeFileSync,
11
- } from "node:fs";
12
- import { createRequire } from "node:module";
13
- import { tmpdir } from "node:os";
14
- import { dirname, join, resolve } from "node:path";
15
- import { pipeline } from "node:stream/promises";
16
- import { execa } from "execa";
17
- import { glob } from "glob";
18
- import type { OverrideSection } from "../contract";
19
- import { fetchBosConfigFromFastKv } from "../fastkv";
20
- import {
21
- loadManifestNormalizationSpec,
22
- normalizePackageManifestsInTree,
23
- } from "../internal/manifest-normalizer";
24
- import type { BosConfig, BosConfigInput } from "../types";
25
- import { saveBosConfig } from "../utils/save-config";
26
- import { writeSnapshot } from "./snapshot";
27
-
28
- const require = createRequire(import.meta.url);
29
-
30
- export const INIT_ROOT_PATTERNS = [
31
- "bos.config.json",
32
- "package.json",
33
- ".env.example",
34
- ".gitignore",
35
- "biome.json",
36
- "bunfig.toml",
37
- "Dockerfile",
38
- "docker-compose.yml",
39
- "railway.json",
40
- ".agent/**",
41
- "AGENTS.md",
42
- ".opencode/skills/everything-dev/**",
43
- ".changeset/config.json",
44
- ".changeset/README.md",
45
- "README.md",
46
- "CONTRIBUTING.md",
47
- ".github/templates/**",
48
- ] as const;
49
-
50
- const FRAMEWORK_PACKAGES = ["every-plugin", "everything-dev"] as const;
51
-
52
- const OVERRIDE_WORKSPACE_MAP: Record<OverrideSection, string[]> = {
53
- ui: ["ui"],
54
- api: ["api"],
55
- host: ["host"],
56
- plugins: [],
57
- };
58
-
59
- interface SourceResult {
60
- sourceDir: string;
61
- parentConfig: BosConfig;
62
- cleanup: () => Promise<void>;
63
- }
64
-
65
- export async function resolveSourceDir(opts: {
66
- extendsAccount: string;
67
- extendsGateway: string;
68
- source?: string;
69
- }): Promise<SourceResult> {
70
- if (opts.source) {
71
- const sourceDir = resolve(opts.source);
72
- if (!existsSync(join(sourceDir, "bos.config.json"))) {
73
- throw new Error(`No bos.config.json found in source directory: ${sourceDir}`);
74
- }
75
- const parentConfig = JSON.parse(
76
- readFileSync(join(sourceDir, "bos.config.json"), "utf-8"),
77
- ) as BosConfig;
78
- return { sourceDir, parentConfig, cleanup: async () => {} };
79
- }
80
-
81
- const parentConfig = await fetchParentConfig(opts.extendsAccount, opts.extendsGateway);
82
-
83
- if (parentConfig.repository) {
84
- const { dir: sourceDir, cleanup } = await downloadTarball(parentConfig.repository);
85
- return { sourceDir, parentConfig, cleanup };
86
- }
87
-
88
- const chainResult = await resolveRepositoryViaExtendsChain(
89
- opts.extendsAccount,
90
- opts.extendsGateway,
91
- );
92
- if (chainResult?.repository) {
93
- const { dir: sourceDir, cleanup } = await downloadTarball(chainResult.repository);
94
- return { sourceDir, parentConfig: chainResult.config, cleanup };
95
- }
96
-
97
- return {
98
- sourceDir: "",
99
- parentConfig,
100
- cleanup: async () => {},
101
- };
102
- }
103
-
104
- export function buildInitPatterns(overrides: OverrideSection[], plugins?: string[]): string[] {
105
- const has = (section: OverrideSection) => overrides.includes(section);
106
- const patterns: string[] = [...INIT_ROOT_PATTERNS];
107
-
108
- if (has("ui")) patterns.push("ui/**");
109
- if (has("api")) patterns.push("api/**");
110
- if (has("host")) patterns.push("host/**");
111
- if (has("plugins")) {
112
- for (const plugin of plugins ?? []) {
113
- patterns.push(`plugins/${plugin}/**`);
114
- }
115
- }
116
-
117
- return patterns;
118
- }
119
-
120
- export function sourcePathToDestinationPath(filePath: string): string {
121
- return filePath.startsWith(".github/templates/")
122
- ? filePath.replace(/^\.github\/templates\//, ".github/")
123
- : filePath;
124
- }
125
-
126
- export async function fetchParentConfig(
127
- extendsAccount: string,
128
- extendsGateway: string,
129
- ): Promise<BosConfig> {
130
- const bosUrl = `bos://${extendsAccount}/${extendsGateway}`;
131
- return fetchBosConfigFromFastKv<BosConfig>(bosUrl);
132
- }
133
-
134
- export async function resolveRepositoryViaExtendsChain(
135
- extendsAccount: string,
136
- extendsGateway: string,
137
- visited = new Set<string>(),
138
- ): Promise<{ repository: string; config: BosConfig } | null> {
139
- const key = `bos://${extendsAccount}/${extendsGateway}`;
140
- if (visited.has(key)) return null;
141
- visited.add(key);
142
-
143
- try {
144
- const config = await fetchParentConfig(extendsAccount, extendsGateway);
145
- if (config.repository) {
146
- return { repository: config.repository, config };
147
- }
148
-
149
- const extendsRef = config.extends;
150
- if (extendsRef && typeof extendsRef === "string") {
151
- const normalized = extendsRef.startsWith("bos://") ? extendsRef : `bos://${extendsRef}`;
152
- const match = normalized.match(/^bos:\/\/([^/]+)\/(.+)$/);
153
- if (match) {
154
- const result = await resolveRepositoryViaExtendsChain(match[1], match[2], visited);
155
- if (result) return result;
156
- }
157
- }
158
-
159
- return null;
160
- } catch {
161
- return null;
162
- }
163
- }
164
-
165
- export async function detectGitRemoteUrl(directory: string): Promise<string | undefined> {
166
- try {
167
- const { stdout } = await execa("git", ["remote", "get-url", "origin"], {
168
- cwd: directory,
169
- stdio: "pipe",
170
- });
171
- const url = stdout.trim();
172
- if (!url) return undefined;
173
- return normalizeGitUrl(url);
174
- } catch {
175
- return undefined;
176
- }
177
- }
178
-
179
- function normalizeGitUrl(url: string): string | undefined {
180
- const sshMatch = url.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
181
- if (sshMatch) {
182
- return `https://github.com/${sshMatch[1]}/${sshMatch[2]}`;
183
- }
184
- const httpsMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/.*)?$/);
185
- if (httpsMatch) {
186
- return `https://github.com/${httpsMatch[1]}/${httpsMatch[2]}`;
187
- }
188
- return url.endsWith(".git") ? url.slice(0, -4) : url;
189
- }
190
-
191
- export async function downloadTarball(
192
- repoUrl: string,
193
- ): Promise<{ dir: string; cleanup: () => Promise<void> }> {
194
- const parsed = parseGitHubUrl(repoUrl);
195
- if (!parsed) {
196
- throw new Error(`Cannot parse repository URL: ${repoUrl}`);
197
- }
198
-
199
- const { owner, repo, branch } = parsed;
200
- const tarballUrl = `https://api.github.com/repos/${owner}/${repo}/tarball/${branch}`;
201
-
202
- const tmpDir = mkTmpDir("bos-init-tarball-");
203
- const tarballPath = join(tmpDir, "source.tar.gz");
204
-
205
- const response = await fetch(tarballUrl, {
206
- headers: { "User-Agent": "everything-dev" },
207
- redirect: "follow",
208
- });
209
-
210
- if (!response.ok) {
211
- rmSync(tmpDir, { recursive: true, force: true });
212
- throw new Error(`GitHub tarball download failed: ${response.status} ${response.statusText}`);
213
- }
214
-
215
- if (!response.body) {
216
- rmSync(tmpDir, { recursive: true, force: true });
217
- throw new Error("GitHub tarball download returned empty body");
218
- }
219
-
220
- const fileStream = createWriteStream(tarballPath);
221
- const reader = response.body as unknown as NodeJS.ReadableStream;
222
- await pipeline(reader, fileStream);
223
-
224
- const extractDir = mkTmpDir("bos-init-extract-");
225
- try {
226
- const tar = require("tar") as {
227
- extract: (opts: { cwd: string; file: string; strip: number }) => Promise<void>;
228
- };
229
- await tar.extract({ cwd: extractDir, file: tarballPath, strip: 1 });
230
- } catch {
231
- await execCommand("tar", ["-xzf", tarballPath, "--strip-components=1", "-C", extractDir]);
232
- }
233
-
234
- rmSync(tmpDir, { recursive: true, force: true });
235
-
236
- return {
237
- dir: extractDir,
238
- cleanup: async () => {
239
- rmSync(extractDir, { recursive: true, force: true });
240
- },
241
- };
242
- }
243
-
244
- function parseGitHubUrl(url: string): { owner: string; repo: string; branch: string } | null {
245
- const httpsMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/.*)?$/);
246
- if (httpsMatch) {
247
- return { owner: httpsMatch[1], repo: httpsMatch[2], branch: "main" };
248
- }
249
-
250
- const sshMatch = url.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
251
- if (sshMatch) {
252
- return { owner: sshMatch[1], repo: sshMatch[2], branch: "main" };
253
- }
254
-
255
- return null;
256
- }
257
-
258
- export async function copyFilteredFiles(
259
- sourceDir: string,
260
- destination: string,
261
- patterns: string[],
262
- _options: {
263
- overrides: OverrideSection[];
264
- plugins?: string[];
265
- },
266
- ): Promise<number> {
267
- if (patterns.length === 0) {
268
- return 0;
269
- }
270
-
271
- const allFiles = new Set<string>();
272
- for (const pattern of patterns) {
273
- const matches = await glob(pattern, {
274
- cwd: sourceDir,
275
- nodir: true,
276
- dot: true,
277
- absolute: false,
278
- ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/.bos/**"],
279
- });
280
- for (const match of matches) {
281
- allFiles.add(match);
282
- }
283
- }
284
-
285
- mkdirSync(destination, { recursive: true });
286
-
287
- let count = 0;
288
- for (const filePath of allFiles) {
289
- const src = join(sourceDir, filePath);
290
- const stat = lstatSync(src);
291
- if (!stat.isFile()) continue;
292
-
293
- const destPath = sourcePathToDestinationPath(filePath);
294
- const dest = join(destination, destPath);
295
- mkdirSync(dirname(dest), { recursive: true });
296
- const content = readFileSync(src);
297
- writeFileSync(dest, content);
298
- count++;
299
- }
300
-
301
- return count;
302
- }
303
-
304
- function stripProductionFields(entry: Record<string, unknown>): void {
305
- delete entry.production;
306
- delete entry.integrity;
307
- delete entry.ssr;
308
- delete entry.ssrIntegrity;
309
- }
310
-
311
- export async function personalizeConfig(
312
- destination: string,
313
- opts: {
314
- extendsAccount: string;
315
- extendsGateway: string;
316
- account?: string;
317
- domain?: string;
318
- plugins?: string[];
319
- overrides: OverrideSection[];
320
- pluginRoutes?: Record<string, string[]>;
321
- workspaceOpts?: { localOverrides?: boolean; sourceDir?: string };
322
- mode?: "init" | "sync";
323
- repository?: string;
324
- title?: string;
325
- description?: string;
326
- testnet?: string;
327
- staging?: unknown;
328
- },
329
- ): Promise<void> {
330
- const has = (section: OverrideSection) => opts.overrides.includes(section);
331
-
332
- const configPath = join(destination, "bos.config.json");
333
- if (existsSync(configPath)) {
334
- const config = JSON.parse(readFileSync(configPath, "utf-8")) as Record<string, unknown>;
335
-
336
- config.extends = `bos://${opts.extendsAccount}/${opts.extendsGateway}`;
337
-
338
- if (opts.account) {
339
- config.account = opts.account;
340
- }
341
- if (opts.domain) {
342
- config.domain = opts.domain;
343
- }
344
- if (opts.repository) {
345
- config.repository = opts.repository;
346
- } else {
347
- delete config.repository;
348
- }
349
-
350
- const inheritableFields = ["title", "description", "testnet", "staging"] as const;
351
- for (const field of inheritableFields) {
352
- if (!(field in opts)) {
353
- delete config[field];
354
- }
355
- }
356
-
357
- if (config.app && typeof config.app === "object") {
358
- const app = config.app as Record<string, unknown>;
359
-
360
- for (const entryKey of Object.keys(app)) {
361
- if (
362
- !has(entryKey as OverrideSection) &&
363
- (entryKey === "host" || entryKey === "ui" || entryKey === "api" || entryKey === "auth")
364
- ) {
365
- delete app[entryKey];
366
- continue;
367
- }
368
- const entry = app[entryKey];
369
- if (entry && typeof entry === "object") {
370
- stripProductionFields(entry as Record<string, unknown>);
371
- }
372
- }
373
- }
374
-
375
- if (has("plugins")) {
376
- if (config.plugins && typeof config.plugins === "object") {
377
- const plugins = config.plugins as Record<string, unknown>;
378
-
379
- if (opts.plugins !== undefined) {
380
- for (const pluginKey of Object.keys(plugins)) {
381
- if (!opts.plugins.includes(pluginKey)) {
382
- delete plugins[pluginKey];
383
- }
384
- }
385
- }
386
-
387
- for (const pluginKey of Object.keys(plugins)) {
388
- const plugin = plugins[pluginKey];
389
- let pluginObj: Record<string, unknown>;
390
-
391
- if (typeof plugin === "string") {
392
- pluginObj = { extends: plugin };
393
- plugins[pluginKey] = pluginObj;
394
- } else if (plugin && typeof plugin === "object") {
395
- pluginObj = { ...(plugin as Record<string, unknown>) };
396
- plugins[pluginKey] = pluginObj;
397
- } else {
398
- continue;
399
- }
400
-
401
- stripProductionFields(pluginObj);
402
- }
403
-
404
- if (Object.keys(plugins).length === 0) {
405
- delete config.plugins;
406
- }
407
- }
408
- } else {
409
- delete config.plugins;
410
- }
411
-
412
- await saveBosConfig(destination, config);
413
- }
414
-
415
- const pkgPath = join(destination, "package.json");
416
- if (existsSync(pkgPath)) {
417
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as Record<string, unknown>;
418
-
419
- if (pkg.workspaces && typeof pkg.workspaces === "object") {
420
- const ws = pkg.workspaces as { packages?: string[] };
421
- if (Array.isArray(ws.packages)) {
422
- ws.packages = ws.packages.filter((p: string) => {
423
- if (p.startsWith("packages/")) return false;
424
- if (p === "host") return has("host");
425
- if (p === "plugins/*") return false;
426
- const pluginMatch = p.match(/^plugins\/([^/]+)/);
427
- if (pluginMatch)
428
- return has("plugins") && (opts.plugins?.includes(pluginMatch[1]) ?? true);
429
- return true;
430
- });
431
-
432
- if (has("plugins") && (opts.plugins?.length ?? 0) > 0) {
433
- for (const plugin of opts.plugins ?? []) {
434
- const pluginWorkspace = `plugins/${plugin}`;
435
- if (!ws.packages.includes(pluginWorkspace)) {
436
- ws.packages.push(pluginWorkspace);
437
- }
438
- }
439
- }
440
- }
441
- }
442
-
443
- if (pkg.scripts && typeof pkg.scripts === "object") {
444
- const scripts = pkg.scripts as Record<string, string>;
445
- const FROM = "bun packages/everything-dev/src/cli.ts";
446
- const TO = "node_modules/.bin/bos";
447
- const rewrite = (key: string) => {
448
- if (scripts[key]?.includes(FROM)) {
449
- scripts[key] = scripts[key].replaceAll(FROM, TO);
450
- }
451
- };
452
- rewrite("dev");
453
- rewrite("dev:ui");
454
- rewrite("dev:api");
455
- rewrite("dev:proxy");
456
- rewrite("build");
457
- rewrite("deploy");
458
- rewrite("publish");
459
- rewrite("start");
460
- rewrite("bos");
461
- scripts.postinstall = "node_modules/.bin/bos types gen || true";
462
- scripts["types:gen"] = "node_modules/.bin/bos types gen";
463
- if (scripts.typecheck) {
464
- scripts.typecheck = scripts.typecheck
465
- .replace("bun run types:gen && ", "")
466
- .replace(/bun run --cwd packages\/everything-dev typecheck & ?/, "");
467
- if (!has("ui")) {
468
- scripts.typecheck = scripts.typecheck.replace(/bun run --cwd ui tsc --noEmit & ?/, "");
469
- }
470
- if (!has("api")) {
471
- scripts.typecheck = scripts.typecheck.replace(/bun run --cwd api tsc --noEmit & ?/, "");
472
- }
473
- if (!has("host")) {
474
- scripts.typecheck = scripts.typecheck.replace(/bun run --cwd host tsc --noEmit & ?/, "");
475
- }
476
- }
477
- }
478
-
479
- if (pkg.devDependencies && typeof pkg.devDependencies === "object") {
480
- const deps = pkg.devDependencies as Record<string, string>;
481
- delete deps["every-plugin"];
482
- delete deps["everything-dev"];
483
- }
484
-
485
- if (!pkg.workspaces || typeof pkg.workspaces !== "object") {
486
- pkg.workspaces = { packages: [], catalog: {} };
487
- }
488
- const workspaces = pkg.workspaces as { packages?: string[]; catalog?: Record<string, string> };
489
- if (!workspaces.catalog || typeof workspaces.catalog !== "object") {
490
- workspaces.catalog = {};
491
- }
492
-
493
- if (!pkg.dependencies) pkg.dependencies = {};
494
- const deps = pkg.dependencies as Record<string, string>;
495
- const spec = opts.workspaceOpts?.sourceDir
496
- ? loadManifestNormalizationSpec(opts.workspaceOpts.sourceDir)
497
- : null;
498
- if (spec) {
499
- workspaces.catalog["everything-dev"] = spec.rootCatalog["everything-dev"];
500
- workspaces.catalog["every-plugin"] = spec.rootCatalog["every-plugin"];
501
- }
502
- const frameworkCatalog = resolveFrameworkCatalog();
503
- for (const [name, version] of Object.entries(frameworkCatalog)) {
504
- workspaces.catalog[name] = version;
505
- }
506
- if (!deps["everything-dev"]) deps["everything-dev"] = "catalog:";
507
- if (!deps["every-plugin"]) deps["every-plugin"] = "catalog:";
508
-
509
- writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
510
- }
511
-
512
- const apiTsConfigPath = join(destination, "api", "tsconfig.json");
513
- if (existsSync(apiTsConfigPath)) {
514
- const apiTsConfig = JSON.parse(readFileSync(apiTsConfigPath, "utf-8")) as {
515
- files?: string[];
516
- [key: string]: unknown;
517
- };
518
- if (apiTsConfig.files) {
519
- const validFiles = apiTsConfig.files.filter((f) => existsSync(join(destination, "api", f)));
520
- if (validFiles.length !== apiTsConfig.files.length) {
521
- if (validFiles.length === 0) {
522
- delete apiTsConfig.files;
523
- } else {
524
- apiTsConfig.files = validFiles;
525
- }
526
- writeFileSync(apiTsConfigPath, `${JSON.stringify(apiTsConfig, null, 2)}\n`);
527
- }
528
- }
529
- }
530
-
531
- await resolveWorkspaceRefs(destination, opts.workspaceOpts);
532
-
533
- if (has("ui")) {
534
- const genContractPath = join(destination, "ui", "src", "lib", "api-types.gen.ts");
535
- if (!existsSync(genContractPath)) {
536
- mkdirSync(dirname(genContractPath), { recursive: true });
537
- writeFileSync(genContractPath, `export type ApiContract = Record<string, never>;\n`);
538
- }
539
- }
540
-
541
- if (has("api")) {
542
- const pluginsClientGenPath = join(destination, "api", "src", "lib", "plugins-types.gen.ts");
543
- if (!existsSync(pluginsClientGenPath)) {
544
- mkdirSync(dirname(pluginsClientGenPath), { recursive: true });
545
- writeFileSync(
546
- pluginsClientGenPath,
547
- `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`,
548
- );
549
- }
550
- }
551
-
552
- const authTypesContent = generateAuthTypesTemplate();
553
- const authTypesPaths: string[] = [];
554
- if (has("ui")) {
555
- authTypesPaths.push(join(destination, "ui", "src", "lib", "auth-types.gen.ts"));
556
- }
557
- if (has("api")) {
558
- authTypesPaths.push(join(destination, "api", "src", "lib", "auth-types.gen.ts"));
559
- }
560
- if (has("host") && existsSync(join(destination, "host", "src"))) {
561
- authTypesPaths.push(join(destination, "host", "src", "lib", "auth-types.gen.ts"));
562
- }
563
- for (const authTypesGenPath of authTypesPaths) {
564
- if (!existsSync(authTypesGenPath)) {
565
- mkdirSync(dirname(authTypesGenPath), { recursive: true });
566
- writeFileSync(authTypesGenPath, authTypesContent);
567
- }
568
- }
569
- }
570
-
571
- function generateAuthTypesTemplate(): string {
572
- return `import type { Auth } from "better-auth";
573
- export type { Auth } from "better-auth";
574
- export type AuthSessionUser = NonNullable<Auth["$Infer"]["Session"]["user"]> & {
575
- role?: string | null;
576
- isAnonymous?: boolean | null;
577
- walletAddress?: string | null;
578
- banned?: boolean | null;
579
- };
580
- export type AuthSessionData = NonNullable<Auth["$Infer"]["Session"]["session"]> & {
581
- activeOrganizationId?: string | null;
582
- };
583
- export type AuthSession = {
584
- user: AuthSessionUser | null;
585
- session: AuthSessionData | null;
586
- };
587
- export interface AuthOrganizationContext {
588
- activeOrganizationId: string | null;
589
- organization: { id: string; name: string; slug: string; logo?: string | null; metadata?: Record<string, unknown> } | null;
590
- member: { id: string; role: string } | null;
591
- isPersonal: boolean;
592
- hasOrganization: boolean;
593
- }
594
- export interface AuthRequestContext {
595
- user: AuthSessionUser | null;
596
- userId: string | null;
597
- isAuthenticated: boolean;
598
- authMethod: "session" | "apiKey" | "anonymous" | "none";
599
- near: {
600
- primaryAccountId: string | null;
601
- linkedAccounts: Array<{ accountId: string; network: string; publicKey: string; isPrimary: boolean }>;
602
- hasNearAccount: boolean;
603
- };
604
- organization: AuthOrganizationContext;
605
- organizations?: Array<{ id: string; role: string; name?: string; slug?: string }>;
606
- }
607
- export type AuthActiveMember = { id: string | null; role: string | null; organizationId: string | null };
608
- export type AuthOrganization = NonNullable<AuthOrganizationContext["organization"]>;
609
- export type AuthOrganizationMember = NonNullable<AuthOrganizationContext["member"]>;
610
- export type AuthOrganizationSummary = NonNullable<AuthRequestContext["organizations"]>[number];
611
- export type AuthBaseSession = Auth["$Infer"]["Session"];
612
- export type createAuthInstance = never;
613
- export interface AuthServices {
614
- auth: Auth;
615
- db: unknown;
616
- driver: { close(): Promise<void> };
617
- handler: (req: Request) => Promise<Response>;
618
- }
619
- `;
620
- }
621
-
622
- export async function runBunInstall(
623
- destination: string,
624
- spinner?: { message: (msg: string) => void },
625
- ): Promise<void> {
626
- await runWithProgress(
627
- "bun",
628
- ["install", "--ignore-scripts"],
629
- destination,
630
- spinner,
631
- "Installing dependencies",
632
- );
633
- }
634
-
635
- export async function runBunInstallForUpgrade(
636
- destination: string,
637
- spinner?: { message: (msg: string) => void },
638
- ): Promise<void> {
639
- await runWithProgress(
640
- "bun",
641
- ["install", "--force"],
642
- destination,
643
- spinner,
644
- "Installing dependencies",
645
- );
646
- }
647
-
648
- export async function runTypesGen(
649
- destination: string,
650
- spinner?: { message: (msg: string) => void },
651
- ): Promise<void> {
652
- const localBosBin = join(destination, "node_modules", ".bin", "bos");
653
- if (existsSync(localBosBin)) {
654
- await runWithProgress(
655
- "node_modules/.bin/bos",
656
- ["types", "gen"],
657
- destination,
658
- spinner,
659
- "Generating types",
660
- );
661
- return;
662
- }
663
-
664
- const localCli = join(destination, "packages", "everything-dev", "src", "cli.ts");
665
- if (existsSync(localCli)) {
666
- await runWithProgress(
667
- "bun",
668
- ["run", "--cwd", "packages/everything-dev", "src/cli.ts", "types", "gen"],
669
- destination,
670
- spinner,
671
- "Generating types",
672
- );
673
- return;
674
- }
675
-
676
- throw new Error("Unable to locate bos CLI for types generation");
677
- }
678
-
679
- export async function runDockerComposeUp(destination: string): Promise<void> {
680
- await execCommand("docker", ["compose", "up", "-d", "--wait"], destination, { stdio: "inherit" });
681
- }
682
-
683
- async function runWithProgress(
684
- command: string,
685
- args: string[],
686
- cwd: string,
687
- spinner: { message: (msg: string) => void } | undefined,
688
- label: string,
689
- ): Promise<void> {
690
- const timeout = COMMAND_TIMEOUTS[command] ?? 2 * 60_000;
691
- const child = execa(command, args, { cwd, stdio: "inherit", timeout });
692
-
693
- if (spinner) {
694
- const start = Date.now();
695
- const interval = setInterval(() => {
696
- const elapsed = Math.round((Date.now() - start) / 1000);
697
- spinner.message(`${label}... (${elapsed}s)`);
698
- }, 2000);
699
- try {
700
- await child;
701
- } finally {
702
- clearInterval(interval);
703
- }
704
- } else {
705
- await child;
706
- }
707
- }
708
-
709
- export function stripOrphanedWorkspacesFromLockfile(
710
- lockfilePath: string,
711
- allowedWorkspaces: string[],
712
- ): void {
713
- if (!existsSync(lockfilePath)) return;
714
-
715
- const content = readFileSync(lockfilePath, "utf-8");
716
- let lockfile: Record<string, unknown>;
717
- try {
718
- lockfile = JSON.parse(content) as Record<string, unknown>;
719
- } catch {
720
- return;
721
- }
722
-
723
- const workspaces = lockfile.workspaces;
724
- if (!workspaces || typeof workspaces !== "object") return;
725
-
726
- const workspaceMap = workspaces as Record<string, unknown>;
727
- const allowed = new Set(["", ...allowedWorkspaces]);
728
-
729
- const keys = Object.keys(workspaceMap);
730
- let changed = false;
731
- for (const key of keys) {
732
- if (!allowed.has(key)) {
733
- delete workspaceMap[key];
734
- changed = true;
735
- }
736
- }
737
-
738
- if (changed) {
739
- writeFileSync(lockfilePath, `${JSON.stringify(lockfile, null, 2)}\n`);
740
- }
741
- }
742
-
743
- export function removeInitLockfile(lockfilePath: string): void {
744
- if (!existsSync(lockfilePath)) return;
745
- rmSync(lockfilePath, { force: true });
746
- }
747
-
748
- const WORKSPACE_LOCAL_PATHS: Record<string, string> = {
749
- "everything-dev": "packages/everything-dev",
750
- "every-plugin": "packages/every-plugin",
751
- };
752
-
753
- function readJsonFile<T>(filePath: string): T {
754
- return JSON.parse(readFileSync(filePath, "utf-8")) as T;
755
- }
756
-
757
- function tryResolvePackageJson(packageName: string): string | null {
758
- try {
759
- return require.resolve(`${packageName}/package.json`);
760
- } catch {
761
- return null;
762
- }
763
- }
764
-
765
- function resolveFrameworkCatalog(): Record<string, string> {
766
- const catalog: Record<string, string> = {};
767
- const everythingDevPackageJson = tryResolvePackageJson("everything-dev");
768
-
769
- if (everythingDevPackageJson) {
770
- try {
771
- const selfPkgDir = dirname(everythingDevPackageJson);
772
- const monorepoPkgPath = join(selfPkgDir, "..", "..", "package.json");
773
- if (existsSync(monorepoPkgPath)) {
774
- const monorepoPkg = readJsonFile<{
775
- workspaces?: { catalog?: Record<string, string> };
776
- }>(monorepoPkgPath);
777
- const sourceCatalog = monorepoPkg.workspaces?.catalog;
778
- if (sourceCatalog && typeof sourceCatalog === "object") {
779
- for (const [name, version] of Object.entries(sourceCatalog)) {
780
- if (typeof version === "string") {
781
- catalog[name] = version;
782
- }
783
- }
784
- }
785
- }
786
- } catch {}
787
-
788
- try {
789
- const selfPkg = readJsonFile<{
790
- version?: string;
791
- workspaces?: { catalog?: Record<string, string> };
792
- }>(everythingDevPackageJson);
793
- if (selfPkg.version && !catalog["everything-dev"]) {
794
- catalog["everything-dev"] = `^${selfPkg.version}`;
795
- }
796
- const sourceCatalog = selfPkg.workspaces?.catalog;
797
- if (sourceCatalog && typeof sourceCatalog === "object") {
798
- for (const [name, version] of Object.entries(sourceCatalog)) {
799
- if (typeof version === "string" && !catalog[name]) {
800
- catalog[name] = version;
801
- }
802
- }
803
- }
804
- } catch {}
805
- }
806
-
807
- for (const packageName of FRAMEWORK_PACKAGES) {
808
- const resolved = tryResolvePackageJson(packageName);
809
- if (!resolved) continue;
810
-
811
- try {
812
- const pkg = readJsonFile<{ version?: string }>(resolved);
813
- if (pkg.version) {
814
- catalog[packageName] = `^${pkg.version}`;
815
- }
816
- } catch {}
817
- }
818
-
819
- return catalog;
820
- }
821
-
822
- export async function scaffoldMinimalProject(
823
- destination: string,
824
- parentConfig: BosConfigInput,
825
- opts: {
826
- extendsAccount: string;
827
- extendsGateway: string;
828
- account?: string;
829
- domain?: string;
830
- plugins?: string[];
831
- overrides: OverrideSection[];
832
- repository?: string;
833
- title?: string;
834
- description?: string;
835
- },
836
- ): Promise<number> {
837
- mkdirSync(destination, { recursive: true });
838
-
839
- const has = (section: OverrideSection) => opts.overrides.includes(section);
840
-
841
- const config: Record<string, unknown> = {
842
- extends: `bos://${opts.extendsAccount}/${opts.extendsGateway}`,
843
- account: opts.account || opts.extendsAccount,
844
- ...(opts.domain ? { domain: opts.domain } : {}),
845
- ...(opts.repository ? { repository: opts.repository } : {}),
846
- ...(opts.title ? { title: opts.title } : {}),
847
- ...(opts.description ? { description: opts.description } : {}),
848
- };
849
-
850
- if (parentConfig.app && typeof parentConfig.app === "object") {
851
- const app: Record<string, unknown> = {};
852
- const parentApp = parentConfig.app as Record<string, Record<string, unknown>>;
853
-
854
- if (has("host") && parentApp.host) {
855
- app.host = { ...parentApp.host };
856
- stripProductionFields(app.host as Record<string, unknown>);
857
- }
858
-
859
- if (has("ui") && parentApp.ui) {
860
- app.ui = { ...parentApp.ui };
861
- stripProductionFields(app.ui as Record<string, unknown>);
862
- }
863
-
864
- if (has("api") && parentApp.api) {
865
- app.api = { ...parentApp.api };
866
- stripProductionFields(app.api as Record<string, unknown>);
867
- }
868
-
869
- if (has("plugins") && parentApp.auth) {
870
- app.auth = { ...parentApp.auth };
871
- stripProductionFields(app.auth as Record<string, unknown>);
872
- }
873
-
874
- if (Object.keys(app).length > 0) {
875
- config.app = app;
876
- }
877
- }
878
-
879
- if (has("plugins") && opts.plugins && opts.plugins.length > 0 && parentConfig.plugins) {
880
- const plugins: Record<string, unknown> = {};
881
- for (const key of opts.plugins) {
882
- const parentPlugin = (parentConfig.plugins as Record<string, unknown>)?.[key];
883
- if (parentPlugin) {
884
- if (typeof parentPlugin === "string") {
885
- plugins[key] = { extends: parentPlugin };
886
- } else {
887
- const pluginCopy = { ...(parentPlugin as Record<string, unknown>) };
888
- stripProductionFields(pluginCopy);
889
- plugins[key] = pluginCopy;
890
- }
891
- }
892
- }
893
- config.plugins = plugins;
894
- }
895
-
896
- await saveBosConfig(destination, config);
897
-
898
- const workspacePackages: string[] = [];
899
- for (const section of opts.overrides) {
900
- workspacePackages.push(...OVERRIDE_WORKSPACE_MAP[section]);
901
- }
902
- if (has("plugins") && opts.plugins) {
903
- for (const plugin of opts.plugins) {
904
- workspacePackages.push(`plugins/${plugin}`);
905
- }
906
- }
907
-
908
- const catalog = resolveFrameworkCatalog();
909
-
910
- const pkg: Record<string, unknown> = {
911
- name: opts.domain || opts.extendsGateway,
912
- private: true,
913
- type: "module",
914
- scripts: {
915
- dev: "node_modules/.bin/bos dev --host remote",
916
- "dev:ui": "node_modules/.bin/bos dev --ui local --api remote",
917
- "dev:api": "node_modules/.bin/bos dev --ui remote --api local",
918
- build: "node_modules/.bin/bos build",
919
- deploy: "node_modules/.bin/bos build --deploy",
920
- publish: "node_modules/.bin/bos publish",
921
- start: "node_modules/.bin/bos start",
922
- typecheck: "node_modules/.bin/bos types gen && tsc --noEmit",
923
- postinstall: "node_modules/.bin/bos types gen || true",
924
- "types:gen": "node_modules/.bin/bos types gen",
925
- bos: "node_modules/.bin/bos",
926
- },
927
- dependencies: {
928
- "everything-dev": "catalog:",
929
- "every-plugin": "catalog:",
930
- },
931
- devDependencies: {},
932
- workspaces: {
933
- packages: workspacePackages,
934
- catalog,
935
- },
936
- };
937
- writeFileSync(join(destination, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`);
938
-
939
- const envExample = generateEnvExample(parentConfig, opts.overrides);
940
- if (envExample) {
941
- writeFileSync(join(destination, ".env.example"), envExample);
942
- }
943
-
944
- writeFileSync(join(destination, ".gitignore"), generateGitignore());
945
-
946
- return 4;
947
- }
948
-
949
- async function resolveWorkspaceRefs(
950
- destination: string,
951
- options?: { localOverrides?: boolean; sourceDir?: string },
952
- ): Promise<void> {
953
- await normalizePackageManifestsInTree({
954
- sourceRootDir: options?.sourceDir ?? destination,
955
- targetDir: destination,
956
- resolveCatalogRefs: false,
957
- preserveCatalogRefs: true,
958
- removeWorkspaceDeps: ["host"],
959
- });
960
-
961
- if (options?.localOverrides && options.sourceDir) {
962
- const rootPkgPath = join(destination, "package.json");
963
- if (existsSync(rootPkgPath)) {
964
- const pkg = JSON.parse(readFileSync(rootPkgPath, "utf-8")) as Record<string, unknown>;
965
- if (!pkg.overrides) pkg.overrides = {};
966
- const overrides = pkg.overrides as Record<string, string>;
967
-
968
- const rootWorkspaces = ((pkg.workspaces as Record<string, string[]>)?.packages ?? []).filter(
969
- Boolean,
970
- );
971
-
972
- for (const [name, relPath] of Object.entries(WORKSPACE_LOCAL_PATHS)) {
973
- if (!rootWorkspaces.some((ws) => ws === relPath || ws === `plugins/${name}`)) {
974
- const srcPkgPath = join(options.sourceDir, relPath, "package.json");
975
- if (existsSync(srcPkgPath)) {
976
- overrides[name] = `file:${relPath}`;
977
- rootWorkspaces.push(relPath);
978
- }
979
- }
980
- }
981
-
982
- if (rootWorkspaces.length > 0) {
983
- if (!pkg.workspaces) pkg.workspaces = {};
984
- (pkg.workspaces as Record<string, string[]>).packages = rootWorkspaces;
985
- }
986
-
987
- writeFileSync(rootPkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
988
- }
989
- }
990
- }
991
-
992
- export async function writeInitSnapshot(
993
- destination: string,
994
- extendsAccount: string,
995
- extendsGateway: string,
996
- sourceDir: string,
997
- patterns: string[],
998
- _options: {
999
- overrides: OverrideSection[];
1000
- plugins?: string[];
1001
- },
1002
- ): Promise<void> {
1003
- const allFiles = new Set<string>();
1004
- for (const pattern of patterns) {
1005
- const matches = await glob(pattern, {
1006
- cwd: sourceDir,
1007
- nodir: true,
1008
- dot: true,
1009
- absolute: false,
1010
- ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/.bos/**"],
1011
- });
1012
- for (const match of matches) {
1013
- allFiles.add(match);
1014
- }
1015
- }
1016
-
1017
- const fileHashes: Record<string, string> = {};
1018
- for (const filePath of allFiles) {
1019
- const src = join(sourceDir, filePath);
1020
- const stat = lstatSync(src);
1021
- if (!stat.isFile()) continue;
1022
- const content = readFileSync(src);
1023
- const destPath = sourcePathToDestinationPath(filePath);
1024
- fileHashes[destPath] = computeHash(content);
1025
- }
1026
-
1027
- await writeSnapshot(destination, {
1028
- parentRef: `bos://${extendsAccount}/${extendsGateway}`,
1029
- files: fileHashes,
1030
- });
1031
- }
1032
-
1033
- function computeHash(data: Uint8Array): string {
1034
- return createHash("sha256").update(data).digest("hex").substring(0, 16);
1035
- }
1036
-
1037
- function mkTmpDir(prefix: string): string {
1038
- return mkdtempSync(join(tmpdir(), `${prefix}-`));
1039
- }
1040
-
1041
- export async function generateDatabaseMigrations(destination: string): Promise<void> {
1042
- const drizzleConfigs = await glob("**/drizzle.config.ts", {
1043
- cwd: destination,
1044
- nodir: true,
1045
- dot: false,
1046
- absolute: false,
1047
- ignore: ["**/node_modules/**"],
1048
- });
1049
-
1050
- for (const configPath of drizzleConfigs) {
1051
- const workspaceDir = dirname(configPath);
1052
- const pkgPath = join(destination, workspaceDir, "package.json");
1053
- if (!existsSync(pkgPath)) continue;
1054
-
1055
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as Record<string, unknown>;
1056
- const scripts = pkg.scripts as Record<string, string> | undefined;
1057
- if (!scripts?.["db:generate"]) continue;
1058
-
1059
- const cwd = join(destination, workspaceDir);
1060
- await execCommand("bun", ["run", "db:generate"], cwd);
1061
- }
1062
- }
1063
-
1064
- const COMMAND_TIMEOUTS: Record<string, number> = {
1065
- bun: 5 * 60_000,
1066
- docker: 5 * 60_000,
1067
- node_modules: 2 * 60_000,
1068
- tar: 60_000,
1069
- };
1070
-
1071
- export async function execCommand(
1072
- command: string,
1073
- args: string[],
1074
- cwd?: string,
1075
- options?: { stdio?: "pipe" | "inherit" },
1076
- ): Promise<void> {
1077
- const timeout = COMMAND_TIMEOUTS[command] ?? 2 * 60_000;
1078
- await execa(command, args, { cwd, stdio: options?.stdio ?? "pipe", timeout });
1079
- }
1080
-
1081
- function generateEnvExample(config: BosConfigInput, overrides: OverrideSection[]): string {
1082
- const has = (section: OverrideSection) => overrides.includes(section);
1083
-
1084
- const lines: string[] = ["# Environment variables"];
1085
- const collectSecrets = (
1086
- obj: Record<string, unknown>,
1087
- includeSection: boolean,
1088
- prefix = "",
1089
- ): void => {
1090
- for (const [key, value] of Object.entries(obj)) {
1091
- if (!includeSection) continue;
1092
- if (key === "secrets" && Array.isArray(value)) {
1093
- for (const secret of value) {
1094
- if (typeof secret === "string") {
1095
- lines.push(`${secret}=`);
1096
- }
1097
- }
1098
- } else if (key === "variables" && isPlainObject(value)) {
1099
- for (const [varKey, varVal] of Object.entries(value as Record<string, unknown>)) {
1100
- if (typeof varVal === "string") {
1101
- lines.push(`${varKey}=${varVal}`);
1102
- }
1103
- }
1104
- } else if (isPlainObject(value) && key !== "extends") {
1105
- collectSecrets(value as Record<string, unknown>, includeSection, `${prefix}${key}.`);
1106
- }
1107
- }
1108
- };
1109
-
1110
- if (config.app && typeof config.app === "object") {
1111
- const app = config.app as Record<string, unknown>;
1112
- collectSecrets(app, has("host"), "host.");
1113
- collectSecrets(app, has("ui"), "ui.");
1114
- collectSecrets(app, has("api"), "api.");
1115
- collectSecrets(app, has("plugins"), "auth.");
1116
- }
1117
- if (has("plugins") && config.plugins && typeof config.plugins === "object") {
1118
- for (const [pluginKey, pluginVal] of Object.entries(
1119
- config.plugins as Record<string, unknown>,
1120
- )) {
1121
- if (isPlainObject(pluginVal)) {
1122
- collectSecrets(pluginVal as Record<string, unknown>, true);
1123
- } else if (typeof pluginVal === "string") {
1124
- lines.push(`# Plugin '${pluginKey}' extends ${pluginVal}`);
1125
- }
1126
- }
1127
- }
1128
-
1129
- lines.push("BETTER_AUTH_SECRET=generate-a-secret-here");
1130
- return `${lines.join("\n")}\n`;
1131
- }
1132
-
1133
- function isPlainObject(value: unknown): value is Record<string, unknown> {
1134
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1135
- }
1136
-
1137
- function generateGitignore(): string {
1138
- return `node_modules/
1139
- dist/
1140
- .env
1141
- .bos/
1142
- *.gen.ts
1143
- *.gen.tsx
1144
- `;
1145
- }