├── packages └── open-source-stack │ ├── tests │ └── setup.ts │ ├── src │ ├── index.test.ts │ └── index.ts │ ├── tsdown.config.ts │ ├── README.md │ ├── vitest.config.ts │ ├── CHANGELOG.md │ ├── tsconfig.json │ └── package.json ├── test-apps ├── react-router-cjs │ ├── .gitignore │ ├── public │ │ └── favicon.ico │ ├── app │ │ ├── routes.ts │ │ ├── entry.client.tsx │ │ ├── root.tsx │ │ ├── routes │ │ │ └── _index.tsx │ │ └── entry.server.tsx │ ├── vite.config.ts │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ └── .eslintrc.cjs └── react-router-esm │ ├── .gitignore │ ├── public │ └── favicon.ico │ ├── app │ ├── routes.ts │ ├── entry.client.tsx │ ├── root.tsx │ ├── routes │ │ └── _index.tsx │ └── entry.server.tsx │ ├── vite.config.ts │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ └── .eslintrc.cjs ├── pnpm-workspace.yaml ├── knip.json ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.yml │ └── bug_report.yml ├── workflows │ ├── publish-commit.yaml │ ├── publish.yaml │ └── pull-request.yaml └── PULL_REQUEST_TEMPLATE.md ├── lefthook.yml ├── .changeset ├── config.json └── README.md ├── SECURITY.MD ├── LICENSE ├── .vscode └── settings.json ├── nx.json ├── biome.json ├── package.json ├── .gitignore ├── README.md └── CODE_OF_CONDUCT.md /packages/open-source-stack/tests/setup.ts: -------------------------------------------------------------------------------- 1 | // Setup your test environment here 2 | -------------------------------------------------------------------------------- /test-apps/react-router-cjs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /.cache 4 | /build 5 | .env 6 | -------------------------------------------------------------------------------- /test-apps/react-router-esm/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /.cache 4 | /build 5 | .env 6 | -------------------------------------------------------------------------------- /test-apps/react-router-cjs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forge-42/open-source-stack/HEAD/test-apps/react-router-cjs/public/favicon.ico -------------------------------------------------------------------------------- /test-apps/react-router-esm/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forge-42/open-source-stack/HEAD/test-apps/react-router-esm/public/favicon.ico -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | - test-apps/* 4 | onlyBuiltDependencies: 5 | - '@biomejs/biome' 6 | - esbuild 7 | - lefthook 8 | -------------------------------------------------------------------------------- /test-apps/react-router-esm/app/routes.ts: -------------------------------------------------------------------------------- 1 | import { route } from "@react-router/dev/routes"; 2 | 3 | export default [ 4 | route("/", "./routes/_index.tsx") 5 | ] -------------------------------------------------------------------------------- /test-apps/react-router-cjs/app/routes.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { route } from "@react-router/dev/routes"; 3 | 4 | export default [ 5 | route("/", "./routes/_index.tsx") 6 | ] -------------------------------------------------------------------------------- /packages/open-source-stack/src/index.test.ts: -------------------------------------------------------------------------------- 1 | import { test } from "." 2 | 3 | describe("test", () => { 4 | it("should work", () => { 5 | expect(test()).toBeUndefined() 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /knip.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/knip@5/schema.json", 3 | "ignoreBinaries": ["pkg-pr-new"], 4 | "ignoreDependencies": ["@changesets/cli", "lefthook"], 5 | "ignoreWorkspaces": ["test-apps/**"] 6 | } 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Ask a Question 4 | url: https://discord.gg/uZ39dxhj 5 | about: Ask questions and discuss with other community members 6 | -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | pre-commit: 2 | parallel: true 3 | commands: 4 | check: 5 | run: pnpm run check --staged --fix --no-errors-on-unmatched 6 | stage_fixed: true 7 | test: 8 | run: pnpm run test 9 | -------------------------------------------------------------------------------- /packages/open-source-stack/tsdown.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsdown" 2 | 3 | export default defineConfig({ 4 | entry: ["src/index.ts"], 5 | sourcemap: true, 6 | dts: true, 7 | minify: false, 8 | clean: false, 9 | format: ["esm", "cjs"], 10 | outDir: "dist", 11 | }) 12 | -------------------------------------------------------------------------------- /test-apps/react-router-cjs/vite.config.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { reactRouter } from "@react-router/dev/vite"; 3 | import { defineConfig } from "vite"; 4 | import tsconfigPaths from "vite-tsconfig-paths"; 5 | 6 | export default defineConfig({ 7 | plugins: [reactRouter(), tsconfigPaths()], 8 | }); 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.2/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": true, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /SECURITY.MD: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 1.0.x | :white_check_mark: | 8 | 9 | ## Reporting a Vulnerability 10 | 11 | In case of a vulnerability please reach out to active maintainers of the project and report it to them. -------------------------------------------------------------------------------- /packages/open-source-stack/README.md: -------------------------------------------------------------------------------- 1 | # Open-source stack 2 | 3 | This is a placeholder npm package for the open-source-stack by Forge 42. 4 | It is a full starter stack to develop CJS/ESM compatible npm packages with TypeScript, Vitest, Biome and GitHub Actions. 5 | Find the template here: 6 | https://github.com/forge-42/open-source-stack -------------------------------------------------------------------------------- /packages/open-source-stack/src/index.ts: -------------------------------------------------------------------------------- 1 | // This is your packages entry point, everything exported from here will be accessible to the end-user. 2 | export const test = (): void => { 3 | // biome-ignore lint/suspicious/noConsole: test output by running this in one of the apps 4 | console.log("This is a test function from the open-source-stack package.") 5 | } 6 | -------------------------------------------------------------------------------- /test-apps/react-router-esm/vite.config.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { reactRouter } from "@react-router/dev/vite"; 3 | import { defineConfig } from "vite"; 4 | import tsconfigPaths from "vite-tsconfig-paths"; 5 | import { reactRouterDevTools } from "react-router-devtools"; 6 | 7 | export default defineConfig({ 8 | plugins: [ reactRouterDevTools(),reactRouter(), tsconfigPaths()], 9 | }); 10 | -------------------------------------------------------------------------------- /packages/open-source-stack/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from "vitest/config" 3 | 4 | export default defineConfig({ 5 | test: { 6 | setupFiles: ["./tests/setup.ts"], 7 | environment: "node", 8 | globals: true, 9 | 10 | coverage: { 11 | all: false, 12 | provider: "v8", 13 | reporter: ["json-summary", "html"], 14 | thresholds: { 15 | statements: 80, 16 | branches: 80, 17 | functions: 80, 18 | lines: 80, 19 | }, 20 | }, 21 | }, 22 | }) 23 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /packages/open-source-stack/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # open-source-stack 2 | 3 | ## 1.1.2 4 | 5 | ### Patch Changes 6 | 7 | - 45ae372: Updated dependencies, fixed a bug with staging script 8 | - ecd88f8: Migrated to tsdown from tsup 9 | 10 | ## 1.1.1 11 | 12 | ### Patch Changes 13 | 14 | - 34f867e: Added provencance to the package release 15 | 16 | ## 1.1.0 17 | 18 | ### Minor Changes 19 | 20 | - f2fbd38: Added changesets to the project 21 | - e051f2f: We have migrated the open-source-stack to use pnpm workspaces with changesets instead of the old npm approach with npm workspaces. 22 | -------------------------------------------------------------------------------- /test-apps/react-router-cjs/app/entry.client.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * By default, Remix will handle hydrating your app on the client for you. 3 | * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ 4 | * For more information, see https://remix.run/file-conventions/entry.client 5 | */ 6 | 7 | import { HydratedRouter } from "react-router/dom" 8 | import { startTransition, StrictMode } from "react"; 9 | import { hydrateRoot } from "react-dom/client"; 10 | 11 | startTransition(() => { 12 | hydrateRoot( 13 | document, 14 | 15 | 16 | 17 | ); 18 | }); 19 | -------------------------------------------------------------------------------- /test-apps/react-router-esm/app/entry.client.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * By default, Remix will handle hydrating your app on the client for you. 3 | * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ 4 | * For more information, see https://remix.run/file-conventions/entry.client 5 | */ 6 | 7 | import { HydratedRouter } from "react-router/dom" 8 | import { startTransition, StrictMode } from "react"; 9 | import { hydrateRoot } from "react-dom/client"; 10 | 11 | startTransition(() => { 12 | hydrateRoot( 13 | document, 14 | 15 | 16 | 17 | ); 18 | }); 19 | -------------------------------------------------------------------------------- /test-apps/react-router-cjs/app/root.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Links, 3 | Meta, 4 | Outlet, 5 | Scripts, 6 | ScrollRestoration, 7 | } from "react-router"; 8 | 9 | export function Layout({ children }: { children: React.ReactNode }) { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {children} 20 | 21 | 22 | 23 | 24 | ); 25 | } 26 | 27 | export default function App() { 28 | return ; 29 | } 30 | -------------------------------------------------------------------------------- /test-apps/react-router-esm/app/root.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Links, 3 | Meta, 4 | Outlet, 5 | Scripts, 6 | ScrollRestoration, 7 | } from "react-router"; 8 | 9 | export function Layout({ children }: { children: React.ReactNode }) { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {children} 20 | 21 | 22 | 23 | 24 | ); 25 | } 26 | 27 | export default function App() { 28 | return ; 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/publish-commit.yaml: -------------------------------------------------------------------------------- 1 | name: 🚀 pkg-pr-new 2 | on: [push, pull_request] 3 | 4 | concurrency: 5 | group: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref }} 6 | cancel-in-progress: true 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v2 14 | 15 | - name: Install pnpm 16 | uses: pnpm/action-setup@v4 17 | 18 | - run: corepack enable 19 | - uses: actions/setup-node@v4 20 | with: 21 | node-version-file: "package.json" 22 | 23 | - name: Install dependencies 24 | run: pnpm install 25 | 26 | - name: Build 27 | run: pnpm run build:all 28 | 29 | - run: npx pkg-pr-new publish ./packages/* 30 | -------------------------------------------------------------------------------- /test-apps/react-router-cjs/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Remix + Vite! 2 | 3 | 📖 See the [Remix docs](https://remix.run/docs) and the [Remix Vite docs](https://remix.run/docs/en/main/guides/vite) for details on supported features. 4 | 5 | ## Development 6 | 7 | Run the Vite dev server: 8 | 9 | ```shellscript 10 | npm run dev 11 | ``` 12 | 13 | ## Deployment 14 | 15 | First, build your app for production: 16 | 17 | ```sh 18 | npm run build 19 | ``` 20 | 21 | Then run the app in production mode: 22 | 23 | ```sh 24 | npm start 25 | ``` 26 | 27 | Now you'll need to pick a host to deploy it to. 28 | 29 | ### DIY 30 | 31 | If you're familiar with deploying Node applications, the built-in Remix app server is production-ready. 32 | 33 | Make sure to deploy the output of `npm run build` 34 | 35 | - `build/server` 36 | - `build/client` 37 | -------------------------------------------------------------------------------- /test-apps/react-router-esm/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Remix + Vite! 2 | 3 | 📖 See the [Remix docs](https://remix.run/docs) and the [Remix Vite docs](https://remix.run/docs/en/main/guides/vite) for details on supported features. 4 | 5 | ## Development 6 | 7 | Run the Vite dev server: 8 | 9 | ```shellscript 10 | npm run dev 11 | ``` 12 | 13 | ## Deployment 14 | 15 | First, build your app for production: 16 | 17 | ```sh 18 | npm run build 19 | ``` 20 | 21 | Then run the app in production mode: 22 | 23 | ```sh 24 | npm start 25 | ``` 26 | 27 | Now you'll need to pick a host to deploy it to. 28 | 29 | ### DIY 30 | 31 | If you're familiar with deploying Node applications, the built-in Remix app server is production-ready. 32 | 33 | Make sure to deploy the output of `npm run build` 34 | 35 | - `build/server` 36 | - `build/client` 37 | -------------------------------------------------------------------------------- /test-apps/react-router-cjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "**/*.ts", 4 | "**/*.tsx", 5 | "**/.server/**/*.ts", 6 | "**/.server/**/*.tsx", 7 | "**/.client/**/*.ts", 8 | "**/.client/**/*.tsx" 9 | ], 10 | "compilerOptions": { 11 | "lib": ["DOM", "DOM.Iterable", "ES2022"], 12 | "types": ["@react-router/node", "vite/client"], 13 | "isolatedModules": true, 14 | "esModuleInterop": true, 15 | "jsx": "react-jsx", 16 | "moduleResolution": "Node", 17 | "resolveJsonModule": true, 18 | "strict": true, 19 | "allowJs": true, 20 | "skipLibCheck": true, 21 | "forceConsistentCasingInFileNames": true, 22 | "baseUrl": ".", 23 | "paths": { 24 | "~/*": ["./app/*"] 25 | }, 26 | 27 | // Vite takes care of building everything, not tsc. 28 | "noEmit": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test-apps/react-router-esm/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "**/*.ts", 4 | "**/*.tsx", 5 | "**/.server/**/*.ts", 6 | "**/.server/**/*.tsx", 7 | "**/.client/**/*.ts", 8 | "**/.client/**/*.tsx" 9 | ], 10 | "compilerOptions": { 11 | "lib": ["DOM", "DOM.Iterable", "ES2022"], 12 | "types": ["@react-router/node", "vite/client"], 13 | "isolatedModules": true, 14 | "esModuleInterop": true, 15 | "jsx": "react-jsx", 16 | "module": "ESNext", 17 | "moduleResolution": "Bundler", 18 | "resolveJsonModule": true, 19 | "target": "ES2022", 20 | "strict": true, 21 | "allowJs": true, 22 | "skipLibCheck": true, 23 | "forceConsistentCasingInFileNames": true, 24 | "baseUrl": ".", 25 | "paths": { 26 | "~/*": ["./app/*"] 27 | }, 28 | 29 | // Vite takes care of building everything, not tsc. 30 | "noEmit": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/open-source-stack/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 4 | "moduleResolution": "Bundler", 5 | "module": "ESNext" /* Specify what module code is generated. */, 6 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 7 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 8 | "strict": true /* Enable all strict type-checking options. */, 9 | "skipLibCheck": true /* Skip type checking all .d.ts files. */, 10 | "types": ["vitest/globals"], 11 | "rootDir": ".", 12 | "outDir": "./dist", 13 | "noEmit": true 14 | }, 15 | "include": ["src/**/*", "tests/**/*"], 16 | "exclude": ["node_modules", "dist"] 17 | } 18 | -------------------------------------------------------------------------------- /test-apps/react-router-esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-router-esm", 3 | "private": true, 4 | "sideEffects": false, 5 | "type": "module", 6 | "scripts": { 7 | "build": "react-router build", 8 | "dev": "react-router dev", 9 | "start": "react-router-serve ./build/server/index.js", 10 | "typecheck": "tsc" 11 | }, 12 | "dependencies": { 13 | "@react-router/node": "^7.5.0", 14 | "@react-router/serve": "^7.5.0", 15 | "isbot": "^5.1.26", 16 | "open-source-stack": "1.1.2", 17 | "react": "^19.1.0", 18 | "react-dom": "^19.1.0", 19 | "react-router": "^7.5.0" 20 | }, 21 | "devDependencies": { 22 | "@react-router/dev": "^7.5.0", 23 | "@types/node": "22.14.1", 24 | "@types/react": "^19.1.2", 25 | "@types/react-dom": "^19.1.2", 26 | "react-router-devtools": "1.1.10", 27 | "typescript": "^5.8.3", 28 | "vite": "^6.2.6", 29 | "vite-tsconfig-paths": "^5.1.4" 30 | }, 31 | "engines": { 32 | "node": ">=22.0.0" 33 | } 34 | } -------------------------------------------------------------------------------- /test-apps/react-router-cjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-router-cjs", 3 | "private": true, 4 | "sideEffects": false, 5 | "type": "commonjs", 6 | "scripts": { 7 | "build": "react-router build", 8 | "dev": "react-router dev", 9 | "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", 10 | "start": "react-router-serve ./build/server/index.js", 11 | "typecheck": "tsc" 12 | }, 13 | "dependencies": { 14 | "@react-router/node": "^7.5.0", 15 | "@react-router/serve": "^7.5.0", 16 | "isbot": "^5.1.26", 17 | "open-source-stack": "1.1.2", 18 | "react": "^19.1.0", 19 | "react-dom": "^19.1.0", 20 | "react-router": "^7.5.0" 21 | }, 22 | "devDependencies": { 23 | "@react-router/dev": "^7.5.0", 24 | "@types/node": "22.14.1", 25 | "@types/react": "^19.1.2", 26 | "@types/react-dom": "^19.1.2", 27 | "typescript": "^5.8.3", 28 | "vite": "^6.2.6", 29 | "vite-tsconfig-paths": "^5.1.4" 30 | }, 31 | "engines": { 32 | "node": ">=22.0.0" 33 | } 34 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea for this project 3 | title: "feat: " 4 | labels: ["🌟 enhancement"] 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: Is your feature request related to a problem? Please describe. 9 | description: A clear and concise description of what the problem is. 10 | validations: 11 | required: true 12 | - type: textarea 13 | attributes: 14 | label: Describe the solution you'd like to see 15 | description: A clear and concise description of what you want to happen. 16 | validations: 17 | required: true 18 | - type: textarea 19 | attributes: 20 | label: Describe alternate solutions 21 | description: A clear and concise description of any alternative solutions or features you've considered. 22 | validations: 23 | required: true 24 | - type: textarea 25 | attributes: 26 | label: Additional information 27 | description: Add any other information related to the feature here. If your feature request is related to any issues or discussions, link them here. 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Forge 42 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test-apps/react-router-cjs/app/routes/_index.tsx: -------------------------------------------------------------------------------- 1 | import type { MetaFunction } from "react-router"; 2 | import { test } from "open-source-stack"; 3 | 4 | export const meta: MetaFunction = () => { 5 | return [{ title: "New Remix App" }, { name: "description", content: "Welcome to Remix!" }]; 6 | }; 7 | 8 | export const loader = () => { 9 | test(); 10 | } 11 | 12 | export default function Index() { 13 | return ( 14 |
15 |

