@bagelink/workspace 1.7.57 → 1.7.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/bgl.mjs CHANGED
@@ -1,548 +1,385 @@
1
1
  #!/usr/bin/env node
2
2
  import { resolve } from 'node:path';
3
3
  import process from 'node:process';
4
- import { existsSync, readFileSync, readdirSync, writeFileSync, unlinkSync, mkdirSync } from 'node:fs';
5
4
  import { spawn } from 'node:child_process';
5
+ import { existsSync, mkdirSync, writeFileSync, readdirSync, readFileSync, unlinkSync } from 'node:fs';
6
6
  import prompts from 'prompts';
7
7
  import { w as writeNetlifyConfig } from '../shared/workspace.Twuo1PFw.mjs';
8
8
 
9
- function isWorkspace(root = process.cwd()) {
10
- const packageJsonPath = resolve(root, "package.json");
11
- if (existsSync(packageJsonPath)) {
12
- try {
13
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
14
- if (packageJson.workspaces !== void 0) {
15
- return true;
16
- }
17
- } catch {
18
- }
19
- }
20
- try {
21
- const items = readdirSync(root, { withFileTypes: true });
22
- const projectDirs = items.filter(
23
- (item) => item.isDirectory() && item.name !== "node_modules" && item.name !== "shared" && item.name !== ".git" && !item.name.startsWith(".") && existsSync(resolve(root, item.name, "package.json"))
24
- );
25
- return projectDirs.length >= 2;
26
- } catch {
27
- return false;
28
- }
29
- }
30
-
31
- const servers = /* @__PURE__ */ new Map();
32
- const seenLines = /* @__PURE__ */ new Set();
33
- const colors = {
34
- reset: "\x1B[0m",
35
- cyan: "\x1B[36m",
36
- green: "\x1B[32m",
37
- yellow: "\x1B[33m",
38
- dim: "\x1B[2m",
39
- red: "\x1B[31m"
40
- };
41
- function clearAndPrintServers() {
42
- if (servers.size > 0) {
43
- process.stdout.write("\x1B[2J\x1B[H");
44
- }
45
- console.log(`${colors.cyan}\u{1F680} Development Servers${colors.reset}
46
- `);
47
- for (const [name, info] of servers.entries()) {
48
- if (info.status === "ready" && info.port) {
49
- const url = `http://localhost:${info.port}`;
50
- console.log(`${colors.green}\u25CF${colors.reset} ${colors.cyan}${name}${colors.reset} ${colors.dim}\u2192${colors.reset} ${url}`);
51
- } else {
52
- console.log(`${colors.yellow}\u25CB${colors.reset} ${colors.dim}${name} (starting...)${colors.reset}`);
53
- }
54
- }
55
- console.log("");
56
- }
57
- async function runDev(filter = "./!shared*", additionalArgs = []) {
58
- const argsStr = additionalArgs.length > 0 ? ` -- ${additionalArgs.join(" ")}` : "";
59
- console.log(`${colors.dim}Starting dev servers with filter: ${filter}${argsStr}${colors.reset}
60
- `);
61
- const command = `bun run --filter '${filter}' dev${argsStr}`;
62
- const proc = spawn(command, {
63
- cwd: process.cwd(),
64
- stdio: ["inherit", "pipe", "pipe"],
65
- shell: true
66
- });
67
- let stdoutBuffer = "";
68
- let stderrBuffer = "";
69
- function processLine(line) {
70
- if (!line.trim()) return;
71
- if (seenLines.has(line)) return;
72
- seenLines.add(line);
73
- const projectMatch = line.match(/^(\w+)\s+dev:/);
74
- if (projectMatch) {
75
- const name = projectMatch[1];
76
- if (!servers.has(name)) {
77
- console.log(`${colors.dim}[DEBUG] Detected project: ${name}${colors.reset}`);
78
- servers.set(name, { name, status: "starting" });
79
- clearAndPrintServers();
80
- }
81
- }
82
- const portMatch = line.match(/Local:\s+http:\/\/localhost:(\d+)/);
83
- if (portMatch) {
84
- const port = Number.parseInt(portMatch[1], 10);
85
- const projectInLine = line.match(/^(\w+)\s+dev:/);
86
- if (projectInLine) {
87
- const name = projectInLine[1];
88
- const info = servers.get(name);
89
- console.log(`${colors.dim}[DEBUG] Port ${port} for project: ${name}, exists: ${!!info}, hasPort: ${info?.port}${colors.reset}`);
90
- if (info && !info.port) {
91
- info.port = port;
92
- info.status = "ready";
93
- clearAndPrintServers();
94
- }
95
- }
96
- }
97
- }
98
- proc.stdout?.setEncoding("utf8");
99
- proc.stderr?.setEncoding("utf8");
100
- proc.stdout?.on("data", (data) => {
101
- if (servers.size === 0) {
102
- console.log(`${colors.dim}Receiving output...${colors.reset}`);
103
- }
104
- stdoutBuffer += data;
105
- const lines = stdoutBuffer.split("\n");
106
- stdoutBuffer = lines.pop() || "";
107
- for (const line of lines) {
108
- processLine(line);
109
- }
110
- });
111
- proc.stderr?.on("data", (data) => {
112
- stderrBuffer += data;
113
- const lines = stderrBuffer.split("\n");
114
- stderrBuffer = lines.pop() || "";
115
- for (const line of lines) {
116
- processLine(line);
117
- }
118
- });
119
- proc.on("error", (error) => {
120
- console.error(`${colors.red}Failed to start dev servers:${colors.reset}`, error.message);
121
- });
122
- process.on("SIGINT", () => {
123
- proc.kill("SIGINT");
124
- process.exit(0);
125
- });
126
- return new Promise((resolve, reject) => {
127
- proc.on("exit", (code) => {
128
- resolve(code || 0);
129
- });
130
- proc.on("error", reject);
131
- });
132
- }
133
-
134
- async function generateWorkspaceConfig(root = process.cwd(), configFile = "bgl.config.ts") {
135
- console.log("\n\u{1F527} No bgl.config.ts found. Let's create one!\n");
9
+ async function initWorkspace(root = process.cwd()) {
10
+ console.log("\n\u{1F680} Creating Bagel workspace...\n");
136
11
  const response = await prompts([
12
+ {
13
+ type: "text",
14
+ name: "workspaceName",
15
+ message: "Workspace name:",
16
+ initial: "my-workspace"
17
+ },
137
18
  {
138
19
  type: "text",
139
20
  name: "projectId",
140
- message: "What is your Bagel project ID?",
141
- initial: "my-project",
142
- validate: (value) => value.length > 0 ? true : "Project ID is required"
21
+ message: "Bagel project ID:",
22
+ initial: "my-project"
143
23
  },
144
24
  {
145
25
  type: "confirm",
146
- name: "useCustomHost",
147
- message: "Use custom production host?",
148
- initial: false
26
+ name: "createFirstProject",
27
+ message: "Create first project?",
28
+ initial: true
149
29
  },
150
30
  {
151
31
  type: (prev) => prev ? "text" : null,
152
- name: "customHost",
153
- message: "Enter production host URL:",
154
- initial: "https://api.example.com"
32
+ name: "firstProjectName",
33
+ message: "First project name:",
34
+ initial: "web"
155
35
  }
156
36
  ]);
157
- if (!response || !response.projectId) {
158
- console.log("\n\u274C Config generation cancelled.\n");
37
+ if (!response || !response.workspaceName) {
38
+ console.log("\n\u274C Workspace creation cancelled.\n");
159
39
  process.exit(1);
160
40
  }
161
- const productionHost = response.useCustomHost === true ? response.customHost : `https://${response.projectId}.bagel.to`;
162
- const configContent = `import { defineWorkspace } from '@bagelink/workspace'
163
- import type { WorkspaceConfig, WorkspaceEnvironment } from '@bagelink/workspace'
41
+ const { workspaceName, projectId, createFirstProject, firstProjectName } = response;
42
+ const workspaceDir = resolve(root, workspaceName);
43
+ createWorkspaceRoot(root, workspaceName, projectId);
44
+ createSharedPackage(workspaceDir);
45
+ if (createFirstProject && firstProjectName) {
46
+ await addProject(firstProjectName, workspaceDir);
47
+ }
48
+ console.log("\n\u2705 Workspace created successfully!");
49
+ console.log("\nNext steps:");
50
+ console.log(` cd ${workspaceName}`);
51
+ console.log(" bun install");
52
+ if (createFirstProject) {
53
+ console.log(` bun run dev:${firstProjectName}`);
54
+ } else {
55
+ console.log(" bgl add <project-name> # Add a project");
56
+ }
57
+ console.log("");
58
+ }
59
+ function createWorkspaceRoot(root, name, projectId) {
60
+ const workspaceDir = resolve(root, name);
61
+ if (existsSync(workspaceDir)) {
62
+ console.error(`\u274C Directory ${name} already exists`);
63
+ process.exit(1);
64
+ }
65
+ mkdirSync(workspaceDir, { recursive: true });
66
+ const packageJson = {
67
+ name,
68
+ private: true,
69
+ workspaces: ["*", "!node_modules"],
70
+ scripts: {
71
+ "dev": "bgl dev",
72
+ "dev:local": "bgl dev --mode localhost",
73
+ "dev:verbose": "bun run --filter './!shared*' dev",
74
+ "build": "bgl build",
75
+ "typecheck": "tsc --noEmit",
76
+ "lint": "eslint . --cache",
77
+ "lint:fix": "eslint . --cache --fix"
78
+ },
79
+ dependencies: {
80
+ "@bagelink/auth": "latest",
81
+ "@bagelink/sdk": "latest",
82
+ "@bagelink/vue": "latest",
83
+ "pinia": "latest",
84
+ "vue": "latest",
85
+ "vue-router": "latest"
86
+ },
87
+ devDependencies: {
88
+ "@bagelink/lint-config": "latest",
89
+ "@bagelink/workspace": "latest",
90
+ "@vitejs/plugin-vue": "latest",
91
+ "eslint": "latest",
92
+ "typescript": "^5.0.0",
93
+ "vite": "latest"
94
+ }
95
+ };
96
+ writeFileSync(
97
+ resolve(workspaceDir, "package.json"),
98
+ `${JSON.stringify(packageJson, null, 2)}
99
+ `
100
+ );
101
+ const bglConfig = `import { defineWorkspace } from '@bagelink/workspace'
164
102
 
165
- const configs: Record<WorkspaceEnvironment, WorkspaceConfig> = {
103
+ export default defineWorkspace({
166
104
  localhost: {
167
105
  host: 'http://localhost:8000',
168
106
  proxy: '/api',
169
107
  openapi_url: 'http://localhost:8000/openapi.json',
170
108
  },
171
109
  development: {
172
- host: '${productionHost}',
110
+ host: 'https://${projectId}.bagel.to',
173
111
  proxy: '/api',
174
- openapi_url: '${productionHost}/openapi.json',
112
+ openapi_url: 'https://${projectId}.bagel.to/openapi.json',
175
113
  },
176
114
  production: {
177
- host: '${productionHost}',
115
+ host: 'https://${projectId}.bagel.to',
178
116
  proxy: '/api',
179
- openapi_url: '${productionHost}/openapi.json',
117
+ openapi_url: 'https://${projectId}.bagel.to/openapi.json',
180
118
  },
181
- }
182
-
183
- export default defineWorkspace(configs)
119
+ })
184
120
  `;
185
- const configPath = resolve(root, configFile);
186
- writeFileSync(configPath, configContent, "utf-8");
187
- console.log(`
188
- \u2705 Created ${configFile}`);
189
- console.log(` Production host: ${productionHost}`);
190
- console.log(` Local dev host: http://localhost:8000
191
- `);
192
- const setupResponse = await prompts([
193
- {
194
- type: "confirm",
195
- name: "updatePackageJson",
196
- message: "Add/update dev scripts in package.json?",
197
- initial: true
198
- },
199
- {
200
- type: "confirm",
201
- name: "updateViteConfig",
202
- message: "Create/update vite.config.ts?",
203
- initial: true
204
- },
205
- {
206
- type: "confirm",
207
- name: "createTsConfig",
208
- message: "Create tsconfig.app.json with path aliases?",
209
- initial: true
210
- },
211
- {
212
- type: "confirm",
213
- name: "generateNetlify",
214
- message: "Generate netlify.toml for deployment?",
215
- initial: true
216
- }
217
- ]);
218
- if (setupResponse.updatePackageJson) {
219
- updatePackageJsonScripts(root);
220
- }
221
- if (setupResponse.updateViteConfig) {
222
- updateViteConfig(root);
223
- }
224
- if (setupResponse.createTsConfig) {
225
- createTsConfig(root);
226
- }
227
- if (setupResponse.generateNetlify) {
228
- const prodConfig = {
229
- host: productionHost,
230
- proxy: "/api"
231
- };
232
- writeNetlifyConfig(prodConfig, resolve(root, "netlify.toml"));
233
- }
234
- console.log("\n\u{1F4A1} You can edit these files to customize your configuration.\n");
235
- }
236
- function updatePackageJsonScripts(root) {
237
- const packageJsonPath = resolve(root, "package.json");
238
- if (!existsSync(packageJsonPath)) {
239
- console.log("\u26A0\uFE0F No package.json found, skipping script update");
240
- return;
241
- }
242
- try {
243
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
244
- if (!packageJson.scripts) {
245
- packageJson.scripts = {};
246
- }
247
- const scriptsToAdd = {
248
- "dev": "vite",
249
- "dev:local": "vite --mode localhost",
250
- "build": "vite build",
251
- "preview": "vite preview"
252
- };
253
- let modified = false;
254
- for (const [key, value] of Object.entries(scriptsToAdd)) {
255
- if (key === "dev" || key === "dev:local") {
256
- if (packageJson.scripts[key] !== value) {
257
- packageJson.scripts[key] = value;
258
- modified = true;
259
- }
260
- } else {
261
- if (!packageJson.scripts[key]) {
262
- packageJson.scripts[key] = value;
263
- modified = true;
264
- }
121
+ writeFileSync(resolve(workspaceDir, "bgl.config.ts"), bglConfig);
122
+ const tsConfig = {
123
+ compilerOptions: {
124
+ target: "ES2020",
125
+ useDefineForClassFields: true,
126
+ module: "ESNext",
127
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
128
+ skipLibCheck: true,
129
+ moduleResolution: "bundler",
130
+ allowImportingTsExtensions: true,
131
+ resolveJsonModule: true,
132
+ isolatedModules: true,
133
+ noEmit: true,
134
+ jsx: "preserve",
135
+ strict: true,
136
+ noUnusedLocals: true,
137
+ noUnusedParameters: true,
138
+ noFallthroughCasesInSwitch: true,
139
+ baseUrl: ".",
140
+ paths: {
141
+ "shared/*": ["./shared/*"]
265
142
  }
266
143
  }
267
- if (modified) {
268
- writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
269
- `, "utf-8");
270
- console.log("\u2705 Updated package.json with dev scripts");
271
- } else {
272
- console.log("\u2139\uFE0F Scripts already up to date in package.json");
273
- }
274
- } catch (error) {
275
- console.error("\u274C Failed to update package.json:", error);
276
- }
277
- }
278
- function createTsConfig(root) {
279
- const tsConfigPath = resolve(root, "tsconfig.app.json");
280
- const tsConfigExists = existsSync(tsConfigPath);
281
- if (tsConfigExists) {
282
- console.log("\u26A0\uFE0F tsconfig.app.json already exists, skipping");
283
- return;
284
- }
285
- const tsConfigContent = `{
286
- "extends": "../tsconfig.json",
287
- "compilerOptions": {
288
- "composite": true,
289
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
290
- "baseUrl": ".",
291
- "paths": {
292
- "@/*": ["./src/*"],
293
- "@shared/*": ["../shared/*"]
294
- }
295
- },
296
- "include": ["src/**/*", "src/**/*.vue"],
297
- "exclude": ["node_modules"]
298
- }
144
+ };
145
+ writeFileSync(
146
+ resolve(workspaceDir, "tsconfig.json"),
147
+ `${JSON.stringify(tsConfig, null, 2)}
148
+ `
149
+ );
150
+ const gitignore = `node_modules
151
+ dist
152
+ .DS_Store
153
+ *.local
154
+ .env.local
155
+ .vite
299
156
  `;
300
- writeFileSync(tsConfigPath, tsConfigContent, "utf-8");
301
- console.log("\u2705 Created tsconfig.app.json");
302
- }
303
- function updateViteConfig(root) {
304
- const viteConfigPath = resolve(root, "vite.config.ts");
305
- const viteConfigExists = existsSync(viteConfigPath);
306
- if (viteConfigExists) {
307
- const existingConfig = readFileSync(viteConfigPath, "utf-8");
308
- if (existingConfig.includes("@bagelink/workspace")) {
309
- console.log("\u2139\uFE0F vite.config.ts already configured");
310
- return;
311
- }
312
- console.log("\u26A0\uFE0F vite.config.ts exists. Please manually add the bagelink plugin:");
313
- console.log("");
314
- console.log(" import { bagelink } from '@bagelink/workspace/vite'");
315
- console.log(" import workspace from './bgl.config'");
316
- console.log("");
317
- console.log(" plugins: [");
318
- console.log(" vue(),");
319
- console.log(" bagelink({ workspace }),");
320
- console.log(" ]");
321
- console.log("");
322
- return;
323
- }
324
- const viteConfigContent = `import { defineConfig } from 'vite'
325
- import vue from '@vitejs/plugin-vue'
326
- import { bagelink } from '@bagelink/workspace/vite'
327
- import workspace from './bgl.config'
157
+ writeFileSync(resolve(workspaceDir, ".gitignore"), gitignore);
158
+ const scriptsDir = resolve(workspaceDir, "scripts");
159
+ mkdirSync(scriptsDir, { recursive: true });
160
+ const devRunnerContent = `#!/usr/bin/env bun
161
+ import { spawn } from 'bun'
162
+ import { readdir } from 'fs/promises'
163
+ import { resolve } from 'path'
328
164
 
329
- // https://vitejs.dev/config/
330
- export default defineConfig({
331
- plugins: [
332
- vue(),
333
- bagelink({ workspace }),
334
- ],
165
+ const projectsRoot = process.cwd()
166
+ const projects = (await readdir(projectsRoot, { withFileTypes: true }))
167
+ .filter(
168
+ item =>
169
+ item.isDirectory()
170
+ && item.name !== 'node_modules'
171
+ && item.name !== 'shared'
172
+ && item.name !== 'scripts'
173
+ && item.name !== '.git'
174
+ && !item.name.startsWith('.'),
175
+ )
176
+ .map(item => item.name)
177
+
178
+ console.log(\`\\n\u{1F680} Starting \${projects.length} project\${projects.length > 1 ? 's' : ''}...\\n\`)
179
+
180
+ const urlPattern = /Local:\\s+(http:\\/\\/localhost:\\d+)/
181
+
182
+ projects.forEach((project) => {
183
+ const proc = spawn({
184
+ cmd: ['bun', 'run', 'dev'],
185
+ cwd: resolve(projectsRoot, project),
186
+ stdout: 'pipe',
187
+ stderr: 'pipe',
188
+ })
189
+
190
+ const decoder = new TextDecoder()
191
+
192
+ proc.stdout.pipeTo(
193
+ new WritableStream({
194
+ write(chunk) {
195
+ const text = decoder.decode(chunk)
196
+ const match = text.match(urlPattern)
197
+ if (match) {
198
+ console.log(\` \u2713 \${project.padEnd(15)} \u2192 \${match[1]}\`)
199
+ }
200
+ },
201
+ }),
202
+ )
203
+
204
+ proc.stderr.pipeTo(
205
+ new WritableStream({
206
+ write(chunk) {
207
+ const text = decoder.decode(chunk)
208
+ if (text.includes('error') || text.includes('Error')) {
209
+ console.error(\` \u2717 \${project}: \${text.trim()}\`)
210
+ }
211
+ },
212
+ }),
213
+ )
214
+ })
215
+
216
+ console.log('\\n\u{1F4A1} Press Ctrl+C to stop all servers\\n')
217
+
218
+ process.on('SIGINT', () => {
219
+ console.log('\\n\\n\u{1F44B} Stopping all servers...\\n')
220
+ process.exit()
335
221
  })
336
222
  `;
337
- writeFileSync(viteConfigPath, viteConfigContent, "utf-8");
338
- console.log("\u2705 Created vite.config.ts");
223
+ writeFileSync(resolve(scriptsDir, "dev.ts"), devRunnerContent);
224
+ console.log(`\u2705 Created workspace: ${name}`);
339
225
  }
