diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..ab1dc9f --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,27 @@ +{ + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2022, + "sourceType": "module", + "project": "./tsconfig.json" + }, + "plugins": ["@typescript-eslint"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-explicit-any": "warn" + }, + "env": { + "node": true, + "es2022": true + }, + "include": [ + "src", + "rollup.config.js" + ] +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..126f10c --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +tags +.DS_Store +node_modules/ +package-lock.json +dist/ +*.tsbuildinfo +.eslintcache diff --git a/EXTERNAL_DEPENDENCIES.md b/EXTERNAL_DEPENDENCIES.md new file mode 100644 index 0000000..b9961ac --- /dev/null +++ b/EXTERNAL_DEPENDENCIES.md @@ -0,0 +1,117 @@ +# External Dependencies Report + +This document lists all external resources referenced in the HTML files of this project. + +## Summary + +The project contains references to external resources. Most pages work with local assets, but some pages require external resources to function properly. + +## External Resources Found + +### CDN Libraries + +1. **Tailwind CSS** + - File: `MobileEmulation/layout.html` + - URL: `https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css` + - Type: CSS Framework + +2. **Google Fonts** + - Files: `index.html`, `AutonomousTestPages/index.html`, `DomSnapshot/google-font.html` + - URLs: + - `https://fonts.googleapis.com/css?family=Gloria+Hallelujah` + - `https://fonts.googleapis.com/css2?family=Tangerine&display=swap` + - Type: Web Fonts + +3. **Marked.js** (Markdown Parser) + - File: `changelog.html` + - URL: `https://cdn.jsdelivr.net/npm/marked/marked.min.js` + - Type: JavaScript Library + +4. **React & Babel** (for logs.html) + - File: `logs.html` + - URLs: + - `https://unpkg.com/react@18/umd/react.development.js` + - `https://unpkg.com/react-dom@18/umd/react-dom.development.js` + - `https://unpkg.com/@babel/standalone@7.10.3/babel.min.js` + - Type: JavaScript Libraries + +5. **Applitools Core Library** + - File: `logs.html` + - URL: `https://unpkg.com/@applitools/core@4.16.2/dist/troubleshoot/logs.js` + - Type: JavaScript Library + +6. **External CSS** + - File: `logs.html` + - URL: `https://noam-gaash.co.il/bio/main.css` + - Type: CSS Stylesheet + +### External Images + +1. **Icon Resources** + - Files: `AutonomousTestPages/AutoMaintenance/subfolder/page11.html`, `page12.html` + - URL: `https://cdn.iconscout.com/icon/free/png-512/free-export-3114432-2598210.png` + - Type: Image (PNG) + +2. **Applitools Demo Images** + - File: `DomSnapshot/ie.html` + - URL: `https://cdn.jsdelivr.net/gh/applitools/demo@gh-pages/DomSnapshot/smurfs.jpg` + - Type: Image (JPG) + +### External API References + +1. **Applitools Eyes API** + - File: `eyes-browser.html` + - URL: `https://eyes.applitools.com/` + - Type: API Service + +### Localhost References (Development) + +Some files reference localhost URLs for testing purposes: +- `http://localhost:7374` (CORS testing) +- `http://localhost:7373` (inline frames testing) +- `https://localhost:1010` (CORS testing) + +### GitHub Pages References + +- Files in `TestPages/ShadowDOM/` reference: + - `https://applitools.github.io/demo/css/reset.css` + +## Impact Analysis + +### Pages Without External Dependencies + +Most test pages work entirely with local assets: +- Most pages in `TestPages/` +- Most pages in `DomSnapshot/` +- Most pages in `AutonomousTestPages/` + +### Pages Requiring External Resources + +The following pages require internet connectivity to function properly: + +1. **index.html** - Uses Google Fonts (will work without it, but font will fall back) +2. **logs.html** - Requires React, Babel, and Applitools libraries +3. **changelog.html** - Requires Marked.js for markdown parsing +4. **MobileEmulation/layout.html** - Requires Tailwind CSS +5. **eyes-browser.html** - Requires Applitools API access +6. Pages with external images (subfolder/page11.html, page12.html) + +## Recommendations + +To make the project fully self-contained: + +1. **Download and vendor external libraries:** + - Download React, ReactDOM, Babel from unpkg + - Download Marked.js from CDN + - Download Tailwind CSS + - Download Google Fonts files + +2. **Replace external image URLs** with local copies + +3. **Update HTML files** to reference local versions + +4. **Mock or document API dependencies** like Applitools Eyes API + +## Server Notes + +The Express server serves all files as static content. External dependencies will still be fetched from their respective URLs when pages are loaded in a browser. If you need the pages to work completely offline, you'll need to download and vendor all external resources. diff --git a/README.md b/README.md new file mode 100644 index 0000000..193b95f --- /dev/null +++ b/README.md @@ -0,0 +1,260 @@ +# Demo Static Server + +A TypeScript-based Express static server for serving demo pages with full type safety. + +## Installation + +```bash +npm install +``` + +## Development + +### Build TypeScript + +```bash +npm run build # Compile TypeScript to JavaScript +npm run build:watch # Watch mode for development +``` + +### Bundle + +```bash +npm run bundle # Build and bundle with Rollup +``` + +### Type Checking & Linting + +```bash +npm run type-check # Type check without emitting files +npm run lint # Lint TypeScript files +``` + +### Clean Build Artifacts + +```bash +npm run clean # Remove dist/ directory +``` + +## Usage + +### As a Module + +Import and use the `DemoServer` class in your TypeScript/JavaScript code: + +```typescript +import DemoServer from './dist/server.js'; + +// Start the server on port 3000 +DemoServer.serveDemo(3000) + .then((info) => { + console.log(`Server running at ${info.url}`); + }) + .catch((error) => { + console.error('Failed to start server:', error.message); + }); +``` + +### From Command Line + +#### Start with default port (3000) + +```bash +npm start # Build and start +npm run start:dev # Start with tsx (no build needed) +``` + +#### Start compiled server directly + +```bash +node dist/server.js # Port 3000 +node dist/server.js 8080 # Custom port +``` + +### Testing + +Run the test script: + +```bash +npm test +``` + +This will start the server and display available URLs for testing. + +## API + +### `DemoServer.serveDemo(port?, projectRoot?)` + +Static method that creates and starts a new server instance. + +**Parameters:** +- `port` (number, optional) - Port number to serve on. Default: 3000 +- `projectRoot` (string, optional) - Custom project root directory to serve. If not provided, auto-detects based on the compiled file location. + +**Returns:** +- `Promise` - Resolves with server information: + ```typescript + interface ServerInfo { + port: number; + url: string; + message: string; + server: http.Server; + projectRoot: string; + } + ``` + +**Examples:** + +```typescript +import DemoServer from './dist/server.js'; + +// Example 1: Start server with defaults (auto-detect project root) +const serverInfo = await DemoServer.serveDemo(); +console.log(serverInfo.message); +// Output: Demo server is running at http://localhost:3000 + +// Example 2: Start server on custom port +const serverInfo2 = await DemoServer.serveDemo(8080); + +// Example 3: Start server with custom port and project root +const serverInfo3 = await DemoServer.serveDemo(8080, '/path/to/project'); +console.log(serverInfo3.projectRoot); +// Output: /path/to/project + +// Access the HTTP server instance if needed +const httpServer = serverInfo.server; +``` + +### CLI Usage with server-cli.js + +The server-cli script supports multiple ways to specify port and project root: + +```bash +# Default (port 3000, auto-detect root) +node dist/server-cli.js + +# Custom port +node dist/server-cli.js 8080 + +# Custom port and project root (positional) +node dist/server-cli.js 8080 /path/to/project + +# Named arguments +node dist/server-cli.js --port 8080 --root /path/to/project + +# Short flags +node dist/server-cli.js -p 8080 -r /path/to/project + +# Help +node dist/server-cli.js --help +``` + +## TypeScript Support + +This project is written in TypeScript and includes full type definitions. When importing the module, you'll get: + +- Full IntelliSense support +- Type checking for all API methods +- Exported interfaces and types +- Source maps for debugging + +The compiled JavaScript files are in the `dist/` directory, with corresponding `.d.ts` type definition files. + +### Exported Types + +```typescript +// Server information returned by serveDemo() +interface ServerInfo { + port: number; + url: string; + message: string; + server: http.Server; + projectRoot: string; +} + +// Options for configuring the server (reserved for future use) +interface ServerOptions { + port?: number; + projectRoot?: string; +} +``` + +### Usage in TypeScript + +```typescript +import DemoServer, { ServerInfo } from './dist/server.js'; + +const info: ServerInfo = await DemoServer.serveDemo(3000, './public'); +console.log(`Server started at ${info.url}`); +console.log(`Serving files from ${info.projectRoot}`); +``` + +## Project Structure + +``` +demo2/ +├── src/ # TypeScript source files +│ ├── server.ts # Main server implementation +│ ├── test-server.ts # Test script +│ └── server-cli.ts # Usage examples +├── dist/ # Compiled JavaScript (gitignored) +│ ├── server.js +│ ├── server.d.ts # Type definitions +│ ├── bundle.js # Rollup bundle +│ └── ... +├── AutonomousTestPages/ # Test pages +├── DomSnapshot/ # DOM snapshot test pages +├── TestPages/ # Various test pages +├── MobileEmulation/ # Mobile emulation pages +├── css/ # Stylesheets +├── images/ # Image assets +├── scripts/ # JavaScript files +├── tsconfig.json # TypeScript configuration +├── rollup.config.js # Rollup bundler configuration +├── .eslintrc.json # ESLint configuration +└── package.json # Project dependencies and scripts +``` + +The server serves all static files from the project root directory: + +- `/` - Main index.html +- `/AutonomousTestPages/` - Autonomous test pages +- `/DomSnapshot/` - DOM snapshot test pages +- `/TestPages/` - Various test pages +- `/MobileEmulation/` - Mobile emulation pages +- `/css/` - Stylesheets +- `/images/` - Image assets +- `/scripts/` - JavaScript files + +## Features + +- Serves all HTML, CSS, JavaScript, and image files +- Automatic HTML extension resolution +- Custom 404 error page +- Error handling middleware +- Support for nested directories +- Directory index files (index.html) + +## External Dependencies + +Some pages reference external resources (CDNs, fonts, etc.). See [EXTERNAL_DEPENDENCIES.md](EXTERNAL_DEPENDENCIES.md) for a complete list. + +These external resources will still be fetched from the internet when pages are loaded in a browser. The server only serves the static files from this project. + +## Build Output + +- **TypeScript compilation**: Generates `.js`, `.d.ts`, and `.js.map` files in `dist/` +- **Rollup bundling**: Creates a single `dist/bundle.js` file with Express as external dependency +- **Source maps**: Included for debugging compiled code + +## Notes + +- The project uses TypeScript with strict mode enabled +- ES6 modules are used (`type: "module"` in package.json) +- All paths are served relative to the project root +- The server will error if the specified port is already in use +- TypeScript definitions are automatically generated during build + +## License + +See [LICENSE](LICENSE) file for details. diff --git a/package.json b/package.json new file mode 100644 index 0000000..c469c32 --- /dev/null +++ b/package.json @@ -0,0 +1,40 @@ +{ + "name": "@applitools/sdk-demo-server", + "version": "1.0.3", + "description": "Static server for demo pages", + "main": "dist/server.js", + "types": "dist/server.d.ts", + "type": "module", + "scripts": { + "build": "tsc", + "build:watch": "tsc --watch", + "bundle": "npm run build && rollup -c", + "start": "npm run build && node dist/server.js", + "start:dev": "tsx src/server.ts", + "test": "npm run build && node dist/test-server.js", + "clean": "rm -rf dist", + "lint": "eslint src --ext .ts", + "type-check": "tsc --noEmit" + }, + "keywords": ["demo", "static", "server", "typescript"], + "author": "", + "license": "MIT", + "dependencies": { + "express": "^4.18.2" + }, + "devDependencies": { + "@rollup/plugin-commonjs": "^25.0.7", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-typescript": "^11.1.6", + "@types/express": "^4.17.21", + "@types/node": "^20.11.5", + "@typescript-eslint/eslint-plugin": "^6.19.0", + "@typescript-eslint/parser": "^6.19.0", + "eslint": "^8.56.0", + "rollup": "^4.9.6", + "tslib": "^2.6.2", + "tsx": "^4.7.0", + "typescript": "^5.3.3" + } +} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..19c0c16 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,26 @@ +import typescript from '@rollup/plugin-typescript'; +import resolve from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import json from '@rollup/plugin-json'; + +export default { + input: 'src/server.ts', + output: { + file: 'dist/bundle.js', + format: 'es', + sourcemap: true, + banner: '#!/usr/bin/env node' + }, + plugins: [ + json(), + resolve({ + preferBuiltins: true + }), + commonjs(), + typescript({ + tsconfig: './tsconfig.json', + outputToFilesystem: false + }) + ], + external: ['express'] +}; diff --git a/src/server-cli.ts b/src/server-cli.ts new file mode 100644 index 0000000..30c3d96 --- /dev/null +++ b/src/server-cli.ts @@ -0,0 +1,107 @@ +import DemoServer from './server.js'; + +/** + * Example: Using DemoServer.serveDemo() as a module with CLI arguments + * + * Usage: + * node dist/server-cli.js + * node dist/server-cli.js 8080 + * node dist/server-cli.js 8080 /path/to/project + * node dist/server-cli.js --port 8080 --root /path/to/project + */ + +// Parse command line arguments +function parseArgs(): { port: number; projectRoot?: string } { + const args = process.argv.slice(2); + let port = 3000; + let projectRoot: string | undefined; + let hasNamedArgs = false; + + // Check for help first + if (args.includes('--help') || args.includes('-h')) { + console.log(` +Usage: node dist/server-cli.js [options] [port] [projectRoot] + +Options: + --port, -p Port number to serve on (default: 3000) + --root, -r Project root directory to serve + --help, -h Show this help message + +Positional Arguments: + port Port number (default: 3000) + projectRoot Project root directory to serve + +Examples: + node dist/server-cli.js + node dist/server-cli.js 8080 + node dist/server-cli.js 8080 /path/to/project + node dist/server-cli.js --port 8080 --root /path/to/project + `); + process.exit(0); + } + + // Handle named arguments (--port, --root) + const usedIndices = new Set(); + for (let i = 0; i < args.length; i++) { + if (args[i] === '--port' || args[i] === '-p') { + port = parseInt(args[i + 1], 10) || 3000; + hasNamedArgs = true; + usedIndices.add(i); + usedIndices.add(i + 1); + i++; + } else if (args[i] === '--root' || args[i] === '-r') { + projectRoot = args[i + 1]; + hasNamedArgs = true; + usedIndices.add(i); + usedIndices.add(i + 1); + i++; + } + } + + // Handle positional arguments only if no named arguments were used + if (!hasNamedArgs) { + if (args.length > 0 && !args[0].startsWith('-')) { + const portArg = parseInt(args[0], 10); + if (!isNaN(portArg)) { + port = portArg; + } + } + + if (args.length > 1 && !args[1].startsWith('-')) { + projectRoot = args[1]; + } + } + + return { port, projectRoot }; +} + +const { port, projectRoot } = parseArgs(); + +console.log('Starting demo server...'); +if (projectRoot) { + console.log(` Port: ${port}`); + console.log(` Project Root: ${projectRoot}\n`); +} else { + console.log(` Port: ${port}`); + console.log(` Project Root: auto-detected\n`); +} + +DemoServer.serveDemo(port, projectRoot) + .then((info) => { + console.log('✓ Server started successfully!'); + console.log(` Port: ${info.port}`); + console.log(` URL: ${info.url}`); + console.log(` Project Root: ${info.projectRoot}\n`); + console.log('Available pages:'); + console.log(` - Main page: ${info.url}/`); + console.log(` - Autonomous Tests: ${info.url}/AutonomousTestPages/`); + console.log(` - DOM Snapshot: ${info.url}/DomSnapshot/`); + console.log(` - Test Pages: ${info.url}/TestPages/`); + console.log(` - Logs Viewer: ${info.url}/logs.html`); + console.log(` - Changelog: ${info.url}/changelog.html\n`); + console.log('Press Ctrl+C to stop the server.'); + }) + .catch((error: Error) => { + console.error('✗ Failed to start server:', error.message); + process.exit(1); + }); diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..937b555 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,236 @@ +import express, { Express, Request, Response, NextFunction } from 'express'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { Server as HttpServer } from 'http'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +/** + * Options for starting the server + */ +export interface ServerOptions { + port?: number; + projectRoot?: string; +} + +/** + * Interface for server information returned by serveDemo + */ +export interface ServerInfo { + port: number; + url: string; + message: string; + server: HttpServer; + projectRoot: string; +} + +/** + * Error with code property for error handling + */ +interface ErrorWithCode extends Error { + code?: string; +} + +/** + * Server class to serve demo static pages + */ +class DemoServer { + private app: Express | null = null; + private server: HttpServer | null = null; + + /** + * Serves the demo static pages on the specified port + * @param port - The port number to serve on (default: 3000) + * @param projectRoot - Optional custom project root directory + * @returns Promise resolving to server information + */ + static serveDemo(port = 3000, projectRoot?: string): Promise { + const instance = new DemoServer(); + return instance.start(port, projectRoot); + } + + /** + * Start the server + * @param port - The port number to serve on + * @param customProjectRoot - Optional custom project root directory + * @returns Promise resolving to server information + */ + start(port = 3000, customProjectRoot?: string): Promise { + return new Promise((resolve, reject) => { + try { + this.app = express(); + + // Get the project root directory + let projectRoot: string; + + if (customProjectRoot) { + // Use custom project root if provided (resolve to absolute path) + projectRoot = path.resolve(customProjectRoot); + } else { + // Auto-detect: When running from dist/, go up one level; otherwise use current directory + projectRoot = __dirname.endsWith('dist') + ? path.resolve(__dirname, '..') + : path.resolve(__dirname, '..', '..'); + } + + console.log(`Serving static files from: ${projectRoot}`); + + // Serve static files from the project root directory + this.app.use(express.static(projectRoot, { + extensions: ['html', 'htm'], + index: 'index.html' + })); + + // Handle 404 errors + this.app.use((_req: Request, res: Response) => { + res.status(404).send(` + + + + 404 - Page Not Found + + + +
+

