├── 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 | 
4 | 
5 | 
6 | 
7 | 
8 | 
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.
--------------------------------------------------------------------------------