@chargebee/chargebee-apps 0.0.1
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/LICENSE +24 -0
- package/README.md +297 -0
- package/SECURITY.md +8 -0
- package/bin/index.js +1 -0
- package/dist/api/iparams.routes.d.ts +6 -0
- package/dist/api/iparams.routes.js +73 -0
- package/dist/api/routes.d.ts +39 -0
- package/dist/api/routes.js +339 -0
- package/dist/commands/create.d.ts +30 -0
- package/dist/commands/create.js +102 -0
- package/dist/commands/index.d.ts +7 -0
- package/dist/commands/index.js +93 -0
- package/dist/commands/package.d.ts +20 -0
- package/dist/commands/package.js +93 -0
- package/dist/commands/run.d.ts +25 -0
- package/dist/commands/run.js +106 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/registry.d.ts +17 -0
- package/dist/registry.js +39 -0
- package/package.json +70 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.setupServer = setupServer;
|
|
7
|
+
exports.invokeEventHandler = invokeEventHandler;
|
|
8
|
+
exports.setupApiRoutes = setupApiRoutes;
|
|
9
|
+
exports.setupAppRoutes = setupAppRoutes;
|
|
10
|
+
const chargebee_apps_libs_1 = require("@chargebee/chargebee-apps-libs");
|
|
11
|
+
const chargebee_apps_shared_1 = require("@chargebee/chargebee-apps-shared");
|
|
12
|
+
const express_1 = __importDefault(require("express"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const registry_1 = require("../registry");
|
|
15
|
+
const iparams_routes_1 = require("./iparams.routes");
|
|
16
|
+
// Create a singleton instance of PathResolver
|
|
17
|
+
const pathResolver = new chargebee_apps_shared_1.PathResolver();
|
|
18
|
+
// Get UI directory paths using centralized path resolver
|
|
19
|
+
const WEB_STATIC_DIR = pathResolver.getPublicLibsUiWebDir();
|
|
20
|
+
// Configurable path variables
|
|
21
|
+
const PATHS = {
|
|
22
|
+
WEB_STATIC_DIR,
|
|
23
|
+
MAIN_HTML_FILE: 'index.html',
|
|
24
|
+
HANDLER_FILE: 'handler/handler.js',
|
|
25
|
+
MANIFEST_FILE: 'manifest.json',
|
|
26
|
+
TEST_DATA_DIR: 'test_data',
|
|
27
|
+
};
|
|
28
|
+
const REGISTRY = registry_1.PublicCliRegistry.getInstance();
|
|
29
|
+
const __logger = REGISTRY.logger;
|
|
30
|
+
/**
|
|
31
|
+
* Sets up and start an Express server for the serverless app tester
|
|
32
|
+
* @param {number} port - The port number to run the server on
|
|
33
|
+
* @param {string} userCodeDir - The user code directory where logs should be stored
|
|
34
|
+
* @param {CBFileSystem} fileSystem - The filesystem implementation to use
|
|
35
|
+
* @param {CBProcess} processService - The process implementation to use
|
|
36
|
+
* @param {DependencyManager} dependencyManager - The dependency manager implementation to use
|
|
37
|
+
*/
|
|
38
|
+
function setupServer(port, userCodeDir, fileSystem = REGISTRY.fileSystem, processService = REGISTRY.process, dependencyManager = REGISTRY.dependencyManager) {
|
|
39
|
+
const app = (0, express_1.default)();
|
|
40
|
+
app.disable('x-powered-by');
|
|
41
|
+
// Create a single CBLogger instance for the entire server session
|
|
42
|
+
const sessionLogger = (0, chargebee_apps_libs_1.createCBPublicLogger)(fileSystem, processService, userCodeDir);
|
|
43
|
+
// CORS middleware
|
|
44
|
+
app.use((_req, res, next) => {
|
|
45
|
+
res.header('Access-Control-Allow-Origin', '*');
|
|
46
|
+
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
47
|
+
res.header('Access-Control-Allow-Headers', 'Content-Type');
|
|
48
|
+
next();
|
|
49
|
+
});
|
|
50
|
+
// JSON parsing
|
|
51
|
+
app.use(express_1.default.json());
|
|
52
|
+
// Static files
|
|
53
|
+
app.use(express_1.default.static(PATHS.WEB_STATIC_DIR));
|
|
54
|
+
// API Routes
|
|
55
|
+
setupApiRoutes(app, sessionLogger, userCodeDir, fileSystem, processService, dependencyManager);
|
|
56
|
+
// Iparams Routes
|
|
57
|
+
(0, iparams_routes_1.setupIparamsRoutes)(app, userCodeDir, fileSystem);
|
|
58
|
+
// App Routes
|
|
59
|
+
setupAppRoutes(app, fileSystem);
|
|
60
|
+
// Start server
|
|
61
|
+
const server = app.listen(port, () => {
|
|
62
|
+
__logger.info(`Server is running on \x1b[4mhttp://localhost:${port}\x1b[0m`);
|
|
63
|
+
});
|
|
64
|
+
// Graceful shutdown
|
|
65
|
+
processService.on('SIGINT', () => {
|
|
66
|
+
__logger.info('Shutting down server...');
|
|
67
|
+
server.close(() => {
|
|
68
|
+
__logger.info('Server stopped!');
|
|
69
|
+
processService.exit(0);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
processService.on('uncaughtException', async (err) => {
|
|
73
|
+
__logger.error(`Uncaught exception thrown: ${err.message}`);
|
|
74
|
+
if (err && err.stack) {
|
|
75
|
+
__logger.error(`Stack Trace: ${err.stack}`);
|
|
76
|
+
}
|
|
77
|
+
processService.exit(1);
|
|
78
|
+
});
|
|
79
|
+
// Handle server errors
|
|
80
|
+
server.on('error', (error) => {
|
|
81
|
+
if (error.code === 'EADDRINUSE') {
|
|
82
|
+
__logger.error(`Port ${port} is already in use. Try a different port with --port option.`);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
__logger.error('Server error:', error);
|
|
86
|
+
}
|
|
87
|
+
processService.exit(1);
|
|
88
|
+
});
|
|
89
|
+
return server;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Invokes an event handler based on the event data
|
|
93
|
+
* @param {EventRecord} eventData - The event data containing event_type and payload
|
|
94
|
+
* @param {ICBLogger} sessionLogger - The logger instance for this server session
|
|
95
|
+
* @param {string} userCodeDir - The user code directory
|
|
96
|
+
* @param {CBFileSystem} fileSystem - The filesystem implementation to use
|
|
97
|
+
* @param {DependencyManager} dependencyManager - The dependency manager implementation to use
|
|
98
|
+
* @returns {Promise<any>} The result from the handler function
|
|
99
|
+
* @throws {Error} If handler file, manifest, or handler function is not found
|
|
100
|
+
*/
|
|
101
|
+
async function invokeEventHandler(eventData, sessionLogger, userCodeDir, fileSystem, dependencyManager) {
|
|
102
|
+
const iparamsService = new chargebee_apps_shared_1.IparamsService(fileSystem);
|
|
103
|
+
const hasIparamsFile = iparamsService.iparamsFileExists(userCodeDir);
|
|
104
|
+
let iparams;
|
|
105
|
+
if (hasIparamsFile) {
|
|
106
|
+
try {
|
|
107
|
+
iparams = iparamsService.validateAndGetInputsForExecution(userCodeDir);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
if (error instanceof chargebee_apps_shared_1.MissingRequiredParamsError) {
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
const err = error;
|
|
114
|
+
return {
|
|
115
|
+
success: false,
|
|
116
|
+
error: `Parameters validation failed: ${err.message}`,
|
|
117
|
+
stack: err.stack
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const configLoader = new chargebee_apps_libs_1.ConfigLoader(fileSystem);
|
|
122
|
+
const allowedModules = configLoader.loadAllowedModules();
|
|
123
|
+
const packageValidator = new chargebee_apps_shared_1.PackageValidator(fileSystem);
|
|
124
|
+
const manifestValidator = new chargebee_apps_shared_1.ManifestValidator(allowedModules, fileSystem);
|
|
125
|
+
const sandbox = new chargebee_apps_libs_1.PublicSandboxWrapper(allowedModules, fileSystem, packageValidator, dependencyManager, manifestValidator);
|
|
126
|
+
const payload = hasIparamsFile
|
|
127
|
+
? { event: eventData, iparams: iparams ?? {} }
|
|
128
|
+
: { event: eventData };
|
|
129
|
+
return await sandbox.execute(userCodeDir, payload, sessionLogger);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Sets up all API routes for the serverless app tester
|
|
133
|
+
* @param {Application} app - Express application instance
|
|
134
|
+
* @param {ICBLogger} sessionLogger - The logger instance for this server session
|
|
135
|
+
* @param {string} userCodeDir - The user code directory
|
|
136
|
+
* @param {CBFileSystem} fileSystem - The filesystem implementation to use
|
|
137
|
+
* @param {CBProcess} process - The process implementation to use
|
|
138
|
+
* @param {DependencyManager} dependencyManager - The dependency manager implementation to use
|
|
139
|
+
*/
|
|
140
|
+
function setupApiRoutes(app, sessionLogger, userCodeDir, fileSystem, process, dependencyManager) {
|
|
141
|
+
// Get list of available test data files
|
|
142
|
+
app.get('/api/manifest', (_req, res, next) => {
|
|
143
|
+
const testDataDir = path_1.default.join(process.cwd(), PATHS.TEST_DATA_DIR);
|
|
144
|
+
const manifestPath = path_1.default.join(process.cwd(), PATHS.MANIFEST_FILE);
|
|
145
|
+
const handlerPath = path_1.default.join(process.cwd(), PATHS.HANDLER_FILE);
|
|
146
|
+
try {
|
|
147
|
+
// Check if manifest.json exists
|
|
148
|
+
const configLoader = new chargebee_apps_libs_1.ConfigLoader(fileSystem);
|
|
149
|
+
const allowedModules = configLoader.loadAllowedModules();
|
|
150
|
+
const manifestValidator = new chargebee_apps_shared_1.ManifestValidator(allowedModules, fileSystem);
|
|
151
|
+
const manifestData = manifestValidator.validateAndGetManifestFile(manifestPath);
|
|
152
|
+
// Load manifest to get configured events
|
|
153
|
+
const configuredEvents = Object.keys(manifestData.events || {});
|
|
154
|
+
// Check if handler.js exists
|
|
155
|
+
const handlerExists = fileSystem.existsSync(handlerPath);
|
|
156
|
+
// Check if test data directory exists
|
|
157
|
+
const testDataExists = fileSystem.existsSync(testDataDir);
|
|
158
|
+
let testDataFiles = [];
|
|
159
|
+
if (testDataExists) {
|
|
160
|
+
testDataFiles = fileSystem.readdirSync(testDataDir)
|
|
161
|
+
.filter((file) => file.endsWith('.json'))
|
|
162
|
+
.map((file) => file.replace('.json', ''));
|
|
163
|
+
}
|
|
164
|
+
// Analyze each configured event
|
|
165
|
+
const events = configuredEvents.map(eventType => {
|
|
166
|
+
const eventConfig = manifestData.events[eventType];
|
|
167
|
+
const handlerName = eventConfig ? eventConfig.handler : null;
|
|
168
|
+
const hasTestData = testDataFiles.includes(eventType);
|
|
169
|
+
// Check if handler function exists in handler.js
|
|
170
|
+
let hasHandlerFunction = false;
|
|
171
|
+
if (handlerExists && handlerName) {
|
|
172
|
+
try {
|
|
173
|
+
delete require.cache[require.resolve(handlerPath)];
|
|
174
|
+
const handlerModule = require(handlerPath);
|
|
175
|
+
hasHandlerFunction = typeof handlerModule[handlerName] === 'function';
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
__logger.error(`Error checking handler function ${handlerName}:`, error.message);
|
|
179
|
+
hasHandlerFunction = false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
__logger.info(`Handler check skipped for ${eventType}: handlerExists=${handlerExists}, handlerName=${handlerName}`);
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
event_type: eventType,
|
|
187
|
+
handler: {
|
|
188
|
+
name: handlerName,
|
|
189
|
+
exists: hasHandlerFunction,
|
|
190
|
+
},
|
|
191
|
+
has_test_data: hasTestData,
|
|
192
|
+
};
|
|
193
|
+
});
|
|
194
|
+
// Return all configured events with their status
|
|
195
|
+
const response = {
|
|
196
|
+
events: events,
|
|
197
|
+
dependencies: manifestData.dependencies || {}
|
|
198
|
+
};
|
|
199
|
+
res.json(response);
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
__logger.error('Error: Failed to get manifest', error);
|
|
203
|
+
if (error instanceof Error && error.message && error.message.includes('manifest.json not found')) {
|
|
204
|
+
__logger.error(`Manifest file not found: ${manifestPath}`);
|
|
205
|
+
res.status(404).json({
|
|
206
|
+
error: 'Manifest file not found',
|
|
207
|
+
message: error.message
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
res.status(500).json({
|
|
212
|
+
error: 'Internal server error',
|
|
213
|
+
message: error instanceof Error && error.message ? error.message : error
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
next(error);
|
|
217
|
+
res.status(500).json({ error: 'Failed to read test data directory' });
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
// Get specific test data file
|
|
221
|
+
app.get('/api/test-data/:eventType', (req, res) => {
|
|
222
|
+
const { eventType } = req.params;
|
|
223
|
+
if (!eventType) {
|
|
224
|
+
res.status(400).json({ error: 'Event type is required' });
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const sanitizedEventType = path_1.default.basename(eventType);
|
|
228
|
+
// Validate: ensure eventType doesn't contain path separators
|
|
229
|
+
if (sanitizedEventType !== eventType || /[\/\\]/.test(eventType)) {
|
|
230
|
+
res.status(400).json({
|
|
231
|
+
error: 'Invalid event type',
|
|
232
|
+
message: 'Event type must not contain path separators'
|
|
233
|
+
});
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
// Construct paths
|
|
237
|
+
const testDataDir = path_1.default.resolve(process.cwd(), PATHS.TEST_DATA_DIR);
|
|
238
|
+
const testDataFile = path_1.default.resolve(testDataDir, `${sanitizedEventType}.json`);
|
|
239
|
+
// Security check: Verify the resolved path is within the test_data directory
|
|
240
|
+
if (!testDataFile.startsWith(testDataDir + path_1.default.sep)) {
|
|
241
|
+
res.status(400).json({
|
|
242
|
+
error: 'Invalid event type',
|
|
243
|
+
message: 'Access denied'
|
|
244
|
+
});
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
try {
|
|
248
|
+
if (fileSystem.existsSync(testDataFile)) {
|
|
249
|
+
const data = fileSystem.readFileSync(testDataFile, 'utf8');
|
|
250
|
+
res.json(JSON.parse(data));
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
res.status(404).json({
|
|
254
|
+
error: `Test data for event '${sanitizedEventType}' not found`,
|
|
255
|
+
path: testDataFile,
|
|
256
|
+
message: 'Check if the test_data directory exists in your app directory'
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
res.status(500).json({ error: 'Failed to read test data file' });
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
// Invoke serverless function
|
|
265
|
+
app.post('/api/invoke', async (req, res) => {
|
|
266
|
+
try {
|
|
267
|
+
const eventData = req.body;
|
|
268
|
+
const result = await invokeEventHandler(eventData, sessionLogger, userCodeDir, fileSystem, dependencyManager);
|
|
269
|
+
if (result.success) {
|
|
270
|
+
return res.json({ status: 'success' });
|
|
271
|
+
}
|
|
272
|
+
__logger.error(`✗ Handler execution failed: ${result.error}`);
|
|
273
|
+
return res.status(500).json({ error: 'Uncaught exception thrown while invoking function', message: result.error, stack: result.stack });
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
if (error instanceof chargebee_apps_shared_1.MissingRequiredParamsError) {
|
|
277
|
+
const paramText = error.missing.length > 1 ? 'parameters' : 'parameter';
|
|
278
|
+
__logger.error(`✗ Missing required ${paramText}: ${error.missing.join(', ')}. Please provide values for these parameters in iparams.local.json`);
|
|
279
|
+
return res.status(400).json({
|
|
280
|
+
error: 'Parameters validation failed',
|
|
281
|
+
message: error.message,
|
|
282
|
+
missingParams: error.missing,
|
|
283
|
+
showPopup: true
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
__logger.error('Error: Failed to invoke event handler', error);
|
|
287
|
+
const errMsg = error instanceof Error ? error.message : '';
|
|
288
|
+
const errorMap = {
|
|
289
|
+
'Handler function': { status: 400, error: 'Handler function not found' },
|
|
290
|
+
'No handler configured': { status: 400, error: 'No handler configured' },
|
|
291
|
+
'handler.js not found': { status: 404, error: 'Handler file not found' },
|
|
292
|
+
'manifest.json not found': { status: 404, error: 'Manifest file not found' }
|
|
293
|
+
};
|
|
294
|
+
for (const [key, { status, error: errorMsg }] of Object.entries(errorMap)) {
|
|
295
|
+
if (errMsg.includes(key)) {
|
|
296
|
+
return res.status(status).json({ error: errorMsg, message: errMsg });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return res.status(500).json({
|
|
300
|
+
error: 'Internal server error',
|
|
301
|
+
message: errMsg || error
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
// Handle 404 for API routes
|
|
306
|
+
app.all('/api', (_req, res) => {
|
|
307
|
+
__logger.info('API endpoint not found');
|
|
308
|
+
res.status(404).json({ error: 'API endpoint not found' });
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Sets up application routes for serving the web UI
|
|
313
|
+
* @param {Application} app - Express application instance
|
|
314
|
+
* @param {CBFileSystem} fileSystem - The filesystem implementation to use
|
|
315
|
+
*/
|
|
316
|
+
function setupAppRoutes(app, fileSystem) {
|
|
317
|
+
// Serve index.html for all other routes (SPA support)
|
|
318
|
+
app.get('/', (_req, res) => {
|
|
319
|
+
const indexPath = path_1.default.join(PATHS.WEB_STATIC_DIR, PATHS.MAIN_HTML_FILE);
|
|
320
|
+
if (fileSystem.existsSync(indexPath)) {
|
|
321
|
+
res.sendFile(indexPath);
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
res.status(404).send('Web UI not found. Please check the file path.');
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
// Error handling middleware
|
|
328
|
+
app.use((error, _req, res, _next) => {
|
|
329
|
+
// Only log and respond if headers haven't been sent yet
|
|
330
|
+
if (!res.headersSent) {
|
|
331
|
+
__logger.error('Error: Failed to handle middleware', error);
|
|
332
|
+
res.status(500).json({
|
|
333
|
+
error: 'Internal server error',
|
|
334
|
+
message: error.message
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
// If headers already sent, error was already handled - don't log again
|
|
338
|
+
});
|
|
339
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { CBFileSystem, CBProcess, CreateOptions } from '@chargebee/chargebee-apps-shared';
|
|
2
|
+
/**
|
|
3
|
+
* Main create command class
|
|
4
|
+
*/
|
|
5
|
+
export declare class CreateCommand {
|
|
6
|
+
private templateService;
|
|
7
|
+
private fileSystem;
|
|
8
|
+
private process;
|
|
9
|
+
constructor(fileSystem?: CBFileSystem, process?: CBProcess);
|
|
10
|
+
/**
|
|
11
|
+
* Creates a new serverless application from a template
|
|
12
|
+
* @param {CreateOptions} options - The options for the create command
|
|
13
|
+
* @param {string} appDir - The directory where the app will be created
|
|
14
|
+
*/
|
|
15
|
+
execute(options: CreateOptions, appDir: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* Validates the app directory
|
|
18
|
+
* @param targetDir - The directory where the app will be created
|
|
19
|
+
* @param appDir - The original app directory argument
|
|
20
|
+
* @param template - The template name to use for scaffolding
|
|
21
|
+
*/
|
|
22
|
+
private validate;
|
|
23
|
+
/**
|
|
24
|
+
* Creates a new serverless application from a template
|
|
25
|
+
* @param targetDir - The directory where the app will be created
|
|
26
|
+
* @param template - The template name to use for scaffolding
|
|
27
|
+
*/
|
|
28
|
+
private createProject;
|
|
29
|
+
}
|
|
30
|
+
export declare const createCommand: CreateCommand;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createCommand = exports.CreateCommand = void 0;
|
|
7
|
+
const chargebee_apps_shared_1 = require("@chargebee/chargebee-apps-shared");
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const registry_1 = require("../registry");
|
|
10
|
+
// Create a singleton instance of PathResolver
|
|
11
|
+
const pathResolver = new chargebee_apps_shared_1.PathResolver();
|
|
12
|
+
/**
|
|
13
|
+
* Main create command class
|
|
14
|
+
*/
|
|
15
|
+
class CreateCommand {
|
|
16
|
+
constructor(fileSystem, process) {
|
|
17
|
+
const registry = registry_1.PublicCliRegistry.getInstance();
|
|
18
|
+
this.fileSystem = fileSystem || registry.fileSystem;
|
|
19
|
+
this.process = process || registry.process;
|
|
20
|
+
// Get templates directory path using centralized path resolver
|
|
21
|
+
const templatesPath = pathResolver.getPublicLibsTemplatesDir();
|
|
22
|
+
this.templateService = new chargebee_apps_shared_1.TemplateService(this.fileSystem, templatesPath);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Creates a new serverless application from a template
|
|
26
|
+
* @param {CreateOptions} options - The options for the create command
|
|
27
|
+
* @param {string} appDir - The directory where the app will be created
|
|
28
|
+
*/
|
|
29
|
+
execute(options, appDir) {
|
|
30
|
+
const template = options.template;
|
|
31
|
+
chargebee_apps_shared_1.__logger.info('Creating new application...');
|
|
32
|
+
const targetDir = path_1.default.resolve(appDir);
|
|
33
|
+
// Validate app directory
|
|
34
|
+
this.validate(targetDir, appDir, template);
|
|
35
|
+
// Create project
|
|
36
|
+
this.createProject(targetDir, template);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Validates the app directory
|
|
40
|
+
* @param targetDir - The directory where the app will be created
|
|
41
|
+
* @param appDir - The original app directory argument
|
|
42
|
+
* @param template - The template name to use for scaffolding
|
|
43
|
+
*/
|
|
44
|
+
validate(targetDir, appDir, template) {
|
|
45
|
+
const defaultTemplate = 'serverless-node-starter-app';
|
|
46
|
+
// Show which template is being used
|
|
47
|
+
if (template === defaultTemplate) {
|
|
48
|
+
chargebee_apps_shared_1.__logger.info(`Using default template: ${template}`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
chargebee_apps_shared_1.__logger.info(`Using template: ${template}`);
|
|
52
|
+
}
|
|
53
|
+
// Validate template
|
|
54
|
+
if (!this.templateService.validateTemplate(template)) {
|
|
55
|
+
const availableTemplates = this.templateService.getAvailableTemplates();
|
|
56
|
+
chargebee_apps_shared_1.__logger.error(`Error: Template '${template}' not found.`);
|
|
57
|
+
chargebee_apps_shared_1.__logger.error(`Available templates: ${availableTemplates.join(', ')}`);
|
|
58
|
+
this.process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
// Validate app directory
|
|
61
|
+
try {
|
|
62
|
+
const conflicts = this.templateService.validateAppDirectory(targetDir, appDir, template);
|
|
63
|
+
if (conflicts.length > 0) {
|
|
64
|
+
chargebee_apps_shared_1.__logger.error(`Error: The following files already exist in the current directory:`);
|
|
65
|
+
conflicts.forEach(file => chargebee_apps_shared_1.__logger.error(` - ${file}`));
|
|
66
|
+
chargebee_apps_shared_1.__logger.error(`Please remove these files or use a different directory.`);
|
|
67
|
+
this.process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
chargebee_apps_shared_1.__logger.error(`Error: ${error.message}`);
|
|
72
|
+
chargebee_apps_shared_1.__logger.error(`Use a different directory name or clear the existing directory first.`);
|
|
73
|
+
this.process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Creates a new serverless application from a template
|
|
78
|
+
* @param targetDir - The directory where the app will be created
|
|
79
|
+
* @param template - The template name to use for scaffolding
|
|
80
|
+
*/
|
|
81
|
+
createProject(targetDir, template) {
|
|
82
|
+
try {
|
|
83
|
+
if (!this.fileSystem.existsSync(targetDir)) {
|
|
84
|
+
this.fileSystem.mkdirSync(targetDir, { recursive: true });
|
|
85
|
+
}
|
|
86
|
+
// Copy template files
|
|
87
|
+
const templatePath = this.templateService.getTemplatePath(template);
|
|
88
|
+
this.templateService.copyTemplateFiles(templatePath, targetDir);
|
|
89
|
+
chargebee_apps_shared_1.__logger.success(`Successfully created app in '${targetDir}' using template '${template}'`);
|
|
90
|
+
chargebee_apps_shared_1.__logger.info(`Directory: ${targetDir}`);
|
|
91
|
+
chargebee_apps_shared_1.__logger.info('Project structure:');
|
|
92
|
+
this.templateService.printProjectStructure(targetDir);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
chargebee_apps_shared_1.__logger.error(`Error creating app: ${error}`);
|
|
96
|
+
this.process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
exports.CreateCommand = CreateCommand;
|
|
101
|
+
// Export for use in commands/index.ts
|
|
102
|
+
exports.createCommand = new CreateCommand();
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.setupCLI = setupCLI;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const package_json_1 = __importDefault(require("../../package.json"));
|
|
9
|
+
const create_1 = require("./create");
|
|
10
|
+
const package_1 = require("./package");
|
|
11
|
+
const run_1 = require("./run");
|
|
12
|
+
const chargebee_apps_shared_1 = require("@chargebee/chargebee-apps-shared");
|
|
13
|
+
const registry_1 = require("../registry");
|
|
14
|
+
/**
|
|
15
|
+
* Sets up the CLI program with all available commands
|
|
16
|
+
* @returns {Command} The configured CLI program
|
|
17
|
+
*/
|
|
18
|
+
function setupCLI() {
|
|
19
|
+
const registry = registry_1.PublicCliRegistry.getInstance();
|
|
20
|
+
const pathResolver = new chargebee_apps_shared_1.PathResolver();
|
|
21
|
+
const templatesPath = pathResolver.getPublicLibsTemplatesDir();
|
|
22
|
+
const templateService = new chargebee_apps_shared_1.TemplateService(registry.fileSystem, templatesPath);
|
|
23
|
+
const availableTemplates = templateService.getAvailableTemplates();
|
|
24
|
+
const program = new commander_1.Command();
|
|
25
|
+
program
|
|
26
|
+
.name('chargebee-apps')
|
|
27
|
+
.description('Chargebee Apps CLI - Create, test, and package serverless applications')
|
|
28
|
+
.version(package_json_1.default.version)
|
|
29
|
+
.usage('<command> <directory> [options]')
|
|
30
|
+
.showHelpAfterError()
|
|
31
|
+
.showSuggestionAfterError()
|
|
32
|
+
.addHelpCommand()
|
|
33
|
+
.configureHelp({
|
|
34
|
+
sortSubcommands: true,
|
|
35
|
+
sortOptions: true,
|
|
36
|
+
subcommandTerm: (cmd) => {
|
|
37
|
+
const args = cmd.registeredArguments.map(arg => {
|
|
38
|
+
return arg.required ? `<${arg.name()}>` : `[${arg.name()}]`;
|
|
39
|
+
}).join(' ');
|
|
40
|
+
const hasOptions = cmd.options.length > 0;
|
|
41
|
+
return `${cmd.name()}${args ? ' ' + args : ''}${hasOptions ? ' [options]' : ''}`;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
program
|
|
45
|
+
.command('create')
|
|
46
|
+
.usage('<directory> [options]')
|
|
47
|
+
.summary('Create a new application')
|
|
48
|
+
.description('Create a new application from a template')
|
|
49
|
+
.argument('<directory>', 'Path where the new application will be created')
|
|
50
|
+
.option('--template <template>', 'Template to scaffold from', package_json_1.default.config.defaultTemplate)
|
|
51
|
+
.addHelpText('after', '\n' + [
|
|
52
|
+
'Available Templates:',
|
|
53
|
+
...availableTemplates.map(t => ` - ${t}`),
|
|
54
|
+
'',
|
|
55
|
+
'Examples:',
|
|
56
|
+
' chargebee-apps create my-app',
|
|
57
|
+
' chargebee-apps create ./projects/new-app --template serverless-node-starter-app',
|
|
58
|
+
' chargebee-apps create ./projects/my-app-with-iparams --template serverless-node-starter-app-with-iparams'
|
|
59
|
+
].join('\n'))
|
|
60
|
+
.action((directory, options) => create_1.createCommand.execute(options, directory));
|
|
61
|
+
program
|
|
62
|
+
.command('run')
|
|
63
|
+
.usage('<directory> [options]')
|
|
64
|
+
.summary('Start a local development server')
|
|
65
|
+
.description('Start a local development server to test your serverless application')
|
|
66
|
+
.argument('<directory>', 'Path to the application directory to run')
|
|
67
|
+
.option('-p, --port <port>', 'Port to run the server on', package_json_1.default.config.defaultPort.toString())
|
|
68
|
+
.addHelpText('after', '\n' + [
|
|
69
|
+
'Examples:',
|
|
70
|
+
' chargebee-apps run .',
|
|
71
|
+
' chargebee-apps run ./my-app',
|
|
72
|
+
' chargebee-apps run ./my-app --port 15001',
|
|
73
|
+
' chargebee-apps run ./my-app -p 15001'
|
|
74
|
+
].join('\n'))
|
|
75
|
+
.action(async (directory, options) => {
|
|
76
|
+
await run_1.runCommand.execute(options, directory);
|
|
77
|
+
});
|
|
78
|
+
program
|
|
79
|
+
.command('package')
|
|
80
|
+
.usage('<directory> [options]')
|
|
81
|
+
.summary('Package an application')
|
|
82
|
+
.description('Package a serverless application into a zip file')
|
|
83
|
+
.argument('<directory>', 'Path to the application directory to package')
|
|
84
|
+
.option('--name <name>', 'Custom name for the package (without extension)', package_json_1.default.config.defaultPackageName)
|
|
85
|
+
.addHelpText('after', '\n' + [
|
|
86
|
+
'Examples:',
|
|
87
|
+
' chargebee-apps package .',
|
|
88
|
+
' chargebee-apps package ./my-app',
|
|
89
|
+
' chargebee-apps package ./my-app --name my-app-v1.0.0'
|
|
90
|
+
].join('\n'))
|
|
91
|
+
.action(async (directory, options) => await package_1.packageCommand.execute(directory, options));
|
|
92
|
+
return program;
|
|
93
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ArchiveService, CBFileSystem, CBProcess, FileManagementService, PackageOptions, PackageValidator } from '@chargebee/chargebee-apps-shared';
|
|
2
|
+
/**
|
|
3
|
+
* Main package command class
|
|
4
|
+
*/
|
|
5
|
+
export declare class PackageCommand {
|
|
6
|
+
private fileSystem;
|
|
7
|
+
private process;
|
|
8
|
+
private fileManagementService;
|
|
9
|
+
private packageValidator;
|
|
10
|
+
private archiveService;
|
|
11
|
+
constructor(fileSystem?: CBFileSystem, process?: CBProcess, fileManagementService?: FileManagementService, packageValidator?: PackageValidator, archiveService?: ArchiveService);
|
|
12
|
+
/**
|
|
13
|
+
* Packages a serverless application into a zip file
|
|
14
|
+
* @param {string} appDir - The application directory to package
|
|
15
|
+
* @param {string} packageName - The name of the package
|
|
16
|
+
*/
|
|
17
|
+
execute(appDir: string, options: PackageOptions): Promise<void>;
|
|
18
|
+
private validate;
|
|
19
|
+
}
|
|
20
|
+
export declare const packageCommand: PackageCommand;
|