@cloudnux/cli 0.1.0

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/dist/cli.js ADDED
@@ -0,0 +1,1130 @@
1
+ #!/usr/bin/env node
2
+
3
+ // node_modules/tsup/assets/esm_shims.js
4
+ import { fileURLToPath } from "url";
5
+ import path from "path";
6
+ var getFilename = () => fileURLToPath(import.meta.url);
7
+ var getDirname = () => path.dirname(getFilename());
8
+ var __dirname = /* @__PURE__ */ getDirname();
9
+
10
+ // src/cli.tsx
11
+ import React7 from "react";
12
+ import { render } from "ink";
13
+
14
+ // src/config/index.ts
15
+ import fs9 from "node:fs";
16
+ import path14 from "node:path";
17
+ import rechoir from "rechoir";
18
+ import { extensions } from "interpret";
19
+ import findUp from "findup-sync";
20
+
21
+ // src/task-manager/task-manager.ts
22
+ import EventEmitter from "node:events";
23
+ import path2 from "node:path";
24
+ var TaskManager = class {
25
+ config;
26
+ environment;
27
+ taskResults;
28
+ events;
29
+ constructor(config2, environment) {
30
+ this.config = config2;
31
+ this.environment = environment;
32
+ this.taskResults = /* @__PURE__ */ new Map();
33
+ this.events = new EventEmitter();
34
+ }
35
+ generateTaskId() {
36
+ return Math.random().toString(36).substring(2, 11);
37
+ }
38
+ getTaskTitle(task, params) {
39
+ return typeof task.title === "function" ? task.title(params) : task.title;
40
+ }
41
+ async executeTask(task, params, parentTaskId) {
42
+ const taskId = parentTaskId ? `${parentTaskId}:${this.generateTaskId()}` : this.generateTaskId();
43
+ const envConfig = this.config.environments[this.environment];
44
+ const taskParams = {
45
+ ...params,
46
+ ...envConfig,
47
+ title: this.getTaskTitle(task, params),
48
+ environment: this.environment,
49
+ modulesPath: this.config.modulesPath,
50
+ cloudProvider: this.config.cloudProvider,
51
+ workingDir: path2.resolve(this.config.workingDir, this.environment),
52
+ ...this.taskResults
53
+ };
54
+ delete taskParams.tasks;
55
+ this.events.emit("new", {
56
+ id: taskId,
57
+ title: taskParams.title,
58
+ parentTaskId
59
+ });
60
+ if (task.skip && task.skip(taskParams)) {
61
+ this.events.emit("skip", {
62
+ id: taskId
63
+ });
64
+ return null;
65
+ }
66
+ this.events.emit("start", {
67
+ id: taskId
68
+ });
69
+ const executeSubTasks = async (subTaskParams) => {
70
+ if (!task.children || task.children.length === 0) {
71
+ return [];
72
+ }
73
+ const results = [];
74
+ for (const child of task.children) {
75
+ const newSubTaskParams = {
76
+ ...subTaskParams,
77
+ ...results.reduce((acc, result2) => {
78
+ return {
79
+ ...acc,
80
+ ...result2
81
+ };
82
+ }, {})
83
+ };
84
+ const result = await this.executeTask(child, newSubTaskParams, taskId);
85
+ results.push(result);
86
+ }
87
+ return results;
88
+ };
89
+ const logger = (arg, data) => {
90
+ this.events.emit("log", {
91
+ id: taskId,
92
+ log: arg,
93
+ data
94
+ });
95
+ };
96
+ const eventEmitter = (type, data) => {
97
+ this.events.emit(type, {
98
+ id: taskId,
99
+ data
100
+ });
101
+ };
102
+ try {
103
+ const result = await task.action(taskParams, logger, eventEmitter, executeSubTasks);
104
+ if (!parentTaskId) {
105
+ this.taskResults = {
106
+ ...this.taskResults,
107
+ ...result
108
+ };
109
+ }
110
+ this.events.emit("success", {
111
+ id: taskId
112
+ });
113
+ return result;
114
+ } catch (error) {
115
+ this.events.emit("failure", {
116
+ id: taskId,
117
+ error: error.message
118
+ });
119
+ throw new Error(`Task ${taskId} failed: ${error.message}`);
120
+ }
121
+ }
122
+ async execute() {
123
+ const envConfig = this.config.environments[this.environment];
124
+ if (!envConfig) {
125
+ throw new Error(`Environment ${this.environment} not found in config`);
126
+ }
127
+ for (const task of envConfig.tasks) {
128
+ try {
129
+ await this.executeTask(task, {});
130
+ } catch (error) {
131
+ return;
132
+ }
133
+ }
134
+ }
135
+ async watch() {
136
+ const envConfig = this.config.environments[this.environment];
137
+ if (envConfig.watch)
138
+ try {
139
+ await this.executeTask(envConfig.watch, {});
140
+ } catch (error) {
141
+ console.error(error);
142
+ return;
143
+ }
144
+ }
145
+ on(event, listener) {
146
+ this.events.on(event, listener);
147
+ }
148
+ off(event) {
149
+ if (event)
150
+ this.events.removeAllListeners(event);
151
+ else
152
+ this.events.removeAllListeners();
153
+ }
154
+ };
155
+
156
+ // src/task-manager/tasks/load-dev-server-template.ts
157
+ import fs from "node:fs/promises";
158
+ import path3 from "node:path";
159
+ import ejs from "ejs";
160
+ var loadDevServerTemplate = {
161
+ title: "Load Dev Server template",
162
+ skip: () => false,
163
+ action: async ({ devServerTemplatePath }) => {
164
+ const devServerTemplatePathResolved = path3.resolve(__dirname, devServerTemplatePath);
165
+ const template = await fs.readFile(devServerTemplatePathResolved, "utf-8");
166
+ const devServerTemplate = ejs.compile(template, {
167
+ "views": [path3.dirname(devServerTemplatePathResolved)]
168
+ });
169
+ return {
170
+ devServerTemplateFunc: devServerTemplate
171
+ };
172
+ }
173
+ };
174
+
175
+ // src/task-manager/tasks/load-module-template.ts
176
+ import fs2 from "node:fs/promises";
177
+ import path4 from "node:path";
178
+ import ejs2 from "ejs";
179
+ var loadModuleTemplate = {
180
+ title: "Load module template",
181
+ skip: () => false,
182
+ action: async ({ moduleTemplatePath }) => {
183
+ const moduleTemplatePathResolved = path4.resolve(__dirname, moduleTemplatePath);
184
+ const template = await fs2.readFile(moduleTemplatePathResolved, "utf-8");
185
+ const moduleTemplate = ejs2.compile(template, {
186
+ "views": [path4.dirname(moduleTemplatePathResolved)]
187
+ });
188
+ return {
189
+ moduleTemplateFunc: moduleTemplate
190
+ };
191
+ }
192
+ };
193
+
194
+ // src/task-manager/tasks/locate-modules.ts
195
+ import path9 from "node:path";
196
+ import fg from "fast-glob";
197
+
198
+ // src/task-manager/tasks/transform-module.ts
199
+ import fs3 from "node:fs/promises";
200
+ import path5 from "node:path";
201
+ var helpers = {
202
+ $$convertRouteParamstoFastifyRouteTemplate: (route) => {
203
+ return route.replace(/{(.*?)}/g, (sub) => ":" + sub.slice(1, -1));
204
+ }
205
+ };
206
+ var transformModule = {
207
+ title: ({ moduleName }) => `Transform module ${moduleName}`,
208
+ skip: () => false,
209
+ action: async ({
210
+ moduleTemplateFunc,
211
+ entrypointPath,
212
+ moduleName,
213
+ workingDir,
214
+ source
215
+ }) => {
216
+ const entrypointContent = await fs3.readFile(entrypointPath, "utf-8");
217
+ const rendered = moduleTemplateFunc({
218
+ source: process.platform === "win32" ? source.replace(/\\/g, "/") : source,
219
+ module: moduleName,
220
+ ...JSON.parse(entrypointContent),
221
+ ...helpers
222
+ });
223
+ const moduleDir = path5.join(workingDir, moduleName);
224
+ await fs3.mkdir(moduleDir, { recursive: true });
225
+ await fs3.writeFile(path5.join(moduleDir, `index.ts`), rendered, "utf-8");
226
+ return {
227
+ moduleDir
228
+ };
229
+ }
230
+ };
231
+
232
+ // src/task-manager/tasks/locate-source-entry.ts
233
+ import findupSync from "findup-sync";
234
+ import fs4 from "node:fs/promises";
235
+ import path6 from "node:path";
236
+ async function getPackageEntryPaths(packagePath) {
237
+ const content = await fs4.readFile(packagePath, "utf-8");
238
+ const pkg = JSON.parse(content);
239
+ const pkgDir = path6.dirname(packagePath);
240
+ const paths = /* @__PURE__ */ new Set();
241
+ [pkg.main, pkg.module].filter(Boolean).forEach((entry) => paths.add(path6.join(pkgDir, entry)));
242
+ if (pkg.exports) {
243
+ const exportPaths = Object.entries(pkg.exports).filter(([_, value]) => {
244
+ if (typeof value === "string") {
245
+ return value.endsWith(".ts") || value.endsWith(".tsx");
246
+ }
247
+ return false;
248
+ }).map(([_, value]) => path6.join(pkgDir, value));
249
+ exportPaths.forEach((path15) => paths.add(path15));
250
+ }
251
+ return Array.from(paths);
252
+ }
253
+ var locateSourceEntry = {
254
+ title: ({ moduleName }) => `Locate source for module ${moduleName}`,
255
+ skip: () => false,
256
+ action: async ({ moduleName, entrypointPath }) => {
257
+ const pkgJson = findupSync("package.json", { cwd: path6.dirname(entrypointPath) });
258
+ if (!pkgJson) {
259
+ throw new Error("Could not locate package.json for module" + moduleName);
260
+ }
261
+ const entries = await getPackageEntryPaths(pkgJson);
262
+ return {
263
+ source: entries[0]
264
+ };
265
+ }
266
+ };
267
+
268
+ // src/task-manager/tasks/transform-trigger-template.ts
269
+ import fs5 from "node:fs/promises";
270
+ import path7 from "node:path";
271
+ var transformTriggerTemplate = {
272
+ title: ({ moduleName }) => `Transform terraform trigger ${moduleName}`,
273
+ skip: (params) => {
274
+ return params.environment === "develop";
275
+ },
276
+ action: async ({
277
+ triggerTemplateFunc,
278
+ entrypointPath,
279
+ moduleName,
280
+ workingDir,
281
+ source
282
+ }) => {
283
+ const entrypointContent = await fs5.readFile(entrypointPath, "utf-8");
284
+ const rendered = triggerTemplateFunc({
285
+ source: process.platform === "win32" ? source.replace(/\\/g, "/") : source,
286
+ module: moduleName,
287
+ ...JSON.parse(entrypointContent)
288
+ });
289
+ const moduleDir = path7.join(workingDir, moduleName);
290
+ await fs5.mkdir(moduleDir, { recursive: true });
291
+ await fs5.writeFile(path7.join(moduleDir, `${moduleName}-triggers.tf`), rendered, "utf-8");
292
+ }
293
+ };
294
+
295
+ // src/task-manager/tasks/build-server.ts
296
+ import path8 from "node:path";
297
+ import tsup from "tsup";
298
+ function mapCloud(provider) {
299
+ switch (provider) {
300
+ case "aws":
301
+ return "@cloudnux/aws-cloud-provider";
302
+ case "azure":
303
+ return "@cloudnux/azure-cloud-provider";
304
+ case "gcp":
305
+ return "@cloudnux/gcp-cloud-provider";
306
+ default:
307
+ return provider;
308
+ }
309
+ }
310
+ var buildServer = {
311
+ title: ({ moduleName }) => `Build Server for ${moduleName}`,
312
+ skip: (params) => {
313
+ return params.environment === "develop";
314
+ },
315
+ action: async ({ moduleDir, cloudProvider, externalPackages }) => {
316
+ debugger;
317
+ const modulePath = path8.resolve(moduleDir, "index.ts");
318
+ return await tsup.build({
319
+ entry: {
320
+ index: modulePath
321
+ },
322
+ external: [...externalPackages],
323
+ noExternal: ["lodash"],
324
+ esbuildOptions: (options) => {
325
+ options.absWorkingDir = moduleDir;
326
+ options.alias = {
327
+ "@@cloudcore": "@cloudnux/cloud-core",
328
+ "@@cloud": mapCloud(cloudProvider),
329
+ "@@datastore": "@cloudnux/datastore",
330
+ "@@utils": "@cloudnux/utils"
331
+ };
332
+ return options;
333
+ },
334
+ bundle: true,
335
+ sourcemap: false,
336
+ outDir: moduleDir,
337
+ platform: "node",
338
+ dts: false,
339
+ watch: false,
340
+ define: {
341
+ __DEV__: process.env.__DEV__ || "false"
342
+ },
343
+ esbuildPlugins: []
344
+ });
345
+ }
346
+ };
347
+
348
+ // src/task-manager/tasks/locate-modules.ts
349
+ var locateModules = {
350
+ title: "load all entrypoints.json",
351
+ skip: () => false,
352
+ action: async (params, _logger, _eventEmitter, executeSubTasks) => {
353
+ const entrypoints = await fg(params.modulesPath);
354
+ const moduleNames = entrypoints.map((entrypoint) => path9.basename(path9.dirname(entrypoint)));
355
+ let output = {
356
+ entrypoints,
357
+ moduleNames
358
+ };
359
+ for (const entrypoint of entrypoints) {
360
+ const taskOutput = await executeSubTasks({
361
+ ...params,
362
+ entrypointPath: entrypoint,
363
+ moduleName: path9.basename(path9.dirname(entrypoint))
364
+ });
365
+ output = {
366
+ ...output,
367
+ ...taskOutput
368
+ };
369
+ }
370
+ return output;
371
+ },
372
+ children: [
373
+ locateSourceEntry,
374
+ transformModule,
375
+ transformTriggerTemplate,
376
+ buildServer
377
+ ]
378
+ };
379
+
380
+ // src/task-manager/tasks/transform-dev-server.ts
381
+ import fs6 from "node:fs/promises";
382
+ import path10 from "node:path";
383
+ var transformDevServer = {
384
+ title: `Transform Dev Server`,
385
+ skip: () => false,
386
+ action: async ({
387
+ moduleNames,
388
+ devServerTemplateFunc,
389
+ workingDir
390
+ }) => {
391
+ const rendered = devServerTemplateFunc({
392
+ source: "./src",
393
+ moduleNames
394
+ });
395
+ await fs6.mkdir(workingDir, { recursive: true });
396
+ await fs6.writeFile(path10.join(workingDir, `app.ts`), rendered, "utf-8");
397
+ }
398
+ };
399
+
400
+ // src/task-manager/tasks/dev-server-watch.ts
401
+ import path11 from "node:path";
402
+ import { fork } from "node:child_process";
403
+ import tsup2 from "tsup";
404
+ function mapCloud2(provider) {
405
+ switch (provider) {
406
+ case "aws":
407
+ return "@cloudnux/aws-cloud-provider";
408
+ case "azure":
409
+ return "@cloudnux/azure-cloud-provider";
410
+ case "gcp":
411
+ return "@cloudnux/gcp-cloud-provider";
412
+ default:
413
+ return provider;
414
+ }
415
+ }
416
+ var devServerWatch = {
417
+ title: "Dev Server Watch",
418
+ skip: () => false,
419
+ action: async ({ workingDir, cloudProvider, externalPackages, ...rest }, logger, eventEmitter) => {
420
+ const entryPath = path11.resolve(workingDir, "app.ts");
421
+ await tsup2.build({
422
+ entry: {
423
+ index: entryPath
424
+ },
425
+ external: [...externalPackages],
426
+ esbuildOptions: (options) => {
427
+ options.absWorkingDir = workingDir;
428
+ options.alias = {
429
+ "@@cloudcore": "@cloudnux/cloud-core",
430
+ "@@cloud": mapCloud2(cloudProvider),
431
+ "@@datastore": "@cloudnux/datastore",
432
+ "@@utils": "@cloudnux/utils"
433
+ };
434
+ return options;
435
+ },
436
+ bundle: true,
437
+ sourcemap: true,
438
+ outDir: workingDir,
439
+ platform: "node",
440
+ dts: false,
441
+ watch: false,
442
+ env: {
443
+ __ENV_PATH__: '"' + path11.resolve(__dirname, "../.env").replace(/\\/g, "\\\\") + '"',
444
+ __DEV__: process.env.__DEV__ || '"development"'
445
+ },
446
+ esbuildPlugins: [
447
+ {
448
+ name: "capture-logs",
449
+ setup: (build) => {
450
+ build.onEnd((buildResult) => {
451
+ if (buildResult.errors.length > 0) {
452
+ buildResult.errors.forEach(logger);
453
+ }
454
+ if (buildResult.warnings.length > 0) {
455
+ buildResult.warnings.forEach(logger);
456
+ }
457
+ });
458
+ }
459
+ },
460
+ startServerPlugin(logger, eventEmitter)
461
+ ]
462
+ // silent: true // Prevent console output
463
+ });
464
+ }
465
+ };
466
+ function startModule(main, execArgv, logger, eventEmitter) {
467
+ const child = fork(main, { env: process.env, execArgv });
468
+ child.on("message", (message) => {
469
+ switch (message.type) {
470
+ case "ERROR":
471
+ logger("Error:", message.payload);
472
+ eventEmitter(message.type, message.payload);
473
+ break;
474
+ case "APP_REGISTERED":
475
+ eventEmitter(message.type, message.payload);
476
+ break;
477
+ case "ROUTE_REGISTERED":
478
+ eventEmitter(message.type, message.payload);
479
+ break;
480
+ case "LISTENING":
481
+ eventEmitter(message.type, message.payload);
482
+ break;
483
+ case "REQUEST":
484
+ eventEmitter(message.type, message.payload);
485
+ break;
486
+ case "RESPONSE":
487
+ eventEmitter(message.type, message.payload);
488
+ break;
489
+ case "LOG":
490
+ eventEmitter(message.type, message.payload);
491
+ case "log":
492
+ eventEmitter(message.type, message.payload);
493
+ default:
494
+ logger("Unknown message:", message);
495
+ break;
496
+ }
497
+ });
498
+ child.on("error", function(error) {
499
+ console.error(error);
500
+ });
501
+ child.on("close", function(code) {
502
+ child.kill("SIGINT");
503
+ });
504
+ return child;
505
+ }
506
+ function startServerPlugin(logger, eventEmitter) {
507
+ logger("Starting server plugin");
508
+ return {
509
+ name: "start servers",
510
+ setup(build) {
511
+ let child;
512
+ const { outdir, logLevel } = build.initialOptions;
513
+ console.log(outdir, logLevel);
514
+ const main = path11.resolve(outdir, "index.cjs");
515
+ build.onEnd(async function({ errors }) {
516
+ if (child) {
517
+ child.kill("SIGINT");
518
+ if (!child.killed) {
519
+ console.error(`cannot stop process ${child.pid}`);
520
+ }
521
+ }
522
+ if (errors && errors.length > 0) return;
523
+ child = startModule(main, ["--enable-source-maps"], logger, eventEmitter);
524
+ });
525
+ }
526
+ };
527
+ }
528
+
529
+ // src/task-manager/tasks/load-trigger-template.ts
530
+ import fs7 from "node:fs/promises";
531
+ import path12 from "node:path";
532
+ import ejs3 from "ejs";
533
+ var loadTriggerTemplate = {
534
+ title: "Load trigger template",
535
+ skip: () => false,
536
+ action: async ({ triggerTemplatePath }) => {
537
+ const triggerTemplatePathResolved = path12.resolve(__dirname, triggerTemplatePath);
538
+ const template = await fs7.readFile(triggerTemplatePathResolved, "utf-8");
539
+ const triggerTemplate = ejs3.compile(template, {
540
+ "views": [path12.dirname(triggerTemplatePathResolved)]
541
+ });
542
+ return {
543
+ triggerTemplateFunc: triggerTemplate
544
+ };
545
+ },
546
+ children: [
547
+ transformTriggerTemplate
548
+ ]
549
+ };
550
+
551
+ // src/task-manager/tasks/transfer-terraform-module.ts
552
+ import fs8 from "node:fs/promises";
553
+ import path13 from "node:path";
554
+ var transferTerraformModules = {
555
+ title: `Transfer Terraform Modules`,
556
+ skip: () => false,
557
+ action: async ({
558
+ workingDir
559
+ }) => {
560
+ await fs8.mkdir(workingDir, { recursive: true });
561
+ const deployDir = path13.join(workingDir, ".deploy");
562
+ const sourceDir = path13.join(__dirname, "../.deploy");
563
+ await fs8.cp(sourceDir, deployDir, {
564
+ recursive: true,
565
+ force: true,
566
+ errorOnExist: false
567
+ });
568
+ }
569
+ };
570
+
571
+ // src/config/defaults.ts
572
+ var defaultConfig = {
573
+ modulesPath: "./packages/modules/**/entrypoint.json",
574
+ cloudProvider: "aws",
575
+ workingDir: "./.epf",
576
+ environments: {
577
+ develop: {
578
+ moduleTemplatePath: "./templates/local/module.ts.ejs",
579
+ devServerTemplatePath: "./templates/local/dev-server.ts.ejs",
580
+ externalPackages: ["sqlite3", "pino"],
581
+ tasks: [
582
+ loadModuleTemplate,
583
+ loadDevServerTemplate,
584
+ locateModules,
585
+ transformDevServer
586
+ ],
587
+ watch: devServerWatch
588
+ },
589
+ production: {
590
+ moduleTemplatePath: "./templates/cloud/entrypoint-build.ts.ejs",
591
+ triggerTemplatePath: "./templates/cloud/entrypoint-triggers.tf.ejs",
592
+ externalPackages: ["aws-sdk", "@aws-sdk/*"],
593
+ tasks: [
594
+ loadModuleTemplate,
595
+ loadTriggerTemplate,
596
+ locateModules,
597
+ transferTerraformModules
598
+ ],
599
+ cloudProvider: {
600
+ type: "aws"
601
+ }
602
+ }
603
+ }
604
+ };
605
+
606
+ // src/config/validator.ts
607
+ function isString(value) {
608
+ return typeof value === "string";
609
+ }
610
+ function isFunction(value) {
611
+ return typeof value === "function";
612
+ }
613
+ function isTaskTitle(value) {
614
+ return isString(value) || isFunction(value);
615
+ }
616
+ function isTask(value) {
617
+ if (!value || typeof value !== "object") return false;
618
+ const task = value;
619
+ if (!isTaskTitle(task.title)) return false;
620
+ if (typeof task.action !== "function") return false;
621
+ if (task.skip !== void 0 && typeof task.skip !== "function") return false;
622
+ if (task.children !== void 0) {
623
+ if (!Array.isArray(task.children)) return false;
624
+ return task.children.every((child) => isTask(child));
625
+ }
626
+ return true;
627
+ }
628
+ function isEnvironment(value) {
629
+ if (!value || typeof value !== "object") return false;
630
+ const env = value;
631
+ if (!Array.isArray(env.tasks)) return false;
632
+ return env.tasks.every((task) => isTask(task));
633
+ }
634
+ function validateConfig(config2) {
635
+ if (!config2 || typeof config2 !== "object") {
636
+ throw new Error("Config must be an object or a function that returns an object");
637
+ }
638
+ const typedConfig = config2;
639
+ if (!isString(typedConfig.modulesPath)) {
640
+ throw new Error("Config.modulesPath must be a string");
641
+ }
642
+ if (!isString(typedConfig.cloudProvider)) {
643
+ throw new Error("Config.cloudProvider must be a string");
644
+ }
645
+ if (!isString(typedConfig.workingDir)) {
646
+ throw new Error("Config.workingDir must be a string");
647
+ }
648
+ if (!typedConfig.environments || typeof typedConfig.environments !== "object") {
649
+ throw new Error("Config.environments must be an object");
650
+ }
651
+ for (const [envName, env] of Object.entries(typedConfig.environments)) {
652
+ if (!isEnvironment(env)) {
653
+ throw new Error(`Invalid environment configuration for "${envName}"`);
654
+ }
655
+ }
656
+ return true;
657
+ }
658
+
659
+ // src/config/index.ts
660
+ var CONFIG_FILE_NAMES = ["epf.config", "epfrc"];
661
+ async function resolveConfig(config2, configContext) {
662
+ if (typeof config2 === "function") {
663
+ try {
664
+ const resolvedConfig = await Promise.resolve(config2(configContext));
665
+ if (!resolvedConfig || typeof resolvedConfig !== "object") {
666
+ throw new Error("Config function must return an object");
667
+ }
668
+ return resolvedConfig;
669
+ } catch (error) {
670
+ if (error instanceof Error) {
671
+ throw new Error(`Error executing config function: ${error?.message}`);
672
+ } else {
673
+ throw new Error("Error executing config function");
674
+ }
675
+ }
676
+ }
677
+ return config2;
678
+ }
679
+ async function loadConfig(configPath = null) {
680
+ let configFile = configPath;
681
+ if (configFile && !fs9.existsSync(configFile)) {
682
+ throw new Error(`Config file not found: ${configFile}`);
683
+ }
684
+ if (!configFile) {
685
+ const searchPath = process.cwd();
686
+ const filePaths = CONFIG_FILE_NAMES.map((name) => `${name}{${Object.keys(extensions).join(",")}}`);
687
+ configFile = findUp(filePaths, { cwd: searchPath });
688
+ if (!configFile) {
689
+ return defaultConfig;
690
+ }
691
+ }
692
+ const result = rechoir.prepare(extensions, configFile);
693
+ if (result === true || Array.isArray(result) && result.length === 0) {
694
+ const ext = path14.extname(configFile);
695
+ const osSpecificImportPath = process.platform === "win32" ? "file:///" : "";
696
+ let rawConfig;
697
+ if (ext === ".json") {
698
+ rawConfig = await import(osSpecificImportPath + configFile);
699
+ } else {
700
+ rawConfig = await import(osSpecificImportPath + configFile);
701
+ }
702
+ rawConfig = await resolveConfig(rawConfig.default);
703
+ const mergedConfig = {
704
+ ...defaultConfig,
705
+ ...rawConfig,
706
+ environments: {
707
+ ...defaultConfig.environments,
708
+ ...rawConfig.environments || {}
709
+ }
710
+ };
711
+ if (validateConfig(mergedConfig))
712
+ return mergedConfig;
713
+ }
714
+ if (Array.isArray(result) && result[0].error) {
715
+ throw new Error(`Failed to load config: ${result[0].error.message}`);
716
+ }
717
+ throw new Error("Unknown error occurred while loading config");
718
+ }
719
+
720
+ // src/arguments/index.ts
721
+ import meow from "meow";
722
+ function loadArgs() {
723
+ const args2 = meow(
724
+ `
725
+ Usage
726
+ $ epf <enviroment>
727
+
728
+ Options
729
+ --config Your config file path
730
+
731
+ Examples
732
+ $ epf develop --config=./config.json
733
+ `,
734
+ {
735
+ importMeta: import.meta,
736
+ flags: {
737
+ config: {
738
+ isRequired: false,
739
+ type: "string"
740
+ }
741
+ }
742
+ }
743
+ );
744
+ return {
745
+ inputs: {
746
+ env: args2.input[0],
747
+ module: args2.input[1]
748
+ },
749
+ flags: {
750
+ configFile: args2.flags.config
751
+ }
752
+ };
753
+ }
754
+
755
+ // src/app.tsx
756
+ import React6, { useEffect, useMemo, useState as useState2 } from "react";
757
+ import { extendTheme, defaultTheme, ThemeProvider } from "@inkjs/ui";
758
+ import { Box as Box6, useStdout, Text as Text6 } from "ink";
759
+
760
+ // src/store/index.ts
761
+ import { create } from "zustand";
762
+ import { immer } from "zustand/middleware/immer";
763
+ var useTaskManager = create()(immer(
764
+ (set, get) => {
765
+ return {
766
+ tasks: {},
767
+ currentTaskId: null,
768
+ environment: "development",
769
+ isRunning: false,
770
+ isListening: false,
771
+ selectedModule: null,
772
+ selectedEndpoint: null,
773
+ modules: [],
774
+ port: "",
775
+ host: "",
776
+ logs: [],
777
+ pinoLogs: [],
778
+ start: async (config2, env) => {
779
+ const taskManager = new TaskManager(config2, env);
780
+ set(() => ({ isRunning: true, environment: env }));
781
+ taskManager.on("new", (task) => get().addTask(task));
782
+ taskManager.on("start", ({ id }) => get().updateTaskStatus(id, "running"));
783
+ taskManager.on("success", ({ id }) => get().updateTaskStatus(id, "completed"));
784
+ taskManager.on("failure", ({ id, error }) => get().updateTaskStatus(id, "error", error));
785
+ taskManager.on("skip", ({ id }) => get().updateTaskStatus(id, "skipped"));
786
+ taskManager.on("log", ({ id, log, data }) => get().addTaskLog(id, log, data));
787
+ taskManager.on("APP_REGISTERED", ({ id, data }) => get().addModule(id, data));
788
+ taskManager.on("ROUTE_REGISTERED", ({ id, data }) => get().addRoute(data));
789
+ taskManager.on("LISTENING", ({ id, data }) => get().startListening(data));
790
+ taskManager.on("ERROR", ({ id, data }) => get().addActionLog(id, data));
791
+ taskManager.on("REQUEST", ({ id, data }) => get().addActionLog(id, data));
792
+ taskManager.on("RESPONSE", ({ id, data }) => get().addActionLog(id, data));
793
+ taskManager.on("LOG", ({ id, data }) => get().addPinoLog(id, data));
794
+ await taskManager.execute();
795
+ set(() => ({ isRunning: false, currentTaskId: null }));
796
+ await taskManager.watch();
797
+ set(() => ({ isWatching: true }));
798
+ },
799
+ addTask: (task) => {
800
+ const newTask = {
801
+ id: task.id,
802
+ title: task.title,
803
+ status: "pending",
804
+ parentId: task.parentTaskId,
805
+ children: [],
806
+ logs: []
807
+ };
808
+ set((state) => {
809
+ state.tasks[task.id] = newTask;
810
+ if (task.parentTaskId) {
811
+ const parentTaskState = state.tasks[task.parentTaskId];
812
+ if (parentTaskState) {
813
+ state.tasks[task.parentTaskId].children = [
814
+ ...parentTaskState.children,
815
+ task.id
816
+ ];
817
+ }
818
+ }
819
+ state.currentTaskId = task.id;
820
+ });
821
+ },
822
+ updateTaskStatus: (taskId, status, error) => {
823
+ set((state) => {
824
+ const task = state.tasks[taskId];
825
+ if (!task) return state;
826
+ task.status = status;
827
+ task.error = error || task.error;
828
+ });
829
+ },
830
+ addTaskLog: (taskId, log, data) => {
831
+ set((state) => {
832
+ const task = state.tasks[taskId];
833
+ if (!task) return state;
834
+ if (data) log = { text: log, data };
835
+ task.logs = [...task.logs, log];
836
+ });
837
+ },
838
+ addModule: (id, opts) => {
839
+ set((state) => {
840
+ let modules = state.modules;
841
+ if (modules.length > 0) {
842
+ state.modules = [...modules, {
843
+ id: opts.prefix,
844
+ name: opts.prefix,
845
+ endpoints: [],
846
+ data: opts
847
+ }];
848
+ return state;
849
+ }
850
+ state.modules = [{
851
+ id: opts.prefix,
852
+ name: opts.prefix,
853
+ endpoints: [],
854
+ data: opts
855
+ }];
856
+ return state;
857
+ });
858
+ },
859
+ addRoute: (endpoint) => {
860
+ set((state) => {
861
+ let modules = state.modules;
862
+ if (modules.length < 0) {
863
+ return state;
864
+ }
865
+ state.modules[modules.length - 1].endpoints = [...modules[modules.length - 1].endpoints, endpoint];
866
+ return state;
867
+ });
868
+ },
869
+ addActionLog(id, data) {
870
+ const logs = get().logs;
871
+ if (logs.length >= 5) {
872
+ logs.shift();
873
+ }
874
+ set({ logs: [...logs, data] });
875
+ },
876
+ addPinoLog: (id, data) => {
877
+ const logs = get().pinoLogs;
878
+ if (logs.length >= 5) {
879
+ logs.shift();
880
+ }
881
+ set({ pinoLogs: [...logs, data] });
882
+ },
883
+ startListening: (data) => {
884
+ if (!data) return;
885
+ set({ isListening: true, port: data.port, host: data.host });
886
+ },
887
+ selectModule: (id) => set({ selectedModule: id }),
888
+ selectEndpoint: (endpoint) => set({ selectedEndpoint: endpoint }),
889
+ resetSelection: () => set({ selectedModule: null, selectedEndpoint: null })
890
+ // Reset both select
891
+ };
892
+ }
893
+ ));
894
+ var selectTask = (taskId) => (state) => state.tasks[taskId];
895
+ var selectRootTasks = () => (state) => {
896
+ return Object.values(state.tasks).filter((task) => !task.parentId);
897
+ };
898
+ var selectProgress = () => (state) => {
899
+ const allTasks = Object.values(state.tasks);
900
+ const completedTasks = allTasks.filter((task) => task.status === "completed");
901
+ return completedTasks.length / allTasks.length;
902
+ };
903
+
904
+ // src/components/prepare-view.tsx
905
+ import React2 from "react";
906
+ import { useShallow } from "zustand/react/shallow";
907
+ import { Box as Box2, Text as Text2 } from "ink";
908
+ import { ProgressBar, StatusMessage, Spinner } from "@inkjs/ui";
909
+
910
+ // src/components/log-entry.tsx
911
+ import React from "react";
912
+ import { Box, Text } from "ink";
913
+ var LogEntry = ({ log }) => {
914
+ if (typeof log !== "string" && "text" in log && "data" in log) {
915
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { color: log.color || "white" }, log.text), log.data && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, JSON.stringify(log.data)));
916
+ }
917
+ if (typeof log !== "string" && "text" in log) {
918
+ const location = log.location ? `${log.location.file}:${log.location.line}:${log.location.column}` : "";
919
+ const details = log.detail ? `
920
+ ${log.detail}` : "";
921
+ const notes = log.notes?.map((note) => note.text).join("\n");
922
+ const prefix = log.pluginName ? `[${log.pluginName}] ` : "";
923
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "\u26A0 ", prefix, log.text), location && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " at ", location), details && /* @__PURE__ */ React.createElement(Text, null, details), notes && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, notes));
924
+ }
925
+ const logLevelMatch = log.match(/^\[(info|error|warning)\]/i);
926
+ const level = logLevelMatch ? logLevelMatch[1].toLowerCase() : "info";
927
+ const message = logLevelMatch ? log.slice(logLevelMatch[0].length).trim() : log;
928
+ const getLogColor = () => {
929
+ switch (level) {
930
+ case "error":
931
+ return "red";
932
+ case "warning":
933
+ return "yellow";
934
+ case "info":
935
+ default:
936
+ return "white";
937
+ }
938
+ };
939
+ const getLogPrefix = () => {
940
+ switch (level) {
941
+ case "error":
942
+ return "\u2716";
943
+ case "warning":
944
+ return "\u26A0";
945
+ case "info":
946
+ default:
947
+ return "\u2139";
948
+ }
949
+ };
950
+ return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: getLogColor() }, getLogPrefix(), " ", message));
951
+ };
952
+
953
+ // src/components/prepare-view.tsx
954
+ var TaskLogs = ({ taskId, maxLogs = 5 }) => {
955
+ const task = useTaskManager((state) => state.tasks[taskId]);
956
+ if (!task?.logs?.length) {
957
+ return null;
958
+ }
959
+ const displayLogs = maxLogs ? task.logs.slice(-maxLogs) : task.logs;
960
+ return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginLeft: 4 }, displayLogs.map((log, index) => /* @__PURE__ */ React2.createElement(LogEntry, { key: `${taskId}-log-${index}`, log })));
961
+ };
962
+ var TaskStatus = ({ taskId, isActive, indent = 0 }) => {
963
+ const task = useTaskManager(selectTask(taskId));
964
+ const currentTaskId = useTaskManager((state) => state.currentTaskId);
965
+ const getStatusVariant = () => {
966
+ if (task.error) return "error";
967
+ if (task.status === "completed" && !task.error) return "success";
968
+ if (task.status === "skipped") return "info";
969
+ if (task.status === "running") return "info";
970
+ return "warning";
971
+ };
972
+ const shouldShowLogs = isActive || task.status === "error";
973
+ return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, null, " ".repeat(indent)), isActive ? /* @__PURE__ */ React2.createElement(Spinner, { label: task.title }) : /* @__PURE__ */ React2.createElement(StatusMessage, { variant: getStatusVariant() }, task.status === "skipped" ? "\u23ED" : void 0, " ", task.title, task.error && /* @__PURE__ */ React2.createElement(Text2, { color: "red" }, " - ", task.error))), shouldShowLogs && /* @__PURE__ */ React2.createElement(TaskLogs, { taskId: task.id }), task.children?.map((child, index) => /* @__PURE__ */ React2.createElement(
974
+ TaskStatus,
975
+ {
976
+ key: `${child}-${index}`,
977
+ taskId: child,
978
+ isActive: task.id === currentTaskId,
979
+ indent: indent + 1
980
+ }
981
+ )));
982
+ };
983
+ var BuildProgress = () => {
984
+ const progress = useTaskManager(selectProgress());
985
+ const tasks = useTaskManager(useShallow(selectRootTasks()));
986
+ const currentTaskId = useTaskManager((state) => state.currentTaskId);
987
+ return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React2.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React2.createElement(ProgressBar, { value: progress * 100 })), /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, Object.values(tasks).map((task) => /* @__PURE__ */ React2.createElement(
988
+ TaskStatus,
989
+ {
990
+ key: task.id,
991
+ taskId: task.id,
992
+ isActive: task.id === currentTaskId
993
+ }
994
+ ))));
995
+ };
996
+ function PrepareView() {
997
+ return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, /* @__PURE__ */ React2.createElement(BuildProgress, null));
998
+ }
999
+
1000
+ // src/components/side-bar.tsx
1001
+ import React3 from "react";
1002
+ import { Box as Box3, Text as Text3, useInput } from "ink";
1003
+ var Sidebar = () => {
1004
+ const { selectedModule, modules, selectEndpoint, selectedEndpoint, resetSelection, selectModule } = useTaskManager();
1005
+ const selectedIndex = modules.findIndex((module) => module.id === selectedModule);
1006
+ useInput((input, key) => {
1007
+ if (key.upArrow && !selectedEndpoint) {
1008
+ const newIndex = (selectedIndex - 1 + modules.length) % modules.length;
1009
+ selectModule(modules[newIndex].id);
1010
+ }
1011
+ if (key.downArrow && !selectedEndpoint) {
1012
+ const newIndex = (selectedIndex + 1) % modules.length;
1013
+ selectModule(modules[newIndex].id);
1014
+ }
1015
+ if (key.return && selectedModule) {
1016
+ selectEndpoint(modules[selectedIndex].endpoints[0]);
1017
+ }
1018
+ if (key.escape) {
1019
+ resetSelection();
1020
+ }
1021
+ });
1022
+ return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", width: "20%", borderStyle: "round", borderColor: "green" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "yellow" }, "Modules"), modules.map((module) => /* @__PURE__ */ React3.createElement(
1023
+ Text3,
1024
+ {
1025
+ key: module.id,
1026
+ color: selectedModule === module.id ? "cyan" : "white"
1027
+ },
1028
+ module.name
1029
+ )));
1030
+ };
1031
+ var side_bar_default = Sidebar;
1032
+
1033
+ // src/components/content-view.tsx
1034
+ import React4, { useState } from "react";
1035
+ import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
1036
+ var ContentView = () => {
1037
+ const { selectedModule, selectedEndpoint, modules, selectEndpoint } = useTaskManager();
1038
+ const module = modules.find((a) => a.id === selectedModule);
1039
+ const selectedIndex = module?.endpoints.findIndex((e) => e.url === selectedEndpoint?.url && e.method === selectedEndpoint?.method) ?? -1;
1040
+ const [logs, setLogs] = useState([
1041
+ "Log entry 1",
1042
+ "Log entry 2",
1043
+ "Log entry 3",
1044
+ "Log entry 4",
1045
+ "Log entry 5",
1046
+ "Log entry 6",
1047
+ "Log entry 7"
1048
+ ]);
1049
+ const displayedLogs = logs.slice(-5);
1050
+ useInput2((input, key) => {
1051
+ if (key.upArrow && selectedModule && module && selectedIndex >= 0) {
1052
+ const newIndex = (selectedIndex - 1 + module.endpoints.length) % module.endpoints.length;
1053
+ selectEndpoint(module.endpoints[newIndex]);
1054
+ }
1055
+ if (key.downArrow && selectedModule && module && selectedIndex >= 0) {
1056
+ const newIndex = (selectedIndex + 1) % module.endpoints.length;
1057
+ selectEndpoint(module.endpoints[newIndex]);
1058
+ }
1059
+ if (key.rightArrow) {
1060
+ setLogs((logs2) => [...logs2, `Log entry ${logs2.length + 1}`]);
1061
+ }
1062
+ });
1063
+ return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", justifyContent: "space-between", width: "80%", borderStyle: "round", borderColor: "blue" }, /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", width: "100%" }, /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: "yellow" }, "Endpoints"), module ? module.endpoints.map((endpoint, index) => /* @__PURE__ */ React4.createElement(
1064
+ Text4,
1065
+ {
1066
+ key: index,
1067
+ color: selectedEndpoint?.url === endpoint.url && endpoint.method === selectedEndpoint?.method ? "cyan" : "white"
1068
+ },
1069
+ endpoint.method,
1070
+ " - ",
1071
+ endpoint.url
1072
+ )) : /* @__PURE__ */ React4.createElement(Text4, null, "Select an app to view its endpoints")), selectedEndpoint && /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", borderStyle: "round", borderColor: "gray" }, /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: "yellow" }, "Logs for ", selectedEndpoint.method, " - ", selectedEndpoint.url, ":"), displayedLogs.map((log, index) => /* @__PURE__ */ React4.createElement(Text4, { key: index }, log))));
1073
+ };
1074
+ var content_view_default = ContentView;
1075
+
1076
+ // src/components/action-logs.tsx
1077
+ import React5 from "react";
1078
+ import { Box as Box5, Text as Text5 } from "ink";
1079
+ var ActionLogs = () => {
1080
+ const logs = useTaskManager((state) => state.logs);
1081
+ return /* @__PURE__ */ React5.createElement(Box5, { borderStyle: "round", flexDirection: "column", borderColor: "green", width: "100%", height: "60%" }, /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: "yellow" }, "General Logs"), logs.map((log, index) => /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { key: index, color: "greenBright" }, "[", log.method, "](", log.url, "): ", log.time), /* @__PURE__ */ React5.createElement(Text5, { key: index }, " - ", JSON.stringify(log)))));
1082
+ };
1083
+ var action_logs_default = ActionLogs;
1084
+
1085
+ // src/app.tsx
1086
+ function App({ config: config2, args: args2 }) {
1087
+ const { stdout } = useStdout();
1088
+ const theme = useMemo(() => {
1089
+ return extendTheme(defaultTheme, {
1090
+ components: {
1091
+ ProgressBar: {
1092
+ styles: {
1093
+ completed: () => {
1094
+ return {
1095
+ color: "green"
1096
+ };
1097
+ }
1098
+ }
1099
+ }
1100
+ }
1101
+ });
1102
+ }, []);
1103
+ useMemo(() => {
1104
+ const onResize = () => {
1105
+ setDimensions([stdout.columns, stdout.rows]);
1106
+ };
1107
+ stdout.on("resize", onResize);
1108
+ return () => {
1109
+ stdout.off("resize", onResize);
1110
+ };
1111
+ }, [stdout]);
1112
+ const [dimensions, setDimensions] = useState2([
1113
+ stdout.columns,
1114
+ stdout.rows
1115
+ ]);
1116
+ const [width, height] = dimensions;
1117
+ const start = useTaskManager((state) => state.start);
1118
+ const isListening = useTaskManager((state) => state.isListening);
1119
+ const port = useTaskManager((state) => state.port);
1120
+ const host = useTaskManager((state) => state.host);
1121
+ useEffect(() => {
1122
+ start(config2, args2.inputs.env);
1123
+ }, []);
1124
+ return /* @__PURE__ */ React6.createElement(ThemeProvider, { theme }, /* @__PURE__ */ React6.createElement(PrepareView, null), isListening && /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column", width, height }, /* @__PURE__ */ React6.createElement(Text6, null, "Server Listening on http://[", host, "]:", port), /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "row", width: "100%", height: "40%" }, /* @__PURE__ */ React6.createElement(side_bar_default, null), /* @__PURE__ */ React6.createElement(content_view_default, null)), /* @__PURE__ */ React6.createElement(action_logs_default, null)));
1125
+ }
1126
+
1127
+ // src/cli.tsx
1128
+ var args = loadArgs();
1129
+ var config = await loadConfig(args.flags.configFile);
1130
+ render(/* @__PURE__ */ React7.createElement(App, { args, config }));