xpine 0.0.58 → 0.0.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/.claude/worktrees/happy-lalande-c6c39c/.claude/settings.local.json +33 -0
  3. package/.claude/worktrees/happy-lalande-c6c39c/.d.ts +1 -0
  4. package/.claude/worktrees/happy-lalande-c6c39c/.gitattributes +2 -0
  5. package/.claude/worktrees/happy-lalande-c6c39c/README.md +571 -0
  6. package/.claude/worktrees/happy-lalande-c6c39c/TODO +2 -0
  7. package/.claude/worktrees/happy-lalande-c6c39c/eslint.config.mjs +27 -0
  8. package/.claude/worktrees/happy-lalande-c6c39c/jsx-runtime.d.ts +1 -0
  9. package/.claude/worktrees/happy-lalande-c6c39c/package-lock.json +5862 -0
  10. package/.claude/worktrees/happy-lalande-c6c39c/package.json +62 -0
  11. package/.claude/worktrees/happy-lalande-c6c39c/tsconfig.json +43 -0
  12. package/.claude/worktrees/happy-lalande-c6c39c/types.ts +55 -0
  13. package/.claude/worktrees/happy-lalande-c6c39c/xpine.config.mjs +1 -0
  14. package/.claude/worktrees/jolly-hofstadter-2764be/.claude/settings.local.json +33 -0
  15. package/.claude/worktrees/jolly-hofstadter-2764be/.d.ts +1 -0
  16. package/.claude/worktrees/jolly-hofstadter-2764be/.gitattributes +2 -0
  17. package/.claude/worktrees/jolly-hofstadter-2764be/README.md +571 -0
  18. package/.claude/worktrees/jolly-hofstadter-2764be/TODO +2 -0
  19. package/.claude/worktrees/jolly-hofstadter-2764be/eslint.config.mjs +27 -0
  20. package/.claude/worktrees/jolly-hofstadter-2764be/jsx-runtime.d.ts +1 -0
  21. package/.claude/worktrees/jolly-hofstadter-2764be/package-lock.json +5862 -0
  22. package/.claude/worktrees/jolly-hofstadter-2764be/package.json +62 -0
  23. package/.claude/worktrees/jolly-hofstadter-2764be/tsconfig.json +43 -0
  24. package/.claude/worktrees/jolly-hofstadter-2764be/types.ts +55 -0
  25. package/.claude/worktrees/jolly-hofstadter-2764be/xpine.config.mjs +1 -0
  26. package/README.md +104 -0
  27. package/create-xpine-app/README.md +20 -0
  28. package/create-xpine-app/index.js +80 -0
  29. package/create-xpine-app/package.json +30 -0
  30. package/create-xpine-app/template/README.md +50 -0
  31. package/create-xpine-app/template/eslint.config.mjs +27 -0
  32. package/create-xpine-app/template/gitignore +5 -0
  33. package/create-xpine-app/template/package.json +32 -0
  34. package/create-xpine-app/template/tsconfig.json +36 -0
  35. package/create-xpine-app/template/xpine.config.mjs +1 -0
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +269 -106
  39. package/dist/src/auth.d.ts.map +1 -1
  40. package/dist/src/express.d.ts +5 -0
  41. package/dist/src/express.d.ts.map +1 -1
  42. package/dist/src/scripts/build.d.ts.map +1 -1
  43. package/dist/src/scripts/xpine-build.js +6 -2
  44. package/dist/src/scripts/xpine-dev.js +15 -21
  45. package/dist/src/util/html.d.ts +9 -1
  46. package/dist/src/util/html.d.ts.map +1 -1
  47. package/dist/src/util/regex.d.ts +2 -0
  48. package/dist/src/util/regex.d.ts.map +1 -1
  49. package/dist/types.d.ts +4 -1
  50. package/dist/types.d.ts.map +1 -1
  51. package/eslint.config.mjs +1 -1
  52. package/package.json +1 -1
  53. package/types.ts +5 -1
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "xpine",
3
+ "version": "0.0.59",
4
+ "main": "dist/index.js",
5
+ "dependencies": {
6
+ "@aws-sdk/client-secrets-manager": "^3.758.0",
7
+ "@tailwindcss/postcss": "^4.1.12",
8
+ "builtin-modules": "^4.0.0",
9
+ "chokidar": "^4.0.3",
10
+ "dotenv": "^16.4.7",
11
+ "esbuild": "^0.25.0",
12
+ "express": "^4.21.2",
13
+ "express-ws": "^5.0.2",
14
+ "fs-extra": "^11.3.0",
15
+ "glob": "^11.0.1",
16
+ "jsonwebtoken": "^9.0.2",
17
+ "micromatch": "^4.0.8",
18
+ "minify-xml": "^4.5.2",
19
+ "postcss": "^8.5.3",
20
+ "request-ip": "^3.3.0",
21
+ "shelljs": "^0.9.2",
22
+ "tsx": "^4.19.3",
23
+ "typescript": "^5.7.3",
24
+ "yargs": "^17.7.2"
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git://github.com/dan-caldwell/xpine.git"
29
+ },
30
+ "scripts": {
31
+ "build": "tsx ./src/scripts/run-build-module.ts && npm run build:types",
32
+ "build:types": "sh ./src/scripts/build-types.sh",
33
+ "xpine-dev-server": "tsx ./src/scripts/xpine-dev-server.ts",
34
+ "test": "cd ./tests && npm run test",
35
+ "test:dev": "cd ./tests && npm run dev",
36
+ "test:start": "cd ./tests && npm run build && npm start",
37
+ "test:build": "cd ./tests && npm run build",
38
+ "test:build:dev": "cd ./tests && npm run build:dev",
39
+ "lint": "npx eslint",
40
+ "fix": "npx eslint --fix",
41
+ "release:patch": "npm run build && npm version patch && npm publish"
42
+ },
43
+ "bin": {
44
+ "xpine-build": "dist/src/scripts/xpine-build.js",
45
+ "xpine-dev": "dist/src/scripts/xpine-dev.js"
46
+ },
47
+ "types": "dist/index.d.ts",
48
+ "type": "module",
49
+ "devDependencies": {
50
+ "@eslint/js": "^9.24.0",
51
+ "@tailwindcss/oxide-darwin-arm64": "^4.1.3",
52
+ "@types/express": "^5.0.0",
53
+ "@types/micromatch": "^4.0.9",
54
+ "@types/node": "^22.13.5",
55
+ "@types/request-ip": "^0.0.41",
56
+ "@types/shelljs": "^0.8.15",
57
+ "@types/yargs": "^17.0.33",
58
+ "axios": "^1.8.4",
59
+ "lightningcss-darwin-arm64": "^1.29.3",
60
+ "typescript-eslint": "^8.29.0"
61
+ }
62
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "compilerOptions": {
3
+ "jsx": "preserve",
4
+ "jsxFragmentFactory": "html.fragment",
5
+ "jsxFactory": "html.createElement",
6
+ "lib": [
7
+ "dom",
8
+ "dom.iterable",
9
+ "esnext"
10
+ ],
11
+ "allowJs": false,
12
+ "skipLibCheck": true,
13
+ "strict": false,
14
+ "forceConsistentCasingInFileNames": false,
15
+ "noEmit": true,
16
+ "esModuleInterop": true,
17
+ "target": "esnext",
18
+ "module": "ESNext",
19
+ "moduleResolution": "node",
20
+ "resolveJsonModule": true,
21
+ "isolatedModules": true,
22
+ "jsxImportSource": "xpine",
23
+ "incremental": true,
24
+ "allowImportingTsExtensions": true,
25
+ // Generate d.ts files
26
+ "declaration": true,
27
+ "emitDeclarationOnly": true,
28
+ "outDir": "dist",
29
+ "declarationMap": true,
30
+ "types": []
31
+ },
32
+ "include": [
33
+ "src/**/*.ts"
34
+ ],
35
+ "exclude": [
36
+ "node_modules",
37
+ "./node_modules",
38
+ "./node_modules/*",
39
+ "./node_modules/@types/node/index.d.ts",
40
+ "dist",
41
+ "**/*.js"
42
+ ],
43
+ }
@@ -0,0 +1,55 @@
1
+ import { NextFunction, Request, Response } from 'express';
2
+ import ts from 'typescript';
3
+
4
+ export type XPineConfig = {
5
+ [key: string]: any;
6
+ }
7
+
8
+ export type TokenUser = {
9
+ email?: string;
10
+ username?: string;
11
+ }
12
+
13
+ export type ServerRequest = Request & {
14
+ user?: TokenUser;
15
+ clientIp?: string;
16
+ }
17
+
18
+ export type WrapperProps = {
19
+ req: ServerRequest;
20
+ children: any;
21
+ config: ConfigFile;
22
+ data?: any;
23
+ routePath?: string;
24
+ }
25
+
26
+ export type ConfigFile = {
27
+ staticPaths?: boolean | (() => Promise<{ [key: string]: string }[]> | { [key: string]: string }[]);
28
+ wrapper?: (props: WrapperProps) => Promise<any>;
29
+ data?: (req: ServerRequest) => Promise<any>;
30
+ routeMiddleware?: (req: ServerRequest, res: Response, next: NextFunction) => void;
31
+ // For multi-segment dynamic routes ([...slug]): validate a slug that was not
32
+ // generated at build time so it can be resolved safely at request time.
33
+ // Return false (the default for unknown slugs) to fall through to the 404 handler.
34
+ isValid?: (slug: string, req: ServerRequest) => boolean | Promise<boolean>;
35
+ }
36
+
37
+ export type PageProps = {
38
+ req: ServerRequest;
39
+ res: Response;
40
+ data: any;
41
+ routePath: string;
42
+ }
43
+
44
+ export type FileItem = {
45
+ file: string;
46
+ size: number;
47
+ }
48
+
49
+ export type ComponentData = {
50
+ path: string;
51
+ contents: string;
52
+ clientContent: string;
53
+ configFiles: string[];
54
+ source: ts.SourceFile;
55
+ }
@@ -0,0 +1 @@
1
+ export default {};
package/README.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  Combines JSX with Alpine.js for a simpler, easier development experience. Includes a static site generator.
4
4
 
