Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Alchemy (required when using alchemy)
# Generate both the secret and the state token using `openssl rand -base64 32`.
ALCHEMY_SECRET=<***>
ALCHEMY_STATE_TOKEN=<***>
HOSTNAME=<devsantara.com | kit.devsantara.com | "your.domain">

# Cloudflare (required for deployment workflows)
# See https://alchemy.run/guides/cloudflare#api-token
CLOUDFLARE_API_TOKEN=<***>
# Your Cloudflare email
CLOUDFLARE_EMAIL=<***>
88 changes: 88 additions & 0 deletions .github/workflows/deployment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: Deployment
on:
push:
branches:
- main
pull_request:
branches:
- main
types:
- opened
- reopened
- synchronize
- closed
concurrency:
group: deployment-${{ github.ref }}
cancel-in-progress: false
env:
NODE_VERSION: '24.11.1'
PNPM_VERSION: '10.23.0'
STAGE: ${{ github.event_name == 'pull_request' && format('preview-{0}', github.event.number) || (github.ref == 'refs/heads/main' && 'production' || github.ref_name) }}
jobs:
deploy:
if: ${{ github.event.action != 'closed' }}
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
- name: Install dependencies
run: pnpm install
- name: Deploy
run: pnpm alchemy deploy --stage ${{ env.STAGE }}
env:
HOSTNAME: ${{ vars.HOSTNAME }}
ALCHEMY_SECRET: ${{ secrets.ALCHEMY_SECRET }}
ALCHEMY_STATE_TOKEN: ${{ secrets.ALCHEMY_STATE_TOKEN }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }}
PULL_REQUEST: ${{ github.event.number }}
GITHUB_SHA: ${{ github.sha }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
cleanup:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'pull_request' && github.event.action == 'closed' }}
permissions:
id-token: write
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
- name: Install dependencies
run: pnpm install
- name: Safety Check
run: |-
if [ "${{ env.STAGE }}" = "production" ]; then
echo "ERROR: Cannot destroy production environment in cleanup job"
exit 1
fi
- name: Destroy Preview Environment
run: pnpm alchemy destroy --stage ${{ env.STAGE }}
env:
HOSTNAME: ${{ vars.HOSTNAME }}
ALCHEMY_SECRET: ${{ secrets.ALCHEMY_SECRET }}
ALCHEMY_STATE_TOKEN: ${{ secrets.ALCHEMY_STATE_TOKEN }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }}
PULL_REQUEST: ${{ github.event.number }}
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ dist/
dist-ssr/
storybook-static/

# Infrastructures
.alchemy/
.wrangler/

# Local files
*.local

Expand Down
5 changes: 3 additions & 2 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"import/no-cycle": ["error", { "maxDepth": 3 }],

// Suspicious
"eslint/no-unneeded-ternary": "off",
"react/react-in-jsx-scope": "off",
"import/no-unassigned-import": ["error", { "allow": ["**/*.css"] }],