340
-
341
- const REDUNDANT_FILES = [
342
- // Old ESLint configs
343
- ".eslintrc",
344
- ".eslintrc.json",
345
- ".eslintrc.js",
346
- ".eslintrc.cjs",
347
- ".eslintrc.yaml",
348
- ".eslintrc.yml",
349
- // Oxlint
350
- "oxlint.json",
351
- // Old Prettier configs (we create .prettierrc)
352
- "prettier.config.js",
353
- "prettier.config.cjs",
354
- "prettier.config.mjs",
355
- ".prettierrc.json",
356
- ".prettierrc.yaml",
357
- ".prettierrc.yml",
358
- ".prettierrc.js",
359
- ".prettierrc.cjs",
360
- ".prettierrc.mjs",
361
- ".prettierrc.toml"
362
- ];
363
- async function setupLint(root = process.cwd(), isWorkspace = false) {
364
- console.log("\n\u{1F50D} Setting up linting...\n");
365
- const response = await prompts([
366
- {
367
- type: "multiselect",
368
- name: "configs",
369
- message: "Select configurations to set up:",
370
- choices: [
371
- { title: "ESLint", value: "eslint", selected: true },
372
- { title: "Prettier", value: "prettier", selected: true },
373
- { title: "EditorConfig", value: "editorconfig", selected: true },
374
- { title: "Git Hooks", value: "githooks", selected: false }
375
- ]
376
- },
377
- {
378
- type: "confirm",
379
- name: "cleanRedundant",
380
- message: "Clean up redundant lint config files?",
381
- initial: true
382
- },
383
- {
384
- type: "confirm",
385
- name: "installDeps",
386
- message: "Install dependencies?",
387
- initial: true
226
+ function createSharedPackage(root) {
227
+ const sharedDir = resolve(root, "shared");
228
+ mkdirSync(sharedDir, { recursive: true });
229
+ const packageJson = {
230
+ name: "shared",
231
+ version: "1.0.0",
232
+ type: "module",
233
+ exports: {
234
+ ".": "./index.ts",
235
+ "./utils": "./utils/index.ts",
236
+ "./types": "./types/index.ts"
388
237
  }
389
- ]);
390
- if (!response || !response.configs) {
391
- console.log("\n\u274C Setup cancelled.\n");
238
+ };
239
+ writeFileSync(
240
+ resolve(sharedDir, "package.json"),
241
+ `${JSON.stringify(packageJson, null, 2)}
242
+ `
243
+ );
244
+ writeFileSync(
245
+ resolve(sharedDir, "index.ts"),
246
+ "// Shared utilities and exports\nexport * from './utils'\n"
247
+ );
248
+ mkdirSync(resolve(sharedDir, "utils"), { recursive: true });
249
+ writeFileSync(
250
+ resolve(sharedDir, "utils", "index.ts"),
251
+ "// Shared utility functions\nexport function formatDate(date: Date): string {\n return date.toISOString()\n}\n"
252
+ );
253
+ mkdirSync(resolve(sharedDir, "types"), { recursive: true });
254
+ writeFileSync(
255
+ resolve(sharedDir, "types", "index.ts"),
256
+ "// Shared types\nexport interface User {\n id: string\n name: string\n}\n"
257
+ );
258
+ console.log("\u2705 Created shared package");
259
+ }
260
+ async function addProject(name, root = process.cwd()) {
261
+ const projectDir = resolve(root, name);
262
+ if (existsSync(projectDir)) {
263
+ console.error(`\u274C Project ${name} already exists`);
392
264
  process.exit(1);
393
265
  }
394
- const { configs, cleanRedundant, installDeps } = response;
395
- if (cleanRedundant) {
396
- await cleanRedundantFiles(root);
397
- }
398
- if (configs.includes("eslint")) {
399
- createEslintConfig(root, isWorkspace);
400
- }
401
- if (configs.includes("prettier")) {
402
- createPrettierConfig(root);
403
- }
404
- if (configs.includes("editorconfig")) {
405
- createEditorConfig(root);
406
- }
407
- if (configs.includes("githooks")) {
408
- createGitHooks(root);
409
- }
410
- updatePackageJsonLint(root, configs);
411
- if (installDeps) {
412
- console.log("\n\u{1F4E6} Installing dependencies...");
413
- console.log("Run: bun add -D @bagelink/lint-config eslint prettier typescript");
266
+ console.log(`
267
+ \u{1F4E6} Creating project: ${name}
268
+ `);
269
+ mkdirSync(projectDir, { recursive: true });
270
+ const isWorkspace = existsSync(resolve(root, "bgl.config.ts"));
271
+ const packageJson = {
272
+ name,
273
+ type: "module",
274
+ scripts: {
275
+ dev: "vite",
276
+ build: "vite build",
277
+ preview: "vite preview"
278
+ },
279
+ dependencies: {},
280
+ devDependencies: {
281
+ "@vitejs/plugin-vue": "latest",
282
+ "vite": "latest",
283
+ "vue": "latest"
284
+ }
285
+ };
286
+ if (isWorkspace) {
287
+ packageJson.dependencies.shared = "workspace:*";
414
288
  }
415
- console.log("\n\u2705 Linting setup complete!");
416
- console.log("\nAvailable commands:");
417
- console.log(" bun run lint - Run linter");
418
- console.log(" bun run lint:fix - Fix linting issues");
419
- console.log(" bun run format - Format code with Prettier");
420
- console.log("");
421
- }
422
- function createEslintConfig(root, isWorkspace) {
423
- const configPath = resolve(root, "eslint.config.js");
424
- const config = isWorkspace ? `import { defineConfig } from '@bagelink/lint-config/eslint'
289
+ writeFileSync(
290
+ resolve(projectDir, "package.json"),
291
+ `${JSON.stringify(packageJson, null, 2)}
292
+ `
293
+ );
294
+ const bglConfigContent = isWorkspace ? `import { defineWorkspace } from '@bagelink/workspace'
295
+ import rootWorkspace from '../bgl.config'
296
+
297
+ export default defineWorkspace({
298
+ localhost: rootWorkspace('localhost'),
299
+ development: rootWorkspace('development'),
300
+ production: rootWorkspace('production'),
301
+ })
302
+ ` : `import { defineWorkspace } from '@bagelink/workspace'
303
+
304
+ export default defineWorkspace({
305
+ localhost: {
306
+ host: 'http://localhost:8000',
307
+ proxy: '/api',
308
+ },
309
+ development: {
310
+ host: 'https://my-project.bagel.to',
311
+ proxy: '/api',
312
+ },
313
+ production: {
314
+ host: 'https://my-project.bagel.to',
315
+ proxy: '/api',
316
+ },
317
+ })
318
+ `;
319
+ writeFileSync(resolve(projectDir, "bgl.config.ts"), bglConfigContent);
320
+ const viteConfig = `import { defineConfig } from 'vite'
321
+ import vue from '@vitejs/plugin-vue'
322
+ import { bagelink } from '@bagelink/workspace/vite'
323
+ import workspace from './bgl.config'
425
324
 
426
325
  export default defineConfig({
427
- // Workspace-level ESLint config
428
- ignores: ['**/dist/**', '**/node_modules/**', '**/.bun-cache/**'],
326
+ plugins: [
327
+ vue(),
328
+ bagelink({ workspace }),
329
+ ],
429
330
  })
430
- ` : `import vue3Config from '@bagelink/lint-config/eslint/vue3'
431
-
432
- export default vue3Config
433
331
  `;
434
- writeFileSync(configPath, config);
435
- console.log("\u2705 Created eslint.config.js");
436
- }
437
- function createPrettierConfig(root) {
438
- const configPath = resolve(root, ".prettierrc");
439
- const config = {
440
- semi: false,
441
- singleQuote: true,
442
- tabWidth: 2,
443
- useTabs: true,
444
- trailingComma: "all",
445
- printWidth: 100,
446
- arrowParens: "avoid"
447
- };
448
- writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
449
- `);
450
- console.log("\u2705 Created .prettierrc");
451
- const ignorePath = resolve(root, ".prettierignore");
452
- const ignore = `dist
453
- node_modules
454
- .bun-cache
455
- *.min.js
456
- *.min.css
332
+ writeFileSync(resolve(projectDir, "vite.config.ts"), viteConfig);
333
+ const srcDir = resolve(projectDir, "src");
334
+ mkdirSync(srcDir, { recursive: true });
335
+ const indexHtml = `<!DOCTYPE html>
336
+ <html lang="en">
337
+ <head>
338
+ <meta charset="UTF-8">
339
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
340
+ <title>${name}</title>
341
+ </head>
342
+ <body>
343
+ <div id="app"></div>
344
+ <script type="module" src="/src/main.ts"><\/script>
345
+ </body>
346
+ </html>
457
347
  `;
458
- writeFileSync(ignorePath, ignore);
459
- console.log("\u2705 Created .prettierignore");
460
- }
461
- function createEditorConfig(root) {
462
- const configPath = resolve(root, ".editorconfig");
463
- const config = `root = true
464
-
465
- [*]
466
- charset = utf-8
467
- indent_style = tab
468
- indent_size = 2
469
- end_of_line = lf
470
- insert_final_newline = true
471
- trim_trailing_whitespace = true
348
+ writeFileSync(resolve(projectDir, "index.html"), indexHtml);
349
+ const mainTs = `import { createApp } from 'vue'
350
+ import App from './App.vue'
472
351
 
473
- [*.md]
474
- trim_trailing_whitespace = false
352
+ createApp(App).mount('#app')
353
+ `;
354
+ writeFileSync(resolve(srcDir, "main.ts"), mainTs);
355
+ const appVue = `<script setup lang="ts">
356
+ import { ref } from 'vue'
357
+ ${isWorkspace ? "import { formatDate } from 'shared/utils'\n" : ""}
358
+ const count = ref(0)
359
+ <\/script>
475
360
 
476
- [*.{json,yml,yaml}]
477
- indent_style = space
478
- indent_size = 2
361
+ <template>
362
+ <div>
363
+ <h1>${name}</h1>
364
+ <button @click="count++">Count: {{ count }}</button>
365
+ ${isWorkspace ? "<p>{{ formatDate(new Date()) }}</p>" : ""}
366
+ </div>
367
+ </template>
479
368
  `;
480
- writeFileSync(configPath, config);
481
- console.log("\u2705 Created .editorconfig");
482
- }
483
- function createGitHooks(root) {
484
- const packageJsonPath = resolve(root, "package.json");
485
- if (!existsSync(packageJsonPath)) {
486
- console.warn("\u26A0\uFE0F No package.json found, skipping git hooks");
487
- return;
488
- }
489
- const lintStagedConfig = {
490
- "*.{js,jsx,ts,tsx,vue}": ["eslint --fix"],
491
- "*.{json,md,yml,yaml}": ["prettier --write"]
492
- };
493
- writeFileSync(
494
- resolve(root, ".lintstagedrc"),
495
- `${JSON.stringify(lintStagedConfig, null, 2)}
496
- `
497
- );
498
- console.log("\u2705 Created .lintstagedrc");
499
- console.log("\u2139\uFE0F Add simple-git-hooks and lint-staged to devDependencies");
500
- console.log(" Then run: npx simple-git-hooks");
501
- }
502
- async function cleanRedundantFiles(root) {
503
- const foundFiles = [];
504
- for (const file of REDUNDANT_FILES) {
505
- const filePath = resolve(root, file);
506
- if (existsSync(filePath)) {
507
- foundFiles.push(file);
508
- }
509
- }
510
- if (foundFiles.length === 0) {
511
- console.log("\u2728 No redundant files found");
512
- return;
513
- }
514
- console.log("\n\u{1F4CB} Found redundant files:");
515
- foundFiles.forEach((file) => {
516
- console.log(` - ${file}`);
517
- });
518
- const confirmResponse = await prompts({
519
- type: "confirm",
520
- name: "confirm",
521
- message: `Delete ${foundFiles.length} redundant file${foundFiles.length > 1 ? "s" : ""}?`,
522
- initial: true
523
- });
524
- if (!confirmResponse.confirm) {
525
- console.log("\u23ED\uFE0F Skipped cleaning redundant files");
526
- return;
527
- }
528
- let deleted = 0;
529
- for (const file of foundFiles) {
530
- try {
531
- unlinkSync(resolve(root, file));
532
- console.log(`\u{1F5D1}\uFE0F Deleted ${file}`);
533
- deleted++;
534
- } catch (error) {
535
- console.error(`\u274C Failed to delete ${file}:`, error);
536
- }
369
+ writeFileSync(resolve(srcDir, "App.vue"), appVue);
370
+ console.log(`\u2705 Created project: ${name}`);
371
+ if (isWorkspace) {
372
+ updateWorkspaceScripts(root, name);
537
373
  }
538
- console.log(`\u2705 Cleaned up ${deleted} file${deleted > 1 ? "s" : ""}`);
374
+ console.log("\nNext steps:");
375
+ console.log(` cd ${name}`);
376
+ console.log(" bun install");
377
+ console.log(" bun run dev");
378
+ console.log("");
539
379
  }
540
- function updatePackageJsonLint(root, configs) {
380
+ function updateWorkspaceScripts(root, projectName) {
541
381
  const packageJsonPath = resolve(root, "package.json");
542
- if (!existsSync(packageJsonPath)) {
543
- console.warn("\u26A0\uFE0F No package.json found");
544
- return;
545
- }
382
+ if (!existsSync(packageJsonPath)) return;
546
383
  try {
547
384
  const packageJson = JSON.parse(
548
385
  readFileSync(packageJsonPath, "utf-8")
@@ -550,474 +387,391 @@ function updatePackageJsonLint(root, configs) {
550
387
  if (!packageJson.scripts) {
551
388
  packageJson.scripts = {};
552
389
  }
553
- if (configs.includes("eslint")) {
554
- if (!packageJson.scripts.lint) {
555
- packageJson.scripts.lint = "eslint .";
556
- }
557
- if (!packageJson.scripts["lint:fix"]) {
558
- packageJson.scripts["lint:fix"] = "eslint . --fix";
559
- }
560
- }
561
- if (configs.includes("prettier")) {
562
- if (!packageJson.scripts.format) {
563
- packageJson.scripts.format = "prettier --write .";
564
- }
565
- if (!packageJson.scripts["format:check"]) {
566
- packageJson.scripts["format:check"] = "prettier --check .";
567
- }
568
- }
390
+ packageJson.scripts[`dev:${projectName}`] = `bun --filter ${projectName} dev`;
391
+ packageJson.scripts[`build:${projectName}`] = `bun --filter ${projectName} build`;
569
392
  writeFileSync(
570
393
  packageJsonPath,
571
394
  `${JSON.stringify(packageJson, null, 2)}
572
395
  `
573
396
  );
574
- console.log("\u2705 Updated package.json with lint scripts");
397
+ console.log(`\u2705 Added scripts: dev:${projectName}, build:${projectName}`);
575
398
  } catch (error) {
576
- console.error("\u274C Failed to update package.json:", error);
399
+ console.warn("\u26A0\uFE0F Could not update workspace scripts");
577
400
  }
578
401
  }
579
-
580
- async function generateSDK(root = process.cwd()) {
581
- console.log("\n\u{1F527} Generating SDK from OpenAPI...\n");
582
- let config = null;
583
- let openApiUrl;
402
+ function listProjects(root = process.cwd()) {
584
403
  try {
585
- const configPath = resolve(root, "bgl.config.ts");
586
- if (existsSync(configPath)) {
587
- const module = await import(`file://${configPath}`);
588
- const workspace = module.default;
589
- if (typeof workspace === "function") {
590
- config = workspace("development");
591
- if (config?.openapi_url) {
592
- openApiUrl = config.openapi_url;
593
- }
594
- }
595
- }
404
+ const items = readdirSync(root, { withFileTypes: true });
405
+ return items.filter(
406
+ (item) => item.isDirectory() && item.name !== "node_modules" && item.name !== "shared" && item.name !== ".git" && !item.name.startsWith(".")
407
+ ).map((item) => item.name);
596
408
  } catch {
409
+ return [];
597
410
  }
598
- const response = await prompts([
599
- {
600
- type: openApiUrl !== void 0 ? null : "text",
601
- name: "openApiUrl",
602
- message: "OpenAPI spec URL:",
603
- initial: openApiUrl ?? "http://localhost:8000/openapi.json"
604
- },
605
- {
606
- type: "text",
607
- name: "outputDir",
608
- message: "Output directory:",
609
- initial: "./src/api"
610
- },
611
- {
612
- type: "confirm",
613
- name: "splitFiles",
614
- message: "Split into organized files?",
615
- initial: true
411
+ }
412
+
413
+ async function runBuild(filter, additionalArgs = []) {
414
+ const argsStr = additionalArgs.length > 0 ? ` -- ${additionalArgs.join(" ")}` : "";
415
+ const resolvedFilter = resolveFilter$1(filter);
416
+ if (!resolvedFilter) return 1;
417
+ const command = `bun run --filter '${resolvedFilter}' build${argsStr}`;
418
+ const proc = spawn(command, {
419
+ cwd: process.cwd(),
420
+ stdio: "inherit",
421
+ shell: true
422
+ });
423
+ proc.on("error", (error) => {
424
+ console.error("Failed to start build:", error.message);
425
+ });
426
+ return new Promise((resolve, reject) => {
427
+ proc.on("exit", (code) => {
428
+ resolve(code || 0);
429
+ });
430
+ proc.on("error", reject);
431
+ });
432
+ }
433
+ function resolveFilter$1(filter) {
434
+ if (filter) return filter;
435
+ const projects = listProjects();
436
+ if (projects.length === 0) {
437
+ console.error("No projects found");
438
+ return null;
439
+ }
440
+ return "./[!shared]*";
441
+ }
442
+
443
+ function isWorkspace(root = process.cwd()) {
444
+ const packageJsonPath = resolve(root, "package.json");
445
+ if (existsSync(packageJsonPath)) {
446
+ try {
447
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
448
+ if (packageJson.workspaces !== void 0) {
449
+ return true;
450
+ }
451
+ } catch {
616
452
  }
617
- ]);
618
- if (!response) {
619
- console.log("\n\u274C SDK generation cancelled.\n");
620
- process.exit(1);
621
453
  }
622
- const finalUrl = openApiUrl ?? response.openApiUrl;
623
- const { outputDir, splitFiles } = response;
624
- console.log(`
625
- \u{1F4E1} Fetching OpenAPI spec from: ${finalUrl}`);
626
- console.log(`\u{1F4C1} Output directory: ${outputDir}
627
- `);
628
454
  try {
629
- const { openAPI } = await import('@bagelink/sdk');
630
- const { types, code } = await openAPI(finalUrl, "/api");
631
- const outputPath = resolve(root, outputDir);
632
- if (!existsSync(outputPath)) {
633
- mkdirSync(outputPath, { recursive: true });
634
- }
635
- const typesPath = resolve(outputPath, "types.d.ts");
636
- writeFileSync(typesPath, types);
637
- console.log("\u2705 Generated types.d.ts");
638
- const apiPath = resolve(outputPath, "api.ts");
639
- writeFileSync(apiPath, code);
640
- console.log("\u2705 Generated api.ts");
641
- const indexPath = resolve(outputPath, "index.ts");
642
- writeFileSync(
643
- indexPath,
644
- "export * from './api'\nexport * from './types.d'\n"
455
+ const items = readdirSync(root, { withFileTypes: true });
456
+ const projectDirs = items.filter(
457
+ (item) => item.isDirectory() && item.name !== "node_modules" && item.name !== "shared" && item.name !== ".git" && !item.name.startsWith(".") && existsSync(resolve(root, item.name, "package.json"))
645
458
  );
646
- console.log("\u2705 Generated index.ts");
647
- if (splitFiles) {
648
- console.log("\n\u{1F500} Splitting into organized files...");
649
- console.log("\u2139\uFE0F File splitting requires @bagelink/sdk bin scripts");
650
- console.log(" Keeping monolithic structure for now");
651
- }
652
- console.log("\n\u2705 SDK generated successfully!");
653
- console.log(`
654
- Import it in your code:`);
655
- console.log(` import { api } from '${outputDir.replace("./src/", "./")}'`);
656
- console.log("");
657
- } catch (error) {
658
- console.error("\n\u274C Failed to generate SDK:");
659
- if (error instanceof Error) {
660
- console.error(error.message);
459
+ return projectDirs.length >= 2;
460
+ } catch {
461
+ return false;
462
+ }
463
+ }
464
+
465
+ const servers = /* @__PURE__ */ new Map();
466
+ const seenLines = /* @__PURE__ */ new Set();
467
+ const colors = {
468
+ reset: "\x1B[0m",
469
+ cyan: "\x1B[36m",
470
+ green: "\x1B[32m",
471
+ yellow: "\x1B[33m",
472
+ dim: "\x1B[2m",
473
+ red: "\x1B[31m"
474
+ };
475
+ function clearAndPrintServers() {
476
+ if (servers.size > 0) {
477
+ process.stdout.write("\x1B[2J\x1B[H");
478
+ }
479
+ console.log(`${colors.cyan}\u{1F680} Development Servers${colors.reset}
480
+ `);
481
+ for (const [name, info] of servers.entries()) {
482
+ if (info.status === "ready" && info.port) {
483
+ const url = `http://localhost:${info.port}`;
484
+ console.log(`${colors.green}\u25CF${colors.reset} ${colors.cyan}${name}${colors.reset} ${colors.dim}\u2192${colors.reset} ${url}`);
661
485
  } else {
662
- console.error(error);
486
+ console.log(`${colors.yellow}\u25CB${colors.reset} ${colors.dim}${name} (starting...)${colors.reset}`);
663
487
  }
664
- console.log("\nMake sure:");
665
- console.log(" 1. @bagelink/sdk is installed: bun add -D @bagelink/sdk");
666
- console.log(" 2. OpenAPI URL is accessible");
667
- console.log(" 3. API server is running (if using localhost)");
668
- process.exit(1);
669
488
  }
489
+ console.log("");
670
490
  }
671
- async function generateSDKForWorkspace(root = process.cwd()) {
672
- console.log("\n\u{1F3E2} Generating SDK for workspace projects...\n");
673
- const fs = await import('node:fs');
674
- const items = fs.readdirSync(root, { withFileTypes: true });
675
- const projects = items.filter(
676
- (item) => item.isDirectory() && item.name !== "node_modules" && item.name !== "shared" && item.name !== ".git" && !item.name.startsWith(".")
677
- ).map((item) => item.name);
678
- if (projects.length === 0) {
679
- console.log("No projects found in workspace");
680
- return;
681
- }
682
- const response = await prompts({
683
- type: "multiselect",
684
- name: "selectedProjects",
685
- message: "Select projects to generate SDK for:",
686
- choices: projects.map((p) => ({ title: p, value: p, selected: true }))
491
+ async function runDev(filter, additionalArgs = []) {
492
+ const argsStr = additionalArgs.length > 0 ? ` -- ${additionalArgs.join(" ")}` : "";
493
+ const resolvedFilter = resolveFilter(filter);
494
+ if (!resolvedFilter) return 1;
495
+ console.log(`${colors.dim}Starting dev servers with filter: ${resolvedFilter}${argsStr}${colors.reset}
496
+ `);
497
+ const command = `bun run --filter '${resolvedFilter}' dev${argsStr}`;
498
+ const proc = spawn(command, {
499
+ cwd: process.cwd(),
500
+ stdio: ["inherit", "pipe", "pipe"],
501
+ shell: true
687
502
  });
688
- if (!response || !response.selectedProjects || response.selectedProjects.length === 0) {
689
- console.log("\n\u274C No projects selected.\n");
690
- return;
503
+ let stdoutBuffer = "";
504
+ let stderrBuffer = "";
505
+ function processLine(line) {
506
+ if (!line.trim()) return;
507
+ if (seenLines.has(line)) return;
508
+ seenLines.add(line);
509
+ const projectMatch = line.match(/^(\w+)\s+dev:/);
510
+ if (projectMatch) {
511
+ const name = projectMatch[1];
512
+ if (!servers.has(name)) {
513
+ console.log(`${colors.dim}[DEBUG] Detected project: ${name}${colors.reset}`);
514
+ servers.set(name, { name, status: "starting" });
515
+ clearAndPrintServers();
516
+ }
517
+ }
518
+ const portMatch = line.match(/Local:\s+http:\/\/localhost:(\d+)/);
519
+ if (portMatch) {
520
+ const port = Number.parseInt(portMatch[1], 10);
521
+ const projectInLine = line.match(/^(\w+)\s+dev:/);
522
+ if (projectInLine) {
523
+ const name = projectInLine[1];
524
+ const info = servers.get(name);
525
+ console.log(`${colors.dim}[DEBUG] Port ${port} for project: ${name}, exists: ${!!info}, hasPort: ${info?.port}${colors.reset}`);
526
+ if (info && !info.port) {
527
+ info.port = port;
528
+ info.status = "ready";
529
+ clearAndPrintServers();
530
+ }
531
+ }
532
+ }
691
533
  }
692
- for (const project of response.selectedProjects) {
693
- console.log(`
694
- \u{1F4E6} Generating SDK for: ${project}`);
695
- const projectPath = resolve(root, project);
696
- try {
697
- await generateSDK(projectPath);
698
- } catch {
699
- console.error(`Failed to generate SDK for ${project}`);
534
+ proc.stdout?.setEncoding("utf8");
535
+ proc.stderr?.setEncoding("utf8");
536
+ proc.stdout?.on("data", (data) => {
537
+ if (servers.size === 0) {
538
+ console.log(`${colors.dim}Receiving output...${colors.reset}`);
539
+ }
540
+ stdoutBuffer += data;
541
+ const lines = stdoutBuffer.split("\n");
542
+ stdoutBuffer = lines.pop() || "";
543
+ for (const line of lines) {
544
+ processLine(line);
545
+ }
546
+ });
547
+ proc.stderr?.on("data", (data) => {
548
+ stderrBuffer += data;
549
+ const lines = stderrBuffer.split("\n");
550
+ stderrBuffer = lines.pop() || "";
551
+ for (const line of lines) {
552
+ processLine(line);
700
553
  }
554
+ });
555
+ proc.on("error", (error) => {
556
+ console.error(`${colors.red}Failed to start dev servers:${colors.reset}`, error.message);
557
+ });
558
+ process.on("SIGINT", () => {
559
+ proc.kill("SIGINT");
560
+ process.exit(0);
561
+ });
562
+ return new Promise((resolve, reject) => {
563
+ proc.on("exit", (code) => {
564
+ resolve(code || 0);
565
+ });
566
+ proc.on("error", reject);
567
+ });
568
+ }
569
+ function resolveFilter(filter) {
570
+ if (filter) return filter;
571
+ const projects = listProjects();
572
+ if (projects.length === 0) {
573
+ console.error("No projects found");
574
+ return null;
701
575
  }
702
- console.log("\n\u2705 All SDKs generated!");
576
+ return "./[!shared]*";
703
577
  }
704
578
 
705
- async function initWorkspace(root = process.cwd()) {
706
- console.log("\n\u{1F680} Creating Bagel workspace...\n");
579
+ async function generateWorkspaceConfig(root = process.cwd(), configFile = "bgl.config.ts") {
580
+ console.log("\n\u{1F527} No bgl.config.ts found. Let's create one!\n");
707
581
  const response = await prompts([
708
- {
709
- type: "text",
710
- name: "workspaceName",
711
- message: "Workspace name:",
712
- initial: "my-workspace"
713
- },
714
582
  {
715
583
  type: "text",
716
584
  name: "projectId",
717
- message: "Bagel project ID:",
718
- initial: "my-project"
585
+ message: "What is your Bagel project ID?",
586
+ initial: "my-project",
587
+ validate: (value) => value.length > 0 ? true : "Project ID is required"
719
588
  },
720
589
  {
721
590
  type: "confirm",
722
- name: "createFirstProject",
723
- message: "Create first project?",
724
- initial: true
725
- },
726
- {
727
- type: (prev) => prev ? "text" : null,
728
- name: "firstProjectName",
729
- message: "First project name:",
730
- initial: "web"
731
- }
732
- ]);
733
- if (!response || !response.workspaceName) {
734
- console.log("\n\u274C Workspace creation cancelled.\n");
735
- process.exit(1);
736
- }
737
- const { workspaceName, projectId, createFirstProject, firstProjectName } = response;
738
- const workspaceDir = resolve(root, workspaceName);
739
- createWorkspaceRoot(root, workspaceName, projectId);
740
- createSharedPackage(workspaceDir);
741
- if (createFirstProject && firstProjectName) {
742
- await addProject(firstProjectName, workspaceDir);
743
- }
744
- console.log("\n\u2705 Workspace created successfully!");
745
- console.log("\nNext steps:");
746
- console.log(` cd ${workspaceName}`);
747
- console.log(" bun install");
748
- if (createFirstProject) {
749
- console.log(` bun run dev:${firstProjectName}`);
750
- } else {
751
- console.log(" bgl add <project-name> # Add a project");
752
- }
753
- console.log("");
754
- }
755
- function createWorkspaceRoot(root, name, projectId) {
756
- const workspaceDir = resolve(root, name);
757
- if (existsSync(workspaceDir)) {
758
- console.error(`\u274C Directory ${name} already exists`);
759
- process.exit(1);
760
- }
761
- mkdirSync(workspaceDir, { recursive: true });
762
- const packageJson = {
763
- name,
764
- private: true,
765
- workspaces: ["*", "!node_modules"],
766
- scripts: {
767
- "dev": "bgl dev",
768
- "dev:local": "bgl dev --mode localhost",
769
- "dev:verbose": "bun run --filter './!shared*' dev",
770
- "build": "bun run --filter './!shared*' build",
771
- "typecheck": "tsc --noEmit",
772
- "lint": "eslint . --cache",
773
- "lint:fix": "eslint . --cache --fix"
774
- },
775
- dependencies: {
776
- "@bagelink/auth": "latest",
777
- "@bagelink/sdk": "latest",
778
- "@bagelink/vue": "latest",
779
- "pinia": "latest",
780
- "vue": "latest",
781
- "vue-router": "latest"
591
+ name: "useCustomHost",
592
+ message: "Use custom production host?",
593
+ initial: false
782
594
  },
783
- devDependencies: {
784
- "@bagelink/lint-config": "latest",
785
- "@bagelink/workspace": "latest",
786
- "@vitejs/plugin-vue": "latest",
787
- "eslint": "latest",
788
- "typescript": "^5.0.0",
789
- "vite": "latest"
595
+ {
596
+ type: (prev) => prev ? "text" : null,
597
+ name: "customHost",
598
+ message: "Enter production host URL:",
599
+ initial: "https://api.example.com"
790
600
  }
791
- };
792
- writeFileSync(
793
- resolve(workspaceDir, "package.json"),
794
- `${JSON.stringify(packageJson, null, 2)}
795
- `
796
- );
797
- const bglConfig = `import { defineWorkspace } from '@bagelink/workspace'
601
+ ]);
602
+ if (!response || !response.projectId) {
603
+ console.log("\n\u274C Config generation cancelled.\n");
604
+ process.exit(1);
605
+ }
606
+ const productionHost = response.useCustomHost === true ? response.customHost : `https://${response.projectId}.bagel.to`;
607
+ const configContent = `import { defineWorkspace } from '@bagelink/workspace'
608
+ import type { WorkspaceConfig, WorkspaceEnvironment } from '@bagelink/workspace'
798
609
 
799
- export default defineWorkspace({
610
+ const configs: Record<WorkspaceEnvironment, WorkspaceConfig> = {
800
611
  localhost: {
801
612
  host: 'http://localhost:8000',
802
613
  proxy: '/api',
803
614
  openapi_url: 'http://localhost:8000/openapi.json',
804
615
  },
805
616
  development: {
806
- host: 'https://${projectId}.bagel.to',
617
+ host: '${productionHost}',
807
618
  proxy: '/api',
808
- openapi_url: 'https://${projectId}.bagel.to/openapi.json',
619
+ openapi_url: '${productionHost}/openapi.json',
809
620
  },
810
621
  production: {
811
- host: 'https://${projectId}.bagel.to',
622
+ host: '${productionHost}',
812
623
  proxy: '/api',
813
- openapi_url: 'https://${projectId}.bagel.to/openapi.json',
624
+ openapi_url: '${productionHost}/openapi.json',
814
625
  },
815
- })
816
- `;
817
- writeFileSync(resolve(workspaceDir, "bgl.config.ts"), bglConfig);
818
- const tsConfig = {
819
- compilerOptions: {
820
- target: "ES2020",
821
- useDefineForClassFields: true,
822
- module: "ESNext",
823
- lib: ["ES2020", "DOM", "DOM.Iterable"],
824
- skipLibCheck: true,
825
- moduleResolution: "bundler",
826
- allowImportingTsExtensions: true,
827
- resolveJsonModule: true,
828
- isolatedModules: true,
829
- noEmit: true,
830
- jsx: "preserve",
831
- strict: true,
832
- noUnusedLocals: true,
833
- noUnusedParameters: true,
834
- noFallthroughCasesInSwitch: true,
835
- baseUrl: ".",
836
- paths: {
837
- "shared/*": ["./shared/*"]
838
- }
839
- }
840
- };
841
- writeFileSync(
842
- resolve(workspaceDir, "tsconfig.json"),
843
- `${JSON.stringify(tsConfig, null, 2)}
844
- `
845
- );
846
- const gitignore = `node_modules
847
- dist
848
- .DS_Store
849
- *.local
850
- .env.local
851
- .vite
852
- `;
853
- writeFileSync(resolve(workspaceDir, ".gitignore"), gitignore);
854
- const scriptsDir = resolve(workspaceDir, "scripts");
855
- mkdirSync(scriptsDir, { recursive: true });
856
- const devRunnerContent = `#!/usr/bin/env bun
857
- import { spawn } from 'bun'
858
- import { readdir } from 'fs/promises'
859
- import { resolve } from 'path'
860
-
861
- const projectsRoot = process.cwd()
862
- const projects = (await readdir(projectsRoot, { withFileTypes: true }))
863
- .filter(
864
- item =>
865
- item.isDirectory()
866
- && item.name !== 'node_modules'
867
- && item.name !== 'shared'
868
- && item.name !== 'scripts'
869
- && item.name !== '.git'
870
- && !item.name.startsWith('.'),
871
- )
872
- .map(item => item.name)
873
-
874
- console.log(\`\\n\u{1F680} Starting \${projects.length} project\${projects.length > 1 ? 's' : ''}...\\n\`)
875
-
876
- const urlPattern = /Local:\\s+(http:\\/\\/localhost:\\d+)/
877
-
878
- projects.forEach((project) => {
879
- const proc = spawn({
880
- cmd: ['bun', 'run', 'dev'],
881
- cwd: resolve(projectsRoot, project),
882
- stdout: 'pipe',
883
- stderr: 'pipe',
884
- })
885
-
886
- const decoder = new TextDecoder()
887
-
888
- proc.stdout.pipeTo(
889
- new WritableStream({
890
- write(chunk) {
891
- const text = decoder.decode(chunk)
892
- const match = text.match(urlPattern)
893
- if (match) {
894
- console.log(\` \u2713 \${project.padEnd(15)} \u2192 \${match[1]}\`)
895
- }
896
- },
897
- }),
898
- )
899
-
900
- proc.stderr.pipeTo(
901
- new WritableStream({
902
- write(chunk) {
903
- const text = decoder.decode(chunk)
904
- if (text.includes('error') || text.includes('Error')) {
905
- console.error(\` \u2717 \${project}: \${text.trim()}\`)
906
- }
907
- },
908
- }),
909
- )
910
- })
911
-
912
- console.log('\\n\u{1F4A1} Press Ctrl+C to stop all servers\\n')
626
+ }
913
627
 
914
- process.on('SIGINT', () => {
915
- console.log('\\n\\n\u{1F44B} Stopping all servers...\\n')
916
- process.exit()
917
- })
628
+ export default defineWorkspace(configs)
918
629
  `;
919
- writeFileSync(resolve(scriptsDir, "dev.ts"), devRunnerContent);
920
- console.log(`\u2705 Created workspace: ${name}`);
630
+ const configPath = resolve(root, configFile);
631
+ writeFileSync(configPath, configContent, "utf-8");
632
+ console.log(`
633
+ \u2705 Created ${configFile}`);
634
+ console.log(` Production host: ${productionHost}`);
635
+ console.log(` Local dev host: http://localhost:8000
636
+ `);
637
+ const setupResponse = await prompts([
638
+ {
639
+ type: "confirm",
640
+ name: "updatePackageJson",
641
+ message: "Add/update dev scripts in package.json?",
642
+ initial: true
643
+ },
644
+ {
645
+ type: "confirm",
646
+ name: "updateViteConfig",
647
+ message: "Create/update vite.config.ts?",
648
+ initial: true
649
+ },
650
+ {
651
+ type: "confirm",
652
+ name: "createTsConfig",
653
+ message: "Create tsconfig.app.json with path aliases?",
654
+ initial: true
655
+ },
656
+ {
657
+ type: "confirm",
658
+ name: "generateNetlify",
659
+ message: "Generate netlify.toml for deployment?",
660
+ initial: true
661
+ }
662
+ ]);
663
+ if (setupResponse.updatePackageJson) {
664
+ updatePackageJsonScripts(root);
665
+ }
666
+ if (setupResponse.updateViteConfig) {
667
+ updateViteConfig(root);
668
+ }
669
+ if (setupResponse.createTsConfig) {
670
+ createTsConfig(root);
671
+ }
672
+ if (setupResponse.generateNetlify) {
673
+ const prodConfig = {
674
+ host: productionHost,
675
+ proxy: "/api"
676
+ };
677
+ writeNetlifyConfig(prodConfig, resolve(root, "netlify.toml"));
678
+ }
679
+ console.log("\n\u{1F4A1} You can edit these files to customize your configuration.\n");
921
680
  }
922
- function createSharedPackage(root) {
923
- const sharedDir = resolve(root, "shared");
924
- mkdirSync(sharedDir, { recursive: true });
925
- const packageJson = {
926
- name: "shared",
927
- version: "1.0.0",
928
- type: "module",
929
- exports: {
930
- ".": "./index.ts",
931
- "./utils": "./utils/index.ts",
932
- "./types": "./types/index.ts"
681
+ function updatePackageJsonScripts(root) {
682
+ const packageJsonPath = resolve(root, "package.json");
683
+ if (!existsSync(packageJsonPath)) {
684
+ console.log("\u26A0\uFE0F No package.json found, skipping script update");
685
+ return;
686
+ }
687
+ try {
688
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
689
+ if (!packageJson.scripts) {
690
+ packageJson.scripts = {};
933
691
  }
934
- };
935
- writeFileSync(
936
- resolve(sharedDir, "package.json"),
937
- `${JSON.stringify(packageJson, null, 2)}
938
- `
939
- );
940
- writeFileSync(
941
- resolve(sharedDir, "index.ts"),
942
- "// Shared utilities and exports\nexport * from './utils'\n"
943
- );
944
- mkdirSync(resolve(sharedDir, "utils"), { recursive: true });
945
- writeFileSync(
946
- resolve(sharedDir, "utils", "index.ts"),
947
- "// Shared utility functions\nexport function formatDate(date: Date): string {\n return date.toISOString()\n}\n"
948
- );
949
- mkdirSync(resolve(sharedDir, "types"), { recursive: true });
950
- writeFileSync(
951
- resolve(sharedDir, "types", "index.ts"),
952
- "// Shared types\nexport interface User {\n id: string\n name: string\n}\n"
953
- );
954
- console.log("\u2705 Created shared package");
692
+ const scriptsToAdd = {
693
+ "dev": "vite",
694
+ "dev:local": "vite --mode localhost",
695
+ "build": "vite build",
696
+ "preview": "vite preview"
697
+ };
698
+ let modified = false;
699
+ for (const [key, value] of Object.entries(scriptsToAdd)) {
700
+ if (key === "dev" || key === "dev:local") {
701
+ if (packageJson.scripts[key] !== value) {
702
+ packageJson.scripts[key] = value;
703
+ modified = true;
704
+ }
705
+ } else {
706
+ if (!packageJson.scripts[key]) {
707
+ packageJson.scripts[key] = value;
708
+ modified = true;
709
+ }
710
+ }
711
+ }
712
+ if (modified) {
713
+ writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
714
+ `, "utf-8");
715
+ console.log("\u2705 Updated package.json with dev scripts");
716
+ } else {
717
+ console.log("\u2139\uFE0F Scripts already up to date in package.json");
718
+ }
719
+ } catch (error) {
720
+ console.error("\u274C Failed to update package.json:", error);
721
+ }
955
722
  }
956
- async function addProject(name, root = process.cwd()) {
957
- const projectDir = resolve(root, name);
958
- if (existsSync(projectDir)) {
959
- console.error(`\u274C Project ${name} already exists`);
960
- process.exit(1);
723
+ function createTsConfig(root) {
724
+ const tsConfigPath = resolve(root, "tsconfig.app.json");
725
+ const tsConfigExists = existsSync(tsConfigPath);
726
+ if (tsConfigExists) {
727
+ console.log("\u26A0\uFE0F tsconfig.app.json already exists, skipping");
728
+ return;
961
729
  }
962
- console.log(`
963
- \u{1F4E6} Creating project: ${name}
964
- `);
965
- mkdirSync(projectDir, { recursive: true });
966
- const isWorkspace = existsSync(resolve(root, "bgl.config.ts"));
967
- const packageJson = {
968
- name,
969
- type: "module",
970
- scripts: {
971
- dev: "vite",
972
- build: "vite build",
973
- preview: "vite preview"
974
- },
975
- dependencies: {},
976
- devDependencies: {
977
- "@vitejs/plugin-vue": "latest",
978
- "vite": "latest",
979
- "vue": "latest"
730
+ const tsConfigContent = `{
731
+ "extends": "../tsconfig.json",
732
+ "compilerOptions": {
733
+ "composite": true,
734
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
735
+ "baseUrl": ".",
736
+ "paths": {
737
+ "@/*": ["./src/*"],
738
+ "@shared/*": ["../shared/*"]
980
739
  }
981
- };
982
- if (isWorkspace) {
983
- packageJson.dependencies.shared = "workspace:*";
984
- }
985
- writeFileSync(
986
- resolve(projectDir, "package.json"),
987
- `${JSON.stringify(packageJson, null, 2)}
988
- `
989
- );
990
- const bglConfigContent = isWorkspace ? `import { defineWorkspace } from '@bagelink/workspace'
991
- import rootWorkspace from '../bgl.config'
992
-
993
- export default defineWorkspace({
994
- localhost: rootWorkspace('localhost'),
995
- development: rootWorkspace('development'),
996
- production: rootWorkspace('production'),
997
- })
998
- ` : `import { defineWorkspace } from '@bagelink/workspace'
999
-
1000
- export default defineWorkspace({
1001
- localhost: {
1002
- host: 'http://localhost:8000',
1003
- proxy: '/api',
1004
- },
1005
- development: {
1006
- host: 'https://my-project.bagel.to',
1007
- proxy: '/api',
1008
- },
1009
- production: {
1010
- host: 'https://my-project.bagel.to',
1011
- proxy: '/api',
1012
- },
1013
- })
740
+ },
741
+ "include": ["src/**/*", "src/**/*.vue"],
742
+ "exclude": ["node_modules"]
743
+ }
1014
744
  `;
1015
- writeFileSync(resolve(projectDir, "bgl.config.ts"), bglConfigContent);
1016
- const viteConfig = `import { defineConfig } from 'vite'
745
+ writeFileSync(tsConfigPath, tsConfigContent, "utf-8");
746
+ console.log("\u2705 Created tsconfig.app.json");
747
+ }
748
+ function updateViteConfig(root) {
749
+ const viteConfigPath = resolve(root, "vite.config.ts");
750
+ const viteConfigExists = existsSync(viteConfigPath);
751
+ if (viteConfigExists) {
752
+ const existingConfig = readFileSync(viteConfigPath, "utf-8");
753
+ if (existingConfig.includes("@bagelink/workspace")) {
754
+ console.log("\u2139\uFE0F vite.config.ts already configured");
755
+ return;
756
+ }
757
+ console.log("\u26A0\uFE0F vite.config.ts exists. Please manually add the bagelink plugin:");
758
+ console.log("");
759
+ console.log(" import { bagelink } from '@bagelink/workspace/vite'");
760
+ console.log(" import workspace from './bgl.config'");
761
+ console.log("");
762
+ console.log(" plugins: [");
763
+ console.log(" vue(),");
764
+ console.log(" bagelink({ workspace }),");
765
+ console.log(" ]");
766
+ console.log("");
767
+ return;
768
+ }
769
+ const viteConfigContent = `import { defineConfig } from 'vite'
1017
770
  import vue from '@vitejs/plugin-vue'
1018
771
  import { bagelink } from '@bagelink/workspace/vite'
1019
772
  import workspace from './bgl.config'
1020
773
 
774
+ // https://vitejs.dev/config/
1021
775
  export default defineConfig({
1022
776
  plugins: [
1023
777
  vue(),
@@ -1025,57 +779,215 @@ export default defineConfig({
1025
779
  ],
1026
780
  })
1027
781
  `;
1028
- writeFileSync(resolve(projectDir, "vite.config.ts"), viteConfig);
1029
- const srcDir = resolve(projectDir, "src");
1030
- mkdirSync(srcDir, { recursive: true });
1031
- const indexHtml = `<!DOCTYPE html>
1032
- <html lang="en">
1033
- <head>
1034
- <meta charset="UTF-8">
1035
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
1036
- <title>${name}</title>
1037
- </head>
1038
- <body>
1039
- <div id="app"></div>
1040
- <script type="module" src="/src/main.ts"><\/script>
1041
- </body>
1042
- </html>
782
+ writeFileSync(viteConfigPath, viteConfigContent, "utf-8");
783
+ console.log("\u2705 Created vite.config.ts");
784
+ }
785
+
786
+ const REDUNDANT_FILES = [
787
+ // Old ESLint configs
788
+ ".eslintrc",
789
+ ".eslintrc.json",
790
+ ".eslintrc.js",
791
+ ".eslintrc.cjs",
792
+ ".eslintrc.yaml",
793
+ ".eslintrc.yml",
794
+ // Oxlint
795
+ "oxlint.json",
796
+ // Old Prettier configs (we create .prettierrc)
797
+ "prettier.config.js",
798
+ "prettier.config.cjs",
799
+ "prettier.config.mjs",
800
+ ".prettierrc.json",
801
+ ".prettierrc.yaml",
802
+ ".prettierrc.yml",
803
+ ".prettierrc.js",
804
+ ".prettierrc.cjs",
805
+ ".prettierrc.mjs",
806
+ ".prettierrc.toml"
807
+ ];
808
+ async function setupLint(root = process.cwd(), isWorkspace = false) {
809
+ console.log("\n\u{1F50D} Setting up linting...\n");
810
+ const response = await prompts([
811
+ {
812
+ type: "multiselect",
813
+ name: "configs",
814
+ message: "Select configurations to set up:",
815
+ choices: [
816
+ { title: "ESLint", value: "eslint", selected: true },
817
+ { title: "Prettier", value: "prettier", selected: true },
818
+ { title: "EditorConfig", value: "editorconfig", selected: true },
819
+ { title: "Git Hooks", value: "githooks", selected: false }
820
+ ]
821
+ },
822
+ {
823
+ type: "confirm",
824
+ name: "cleanRedundant",
825
+ message: "Clean up redundant lint config files?",
826
+ initial: true
827
+ },
828
+ {
829
+ type: "confirm",
830
+ name: "installDeps",
831
+ message: "Install dependencies?",
832
+ initial: true
833
+ }
834
+ ]);
835
+ if (!response || !response.configs) {
836
+ console.log("\n\u274C Setup cancelled.\n");
837
+ process.exit(1);
838
+ }
839
+ const { configs, cleanRedundant, installDeps } = response;
840
+ if (cleanRedundant) {
841
+ await cleanRedundantFiles(root);
842
+ }
843
+ if (configs.includes("eslint")) {
844
+ createEslintConfig(root, isWorkspace);
845
+ }
846
+ if (configs.includes("prettier")) {
847
+ createPrettierConfig(root);
848
+ }
849
+ if (configs.includes("editorconfig")) {
850
+ createEditorConfig(root);
851
+ }
852
+ if (configs.includes("githooks")) {
853
+ createGitHooks(root);
854
+ }
855
+ updatePackageJsonLint(root, configs);
856
+ if (installDeps) {
857
+ console.log("\n\u{1F4E6} Installing dependencies...");
858
+ console.log("Run: bun add -D @bagelink/lint-config eslint prettier typescript");
859
+ }
860
+ console.log("\n\u2705 Linting setup complete!");
861
+ console.log("\nAvailable commands:");
862
+ console.log(" bun run lint - Run linter");
863
+ console.log(" bun run lint:fix - Fix linting issues");
864
+ console.log(" bun run format - Format code with Prettier");
865
+ console.log("");
866
+ }
867
+ function createEslintConfig(root, isWorkspace) {
868
+ const configPath = resolve(root, "eslint.config.js");
869
+ const config = isWorkspace ? `import { defineConfig } from '@bagelink/lint-config/eslint'
870
+
871
+ export default defineConfig({
872
+ // Workspace-level ESLint config
873
+ ignores: ['**/dist/**', '**/node_modules/**', '**/.bun-cache/**'],
874
+ })
875
+ ` : `import vue3Config from '@bagelink/lint-config/eslint/vue3'
876
+
877
+ export default vue3Config
1043
878
  `;
1044
- writeFileSync(resolve(projectDir, "index.html"), indexHtml);
1045
- const mainTs = `import { createApp } from 'vue'
1046
- import App from './App.vue'
879
+ writeFileSync(configPath, config);
880
+ console.log("\u2705 Created eslint.config.js");
881
+ }
882
+ function createPrettierConfig(root) {
883
+ const configPath = resolve(root, ".prettierrc");
884
+ const config = {
885
+ semi: false,
886
+ singleQuote: true,
887
+ tabWidth: 2,
888
+ useTabs: true,
889
+ trailingComma: "all",
890
+ printWidth: 100,
891
+ arrowParens: "avoid"
892
+ };
893
+ writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
894
+ `);
895
+ console.log("\u2705 Created .prettierrc");
896
+ const ignorePath = resolve(root, ".prettierignore");
897
+ const ignore = `dist
898
+ node_modules
899
+ .bun-cache
900
+ *.min.js
901
+ *.min.css
902
+ `;
903
+ writeFileSync(ignorePath, ignore);
904
+ console.log("\u2705 Created .prettierignore");
905
+ }
906
+ function createEditorConfig(root) {
907
+ const configPath = resolve(root, ".editorconfig");
908
+ const config = `root = true
1047
909
 
1048
- createApp(App).mount('#app')
1049
- `;
1050
- writeFileSync(resolve(srcDir, "main.ts"), mainTs);
1051
- const appVue = `<script setup lang="ts">
1052
- import { ref } from 'vue'
1053
- ${isWorkspace ? "import { formatDate } from 'shared/utils'\n" : ""}
1054
- const count = ref(0)
1055
- <\/script>
910
+ [*]
911
+ charset = utf-8
912
+ indent_style = tab
913
+ indent_size = 2
914
+ end_of_line = lf
915
+ insert_final_newline = true
916
+ trim_trailing_whitespace = true
1056
917
 
1057
- <template>
1058
- <div>
1059
- <h1>${name}</h1>
1060
- <button @click="count++">Count: {{ count }}</button>
1061
- ${isWorkspace ? "<p>{{ formatDate(new Date()) }}</p>" : ""}
1062
- </div>
1063
- </template>
918
+ [*.md]
919
+ trim_trailing_whitespace = false
920
+
921
+ [*.{json,yml,yaml}]
922
+ indent_style = space
923
+ indent_size = 2
1064
924
  `;
1065
- writeFileSync(resolve(srcDir, "App.vue"), appVue);
1066
- console.log(`\u2705 Created project: ${name}`);
1067
- if (isWorkspace) {
1068
- updateWorkspaceScripts(root, name);
925
+ writeFileSync(configPath, config);
926
+ console.log("\u2705 Created .editorconfig");
927
+ }
928
+ function createGitHooks(root) {
929
+ const packageJsonPath = resolve(root, "package.json");
930
+ if (!existsSync(packageJsonPath)) {
931
+ console.warn("\u26A0\uFE0F No package.json found, skipping git hooks");
932
+ return;
1069
933
  }
1070
- console.log("\nNext steps:");
1071
- console.log(` cd ${name}`);
1072
- console.log(" bun install");
1073
- console.log(" bun run dev");
1074
- console.log("");
934
+ const lintStagedConfig = {
935
+ "*.{js,jsx,ts,tsx,vue}": ["eslint --fix"],
936
+ "*.{json,md,yml,yaml}": ["prettier --write"]
937
+ };
938
+ writeFileSync(
939
+ resolve(root, ".lintstagedrc"),
940
+ `${JSON.stringify(lintStagedConfig, null, 2)}
941
+ `
942
+ );
943
+ console.log("\u2705 Created .lintstagedrc");
944
+ console.log("\u2139\uFE0F Add simple-git-hooks and lint-staged to devDependencies");
945
+ console.log(" Then run: npx simple-git-hooks");
1075
946
  }
1076
- function updateWorkspaceScripts(root, projectName) {
947
+ async function cleanRedundantFiles(root) {
948
+ const foundFiles = [];
949
+ for (const file of REDUNDANT_FILES) {
950
+ const filePath = resolve(root, file);
951
+ if (existsSync(filePath)) {
952
+ foundFiles.push(file);
953
+ }
954
+ }
955
+ if (foundFiles.length === 0) {
956
+ console.log("\u2728 No redundant files found");
957
+ return;
958
+ }
959
+ console.log("\n\u{1F4CB} Found redundant files:");
960
+ foundFiles.forEach((file) => {
961
+ console.log(` - ${file}`);
962
+ });
963
+ const confirmResponse = await prompts({
964
+ type: "confirm",
965
+ name: "confirm",
966
+ message: `Delete ${foundFiles.length} redundant file${foundFiles.length > 1 ? "s" : ""}?`,
967
+ initial: true
968
+ });
969
+ if (!confirmResponse.confirm) {
970
+ console.log("\u23ED\uFE0F Skipped cleaning redundant files");
971
+ return;
972
+ }
973
+ let deleted = 0;
974
+ for (const file of foundFiles) {
975
+ try {
976
+ unlinkSync(resolve(root, file));
977
+ console.log(`\u{1F5D1}\uFE0F Deleted ${file}`);
978
+ deleted++;
979
+ } catch (error) {
980
+ console.error(`\u274C Failed to delete ${file}:`, error);
981
+ }
982
+ }
983
+ console.log(`\u2705 Cleaned up ${deleted} file${deleted > 1 ? "s" : ""}`);
984
+ }
985
+ function updatePackageJsonLint(root, configs) {
1077
986
  const packageJsonPath = resolve(root, "package.json");
1078
- if (!existsSync(packageJsonPath)) return;
987
+ if (!existsSync(packageJsonPath)) {
988
+ console.warn("\u26A0\uFE0F No package.json found");
989
+ return;
990
+ }
1079
991
  try {
1080
992
  const packageJson = JSON.parse(
1081
993
  readFileSync(packageJsonPath, "utf-8")
@@ -1083,27 +995,156 @@ function updateWorkspaceScripts(root, projectName) {
1083
995
  if (!packageJson.scripts) {
1084
996
  packageJson.scripts = {};
1085
997
  }
1086
- packageJson.scripts[`dev:${projectName}`] = `bun --filter ${projectName} dev`;
1087
- packageJson.scripts[`build:${projectName}`] = `bun --filter ${projectName} build`;
998
+ if (configs.includes("eslint")) {
999
+ if (!packageJson.scripts.lint) {
1000
+ packageJson.scripts.lint = "eslint .";
1001
+ }
1002
+ if (!packageJson.scripts["lint:fix"]) {
1003
+ packageJson.scripts["lint:fix"] = "eslint . --fix";
1004
+ }
1005
+ }
1006
+ if (configs.includes("prettier")) {
1007
+ if (!packageJson.scripts.format) {
1008
+ packageJson.scripts.format = "prettier --write .";
1009
+ }
1010
+ if (!packageJson.scripts["format:check"]) {
1011
+ packageJson.scripts["format:check"] = "prettier --check .";
1012
+ }
1013
+ }
1088
1014
  writeFileSync(
1089
1015
  packageJsonPath,
1090
1016
  `${JSON.stringify(packageJson, null, 2)}
1091
1017
  `
1092
1018
  );
1093
- console.log(`\u2705 Added scripts: dev:${projectName}, build:${projectName}`);
1019
+ console.log("\u2705 Updated package.json with lint scripts");
1094
1020
  } catch (error) {
1095
- console.warn("\u26A0\uFE0F Could not update workspace scripts");
1021
+ console.error("\u274C Failed to update package.json:", error);
1096
1022
  }
1097
1023
  }
1098
- function listProjects(root = process.cwd()) {
1024
+
1025
+ async function generateSDK(root = process.cwd()) {
1026
+ console.log("\n\u{1F527} Generating SDK from OpenAPI...\n");
1027
+ let config = null;
1028
+ let openApiUrl;
1099
1029
  try {
1100
- const items = readdirSync(root, { withFileTypes: true });
1101
- return items.filter(
1102
- (item) => item.isDirectory() && item.name !== "node_modules" && item.name !== "shared" && item.name !== ".git" && !item.name.startsWith(".")
1103
- ).map((item) => item.name);
1030
+ const configPath = resolve(root, "bgl.config.ts");
1031
+ if (existsSync(configPath)) {
1032
+ const module = await import(`file://${configPath}`);
1033
+ const workspace = module.default;
1034
+ if (typeof workspace === "function") {
1035
+ config = workspace("development");
1036
+ if (config?.openapi_url) {
1037
+ openApiUrl = config.openapi_url;
1038
+ }
1039
+ }
1040
+ }
1104
1041
  } catch {
1105
- return [];
1106
1042
  }
1043
+ const response = await prompts([
1044
+ {
1045
+ type: openApiUrl !== void 0 ? null : "text",
1046
+ name: "openApiUrl",
1047
+ message: "OpenAPI spec URL:",
1048
+ initial: openApiUrl ?? "http://localhost:8000/openapi.json"
1049
+ },
1050
+ {
1051
+ type: "text",
1052
+ name: "outputDir",
1053
+ message: "Output directory:",
1054
+ initial: "./src/api"
1055
+ },
1056
+ {
1057
+ type: "confirm",
1058
+ name: "splitFiles",
1059
+ message: "Split into organized files?",
1060
+ initial: true
1061
+ }
1062
+ ]);
1063
+ if (!response) {
1064
+ console.log("\n\u274C SDK generation cancelled.\n");
1065
+ process.exit(1);
1066
+ }
1067
+ const finalUrl = openApiUrl ?? response.openApiUrl;
1068
+ const { outputDir, splitFiles } = response;
1069
+ console.log(`
1070
+ \u{1F4E1} Fetching OpenAPI spec from: ${finalUrl}`);
1071
+ console.log(`\u{1F4C1} Output directory: ${outputDir}
1072
+ `);
1073
+ try {
1074
+ const { openAPI } = await import('@bagelink/sdk');
1075
+ const { types, code } = await openAPI(finalUrl, "/api");
1076
+ const outputPath = resolve(root, outputDir);
1077
+ if (!existsSync(outputPath)) {
1078
+ mkdirSync(outputPath, { recursive: true });
1079
+ }
1080
+ const typesPath = resolve(outputPath, "types.d.ts");
1081
+ writeFileSync(typesPath, types);
1082
+ console.log("\u2705 Generated types.d.ts");
1083
+ const apiPath = resolve(outputPath, "api.ts");
1084
+ writeFileSync(apiPath, code);
1085
+ console.log("\u2705 Generated api.ts");
1086
+ const indexPath = resolve(outputPath, "index.ts");
1087
+ writeFileSync(
1088
+ indexPath,
1089
+ "export * from './api'\nexport * from './types.d'\n"
1090
+ );
1091
+ console.log("\u2705 Generated index.ts");
1092
+ if (splitFiles) {
1093
+ console.log("\n\u{1F500} Splitting into organized files...");
1094
+ console.log("\u2139\uFE0F File splitting requires @bagelink/sdk bin scripts");
1095
+ console.log(" Keeping monolithic structure for now");
1096
+ }
1097
+ console.log("\n\u2705 SDK generated successfully!");
1098
+ console.log(`
1099
+ Import it in your code:`);
1100
+ console.log(` import { api } from '${outputDir.replace("./src/", "./")}'`);
1101
+ console.log("");
1102
+ } catch (error) {
1103
+ console.error("\n\u274C Failed to generate SDK:");
1104
+ if (error instanceof Error) {
1105
+ console.error(error.message);
1106
+ } else {
1107
+ console.error(error);
1108
+ }
1109
+ console.log("\nMake sure:");
1110
+ console.log(" 1. @bagelink/sdk is installed: bun add -D @bagelink/sdk");
1111
+ console.log(" 2. OpenAPI URL is accessible");
1112
+ console.log(" 3. API server is running (if using localhost)");
1113
+ process.exit(1);
1114
+ }
1115
+ }
1116
+ async function generateSDKForWorkspace(root = process.cwd()) {
1117
+ console.log("\n\u{1F3E2} Generating SDK for workspace projects...\n");
1118
+ const fs = await import('node:fs');
1119
+ const items = fs.readdirSync(root, { withFileTypes: true });
1120
+ const projects = items.filter(
1121
+ (item) => item.isDirectory() && item.name !== "node_modules" && item.name !== "shared" && item.name !== ".git" && !item.name.startsWith(".")
1122
+ ).map((item) => item.name);
1123
+ if (projects.length === 0) {
1124
+ console.log("No projects found in workspace");
1125
+ return;
1126
+ }
1127
+ const response = await prompts({
1128
+ type: "multiselect",
1129
+ name: "selectedProjects",
1130
+ message: "Select projects to generate SDK for:",
1131
+ choices: projects.map((p) => ({ title: p, value: p, selected: true }))
1132
+ });
1133
+ if (!response || !response.selectedProjects || response.selectedProjects.length === 0) {
1134
+ console.log("\n\u274C No projects selected.\n");
1135
+ return;
1136
+ }
1137
+ for (const project of response.selectedProjects) {
1138
+ console.log(`
1139
+ \u{1F4E6} Generating SDK for: ${project}`);
1140
+ const projectPath = resolve(root, project);
1141
+ try {
1142
+ await generateSDK(projectPath);
1143
+ } catch {
1144
+ console.error(`Failed to generate SDK for ${project}`);
1145
+ }
1146
+ }
1147
+ console.log("\n\u2705 All SDKs generated!");
1107
1148
  }
1108
1149
 
1109
1150
  const [, , command, subcommand, ...args] = process.argv;
@@ -1181,10 +1222,21 @@ SDK Commands:
1181
1222
  process.exit(1);
1182
1223
  }
1183
1224
  } else if (command === "dev") {
1184
- const filter = subcommand || "./!shared*";
1185
- const additionalArgs = args;
1225
+ const { filter, additionalArgs } = parseFilterArgs(
1226
+ void 0,
1227
+ subcommand,
1228
+ args
1229
+ );
1186
1230
  const exitCode = await runDev(filter, additionalArgs);
1187
1231
  process.exit(exitCode);
1232
+ } else if (command === "build") {
1233
+ const { filter, additionalArgs } = parseFilterArgs(
1234
+ void 0,
1235
+ subcommand,
1236
+ args
1237
+ );
1238
+ const exitCode = await runBuild(filter, additionalArgs);
1239
+ process.exit(exitCode);
1188
1240
  } else {
1189
1241
  console.log(`
1190
1242
  Bagel Workspace CLI
@@ -1197,6 +1249,7 @@ Usage:
1197
1249
  bgl list List all projects in workspace
1198
1250
  bgl dev [filter] [...args] Run dev servers with clean output (default: './!shared*')
1199
1251
  Additional args are passed to vite (e.g., --mode localhost)
1252
+ bgl build [project] [...args] Build project by directory (default: all projects)
1200
1253
  bgl lint init Set up linting (auto-detects workspace)
1201
1254
  bgl sdk generate Generate SDK (auto-detects workspace)
1202
1255
 
@@ -1210,6 +1263,18 @@ Note: Commands auto-detect workspace mode based on directory structure
1210
1263
  process.exit(command === "--help" || command === "-h" ? 0 : 1);
1211
1264
  }
1212
1265
  }
1266
+ function normalizeFilter(input) {
1267
+ if (input.startsWith(".")) return input;
1268
+ return `./${input}`;
1269
+ }
1270
+ function parseFilterArgs(defaultFilter, subcommandArg, argsList = []) {
1271
+ const tokens = [subcommandArg, ...argsList].filter(Boolean);
1272
+ const nonFlagIndexes = tokens.map((token, index) => token.startsWith("-") ? -1 : index).filter((index) => index >= 0);
1273
+ const filterIndex = nonFlagIndexes.length > 0 ? nonFlagIndexes[nonFlagIndexes.length - 1] : -1;
1274
+ const filter = filterIndex >= 0 ? normalizeFilter(tokens[filterIndex]) : defaultFilter;
1275
+ const additionalArgs = filterIndex >= 0 ? tokens.filter((_, index) => index !== filterIndex) : tokens;
1276
+ return { filter, additionalArgs };
1277
+ }
1213
1278
  main().catch((error) => {
1214
1279
  console.error("Error:", error);
1215
1280
  process.exit(1);