feat!: Migrate to modern dependencies

@gr2m I know you don't want to do a major release right now, but I think this may be the best way to continue. Octokit is dropping support for older Node now, and I think a recent Octokit is quite important for Probot users wanting to use modern GitHub features.
This PR updates all dependencies & fixes compatibility with modern TypeScript using a workaround (I'll open a bug in TS about this later).
Also, some time ago, GitHub renamed the "Abuse rate limit" to "secondary rate limit". This adjusts Probot to these changes by updating octokit and fixing the tests.
The current octokit JS builds on npm are broken because of a bug in npm 9, so this should not be merged before that is released.
This needs some fixes, I'm waiting for https://github.com/octokit/webhooks.js/issues/844 to be resolved/closed.
It's a draft for now to gather feedback.
Fixes #1815.

This should also resolve #1784
Note that @octokit/webhooks only accepts string payloads as of v11

You will most likely want to update @octokit/webhooks-types and @octokit/webhooks-examples
Would it be best to change the default webhooks path to be /webhooks to avoid any kind of issues between the HTTTP server and the webhooks?

You will most likely want to update @octokit/webhooks-types and @octokit/webhooks-examples

Yes, I'll update these too, thanks!

Would it be best to change the default webhooks path to be /webhooks to avoid any kind of issues between the HTTP server and the webhooks?

I'd prefer not too, because then every Probot user would have to change their webhook path in the apps settings on GitHub, which would make migration more complicated and could lead to unexpected issues after migration if people forget.
But if you want to keep the current behaviour of @octokit/webhooks, I guess we'll make this change.
I'd also like to hear @gr2m's opinion about this.

You will most likely want to update @octokit/webhooks-types and @octokit/webhooks-examples

Actually, these are already at their latest version here, they seem to be using a different versioning scheme.

hey just want to say thanks for all the work you put into this! I'm slowly catching up with notifications, but I'll need more time to review this one and the related issues in @octokit. Please bear with me

FYI, the npm problems with the Octokit modules used in Probot have been fixed, simply update to the latest version.
The next major versions, which are in progress, will drop support for Node v14 and v16, and drops node-fetch.

Hi, chiming in since I have a draft PR for ESM support: #1825
I can try to get these changes into my branch too, but really wanted to focus my PR on ESM support.
Looking forward to feedback (sorry for OT, feel free to collapse and respond in my PR)

@pixelass sorry after I collapsed your comment I see that your PR and this one here by @AaronDewes has actually quite an overlap, like the upgrade of Jest to the latest version.
Can you two coordinate? I'm happy to do the transition to ESM, it's going to happen with Octokit too, hopefully sooner than later.
I would wait for the breaking changes coming from Octokit which will drop the dependency on node-fetch and let us use much more modern Node APIs. We can drop support for Node 16 and below, we only need to support Node 18 and 20 as part of the change.
Thank you both <3 And we all have to give a big shoutout to @wolfy1339 who is driving the upgrade of all the @octokit/* packages.

@gr2m thanks for the quick reply. I'm happy to help ge this done. Sadly the heroku app for slack seems to be down (https://probot-slackin.herokuapp.com/) so I'll post here.
@AaronDewes nice to meet you. 👋

Sadly the heroku app for slack seems to be down

yeah we no longer use it. We use https://github.com/probot/probot/discussions/ instead, but in this case communicating here is perfectly fine. Sometime we setup sync calls via zoom or similar, feel free to do that if it's helpful

@pixelass Are you on Discord, Telegram or Signal? Maybe we can talk there?

I've experimented with ESM, but Nock does not yet work with it (And also not with fetch()), which breaks the tests

Instead of nock try fetch-mock

Instead of adding the environment variables, and calling jest directly in the workflows, put it in the npm scripts instead.
That way anybody using the scripts can get jest running smoothly on their local machine

@pixelass Are you on Discord, Telegram or Signal? Maybe we can talk there?

Discord or signal
Feel free to  drop in to our open-source space: https://discord.com/channels/1091306623819059300/1112481346389024778

Fyi that link won't work if they aren't already in the server. You want the invite link

hi, folks. i'm really interested in seeing the move to ESM happen, so i jumped in to help provide some feedback on #1825 and i've realized that i should have coordinated with more of you. sorry about that and i hope i havent overstepped. i havent been very active in the org outside of https://github.com/repository-settings/app, which used to be part of the org, but i'm interested in helping out with these related efforts.
i'd overlooked that this separate effort was also happening until @pixelass linked to it from #1825. in that PR, i've mentioned that i've had success converting projects testing with jest over to vitest because they are api compatible and vitest avoids some of the roadblocks that exist for using jest with esm. i'm hopeful that change will remove the remaining roadblocks for #1825, but i'm interested in working together to coordinate with the changes here, especially since both lines introduce breaking changes.

I've experimented with ESM, but Nock does not yet work with it (And also not with fetch()), which breaks the tests

i've been working to move a bunch of projects over to ESM, including semantic-release and a bunch of less used projects. i have not run into problems with nock specifically related to ESM, but i have hit issues with node v18 and above. so far, i've been able to avoid needing to move away from nock by using the --no-experimental-fetch flag when running tests. that might be a less disruptive option for this effort so that a bigger change could be isolated away from these already complex PRs.

Can you two coordinate?

i'm interested to join in if there are existing conversations happening. would it be helpful if i created an alpha or beta branch that we could target with these branches so that we could merge each of the PRs with breaking changes there and make sure that the overall set of changes are good to go before promoting?

The Octokit packages aren't yet done with the new releases, due to some bugs found.
That is one of the big blockers for this PR

I will try to support you regarding this PR :).

@wolfy1339 @Uzlopak Can you have a look at this PR?

Not sure about the CI failure yet, it does not happen locally.

You can imho not pass options via environment variables in windows. I guess you need cross-env to do this also in windows

You can imho not pass options via environment variables in windows. I guess you need cross-env to do this also in windows

I didn't check Windows yet :sweat_smile:, I'll fix that.

Is it failing because of the experimental flag?

Is it failing because of the experimental flag?

Very likely, I'll investigate that. Tests work sometimes when rerunning and always work locally for me.

Do you use the experimental-vm-loader to run jest faster?

Do you use the experimental-vm-loader to run jest faster?

This PR also allows ESM modules for probot run in the CLI, and the resolveAppFunction test won't work without this flag, because Jest doesn't properly support ESM with this flag. It isn't used for performance reasons

Looks good 👍
I only had the one comment

The e2e test fails for me, if we dont build probot, as the bin files are accessing the compiled lib folder.
But if we build probot, the e2e tests pass for me.

I hope we get this merged soon.

WHOA! VITEST! MY HERO IF THIS LANDS.

WHOA! VITEST! MY HERO IF THIS LANDS.

I hoped it was less broken in CI than Jest, because it doesn't use experimental features. I had to disable threads to use process.chdir, so it's not as fast as it could be, but seems still faster than Jest.

Let me see about process.chdir...

Let me see about process.chdir...

https://github.com/vitest-dev/vitest/issues/566 But feel free to have a look, maybe you can replace it.

You can remove the body-parser as dependency as mentioned before ;)
here with a git patch.
From 82a8d4cfa8559e4f2ee09fd6ca2997228fb9f5a8 Mon Sep 17 00:00:00 2001
From: uzlopak<EMAIL_ADDRESS>Date: Mon, 13 Nov 2023 12:00:04 +0100
Subject: [PATCH] remove body-parser dependency

---
 package-lock.json    | 40 ----------------------------------------
 package.json         |  1 -
 src/apps/setup.ts    |  4 ++--
 test/e2e/e2e.test.ts |  3 +--
 4 files changed, 3 insertions(+), 45 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 62900e49..de2cb615 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -52,7 +52,6 @@
         "@types/supertest": "^2.0.16",
         "@vitest/coverage-v8": "^0.34.6",
         "@vitest/ui": "^0.34.6",
-        "body-parser": "^1.20.2",
         "bottleneck": "^2.19.5",
         "connect-sse": "^1.2.0",
         "execa": "^5.0.0",
@@ -2927,30 +2926,6 @@
       "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
       "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="
     },
-    "node_modules/body-parser": {
-      "version": "1.20.2",
-      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
-      "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
-      "dev": true,
-      "dependencies": {
-        "bytes": "3.1.2",
-        "content-type": "~1.0.5",
-        "debug": "2.6.9",
-        "depd": "2.0.0",
-        "destroy": "1.2.0",
-        "http-errors": "2.0.0",
-        "iconv-lite": "0.4.24",
-        "on-finished": "2.4.1",
-        "qs": "6.11.0",
-        "raw-body": "2.5.2",
-        "type-is": "~1.6.18",
-        "unpipe": "1.0.0"
-      },
-      "engines": {
-        "node": ">= 0.8",
-        "npm": "1.2.8000 || >= 1.4.16"
-      }
-    },
     "node_modules/bottleneck": {
       "version": "2.19.5",
       "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
@@ -10858,21 +10833,6 @@
         "node": ">= 0.6"
       }
     },
-    "node_modules/raw-body": {
-      "version": "2.5.2",
-      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
-      "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
-      "dev": true,
-      "dependencies": {
-        "bytes": "3.1.2",
-        "http-errors": "2.0.0",
-        "iconv-lite": "0.4.24",
-        "unpipe": "1.0.0"
-      },
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
     "node_modules/rc": {
       "version": "1.2.8",
       "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
diff --git a/package.json b/package.json
index f9455485..71031245 100644
--- a/package.json
+++ b/package.json
@@ -77,7 +77,6 @@
     "@types/supertest": "^2.0.16",
     "@vitest/coverage-v8": "^0.34.6",
     "@vitest/ui": "^0.34.6",
-    "body-parser": "^1.20.2",
     "bottleneck": "^2.19.5",
     "connect-sse": "^1.2.0",
     "execa": "^5.0.0",
diff --git a/src/apps/setup.ts b/src/apps/setup.ts
index fa7adaf9..18407e31 100644
--- a/src/apps/setup.ts
+++ b/src/apps/setup.ts
@@ -1,4 +1,4 @@
-import bodyParser from "body-parser";
+import express from "express";
 import { exec } from "child_process";
 import type { Request, Response } from "express";
 import updateDotenv from "update-dotenv";
@@ -77,7 +77,7 @@ export const setupAppFactory = (
       res.render("import.handlebars", { WEBHOOK_PROXY_URL, GH_HOST });
     });

-    route.post("/probot/import", bodyParser.json(), async (req, res) => {
+    route.post("/probot/import", express.json(), async (req, res) => {
       const { appId, pem, webhook_secret } = req.body;
       if (!appId || !pem || !webhook_secret) {
         res.status(400).send("appId and/or pem and/or webhook_secret missing");
diff --git a/test/e2e/e2e.test.ts b/test/e2e/e2e.test.ts
index 5b9bf899..f4f3a234 100644
--- a/test/e2e/e2e.test.ts
+++ b/test/e2e/e2e.test.ts
@@ -2,7 +2,6 @@ import execa from "execa";
 import getPort from "get-port";

 import { sign } from "@octokit/webhooks-methods";
-import bodyParser from "body-parser";
 import express from "express";

 import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
@@ -58,7 +57,7 @@ describe("end-to-end-tests", () => {
         res.status(201).json({});
       });

-    app.use(bodyParser.json());
+    app.use(express.json());
     app.use("/api/v3", httpMock);
     server = app.listen(mockServerPort);

--
2.34.1

Here the patch for destructuring the options
From c1cc2407c4e157d76b85dcfd4b186de5643fc459 Mon Sep 17 00:00:00 2001
From: uzlopak<EMAIL_ADDRESS>Date: Mon, 13 Nov 2023 15:10:15 +0100
Subject: [PATCH] destructure opts in probot-receive

---
 src/bin/probot-receive.ts | 38 ++++++++++++++++++++++++--------------
 1 file changed, 24 insertions(+), 14 deletions(-)

diff --git a/src/bin/probot-receive.ts b/src/bin/probot-receive.ts
index b8eff747..65401c59 100644
--- a/src/bin/probot-receive.ts
+++ b/src/bin/probot-receive.ts
@@ -74,37 +74,47 @@ async function main() {
     )
     .parse(process.argv);

-  const opts = program.opts();
-  const githubToken = opts.token;
+  const {
+    app: appId,
+    baseUrl,
+    token: githubToken,
+    event,
+    payloadPath,
+    logLevel,
+    logFormat,
+    logLevelInString,
+    logMessageKey,
+    sentryDsn,
+  } = program.opts();

-  if (!opts.event || !opts.payloadPath) {
+  if (!event || !payloadPath) {
     program.help();
   }

   const privateKey = getPrivateKey();
-  if (!githubToken && (!opts.app || !privateKey)) {
+  if (!githubToken && (!appId || !privateKey)) {
     console.warn(
       "No token specified and no certificate found, which means you will not be able to do authenticated requests to GitHub",
     );
   }

   const payload = JSON.parse(
-    fs.readFileSync(path.resolve(opts.payloadPath), "utf8"),
+    fs.readFileSync(path.resolve(payloadPath), "utf8"),
   );
   const log = getLog({
-    level: opts.logLevel,
-    logFormat: opts.logFormat,
-    logLevelInString: opts.logLevelInString,
-    logMessageKey: opts.logMessageKey,
-    sentryDsn: opts.sentryDsn,
+    level: logLevel,
+    logFormat,
+    logLevelInString,
+    logMessageKey,
+    sentryDsn,
   });

   const probot = new Probot({
-    appId: opts.app,
+    appId,
     privateKey: String(privateKey),
     githubToken: githubToken,
     log,
-    baseUrl: opts.baseUrl,
+    baseUrl: baseUrl,
   });

   const expressApp = express();
@@ -121,8 +131,8 @@ async function main() {
   );
   await probot.load(appFn, options);

-  probot.log.debug("Receiving event", opts.event);
-  probot.receive({ name: opts.event, payload, id: uuidv4() }).catch(() => {
+  probot.log.debug("Receiving event", event);
+  probot.receive({ name: event, payload, id: uuidv4() }).catch(() => {
     // Process must exist non-zero to indicate that the action failed to run
     process.exit(1);
   });
--
2.34.1

To run the tests in parallel, I have following proposal:
From 031128ed86918d196df8a7411fd5d8722cd226ff Mon Sep 17 00:00:00 2001
From: uzlopak<EMAIL_ADDRESS>Date: Mon, 13 Nov 2023 18:04:21 +0100
Subject: [PATCH] enable parallel testing

---
 package.json              |  4 ++--
 src/apps/default.ts       |  5 +++--
 src/server/server.ts      |  3 +++
 src/types.ts              |  2 ++
 test/apps/default.test.ts | 33 ++++++++++++++-------------------
 5 files changed, 24 insertions(+), 23 deletions(-)

diff --git a/package.json b/package.json
index 71031245..414a504d 100644
--- a/package.json
+++ b/package.json
@@ -13,8 +13,8 @@
     "lint": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\" \"docs/*.md\" *.md package.json tsconfig.json --end-of-line auto",
     "lint:fix": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"docs/*.md\" *.md package.json tsconfig.json --end-of-line auto",
     "pretest": "npm run build && tsc --noEmit -p test",
-    "test": "vitest run --no-threads",
-    "test:dev": "vitest --no-threads --ui --coverage",
+    "test": "vitest run",
+    "test:dev": "vitest --ui --coverage",
     "test:coverage": "vitest run --no-threads --coverage",
     "posttest": "npm run lint",
     "doc": "typedoc --options .typedoc.json"
diff --git a/src/apps/default.ts b/src/apps/default.ts
index 546e8501..54de6cc4 100644
--- a/src/apps/default.ts
+++ b/src/apps/default.ts
@@ -1,9 +1,10 @@
 import type { ApplicationFunctionOptions, Probot } from "../index";
 import { loadPackageJson } from "../helpers/load-package-json";
+import { resolve } from "path";

 export function defaultApp(
   _app: Probot,
-  { getRouter }: ApplicationFunctionOptions,
+  { getRouter, cwd = process.cwd() }: ApplicationFunctionOptions,
 ) {
   if (!getRouter) {
     throw new Error("getRouter() is required for defaultApp");
@@ -12,7 +13,7 @@ export function defaultApp(
   const router = getRouter();

   router.get("/probot", (_req, res) => {
-    const pkg = loadPackageJson();
+    const pkg = loadPackageJson(resolve(cwd, "package.json"));

     res.render("probot.handlebars", pkg);
   });
diff --git a/src/server/server.ts b/src/server/server.ts
index 318110d2..cb41e8f3 100644
--- a/src/server/server.ts
+++ b/src/server/server.ts
@@ -18,6 +18,7 @@ import EventSource from "eventsource";
 export const defaultWebhooksPath = "/api/github/webhooks";

 type State = {
+  cwd?: string;
   httpServer?: HttpServer;
   port?: number;
   host?: string;
@@ -44,6 +45,7 @@ export class Server {
     });

     this.state = {
+      cwd: options.cwd || process.cwd(),
       port: options.port,
       host: options.host,
       webhookPath: options.webhookPath || defaultWebhooksPath,
@@ -74,6 +76,7 @@ export class Server {

   public async load(appFn: ApplicationFunction) {
     await appFn(this.probotApp, {
+      cwd: this.state.cwd,
       getRouter: (path) => this.router(path),
     });
   }
diff --git a/src/types.ts b/src/types.ts
index bfc0026a..e31b1506 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -53,6 +53,7 @@ export type ProbotWebhooks = Webhooks<SimplifiedObject>;

 export type ApplicationFunctionOptions = {
   getRouter?: (path?: string) => express.Router;
+  cwd?: string;
   [key: string]: unknown;
 };
 export type ApplicationFunction = (
@@ -61,6 +62,7 @@ export type ApplicationFunction = (
 ) => void;

 export type ServerOptions = {
+  cwd?: string;
   log?: Logger;
   port?: number;
   host?: string;
diff --git a/test/apps/default.test.ts b/test/apps/default.test.ts
index f07f6584..b3828a8a 100644
--- a/test/apps/default.test.ts
+++ b/test/apps/default.test.ts
@@ -2,14 +2,13 @@ import Stream from "stream";

 import pino from "pino";
 import request from "supertest";
-import { beforeEach, describe, expect, it, afterEach } from "vitest";
+import { describe, expect, it } from "vitest";

 import { Probot, Server } from "../../src";
 import { defaultApp } from "../../src/apps/default";

 describe("default app", () => {
-  let server: Server;
-  let output: any;
+  let output = [];

   const streamLogsToOutput = new Stream.Writable({ objectMode: true });
   streamLogsToOutput._write = (object, _encoding, done) => {
@@ -17,31 +16,30 @@ describe("default app", () => {
     done();
   };

-  beforeEach(async () => {
+  async function instantiateServer(cwd = process.cwd()) {
     output = [];
-    server = new Server({
+    const server = new Server({
       Probot: Probot.defaults({
         appId: 1,
         privateKey: "private key",
       }),
       log: pino(streamLogsToOutput),
+      cwd,
     });

     await server.load(defaultApp);
-  });
+    return server;
+  }

   describe("GET /probot", () => {
-    it("returns a 200 response", () => {
+    it("returns a 200 response", async () => {
+      const server = await instantiateServer();
       return request(server.expressApp).get("/probot").expect(200);
     });

     describe("get info from package.json", () => {
-      let cwd: string;
-      beforeEach(() => {
-        cwd = process.cwd();
-      });
-
       it("returns the correct HTML with values", async () => {
+        const server = await instantiateServer();
         const actual = await request(server.expressApp)
           .get("/probot")
           .expect(200);
@@ -51,23 +49,20 @@ describe("default app", () => {
       });

       it("returns the correct HTML without values", async () => {
-        process.chdir(__dirname);
+        const server = await instantiateServer(__dirname);
         const actual = await request(server.expressApp)
           .get("/probot")
           .expect(200);
         expect(actual.text).toMatch("Welcome to your Probot App");
       });
-
-      afterEach(() => {
-        process.chdir(cwd);
-      });
     });
   });

   // Redirect does not work because webhooks middleware is using root path
   describe("GET /", () => {
-    it("redirects to /probot", () => {
-      return request(server.expressApp)
+    it("redirects to /probot", async () => {
+      const server = await instantiateServer(__dirname);
+      await request(server.expressApp)
         .get("/")
         .expect(302)
         .expect("location", "/probot");
--
2.34.1

How about removing handlebars?
From 671614a6ce13654e57769f39ac099da1abe92961 Mon Sep 17 00:00:00 2001
From: uzlopak<EMAIL_ADDRESS>Date: Tue, 14 Nov 2023 01:35:18 +0100
Subject: [PATCH] remove handlebars

---
 package.json                                  |   1 -
 src/apps/default.ts                           |  12 +-
 src/apps/setup.ts                             |  33 ++++-
 src/server/server.ts                          |   9 --
 .../import.handlebars => src/views/import.ts  |  20 ++-
 .../probot.handlebars => src/views/probot.ts  |  37 +++--
 views/setup.handlebars => src/views/setup.ts  |  43 ++++--
 .../views/success.ts                          |   8 +-
 test/apps/__snapshots__/default.test.ts.snap  |  70 ++++++++++
 test/apps/__snapshots__/setup.test.ts.snap    | 127 +++++++++++++++++-
 test/apps/default.test.ts                     |   2 +
 test/apps/setup.test.ts                       |  22 ++-
 12 files changed, 325 insertions(+), 59 deletions(-)
 rename views/import.handlebars => src/views/import.ts (88%)
 rename views/probot.handlebars => src/views/probot.ts (62%)
 rename views/setup.handlebars => src/views/setup.ts (62%)
 rename views/success.handlebars => src/views/success.ts (89%)
 create mode 100644 test/apps/__snapshots__/default.test.ts.snap

diff --git a/package.json b/package.json
index d63724a4..afb119df 100644
--- a/package.json
+++ b/package.json
@@ -54,7 +54,6 @@
     "dotenv": "^16.3.1",
     "eventsource": "^2.0.2",
     "express": "^4.18.2",
-    "express-handlebars": "^7.1.2",
     "ioredis": "^5.3.2",
     "js-yaml": "^4.1.0",
     "lru-cache": "^10.0.2",
diff --git a/src/apps/default.ts b/src/apps/default.ts
index 54de6cc4..bd15dd7b 100644
--- a/src/apps/default.ts
+++ b/src/apps/default.ts
@@ -2,6 +2,8 @@ import type { ApplicationFunctionOptions, Probot } from "../index";
 import { loadPackageJson } from "../helpers/load-package-json";
 import { resolve } from "path";

+import { probotView } from "../views/probot";
+
 export function defaultApp(
   _app: Probot,
   { getRouter, cwd = process.cwd() }: ApplicationFunctionOptions,
@@ -10,12 +12,16 @@ export function defaultApp(
     throw new Error("getRouter() is required for defaultApp");
   }

+  const pkg = loadPackageJson(resolve(cwd, "package.json"));
+  const probotViewRendered = probotView({
+    name: pkg.name,
+    version: pkg.version,
+    description: pkg.description,
+  });
   const router = getRouter();

   router.get("/probot", (_req, res) => {
-    const pkg = loadPackageJson(resolve(cwd, "package.json"));
-
-    res.render("probot.handlebars", pkg);
+    res.send(probotViewRendered);
   });

   router.get("/", (_req, res) => res.redirect("/probot"));
diff --git a/src/apps/setup.ts b/src/apps/setup.ts
index 18407e31..8935f8ba 100644
--- a/src/apps/setup.ts
+++ b/src/apps/setup.ts
@@ -9,6 +9,10 @@ import { getLoggingMiddleware } from "../server/logging-middleware";
 import type { ApplicationFunctionOptions } from "../types";
 import { isProduction } from "../helpers/is-production";

+import { importView } from "../views/import";
+import { setupView } from "../views/setup";
+import { successView } from "../views/success";
+
 export const setupAppFactory = (
   host: string | undefined,
   port: number | undefined,
@@ -18,6 +22,7 @@ export const setupAppFactory = (
     { getRouter }: ApplicationFunctionOptions,
   ) {
     const setup: ManifestCreation = new ManifestCreation();
+    const pkg = setup.pkg;

     // If not on Glitch or Production, create a smee URL
     if (
@@ -43,11 +48,18 @@ export const setupAppFactory = (

     route.get("/probot", async (req, res) => {
       const baseUrl = getBaseUrl(req);
-      const pkg = setup.pkg;
       const manifest = setup.getManifest(pkg, baseUrl);
       const createAppUrl = setup.createAppUrl;
       // Pass the manifest to be POST'd
-      res.render("setup.handlebars", { pkg, createAppUrl, manifest });
+      res.send(
+        setupView({
+          name: pkg.name,
+          version: pkg.version,
+          description: pkg.description,
+          createAppUrl,
+          manifest,
+        }),
+      );
     });

     route.get("/probot/setup", async (req: Request, res: Response) => {
@@ -71,10 +83,17 @@ export const setupAppFactory = (
       res.redirect(`${response}/installations/new`);
     });

+    const { WEBHOOK_PROXY_URL, GHE_HOST } = process.env;
+    const GH_HOST = `https://${GHE_HOST ?? "github.com"}`;
+
+    const importViewRendered = importView({
+      name: pkg.name,
+      WEBHOOK_PROXY_URL,
+      GH_HOST,
+    });
+
     route.get("/probot/import", async (_req, res) => {
-      const { WEBHOOK_PROXY_URL, GHE_HOST } = process.env;
-      const GH_HOST = `https://${GHE_HOST ?? "github.com"}`;
-      res.render("import.handlebars", { WEBHOOK_PROXY_URL, GH_HOST });
+      res.send(importViewRendered);
     });

     route.post("/probot/import", express.json(), async (req, res) => {
@@ -92,8 +111,10 @@ export const setupAppFactory = (
       printRestartMessage(app);
     });

+    const successViewRendered = successView({ name: pkg.name });
+
     route.get("/probot/success", async (_req, res) => {
-      res.render("success.handlebars");
+      res.send(successViewRendered);
     });

     route.get("/", (_req, res) => res.redirect("/probot"));
diff --git a/src/server/server.ts b/src/server/server.ts
index cb41e8f3..91b12580 100644
--- a/src/server/server.ts
+++ b/src/server/server.ts
@@ -11,7 +11,6 @@ import { createWebhookProxy } from "../helpers/webhook-proxy";
 import { VERSION } from "../version";
 import type { ApplicationFunction, ServerOptions } from "../types";
 import { Probot } from "../";
-import { engine } from "express-handlebars";
 import EventSource from "eventsource";

 // the default path as defined in @octokit/webhooks
@@ -63,14 +62,6 @@ export class Server {
       }),
     );

-    this.expressApp.engine(
-      "handlebars",
-      engine({
-        defaultLayout: false,
-      }),
-    );
-    this.expressApp.set("view engine", "handlebars");
-    this.expressApp.set("views", join(__dirname, "..", "..", "views"));
     this.expressApp.get("/ping", (_req, res) => res.end("PONG"));
   }

diff --git a/views/import.handlebars b/src/views/import.ts
similarity index 88%
rename from views/import.handlebars
rename to src/views/import.ts
index 809c858f..aff1a33d 100644
--- a/views/import.handlebars
+++ b/src/views/import.ts
@@ -1,11 +1,20 @@
-<!DOCTYPE html>
+export function importView({
+  name,
+  GH_HOST,
+  WEBHOOK_PROXY_URL = "",
+}: {
+  name: string;
+  GH_HOST: string;
+  WEBHOOK_PROXY_URL?: string;
+}): string {
+  return `<!DOCTYPE html>
 <html lang="en" class="height-full" data-color-mode="auto" data-light-theme="light" data-dark-theme="dark">

 <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <meta http-equiv="X-UA-Compatible" content="ie=edge">
-    <title>Import {{#if pkg.name }}{{ pkg.name }}{{else}}Your App{{/if}} | built with Probot</title>
+    <title>Import ${name || "Your App"} | built with Probot</title>
     <link rel="icon" href="/probot/static/probot-head.png">
     <link rel="stylesheet" href="/probot/static/primer.css">
 </head>
@@ -20,9 +29,9 @@
             <h3>Step 1:</h3>
             <p class="d-block mt-2">
                 Replace your app's Webhook URL with <br>
-                <b>{{ WEBHOOK_PROXY_URL }}</b>
+                <b>${WEBHOOK_PROXY_URL}</b>
             </p>
-            <a class="d-block mt-2" href="{{ GH_HOST }}/settings/apps" target="__blank" rel="noreferrer">
+            <a class="d-block mt-2" href="${GH_HOST}/settings/apps" target="__blank" rel="noreferrer">
                 You can do it here
             </a>

@@ -81,4 +90,5 @@
     </script>
 </body>

-</html>
\ No newline at end of file
+</html>`;
+}
diff --git a/views/probot.handlebars b/src/views/probot.ts
similarity index 62%
rename from views/probot.handlebars
rename to src/views/probot.ts
index 80977a07..30ed5abd 100644
--- a/views/probot.handlebars
+++ b/src/views/probot.ts
@@ -1,10 +1,19 @@
-<!DOCTYPE html>
+export function probotView({
+  name,
+  description,
+  version,
+}: {
+  name: string;
+  description: string;
+  version: string;
+}): string {
+  return `<!DOCTYPE html>
 <html lang="en" class="height-full" data-color-mode="auto" data-light-theme="light" data-dark-theme="dark">
   <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <meta http-equiv="X-UA-Compatible" content="ie=edge">
-    <title>{{#if name }}{{ name }}{{else}}Your App{{/if}} | built with Probot</title>
+    <title>${name || "Your App"} | built with Probot</title>
     <link rel="icon" href="/probot/static/probot-head.png">
     <link rel="stylesheet" href="/probot/static/primer.css">
   </head>
@@ -13,17 +22,18 @@
       <img src="/probot/static/robot.svg" alt="Probot Logo" width="100" class="mb-6">
       <div class="box-shadow rounded-2 border p-6 bg-white">
         <h1>
-          Welcome to {{#if name }}{{ name }}{{else}}your Probot App{{/if}}
-          {{#if version}}
-            <span class="Label Label--outline v-align-middle ml-2 text-gray-light">v{{ version }}</span>
-          {{/if}}
-        </h1>
+          Welcome to ${name || "your Probot App"}
+${
+  version
+    ? `            <span class="Label Label--outline v-align-middle ml-2 text-gray-light">v${version}</span>\n`
+    : ""
+}        </h1>

-        {{#if description }}
-          <p>{{ description }}</p>
-        {{else}}
-          <p>This bot was built using <a href="https://github.com/probot/probot">Probot</a>, a framework for building GitHub Apps.</p>
-        {{/if}}
+          <p>${
+            description
+              ? description
+              : 'This bot was built using <a href="https://github.com/probot/probot">Probot</a>, a framework for building GitHub Apps.'
+          }</p>
       </div>

       <div class="mt-4">
@@ -35,4 +45,5 @@
       </div>
     </div>
   </body>
-</html>
+</html>`;
+}
diff --git a/views/setup.handlebars b/src/views/setup.ts
similarity index 62%
rename from views/setup.handlebars
rename to src/views/setup.ts
index d9e282f9..95a920d3 100644
--- a/views/setup.handlebars
+++ b/src/views/setup.ts
@@ -1,10 +1,24 @@
+export function setupView({
+  name,
+  description,
+  version,
+  createAppUrl,
+  manifest,
+}: {
+  name: string;
+  description: string;
+  version: string;
+  createAppUrl: string;
+  manifest: string;
+}): string {
+  return `<!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en" class="height-full" data-color-mode="auto" data-light-theme="light" data-dark-theme="dark">
   <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <meta http-equiv="X-UA-Compatible" content="ie=edge">
-    <title>Setup {{#if pkg.name }}{{ pkg.name }}{{else}}Your App{{/if}} | built with Probot</title>
+    <title>Setup ${name || "Your App"} | built with Probot</title>
     <link rel="icon" href="/probot/static/probot-head.png">
     <link rel="stylesheet" href="/probot/static/primer.css">
   </head>
@@ -13,17 +27,19 @@
       <img src="/probot/static/robot.svg" alt="Probot Logo" width="100" class="mb-6">
       <div class="box-shadow rounded-2 border p-6 bg-white">
         <h1>
-          Welcome to {{#if pkg.name }}{{ pkg.name }}{{else}}your Probot App{{/if}}
-          {{#if version}}
-            <span class="Label Label--outline v-align-middle ml-2 text-gray-light">v{{ pkg.version }}</span>
-          {{/if}}
+          Welcome to ${name || "your Probot App"}
+          ${
+            version
+              ? `<span class="Label Label--outline v-align-middle ml-2 text-gray-light">v${version}</span>`
+              : ""
+          }
         </h1>

-        {{#if pkg.description }}
-          <p>{{ pkg.description }}</p>
-        {{else}}
-          <p>This app was built using <a href="https://github.com/probot/probot">Probot</a>, a framework for building GitHub Apps.</p>
-        {{/if}}
+        <p>${
+          description
+            ? description
+            : 'This app was built using <a href="https://github.com/probot/probot">Probot</a>, a framework for building GitHub Apps.'
+        }</p>

         <div class="text-left mt-6">
           <h2 class="alt-h3 mb-2">Getting Started</h2>
@@ -31,8 +47,8 @@
           <p>To start building a GitHub App, you'll need to register a new app on GitHub.</p>
           <br>

-          <form action="{{ createAppUrl }}" method="post" target="_blank" class="d-flex flex-items-center">
-            <button class="btn btn-outline" name="manifest" id="manifest" value='{{ manifest }}' >Register GitHub App</button>
+          <form action="${createAppUrl}" method="post" target="_blank" class="d-flex flex-items-center">
+            <button class="btn btn-outline" name="manifest" id="manifest" value='${manifest}' >Register GitHub App</button>
             <a href="/probot/import" class="ml-2">or use an existing Github App</a>
           </form>
         </div>
@@ -47,4 +63,5 @@
       </div>
     </div>
   </body>
-</html>
+</html>`;
+}
diff --git a/views/success.handlebars b/src/views/success.ts
similarity index 89%
rename from views/success.handlebars
rename to src/views/success.ts
index 6ea5d23d..50d88c34 100644
--- a/views/success.handlebars
+++ b/src/views/success.ts
@@ -1,10 +1,11 @@
-<!DOCTYPE html>
+export function successView({ name }: { name: string }): string {
+  return `<!DOCTYPE html>
 <html lang="en" class="height-full" data-color-mode="auto" data-light-theme="light" data-dark-theme="dark">
   <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <meta http-equiv="X-UA-Compatible" content="ie=edge">
-    <title>Setup {{#if pkg.name }}{{ pkg.name }}{{else}}Your App{{/if}} | built with Probot</title>
+    <title>Setup ${name || "Your App"} | built with Probot</title>
     <link rel="icon" href="/probot/static/probot-head.png">
     <link rel="stylesheet" href="/probot/static/primer.css">
   </head>
@@ -28,4 +29,5 @@
       </div>
     </div>
   </body>
-</html>
+</html>`;
+}
diff --git a/test/apps/__snapshots__/default.test.ts.snap b/test/apps/__snapshots__/default.test.ts.snap
new file mode 100644
index 00000000..4f7dfdbf
--- /dev/null
+++ b/test/apps/__snapshots__/default.test.ts.snap
@@ -0,0 +1,70 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`default app > GET /probot > get info from package.json > returns the correct HTML with values 1`] = `
+"<!DOCTYPE html>
+<html lang=\\"en\\" class=\\"height-full\\" data-color-mode=\\"auto\\" data-light-theme=\\"light\\" data-dark-theme=\\"dark\\">
+  <head>
+    <meta charset=\\"UTF-8\\">
+    <meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1.0\\">
+    <meta http-equiv=\\"X-UA-Compatible\\" content=\\"ie=edge\\">
+    <title>probot | built with Probot</title>
+    <link rel=\\"icon\\" href=\\"/probot/static/probot-head.png\\">
+    <link rel=\\"stylesheet\\" href=\\"/probot/static/primer.css\\">
+  </head>
+  <body class=\\"height-full bg-gray-light\\">
+    <div class=\\"d-flex flex-column flex-justify-center flex-items-center text-center height-full\\">
+      <img src=\\"/probot/static/robot.svg\\" alt=\\"Probot Logo\\" width=\\"100\\" class=\\"mb-6\\">
+      <div class=\\"box-shadow rounded-2 border p-6 bg-white\\">
+        <h1>
+          Welcome to probot
+            <span class=\\"Label Label--outline v-align-middle ml-2 text-gray-light\\">v0.0.0-development</span>
+        </h1>
+
+          <p>A framework for building GitHub Apps to automate and improve your workflow</p>
+      </div>
+
+      <div class=\\"mt-4\\">
+        <h4 class=\\"alt-h4 text-gray-light\\">Need help?</h4>
+        <div class=\\"d-flex flex-justify-center mt-2\\">
+          <a href=\\"https://probot.github.io/docs/\\" class=\\"btn btn-outline mr-2\\">Documentation</a>
+          <a href=\\"https://probot-slackin.herokuapp.com/\\" class=\\"btn btn-outline\\">Chat on Slack</a>
+        </div>
+      </div>
+    </div>
+  </body>
+</html>"
+`;
+
+exports[`default app > GET /probot > get info from package.json > returns the correct HTML without values 1`] = `
+"<!DOCTYPE html>
+<html lang=\\"en\\" class=\\"height-full\\" data-color-mode=\\"auto\\" data-light-theme=\\"light\\" data-dark-theme=\\"dark\\">
+  <head>
+    <meta charset=\\"UTF-8\\">
+    <meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1.0\\">
+    <meta http-equiv=\\"X-UA-Compatible\\" content=\\"ie=edge\\">
+    <title>Your App | built with Probot</title>
+    <link rel=\\"icon\\" href=\\"/probot/static/probot-head.png\\">
+    <link rel=\\"stylesheet\\" href=\\"/probot/static/primer.css\\">
+  </head>
+  <body class=\\"height-full bg-gray-light\\">
+    <div class=\\"d-flex flex-column flex-justify-center flex-items-center text-center height-full\\">
+      <img src=\\"/probot/static/robot.svg\\" alt=\\"Probot Logo\\" width=\\"100\\" class=\\"mb-6\\">
+      <div class=\\"box-shadow rounded-2 border p-6 bg-white\\">
+        <h1>
+          Welcome to your Probot App
+        </h1>
+
+          <p>This bot was built using <a href=\\"https://github.com/probot/probot\\">Probot</a>, a framework for building GitHub Apps.</p>
+      </div>
+
+      <div class=\\"mt-4\\">
+        <h4 class=\\"alt-h4 text-gray-light\\">Need help?</h4>
+        <div class=\\"d-flex flex-justify-center mt-2\\">
+          <a href=\\"https://probot.github.io/docs/\\" class=\\"btn btn-outline mr-2\\">Documentation</a>
+          <a href=\\"https://probot-slackin.herokuapp.com/\\" class=\\"btn btn-outline\\">Chat on Slack</a>
+        </div>
+      </div>
+    </div>
+  </body>
+</html>"
+`;
diff --git a/test/apps/__snapshots__/setup.test.ts.snap b/test/apps/__snapshots__/setup.test.ts.snap
index fefb1bd7..43e8265e 100644
--- a/test/apps/__snapshots__/setup.test.ts.snap
+++ b/test/apps/__snapshots__/setup.test.ts.snap
@@ -1,6 +1,95 @@
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

-exports[`Setup app > GET /probot/setup > returns a redirect 1`] = `
+exports[`Setup app > GET /probot/import > renders importView 1`] = `
+"<!DOCTYPE html>
+<html lang=\\"en\\" class=\\"height-full\\" data-color-mode=\\"auto\\" data-light-theme=\\"light\\" data-dark-theme=\\"dark\\">
+
+<head>
+    <meta charset=\\"UTF-8\\">
+    <meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1.0\\">
+    <meta http-equiv=\\"X-UA-Compatible\\" content=\\"ie=edge\\">
+    <title>Import probot | built with Probot</title>
+    <link rel=\\"icon\\" href=\\"/probot/static/probot-head.png\\">
+    <link rel=\\"stylesheet\\" href=\\"/probot/static/primer.css\\">
+</head>
+
+<body class=\\"bg-gray-light\\">
+    <div class=\\"d-flex flex-column flex-justify-center flex-items-center text-center height-full py-6\\">
+        <a href=\\"/probot\\"><img src=\\"/probot/static/robot.svg\\" alt=\\"Probot Logo\\" width=\\"100\\" class=\\"mb-6\\"></a>
+        <div class=\\"box-shadow rounded-2 border p-6 bg-white\\">
+            <h2>Use existing Github App</h2>
+            <br>
+
+            <h3>Step 1:</h3>
+            <p class=\\"d-block mt-2\\">
+                Replace your app's Webhook URL with <br>
+                <b></b>
+            </p>
+            <a class=\\"d-block mt-2\\" href=\\"https://github.com/settings/apps\\" target=\\"__blank\\" rel=\\"noreferrer\\">
+                You can do it here
+            </a>
+
+            <br>
+            <h3>Step 2:</h3>
+            <p class=\\"mt-2\\">Fill out this form</p>
+            <form onsubmit=\\"return onSubmit(event) || false\\">
+                <label class=\\"d-block mt-2\\" for=\\"appId\\">App Id</label>
+                <input class=\\"form-control width-full\\" type=\\"text\\" required=\\"true\\" id=\\"appId\\" name=\\"appId\\"><br>
+
+                <label class=\\"d-block mt-3\\" for=\\"whs\\">Webhook secret (required!)</label>
+                <input class=\\"form-control width-full\\" type=\\"password\\" required=\\"true\\" id=\\"whs\\" name=\\"whs\\"><br>
+
+                <label class=\\"d-block mt-3\\" for=\\"pem\\">Private Key</label>
+                <input class=\\"form-control width-full m-2\\" type=\\"file\\" accept=\\".pem\\" required=\\"true\\" id=\\"pem\\"
+                    name=\\"pem\\">
+                <br>
+
+                <button class=\\"btn btn-outline m-2\\" type=\\"submit\\">Submit</button>
+            </form>
+        </div>
+
+        <div class=\\"mt-4\\">
+            <h4 class=\\"alt-h4 text-gray-light\\">Need help?</h4>
+            <div class=\\"d-flex flex-justify-center mt-2\\">
+                <a href=\\"https://probot.github.io/docs/\\" class=\\"btn btn-outline mr-2\\">Documentation</a>
+                <a href=\\"https://probot-slackin.herokuapp.com/\\" class=\\"btn btn-outline\\">Chat on Slack</a>
+            </div>
+        </div>
+    </div>
+    <script>
+        function onSubmit(e) {
+            e.preventDefault();
+
+            const idEl = document.getElementById('appId');
+            const appId = idEl.value;
+
+
+            const secretEl = document.getElementById('whs');
+            const webhook_secret = secretEl.value;
+
+            const fileEl = document.getElementById('pem');
+            const file = fileEl.files[0];
+
+            file.text().then((text) => fetch('', {
+                method: 'POST',
+                headers: { 'content-type': 'application/json' },
+                body: JSON.stringify({ appId, pem: text, webhook_secret })
+            })).then((r) => {
+                if (r.ok) {
+                    location.replace('/probot/success');
+                }
+            }).catch((e) => alert(e));
+            return false;
+        }
+    </script>
+</body>
+
+</html>"
+`;
+
+exports[`Setup app > GET /probot/setup > returns a redirect 1`] = `"Found. Redirecting to /apps/my-app/installations/new"`;
+
+exports[`Setup app > GET /probot/setup > returns a redirect 2`] = `
 [
   [
     {
@@ -24,6 +113,42 @@ exports[`Setup app > GET /probot/setup > returns a redirect 1`] = `
 ]
 `;

+exports[`Setup app > GET /probot/success > returns a 200 response 1`] = `
+"<!DOCTYPE html>
+<html lang=\\"en\\" class=\\"height-full\\" data-color-mode=\\"auto\\" data-light-theme=\\"light\\" data-dark-theme=\\"dark\\">
+  <head>
+    <meta charset=\\"UTF-8\\">
+    <meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1.0\\">
+    <meta http-equiv=\\"X-UA-Compatible\\" content=\\"ie=edge\\">
+    <title>Setup probot | built with Probot</title>
+    <link rel=\\"icon\\" href=\\"/probot/static/probot-head.png\\">
+    <link rel=\\"stylesheet\\" href=\\"/probot/static/primer.css\\">
+  </head>
+  <body class=\\"height-full bg-gray-light\\">
+    <div class=\\"d-flex flex-column flex-justify-center flex-items-center text-center height-full\\">
+      <img src=\\"/probot/static/robot.svg\\" alt=\\"Probot Logo\\" width=\\"100\\" class=\\"mb-6\\">
+      <div class=\\"box-shadow rounded-2 border p-6 bg-white\\">
+        <div class=\\"text-center\\">
+          <h1 class=\\"alt-h3 mb-2\\">Congrats! You have successfully installed your app!
+            <br>
+            Checkout <a href=\\"https://probot.github.io/docs/webhooks/\\">Receiving webhooks</a> and <a href=\\"https://probot.github.io/docs/github-api/\\">Interacting with GitHub</a> to learn more!</h1>
+        </div>
+      </div>
+
+      <div class=\\"mt-4\\">
+        <h4 class=\\"alt-h4 text-gray-light\\">Need help?</h4>
+        <div class=\\"d-flex flex-justify-center mt-2\\">
+          <a href=\\"https://probot.github.io/docs/\\" class=\\"btn btn-outline mr-2\\">Documentation</a>
+          <a href=\\"https://probot-slackin.herokuapp.com/\\" class=\\"btn btn-outline\\">Chat on Slack</a>
+        </div>
+      </div>
+    </div>
+  </body>
+</html>"
+`;
+
+exports[`Setup app > POST /probot/import > 400 when keys are missing 1`] = `"appId and/or pem and/or webhook_secret missing"`;
+
 exports[`Setup app > POST /probot/import > updates .env 1`] = `
 [
   [
diff --git a/test/apps/default.test.ts b/test/apps/default.test.ts
index b3828a8a..c149be09 100644
--- a/test/apps/default.test.ts
+++ b/test/apps/default.test.ts
@@ -46,6 +46,7 @@ describe("default app", () => {
         expect(actual.text).toMatch("Welcome to probot");
         expect(actual.text).toMatch("A framework for building GitHub Apps");
         expect(actual.text).toMatch(/v\d+\.\d+\.\d+/);
+        expect(actual.text).toMatchSnapshot();
       });

       it("returns the correct HTML without values", async () => {
@@ -54,6 +55,7 @@ describe("default app", () => {
           .get("/probot")
           .expect(200);
         expect(actual.text).toMatch("Welcome to your Probot App");
+        expect(actual.text).toMatchSnapshot();
       });
     });
   });
diff --git a/test/apps/setup.test.ts b/test/apps/setup.test.ts
index 7154634f..53782911 100644
--- a/test/apps/setup.test.ts
+++ b/test/apps/setup.test.ts
@@ -133,20 +133,26 @@ describe("Setup app", () => {

       await server.load(setupAppFactory(undefined, undefined));

-      await request(server.expressApp)
+      const setupResponse = await request(server.expressApp)
         .get("/probot/setup")
         .query({ code: "123" })
         .expect(302)
         .expect("Location", "/apps/my-app/installations/new");

+      expect(setupResponse.text).toMatchSnapshot();
+
       expect(mocks.createChannel).toHaveBeenCalledTimes(2);
       expect(mocks.updateDotenv.mock.calls).toMatchSnapshot();
     });
   });

   describe("GET /probot/import", () => {
-    it("renders import.handlebars", async () => {
-      await request(server.expressApp).get("/probot/import").expect(200);
+    it("renders importView", async () => {
+      const importView = await request(server.expressApp)
+        .get("/probot/import")
+        .expect(200);
+
+      expect(importView.text).toMatchSnapshot();
     });
   });

@@ -175,17 +181,23 @@ describe("Setup app", () => {
         webhook_secret: "baz",
       });

-      await request(server.expressApp)
+      const importResponse = await request(server.expressApp)
         .post("/probot/import")
         .set("content-type", "application/json")
         .send(body)
         .expect(400);
+
+      expect(importResponse.text).toMatchSnapshot();
     });
   });

   describe("GET /probot/success", () => {
     it("returns a 200 response", async () => {
-      await request(server.expressApp).get("/probot/success").expect(200);
+      const successResponse = await request(server.expressApp)
+        .get("/probot/success")
+        .expect(200);
+
+      expect(successResponse.text).toMatchSnapshot();

       expect(mocks.createChannel).toHaveBeenCalledTimes(1);
     });
--
2.34.1

@AaronDewes
Fix conflicts and merge? :)))

@AaronDewes Fix conflicts and merge? :)))

Sure, I'll do that in about 1h.

🔥 Legends, thanks for the hard work!

Amazing! 🎉


How is this project useful to you?

Since GitHub Actions has released "saveState", this action is not as useful as before.
Therefore, I'm thinking of deprecating it, unless someone can tell me there is a utility to keeping it around (besides for the sake of not having to migrate existing code).
I'll keep the repo there regardless, even if I deprecate the action, but I'll probably Archive it if it isn't useful to anybody.

https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#using-workflow-commands-to-access-toolkit-functions

Example: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#example


2 bugs

Hey there! Thanks for this plugin, it's an awesome improvement over Godot's pretty clunky animation workflow.
I discovered 2 bugs this week in my project. One of them I believe I was able to solve with a simple code change in the plugin, and the other I was able to fix in my project, but I have absolutely no clue what caused it.

Changing the value of Sprite.frame at the same time as Sprite.animation key is not being updated correctly when the animation track is below the frame track (pictured).

Place the animation track above the frame track fixes this issue (pictured).

From testing, it appears that the AnimationPlayer always plays the first frame (and no others) that is used in the AnimatedSprite2D node animation. This will not update in game until the animation key is reached on the animation track, where frames after this point will update correctly. I assume that the engine reads the tracks top-down, so any sprite frames that appear directly "up or left" of the animation key are treated as having no value and are skipped entirely. You can easily test this by importing an animation, then change the value of Sprite.frame at time 0 to anything else, and you can see that this frame will be ignored in favor of the first frame from the AnimatedSprite2D. If you move the Sprite.animation key further along the timeline, this will become true for all frames above or before it. However, if the animation plays again (queue or loop), it will be updated despite this, presumably because the "animation" value was already set by the previous play of it. If the animation changes to something else first, this will break again.

I fixed this pretty simply by just reversing the order that the plugin adds the tracks by default.
anim_track = animation.add_track(Animation.TYPE_VALUE, 0)
frame_track = animation.add_track(Animation.TYPE_VALUE, 1)

I had a very strange bug of an animation that was somehow constantly queueing itself to be played again (specifically queuing, not looping) infinitely.

How this happened, I have no idea, and I wasn't able to replicate the cause. However, when opening up the animation library in the scene for it, I found the line that I assume was causing that to happen.

Looking over the plugins code, I wasn't able to find anything that could cause this to happen, but I don't understand it well enough to rule it out since it's the only plugin I'm using.
This was fixed by changing the name of the animation in the AnimationPlayer, saving, reloading the project, then changing the animation name back to what it was originally. I don't have a code fix for this to prevent it from occurring again.

Bug 2 is very odd indeed. As you've said, the plugin itself has no reference to AnimationPlayer.queue, so I don't see how it could cause the queue issue. Since it's not reproducible there's not much I can do, but if you're able to reproduce it you can report more details here.
As for the first issue if the solutions works for you, you can open up a PR and I'll be happy to merge it.


Project: Create collective flow (design)

New flow to create a collective to embrace the new business model discussed in #2251.
This closes #2331 #2295 #2251
Links

Google Drive
Specs

Design

Latest design file: https://www.figma.com/file/e71tBo0Sr8J7R5n6iMkqI42d/OC.COM-04-Collectives-→-Create-Edit?node-id=7762%3A42619
Production ready: No

Next steps:

Work on the specifications to make sure we don't forget anything big
@opencollective/design  to work on wireframes (with me)
Call with the team to discuss the first wireframes once ready to share
Second iteration on wireframes
Call to approve the wireframes
Design the different screens
Implementation
Design fix ups based on implementation feedback
Staging
Last checks and fixes
Production

Sorry unpinned by mistake

What is our policy for "pinned" issues? Why pinning this one over the 374 others? ping @Memo-Es

@xdamman finally got a chance to watch the Friday video where you cover this.

Create new fiscal host is redundant to selecting 'our organization' as bank account owner in the previous screen. It should assume that if you click 'fiscal sponsor' instead of 'our organization' that means you're not creating a new host.
I'm still confused about what's going to happen to open source Collectives who don't want to join OSC (want to self-host or join OC EU etc) - what will their path be, because they will likely click "open source" on that first screen. Maybe add an option on the 'apply to OSC' screen next to "github" and "manual verification" that says "use another host or start your own", which sends you back to the other flow?

It's redundant but it's ok because it's a very different user story. It's one thing to use your organization as a fiscal host for your collective, it's an entirely different ballgame to become a fiscal host for others (need to define formal policies, terms of service, application process, pricing strategy, ...)
There is a already a "request manual verification" button below the "Verify using Github stars" one

If they pick manual process, they can apply to OSC or to any other host (or use their own) like any other new collective.

Wireframes update
In a call with @alanna we talked about making ridiculously easy to create a collective so we discussed a 1-step-version and a contact extra step. (after choosing open source vs any other initiative)
Next, are the results and the comments.

Create Collective
Contact

@alanna
First screen is much better! For “what does your Collective do” that’s the short description, right? We should just make sure it’s character limited to match the short description field.
The second screen looks ok, but do we really need it as part of the setup process? Can’t they add that info later if they want? The basic contact from is already built in and 99% of Collectives will just stick with that anyway.
I think we should be making this setup flow as simple as possible and focus on guiding creators to add to their Collective once they reach their actual page.

Any thoughts? @xdamman @piamancini

Here's my opinion:
One major goal of the Collective onboarding redesign is to reduce barriers to new Collectives getting on board.
A big way we're achieving that is to make the initial setup super simple, then focusing on encouraging core contributors to build out their Collective based on their priorities, once their basic page is set up. If that's about financial contributions, they'll set that up. If it's about adding team members and creating updates, they'll do that. Different Collectives will have different focuses and that's OK.
So I think we should make the initial setup super simple and take out everything that's not absolutely necessary, really strip it down. I think what's on this single screen is enough:

Then, we can really invest in the experience of new users once they reach their new Collective page, having really good action buttons for all the ways they can build up and enhance their Collective from there. I think this will be a much improved onboarding process and help guide people to the areas that are most important for their communities.

Just to clarify, I still think we need the "open source" or "other initiative" screen before this, so we can have a good experience for the large number of Collectives who are coming to us looking to join Open Source Collective. The designs already shared above for that flow are great. I hope this is a temporary state, and in the future we have many other communities just as large as Open Source, but for now we should make sure it's an easy flow for them.

100% agree with @alanna on this one. I think asking for contact details here is a distraction.
The smart default of having a contact form will probably fit most collectives, at least at the begining. We should make it easy to change.

I agree - having to think about what's their preferred contact details at the beginning can make them have to go back to their communities to figure this out, therefore distracting them from starting a collective. This doesn't seem essential at this point in the flow.

Update
This is the version presented in today's demo, it features a 1-step collective creation with special attention on the collective values (See values modal). And the start of the onboarding inside the collective page.
Link to the prototype
@xdamman @alanna @piamancini

Sign in →
Type of collective →
Create collective →
Values modal →
Onboarding 1 →
Onboarding 2 →
Onboarding 3 →

I think this is very well resolved @Memo-Es . It allows for @xdamman's request to have the contact form details in the flow, without blocking it with them.

Looks good!

Looks good.
However, there is a confusion in the last step “choose the way people can contact Agora”. Social media accounts are not ways to contact the collective, they are primarily ways to receive updates.
What was wrong with the wireframe of Karim for that particular step? I understand there was a confusion between the concept of recipient and core contributor (since we don’t distinguish yet between the two), but adding a step “add core contributors” before seems to solve that, no?
I also really think that we don’t understand each other in those conversations. The experience of the team is mostly with open source communities. When was the last time that you started or joined a non open source collective? What was your experience? From my experience, one of the very first steps they do is create a gmail address (or protonmail) so that they can be contacted. They absolutely need an email address.
I understand that today most collectives on opencollective.com are open source and those don’t need (and probably don’t even want) yet another way to be contacted. So maybe we can skip that step for open source collectives?
Another way to solve this issue is to put this feature out of the open collective core and ask hosts of non open source collective to finance the development of such feature. This would mean that we need to think more in terms of enabling hosts to customize the experience.

From my experience, one of the very first steps they do is create a gmail address (or protonmail) so that they can be contacted. They absolutely need an email address.

We already have contact functionality built in. There's a nice button and form. Users will not have a problem contacting a Collective even if they don't add extra contact info. The screen in the flow here gives them many options for adding more ways to communicate if they want.

That's definitely not the best for non open source collectives. It would go a very long way for them to be able to share in other places (posters, flyers, emails, ...) an actual email address. But let's move on. We will make this part of another project lead by non open source hosts.

I hope this is a temporary state, and in the future we have many other communities just as large as Open Source, but for now we should make sure it's an easy flow for them.

Same. But there is a chicken and egg here. In order to make other types big we need to make a statement and drive attention to them. So I'd love to see a third option on the selection of type of collective:

For Open Source Projects
For Local Groups of #ClimateJustice Movements
For all other initiatives (maybe this one can just be a link below in the same style as "skip")

In the same way that there is a special flow for open source projects, there could eventually be a special flow for #ClimateJustice movements (not now though).

A big way we're achieving that is to make the initial setup super simple, then focusing on encouraging core contributors to build out their Collective based on their priorities, once their basic page is set up. If that's about financial contributions, they'll set that up. If it's about adding team members and creating updates, they'll do that. Different Collectives will have different focuses and that's OK.

I agree with that. So instead of having an onboarding like this

step 1
step 2
step 3

Why not simply have good calls to action within the page where sections are empty at first?
Maybe also add a place with check marks showing a list of TODOs (not as a modal that can be quickly dismissed then people don't know how to ever get back to it)

[x] Add a logo
[ ] Add a fiscal host
[ ] Edit tiers

This is also a good opportunity to improve how people can get to /edit (the gear on the avatar is not obvious).

Why not simply have good calls to action within the page where sections are empty at first?
Maybe also add a place with check marks showing a list of TODOs (not as a modal that can be quickly dismissed then people don't know how to ever get back to it)

I think this design achieves that without getting in the way of users. We want them to add contributors but we want them to be able to skip it as well. It's like when you set up a phone, you can go through all the steps of click skip and finish later.
At this stage, we would like to start implementing instead of going back to the drawing board. I don't think the current design is going to be substantially better by adding CTAs - which by the way, you see already when you are logged in as an admin. Sure they, can be improved, but the goal of making it very easy for collectives to start is achieved here.
If you don't think the contact is important now, we can remove that step. I don't quite follow what you want to say above in relation to that.
I am ok with the idea of adding open source, climate change and others. Where would you take the climate change ones to? How different is is from the others path?

Personally I don't think we need more complexity at this point, and I don't see the necessity of adding more options to the initial "open source or other" selection screen. The only reason we have that branching option is because we have a completely seperate technical implementation of the onboarding process for open source projects, i.e. the Github verification flow. There's no need for a separation for other categories unless they have a similar specialised flow.
I look forward to a time when there are many verticals who create their own unique onboard flows and it's no longer up to Open Collective to build and design them, but we're not there yet. The goal now is to make onboarding as easy and simple as possible, while not damaging the existing open source onboarding process. I think the design where it is now achieves this, and we should move forward. Otherwise we are delaying what will be a big improvement for our user experience and letting the perfect be the enemy of the good.

Here are my comments based on latest renderings from @Memo-Es

@xdamman Could you elaborate a little bit more on this one?

I made the prototype private because we are working on it right now, also tomorrow will share a google doc for copy editing

That screen is about “Contact” as the top of the modal suggests.
Twitter is before all a tool to communicate and follow updates. So that’s not the right place to ask for their twitter account. It should be asked along with their website url, facebook page, etc. (Btw, most collectives don’t have a twitter account, most however do have a facebook one)
So, when it comes to contact the collective, open source collectives will most likely opt to redirect people to write an issue on their repo, e.g. https://github.com/opencollective/opencollective/issues
All other collectives, either already have created a generic email address, have a website with a contact form or, most likely scenario, they don’t have anything yet and they would love to have an easy way to contact them privately. Others, would rather avoid having people to contact privately and force people to use the conversations feature to reach out to them.
So that’s the purpose of that step in that modal.

Let's also add the contact form in the design, hello@ email is not ready to be shared publicly yet but we have a contact form already developed.

This screen is only showed to the creator of the collective so I’d definitely share the info@ email there. We don’t have (yet) to communicate it publicly on the collective page, we can keep that for another issue. But at least they would know that they can communicate on their own channels that email address.
Yes, maybe this modal can be larger and show more explicitly a small version of the contact form and asking, with examples, what they prefer (send those messages to an existing email address of your choice, to one we create for you info@, or, and this need probably an updated design, to an external url such as github issues or a facebook page)

But at least they would know that they can communicate on their own channels that email address.

The issue still stands if they share it by other means (social networks, their website...). We shouldn't encourage the public use of this address while the spam issue hasn't been properly investigated.

Huh. We need to fix that. Is there an issue for it yet? (Couldn’t find any with a quick search on “email”) Should we consider putting a bounty on it and reach out to people with experience with such topics?
In the meantime, can you live with it? I’m part of a gazillion of collectives and spam issues has never been an issue for me so far. If not, we can omit it and just say “message will be sent to all core contributors”. But that doesn’t make me happy ;-)
(Also given that through this interface one clearly won’t be able to assume anymore that there is an info@ for every collective, mass spamming potential is reduced)

The problem is not about admins getting spammed, it's about email providers (Google) flagging our server as sending spam. We rely too much on emails (signup/SignIn) to take the risk of letting that happen. Without saying, sending spam is against Heroku's TOS and we don't want them to take action against our servers neither. So it's not just a preference, it's a real blocker.
I should have created an issue for it at the time, I'll do that tomorrow to ensure that we take action. Because of its nature it will be hard to outsource this issue, but some expertise with Mailgun and the email protocol would be really helpful. A first step would be to contact Mailgun, they probably have some answers to this.

A couple of issues that were left pending in Friday's demo:

The Open Collective way
Right now, we are displaying three central values that conform to the Open Collective way: Your data is yours, safe space, and transparency.

@alanna and I discussed this issue, and she thinks that if such representation of values ends up in the final design, it should be something more aligned to what we have here: https://docs.opencollective.com/help/about/values

Where can people find (Collective)?
After the last round of feedback with both @xdamman and @alanna, I worked on this option:

Do you have a website?
This is one of the most critical touchpoints for fiscal hosts to choose whether if they host a collective or not. Besides is the most common referring point of the general community.

Turn on conversations in your collective
We introduce the conversations feature to the new collective creator, after the onboarding they can go and start creating conversations/updates.

Connect your social platforms
The fields here directly feed the following contact buttons in today's collective page:

We removed the @opencollective address option due to the spam issue.
Any thoughts? @piamancini @Betree

LGTM thank you @Memo-Es

LGTM too. Let's acknowledge that the contact address is meant to be re-added as soon as we resolve the SPAM issue.

As discussed in the weekly design call, let's remove the switch to activate conversations as they will be activated by default.
Let's also replace the github field with a facebook one for non open source projects (we can tell by their tag if they picked something else than open source on the first screen)

(can we have an illustration for that?)

@xdamman Actually there was already an issue for the SPAM problem, it was just not public: https://github.com/opencollectiveinc/opencollective-security/issues/28

@Memo-Es please don't block this on the climate illustration - put a placeholder please when this is ready for us to start production so we can move faster. Illustrations are taking quite a while. Thanks!

Illustrations for the Create Collective Page sections
Any feedback? @piamancini

Contribute
Updates
Conversations
Budget
Events
About

all good to go @Memo-Es -


Tx/VP prefix iter should include keys from write log

The prefix iterator for Tx and VP posterior state should also iterate over the storage keys from write log. Currently, they only iterate over the keys committed to the storage, but replace any overwritten values from write log.
┆Issue is synchronized with this Asana task by Unito

not sure if this one is actually a "good first issue", as the host env implementation employs some less-common concepts


Add alternative GPS url coordinate

IMPORTANT: All PRs must be linked to an issue (except for extremely trivial and straightforward changes).
Fixes #2713
Description
Clear and concise code change description.
Alternative(s) considered
Have you considered any alternatives? And if so, why have you chosen the approach in this PR?
Type
Choose one: (Bug fix | Feature | Documentation | Testing | Code health | Builds | Releases | Other)
Screenshots (if applicable)
Checklist

[x] I have read and acknowledged the Code of conduct.
[x] I have read the Contributing page.
[x] I have signed the Google Individual CLA, or I am covered by my company's Corporate CLA.
[x] I have discussed my proposed solution with code owners in the linked issue(s) and we have agreed upon the general approach.
[x] I have run ./gradlew spotlessApply and ./gradlew spotlessCheck to check my code follows the style guide of this project.
[x] I have run ./gradlew check and ./gradlew connectedCheck to test my changes locally.
[x] I have built and run the demo app(s) to verify my change fixes the issue and/or does not break the demo app(s).

All changes updated cc @jingtang10


Yes
Source
source
TensorFlow version
2.18
Custom code
Yes
OS platform and distribution
windows 10
Mobile device
No response
Python version
3.12
Bazel version
No response
GCC/compiler version
No response
CUDA/cuDNN version
No response
GPU model and memory
No response
Current behavior?
i want a successful run
Standalone code to reproduce the issue
ImportError                               Traceback (most recent call last)
File ~\anaconda3\envs\tensorflow\Lib\site-packages\tensorflow\python\pywrap_tensorflow.py:70
     69 try:
---> 70   from tensorflow.python._pywrap_tensorflow_internal import *
     71 # This try catch logic is because there is no bazel equivalent for py_extension.
     72 # Externally in opensource we must enable exceptions to load the shared object
     73 # by exposing the PyInit symbols with pybind. This error will only be
     74 # caught internally or if someone changes the name of the target _pywrap_tensorflow_internal.
     75
     76 # This logic is used in other internal projects using py_extension.

ImportError: DLL load failed while importing _pywrap_tensorflow_internal: A dynamic link library (DLL) initialization routine failed.

During handling of the above exception, another exception occurred:

ImportError                               Traceback (most recent call last)
Cell In[4], line 1
----> 1 import tensorflow as tf

File ~\anaconda3\envs\tensorflow\Lib\site-packages\tensorflow\__init__.py:40
     37 _os.environ.setdefault("ENABLE_RUNTIME_UPTIME_TELEMETRY", "1")
     39 # Do not remove this line; See https://github.com/tensorflow/tensorflow/issues/42596
---> 40 from tensorflow.python import pywrap_tensorflow as _pywrap_tensorflow  # pylint: disable=unused-import
     41 from tensorflow.python.tools import module_util as _module_util
     42 from tensorflow.python.util.lazy_loader import KerasLazyLoader as _KerasLazyLoader

File ~\anaconda3\envs\tensorflow\Lib\site-packages\tensorflow\python\pywrap_tensorflow.py:85
     83     sys.setdlopenflags(_default_dlopen_flags)
     84 except ImportError:
---> 85   raise ImportError(
     86       f'{traceback.format_exc()}'
     87       f'\n\nFailed to load the native TensorFlow runtime.\n'
     88       f'See https://www.tensorflow.org/install/errors '
     89       f'for some common causes and solutions.\n'
     90       f'If you need help, create an issue '
     91       f'at https://github.com/tensorflow/tensorflow/issues '
     92       f'and include the entire stack trace above this error message.')

ImportError: Traceback (most recent call last):
  File "C:\Users\user\anaconda3\envs\tensorflow\Lib\site-packages\tensorflow\python\pywrap_tensorflow.py", line 70, in <module>
    from tensorflow.python._pywrap_tensorflow_internal import *
ImportError: DLL load failed while importing _pywrap_tensorflow_internal: A dynamic link library (DLL) initialization routine failed.

Failed to load the native TensorFlow runtime.
See https://www.tensorflow.org/install/errors for some common causes and solutions.
If you need help, create an issue at https://github.com/tensorflow/tensorflow/issues and include the entire stack trace above this error message.

Relevant log output
ImportError                               Traceback (most recent call last)
File ~\anaconda3\envs\tensorflow\Lib\site-packages\tensorflow\python\pywrap_tensorflow.py:70
     69 try:
---> 70   from tensorflow.python._pywrap_tensorflow_internal import *
     71 # This try catch logic is because there is no bazel equivalent for py_extension.
     72 # Externally in opensource we must enable exceptions to load the shared object
     73 # by exposing the PyInit symbols with pybind. This error will only be
     74 # caught internally or if someone changes the name of the target _pywrap_tensorflow_internal.
     75
     76 # This logic is used in other internal projects using py_extension.

ImportError: DLL load failed while importing _pywrap_tensorflow_internal: A dynamic link library (DLL) initialization routine failed.

During handling of the above exception, another exception occurred:

ImportError                               Traceback (most recent call last)
Cell In[4], line 1
----> 1 import tensorflow as tf

File ~\anaconda3\envs\tensorflow\Lib\site-packages\tensorflow\__init__.py:40
     37 _os.environ.setdefault("ENABLE_RUNTIME_UPTIME_TELEMETRY", "1")
     39 # Do not remove this line; See https://github.com/tensorflow/tensorflow/issues/42596
---> 40 from tensorflow.python import pywrap_tensorflow as _pywrap_tensorflow  # pylint: disable=unused-import
     41 from tensorflow.python.tools import module_util as _module_util
     42 from tensorflow.python.util.lazy_loader import KerasLazyLoader as _KerasLazyLoader

File ~\anaconda3\envs\tensorflow\Lib\site-packages\tensorflow\python\pywrap_tensorflow.py:85
     83     sys.setdlopenflags(_default_dlopen_flags)
     84 except ImportError:
---> 85   raise ImportError(
     86       f'{traceback.format_exc()}'
     87       f'\n\nFailed to load the native TensorFlow runtime.\n'
     88       f'See https://www.tensorflow.org/install/errors '
     89       f'for some common causes and solutions.\n'
     90       f'If you need help, create an issue '
     91       f'at https://github.com/tensorflow/tensorflow/issues '
     92       f'and include the entire stack trace above this error message.')

ImportError: Traceback (most recent call last):
  File "C:\Users\user\anaconda3\envs\tensorflow\Lib\site-packages\tensorflow\python\pywrap_tensorflow.py", line 70, in <module>
    from tensorflow.python._pywrap_tensorflow_internal import *
ImportError: DLL load failed while importing _pywrap_tensorflow_internal: A dynamic link library (DLL) initialization routine failed.

Failed to load the native TensorFlow runtime.
See https://www.tensorflow.org/install/errors for some common causes and solutions.
If you need help, create an issue at https://github.com/tensorflow/tensorflow/issues and include the entire stack trace above this error message.

same error here

Same error here

Hi @Davischoice1 ,
Apologies for the delay. Could you please provide the tensorflow and the compatible version which you are trying to install. Also there are at least 3 possible scenarios:
You need to install the MSVC 2019 redistributable
Your CPU does not support AVX2 instructions
Your CPU/Python is on 32 bits
There is a library that is in a different location/not installed on your system that cannot be loaded.
https://github.com/tensorflow/tensorflow/issues/61887
Thank you!

tensorflow version: 2.18.Orc2

actually is version 2.18.0

Hi @langtontdangare ,
Could you please open another issue with all the relevant details? This will make it easier for us to track and assist you effectively.
Thank you!


create an example that randomly creates market events

a few users, one market, one product, a seed price and go!

This would be helpful to have but no rush to get it. Removed milestone 1.1


[Bug?]: Vite server config is being ignored

Duplicates

[X] I have searched the existing issues

Latest version

[X] I have tested the latest version

Current behavior 😯
example app.config.ts:
export default defineConfig({
    vite: {
        server: {
            host: "<IP_ADDRESS>",
            port: 8080,
        },
        plugins: [],
    },
});

but this isnt working, its still running on port:3000
Expected behavior 🤔
Run server on specified PORT and HOST
Steps to reproduce 🕹
Steps:
1.Edit vite server options on app.config.ts
2.Run server
3.
4.
Context 🔦
A fix:
https://nitro.unjs.io/deploy/runtimes/node#environment-variables
in .env
PORT=3000
HOST=<IP_ADDRESS>

I think this is because Im not using vite-solid-plugin?
Hope this helps someone at least.
Your environment 🌎
No response

Solid start already configures vite-plugin-solid for you, so that's not the issue. The disconnect must be somewhere else.

I suspect this is due to Vinxi. I added back the dev server options because some of them work. Others may not. Will need to double check with @nksaraf to get clarity on this.

Yeah I feel like the host and port shouldnt be server config since you probably only mean the dev server. So I think the dev command is the place those things should be overriden. You can use '--host' and '--port' options (can make it part of your npm dev script) or HOST and PORT environment variables

It would be convenient to have the options on the start command as well


[openblas] adds gcc14 support patch

Fixes #38739
Upstream Issue: https://github.com/OpenMathLib/OpenBLAS/issues/4668

[X] Changes comply with the maintainer guide.
[ ] SHA512s are updated for each updated download.
[ ] The "supports" clause reflects platforms that may be fixed by this new version.
[ ] Any fixed CI baseline entries are removed from that file.
[ ] Any patches that are no longer applied are deleted from the port's directory.
[X] The version database is fixed by rerunning ./vcpkg x-add-version --all and committing the result.
[X] Only one version is added to each modified port's versions file.

Thanks!


iOS iso15693 - Issue with Present Password Command (0xB3)

This is how I send the 'present password' command:
Uint8List feedPassword = Uint8List.fromList([0x01, 0x55, 0xAA, 0x55, 0xAA, 0x9C, 0xDE, 0x2C, 0x74]);
int pwdCommandCode = 0xB3;
var requestFlagsForPwdAuth = <Iso15693RequestFlag>{};
requestFlagsForPwdAuth.add(Iso15693RequestFlag.highDataRate);
Uint8List? pwdResponse = await stTag?.customCommand(requestFlags: requestFlagsForPwdAuth, customCommandCode: pwdCommandCode, customRequestParameters: feedPassword);
I've made sure that the password is correct such that when I forcefully feed a wrong password, a platform exception gets fired everytime. If the password is correct, an empty command response is received and there is no exception. The code follows through.
Now after the present password command, if I attempt normal read
List<Uint8List>? readData = await stTag?.readMultipleBlocks(requestFlags: requestFlagsForRead, blockNumber: 4, numberOfBlocks: 1);
, I always get this exception on the call to read command -
PlatformException(invalid_parameter, Tag is not found, null, null)
I've tried using extendedReadSingleBlock / extendedReadMultipleBlocks, but have got the same results.

Nothing wrong with the command.
I had missed adding await on one of the method calls inside onDiscovered.
Apparently this affects only on the 'customCommand' API.
Marking this issues as Closed.


Calendar.dates(byMatching: ...) does not respect repeatedTimePolicy when .nanosecond is set

When searching by dates matching date components, the repeatedTimePolicy field is not respected if the nanoseconds field is set. For example, we want to find the date expected which is just after a daylight switch:
let start    = Date(timeIntervalSince1970:<PHONE_NUMBER>.0) // 2024-11-02T01:20:00-0700
let expected = Date(timeIntervalSince1970:<PHONE_NUMBER>.0) // 2024-11-03T01:20:00-0800

var dc = DateComponents()
dc.hour = 1
dc.minute = 20
dc.second = 0
dc.nanosecond = 0

var sequenceIterator = gregorian.dates(byMatching: dc, startingAt: start,  matchingPolicy: .strict, repeatedTimePolicy: .last).makeIterator()
let result = sequenceIterator.next()
assert(result == expected)

when nanosecond is set to a value, enumeration always returns the 01:20 before the switch and the assertion fails.
The bug occurs in Calendar.dateAfterMatchingNanosecond(...) in Calendar_Enumerate.swift. This method simply returns a new date using Calendar.date(from: DateComponents), but in the test above there are two dates matching the components, and we need a way to specify if we want the first or second one.

rdar://124640028


READMEs for Models and Tokenizer

create diagrams that explains what is being done in each architecture and each tokenizing strategy

diagrams are here:
https://drive.google.com/file/d/19ZxJ5X4JyBUvbJavR1NBig0UCSz2x75D/view?usp=drive_link


Fix the values passed to the callback function for fail(). This fixes #257

Fix the values passed to the callback function for fail(). This fixes #257

Description

This PR makes the fail() function work the same way as success(), allowing custom status codes and return content.
Motivation and Context

[x] I have raised an issue to propose this change (required)

See https://github.com/openfaas/templates/issues/257 for details
Which issue(s) this PR fixes

Fixes #257
How Has This Been Tested?

[x] ran verify.sh
[x] tested with a small test function shown in #257

Types of changes

[x] Bug fix (non-breaking change which fixes an issue)
[ ] New feature (non-breaking change which adds functionality)
[x] Breaking change (fix or feature that would cause existing functionality to change)
[ ] Version change (see: Impact to existing users)

Impact to existing users

Existing users that rely on the current behavior of fail() always returning 500 Internal Server Error will have to alter their code.
Checklist:

[x] My code follows the code style of this project.
[ ] My change requires a change to the documentation.
[ ] I have updated the documentation accordingly.
[x] I've read the CONTRIBUTION guide
[x] I have signed-off my commits with git commit -s
[ ] I have added tests to cover my changes.
[x] All new and existing tests passed.

When do you think you can do this review and test @viveksyngh?

@hrak please can you see the notes I posted last week?

@hrak please can you see the notes I posted last week?

Hi! Sorry, life happened in the meantime, in the process of buying a house. Will address this asap.

Not a problem. We appreciate the contribution and it will make things better.

I'm going to fix this since we've waited long enough to have it resolved and I have a few minutes to test my work at the same time.
Thanks again for your interest @hrak
If you value our work and project, consider becoming a GitHub Sponsor if you are not already.


Use ApplyDeployment instead of ApplyDeploymentWithForce

Use ApplyDeployment instead of ApplyDeploymentWithForce
since in ApplyDeployment it sets an anotation that is the hash of the spec: https://github.com/openshift/library-go/blob/245d1ca16d1514313f3a7ab13ff2c9e3b07b5f43/pkg/operator/resource/resourceapply/apps.go#L95
and ApplyDeployment calls ApplyDeploymentWithForce, which then checks that hash to decide or not to force apply: https://github.com/openshift/library-go/blob/master/pkg/operator/resource/resourceapply/apps.go#L131
Seems go fmt with 1.19 also caught some things there :sweat_smile:

/hold

/lgtm
/hold cancel


how to install?

I have problems with the installation. when i type: "ionic cordova plugin add https://github.com/MobileChromeApps/cordova-plugin-chrome-apps-sockets-udp" i get an error: "failed to fetch plugin
did i type something wrong, or doesn´t it work anymore?

Use ionic cordova plugin add cordova-plugin-chrome-apps-sockets-udp instead.


Default site_id is -1

I ran into this in #498  - is there a reason why the default site_id is -1 and not null/nil?

This is to support users shared between sites.
check this comment:
https://github.com/owen2345/camaleon-cms/pull/498#issuecomment-250519649

I'm saying a null value should work as well as -1 and be in line with rails conventions.

 I will change for the next version.

Thanks!


Non-SRIOV like H16r, CentOS 7.6+ and RDMA = impossible?

Hello! I'm a bit confused about this situation, it would be great to clarify:
TL;DR: I want to use RDMA/Infiniband on a non-SRIOV Instance like H16r with a recent CentOS (7.8 or if need be 7.7). Am I right this is not possible?
I'm successfully using CentOS 7.5 but I fear that the necessary kernel drivers (msft-rdma-drivers as present in the repositories of OpenLogic until 7.5) are no longer maintained for later kernel releases.
Currently the documentation says:

On RDMA-capable VMs, add the InfiniBandDriver<Linux|Windows> extension to enable InfiniBand.

But the next sentence clarifies that the InfiniBandDriverLinux is only helpful for SR-IOV:

On Linux, the InfiniBandDriverLinux VM extension installs the Mellanox OFED drivers (on SR-IOV VMs) for RDMA connectivity.

On https://docs.microsoft.com/en-us/azure/virtual-machines/h-series?toc=/azure/virtual-machines/linux/toc.json&bc=/azure/virtual-machines/linux/breadcrumb/toc.json#supported-os-images-linux :

For non-SR-IOV enabled VMs, CentOS-based version 6.5 HPC or a later version, up to 7.5 are suitable. For H-series VMs, versions 7.1 to 7.5 are recommended.

But, as CentOS releases are point-in-time and do not get security updates if you freeze your repositories to an earlier version - I have a hard time seeing "7.1 to 7.5" as recommended. My recommendation in this case would be to only use SR-IOV instances.
Or is it a viable way to update everything but the kernel to the latest CentOS 7? Has this been tested?

Document Details
⚠ Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

ID: 5d20f8d6-f126-ea03-1bcd-942fc642edee
Version Independent ID: 2ffc013c-91a1-73e0-451b-d02a36deb38f
Content: Azure VM sizes - HPC - Azure Virtual Machines
Content Source: articles/virtual-machines/sizes-hpc.md
Service: virtual-machines
GitHub Login: @vermagit
Microsoft Alias: amverma

@wandhydrant Your understanding of the support matrix of the CentOS versions vis-a-vis SR-IOV VM types is correct: <=7.5 for non-SR-IOV types such as H16r, and >=7.6 for SR-IOV types. The IB Linux extension installs only OFED, not the ND drivers for the non-SR-IOV VM sizes.
You make a reasonable point on the need to use newer versions. However, the ND drivers on those, if installable, would be unsupported. I will let others chime in on the feasibility, and instructions for doing so.
Note that the non-SR-IOV VM sizes may be upgraded to SR-IOV in the coming months, so that this bifurcation does not persist.

Thank you very much for this clarification. For me, this issue can be closed.

We are currently cleaning up old issues and closing out items that are greater than 90 days old. If an issue is still present, please open a new feedback item on the document so we can prioritize correctly.
#please-close


Global variables for customising

At the moment, you have to do a lot of digging to customise the website to add your own flare to it.
Suggestion
Add a global CSS variable file where you can set the following:

Hero background color
Font family
Font size
Font color
Number of cards displayed on a page

Open to other suggestions too.

@wrussell1999 We can add a CSS file for that, which will be used in all the pages, the number of cards need to be dynamic right?

@anjalisoni3655 what I mean by number of cards is I'm using a CSS grid to say how many cards across for different screen sizes. Maybe this could be changed if someone wanted less cards.

@wrussell1999 yah correct, can you explain more how you want the structure of the styling files be

@anjalisoni3655 here's an example from another Jekyll template https://github.com/mmistakes/minimal-mistakes/blob/master/_sass/minimal-mistakes/_variables.scss

Is someone doing this? We could take it. @anjalisoni3655 @wrussell1999

Is someone doing this? We could take it. @anjalisoni3655 @wrussell1999

You can do that!


msgflo-setup fails with timeout error with noflo-runtime-msgflo

msgflo-setup does things in this order:

Start participants, wait for STDOUT (#92)
Subscribe to fbp queue and wait for discovery messages

However, noflo-runtime-msgflo currently sends the discovery message only once, at start-up. This means msgflo-setup never receives it, as the fbp queue is subscribed only later.

Yes, should subscribe right away, before starting the participants.


[cluster-test] Use correct asg name instead of hardcoded value

Summary
Use correct asg name instead of hardcoded value

@bors-libra r+

:pushpin: Commit a8bef82 has been approved by davidiw

:hourglass: Testing commit a8bef82cf125f935b8b616b12aabdf65a16fc644 with merge c805ff602d6f8ee27008a0cab51f83d45e746805...

:broken_heart: Test failed - checks-actions_land_blocking_test

@bors-libra retry

:hourglass: Testing commit a8bef82cf125f935b8b616b12aabdf65a16fc644 with merge b5666e19883c6bc634fb6069646b576e7699989e...

:sunny: Test successful - checks-actions_land_blocking_test, checks-circle_commit_workflow
Approved by: davidiw
Pushing b5666e19883c6bc634fb6069646b576e7699989e to master...


ZEPPELIN-388: auto nav to new note

This PR relates to: https://issues.apache.org/jira/browse/ZEPPELIN-388 Auto navigate to the newly created Note right after its creation.
Current User Experience of Adding New Note (No response after creating a new note):

After Pull Request (auto navigates to new note):

Great work, thanks @MikeTYChen

Thanks @MikeTYChen for really useful improvement.
CI build fails on
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running org.apache.zeppelin.ZeppelinIT
Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 7.275 sec <<< FAILURE! - in org.apache.zeppelin.ZeppelinIT
testAngularDisplay(org.apache.zeppelin.ZeppelinIT)  Time elapsed: 7.18 sec  <<< FAILURE!
java.lang.AssertionError: expected:<1> but was:<0>
	at org.junit.Assert.fail(Assert.java:88)
	at org.junit.Assert.failNotEquals(Assert.java:743)
	at org.junit.Assert.assertEquals(Assert.java:118)
	at org.junit.Assert.assertEquals(Assert.java:555)
	at org.junit.Assert.assertEquals(Assert.java:542)
	at org.apache.zeppelin.ZeppelinIT.createNewNoteAndGetName(ZeppelinIT.java:329)
	at org.apache.zeppelin.ZeppelinIT.testAngularDisplay(ZeppelinIT.java:174)

Results :

Failed tests:
  ZeppelinIT.testAngularDisplay:174->createNewNoteAndGetName:329 expected:<1> but was:<0>

Tests run: 1, Failures: 1, Errors: 0, Skipped: 0

It's because of test case get link of newly created notebook and click to move it (see https://github.com/apache/incubator-zeppelin/blob/master/zeppelin-server/src/test/java/org/apache/zeppelin/ZeppelinIT.java#L175). With this PR, ZeppelinIT test should be changed.

@MikeTYChen I think the broadcastNote method in NotebookServer.java isn't sending the new notebook object to the current socket. May be we need to fix it and getNewNoteID should use the id from that response instead of picking it from the list. Thank you.

Thanks @r-kamath for great suggestion. I pushed a fix to add broadcast and remove the previous PR. Suggestions or improvements on the new PR?

How about this approach?
[webapp]

Open notebook on NOTE message. if notebook is not already opened.

[zeppelin server]

when it receive NEW_NOTE from webapp, it send back NOTE (to make webapp open new notebook)  as well as broadcast NOTES_INFO (to update notebook list)

what do you think?

@Leemoonsoo @MikeTYChen I pushed #309 earlier to address same issue as this one and it has similar approach @Leemoonsoo suggested above. Can you guys review this?

It may even be easier to use a REST API than websocket on that one

@MikeTYChen This issue has been resolved by #309 Do you mind to close this one?


Add 'matrix' as a supported chart type

Codecov Report

Merging #63 (5e8f2a5) into main (7d21526) will not change coverage.
The diff coverage is n/a.

:exclamation: Current head 5e8f2a5 differs from pull request most recent head 1b1f8a2. Consider uploading reports for the commit 1b1f8a2 to get more accurate results

@@           Coverage Diff           @@
##             main      #63   +/-   ##
=======================================
  Coverage   99.89%   99.89%
=======================================
  Files          11       11
  Lines         967      967
  Branches      219      219
=======================================
  Hits          966      966
  Misses          1        1

Flag
Coverage Δ

unittests
99.89% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.


Many changes

Remove more biased options.txt values
Make the accessibility onboarding screen open properly
Remove World Host config from versions that don't have World Host
Add Borderless Mining
Replace FLK with QKL where possible
Remove Krypton

Also resolves #17 and #18.

The sim distance could be kept ig
Krypton really doesn't do much on the client. If anything, ImmediatelyFast does way more for many entities. But I'm serious when I say Krypton basically does nothing at all (on the client). Krypton also has some major issues that the dev refuses to fix. For instance, try joining viaproxy.raphimc.net with Krypton, and then try without. You'll fail the antibot check with Krypton installed because Krypton violates certain specs of the Minecraft protocol.

Oh now I see why. Do you have any other examples of Krypton breaking things? I'll likely remove it from Adrenaline either way as it hasn't been proven to do basically anything on the client and I understand the stuff you said before

I don't have any other specific examples, but I'm sure @RaphiMC could provide a few. As for the simulation-distance, that is technically changing behavior that a new player wouldn't necessarily understand. I feel like that kind of change belongs more in just Adrenaline, as that's all about performance, and is such targeted to weaker PCs.

I see what you are saying. Well, this PR should be good then - I’ll merge it while I’m working on the next update so that the auto updating instance doesn’t get too out of sync with the Modrinth pack. Thanks :)

While I don't have any more examples of Krypton violating the minecraft protocol, I can say that it does pretty much nothing on the client and is definitely not worth the network level changes (and mod incompatibilities) it makes.


⚠ Bofrost.at has degraded performance

In b6d4478, Bofrost.at (https://www.bofrost.at) experienced degraded performance:

HTTP code: 200
Response time: 1394 ms

Resolved: Bofrost.at performance has improved in 93240a2.


[Workers] Update Fauna tutorial

Updated "Create a serverless, globally distributed REST API with Fauna" tutorial:

Use Hono as a web framework instead of Worktop, as Worktop is not currently being maintained.
Use C3 for initializing a Worker project.
Use TypeScript.
Rewrite some phrases.

Note

Should we remove ?utm_source=Cloudflare&utm_medium=referral&utm_campaign=Q4_CF_2021 in links to Fauna.
Reference-style links does not work in aside. Perhaps, we don't have to use Reference-style.
Currently, the repo hosting complete code is my repo. Should I make it in Cloudflare org?
Fauna Workers quickstart appears to be outdated.

Hi @deadlypants1973 !
Since this PR was merged, we can initialize a Hono project more easily with C3. I'll need to rewrite this tutorial accordingly. I'll tackle it next week. Thank you!
https://github.com/cloudflare/workers-sdk/pull/3552

Wow, just a few days ago, the new version v10 of Fauna FQL was released as GA. The tutorial was originally written for v4, so I need to completely revise both the article and the sample project. I'll need some time for this.

Ooops.
I’m really sorry. I mistakenly pushed everyone's commits that were unrelated to this PR with the review request and tags. I’m really sorry.

Hi @deadlypants1973!
I made a big mistake. But I've done revising this tutorial. As mentioned, the API and dashboard of Fauna have undergone major changes. I've updated the example code and removed some verbose descriptions. Please review it! Thanks!