Expand All @@ -67,15 +68,15 @@
"default-param-last": "error",
"func-names": ["error", "as-needed", { "generators": "as-needed" }],
"max-params": ["error", { "max": 3 }],
"new-cap": "error",
"no-duplicate-imports": "error",
"no-implicit-coercion": "error",
"no-template-curly-in-string": "error",
"yoda": "error",
"typescript/consistent-type-definitions": ["error", "interface"],
"unicorn/filename-case": ["error", { "case": "kebabCase" }],
"react/self-closing-comp": "warn",
"import/no-duplicates": "error"
"import/no-duplicates": "error",
"eslint/func-style": ["error", "declaration"]
},
"overrides": [
{
Expand Down
16 changes: 16 additions & 0 deletions @types/env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// This file infers types for the cloudflare:workers environment from your Alchemy Worker.
// @see https://alchemy.run/concepts/bindings/#type-safe-bindings

import type { worker } from '../alchemy.run.ts';

export type CloudflareEnv = typeof worker.Env;

declare global {
type Env = CloudflareEnv;
}

declare module 'cloudflare:workers' {
namespace Cloudflare {
export interface Env extends CloudflareEnv {}
}
}
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
# Changelog

## [0.2.0](https://github.com/devsantara/kit/compare/0.1.0...0.2.0) (2026-01-06)

### Features

- **server:** add server wait until ([e3e9ad4](https://github.com/devsantara/kit/commit/e3e9ad45a76a03f9427ef9b70bb84aa9709894d6))
- **ui:** add custom sans and mono fonts ([65a1f90](https://github.com/devsantara/kit/commit/65a1f905f7515a180f67c144234460d2faa5f0c3))
- **ui:** setup tailwindcss ([ca471dc](https://github.com/devsantara/kit/commit/ca471dcb4cbd90308d275a788df4fa0e3b7eb000))
- **ui:** setup ui library ([2c0647e](https://github.com/devsantara/kit/commit/2c0647e115bca204d8cde8658bd698c687e96dc8))

### Bug Fixes

- **deps:** some package imported from external module but never used ([968b7b2](https://github.com/devsantara/kit/commit/968b7b2c64165610d9a98845641254af381c2fe2))
- **typescript:** server bundles can leaking into client bundles ([c691288](https://github.com/devsantara/kit/commit/c6912881434110cb84ba85d25da907a615aad25f))

### Performance

- **react:** add react-compiler ([b180469](https://github.com/devsantara/kit/commit/b18046948e2834b9e1b5b861459e5a44e2c1de8c))

### Continuous Integration

- **deployment:** setup infrastructure and workflows ([39a0169](https://github.com/devsantara/kit/commit/39a0169758d01c45fcb792510b3f7915369bd023))

### Chores

- **bump:** update packages ([d8d9e57](https://github.com/devsantara/kit/commit/d8d9e573f50837ab6c7339e7fe7842e954a12e22))
- **formatter:** migrate prettier to oxfmt ([975a6c1](https://github.com/devsantara/kit/commit/975a6c17e47e7ff8d1adc3340d956f9cb1722488))
- **linter:** migrate eslint to oxlint ([b8ef08b](https://github.com/devsantara/kit/commit/b8ef08b8deef8d6b13f01e0aa313309ba7cacb8c))
- **vscode:** prevent routeTree file from being exposed in the editor ([56f72ed](https://github.com/devsantara/kit/commit/56f72ed287d895b67d40cddd0f539df03e1ba1a6))

## [0.1.0](https://github.com/devsantara/kit/compare/...0.1.0) (2025-12-14)

### Features
Expand Down
77 changes: 77 additions & 0 deletions alchemy.run.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* Infrastructure provisioning with **Alchemy** for Cloudflare platform
*
* By default (when running locally) the stage will be your username ($USER, or $USERNAME on Windows).
* We can also specify a stage with the `--stage` flag.
*
* @example
* ```bash
* pnpm alchemy:deploy --stage production
* ```
*/

import alchemy, { type Scope } from 'alchemy';
import { TanStackStart } from 'alchemy/cloudflare';
import { GitHubComment } from 'alchemy/github';
import { CloudflareStateStore, FileSystemStateStore } from 'alchemy/state';

import packageJson from './package.json' with { type: 'json' };
import { alchemyEnv } from './src/lib/env/alchemy.ts';

const ALCHEMY_SECRET = alchemyEnv.ALCHEMY_SECRET;
const ALCHEMY_STATE_TOKEN = alchemy.secret(alchemyEnv.ALCHEMY_STATE_TOKEN);

function isProductionStage(scope: Scope) {
return scope.stage === 'production';
}
function isPreviewStage(scope: Scope) {
return scope.stage.startsWith('preview');
}

const app = await alchemy('kit', {
password: ALCHEMY_SECRET,
stateStore: (scope) => {
if (isProductionStage(scope) || isPreviewStage(scope)) {
return new CloudflareStateStore(scope, {
scriptName: 'alchemy-state-service',
stateToken: ALCHEMY_STATE_TOKEN,
});
}
return new FileSystemStateStore(scope);
},
});

export const worker = await TanStackStart('website', {
adopt: true,
observability: isProductionStage(app) ? { enabled: true } : undefined,
url: isProductionStage(app) ? false : true,
domains: isProductionStage(app) ? [alchemyEnv.HOSTNAME] : undefined,
placement: isProductionStage(app) ? { mode: 'smart' } : undefined,
bindings: {},
});

const workerDomain = worker.domains?.[0];
const workerUrl = workerDomain ? `https://${workerDomain.name}` : worker.url;
console.info({ worker: worker.name, url: workerUrl });

if (process.env.PULL_REQUEST) {
// If this is a PR, add a comment to the PR with the preview URL
// It will auto-update with each push
await GitHubComment('preview-comment', {
owner: packageJson.author,
repository: packageJson.name,
issueNumber: Number(process.env.PULL_REQUEST),
body: `## 🚀 Preview Deployment (${process.env.PULL_REQUEST})

Your changes have been deployed to a preview environment:

| Name | Preview URL | Commit | Updated (UTC) |
| :----------------- | :---------------------------- | :------------------------------------- | :------------------------------------------ |
| **${worker.name}** | [Visit Preview](${workerUrl}) | ${process.env.GITHUB_SHA?.slice(0, 7)} | ${new Date(worker.updatedAt).toUTCString()} |

---
<sub>🏗️ This comment updates automatically with each push.</sub>`,
});
}

await app.finalize();
63 changes: 35 additions & 28 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kit",
"version": "0.1.0",
"version": "0.2.0",
"private": true,
"description": "The blueprint for your next big idea",
"keywords": [],
Expand All @@ -17,11 +17,14 @@
"type": "module",
"sideEffects": false,
"scripts": {
"prepare": "husky",
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"start": "node .output/server/index.mjs",
"prepare": "husky",
"alchemy": "alchemy",
"alchemy:dev": "alchemy dev --env-file ./.env",
"alchemy:deploy": "alchemy deploy --env-file ./.env",
"alchemy:destroy": "alchemy destroy --env-file ./.env",
"commitlint": "commitlint --edit",
"format": "oxfmt",
"format:check": "oxfmt --check",
Expand Down Expand Up @@ -63,58 +66,62 @@
"@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.8",
"@t3-oss/env-core": "^0.13.10",
"@tailwindcss/vite": "^4.1.18",
"@tanstack/react-router": "^1.141.2",
"@tanstack/react-start": "^1.141.3",
"@tanstack/react-router": "^1.144.0",
"@tanstack/react-start": "^1.145.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"date-fns": "^4.1.0",
"embla-carousel-react": "^8.6.0",
"input-otp": "^1.4.2",
"lucide-react": "^0.561.0",
"lucide-react": "^0.562.0",
"next-themes": "^0.4.6",
"react": "^19.2.3",
"react-day-picker": "^9.12.0",
"react-day-picker": "^9.13.0",
"react-dom": "^19.2.3",
"react-resizable-panels": "^3.0.6",
"recharts": "2.15.4",
"react-resizable-panels": "^4.2.1",
"recharts": "3.6.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.4.0",
"tailwindcss": "^4.1.18",
"vaul": "^1.1.2",
"zod": "^4.1.13"
"zod": "^4.3.5"
},
"devDependencies": {
"@commitlint/cli": "^20.2.0",
"@commitlint/config-conventional": "^20.2.0",
"@cloudflare/vite-plugin": "^1.19.0",
"@cloudflare/workers-types": "^4.20260103.0",
"@commitlint/cli": "^20.3.0",
"@commitlint/config-conventional": "^20.3.0",
"@commitlint/types": "^20.2.0",
"@release-it/conventional-changelog": "10.0.1",
"@storybook/addon-docs": "^10.1.8",
"@storybook/react-vite": "^10.1.8",
"@tanstack/devtools-vite": "^0.3.12",
"@tanstack/react-devtools": "^0.8.4",
"@tanstack/react-router-devtools": "^1.141.2",
"@types/node": "^25.0.1",
"@release-it/conventional-changelog": "10.0.4",
"@storybook/addon-docs": "^10.1.11",
"@storybook/react-vite": "^10.1.11",
"@tanstack/devtools-vite": "^0.4.0",
"@tanstack/react-devtools": "^0.9.0",
"@tanstack/react-router-devtools": "^1.144.0",
"@types/node": "^25.0.3",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.2",
"alchemy": "^0.83.0",
"babel-plugin-react-compiler": "^1.0.0",
"conventional-changelog-conventionalcommits": "9.1.0",
"globals": "^16.5.0",
"husky": "^9.1.7",
"lint-staged": "^16.2.7",
"nitro": "3.0.1-alpha.1",
"oxfmt": "^0.20.0",
"oxlint": "^1.35.0",
"release-it": "19.0.4",
"storybook": "^10.1.8",
"oxfmt": "^0.22.0",
"oxlint": "^1.37.0",
"release-it": "19.2.2",
"storybook": "^10.1.11",
"tw-animate-css": "^1.4.0",
"typescript": "^5.9.3",
"vite": "^7.2.7",
"vite-tsconfig-paths": "^5.1.4"
"vite": "^7.3.0",
"vite-tsconfig-paths": "^6.0.3"
},
"resolutions": {
"conventional-changelog-conventionalcommits": "8.0.0"
"conventional-changelog-conventionalcommits": "8.0.0",
"libsodium-wrappers": "0.7.15"
},
"engines": {
"node": "24.11.1"
Expand Down
Loading