@kernelminds/create-enclave 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.
@@ -0,0 +1,752 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs = require("fs");
4
+ import path = require("path");
5
+ import child_process = require("child_process");
6
+ import ts = require('typescript');
7
+ import prompt = require('@inquirer/prompts');
8
+ import crypto = require("crypto");
9
+
10
+ let applicationIdentifier = "scailo-test-enclave";
11
+ let applicationName = "Scailo Test Enclave";
12
+
13
+ let version = "0.0.1";
14
+ const rootFolder = path.dirname(__dirname);
15
+
16
+ type enclaveRuntime = "node" | "golang" | "python";
17
+ type entryPointManagement = "platform_redirect" | "direct_url";
18
+
19
+ let selectedEnclaveRuntime: enclaveRuntime = "node";
20
+ let selectedEntryPointManagement: entryPointManagement = "platform_redirect";
21
+
22
+ async function acceptUserInputs() {
23
+ applicationName = (await prompt.input({
24
+ message: "Enter the Application Name: ",
25
+ default: applicationName,
26
+ required: true,
27
+ validate: input => input.length > 0
28
+ })).trim();
29
+
30
+ applicationIdentifier = applicationName.split(" ").join("-").toLowerCase();
31
+ applicationIdentifier = (await prompt.input({
32
+ message: "Enter the Application Identifier: ",
33
+ default: applicationIdentifier,
34
+ required: true,
35
+ validate: input => input.length > 0
36
+ })).trim();
37
+
38
+ version = (await prompt.input({
39
+ message: "Enter the Initial Version Number (Semver Format): ",
40
+ default: version,
41
+ required: true,
42
+ validate: input => input.length > 0
43
+ })).trim();
44
+
45
+ selectedEnclaveRuntime = (await prompt.select({
46
+ message: "Select the Enclave Runtime",
47
+ choices: [
48
+ { name: "Node", value: "node", description: "Create a Node based Enclave" },
49
+ { name: "Golang", value: "golang", description: "Create a Golang based Enclave" },
50
+ { name: "Python", value: "python", description: "Create a Python based Enclave" },
51
+ ], default: ""
52
+ })).trim() as enclaveRuntime;
53
+
54
+ selectedEntryPointManagement = (await prompt.select({
55
+ message: "Select the Entry Point Type",
56
+ choices: [
57
+ { name: "Platform Redirect", value: "platform_redirect", description: `Entry Point will be managed by the Platform. The application will need to implement: GET /enclave/${applicationIdentifier}/ingress/{token}` },
58
+ { name: "Direct URL", value: "direct_url", description: "Entry Point will be managed by the Application. The application will need to implement: GET /" },
59
+ ], default: "platform_redirect"
60
+ })).trim() as entryPointManagement;
61
+
62
+ console.log(`Application Name: ${applicationName}`);
63
+ console.log(`Application Identifier: ${applicationIdentifier}`);
64
+ console.log(`Version: ${version}`);
65
+ console.log(`Enclave Runtime: ${selectedEnclaveRuntime}`);
66
+ console.log(`Entry Point Type: ${selectedEntryPointManagement}`);
67
+ }
68
+
69
+ function spawnChildProcess(command: string, args: string[] = [], options = {}) {
70
+ return new Promise((resolve, reject) => {
71
+ const child = child_process.spawn(command, args, { ...options, shell: process.platform === "win32" ? true : undefined });
72
+
73
+ // Optional: Log stdout and stderr for debugging
74
+ child.stdout.on('data', (data) => {
75
+ console.log(`${data}`);
76
+ });
77
+
78
+ child.stderr.on('data', (data) => {
79
+ console.error(`stderr: ${data}`);
80
+ });
81
+
82
+ child.on('close', (code) => {
83
+ if (code === 0) {
84
+ resolve(`Child process exited with code ${code}`);
85
+ } else {
86
+ reject(new Error(`Child process exited with code ${code}`));
87
+ }
88
+ });
89
+
90
+ child.on('error', (err) => {
91
+ reject(err);
92
+ });
93
+ });
94
+ }
95
+
96
+ async function setupGitIgnore() {
97
+ // Setup the .gitignore file
98
+ const gitignoreList = [
99
+ "node_modules/",
100
+ ".env",
101
+ "tsconfig.tsbuildinfo",
102
+ ".DS_Store"
103
+ ];
104
+
105
+ fs.writeFileSync(".gitignore", gitignoreList.join("\n").trim(), { flag: "w", flush: true });
106
+ }
107
+
108
+ async function setupCommonNPMDependencies() {
109
+ // Setup the node modules installation
110
+ const npmDependencies = [
111
+ "@kernelminds/scailo-sdk@latest",
112
+ "@bufbuild/protobuf@1.10.0",
113
+ "@connectrpc/connect-web@1.7.0",
114
+ "path-to-regexp@6.1.0"
115
+ ];
116
+
117
+ await spawnChildProcess("npm", ["install", ...npmDependencies, "--save"]);
118
+
119
+ const npmDevDependencies = [
120
+ "tailwindcss",
121
+ "@tailwindcss/cli",
122
+ "daisyui@latest",
123
+ "esbuild",
124
+ "@inquirer/prompts@7.8.6",
125
+ "concurrently@9.2.1",
126
+ "semver",
127
+ "@types/semver",
128
+ "yaml",
129
+ "adm-zip",
130
+ "@types/adm-zip",
131
+ "typescript",
132
+ "@types/node",
133
+ "favicons",
134
+ ];
135
+
136
+ await spawnChildProcess("npm", ["install", ...npmDevDependencies, "--save-dev"]);
137
+ }
138
+
139
+ async function setupDependencies({ selectedEnclaveRuntime }: { selectedEnclaveRuntime: enclaveRuntime }) {
140
+ if (selectedEnclaveRuntime == "node") {
141
+ await setupDependenciesForNode();
142
+ } else if (selectedEnclaveRuntime == "golang") {
143
+ await setupDependenciesForGolang();
144
+ } else if (selectedEnclaveRuntime == "python") {
145
+ await setupDependenciesForPython();
146
+ }
147
+
148
+ // Create the tsconfig.json
149
+ await spawnChildProcess("npx", ["tsc", "--init"]);
150
+ }
151
+
152
+ async function setupDependenciesForNode() {
153
+ await setupCommonNPMDependencies();
154
+
155
+ const npmDependencies = [
156
+ "@connectrpc/connect-node@1.7.0",
157
+ "fastify@4.28.1",
158
+ "@fastify/static@7.0.4",
159
+ "fastify-favicon@4.3.0",
160
+ "dotenv",
161
+ "redis@4.7.0",
162
+ "@fastify/cookie@9.4.0",
163
+ "pino-pretty@13.1.2"
164
+ ]
165
+
166
+ await spawnChildProcess("npm", ["install", ...npmDependencies, "--save"]);
167
+ }
168
+
169
+ async function setupDependenciesForGolang() {
170
+ await setupCommonNPMDependencies();
171
+ }
172
+
173
+ async function setupDependenciesForPython() {
174
+ await setupCommonNPMDependencies();
175
+ }
176
+
177
+ async function setupScripts() {
178
+ const scriptsFolderName = path.join("scripts")
179
+ let folders = [
180
+ path.join(scriptsFolderName),
181
+ ];
182
+ for (const folder of folders) {
183
+ fs.mkdirSync(folder, { recursive: true });
184
+ }
185
+ // Copy package.ts
186
+ fs.copyFileSync(path.join(rootFolder, "src", "package.ts"), path.join(scriptsFolderName, "package.ts"));
187
+ }
188
+
189
+ function createResourcesFolders() {
190
+ const resourcesFolderName = path.join("resources");
191
+ const distFolderName = path.join(resourcesFolderName, "dist");
192
+ const srcFoldername = path.join(resourcesFolderName, "src");
193
+ let resourcesFolders = [
194
+ path.join(resourcesFolderName),
195
+
196
+ path.join(distFolderName),
197
+ path.join(distFolderName, "css"),
198
+ path.join(distFolderName, "img"),
199
+ path.join(distFolderName, "js"),
200
+
201
+ path.join(srcFoldername),
202
+ path.join(srcFoldername, "ts"),
203
+ path.join(srcFoldername, "css")
204
+ ];
205
+
206
+ for (const folder of resourcesFolders) {
207
+ fs.mkdirSync(folder, { recursive: true });
208
+ }
209
+
210
+ const appCSSPath = path.join(srcFoldername, "css", "app.css");
211
+ const appEntryTSPath = path.join(srcFoldername, "ts", "app.ts");
212
+ const routerEntryTSPath = path.join(srcFoldername, "ts", "router.ts");
213
+ // Copy the img folder
214
+ fs.cpSync(path.join(rootFolder, "img"), path.join(distFolderName, "img"), { recursive: true });
215
+
216
+ return {
217
+ appCSSPath,
218
+ distFolderName,
219
+ appEntryTSPath,
220
+ routerEntryTSPath
221
+ }
222
+ }
223
+
224
+ async function createBuildScripts({ appCSSPath, distFolderName, appEntryTSPath, selectedEnclaveRuntime }: { appCSSPath: string, distFolderName: string, appEntryTSPath: string, selectedEnclaveRuntime: enclaveRuntime }) {
225
+ // Write all the package JSON scripts here
226
+ let packageJSON = JSON.parse(fs.readFileSync("package.json", "utf-8")) as Object;
227
+ let packageJSONScripts = (<any>packageJSON).scripts || {} as Object;
228
+
229
+ let scripts = [
230
+ ["css:build", `npx tailwindcss -i ${appCSSPath} -o ${path.join(distFolderName, "css", "bundle.css")} --minify`],
231
+ ["css:watch", `npx tailwindcss -i ${appCSSPath} -o ${path.join(distFolderName, "css", "bundle.css")} --watch`],
232
+
233
+ ["ui:build", `npx esbuild ${appEntryTSPath} --bundle --outfile=${path.join(distFolderName, "js", "bundle.src.min.js")} --minify`],
234
+ ["ui:watch", `npx esbuild ${appEntryTSPath} --bundle --outfile=${path.join(distFolderName, "js", "bundle.src.min.js")} --watch`],
235
+
236
+ ["dev:watch", `npx concurrently "npm run ui:watch" "npm run css:watch"`],
237
+
238
+ ["package", `npx tsx scripts/package.ts`],
239
+ ]
240
+
241
+ if (selectedEnclaveRuntime == "node") {
242
+ scripts.push(["dev:start", `npx tsx -r dotenv/config server.ts`]);
243
+ scripts.push(["start", `npx tsx server.ts`]); // This is in production
244
+ } else if (selectedEnclaveRuntime == "golang") {
245
+ scripts.push(["dev:start", `go run .`]);
246
+ scripts.push(["start", `go run .`]); // This is in production
247
+ } else if (selectedEnclaveRuntime == "python") {
248
+ scripts.push(["dev:start", `uv run server.py`]);
249
+ scripts.push(["start", `uv run server.py`]); // This is in production
250
+ }
251
+
252
+ for (let i = 0; i < scripts.length; i++) {
253
+ let script = scripts[i]!;
254
+ packageJSONScripts[script[0]!] = script[1];
255
+ }
256
+
257
+ (<any>packageJSON).scripts = packageJSONScripts;
258
+ (<any>packageJSON).version = version;
259
+ fs.writeFileSync("package.json", JSON.stringify(packageJSON, null, 2), { flag: "w", flush: true });
260
+ }
261
+
262
+ async function createIndexHTML({ appName, version, enclaveName, selectedEntryPointManagement }: { appName: string, version: string, enclaveName: string, selectedEntryPointManagement: entryPointManagement }) {
263
+
264
+ const hrefPrefix = selectedEntryPointManagement == "platform_redirect" ? `/enclave/${enclaveName}` : ``;
265
+
266
+ const html = `
267
+ <!DOCTYPE html>
268
+ <html lang="en">
269
+ <head>
270
+ <meta charset="UTF-8">
271
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
272
+ <link rel="shortcut icon" href="${hrefPrefix}/resources/dist/img/favicon.ico" type="image/x-icon">
273
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
274
+ <link rel="preload" as="script" href="${hrefPrefix}/resources/dist/js/bundle.src.min.js">
275
+ <link rel="stylesheet" href="${hrefPrefix}/resources/dist/css/bundle.css">
276
+ <title>${appName}</title>
277
+ </head>
278
+ <body class="text-gray-800">
279
+ <div class="container text-center">
280
+ <h1 class="text-3xl font-bold">${appName}</h1>
281
+ </div>
282
+ <div id="container" class="container"></div>
283
+ <!-- Attach the JS bundle here -->
284
+ <script src="${hrefPrefix}/resources/dist/js/bundle.src.min.js"></script>
285
+ </body>
286
+ </html>
287
+ `;
288
+
289
+ // Create index.html
290
+ fs.writeFileSync("index.html", html.trim(), { flag: "w", flush: true });
291
+ }
292
+
293
+ async function createEntryTS({ appEntryTSPath, enclaveName, selectedEntryPointManagement }: { appEntryTSPath: string, enclaveName: string, selectedEntryPointManagement: entryPointManagement }) {
294
+ const script = `
295
+ import { createConnectTransport } from "@connectrpc/connect-web";
296
+ import { Router } from "./router";
297
+
298
+ export const enclaveName = "${enclaveName}";
299
+
300
+ /**
301
+ * Message handler type for Scailo enclave. Receives messages from the parent application
302
+ */
303
+ export type ScailoEnclaveMessage = {
304
+ type: "refresh",
305
+ payload: any
306
+ };
307
+
308
+ /**
309
+ * Message handler for Scailo enclave
310
+ */
311
+ window.addEventListener("message", (evt: MessageEvent<ScailoEnclaveMessage>) => {
312
+ if (evt.data.type == "refresh") {
313
+ location.reload();
314
+ }
315
+ });
316
+
317
+ async function wait(ms: number) {
318
+ return new Promise(resolve => setTimeout(resolve, ms));
319
+ }
320
+
321
+ /** Starts the router */
322
+ function startRouter() {
323
+ let r = new Router();
324
+ const routePrefix = ${selectedEntryPointManagement == "platform_redirect" ? "\`/enclave/\${enclaveName}\`" : "``"};
325
+ r.add(\`\${routePrefix}/ui\`, async (ctx) => {
326
+ const container = document.getElementById("container") as HTMLDivElement;
327
+ while (true) {
328
+ await wait(1000);
329
+ let payload = await (await fetch(\`\${routePrefix}/api/random\`, { method: "GET", headers: { "Content-Type": "application/json" } })).json() as { random: number };
330
+ container.innerHTML = payload.random.toString();
331
+ }
332
+ });
333
+
334
+ r.add(\`\${routePrefix}/404\`, async (ctx) => {
335
+ handlePageNotFound(ctx);
336
+ });
337
+
338
+ r.setDefault((ctx) => {
339
+ location.href = \`\${routePrefix}/ui\`;
340
+ });
341
+
342
+ r.start();
343
+ }
344
+
345
+ /** Handles page not found */
346
+ function handlePageNotFound(ctx: any) {
347
+ let content = <HTMLDivElement>document.getElementById("container");
348
+ content.innerHTML = "Invalid page";
349
+ }
350
+
351
+ window.addEventListener("load", async (evt) => {
352
+ evt.preventDefault();
353
+ startRouter();
354
+ });
355
+
356
+ export function getReadTransport() {
357
+ return createConnectTransport({
358
+ // Need to use binary format (at least for the time being)
359
+ baseUrl: location.origin, useBinaryFormat: false, interceptors: []
360
+ });
361
+ }
362
+ `;
363
+
364
+ // Create index.ts
365
+ fs.writeFileSync(appEntryTSPath, script.trim(), { flag: "w", flush: true });
366
+ }
367
+
368
+ async function createRouterTS({ routerEntryTSPath }: { routerEntryTSPath: string }) {
369
+ const script = `
370
+
371
+ import { match, MatchFunction } from "path-to-regexp";
372
+
373
+ /**Stores the relationship between the path and the provided callback */
374
+ interface relation {
375
+ path: MatchFunction<object> | null
376
+ callback: (ctx: context) => void
377
+ }
378
+
379
+ /**Class that performs the routing */
380
+ export class Router {
381
+ relationships: relation[]
382
+ defaultRelation: relation
383
+
384
+ constructor() {
385
+ this.relationships = [];
386
+ this.defaultRelation = <relation>{};
387
+ }
388
+
389
+ /**Adds a path along with the callback to the router */
390
+ add(path: string, callback: (ctx: context) => void) {
391
+ let r = <relation>{
392
+ path: match(path),
393
+ callback: callback
394
+ };
395
+ this.relationships.push(r);
396
+ }
397
+
398
+ setDefault(callback: (ctx: context) => void) {
399
+ this.defaultRelation = {
400
+ path: null,
401
+ callback: callback
402
+ }
403
+ }
404
+
405
+ /**Navigates the user to the provided href */
406
+ private _navigate(href: string, searchParams: string) {
407
+ if (!href.startsWith("tel")) {
408
+ history.pushState({ href: href, searchParams: searchParams }, "", href + searchParams);
409
+ this.traverseRelationships(href, searchParams);
410
+ }
411
+ }
412
+
413
+ private traverseRelationships(href: string, searchParams: string) {
414
+ for (let i = 0; i < this.relationships.length; i++) {
415
+ let t = this.relationships[i];
416
+ let match = t.path!(href);
417
+ if (match) {
418
+ let c = <context>{
419
+ querystring: searchParams,
420
+ pathname: href,
421
+ path: href + searchParams,
422
+ params: match.params
423
+ }
424
+ t.callback(c);
425
+ return
426
+ }
427
+ }
428
+ this.defaultRelation.callback({
429
+ querystring: location.search,
430
+ pathname: location.pathname,
431
+ path: location.pathname + location.search,
432
+ params: null
433
+ });
434
+ }
435
+
436
+ _handleAnchorTag(t: HTMLAnchorElement, e: MouseEvent) {
437
+ if (t.getAttribute("download") != null || t.getAttribute("target") != null || t.getAttribute("href") == null) {
438
+ return true
439
+ } else {
440
+ // Prevent default only in the case where the default functionality of the link is being overridden
441
+ e.preventDefault();
442
+ let tempHref = t.href.replace(location.origin, "");
443
+ let split = tempHref.split("?");
444
+ let href = "";
445
+ let searchParams = "";
446
+ if (split.length == 1) {
447
+ href = split[0];
448
+ } else {
449
+ href = split[0];
450
+ searchParams = "?" + split[1];
451
+ }
452
+ this._navigate(href, searchParams);
453
+ }
454
+ }
455
+
456
+ /**Starts the router */
457
+ start() {
458
+ let localThis = this;
459
+ document.addEventListener("click", e => {
460
+ if (e.ctrlKey || e.metaKey) {
461
+ // If control key or any other modifier key has been clicked, do not handle it wih this library
462
+ return true
463
+ }
464
+ let el = <HTMLElement>e.target;
465
+ if (el.nodeName.toLowerCase() == "a") {
466
+ this._handleAnchorTag(<HTMLAnchorElement>el, e);
467
+ } else if (el.nodeName.toLowerCase() == "button") {
468
+ return true
469
+ } else {
470
+ let parentAnchor: HTMLAnchorElement | null = null;
471
+ let parentEl = el.parentElement;
472
+ if (parentEl == null) {
473
+ return true
474
+ }
475
+ while (parentEl != null) {
476
+ if (parentEl.nodeName.toLowerCase() == "body") {
477
+ break
478
+ }
479
+ if (parentEl.nodeName.toLowerCase() == "a") {
480
+ parentAnchor = <HTMLAnchorElement>parentEl;
481
+ break
482
+ }
483
+ parentEl = parentEl.parentElement;
484
+ }
485
+ if (parentAnchor == null) {
486
+ return true
487
+ }
488
+ // Handle click of the parent element here
489
+ this._handleAnchorTag(parentAnchor, e);
490
+ }
491
+ });
492
+
493
+ window.addEventListener("popstate", function (e) {
494
+ e.preventDefault();
495
+ localThis.traverseRelationships(location.pathname, location.search)
496
+ });
497
+
498
+ /**Create the global function to route to a certain URL */
499
+ (<any>window).routeTo = function (url: string) {
500
+ let t = new URL(location.origin + url);
501
+ localThis._navigate(t.pathname, t.search);
502
+ }
503
+
504
+ this._navigate(location.pathname, location.search);
505
+ }
506
+ }
507
+
508
+ /**Stores the router parameters */
509
+ export interface context {
510
+ querystring: string
511
+ /**Is the object that consists of the matched parameters */
512
+ params: any
513
+ /**The pathname void of query string "/login" */
514
+ pathname: string
515
+ /**Pathname and query string "/login?foo=bar" */
516
+ path: string
517
+ }
518
+
519
+ `;
520
+ // Create router.ts
521
+ fs.writeFileSync(routerEntryTSPath, script.trim(), { flag: "w", flush: true });
522
+ }
523
+
524
+ async function createManifest({ appName, version, enclaveName, appIdentifier, selectedEnclaveRuntime, selectedEntryPointManagement }: { appName: string, version: string, enclaveName: string, appIdentifier: string, selectedEnclaveRuntime: enclaveRuntime, selectedEntryPointManagement: entryPointManagement }) {
525
+ let startExec = "";
526
+ if (selectedEnclaveRuntime == "node") {
527
+ startExec = `npm start`;
528
+ } else if (selectedEnclaveRuntime == "golang") {
529
+ startExec = `go run .`;
530
+ } else if (selectedEnclaveRuntime == "python") {
531
+ startExec = `uv run server.py`;
532
+ }
533
+ let manifest = `
534
+ manifest_version: 1
535
+ enclave_runtime: ${selectedEnclaveRuntime}
536
+ app_version: ${version}
537
+ app_name: ${appName}
538
+ enclave_name: ${enclaveName}
539
+ app_unique_identifier: "${appIdentifier}"
540
+ start_exec: "${startExec}"
541
+ entry_point_management: "${selectedEntryPointManagement}"
542
+ env_variables:
543
+ - name: APP_NAME
544
+ value: "${appName}"
545
+ is_secret: false
546
+ - name: ENCLAVE_NAME
547
+ value: "${enclaveName}"
548
+ is_secret: false
549
+ - name: APP_UNIQUE_IDENTIFIER
550
+ value: "${appIdentifier}"
551
+ is_secret: false
552
+ resources:
553
+ logos:
554
+ - resources/dist/img/logo.png
555
+ folders: []`;
556
+
557
+ if (selectedEnclaveRuntime == "node") {
558
+ manifest += `
559
+ files:
560
+ - index.html
561
+ - server.ts
562
+ - utils.ts
563
+ `
564
+ } else if (selectedEnclaveRuntime == "golang") {
565
+ manifest += `
566
+ files:
567
+ - index.html
568
+ - server.go
569
+ - utils.go
570
+ - go.mod
571
+ - go.sum
572
+ `
573
+ } else if (selectedEnclaveRuntime == "python") {
574
+ manifest += `
575
+ files:
576
+ - index.html
577
+ - server.py
578
+ - utils.py
579
+ - pyproject.toml
580
+ - uv.lock
581
+ - .python-version
582
+ `
583
+ }
584
+
585
+ // Create MANIFEST.yaml
586
+ fs.writeFileSync("MANIFEST.yaml", manifest.trim(), { flag: "w", flush: true });
587
+ }
588
+
589
+ function getUtilsForNode(entryPoint: entryPointManagement) {
590
+ const f = `
591
+ export function getEnclavePrefix(enclaveName: string): string {
592
+ return ${entryPoint == "platform_redirect" ? '`/enclave/${enclaveName}`' : `""`}
593
+ }
594
+ `;
595
+ return f;
596
+ }
597
+
598
+ function getUtilsForGolang(entryPoint: entryPointManagement) {
599
+ const f = `
600
+ package main
601
+
602
+ import "fmt"
603
+
604
+ func getEnclavePrefix(enclaveName string) string {
605
+ return ${entryPoint == "platform_redirect" ? `fmt.Sprintf("/enclave/%s", enclaveName)` : `""`}
606
+ }
607
+ `;
608
+
609
+ return f;
610
+ }
611
+
612
+ function getUtilsForPython(entryPoint: entryPointManagement) {
613
+ const f = `
614
+ def get_enclave_prefix(enclave_name: str) -> str:
615
+ return ${entryPoint == "platform_redirect" ? 'f"/enclave/{enclave_name}"' : `""`}
616
+ `;
617
+ return f;
618
+ }
619
+
620
+ async function createTestServer({ selectedEnclaveRuntime, enclaveName, entryPoint }: { selectedEnclaveRuntime: enclaveRuntime, enclaveName: string, entryPoint: entryPointManagement }) {
621
+ if (selectedEnclaveRuntime == "node") {
622
+ fs.copyFileSync(path.join(rootFolder, "server", "node", "server.ts"), "server.ts");
623
+ fs.writeFileSync("utils.ts", getUtilsForNode(entryPoint).trim(), { flag: "w", flush: true });
624
+ } else if (selectedEnclaveRuntime == "golang") {
625
+ fs.copyFileSync(path.join(rootFolder, "server", "golang", "server.go"), "server.go");
626
+ fs.writeFileSync("utils.go", getUtilsForGolang(entryPoint).trim(), { flag: "w", flush: true });
627
+ fs.copyFileSync(path.join(rootFolder, "server", "golang", "go.mod"), "go.mod");
628
+ fs.copyFileSync(path.join(rootFolder, "server", "golang", "go.sum"), "go.sum");
629
+ } else if (selectedEnclaveRuntime == "python") {
630
+ fs.copyFileSync(path.join(rootFolder, "server", "python", "server.py"), "server.py");
631
+ fs.writeFileSync("utils.py", getUtilsForPython(entryPoint).trim(), { flag: "w", flush: true });
632
+ fs.copyFileSync(path.join(rootFolder, "server", "python", "pyproject.toml"), "pyproject.toml");
633
+ fs.copyFileSync(path.join(rootFolder, "server", "python", "uv.lock"), "uv.lock");
634
+ fs.copyFileSync(path.join(rootFolder, "server", "python", ".python-version"), ".python-version");
635
+ }
636
+
637
+ const envFile = `
638
+ ENCLAVE_NAME=${enclaveName}
639
+ SCAILO_API=http://127.0.0.1:21000
640
+ PORT=9090
641
+ PRODUCTION=false
642
+ USERNAME=
643
+ PASSWORD=
644
+
645
+ # Redis
646
+ REDIS_USERNAME=
647
+ REDIS_PASSWORD=
648
+ REDIS_URL=localhost:6379
649
+
650
+ WORKFLOW_EVENTS_CHANNEL=GENESIS-WORKFLOW-EVENTS
651
+ COOKIE_SIGNATURE_SECRET=${crypto.randomBytes(32).toString('hex')}`;
652
+
653
+ fs.writeFileSync(".env", envFile.trim(), { flag: "w", flush: true });
654
+ }
655
+
656
+ async function fixTSConfig() {
657
+ const configFileName = ts.findConfigFile(
658
+ "tsconfig.json",
659
+ ts.sys.fileExists,
660
+ );
661
+
662
+ if (!configFileName) {
663
+ throw new Error(`Could not find a valid tsconfig.json`);
664
+ }
665
+
666
+ const configFileText = ts.sys.readFile(configFileName);
667
+ if (!configFileText) {
668
+ throw new Error(`Could not read file ${configFileName}`);
669
+ }
670
+
671
+ const { config, error } = ts.parseConfigFileTextToJson(configFileName, configFileText);
672
+
673
+ if (error) {
674
+ throw new Error(`Error parsing tsconfig.json: ${error.messageText}`);
675
+ }
676
+
677
+ const parsedCommandLine = ts.parseJsonConfigFileContent(
678
+ config,
679
+ ts.sys,
680
+ path.dirname(configFileName)
681
+ );
682
+
683
+ parsedCommandLine.options.verbatimModuleSyntax = false;
684
+ parsedCommandLine.options.sourceMap = false;
685
+ parsedCommandLine.options.declaration = false;
686
+ parsedCommandLine.options.declarationMap = false;
687
+
688
+ fs.writeFileSync("tsconfig.json", JSON.stringify(parsedCommandLine, null, 4), { flag: "w", flush: true });
689
+ }
690
+
691
+ async function runPostSetupScripts({ selectedEnclaveRuntime }: { selectedEnclaveRuntime: enclaveRuntime }) {
692
+ // Run the first CSS build
693
+ await spawnChildProcess("npm", ["run", "css:build"]);
694
+ await spawnChildProcess("npm", ["run", "ui:build"]);
695
+
696
+ if (selectedEnclaveRuntime == "node") {
697
+
698
+ } else if (selectedEnclaveRuntime == "golang") {
699
+ await spawnChildProcess("go", ["mod", "tidy"]);
700
+ await spawnChildProcess("goimports", ["-w", "."]);
701
+ } else if (selectedEnclaveRuntime == "python") {
702
+ await spawnChildProcess("uv", ["sync", "--all-groups"]);
703
+ }
704
+ }
705
+
706
+ /**
707
+ * Constant that stores the daisyUI plugin
708
+ */
709
+ const daisyUiPlugin = `
710
+ @plugin "daisyui" {
711
+ themes: light --default, dark --prefersdark;
712
+ }`
713
+
714
+ async function main() {
715
+ await acceptUserInputs();
716
+
717
+ // Create the destination folder
718
+ fs.mkdirSync(applicationIdentifier, { recursive: true });
719
+ // Copy the .vscode folder
720
+ fs.mkdirSync(path.join(applicationIdentifier, ".vscode"), { recursive: true });
721
+ fs.cpSync(path.join(rootFolder, ".vscode"), path.join(applicationIdentifier, ".vscode"), { recursive: true });
722
+ fs.copyFileSync(path.join(rootFolder, "README.md"), path.join(applicationIdentifier, "README.md"));
723
+
724
+ // Change the directory
725
+ process.chdir(applicationIdentifier);
726
+ // Create the package.json
727
+ await spawnChildProcess("npm", ["init", "-y"]);
728
+
729
+ await setupGitIgnore();
730
+ await setupDependencies({ selectedEnclaveRuntime });
731
+ await setupScripts();
732
+
733
+ // Create the resources folder
734
+ const { appCSSPath, distFolderName, appEntryTSPath, routerEntryTSPath } = createResourcesFolders();
735
+ // Create the input css
736
+
737
+
738
+ fs.writeFileSync(appCSSPath, [`@import "tailwindcss"`, daisyUiPlugin].map(a => `${a};`).join("\n"), { flag: "w", flush: true });
739
+
740
+ await createIndexHTML({ appName: applicationName, version, enclaveName: applicationIdentifier, selectedEntryPointManagement });
741
+ await createEntryTS({ appEntryTSPath, enclaveName: applicationIdentifier, selectedEntryPointManagement });
742
+ await createRouterTS({ routerEntryTSPath });
743
+ await createManifest({ appName: applicationName, version, enclaveName: applicationIdentifier, appIdentifier: `${applicationIdentifier}.enc`, selectedEnclaveRuntime, selectedEntryPointManagement });
744
+ await createTestServer({ selectedEnclaveRuntime, enclaveName: applicationIdentifier, entryPoint: selectedEntryPointManagement });
745
+
746
+ await createBuildScripts({ appCSSPath, distFolderName, appEntryTSPath, selectedEnclaveRuntime });
747
+ await fixTSConfig();
748
+ await runPostSetupScripts({ selectedEnclaveRuntime });
749
+ console.log("Your app is ready! What are you going to build?");
750
+ }
751
+
752
+ main();