diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 0000000..c30bbf5 --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,108 @@ +name: Nightly Checks + +on: + workflow_dispatch: + pull_request: + branches: [main] + push: + branches: [main] + schedule: + # Run checks nightly at midnight + - cron: '0 0 * * *' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: {} + +jobs: + prepare: + name: Prepare Targets + runs-on: ubuntu-latest + timeout-minutes: 10 + outputs: + examples: ${{ steps.targets.outputs.examples }} + templates: ${{ steps.targets.outputs.templates }} + steps: + - uses: actions/checkout@v4 + - name: Find all build targets + id: targets + run: | + examples=$( + find templates -mindepth 1 -maxdepth 1 -type d | + jq -cnR '[inputs | select(length > 0)]' + ) + templates=$( + find templates -mindepth 1 -maxdepth 1 -type d | + jq -cnR '[inputs | select(length > 0)]' + ) + echo "examples=${examples}" >> $GITHUB_OUTPUT + echo "templates=${templates}" >> $GITHUB_OUTPUT + + examples: + name: Check Examples + runs-on: ubuntu-latest + timeout-minutes: 10 + needs: [prepare] + strategy: + matrix: + example: ${{ fromJSON(needs.prepare.outputs.examples) }} + fail-fast: true + steps: + - uses: actions/checkout@v4 + with: + sparse-checkout: ${{ matrix.example }} + sparse-checkout-cone-mode: false + - name: Move files to root + run: | + ls -lah + shopt -s dotglob + mv ${{ matrix.example }}/* . + rm -rf examples + ls -lah + - name: Install pnpm + uses: pnpm/action-setup@v3 + - name: Install node + uses: actions/setup-node@v4 + with: + node-version: 20.14.0 + - name: Install dependencies + run: pnpm install + - run: pnpm lint + - run: pnpm check + - run: pnpm test + + templates: + name: Check Templates + runs-on: ubuntu-latest + timeout-minutes: 10 + needs: [prepare] + strategy: + matrix: + template: ${{ fromJSON(needs.prepare.outputs.templates) }} + fail-fast: true + steps: + - uses: actions/checkout@v4 + with: + sparse-checkout: ${{ matrix.template }} + sparse-checkout-cone-mode: false + - name: Move files to root + run: | + ls -lah + shopt -s dotglob + mv ${{ matrix.template }}/* . + rm -rf templates + ls -lah + - name: Install pnpm + uses: pnpm/action-setup@v3 + - name: Install node + uses: actions/setup-node@v4 + with: + node-version: 20.14.0 + - name: Install dependencies + run: pnpm install + - run: pnpm lint + - run: pnpm check + - run: pnpm build + - run: pnpm test diff --git a/README.md b/README.md index 984342a..0b7e7df 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ +[![Nightly Build](https://github.com/Effect-TS/examples/workflows/Nightly%20Checks/badge.svg)](https://github.com/Effect-TS/examples/actions) + # Effect Examples ## Create Effect App -The easiest way to get started with Effect is by using `create-effect-app`. +The easiest way to get started with Effect is by using `create-effect-app`. -This CLI tool enables you to quickly bootstrap a project with Effect, with everything pre-configured for you. +This CLI tool enables you to quickly bootstrap a project with Effect, with everything pre-configured for you. -You can create a new project using one of our [project templates](./templates) or by using one of the [official Effect examples](./examples). +You can create a new project using one of our [project templates](./templates) or by using one of the [official Effect examples](./examples). See [the documentation](./packages/create-effect-app/README.md) for more information. @@ -22,13 +24,13 @@ The available examples include: ## Templates -This repository contains templates which can be used to quickly bootstrap a new project with Effect via the `create-effect-app` CLI tool. +This repository contains templates which can be used to quickly bootstrap a new project with Effect via the `create-effect-app` CLI tool. These templates were developed to mirror the project configuration recommneded by the Effect core team and are thus somewhat opinionated. ### Basic -The `basic` template is meant to serve as the foundation for building a single package or library with Effect. +The `basic` template is meant to serve as the foundation for building a single package or library with Effect. The template features: @@ -44,7 +46,7 @@ For more information, see the template [README](./templates/basic/README.md). ### Monorepo -The `monorepo` template is meant to serve as the foundation for building multiple packages or applications with Effect. +The `monorepo` template is meant to serve as the foundation for building multiple packages or applications with Effect. The template features everything included with the `basic` template in addition to: @@ -61,4 +63,3 @@ The template features everything included with the `basic` template, except with - Pre-configured build pipeline is via [`tsup`](https://github.com/egoist/tsup) to support bundling to a single file For more information, see the template [README](./templates/cli/README.md). - diff --git a/examples/http-server/.prettierrc.json b/examples/http-server/.prettierrc.json deleted file mode 100644 index cce9d3c..0000000 --- a/examples/http-server/.prettierrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "semi": false -} diff --git a/examples/http-server/eslint.config.mjs b/examples/http-server/eslint.config.mjs new file mode 100644 index 0000000..90e2807 --- /dev/null +++ b/examples/http-server/eslint.config.mjs @@ -0,0 +1,122 @@ +import { fixupPluginRules } from "@eslint/compat" +import { FlatCompat } from "@eslint/eslintrc" +import js from "@eslint/js" +import tsParser from "@typescript-eslint/parser" +import codegen from "eslint-plugin-codegen" +import deprecation from "eslint-plugin-deprecation" +import _import from "eslint-plugin-import" +import simpleImportSort from "eslint-plugin-simple-import-sort" +import sortDestructureKeys from "eslint-plugin-sort-destructure-keys" +import path from "node:path" +import { fileURLToPath } from "node:url" + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all +}) + +export default [ + { + ignores: ["**/dist", "**/build", "**/docs", "**/*.md"] + }, + ...compat.extends( + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:@effect/recommended" + ), + { + plugins: { + deprecation, + import: fixupPluginRules(_import), + "sort-destructure-keys": sortDestructureKeys, + "simple-import-sort": simpleImportSort, + codegen + }, + + languageOptions: { + parser: tsParser, + ecmaVersion: 2018, + sourceType: "module" + }, + + settings: { + "import/parsers": { + "@typescript-eslint/parser": [".ts", ".tsx"] + }, + + "import/resolver": { + typescript: { + alwaysTryTypes: true + } + } + }, + + rules: { + "codegen/codegen": "error", + "no-fallthrough": "off", + "no-irregular-whitespace": "off", + "object-shorthand": "error", + "prefer-destructuring": "off", + "sort-imports": "off", + + "no-restricted-syntax": ["error", { + selector: "CallExpression[callee.property.name='push'] > SpreadElement.arguments", + message: "Do not use spread arguments in Array.push" + }], + + "no-unused-vars": "off", + "prefer-rest-params": "off", + "prefer-spread": "off", + "import/first": "error", + "import/newline-after-import": "error", + "import/no-duplicates": "error", + "import/no-unresolved": "off", + "import/order": "off", + "simple-import-sort/imports": "off", + "sort-destructure-keys/sort-destructure-keys": "error", + "deprecation/deprecation": "off", + + "@typescript-eslint/array-type": ["warn", { + default: "generic", + readonly: "generic" + }], + + "@typescript-eslint/member-delimiter-style": 0, + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/ban-types": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-empty-interface": "off", + "@typescript-eslint/consistent-type-imports": "warn", + + "@typescript-eslint/no-unused-vars": ["error", { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_" + }], + + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/camelcase": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/no-array-constructor": "off", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/no-namespace": "off", + + "@effect/dprint": ["error", { + config: { + indentWidth: 2, + lineWidth: 120, + semiColons: "asi", + quoteStyle: "alwaysDouble", + trailingCommas: "never", + operatorPosition: "maintain", + "arrowFunction.useParentheses": "force" + } + }] + } + } +] diff --git a/examples/http-server/package.json b/examples/http-server/package.json index 3794712..d1336bf 100644 --- a/examples/http-server/package.json +++ b/examples/http-server/package.json @@ -4,7 +4,10 @@ "type": "module", "description": "", "scripts": { + "check": "tsc -b tsconfig.json", "dev": "tsx --env-file=.env --watch src/main.ts", + "lint": "eslint \"**/{src,test,examples,scripts,dtslint}/**/*.{ts,mjs}\"", + "lint-fix": "pnpm lint --fix", "test": "vitest" }, "keywords": [], @@ -29,10 +32,22 @@ "@effect/schema": "^0.72.1", "@effect/sql": "^0.10.1", "@effect/sql-sqlite-node": "^0.10.1", + "@eslint/compat": "1.1.1", + "@eslint/eslintrc": "3.1.0", + "@eslint/js": "9.10.0", "@opentelemetry/exporter-trace-otlp-http": "^0.53.0", "@opentelemetry/sdk-trace-base": "^1.26.0", "@opentelemetry/sdk-trace-node": "^1.26.0", + "@typescript-eslint/eslint-plugin": "^8.4.0", + "@typescript-eslint/parser": "^8.4.0", "effect": "^3.7.1", + "eslint": "^9.10.0", + "eslint-import-resolver-typescript": "^3.6.3", + "eslint-plugin-codegen": "^0.28.0", + "eslint-plugin-deprecation": "^3.0.0", + "eslint-plugin-import": "^2.30.0", + "eslint-plugin-simple-import-sort": "^12.1.1", + "eslint-plugin-sort-destructure-keys": "^2.0.0", "uuid": "^10.0.0" } } diff --git a/examples/http-server/src/Accounts/Policy.ts b/examples/http-server/src/Accounts/Policy.ts index f97387a..7108a7e 100644 --- a/examples/http-server/src/Accounts/Policy.ts +++ b/examples/http-server/src/Accounts/Policy.ts @@ -1,5 +1,5 @@ import { Effect, Layer } from "effect" -import type { policy } from "../Domain/Policy.js" +import { policy } from "../Domain/Policy.js" import type { UserId } from "../Domain/User.js" // eslint-disable-next-line require-yield diff --git a/examples/http-server/src/Groups/Policy.ts b/examples/http-server/src/Groups/Policy.ts index a5754a3..f4501c9 100644 --- a/examples/http-server/src/Groups/Policy.ts +++ b/examples/http-server/src/Groups/Policy.ts @@ -1,6 +1,6 @@ import { Effect, Layer } from "effect" import type { Group } from "../Domain/Group.js" -import type { policy } from "../Domain/Policy.js" +import { policy } from "../Domain/Policy.js" // eslint-disable-next-line require-yield const make = Effect.gen(function*() { diff --git a/examples/http-server/src/People/Policy.ts b/examples/http-server/src/People/Policy.ts index be24ddf..72fc79c 100644 --- a/examples/http-server/src/People/Policy.ts +++ b/examples/http-server/src/People/Policy.ts @@ -1,7 +1,7 @@ import { Effect, Layer, pipe } from "effect" import type { GroupId } from "../Domain/Group.js" import type { Person, PersonId } from "../Domain/Person.js" -import type { policy, policyCompose, Unauthorized } from "../Domain/Policy.js" +import { policy, policyCompose, Unauthorized } from "../Domain/Policy.js" import { Groups } from "../Groups.js" import { GroupsPolicy } from "../Groups/Policy.js" import { People } from "../People.js" diff --git a/examples/http-server/tsconfig.base.json b/examples/http-server/tsconfig.base.json index c31298c..d5d2362 100644 --- a/examples/http-server/tsconfig.base.json +++ b/examples/http-server/tsconfig.base.json @@ -1,20 +1,53 @@ { "compilerOptions": { + "strict": true, + "exactOptionalPropertyTypes": true, + "moduleDetection": "force", "composite": true, - "incremental": true, - "outDir": "dist", + "downlevelIteration": true, "resolveJsonModule": true, + "esModuleInterop": false, + "declaration": true, "skipLibCheck": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, "moduleResolution": "NodeNext", - "module": "NodeNext", - "allowSyntheticDefaultImports": true, - "exactOptionalPropertyTypes": true, - "noErrorTruncation": true, - "lib": ["ESNext", "DOM"], + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable" + ], + "types": [], + "isolatedModules": true, "sourceMap": true, - "strict": true, + "declarationMap": true, + "noImplicitReturns": false, + "noUnusedLocals": true, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true, + "noEmitOnError": false, + "noErrorTruncation": false, + "allowJs": false, + "checkJs": false, + "forceConsistentCasingInFileNames": true, + "noImplicitAny": true, + "noImplicitThis": true, + "noUncheckedIndexedAccess": false, "strictNullChecks": true, - "target": "ESNext", - "plugins": [{ "name": "@effect/language-service" }] + "baseUrl": ".", + "target": "ES2022", + "module": "NodeNext", + "incremental": true, + "removeComments": false, + "plugins": [ + { + "name": "@effect/language-service" + } + ], + "paths": { + "app/*": [ + "src/*.js" + ] + } } } diff --git a/examples/http-server/tsconfig.json b/examples/http-server/tsconfig.json index 6cefa88..9f919fd 100644 --- a/examples/http-server/tsconfig.json +++ b/examples/http-server/tsconfig.json @@ -1,11 +1,12 @@ { - "compilerOptions": { - "composite": true, - "incremental": true - }, - "files": [], + "extends": "./tsconfig.base.json", + "include": [], "references": [ - { "path": "./tsconfig.src.json" }, - { "path": "./tsconfig.test.json" } + { + "path": "tsconfig.src.json" + }, + { + "path": "tsconfig.test.json" + } ] } diff --git a/examples/http-server/tsconfig.src.json b/examples/http-server/tsconfig.src.json index c276b25..d6ce436 100644 --- a/examples/http-server/tsconfig.src.json +++ b/examples/http-server/tsconfig.src.json @@ -1,9 +1,14 @@ { "extends": "./tsconfig.base.json", + "include": [ + "src" + ], "compilerOptions": { + "types": [ + "node" + ], "outDir": "dist", - "rootDir": "src", - "tsBuildInfoFile": "./tsconfig.src.tsbuildinfo" - }, - "include": ["src/**/*"] + "tsBuildInfoFile": ".tsbuildinfo/src.tsbuildinfo", + "rootDir": "src" + } } diff --git a/examples/http-server/tsconfig.test.json b/examples/http-server/tsconfig.test.json index fa5fce7..0453366 100644 --- a/examples/http-server/tsconfig.test.json +++ b/examples/http-server/tsconfig.test.json @@ -1,14 +1,19 @@ { "extends": "./tsconfig.base.json", + "include": [ + "test" + ], + "references": [ + { + "path": "tsconfig.src.json" + } + ], "compilerOptions": { - "outDir": "dist", + "types": [ + "node" + ], + "tsBuildInfoFile": ".tsbuildinfo/test.tsbuildinfo", "rootDir": "test", - "baseUrl": "./", - "tsBuildInfoFile": "./tsconfig.test.tsbuildinfo", - "paths": { - "app/*": ["src/*.js"] - } - }, - "include": ["test/**/*"], - "references": [{ "path": "./tsconfig.src.json" }] + "noEmit": true + } } diff --git a/flake.lock b/flake.lock index d937412..16cb372 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1725194671, - "narHash": "sha256-tLGCFEFTB5TaOKkpfw3iYT9dnk4awTP/q4w+ROpMfuw=", + "lastModified": 1726583932, + "narHash": "sha256-zACxiQx8knB3F8+Ze+1BpiYrI+CbhxyWpcSID9kVhkQ=", "owner": "nixos", "repo": "nixpkgs", - "rev": "b833ff01a0d694b910daca6e2ff4a3f26dee478c", + "rev": "658e7223191d2598641d50ee4e898126768fe847", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index ca8f98c..e5d2a59 100644 --- a/flake.nix +++ b/flake.nix @@ -2,19 +2,26 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; }; - outputs = {nixpkgs, ...}: let - forAllSystems = function: - nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed - (system: function nixpkgs.legacyPackages.${system}); - in { - formatter = forAllSystems (pkgs: pkgs.alejandra); - devShells = forAllSystems (pkgs: { - default = pkgs.mkShell { - packages = with pkgs; [ - corepack - nodejs - ]; - }; - }); - }; + outputs = + { nixpkgs, ... }: + let + forAllSystems = + function: + nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed ( + system: function nixpkgs.legacyPackages.${system} + ); + in + { + formatter = forAllSystems (pkgs: pkgs.alejandra); + devShells = forAllSystems (pkgs: { + default = pkgs.mkShell { + packages = with pkgs; [ + corepack + findutils + jq + nodejs + ]; + }; + }); + }; }