Canvas: Generate Github nodes out of the dodo-app config
Change-Id: Ifc5b09deb39352a3025f7ea66ce39b421daac94d
diff --git a/apps/canvas/back/code/apps/canvas/config/package-lock.json b/apps/canvas/back/code/apps/canvas/config/package-lock.json
new file mode 100644
index 0000000..26d4370
--- /dev/null
+++ b/apps/canvas/back/code/apps/canvas/config/package-lock.json
@@ -0,0 +1,6 @@
+{
+ "name": "config",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {}
+}
diff --git a/apps/canvas/back/jest.config.js b/apps/canvas/back/jest.config.js
index 86f88fb..90b5123 100644
--- a/apps/canvas/back/jest.config.js
+++ b/apps/canvas/back/jest.config.js
@@ -1,11 +1,20 @@
-const { createDefaultPreset } = require("ts-jest");
-
-const tsJestTransformCfg = createDefaultPreset().transform;
-
-/** @type {import("jest").Config} **/
-module.exports = {
- testEnvironment: "node",
- transform: {
- ...tsJestTransformCfg,
+/** @type {import('ts-jest').JestConfigWithTsJest} */
+export default {
+ preset: 'ts-jest/presets/default-esm',
+ testEnvironment: 'node',
+ moduleNameMapper: {
+ '^(\\.{1,2}/.*)\\.js$': '$1',
+ '^config$': '<rootDir>/../config/src/index.ts'
},
+ transform: {
+ '^.+\\.tsx?$': [
+ 'ts-jest',
+ {
+ useESM: true,
+ },
+ ],
+ },
+ testMatch: [
+ "**/src/**/*.test.ts"
+ ]
};
\ No newline at end of file
diff --git a/apps/canvas/back/package.json b/apps/canvas/back/package.json
index b32b801..66bf02b 100644
--- a/apps/canvas/back/package.json
+++ b/apps/canvas/back/package.json
@@ -5,7 +5,7 @@
"main": "index.js",
"type": "module",
"scripts": {
- "build": "tsc",
+ "build": "node --max-old-space-size=4096 node_modules/.bin/tsc",
"test": "jest",
"format": "prettier --write src/**/*.{js,ts,jsx,tsx} --list-different",
"format-check": "prettier --check src/**/*.{js,ts,jsx,tsx}",
diff --git a/apps/canvas/back/src/github.ts b/apps/canvas/back/src/github.ts
index 7657043..df8eacf 100644
--- a/apps/canvas/back/src/github.ts
+++ b/apps/canvas/back/src/github.ts
@@ -1,34 +1,11 @@
import axios from "axios";
-import { z } from "zod";
-
-export const GithubRepositorySchema = z.object({
- id: z.number(),
- name: z.string(),
- full_name: z.string(),
- html_url: z.string(),
- ssh_url: z.string(),
-});
-
-const DeployKeysSchema = z.array(
- z.object({
- id: z.number(),
- key: z.string(),
- }),
-);
-
-const WebhookSchema = z.object({
- id: z.number(),
- config: z.object({
- url: z.string().optional(), // url might not always be present
- content_type: z.string().optional(),
- }),
- events: z.array(z.string()),
- active: z.boolean(),
-});
-
-const ListWebhooksResponseSchema = z.array(WebhookSchema);
-
-export type GithubRepository = z.infer<typeof GithubRepositorySchema>;
+import {
+ GithubRepository,
+ GithubRepositoriesSchema,
+ DeployKeysSchema,
+ ListWebhooksResponseSchema,
+ DeployKeys,
+} from "config";
export class GithubClient {
private token: string;
@@ -49,13 +26,13 @@
const response = await axios.get("https://api.github.com/user/repos", {
headers: this.getHeaders(),
});
- return z.array(GithubRepositorySchema).parse(response.data);
+ return GithubRepositoriesSchema.parse(response.data);
}
async addDeployKey(repoPath: string, key: string) {
const sshUrl = repoPath;
const repoOwnerAndName = sshUrl.replace("git@github.com:", "").replace(".git", "");
- let existingKeys: z.infer<typeof DeployKeysSchema> = [];
+ let existingKeys: DeployKeys = [];
const response = await axios.get(`https://api.github.com/repos/${repoOwnerAndName}/keys`, {
headers: this.getHeaders(),
});
diff --git a/apps/canvas/back/src/index.ts b/apps/canvas/back/src/index.ts
index 0ba25b0..82e55fe 100644
--- a/apps/canvas/back/src/index.ts
+++ b/apps/canvas/back/src/index.ts
@@ -12,7 +12,16 @@
import shell from "shelljs";
import { RealFileSystem } from "./lib/fs.js";
import path from "node:path";
-import { Env, generateDodoConfig, ConfigSchema, AppNode, ConfigWithInput, configToGraph, Network } from "config";
+import {
+ Env,
+ generateDodoConfig,
+ ConfigSchema,
+ AppNode,
+ ConfigWithInput,
+ configToGraph,
+ Network,
+ GithubRepository,
+} from "config";
async function generateKey(root: string): Promise<[string, string]> {
const privKeyPath = path.join(root, "key");
@@ -325,12 +334,18 @@
resp.write(JSON.stringify({ error: "Invalid configuration", issues: config.error.format() }));
return;
}
+ let repos: GithubRepository[] = [];
+ if (p.githubToken) {
+ const github = new GithubClient(p.githubToken);
+ repos = await github.getRepositories();
+ }
const state = req.body.state
? JSON.stringify(req.body.state)
: JSON.stringify(
configToGraph(
config.data,
getNetworks(resp.locals.username),
+ repos,
p.state ? JSON.parse(Buffer.from(p.state).toString("utf8")) : null,
),
);
diff --git a/apps/canvas/back/src/lib/nodejs.test.ts b/apps/canvas/back/src/lib/nodejs.test.ts
index 5de8423..579cad5 100644
--- a/apps/canvas/back/src/lib/nodejs.test.ts
+++ b/apps/canvas/back/src/lib/nodejs.test.ts
@@ -1,9 +1,7 @@
import { NodeJSAnalyzer } from "./nodejs.js";
-import { FileSystem, RealFileSystem } from "./fs.js";
+import { FileSystem } from "./fs.js";
import { Volume, IFs, createFsFromVolume } from "memfs";
import { test, expect } from "@jest/globals";
-import { expandValue } from "./env.js";
-import shell from "shelljs";
class InMemoryFileSystem implements FileSystem {
constructor(private readonly fs: IFs) {}
@@ -19,14 +17,6 @@
}
}
-test("canvas", async () => {
- const fs: FileSystem = new RealFileSystem("/home/gio/code/apps/canvas/back");
- const analyzer = new NodeJSAnalyzer();
- expect(analyzer.detect(fs, "/")).toBe(true);
- const info = await analyzer.analyze(fs, "/");
- console.log(info);
-});
-
test("nodejs", async () => {
return;
const root = "/";
@@ -55,29 +45,3 @@
const info = await analyzer.analyze(fs, root);
console.log(info);
});
-
-test("env", () => {
- console.log(expandValue("${PORT} ${DODO_VOLUME_DB}"));
- console.log(expandValue("$PORT $DODO_VOLUME_DB"));
- console.log(expandValue("${UNDEFINED:-${MACHINE}${UNDEFINED:-default}}"));
-});
-
-test("clone", async () => {
- expect(shell.which("ssh-agent")).toBeTruthy();
- expect(shell.which("ssh-add")).toBeTruthy();
- expect(shell.which("git")).toBeTruthy();
- expect(
- shell.exec(
- "GIT_SSH_COMMAND='ssh -i /home/gio/.ssh/key -o IdentitiesOnly=yes' git clone git@github.com:giolekva/dodo-blog.git /tmp/dodo-blog",
- ).code,
- ).toBe(0);
- const fs: FileSystem = new RealFileSystem("/tmp/dodo-blog");
- const analyzer = new NodeJSAnalyzer();
- expect(analyzer.detect(fs, "/")).toBe(true);
- const info = await analyzer.analyze(fs, "/");
- console.log(info);
-});
-
-test("keygen", () => {
- expect(shell.exec(`ssh-keygen -y -t ed25519 -f /tmp/key`).code).toBe(0);
-});
diff --git a/apps/canvas/back/tsconfig.json b/apps/canvas/back/tsconfig.json
index b92e830..a0651ce 100644
--- a/apps/canvas/back/tsconfig.json
+++ b/apps/canvas/back/tsconfig.json
@@ -13,7 +13,7 @@
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
- "target": "es2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
+ "target": "es2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
@@ -26,8 +26,8 @@
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
- "module": "node16",
- "moduleResolution": "node16",
+ "module": "es2022",
+ "moduleResolution": "node",
"allowImportingTsExtensions": false,
"noEmit": false,
// "rootDir": "./", /* Specify the root folder within your source files. */