404 - Page Not Found

+

The requested page could not be found.

+

Return to Home

+
+ + + `); + }); + + // Error handling middleware + this.app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => { + console.error('Server error:', err); + res.status(500).send(` + + + + 500 - Server Error + + + +
+

500 - Internal Server Error

+

An error occurred while processing your request.

+
+ + + `); + }); + + // Start the server + this.server = this.app.listen(port, () => { + if (!this.server) { + reject(new Error('Server failed to start')); + return; + } + + const info: ServerInfo = { + port, + url: `http://localhost:${port}`, + message: `Demo server is running at http://localhost:${port}`, + server: this.server, + projectRoot + }; + console.log(info.message); + resolve(info); + }); + + this.server.on('error', (error: ErrorWithCode) => { + if (error.code === 'EADDRINUSE') { + reject(new Error(`Port ${port} is already in use`)); + } else { + reject(error); + } + }); + + } catch (error) { + reject(error); + } + }); + } + + /** + * Stop the server + * @returns Promise that resolves when server is stopped + */ + stop(): Promise { + return new Promise((resolve, reject) => { + if (this.server) { + this.server.close((err) => { + if (err) { + reject(err); + } else { + console.log('Server stopped'); + resolve(); + } + }); + } else { + resolve(); + } + }); + } +} + +// Export the server class +export default DemoServer; + +// If this file is run directly, start the server +if (import.meta.url === `file://${process.argv[1]}`) { + const port = process.argv[2] ? parseInt(process.argv[2], 10) : 3000; + const projectRoot = process.argv[3] ? process.argv[3] : undefined; + + DemoServer.serveDemo(port, projectRoot) + .then((info) => { + console.log('Server started successfully!'); + console.log(`Visit ${info.url} to view the demo pages`); + }) + .catch((error: Error) => { + console.error('Failed to start server:', error.message); + process.exit(1); + }); +} diff --git a/src/test-server.ts b/src/test-server.ts new file mode 100644 index 0000000..72f45ec --- /dev/null +++ b/src/test-server.ts @@ -0,0 +1,35 @@ +import DemoServer from './server.js'; + +/** + * Simple test script for the DemoServer + */ +async function testServer(): Promise { + console.log('Testing DemoServer...\n'); + + try { + // Test 1: Start server on default port + console.log('Test 1: Starting server on port 3000...'); + const serverInfo = await DemoServer.serveDemo(3000); + console.log('✓ Server started successfully'); + console.log(` URL: ${serverInfo.url}`); + console.log(` Port: ${serverInfo.port}\n`); + + // Keep the server running for manual testing + console.log('Server is running. Press Ctrl+C to stop.\n'); + console.log('Try visiting:'); + console.log(` - ${serverInfo.url}`); + console.log(` - ${serverInfo.url}/AutonomousTestPages/`); + console.log(` - ${serverInfo.url}/DomSnapshot/`); + console.log(` - ${serverInfo.url}/TestPages/`); + console.log(` - ${serverInfo.url}/logs.html`); + console.log(` - ${serverInfo.url}/changelog.html\n`); + + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + console.error('✗ Test failed:', errorMessage); + process.exit(1); + } +} + +// Run tests +testServer(); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e5c0aba --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "lib": ["ES2022"], + "moduleResolution": "node", + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}