Welcome to Remix

16 | 33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /test-apps/react-router-esm/app/routes/_index.tsx: -------------------------------------------------------------------------------- 1 | import type { MetaFunction } from "react-router"; 2 | // Import and test your package 3 | import { test } from "open-source-stack"; 4 | 5 | export const meta: MetaFunction = () => { 6 | return [{ title: "New Remix App" }, { name: "description", content: "Welcome to Remix!" }]; 7 | }; 8 | 9 | export default function Index() { 10 | test(); 11 | return ( 12 |
13 |

Welcome to Remix

14 | 31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "biome.enabled": true, 3 | "editor.defaultFormatter": "biomejs.biome", 4 | "editor.formatOnSave": true, 5 | "javascript.format.enable": false, 6 | "javascript.suggest.autoImports": true, 7 | "javascript.suggest.paths": true, 8 | "typescript.format.enable": false, 9 | "typescript.suggest.paths": true, 10 | "typescript.suggest.autoImports": true, 11 | "editor.renderWhitespace": "all", 12 | "editor.rulers": [120, 160], 13 | "editor.codeActionsOnSave": { 14 | "source.fixAll": "always", 15 | "source.organizeImports": "never", 16 | "source.organizeImports.biome": "always", 17 | "quickfix.biome": "always" 18 | }, 19 | "editor.insertSpaces": false, 20 | "editor.detectIndentation": true, 21 | "editor.trimAutoWhitespace": true, 22 | "files.trimTrailingWhitespace": true, 23 | "files.trimTrailingWhitespaceInRegexAndStrings": true, 24 | "files.trimFinalNewlines": true, 25 | "explorer.fileNesting.patterns": { 26 | "*.ts": "${basename}.*.${extname}", 27 | ".env": ".env.*", 28 | "*.tsx": "${basename}.*.${extname},${basename}.*.ts", 29 | "package.json": "*.json, *.yml, *.config.js, *.config.ts, *.yaml" 30 | }, 31 | "eslint.enable": false, 32 | "eslint.format.enable": false, 33 | "prettier.enable": false 34 | } 35 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 3 | "defaultBase": "main", 4 | "useInferencePlugins": false, 5 | "parallel": 5, 6 | "namedInputs": { 7 | "sharedGlobals": ["{workspaceRoot}/package.json", "{workspaceRoot}/tsconfig.json", "{workspaceRoot}/biome.json"], 8 | "default": ["sharedGlobals", "{projectRoot}/**/*", "!{projectRoot}/**/*.md"], 9 | "production": ["default", "!{projectRoot}/tests/**/*"] 10 | }, 11 | "targetDefaults": { 12 | "check": { 13 | "cache": false 14 | }, 15 | "test:unit": { 16 | "cache": true, 17 | "dependsOn": ["^build"], 18 | "inputs": ["default", "^production"] 19 | }, 20 | "test:types": { 21 | "cache": true, 22 | "dependsOn": ["^build"], 23 | "inputs": ["default", "^production"] 24 | }, 25 | "test:publint": { 26 | "cache": true, 27 | "dependsOn": ["build"], 28 | "inputs": ["production"] 29 | }, 30 | "build": { 31 | "cache": true, 32 | "dependsOn": ["^build"], 33 | "inputs": ["production", "^production"], 34 | "outputs": ["{projectRoot}/build", "{projectRoot}/dist"] 35 | }, 36 | "test:unused": { 37 | "cache": true, 38 | "inputs": ["{workspaceRoot}/**/*"] 39 | }, 40 | "test:deps": { 41 | "cache": true, 42 | "inputs": ["{workspaceRoot}/**/package.json"] 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: write 14 | pull-requests: write 15 | actions: write 16 | id-token: write 17 | steps: 18 | - name: Checkout Repo 19 | uses: actions/checkout@v3 20 | 21 | - name: Install pnpm 22 | uses: pnpm/action-setup@v4 23 | 24 | - name: Setup Node.js 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version-file: "package.json" 28 | 29 | - name: Install Dependencies 30 | run: pnpm install 31 | 32 | # - name: 🔐 Setup npm auth 33 | # run: | 34 | # echo "registry=https://registry.npmjs.org" >> ~/.npmrc 35 | # echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ~/.npmrc 36 | 37 | - name: Create Release Pull Request or Publish to npm 38 | id: changesets 39 | uses: changesets/action@v1 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 43 | with: 44 | title: "🚀 Release PR" 45 | commit: "chore: release" 46 | version: pnpm run version 47 | publish: pnpm run release 48 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yaml: -------------------------------------------------------------------------------- 1 | name: 🚀 PR 2 | 3 | concurrency: 4 | group: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: [pull_request] # Run only on pull_request, to also get status updates in PRs. We omit push because this would run the steps two times (for push and pull_request). 8 | 9 | permissions: 10 | actions: write 11 | contents: read 12 | # Required to put a comment into the pull-request 13 | pull-requests: write 14 | 15 | jobs: 16 | lint: 17 | name: ⬣ Linting 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: biomejs/setup-biome@v2 22 | - run: biome ci . --reporter=github 23 | 24 | typecheck: 25 | name: 🔎 Validation pipeline 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: 🛑 Cancel Previous Runs 29 | uses: styfle/cancel-workflow-action@0.12.1 30 | 31 | - name: ⬇️ Checkout repo 32 | uses: actions/checkout@v4 33 | - name: Install pnpm 34 | uses: pnpm/action-setup@v4 35 | - name: ⎔ Setup node 36 | uses: actions/setup-node@v4 37 | with: 38 | node-version-file: "package.json" 39 | 40 | - name: 📥 Download deps 41 | run: pnpm install 42 | 43 | - name: 🔎 Run all tests 44 | run: pnpm run test 45 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", 3 | "vcs": { 4 | "enabled": true, 5 | "clientKind": "git", 6 | "defaultBranch": "main", 7 | "useIgnoreFile": true 8 | }, 9 | "formatter": { 10 | "ignore": ["test-apps"], 11 | "enabled": true, 12 | "formatWithErrors": false, 13 | "indentStyle": "tab", 14 | "lineEnding": "lf", 15 | "lineWidth": 120 16 | }, 17 | "organizeImports": { 18 | "ignore": ["test-apps"], 19 | "enabled": true 20 | }, 21 | "linter": { 22 | "ignore": ["test-apps"], 23 | "enabled": true, 24 | "rules": { 25 | "recommended": true, 26 | "suspicious": { 27 | "recommended": true, 28 | "noConsole": "error" 29 | }, 30 | "style": { 31 | "recommended": true 32 | }, 33 | "complexity": { 34 | "recommended": true 35 | }, 36 | "security": { 37 | "recommended": true 38 | }, 39 | "performance": { 40 | "recommended": true 41 | }, 42 | "correctness": { 43 | "recommended": true, 44 | "noUnusedImports": "error", 45 | "noUnusedVariables": "error", 46 | "noUnusedLabels": "error", 47 | "noUnusedFunctionParameters": "error" 48 | }, 49 | "a11y": { 50 | "recommended": true 51 | }, 52 | "nursery": { 53 | "recommended": true 54 | } 55 | } 56 | }, 57 | "javascript": { 58 | "formatter": { 59 | "semicolons": "asNeeded", 60 | "trailingCommas": "es5" 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | # Description 4 | 5 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. 6 | List any dependencies that are required for this change. 7 | 8 | ## Type of change 9 | 10 | Please mark relevant options with an `x` in the brackets. 11 | 12 | - [ ] Bug fix (non-breaking change which fixes an issue) 13 | - [ ] New feature (non-breaking change which adds functionality) 14 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 15 | - [ ] This change requires a documentation update 16 | - [ ] Algorithm update - updates algorithm documentation/questions/answers etc. 17 | - [ ] Other (please describe): 18 | 19 | # How Has This Been Tested? 20 | 21 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also 22 | list any relevant details for your test configuration 23 | 24 | - [ ] Integration tests 25 | - [ ] Unit tests 26 | - [ ] Manual tests 27 | - [ ] No tests required 28 | 29 | # Reviewer checklist 30 | 31 | Mark everything that needs to be checked before merging the PR. 32 | 33 | - [ ] Check if the UI is working as expected and is satisfactory 34 | - [ ] Check if the code is well documented 35 | - [ ] Check if the behavior is what is expected 36 | - [ ] Check if the code is well tested 37 | - [ ] Check if the code is readable and well formatted 38 | - [ ] Additional checks (document below if any) 39 | 40 | # Screenshots (if appropriate): 41 | 42 | # Questions (if appropriate): 43 | -------------------------------------------------------------------------------- /packages/open-source-stack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "open-source-stack", 3 | "version": "1.1.2", 4 | "description": "Minimal open-source stack to help you ship an open-source package in TS", 5 | "main": "./dist/index.cjs", 6 | "module": "./dist/index.js", 7 | "types": "./dist/index.d.ts", 8 | "type": "module", 9 | "exports": { 10 | "./package.json": "./package.json", 11 | ".": { 12 | "import": { 13 | "types": "./dist/index.d.ts", 14 | "import": "./dist/index.js", 15 | "default": "./dist/index.js" 16 | }, 17 | "require": { 18 | "types": "./dist/index.d.cts", 19 | "import": "./dist/index.cjs", 20 | "require": "./dist/index.cjs" 21 | } 22 | } 23 | }, 24 | "scripts": { 25 | "build": "tsdown src/index.ts", 26 | "prepublishOnly": "pnpm run build && pnpm run test:exports", 27 | "test:unit": "vitest run --passWithNoTests", 28 | "test:cov": "vitest run --coverage", 29 | "test:types": "tsc", 30 | "test:publint": "publint --strict", 31 | "test:exports": "attw --pack ." 32 | }, 33 | "author": "", 34 | "license": "MIT", 35 | "repository": { 36 | "type": "git", 37 | "url": "git+https://github.com/forge-42/open-source-stack.git" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/forge-42/open-source-stack/issues" 41 | }, 42 | "files": ["dist"], 43 | "homepage": "https://github.com/forge-42/open-source-stack#readme", 44 | "publishConfig": { 45 | "provenance": true 46 | }, 47 | "devDependencies": { 48 | "@arethetypeswrong/cli": "^0.17.4", 49 | "@changesets/cli": "^2.29.0", 50 | "@types/node": "22.14.1", 51 | "@vitest/coverage-v8": "^3.1.1", 52 | "happy-dom": "^17.4.4", 53 | "tsdown": "^0.15.6", 54 | "typescript": "^5.8.3", 55 | "vitest": "^3.1.1" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug Report 2 | description: Create a bug report to help us improve 3 | title: "bug: " 4 | labels: ["🐞 unconfirmed bug"] 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: Provide environment information 9 | description: | 10 | Run this command in your project root and paste the results: 11 | ```bash 12 | npx envinfo --system --binaries 13 | ``` 14 | 15 | validations: 16 | required: true 17 | - type: textarea 18 | attributes: 19 | label: Describe the bug 20 | description: A clear and concise description of the bug, as well as what you expected to happen when encountering it. 21 | validations: 22 | required: true 23 | - type: input 24 | attributes: 25 | label: Reproduction repo 26 | description: If applicable, please provide a link to a reproduction repo or a Stackblitz / CodeSandbox project. Your issue may be closed if this is not provided and we are unable to reproduce the issue. 27 | - type: textarea 28 | attributes: 29 | label: Expected behavior 30 | description: A clear and concise description of what you expected to happen. 31 | validations: 32 | required: true 33 | - type: textarea 34 | attributes: 35 | label: Actual behavior 36 | description: A clear and concise description of what actually happened. 37 | validations: 38 | required: true 39 | - type: textarea 40 | attributes: 41 | label: Steps to reproduce 42 | description: Steps to reproduce the behavior. 43 | placeholder: | 44 | 1. Go to '...' 45 | 2. Click on '....' 46 | 3. Scroll down to '....' 47 | 4. See error 48 | validations: 49 | required: true 50 | - type: textarea 51 | attributes: 52 | label: Screenshots 53 | description: If applicable, add screenshots to help explain your problem. 54 | - type: textarea 55 | attributes: 56 | label: Additional context 57 | description: Add any other context about the problem here. 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "open-source-stack-template", 3 | "version": "1.0.0", 4 | "description": "Minimal open-source stack to help you ship an open-source package in TS", 5 | "scripts": { 6 | "build": "nx affected --targets=build --exclude=test-apps/**", 7 | "build:all": "nx run-many --targets=build --exclude=test-apps/**", 8 | "clean": "git clean -fdX .", 9 | "clean:build": "git clean -fdx -e node_modules .", 10 | "dev": "pnpm run build:all && nx watch --all -i -- pnpm run build:all", 11 | "check": "biome check .", 12 | "check:fix": "biome check --fix .", 13 | "test": "pnpm run test:ci", 14 | "test:ci": "nx run-many --targets=check,test:deps,test:unused,test:unit,test:types,test:publint,build", 15 | "test:types": "nx affected --target=test:types --exclude=test-apps/**", 16 | "test:unit": "nx affected --target=test:unit --exclude=test-apps/**", 17 | "test:cov": "nx affected --target=test:cov --exclude=test-apps/**", 18 | "test:publint": "nx affected --target=test:publint --exclude=test-apps/**", 19 | "test:unused": "knip", 20 | "test:deps": "sherif", 21 | "changeset": "changeset", 22 | "release": "changeset publish", 23 | "local-release": "changeset version && changeset publish", 24 | "version": "changeset version", 25 | "watch": "" 26 | }, 27 | "author": "forge-42", 28 | "license": "MIT", 29 | "repository": { 30 | "type": "git", 31 | "url": "https://github.com/forge-42/open-source-stack.git" 32 | }, 33 | "bugs": { 34 | "url": "https://github.com/forge-42/open-source-stack/issues" 35 | }, 36 | "homepage": "https://github.com/forge-42/open-source-stack#readme", 37 | "devDependencies": { 38 | "@biomejs/biome": "^1.9.4", 39 | "@changesets/cli": "^2.29.0", 40 | "@types/node": "22.14.1", 41 | "knip": "^5.64.2", 42 | "lefthook": "^1.11.10", 43 | "nx": "^21.6.4", 44 | "publint": "^0.3.14", 45 | "sherif": "^1.6.1" 46 | }, 47 | "packageManager": "pnpm@10.8.0", 48 | "engines": { 49 | "pnpm": ">=10.8.0", 50 | "node": ">=20.0.0" 51 | }, 52 | "nx": { 53 | "includedScripts": ["test:unused", "test:deps", "check"] 54 | }, 55 | "overrides": { 56 | "open-source-stack": "workspace:*" 57 | }, 58 | "private": true 59 | } 60 | -------------------------------------------------------------------------------- /test-apps/react-router-cjs/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * This is intended to be a basic starting point for linting in your app. 3 | * It relies on recommended configs out of the box for simplicity, but you can 4 | * and should modify this configuration to best suit your team's needs. 5 | */ 6 | 7 | /** @type {import('eslint').Linter.Config} */ 8 | module.exports = { 9 | root: true, 10 | parserOptions: { 11 | ecmaVersion: "latest", 12 | sourceType: "module", 13 | ecmaFeatures: { 14 | jsx: true, 15 | }, 16 | }, 17 | env: { 18 | browser: true, 19 | commonjs: true, 20 | es6: true, 21 | }, 22 | ignorePatterns: ["!**/.server", "!**/.client"], 23 | 24 | // Base config 25 | extends: ["eslint:recommended"], 26 | 27 | overrides: [ 28 | // React 29 | { 30 | files: ["**/*.{js,jsx,ts,tsx}"], 31 | plugins: ["react", "jsx-a11y"], 32 | extends: [ 33 | "plugin:react/recommended", 34 | "plugin:react/jsx-runtime", 35 | "plugin:react-hooks/recommended", 36 | "plugin:jsx-a11y/recommended", 37 | ], 38 | settings: { 39 | react: { 40 | version: "detect", 41 | }, 42 | formComponents: ["Form"], 43 | linkComponents: [ 44 | { name: "Link", linkAttribute: "to" }, 45 | { name: "NavLink", linkAttribute: "to" }, 46 | ], 47 | "import/resolver": { 48 | typescript: {}, 49 | }, 50 | }, 51 | }, 52 | 53 | // Typescript 54 | { 55 | files: ["**/*.{ts,tsx}"], 56 | plugins: ["@typescript-eslint", "import"], 57 | parser: "@typescript-eslint/parser", 58 | settings: { 59 | "import/internal-regex": "^~/", 60 | "import/resolver": { 61 | node: { 62 | extensions: [".ts", ".tsx"], 63 | }, 64 | typescript: { 65 | alwaysTryTypes: true, 66 | }, 67 | }, 68 | }, 69 | extends: [ 70 | "plugin:@typescript-eslint/recommended", 71 | "plugin:import/recommended", 72 | "plugin:import/typescript", 73 | ], 74 | }, 75 | 76 | // Node 77 | { 78 | files: [".eslintrc.cjs"], 79 | env: { 80 | node: true, 81 | }, 82 | }, 83 | ], 84 | }; 85 | -------------------------------------------------------------------------------- /test-apps/react-router-esm/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * This is intended to be a basic starting point for linting in your app. 3 | * It relies on recommended configs out of the box for simplicity, but you can 4 | * and should modify this configuration to best suit your team's needs. 5 | */ 6 | 7 | /** @type {import('eslint').Linter.Config} */ 8 | module.exports = { 9 | root: true, 10 | parserOptions: { 11 | ecmaVersion: "latest", 12 | sourceType: "module", 13 | ecmaFeatures: { 14 | jsx: true, 15 | }, 16 | }, 17 | env: { 18 | browser: true, 19 | commonjs: true, 20 | es6: true, 21 | }, 22 | ignorePatterns: ["!**/.server", "!**/.client"], 23 | 24 | // Base config 25 | extends: ["eslint:recommended"], 26 | 27 | overrides: [ 28 | // React 29 | { 30 | files: ["**/*.{js,jsx,ts,tsx}"], 31 | plugins: ["react", "jsx-a11y"], 32 | extends: [ 33 | "plugin:react/recommended", 34 | "plugin:react/jsx-runtime", 35 | "plugin:react-hooks/recommended", 36 | "plugin:jsx-a11y/recommended", 37 | ], 38 | settings: { 39 | react: { 40 | version: "detect", 41 | }, 42 | formComponents: ["Form"], 43 | linkComponents: [ 44 | { name: "Link", linkAttribute: "to" }, 45 | { name: "NavLink", linkAttribute: "to" }, 46 | ], 47 | "import/resolver": { 48 | typescript: {}, 49 | }, 50 | }, 51 | }, 52 | 53 | // Typescript 54 | { 55 | files: ["**/*.{ts,tsx}"], 56 | plugins: ["@typescript-eslint", "import"], 57 | parser: "@typescript-eslint/parser", 58 | settings: { 59 | "import/internal-regex": "^~/", 60 | "import/resolver": { 61 | node: { 62 | extensions: [".ts", ".tsx"], 63 | }, 64 | typescript: { 65 | alwaysTryTypes: true, 66 | }, 67 | }, 68 | }, 69 | extends: [ 70 | "plugin:@typescript-eslint/recommended", 71 | "plugin:import/recommended", 72 | "plugin:import/typescript", 73 | ], 74 | }, 75 | 76 | // Node 77 | { 78 | files: [".eslintrc.cjs"], 79 | env: { 80 | node: true, 81 | }, 82 | }, 83 | ], 84 | }; 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | /dist 132 | .history 133 | .react-router 134 | .nx -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Open-source stack 2 | 3 | ![GitHub Repo stars](https://img.shields.io/github/stars/forge-42/open-source-stack?style=social) 4 | ![npm](https://img.shields.io/npm/v/open-source-stack?style=plastic) 5 | ![GitHub](https://img.shields.io/github/license/forge-42/open-source-stack?style=plastic) 6 | ![npm](https://img.shields.io/npm/dy/open-source-stack?style=plastic) 7 | ![npm](https://img.shields.io/npm/dw/open-source-stack?style=plastic) 8 | ![GitHub top language](https://img.shields.io/github/languages/top/forge-42/open-source-stack?style=plastic) 9 | 10 | Full starter stack to develop CJS/ESM compatible npm packages with TypeScript, Vitest, Biome, Prettier, and GitHub Actions. 11 | 12 | Detailed overview of the stack: 13 | https://youtu.be/ABRpwxLdGho 14 | 15 | Deploy your open-source project to npm with ease, with fully covered bundling, testing, linting and deployment setup out of the box, 16 | don't worry about CJS or ESM, bundling your typescript definitions or anything else, focus on coding out your solution and let the stack take care of the rest. 17 | 18 | Build your own open-source project today! 🚀 19 | 20 | ## Tools 21 | 22 | - **TypeScript**: TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. 23 | - **Vitest**: A modern test runner built on top of Vite. 24 | - **Biome**: Biome statically analyzes your code to find issues and formats your code with a consistent, opinionated style. 25 | - **GitHub Actions**: Automate your workflow from idea to production. 26 | - **tsdown** - Simple to config bundler powered by rolldown. 27 | - **Changeset** - A way to manage your versioning and changelog with a focus on monorepos. 28 | - **pnpm workspaces** - A way to manage multiple packages in a single repository. 29 | 30 | ## Features 31 | 32 | - **NX workflows and caching** - Use the power of NX to manage your monorepo and speed up your builds with caching. 33 | - **ESM/CJS ready** - Write your code in TypeScript and publish it as ESM and CJS with 0 configuration. 34 | - **Are The types wrong? ready** - Passes all the checks for typings on https://arethetypeswrong.github.io/ by default. 35 | - **ESM/CJS test apps setup** - Test your package in both ESM and CJS environments already setup for you. 36 | - **Test runner setup** - Test your open source package with Vitest already setup for you. 37 | - **Linting setup** - Lint your code with Biome already setup for you. 38 | - **GitHub Actions setup** - Automate deployments to npm by using GitHub Actions. 39 | - **Changeset versioning & automation** - Automate releases with Changeset and GitHub Actions. 40 | 41 | ## Setup 42 | 43 | 1. Use this template to create a new repository. 44 | 2. Clone the repository. 45 | 3. Change the package name in `package.json`. 46 | 4. Change the `open-source-stack` dependency in your test-apps to your name 47 | 5. Change the `open-source-stack` folder name in packages to your package name 48 | 6. Install the dependencies with `npm install`. 49 | 7. Change the `repository`, `bugs`, and `homepage` fields in `package.json` to your github repo. 50 | 8. Change the license if required. 51 | 9. Add the NPM_TOKEN secret to your GitHub repository. 52 | 10. Allow GitHub Actions to create and approve pull requests. (Settings -> Actions -> Workflow permissions) 53 | 11. Start coding! 54 | 55 | ## Development 56 | 57 | To start developing your package, run the following command: 58 | 59 | ```bash 60 | pnpm run dev 61 | ``` 62 | 63 | This will start the watch mode for your package and build it on every change. 64 | 65 | Then you can test your package in the test-apps folder. 66 | 67 | Pick one of the test apps and run the following commands: 68 | 69 | ```bash 70 | cd test-apps/react-router-esm 71 | 72 | pnpm run dev 73 | ``` 74 | 75 | If you want to add more packages, don't forget to add them to the `overrides` section in the root `package.json`. 76 | 77 | Also, if you want to add more test-apps using the latest version of the package instead of `workspace:*` in the dependencies is recommended. 78 | 79 | ## Scripts 80 | 81 | - `pnpm run build` - Build the package(s). 82 | - `pnpm run test` - Run the tests. 83 | - `pnpm run check` - Lint the code. 84 | - `pnpm run dev` - Start the package(s) watch mode. 85 | -------------------------------------------------------------------------------- /test-apps/react-router-cjs/app/entry.server.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * By default, Remix will handle generating the HTTP Response for you. 3 | * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ 4 | * For more information, see https://remix.run/file-conventions/entry.server 5 | */ 6 | 7 | import { PassThrough } from "node:stream"; 8 | import { createReadableStreamFromReadable } from "@react-router/node"; 9 | import { type AppLoadContext, type EntryContext, ServerRouter } from "react-router" 10 | import { isbot } from "isbot"; 11 | import { renderToPipeableStream } from "react-dom/server"; 12 | 13 | const ABORT_DELAY = 5_000; 14 | 15 | export default function handleRequest( 16 | request: Request, 17 | responseStatusCode: number, 18 | responseHeaders: Headers, 19 | reactRouterContext: EntryContext, 20 | // This is ignored so we can keep it in the template for visibility. Feel 21 | // free to delete this parameter in your app if you're not using it! 22 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 23 | loadContext: AppLoadContext 24 | ) { 25 | return isbot(request.headers.get("user-agent") || "") 26 | ? handleBotRequest( 27 | request, 28 | responseStatusCode, 29 | responseHeaders, 30 | reactRouterContext 31 | ) 32 | : handleBrowserRequest( 33 | request, 34 | responseStatusCode, 35 | responseHeaders, 36 | reactRouterContext 37 | ); 38 | } 39 | 40 | function handleBotRequest( 41 | request: Request, 42 | responseStatusCode: number, 43 | responseHeaders: Headers, 44 | reactRouterContext: EntryContext 45 | ) { 46 | return new Promise((resolve, reject) => { 47 | let shellRendered = false; 48 | const { pipe, abort } = renderToPipeableStream( 49 | , 50 | { 51 | onAllReady() { 52 | shellRendered = true; 53 | const body = new PassThrough(); 54 | const stream = createReadableStreamFromReadable(body); 55 | 56 | responseHeaders.set("Content-Type", "text/html"); 57 | 58 | resolve( 59 | new Response(stream, { 60 | headers: responseHeaders, 61 | status: responseStatusCode, 62 | }) 63 | ); 64 | 65 | pipe(body); 66 | }, 67 | onShellError(error: unknown) { 68 | reject(error); 69 | }, 70 | onError(error: unknown) { 71 | responseStatusCode = 500; 72 | // Log streaming rendering errors from inside the shell. Don't log 73 | // errors encountered during initial shell rendering since they'll 74 | // reject and get logged in handleDocumentRequest. 75 | if (shellRendered) { 76 | console.error(error); 77 | } 78 | }, 79 | } 80 | ); 81 | 82 | setTimeout(abort, ABORT_DELAY); 83 | }); 84 | } 85 | 86 | function handleBrowserRequest( 87 | request: Request, 88 | responseStatusCode: number, 89 | responseHeaders: Headers, 90 | reactRouterContext: EntryContext 91 | ) { 92 | return new Promise((resolve, reject) => { 93 | let shellRendered = false; 94 | const { pipe, abort } = renderToPipeableStream( 95 | , 96 | { 97 | onShellReady() { 98 | shellRendered = true; 99 | const body = new PassThrough(); 100 | const stream = createReadableStreamFromReadable(body); 101 | 102 | responseHeaders.set("Content-Type", "text/html"); 103 | 104 | resolve( 105 | new Response(stream, { 106 | headers: responseHeaders, 107 | status: responseStatusCode, 108 | }) 109 | ); 110 | 111 | pipe(body); 112 | }, 113 | onShellError(error: unknown) { 114 | reject(error); 115 | }, 116 | onError(error: unknown) { 117 | responseStatusCode = 500; 118 | // Log streaming rendering errors from inside the shell. Don't log 119 | // errors encountered during initial shell rendering since they'll 120 | // reject and get logged in handleDocumentRequest. 121 | if (shellRendered) { 122 | console.error(error); 123 | } 124 | }, 125 | } 126 | ); 127 | 128 | setTimeout(abort, ABORT_DELAY); 129 | }); 130 | } 131 | -------------------------------------------------------------------------------- /test-apps/react-router-esm/app/entry.server.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * By default, Remix will handle generating the HTTP Response for you. 3 | * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ 4 | * For more information, see https://remix.run/file-conventions/entry.server 5 | */ 6 | 7 | import { PassThrough } from "node:stream"; 8 | import { createReadableStreamFromReadable } from "@react-router/node"; 9 | import { type AppLoadContext, type EntryContext, ServerRouter } from "react-router" 10 | import { isbot } from "isbot"; 11 | import { renderToPipeableStream } from "react-dom/server"; 12 | 13 | const ABORT_DELAY = 5_000; 14 | 15 | export default function handleRequest( 16 | request: Request, 17 | responseStatusCode: number, 18 | responseHeaders: Headers, 19 | reactRouterContext: EntryContext, 20 | // This is ignored so we can keep it in the template for visibility. Feel 21 | // free to delete this parameter in your app if you're not using it! 22 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 23 | loadContext: AppLoadContext 24 | ) { 25 | return isbot(request.headers.get("user-agent") || "") 26 | ? handleBotRequest( 27 | request, 28 | responseStatusCode, 29 | responseHeaders, 30 | reactRouterContext 31 | ) 32 | : handleBrowserRequest( 33 | request, 34 | responseStatusCode, 35 | responseHeaders, 36 | reactRouterContext 37 | ); 38 | } 39 | 40 | function handleBotRequest( 41 | request: Request, 42 | responseStatusCode: number, 43 | responseHeaders: Headers, 44 | reactRouterContext: EntryContext 45 | ) { 46 | return new Promise((resolve, reject) => { 47 | let shellRendered = false; 48 | const { pipe, abort } = renderToPipeableStream( 49 | , 50 | { 51 | onAllReady() { 52 | shellRendered = true; 53 | const body = new PassThrough(); 54 | const stream = createReadableStreamFromReadable(body); 55 | 56 | responseHeaders.set("Content-Type", "text/html"); 57 | 58 | resolve( 59 | new Response(stream, { 60 | headers: responseHeaders, 61 | status: responseStatusCode, 62 | }) 63 | ); 64 | 65 | pipe(body); 66 | }, 67 | onShellError(error: unknown) { 68 | reject(error); 69 | }, 70 | onError(error: unknown) { 71 | responseStatusCode = 500; 72 | // Log streaming rendering errors from inside the shell. Don't log 73 | // errors encountered during initial shell rendering since they'll 74 | // reject and get logged in handleDocumentRequest. 75 | if (shellRendered) { 76 | console.error(error); 77 | } 78 | }, 79 | } 80 | ); 81 | 82 | setTimeout(abort, ABORT_DELAY); 83 | }); 84 | } 85 | 86 | function handleBrowserRequest( 87 | request: Request, 88 | responseStatusCode: number, 89 | responseHeaders: Headers, 90 | reactRouterContext: EntryContext 91 | ) { 92 | return new Promise((resolve, reject) => { 93 | let shellRendered = false; 94 | const { pipe, abort } = renderToPipeableStream( 95 | , 96 | { 97 | onShellReady() { 98 | shellRendered = true; 99 | const body = new PassThrough(); 100 | const stream = createReadableStreamFromReadable(body); 101 | 102 | responseHeaders.set("Content-Type", "text/html"); 103 | 104 | resolve( 105 | new Response(stream, { 106 | headers: responseHeaders, 107 | status: responseStatusCode, 108 | }) 109 | ); 110 | 111 | pipe(body); 112 | }, 113 | onShellError(error: unknown) { 114 | reject(error); 115 | }, 116 | onError(error: unknown) { 117 | responseStatusCode = 500; 118 | // Log streaming rendering errors from inside the shell. Don't log 119 | // errors encountered during initial shell rendering since they'll 120 | // reject and get logged in handleDocumentRequest. 121 | if (shellRendered) { 122 | console.error(error); 123 | } 124 | }, 125 | } 126 | ); 127 | 128 | setTimeout(abort, ABORT_DELAY); 129 | }); 130 | } 131 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. --------------------------------------------------------------------------------