From 177264878c7abab85f5480ff132b9328934b7510 Mon Sep 17 00:00:00 2001 From: Charles Nykamp Date: Wed, 22 Apr 2026 11:26:57 -0500 Subject: [PATCH 1/6] feat: add unified dev server with concurrently Start all three dev servers (API, app, web) with a single 'npm run dev' command. This simplifies the developer experience by eliminating the need to run multiple terminals for: - npm run dev --workspace @doenet-tools/api (port 3000) - npm run dev --workspace @doenet-tools/app (port 8000) - npm run dev --workspace @doenet-tools/web (port 4321) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 19 ++++++--- package-lock.json | 101 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 + 3 files changed, 117 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0646e8fdd..479c490d8 100644 --- a/README.md +++ b/README.md @@ -66,15 +66,24 @@ npm run build --workspace @doenet-tools/shared **7. Start the dev servers** -Run each in its own terminal: +All three dev servers can be started together with a single command: ```bash -npm run dev --workspace @doenet-tools/api # Express API → http://localhost:3000 -npm run dev --workspace @doenet-tools/app # React SPA → http://localhost:8000 -npm run dev --workspace @doenet-tools/web # Astro site → http://localhost:4321 +npm run dev ``` -The `app` dev server proxies `/api/*` to the API, so both must be running for the full app to work. +This starts: +- Express API → http://localhost:3000 +- React SPA → http://localhost:8000 (proxies `/api/*` to the API) +- Astro site → http://localhost:4321 + +Alternatively, run each in a separate terminal if needed: + +```bash +npm run dev --workspace @doenet-tools/api # Express API +npm run dev --workspace @doenet-tools/app # React SPA +npm run dev --workspace @doenet-tools/web # Astro site +``` --- diff --git a/package-lock.json b/package-lock.json index 98a59a652..657f43f14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@eslint/js": "^9.39.2", "@types/luxon": "^3.7.1", "@types/node": "^25.0.10", + "concurrently": "^9.1.0", "cypress": "^15.10.0", "cypress-axe": "^1.7.0", "cypress-file-upload": "^5.0.8", @@ -9569,6 +9570,93 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/concurrently/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/concurrently/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/confbox": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", @@ -20019,6 +20107,19 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/shiki": { "version": "3.22.0", "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.22.0.tgz", diff --git a/package.json b/package.json index e2a5fb4a5..227bcf775 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "postinstall": "npm run prisma:generate --workspace @doenet-tools/api", "setup": "node scripts/setup.js", "db:setup": "npm run prisma:migrate-dev --workspace @doenet-tools/api && npm run prisma:seed --workspace @doenet-tools/api", + "dev": "concurrently \"npm run dev --workspace @doenet-tools/api\" \"npm run dev --workspace @doenet-tools/app\" \"npm run dev --workspace @doenet-tools/web\"", "format": "prettier . --write", "format:check": "prettier . --check", "lint": "npm run lint --workspaces --if-present", @@ -24,6 +25,7 @@ "@eslint/js": "^9.39.2", "@types/luxon": "^3.7.1", "@types/node": "^25.0.10", + "concurrently": "^9.1.0", "cypress": "^15.10.0", "cypress-axe": "^1.7.0", "cypress-file-upload": "^5.0.8", From fabad3c105bbc1cd5ed0dd83228126dbe4f3d54a Mon Sep 17 00:00:00 2001 From: Charles Nykamp Date: Wed, 22 Apr 2026 12:27:09 -0500 Subject: [PATCH 2/6] feat: resolve shared package from source in dev Use a Vite serve-only resolver for the app and a dev-only tsconfig for the API so local development does not require a prebuilt shared package. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 15 ++-- apps/api/package.json | 2 +- apps/api/tsconfig.dev.json | 9 +++ apps/app/vite.config.mts | 137 ++++++++++++++++++++++--------------- 4 files changed, 96 insertions(+), 67 deletions(-) create mode 100644 apps/api/tsconfig.dev.json diff --git a/README.md b/README.md index 479c490d8..25135b35c 100644 --- a/README.md +++ b/README.md @@ -56,15 +56,7 @@ npm run db:setup This creates the required database tables and seeds them with minimal data. -**6. Build the shared package** - -The `shared` package must be built before starting the dev servers: - -```bash -npm run build --workspace @doenet-tools/shared -``` - -**7. Start the dev servers** +**6. Start the dev servers** All three dev servers can be started together with a single command: @@ -73,10 +65,15 @@ npm run dev ``` This starts: + - Express API → http://localhost:3000 - React SPA → http://localhost:8000 (proxies `/api/*` to the API) - Astro site → http://localhost:4321 +No separate `shared` build is required for local development. The app and API +resolve `@doenet-tools/shared` directly from `packages/shared/src` while the dev +servers are running. + Alternatively, run each in a separate terminal if needed: ```bash diff --git a/apps/api/package.json b/apps/api/package.json index 9aeaeda02..781b64090 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -10,7 +10,7 @@ "scripts": { "build:assets": "npx tsx scripts/copy-assets-to-dist.ts", "build": "tsc --build && npm run build:assets", - "dev": "tsx watch src/index.ts", + "dev": "tsx watch --tsconfig tsconfig.dev.json src/index.ts", "lint": "eslint --fix", "lint:check": "eslint", "prisma:generate": "prisma generate", diff --git a/apps/api/tsconfig.dev.json b/apps/api/tsconfig.dev.json new file mode 100644 index 000000000..a894b5acc --- /dev/null +++ b/apps/api/tsconfig.dev.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "baseUrl": "../../", + "paths": { + "@doenet-tools/shared": ["packages/shared/src/index.ts"] + } + } +} diff --git a/apps/app/vite.config.mts b/apps/app/vite.config.mts index e9ec1a34c..32822239d 100644 --- a/apps/app/vite.config.mts +++ b/apps/app/vite.config.mts @@ -2,70 +2,93 @@ import { NodeGlobalsPolyfillPlugin } from "@esbuild-plugins/node-globals-polyfil import { NodeModulesPolyfillPlugin } from "@esbuild-plugins/node-modules-polyfill"; import react from "@vitejs/plugin-react"; import nodePolyfills from "rollup-plugin-polyfill-node"; -import { defineConfig } from "vite"; +import { defineConfig, Plugin } from "vite"; +import { fileURLToPath } from "url"; +import { dirname, resolve } from "path"; import { createRequire } from "module"; const require = createRequire(import.meta.url); +const __dirname = dirname(fileURLToPath(import.meta.url)); -export default defineConfig({ - // Node.js global to browser globalThis - define: { - global: "globalThis", - }, - optimizeDeps: { - // Pre-bundle known heavy/late-loaded deps used by component tests to - // avoid Vite re-optimization and hot-reload churn during Cypress runs. - include: [ - "@doenet/v06-to-v07", - "@doenet/doenetml-iframe", - "better-react-mathjax", - "ipfs-only-hash", - "math-expressions", - "cssesc", +// Plugin to resolve @doenet-tools/shared from source in dev mode +function devSharedSourcePlugin(): Plugin { + return { + name: "dev-shared-source", + apply: "serve", + enforce: "pre", + resolveId(id) { + if (id === "@doenet-tools/shared") { + return resolve(__dirname, "../../packages/shared/src/index.ts"); + } + }, + }; +} + +export default defineConfig(({ command }) => { + const useSharedSource = command === "serve"; + + return { + // Node.js global to browser globalThis + define: { + global: "globalThis", + }, + optimizeDeps: { + // Pre-bundle known heavy/late-loaded deps used by component tests to + // avoid Vite re-optimization and hot-reload churn during Cypress runs. + include: [ + "@doenet/v06-to-v07", + "@doenet/doenetml-iframe", + "better-react-mathjax", + "ipfs-only-hash", + "math-expressions", + "cssesc", + ], + // Exclude shared from optimization so local dev resolves directly to source. + exclude: useSharedSource ? ["@doenet-tools/shared"] : [], + }, + plugins: [ + ...(useSharedSource ? [devSharedSourcePlugin()] : []), + react(), + // Enable esbuild polyfill plugins + NodeGlobalsPolyfillPlugin({ + process: true, + buffer: true, + }), + NodeModulesPolyfillPlugin(), ], - }, - plugins: [ - react(), - // Enable esbuild polyfill plugins - NodeGlobalsPolyfillPlugin({ - process: true, - buffer: true, - }), - NodeModulesPolyfillPlugin(), - ], - server: { - port: 8000, - proxy: { - "/cyapi": "http://apache", - //"/media": "http://apache", - "/api": "http://localhost:3000", - "/media": "http://localhost:3000", - //"/api": "http://apache", + server: { + port: 8000, + proxy: { + "/cyapi": "http://apache", + //"/media": "http://apache", + "/api": "http://localhost:3000", + "/media": "http://localhost:3000", + //"/api": "http://apache", + }, }, - }, - resolve: {}, - worker: { - format: "iife", - }, - build: { - outDir: "./dist", - rollupOptions: { - plugins: [nodePolyfills()], - input: "index.html", + worker: { + format: "iife", }, - commonjsOptions: { - transformMixedEsModules: true, - // Bugfix required to handle issue with vite, rollup and libs (like react-datetime) - // https://github.com/vitejs/vite/issues/2139#issuecomment-1399098579 - defaultIsModuleExports(id) { - try { - const module = require(id); - if (module?.default) return false; - return "auto"; - } catch (_error) { - return "auto"; - } + build: { + outDir: "./dist", + rollupOptions: { + plugins: [nodePolyfills()], + input: "index.html", + }, + commonjsOptions: { + transformMixedEsModules: true, + // Bugfix required to handle issue with vite, rollup and libs (like react-datetime) + // https://github.com/vitejs/vite/issues/2139#issuecomment-1399098579 + defaultIsModuleExports(id) { + try { + const module = require(id); + if (module?.default) return false; + return "auto"; + } catch (_error) { + return "auto"; + } + }, }, }, - }, + }; }); From 67684bd476a25cd3f2142df52c1ded6e1c7c4081 Mon Sep 17 00:00:00 2001 From: Charles Nykamp Date: Wed, 22 Apr 2026 12:31:02 -0500 Subject: [PATCH 3/6] feat: watch shared package during dev Run an initial shared build before the root dev workflow starts, then keep the shared workspace rebuilding in watch mode alongside the API, app, and web dev servers. This removes the custom dev-only shared source resolution setup and uses the standard built-package workflow instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 9 +-- apps/api/package.json | 2 +- apps/api/tsconfig.dev.json | 9 --- apps/app/vite.config.mts | 136 +++++++++++++++-------------------- package.json | 2 +- packages/shared/package.json | 1 + 6 files changed, 64 insertions(+), 95 deletions(-) delete mode 100644 apps/api/tsconfig.dev.json diff --git a/README.md b/README.md index 25135b35c..4ea46e0dd 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ This creates the required database tables and seeds them with minimal data. **6. Start the dev servers** -All three dev servers can be started together with a single command: +All dev processes can be started together with a single command: ```bash npm run dev @@ -66,17 +66,18 @@ npm run dev This starts: +- Shared package watcher - Express API → http://localhost:3000 - React SPA → http://localhost:8000 (proxies `/api/*` to the API) - Astro site → http://localhost:4321 -No separate `shared` build is required for local development. The app and API -resolve `@doenet-tools/shared` directly from `packages/shared/src` while the dev -servers are running. +This command first builds `shared` once, then starts its watch process alongside +the app, API, and web dev servers. Alternatively, run each in a separate terminal if needed: ```bash +npm run dev --workspace @doenet-tools/shared # Shared package watcher npm run dev --workspace @doenet-tools/api # Express API npm run dev --workspace @doenet-tools/app # React SPA npm run dev --workspace @doenet-tools/web # Astro site diff --git a/apps/api/package.json b/apps/api/package.json index 781b64090..9aeaeda02 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -10,7 +10,7 @@ "scripts": { "build:assets": "npx tsx scripts/copy-assets-to-dist.ts", "build": "tsc --build && npm run build:assets", - "dev": "tsx watch --tsconfig tsconfig.dev.json src/index.ts", + "dev": "tsx watch src/index.ts", "lint": "eslint --fix", "lint:check": "eslint", "prisma:generate": "prisma generate", diff --git a/apps/api/tsconfig.dev.json b/apps/api/tsconfig.dev.json deleted file mode 100644 index a894b5acc..000000000 --- a/apps/api/tsconfig.dev.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "baseUrl": "../../", - "paths": { - "@doenet-tools/shared": ["packages/shared/src/index.ts"] - } - } -} diff --git a/apps/app/vite.config.mts b/apps/app/vite.config.mts index 32822239d..fcfa0e06c 100644 --- a/apps/app/vite.config.mts +++ b/apps/app/vite.config.mts @@ -2,93 +2,69 @@ import { NodeGlobalsPolyfillPlugin } from "@esbuild-plugins/node-globals-polyfil import { NodeModulesPolyfillPlugin } from "@esbuild-plugins/node-modules-polyfill"; import react from "@vitejs/plugin-react"; import nodePolyfills from "rollup-plugin-polyfill-node"; -import { defineConfig, Plugin } from "vite"; -import { fileURLToPath } from "url"; -import { dirname, resolve } from "path"; +import { defineConfig } from "vite"; import { createRequire } from "module"; const require = createRequire(import.meta.url); -const __dirname = dirname(fileURLToPath(import.meta.url)); -// Plugin to resolve @doenet-tools/shared from source in dev mode -function devSharedSourcePlugin(): Plugin { - return { - name: "dev-shared-source", - apply: "serve", - enforce: "pre", - resolveId(id) { - if (id === "@doenet-tools/shared") { - return resolve(__dirname, "../../packages/shared/src/index.ts"); - } - }, - }; -} - -export default defineConfig(({ command }) => { - const useSharedSource = command === "serve"; - - return { - // Node.js global to browser globalThis - define: { - global: "globalThis", - }, - optimizeDeps: { - // Pre-bundle known heavy/late-loaded deps used by component tests to - // avoid Vite re-optimization and hot-reload churn during Cypress runs. - include: [ - "@doenet/v06-to-v07", - "@doenet/doenetml-iframe", - "better-react-mathjax", - "ipfs-only-hash", - "math-expressions", - "cssesc", - ], - // Exclude shared from optimization so local dev resolves directly to source. - exclude: useSharedSource ? ["@doenet-tools/shared"] : [], - }, - plugins: [ - ...(useSharedSource ? [devSharedSourcePlugin()] : []), - react(), - // Enable esbuild polyfill plugins - NodeGlobalsPolyfillPlugin({ - process: true, - buffer: true, - }), - NodeModulesPolyfillPlugin(), +export default defineConfig({ + // Node.js global to browser globalThis + define: { + global: "globalThis", + }, + optimizeDeps: { + // Pre-bundle known heavy/late-loaded deps used by component tests to + // avoid Vite re-optimization and hot-reload churn during Cypress runs. + include: [ + "@doenet/v06-to-v07", + "@doenet/doenetml-iframe", + "better-react-mathjax", + "ipfs-only-hash", + "math-expressions", + "cssesc", ], - server: { - port: 8000, - proxy: { - "/cyapi": "http://apache", - //"/media": "http://apache", - "/api": "http://localhost:3000", - "/media": "http://localhost:3000", - //"/api": "http://apache", - }, + }, + plugins: [ + react(), + // Enable esbuild polyfill plugins + NodeGlobalsPolyfillPlugin({ + process: true, + buffer: true, + }), + NodeModulesPolyfillPlugin(), + ], + server: { + port: 8000, + proxy: { + "/cyapi": "http://apache", + //"/media": "http://apache", + "/api": "http://localhost:3000", + "/media": "http://localhost:3000", + //"/api": "http://apache", }, - worker: { - format: "iife", + }, + worker: { + format: "iife", + }, + build: { + outDir: "./dist", + rollupOptions: { + plugins: [nodePolyfills()], + input: "index.html", }, - build: { - outDir: "./dist", - rollupOptions: { - plugins: [nodePolyfills()], - input: "index.html", - }, - commonjsOptions: { - transformMixedEsModules: true, - // Bugfix required to handle issue with vite, rollup and libs (like react-datetime) - // https://github.com/vitejs/vite/issues/2139#issuecomment-1399098579 - defaultIsModuleExports(id) { - try { - const module = require(id); - if (module?.default) return false; - return "auto"; - } catch (_error) { - return "auto"; - } - }, + commonjsOptions: { + transformMixedEsModules: true, + // Bugfix required to handle issue with vite, rollup and libs (like react-datetime) + // https://github.com/vitejs/vite/issues/2139#issuecomment-1399098579 + defaultIsModuleExports(id) { + try { + const module = require(id); + if (module?.default) return false; + return "auto"; + } catch (_error) { + return "auto"; + } }, }, - }; + }, }); diff --git a/package.json b/package.json index 227bcf775..4eb902490 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "postinstall": "npm run prisma:generate --workspace @doenet-tools/api", "setup": "node scripts/setup.js", "db:setup": "npm run prisma:migrate-dev --workspace @doenet-tools/api && npm run prisma:seed --workspace @doenet-tools/api", - "dev": "concurrently \"npm run dev --workspace @doenet-tools/api\" \"npm run dev --workspace @doenet-tools/app\" \"npm run dev --workspace @doenet-tools/web\"", + "dev": "npm run build --workspace @doenet-tools/shared && concurrently \"npm run dev --workspace @doenet-tools/shared\" \"npm run dev --workspace @doenet-tools/api\" \"npm run dev --workspace @doenet-tools/app\" \"npm run dev --workspace @doenet-tools/web\"", "format": "prettier . --write", "format:check": "prettier . --check", "lint": "npm run lint --workspaces --if-present", diff --git a/packages/shared/package.json b/packages/shared/package.json index 8c76006b0..6c814b840 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -16,6 +16,7 @@ } }, "scripts": { + "dev": "tsc --build --watch --preserveWatchOutput", "lint": "eslint --fix", "lint:check": "eslint", "build": "tsc --build" From 61ff7da5d958b95b97127a737807a1557f579daa Mon Sep 17 00:00:00 2001 From: Charles Nykamp Date: Wed, 22 Apr 2026 12:39:30 -0500 Subject: [PATCH 4/6] fix: force shared rebuild when starting dev Force the initial shared build and shared watch mode so deleting packages/shared/dist does not leave the dev startup depending on stale TypeScript incremental state. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- package.json | 2 +- packages/shared/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4eb902490..86f6b306f 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "postinstall": "npm run prisma:generate --workspace @doenet-tools/api", "setup": "node scripts/setup.js", "db:setup": "npm run prisma:migrate-dev --workspace @doenet-tools/api && npm run prisma:seed --workspace @doenet-tools/api", - "dev": "npm run build --workspace @doenet-tools/shared && concurrently \"npm run dev --workspace @doenet-tools/shared\" \"npm run dev --workspace @doenet-tools/api\" \"npm run dev --workspace @doenet-tools/app\" \"npm run dev --workspace @doenet-tools/web\"", + "dev": "npm run build --workspace @doenet-tools/shared -- --force && concurrently \"npm run dev --workspace @doenet-tools/shared\" \"npm run dev --workspace @doenet-tools/api\" \"npm run dev --workspace @doenet-tools/app\" \"npm run dev --workspace @doenet-tools/web\"", "format": "prettier . --write", "format:check": "prettier . --check", "lint": "npm run lint --workspaces --if-present", diff --git a/packages/shared/package.json b/packages/shared/package.json index 6c814b840..f16876ea5 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -16,7 +16,7 @@ } }, "scripts": { - "dev": "tsc --build --watch --preserveWatchOutput", + "dev": "tsc --build --watch --preserveWatchOutput --force", "lint": "eslint --fix", "lint:check": "eslint", "build": "tsc --build" From e88354682e503f562611352e0ee67defd90be9cd Mon Sep 17 00:00:00 2001 From: Charles Nykamp Date: Wed, 22 Apr 2026 22:09:58 -0500 Subject: [PATCH 5/6] feat: route blog through local app origin Proxy Astro blog pages and assets through the app dev server so local blog URLs work under localhost:8000/blog/... like production. Also add the Astro homepage env configuration to setup and document the updated local dev flow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 16 +++++++++++++--- apps/app/vite.config.mts | 5 +++++ apps/web/.env.example | 8 ++++---- apps/web/astro.config.mjs | 3 +++ scripts/setup.js | 4 ++++ 5 files changed, 29 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4ea46e0dd..db52ffda3 100644 --- a/README.md +++ b/README.md @@ -32,13 +32,18 @@ cd DoenetTools npm install ``` -**3. Create the `.env` file** +**3. Create the `.env` files** ```bash npm run setup ``` -This copies `apps/api/.env.example` to `apps/api/.env`. The defaults work for local development, but edit as needed (e.g. change `DATABASE_HOST`, `DATABASE_PORT`, or `DATABASE_PASSWORD` if your MySQL setup differs — and update `DATABASE_URL` to match). +This copies: + +- `apps/api/.env.example` to `apps/api/.env` +- `apps/web/.env.example` to `apps/web/.env` + +The API defaults work for local development, but edit as needed (e.g. change `DATABASE_HOST`, `DATABASE_PORT`, or `DATABASE_PASSWORD` if your MySQL setup differs — and update `DATABASE_URL` to match). The web env sets the Astro header logo link target used in local development. **4. Start the database** @@ -68,12 +73,17 @@ This starts: - Shared package watcher - Express API → http://localhost:3000 -- React SPA → http://localhost:8000 (proxies `/api/*` to the API) +- React SPA → http://localhost:8000 (proxies `/api/*` to the API and `/blog/*` to Astro) - Astro site → http://localhost:4321 This command first builds `shared` once, then starts its watch process alongside the app, API, and web dev servers. +For local development, use the app origin for both frontends: + +- app pages → `http://localhost:8000/...` +- blog pages → `http://localhost:8000/blog/...` + Alternatively, run each in a separate terminal if needed: ```bash diff --git a/apps/app/vite.config.mts b/apps/app/vite.config.mts index fcfa0e06c..c431c5e7e 100644 --- a/apps/app/vite.config.mts +++ b/apps/app/vite.config.mts @@ -36,6 +36,11 @@ export default defineConfig({ server: { port: 8000, proxy: { + // Route blog pages and Astro-generated assets through the same frontend + // entry point used by the app in production. + "/blog": "http://localhost:4321", + "/_astro": "http://localhost:4321", + "/_image": "http://localhost:4321", "/cyapi": "http://apache", //"/media": "http://apache", "/api": "http://localhost:3000", diff --git a/apps/web/.env.example b/apps/web/.env.example index 5b806721b..557c94583 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -1,9 +1,9 @@ # General Environment Variables Sample # This file shows all available environment variables for the Doenet blog -# For local development, use .env.local -# For production builds, use .env.production +# For local development, use .env +# For production builds, override as needed # Main Doenet application URL -# Local development: http://localhost:3000 +# Local development: http://localhost:8000 # Production: https://doenet.org -PUBLIC_DOENET_MAIN_URL=https://doenet.org +PUBLIC_DOENET_MAIN_URL=http://localhost:8000 diff --git a/apps/web/astro.config.mjs b/apps/web/astro.config.mjs index f0656b3c7..aa12c5d8b 100644 --- a/apps/web/astro.config.mjs +++ b/apps/web/astro.config.mjs @@ -15,6 +15,9 @@ export default defineConfig({ // and possibly add redirects // aka `beta.doenet.org/blog/...` to `doenet.org/blog/...` site: "https://beta.doenet.org", + devToolbar: { + enabled: false, + }, integrations: [mdx(), sitemap(), react()], markdown: { remarkPlugins: [remarkMath], diff --git a/scripts/setup.js b/scripts/setup.js index 097d6f166..ebbebd3a4 100644 --- a/scripts/setup.js +++ b/scripts/setup.js @@ -19,3 +19,7 @@ copyEnv( path.join(root, "apps/api/.env"), path.join(root, "apps/api/.env.example"), ); +copyEnv( + path.join(root, "apps/web/.env"), + path.join(root, "apps/web/.env.example"), +); From 7654d6ddc59038e2b77c1ba8dbe24d51a10b3e62 Mon Sep 17 00:00:00 2001 From: Charles Nykamp Date: Wed, 29 Apr 2026 10:26:05 -0500 Subject: [PATCH 6/6] refactor: astro site env vars --- README.md | 9 +++------ apps/web/.env | 11 ++++++++++- apps/web/.env.dev3 | 2 +- apps/web/.env.example | 19 ------------------- apps/web/.env.prod | 2 +- apps/web/README.md | 2 +- apps/web/src/components/Header.astro | 6 +++--- apps/web/src/consts.ts | 2 +- .../learn-doenet-saint-louis-university.mdx | 4 ++-- scripts/setup.js | 4 ---- 10 files changed, 22 insertions(+), 39 deletions(-) delete mode 100644 apps/web/.env.example diff --git a/README.md b/README.md index db52ffda3..89f67e9cf 100644 --- a/README.md +++ b/README.md @@ -32,18 +32,15 @@ cd DoenetTools npm install ``` -**3. Create the `.env` files** +**3. Create the API `.env` file** ```bash npm run setup ``` -This copies: +This copies `apps/api/.env.example` to `apps/api/.env`. -- `apps/api/.env.example` to `apps/api/.env` -- `apps/web/.env.example` to `apps/web/.env` - -The API defaults work for local development, but edit as needed (e.g. change `DATABASE_HOST`, `DATABASE_PORT`, or `DATABASE_PASSWORD` if your MySQL setup differs — and update `DATABASE_URL` to match). The web env sets the Astro header logo link target used in local development. +The API defaults work for local development, but edit as needed (e.g. change `DATABASE_HOST`, `DATABASE_PORT`, or `DATABASE_PASSWORD` if your MySQL setup differs — and update `DATABASE_URL` to match). The web defaults are already committed in `apps/web/.env`; use `apps/web/.env.local` for machine-specific overrides. **4. Start the database** diff --git a/apps/web/.env b/apps/web/.env index 3b7170051..2d8002e0f 100644 --- a/apps/web/.env +++ b/apps/web/.env @@ -1,4 +1,13 @@ # Default local build environment variables for the Doenet blog. # This file is intentionally committed — all PUBLIC_ vars are public. -PUBLIC_DOENET_MAIN_URL=http://localhost:8000 + +# Environment-specific build values live in .env.dev3 and .env.prod. +# Use .env.local for machine-specific overrides. +# Use `npm run build -- --mode dev3` or `npm run build -- --mode prod` to +# select a non-default env file. + +# Main Doenet application URL +PUBLIC_APP_URL=http://localhost:8000 + +# Canonical site URL used at build time for metadata and sitemap generation PUBLIC_SITE_URL=http://localhost:4321 diff --git a/apps/web/.env.dev3 b/apps/web/.env.dev3 index a2c509901..552c0d96c 100644 --- a/apps/web/.env.dev3 +++ b/apps/web/.env.dev3 @@ -1,4 +1,4 @@ # Development environment variables for the Doenet blog. # This file is intentionally committed — all PUBLIC_ vars are public. -PUBLIC_DOENET_MAIN_URL=https://dev3.doenet.org +PUBLIC_APP_URL=https://dev3.doenet.org PUBLIC_SITE_URL=https://dev3.doenet.org diff --git a/apps/web/.env.example b/apps/web/.env.example deleted file mode 100644 index 02590194f..000000000 --- a/apps/web/.env.example +++ /dev/null @@ -1,19 +0,0 @@ -# General Environment Variables Sample -# This file shows all available environment variables for the Doenet blog -# Default local build values live in .env. -# Environment-specific build values live in .env.dev3 and .env.prod. -# Use .env.local for machine-specific overrides. -# Use `npm run build -- --mode dev3` or `npm run build -- --mode prod` to -# select a non-default env file. - -# Main Doenet application URL -# Local development: http://localhost:8000 -# Dev3: https://dev3.doenet.org -# Production: https://beta.doenet.org -PUBLIC_DOENET_MAIN_URL=https://beta.doenet.org - -# Canonical site URL used at build time for metadata and sitemap generation -# Local development: http://localhost:4321 -# Dev3: https://dev3.doenet.org -# Production: https://beta.doenet.org -PUBLIC_SITE_URL=https://beta.doenet.org diff --git a/apps/web/.env.prod b/apps/web/.env.prod index a542725d0..9bc6c6a11 100644 --- a/apps/web/.env.prod +++ b/apps/web/.env.prod @@ -1,4 +1,4 @@ # Production environment variables for the Doenet blog. # This file is intentionally committed — all PUBLIC_ vars are public. -PUBLIC_DOENET_MAIN_URL=https://beta.doenet.org +PUBLIC_APP_URL=https://beta.doenet.org PUBLIC_SITE_URL=https://beta.doenet.org diff --git a/apps/web/README.md b/apps/web/README.md index f9c917420..cafd19d42 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -70,7 +70,7 @@ Build commands: Public env vars: -- `PUBLIC_DOENET_MAIN_URL`: URL for the main Doenet site. If not specified, the header shows a plain image. +- `PUBLIC_APP_URL`: URL for the main Doenet app. If not specified, the header shows a plain image. - `PUBLIC_SITE_URL`: canonical site URL used for build-time metadata and sitemap generation. ## 👀 Want to learn more? diff --git a/apps/web/src/components/Header.astro b/apps/web/src/components/Header.astro index cbdbd86a8..86b3703a9 100644 --- a/apps/web/src/components/Header.astro +++ b/apps/web/src/components/Header.astro @@ -1,6 +1,6 @@ --- import { getImage } from "astro:assets"; -import { BLOG_BASE_URL, DOENET_MAIN_URL } from "../consts"; +import { APP_URL, BLOG_BASE_URL } from "../consts"; import HeaderLink from "./HeaderLink.astro"; import doenetLogo from "../../public/Doenet_Logo_Frontpage_color_text.png"; @@ -11,8 +11,8 @@ const optimizedLogo = await getImage({ src: doenetLogo });