@gxp-dev/tools 2.0.63 → 2.0.64
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 +32 -31
- package/bin/gx-devtools.js +74 -54
- package/bin/lib/cli.js +23 -21
- package/bin/lib/commands/add-dependency.js +366 -325
- package/bin/lib/commands/assets.js +137 -139
- package/bin/lib/commands/build.js +169 -174
- package/bin/lib/commands/datastore.js +181 -183
- package/bin/lib/commands/dev.js +127 -131
- package/bin/lib/commands/extensions.js +147 -149
- package/bin/lib/commands/extract-config.js +73 -67
- package/bin/lib/commands/index.js +12 -12
- package/bin/lib/commands/init.js +342 -240
- package/bin/lib/commands/publish.js +69 -75
- package/bin/lib/commands/socket.js +69 -69
- package/bin/lib/commands/ssl.js +14 -14
- package/bin/lib/constants.js +10 -24
- package/bin/lib/tui/App.tsx +761 -705
- package/bin/lib/tui/components/AIPanel.tsx +191 -171
- package/bin/lib/tui/components/CommandInput.tsx +394 -343
- package/bin/lib/tui/components/GeminiPanel.tsx +175 -151
- package/bin/lib/tui/components/Header.tsx +23 -21
- package/bin/lib/tui/components/LogPanel.tsx +244 -220
- package/bin/lib/tui/components/TabBar.tsx +50 -48
- package/bin/lib/tui/components/WelcomeScreen.tsx +126 -71
- package/bin/lib/tui/index.tsx +37 -39
- package/bin/lib/tui/services/AIService.ts +518 -462
- package/bin/lib/tui/services/ExtensionService.ts +140 -129
- package/bin/lib/tui/services/GeminiService.ts +367 -337
- package/bin/lib/tui/services/ServiceManager.ts +344 -322
- package/bin/lib/tui/services/SocketService.ts +168 -168
- package/bin/lib/tui/services/ViteService.ts +88 -88
- package/bin/lib/tui/services/index.ts +47 -22
- package/bin/lib/utils/ai-scaffold.js +291 -280
- package/bin/lib/utils/extract-config.js +157 -140
- package/bin/lib/utils/files.js +82 -86
- package/bin/lib/utils/index.js +7 -7
- package/bin/lib/utils/paths.js +34 -34
- package/bin/lib/utils/prompts.js +194 -169
- package/bin/lib/utils/ssl.js +79 -81
- package/browser-extensions/README.md +0 -1
- package/browser-extensions/chrome/background.js +244 -237
- package/browser-extensions/chrome/content.js +32 -29
- package/browser-extensions/chrome/devtools.html +7 -7
- package/browser-extensions/chrome/devtools.js +19 -19
- package/browser-extensions/chrome/inspector.js +802 -767
- package/browser-extensions/chrome/manifest.json +71 -63
- package/browser-extensions/chrome/panel.html +674 -636
- package/browser-extensions/chrome/panel.js +722 -712
- package/browser-extensions/chrome/popup.html +586 -543
- package/browser-extensions/chrome/popup.js +282 -244
- package/browser-extensions/chrome/rules.json +1 -1
- package/browser-extensions/chrome/test-chrome.html +216 -136
- package/browser-extensions/chrome/test-mixed-content.html +284 -189
- package/browser-extensions/chrome/test-uri-pattern.html +221 -198
- package/browser-extensions/firefox/README.md +9 -6
- package/browser-extensions/firefox/background.js +221 -218
- package/browser-extensions/firefox/content.js +55 -52
- package/browser-extensions/firefox/debug-errors.html +386 -228
- package/browser-extensions/firefox/debug-https.html +153 -105
- package/browser-extensions/firefox/devtools.html +7 -7
- package/browser-extensions/firefox/devtools.js +23 -20
- package/browser-extensions/firefox/inspector.js +802 -767
- package/browser-extensions/firefox/manifest.json +68 -68
- package/browser-extensions/firefox/panel.html +674 -636
- package/browser-extensions/firefox/panel.js +722 -712
- package/browser-extensions/firefox/popup.html +572 -535
- package/browser-extensions/firefox/popup.js +281 -236
- package/browser-extensions/firefox/test-gramercy.html +170 -125
- package/browser-extensions/firefox/test-imports.html +59 -55
- package/browser-extensions/firefox/test-masking.html +231 -140
- package/browser-extensions/firefox/test-uri-pattern.html +221 -198
- package/dist/tui/App.d.ts +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +154 -150
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/components/AIPanel.d.ts.map +1 -1
- package/dist/tui/components/AIPanel.js +42 -35
- package/dist/tui/components/AIPanel.js.map +1 -1
- package/dist/tui/components/CommandInput.d.ts +1 -1
- package/dist/tui/components/CommandInput.d.ts.map +1 -1
- package/dist/tui/components/CommandInput.js +92 -62
- package/dist/tui/components/CommandInput.js.map +1 -1
- package/dist/tui/components/GeminiPanel.d.ts.map +1 -1
- package/dist/tui/components/GeminiPanel.js +37 -30
- package/dist/tui/components/GeminiPanel.js.map +1 -1
- package/dist/tui/components/Header.d.ts.map +1 -1
- package/dist/tui/components/Header.js +1 -1
- package/dist/tui/components/Header.js.map +1 -1
- package/dist/tui/components/LogPanel.d.ts +1 -1
- package/dist/tui/components/LogPanel.d.ts.map +1 -1
- package/dist/tui/components/LogPanel.js +26 -24
- package/dist/tui/components/LogPanel.js.map +1 -1
- package/dist/tui/components/TabBar.d.ts +2 -2
- package/dist/tui/components/TabBar.d.ts.map +1 -1
- package/dist/tui/components/TabBar.js +11 -11
- package/dist/tui/components/TabBar.js.map +1 -1
- package/dist/tui/components/WelcomeScreen.d.ts.map +1 -1
- package/dist/tui/components/WelcomeScreen.js +6 -6
- package/dist/tui/components/WelcomeScreen.js.map +1 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +8 -8
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/services/AIService.d.ts +2 -2
- package/dist/tui/services/AIService.d.ts.map +1 -1
- package/dist/tui/services/AIService.js +165 -125
- package/dist/tui/services/AIService.js.map +1 -1
- package/dist/tui/services/ExtensionService.d.ts +1 -1
- package/dist/tui/services/ExtensionService.d.ts.map +1 -1
- package/dist/tui/services/ExtensionService.js +33 -26
- package/dist/tui/services/ExtensionService.js.map +1 -1
- package/dist/tui/services/GeminiService.d.ts +1 -1
- package/dist/tui/services/GeminiService.d.ts.map +1 -1
- package/dist/tui/services/GeminiService.js +87 -76
- package/dist/tui/services/GeminiService.js.map +1 -1
- package/dist/tui/services/ServiceManager.d.ts +3 -3
- package/dist/tui/services/ServiceManager.d.ts.map +1 -1
- package/dist/tui/services/ServiceManager.js +72 -58
- package/dist/tui/services/ServiceManager.js.map +1 -1
- package/dist/tui/services/SocketService.d.ts.map +1 -1
- package/dist/tui/services/SocketService.js +32 -32
- package/dist/tui/services/SocketService.js.map +1 -1
- package/dist/tui/services/ViteService.d.ts.map +1 -1
- package/dist/tui/services/ViteService.js +26 -28
- package/dist/tui/services/ViteService.js.map +1 -1
- package/dist/tui/services/index.d.ts +6 -6
- package/dist/tui/services/index.d.ts.map +1 -1
- package/dist/tui/services/index.js +6 -6
- package/dist/tui/services/index.js.map +1 -1
- package/mcp/gxp-api-server.js +83 -81
- package/package.json +109 -93
- package/runtime/PortalContainer.vue +258 -234
- package/runtime/dev-tools/DevToolsModal.vue +153 -155
- package/runtime/dev-tools/LayoutSwitcher.vue +144 -140
- package/runtime/dev-tools/MockDataEditor.vue +456 -433
- package/runtime/dev-tools/SocketSimulator.vue +379 -371
- package/runtime/dev-tools/StoreInspector.vue +517 -455
- package/runtime/dev-tools/index.js +5 -5
- package/runtime/fallback-layouts/PrivateLayout.vue +2 -2
- package/runtime/fallback-layouts/PublicLayout.vue +2 -2
- package/runtime/fallback-layouts/SystemLayout.vue +2 -2
- package/runtime/gxpStringsPlugin.js +159 -134
- package/runtime/index.html +17 -19
- package/runtime/main.js +24 -22
- package/runtime/mock-api/auth-middleware.js +15 -15
- package/runtime/mock-api/image-generator.js +46 -46
- package/runtime/mock-api/index.js +55 -55
- package/runtime/mock-api/response-generator.js +116 -105
- package/runtime/mock-api/route-generator.js +107 -84
- package/runtime/mock-api/socket-triggers.js +94 -93
- package/runtime/mock-api/spec-loader.js +79 -80
- package/runtime/package.json +3 -0
- package/runtime/server.js +68 -68
- package/runtime/stores/gxpPortalConfigStore.js +204 -186
- package/runtime/stores/index.js +2 -2
- package/runtime/vite-inspector-plugin.js +858 -707
- package/runtime/vite-source-tracker-plugin.js +132 -113
- package/runtime/vite.config.js +191 -139
- package/scripts/launch-chrome.js +41 -41
- package/scripts/pack-chrome.js +38 -39
- package/socket-events/AiSessionMessageCreated.json +17 -17
- package/socket-events/SocialStreamPostCreated.json +23 -23
- package/socket-events/SocialStreamPostVariantCompleted.json +22 -22
- package/template/.claude/agents/gxp-developer.md +100 -99
- package/template/.claude/settings.json +7 -7
- package/template/AGENTS.md +30 -23
- package/template/GEMINI.md +20 -20
- package/template/README.md +70 -53
- package/template/app-manifest.json +2 -4
- package/template/configuration.json +10 -10
- package/template/default-styling.css +1 -1
- package/template/index.html +18 -20
- package/template/main.js +24 -22
- package/template/src/DemoPage.vue +415 -362
- package/template/src/Plugin.vue +76 -85
- package/template/src/stores/index.js +3 -3
- package/template/src/stores/test-data.json +164 -172
- package/template/theme-layouts/AdditionalStyling.css +50 -50
- package/template/theme-layouts/PrivateLayout.vue +8 -12
- package/template/theme-layouts/PublicLayout.vue +8 -12
- package/template/theme-layouts/SystemLayout.vue +8 -12
- package/template/vite.extend.js +45 -0
- package/template/vite.config.js +0 -409
|
@@ -5,55 +5,55 @@
|
|
|
5
5
|
* Loads OpenAPI and AsyncAPI specs to help users configure dependencies.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
const fs = require("fs")
|
|
9
|
-
const path = require("path")
|
|
10
|
-
const https = require("https")
|
|
11
|
-
const http = require("http")
|
|
12
|
-
const { ENVIRONMENT_URLS } = require("../constants")
|
|
13
|
-
const { findProjectRoot } = require("../utils")
|
|
8
|
+
const fs = require("fs")
|
|
9
|
+
const path = require("path")
|
|
10
|
+
const https = require("https")
|
|
11
|
+
const http = require("http")
|
|
12
|
+
const { ENVIRONMENT_URLS } = require("../constants")
|
|
13
|
+
const { findProjectRoot } = require("../utils")
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Fetch JSON from a URL with timeout
|
|
17
17
|
*/
|
|
18
18
|
function fetchJson(url, timeoutMs = 15000) {
|
|
19
19
|
return new Promise((resolve, reject) => {
|
|
20
|
-
const client = url.startsWith("https") ? https : http
|
|
20
|
+
const client = url.startsWith("https") ? https : http
|
|
21
21
|
const options = {
|
|
22
22
|
rejectUnauthorized: false, // Allow self-signed certs for local dev
|
|
23
23
|
timeout: timeoutMs,
|
|
24
|
-
}
|
|
24
|
+
}
|
|
25
25
|
|
|
26
26
|
const req = client
|
|
27
27
|
.get(url, options, (res) => {
|
|
28
28
|
// Check for non-200 status
|
|
29
29
|
if (res.statusCode !== 200) {
|
|
30
|
-
reject(new Error(`HTTP ${res.statusCode} from ${url}`))
|
|
31
|
-
return
|
|
30
|
+
reject(new Error(`HTTP ${res.statusCode} from ${url}`))
|
|
31
|
+
return
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
let data = ""
|
|
35
|
-
res.on("data", (chunk) => (data += chunk))
|
|
34
|
+
let data = ""
|
|
35
|
+
res.on("data", (chunk) => (data += chunk))
|
|
36
36
|
res.on("end", () => {
|
|
37
37
|
try {
|
|
38
|
-
resolve(JSON.parse(data))
|
|
38
|
+
resolve(JSON.parse(data))
|
|
39
39
|
} catch (e) {
|
|
40
|
-
reject(new Error(`Failed to parse JSON from ${url}: ${e.message}`))
|
|
40
|
+
reject(new Error(`Failed to parse JSON from ${url}: ${e.message}`))
|
|
41
41
|
}
|
|
42
|
-
})
|
|
42
|
+
})
|
|
43
43
|
})
|
|
44
44
|
.on("error", reject)
|
|
45
45
|
.on("timeout", () => {
|
|
46
|
-
req.destroy()
|
|
47
|
-
reject(new Error(`Request timeout after ${timeoutMs}ms for ${url}`))
|
|
48
|
-
})
|
|
49
|
-
})
|
|
46
|
+
req.destroy()
|
|
47
|
+
reject(new Error(`Request timeout after ${timeoutMs}ms for ${url}`))
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* Group OpenAPI paths by their tags
|
|
54
54
|
*/
|
|
55
55
|
function groupPathsByTag(openApiSpec) {
|
|
56
|
-
const tagGroups = {}
|
|
56
|
+
const tagGroups = {}
|
|
57
57
|
|
|
58
58
|
// Initialize tag groups with info from tags array
|
|
59
59
|
if (openApiSpec.tags) {
|
|
@@ -63,14 +63,16 @@ function groupPathsByTag(openApiSpec) {
|
|
|
63
63
|
description: tag.description || "",
|
|
64
64
|
paths: [],
|
|
65
65
|
asyncMessages: [],
|
|
66
|
-
}
|
|
66
|
+
}
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
// Group paths by their tags
|
|
71
|
-
for (const [pathUrl, pathMethods] of Object.entries(
|
|
71
|
+
for (const [pathUrl, pathMethods] of Object.entries(
|
|
72
|
+
openApiSpec.paths || {},
|
|
73
|
+
)) {
|
|
72
74
|
for (const [method, pathInfo] of Object.entries(pathMethods)) {
|
|
73
|
-
if (typeof pathInfo !== "object" || !pathInfo.tags) continue
|
|
75
|
+
if (typeof pathInfo !== "object" || !pathInfo.tags) continue
|
|
74
76
|
|
|
75
77
|
for (const tag of pathInfo.tags) {
|
|
76
78
|
if (!tagGroups[tag]) {
|
|
@@ -79,12 +81,12 @@ function groupPathsByTag(openApiSpec) {
|
|
|
79
81
|
description: "",
|
|
80
82
|
paths: [],
|
|
81
83
|
asyncMessages: [],
|
|
82
|
-
}
|
|
84
|
+
}
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
// Extract permission and permission_key from x-permission
|
|
86
|
-
const permission = pathInfo["x-permission"]?.permission
|
|
87
|
-
const permissionKey = pathInfo["x-permission"]?.permission_key
|
|
88
|
+
const permission = pathInfo["x-permission"]?.permission
|
|
89
|
+
const permissionKey = pathInfo["x-permission"]?.permission_key
|
|
88
90
|
|
|
89
91
|
tagGroups[tag].paths.push({
|
|
90
92
|
path: pathUrl,
|
|
@@ -93,29 +95,29 @@ function groupPathsByTag(openApiSpec) {
|
|
|
93
95
|
summary: pathInfo.summary || "",
|
|
94
96
|
permission: permission || null,
|
|
95
97
|
permissionKey: permissionKey || null,
|
|
96
|
-
})
|
|
98
|
+
})
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
101
|
}
|
|
100
102
|
|
|
101
|
-
return tagGroups
|
|
103
|
+
return tagGroups
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
/**
|
|
105
107
|
* Extract messages from AsyncAPI and map to tags
|
|
106
108
|
*/
|
|
107
109
|
function mapAsyncMessagesToTags(asyncApiSpec, tagGroups) {
|
|
108
|
-
const messages = asyncApiSpec?.components?.messages || {}
|
|
110
|
+
const messages = asyncApiSpec?.components?.messages || {}
|
|
109
111
|
|
|
110
112
|
for (const [messageName, messageInfo] of Object.entries(messages)) {
|
|
111
113
|
// Try to find matching tag based on message name
|
|
112
114
|
// Messages often have format like "GameUpdated", "LeaderboardCreated" etc.
|
|
113
115
|
const baseName = messageName
|
|
114
116
|
.replace(/Created$|Updated$|Deleted$|Changed$|Event$/, "")
|
|
115
|
-
.toLowerCase()
|
|
117
|
+
.toLowerCase()
|
|
116
118
|
|
|
117
119
|
for (const [tagName, tagGroup] of Object.entries(tagGroups)) {
|
|
118
|
-
const tagLower = tagName.toLowerCase()
|
|
120
|
+
const tagLower = tagName.toLowerCase()
|
|
119
121
|
// Match if tag contains the base name or vice versa
|
|
120
122
|
if (
|
|
121
123
|
tagLower.includes(baseName) ||
|
|
@@ -125,12 +127,12 @@ function mapAsyncMessagesToTags(asyncApiSpec, tagGroups) {
|
|
|
125
127
|
tagGroup.asyncMessages.push({
|
|
126
128
|
name: messageName,
|
|
127
129
|
description: messageInfo.description || messageInfo.summary || "",
|
|
128
|
-
})
|
|
130
|
+
})
|
|
129
131
|
}
|
|
130
132
|
}
|
|
131
133
|
}
|
|
132
134
|
|
|
133
|
-
return tagGroups
|
|
135
|
+
return tagGroups
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
/**
|
|
@@ -138,188 +140,203 @@ function mapAsyncMessagesToTags(asyncApiSpec, tagGroups) {
|
|
|
138
140
|
*/
|
|
139
141
|
async function selectWithTypeAhead(question, options) {
|
|
140
142
|
return new Promise((resolve) => {
|
|
141
|
-
const stdin = process.stdin
|
|
142
|
-
const stdout = process.stdout
|
|
143
|
+
const stdin = process.stdin
|
|
144
|
+
const stdout = process.stdout
|
|
143
145
|
|
|
144
|
-
let selectedIndex = 0
|
|
145
|
-
let filter = ""
|
|
146
|
-
let filteredOptions = [...options]
|
|
146
|
+
let selectedIndex = 0
|
|
147
|
+
let filter = ""
|
|
148
|
+
let filteredOptions = [...options]
|
|
147
149
|
|
|
148
150
|
const applyFilter = () => {
|
|
149
151
|
if (!filter) {
|
|
150
|
-
filteredOptions = [...options]
|
|
152
|
+
filteredOptions = [...options]
|
|
151
153
|
} else {
|
|
152
|
-
const lowerFilter = filter.toLowerCase()
|
|
154
|
+
const lowerFilter = filter.toLowerCase()
|
|
153
155
|
filteredOptions = options.filter(
|
|
154
156
|
(opt) =>
|
|
155
157
|
opt.label.toLowerCase().includes(lowerFilter) ||
|
|
156
|
-
(opt.description &&
|
|
157
|
-
|
|
158
|
+
(opt.description &&
|
|
159
|
+
opt.description.toLowerCase().includes(lowerFilter)),
|
|
160
|
+
)
|
|
158
161
|
}
|
|
159
|
-
selectedIndex = Math.min(
|
|
160
|
-
|
|
162
|
+
selectedIndex = Math.min(
|
|
163
|
+
selectedIndex,
|
|
164
|
+
Math.max(0, filteredOptions.length - 1),
|
|
165
|
+
)
|
|
166
|
+
}
|
|
161
167
|
|
|
162
|
-
const maxVisible = 10
|
|
168
|
+
const maxVisible = 10
|
|
163
169
|
// Total lines rendered: question + filter + blank + scroll-up + maxVisible + scroll-down = maxVisible + 5
|
|
164
|
-
const totalLines = maxVisible + 5
|
|
170
|
+
const totalLines = maxVisible + 5
|
|
165
171
|
|
|
166
172
|
const render = () => {
|
|
167
|
-
stdout.write("\x1B[?25l")
|
|
173
|
+
stdout.write("\x1B[?25l") // Hide cursor
|
|
168
174
|
|
|
169
175
|
// Calculate scroll window
|
|
170
|
-
let startIdx = 0
|
|
176
|
+
let startIdx = 0
|
|
171
177
|
if (filteredOptions.length > maxVisible) {
|
|
172
|
-
startIdx = Math.max(0, selectedIndex - Math.floor(maxVisible / 2))
|
|
173
|
-
startIdx = Math.min(startIdx, filteredOptions.length - maxVisible)
|
|
178
|
+
startIdx = Math.max(0, selectedIndex - Math.floor(maxVisible / 2))
|
|
179
|
+
startIdx = Math.min(startIdx, filteredOptions.length - maxVisible)
|
|
174
180
|
}
|
|
175
|
-
const endIdx = Math.min(startIdx + maxVisible, filteredOptions.length)
|
|
176
|
-
const visibleOptions = filteredOptions.slice(startIdx, endIdx)
|
|
181
|
+
const endIdx = Math.min(startIdx + maxVisible, filteredOptions.length)
|
|
182
|
+
const visibleOptions = filteredOptions.slice(startIdx, endIdx)
|
|
177
183
|
|
|
178
184
|
// Clear screen area
|
|
179
|
-
stdout.write(`\x1B[${totalLines}A`)
|
|
185
|
+
stdout.write(`\x1B[${totalLines}A`)
|
|
180
186
|
for (let i = 0; i < totalLines; i++) {
|
|
181
|
-
stdout.write("\x1B[2K\n")
|
|
187
|
+
stdout.write("\x1B[2K\n")
|
|
182
188
|
}
|
|
183
|
-
stdout.write(`\x1B[${totalLines}A`)
|
|
189
|
+
stdout.write(`\x1B[${totalLines}A`)
|
|
184
190
|
|
|
185
191
|
// Print question and filter
|
|
186
|
-
stdout.write(`\x1B[36m?\x1B[0m ${question}\n`)
|
|
187
|
-
stdout.write(
|
|
188
|
-
|
|
192
|
+
stdout.write(`\x1B[36m?\x1B[0m ${question}\n`)
|
|
193
|
+
stdout.write(
|
|
194
|
+
` Filter: ${filter}\x1B[90m (type to filter, arrows to navigate)\x1B[0m\n`,
|
|
195
|
+
)
|
|
196
|
+
stdout.write(`\n`)
|
|
189
197
|
|
|
190
198
|
// Print scroll indicator if needed
|
|
191
199
|
if (startIdx > 0) {
|
|
192
|
-
stdout.write(` \x1B[90m↑ ${startIdx} more above\x1B[0m\n`)
|
|
200
|
+
stdout.write(` \x1B[90m↑ ${startIdx} more above\x1B[0m\n`)
|
|
193
201
|
} else {
|
|
194
|
-
stdout.write(`\n`)
|
|
202
|
+
stdout.write(`\n`)
|
|
195
203
|
}
|
|
196
204
|
|
|
197
205
|
// Print visible options
|
|
198
206
|
visibleOptions.forEach((opt, i) => {
|
|
199
|
-
const actualIndex = startIdx + i
|
|
200
|
-
const isSelected = actualIndex === selectedIndex
|
|
201
|
-
const prefix = isSelected ? "\x1B[36m❯\x1B[0m" : " "
|
|
202
|
-
const label = isSelected ? `\x1B[36m${opt.label}\x1B[0m` : opt.label
|
|
207
|
+
const actualIndex = startIdx + i
|
|
208
|
+
const isSelected = actualIndex === selectedIndex
|
|
209
|
+
const prefix = isSelected ? "\x1B[36m❯\x1B[0m" : " "
|
|
210
|
+
const label = isSelected ? `\x1B[36m${opt.label}\x1B[0m` : opt.label
|
|
203
211
|
|
|
204
212
|
if (opt.description) {
|
|
205
|
-
stdout.write(
|
|
213
|
+
stdout.write(
|
|
214
|
+
`${prefix} ${label} \x1B[90m- ${opt.description}\x1B[0m\n`,
|
|
215
|
+
)
|
|
206
216
|
} else {
|
|
207
|
-
stdout.write(`${prefix} ${label}\n`)
|
|
217
|
+
stdout.write(`${prefix} ${label}\n`)
|
|
208
218
|
}
|
|
209
|
-
})
|
|
219
|
+
})
|
|
210
220
|
|
|
211
221
|
// Pad remaining lines
|
|
212
222
|
for (let i = visibleOptions.length; i < maxVisible; i++) {
|
|
213
|
-
stdout.write(`\n`)
|
|
223
|
+
stdout.write(`\n`)
|
|
214
224
|
}
|
|
215
225
|
|
|
216
226
|
// Print scroll indicator if needed
|
|
217
227
|
if (endIdx < filteredOptions.length) {
|
|
218
|
-
stdout.write(
|
|
228
|
+
stdout.write(
|
|
229
|
+
` \x1B[90m↓ ${filteredOptions.length - endIdx} more below\x1B[0m\n`,
|
|
230
|
+
)
|
|
219
231
|
} else {
|
|
220
|
-
stdout.write(`\n`)
|
|
232
|
+
stdout.write(`\n`)
|
|
221
233
|
}
|
|
222
234
|
|
|
223
|
-
stdout.write(`\x1B[?25h`)
|
|
224
|
-
}
|
|
235
|
+
stdout.write(`\x1B[?25h`) // Show cursor
|
|
236
|
+
}
|
|
225
237
|
|
|
226
238
|
const cleanup = () => {
|
|
227
|
-
stdin.setRawMode(false)
|
|
228
|
-
stdin.removeAllListeners("data")
|
|
229
|
-
stdin.pause()
|
|
239
|
+
stdin.setRawMode(false)
|
|
240
|
+
stdin.removeAllListeners("data")
|
|
241
|
+
stdin.pause()
|
|
230
242
|
// Clear UI
|
|
231
|
-
stdout.write(`\x1B[${totalLines}A`)
|
|
243
|
+
stdout.write(`\x1B[${totalLines}A`)
|
|
232
244
|
for (let i = 0; i < totalLines; i++) {
|
|
233
|
-
stdout.write("\x1B[2K\n")
|
|
245
|
+
stdout.write("\x1B[2K\n")
|
|
234
246
|
}
|
|
235
|
-
stdout.write(`\x1B[${totalLines}A`)
|
|
236
|
-
}
|
|
247
|
+
stdout.write(`\x1B[${totalLines}A`)
|
|
248
|
+
}
|
|
237
249
|
|
|
238
250
|
// Initial render with spacing (must match totalLines)
|
|
239
251
|
for (let i = 0; i < totalLines; i++) {
|
|
240
|
-
console.log("")
|
|
252
|
+
console.log("")
|
|
241
253
|
}
|
|
242
254
|
|
|
243
|
-
stdin.setRawMode(true)
|
|
244
|
-
stdin.resume()
|
|
245
|
-
stdin.setEncoding("utf8")
|
|
255
|
+
stdin.setRawMode(true)
|
|
256
|
+
stdin.resume()
|
|
257
|
+
stdin.setEncoding("utf8")
|
|
246
258
|
|
|
247
|
-
let buffer = ""
|
|
259
|
+
let buffer = ""
|
|
248
260
|
stdin.on("data", (data) => {
|
|
249
|
-
buffer += data
|
|
261
|
+
buffer += data
|
|
250
262
|
|
|
251
263
|
while (buffer.length > 0) {
|
|
252
264
|
// Ctrl+C
|
|
253
265
|
if (buffer[0] === "\x03") {
|
|
254
|
-
cleanup()
|
|
255
|
-
process.exit(0)
|
|
266
|
+
cleanup()
|
|
267
|
+
process.exit(0)
|
|
256
268
|
}
|
|
257
269
|
|
|
258
270
|
// Enter
|
|
259
271
|
if (buffer[0] === "\r" || buffer[0] === "\n") {
|
|
260
|
-
cleanup()
|
|
261
|
-
const selected = filteredOptions[selectedIndex]
|
|
272
|
+
cleanup()
|
|
273
|
+
const selected = filteredOptions[selectedIndex]
|
|
262
274
|
if (selected) {
|
|
263
|
-
stdout.write(
|
|
264
|
-
|
|
275
|
+
stdout.write(
|
|
276
|
+
`\x1B[36m?\x1B[0m ${question} \x1B[36m${selected.label}\x1B[0m\n`,
|
|
277
|
+
)
|
|
278
|
+
resolve(selected.value)
|
|
265
279
|
} else {
|
|
266
|
-
resolve(null)
|
|
280
|
+
resolve(null)
|
|
267
281
|
}
|
|
268
|
-
buffer = buffer.slice(1)
|
|
269
|
-
return
|
|
282
|
+
buffer = buffer.slice(1)
|
|
283
|
+
return
|
|
270
284
|
}
|
|
271
285
|
|
|
272
286
|
// Escape sequences
|
|
273
287
|
if (buffer.startsWith("\x1b[A") || buffer.startsWith("\x1bOA")) {
|
|
274
288
|
// Up arrow
|
|
275
|
-
selectedIndex = Math.max(0, selectedIndex - 1)
|
|
276
|
-
render()
|
|
277
|
-
buffer = buffer.slice(3)
|
|
278
|
-
continue
|
|
289
|
+
selectedIndex = Math.max(0, selectedIndex - 1)
|
|
290
|
+
render()
|
|
291
|
+
buffer = buffer.slice(3)
|
|
292
|
+
continue
|
|
279
293
|
}
|
|
280
294
|
if (buffer.startsWith("\x1b[B") || buffer.startsWith("\x1bOB")) {
|
|
281
295
|
// Down arrow
|
|
282
|
-
selectedIndex = Math.min(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
296
|
+
selectedIndex = Math.min(
|
|
297
|
+
filteredOptions.length - 1,
|
|
298
|
+
selectedIndex + 1,
|
|
299
|
+
)
|
|
300
|
+
render()
|
|
301
|
+
buffer = buffer.slice(3)
|
|
302
|
+
continue
|
|
286
303
|
}
|
|
287
304
|
if (buffer.startsWith("\x1b[") || buffer.startsWith("\x1bO")) {
|
|
288
305
|
if (buffer.length >= 3) {
|
|
289
|
-
buffer = buffer.slice(3)
|
|
290
|
-
continue
|
|
306
|
+
buffer = buffer.slice(3)
|
|
307
|
+
continue
|
|
291
308
|
}
|
|
292
|
-
break
|
|
309
|
+
break
|
|
293
310
|
}
|
|
294
311
|
if (buffer.startsWith("\x1b")) {
|
|
295
312
|
if (buffer.length >= 2) {
|
|
296
|
-
buffer = buffer.slice(1)
|
|
297
|
-
continue
|
|
313
|
+
buffer = buffer.slice(1)
|
|
314
|
+
continue
|
|
298
315
|
}
|
|
299
|
-
break
|
|
316
|
+
break
|
|
300
317
|
}
|
|
301
318
|
|
|
302
319
|
// Backspace
|
|
303
320
|
if (buffer[0] === "\x7f" || buffer[0] === "\b") {
|
|
304
|
-
filter = filter.slice(0, -1)
|
|
305
|
-
applyFilter()
|
|
306
|
-
render()
|
|
307
|
-
buffer = buffer.slice(1)
|
|
308
|
-
continue
|
|
321
|
+
filter = filter.slice(0, -1)
|
|
322
|
+
applyFilter()
|
|
323
|
+
render()
|
|
324
|
+
buffer = buffer.slice(1)
|
|
325
|
+
continue
|
|
309
326
|
}
|
|
310
327
|
|
|
311
328
|
// Regular character
|
|
312
329
|
if (buffer[0].charCodeAt(0) >= 32 && buffer[0].charCodeAt(0) < 127) {
|
|
313
|
-
filter += buffer[0]
|
|
314
|
-
applyFilter()
|
|
315
|
-
render()
|
|
330
|
+
filter += buffer[0]
|
|
331
|
+
applyFilter()
|
|
332
|
+
render()
|
|
316
333
|
}
|
|
317
|
-
buffer = buffer.slice(1)
|
|
334
|
+
buffer = buffer.slice(1)
|
|
318
335
|
}
|
|
319
|
-
})
|
|
336
|
+
})
|
|
320
337
|
|
|
321
|
-
render()
|
|
322
|
-
})
|
|
338
|
+
render()
|
|
339
|
+
})
|
|
323
340
|
}
|
|
324
341
|
|
|
325
342
|
/**
|
|
@@ -327,169 +344,175 @@ async function selectWithTypeAhead(question, options) {
|
|
|
327
344
|
*/
|
|
328
345
|
async function multiSelectPrompt(question, options) {
|
|
329
346
|
return new Promise((resolve) => {
|
|
330
|
-
const stdin = process.stdin
|
|
331
|
-
const stdout = process.stdout
|
|
347
|
+
const stdin = process.stdin
|
|
348
|
+
const stdout = process.stdout
|
|
332
349
|
|
|
333
|
-
let selectedIndex = 0
|
|
334
|
-
const selected = new Set()
|
|
335
|
-
const maxVisible = 10
|
|
350
|
+
let selectedIndex = 0
|
|
351
|
+
const selected = new Set()
|
|
352
|
+
const maxVisible = 10
|
|
336
353
|
// Total lines: question + hint + selected-count + scroll-up + maxVisible + scroll-down = maxVisible + 5
|
|
337
|
-
const totalLines = maxVisible + 5
|
|
354
|
+
const totalLines = maxVisible + 5
|
|
338
355
|
|
|
339
356
|
const render = () => {
|
|
340
|
-
stdout.write("\x1B[?25l")
|
|
357
|
+
stdout.write("\x1B[?25l")
|
|
341
358
|
|
|
342
359
|
// Calculate scroll window
|
|
343
|
-
let startIdx = 0
|
|
360
|
+
let startIdx = 0
|
|
344
361
|
if (options.length > maxVisible) {
|
|
345
|
-
startIdx = Math.max(0, selectedIndex - Math.floor(maxVisible / 2))
|
|
346
|
-
startIdx = Math.min(startIdx, options.length - maxVisible)
|
|
362
|
+
startIdx = Math.max(0, selectedIndex - Math.floor(maxVisible / 2))
|
|
363
|
+
startIdx = Math.min(startIdx, options.length - maxVisible)
|
|
347
364
|
}
|
|
348
|
-
const endIdx = Math.min(startIdx + maxVisible, options.length)
|
|
349
|
-
const visibleOptions = options.slice(startIdx, endIdx)
|
|
365
|
+
const endIdx = Math.min(startIdx + maxVisible, options.length)
|
|
366
|
+
const visibleOptions = options.slice(startIdx, endIdx)
|
|
350
367
|
|
|
351
|
-
stdout.write(`\x1B[${totalLines}A`)
|
|
368
|
+
stdout.write(`\x1B[${totalLines}A`)
|
|
352
369
|
for (let i = 0; i < totalLines; i++) {
|
|
353
|
-
stdout.write("\x1B[2K\n")
|
|
370
|
+
stdout.write("\x1B[2K\n")
|
|
354
371
|
}
|
|
355
|
-
stdout.write(`\x1B[${totalLines}A`)
|
|
372
|
+
stdout.write(`\x1B[${totalLines}A`)
|
|
356
373
|
|
|
357
|
-
stdout.write(`\x1B[36m?\x1B[0m ${question}\n`)
|
|
358
|
-
stdout.write(
|
|
359
|
-
|
|
374
|
+
stdout.write(`\x1B[36m?\x1B[0m ${question}\n`)
|
|
375
|
+
stdout.write(
|
|
376
|
+
` \x1B[90m(Space to toggle, Enter to confirm, A to toggle all)\x1B[0m\n`,
|
|
377
|
+
)
|
|
378
|
+
stdout.write(` Selected: ${selected.size} of ${options.length}\n`)
|
|
360
379
|
|
|
361
380
|
if (startIdx > 0) {
|
|
362
|
-
stdout.write(` \x1B[90m↑ ${startIdx} more above\x1B[0m\n`)
|
|
381
|
+
stdout.write(` \x1B[90m↑ ${startIdx} more above\x1B[0m\n`)
|
|
363
382
|
} else {
|
|
364
|
-
stdout.write(`\n`)
|
|
383
|
+
stdout.write(`\n`)
|
|
365
384
|
}
|
|
366
385
|
|
|
367
386
|
visibleOptions.forEach((opt, i) => {
|
|
368
|
-
const actualIndex = startIdx + i
|
|
369
|
-
const isHighlighted = actualIndex === selectedIndex
|
|
370
|
-
const isSelected = selected.has(actualIndex)
|
|
387
|
+
const actualIndex = startIdx + i
|
|
388
|
+
const isHighlighted = actualIndex === selectedIndex
|
|
389
|
+
const isSelected = selected.has(actualIndex)
|
|
371
390
|
|
|
372
|
-
const checkbox = isSelected ? "\x1B[32m◉\x1B[0m" : "○"
|
|
373
|
-
const prefix = isHighlighted ? "\x1B[36m❯\x1B[0m" : " "
|
|
374
|
-
const label = isHighlighted ? `\x1B[36m${opt.label}\x1B[0m` : opt.label
|
|
391
|
+
const checkbox = isSelected ? "\x1B[32m◉\x1B[0m" : "○"
|
|
392
|
+
const prefix = isHighlighted ? "\x1B[36m❯\x1B[0m" : " "
|
|
393
|
+
const label = isHighlighted ? `\x1B[36m${opt.label}\x1B[0m` : opt.label
|
|
375
394
|
|
|
376
395
|
if (opt.description) {
|
|
377
|
-
stdout.write(
|
|
396
|
+
stdout.write(
|
|
397
|
+
`${prefix} ${checkbox} ${label} \x1B[90m- ${opt.description}\x1B[0m\n`,
|
|
398
|
+
)
|
|
378
399
|
} else {
|
|
379
|
-
stdout.write(`${prefix} ${checkbox} ${label}\n`)
|
|
400
|
+
stdout.write(`${prefix} ${checkbox} ${label}\n`)
|
|
380
401
|
}
|
|
381
|
-
})
|
|
402
|
+
})
|
|
382
403
|
|
|
383
404
|
for (let i = visibleOptions.length; i < maxVisible; i++) {
|
|
384
|
-
stdout.write(`\n`)
|
|
405
|
+
stdout.write(`\n`)
|
|
385
406
|
}
|
|
386
407
|
|
|
387
408
|
if (endIdx < options.length) {
|
|
388
|
-
stdout.write(
|
|
409
|
+
stdout.write(
|
|
410
|
+
` \x1B[90m↓ ${options.length - endIdx} more below\x1B[0m\n`,
|
|
411
|
+
)
|
|
389
412
|
} else {
|
|
390
|
-
stdout.write(`\n`)
|
|
413
|
+
stdout.write(`\n`)
|
|
391
414
|
}
|
|
392
415
|
|
|
393
|
-
stdout.write(`\x1B[?25h`)
|
|
394
|
-
}
|
|
416
|
+
stdout.write(`\x1B[?25h`)
|
|
417
|
+
}
|
|
395
418
|
|
|
396
419
|
const cleanup = () => {
|
|
397
|
-
stdin.setRawMode(false)
|
|
398
|
-
stdin.removeAllListeners("data")
|
|
399
|
-
stdin.pause()
|
|
400
|
-
stdout.write(`\x1B[${totalLines}A`)
|
|
420
|
+
stdin.setRawMode(false)
|
|
421
|
+
stdin.removeAllListeners("data")
|
|
422
|
+
stdin.pause()
|
|
423
|
+
stdout.write(`\x1B[${totalLines}A`)
|
|
401
424
|
for (let i = 0; i < totalLines; i++) {
|
|
402
|
-
stdout.write("\x1B[2K\n")
|
|
425
|
+
stdout.write("\x1B[2K\n")
|
|
403
426
|
}
|
|
404
|
-
stdout.write(`\x1B[${totalLines}A`)
|
|
405
|
-
}
|
|
427
|
+
stdout.write(`\x1B[${totalLines}A`)
|
|
428
|
+
}
|
|
406
429
|
|
|
407
430
|
for (let i = 0; i < totalLines; i++) {
|
|
408
|
-
console.log("")
|
|
431
|
+
console.log("")
|
|
409
432
|
}
|
|
410
433
|
|
|
411
|
-
stdin.setRawMode(true)
|
|
412
|
-
stdin.resume()
|
|
413
|
-
stdin.setEncoding("utf8")
|
|
434
|
+
stdin.setRawMode(true)
|
|
435
|
+
stdin.resume()
|
|
436
|
+
stdin.setEncoding("utf8")
|
|
414
437
|
|
|
415
|
-
let buffer = ""
|
|
438
|
+
let buffer = ""
|
|
416
439
|
stdin.on("data", (data) => {
|
|
417
|
-
buffer += data
|
|
440
|
+
buffer += data
|
|
418
441
|
|
|
419
442
|
while (buffer.length > 0) {
|
|
420
443
|
if (buffer[0] === "\x03") {
|
|
421
|
-
cleanup()
|
|
422
|
-
process.exit(0)
|
|
444
|
+
cleanup()
|
|
445
|
+
process.exit(0)
|
|
423
446
|
}
|
|
424
447
|
|
|
425
448
|
if (buffer[0] === "\r" || buffer[0] === "\n") {
|
|
426
|
-
cleanup()
|
|
427
|
-
const selectedOptions = options.filter((_, i) => selected.has(i))
|
|
449
|
+
cleanup()
|
|
450
|
+
const selectedOptions = options.filter((_, i) => selected.has(i))
|
|
428
451
|
stdout.write(
|
|
429
|
-
`\x1B[36m?\x1B[0m ${question} \x1B[36m${selectedOptions.length} selected\x1B[0m\n
|
|
430
|
-
)
|
|
431
|
-
resolve(selectedOptions.map((opt) => opt.value))
|
|
432
|
-
buffer = buffer.slice(1)
|
|
433
|
-
return
|
|
452
|
+
`\x1B[36m?\x1B[0m ${question} \x1B[36m${selectedOptions.length} selected\x1B[0m\n`,
|
|
453
|
+
)
|
|
454
|
+
resolve(selectedOptions.map((opt) => opt.value))
|
|
455
|
+
buffer = buffer.slice(1)
|
|
456
|
+
return
|
|
434
457
|
}
|
|
435
458
|
|
|
436
459
|
if (buffer.startsWith("\x1b[A") || buffer.startsWith("\x1bOA")) {
|
|
437
|
-
selectedIndex = Math.max(0, selectedIndex - 1)
|
|
438
|
-
render()
|
|
439
|
-
buffer = buffer.slice(3)
|
|
440
|
-
continue
|
|
460
|
+
selectedIndex = Math.max(0, selectedIndex - 1)
|
|
461
|
+
render()
|
|
462
|
+
buffer = buffer.slice(3)
|
|
463
|
+
continue
|
|
441
464
|
}
|
|
442
465
|
if (buffer.startsWith("\x1b[B") || buffer.startsWith("\x1bOB")) {
|
|
443
|
-
selectedIndex = Math.min(options.length - 1, selectedIndex + 1)
|
|
444
|
-
render()
|
|
445
|
-
buffer = buffer.slice(3)
|
|
446
|
-
continue
|
|
466
|
+
selectedIndex = Math.min(options.length - 1, selectedIndex + 1)
|
|
467
|
+
render()
|
|
468
|
+
buffer = buffer.slice(3)
|
|
469
|
+
continue
|
|
447
470
|
}
|
|
448
471
|
if (buffer.startsWith("\x1b[") || buffer.startsWith("\x1bO")) {
|
|
449
472
|
if (buffer.length >= 3) {
|
|
450
|
-
buffer = buffer.slice(3)
|
|
451
|
-
continue
|
|
473
|
+
buffer = buffer.slice(3)
|
|
474
|
+
continue
|
|
452
475
|
}
|
|
453
|
-
break
|
|
476
|
+
break
|
|
454
477
|
}
|
|
455
478
|
if (buffer.startsWith("\x1b")) {
|
|
456
479
|
if (buffer.length >= 2) {
|
|
457
|
-
buffer = buffer.slice(1)
|
|
458
|
-
continue
|
|
480
|
+
buffer = buffer.slice(1)
|
|
481
|
+
continue
|
|
459
482
|
}
|
|
460
|
-
break
|
|
483
|
+
break
|
|
461
484
|
}
|
|
462
485
|
|
|
463
486
|
// Spacebar toggle
|
|
464
487
|
if (buffer[0] === " ") {
|
|
465
488
|
if (selected.has(selectedIndex)) {
|
|
466
|
-
selected.delete(selectedIndex)
|
|
489
|
+
selected.delete(selectedIndex)
|
|
467
490
|
} else {
|
|
468
|
-
selected.add(selectedIndex)
|
|
491
|
+
selected.add(selectedIndex)
|
|
469
492
|
}
|
|
470
|
-
render()
|
|
471
|
-
buffer = buffer.slice(1)
|
|
472
|
-
continue
|
|
493
|
+
render()
|
|
494
|
+
buffer = buffer.slice(1)
|
|
495
|
+
continue
|
|
473
496
|
}
|
|
474
497
|
|
|
475
498
|
// 'a' or 'A' toggle all
|
|
476
499
|
if (buffer[0] === "a" || buffer[0] === "A") {
|
|
477
500
|
if (selected.size === options.length) {
|
|
478
|
-
selected.clear()
|
|
501
|
+
selected.clear()
|
|
479
502
|
} else {
|
|
480
|
-
options.forEach((_, i) => selected.add(i))
|
|
503
|
+
options.forEach((_, i) => selected.add(i))
|
|
481
504
|
}
|
|
482
|
-
render()
|
|
483
|
-
buffer = buffer.slice(1)
|
|
484
|
-
continue
|
|
505
|
+
render()
|
|
506
|
+
buffer = buffer.slice(1)
|
|
507
|
+
continue
|
|
485
508
|
}
|
|
486
509
|
|
|
487
|
-
buffer = buffer.slice(1)
|
|
510
|
+
buffer = buffer.slice(1)
|
|
488
511
|
}
|
|
489
|
-
})
|
|
512
|
+
})
|
|
490
513
|
|
|
491
|
-
render()
|
|
492
|
-
})
|
|
514
|
+
render()
|
|
515
|
+
})
|
|
493
516
|
}
|
|
494
517
|
|
|
495
518
|
/**
|
|
@@ -497,129 +520,135 @@ async function multiSelectPrompt(question, options) {
|
|
|
497
520
|
*/
|
|
498
521
|
async function textInput(question, defaultValue = "") {
|
|
499
522
|
return new Promise((resolve) => {
|
|
500
|
-
const stdin = process.stdin
|
|
501
|
-
const stdout = process.stdout
|
|
523
|
+
const stdin = process.stdin
|
|
524
|
+
const stdout = process.stdout
|
|
502
525
|
|
|
503
|
-
let value = defaultValue
|
|
504
|
-
let cursorPos = value.length
|
|
526
|
+
let value = defaultValue
|
|
527
|
+
let cursorPos = value.length
|
|
505
528
|
|
|
506
529
|
const render = () => {
|
|
507
|
-
stdout.write("\x1B[?25l")
|
|
508
|
-
stdout.write("\r\x1B[2K")
|
|
509
|
-
stdout.write(`\x1B[36m?\x1B[0m ${question}: ${value}`)
|
|
510
|
-
const totalLength = question.length + 4 + cursorPos
|
|
511
|
-
stdout.write(`\r\x1B[${totalLength}C`)
|
|
512
|
-
stdout.write("\x1B[?25h")
|
|
513
|
-
}
|
|
530
|
+
stdout.write("\x1B[?25l")
|
|
531
|
+
stdout.write("\r\x1B[2K")
|
|
532
|
+
stdout.write(`\x1B[36m?\x1B[0m ${question}: ${value}`)
|
|
533
|
+
const totalLength = question.length + 4 + cursorPos
|
|
534
|
+
stdout.write(`\r\x1B[${totalLength}C`)
|
|
535
|
+
stdout.write("\x1B[?25h")
|
|
536
|
+
}
|
|
514
537
|
|
|
515
538
|
const cleanup = () => {
|
|
516
|
-
stdin.setRawMode(false)
|
|
517
|
-
stdin.removeAllListeners("data")
|
|
518
|
-
stdin.pause()
|
|
519
|
-
}
|
|
539
|
+
stdin.setRawMode(false)
|
|
540
|
+
stdin.removeAllListeners("data")
|
|
541
|
+
stdin.pause()
|
|
542
|
+
}
|
|
520
543
|
|
|
521
|
-
console.log("")
|
|
522
|
-
render()
|
|
544
|
+
console.log("")
|
|
545
|
+
render()
|
|
523
546
|
|
|
524
|
-
stdin.setRawMode(true)
|
|
525
|
-
stdin.resume()
|
|
526
|
-
stdin.setEncoding("utf8")
|
|
547
|
+
stdin.setRawMode(true)
|
|
548
|
+
stdin.resume()
|
|
549
|
+
stdin.setEncoding("utf8")
|
|
527
550
|
|
|
528
551
|
stdin.on("data", (key) => {
|
|
529
552
|
if (key === "\x03") {
|
|
530
|
-
cleanup()
|
|
531
|
-
process.exit(0)
|
|
553
|
+
cleanup()
|
|
554
|
+
process.exit(0)
|
|
532
555
|
}
|
|
533
556
|
|
|
534
557
|
if (key === "\r" || key === "\n") {
|
|
535
|
-
cleanup()
|
|
536
|
-
stdout.write("\r\x1B[2K")
|
|
537
|
-
stdout.write(`\x1B[36m?\x1B[0m ${question}: \x1B[36m${value}\x1B[0m\n`)
|
|
538
|
-
resolve(value)
|
|
539
|
-
return
|
|
558
|
+
cleanup()
|
|
559
|
+
stdout.write("\r\x1B[2K")
|
|
560
|
+
stdout.write(`\x1B[36m?\x1B[0m ${question}: \x1B[36m${value}\x1B[0m\n`)
|
|
561
|
+
resolve(value)
|
|
562
|
+
return
|
|
540
563
|
}
|
|
541
564
|
|
|
542
565
|
if (key === "\x7f" || key === "\b") {
|
|
543
566
|
if (cursorPos > 0) {
|
|
544
|
-
value = value.slice(0, cursorPos - 1) + value.slice(cursorPos)
|
|
545
|
-
cursorPos
|
|
567
|
+
value = value.slice(0, cursorPos - 1) + value.slice(cursorPos)
|
|
568
|
+
cursorPos--
|
|
546
569
|
}
|
|
547
|
-
render()
|
|
548
|
-
return
|
|
570
|
+
render()
|
|
571
|
+
return
|
|
549
572
|
}
|
|
550
573
|
|
|
551
574
|
if (key === "\x1b[D") {
|
|
552
|
-
cursorPos = Math.max(0, cursorPos - 1)
|
|
553
|
-
render()
|
|
554
|
-
return
|
|
575
|
+
cursorPos = Math.max(0, cursorPos - 1)
|
|
576
|
+
render()
|
|
577
|
+
return
|
|
555
578
|
}
|
|
556
579
|
|
|
557
580
|
if (key === "\x1b[C") {
|
|
558
|
-
cursorPos = Math.min(value.length, cursorPos + 1)
|
|
559
|
-
render()
|
|
560
|
-
return
|
|
581
|
+
cursorPos = Math.min(value.length, cursorPos + 1)
|
|
582
|
+
render()
|
|
583
|
+
return
|
|
561
584
|
}
|
|
562
585
|
|
|
563
|
-
if (
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
586
|
+
if (
|
|
587
|
+
key.length === 1 &&
|
|
588
|
+
key.charCodeAt(0) >= 32 &&
|
|
589
|
+
key.charCodeAt(0) < 127
|
|
590
|
+
) {
|
|
591
|
+
value = value.slice(0, cursorPos) + key + value.slice(cursorPos)
|
|
592
|
+
cursorPos++
|
|
593
|
+
render()
|
|
567
594
|
}
|
|
568
|
-
})
|
|
569
|
-
})
|
|
595
|
+
})
|
|
596
|
+
})
|
|
570
597
|
}
|
|
571
598
|
|
|
572
599
|
/**
|
|
573
600
|
* Main command handler
|
|
574
601
|
*/
|
|
575
602
|
async function addDependencyCommand(argv) {
|
|
576
|
-
const environment = argv.env || "staging"
|
|
603
|
+
const environment = argv.env || "staging"
|
|
577
604
|
|
|
578
|
-
console.log("")
|
|
579
|
-
console.log("\x1B[36m╔════════════════════════════════════════════╗\x1B[0m")
|
|
580
|
-
console.log("\x1B[36m║ Add API Dependency Wizard ║\x1B[0m")
|
|
581
|
-
console.log("\x1B[36m╚════════════════════════════════════════════╝\x1B[0m")
|
|
582
|
-
console.log("")
|
|
605
|
+
console.log("")
|
|
606
|
+
console.log("\x1B[36m╔════════════════════════════════════════════╗\x1B[0m")
|
|
607
|
+
console.log("\x1B[36m║ Add API Dependency Wizard ║\x1B[0m")
|
|
608
|
+
console.log("\x1B[36m╚════════════════════════════════════════════╝\x1B[0m")
|
|
609
|
+
console.log("")
|
|
583
610
|
|
|
584
611
|
// Get environment URLs
|
|
585
|
-
const envUrls = ENVIRONMENT_URLS[environment]
|
|
612
|
+
const envUrls = ENVIRONMENT_URLS[environment]
|
|
586
613
|
if (!envUrls) {
|
|
587
|
-
console.error(`\x1B[31m✗ Unknown environment: ${environment}\x1B[0m`)
|
|
588
|
-
console.log(` Available: ${Object.keys(ENVIRONMENT_URLS).join(", ")}`)
|
|
589
|
-
process.exit(1)
|
|
614
|
+
console.error(`\x1B[31m✗ Unknown environment: ${environment}\x1B[0m`)
|
|
615
|
+
console.log(` Available: ${Object.keys(ENVIRONMENT_URLS).join(", ")}`)
|
|
616
|
+
process.exit(1)
|
|
590
617
|
}
|
|
591
618
|
|
|
592
|
-
console.log(`\x1B[90mEnvironment: ${environment}\x1B[0m`)
|
|
593
|
-
console.log(`\x1B[90mLoading API specifications...\x1B[0m`)
|
|
619
|
+
console.log(`\x1B[90mEnvironment: ${environment}\x1B[0m`)
|
|
620
|
+
console.log(`\x1B[90mLoading API specifications...\x1B[0m`)
|
|
594
621
|
|
|
595
622
|
// Fetch specs
|
|
596
|
-
let openApiSpec, asyncApiSpec
|
|
623
|
+
let openApiSpec, asyncApiSpec
|
|
597
624
|
try {
|
|
598
|
-
[openApiSpec, asyncApiSpec] = await Promise.all([
|
|
625
|
+
;[openApiSpec, asyncApiSpec] = await Promise.all([
|
|
599
626
|
fetchJson(envUrls.openApiSpec),
|
|
600
|
-
fetchJson(envUrls.asyncApiSpec).catch(() => ({
|
|
601
|
-
|
|
627
|
+
fetchJson(envUrls.asyncApiSpec).catch(() => ({
|
|
628
|
+
components: { messages: {} },
|
|
629
|
+
})),
|
|
630
|
+
])
|
|
602
631
|
} catch (error) {
|
|
603
|
-
console.error(`\x1B[31m✗ Failed to load API specs: ${error.message}\x1B[0m`)
|
|
604
|
-
process.exit(1)
|
|
632
|
+
console.error(`\x1B[31m✗ Failed to load API specs: ${error.message}\x1B[0m`)
|
|
633
|
+
process.exit(1)
|
|
605
634
|
}
|
|
606
635
|
|
|
607
|
-
console.log(`\x1B[32m✓ Loaded OpenAPI spec\x1B[0m`)
|
|
608
|
-
console.log(`\x1B[32m✓ Loaded AsyncAPI spec\x1B[0m`)
|
|
609
|
-
console.log("")
|
|
636
|
+
console.log(`\x1B[32m✓ Loaded OpenAPI spec\x1B[0m`)
|
|
637
|
+
console.log(`\x1B[32m✓ Loaded AsyncAPI spec\x1B[0m`)
|
|
638
|
+
console.log("")
|
|
610
639
|
|
|
611
640
|
// Group paths by tags and map async messages
|
|
612
|
-
let tagGroups = groupPathsByTag(openApiSpec)
|
|
613
|
-
tagGroups = mapAsyncMessagesToTags(asyncApiSpec, tagGroups)
|
|
641
|
+
let tagGroups = groupPathsByTag(openApiSpec)
|
|
642
|
+
tagGroups = mapAsyncMessagesToTags(asyncApiSpec, tagGroups)
|
|
614
643
|
|
|
615
644
|
// Filter out empty tags
|
|
616
645
|
const tags = Object.values(tagGroups)
|
|
617
646
|
.filter((t) => t.paths.length > 0)
|
|
618
|
-
.sort((a, b) => a.name.localeCompare(b.name))
|
|
647
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
619
648
|
|
|
620
649
|
if (tags.length === 0) {
|
|
621
|
-
console.error("\x1B[31m✗ No API tags found in spec\x1B[0m")
|
|
622
|
-
process.exit(1)
|
|
650
|
+
console.error("\x1B[31m✗ No API tags found in spec\x1B[0m")
|
|
651
|
+
process.exit(1)
|
|
623
652
|
}
|
|
624
653
|
|
|
625
654
|
// Step 1: Select a tag
|
|
@@ -627,88 +656,98 @@ async function addDependencyCommand(argv) {
|
|
|
627
656
|
label: t.name,
|
|
628
657
|
description: `${t.paths.length} endpoints${t.asyncMessages.length > 0 ? `, ${t.asyncMessages.length} events` : ""}`,
|
|
629
658
|
value: t,
|
|
630
|
-
}))
|
|
659
|
+
}))
|
|
631
660
|
|
|
632
|
-
const selectedTag = await selectWithTypeAhead(
|
|
661
|
+
const selectedTag = await selectWithTypeAhead(
|
|
662
|
+
"Select an API model (tag):",
|
|
663
|
+
tagOptions,
|
|
664
|
+
)
|
|
633
665
|
|
|
634
666
|
if (!selectedTag) {
|
|
635
|
-
console.log("Cancelled.")
|
|
636
|
-
process.exit(0)
|
|
667
|
+
console.log("Cancelled.")
|
|
668
|
+
process.exit(0)
|
|
637
669
|
}
|
|
638
670
|
|
|
639
|
-
console.log("")
|
|
671
|
+
console.log("")
|
|
640
672
|
|
|
641
673
|
// Step 2: Select paths
|
|
642
674
|
const pathOptions = selectedTag.paths.map((p) => ({
|
|
643
675
|
label: `${p.method} ${p.path}`,
|
|
644
676
|
description: p.summary,
|
|
645
677
|
value: p,
|
|
646
|
-
}))
|
|
678
|
+
}))
|
|
647
679
|
|
|
648
680
|
const selectedPaths = await multiSelectPrompt(
|
|
649
681
|
`Select API endpoints for ${selectedTag.name}:`,
|
|
650
|
-
pathOptions
|
|
651
|
-
)
|
|
682
|
+
pathOptions,
|
|
683
|
+
)
|
|
652
684
|
|
|
653
685
|
if (selectedPaths.length === 0) {
|
|
654
|
-
console.log("\x1B[33m⚠ No endpoints selected. Using all endpoints.\x1B[0m")
|
|
655
|
-
selectedPaths.push(...selectedTag.paths)
|
|
686
|
+
console.log("\x1B[33m⚠ No endpoints selected. Using all endpoints.\x1B[0m")
|
|
687
|
+
selectedPaths.push(...selectedTag.paths)
|
|
656
688
|
}
|
|
657
689
|
|
|
658
|
-
console.log("")
|
|
690
|
+
console.log("")
|
|
659
691
|
|
|
660
692
|
// Step 3: Select async messages (if any)
|
|
661
|
-
let selectedMessages = []
|
|
693
|
+
let selectedMessages = []
|
|
662
694
|
if (selectedTag.asyncMessages.length > 0) {
|
|
663
695
|
const messageOptions = selectedTag.asyncMessages.map((m) => ({
|
|
664
696
|
label: m.name,
|
|
665
697
|
description: m.description,
|
|
666
698
|
value: m,
|
|
667
|
-
}))
|
|
699
|
+
}))
|
|
668
700
|
|
|
669
701
|
selectedMessages = await multiSelectPrompt(
|
|
670
702
|
`Select socket events for ${selectedTag.name}:`,
|
|
671
|
-
messageOptions
|
|
672
|
-
)
|
|
673
|
-
console.log("")
|
|
703
|
+
messageOptions,
|
|
704
|
+
)
|
|
705
|
+
console.log("")
|
|
674
706
|
}
|
|
675
707
|
|
|
676
708
|
// Step 4: Enter identifier
|
|
677
709
|
const defaultIdentifier = selectedTag.name
|
|
678
710
|
.toLowerCase()
|
|
679
711
|
.replace(/[^a-z0-9]+/g, "_")
|
|
680
|
-
.replace(/^_|_$/g, "")
|
|
712
|
+
.replace(/^_|_$/g, "")
|
|
681
713
|
|
|
682
|
-
const identifier = await textInput(
|
|
714
|
+
const identifier = await textInput(
|
|
715
|
+
"Enter dependency identifier:",
|
|
716
|
+
defaultIdentifier,
|
|
717
|
+
)
|
|
683
718
|
|
|
684
719
|
// Collect all permissions from selected paths
|
|
685
|
-
const allPermissions = new Set()
|
|
686
|
-
let permissionKey = null
|
|
720
|
+
const allPermissions = new Set()
|
|
721
|
+
let permissionKey = null
|
|
687
722
|
for (const pathInfo of selectedPaths) {
|
|
688
723
|
if (pathInfo.permission) {
|
|
689
|
-
allPermissions.add(pathInfo.permission)
|
|
724
|
+
allPermissions.add(pathInfo.permission)
|
|
690
725
|
}
|
|
691
726
|
// Get permission_key from first path that has it (should be same for all)
|
|
692
727
|
if (!permissionKey && pathInfo.permissionKey) {
|
|
693
|
-
permissionKey = pathInfo.permissionKey
|
|
728
|
+
permissionKey = pathInfo.permissionKey
|
|
694
729
|
}
|
|
695
730
|
}
|
|
696
731
|
|
|
697
732
|
// Build operations object from selected paths
|
|
698
|
-
const operations = {}
|
|
733
|
+
const operations = {}
|
|
699
734
|
for (const pathInfo of selectedPaths) {
|
|
700
735
|
if (pathInfo.operationId) {
|
|
701
736
|
// Remove "portal.v1.project." prefix from operationId
|
|
702
|
-
const cleanOperationId = pathInfo.operationId.replace(
|
|
737
|
+
const cleanOperationId = pathInfo.operationId.replace(
|
|
738
|
+
/^portal\.v1\.project\./,
|
|
739
|
+
"",
|
|
740
|
+
)
|
|
703
741
|
// Prepend method to path (e.g., "get:/v1/projects/...")
|
|
704
|
-
operations[cleanOperationId] =
|
|
742
|
+
operations[cleanOperationId] =
|
|
743
|
+
`${pathInfo.method.toLowerCase()}:${pathInfo.path}`
|
|
705
744
|
}
|
|
706
745
|
}
|
|
707
746
|
|
|
708
747
|
// Build events object
|
|
709
|
-
const events = {}
|
|
748
|
+
const events = {}
|
|
710
749
|
for (const msg of selectedMessages) {
|
|
711
|
-
events[msg.name] = msg.name
|
|
750
|
+
events[msg.name] = msg.name
|
|
712
751
|
}
|
|
713
752
|
|
|
714
753
|
// Build the dependency object
|
|
@@ -719,48 +758,50 @@ async function addDependencyCommand(argv) {
|
|
|
719
758
|
permissions: Array.from(allPermissions).sort(),
|
|
720
759
|
operations,
|
|
721
760
|
events,
|
|
722
|
-
}
|
|
761
|
+
}
|
|
723
762
|
|
|
724
|
-
console.log("")
|
|
725
|
-
console.log("\x1B[36m─────────────────────────────────────────────\x1B[0m")
|
|
726
|
-
console.log("\x1B[36mGenerated Dependency Configuration:\x1B[0m")
|
|
727
|
-
console.log("\x1B[36m─────────────────────────────────────────────\x1B[0m")
|
|
728
|
-
console.log(JSON.stringify(dependency, null, 2))
|
|
729
|
-
console.log("")
|
|
763
|
+
console.log("")
|
|
764
|
+
console.log("\x1B[36m─────────────────────────────────────────────\x1B[0m")
|
|
765
|
+
console.log("\x1B[36mGenerated Dependency Configuration:\x1B[0m")
|
|
766
|
+
console.log("\x1B[36m─────────────────────────────────────────────\x1B[0m")
|
|
767
|
+
console.log(JSON.stringify(dependency, null, 2))
|
|
768
|
+
console.log("")
|
|
730
769
|
|
|
731
770
|
// Find and update app-manifest.json
|
|
732
|
-
const projectPath = findProjectRoot()
|
|
733
|
-
const manifestPath = path.join(projectPath, "app-manifest.json")
|
|
771
|
+
const projectPath = findProjectRoot()
|
|
772
|
+
const manifestPath = path.join(projectPath, "app-manifest.json")
|
|
734
773
|
|
|
735
774
|
if (!fs.existsSync(manifestPath)) {
|
|
736
|
-
console.log(
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
775
|
+
console.log(
|
|
776
|
+
"\x1B[33m⚠ app-manifest.json not found. Creating new file.\x1B[0m",
|
|
777
|
+
)
|
|
778
|
+
const manifest = { dependencies: [dependency] }
|
|
779
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2))
|
|
780
|
+
console.log(`\x1B[32m✓ Created app-manifest.json with dependency\x1B[0m`)
|
|
740
781
|
} else {
|
|
741
|
-
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"))
|
|
742
|
-
manifest.dependencies = manifest.dependencies || []
|
|
782
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"))
|
|
783
|
+
manifest.dependencies = manifest.dependencies || []
|
|
743
784
|
|
|
744
785
|
// Check for existing dependency with same identifier
|
|
745
786
|
const existingIndex = manifest.dependencies.findIndex(
|
|
746
|
-
(d) => d.identifier === identifier
|
|
747
|
-
)
|
|
787
|
+
(d) => d.identifier === identifier,
|
|
788
|
+
)
|
|
748
789
|
|
|
749
790
|
if (existingIndex >= 0) {
|
|
750
|
-
manifest.dependencies[existingIndex] = dependency
|
|
751
|
-
console.log(`\x1B[32m✓ Updated existing dependency: ${identifier}\x1B[0m`)
|
|
791
|
+
manifest.dependencies[existingIndex] = dependency
|
|
792
|
+
console.log(`\x1B[32m✓ Updated existing dependency: ${identifier}\x1B[0m`)
|
|
752
793
|
} else {
|
|
753
|
-
manifest.dependencies.push(dependency)
|
|
754
|
-
console.log(`\x1B[32m✓ Added new dependency: ${identifier}\x1B[0m`)
|
|
794
|
+
manifest.dependencies.push(dependency)
|
|
795
|
+
console.log(`\x1B[32m✓ Added new dependency: ${identifier}\x1B[0m`)
|
|
755
796
|
}
|
|
756
797
|
|
|
757
|
-
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2))
|
|
798
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2))
|
|
758
799
|
}
|
|
759
800
|
|
|
760
|
-
console.log(`\x1B[32m✓ Saved to ${manifestPath}\x1B[0m`)
|
|
761
|
-
console.log("")
|
|
801
|
+
console.log(`\x1B[32m✓ Saved to ${manifestPath}\x1B[0m`)
|
|
802
|
+
console.log("")
|
|
762
803
|
}
|
|
763
804
|
|
|
764
805
|
module.exports = {
|
|
765
806
|
addDependencyCommand,
|
|
766
|
-
}
|
|
807
|
+
}
|