@gxp-dev/tools 2.0.11 → 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 +433 -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",
|
|
@@ -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
|
+
}
|
|
139
|
+
|
|
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
|
+
}
|
|
154
160
|
|
|
155
|
-
//
|
|
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,367 @@ 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
|
+
]);
|
|
171
317
|
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
+
});
|
|
177
364
|
}
|
|
178
365
|
|
|
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
|
-
);
|
|
366
|
+
aiChoice = await arrowSelectPrompt("Choose AI provider for scaffolding", providerOptions);
|
|
367
|
+
if (aiChoice !== "skip") {
|
|
368
|
+
selectedProvider = aiChoice;
|
|
191
369
|
}
|
|
370
|
+
}
|
|
192
371
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
"
|
|
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"
|
|
197
377
|
);
|
|
198
|
-
|
|
199
|
-
if (
|
|
200
|
-
|
|
378
|
+
|
|
379
|
+
if (buildPrompt) {
|
|
380
|
+
await runAIScaffolding(projectPath, appName, description, buildPrompt, selectedProvider);
|
|
201
381
|
}
|
|
382
|
+
}
|
|
202
383
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
fs.mkdirSync(socketEventsDest, { recursive: true });
|
|
209
|
-
}
|
|
384
|
+
// 4. SSL Setup
|
|
385
|
+
console.log("");
|
|
386
|
+
console.log("─".repeat(50));
|
|
387
|
+
console.log("🔒 SSL Configuration");
|
|
388
|
+
console.log("─".repeat(50));
|
|
210
389
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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;
|
|
219
403
|
}
|
|
220
404
|
}
|
|
221
405
|
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
);
|
|
228
|
-
sslSetup =
|
|
229
|
-
sslChoice.toLowerCase() !== "n" && sslChoice.toLowerCase() !== "no";
|
|
406
|
+
// 5. Start App
|
|
407
|
+
console.log("");
|
|
408
|
+
console.log("─".repeat(50));
|
|
409
|
+
console.log("🚀 Start Development");
|
|
410
|
+
console.log("─".repeat(50));
|
|
230
411
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
+
];
|
|
235
417
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
418
|
+
const startChoice = await arrowSelectPrompt("How would you like to start the development server?", startOptions);
|
|
419
|
+
|
|
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
|
|
244
435
|
}
|
|
245
|
-
}
|
|
246
436
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
+
]);
|
|
256
446
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
447
|
+
if (browserChoice !== "skip") {
|
|
448
|
+
setTimeout(() => {
|
|
449
|
+
launchBrowserExtension(projectPath, browserChoice);
|
|
450
|
+
}, 3000);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
devProcess = launchDevServer(projectPath, { withMock: true, noHttps: !sslSetup });
|
|
261
454
|
} else {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
console.log("🔒 Then use HTTPS development: npm run dev");
|
|
455
|
+
// Print final instructions
|
|
456
|
+
printFinalInstructions(projectPath, appName, sslSetup);
|
|
265
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}`);
|
|
266
478
|
console.log("");
|
|
267
479
|
console.log("📖 Project structure:");
|
|
268
480
|
console.log(" • src/Plugin.vue - Your app entry point (customize this!)");
|
|
269
481
|
console.log(" • src/DemoPage.vue - Example component");
|
|
270
482
|
console.log(" • theme-layouts/ - Customizable layout templates");
|
|
271
|
-
console.log(
|
|
272
|
-
|
|
273
|
-
);
|
|
274
|
-
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
|
+
}
|
|
275
515
|
|
|
276
|
-
//
|
|
277
|
-
if (
|
|
516
|
+
// New project - require a name
|
|
517
|
+
if (!argv.name) {
|
|
278
518
|
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`);
|
|
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);
|
|
316
526
|
}
|
|
527
|
+
} else {
|
|
528
|
+
projectName = argv.name;
|
|
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;
|
|
317
567
|
}
|
|
568
|
+
|
|
569
|
+
// Run interactive configuration
|
|
570
|
+
await runInteractiveConfig(projectPath, projectName);
|
|
318
571
|
}
|
|
319
572
|
|
|
320
573
|
module.exports = {
|