5
+ ### Quick start
6
+
7
+ Scaffold a new app with pages, demo content, and an API endpoint already set up:
8
+
9
+ ```
10
+ npx create-xpine-app my-app
11
+ cd my-app
12
+ npm install
13
+ npm run dev
14
+ ```
15
+
5
16
  ### Install
6
17
 
7
18
  `npm install xpine`
@@ -155,6 +166,47 @@ You can create catch all routes by naming the file \_all\_.(jsx|tsx|js|ts). You
155
166
 
156
167
  You can get the route param in your function with req.params[0], such as how express handles catch all routes.
157
168
 
169
+ ### Multi-segment dynamic routes (`[...slug]`)
170
+
171
+ Catch all (`_all_`) routes register a single Express wildcard, which means they match _every_ path under their prefix — including paths that should not exist. For deeply nested but known dynamic routes (e.g. a blog post at `/blog/technology/devops/my-blog-post`), use a multi-segment dynamic param instead. Name the file `[...slug].(jsx|tsx|js|ts)`; the captured value (which may contain slashes) is available as `req.params.slug`.
172
+
173
+ Instead of a wildcard, XPine registers an **explicit Express route for each slug** returned by the `staticPaths` config function, so unknown paths safely fall through to your 404 page:
174
+
175
+ ```
176
+ // /src/pages/blog/+config.ts
177
+ export default {
178
+ staticPaths() {
179
+ // Slugs can come from a CMS/database
180
+ return [
181
+ { slug: 'technology/devops/my-blog-post' },
182
+ ];
183
+ },
184
+ };
185
+ ```
186
+
187
+ ```
188
+ // /src/pages/blog/[...slug].tsx
189
+ import { PageProps } from 'xpine/dist/types';
190
+
191
+ export default function BlogPost({ req }: PageProps) {
192
+ return <div>{req.params.slug}</div>;
193
+ }
194
+ ```
195
+
196
+ Each slug from `staticPaths` is also statically generated at build time. To allow slugs that are not known at build time (without falling back to an unsafe catch-all), add an `isValid` function to the config. It receives the requested slug and runs at request time; return `false` (the default for unknown slugs) to fall through to the 404 handler:
197
+
198
+ ```
199
+ export default {
200
+ staticPaths() {
201
+ return [{ slug: 'technology/devops/my-blog-post' }];
202
+ },
203
+ // Resolve newly-published slugs without a rebuild
204
+ async isValid(slug, req) {
205
+ return await postExists(slug);
206
+ },
207
+ };
208
+ ```
209
+
158
210
  ### Route specific middleware
