diff --git a/README.md b/README.md index 86626a6..e6dbd80 100644 --- a/README.md +++ b/README.md @@ -1,99 +1,211 @@ - - - -[![Version][version-shield]][version-url] -[![MIT License][license-shield]][license-url] -[![LinkedIn][linkedin-shield]][linkedin-url] - - -
-
- - Logo - - -

ExpressoTS Framework

- -

- Everything you need to know to build applications with ExpressoTS -
- Explore the docs ยป -
-
- Let's discuss - ยท - Report Bug - ยท - Request Feature -

-
- - -
- Table of Contents -
    -
  1. About The Project
  2. -
  3. Getting Started
  4. -
  5. Contributing
  6. -
  7. Support the project
  8. -
  9. License
  10. -
-
- - -# About The Project - -ExpressoTS is a [Typescript](https://www.typescriptlang.org/) + [Node.js](https://nodejs.org/en/) lightweight framework for quick building scalable, easy to read and maintain, server-side applications ๐ŸŽ - -## Getting Started - -- Here is our [Site](https://expresso-ts.com/) -- You can find our [Documentation here](https://doc.expresso-ts.com/) -- Checkout our [First Steps documentation](https://doc.expresso-ts.com/docs/overview/first-steps) -- Our [CLI Documentation](https://doc.expresso-ts.com/docs/cli/overview) +# ExpressoTS Templates Repository + +Community-maintained templates and pricing data for the [ExpressoTS CLI](https://github.com/expressots/expressots-cli). + +## What's This? -## Contributing +This repository contains all the templates used by ExpressoTS CLI to generate: +- **CI/CD Pipelines** (GitHub Actions, GitLab CI, CircleCI, Jenkins, etc.) +- **Docker Configurations** (Dockerfiles, docker-compose) +- **Kubernetes Manifests** (deployments, services, ingress) +- **Migration Guides** (Heroku โ†’ Railway/Render/Fly, Compose โ†’ K8s) +- **Cloud Provider Pricing** (for cost estimation) + +## Why a Separate Repository? + +**Fast Updates:** Templates can be updated without releasing a new CLI version. Users get updates within 24 hours. + +**Community Contributions:** Anyone can contribute templates or update pricing - no CLI expertise needed. + +**Offline Support:** CLI has embedded fallbacks, so it works without internet. + +**Version Control:** Templates are versioned independently from the CLI. + +## Repository Structure + +``` +cli-templates/ +โ”œโ”€โ”€ manifest.json # Template registry with versions +โ”œโ”€โ”€ pricing.json # Cloud provider pricing data +โ”œโ”€โ”€ README.md # This file +โ”œโ”€โ”€ cicd/ # CI/CD pipeline templates +โ”‚ โ”œโ”€โ”€ github/ +โ”‚ โ”‚ โ”œโ”€โ”€ basic.yml +โ”‚ โ”‚ โ”œโ”€โ”€ comprehensive.yml +โ”‚ โ”‚ โ””โ”€โ”€ security-focused.yml +โ”‚ โ”œโ”€โ”€ gitlab/ +โ”‚ โ”œโ”€โ”€ circleci/ +โ”‚ โ”œโ”€โ”€ jenkins/ +โ”‚ โ”œโ”€โ”€ bitbucket/ +โ”‚ โ””โ”€โ”€ azure/ +โ”œโ”€โ”€ docker/ # Docker templates +โ”‚ โ”œโ”€โ”€ Dockerfile.production.tpl +โ”‚ โ”œโ”€โ”€ Dockerfile.development.tpl +โ”‚ โ”œโ”€โ”€ docker-compose.yml.tpl +โ”‚ โ””โ”€โ”€ docker-compose.development.yml.tpl +โ”œโ”€โ”€ kubernetes/ # Kubernetes manifest templates +โ”‚ โ”œโ”€โ”€ deployment.yml.tpl +โ”‚ โ”œโ”€โ”€ service.yml.tpl +โ”‚ โ”œโ”€โ”€ configmap.yml.tpl +โ”‚ โ””โ”€โ”€ ingress.yml.tpl +โ””โ”€โ”€ migrations/ # Migration templates + โ”œโ”€โ”€ heroku-to-railway/ + โ”œโ”€โ”€ heroku-to-render/ + โ”œโ”€โ”€ heroku-to-fly/ + โ””โ”€โ”€ compose-to-kubernetes/ +``` + +## How to Use + +### For Users -Welcome to the ExpressoTS community, a place bustling with innovative minds just like yours. We're absolutely thrilled to have you here! -ExpressoTS is more than just a TypeScript framework; it's a collective effort by developers who are passionate about creating a more efficient, secure, and robust web ecosystem. We firmly believe that the best ideas come from a diversity of perspectives, backgrounds, and skills. +Users don't need to interact with this repository directly. The ExpressoTS CLI automatically fetches templates: -Why Contribute to Documentation? +```bash +# List available templates +expressots templates list -- **Share Knowledge**: If you've figured out something cool, why keep it to yourself? -- **Build Your Portfolio**: Contributing to an open-source project like ExpressoTS is a great way to showcase your skills. -- **Join a Network**: Get to know a community of like-minded developers. -- **Improve the Product**: Help us fill in the gaps, correct errors, or make complex topics easier to understand. +# Update to latest versions +expressots templates update -Ready to contribute? +# Generate CI/CD pipeline +expressots cicd generate github --strategy comprehensive +``` -- [Contributing Guidelines](https://github.com/expressots/expressots/blob/main/CONTRIBUTING.md) -- [How to Contribute](https://github.com/expressots/expressots/blob/main/CONTRIBUTING_HOWTO.md) -- [Coding Guidelines](https://github.com/rsaz/TypescriptCodingGuidelines) +### For Contributors -## Support the project +Want to improve templates or update pricing? See [CONTRIBUTING.md](./CONTRIBUTING.md) for detailed instructions. -ExpressoTS is an independent open source project with ongoing development made possible thanks to your support. If you'd like to help, please consider: +**Quick start:** -- Become a **[sponsor on GitHub](https://github.com/sponsors/expressots)** -- Follow the **[organization](https://github.com/expressots)** on GitHub and Star โญ the project -- Subscribe to the Twitch channel: **[Richard Zampieri](https://www.twitch.tv/richardzampieri)** -- Join our **[Discord](https://discord.com/invite/PyPJfGK)** -- Contribute submitting **[issues and pull requests](https://github.com/expressots/expressots/issues)** -- Share the project with your friends and colleagues +1. Fork this repository +2. Edit a template file (e.g., `cicd/github/basic.yml`) +3. Update `manifest.json` with new version +4. Test locally: + ```bash + expressots templates repo set YOUR_USERNAME/templates + expressots templates update + expressots cicd generate github + ``` +5. Submit a pull request + +### For Organizations + +Use custom templates for your organization: + +```bash +# Fork this repo, customize, then: +expressots templates repo set mycompany/expressots-templates + +# Team members automatically get company templates +expressots cicd generate github +``` + +## Template Syntax + +Templates use Mustache-like syntax: + +```yaml +name: {{projectName}} + +{{#includeSecurity}} +security: + scan: enabled +{{/includeSecurity}} + +{{^isProduction}} +dev-tools: + - nodemon +{{/isProduction}} +``` + +**Supported:** +- Variables: `{{name}}` +- Conditionals: `{{#cond}}...{{/cond}}` +- Negative conditionals: `{{^cond}}...{{/cond}}` +- Loops: `{{#each items}}...{{/each}}` + +## Contributing + +We welcome contributions! See [CONTRIBUTING.md](./CONTRIBUTING.md) for: +- How to add new templates +- How to update pricing data +- Testing guidelines +- Review process +- Best practices + +**Common contributions:** +- Add new CI/CD strategies +- Update cloud provider pricing +- Improve security scanning +- Add new platform support +- Fix bugs in templates + +## Pricing Data + +Cloud provider pricing is stored in `cli-templates/pricing.json` and used by the CLI's cost estimation features. + +**Updating pricing:** + +1. Visit official pricing pages (links in `pricing.json`) +2. Update prices for a provider +3. Update `lastVerified` date +4. Submit PR with source URLs + +**Example:** +```json +{ + "providers": { + "aws": { + "cpuPerHour": 0.04048, + "source": "https://aws.amazon.com/fargate/pricing/", + "lastVerified": "2026-01-15" + } + } +} +``` + +## Versioning + +### Template Versions + +Each template has its own version: +- **Patch (1.0.1):** Bug fixes, typos +- **Minor (1.1.0):** New features, backward compatible +- **Major (2.0.0):** Breaking changes + +### Manifest Version + +The manifest version tracks overall structure changes: +```json +{ + "version": "1.0.0", + "updated": "2026-01-07T00:00:00Z" +} +``` + +## Release Process + +1. PRs merged to `main` branch +2. CI validates manifest and templates +3. Changes immediately available to CLI users +4. Users' local cache updates within 24 hours +5. Or manually: `expressots templates update` + +## Support + +- **Documentation:** https://expresso-ts.com/docs/cli/templates +- **Issues:** https://github.com/expressots/expressots-cli/issues +- **Discord:** https://discord.gg/expressots ## License -Distributed under the MIT License. See [`LICENSE.txt`](https://github.com/expressots/expressots/blob/main/LICENSE) for more information. +MIT License - see [LICENSE](./LICENSE) for details. + +## Acknowledgments -

(back to top)

