@bagelink/workspace 1.7.57 → 1.7.59

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