@bagelink/workspace 1.7.3 ā 1.7.8
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/README.md +236 -8
- package/bin/bgl.ts +102 -4
- package/dist/bin/bgl.cjs +80 -5
- package/dist/bin/bgl.mjs +80 -5
- package/dist/index.cjs +18 -42
- package/dist/index.d.cts +41 -1
- package/dist/index.d.mts +41 -1
- package/dist/index.d.ts +41 -1
- package/dist/index.mjs +4 -36
- package/dist/shared/workspace.BMTTo3s8.cjs +993 -0
- package/dist/shared/workspace.COhZ__uF.mjs +973 -0
- package/package.json +15 -4
- package/src/detect.ts +90 -0
- package/src/index.ts +5 -0
- package/src/init.ts +36 -9
- package/src/lint.ts +235 -0
- package/src/sdk.ts +175 -0
- package/src/workspace.ts +505 -0
- package/templates/dev-runner.ts +61 -0
- package/dist/shared/workspace.BaaKkm9b.mjs +0 -182
- package/dist/shared/workspace.CkP5t0--.cjs +0 -190
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bagelink/workspace",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.7.
|
|
4
|
+
"version": "1.7.8",
|
|
5
5
|
"description": "Monorepo workspace tooling for Bagel projects with proxy and config management",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Bagel Studio",
|
|
@@ -52,16 +52,27 @@
|
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"prompts": "^2.4.2"
|
|
54
54
|
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"@bagelink/lint-config": ">=1.0.0",
|
|
57
|
+
"@bagelink/sdk": ">=1.0.0",
|
|
58
|
+
"vite": ">=5.0.0"
|
|
59
|
+
},
|
|
60
|
+
"peerDependenciesMeta": {
|
|
61
|
+
"@bagelink/lint-config": {
|
|
62
|
+
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"@bagelink/sdk": {
|
|
65
|
+
"optional": true
|
|
66
|
+
}
|
|
67
|
+
},
|
|
55
68
|
"devDependencies": {
|
|
69
|
+
"@types/bun": "^1.1.16",
|
|
56
70
|
"@types/node": "^24.0.0",
|
|
57
71
|
"@types/prompts": "^2.4.9",
|
|
58
72
|
"rimraf": "^6.0.1",
|
|
59
73
|
"typescript": "^5.8.3",
|
|
60
74
|
"unbuild": "^3.5.0"
|
|
61
75
|
},
|
|
62
|
-
"peerDependencies": {
|
|
63
|
-
"vite": ">=5.0.0"
|
|
64
|
-
},
|
|
65
76
|
"scripts": {
|
|
66
77
|
"dev": "unbuild --stub",
|
|
67
78
|
"build": "unbuild",
|
package/src/detect.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import process from 'node:process'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Detect if current directory is a workspace root
|
|
7
|
+
*/
|
|
8
|
+
export function isWorkspace(root: string = process.cwd()): boolean {
|
|
9
|
+
// Check if package.json has workspaces field
|
|
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 !== undefined) {
|
|
15
|
+
return true
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Ignore parse errors
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Check if there are multiple project directories
|
|
24
|
+
// (directories with their own package.json, excluding shared, node_modules, etc.)
|
|
25
|
+
try {
|
|
26
|
+
const items = readdirSync(root, { withFileTypes: true })
|
|
27
|
+
const projectDirs = items.filter(
|
|
28
|
+
item => item.isDirectory()
|
|
29
|
+
&& item.name !== 'node_modules'
|
|
30
|
+
&& item.name !== 'shared'
|
|
31
|
+
&& item.name !== '.git'
|
|
32
|
+
&& !item.name.startsWith('.')
|
|
33
|
+
&& existsSync(resolve(root, item.name, 'package.json')),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
// If we have 2+ project directories, it's likely a workspace
|
|
37
|
+
return projectDirs.length >= 2
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get workspace info
|
|
46
|
+
*/
|
|
47
|
+
export function getWorkspaceInfo(root: string = process.cwd()): {
|
|
48
|
+
isWorkspace: boolean
|
|
49
|
+
projects: string[]
|
|
50
|
+
hasShared: boolean
|
|
51
|
+
} {
|
|
52
|
+
const workspace = isWorkspace(root)
|
|
53
|
+
|
|
54
|
+
if (!workspace) {
|
|
55
|
+
return {
|
|
56
|
+
isWorkspace: false,
|
|
57
|
+
projects: [],
|
|
58
|
+
hasShared: false,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const items = readdirSync(root, { withFileTypes: true })
|
|
64
|
+
const projects = items
|
|
65
|
+
.filter(
|
|
66
|
+
item => item.isDirectory()
|
|
67
|
+
&& item.name !== 'node_modules'
|
|
68
|
+
&& item.name !== 'shared'
|
|
69
|
+
&& item.name !== '.git'
|
|
70
|
+
&& !item.name.startsWith('.')
|
|
71
|
+
&& existsSync(resolve(root, item.name, 'package.json')),
|
|
72
|
+
)
|
|
73
|
+
.map(item => item.name)
|
|
74
|
+
|
|
75
|
+
const hasShared = existsSync(resolve(root, 'shared'))
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
isWorkspace: true,
|
|
79
|
+
projects,
|
|
80
|
+
hasShared,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return {
|
|
85
|
+
isWorkspace: false,
|
|
86
|
+
projects: [],
|
|
87
|
+
hasShared: false,
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -34,6 +34,11 @@ export {
|
|
|
34
34
|
writeNetlifyConfig,
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
export { setupLint } from './lint'
|
|
38
|
+
export { generateSDK, generateSDKForWorkspace } from './sdk'
|
|
39
|
+
export { addProject, initWorkspace, listProjects } from './workspace'
|
|
40
|
+
export { getWorkspaceInfo, isWorkspace } from './detect'
|
|
41
|
+
|
|
37
42
|
/**
|
|
38
43
|
* Define workspace configuration
|
|
39
44
|
* Simple helper to get config from a config map
|
package/src/init.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { WorkspaceConfig } from './types'
|
|
1
2
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
3
|
import { resolve } from 'node:path'
|
|
3
4
|
import process from 'node:process'
|
|
4
5
|
import prompts from 'prompts'
|
|
6
|
+
import { writeNetlifyConfig } from './netlify'
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Generate bgl.config.ts file interactively
|
|
@@ -75,12 +77,12 @@ export default defineWorkspace(configs)
|
|
|
75
77
|
console.log(` Production host: ${productionHost}`)
|
|
76
78
|
console.log(` Local dev host: http://localhost:8000\n`)
|
|
77
79
|
|
|
78
|
-
// Ask if they want to update package.json and
|
|
80
|
+
// Ask if they want to update package.json, vite.config, and netlify.toml
|
|
79
81
|
const setupResponse = await prompts([
|
|
80
82
|
{
|
|
81
83
|
type: 'confirm',
|
|
82
84
|
name: 'updatePackageJson',
|
|
83
|
-
message: 'Add dev scripts
|
|
85
|
+
message: 'Add/update dev scripts in package.json?',
|
|
84
86
|
initial: true,
|
|
85
87
|
},
|
|
86
88
|
{
|
|
@@ -89,6 +91,12 @@ export default defineWorkspace(configs)
|
|
|
89
91
|
message: 'Create/update vite.config.ts?',
|
|
90
92
|
initial: true,
|
|
91
93
|
},
|
|
94
|
+
{
|
|
95
|
+
type: 'confirm',
|
|
96
|
+
name: 'generateNetlify',
|
|
97
|
+
message: 'Generate netlify.toml for deployment?',
|
|
98
|
+
initial: true,
|
|
99
|
+
},
|
|
92
100
|
])
|
|
93
101
|
|
|
94
102
|
if (setupResponse.updatePackageJson) {
|
|
@@ -99,6 +107,14 @@ export default defineWorkspace(configs)
|
|
|
99
107
|
updateViteConfig(root)
|
|
100
108
|
}
|
|
101
109
|
|
|
110
|
+
if (setupResponse.generateNetlify) {
|
|
111
|
+
const prodConfig: WorkspaceConfig = {
|
|
112
|
+
host: productionHost,
|
|
113
|
+
proxy: '/api',
|
|
114
|
+
}
|
|
115
|
+
writeNetlifyConfig(prodConfig, resolve(root, 'netlify.toml'))
|
|
116
|
+
}
|
|
117
|
+
|
|
102
118
|
console.log('\nš” You can edit these files to customize your configuration.\n')
|
|
103
119
|
}
|
|
104
120
|
|
|
@@ -160,7 +176,7 @@ function updatePackageJsonScripts(root: string): void {
|
|
|
160
176
|
packageJson.scripts = {}
|
|
161
177
|
}
|
|
162
178
|
|
|
163
|
-
// Add scripts
|
|
179
|
+
// Add/overwrite dev scripts
|
|
164
180
|
const scriptsToAdd = {
|
|
165
181
|
'dev': 'vite',
|
|
166
182
|
'dev:local': 'vite --mode localhost',
|
|
@@ -168,20 +184,31 @@ function updatePackageJsonScripts(root: string): void {
|
|
|
168
184
|
'preview': 'vite preview',
|
|
169
185
|
}
|
|
170
186
|
|
|
171
|
-
|
|
187
|
+
// Always overwrite dev and dev:local, preserve others if they exist
|
|
188
|
+
let modified = false
|
|
172
189
|
for (const [key, value] of Object.entries(scriptsToAdd)) {
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
|
|
190
|
+
if (key === 'dev' || key === 'dev:local') {
|
|
191
|
+
// Always overwrite these
|
|
192
|
+
if (packageJson.scripts[key] !== value) {
|
|
193
|
+
packageJson.scripts[key] = value
|
|
194
|
+
modified = true
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
// Only add if doesn't exist
|
|
199
|
+
if (!packageJson.scripts[key]) {
|
|
200
|
+
packageJson.scripts[key] = value
|
|
201
|
+
modified = true
|
|
202
|
+
}
|
|
176
203
|
}
|
|
177
204
|
}
|
|
178
205
|
|
|
179
|
-
if (
|
|
206
|
+
if (modified) {
|
|
180
207
|
writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, 'utf-8')
|
|
181
208
|
console.log('ā
Updated package.json with dev scripts')
|
|
182
209
|
}
|
|
183
210
|
else {
|
|
184
|
-
console.log('ā¹ļø Scripts already
|
|
211
|
+
console.log('ā¹ļø Scripts already up to date in package.json')
|
|
185
212
|
}
|
|
186
213
|
}
|
|
187
214
|
catch (error) {
|
package/src/lint.ts
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import process from 'node:process'
|
|
4
|
+
import prompts from 'prompts'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Set up linting in a project
|
|
8
|
+
*/
|
|
9
|
+
export async function setupLint(
|
|
10
|
+
root: string = process.cwd(),
|
|
11
|
+
isWorkspace: boolean = false,
|
|
12
|
+
): Promise<void> {
|
|
13
|
+
console.log('\nš Setting up linting...\n')
|
|
14
|
+
|
|
15
|
+
const response = await prompts([
|
|
16
|
+
{
|
|
17
|
+
type: 'multiselect',
|
|
18
|
+
name: 'configs',
|
|
19
|
+
message: 'Select configurations to set up:',
|
|
20
|
+
choices: [
|
|
21
|
+
{ title: 'ESLint', value: 'eslint', selected: true },
|
|
22
|
+
{ title: 'Prettier', value: 'prettier', selected: true },
|
|
23
|
+
{ title: 'EditorConfig', value: 'editorconfig', selected: true },
|
|
24
|
+
{ title: 'Git Hooks', value: 'githooks', selected: false },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
type: 'confirm',
|
|
29
|
+
name: 'installDeps',
|
|
30
|
+
message: 'Install dependencies?',
|
|
31
|
+
initial: true,
|
|
32
|
+
},
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
if (!response || !response.configs) {
|
|
36
|
+
console.log('\nā Setup cancelled.\n')
|
|
37
|
+
process.exit(1)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const { configs, installDeps } = response
|
|
41
|
+
|
|
42
|
+
// Create config files
|
|
43
|
+
if (configs.includes('eslint')) {
|
|
44
|
+
createEslintConfig(root, isWorkspace)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (configs.includes('prettier')) {
|
|
48
|
+
createPrettierConfig(root)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (configs.includes('editorconfig')) {
|
|
52
|
+
createEditorConfig(root)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (configs.includes('githooks')) {
|
|
56
|
+
createGitHooks(root)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Update package.json
|
|
60
|
+
updatePackageJsonLint(root, configs)
|
|
61
|
+
|
|
62
|
+
if (installDeps) {
|
|
63
|
+
console.log('\nš¦ Installing dependencies...')
|
|
64
|
+
console.log('Run: bun add -D @bagelink/lint-config eslint prettier typescript')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log('\nā
Linting setup complete!')
|
|
68
|
+
console.log('\nAvailable commands:')
|
|
69
|
+
console.log(' bun run lint - Run linter')
|
|
70
|
+
console.log(' bun run lint:fix - Fix linting issues')
|
|
71
|
+
console.log(' bun run format - Format code with Prettier')
|
|
72
|
+
console.log('')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Create ESLint config
|
|
77
|
+
*/
|
|
78
|
+
function createEslintConfig(root: string, isWorkspace: boolean): void {
|
|
79
|
+
const configPath = resolve(root, 'eslint.config.js')
|
|
80
|
+
|
|
81
|
+
const config = isWorkspace
|
|
82
|
+
? `import { defineConfig } from '@bagelink/lint-config/eslint'
|
|
83
|
+
|
|
84
|
+
export default defineConfig({
|
|
85
|
+
// Workspace-level ESLint config
|
|
86
|
+
ignores: ['**/dist/**', '**/node_modules/**', '**/.bun-cache/**'],
|
|
87
|
+
})
|
|
88
|
+
`
|
|
89
|
+
: `import vue3Config from '@bagelink/lint-config/eslint/vue3'
|
|
90
|
+
|
|
91
|
+
export default vue3Config
|
|
92
|
+
`
|
|
93
|
+
|
|
94
|
+
writeFileSync(configPath, config)
|
|
95
|
+
console.log('ā
Created eslint.config.js')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Create Prettier config
|
|
100
|
+
*/
|
|
101
|
+
function createPrettierConfig(root: string): void {
|
|
102
|
+
const configPath = resolve(root, '.prettierrc')
|
|
103
|
+
|
|
104
|
+
const config = {
|
|
105
|
+
semi: false,
|
|
106
|
+
singleQuote: true,
|
|
107
|
+
tabWidth: 2,
|
|
108
|
+
useTabs: true,
|
|
109
|
+
trailingComma: 'all',
|
|
110
|
+
printWidth: 100,
|
|
111
|
+
arrowParens: 'avoid',
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`)
|
|
115
|
+
console.log('ā
Created .prettierrc')
|
|
116
|
+
|
|
117
|
+
// .prettierignore
|
|
118
|
+
const ignorePath = resolve(root, '.prettierignore')
|
|
119
|
+
const ignore = `dist
|
|
120
|
+
node_modules
|
|
121
|
+
.bun-cache
|
|
122
|
+
*.min.js
|
|
123
|
+
*.min.css
|
|
124
|
+
`
|
|
125
|
+
|
|
126
|
+
writeFileSync(ignorePath, ignore)
|
|
127
|
+
console.log('ā
Created .prettierignore')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Create EditorConfig
|
|
132
|
+
*/
|
|
133
|
+
function createEditorConfig(root: string): void {
|
|
134
|
+
const configPath = resolve(root, '.editorconfig')
|
|
135
|
+
|
|
136
|
+
const config = `root = true
|
|
137
|
+
|
|
138
|
+
[*]
|
|
139
|
+
charset = utf-8
|
|
140
|
+
indent_style = tab
|
|
141
|
+
indent_size = 2
|
|
142
|
+
end_of_line = lf
|
|
143
|
+
insert_final_newline = true
|
|
144
|
+
trim_trailing_whitespace = true
|
|
145
|
+
|
|
146
|
+
[*.md]
|
|
147
|
+
trim_trailing_whitespace = false
|
|
148
|
+
|
|
149
|
+
[*.{json,yml,yaml}]
|
|
150
|
+
indent_style = space
|
|
151
|
+
indent_size = 2
|
|
152
|
+
`
|
|
153
|
+
|
|
154
|
+
writeFileSync(configPath, config)
|
|
155
|
+
console.log('ā
Created .editorconfig')
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Create Git Hooks
|
|
160
|
+
*/
|
|
161
|
+
function createGitHooks(root: string): void {
|
|
162
|
+
const packageJsonPath = resolve(root, 'package.json')
|
|
163
|
+
|
|
164
|
+
if (!existsSync(packageJsonPath)) {
|
|
165
|
+
console.warn('ā ļø No package.json found, skipping git hooks')
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// .lintstagedrc
|
|
170
|
+
const lintStagedConfig = {
|
|
171
|
+
'*.{js,jsx,ts,tsx,vue}': ['eslint --fix'],
|
|
172
|
+
'*.{json,md,yml,yaml}': ['prettier --write'],
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
writeFileSync(
|
|
176
|
+
resolve(root, '.lintstagedrc'),
|
|
177
|
+
`${JSON.stringify(lintStagedConfig, null, 2)}\n`,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
console.log('ā
Created .lintstagedrc')
|
|
181
|
+
console.log('ā¹ļø Add simple-git-hooks and lint-staged to devDependencies')
|
|
182
|
+
console.log(' Then run: npx simple-git-hooks')
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Update package.json with lint scripts
|
|
187
|
+
*/
|
|
188
|
+
function updatePackageJsonLint(root: string, configs: string[]): void {
|
|
189
|
+
const packageJsonPath = resolve(root, 'package.json')
|
|
190
|
+
|
|
191
|
+
if (!existsSync(packageJsonPath)) {
|
|
192
|
+
console.warn('ā ļø No package.json found')
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const packageJson = JSON.parse(
|
|
198
|
+
readFileSync(packageJsonPath, 'utf-8'),
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if (!packageJson.scripts) {
|
|
202
|
+
packageJson.scripts = {}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Add lint scripts
|
|
206
|
+
if (configs.includes('eslint')) {
|
|
207
|
+
if (!packageJson.scripts.lint) {
|
|
208
|
+
packageJson.scripts.lint = 'eslint .'
|
|
209
|
+
}
|
|
210
|
+
if (!packageJson.scripts['lint:fix']) {
|
|
211
|
+
packageJson.scripts['lint:fix'] = 'eslint . --fix'
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Add format scripts
|
|
216
|
+
if (configs.includes('prettier')) {
|
|
217
|
+
if (!packageJson.scripts.format) {
|
|
218
|
+
packageJson.scripts.format = 'prettier --write .'
|
|
219
|
+
}
|
|
220
|
+
if (!packageJson.scripts['format:check']) {
|
|
221
|
+
packageJson.scripts['format:check'] = 'prettier --check .'
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
writeFileSync(
|
|
226
|
+
packageJsonPath,
|
|
227
|
+
`${JSON.stringify(packageJson, null, 2)}\n`,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
console.log('ā
Updated package.json with lint scripts')
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
console.error('ā Failed to update package.json:', error)
|
|
234
|
+
}
|
|
235
|
+
}
|
package/src/sdk.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import type { WorkspaceConfig } from './types'
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { resolve } from 'node:path'
|
|
4
|
+
import process from 'node:process'
|
|
5
|
+
import prompts from 'prompts'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generate SDK from OpenAPI spec
|
|
9
|
+
*/
|
|
10
|
+
export async function generateSDK(
|
|
11
|
+
root: string = process.cwd(),
|
|
12
|
+
): Promise<void> {
|
|
13
|
+
console.log('\nš§ Generating SDK from OpenAPI...\n')
|
|
14
|
+
|
|
15
|
+
// Try to load config
|
|
16
|
+
let config: WorkspaceConfig | null = null
|
|
17
|
+
let openApiUrl: string | undefined
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const configPath = resolve(root, 'bgl.config.ts')
|
|
21
|
+
if (existsSync(configPath)) {
|
|
22
|
+
const module = await import(`file://${configPath}`)
|
|
23
|
+
const workspace = module.default
|
|
24
|
+
if (typeof workspace === 'function') {
|
|
25
|
+
config = workspace('development')
|
|
26
|
+
if (config?.openapi_url) {
|
|
27
|
+
openApiUrl = config.openapi_url
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Ignore config load errors
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Prompt for missing info
|
|
37
|
+
const response = await prompts([
|
|
38
|
+
{
|
|
39
|
+
type: openApiUrl !== undefined ? null : 'text',
|
|
40
|
+
name: 'openApiUrl',
|
|
41
|
+
message: 'OpenAPI spec URL:',
|
|
42
|
+
initial: openApiUrl ?? 'http://localhost:8000/openapi.json',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: 'text',
|
|
46
|
+
name: 'outputDir',
|
|
47
|
+
message: 'Output directory:',
|
|
48
|
+
initial: './src/api',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
type: 'confirm',
|
|
52
|
+
name: 'splitFiles',
|
|
53
|
+
message: 'Split into organized files?',
|
|
54
|
+
initial: true,
|
|
55
|
+
},
|
|
56
|
+
])
|
|
57
|
+
|
|
58
|
+
if (!response) {
|
|
59
|
+
console.log('\nā SDK generation cancelled.\n')
|
|
60
|
+
process.exit(1)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const finalUrl = openApiUrl ?? response.openApiUrl
|
|
64
|
+
const { outputDir, splitFiles } = response
|
|
65
|
+
|
|
66
|
+
console.log(`\nš” Fetching OpenAPI spec from: ${finalUrl}`)
|
|
67
|
+
console.log(`š Output directory: ${outputDir}\n`)
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
// Dynamic import of @bagelink/sdk
|
|
71
|
+
const { openAPI } = await import('@bagelink/sdk')
|
|
72
|
+
|
|
73
|
+
const { types, code } = await openAPI(finalUrl, '/api')
|
|
74
|
+
|
|
75
|
+
const outputPath = resolve(root, outputDir)
|
|
76
|
+
if (!existsSync(outputPath)) {
|
|
77
|
+
mkdirSync(outputPath, { recursive: true })
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Write types
|
|
81
|
+
const typesPath = resolve(outputPath, 'types.d.ts')
|
|
82
|
+
writeFileSync(typesPath, types)
|
|
83
|
+
console.log('ā
Generated types.d.ts')
|
|
84
|
+
|
|
85
|
+
// Write API client
|
|
86
|
+
const apiPath = resolve(outputPath, 'api.ts')
|
|
87
|
+
writeFileSync(apiPath, code)
|
|
88
|
+
console.log('ā
Generated api.ts')
|
|
89
|
+
|
|
90
|
+
// Write index
|
|
91
|
+
const indexPath = resolve(outputPath, 'index.ts')
|
|
92
|
+
writeFileSync(
|
|
93
|
+
indexPath,
|
|
94
|
+
'export * from \'./api\'\nexport * from \'./types.d\'\n',
|
|
95
|
+
)
|
|
96
|
+
console.log('ā
Generated index.ts')
|
|
97
|
+
|
|
98
|
+
if (splitFiles) {
|
|
99
|
+
console.log('\nš Splitting into organized files...')
|
|
100
|
+
console.log('ā¹ļø File splitting requires @bagelink/sdk bin scripts')
|
|
101
|
+
console.log(' Keeping monolithic structure for now')
|
|
102
|
+
// Note: File splitting requires unpublished bin scripts
|
|
103
|
+
// Users can manually run: bunx bagelink generate
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log('\nā
SDK generated successfully!')
|
|
107
|
+
console.log(`\nImport it in your code:`)
|
|
108
|
+
console.log(` import { api } from '${outputDir.replace('./src/', './')}'`)
|
|
109
|
+
console.log('')
|
|
110
|
+
}
|
|
111
|
+
catch (error: unknown) {
|
|
112
|
+
console.error('\nā Failed to generate SDK:')
|
|
113
|
+
if (error instanceof Error) {
|
|
114
|
+
console.error(error.message)
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
console.error(error)
|
|
118
|
+
}
|
|
119
|
+
console.log('\nMake sure:')
|
|
120
|
+
console.log(' 1. @bagelink/sdk is installed: bun add -D @bagelink/sdk')
|
|
121
|
+
console.log(' 2. OpenAPI URL is accessible')
|
|
122
|
+
console.log(' 3. API server is running (if using localhost)')
|
|
123
|
+
process.exit(1)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Generate SDK for all projects in workspace
|
|
129
|
+
*/
|
|
130
|
+
export async function generateSDKForWorkspace(root: string = process.cwd()): Promise<void> {
|
|
131
|
+
console.log('\nš¢ Generating SDK for workspace projects...\n')
|
|
132
|
+
|
|
133
|
+
// Find all projects
|
|
134
|
+
const fs = await import('node:fs')
|
|
135
|
+
const items = fs.readdirSync(root, { withFileTypes: true })
|
|
136
|
+
const projects = items
|
|
137
|
+
.filter(
|
|
138
|
+
item => item.isDirectory()
|
|
139
|
+
&& item.name !== 'node_modules'
|
|
140
|
+
&& item.name !== 'shared'
|
|
141
|
+
&& item.name !== '.git'
|
|
142
|
+
&& !item.name.startsWith('.'),
|
|
143
|
+
)
|
|
144
|
+
.map(item => item.name)
|
|
145
|
+
|
|
146
|
+
if (projects.length === 0) {
|
|
147
|
+
console.log('No projects found in workspace')
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const response = await prompts({
|
|
152
|
+
type: 'multiselect',
|
|
153
|
+
name: 'selectedProjects',
|
|
154
|
+
message: 'Select projects to generate SDK for:',
|
|
155
|
+
choices: projects.map(p => ({ title: p, value: p, selected: true })),
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
if (!response || !response.selectedProjects || response.selectedProjects.length === 0) {
|
|
159
|
+
console.log('\nā No projects selected.\n')
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
for (const project of response.selectedProjects) {
|
|
164
|
+
console.log(`\nš¦ Generating SDK for: ${project}`)
|
|
165
|
+
const projectPath = resolve(root, project)
|
|
166
|
+
try {
|
|
167
|
+
await generateSDK(projectPath)
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
console.error(`Failed to generate SDK for ${project}`)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log('\nā
All SDKs generated!')
|
|
175
|
+
}
|