visionos-monorepo 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/worktrees/competent-burnell-8d1330/README.md +138 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/package.json +35 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/scripts/copy-web-assets.mjs +12 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/logout.ts +12 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/open.ts +19 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/start.ts +97 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/status.ts +23 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/userinfo.ts +47 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/index.ts +23 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/auth.ts +84 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/browser.ts +37 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/localState.ts +80 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/runtime.ts +203 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/runtime/index.ts +36 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/types/inquirer.d.ts +9 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/tsconfig.json +19 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/index.html +15 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/package.json +27 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/postcss.config.cjs +7 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/App.tsx +57 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/components/CliAuthPage.tsx +385 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/components/ManifestoPage.tsx +946 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/components/TrackCard.tsx +19 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/lib/api.ts +58 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/main.tsx +11 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/styles/index.css +33 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/styles/manifesto.css +1398 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/tailwind.config.ts +36 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/tsconfig.json +25 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/vite.config.ts +20 -0
- package/.claude/worktrees/competent-burnell-8d1330/package-lock.json +5278 -0
- package/.claude/worktrees/competent-burnell-8d1330/package.json +24 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/package.json +25 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/src/app.ts +71 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/src/config/env.ts +14 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/src/features/auth/sessionStore.ts +74 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/src/index.ts +8 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/src/routes/auth.ts +112 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/src/routes/health.ts +14 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/tsconfig.json +19 -0
- package/.claude/worktrees/competent-burnell-8d1330/shared/package.json +24 -0
- package/.claude/worktrees/competent-burnell-8d1330/shared/src/index.ts +91 -0
- package/.claude/worktrees/competent-burnell-8d1330/shared/tsconfig.json +16 -0
- package/.claude/worktrees/competent-burnell-8d1330/tsconfig.base.json +12 -0
- package/.claude/worktrees/competent-burnell-8d1330/visionos-manifesto/index.html +392 -0
- package/.claude/worktrees/competent-burnell-8d1330/visionos-manifesto/script.js +146 -0
- package/.claude/worktrees/competent-burnell-8d1330/visionos-manifesto/styles.css +1082 -0
- package/.claude/worktrees/vigilant-napier-0de76f/README.md +138 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/package.json +35 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/scripts/copy-web-assets.mjs +12 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/logout.ts +12 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/open.ts +19 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/start.ts +97 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/status.ts +23 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/userinfo.ts +47 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/index.ts +23 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/auth.ts +84 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/browser.ts +37 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/localState.ts +80 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/runtime.ts +203 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/runtime/index.ts +36 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/types/inquirer.d.ts +9 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/tsconfig.json +19 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/index.html +15 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/package.json +27 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/postcss.config.cjs +7 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/App.tsx +57 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/components/CliAuthPage.tsx +385 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/components/ManifestoPage.tsx +946 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/components/TrackCard.tsx +19 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/lib/api.ts +58 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/main.tsx +11 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/styles/index.css +33 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/styles/manifesto.css +1398 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/tailwind.config.ts +36 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/tsconfig.json +25 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/vite.config.ts +20 -0
- package/.claude/worktrees/vigilant-napier-0de76f/package-lock.json +5278 -0
- package/.claude/worktrees/vigilant-napier-0de76f/package.json +24 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/package.json +25 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/src/app.ts +71 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/src/config/env.ts +14 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/src/features/auth/sessionStore.ts +74 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/src/index.ts +8 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/src/routes/auth.ts +112 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/src/routes/health.ts +14 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/tsconfig.json +19 -0
- package/.claude/worktrees/vigilant-napier-0de76f/shared/package.json +24 -0
- package/.claude/worktrees/vigilant-napier-0de76f/shared/src/index.ts +91 -0
- package/.claude/worktrees/vigilant-napier-0de76f/shared/tsconfig.json +16 -0
- package/.claude/worktrees/vigilant-napier-0de76f/tsconfig.base.json +12 -0
- package/.claude/worktrees/vigilant-napier-0de76f/visionos-manifesto/index.html +392 -0
- package/.claude/worktrees/vigilant-napier-0de76f/visionos-manifesto/script.js +146 -0
- package/.claude/worktrees/vigilant-napier-0de76f/visionos-manifesto/styles.css +1082 -0
- package/.github/workflows/publish.yml +30 -0
- package/README.md +175 -0
- package/cli/README.md +165 -0
- package/cli/package.json +36 -0
- package/cli/scripts/copy-web-assets.mjs +12 -0
- package/cli/src/commands/lessons.ts +68 -0
- package/cli/src/commands/login.ts +46 -0
- package/cli/src/commands/logout.ts +12 -0
- package/cli/src/commands/open.ts +29 -0
- package/cli/src/commands/start.ts +146 -0
- package/cli/src/commands/status.ts +28 -0
- package/cli/src/commands/userinfo.ts +59 -0
- package/cli/src/index.ts +109 -0
- package/cli/src/lib/auth.ts +84 -0
- package/cli/src/lib/browser.ts +37 -0
- package/cli/src/lib/content.ts +57 -0
- package/cli/src/lib/lessonPrinter.ts +38 -0
- package/cli/src/lib/lessonRunner.ts +381 -0
- package/cli/src/lib/localState.ts +114 -0
- package/cli/src/lib/loginFlow.ts +74 -0
- package/cli/src/lib/progress.ts +94 -0
- package/cli/src/lib/runtime.ts +220 -0
- package/cli/src/lib/validator.ts +401 -0
- package/cli/src/runtime/index.ts +108 -0
- package/cli/src/types/inquirer.d.ts +9 -0
- package/cli/tsconfig.json +19 -0
- package/client/index.html +15 -0
- package/client/package.json +27 -0
- package/client/postcss.config.cjs +7 -0
- package/client/src/App.tsx +102 -0
- package/client/src/components/AccountPage.tsx +79 -0
- package/client/src/components/AuthPanel.tsx +312 -0
- package/client/src/components/CliAuthPage.tsx +367 -0
- package/client/src/components/CreatorPortal.tsx +885 -0
- package/client/src/components/ErrorBoundary.tsx +92 -0
- package/client/src/components/ManifestoPage.tsx +1126 -0
- package/client/src/components/TrackCard.tsx +19 -0
- package/client/src/lib/api.ts +215 -0
- package/client/src/main.tsx +14 -0
- package/client/src/styles/index.css +33 -0
- package/client/src/styles/manifesto.css +1828 -0
- package/client/tailwind.config.ts +36 -0
- package/client/tsconfig.json +25 -0
- package/client/vercel.json +8 -0
- package/client/vite.config.ts +33 -0
- package/package.json +27 -0
- package/server/package.json +26 -0
- package/server/src/app.ts +132 -0
- package/server/src/config/env.ts +135 -0
- package/server/src/features/accounts/accountStore.ts +359 -0
- package/server/src/features/accounts/contentStore.ts +264 -0
- package/server/src/features/accounts/password.ts +26 -0
- package/server/src/features/auth/sessionStore.ts +79 -0
- package/server/src/index.ts +8 -0
- package/server/src/routes/auth.ts +328 -0
- package/server/src/routes/content.ts +174 -0
- package/server/src/routes/health.ts +14 -0
- package/server/src/routes/progress.ts +105 -0
- package/server/tsconfig.json +19 -0
- package/shared/package.json +24 -0
- package/shared/src/index.ts +455 -0
- package/shared/tsconfig.json +16 -0
- package/tsconfig.base.json +12 -0
- package/visionos-manifesto/index.html +392 -0
- package/visionos-manifesto/script.js +146 -0
- package/visionos-manifesto/styles.css +1082 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Config } from "tailwindcss";
|
|
2
|
+
|
|
3
|
+
const config: Config = {
|
|
4
|
+
content: ["./index.html", "./src/**/*.{ts,tsx}"],
|
|
5
|
+
theme: {
|
|
6
|
+
extend: {
|
|
7
|
+
colors: {
|
|
8
|
+
ink: "#0A0A0A",
|
|
9
|
+
"ink-soft": "#3A3A3A",
|
|
10
|
+
"ink-muted": "#6B6B6B",
|
|
11
|
+
beige: "#E0DFD5",
|
|
12
|
+
"beige-light": "#E8E7DF",
|
|
13
|
+
"beige-dark": "#D4D3CB",
|
|
14
|
+
tide: "#0e7490",
|
|
15
|
+
coral: "#E05A3A",
|
|
16
|
+
sand: "#FAFAF8",
|
|
17
|
+
mist: "#E0DFD5"
|
|
18
|
+
},
|
|
19
|
+
fontFamily: {
|
|
20
|
+
sans: ["Inter", "-apple-system", "BlinkMacSystemFont", "sans-serif"],
|
|
21
|
+
mono: ["JetBrains Mono", "SF Mono", "Fira Code", "monospace"]
|
|
22
|
+
},
|
|
23
|
+
boxShadow: {
|
|
24
|
+
panel: "0 20px 60px rgba(8, 17, 31, 0.12)"
|
|
25
|
+
},
|
|
26
|
+
borderRadius: {
|
|
27
|
+
lg: "16px",
|
|
28
|
+
md: "8px",
|
|
29
|
+
sm: "4px"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
plugins: []
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default config;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "ES2020",
|
|
5
|
+
"lib": [
|
|
6
|
+
"ES2020",
|
|
7
|
+
"DOM",
|
|
8
|
+
"DOM.Iterable"
|
|
9
|
+
],
|
|
10
|
+
"module": "ESNext",
|
|
11
|
+
"moduleResolution": "Bundler",
|
|
12
|
+
"jsx": "react-jsx",
|
|
13
|
+
"types": [
|
|
14
|
+
"vite/client",
|
|
15
|
+
"node"
|
|
16
|
+
],
|
|
17
|
+
"noEmit": true
|
|
18
|
+
},
|
|
19
|
+
"include": [
|
|
20
|
+
"src",
|
|
21
|
+
"vite.config.ts",
|
|
22
|
+
"tailwind.config.ts",
|
|
23
|
+
"../shared/src/**/*.ts"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { defineConfig, loadEnv } from "vite";
|
|
4
|
+
import react from "@vitejs/plugin-react";
|
|
5
|
+
|
|
6
|
+
const rootDir = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
export default defineConfig(({ mode }) => {
|
|
9
|
+
const env = loadEnv(mode, rootDir, "");
|
|
10
|
+
const apiProxyTarget = env.VITE_API_PROXY_TARGET ?? "https://visionos.onrender.com";
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
plugins: [react()],
|
|
14
|
+
resolve: {
|
|
15
|
+
alias: {
|
|
16
|
+
"@": path.resolve(rootDir, "./src")
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
server: {
|
|
20
|
+
fs: {
|
|
21
|
+
allow: [path.resolve(rootDir, "..")]
|
|
22
|
+
},
|
|
23
|
+
proxy: {
|
|
24
|
+
"/api": {
|
|
25
|
+
target: apiProxyTarget,
|
|
26
|
+
changeOrigin: true,
|
|
27
|
+
secure: true,
|
|
28
|
+
rewrite: (requestPath) => requestPath.replace(/^\/api/, "")
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "visionos-monorepo",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"packageManager": "npm@10",
|
|
6
|
+
"workspaces": [
|
|
7
|
+
"cli",
|
|
8
|
+
"server",
|
|
9
|
+
"client",
|
|
10
|
+
"shared"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "npm run build -w shared && npm run build -w server && npm run build -w client && npm run build -w cli",
|
|
14
|
+
"pack:cli": "npm run build && npm pack -w cli",
|
|
15
|
+
"dev": "concurrently -k -n server,client -c blue,magenta \"npm run dev:server\" \"npm run dev:client\"",
|
|
16
|
+
"dev:cli": "npm run dev -w cli",
|
|
17
|
+
"dev:server": "npm run dev -w server",
|
|
18
|
+
"dev:client": "npm run dev -w client",
|
|
19
|
+
"lint": "npm run lint --workspaces --if-present"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"concurrently": "^9.2.1"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"visionos-cli": "^0.1.7"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@visionos/server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsup src/index.ts --format esm --clean --out-dir dist",
|
|
8
|
+
"dev": "tsx watch src/index.ts",
|
|
9
|
+
"start": "node dist/index.js",
|
|
10
|
+
"lint": "tsc --noEmit -p tsconfig.json"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"cors": "^2.8.5",
|
|
14
|
+
"dotenv": "^16.4.5",
|
|
15
|
+
"express": "^4.21.1",
|
|
16
|
+
"mongodb": "^6.21.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/cors": "^2.8.17",
|
|
20
|
+
"@types/express": "^4.17.21",
|
|
21
|
+
"@types/node": "^22.10.1",
|
|
22
|
+
"tsup": "^8.3.0",
|
|
23
|
+
"tsx": "^4.19.1",
|
|
24
|
+
"typescript": "^5.6.3"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import cors from "cors";
|
|
3
|
+
import express from "express";
|
|
4
|
+
import { env } from "./config/env.js";
|
|
5
|
+
import { AccountStoreError, createAccountStore, type AccountStore } from "./features/accounts/accountStore.js";
|
|
6
|
+
import { MongoContentStore, type ContentStore } from "./features/accounts/contentStore.js";
|
|
7
|
+
import { createAuthRouter, type CreateAuthRouterOptions } from "./routes/auth.js";
|
|
8
|
+
import { healthRouter } from "./routes/health.js";
|
|
9
|
+
import { createProgressRouter } from "./routes/progress.js";
|
|
10
|
+
import { createContentRouter } from "./routes/content.js";
|
|
11
|
+
|
|
12
|
+
export interface CreateAppOptions extends CreateAuthRouterOptions {
|
|
13
|
+
accountStore?: AccountStore | null;
|
|
14
|
+
contentStore?: ContentStore | null;
|
|
15
|
+
apiBaseUrl?: string;
|
|
16
|
+
clientOrigin?: string;
|
|
17
|
+
corsOrigin?: any;
|
|
18
|
+
mode?: "development" | "embedded";
|
|
19
|
+
staticSiteDir?: string;
|
|
20
|
+
getProgress?: (userId: string) => Promise<any[]> | any[];
|
|
21
|
+
upsertProgress?: (userId: string, input: any) => Promise<any> | any;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeOrigin(origin: string) {
|
|
25
|
+
return origin.trim().replace(/\/+$/, "");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function toAllowedOriginSet(corsOrigin: any) {
|
|
29
|
+
if (corsOrigin === true) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const origins = Array.isArray(corsOrigin)
|
|
34
|
+
? corsOrigin
|
|
35
|
+
: typeof corsOrigin === "string"
|
|
36
|
+
? corsOrigin.split(",")
|
|
37
|
+
: [];
|
|
38
|
+
|
|
39
|
+
return new Set(
|
|
40
|
+
origins
|
|
41
|
+
.filter((origin): origin is string => typeof origin === "string")
|
|
42
|
+
.map(normalizeOrigin)
|
|
43
|
+
.filter(Boolean)
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createApp(options: CreateAppOptions = {}) {
|
|
48
|
+
const app = express();
|
|
49
|
+
const clientOrigin = options.clientOrigin ?? env.clientOrigin;
|
|
50
|
+
const corsOrigin = options.corsOrigin ?? env.corsOrigin;
|
|
51
|
+
const allowedOrigins = toAllowedOriginSet(corsOrigin);
|
|
52
|
+
const apiBaseUrl = options.apiBaseUrl ?? `http://${env.host}:${env.port}`;
|
|
53
|
+
const accountStore = options.accountStore ?? createAccountStore(env.mongoUri, env.mongoDbName);
|
|
54
|
+
const contentStore = options.contentStore ?? (env.mongoUri ? new MongoContentStore(env.mongoUri, env.mongoDbName) : null);
|
|
55
|
+
const authRouter = createAuthRouter({
|
|
56
|
+
accountStore,
|
|
57
|
+
clientOrigin,
|
|
58
|
+
googleClientId: options.googleClientId ?? env.googleClientId,
|
|
59
|
+
getCurrentUser: options.getCurrentUser,
|
|
60
|
+
onAuthenticated: options.onAuthenticated
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
app.use(
|
|
64
|
+
cors({
|
|
65
|
+
origin(origin, callback) {
|
|
66
|
+
if (!origin || allowedOrigins === true || allowedOrigins.has(normalizeOrigin(origin))) {
|
|
67
|
+
callback(null, true);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
callback(null, false);
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
);
|
|
75
|
+
app.use(express.json());
|
|
76
|
+
app.use(express.urlencoded({ extended: true }));
|
|
77
|
+
|
|
78
|
+
app.use("/auth", authRouter);
|
|
79
|
+
app.use(
|
|
80
|
+
"/progress",
|
|
81
|
+
createProgressRouter(
|
|
82
|
+
accountStore,
|
|
83
|
+
options.getCurrentUser,
|
|
84
|
+
options.getProgress,
|
|
85
|
+
options.upsertProgress
|
|
86
|
+
)
|
|
87
|
+
);
|
|
88
|
+
app.use("/content", createContentRouter(accountStore, contentStore));
|
|
89
|
+
app.use("/health", healthRouter);
|
|
90
|
+
|
|
91
|
+
app.get("/runtime/info", (_req, res) => {
|
|
92
|
+
res.status(200).json({
|
|
93
|
+
runtime: {
|
|
94
|
+
appUrl: clientOrigin,
|
|
95
|
+
apiBaseUrl,
|
|
96
|
+
mainAppUrl: env.mainClientOrigin,
|
|
97
|
+
mode: options.mode ?? "development"
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (options.staticSiteDir) {
|
|
103
|
+
app.use(express.static(options.staticSiteDir));
|
|
104
|
+
|
|
105
|
+
app.get("*", (_req, res) => {
|
|
106
|
+
res.sendFile(path.join(options.staticSiteDir as string, "index.html"));
|
|
107
|
+
});
|
|
108
|
+
} else {
|
|
109
|
+
app.get("/", (_req, res) => {
|
|
110
|
+
res.status(200).json({
|
|
111
|
+
message: "VisionOS API bootstrap is running."
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
app.use((error: unknown, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
|
|
117
|
+
if (error instanceof AccountStoreError) {
|
|
118
|
+
res.status(error.status).json({
|
|
119
|
+
message: error.message
|
|
120
|
+
});
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const message = error instanceof Error ? error.message : "Unexpected VisionOS server error.";
|
|
125
|
+
|
|
126
|
+
res.status(500).json({
|
|
127
|
+
message
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return app;
|
|
132
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
let mainClientOrigin = "http://localhost:5173";
|
|
8
|
+
|
|
9
|
+
function loadEnvFile(filePath: string) {
|
|
10
|
+
if (fs.existsSync(filePath)) {
|
|
11
|
+
const result = dotenv.config({ path: filePath });
|
|
12
|
+
if (result.parsed?.CLIENT_ORIGIN) {
|
|
13
|
+
mainClientOrigin = result.parsed.CLIENT_ORIGIN;
|
|
14
|
+
}
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 1. Try standard current working directory
|
|
21
|
+
const defaultResult = dotenv.config();
|
|
22
|
+
if (defaultResult.parsed?.CLIENT_ORIGIN) {
|
|
23
|
+
mainClientOrigin = defaultResult.parsed.CLIENT_ORIGIN;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 2. Walk up parent directories of process.cwd() to find `.env` or `server/.env`
|
|
27
|
+
try {
|
|
28
|
+
let currentDir = process.cwd();
|
|
29
|
+
for (let i = 0; i < 10; i++) {
|
|
30
|
+
const rootEnv = path.join(currentDir, ".env");
|
|
31
|
+
const serverEnv = path.join(currentDir, "server", ".env");
|
|
32
|
+
|
|
33
|
+
if (loadEnvFile(serverEnv)) {
|
|
34
|
+
break;
|
|
35
|
+
} else if (loadEnvFile(rootEnv)) {
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const parent = path.dirname(currentDir);
|
|
40
|
+
if (parent === currentDir) break;
|
|
41
|
+
currentDir = parent;
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
// Ignore fallback errors
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 3. Walk up directories starting from import.meta.url to find `.env` or `server/.env`
|
|
48
|
+
try {
|
|
49
|
+
const startDir = path.dirname(fileURLToPath(import.meta.url));
|
|
50
|
+
let currentDir = startDir;
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < 10; i++) {
|
|
53
|
+
const rootEnv = path.join(currentDir, ".env");
|
|
54
|
+
const serverEnv = path.join(currentDir, "server", ".env");
|
|
55
|
+
|
|
56
|
+
if (loadEnvFile(serverEnv)) {
|
|
57
|
+
break;
|
|
58
|
+
} else if (loadEnvFile(rootEnv)) {
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const parent = path.dirname(currentDir);
|
|
63
|
+
if (parent === currentDir) break;
|
|
64
|
+
currentDir = parent;
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// Ignore fallback errors
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 4. Try global user configuration directory (~/.visionos/.env)
|
|
71
|
+
try {
|
|
72
|
+
const globalEnvPath = path.join(os.homedir(), ".visionos", ".env");
|
|
73
|
+
if (fs.existsSync(globalEnvPath)) {
|
|
74
|
+
const result = dotenv.config({ path: globalEnvPath });
|
|
75
|
+
if (result.parsed?.CLIENT_ORIGIN) {
|
|
76
|
+
mainClientOrigin = result.parsed.CLIENT_ORIGIN;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
// Ignore fallback errors
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const rawPort = Number(process.env.PORT ?? 4000);
|
|
84
|
+
const host = process.env.HOST ?? "127.0.0.1";
|
|
85
|
+
const clientOrigin = process.env.CLIENT_ORIGIN ?? "http://localhost:5173";
|
|
86
|
+
const localDevOrigins = ["http://localhost:5173", "http://127.0.0.1:5173"];
|
|
87
|
+
const deployedOrigins = ["https://vision-os-client.vercel.app", "https://visionos.onrender.com"];
|
|
88
|
+
|
|
89
|
+
function normalizeOrigin(origin?: string) {
|
|
90
|
+
return origin?.trim().replace(/\/+$/, "");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function parseOrigins(value?: string) {
|
|
94
|
+
return (value ?? "")
|
|
95
|
+
.split(",")
|
|
96
|
+
.map(normalizeOrigin)
|
|
97
|
+
.filter(Boolean);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function uniqueOrigins(origins: Array<string | undefined>) {
|
|
101
|
+
return [
|
|
102
|
+
...new Set(
|
|
103
|
+
origins
|
|
104
|
+
.map(normalizeOrigin)
|
|
105
|
+
.filter((origin): origin is string => Boolean(origin))
|
|
106
|
+
)
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function resolveCorsOrigin() {
|
|
111
|
+
const configuredOrigins = parseOrigins(process.env.CORS_ORIGIN);
|
|
112
|
+
|
|
113
|
+
if (configuredOrigins.includes("*")) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return uniqueOrigins([
|
|
118
|
+
...configuredOrigins,
|
|
119
|
+
clientOrigin,
|
|
120
|
+
mainClientOrigin,
|
|
121
|
+
...localDevOrigins,
|
|
122
|
+
...deployedOrigins
|
|
123
|
+
]);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export const env = {
|
|
127
|
+
host,
|
|
128
|
+
port: Number.isNaN(rawPort) ? 4000 : rawPort,
|
|
129
|
+
clientOrigin,
|
|
130
|
+
mainClientOrigin,
|
|
131
|
+
corsOrigin: resolveCorsOrigin(),
|
|
132
|
+
googleClientId: process.env.GOOGLE_CLIENT_ID,
|
|
133
|
+
mongoDbName: process.env.MONGODB_DB ?? "visionos",
|
|
134
|
+
mongoUri: process.env.MONGODB_URI
|
|
135
|
+
};
|