@gxp-dev/tools 2.0.10 → 2.0.12
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 +448 -180
- package/bin/lib/tui/App.tsx +107 -0
- package/bin/lib/utils/ai-scaffold.js +806 -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 +84 -0
- package/dist/tui/App.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",
|
|
@@ -116,6 +79,21 @@ async function initCommand(argv) {
|
|
|
116
79
|
dest: "src/DemoPage.vue",
|
|
117
80
|
desc: "DemoPage.vue (Example component)",
|
|
118
81
|
},
|
|
82
|
+
{
|
|
83
|
+
src: "default-styling.css",
|
|
84
|
+
dest: "default-styling.css",
|
|
85
|
+
desc: "default-styling.css",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
src: "app-instructions.md",
|
|
89
|
+
dest: "app-instructions.md",
|
|
90
|
+
desc: "app-instructions.md",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
src: "configuration.json",
|
|
94
|
+
dest: "configuration.json",
|
|
95
|
+
desc: "configuration.json",
|
|
96
|
+
},
|
|
119
97
|
{
|
|
120
98
|
src: "app-manifest.json",
|
|
121
99
|
dest: "app-manifest.json",
|
|
@@ -128,6 +106,27 @@ async function initCommand(argv) {
|
|
|
128
106
|
dest: "README.md",
|
|
129
107
|
desc: "README.md (Project documentation)",
|
|
130
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
|
+
},
|
|
131
130
|
];
|
|
132
131
|
|
|
133
132
|
// Copy template files
|
|
@@ -136,12 +135,67 @@ async function initCommand(argv) {
|
|
|
136
135
|
const destPath = path.join(projectPath, file.dest);
|
|
137
136
|
safeCopyFile(srcPath, destPath, file.desc);
|
|
138
137
|
});
|
|
138
|
+
}
|
|
139
139
|
|
|
140
|
-
|
|
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
|
|
141
196
|
const assetsDir = path.join(projectPath, "src", "assets");
|
|
142
197
|
if (!fs.existsSync(assetsDir)) {
|
|
143
198
|
fs.mkdirSync(assetsDir, { recursive: true });
|
|
144
|
-
// Add a .gitkeep to ensure the directory is tracked
|
|
145
199
|
fs.writeFileSync(path.join(assetsDir, ".gitkeep"), "");
|
|
146
200
|
console.log("✓ Created src/assets/ directory for project assets");
|
|
147
201
|
}
|
|
@@ -153,153 +207,367 @@ async function initCommand(argv) {
|
|
|
153
207
|
fs.copyFileSync(envExamplePath, envPath);
|
|
154
208
|
console.log("✓ Created .env file from .env.example");
|
|
155
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));
|
|
156
300
|
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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);
|
|
321
|
+
|
|
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: export GEMINI_API_KEY=your_key or gcloud auth login");
|
|
344
|
+
console.log("");
|
|
345
|
+
aiChoice = "skip";
|
|
346
|
+
} else {
|
|
347
|
+
// Build provider options
|
|
348
|
+
const providerOptions = [
|
|
349
|
+
{ label: "Skip AI scaffolding", value: "skip" },
|
|
350
|
+
];
|
|
351
|
+
|
|
352
|
+
for (const provider of availableProviders) {
|
|
353
|
+
let authInfo = "";
|
|
354
|
+
if (provider.id === "gemini") {
|
|
355
|
+
authInfo = provider.method === "api_key" ? "via API key" : "via gcloud";
|
|
356
|
+
} else {
|
|
357
|
+
authInfo = "logged in";
|
|
358
|
+
}
|
|
359
|
+
providerOptions.push({
|
|
360
|
+
label: `${provider.name}`,
|
|
361
|
+
value: provider.id,
|
|
362
|
+
description: `${authInfo}`,
|
|
363
|
+
});
|
|
162
364
|
}
|
|
163
365
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
"../scripts/launch-chrome.js"
|
|
168
|
-
);
|
|
169
|
-
const launchChromeDest = path.join(scriptsDir, "launch-chrome.js");
|
|
170
|
-
if (fs.existsSync(launchChromeSource)) {
|
|
171
|
-
safeCopyFile(
|
|
172
|
-
launchChromeSource,
|
|
173
|
-
launchChromeDest,
|
|
174
|
-
"Chrome launcher script"
|
|
175
|
-
);
|
|
366
|
+
aiChoice = await arrowSelectPrompt("Choose AI provider for scaffolding", providerOptions);
|
|
367
|
+
if (aiChoice !== "skip") {
|
|
368
|
+
selectedProvider = aiChoice;
|
|
176
369
|
}
|
|
370
|
+
}
|
|
177
371
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
"
|
|
372
|
+
let buildPrompt = "";
|
|
373
|
+
if (selectedProvider) {
|
|
374
|
+
buildPrompt = await multiLinePrompt(
|
|
375
|
+
"📝 Describe your plugin (what it does, key features, UI elements):",
|
|
376
|
+
"Press Enter twice when done"
|
|
182
377
|
);
|
|
183
|
-
|
|
184
|
-
if (
|
|
185
|
-
|
|
378
|
+
|
|
379
|
+
if (buildPrompt) {
|
|
380
|
+
await runAIScaffolding(projectPath, appName, description, buildPrompt, selectedProvider);
|
|
186
381
|
}
|
|
382
|
+
}
|
|
187
383
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
fs.mkdirSync(socketEventsDest, { recursive: true });
|
|
194
|
-
}
|
|
384
|
+
// 4. SSL Setup
|
|
385
|
+
console.log("");
|
|
386
|
+
console.log("─".repeat(50));
|
|
387
|
+
console.log("🔒 SSL Configuration");
|
|
388
|
+
console.log("─".repeat(50));
|
|
195
389
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
390
|
+
const sslChoice = await arrowSelectPrompt("Set up SSL certificates for HTTPS development?", [
|
|
391
|
+
{ label: "Yes, set up SSL", value: "yes", description: "Recommended for full feature access" },
|
|
392
|
+
{ label: "Skip SSL setup", value: "no", description: "Can be set up later with npm run setup-ssl" },
|
|
393
|
+
]);
|
|
394
|
+
|
|
395
|
+
let sslSetup = false;
|
|
396
|
+
if (sslChoice === "yes") {
|
|
397
|
+
console.log("\n🔒 Setting up HTTPS development environment...");
|
|
398
|
+
ensureMkcertInstalled();
|
|
399
|
+
const certs = generateSSLCertificates(projectPath);
|
|
400
|
+
if (certs) {
|
|
401
|
+
updateEnvWithCertPaths(projectPath, certs);
|
|
402
|
+
sslSetup = true;
|
|
204
403
|
}
|
|
205
404
|
}
|
|
206
405
|
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
406
|
+
// 5. Start App
|
|
407
|
+
console.log("");
|
|
408
|
+
console.log("─".repeat(50));
|
|
409
|
+
console.log("🚀 Start Development");
|
|
410
|
+
console.log("─".repeat(50));
|
|
411
|
+
|
|
412
|
+
const startOptions = [
|
|
413
|
+
{ label: "Start app", value: "start", description: sslSetup ? "HTTPS dev server" : "HTTP dev server" },
|
|
414
|
+
{ label: "Start app with Mock API", value: "start-mock", description: "Dev server + Socket.IO + Mock API" },
|
|
415
|
+
{ label: "Skip", value: "skip" },
|
|
416
|
+
];
|
|
215
417
|
|
|
216
|
-
|
|
217
|
-
console.log("\n🔒 Setting up HTTPS development environment...");
|
|
218
|
-
ensureMkcertInstalled();
|
|
219
|
-
const certs = generateSSLCertificates(projectPath);
|
|
418
|
+
const startChoice = await arrowSelectPrompt("How would you like to start the development server?", startOptions);
|
|
220
419
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
420
|
+
let devProcess = null;
|
|
421
|
+
if (startChoice === "start") {
|
|
422
|
+
// 6. Browser Extension (only if starting)
|
|
423
|
+
console.log("");
|
|
424
|
+
const browserChoice = await arrowSelectPrompt("Launch browser with GxP extension?", [
|
|
425
|
+
{ label: "Chrome", value: "chrome", description: "Launch Chrome with DevTools panel" },
|
|
426
|
+
{ label: "Firefox", value: "firefox", description: "Launch Firefox with DevTools panel" },
|
|
427
|
+
{ label: "Skip", value: "skip" },
|
|
428
|
+
]);
|
|
429
|
+
|
|
430
|
+
if (browserChoice !== "skip") {
|
|
431
|
+
// Launch browser first (it will connect when server starts)
|
|
432
|
+
setTimeout(() => {
|
|
433
|
+
launchBrowserExtension(projectPath, browserChoice);
|
|
434
|
+
}, 3000); // Wait a bit for server to start
|
|
229
435
|
}
|
|
230
|
-
}
|
|
231
436
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
437
|
+
devProcess = launchDevServer(projectPath, { noHttps: !sslSetup });
|
|
438
|
+
} else if (startChoice === "start-mock") {
|
|
439
|
+
// 6. Browser Extension (only if starting)
|
|
440
|
+
console.log("");
|
|
441
|
+
const browserChoice = await arrowSelectPrompt("Launch browser with GxP extension?", [
|
|
442
|
+
{ label: "Chrome", value: "chrome", description: "Launch Chrome with DevTools panel" },
|
|
443
|
+
{ label: "Firefox", value: "firefox", description: "Launch Firefox with DevTools panel" },
|
|
444
|
+
{ label: "Skip", value: "skip" },
|
|
445
|
+
]);
|
|
241
446
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
447
|
+
if (browserChoice !== "skip") {
|
|
448
|
+
setTimeout(() => {
|
|
449
|
+
launchBrowserExtension(projectPath, browserChoice);
|
|
450
|
+
}, 3000);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
devProcess = launchDevServer(projectPath, { withMock: true, noHttps: !sslSetup });
|
|
246
454
|
} else {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
console.log("🔒 Then use HTTPS development: npm run dev");
|
|
455
|
+
// Print final instructions
|
|
456
|
+
printFinalInstructions(projectPath, appName, sslSetup);
|
|
250
457
|
}
|
|
458
|
+
|
|
459
|
+
return devProcess;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Print final setup instructions
|
|
464
|
+
* @param {string} projectPath - Project path
|
|
465
|
+
* @param {string} projectName - Project name
|
|
466
|
+
* @param {boolean} sslSetup - Whether SSL was set up
|
|
467
|
+
*/
|
|
468
|
+
function printFinalInstructions(projectPath, projectName, sslSetup) {
|
|
469
|
+
console.log("");
|
|
470
|
+
console.log("─".repeat(50));
|
|
471
|
+
console.log("✅ Project setup complete!");
|
|
472
|
+
console.log("─".repeat(50));
|
|
473
|
+
console.log("");
|
|
474
|
+
console.log("🎨 GX ComponentKit component library included!");
|
|
475
|
+
console.log("🗃️ GxP Datastore included with Pinia integration!");
|
|
476
|
+
console.log("");
|
|
477
|
+
console.log(`📁 Project location: ${projectPath}`);
|
|
251
478
|
console.log("");
|
|
252
479
|
console.log("📖 Project structure:");
|
|
253
480
|
console.log(" • src/Plugin.vue - Your app entry point (customize this!)");
|
|
254
481
|
console.log(" • src/DemoPage.vue - Example component");
|
|
255
482
|
console.log(" • theme-layouts/ - Customizable layout templates");
|
|
256
|
-
console.log(
|
|
257
|
-
|
|
258
|
-
);
|
|
259
|
-
console.log(
|
|
483
|
+
console.log(" • app-manifest.json - Plugin configuration");
|
|
484
|
+
console.log("");
|
|
485
|
+
console.log("🚀 To start development:");
|
|
486
|
+
console.log(` cd ${projectName}`);
|
|
487
|
+
if (sslSetup) {
|
|
488
|
+
console.log(" npm run dev # HTTPS with TUI");
|
|
489
|
+
console.log(" npm run dev-http # HTTP only");
|
|
490
|
+
} else {
|
|
491
|
+
console.log(" npm run dev-http # HTTP dev server");
|
|
492
|
+
console.log(" npm run setup-ssl # Then npm run dev for HTTPS");
|
|
493
|
+
}
|
|
494
|
+
console.log("");
|
|
495
|
+
console.log("📚 Documentation: https://docs.gramercytech.com/gxp-toolkit");
|
|
496
|
+
console.log("");
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Initialize command - sets up a new GxP project or updates existing one
|
|
501
|
+
*/
|
|
502
|
+
async function initCommand(argv) {
|
|
503
|
+
const currentDir = process.cwd();
|
|
504
|
+
const hasPackageJson = fs.existsSync(path.join(currentDir, "package.json"));
|
|
505
|
+
let projectPath = currentDir;
|
|
506
|
+
let projectName;
|
|
507
|
+
|
|
508
|
+
// Handle existing project update
|
|
509
|
+
if (hasPackageJson && !argv.name) {
|
|
510
|
+
console.log("Updating existing project...");
|
|
511
|
+
updateExistingProject(projectPath);
|
|
512
|
+
console.log("✅ Project updated!");
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
260
515
|
|
|
261
|
-
//
|
|
262
|
-
if (
|
|
516
|
+
// New project - require a name
|
|
517
|
+
if (!argv.name) {
|
|
263
518
|
console.log("");
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
);
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
console.log(`\n🚀 Launching gxdev TUI in ${projectName}...`);
|
|
272
|
-
// Change to project directory and launch TUI
|
|
273
|
-
process.chdir(projectPath);
|
|
274
|
-
|
|
275
|
-
// Try to launch TUI
|
|
276
|
-
const tuiPath = path.join(
|
|
277
|
-
__dirname,
|
|
278
|
-
"..",
|
|
279
|
-
"..",
|
|
280
|
-
"..",
|
|
281
|
-
"dist",
|
|
282
|
-
"tui",
|
|
283
|
-
"index.js"
|
|
284
|
-
);
|
|
285
|
-
if (fs.existsSync(tuiPath)) {
|
|
286
|
-
try {
|
|
287
|
-
const { startTUI } = await import(tuiPath);
|
|
288
|
-
startTUI({ autoStart: [], args: {} });
|
|
289
|
-
} catch (err) {
|
|
290
|
-
console.error("Could not launch TUI:", err.message);
|
|
291
|
-
console.log(`\nTo start manually:\n cd ${projectName}\n gxdev`);
|
|
292
|
-
}
|
|
293
|
-
} else {
|
|
294
|
-
console.log(
|
|
295
|
-
'TUI not available. Run "npm run build:tui" in gx-devtools first.'
|
|
296
|
-
);
|
|
297
|
-
console.log(`\nTo start manually:\n cd ${projectName}\n gxdev`);
|
|
298
|
-
}
|
|
299
|
-
} else {
|
|
300
|
-
console.log(`\nTo get started:\n cd ${projectName}\n gxdev`);
|
|
519
|
+
console.log("🚀 GxP Plugin Creator");
|
|
520
|
+
console.log("─".repeat(40));
|
|
521
|
+
console.log("");
|
|
522
|
+
projectName = await promptUser("📝 Project name: ");
|
|
523
|
+
if (!projectName) {
|
|
524
|
+
console.error("❌ Project name is required!");
|
|
525
|
+
process.exit(1);
|
|
301
526
|
}
|
|
527
|
+
} else {
|
|
528
|
+
projectName = argv.name;
|
|
302
529
|
}
|
|
530
|
+
|
|
531
|
+
// Create project directory
|
|
532
|
+
projectPath = path.join(currentDir, projectName);
|
|
533
|
+
if (fs.existsSync(projectPath)) {
|
|
534
|
+
console.error(`\n❌ Directory ${projectName} already exists!`);
|
|
535
|
+
process.exit(1);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
console.log("");
|
|
539
|
+
console.log(`📁 Creating project: ${projectName}`);
|
|
540
|
+
console.log("─".repeat(40));
|
|
541
|
+
fs.mkdirSync(projectPath, { recursive: true });
|
|
542
|
+
|
|
543
|
+
// Create package.json
|
|
544
|
+
const initialDescription = argv.description || "A GxP kiosk plugin";
|
|
545
|
+
createPackageJson(projectPath, projectName, initialDescription);
|
|
546
|
+
|
|
547
|
+
// Copy template files
|
|
548
|
+
const paths = resolveGxPaths();
|
|
549
|
+
copyTemplateFiles(projectPath, paths);
|
|
550
|
+
copyExtensionScripts(projectPath, paths);
|
|
551
|
+
createSupportingFiles(projectPath);
|
|
552
|
+
|
|
553
|
+
// Install dependencies
|
|
554
|
+
console.log("");
|
|
555
|
+
installDependencies(projectPath);
|
|
556
|
+
|
|
557
|
+
// Change to project directory
|
|
558
|
+
process.chdir(projectPath);
|
|
559
|
+
|
|
560
|
+
// If CLI provided build prompt, skip interactive and just run AI
|
|
561
|
+
if (argv.build) {
|
|
562
|
+
updateAppManifest(projectPath, projectName, initialDescription);
|
|
563
|
+
const provider = argv.provider || "gemini"; // Default to gemini for backward compatibility
|
|
564
|
+
await runAIScaffolding(projectPath, projectName, initialDescription, argv.build, provider);
|
|
565
|
+
printFinalInstructions(projectPath, projectName, false);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Run interactive configuration
|
|
570
|
+
await runInteractiveConfig(projectPath, projectName);
|
|
303
571
|
}
|
|
304
572
|
|
|
305
573
|
module.exports = {
|