~repos /edge-city
git clone https://pyrossh.dev/repos/edge-city.git
edge-city is a next level meta-framework for react that runs only on edge runtimes
1fc95d95
—
Peter John 2 years ago
Fix example
- .gitignore +5 -1
- {example/.vscode → .vscode}/extensions.json +0 -0
- {example/.vscode → .vscode}/settings.json +0 -0
- example/Dockerfile → Dockerfile +0 -0
- bin/create-app.js +0 -31
- bun.lockb +0 -0
- {example/components → components}/Counter/Counter.jsx +0 -0
- {example/components → components}/Timer/Timer.jsx +0 -0
- {example/components → components}/Todo/Todo.css +0 -0
- {example/components → components}/Todo/Todo.jsx +0 -0
- {example/containers → containers}/TodoList/TodoList.jsx +0 -0
- {example/db → db}/index.js +0 -0
- {example/db → db}/migrations/0000_empty_shatterstar.sql +0 -0
- {example/db → db}/migrations/meta/0000_snapshot.json +0 -0
- {example/db → db}/migrations/meta/_journal.json +0 -0
- example/drizzle.config.json → drizzle.config.json +0 -0
- example/.gitignore +0 -5
- example/bun.lockb +0 -0
- example/main.js +0 -3
- example/package.json +0 -55
- example/readme.md +0 -18
- example/jsconfig.json → jsconfig.json +0 -0
- main.js +8 -0
- package.json +48 -21
- {example/pages → pages}/404/page.css +0 -0
- {example/pages → pages}/404/page.jsx +0 -0
- {example/pages → pages}/about/page.css +0 -0
- {example/pages → pages}/about/page.jsx +0 -0
- {example/pages → pages}/layout.css +0 -0
- {example/pages → pages}/layout.jsx +0 -0
- {example/pages → pages}/page.css +0 -0
- {example/pages → pages}/page.jsx +0 -0
- {example/pages → pages}/page.spec.js +0 -0
- {example/pages → pages}/todos/page.css +0 -0
- {example/pages → pages}/todos/page.jsx +0 -0
- parotta/bun.lockb +0 -0
- parotta/package.json +15 -0
- parotta/readme.md +13 -0
- runtime.js → parotta/runtime.js +0 -0
- bin/cli.js → parotta/server.js +40 -50
- example/playwright.config.js → playwright.config.js +0 -0
- readme.md +18 -13
- {example/services → services}/auth.service.js +0 -0
- {example/services → services}/todos.service.js +0 -0
- {example/static → static}/favicon.ico +0 -0
- {example/static → static}/logo192.png +0 -0
- {example/static → static}/logo512.png +0 -0
- {example/static → static}/manifest.json +0 -0
- {example/static → static}/robots.txt +0 -0
.gitignore
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
+
.cache
|
|
2
|
+
.dist
|
|
1
|
-
node_modules
|
|
3
|
+
node_modules
|
|
4
|
+
.env
|
|
5
|
+
test-results
|
{example/.vscode → .vscode}/extensions.json
RENAMED
|
File without changes
|
{example/.vscode → .vscode}/settings.json
RENAMED
|
File without changes
|
example/Dockerfile → Dockerfile
RENAMED
|
File without changes
|
bin/create-app.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import { Command } from 'commander';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import fs from 'fs';
|
|
6
|
-
import os from 'os';
|
|
7
|
-
import chalk from 'chalk';
|
|
8
|
-
import gunzip from 'gunzip-file';
|
|
9
|
-
import packageJson from './package.json'
|
|
10
|
-
|
|
11
|
-
process.on('SIGINT', () => process.exit(0))
|
|
12
|
-
process.on('SIGTERM', () => process.exit(0))
|
|
13
|
-
|
|
14
|
-
let projectPath = "";
|
|
15
|
-
const program = new Command(packageJson.name)
|
|
16
|
-
.version(packageJson.version)
|
|
17
|
-
.arguments('<project-directory>')
|
|
18
|
-
.usage(`${chalk.green('<project-directory>')} [options]`)
|
|
19
|
-
.action((name) => {
|
|
20
|
-
projectPath = name
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
// program.parse();
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const tmpDir = os.tmpdir();
|
|
27
|
-
console.log('tmpDir', tmpDir);
|
|
28
|
-
const tmpFile = path.join(tmpDir, "main.zip");
|
|
29
|
-
const res = await fetch("https://github.com/pyrossh/parotta/archive/refs/heads/main.zip");
|
|
30
|
-
await Bun.write(tmpFile, res);
|
|
31
|
-
await gunzip(tmpFile, os.tmpdir());
|
bun.lockb
CHANGED
|
Binary file
|
{example/components → components}/Counter/Counter.jsx
RENAMED
|
File without changes
|
{example/components → components}/Timer/Timer.jsx
RENAMED
|
File without changes
|
{example/components → components}/Todo/Todo.css
RENAMED
|
File without changes
|
{example/components → components}/Todo/Todo.jsx
RENAMED
|
File without changes
|
{example/containers → containers}/TodoList/TodoList.jsx
RENAMED
|
File without changes
|
{example/db → db}/index.js
RENAMED
|
File without changes
|
{example/db → db}/migrations/0000_empty_shatterstar.sql
RENAMED
|
File without changes
|
{example/db → db}/migrations/meta/0000_snapshot.json
RENAMED
|
File without changes
|
{example/db → db}/migrations/meta/_journal.json
RENAMED
|
File without changes
|
example/drizzle.config.json → drizzle.config.json
RENAMED
|
File without changes
|
example/.gitignore
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
.cache
|
|
2
|
-
.dist
|
|
3
|
-
node_modules
|
|
4
|
-
.env
|
|
5
|
-
test-results
|
example/bun.lockb
DELETED
|
Binary file
|
example/main.js
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import DevServer from "parotta/bin/cli";
|
|
2
|
-
|
|
3
|
-
export default DevServer;
|
example/package.json
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "parotta-example",
|
|
3
|
-
"type": "module",
|
|
4
|
-
"scripts": {
|
|
5
|
-
"dev": "bunx parotta",
|
|
6
|
-
"start": "bunx parotta",
|
|
7
|
-
"build": "docker build . -t example",
|
|
8
|
-
"run": "docker run -p 3000:3000 example",
|
|
9
|
-
"test": "playwright test",
|
|
10
|
-
"test:report": "playwright show-report"
|
|
11
|
-
},
|
|
12
|
-
"dependencies": {
|
|
13
|
-
"@neondatabase/serverless": "^0.2.9",
|
|
14
|
-
"drizzle-auth-adaptor-pg": "^0.0.3",
|
|
15
|
-
"drizzle-orm": "0.25.4",
|
|
16
|
-
"next-auth": "^4.22.1",
|
|
17
|
-
"normalize.css": "^8.0.1",
|
|
18
|
-
"parotta": "file:../",
|
|
19
|
-
"react": "18.2.0",
|
|
20
|
-
"react-dom": "^18.2.0",
|
|
21
|
-
"sql-highlight": "^4.3.2"
|
|
22
|
-
},
|
|
23
|
-
"devDependencies": {
|
|
24
|
-
"drizzle-kit": "0.17.6",
|
|
25
|
-
"@playwright/test": "^1.31.2",
|
|
26
|
-
"eslint": "^8.35.0",
|
|
27
|
-
"eslint-config-react-app": "^7.0.1",
|
|
28
|
-
"eslint-config-recommended": "^4.1.0"
|
|
29
|
-
},
|
|
30
|
-
"parotta": {
|
|
31
|
-
"hydrate": true,
|
|
32
|
-
"css": [
|
|
33
|
-
"node_modules/normalize.css/normalize.css"
|
|
34
|
-
]
|
|
35
|
-
},
|
|
36
|
-
"eslintConfig": {
|
|
37
|
-
"root": true,
|
|
38
|
-
"parserOptions": {
|
|
39
|
-
"ecmaVersion": "latest",
|
|
40
|
-
"sourceType": "module"
|
|
41
|
-
},
|
|
42
|
-
"extends": [
|
|
43
|
-
"eslint:recommended",
|
|
44
|
-
"react-app"
|
|
45
|
-
],
|
|
46
|
-
"ignorePatterns": [
|
|
47
|
-
"dist"
|
|
48
|
-
],
|
|
49
|
-
"rules": {
|
|
50
|
-
"react/prop-types": "warn",
|
|
51
|
-
"react/react-in-jsx-scope": "off",
|
|
52
|
-
"no-unused-vars": "warn"
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
example/readme.md
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# Sample Parotta Application
|
|
2
|
-
|
|
3
|
-
## Requirements
|
|
4
|
-
|
|
5
|
-
1. bun >= v0.5.8
|
|
6
|
-
|
|
7
|
-
## Setup
|
|
8
|
-
|
|
9
|
-
1. `bun i`
|
|
10
|
-
2. `bunx playright install`
|
|
11
|
-
|
|
12
|
-
## Running
|
|
13
|
-
|
|
14
|
-
`bun run dev`
|
|
15
|
-
|
|
16
|
-
## Testing
|
|
17
|
-
|
|
18
|
-
`bun test`
|
example/jsconfig.json → jsconfig.json
RENAMED
|
File without changes
|
main.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import ReactDom from "react-dom/server";
|
|
3
|
+
import server from "parotta/server.js";
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
port: 3000,
|
|
7
|
+
fetch: server,
|
|
8
|
+
}
|
package.json
CHANGED
|
@@ -1,27 +1,54 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "parotta",
|
|
2
|
+
"name": "parotta-example",
|
|
3
|
-
"version": "0.3.0",
|
|
4
3
|
"type": "module",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"dev": "bun --hot main.js",
|
|
6
|
+
"start": "bun main.js",
|
|
7
|
+
"build": "docker build . -t example",
|
|
8
|
+
"run": "docker run -p 3000:3000 example",
|
|
9
|
+
"test": "bun test",
|
|
10
|
+
"test-e2e": "playwright test"
|
|
11
|
+
},
|
|
5
12
|
"dependencies": {
|
|
13
|
+
"@neondatabase/serverless": "^0.2.9",
|
|
14
|
+
"drizzle-auth-adaptor-pg": "^0.0.3",
|
|
6
|
-
"
|
|
15
|
+
"drizzle-orm": "0.25.4",
|
|
7
|
-
"
|
|
16
|
+
"next-auth": "^4.22.1",
|
|
8
|
-
"commander": "10.0.0",
|
|
9
|
-
"extract-files": "^13.0.0",
|
|
10
|
-
"gunzip-file": "^0.1.1",
|
|
11
|
-
"history": "^5.3.0",
|
|
12
|
-
"mime-types": "2.1.35",
|
|
13
|
-
"
|
|
17
|
+
"normalize.css": "^8.0.1",
|
|
14
|
-
"postcss": "^8.4.21",
|
|
15
|
-
"postcss-custom-media": "^9.1.2",
|
|
16
|
-
"postcss-nesting": "^11.2.1",
|
|
17
|
-
"radix3": "^1.0.0",
|
|
18
|
-
"react": "
|
|
18
|
+
"react": "18.2.0",
|
|
19
19
|
"react-dom": "^18.2.0",
|
|
20
|
+
"sql-highlight": "^4.3.2"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
20
|
-
"
|
|
23
|
+
"drizzle-kit": "0.17.6",
|
|
24
|
+
"@playwright/test": "^1.31.2",
|
|
21
|
-
"
|
|
25
|
+
"eslint": "^8.35.0",
|
|
22
|
-
"
|
|
26
|
+
"eslint-config-react-app": "^7.0.1",
|
|
27
|
+
"eslint-config-recommended": "^4.1.0"
|
|
28
|
+
},
|
|
29
|
+
"parotta": {
|
|
30
|
+
"hydrate": true,
|
|
31
|
+
"css": [
|
|
32
|
+
"node_modules/normalize.css/normalize.css"
|
|
33
|
+
]
|
|
23
34
|
},
|
|
35
|
+
"eslintConfig": {
|
|
36
|
+
"root": true,
|
|
37
|
+
"parserOptions": {
|
|
38
|
+
"ecmaVersion": "latest",
|
|
39
|
+
"sourceType": "module"
|
|
40
|
+
},
|
|
41
|
+
"extends": [
|
|
42
|
+
"eslint:recommended",
|
|
43
|
+
"react-app"
|
|
44
|
+
],
|
|
45
|
+
"ignorePatterns": [
|
|
46
|
+
"dist"
|
|
47
|
+
],
|
|
24
|
-
|
|
48
|
+
"rules": {
|
|
49
|
+
"react/prop-types": "warn",
|
|
50
|
+
"react/react-in-jsx-scope": "off",
|
|
25
|
-
|
|
51
|
+
"no-unused-vars": "warn"
|
|
52
|
+
}
|
|
26
53
|
}
|
|
27
|
-
}
|
|
54
|
+
}
|
{example/pages → pages}/404/page.css
RENAMED
|
File without changes
|
{example/pages → pages}/404/page.jsx
RENAMED
|
File without changes
|
{example/pages → pages}/about/page.css
RENAMED
|
File without changes
|
{example/pages → pages}/about/page.jsx
RENAMED
|
File without changes
|
{example/pages → pages}/layout.css
RENAMED
|
File without changes
|
{example/pages → pages}/layout.jsx
RENAMED
|
File without changes
|
{example/pages → pages}/page.css
RENAMED
|
File without changes
|
{example/pages → pages}/page.jsx
RENAMED
|
File without changes
|
{example/pages → pages}/page.spec.js
RENAMED
|
File without changes
|
{example/pages → pages}/todos/page.css
RENAMED
|
File without changes
|
{example/pages → pages}/todos/page.jsx
RENAMED
|
File without changes
|
parotta/bun.lockb
ADDED
|
Binary file
|
parotta/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "parotta",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"autoprefixer": "^10.4.14",
|
|
7
|
+
"history": "^5.3.0",
|
|
8
|
+
"mime-types": "2.1.35",
|
|
9
|
+
"postcss": "^8.4.21",
|
|
10
|
+
"postcss-custom-media": "^9.1.2",
|
|
11
|
+
"postcss-nesting": "^11.2.1",
|
|
12
|
+
"radix3": "^1.0.0",
|
|
13
|
+
"walkdir": "0.4.1"
|
|
14
|
+
}
|
|
15
|
+
}
|
parotta/readme.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# parotta
|
|
2
|
+
|
|
3
|
+
parotta is a next level meta-framework for react that runs only on edge runtimes.
|
|
4
|
+
It uses bun as its bundler/transpiler and development mode as its quick and fast.
|
|
5
|
+
It uses File System routing with streaming SSR + CSR as the method to render pages. Basically a MPA + SPA Transitional App.
|
|
6
|
+
It is very opionated and has set of idiomatic ways of doing things.
|
|
7
|
+
It has inbuilt rpc mechanism to access server resources instead of a typical REST API.
|
|
8
|
+
|
|
9
|
+
### Todo
|
|
10
|
+
1. Add build step
|
|
11
|
+
2. Deploy to Node (using edge-runtime), Docker, Deno deploy, Vercel edge functions, Cloudflare workers, Bun edge (whenever it releases)
|
|
12
|
+
3. Hydrate rpc cache
|
|
13
|
+
4. Build a Website with Docs using parotta
|
runtime.js → parotta/runtime.js
RENAMED
|
File without changes
|
bin/cli.js → parotta/server.js
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import React from "react";
|
|
2
|
-
|
|
2
|
+
import { renderToReadableStream } from "react-dom/server";
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import walkdir from 'walkdir';
|
|
@@ -10,12 +10,10 @@ import postcssNesting from "postcss-nesting";
|
|
|
10
10
|
import { createMemoryHistory } from "history";
|
|
11
11
|
import { createRouter } from 'radix3';
|
|
12
12
|
import mimeTypes from "mime-types";
|
|
13
|
-
import React from "react";
|
|
14
|
-
import { renderToReadableStream } from "react-dom/server";
|
|
15
|
-
import { HeadApp, BodyApp } from ".
|
|
13
|
+
import { HeadApp, BodyApp } from "./runtime";
|
|
16
14
|
|
|
17
|
-
const version = (await import(path.join(import.meta.dir, "
|
|
15
|
+
// const version = (await import(path.join(import.meta.dir, "package.json"))).default.version;
|
|
18
|
-
console.log(`parotta v${version}`)
|
|
16
|
+
// console.log(`parotta v${version}`)
|
|
19
17
|
console.log(`running with cwd=${path.basename(process.cwd())} node_env=${process.env.NODE_ENV}`);
|
|
20
18
|
|
|
21
19
|
const isProd = process.env.NODE_ENV === "production";
|
|
@@ -178,7 +176,7 @@ const renderPage = async (url) => {
|
|
|
178
176
|
"react-dom/client": `https://esm.sh/react-dom@18.2.0${devTag}/client`,
|
|
179
177
|
"nprogress": "https://esm.sh/nprogress@0.2.0",
|
|
180
178
|
// "parotta/runtime": `https://esm.sh/parotta@${version}/runtime.js`,
|
|
181
|
-
"parotta/runtime": `
|
|
179
|
+
"parotta/runtime": `/parotta/runtime.js`,
|
|
182
180
|
...nodeDeps,
|
|
183
181
|
...components,
|
|
184
182
|
...containers,
|
|
@@ -241,6 +239,7 @@ const renderJs = async (srcFile) => {
|
|
|
241
239
|
const result = await transpiler.transform(jsText);
|
|
242
240
|
// inject code which calls the api for that function
|
|
243
241
|
const lines = result.split("\n");
|
|
242
|
+
// lines.unshift(`import React from "react";`);
|
|
244
243
|
|
|
245
244
|
// replace all .service imports which rpc interface
|
|
246
245
|
let addRpcImport = false;
|
|
@@ -256,7 +255,7 @@ const renderJs = async (srcFile) => {
|
|
|
256
255
|
}
|
|
257
256
|
})
|
|
258
257
|
if (addRpcImport) {
|
|
259
|
-
lines.unshift(`import { rpc } from "parotta/runtime"`);
|
|
258
|
+
lines.unshift(`import { rpc } from "parotta/runtime";`);
|
|
260
259
|
}
|
|
261
260
|
// remove .css and .service imports
|
|
262
261
|
const filteredJsx = lines.filter((ln) => !ln.includes(".css") && !ln.includes(".service")).join("\n");
|
|
@@ -291,47 +290,38 @@ const sendFile = async (src) => {
|
|
|
291
290
|
}
|
|
292
291
|
}
|
|
293
292
|
|
|
294
|
-
export default {
|
|
295
|
-
port: 3000,
|
|
296
|
-
|
|
293
|
+
const server = async (req) => {
|
|
297
|
-
|
|
294
|
+
const url = new URL(req.url);
|
|
298
|
-
|
|
295
|
+
console.log(req.method, url.pathname);
|
|
299
|
-
|
|
296
|
+
// maybe this is needed
|
|
300
|
-
|
|
297
|
+
if (url.pathname.startsWith("/parotta/")) {
|
|
301
|
-
|
|
298
|
+
return renderJs(path.join(import.meta.dir, url.pathname.replace("/parotta/", "")));
|
|
302
|
-
|
|
299
|
+
}
|
|
303
|
-
|
|
300
|
+
if (url.pathname.endsWith(".css")) {
|
|
304
|
-
|
|
301
|
+
return renderCss(path.join(process.cwd(), url.pathname));
|
|
305
|
-
|
|
302
|
+
}
|
|
306
|
-
|
|
303
|
+
if (url.pathname.endsWith(".js") || url.pathname.endsWith(".jsx")) {
|
|
307
|
-
|
|
304
|
+
return renderJs(path.join(process.cwd(), url.pathname));
|
|
308
|
-
|
|
305
|
+
}
|
|
309
|
-
|
|
306
|
+
const match = serverRouter.lookup(url.pathname);
|
|
310
|
-
|
|
307
|
+
if (match) {
|
|
311
|
-
|
|
308
|
+
if (match.file) {
|
|
312
|
-
|
|
309
|
+
return sendFile(path.join(process.cwd(), `/static${match.file}`));
|
|
313
|
-
}
|
|
314
|
-
if (match.page && req.headers.get("Accept")?.includes('text/html')) {
|
|
315
|
-
return renderPage(url);
|
|
316
|
-
}
|
|
317
|
-
if (match.service) {
|
|
318
|
-
return renderApi(match.key, match.service, req);
|
|
319
|
-
}
|
|
320
310
|
}
|
|
321
|
-
if (req.headers.get("Accept")?.includes('text/html')) {
|
|
311
|
+
if (match.page && req.headers.get("Accept")?.includes('text/html')) {
|
|
322
312
|
return renderPage(url);
|
|
323
313
|
}
|
|
314
|
+
if (match.service) {
|
|
315
|
+
return renderApi(match.key, match.service, req);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (req.headers.get("Accept")?.includes('text/html')) {
|
|
319
|
+
return renderPage(url);
|
|
320
|
+
}
|
|
324
|
-
|
|
321
|
+
return new Response(`{"message": "not found"}`, {
|
|
325
|
-
|
|
322
|
+
headers: { 'Content-Type': 'application/json' },
|
|
326
|
-
|
|
323
|
+
status: 404,
|
|
327
|
-
|
|
324
|
+
});
|
|
328
|
-
},
|
|
329
|
-
error(error) {
|
|
330
|
-
console.log("error", error);
|
|
331
|
-
return new Response(`<pre>${error}\n${error.stack}</pre>`, {
|
|
332
|
-
headers: {
|
|
333
|
-
"Content-Type": "text/html",
|
|
334
|
-
},
|
|
335
|
-
});
|
|
336
|
-
},
|
|
337
|
-
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export default server;
|
example/playwright.config.js → playwright.config.js
RENAMED
|
File without changes
|
readme.md
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
|
+
# Sample Parotta Application
|
|
2
|
+
|
|
1
|
-
#
|
|
3
|
+
## Requirements
|
|
2
|
-
|
|
3
|
-
|
|
4
|
+
|
|
4
|
-
It uses bun as its bundler/transpiler and development mode as its quick and fast.
|
|
5
|
-
It uses File System routing with streaming SSR + CSR as the method to render pages. Basically a MPA + SPA Transitional App.
|
|
6
|
-
|
|
5
|
+
1. bun >= v0.5.8
|
|
7
|
-
|
|
6
|
+
|
|
8
|
-
|
|
9
|
-
##
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
10
|
-
1.
|
|
9
|
+
1. `bun i`
|
|
11
|
-
2. Deploy to Node (using edge-runtime), Docker, Deno deploy, Vercel edge functions, Cloudflare workers, Bun edge (whenever it releases)
|
|
12
|
-
|
|
10
|
+
2. `bunx playright install`
|
|
11
|
+
|
|
12
|
+
## Running
|
|
13
|
+
|
|
13
|
-
|
|
14
|
+
`bun run dev`
|
|
15
|
+
|
|
16
|
+
## Testing
|
|
17
|
+
|
|
18
|
+
`bun test`
|
{example/services → services}/auth.service.js
RENAMED
|
File without changes
|
{example/services → services}/todos.service.js
RENAMED
|
File without changes
|
{example/static → static}/favicon.ico
RENAMED
|
File without changes
|
{example/static → static}/logo192.png
RENAMED
|
File without changes
|
{example/static → static}/logo512.png
RENAMED
|
File without changes
|
{example/static → static}/manifest.json
RENAMED
|
File without changes
|
{example/static → static}/robots.txt
RENAMED
|
File without changes
|