+This repository is maintained by the ExpressoTS community. Special thanks to all contributors who help keep templates and pricing data up-to-date! - - +--- -[version-shield]: https://img.shields.io/github/v/tag/expressots/templates?style=for-the-badge&logo=github -[version-url]: https://img.shields.io/github/v/tag/expressots/templates?style=for-the-badge&logo=github -[license-shield]: https://img.shields.io/github/license/expressots/expressots-project-template?style=for-the-badge -[license-url]: https://github.com/expressots/expressots-project-template/blob/main/LICENSE -[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555 -[linkedin-url]: https://www.linkedin.com/company/expresso-ts/ -[product-screenshot]: images/screenshot.png +**Built with โค๏ธ by the ExpressoTS community** diff --git a/application/.gitignore b/application/.gitignore new file mode 100644 index 0000000..d6b5299 --- /dev/null +++ b/application/.gitignore @@ -0,0 +1,30 @@ +# Dependencies +node_modules/ + +# Build output +dist/ +*.tsbuildinfo + +# Environment files (except examples) +.env +.env.local +.env.staging +.env.prod +!.env.example + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Testing +coverage/ + +# Logs +*.log +npm-debug.log* \ No newline at end of file diff --git a/application/README.md b/application/README.md new file mode 100644 index 0000000..7d7625e --- /dev/null +++ b/application/README.md @@ -0,0 +1,116 @@ +# ExpressoTS Application + +A modern, type-safe Node.js backend powered by ExpressoTS v4.0. + +## Features + +- ๐Ÿ”ง **Type-Safe Configuration** - Full TypeScript inference for config values +- ๐Ÿš€ **Zero-Config Bootstrap** - Just run and go +- ๐Ÿ“ฆ **Build-Time Path Resolution** - No runtime overhead +- ๐Ÿงช **Testing Ready** - Jest configured out of the box + +## Quick Start + +```bash +# Install dependencies +npm install + +# Start development server +npm run dev + +# Build for production +npm run build + +# Run in production +npm run prod +``` + +## Project Structure + +``` +src/ +โ”œโ”€โ”€ main.ts # Application entry point +โ”œโ”€โ”€ app.ts # Application class (middleware, lifecycle) +โ”œโ”€โ”€ config.ts # Type-safe configuration +โ””โ”€โ”€ app.controller.ts # Example controller +``` + +## Configuration + +Configuration is managed through `src/config.ts` using the `defineConfig` helper: + +```typescript +export const appConfig = defineConfig({ + app: { + name: Env.string("APP_NAME").default("My App"), + version: Env.string("APP_VERSION").default("1.0.0"), + }, + server: { + port: Env.number("PORT").default(3000), + }, +}); +``` + +### Environment Files + +- `.env.local` - Development environment (default) +- `.env.staging` - Staging environment +- `.env.prod` - Production environment + +## Scaffolding + +Use the CLI to generate new resources: + +```bash +# Generate a controller +npx expressots generate controller user + +# Generate a use case +npx expressots generate usecase user/create-user + +# Generate a module with controller and use case +npx expressots generate module user +``` + +### Scaffold Configuration + +Customize scaffolding in `expressots.config.ts`: + +```typescript +const config: ExpressoConfig = { + opinionated: true, // Enable structured folders + scaffoldPattern: Pattern.KEBAB_CASE, + scaffoldSchematics: { + controller: "controllers", + usecase: "useCases", + entity: "entities", + }, +}; +``` + +## Testing + +```bash +# Run tests +npm test + +# Run tests with coverage +npm run test:cov + +# Watch mode +npm run test:watch +``` + +## API Endpoints + +| Method | Path | Description | +|--------|------|-------------| +| GET | /api/ | Application info | +| GET | /api/health | Health check | + +## Learn More + +- [ExpressoTS Documentation](https://expresso-ts.com) +- [GitHub Repository](https://github.com/expressots) +- [Discord Community](https://discord.gg/PyPJfGK) + diff --git a/application/eslint.config.mjs b/application/eslint.config.mjs new file mode 100644 index 0000000..33c6935 --- /dev/null +++ b/application/eslint.config.mjs @@ -0,0 +1,43 @@ +import js from "@eslint/js"; +import tseslint from "typescript-eslint"; +import { fileURLToPath } from "node:url"; + +const __dirname = fileURLToPath(new URL(".", import.meta.url)); + +export default tseslint.config( + js.configs.recommended, + ...tseslint.configs.recommended, + { + files: ["src/**/*.ts", "test/**/*.ts"], + languageOptions: { + parserOptions: { + project: true, + tsconfigRootDir: __dirname, + }, + }, + rules: { + // TypeScript-specific rules + "@typescript-eslint/no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + }, + ], + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-non-null-assertion": "warn", + }, + }, + { + ignores: [ + "node_modules/**", + "dist/**", + "coverage/**", + "*.config.ts", + "*.config.js", + "*.config.mjs", + ], + } +); diff --git a/opinionated/expressots.config.ts b/application/expressots.config.ts similarity index 100% rename from opinionated/expressots.config.ts rename to application/expressots.config.ts diff --git a/application/jest.config.ts b/application/jest.config.ts new file mode 100644 index 0000000..251492b --- /dev/null +++ b/application/jest.config.ts @@ -0,0 +1,17 @@ +import type { Config } from "jest"; + +const config: Config = { + preset: "ts-jest", + testEnvironment: "node", + rootDir: ".", + testMatch: ["/test/**/*.spec.ts"], + moduleNameMapper: { + "^@app/(.*)$": "/src/$1", + }, + modulePathIgnorePatterns: ["/dist"], + collectCoverageFrom: ["src/**/*.ts", "!src/**/*.d.ts", "!src/main.ts"], + coverageDirectory: "coverage", + coverageReporters: ["text", "lcov"], +}; + +export default config; diff --git a/application/package.json b/application/package.json new file mode 100644 index 0000000..1df5f32 --- /dev/null +++ b/application/package.json @@ -0,0 +1,43 @@ +{ + "name": "expressots-app", + "version": "1.0.0", + "description": "ExpressoTS Application", + "author": "", + "license": "MIT", + "main": "dist/src/main.js", + "scripts": { + "dev": "expressots dev", + "build": "expressots build", + "prod": "expressots prod", + "test": "jest --runInBand", + "test:watch": "jest --watchAll", + "coverage": "jest --coverage", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\" --fix" + }, + "dependencies": { + "@expressots/adapter-express": "^4.0.0", + "@expressots/core": "^4.0.0", + "@expressots/shared": "^4.0.0", + "express": "5.2.1", + "reflect-metadata": "^0.2.2" + }, + "devDependencies": { + "@eslint/js": "9.39.2", + "@expressots/cli": "^4.0.0", + "@types/express": "5.0.6", + "@types/jest": "30.0.0", + "@types/node": "25.0.3", + "@typescript-eslint/eslint-plugin": "8.51.0", + "@typescript-eslint/parser": "8.51.0", + "eslint": "9.39.2", + "jest": "30.0.0", + "nodemon": "3.1.11", + "prettier": "3.7.4", + "ts-jest": "29.4.6", + "tsconfig-paths": "4.2.0", + "tsx": "4.21.0", + "typescript": "5.9.3", + "typescript-eslint": "8.51.0" + } +} diff --git a/non_opinionated/src/app.controller.ts b/application/src/app.controller.ts similarity index 100% rename from non_opinionated/src/app.controller.ts rename to application/src/app.controller.ts diff --git a/non_opinionated/src/app.ts b/application/src/app.ts similarity index 61% rename from non_opinionated/src/app.ts rename to application/src/app.ts index cff71a4..cb2d114 100644 --- a/non_opinionated/src/app.ts +++ b/application/src/app.ts @@ -3,18 +3,21 @@ import { AppContainer, CreateModule } from "@expressots/core"; import { AppController } from "./app.controller"; export class App extends AppExpress { - private config: AppContainer = this.configContainer([ + private container: AppContainer = this.configContainer([ CreateModule([AppController]), ]); - async globalConfiguration(): Promise {} + globalConfiguration(): void {} async configureServices(): Promise { - this.Middleware.addBodyParser(); - this.Middleware.setErrorHandler({ showStackTrace: true }); + // __MIDDLEWARE_PRESET_PLACEHOLDER__ + + this.Middleware.setErrorHandler({ + showStackTrace: await this.isDevelopment(), + }); } - + async postServerInitialization(): Promise {} async serverShutdown(): Promise {} -} \ No newline at end of file +} diff --git a/application/src/main.ts b/application/src/main.ts new file mode 100644 index 0000000..8236042 --- /dev/null +++ b/application/src/main.ts @@ -0,0 +1,4 @@ +import { bootstrap } from "@expressots/core"; +import { App } from "./app"; + +bootstrap(App); diff --git a/application/test/app.controller.spec.ts b/application/test/app.controller.spec.ts new file mode 100644 index 0000000..dcd7894 --- /dev/null +++ b/application/test/app.controller.spec.ts @@ -0,0 +1,37 @@ +import { + createTestApp, + setupExpressoTSMatchers, + TestAppResult, +} from "@expressots/core"; +import { afterAll, beforeAll, describe, it } from "@jest/globals"; +import { App } from "../src/app"; + +// Setup ExpressoTS custom matchers +setupExpressoTSMatchers(); + +describe("AppController", () => { + let testApp: TestAppResult; + + beforeAll(async () => { + testApp = await createTestApp(App, { + env: { + NODE_ENV: "test", + }, + autoCleanup: false, + }); + }); + + afterAll(async () => { + await testApp.cleanup(); + }); + + describe("GET /", () => { + it("should return welcome message", async () => { + await testApp.request + .get("/") + .expectStatus(200) + .expectBody("Hello from ExpressoTS!") + .execute(); + }); + }); +}); diff --git a/application/tsconfig.build.json b/application/tsconfig.build.json new file mode 100644 index 0000000..4864d83 --- /dev/null +++ b/application/tsconfig.build.json @@ -0,0 +1,22 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./", + "declaration": false, + "declarationMap": false, + "sourceMap": false, + "removeComments": true, + "incremental": true, + "tsBuildInfoFile": "./dist/.tsbuildinfo", + "isolatedModules": true, + "types": ["node"], + "baseUrl": ".", + "paths": { + "@useCases/*": ["./src/useCases/*"], + "@controllers/*": ["./src/controllers/*"] + } + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist", "test"] +} diff --git a/application/tsconfig.json b/application/tsconfig.json new file mode 100644 index 0000000..a921762 --- /dev/null +++ b/application/tsconfig.json @@ -0,0 +1,50 @@ +{ + "compilerOptions": { + // Language and Environment + "target": "ES2021", + "lib": ["ES2021"], + + // Modules + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "resolveJsonModule": true, + + // Emit + "outDir": "./dist", + "rootDir": "./", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "removeComments": true, + + // ExpressoTS Required: Decorators and Metadata + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + + // Path Mapping + "baseUrl": "./src", + "paths": { + "@useCases/*": ["./useCases/*"], + "@controllers/*": ["./controllers/*"] + }, + + // Type Checking + "strict": true, + "noImplicitAny": false, + "strictPropertyInitialization": false, + "skipLibCheck": true, + + // Performance + "incremental": true, + "tsBuildInfoFile": "./dist/.tsbuildinfo", + + // Interop Constraints + "forceConsistentCasingInFileNames": true, + + // Type Definitions + "types": ["node", "jest"] + }, + "include": ["src/**/*.ts", "test/**/*.ts", "expressots.config.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/cli-templates/README.md b/cli-templates/README.md new file mode 100644 index 0000000..d360cc9 --- /dev/null +++ b/cli-templates/README.md @@ -0,0 +1,70 @@ +# ExpressoTS CLI Templates + +This directory contains templates used by the ExpressoTS CLI for generating CI/CD pipelines, Docker configurations, Kubernetes manifests, and migration guides. + +## Structure + +``` +cli-templates/ +โ”œโ”€โ”€ manifest.json # Template registry with versions +โ”œโ”€โ”€ cicd/ # CI/CD pipeline templates +โ”‚ โ”œโ”€โ”€ github/ +โ”‚ โ”‚ โ”œโ”€โ”€ basic.yml +โ”‚ โ”‚ โ”œโ”€โ”€ comprehensive.yml +โ”‚ โ”‚ โ””โ”€โ”€ security-focused.yml +โ”‚ โ”œโ”€โ”€ gitlab/ +โ”‚ โ”‚ โ”œโ”€โ”€ basic.yml +โ”‚ โ”‚ โ”œโ”€โ”€ comprehensive.yml +โ”‚ โ”‚ โ””โ”€โ”€ security-focused.yml +โ”‚ โ”œโ”€โ”€ circleci/ +โ”‚ โ”œโ”€โ”€ jenkins/ +โ”‚ โ”œโ”€โ”€ bitbucket/ +โ”‚ โ””โ”€โ”€ azure/ +โ”œโ”€โ”€ docker/ # Docker templates +โ”‚ โ”œโ”€โ”€ Dockerfile.production.tpl +โ”‚ โ”œโ”€โ”€ Dockerfile.development.tpl +โ”‚ โ”œโ”€โ”€ docker-compose.yml.tpl +โ”‚ โ””โ”€โ”€ docker-compose.development.yml.tpl +โ”œโ”€โ”€ kubernetes/ # Kubernetes manifest templates +โ”‚ โ”œโ”€โ”€ deployment.yml.tpl +โ”‚ โ”œโ”€โ”€ service.yml.tpl +โ”‚ โ”œโ”€โ”€ configmap.yml.tpl +โ”‚ โ””โ”€โ”€ ingress.yml.tpl +โ””โ”€โ”€ migrations/ # Migration templates + โ”œโ”€โ”€ heroku-to-railway/ + โ”œโ”€โ”€ heroku-to-render/ + โ”œโ”€โ”€ heroku-to-fly/ + โ””โ”€โ”€ compose-to-kubernetes/ +``` + +## Template Syntax + +Templates use Mustache-like syntax: + +- `{{variable}}` - Variable substitution +- `{{#condition}}...{{/condition}}` - Conditional blocks +- `{{^condition}}...{{/condition}}` - Negative conditionals + +## Usage + +```bash +# List available templates +expressots templates list + +# Update cache +expressots templates update + +# Show status +expressots templates status +``` + +## Contributing + +1. Fork this repository +2. Add or modify templates +3. Update manifest.json +4. Submit a pull request + +## License + +MIT License diff --git a/cli-templates/cicd/azure/basic.yml b/cli-templates/cicd/azure/basic.yml new file mode 100644 index 0000000..df2acd8 --- /dev/null +++ b/cli-templates/cicd/azure/basic.yml @@ -0,0 +1,43 @@ +# Azure DevOps Pipeline +# Generated by ExpressoTS CLI +# Strategy: basic +# Template Version: 1.0.0 + +trigger: + - {{branch}} + +pool: + vmImage: 'ubuntu-latest' + +variables: + nodeVersion: '{{nodeVersion}}' + +stages: + - stage: Build + displayName: 'Build Stage' + jobs: + - job: BuildJob + displayName: 'Build, Lint, and Test' + steps: + - task: NodeTool@0 + inputs: + versionSpec: '$(nodeVersion)' + displayName: 'Install Node.js' + + - script: {{installCmd}} + displayName: 'Install dependencies' + + - script: {{lintCmd}} + displayName: 'Run linter' + + - script: {{testCmd}} + displayName: 'Run tests' + + - script: {{buildCmd}} + displayName: 'Build application' + + - task: PublishBuildArtifacts@1 + inputs: + pathToPublish: 'dist' + artifactName: 'dist' + displayName: 'Publish build artifacts' diff --git a/cli-templates/cicd/bitbucket/basic.yml b/cli-templates/cicd/bitbucket/basic.yml new file mode 100644 index 0000000..290f932 --- /dev/null +++ b/cli-templates/cicd/bitbucket/basic.yml @@ -0,0 +1,72 @@ +# Bitbucket Pipelines +# Generated by ExpressoTS CLI +# Strategy: basic +# Template Version: 1.0.0 + +image: node:{{nodeVersion}}-alpine + +definitions: + caches: + npm: $HOME/.npm + +pipelines: + default: + - step: + name: Lint + caches: + - npm + - node + script: + - {{installCmd}} + - {{lintCmd}} + + - step: + name: Test + caches: + - npm + - node + script: + - {{installCmd}} + - {{testCmd}} + + - step: + name: Build + caches: + - npm + - node + script: + - {{installCmd}} + - {{buildCmd}} + artifacts: + - dist/** + + branches: + {{branch}}: + - step: + name: Lint + caches: + - npm + - node + script: + - {{installCmd}} + - {{lintCmd}} + + - step: + name: Test + caches: + - npm + - node + script: + - {{installCmd}} + - {{testCmd}} + + - step: + name: Build + caches: + - npm + - node + script: + - {{installCmd}} + - {{buildCmd}} + artifacts: + - dist/** diff --git a/cli-templates/cicd/circleci/basic.yml b/cli-templates/cicd/circleci/basic.yml new file mode 100644 index 0000000..bcaa6e2 --- /dev/null +++ b/cli-templates/cicd/circleci/basic.yml @@ -0,0 +1,59 @@ +# CircleCI Configuration +# Generated by ExpressoTS CLI +# Strategy: basic +# Template Version: 1.0.0 + +version: 2.1 + +orbs: + node: circleci/node@5.1.0 + +jobs: + lint: + executor: + name: node/default + tag: '{{nodeVersion}}' + steps: + - checkout + - node/install-packages + - run: + name: Run linter + command: {{lintCmd}} + + test: + executor: + name: node/default + tag: '{{nodeVersion}}' + steps: + - checkout + - node/install-packages + - run: + name: Run tests + command: {{testCmd}} + + build: + executor: + name: node/default + tag: '{{nodeVersion}}' + steps: + - checkout + - node/install-packages + - run: + name: Build application + command: {{buildCmd}} + - persist_to_workspace: + root: . + paths: + - dist + +workflows: + version: 2 + build-and-test: + jobs: + - lint + - test: + requires: + - lint + - build: + requires: + - test diff --git a/cli-templates/cicd/circleci/comprehensive.yml b/cli-templates/cicd/circleci/comprehensive.yml new file mode 100644 index 0000000..db07cd0 --- /dev/null +++ b/cli-templates/cicd/circleci/comprehensive.yml @@ -0,0 +1,99 @@ +# CircleCI Configuration +# Generated by ExpressoTS CLI +# Strategy: comprehensive +# Template Version: 1.0.0 + +version: 2.1 + +orbs: + node: circleci/node@5.1.0 + docker: circleci/docker@2.4.0 + +jobs: + lint: + executor: + name: node/default + tag: '{{nodeVersion}}' + steps: + - checkout + - node/install-packages + - run: + name: Run linter + command: {{lintCmd}} + + test: + executor: + name: node/default + tag: '{{nodeVersion}}' + steps: + - checkout + - node/install-packages + - run: + name: Run tests + command: {{testCmd}} +{{#includeCoverage}} + - run: + name: Run tests with coverage + command: {{testCmd}} -- --coverage + - store_artifacts: + path: coverage +{{/includeCoverage}} + + security: + docker: + - image: aquasec/trivy:latest + steps: + - checkout + - run: + name: Run security scan + command: trivy fs --severity HIGH,CRITICAL . + + build: + executor: + name: node/default + tag: '{{nodeVersion}}' + steps: + - checkout + - node/install-packages + - run: + name: Build application + command: {{buildCmd}} + - persist_to_workspace: + root: . + paths: + - dist + + docker-build: + executor: docker/docker + steps: + - checkout + - setup_remote_docker + - docker/check + - docker/build: + image: {{dockerRegistry}}/$CIRCLE_PROJECT_REPONAME + tag: $CIRCLE_SHA1 + - docker/push: + image: {{dockerRegistry}}/$CIRCLE_PROJECT_REPONAME + tag: $CIRCLE_SHA1 + +workflows: + version: 2 + build-test-deploy: + jobs: + - lint + - test: + requires: + - lint + - security: + requires: + - lint + - build: + requires: + - test + - security + - docker-build: + requires: + - build + filters: + branches: + only: {{branch}} diff --git a/cli-templates/cicd/github/basic.yml b/cli-templates/cicd/github/basic.yml new file mode 100644 index 0000000..c83ded0 --- /dev/null +++ b/cli-templates/cicd/github/basic.yml @@ -0,0 +1,79 @@ +# GitHub Actions CI/CD Pipeline +# Generated by ExpressoTS CLI +# Strategy: {{strategy}} +# Template Version: 1.0.0 + +name: CI/CD Pipeline + +on: + push: + branches: [{{branch}}] + pull_request: + branches: [{{branch}}] + +env: + NODE_VERSION: '{{nodeVersion}}' + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: '{{packageManager}}' + + - name: Install dependencies + run: {{installCmd}} + + - name: Run linter + run: {{lintCmd}} + + test: + name: Test + runs-on: ubuntu-latest + needs: lint + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: '{{packageManager}}' + + - name: Install dependencies + run: {{installCmd}} + + - name: Run tests + run: {{testCmd}} + + build: + name: Build + runs-on: ubuntu-latest + needs: test + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: '{{packageManager}}' + + - name: Install dependencies + run: {{installCmd}} + + - name: Build application + run: {{buildCmd}} + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + retention-days: 7 diff --git a/cli-templates/cicd/github/comprehensive.yml b/cli-templates/cicd/github/comprehensive.yml new file mode 100644 index 0000000..80bef76 --- /dev/null +++ b/cli-templates/cicd/github/comprehensive.yml @@ -0,0 +1,183 @@ +# GitHub Actions CI/CD Pipeline +# Generated by ExpressoTS CLI +# Strategy: comprehensive +# Template Version: 1.0.0 + +name: CI/CD Pipeline + +on: + push: + branches: [{{branch}}] + pull_request: + branches: [{{branch}}] + +env: + NODE_VERSION: '{{nodeVersion}}' + REGISTRY: {{dockerRegistry}} + IMAGE_NAME: ${{ github.repository }} + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: '{{packageManager}}' + + - name: Install dependencies + run: {{installCmd}} + + - name: Run linter + run: {{lintCmd}} + + test: + name: Test + runs-on: ubuntu-latest + needs: lint + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: '{{packageManager}}' + + - name: Install dependencies + run: {{installCmd}} + + - name: Run tests + run: {{testCmd}} +{{#includeCoverage}} + + - name: Run tests with coverage + run: {{testCmd}} -- --coverage + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: false +{{/includeCoverage}} + + security: + name: Security Scan + runs-on: ubuntu-latest + needs: lint + steps: + - uses: actions/checkout@v4 + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + scan-type: 'fs' + scan-ref: '.' + format: 'sarif' + output: 'trivy-results.sarif' + + - name: Upload Trivy scan results + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: 'trivy-results.sarif' + +{{#includeE2E}} + e2e: + name: E2E Tests + runs-on: ubuntu-latest + needs: test + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: '{{packageManager}}' + + - name: Install dependencies + run: {{installCmd}} + + - name: Build application + run: {{buildCmd}} + + - name: Start application + run: | + npm start & + sleep 10 + + - name: Run E2E tests + run: npm run test:e2e +{{/includeE2E}} + + build: + name: Build + runs-on: ubuntu-latest + needs: [test, security{{#includeE2E}}, e2e{{/includeE2E}}] + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: '{{packageManager}}' + + - name: Install dependencies + run: {{installCmd}} + + - name: Build application + run: {{buildCmd}} + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + retention-days: 7 + + docker: + name: Docker Build + runs-on: ubuntu-latest + needs: build + if: github.event_name == 'push' && github.ref == 'refs/heads/{{branch}}' + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=sha,prefix= + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/cli-templates/cicd/github/security-focused.yml b/cli-templates/cicd/github/security-focused.yml new file mode 100644 index 0000000..d8791e8 --- /dev/null +++ b/cli-templates/cicd/github/security-focused.yml @@ -0,0 +1,209 @@ +# GitHub Actions CI/CD Pipeline +# Generated by ExpressoTS CLI +# Strategy: security-focused +# Template Version: 1.0.0 + +name: Security-Focused CI/CD Pipeline + +on: + push: + branches: [{{branch}}] + pull_request: + branches: [{{branch}}] + schedule: + - cron: '0 0 * * 0' # Weekly security scan + +env: + NODE_VERSION: '{{nodeVersion}}' + REGISTRY: {{dockerRegistry}} + IMAGE_NAME: ${{ github.repository }} + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: '{{packageManager}}' + + - name: Install dependencies + run: {{installCmd}} + + - name: Run linter + run: {{lintCmd}} + + test: + name: Test + runs-on: ubuntu-latest + needs: lint + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: '{{packageManager}}' + + - name: Install dependencies + run: {{installCmd}} + + - name: Run tests + run: {{testCmd}} + + security-trivy: + name: Trivy Security Scan + runs-on: ubuntu-latest + needs: lint + steps: + - uses: actions/checkout@v4 + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + scan-type: 'fs' + scan-ref: '.' + format: 'sarif' + output: 'trivy-results.sarif' + severity: 'CRITICAL,HIGH' + + - name: Upload Trivy scan results + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: 'trivy-results.sarif' + + security-snyk: + name: Snyk Security Scan + runs-on: ubuntu-latest + needs: lint + steps: + - uses: actions/checkout@v4 + + - name: Run Snyk security scan + uses: snyk/actions/node@master + continue-on-error: true + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + args: --severity-threshold=high + + security-npm-audit: + name: NPM Audit + runs-on: ubuntu-latest + needs: lint + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Run npm audit + run: npm audit --audit-level=high + + security-owasp: + name: OWASP Dependency Check + runs-on: ubuntu-latest + needs: lint + steps: + - uses: actions/checkout@v4 + + - name: OWASP Dependency Check + uses: dependency-check/Dependency-Check_Action@main + with: + project: '{{projectName}}' + path: '.' + format: 'HTML' + + - name: Upload OWASP report + uses: actions/upload-artifact@v4 + with: + name: owasp-report + path: reports/ + + build: + name: Build + runs-on: ubuntu-latest + needs: [test, security-trivy, security-snyk, security-npm-audit] + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: '{{packageManager}}' + + - name: Install dependencies + run: {{installCmd}} + + - name: Build application + run: {{buildCmd}} + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + retention-days: 7 + + docker: + name: Docker Build & Scan + runs-on: ubuntu-latest + needs: build + if: github.event_name == 'push' && github.ref == 'refs/heads/{{branch}}' + permissions: + contents: read + packages: write + security-events: write + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build image + uses: docker/build-push-action@v5 + with: + context: . + load: true + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} + + - name: Scan container for vulnerabilities + uses: aquasecurity/trivy-action@master + with: + image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} + format: 'sarif' + output: 'container-scan.sarif' + severity: 'CRITICAL,HIGH' + + - name: Upload container scan results + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: 'container-scan.sarif' + + - name: Push image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/cli-templates/cicd/gitlab/basic.yml b/cli-templates/cicd/gitlab/basic.yml new file mode 100644 index 0000000..e6c5afd --- /dev/null +++ b/cli-templates/cicd/gitlab/basic.yml @@ -0,0 +1,50 @@ +# GitLab CI/CD Pipeline +# Generated by ExpressoTS CLI +# Strategy: basic +# Template Version: 1.0.0 + +image: node:{{nodeVersion}}-alpine + +stages: + - lint + - test + - build + +variables: + npm_config_cache: "$CI_PROJECT_DIR/.npm" + +cache: + key: ${CI_COMMIT_REF_SLUG} + paths: + - .npm/ + - node_modules/ + +lint: + stage: lint + script: + - {{installCmd}} + - {{lintCmd}} + only: + - {{branch}} + - merge_requests + +test: + stage: test + script: + - {{installCmd}} + - {{testCmd}} + only: + - {{branch}} + - merge_requests + +build: + stage: build + script: + - {{installCmd}} + - {{buildCmd}} + artifacts: + paths: + - dist/ + expire_in: 1 week + only: + - {{branch}} diff --git a/cli-templates/cicd/gitlab/comprehensive.yml b/cli-templates/cicd/gitlab/comprehensive.yml new file mode 100644 index 0000000..63ccf8c --- /dev/null +++ b/cli-templates/cicd/gitlab/comprehensive.yml @@ -0,0 +1,86 @@ +# GitLab CI/CD Pipeline +# Generated by ExpressoTS CLI +# Strategy: comprehensive +# Template Version: 1.0.0 + +image: node:{{nodeVersion}}-alpine + +stages: + - lint + - test + - security + - build + - docker + - deploy + +variables: + npm_config_cache: "$CI_PROJECT_DIR/.npm" + DOCKER_IMAGE: $CI_REGISTRY_IMAGE + +cache: + key: ${CI_COMMIT_REF_SLUG} + paths: + - .npm/ + - node_modules/ + +lint: + stage: lint + script: + - {{installCmd}} + - {{lintCmd}} + only: + - {{branch}} + - merge_requests + +test: + stage: test + script: + - {{installCmd}} + - {{testCmd}}{{#includeCoverage}} --coverage{{/includeCoverage}} +{{#includeCoverage}} + coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/' + artifacts: + reports: + coverage_report: + coverage_format: cobertura + path: coverage/cobertura-coverage.xml +{{/includeCoverage}} + only: + - {{branch}} + - merge_requests + +security: + stage: security + image: aquasec/trivy:latest + script: + - trivy fs --severity HIGH,CRITICAL --exit-code 1 . + allow_failure: true + only: + - {{branch}} + - merge_requests + +build: + stage: build + script: + - {{installCmd}} + - {{buildCmd}} + artifacts: + paths: + - dist/ + expire_in: 1 week + only: + - {{branch}} + +docker: + stage: docker + image: docker:latest + services: + - docker:dind + before_script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + script: + - docker build -t $DOCKER_IMAGE:$CI_COMMIT_SHA -t $DOCKER_IMAGE:latest . + - docker push $DOCKER_IMAGE:$CI_COMMIT_SHA + - docker push $DOCKER_IMAGE:latest + only: + - {{branch}} diff --git a/cli-templates/cicd/gitlab/security-focused.yml b/cli-templates/cicd/gitlab/security-focused.yml new file mode 100644 index 0000000..90f8946 --- /dev/null +++ b/cli-templates/cicd/gitlab/security-focused.yml @@ -0,0 +1,97 @@ +# GitLab CI/CD Pipeline +# Generated by ExpressoTS CLI +# Strategy: security-focused +# Template Version: 1.0.0 + +image: node:{{nodeVersion}}-alpine + +stages: + - lint + - test + - security + - sast + - build + - container-scanning + - deploy + +include: + - template: Security/SAST.gitlab-ci.yml + - template: Security/Dependency-Scanning.gitlab-ci.yml + - template: Security/Secret-Detection.gitlab-ci.yml + - template: Security/Container-Scanning.gitlab-ci.yml + +variables: + npm_config_cache: "$CI_PROJECT_DIR/.npm" + DOCKER_IMAGE: $CI_REGISTRY_IMAGE + SAST_EXCLUDED_PATHS: "node_modules, dist, coverage" + +cache: + key: ${CI_COMMIT_REF_SLUG} + paths: + - .npm/ + - node_modules/ + +lint: + stage: lint + script: + - {{installCmd}} + - {{lintCmd}} + only: + - {{branch}} + - merge_requests + +test: + stage: test + script: + - {{installCmd}} + - {{testCmd}} + only: + - {{branch}} + - merge_requests + +npm-audit: + stage: security + script: + - npm audit --audit-level=high + allow_failure: true + only: + - {{branch}} + - merge_requests + +trivy-scan: + stage: security + image: aquasec/trivy:latest + script: + - trivy fs --severity CRITICAL,HIGH --exit-code 1 --format sarif -o trivy-results.sarif . + artifacts: + reports: + sast: trivy-results.sarif + allow_failure: false + only: + - {{branch}} + - merge_requests + +build: + stage: build + script: + - {{installCmd}} + - {{buildCmd}} + artifacts: + paths: + - dist/ + expire_in: 1 week + only: + - {{branch}} + +docker: + stage: container-scanning + image: docker:latest + services: + - docker:dind + before_script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + script: + - docker build -t $DOCKER_IMAGE:$CI_COMMIT_SHA . + - docker push $DOCKER_IMAGE:$CI_COMMIT_SHA + only: + - {{branch}} diff --git a/cli-templates/cicd/jenkins/basic.groovy b/cli-templates/cicd/jenkins/basic.groovy new file mode 100644 index 0000000..357d9c8 --- /dev/null +++ b/cli-templates/cicd/jenkins/basic.groovy @@ -0,0 +1,51 @@ +// Jenkinsfile +// Generated by ExpressoTS CLI +// Strategy: basic +// Template Version: 1.0.0 + +pipeline { + agent { + docker { + image 'node:{{nodeVersion}}-alpine' + } + } + + environment { + npm_config_cache = "${WORKSPACE}/.npm" + } + + stages { + stage('Install') { + steps { + sh '{{installCmd}}' + } + } + + stage('Lint') { + steps { + sh '{{lintCmd}}' + } + } + + stage('Test') { + steps { + sh '{{testCmd}}' + } + } + + stage('Build') { + steps { + sh '{{buildCmd}}' + } + } + } + + post { + always { + cleanWs() + } + success { + archiveArtifacts artifacts: 'dist/**/*', fingerprint: true + } + } +} diff --git a/cli-templates/docker/Dockerfile.development.tpl b/cli-templates/docker/Dockerfile.development.tpl new file mode 100644 index 0000000..614b04e --- /dev/null +++ b/cli-templates/docker/Dockerfile.development.tpl @@ -0,0 +1,35 @@ +# Development Dockerfile +# Generated by ExpressoTS CLI +# Template Version: 1.0.0 + +FROM node:{{nodeVersion}}-alpine + +WORKDIR /app + +# Install development tools +RUN apk add --no-cache git + +# Copy package files +{{#hasLocalDeps}} +COPY package.docker.json ./package.json +COPY .docker-deps/ ./.docker-deps/ +{{/hasLocalDeps}} +{{^hasLocalDeps}} +COPY package*.json ./ +{{/hasLocalDeps}} + +# Install all dependencies (including dev) +RUN {{installCommand}} + +# Copy source code +COPY . . + +# Set environment variables +ENV NODE_ENV=development +ENV PORT={{port}} + +# Expose port +EXPOSE {{port}} + +# Start with hot reload +CMD ["npm", "run", "dev"] diff --git a/cli-templates/docker/Dockerfile.production.tpl b/cli-templates/docker/Dockerfile.production.tpl new file mode 100644 index 0000000..c58eb75 --- /dev/null +++ b/cli-templates/docker/Dockerfile.production.tpl @@ -0,0 +1,59 @@ +# Production Dockerfile +# Generated by ExpressoTS CLI +# Template Version: 1.0.0 + +# Build stage +FROM node:{{nodeVersion}}-alpine AS builder + +WORKDIR /app + +# Copy package files +{{#hasLocalDeps}} +COPY package.docker.json ./package.json +{{/hasLocalDeps}} +{{^hasLocalDeps}} +COPY package*.json ./ +{{/hasLocalDeps}} + +# Install dependencies +RUN {{installCommand}} + +# Copy source code +COPY . . + +# Build the application +RUN {{buildCommand}} + +# Prune dev dependencies +RUN npm prune --production + +# Production stage +FROM node:{{nodeVersion}}-alpine AS production + +WORKDIR /app + +# Create non-root user +RUN addgroup -g 1001 -S nodejs && \ + adduser -S expressots -u 1001 -G nodejs + +# Copy built files from builder +COPY --from=builder --chown=expressots:nodejs /app/node_modules ./node_modules +COPY --from=builder --chown=expressots:nodejs /app/dist ./dist +COPY --from=builder --chown=expressots:nodejs /app/package.json ./ + +# Set environment variables +ENV NODE_ENV=production +ENV PORT={{port}} + +# Switch to non-root user +USER expressots + +# Expose port +EXPOSE {{port}} + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:{{port}}{{healthCheckEndpoint}} || exit 1 + +# Start the application +CMD ["node", "{{entryPoint}}"] diff --git a/cli-templates/docker/docker-compose.development.yml.tpl b/cli-templates/docker/docker-compose.development.yml.tpl new file mode 100644 index 0000000..d71a398 --- /dev/null +++ b/cli-templates/docker/docker-compose.development.yml.tpl @@ -0,0 +1,28 @@ +# Docker Compose - Development +# Generated by ExpressoTS CLI +# Template Version: 1.0.0 + +version: '3.8' + +services: + app: + build: + context: . + dockerfile: Dockerfile.development + container_name: {{projectName}}-dev + ports: + - "{{port}}:{{port}}" + environment: + - NODE_ENV=development + - PORT={{port}} + volumes: + - ./src:/app/src:ro + - ./package.json:/app/package.json:ro + - ./tsconfig.json:/app/tsconfig.json:ro + - ./expressots.config.ts:/app/expressots.config.ts:ro + networks: + - dev-network + +networks: + dev-network: + driver: bridge diff --git a/cli-templates/docker/docker-compose.yml.tpl b/cli-templates/docker/docker-compose.yml.tpl new file mode 100644 index 0000000..579585b --- /dev/null +++ b/cli-templates/docker/docker-compose.yml.tpl @@ -0,0 +1,30 @@ +# Docker Compose - Production +# Generated by ExpressoTS CLI +# Template Version: 1.0.0 + +version: '3.8' + +services: + app: + build: + context: . + dockerfile: Dockerfile + container_name: {{projectName}} + restart: unless-stopped + ports: + - "{{port}}:{{port}}" + environment: + - NODE_ENV=production + - PORT={{port}} + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:{{port}}{{healthCheckEndpoint}}"] + interval: 30s + timeout: 3s + retries: 3 + start_period: 10s + networks: + - app-network + +networks: + app-network: + driver: bridge diff --git a/cli-templates/kubernetes/configmap.yml.tpl b/cli-templates/kubernetes/configmap.yml.tpl new file mode 100644 index 0000000..5d5fea7 --- /dev/null +++ b/cli-templates/kubernetes/configmap.yml.tpl @@ -0,0 +1,17 @@ +# Kubernetes ConfigMap +# Generated by ExpressoTS CLI +# Template Version: 1.0.0 + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{appName}}-config + namespace: {{namespace}} + labels: + app: {{appName}} +data: + NODE_ENV: "production" + PORT: "{{port}}" + # Add your application configuration here + # LOG_LEVEL: "info" + # API_VERSION: "v1" diff --git a/cli-templates/kubernetes/deployment.yml.tpl b/cli-templates/kubernetes/deployment.yml.tpl new file mode 100644 index 0000000..9ad5f69 --- /dev/null +++ b/cli-templates/kubernetes/deployment.yml.tpl @@ -0,0 +1,54 @@ +# Kubernetes Deployment +# Generated by ExpressoTS CLI +# Template Version: 1.0.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{appName}} + namespace: {{namespace}} + labels: + app: {{appName}} +spec: + replicas: {{replicas}} + selector: + matchLabels: + app: {{appName}} + template: + metadata: + labels: + app: {{appName}} + spec: + containers: + - name: app + image: your-registry/{{appName}}:latest + ports: + - containerPort: {{port}} + name: http + env: + - name: NODE_ENV + value: "production" + - name: PORT + value: "{{port}}" + envFrom: + - configMapRef: + name: {{appName}}-config + resources: + requests: + memory: "{{memory}}" + cpu: "{{cpu}}" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + httpGet: + path: {{healthCheckPath}} + port: {{port}} + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + httpGet: + path: {{healthCheckPath}} + port: {{port}} + initialDelaySeconds: 5 + periodSeconds: 5 diff --git a/cli-templates/kubernetes/ingress.yml.tpl b/cli-templates/kubernetes/ingress.yml.tpl new file mode 100644 index 0000000..8792083 --- /dev/null +++ b/cli-templates/kubernetes/ingress.yml.tpl @@ -0,0 +1,30 @@ +# Kubernetes Ingress +# Generated by ExpressoTS CLI +# Template Version: 1.0.0 + +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{appName}}-ingress + namespace: {{namespace}} + labels: + app: {{appName}} + annotations: + kubernetes.io/ingress.class: nginx + cert-manager.io/cluster-issuer: letsencrypt-prod +spec: + tls: + - hosts: + - {{appName}}.example.com + secretName: {{appName}}-tls + rules: + - host: {{appName}}.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{appName}} + port: + number: 80 diff --git a/cli-templates/kubernetes/service.yml.tpl b/cli-templates/kubernetes/service.yml.tpl new file mode 100644 index 0000000..fc51f9e --- /dev/null +++ b/cli-templates/kubernetes/service.yml.tpl @@ -0,0 +1,20 @@ +# Kubernetes Service +# Generated by ExpressoTS CLI +# Template Version: 1.0.0 + +apiVersion: v1 +kind: Service +metadata: + name: {{appName}} + namespace: {{namespace}} + labels: + app: {{appName}} +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: {{port}} + protocol: TCP + name: http + selector: + app: {{appName}} diff --git a/cli-templates/manifest.json b/cli-templates/manifest.json new file mode 100644 index 0000000..edb3ac8 --- /dev/null +++ b/cli-templates/manifest.json @@ -0,0 +1,147 @@ +{ + "$schema": "https://raw.githubusercontent.com/expressots/templates/main/manifest.schema.json", + "version": "1.0.0", + "updated": "2026-01-07T00:00:00Z", + "description": "ExpressoTS CLI Templates Repository", + "templates": { + "cicd": { + "github": { + "basic": { + "path": "cicd/github/basic.yml", + "version": "1.0.0", + "description": "Basic GitHub Actions CI/CD pipeline with lint, test, and build" + }, + "comprehensive": { + "path": "cicd/github/comprehensive.yml", + "version": "1.0.0", + "description": "Full CI/CD pipeline with security scanning, E2E tests, and Docker" + }, + "security-focused": { + "path": "cicd/github/security-focused.yml", + "version": "1.0.0", + "description": "Security-focused pipeline with Trivy, Snyk, and OWASP checks" + } + }, + "gitlab": { + "basic": { + "path": "cicd/gitlab/basic.yml", + "version": "1.0.0", + "description": "Basic GitLab CI pipeline" + }, + "comprehensive": { + "path": "cicd/gitlab/comprehensive.yml", + "version": "1.0.0", + "description": "Comprehensive GitLab CI pipeline" + }, + "security-focused": { + "path": "cicd/gitlab/security-focused.yml", + "version": "1.0.0", + "description": "Security-focused GitLab CI pipeline" + } + }, + "circleci": { + "basic": { + "path": "cicd/circleci/basic.yml", + "version": "1.0.0", + "description": "Basic CircleCI pipeline" + }, + "comprehensive": { + "path": "cicd/circleci/comprehensive.yml", + "version": "1.0.0", + "description": "Comprehensive CircleCI pipeline" + } + }, + "jenkins": { + "basic": { + "path": "cicd/jenkins/basic.groovy", + "version": "1.0.0", + "description": "Basic Jenkinsfile" + } + }, + "bitbucket": { + "basic": { + "path": "cicd/bitbucket/basic.yml", + "version": "1.0.0", + "description": "Basic Bitbucket Pipelines" + } + }, + "azure": { + "basic": { + "path": "cicd/azure/basic.yml", + "version": "1.0.0", + "description": "Basic Azure DevOps pipeline" + } + } + }, + "docker": { + "production": { + "path": "docker/Dockerfile.production.tpl", + "version": "1.0.0", + "description": "Production-optimized multi-stage Dockerfile" + }, + "development": { + "path": "docker/Dockerfile.development.tpl", + "version": "1.0.0", + "description": "Development Dockerfile with hot reload" + }, + "compose": { + "path": "docker/docker-compose.yml.tpl", + "version": "1.0.0", + "description": "Production Docker Compose configuration" + }, + "compose-development": { + "path": "docker/docker-compose.development.yml.tpl", + "version": "1.0.0", + "description": "Development Docker Compose with volume mounts" + } + }, + "kubernetes": { + "deployment": { + "path": "kubernetes/deployment.yml.tpl", + "version": "1.0.0", + "description": "Kubernetes Deployment manifest" + }, + "service": { + "path": "kubernetes/service.yml.tpl", + "version": "1.0.0", + "description": "Kubernetes Service manifest" + }, + "configmap": { + "path": "kubernetes/configmap.yml.tpl", + "version": "1.0.0", + "description": "Kubernetes ConfigMap manifest" + }, + "ingress": { + "path": "kubernetes/ingress.yml.tpl", + "version": "1.0.0", + "description": "Kubernetes Ingress manifest" + } + }, + "migrations": { + "heroku-to-railway": { + "checklist": { + "path": "migrations/heroku-to-railway/checklist.md.tpl", + "version": "1.0.0" + } + }, + "heroku-to-render": { + "checklist": { + "path": "migrations/heroku-to-render/checklist.md.tpl", + "version": "1.0.0" + } + }, + "heroku-to-fly": { + "checklist": { + "path": "migrations/heroku-to-fly/checklist.md.tpl", + "version": "1.0.0" + } + }, + "compose-to-kubernetes": { + "checklist": { + "path": "migrations/compose-to-kubernetes/checklist.md.tpl", + "version": "1.0.0" + } + } + } + } +} diff --git a/cli-templates/migrations/compose-to-kubernetes/checklist.md.tpl b/cli-templates/migrations/compose-to-kubernetes/checklist.md.tpl new file mode 100644 index 0000000..68b01fb --- /dev/null +++ b/cli-templates/migrations/compose-to-kubernetes/checklist.md.tpl @@ -0,0 +1,48 @@ +# Docker Compose to Kubernetes Migration Checklist + +Generated by ExpressoTS CLI + +## Pre-Migration + +- [ ] Review existing docker-compose.yml +- [ ] Identify all services and dependencies +- [ ] Set up Kubernetes cluster (local or cloud) +- [ ] Install kubectl and configure context +- [ ] Create container registry access + +## Migration Steps + +### 1. Create Kubernetes Manifests +- [ ] Create namespace +- [ ] Create ConfigMaps for configuration +- [ ] Create Secrets for sensitive data +- [ ] Create Deployments for each service +- [ ] Create Services for networking +- [ ] Create Ingress for external access + +### 2. Container Images +- [ ] Push images to container registry +- [ ] Update image references in manifests +- [ ] Configure image pull secrets if needed + +### 3. Persistent Storage +- [ ] Create PersistentVolumeClaims +- [ ] Migrate data volumes +- [ ] Update pod specs with volume mounts + +### 4. Networking +- [ ] Configure service discovery +- [ ] Set up internal DNS +- [ ] Configure load balancing + +### 5. Deploy +- [ ] Apply manifests: `kubectl apply -f k8s/` +- [ ] Verify pods are running +- [ ] Check logs for errors + +## Post-Migration + +- [ ] Set up monitoring (Prometheus/Grafana) +- [ ] Configure alerting +- [ ] Set up CI/CD for Kubernetes +- [ ] Document runbooks diff --git a/cli-templates/migrations/heroku-to-fly/checklist.md.tpl b/cli-templates/migrations/heroku-to-fly/checklist.md.tpl new file mode 100644 index 0000000..b6e759a --- /dev/null +++ b/cli-templates/migrations/heroku-to-fly/checklist.md.tpl @@ -0,0 +1,41 @@ +# Heroku to Fly.io Migration Checklist + +Generated by ExpressoTS CLI + +## Pre-Migration + +- [ ] Export Heroku environment variables +- [ ] Install Fly CLI: `brew install flyctl` or `curl -L https://fly.io/install.sh | sh` +- [ ] Login to Fly: `flyctl auth login` +- [ ] Backup database if applicable + +## Migration Steps + +### 1. Initialize Fly App +- [ ] Run `flyctl launch` in project directory +- [ ] Review generated `fly.toml` +- [ ] Configure regions + +### 2. Environment Variables +- [ ] Set secrets: `flyctl secrets set KEY=value` +- [ ] Review `[env]` section in fly.toml + +### 3. Database Migration (if applicable) +- [ ] Create Fly Postgres: `flyctl postgres create` +- [ ] Attach to app: `flyctl postgres attach` +- [ ] Migrate data + +### 4. Deploy +- [ ] Deploy: `flyctl deploy` +- [ ] Check status: `flyctl status` +- [ ] View logs: `flyctl logs` + +### 5. DNS & Scaling +- [ ] Configure custom domain +- [ ] Scale as needed: `flyctl scale count 2` + +## Post-Migration + +- [ ] Verify all features +- [ ] Update CI/CD to use Fly +- [ ] Delete Heroku app diff --git a/cli-templates/migrations/heroku-to-railway/checklist.md.tpl b/cli-templates/migrations/heroku-to-railway/checklist.md.tpl new file mode 100644 index 0000000..3b71578 --- /dev/null +++ b/cli-templates/migrations/heroku-to-railway/checklist.md.tpl @@ -0,0 +1,54 @@ +# Heroku to Railway Migration Checklist + +Generated by ExpressoTS CLI + +## Pre-Migration + +- [ ] Export Heroku environment variables: `heroku config -a ` +- [ ] Export Heroku add-ons list: `heroku addons -a ` +- [ ] Backup database if applicable +- [ ] Review current Heroku dyno configuration +- [ ] Create Railway account at https://railway.app + +## Migration Steps + +### 1. Environment Setup +- [ ] Create new Railway project +- [ ] Link GitHub repository +- [ ] Configure environment variables in Railway dashboard + +### 2. Database Migration (if applicable) +- [ ] Provision Railway database +- [ ] Update DATABASE_URL in Railway +- [ ] Migrate data from Heroku database + +### 3. Deploy Configuration +- [ ] Review `railway.json` configuration +- [ ] Configure build settings +- [ ] Set start command + +### 4. Testing +- [ ] Deploy to Railway staging +- [ ] Verify application starts correctly +- [ ] Test all endpoints +- [ ] Check logs for errors + +### 5. DNS & Cutover +- [ ] Configure custom domain in Railway +- [ ] Update DNS records +- [ ] Monitor application health + +## Post-Migration + +- [ ] Verify all features work correctly +- [ ] Update CI/CD pipelines +- [ ] Update documentation +- [ ] Scale down/delete Heroku app + +## Environment Variable Mapping + +| Heroku | Railway | +|--------|---------| +| PORT | PORT (auto-configured) | +| DATABASE_URL | DATABASE_URL | +| NODE_ENV | NODE_ENV | diff --git a/cli-templates/migrations/heroku-to-render/checklist.md.tpl b/cli-templates/migrations/heroku-to-render/checklist.md.tpl new file mode 100644 index 0000000..13d6b28 --- /dev/null +++ b/cli-templates/migrations/heroku-to-render/checklist.md.tpl @@ -0,0 +1,42 @@ +# Heroku to Render Migration Checklist + +Generated by ExpressoTS CLI + +## Pre-Migration + +- [ ] Export Heroku environment variables +- [ ] Export Heroku add-ons list +- [ ] Backup database if applicable +- [ ] Create Render account at https://render.com + +## Migration Steps + +### 1. Create Render Service +- [ ] Create new Web Service in Render +- [ ] Connect GitHub/GitLab repository +- [ ] Configure build command +- [ ] Configure start command + +### 2. Environment Variables +- [ ] Add all environment variables +- [ ] Configure any secret groups + +### 3. Database Migration (if applicable) +- [ ] Create PostgreSQL database in Render +- [ ] Update DATABASE_URL +- [ ] Migrate data + +### 4. Testing +- [ ] Deploy to Render +- [ ] Verify application health +- [ ] Test all endpoints + +### 5. DNS Cutover +- [ ] Configure custom domain +- [ ] Update DNS records + +## Post-Migration + +- [ ] Verify all features +- [ ] Update CI/CD pipelines +- [ ] Delete Heroku app diff --git a/cli-templates/pricing.json b/cli-templates/pricing.json new file mode 100644 index 0000000..3c7a1dc --- /dev/null +++ b/cli-templates/pricing.json @@ -0,0 +1,121 @@ +{ + "$schema": "https://raw.githubusercontent.com/expressots/pricing/main/pricing.schema.json", + "version": "1.0.0", + "updated": "2026-01-07T00:00:00Z", + "description": "ExpressoTS CLI Cloud Provider Pricing Data", + "disclaimer": "Prices are estimates and may vary by region. Check provider websites for current pricing.", + "providers": { + "aws": { + "serviceName": "ECS Fargate", + "model": "per-hour", + "basePrice": 0, + "cpuPerHour": 0.04048, + "memoryPerGbHour": 0.004445, + "storagePerGb": 0.10, + "bandwidthPerGb": 0.09, + "freeBandwidth": 100, + "freeCredits": 0, + "notes": "Prices for us-east-1 region", + "source": "https://aws.amazon.com/fargate/pricing/", + "lastVerified": "2026-01-05" + }, + "gcp": { + "serviceName": "Cloud Run", + "model": "per-hour", + "basePrice": 0, + "cpuPerHour": 0.024, + "memoryPerGbHour": 0.0025, + "storagePerGb": 0.10, + "bandwidthPerGb": 0.12, + "freeBandwidth": 200, + "freeCredits": 300, + "notes": "Pay only for what you use, scales to zero", + "source": "https://cloud.google.com/run/pricing", + "lastVerified": "2026-01-05" + }, + "azure": { + "serviceName": "Container Apps", + "model": "per-hour", + "basePrice": 0, + "cpuPerHour": 0.024, + "memoryPerGbHour": 0.003, + "storagePerGb": 0.10, + "bandwidthPerGb": 0.087, + "freeBandwidth": 100, + "freeCredits": 200, + "notes": "First 180,000 vCPU-seconds free per month", + "source": "https://azure.microsoft.com/pricing/details/container-apps/", + "lastVerified": "2026-01-05" + }, + "railway": { + "serviceName": "Web Service", + "model": "usage", + "basePrice": 5, + "cpuPerHour": 0.000463, + "memoryPerGbHour": 0.000231, + "storagePerGb": 0.25, + "bandwidthPerGb": 0, + "freeBandwidth": 1000, + "freeCredits": 5, + "notes": "Usage-based pricing, great DX", + "source": "https://railway.app/pricing", + "lastVerified": "2026-01-05" + }, + "render": { + "serviceName": "Web Service", + "model": "per-month", + "basePrice": 7, + "cpuPerHour": 0, + "memoryPerGbHour": 0, + "storagePerGb": 0.25, + "bandwidthPerGb": 0.10, + "freeBandwidth": 100, + "freeCredits": 0, + "notes": "Simple pricing, auto-scaling available", + "source": "https://render.com/pricing", + "lastVerified": "2026-01-05" + }, + "fly": { + "serviceName": "Machines", + "model": "per-hour", + "basePrice": 0, + "cpuPerHour": 0.0000158, + "memoryPerGbHour": 0.0000047, + "storagePerGb": 0.15, + "bandwidthPerGb": 0.02, + "freeBandwidth": 100, + "freeCredits": 0, + "notes": "Pay for resources while running, scales to zero", + "source": "https://fly.io/docs/about/pricing/", + "lastVerified": "2026-01-05" + }, + "digitalocean": { + "serviceName": "App Platform", + "model": "per-month", + "basePrice": 5, + "cpuPerHour": 0, + "memoryPerGbHour": 0, + "storagePerGb": 0.10, + "bandwidthPerGb": 0.01, + "freeBandwidth": 500, + "freeCredits": 0, + "notes": "Simple pricing, good for small projects", + "source": "https://www.digitalocean.com/pricing/app-platform", + "lastVerified": "2026-01-05" + }, + "heroku": { + "serviceName": "Eco Dyno", + "model": "per-month", + "basePrice": 5, + "cpuPerHour": 0, + "memoryPerGbHour": 0, + "storagePerGb": 0, + "bandwidthPerGb": 0, + "freeBandwidth": 2000, + "freeCredits": 0, + "notes": "Basic: $7/mo, Standard: $25/mo, Performance: $250+/mo", + "source": "https://www.heroku.com/pricing", + "lastVerified": "2026-01-05" + } + } +} diff --git a/commitlint.config.ts b/commitlint.config.ts index 149bce3..3cbf29f 100644 --- a/commitlint.config.ts +++ b/commitlint.config.ts @@ -1,7 +1,12 @@ -import type { UserConfig } from '@commitlint/types' +import type { UserConfig } from "@commitlint/types"; const Configuration: UserConfig = { - extends: ['@commitlint/config-conventional'] -} + extends: ["@commitlint/config-conventional"], + rules: { + "subject-max-length": [0], + "body-max-line-length": [0], + "header-max-length": [0], + }, +}; -export default Configuration \ No newline at end of file +export default Configuration; diff --git a/micro/README.md b/micro/README.md index c09d790..b36e0af 100644 --- a/micro/README.md +++ b/micro/README.md @@ -1,74 +1,234 @@ -# Expresso TS +# ExpressoTS Micro -A Typescript + [Node.js]("https://nodejs.org/en/") lightweight framework for quick building scalable, easy to read and maintain, server-side applications ๐Ÿš€ +A lightweight, minimal ExpressoTS microservice template with powerful enterprise features. -## Philosophy +## Quick Start -ExpressoTS is a TypeScript lightweight framework for building scalable, readable and maintainable server-side applications. The framework provides a level of abstraction on top of common HTTP server framework Expressjs exposing their API's directly to the developer. This provides freedom and brings to the developer a tool that is well known and easy to use. +```bash +# Install dependencies +npm install -## How to use +# Start development server +npm run dev -### Executing in development mode +# Build for production +npm run build -```bash -npm run dev +# Run in production +npm run prod ``` -### Generating production build +## Project Structure -```bash -npm run build +``` +src/ +โ””โ”€โ”€ api.ts # Single file API + +examples/ # Advanced feature examples +โ”œโ”€โ”€ circuit-breaker.example.ts +โ”œโ”€โ”€ service-discovery.example.ts +โ”œโ”€โ”€ service-client.example.ts +โ”œโ”€โ”€ serverless-lambda.example.ts +โ””โ”€โ”€ full-di-api.example.ts ``` -### Executing in production mode +## Adding Routes -```bash -npm run prod +```typescript +import { micro } from "@expressots/adapter-express"; + +const app = micro(); + +// Simple GET route - return value is auto-sent as JSON +app.get("/users", () => { + return { users: [] }; +}); + +// POST route with request body +app.post("/users", (req) => { + const user = req.body; + return user; +}); + +// Route with parameters +app.get("/users/:id", (req) => { + return { id: req.params.id }; +}); + +// Route with query parameters +app.get("/search", (req) => { + return { query: req.query.q }; +}); + +// Use res directly for custom responses +app.post("/custom", (req, res) => { + res.status(201).json({ created: true }); +}); + +app.listen(3000); +``` + +## Middleware Support + +```typescript +// Global middleware +app.use((req, res, next) => { + console.log(`${req.method} ${req.path}`); + next(); +}); + +// Path-specific middleware +app.use("/api", authMiddleware); + +// Route-specific middleware (before handler) +const validate = (req, res, next) => { + if (!req.body.name) { + return res.status(400).json({ error: "Name required" }); + } + next(); +}; + +app.post("/users", validate, (req) => { + return { created: true }; +}); ``` -## Test +## Error Handling -How to run test scripts +```typescript +app.setErrorHandler((err, req, res, next) => { + console.error(err); + res.status(500).json({ error: err.message }); +}); +``` -### Unit tests +## Configuration -```bash -npm run test +```typescript +const app = micro({ + autoParseJson: true, // Enable JSON body parsing (default: true) + globalPrefix: "/api", // Add prefix to all routes + showBanner: true, // Show startup banner (default: true) +}); ``` -### Test coverage +## Advanced Features -```bash -npm run test:cov +ExpressoTS Micro includes powerful enterprise features for building production-ready microservices: + +### Circuit Breaker + +Protect your services from cascading failures: + +```typescript +import { CircuitBreaker } from "@expressots/adapter-express"; + +const breaker = new CircuitBreaker({ + failureThreshold: 5, + successThreshold: 2, + timeout: 60000, +}); + +app.get("/external-api", async () => { + return await breaker.execute(async () => { + const response = await fetch("https://api.example.com/data"); + return response.json(); + }); +}); +``` + +### Service Discovery + +Register and discover service instances with load balancing: + +```typescript +import { ServiceDiscovery } from "@expressots/adapter-express"; + +const discovery = new ServiceDiscovery({ type: "static" }); + +discovery.registerService({ + id: "user-service-1", + name: "user-service", + host: "localhost", + port: 3001, + health: "healthy", + lastCheck: new Date(), +}); + +// Get a healthy instance (round-robin load balancing) +const instance = discovery.getService("user-service"); +``` + +### Service Client + +HTTP client with retry logic and circuit breaker integration: + +```typescript +import { ServiceClient } from "@expressots/adapter-express"; + +const client = new ServiceClient({ + name: "user-service", + baseUrl: "http://localhost:3001", + timeout: 5000, + retries: 3, + circuitBreaker: { + failureThreshold: 5, + timeout: 60000, + }, +}); + +app.get("/users", async () => { + return await client.get("/api/users"); +}); ``` -## Documentation +### Serverless Deployment + +Deploy to AWS Lambda, Vercel, or Cloudflare Workers. See `examples/serverless-lambda.example.ts`. + +## Upgrading to Full DI -- Here is our [Official Documentation](https://expresso-ts.com/) -- Checkout our [First Steps documentation](https://expresso-ts.com/docs/overview/first-steps) -- Our [CLI Documentation](https://expresso-ts.com/docs/category/cli) +When you need dependency injection and more advanced features, upgrade to `createMicroAPI()`: -## Questions +```typescript +import { createMicroAPI } from "@expressots/adapter-express"; -For questions and support please use the Official [Discord Channel](https://discord.com/invite/PyPJfGK). We have a very active community there, that will be happy to help you. Post your questions in the channel called **HELP EXPRESSO TS** and forum called **help**. +const microAPI = createMicroAPI(); +microAPI.setGlobalRoutePrefix("/api/v1"); -## Issues +const app = microAPI.build(); +app.Middleware.parse(); -The [Issue Reporting Channel](https://github.com/expressots/expressots/issues) is for bug report and feature request **only**. +app.Route.get("/users", async (req, res) => { + res.json([]); +}); -Before you create an issue, please make sure you read the [Contribution Guidelines](CONTRIBUTING.md). +await app.listen(3000); +``` + +See `examples/full-di-api.example.ts` for a complete example. + +## Examples -## Support the project +Check the `examples/` folder for complete working examples of: -Expresso TS is an MIT-licensed open source project. It's an independent project with ongoing development made possible thanks to your support. If you'd like to help, please consider: +- **Circuit Breaker** - Fault tolerance pattern +- **Service Discovery** - Service registration and load balancing +- **Service Client** - HTTP client with retries +- **Serverless** - AWS Lambda deployment +- **Full DI API** - Upgrade path with dependency injection -- Become a sponsor on **[Sponsor no GitHub](https://github.com/sponsors/expressots)** -- Follow the **[organization](https://github.com/expressots)** on GitHub and Star โญ the project -- Subscribe to the Twitch channel: **[Richard Zampieri](https://www.twitch.tv/richardzampieri)** -- Join our **[Discord](https://discord.com/invite/PyPJfGK)** -- Contribute submitting **[issues and pull requests](https://github.com/expressots/expressots/issues/new/choose)** -- Share the project with your friends and colleagues +Run examples with: + +```bash +npm run example:circuit-breaker +npm run example:service-discovery +``` -## License +## Learn More -ExpressoTS is **[MIT licensed](LICENSE.md)** \ No newline at end of file +- [ExpressoTS Documentation](https://expresso-ts.com) +- [GitHub Repository](https://github.com/expressots) +- [Discord Community](https://discord.gg/PyPJfGK) +- [Advanced Features Guide](./ADVANCED.md) +- [Upgrading Guide](./UPGRADING.md) diff --git a/micro/eslint.config.mjs b/micro/eslint.config.mjs new file mode 100644 index 0000000..f480369 --- /dev/null +++ b/micro/eslint.config.mjs @@ -0,0 +1,43 @@ +import js from "@eslint/js"; +import tseslint from "typescript-eslint"; +import { fileURLToPath } from "node:url"; + +const __dirname = fileURLToPath(new URL(".", import.meta.url)); + +export default tseslint.config( + js.configs.recommended, + ...tseslint.configs.recommended, + { + files: ["src/**/*.ts", "test/**/*.ts"], + languageOptions: { + parserOptions: { + project: true, + tsconfigRootDir: __dirname, + }, + }, + rules: { + // TypeScript-specific rules + "@typescript-eslint/no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + }, + ], + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-non-null-assertion": "warn", + }, + }, + { + ignores: [ + "node_modules/**", + "dist/**", + "coverage/**", + "*.config.ts", + "*.config.js", + "*.config.mjs", + ], + }, +); diff --git a/micro/examples/README.md b/micro/examples/README.md new file mode 100644 index 0000000..0b486fd --- /dev/null +++ b/micro/examples/README.md @@ -0,0 +1,133 @@ +# ExpressoTS Micro Examples + +This folder contains examples demonstrating the advanced features available in ExpressoTS Micro. + +## Running Examples + +Each example can be run independently using the provided npm scripts: + +```bash +# Circuit Breaker - Fault tolerance pattern +npm run example:circuit-breaker + +# Service Discovery - Service registration and load balancing +npm run example:service-discovery + +# Service Client - HTTP client with retries +npm run example:service-client + +# Full DI API - Upgrade path with dependency injection +npm run example:full-di-api +``` + +For the serverless example, see the deployment instructions in the file. + +## Examples Overview + +### 1. Circuit Breaker (`circuit-breaker.example.ts`) + +Demonstrates the Circuit Breaker pattern for protecting your service from cascading failures: + +- **CLOSED**: Normal operation, requests pass through +- **OPEN**: Requests fail immediately (service unavailable) +- **HALF_OPEN**: Testing if service has recovered + +Key features: +- Configurable failure and success thresholds +- Automatic recovery after timeout +- Manual reset and open controls +- Statistics and monitoring + +### 2. Service Discovery (`service-discovery.example.ts`) + +Shows how to implement service discovery for microservices: + +- Static service registration +- Round-robin load balancing +- Health status tracking +- Dynamic service registration/deregistration + +Use cases: +- Microservice architecture +- Multiple service instances +- Blue/green deployments + +### 3. Service Client (`service-client.example.ts`) + +Demonstrates HTTP client for service-to-service communication: + +- Automatic retries with exponential backoff +- Request timeouts +- Circuit breaker integration +- Custom headers per request +- Query parameter handling + +Use cases: +- API Gateway pattern +- Service composition +- External API calls + +### 4. Serverless Lambda (`serverless-lambda.example.ts`) + +Shows how to deploy to AWS Lambda: + +- Same code works locally and on Lambda +- AWS SAM template example +- Environment-aware configuration +- Binary content handling + +Deployment steps included in the file. + +### 5. Full DI API (`full-di-api.example.ts`) + +Upgrade path from simple `micro()` to full `createMicroAPI()`: + +- Dependency injection container +- Provider registration (singleton, transient) +- Middleware pipeline +- Route management + +## Feature Comparison + +| Feature | micro() | createMicroAPI() | +|---------|---------|------------------| +| Simple routing | โœ… | โœ… | +| Auto-response | โœ… | โŒ | +| Middleware | โœ… basic | โœ… pipeline | +| DI Container | โŒ | โœ… | +| Provider registration | โŒ | โœ… | + +## When to Use What + +### Use `micro()` when: +- Building simple APIs or serverless functions +- Prototyping quickly +- Don't need dependency injection +- Want minimal boilerplate + +### Use `createMicroAPI()` when: +- Building larger microservices +- Need dependency injection +- Need advanced middleware pipeline +- Building with provider pattern + +### Use Circuit Breaker when: +- Calling external/unreliable services +- Need fault tolerance +- Want to prevent cascading failures + +### Use Service Discovery when: +- Running multiple service instances +- Need load balancing +- Building microservice mesh + +### Use Service Client when: +- Service-to-service communication +- Need automatic retries +- Want circuit breaker on HTTP calls + +## Learn More + +- [ExpressoTS Documentation](https://expresso-ts.com) +- [Advanced Features Guide](../ADVANCED.md) +- [Upgrading Guide](../UPGRADING.md) diff --git a/micro/examples/circuit-breaker.example.ts b/micro/examples/circuit-breaker.example.ts new file mode 100644 index 0000000..171a51d --- /dev/null +++ b/micro/examples/circuit-breaker.example.ts @@ -0,0 +1,86 @@ +/** + * Circuit Breaker Example + * + * Demonstrates how to use the CircuitBreaker pattern to protect your + * microservice from cascading failures when calling external services. + * + * Run with: npm run example:circuit-breaker + */ + +import { micro, CircuitBreaker } from "@expressots/adapter-express"; + +const app = micro(); + +// Create a circuit breaker for external API calls +const externalApiBreaker = new CircuitBreaker({ + // Open circuit after 5 failures + failureThreshold: 5, + // Try to close circuit after 2 successes in half-open state + successThreshold: 2, + // Wait 30 seconds before trying to close an open circuit + timeout: 30000, + // Count failures within 10 second window + monitoringPeriod: 10000, +}); + +// Simulated external API call (replace with real API) +async function callExternalApi(): Promise<{ data: string }> { + // Simulate random failures for demo + if (Math.random() < 0.3) { + throw new Error("External API unavailable"); + } + return { data: "Success from external API" }; +} + +// Protected endpoint using circuit breaker +app.get("/api/external", async () => { + try { + const result = await externalApiBreaker.execute(async () => { + return await callExternalApi(); + }); + return result; + } catch (error) { + if (error instanceof Error && error.message === "Circuit breaker is OPEN") { + // Service is temporarily unavailable + return { + error: "Service temporarily unavailable", + message: "Please try again later", + retryAfter: 30, + }; + } + return { error: (error as Error).message }; + } +}); + +// Health check with circuit breaker status +app.get("/health", () => { + const stats = externalApiBreaker.getStats(); + return { + status: "healthy", + circuitBreaker: { + state: stats.state, + failures: stats.failures, + successes: stats.successes, + totalCalls: stats.totalCalls, + lastFailure: stats.lastFailure, + lastSuccess: stats.lastSuccess, + }, + }; +}); + +// Endpoint to manually reset the circuit breaker (for testing) +app.post("/admin/reset-circuit", () => { + externalApiBreaker.reset(); + return { message: "Circuit breaker reset" }; +}); + +// Endpoint to manually open the circuit (for maintenance) +app.post("/admin/open-circuit", () => { + externalApiBreaker.open(); + return { message: "Circuit breaker opened" }; +}); + +app.listen(3000, { + appName: "Circuit Breaker Example", + appVersion: "1.0.0", +}); diff --git a/micro/examples/full-di-api.example.ts b/micro/examples/full-di-api.example.ts new file mode 100644 index 0000000..34aca85 --- /dev/null +++ b/micro/examples/full-di-api.example.ts @@ -0,0 +1,139 @@ +/** + * Full DI API Example + * + * Demonstrates how to upgrade from the simple micro() API to the full + * createMicroAPI() with dependency injection container. + * + * Use this when you need: + * - Dependency injection + * - Middleware pipeline + * - Route management with prefixes + * - Provider registration + * + * Run with: npm run example:full-di-api + */ + +import { createMicroAPI } from "@expressots/adapter-express"; + +// Create a micro API with DI container +const microAPI = createMicroAPI(); + +// Set global route prefix (all routes will be prefixed with /api/v1) +microAPI.setGlobalRoutePrefix("/api/v1"); + +// Access the container for dependency injection +const container = microAPI.Container; + +// Register services in the container +// Example: Register a singleton service +class UserRepository { + private users = [ + { id: "1", name: "Alice", email: "alice@example.com" }, + { id: "2", name: "Bob", email: "bob@example.com" }, + ]; + + findAll() { + return this.users; + } + + findById(id: string) { + return this.users.find((u) => u.id === id); + } + + create(user: { name: string; email: string }) { + const newUser = { id: String(Date.now()), ...user }; + this.users.push(newUser); + return newUser; + } +} + +// Register as singleton (same instance throughout app lifecycle) +container.addSingleton(UserRepository); + +// Build the web server +const app = microAPI.build(); + +// Configure middleware +app.Middleware.parse(); // Enable JSON body parsing + +// Add custom middleware +app.Middleware.addMiddleware((req, _res, next) => { + console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); + next(); +}); + +// Define routes using the Route interface +// Note: Routes are prefixed with /api/v1 + +app.Route.get("/", (req, res) => { + res.json({ + message: "Welcome to ExpressoTS Micro API with DI", + version: "1.0.0", + }); +}); + +app.Route.get("/users", (req, res) => { + // Get the repository from the container + const userRepo = container.get(UserRepository); + res.json(userRepo.findAll()); +}); + +app.Route.get("/users/:id", (req, res) => { + const userRepo = container.get(UserRepository); + const user = userRepo.findById(req.params.id); + + if (!user) { + res.status(404).json({ error: "User not found" }); + return; + } + + res.json(user); +}); + +app.Route.post("/users", (req, res) => { + const userRepo = container.get(UserRepository); + const user = userRepo.create(req.body); + res.status(201).json(user); +}); + +app.Route.get("/health", (req, res) => { + res.json({ + status: "healthy", + timestamp: new Date().toISOString(), + }); +}); + +// Set custom error handler +app.Middleware.setErrorHandler((err, req, res, next) => { + console.error("Error:", err); + res.status(500).json({ + error: err.message, + path: req.path, + }); +}); + +// Start the server +app.listen(3000, { + appName: "Full DI API Example", + appVersion: "1.0.0", +}); + +/** + * Feature Comparison: micro() vs createMicroAPI() + * + * | Feature | micro() | createMicroAPI() | + * |------------------------|-----------------|------------------| + * | Simple routing | โœ… | โœ… | + * | Auto-response | โœ… | โŒ | + * | Middleware | โœ… (basic) | โœ… (pipeline) | + * | DI Container | โŒ | โœ… | + * | Route prefix | โœ… (config) | โœ… | + * | Provider registration | โŒ | โœ… | + * | Middleware presets | โŒ | โœ… | + * + * When to upgrade: + * - Need dependency injection + * - Need advanced middleware pipeline + * - Need provider registration (singletons, etc.) + * - Building a larger microservice + */ diff --git a/micro/examples/serverless-lambda.example.ts b/micro/examples/serverless-lambda.example.ts new file mode 100644 index 0000000..3caeb27 --- /dev/null +++ b/micro/examples/serverless-lambda.example.ts @@ -0,0 +1,115 @@ +/** + * Serverless Lambda Example + * + * Demonstrates how to deploy an ExpressoTS Micro API to AWS Lambda. + * The same code works locally for development and on Lambda for production. + * + * Deployment: + * 1. Build: npm run build + * 2. Package: zip -r function.zip dist/ node_modules/ + * 3. Deploy to AWS Lambda with API Gateway + * + * For local development: npm run dev + */ + +import { micro, awsLambdaAdapter } from "@expressots/adapter-express"; + +// Create the micro API +const app = micro({ + showBanner: process.env.AWS_LAMBDA_FUNCTION_NAME ? false : true, +}); + +// Define your routes +app.get("/", () => { + return { + message: "Hello from ExpressoTS Micro on Lambda!", + environment: process.env.AWS_LAMBDA_FUNCTION_NAME ? "lambda" : "local", + }; +}); + +app.get("/users", () => { + return { + users: [ + { id: 1, name: "Alice" }, + { id: 2, name: "Bob" }, + ], + }; +}); + +app.get("/users/:id", (req) => { + return { + id: req.params.id, + name: `User ${req.params.id}`, + requestedAt: new Date().toISOString(), + }; +}); + +app.post("/users", (req) => { + return { + id: Date.now(), + ...req.body, + createdAt: new Date().toISOString(), + }; +}); + +app.get("/health", () => { + return { + status: "healthy", + timestamp: new Date().toISOString(), + memoryUsage: process.memoryUsage(), + lambdaContext: process.env.AWS_LAMBDA_FUNCTION_NAME + ? { + functionName: process.env.AWS_LAMBDA_FUNCTION_NAME, + memoryLimit: process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE, + region: process.env.AWS_REGION, + } + : null, + }; +}); + +// Error handling +app.setErrorHandler((err, _req, res) => { + console.error("Error:", err); + res.status(500).json({ + error: err.message, + stack: process.env.NODE_ENV === "development" ? err.stack : undefined, + }); +}); + +// Export for Lambda +// AWS Lambda will call this handler +export const handler = awsLambdaAdapter(app.getApp(), { + debug: process.env.DEBUG === "true", + binaryContentTypes: ["application/octet-stream", "image/*"], +}); + +// Local development +// Only starts the server when running locally (not on Lambda) +if (!process.env.AWS_LAMBDA_FUNCTION_NAME) { + app.listen(3000, { + appName: "Lambda Example", + appVersion: "1.0.0", + }); +} + +/** + * AWS SAM Template Example (template.yaml): + * + * AWSTemplateFormatVersion: '2010-09-09' + * Transform: AWS::Serverless-2016-10-31 + * + * Resources: + * ExpressoTSFunction: + * Type: AWS::Serverless::Function + * Properties: + * Handler: dist/examples/serverless-lambda.example.handler + * Runtime: nodejs20.x + * MemorySize: 256 + * Timeout: 30 + * Events: + * Api: + * Type: HttpApi + * Properties: + * Path: /{proxy+} + * Method: ANY + */ diff --git a/micro/examples/service-client.example.ts b/micro/examples/service-client.example.ts new file mode 100644 index 0000000..f791af7 --- /dev/null +++ b/micro/examples/service-client.example.ts @@ -0,0 +1,175 @@ +/** + * Service Client Example + * + * Demonstrates how to use ServiceClient for service-to-service communication + * with automatic retries, timeouts, and circuit breaker integration. + * + * Run with: npm run example:service-client + */ + +import { micro, ServiceClient } from "@expressots/adapter-express"; + +const app = micro(); + +// Create service clients for different microservices +const userService = new ServiceClient({ + name: "user-service", + baseUrl: "http://localhost:3001", + timeout: 5000, // 5 second timeout + retries: 3, // Retry failed requests up to 3 times + circuitBreaker: { + failureThreshold: 5, + timeout: 30000, + }, + headers: { + "X-Service-Name": "api-gateway", + }, +}); + +const orderService = new ServiceClient({ + name: "order-service", + baseUrl: "http://localhost:4001", + timeout: 10000, // Longer timeout for order processing + retries: 2, + circuitBreaker: true, // Use default circuit breaker config +}); + +// Disable circuit breaker for analytics (non-critical) +const analyticsService = new ServiceClient({ + name: "analytics-service", + baseUrl: "http://localhost:5001", + timeout: 3000, + retries: 1, + circuitBreaker: false, +}); + +// Get user by ID +app.get("/users/:id", async (req) => { + try { + interface User { + id: string; + name: string; + email: string; + } + const user = await userService.get(`/api/users/${req.params.id}`); + return user; + } catch (error) { + if ((error as Error).message.includes("Circuit breaker is OPEN")) { + return { error: "User service temporarily unavailable" }; + } + return { error: (error as Error).message }; + } +}); + +// Create a new user +app.post("/users", async (req) => { + try { + interface User { + id: string; + name: string; + email: string; + } + const user = await userService.post("/api/users", req.body); + return user; + } catch (error) { + return { error: (error as Error).message }; + } +}); + +// Create an order (calls user and order services) +app.post("/orders", async (req) => { + const { userId, items } = req.body; + + try { + // First verify user exists + interface User { + id: string; + name: string; + } + const user = await userService.get(`/api/users/${userId}`); + + // Then create the order + interface Order { + id: string; + userId: string; + items: unknown[]; + total: number; + } + const order = await orderService.post("/api/orders", { + userId, + items, + }); + + // Fire-and-forget analytics (don't wait, don't fail if it errors) + analyticsService + .post("/events", { + event: "order.created", + orderId: order.id, + userId, + timestamp: new Date().toISOString(), + }) + .catch(() => { + // Ignore analytics errors + }); + + return { order, user }; + } catch (error) { + return { error: (error as Error).message }; + } +}); + +// Get service health and statistics +app.get("/health", () => { + return { + status: "healthy", + services: { + user: userService.getStats(), + order: orderService.getStats(), + analytics: analyticsService.getStats(), + }, + }; +}); + +// Demonstrate custom headers per request +app.get("/users/:id/with-auth", async (req) => { + try { + interface User { + id: string; + name: string; + } + const user = await userService.get(`/api/users/${req.params.id}`, { + headers: { + Authorization: `Bearer ${req.headers.authorization}`, + "X-Request-ID": `req-${Date.now()}`, + }, + }); + return user; + } catch (error) { + return { error: (error as Error).message }; + } +}); + +// Demonstrate query parameters +app.get("/users", async (req) => { + try { + interface UserList { + users: unknown[]; + total: number; + } + const result = await userService.call("/api/users", { + params: { + page: (req.query.page as string) || "1", + limit: (req.query.limit as string) || "10", + sort: (req.query.sort as string) || "createdAt", + }, + }); + return result; + } catch (error) { + return { error: (error as Error).message }; + } +}); + +app.listen(3000, { + appName: "Service Client Example", + appVersion: "1.0.0", +}); diff --git a/micro/examples/service-discovery.example.ts b/micro/examples/service-discovery.example.ts new file mode 100644 index 0000000..7f3e843 --- /dev/null +++ b/micro/examples/service-discovery.example.ts @@ -0,0 +1,140 @@ +/** + * Service Discovery Example + * + * Demonstrates how to use ServiceDiscovery for registering and discovering + * microservices with round-robin load balancing. + * + * Run with: npm run example:service-discovery + */ + +import { micro, ServiceDiscovery, ServiceClient } from "@expressots/adapter-express"; + +const app = micro(); + +// Create a service discovery instance (static mode for this example) +const discovery = new ServiceDiscovery({ + type: "static", + debug: true, // Enable logging for demo +}); + +// Register some service instances +// In production, services would self-register on startup +discovery.registerService({ + id: "user-service-1", + name: "user-service", + host: "localhost", + port: 3001, + health: "healthy", + lastCheck: new Date(), + metadata: { version: "1.0.0", region: "us-east-1" }, +}); + +discovery.registerService({ + id: "user-service-2", + name: "user-service", + host: "localhost", + port: 3002, + health: "healthy", + lastCheck: new Date(), + metadata: { version: "1.0.0", region: "us-west-1" }, +}); + +discovery.registerService({ + id: "order-service-1", + name: "order-service", + host: "localhost", + port: 4001, + health: "healthy", + lastCheck: new Date(), +}); + +// Get users - demonstrates round-robin load balancing +app.get("/users", async () => { + const instance = discovery.getService("user-service"); + + if (!instance) { + return { error: "No healthy user-service instances available" }; + } + + // Create client for this instance + const client = new ServiceClient({ + name: "user-service", + baseUrl: `http://${instance.host}:${instance.port}`, + timeout: 5000, + }); + + try { + // In real scenario, make the actual call + // const users = await client.get("/api/users"); + return { + message: `Would call user-service at ${instance.host}:${instance.port}`, + instance: { + id: instance.id, + host: instance.host, + port: instance.port, + }, + }; + } catch (error) { + // Mark instance as unhealthy on failure + discovery.updateHealth(instance.name, instance.id, "unhealthy"); + return { error: (error as Error).message }; + } +}); + +// List all registered services +app.get("/services", () => { + const serviceNames = discovery.listServices(); + const services: Record = {}; + + for (const name of serviceNames) { + services[name] = discovery.getServiceInstances(name, false); + } + + return { services }; +}); + +// Get service statistics +app.get("/services/stats", () => { + return { stats: discovery.getStats() }; +}); + +// Register a new service instance (for self-registration) +app.post("/services/register", (req) => { + const { id, name, host, port, metadata } = req.body; + + discovery.registerService({ + id, + name, + host, + port, + health: "healthy", + lastCheck: new Date(), + metadata, + }); + + return { message: `Registered ${name} (${id})` }; +}); + +// Deregister a service instance +app.delete("/services/:name/:id", (req) => { + const { name, id } = req.params; + discovery.deregisterService(name, id); + return { message: `Deregistered ${name} (${id})` }; +}); + +// Update service health +app.put("/services/:name/:id/health", (req) => { + const { name, id } = req.params; + const { health } = req.body; + + const updated = discovery.updateHealth(name, id, health); + if (updated) { + return { message: `Updated ${name} (${id}) health to ${health}` }; + } + return { error: "Service instance not found" }; +}); + +app.listen(3000, { + appName: "Service Discovery Example", + appVersion: "1.0.0", +}); diff --git a/micro/expressots.config.ts b/micro/expressots.config.ts index 3dc3853..90360e7 100644 --- a/micro/expressots.config.ts +++ b/micro/expressots.config.ts @@ -1,7 +1,7 @@ import { ExpressoConfig, Pattern } from "@expressots/shared"; const config: ExpressoConfig = { - entryPoint: "api", + entryPoint: "src/api", sourceRoot: "src", scaffoldPattern: Pattern.KEBAB_CASE, opinionated: false, diff --git a/micro/jest.config.ts b/micro/jest.config.ts index 1910514..251492b 100644 --- a/micro/jest.config.ts +++ b/micro/jest.config.ts @@ -1,15 +1,17 @@ -import type { JestConfigWithTsJest } from "ts-jest"; +import type { Config } from "jest"; -const jestConfig: JestConfigWithTsJest = { +const config: Config = { preset: "ts-jest", - rootDir: "./", testEnvironment: "node", - verbose: true, - automock: false, - testMatch: ["**/*.test.ts", "**/*.spec.ts"], - coverageDirectory: "./coverage", - coverageReporters: ["text", "html", "json"], - modulePathIgnorePatterns: ["/dist/"], + rootDir: ".", + testMatch: ["/test/**/*.spec.ts"], + moduleNameMapper: { + "^@app/(.*)$": "/src/$1", + }, + modulePathIgnorePatterns: ["/dist"], + collectCoverageFrom: ["src/**/*.ts", "!src/**/*.d.ts", "!src/main.ts"], + coverageDirectory: "coverage", + coverageReporters: ["text", "lcov"], }; -export default jestConfig; +export default config; diff --git a/micro/package.json b/micro/package.json index 87b907e..4d8c437 100644 --- a/micro/package.json +++ b/micro/package.json @@ -1,41 +1,45 @@ { - "name": "micro", + "name": "expressots-micro", "version": "1.0.0", - "description": "", + "description": "ExpressoTS Microservice - Lightweight API with Circuit Breaker, Service Discovery, and Serverless support", "author": "", - "private": true, - "license": "UNLICENSED", + "license": "MIT", + "main": "dist/src/api.js", "scripts": { "build": "expressots build", "dev": "expressots dev", "prod": "expressots prod", + "format": "prettier --write \"src/**/*.ts\"", + "lint": "eslint \"src/**/*.ts\" --fix", "test": "jest", - "test:watch": "jest --watchAll", - "test:cov": "jest --coverage", - "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", - "lint": "eslint \"src/**/*.ts\" --fix" + "test:watch": "jest --watch", + "test:coverage": "jest --coverage", + "example:circuit-breaker": "tsx examples/circuit-breaker.example.ts", + "example:service-discovery": "tsx examples/service-discovery.example.ts", + "example:service-client": "tsx examples/service-client.example.ts", + "example:full-di-api": "tsx examples/full-di-api.example.ts" }, - "keywords": [], "dependencies": { - "@expressots/adapter-express": "3.0.0", - "@expressots/core": "3.0.0", - "@expressots/shared": "3.0.0" + "@expressots/adapter-express": "file:../adapter-express/expressots-adapter-express-4.0.0-beta.1.tgz", + "@expressots/core": "file:../expressots/expressots-core-4.0.0-beta.1.tgz", + "@expressots/shared": "file:../shared/expressots-shared-4.0.0-beta.1.tgz", + "express": "5.2.1" }, "devDependencies": { - "@expressots/cli": "3.0.0", - "@types/express": "5.0.0", - "@types/jest": "29.5.14", - "@types/node": "20.12.7", - "@typescript-eslint/eslint-plugin": "8.0.0", - "@typescript-eslint/parser": "8.0.0", - "eslint": "8.57.0", - "eslint-config-prettier": "9.0.0", - "eslint-plugin-prettier": "5.0.0", - "jest": "29.7.0", - "prettier": "3.2.5", - "supertest": "^7.0.0", - "ts-jest": "29.1.2", - "tsx": "4.19.2", - "typescript": "5.1.3" + "@eslint/js": "9.39.2", + "@expressots/cli": "file:../expressots-cli/expressots-cli-4.0.0-beta.1.tgz", + "@types/express": "5.0.6", + "@types/jest": "30.0.0", + "@types/node": "25.0.3", + "@typescript-eslint/eslint-plugin": "8.51.0", + "@typescript-eslint/parser": "8.51.0", + "eslint": "9.39.2", + "jest": "30.2.0", + "nodemon": "3.1.11", + "prettier": "3.7.4", + "ts-jest": "29.4.6", + "tsx": "4.21.0", + "typescript": "5.9.3", + "typescript-eslint": "^8.53.0" } } diff --git a/micro/src/api.ts b/micro/src/api.ts index 57f6655..5f492ca 100644 --- a/micro/src/api.ts +++ b/micro/src/api.ts @@ -1,12 +1,9 @@ -import { createMicroAPI } from "@expressots/adapter-express"; -import { Request, Response } from "express"; +import { micro } from "@expressots/adapter-express"; -const microAPI = createMicroAPI(); +const app = micro(); -const app = microAPI.build(); - -app.Route.get("/", (req: Request, res: Response) => { - res.send("Hello from ExpressoTS Micro API!"); +app.get("/", () => { + return "Hello from ExpressoTS Micro API!"; }); app.listen(3000); diff --git a/micro/test/api.spec.ts b/micro/test/api.spec.ts index 81e938e..9c77e11 100644 --- a/micro/test/api.spec.ts +++ b/micro/test/api.spec.ts @@ -1,26 +1,29 @@ -import { createMicroAPI } from "@expressots/adapter-express"; -import { Server } from "http"; -import request from "supertest"; +import { micro, MicroApp } from "@expressots/adapter-express"; +import { AddressInfo } from "net"; -describe("MicroAPI Root Route", () => { - let httpServer: Server; +describe("Micro API", () => { + let api: MicroApp; + let baseUrl: string; - beforeAll(() => { - const microAPI = createMicroAPI(); - const app = microAPI.build(); - app.listen(0); + beforeAll(async () => { + api = micro({ showBanner: false }); + api.get("/", () => "Hello from ExpressoTS Micro API!"); + await api.listen(0); - httpServer = microAPI.getHttpServer(); + const { port } = api.getHttpServer().address() as AddressInfo; + baseUrl = `http://localhost:${port}`; }); - afterAll(() => { - httpServer.close(); + afterAll(async () => { + await new Promise((resolve) => + api.getHttpServer().close(() => resolve()), + ); }); - it("should return Hello from ExpressoTS!", () => { - request(httpServer) - .get("/") - .expect(200) - .expect("Hello from ExpressoTS!"); + it("should return hello message on GET /", async () => { + const response = await fetch(`${baseUrl}/`); + + expect(response.status).toBe(200); + expect(await response.text()).toBe("Hello from ExpressoTS Micro API!"); }); }); diff --git a/micro/tsconfig.build.json b/micro/tsconfig.build.json index 4764b76..3579133 100644 --- a/micro/tsconfig.build.json +++ b/micro/tsconfig.build.json @@ -1,22 +1,18 @@ { + "extends": "./tsconfig.json", "compilerOptions": { - "target": "ES2021", - "module": "commonjs", "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "moduleResolution": "node", - "resolveJsonModule": true, + "rootDir": "./", + "declaration": false, + "declarationMap": false, + "sourceMap": false, "removeComments": true, - "noImplicitAny": false, - "strictPropertyInitialization": false, - "types": ["reflect-metadata", "node", "jest"], - "experimentalDecorators": true, - "emitDecoratorMetadata": true + "incremental": true, + "tsBuildInfoFile": "./dist/.tsbuildinfo", + "isolatedModules": true, + "types": ["node"], + "baseUrl": "." }, "include": ["src/**/*.ts"], - "exclude": ["node_modules", "dist", "test/**/*"] + "exclude": ["node_modules", "dist", "test"] } diff --git a/micro/tsconfig.json b/micro/tsconfig.json index 6ba3401..99d10f7 100644 --- a/micro/tsconfig.json +++ b/micro/tsconfig.json @@ -1,23 +1,46 @@ { "compilerOptions": { + // Language and Environment + "target": "ES2021", + "lib": ["ES2021"], + + // Modules "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "resolveJsonModule": true, + + // Emit + "outDir": "./dist", + "rootDir": "./", "declaration": true, + "declarationMap": true, + "sourceMap": true, "removeComments": true, + + // ExpressoTS Required: Decorators and Metadata "experimentalDecorators": true, "emitDecoratorMetadata": true, - "allowSyntheticDefaultImports": true, - "resolveJsonModule": true, - "target": "ES2021", - "sourceMap": true, - "rootDir": "./src", - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, + + // Path Mapping + "baseUrl": "./src", + + // Type Checking "strict": true, "noImplicitAny": false, "strictPropertyInitialization": false, "skipLibCheck": true, - "types": ["reflect-metadata", "node", "jest"] + + // Performance + "incremental": true, + "tsBuildInfoFile": "./dist/.tsbuildinfo", + + // Interop Constraints + "forceConsistentCasingInFileNames": true, + + // Type Definitions + "types": ["node", "jest"] }, - "include": ["src/**/*.ts"], - "exclude": ["node_modules", "dist", "test/**/*"] + "include": ["src/**/*.ts", "test/**/*.ts", "expressots.config.ts"], + "exclude": ["node_modules", "dist"] } diff --git a/non_opinionated/.eslintrc.js b/non_opinionated/.eslintrc.js deleted file mode 100644 index 9340aff..0000000 --- a/non_opinionated/.eslintrc.js +++ /dev/null @@ -1,29 +0,0 @@ -module.exports = { - parser: "@typescript-eslint/parser", - plugins: ["@typescript-eslint/eslint-plugin"], - extends: [ - "plugin:@typescript-eslint/recommended", - "plugin:prettier/recommended", - ], - root: true, - env: { - node: true, - jest: true, - }, - ignorePatterns: [".eslintrc.js"], - rules: { - "@typescript-eslint/interface-name-prefix": "off", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/no-inferrable-types": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/no-empty-interface": "off", - "@typescript-eslint/no-unused-vars": "off", - "no-trailing-spaces": ["error", { skipBlankLines: true }], - "no-multi-spaces": ["error", { ignoreEOLComments: true }], - "no-multi-spaces": "off", - "prettier/prettier": ["error", { endOfLine: "auto" }, { tabWidth: 4 }], - "prettier/prettier": "off", - }, -}; diff --git a/non_opinionated/.gitignore b/non_opinionated/.gitignore deleted file mode 100644 index 94709eb..0000000 --- a/non_opinionated/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ -# compiled output -/dist -/node_modules -.env - -# Logs -logs -*.log -npm-debug.log* -pnpm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* - -# OS -.DS_Store - -# Tests -/coverage -/.nyc_output - -# IDEs and editors -/.idea -.project -.classpath -.c9/ -*.launch -.settings/ -*.sublime-workspace - -# IDE - VSCode -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json \ No newline at end of file diff --git a/non_opinionated/.prettierrc b/non_opinionated/.prettierrc deleted file mode 100644 index d7708b4..0000000 --- a/non_opinionated/.prettierrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "singleQuote": false, - "trailingComma": "all", - "endOfLine": "auto", - "tabWidth": 4 -} \ No newline at end of file diff --git a/non_opinionated/README.md b/non_opinionated/README.md deleted file mode 100644 index c09d790..0000000 --- a/non_opinionated/README.md +++ /dev/null @@ -1,74 +0,0 @@ -# Expresso TS - -A Typescript + [Node.js]("https://nodejs.org/en/") lightweight framework for quick building scalable, easy to read and maintain, server-side applications ๐Ÿš€ - -## Philosophy - -ExpressoTS is a TypeScript lightweight framework for building scalable, readable and maintainable server-side applications. The framework provides a level of abstraction on top of common HTTP server framework Expressjs exposing their API's directly to the developer. This provides freedom and brings to the developer a tool that is well known and easy to use. - -## How to use - -### Executing in development mode - -```bash -npm run dev -``` - -### Generating production build - -```bash -npm run build -``` - -### Executing in production mode - -```bash -npm run prod -``` - -## Test - -How to run test scripts - -### Unit tests - -```bash -npm run test -``` - -### Test coverage - -```bash -npm run test:cov -``` - -## Documentation - -- Here is our [Official Documentation](https://expresso-ts.com/) -- Checkout our [First Steps documentation](https://expresso-ts.com/docs/overview/first-steps) -- Our [CLI Documentation](https://expresso-ts.com/docs/category/cli) - -## Questions - -For questions and support please use the Official [Discord Channel](https://discord.com/invite/PyPJfGK). We have a very active community there, that will be happy to help you. Post your questions in the channel called **HELP EXPRESSO TS** and forum called **help**. - -## Issues - -The [Issue Reporting Channel](https://github.com/expressots/expressots/issues) is for bug report and feature request **only**. - -Before you create an issue, please make sure you read the [Contribution Guidelines](CONTRIBUTING.md). - -## Support the project - -Expresso TS is an MIT-licensed open source project. It's an independent project with ongoing development made possible thanks to your support. If you'd like to help, please consider: - -- Become a sponsor on **[Sponsor no GitHub](https://github.com/sponsors/expressots)** -- Follow the **[organization](https://github.com/expressots)** on GitHub and Star โญ the project -- Subscribe to the Twitch channel: **[Richard Zampieri](https://www.twitch.tv/richardzampieri)** -- Join our **[Discord](https://discord.com/invite/PyPJfGK)** -- Contribute submitting **[issues and pull requests](https://github.com/expressots/expressots/issues/new/choose)** -- Share the project with your friends and colleagues - -## License - -ExpressoTS is **[MIT licensed](LICENSE.md)** \ No newline at end of file diff --git a/non_opinionated/expressots.config.ts b/non_opinionated/expressots.config.ts deleted file mode 100644 index cc58e55..0000000 --- a/non_opinionated/expressots.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ExpressoConfig, Pattern } from "@expressots/shared"; - -const config: ExpressoConfig = { - entryPoint: "main", - sourceRoot: "src", - scaffoldPattern: Pattern.KEBAB_CASE, - opinionated: false -}; - -export default config; \ No newline at end of file diff --git a/non_opinionated/jest.config.ts b/non_opinionated/jest.config.ts deleted file mode 100644 index cebb7e5..0000000 --- a/non_opinionated/jest.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { JestConfigWithTsJest } from "ts-jest"; - -const jestConfig: JestConfigWithTsJest = { - preset: "ts-jest", - rootDir: "./", - testEnvironment: "node", - verbose: true, - automock: false, - testMatch: ["**/*.test.ts", "**/*.spec.ts"], - coverageDirectory: "./coverage", - coverageReporters: ["text", "html", "json"], -}; - -export default jestConfig; diff --git a/non_opinionated/package.json b/non_opinionated/package.json deleted file mode 100644 index c9eedda..0000000 --- a/non_opinionated/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "nopinionated", - "version": "1.0.0", - "description": "", - "author": "", - "private": true, - "license": "UNLICENSED", - "scripts": { - "build": "expressots build", - "dev": "expressots dev", - "prod": "expressots prod", - "test": "jest", - "test:watch": "jest --watchAll", - "test:cov": "jest --coverage", - "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", - "lint": "eslint \"src/**/*.ts\" --fix" - }, - "keywords": [], - "dependencies": { - "@expressots/adapter-express": "3.0.0", - "@expressots/core": "3.0.0", - "@expressots/shared": "3.0.0" - }, - "devDependencies": { - "@expressots/cli": "3.0.0", - "@types/express": "5.0.0", - "@types/jest": "29.5.14", - "@types/node": "20.12.7", - "@typescript-eslint/eslint-plugin": "8.0.0", - "@typescript-eslint/parser": "8.0.0", - "eslint": "8.57.0", - "eslint-config-prettier": "9.0.0", - "eslint-plugin-prettier": "5.0.0", - "jest": "29.7.0", - "prettier": "3.2.5", - "supertest": "^7.0.0", - "ts-jest": "29.1.2", - "tsx": "4.19.2", - "typescript": "5.1.3" - } -} diff --git a/non_opinionated/src/main.ts b/non_opinionated/src/main.ts deleted file mode 100644 index 11413b3..0000000 --- a/non_opinionated/src/main.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { AppFactory } from "@expressots/core"; -import { App } from "./app"; - -AppFactory.create(App).then((app) => app.listen(3000)); \ No newline at end of file diff --git a/non_opinionated/test/app.controller.spec.ts b/non_opinionated/test/app.controller.spec.ts deleted file mode 100644 index 3c43fc3..0000000 --- a/non_opinionated/test/app.controller.spec.ts +++ /dev/null @@ -1,29 +0,0 @@ -import request from "supertest"; -import { Server } from "http"; - -import { AppFactory, StatusCode } from '@expressots/core'; -import { IWebServerBuilder } from '@expressots/shared'; -import { App } from "../src/app"; - - -describe("AppController", () => { - let server: Server; - let webServerBuilder: IWebServerBuilder - - beforeAll(async () => { - webServerBuilder = await AppFactory.create(App); - const app = await webServerBuilder.listen(3000); - server = await app.getHttpServer(); - }); - - afterAll(async () => { - await server.close(); - }); - - it("returns Hello Expresso TS!", async () => { - return request(server) - .get("/") - .expect(StatusCode.OK) - .expect("Hello from ExpressoTS!"); - }); -}); diff --git a/non_opinionated/tsconfig.build.json b/non_opinionated/tsconfig.build.json deleted file mode 100644 index 6f440dc..0000000 --- a/non_opinionated/tsconfig.build.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2021", - "module": "commonjs", - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "moduleResolution": "node", - "resolveJsonModule": true, - "removeComments": true, - "noImplicitAny": false, - "strictPropertyInitialization": false, - "types": ["node", "jest"], - "experimentalDecorators": true, - "emitDecoratorMetadata": true - }, - "include": ["src/**/*.ts"], - "exclude": ["node_modules", "dist", "test/**/*"] -} diff --git a/non_opinionated/tsconfig.json b/non_opinionated/tsconfig.json deleted file mode 100644 index 981d0b7..0000000 --- a/non_opinionated/tsconfig.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "ES2021", - "declaration": true, - "removeComments": true, - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "resolveJsonModule": true, - "sourceMap": true, - "rootDir": "./src", - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "noImplicitAny": false, - "strictPropertyInitialization": false, - "skipLibCheck": true, - "types": ["node", "jest"] - }, - "include": ["src/**/*.ts"], - "exclude": ["node_modules", "dist", "test/**/*"] -} diff --git a/opinionated/.env.development b/opinionated/.env.development deleted file mode 100644 index ea93d3e..0000000 --- a/opinionated/.env.development +++ /dev/null @@ -1,2 +0,0 @@ -# This file is used to set environment variables for development environment -PORT=3000 \ No newline at end of file diff --git a/opinionated/.env.production b/opinionated/.env.production deleted file mode 100644 index 71733c8..0000000 --- a/opinionated/.env.production +++ /dev/null @@ -1,2 +0,0 @@ -# This file is used to set environment variables for production environment -PORT=4000 \ No newline at end of file diff --git a/opinionated/.eslintrc.js b/opinionated/.eslintrc.js deleted file mode 100644 index f335b8d..0000000 --- a/opinionated/.eslintrc.js +++ /dev/null @@ -1,28 +0,0 @@ -module.exports = { - parser: "@typescript-eslint/parser", - plugins: ["@typescript-eslint/eslint-plugin"], - extends: [ - "plugin:@typescript-eslint/recommended", - "plugin:prettier/recommended", - ], - root: true, - env: { - node: true, - jest: true, - }, - ignorePatterns: [".eslintrc.js"], - rules: { - "@typescript-eslint/interface-name-prefix": "off", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/no-inferrable-types": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/no-empty-interface": "off", - "@typescript-eslint/no-unused-vars": "off", - "no-trailing-spaces": ["error", { skipBlankLines: true }], - "no-multi-spaces": ["error", { ignoreEOLComments: true }], - "no-multi-spaces": "off", - "prettier/prettier": "off", - }, -}; diff --git a/opinionated/.gitignore b/opinionated/.gitignore deleted file mode 100644 index 94709eb..0000000 --- a/opinionated/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ -# compiled output -/dist -/node_modules -.env - -# Logs -logs -*.log -npm-debug.log* -pnpm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* - -# OS -.DS_Store - -# Tests -/coverage -/.nyc_output - -# IDEs and editors -/.idea -.project -.classpath -.c9/ -*.launch -.settings/ -*.sublime-workspace - -# IDE - VSCode -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json \ No newline at end of file diff --git a/opinionated/.prettierrc b/opinionated/.prettierrc deleted file mode 100644 index d7708b4..0000000 --- a/opinionated/.prettierrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "singleQuote": false, - "trailingComma": "all", - "endOfLine": "auto", - "tabWidth": 4 -} \ No newline at end of file diff --git a/opinionated/README.md b/opinionated/README.md deleted file mode 100644 index c09d790..0000000 --- a/opinionated/README.md +++ /dev/null @@ -1,74 +0,0 @@ -# Expresso TS - -A Typescript + [Node.js]("https://nodejs.org/en/") lightweight framework for quick building scalable, easy to read and maintain, server-side applications ๐Ÿš€ - -## Philosophy - -ExpressoTS is a TypeScript lightweight framework for building scalable, readable and maintainable server-side applications. The framework provides a level of abstraction on top of common HTTP server framework Expressjs exposing their API's directly to the developer. This provides freedom and brings to the developer a tool that is well known and easy to use. - -## How to use - -### Executing in development mode - -```bash -npm run dev -``` - -### Generating production build - -```bash -npm run build -``` - -### Executing in production mode - -```bash -npm run prod -``` - -## Test - -How to run test scripts - -### Unit tests - -```bash -npm run test -``` - -### Test coverage - -```bash -npm run test:cov -``` - -## Documentation - -- Here is our [Official Documentation](https://expresso-ts.com/) -- Checkout our [First Steps documentation](https://expresso-ts.com/docs/overview/first-steps) -- Our [CLI Documentation](https://expresso-ts.com/docs/category/cli) - -## Questions - -For questions and support please use the Official [Discord Channel](https://discord.com/invite/PyPJfGK). We have a very active community there, that will be happy to help you. Post your questions in the channel called **HELP EXPRESSO TS** and forum called **help**. - -## Issues - -The [Issue Reporting Channel](https://github.com/expressots/expressots/issues) is for bug report and feature request **only**. - -Before you create an issue, please make sure you read the [Contribution Guidelines](CONTRIBUTING.md). - -## Support the project - -Expresso TS is an MIT-licensed open source project. It's an independent project with ongoing development made possible thanks to your support. If you'd like to help, please consider: - -- Become a sponsor on **[Sponsor no GitHub](https://github.com/sponsors/expressots)** -- Follow the **[organization](https://github.com/expressots)** on GitHub and Star โญ the project -- Subscribe to the Twitch channel: **[Richard Zampieri](https://www.twitch.tv/richardzampieri)** -- Join our **[Discord](https://discord.com/invite/PyPJfGK)** -- Contribute submitting **[issues and pull requests](https://github.com/expressots/expressots/issues/new/choose)** -- Share the project with your friends and colleagues - -## License - -ExpressoTS is **[MIT licensed](LICENSE.md)** \ No newline at end of file diff --git a/opinionated/jest.config.ts b/opinionated/jest.config.ts deleted file mode 100644 index bac9448..0000000 --- a/opinionated/jest.config.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { JestConfigWithTsJest } from "ts-jest"; - -const jestConfig: JestConfigWithTsJest = { - preset: "ts-jest", - rootDir: "./", - testEnvironment: "node", - verbose: true, - automock: false, - testMatch: ["**/*.test.ts", "**/*.spec.ts"], - coverageDirectory: "./coverage", - coverageReporters: ["text", "html", "json"], - moduleNameMapper: { - "^@entities/(.*)$": "/src/entities/$1", - "^@providers/(.*)$": "/src/providers/$1", - "^@repositories/(.*)$": "/src/repositories/$1", - "^@useCases/(.*)$": "/src/useCases/$1", - }, - modulePathIgnorePatterns: ["/dist/"], -}; - -export default jestConfig; diff --git a/opinionated/package.json b/opinionated/package.json deleted file mode 100644 index e21afa9..0000000 --- a/opinionated/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "opinionated", - "version": "1.0.0", - "description": "", - "author": "", - "private": true, - "license": "UNLICENSED", - "scripts": { - "build": "expressots build", - "dev": "expressots dev", - "prod": "expressots prod", - "test": "jest", - "test:watch": "jest --watchAll", - "test:cov": "jest --coverage", - "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", - "lint": "eslint \"src/**/*.ts\" --fix" - }, - "keywords": [], - "dependencies": { - "@expressots/adapter-express": "3.0.0", - "@expressots/core": "3.0.0", - "@expressots/shared": "3.0.0" - }, - "devDependencies": { - "@expressots/cli": "3.0.0", - "@types/express": "5.0.0", - "@types/jest": "29.5.14", - "@types/node": "20.12.7", - "@typescript-eslint/eslint-plugin": "8.0.0", - "@typescript-eslint/parser": "8.0.0", - "eslint": "8.57.0", - "eslint-config-prettier": "9.0.0", - "eslint-plugin-prettier": "5.0.0", - "jest": "29.7.0", - "prettier": "3.2.5", - "supertest": "^7.0.0", - "ts-jest": "29.1.2", - "tsconfig-paths": "4.2.0", - "tsx": "4.19.2", - "typescript": "5.1.3" - } -} diff --git a/opinionated/register-path.js b/opinionated/register-path.js deleted file mode 100644 index 116ebb7..0000000 --- a/opinionated/register-path.js +++ /dev/null @@ -1,17 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ - -const path = require("path"); -const tsconfigPaths = require("tsconfig-paths"); -const tsconfig = require("./tsconfig.build.json"); - -const baseUrl = tsconfig.compilerOptions.baseUrl || "."; -const outDir = tsconfig.compilerOptions.outDir || "."; - -let baseUrlPath = path.resolve(outDir, baseUrl); - -const explicitPaths = { - baseUrl: baseUrlPath, - paths: tsconfig.compilerOptions.paths, -}; - -tsconfigPaths.register(explicitPaths); diff --git a/opinionated/src/app.ts b/opinionated/src/app.ts deleted file mode 100644 index 343b6e8..0000000 --- a/opinionated/src/app.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { AppExpress } from "@expressots/adapter-express"; -import { AppContainer, Env } from "@expressots/core"; -import { AppModule } from "@useCases/app/app.module"; - -export class App extends AppExpress { - private config: AppContainer = this.configContainer([AppModule]); - - async globalConfiguration(): Promise { - this.setGlobalRoutePrefix("/v1"); - - this.initEnvironment("development", { - env: { - development: ".env.development", - production: ".env.production", - }, - }); - } - - async configureServices(): Promise { - this.Provider.register(Env); - - this.Middleware.addBodyParser(); - this.Middleware.setErrorHandler({ showStackTrace: true }); - } - - async postServerInitialization(): Promise { - if (await this.isDevelopment()) { - this.Provider.get(Env).checkFile(".env.development"); - } - } - - async serverShutdown(): Promise {} -} diff --git a/opinionated/src/env.ts b/opinionated/src/env.ts deleted file mode 100644 index a2d2d25..0000000 --- a/opinionated/src/env.ts +++ /dev/null @@ -1,11 +0,0 @@ -import pkg from "../package.json"; - -export const env = { - App: { - appName: pkg.name, - appVersion: pkg.version, - get Port() { - return process.env.PORT || 3000; - } - }, -}; \ No newline at end of file diff --git a/opinionated/src/main.ts b/opinionated/src/main.ts deleted file mode 100644 index 693468f..0000000 --- a/opinionated/src/main.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { AppFactory } from "@expressots/core"; -import { App } from "app"; -import { env } from "env"; - -AppFactory.create(App).then((app) => - app.listen(env.App.Port, { - appName: env.App.appName, - appVersion: env.App.appVersion, - }), -); diff --git a/opinionated/src/useCases/app/app.controller.ts b/opinionated/src/useCases/app/app.controller.ts deleted file mode 100644 index dab0c36..0000000 --- a/opinionated/src/useCases/app/app.controller.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { controller, Get } from "@expressots/adapter-express"; -import { inject } from "@expressots/core"; -import { AppUseCase } from "./app.usecase"; - -@controller("/") -export class AppController { - @inject(AppUseCase) private appUseCase: AppUseCase; - - @Get("/") - execute() { - return this.appUseCase.execute(); - } -} diff --git a/opinionated/src/useCases/app/app.module.ts b/opinionated/src/useCases/app/app.module.ts deleted file mode 100644 index e148a06..0000000 --- a/opinionated/src/useCases/app/app.module.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ContainerModule, CreateModule } from "@expressots/core"; -import { AppController } from "./app.controller"; - -export const AppModule: ContainerModule = CreateModule([AppController]); \ No newline at end of file diff --git a/opinionated/src/useCases/app/app.usecase.ts b/opinionated/src/useCases/app/app.usecase.ts deleted file mode 100644 index c491abb..0000000 --- a/opinionated/src/useCases/app/app.usecase.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { provide } from "@expressots/core"; - -@provide(AppUseCase) -export class AppUseCase { - execute() { - return "Hello from ExpressoTS!"; - } -} diff --git a/opinionated/test/app.controller.spec.ts b/opinionated/test/app.controller.spec.ts deleted file mode 100644 index 41f829e..0000000 --- a/opinionated/test/app.controller.spec.ts +++ /dev/null @@ -1,29 +0,0 @@ -import request from "supertest"; - -import { AppFactory, StatusCode } from "@expressots/core"; -import { IWebServerBuilder } from "@expressots/shared"; - -import { Server } from "http"; -import { App } from "../src/app"; - -describe("AppController", () => { - let server: Server; - let webServerBuilder: IWebServerBuilder; - - beforeAll(async () => { - webServerBuilder = await AppFactory.create(App); - const app = await webServerBuilder.listen(3000); - server = await app.getHttpServer(); - }); - - afterAll(async () => { - await server.close(); - }); - - it("returns a valid AppResponse", async () => { - return request(server) - .get("/v1") - .expect(StatusCode.OK) - .expect("Hello from ExpressoTS!"); - }); -}); diff --git a/opinionated/tsconfig.build.json b/opinionated/tsconfig.build.json deleted file mode 100644 index bed4d89..0000000 --- a/opinionated/tsconfig.build.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2021", - "module": "commonjs", - "declaration": false, - "sourceMap": false, - "removeComments": true, - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "resolveJsonModule": true, - "outDir": "./dist", - "rootDir": "./", - "baseUrl": "./src", - "paths": { - "@entities/*": ["entities/*"], - "@providers/*": ["providers/*"], - "@repositories/*": ["repositories/*"], - "@useCases/*": ["useCases/*"] - }, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "allowJs": true, - "noImplicitAny": false, - "strictPropertyInitialization": false, - "skipLibCheck": true, - "types": ["node", "jest"] - }, - "include": ["src/**/*.ts"], - "exclude": ["node_modules", "dist", "test"] -} diff --git a/opinionated/tsconfig.json b/opinionated/tsconfig.json deleted file mode 100644 index 6e94d5e..0000000 --- a/opinionated/tsconfig.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2021", - "module": "commonjs", - "lib": ["es2021"], - "declaration": false, - "removeComments": true, - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "resolveJsonModule": true, - "sourceMap": false, - "outDir": "./dist", - "rootDir": "./", - "baseUrl": "./src", - "paths": { - "@entities/*": ["entities/*"], - "@providers/*": ["providers/*"], - "@repositories/*": ["repositories/*"], - "@useCases/*": ["useCases/*"] - }, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "allowJs": true, - "noImplicitAny": false, - "strictPropertyInitialization": false, - "skipLibCheck": true, - "types": ["node", "jest"] - }, - "include": ["src/**/*.ts"], - "exclude": ["node_modules", "dist", "test/**/*.ts"] -} diff --git a/package.json b/package.json index 1f03d1d..f07964b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@expressots/templates", - "version": "3.0.0", + "version": "4.0.0-beta.1", "description": "Expressots templates", "author": "Richard Zampieri", "license": "MIT", diff --git a/provider/package.json b/provider/package.json index 14f8b22..d59cf0d 100644 --- a/provider/package.json +++ b/provider/package.json @@ -64,7 +64,7 @@ "lint:fix": "eslint \"src/**/*.ts\" --fix" }, "dependencies": { - "@expressots/core": "3.0.0" + "@expressots/core": "^4.0.0" }, "devDependencies": { "@commitlint/cli": "19.5.0",