159
211
 
160
212
  If you need route specific middleware, e.g. for file uploads, you can specify a `routeMiddleware` function in a config variable in the endpoint file:
@@ -168,6 +220,58 @@ export const config = {
168
220
  }
169
221
  ```
170
222
 
223
+ ### CSRF protection
224
+
225
+ Because authentication is cookie based, state-changing requests (POST/PUT/PATCH/DELETE) are exposed to cross-site request forgery. Enable the built-in guard by setting `csrf` in your `xpine.config.mjs`:
226
+
227
+ ```
228
+ export default {
229
+ csrf: true,
230
+ }
231
+ ```
232
+
233
+ It uses a stateless **signed double-submit cookie**. On safe requests (GET/HEAD/OPTIONS) it sets a `csrfToken` cookie (readable by client JS). On state-changing requests it requires that same token to be echoed back in an `x-csrf-token` header (or a `_csrf` form field) — a cross-site attacker can make the browser send the cookie but cannot read it or set the header, so forged requests are rejected with a 403. The token is HMAC-signed so it can't be forged even by an attacker who can plant a cookie.
234
+
235
+ Set a secret via the `CSRF_SECRET` env var (it falls back to `JWT_PRIVATE_KEY`). The server fails to start if neither is set.
236
+
237
+ For `fetch`/Alpine requests, read the cookie and send it back:
238
+ ```
239
+ const token = document.cookie.split('; ').find(c => c.startsWith('csrfToken='))?.split('=')[1];
240
+ await fetch('/api/thing', {
241
+ method: 'POST',
242
+ headers: { 'x-csrf-token': decodeURIComponent(token) },
243
+ });
244
+ ```
245
+ For server-rendered HTML forms, the token is available on `res.locals.csrfToken` (pages receive `res`); render it as a hidden `<input name="_csrf">`.
246
+
247
+ You can customize the cookie/header/field names, cookie attributes, and skip specific path prefixes (e.g. signed webhooks):
248
+ ```
249
+ export default {
250
+ csrf: {
251
+ headerName: 'x-csrf-token',
252
+ cookie: { sameSite: 'strict', secure: true },
253
+ ignorePaths: ['/api/webhooks/'],
254
+ },
255
+ }
256
+ ```
257
+
258
+ ### HTML escaping (XSS protection)
259
+
260
+ Values interpolated into JSX are HTML-escaped by default, so rendering user input is safe:
261
+ ```
262
+ // req.params.slug = '<img src=x onerror=alert(1)>'
263
+ <div>{req.params.slug}</div> // renders &lt;img src=x onerror=alert(1)&gt;
264
+ ```
265
+ Nested components and elements are not re-escaped, and attribute values are escaped too. Text inside `<script>`/`<style>` is left raw (it isn't HTML) — never interpolate untrusted data there.
266
+
267
+ If you have trusted HTML that should render as-is, opt out explicitly with `raw()`:
268
+ ```
269
+ import { raw } from 'xpine';
270
+
271
+ <div>{raw(trustedHtmlString)}</div>
272
+ ```
273
+ Only use `raw()` with HTML you control — never with user input.
274
+
171
275
  ### Static Site Generation
172
276
 
173
277
  Generate path specific static pages by specifying in the config of either the page's file, such as `/src/pages/about.tsx` with a config export:
@@ -0,0 +1,20 @@
1
+ # create-xpine-app
2
+
3
+ Scaffold a new [XPine](https://github.com/dan-caldwell/xpine) app (Alpine.js + JSX + Tailwind) with one command:
4
+
5
+ ```
6
+ npx create-xpine-app my-app
7
+ cd my-app
8
+ npm install
9
+ npm run dev
10
+ ```
11
+
12
+ Run `npx create-xpine-app .` to scaffold into the current directory.
13
+
14
+ The generated app includes:
15
+
16
+ - File-based routing with a shared page wrapper
17
+ - Server-side data fetching from the [JSONPlaceholder API](https://jsonplaceholder.typicode.com) (posts and users pages)
18
+ - A dynamic route (`/posts/[id]`) with static generation for known paths
19
+ - An Express API endpoint (`/api/posts`)
20
+ - Alpine.js interactivity examples and Tailwind CSS styling
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import readline from 'node:readline/promises';
6
+
7
+ const templateDir = path.join(path.dirname(fileURLToPath(import.meta.url)), 'template');
8
+
9
+ const helpMessage = `
10
+ Usage: npx create-xpine-app [project-name]
11
+
12
+ Scaffolds a new XPine app in a directory named [project-name].
13
+ Pass "." to scaffold into the current directory.
14
+
15
+ Options:
16
+ -h, --help Show this message
17
+ `;
18
+
19
+ function exitWithError(message) {
20
+ console.error(`\x1b[31m${message}\x1b[0m`);
21
+ process.exit(1);
22
+ }
23
+
24
+ function isValidProjectName(name) {
25
+ return /^[a-z0-9~-][a-z0-9._~-]*$/.test(name);
26
+ }
27
+
28
+ function isEmptyDir(dir) {
29
+ return fs.readdirSync(dir).filter((file) => file !== '.git').length === 0;
30
+ }
31
+
32
+ async function getProjectName() {
33
+ const arg = process.argv[2];
34
+ if (arg) return arg;
35
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
36
+ const answer = await rl.question('Project name: (my-xpine-app) ');
37
+ rl.close();
38
+ return answer.trim() || 'my-xpine-app';
39
+ }
40
+
41
+ if (['-h', '--help'].includes(process.argv[2])) {
42
+ console.log(helpMessage);
43
+ process.exit(0);
44
+ }
45
+
46
+ const projectName = await getProjectName();
47
+ const useCurrentDir = projectName === '.';
48
+ const targetDir = useCurrentDir ? process.cwd() : path.resolve(process.cwd(), projectName);
49
+ const packageName = useCurrentDir ? path.basename(targetDir) : projectName;
50
+
51
+ if (!isValidProjectName(packageName)) {
52
+ exitWithError(`Invalid project name: "${packageName}". Use lowercase letters, numbers, dashes, dots, and underscores.`);
53
+ }
54
+
55
+ if (fs.existsSync(targetDir) && !isEmptyDir(targetDir)) {
56
+ exitWithError(`The directory ${useCurrentDir ? 'you are in' : `"${projectName}"`} already exists and is not empty.`);
57
+ }
58
+
59
+ console.log(`\nScaffolding XPine app in ${targetDir}...`);
60
+
61
+ fs.cpSync(templateDir, targetDir, { recursive: true });
62
+
63
+ // npm strips .gitignore files from published packages, so the template ships
64
+ // it as "gitignore" and it gets renamed here
65
+ fs.renameSync(path.join(targetDir, 'gitignore'), path.join(targetDir, '.gitignore'));
66
+
67
+ const packageJsonPath = path.join(targetDir, 'package.json');
68
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
69
+ packageJson.name = packageName;
70
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
71
+
72
+ console.log(`
73
+ Done! Next steps:
74
+ ${useCurrentDir ? '' : `
75
+ cd ${projectName}`}
76
+ npm install
77
+ npm run dev
78
+
79
+ Your app will be running at http://localhost:3000
80
+ `);
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "create-xpine-app",
3
+ "version": "0.0.1",
4
+ "description": "Scaffold a new XPine app with one command",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-xpine-app": "index.js"
8
+ },
9
+ "files": [
10
+ "index.js",
11
+ "template"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git://github.com/dan-caldwell/xpine.git",
19
+ "directory": "create-xpine-app"
20
+ },
21
+ "keywords": [
22
+ "xpine",
23
+ "create-xpine-app",
24
+ "alpinejs",
25
+ "jsx",
26
+ "tailwind",
27
+ "scaffold"
28
+ ],
29
+ "license": "ISC"
30
+ }
@@ -0,0 +1,50 @@
1
+ # My XPine App
2
+
3
+ A starter app built with [XPine](https://github.com/dan-caldwell/xpine) — Alpine.js + JSX + Tailwind. Demo content comes from the [JSONPlaceholder API](https://jsonplaceholder.typicode.com).
4
+
5
+ ## Getting started
6
+
7
+ ```
8
+ npm install
9
+ npm run dev
10
+ ```
11
+
12
+ Then open http://localhost:3000
13
+
14
+ ## Scripts
15
+
16
+ | Command | Description |
17
+ | --- | --- |
18
+ | `npm run dev` | Start the dev server with live reload |
19
+ | `npm run build` | Build the app for production (includes static page generation) |
20
+ | `npm start` | Run the production build |
21
+ | `npm run lint` | Lint the code |
22
+
23
+ ## Project structure
24
+
25
+ ```
26
+ src/
27
+ components/ Shared JSX components (Base layout, Navbar)
28
+ pages/ File-based routes
29
+ +config.tsx Shared page wrapper (applies to all pages in this folder and below)
30
+ index.tsx / — home page with Alpine.js examples
31
+ posts/
32
+ index.tsx /posts — post list fetched server-side
33
+ [id].tsx /posts/:id — dynamic route with static generation for known ids
34
+ users/
35
+ index.tsx /users
36
+ api/
37
+ posts.GET.ts /api/posts — Express API endpoint
38
+ 404.tsx Not-found page
39
+ public/
40
+ scripts/ Client-side entry (starts Alpine.js)
41
+ styles/ Global CSS (Tailwind)
42
+ server/ Express server setup
43
+ util/ Shared server-side helpers
44
+ ```
45
+
46
+ ## Learn more
47
+
48
+ - Page routing, static generation, API endpoints, and more: [XPine README](https://github.com/dan-caldwell/xpine#readme)
49
+ - Alpine.js docs: https://alpinejs.dev
50
+ - Tailwind CSS docs: https://tailwindcss.com
@@ -0,0 +1,27 @@
1
+ import eslint from '@eslint/js';
2
+ import tseslint from 'typescript-eslint';
3
+
4
+ export default tseslint.config(
5
+ eslint.configs.recommended,
6
+ tseslint.configs.recommended,
7
+ {
8
+ rules: {
9
+ 'eol-last': 2,
10
+ 'quotes': ['error', 'single'],
11
+ 'comma-dangle': ['error', {
12
+ 'objects': 'always',
13
+ 'arrays': 'never',
14
+ 'imports': 'never',
15
+ 'exports': 'never',
16
+ 'functions': 'never',
17
+ }],
18
+ 'semi': 'error',
19
+ '@typescript-eslint/ban-ts-comment': 'off',
20
+ '@typescript-eslint/no-require-imports': 'off',
21
+ },
22
+ files: ['src/**/*.{js,mjs,ts,tsx,jsx}'],
23
+ },
24
+ {
25
+ ignores: ['dist/'],
26
+ }
27
+ );
@@ -0,0 +1,5 @@
1
+ node_modules
2
+ dist
3
+ .env*
4
+ *.tsbuildinfo
5
+ .DS_Store
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "xpine-app",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "PORT=3000 LOCALHOST=1 xpine-dev",
8
+ "build": "xpine-build --removePreviousBuild",
9
+ "start": "PORT=3000 node ./dist/server/run/prod.js",
10
+ "lint": "npx eslint",
11
+ "fix": "npx eslint --fix"
12
+ },
13
+ "dependencies": {
14
+ "alpinejs": "^3.14.8",
15
+ "cookie-parser": "^1.4.7",
16
+ "express": "^4.21.2",
17
+ "morgan": "^1.10.0",
18
+ "tailwindcss": "^4.1.12",
19
+ "xpine": "^0.0.59"
20
+ },
21
+ "devDependencies": {
22
+ "@eslint/js": "^9.24.0",
23
+ "@types/alpinejs": "^3.13.11",
24
+ "@types/cookie-parser": "^1.4.8",
25
+ "@types/express": "^5.0.0",
26
+ "@types/morgan": "^1.9.9",
27
+ "@types/node": "^22.13.5",
28
+ "eslint": "^9.24.0",
29
+ "typescript": "^5.7.3",
30
+ "typescript-eslint": "^8.29.0"
31
+ }
32
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "compilerOptions": {
3
+ "jsx": "preserve",
4
+ "jsxFragmentFactory": "html.fragment",
5
+ "jsxFactory": "html.createElement",
6
+ "jsxImportSource": "xpine",
7
+ "lib": [
8
+ "dom",
9
+ "dom.iterable",
10
+ "esnext"
11
+ ],
12
+ "target": "esnext",
13
+ "module": "ESNext",
14
+ "moduleResolution": "node",
15
+ "allowJs": false,
16
+ "skipLibCheck": true,
17
+ "strict": false,
18
+ "noEmit": true,
19
+ "esModuleInterop": true,
20
+ "resolveJsonModule": true,
21
+ "isolatedModules": true,
22
+ "forceConsistentCasingInFileNames": false,
23
+ "incremental": true,
24
+ "allowImportingTsExtensions": true
25
+ },
26
+ "include": [
27
+ "**/*.ts",
28
+ "**/*.tsx",
29
+ "eslint.config.mjs"
30
+ ],
31
+ "exclude": [
32
+ "node_modules",
33
+ "dist",
34
+ "**/*.js"
35
+ ]
36
+ }
@@ -0,0 +1 @@
1
+ export default {};
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export * from './src/util/env';
4
4
  export * from './src/util/get-config';
5
5
  export * from './src/scripts/build';
6
6
  export * from './src/auth';
7
+ export * from './src/csrf';
7
8
  export * from './src/util/html';
8
9
  export * from './src/context';
9
10
  export * from './src/util/web';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC"}