feat(dev): auto-switch ports on dev:all when defaults are busy (#9)

* Refactor project name from "Open Claude Design" to "Open Design"

- Updated project name in package.json, package-lock.json, and README files.
- Changed CLI commands and references from "ocd" to "od".
- Adjusted file structure references in documentation and code to reflect new naming conventions.
- Enhanced .gitignore to include new runtime data files.
- Updated metadata in LICENSE file to match new project name.

* Add contributing guidelines in English and Chinese

- Introduced CONTRIBUTING.md and CONTRIBUTING.zh-CN.md to provide clear instructions for contributors.
- Outlined contribution types, local setup instructions, and merging criteria for skills and design systems.
- Enhanced README files to reference the new contributing guidelines.

* Update README and documentation for deck framework directives

- Clarified DECK_FRAMEWORK_DIRECTIVE description in both English and Chinese README files to specify conditions for deck kind without a skill seed.
- Added detailed workflow instructions in deck-framework.ts to emphasize the importance of copying the framework before adding content.
- Enhanced discovery.ts to reinforce the framework-first approach for deck projects.
- Updated system.ts to ensure proper handling of deck projects with and without bound skills, preventing re-authorship of scaling and navigation logic.

* Update README and documentation for deck framework directives

- Clarified DECK_FRAMEWORK_DIRECTIVE description in both English and Chinese README files to specify conditions for deck kind without a skill seed.
- Added detailed workflow instructions in deck-framework.ts to emphasize the importance of copying the framework before adding content.
- Enhanced discovery.ts to reinforce the framework-first approach for deck projects.
- Updated system.ts to ensure proper handling of deck projects with and without bound skills, preventing re-authorship of scaling and navigation logic.

* Enhance README and add star promotion assets

- Added a "Star us" section in both English and Chinese README files to encourage users to star the project on GitHub.
- Included a new image asset for the star promotion.
- Introduced a new HTML file for a dedicated star promotion page.
- Updated .gitignore to exclude new cursor-related files.

* feat(dev): auto-switch ports on dev:all when defaults are busy

Adds a small launcher (scripts/dev-all.mjs) that probes free ports
for the daemon (OD_PORT, default 7456) and Vite (VITE_PORT, default
5173) before invoking concurrently, so a stray process holding
either port no longer breaks the boot. The resolved ports are
exported into the child env; vite.config.ts now reads VITE_PORT to
keep its dev server and /api proxy aligned with the daemon's actual
port.

Made-with: Cursor
This commit is contained in:
Tom Huang
2026-04-28 22:25:45 +08:00
committed by GitHub
parent d243b37d74
commit 94941f59a9
3 changed files with 87 additions and 2 deletions
+1 -1
View File
@@ -11,7 +11,7 @@
"scripts": {
"daemon": "node daemon/cli.js --no-open",
"dev": "vite",
"dev:all": "concurrently -k -n daemon,web -c cyan,magenta \"npm:daemon\" \"npm:dev\"",
"dev:all": "node scripts/dev-all.mjs",
"build": "tsc -b && vite build",
"preview": "vite preview",
"typecheck": "tsc -b --noEmit",
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env node
// Launcher for `npm run dev:all`.
//
// Probes for free ports for the daemon (OD_PORT, default 7456) and the Vite
// dev server (VITE_PORT, default 5173) before spawning `concurrently`, so a
// stray process holding either port doesn't kill the whole boot. The
// resolved ports are exported into the child env, which means:
// * the daemon's cli.js sees the new OD_PORT and binds to it
// * vite.config.ts reads the same OD_PORT and points its /api proxy at
// the daemon's actual port
// * Vite itself binds to VITE_PORT
//
// If a port is busy we walk forward up to PORT_SEARCH_RANGE steps and log
// the switch so the user notices.
import { spawn } from 'node:child_process';
import net from 'node:net';
const HOST = '127.0.0.1';
const PORT_SEARCH_RANGE = 50;
function isPortFree(port, host = HOST) {
return new Promise((resolve) => {
const server = net.createServer();
server.unref();
server.once('error', () => resolve(false));
server.listen({ port, host, exclusive: true }, () => {
server.close(() => resolve(true));
});
});
}
async function findFreePort(start, label) {
for (let port = start; port < start + PORT_SEARCH_RANGE; port++) {
if (await isPortFree(port)) return port;
}
throw new Error(
`[dev:all] could not find a free ${label} port near ${start} (tried ${PORT_SEARCH_RANGE})`,
);
}
const desiredDaemon = Number(process.env.OD_PORT) || 7456;
const desiredVite = Number(process.env.VITE_PORT) || 5173;
const daemonPort = await findFreePort(desiredDaemon, 'daemon');
const vitePort = await findFreePort(desiredVite, 'vite');
if (daemonPort !== desiredDaemon) {
console.log(
`[dev:all] daemon port ${desiredDaemon} is busy, switching to ${daemonPort}`,
);
}
if (vitePort !== desiredVite) {
console.log(
`[dev:all] vite port ${desiredVite} is busy, switching to ${vitePort}`,
);
}
const env = {
...process.env,
OD_PORT: String(daemonPort),
VITE_PORT: String(vitePort),
};
// We spawn the local `concurrently` bin via shell so Windows .cmd shims
// resolve correctly. The `npm:daemon` / `npm:dev` shorthand runs the
// matching package.json scripts, so any future tweak to those scripts is
// picked up automatically.
const child = spawn(
'concurrently',
['-k', '-n', 'daemon,web', '-c', 'cyan,magenta', 'npm:daemon', 'npm:dev'],
{ env, stdio: 'inherit', shell: true },
);
child.on('exit', (code, signal) => {
if (signal) process.kill(process.pid, signal);
else process.exit(code ?? 0);
});
for (const sig of ['SIGINT', 'SIGTERM']) {
process.on(sig, () => {
if (!child.killed) child.kill(sig);
});
}
+2 -1
View File
@@ -2,11 +2,12 @@ import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
const DAEMON_PORT = Number(process.env.OD_PORT) || 7456;
const VITE_PORT = Number(process.env.VITE_PORT) || 5173;
export default defineConfig({
plugins: [react()],
server: {
port: 5173,
port: VITE_PORT,
proxy: {
'/api': {
target: `http://127.0.0.1:${DAEMON_PORT}`,