@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.
- package/.vscode/settings.json +6 -0
- package/README.md +202 -0
- package/bin/create-enclave.js +616 -0
- package/img/favicon.ico +0 -0
- package/img/logo.png +0 -0
- package/package.json +46 -0
- package/server/golang/go.mod +50 -0
- package/server/golang/go.sum +198 -0
- package/server/golang/server.go +488 -0
- package/server/golang/utils.go +7 -0
- package/server/node/server.ts +281 -0
- package/server/node/utils.ts +3 -0
- package/server/python/.python-version +1 -0
- package/server/python/pyproject.toml +14 -0
- package/server/python/server.py +446 -0
- package/server/python/utils.py +2 -0
- package/server/python/uv.lock +798 -0
- package/src/create-enclave.ts +752 -0
- package/src/package.ts +179 -0
|
@@ -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();
|