@gxp-dev/tools 2.0.11 → 2.0.13
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/bin/lib/cli.js +42 -0
- package/bin/lib/commands/extract-config.js +186 -0
- package/bin/lib/commands/index.js +2 -0
- package/bin/lib/commands/init.js +446 -180
- package/bin/lib/tui/App.tsx +158 -41
- package/bin/lib/tui/components/CommandInput.tsx +100 -95
- package/bin/lib/tui/components/LogPanel.tsx +30 -17
- package/bin/lib/tui/components/TabBar.tsx +1 -1
- package/bin/lib/utils/ai-scaffold.js +877 -0
- package/bin/lib/utils/extract-config.js +468 -0
- package/bin/lib/utils/files.js +43 -2
- package/bin/lib/utils/index.js +4 -0
- package/bin/lib/utils/prompts.js +352 -0
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +134 -40
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/components/CommandInput.d.ts.map +1 -1
- package/dist/tui/components/CommandInput.js +87 -89
- package/dist/tui/components/CommandInput.js.map +1 -1
- package/dist/tui/components/LogPanel.d.ts +4 -2
- package/dist/tui/components/LogPanel.d.ts.map +1 -1
- package/dist/tui/components/LogPanel.js +25 -15
- package/dist/tui/components/LogPanel.js.map +1 -1
- package/dist/tui/components/TabBar.js +1 -1
- package/dist/tui/components/TabBar.js.map +1 -1
- package/mcp/gxp-api-server.js +524 -0
- package/package.json +4 -2
- package/runtime/stores/gxpPortalConfigStore.js +9 -0
- package/template/.claude/agents/gxp-developer.md +335 -0
- package/template/.claude/settings.json +9 -0
- package/template/AGENTS.md +125 -0
- package/template/GEMINI.md +80 -0
- package/template/app-manifest.json +1 -0
package/bin/lib/commands/init.js
CHANGED
|
@@ -1,85 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Init Command
|
|
3
3
|
*
|
|
4
|
-
* Sets up a new GxP project
|
|
4
|
+
* Sets up a new GxP project with interactive configuration.
|
|
5
|
+
*
|
|
6
|
+
* Flow:
|
|
7
|
+
* 1. Create project directory with provided name
|
|
8
|
+
* 2. Copy template files and install dependencies
|
|
9
|
+
* 3. Run interactive configuration:
|
|
10
|
+
* - App name (prepopulated from package.json)
|
|
11
|
+
* - Description (prepopulated from package.json)
|
|
12
|
+
* - AI scaffolding (optional)
|
|
13
|
+
* 4. Prompt to start the app
|
|
14
|
+
* 5. Prompt to launch browser with extension
|
|
5
15
|
*/
|
|
6
16
|
|
|
7
17
|
const path = require("path");
|
|
8
18
|
const fs = require("fs");
|
|
19
|
+
const { spawn } = require("child_process");
|
|
9
20
|
const { REQUIRED_DEPENDENCIES } = require("../constants");
|
|
10
21
|
const {
|
|
11
22
|
findProjectRoot,
|
|
12
23
|
resolveGxPaths,
|
|
13
24
|
promptUser,
|
|
25
|
+
arrowSelectPrompt,
|
|
26
|
+
inputWithDefault,
|
|
27
|
+
multiLinePrompt,
|
|
14
28
|
safeCopyFile,
|
|
15
29
|
createPackageJson,
|
|
30
|
+
updateAppManifest,
|
|
16
31
|
installDependencies,
|
|
17
32
|
updateExistingProject,
|
|
18
33
|
ensureMkcertInstalled,
|
|
19
34
|
generateSSLCertificates,
|
|
20
35
|
updateEnvWithCertPaths,
|
|
36
|
+
runAIScaffolding,
|
|
37
|
+
getAvailableProviders,
|
|
21
38
|
} = require("../utils");
|
|
22
39
|
|
|
23
40
|
/**
|
|
24
|
-
*
|
|
41
|
+
* Copy template files to project
|
|
42
|
+
* @param {string} projectPath - Target project path
|
|
43
|
+
* @param {object} paths - Resolved GxP paths
|
|
25
44
|
*/
|
|
26
|
-
|
|
27
|
-
const currentDir = process.cwd();
|
|
28
|
-
const hasPackageJson = fs.existsSync(path.join(currentDir, "package.json"));
|
|
29
|
-
let projectPath = currentDir;
|
|
30
|
-
let projectName;
|
|
31
|
-
let sslSetup = false;
|
|
32
|
-
|
|
33
|
-
if (!hasPackageJson && !argv.name) {
|
|
34
|
-
// New project - prompt for name
|
|
35
|
-
projectName = await promptUser("Enter project name: ");
|
|
36
|
-
if (!projectName) {
|
|
37
|
-
console.error("Project name is required!");
|
|
38
|
-
process.exit(1);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Create project directory
|
|
42
|
-
projectPath = path.join(currentDir, projectName);
|
|
43
|
-
if (fs.existsSync(projectPath)) {
|
|
44
|
-
console.error(`Directory ${projectName} already exists!`);
|
|
45
|
-
process.exit(1);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
console.log(`Creating new project: ${projectName}`);
|
|
49
|
-
fs.mkdirSync(projectPath, { recursive: true });
|
|
50
|
-
|
|
51
|
-
// Create package.json
|
|
52
|
-
createPackageJson(projectPath, projectName);
|
|
53
|
-
|
|
54
|
-
// Install dependencies
|
|
55
|
-
installDependencies(projectPath);
|
|
56
|
-
} else if (hasPackageJson) {
|
|
57
|
-
// Existing project - update it
|
|
58
|
-
console.log("Updating existing project...");
|
|
59
|
-
updateExistingProject(projectPath);
|
|
60
|
-
} else if (argv.name) {
|
|
61
|
-
// New project with provided name
|
|
62
|
-
projectName = argv.name;
|
|
63
|
-
projectPath = path.join(currentDir, projectName);
|
|
64
|
-
|
|
65
|
-
if (fs.existsSync(projectPath)) {
|
|
66
|
-
console.error(`Directory ${projectName} already exists!`);
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
console.log(`Creating new project: ${projectName}`);
|
|
71
|
-
fs.mkdirSync(projectPath, { recursive: true });
|
|
72
|
-
createPackageJson(projectPath, projectName);
|
|
73
|
-
installDependencies(projectPath);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Copy template files
|
|
77
|
-
// Note: PortalContainer.vue (formerly App.vue) is now in runtime/ and accessed via @gx-runtime alias
|
|
78
|
-
// Users don't need a copy - it's immutable and loaded from node_modules
|
|
79
|
-
const paths = resolveGxPaths();
|
|
80
|
-
// Note: main.js, index.html, and vite.config.js are NOT copied by default.
|
|
81
|
-
// They are served from the runtime directory. Users can publish them
|
|
82
|
-
// for customization using: gxdev publish main.js / index.html / vite.config.js
|
|
45
|
+
function copyTemplateFiles(projectPath, paths) {
|
|
83
46
|
const filesToCopy = [
|
|
84
47
|
{
|
|
85
48
|
src: "theme-layouts/SystemLayout.vue",
|
|
@@ -143,6 +106,27 @@ async function initCommand(argv) {
|
|
|
143
106
|
dest: "README.md",
|
|
144
107
|
desc: "README.md (Project documentation)",
|
|
145
108
|
},
|
|
109
|
+
// AI Agent configuration files
|
|
110
|
+
{
|
|
111
|
+
src: "AGENTS.md",
|
|
112
|
+
dest: "AGENTS.md",
|
|
113
|
+
desc: "AGENTS.md (Codex/AI agent instructions)",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
src: "GEMINI.md",
|
|
117
|
+
dest: "GEMINI.md",
|
|
118
|
+
desc: "GEMINI.md (Gemini Code Assist instructions)",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
src: ".claude/agents/gxp-developer.md",
|
|
122
|
+
dest: ".claude/agents/gxp-developer.md",
|
|
123
|
+
desc: "Claude Code subagent (GxP developer)",
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
src: ".claude/settings.json",
|
|
127
|
+
dest: ".claude/settings.json",
|
|
128
|
+
desc: "Claude Code MCP settings (GxP API server)",
|
|
129
|
+
},
|
|
146
130
|
];
|
|
147
131
|
|
|
148
132
|
// Copy template files
|
|
@@ -151,12 +135,67 @@ async function initCommand(argv) {
|
|
|
151
135
|
const destPath = path.join(projectPath, file.dest);
|
|
152
136
|
safeCopyFile(srcPath, destPath, file.desc);
|
|
153
137
|
});
|
|
138
|
+
}
|
|
154
139
|
|
|
155
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Copy extension scripts to project
|
|
142
|
+
* @param {string} projectPath - Target project path
|
|
143
|
+
* @param {object} paths - Resolved GxP paths
|
|
144
|
+
*/
|
|
145
|
+
function copyExtensionScripts(projectPath, paths) {
|
|
146
|
+
const scriptsDir = path.join(projectPath, "scripts");
|
|
147
|
+
if (!fs.existsSync(scriptsDir)) {
|
|
148
|
+
fs.mkdirSync(scriptsDir, { recursive: true });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Copy launch-chrome.js script
|
|
152
|
+
const launchChromeSource = path.join(
|
|
153
|
+
paths.templateDir,
|
|
154
|
+
"../scripts/launch-chrome.js"
|
|
155
|
+
);
|
|
156
|
+
const launchChromeDest = path.join(scriptsDir, "launch-chrome.js");
|
|
157
|
+
if (fs.existsSync(launchChromeSource)) {
|
|
158
|
+
safeCopyFile(launchChromeSource, launchChromeDest, "Chrome launcher script");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Copy pack-chrome.js script
|
|
162
|
+
const packChromeSource = path.join(
|
|
163
|
+
paths.templateDir,
|
|
164
|
+
"../scripts/pack-chrome.js"
|
|
165
|
+
);
|
|
166
|
+
const packChromeDest = path.join(scriptsDir, "pack-chrome.js");
|
|
167
|
+
if (fs.existsSync(packChromeSource)) {
|
|
168
|
+
safeCopyFile(packChromeSource, packChromeDest, "Chrome packaging script");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Copy socket events directory
|
|
172
|
+
const socketEventsSource = paths.socketEventsDir;
|
|
173
|
+
const socketEventsDest = path.join(projectPath, "socket-events");
|
|
174
|
+
if (fs.existsSync(socketEventsSource)) {
|
|
175
|
+
if (!fs.existsSync(socketEventsDest)) {
|
|
176
|
+
fs.mkdirSync(socketEventsDest, { recursive: true });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const eventFiles = fs
|
|
180
|
+
.readdirSync(socketEventsSource)
|
|
181
|
+
.filter((file) => file.endsWith(".json"));
|
|
182
|
+
eventFiles.forEach((file) => {
|
|
183
|
+
const srcPath = path.join(socketEventsSource, file);
|
|
184
|
+
const destPath = path.join(socketEventsDest, file);
|
|
185
|
+
safeCopyFile(srcPath, destPath, `Socket event: ${file}`);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Create supporting directories and files
|
|
192
|
+
* @param {string} projectPath - Target project path
|
|
193
|
+
*/
|
|
194
|
+
function createSupportingFiles(projectPath) {
|
|
195
|
+
// Create /src/assets/ directory
|
|
156
196
|
const assetsDir = path.join(projectPath, "src", "assets");
|
|
157
197
|
if (!fs.existsSync(assetsDir)) {
|
|
158
198
|
fs.mkdirSync(assetsDir, { recursive: true });
|
|
159
|
-
// Add a .gitkeep to ensure the directory is tracked
|
|
160
199
|
fs.writeFileSync(path.join(assetsDir, ".gitkeep"), "");
|
|
161
200
|
console.log("✓ Created src/assets/ directory for project assets");
|
|
162
201
|
}
|
|
@@ -168,153 +207,380 @@ async function initCommand(argv) {
|
|
|
168
207
|
fs.copyFileSync(envExamplePath, envPath);
|
|
169
208
|
console.log("✓ Created .env file from .env.example");
|
|
170
209
|
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Read project info from package.json
|
|
214
|
+
* @param {string} projectPath - Project path
|
|
215
|
+
* @returns {object} - { name, description }
|
|
216
|
+
*/
|
|
217
|
+
function readProjectInfo(projectPath) {
|
|
218
|
+
const packageJsonPath = path.join(projectPath, "package.json");
|
|
219
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
220
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
221
|
+
return {
|
|
222
|
+
name: pkg.name || "",
|
|
223
|
+
description: pkg.description || "",
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
return { name: "", description: "" };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Update package.json with new name and description
|
|
231
|
+
* @param {string} projectPath - Project path
|
|
232
|
+
* @param {string} name - New name
|
|
233
|
+
* @param {string} description - New description
|
|
234
|
+
*/
|
|
235
|
+
function updatePackageJson(projectPath, name, description) {
|
|
236
|
+
const packageJsonPath = path.join(projectPath, "package.json");
|
|
237
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
238
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
239
|
+
pkg.name = name;
|
|
240
|
+
pkg.description = description;
|
|
241
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, "\t"));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Launch the development server
|
|
247
|
+
* @param {string} projectPath - Project path
|
|
248
|
+
* @param {object} options - { withMock: boolean, noHttps: boolean }
|
|
249
|
+
* @returns {ChildProcess} - The spawned process
|
|
250
|
+
*/
|
|
251
|
+
function launchDevServer(projectPath, options = {}) {
|
|
252
|
+
const args = ["run"];
|
|
253
|
+
|
|
254
|
+
if (options.withMock) {
|
|
255
|
+
args.push("dev");
|
|
256
|
+
// The dev script with mock runs through TUI
|
|
257
|
+
} else {
|
|
258
|
+
args.push(options.noHttps ? "dev-http" : "dev");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
console.log(`\n🚀 Starting development server...`);
|
|
262
|
+
|
|
263
|
+
const child = spawn("npm", args, {
|
|
264
|
+
cwd: projectPath,
|
|
265
|
+
stdio: "inherit",
|
|
266
|
+
shell: true,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
return child;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Launch browser with extension
|
|
274
|
+
* @param {string} projectPath - Project path
|
|
275
|
+
* @param {string} browser - 'chrome' or 'firefox'
|
|
276
|
+
*/
|
|
277
|
+
function launchBrowserExtension(projectPath, browser) {
|
|
278
|
+
console.log(`\n🌐 Launching ${browser} with GxP extension...`);
|
|
279
|
+
|
|
280
|
+
const gxdevPath = path.join(__dirname, "..", "..", "gx-devtools.js");
|
|
281
|
+
|
|
282
|
+
spawn("node", [gxdevPath, `ext:${browser}`], {
|
|
283
|
+
cwd: projectPath,
|
|
284
|
+
stdio: "inherit",
|
|
285
|
+
shell: true,
|
|
286
|
+
detached: true,
|
|
287
|
+
}).unref();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Run interactive configuration after project creation
|
|
292
|
+
* @param {string} projectPath - Path to project directory
|
|
293
|
+
* @param {string} initialName - Initial project name from CLI
|
|
294
|
+
*/
|
|
295
|
+
async function runInteractiveConfig(projectPath, initialName) {
|
|
296
|
+
console.log("");
|
|
297
|
+
console.log("─".repeat(50));
|
|
298
|
+
console.log("📝 Configure Your Plugin");
|
|
299
|
+
console.log("─".repeat(50));
|
|
300
|
+
|
|
301
|
+
// Read current values from package.json
|
|
302
|
+
const projectInfo = readProjectInfo(projectPath);
|
|
303
|
+
const defaultName = projectInfo.name || initialName || path.basename(projectPath);
|
|
304
|
+
const defaultDescription = projectInfo.description || "A GxP kiosk plugin";
|
|
305
|
+
|
|
306
|
+
// 1. App Name - with prepopulated value and custom option
|
|
307
|
+
const appName = await arrowSelectPrompt("App name", [
|
|
308
|
+
{ label: defaultName, value: defaultName, description: "From package.json" },
|
|
309
|
+
{ label: "Enter custom name", value: "__custom__", isCustomInput: true, defaultValue: defaultName },
|
|
310
|
+
]);
|
|
311
|
+
|
|
312
|
+
// 2. Description - with prepopulated value and custom option
|
|
313
|
+
const description = await arrowSelectPrompt("Description", [
|
|
314
|
+
{ label: defaultDescription, value: defaultDescription, description: "From package.json" },
|
|
315
|
+
{ label: "Enter custom description", value: "__custom__", isCustomInput: true, defaultValue: "" },
|
|
316
|
+
]);
|
|
317
|
+
|
|
318
|
+
// Update package.json and app-manifest with new values
|
|
319
|
+
updatePackageJson(projectPath, appName, description);
|
|
320
|
+
updateAppManifest(projectPath, appName, description);
|
|
171
321
|
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
322
|
+
// 3. AI Scaffolding
|
|
323
|
+
console.log("");
|
|
324
|
+
console.log("─".repeat(50));
|
|
325
|
+
console.log("🤖 AI-Powered Scaffolding");
|
|
326
|
+
console.log("─".repeat(50));
|
|
327
|
+
console.log(" Describe what you want to build and AI will generate");
|
|
328
|
+
console.log(" starter components, views, and manifest configuration.");
|
|
329
|
+
console.log("");
|
|
330
|
+
|
|
331
|
+
// Check available AI providers
|
|
332
|
+
const providers = await getAvailableProviders();
|
|
333
|
+
const availableProviders = providers.filter((p) => p.available);
|
|
334
|
+
|
|
335
|
+
let aiChoice = "skip";
|
|
336
|
+
let selectedProvider = null;
|
|
337
|
+
|
|
338
|
+
if (availableProviders.length === 0) {
|
|
339
|
+
console.log(" ⚠️ No AI providers available.");
|
|
340
|
+
console.log(" To enable AI scaffolding, set up one of:");
|
|
341
|
+
console.log(" • Claude CLI: npm install -g @anthropic-ai/claude-code && claude login");
|
|
342
|
+
console.log(" • Codex CLI: npm install -g @openai/codex && codex auth");
|
|
343
|
+
console.log(" • Gemini CLI: npm install -g @google/gemini-cli && gemini");
|
|
344
|
+
console.log(" • Gemini API: export GEMINI_API_KEY=your_key");
|
|
345
|
+
console.log("");
|
|
346
|
+
aiChoice = "skip";
|
|
347
|
+
} else {
|
|
348
|
+
// Build provider options
|
|
349
|
+
const providerOptions = [
|
|
350
|
+
{ label: "Skip AI scaffolding", value: "skip" },
|
|
351
|
+
];
|
|
352
|
+
|
|
353
|
+
for (const provider of availableProviders) {
|
|
354
|
+
let authInfo = "";
|
|
355
|
+
if (provider.id === "gemini") {
|
|
356
|
+
switch (provider.method) {
|
|
357
|
+
case "cli":
|
|
358
|
+
authInfo = "logged in";
|
|
359
|
+
break;
|
|
360
|
+
case "api_key":
|
|
361
|
+
authInfo = "via API key";
|
|
362
|
+
break;
|
|
363
|
+
case "gcloud":
|
|
364
|
+
authInfo = "via gcloud";
|
|
365
|
+
break;
|
|
366
|
+
default:
|
|
367
|
+
authInfo = "";
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
authInfo = "logged in";
|
|
371
|
+
}
|
|
372
|
+
providerOptions.push({
|
|
373
|
+
label: `${provider.name}`,
|
|
374
|
+
value: provider.id,
|
|
375
|
+
description: `${authInfo}`,
|
|
376
|
+
});
|
|
177
377
|
}
|
|
178
378
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
"../scripts/launch-chrome.js"
|
|
183
|
-
);
|
|
184
|
-
const launchChromeDest = path.join(scriptsDir, "launch-chrome.js");
|
|
185
|
-
if (fs.existsSync(launchChromeSource)) {
|
|
186
|
-
safeCopyFile(
|
|
187
|
-
launchChromeSource,
|
|
188
|
-
launchChromeDest,
|
|
189
|
-
"Chrome launcher script"
|
|
190
|
-
);
|
|
379
|
+
aiChoice = await arrowSelectPrompt("Choose AI provider for scaffolding", providerOptions);
|
|
380
|
+
if (aiChoice !== "skip") {
|
|
381
|
+
selectedProvider = aiChoice;
|
|
191
382
|
}
|
|
383
|
+
}
|
|
192
384
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
"
|
|
385
|
+
let buildPrompt = "";
|
|
386
|
+
if (selectedProvider) {
|
|
387
|
+
buildPrompt = await multiLinePrompt(
|
|
388
|
+
"📝 Describe your plugin (what it does, key features, UI elements):",
|
|
389
|
+
"Press Enter twice when done"
|
|
197
390
|
);
|
|
198
|
-
|
|
199
|
-
if (
|
|
200
|
-
|
|
391
|
+
|
|
392
|
+
if (buildPrompt) {
|
|
393
|
+
await runAIScaffolding(projectPath, appName, description, buildPrompt, selectedProvider);
|
|
201
394
|
}
|
|
395
|
+
}
|
|
202
396
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
fs.mkdirSync(socketEventsDest, { recursive: true });
|
|
209
|
-
}
|
|
397
|
+
// 4. SSL Setup
|
|
398
|
+
console.log("");
|
|
399
|
+
console.log("─".repeat(50));
|
|
400
|
+
console.log("🔒 SSL Configuration");
|
|
401
|
+
console.log("─".repeat(50));
|
|
210
402
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
403
|
+
const sslChoice = await arrowSelectPrompt("Set up SSL certificates for HTTPS development?", [
|
|
404
|
+
{ label: "Yes, set up SSL", value: "yes", description: "Recommended for full feature access" },
|
|
405
|
+
{ label: "Skip SSL setup", value: "no", description: "Can be set up later with npm run setup-ssl" },
|
|
406
|
+
]);
|
|
407
|
+
|
|
408
|
+
let sslSetup = false;
|
|
409
|
+
if (sslChoice === "yes") {
|
|
410
|
+
console.log("\n🔒 Setting up HTTPS development environment...");
|
|
411
|
+
ensureMkcertInstalled();
|
|
412
|
+
const certs = generateSSLCertificates(projectPath);
|
|
413
|
+
if (certs) {
|
|
414
|
+
updateEnvWithCertPaths(projectPath, certs);
|
|
415
|
+
sslSetup = true;
|
|
219
416
|
}
|
|
220
417
|
}
|
|
221
418
|
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
);
|
|
228
|
-
sslSetup =
|
|
229
|
-
sslChoice.toLowerCase() !== "n" && sslChoice.toLowerCase() !== "no";
|
|
419
|
+
// 5. Start App
|
|
420
|
+
console.log("");
|
|
421
|
+
console.log("─".repeat(50));
|
|
422
|
+
console.log("🚀 Start Development");
|
|
423
|
+
console.log("─".repeat(50));
|
|
230
424
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
425
|
+
const startOptions = [
|
|
426
|
+
{ label: "Start app", value: "start", description: sslSetup ? "HTTPS dev server" : "HTTP dev server" },
|
|
427
|
+
{ label: "Start app with Mock API", value: "start-mock", description: "Dev server + Socket.IO + Mock API" },
|
|
428
|
+
{ label: "Skip", value: "skip" },
|
|
429
|
+
];
|
|
235
430
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
431
|
+
const startChoice = await arrowSelectPrompt("How would you like to start the development server?", startOptions);
|
|
432
|
+
|
|
433
|
+
let devProcess = null;
|
|
434
|
+
if (startChoice === "start") {
|
|
435
|
+
// 6. Browser Extension (only if starting)
|
|
436
|
+
console.log("");
|
|
437
|
+
const browserChoice = await arrowSelectPrompt("Launch browser with GxP extension?", [
|
|
438
|
+
{ label: "Chrome", value: "chrome", description: "Launch Chrome with DevTools panel" },
|
|
439
|
+
{ label: "Firefox", value: "firefox", description: "Launch Firefox with DevTools panel" },
|
|
440
|
+
{ label: "Skip", value: "skip" },
|
|
441
|
+
]);
|
|
442
|
+
|
|
443
|
+
if (browserChoice !== "skip") {
|
|
444
|
+
// Launch browser first (it will connect when server starts)
|
|
445
|
+
setTimeout(() => {
|
|
446
|
+
launchBrowserExtension(projectPath, browserChoice);
|
|
447
|
+
}, 3000); // Wait a bit for server to start
|
|
244
448
|
}
|
|
245
|
-
}
|
|
246
449
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
450
|
+
devProcess = launchDevServer(projectPath, { noHttps: !sslSetup });
|
|
451
|
+
} else if (startChoice === "start-mock") {
|
|
452
|
+
// 6. Browser Extension (only if starting)
|
|
453
|
+
console.log("");
|
|
454
|
+
const browserChoice = await arrowSelectPrompt("Launch browser with GxP extension?", [
|
|
455
|
+
{ label: "Chrome", value: "chrome", description: "Launch Chrome with DevTools panel" },
|
|
456
|
+
{ label: "Firefox", value: "firefox", description: "Launch Firefox with DevTools panel" },
|
|
457
|
+
{ label: "Skip", value: "skip" },
|
|
458
|
+
]);
|
|
256
459
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
460
|
+
if (browserChoice !== "skip") {
|
|
461
|
+
setTimeout(() => {
|
|
462
|
+
launchBrowserExtension(projectPath, browserChoice);
|
|
463
|
+
}, 3000);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
devProcess = launchDevServer(projectPath, { withMock: true, noHttps: !sslSetup });
|
|
261
467
|
} else {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
console.log("🔒 Then use HTTPS development: npm run dev");
|
|
468
|
+
// Print final instructions
|
|
469
|
+
printFinalInstructions(projectPath, appName, sslSetup);
|
|
265
470
|
}
|
|
471
|
+
|
|
472
|
+
return devProcess;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Print final setup instructions
|
|
477
|
+
* @param {string} projectPath - Project path
|
|
478
|
+
* @param {string} projectName - Project name
|
|
479
|
+
* @param {boolean} sslSetup - Whether SSL was set up
|
|
480
|
+
*/
|
|
481
|
+
function printFinalInstructions(projectPath, projectName, sslSetup) {
|
|
482
|
+
console.log("");
|
|
483
|
+
console.log("─".repeat(50));
|
|
484
|
+
console.log("✅ Project setup complete!");
|
|
485
|
+
console.log("─".repeat(50));
|
|
486
|
+
console.log("");
|
|
487
|
+
console.log("🎨 GX ComponentKit component library included!");
|
|
488
|
+
console.log("🗃️ GxP Datastore included with Pinia integration!");
|
|
489
|
+
console.log("");
|
|
490
|
+
console.log(`📁 Project location: ${projectPath}`);
|
|
266
491
|
console.log("");
|
|
267
492
|
console.log("📖 Project structure:");
|
|
268
493
|
console.log(" • src/Plugin.vue - Your app entry point (customize this!)");
|
|
269
494
|
console.log(" • src/DemoPage.vue - Example component");
|
|
270
495
|
console.log(" • theme-layouts/ - Customizable layout templates");
|
|
271
|
-
console.log(
|
|
272
|
-
|
|
273
|
-
);
|
|
274
|
-
console.log(
|
|
496
|
+
console.log(" • app-manifest.json - Plugin configuration");
|
|
497
|
+
console.log("");
|
|
498
|
+
console.log("🚀 To start development:");
|
|
499
|
+
console.log(` cd ${projectName}`);
|
|
500
|
+
if (sslSetup) {
|
|
501
|
+
console.log(" npm run dev # HTTPS with TUI");
|
|
502
|
+
console.log(" npm run dev-http # HTTP only");
|
|
503
|
+
} else {
|
|
504
|
+
console.log(" npm run dev-http # HTTP dev server");
|
|
505
|
+
console.log(" npm run setup-ssl # Then npm run dev for HTTPS");
|
|
506
|
+
}
|
|
507
|
+
console.log("");
|
|
508
|
+
console.log("📚 Documentation: https://docs.gramercytech.com/gxp-toolkit");
|
|
509
|
+
console.log("");
|
|
510
|
+
}
|
|
275
511
|
|
|
276
|
-
|
|
277
|
-
|
|
512
|
+
/**
|
|
513
|
+
* Initialize command - sets up a new GxP project or updates existing one
|
|
514
|
+
*/
|
|
515
|
+
async function initCommand(argv) {
|
|
516
|
+
const currentDir = process.cwd();
|
|
517
|
+
const hasPackageJson = fs.existsSync(path.join(currentDir, "package.json"));
|
|
518
|
+
let projectPath = currentDir;
|
|
519
|
+
let projectName;
|
|
520
|
+
|
|
521
|
+
// Handle existing project update
|
|
522
|
+
if (hasPackageJson && !argv.name) {
|
|
523
|
+
console.log("Updating existing project...");
|
|
524
|
+
updateExistingProject(projectPath);
|
|
525
|
+
console.log("✅ Project updated!");
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// New project - require a name
|
|
530
|
+
if (!argv.name) {
|
|
278
531
|
console.log("");
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
);
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
console.log(`\n🚀 Launching gxdev TUI in ${projectName}...`);
|
|
287
|
-
// Change to project directory and launch TUI
|
|
288
|
-
process.chdir(projectPath);
|
|
289
|
-
|
|
290
|
-
// Try to launch TUI
|
|
291
|
-
const tuiPath = path.join(
|
|
292
|
-
__dirname,
|
|
293
|
-
"..",
|
|
294
|
-
"..",
|
|
295
|
-
"..",
|
|
296
|
-
"dist",
|
|
297
|
-
"tui",
|
|
298
|
-
"index.js"
|
|
299
|
-
);
|
|
300
|
-
if (fs.existsSync(tuiPath)) {
|
|
301
|
-
try {
|
|
302
|
-
const { startTUI } = await import(tuiPath);
|
|
303
|
-
startTUI({ autoStart: [], args: {} });
|
|
304
|
-
} catch (err) {
|
|
305
|
-
console.error("Could not launch TUI:", err.message);
|
|
306
|
-
console.log(`\nTo start manually:\n cd ${projectName}\n gxdev`);
|
|
307
|
-
}
|
|
308
|
-
} else {
|
|
309
|
-
console.log(
|
|
310
|
-
'TUI not available. Run "npm run build:tui" in gx-devtools first.'
|
|
311
|
-
);
|
|
312
|
-
console.log(`\nTo start manually:\n cd ${projectName}\n gxdev`);
|
|
313
|
-
}
|
|
314
|
-
} else {
|
|
315
|
-
console.log(`\nTo get started:\n cd ${projectName}\n gxdev`);
|
|
532
|
+
console.log("🚀 GxP Plugin Creator");
|
|
533
|
+
console.log("─".repeat(40));
|
|
534
|
+
console.log("");
|
|
535
|
+
projectName = await promptUser("📝 Project name: ");
|
|
536
|
+
if (!projectName) {
|
|
537
|
+
console.error("❌ Project name is required!");
|
|
538
|
+
process.exit(1);
|
|
316
539
|
}
|
|
540
|
+
} else {
|
|
541
|
+
projectName = argv.name;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Create project directory
|
|
545
|
+
projectPath = path.join(currentDir, projectName);
|
|
546
|
+
if (fs.existsSync(projectPath)) {
|
|
547
|
+
console.error(`\n❌ Directory ${projectName} already exists!`);
|
|
548
|
+
process.exit(1);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
console.log("");
|
|
552
|
+
console.log(`📁 Creating project: ${projectName}`);
|
|
553
|
+
console.log("─".repeat(40));
|
|
554
|
+
fs.mkdirSync(projectPath, { recursive: true });
|
|
555
|
+
|
|
556
|
+
// Create package.json
|
|
557
|
+
const initialDescription = argv.description || "A GxP kiosk plugin";
|
|
558
|
+
createPackageJson(projectPath, projectName, initialDescription);
|
|
559
|
+
|
|
560
|
+
// Copy template files
|
|
561
|
+
const paths = resolveGxPaths();
|
|
562
|
+
copyTemplateFiles(projectPath, paths);
|
|
563
|
+
copyExtensionScripts(projectPath, paths);
|
|
564
|
+
createSupportingFiles(projectPath);
|
|
565
|
+
|
|
566
|
+
// Install dependencies
|
|
567
|
+
console.log("");
|
|
568
|
+
installDependencies(projectPath);
|
|
569
|
+
|
|
570
|
+
// Change to project directory
|
|
571
|
+
process.chdir(projectPath);
|
|
572
|
+
|
|
573
|
+
// If CLI provided build prompt, skip interactive and just run AI
|
|
574
|
+
if (argv.build) {
|
|
575
|
+
updateAppManifest(projectPath, projectName, initialDescription);
|
|
576
|
+
const provider = argv.provider || "gemini"; // Default to gemini for backward compatibility
|
|
577
|
+
await runAIScaffolding(projectPath, projectName, initialDescription, argv.build, provider);
|
|
578
|
+
printFinalInstructions(projectPath, projectName, false);
|
|
579
|
+
return;
|
|
317
580
|
}
|
|
581
|
+
|
|
582
|
+
// Run interactive configuration
|
|
583
|
+
await runInteractiveConfig(projectPath, projectName);
|
|
318
584
|
}
|
|
319
585
|
|
|
320
586
|
module.exports = {
|