@org-press/deploy-cloudflare 0.9.12
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.
- package/LICENSE +29 -0
- package/README.md +239 -0
- package/dist/index.js +169 -0
- package/package.json +48 -0
- package/src/adapter.test.ts +693 -0
- package/src/adapter.ts +287 -0
- package/src/index.ts +27 -0
- package/src/types.ts +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
GNU GENERAL PUBLIC LICENSE
|
|
2
|
+
Version 2, June 1991
|
|
3
|
+
|
|
4
|
+
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
|
5
|
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
6
|
+
|
|
7
|
+
Everyone is permitted to copy and distribute verbatim copies
|
|
8
|
+
of this license document, but changing it is not allowed.
|
|
9
|
+
|
|
10
|
+
For the full license text, see: https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
Org-Press - Static site generator for org-mode files
|
|
15
|
+
Copyright (C) 2024-2026
|
|
16
|
+
|
|
17
|
+
This program is free software; you can redistribute it and/or modify
|
|
18
|
+
it under the terms of the GNU General Public License as published by
|
|
19
|
+
the Free Software Foundation; either version 2 of the License, or
|
|
20
|
+
(at your option) any later version.
|
|
21
|
+
|
|
22
|
+
This program is distributed in the hope that it will be useful,
|
|
23
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
24
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
25
|
+
GNU General Public License for more details.
|
|
26
|
+
|
|
27
|
+
You should have received a copy of the GNU General Public License
|
|
28
|
+
along with this program; if not, write to the Free Software
|
|
29
|
+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
package/README.md
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# @org-press/deploy-cloudflare
|
|
2
|
+
|
|
3
|
+
Cloudflare Pages deploy adapter for [org-press](https://orgp.dev).
|
|
4
|
+
|
|
5
|
+
Deploys static sites to Cloudflare Pages using the wrangler CLI.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @org-press/deploy-cloudflare
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @org-press/deploy-cloudflare
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
You also need wrangler installed:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -D wrangler
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### Basic Usage
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { cloudflareAdapter } from '@org-press/deploy-cloudflare';
|
|
27
|
+
|
|
28
|
+
export default defineConfig({
|
|
29
|
+
deploy: {
|
|
30
|
+
adapter: cloudflareAdapter({
|
|
31
|
+
project: 'my-site',
|
|
32
|
+
}),
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Branch Deployments (Previews)
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { cloudflareAdapter } from '@org-press/deploy-cloudflare';
|
|
41
|
+
|
|
42
|
+
export default defineConfig({
|
|
43
|
+
deploy: {
|
|
44
|
+
adapter: cloudflareAdapter({
|
|
45
|
+
project: 'my-site',
|
|
46
|
+
branch: 'preview',
|
|
47
|
+
}),
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Full Configuration
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { cloudflareAdapter } from '@org-press/deploy-cloudflare';
|
|
56
|
+
|
|
57
|
+
export default defineConfig({
|
|
58
|
+
deploy: {
|
|
59
|
+
adapter: cloudflareAdapter({
|
|
60
|
+
// Cloudflare Pages project name (required)
|
|
61
|
+
project: 'my-site',
|
|
62
|
+
|
|
63
|
+
// Cloudflare account ID (optional, uses CF_ACCOUNT_ID env var if not set)
|
|
64
|
+
accountId: '1234567890abcdef',
|
|
65
|
+
|
|
66
|
+
// Branch for preview deployments (optional)
|
|
67
|
+
// If not specified, deploys to production
|
|
68
|
+
branch: 'preview',
|
|
69
|
+
|
|
70
|
+
// Deployment commit message (optional)
|
|
71
|
+
commitMessage: 'Deploy from CI',
|
|
72
|
+
}),
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Configuration Options
|
|
78
|
+
|
|
79
|
+
| Option | Type | Default | Description |
|
|
80
|
+
|--------|------|---------|-------------|
|
|
81
|
+
| `project` | `string` | **required** | Cloudflare Pages project name |
|
|
82
|
+
| `accountId` | `string` | From `CF_ACCOUNT_ID` | Cloudflare account ID |
|
|
83
|
+
| `branch` | `string` | - | Branch for preview deployments |
|
|
84
|
+
| `commitMessage` | `string` | `'Deploy from org-press'` | Deployment message shown in dashboard |
|
|
85
|
+
|
|
86
|
+
## Environment Variables
|
|
87
|
+
|
|
88
|
+
| Variable | Description |
|
|
89
|
+
|----------|-------------|
|
|
90
|
+
| `CLOUDFLARE_API_TOKEN` | Cloudflare API token for authentication |
|
|
91
|
+
| `CF_API_TOKEN` | Alternative API token variable |
|
|
92
|
+
| `CF_ACCOUNT_ID` | Cloudflare account ID |
|
|
93
|
+
|
|
94
|
+
## How It Works
|
|
95
|
+
|
|
96
|
+
The adapter deploys your site using the wrangler CLI:
|
|
97
|
+
|
|
98
|
+
1. Validates wrangler is available
|
|
99
|
+
2. Runs `wrangler pages deploy <outDir> --project-name <project>`
|
|
100
|
+
3. Parses the deployment URL from wrangler output
|
|
101
|
+
4. Returns the deployment result with URL
|
|
102
|
+
|
|
103
|
+
## Deployment URLs
|
|
104
|
+
|
|
105
|
+
Cloudflare Pages provides different URLs based on deployment type:
|
|
106
|
+
|
|
107
|
+
- **Production**: `https://<project>.pages.dev`
|
|
108
|
+
- **Branch/Preview**: `https://<branch>.<project>.pages.dev`
|
|
109
|
+
- **Custom Domain**: Configure in Cloudflare dashboard
|
|
110
|
+
|
|
111
|
+
## Dry Run Mode
|
|
112
|
+
|
|
113
|
+
Test your deployment configuration without actually deploying:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const result = await deploy({
|
|
117
|
+
adapter: cloudflareAdapter({ project: 'my-site' }),
|
|
118
|
+
dryRun: true,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
console.log(result.url); // Predicted Cloudflare Pages URL
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Authentication
|
|
125
|
+
|
|
126
|
+
Wrangler handles authentication in several ways:
|
|
127
|
+
|
|
128
|
+
1. **API Token** (recommended): Set `CLOUDFLARE_API_TOKEN` environment variable
|
|
129
|
+
2. **OAuth**: Run `wrangler login` to authenticate interactively
|
|
130
|
+
3. **Cached credentials**: Wrangler caches credentials after login
|
|
131
|
+
|
|
132
|
+
### Creating an API Token
|
|
133
|
+
|
|
134
|
+
1. Go to Cloudflare Dashboard > My Profile > API Tokens
|
|
135
|
+
2. Create a token with "Cloudflare Pages: Edit" permission
|
|
136
|
+
3. Set the token as `CLOUDFLARE_API_TOKEN` environment variable
|
|
137
|
+
|
|
138
|
+
## CI/CD Examples
|
|
139
|
+
|
|
140
|
+
### GitHub Actions
|
|
141
|
+
|
|
142
|
+
```yaml
|
|
143
|
+
name: Deploy to Cloudflare Pages
|
|
144
|
+
|
|
145
|
+
on:
|
|
146
|
+
push:
|
|
147
|
+
branches: [main]
|
|
148
|
+
|
|
149
|
+
jobs:
|
|
150
|
+
deploy:
|
|
151
|
+
runs-on: ubuntu-latest
|
|
152
|
+
steps:
|
|
153
|
+
- uses: actions/checkout@v4
|
|
154
|
+
|
|
155
|
+
- uses: pnpm/action-setup@v2
|
|
156
|
+
with:
|
|
157
|
+
version: 8
|
|
158
|
+
|
|
159
|
+
- uses: actions/setup-node@v4
|
|
160
|
+
with:
|
|
161
|
+
node-version: 20
|
|
162
|
+
cache: 'pnpm'
|
|
163
|
+
|
|
164
|
+
- run: pnpm install
|
|
165
|
+
- run: pnpm build
|
|
166
|
+
|
|
167
|
+
- name: Deploy to Cloudflare Pages
|
|
168
|
+
run: pnpm deploy
|
|
169
|
+
env:
|
|
170
|
+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
171
|
+
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Preview Deployments on PR
|
|
175
|
+
|
|
176
|
+
```yaml
|
|
177
|
+
name: Preview Deployment
|
|
178
|
+
|
|
179
|
+
on:
|
|
180
|
+
pull_request:
|
|
181
|
+
branches: [main]
|
|
182
|
+
|
|
183
|
+
jobs:
|
|
184
|
+
preview:
|
|
185
|
+
runs-on: ubuntu-latest
|
|
186
|
+
steps:
|
|
187
|
+
- uses: actions/checkout@v4
|
|
188
|
+
|
|
189
|
+
- uses: pnpm/action-setup@v2
|
|
190
|
+
with:
|
|
191
|
+
version: 8
|
|
192
|
+
|
|
193
|
+
- uses: actions/setup-node@v4
|
|
194
|
+
with:
|
|
195
|
+
node-version: 20
|
|
196
|
+
cache: 'pnpm'
|
|
197
|
+
|
|
198
|
+
- run: pnpm install
|
|
199
|
+
- run: pnpm build
|
|
200
|
+
|
|
201
|
+
- name: Deploy Preview
|
|
202
|
+
run: pnpm deploy --branch pr-${{ github.event.number }}
|
|
203
|
+
env:
|
|
204
|
+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
205
|
+
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Requirements
|
|
209
|
+
|
|
210
|
+
- Node.js 18+
|
|
211
|
+
- wrangler CLI (installed as dev dependency)
|
|
212
|
+
- Cloudflare account with Pages enabled
|
|
213
|
+
- API token or interactive login
|
|
214
|
+
|
|
215
|
+
## Troubleshooting
|
|
216
|
+
|
|
217
|
+
### "wrangler is not available"
|
|
218
|
+
|
|
219
|
+
Install wrangler as a dev dependency:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
npm install -D wrangler
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### "Authentication failed"
|
|
226
|
+
|
|
227
|
+
Ensure your API token has the correct permissions:
|
|
228
|
+
- Cloudflare Pages: Edit
|
|
229
|
+
- Account: Read (if using account ID auto-detection)
|
|
230
|
+
|
|
231
|
+
### "Project not found"
|
|
232
|
+
|
|
233
|
+
The project must exist in your Cloudflare Pages dashboard before deployment. Create it via:
|
|
234
|
+
- Cloudflare dashboard
|
|
235
|
+
- `wrangler pages project create <name>`
|
|
236
|
+
|
|
237
|
+
## License
|
|
238
|
+
|
|
239
|
+
GPL-2.0
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// src/adapter.ts
|
|
2
|
+
import { spawnSync } from "child_process";
|
|
3
|
+
var CloudflareAdapter = class {
|
|
4
|
+
name = "cloudflare";
|
|
5
|
+
description = "Deploy to Cloudflare Pages";
|
|
6
|
+
config;
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Validate adapter configuration
|
|
12
|
+
*
|
|
13
|
+
* Checks:
|
|
14
|
+
* - wrangler is available (via npx)
|
|
15
|
+
* - Project name is valid
|
|
16
|
+
* - API token is available in environment
|
|
17
|
+
*/
|
|
18
|
+
async validate(adapterConfig) {
|
|
19
|
+
const errors = [];
|
|
20
|
+
const warnings = [];
|
|
21
|
+
const wranglerCheck = spawnSync("npx", ["wrangler", "--version"], {
|
|
22
|
+
encoding: "utf-8",
|
|
23
|
+
timeout: 3e4
|
|
24
|
+
});
|
|
25
|
+
if (wranglerCheck.status !== 0) {
|
|
26
|
+
errors.push(
|
|
27
|
+
"wrangler is not available. Install with: npm install -D wrangler"
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
const project = adapterConfig.options.project || this.config.project;
|
|
31
|
+
if (!project) {
|
|
32
|
+
errors.push("Cloudflare Pages project name is required");
|
|
33
|
+
} else {
|
|
34
|
+
const projectPattern = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/;
|
|
35
|
+
if (!projectPattern.test(project)) {
|
|
36
|
+
errors.push(
|
|
37
|
+
`Invalid project name: "${project}". Must be lowercase alphanumeric with hyphens, not starting or ending with hyphen.`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const apiToken = adapterConfig.env.CLOUDFLARE_API_TOKEN || adapterConfig.env.CF_API_TOKEN;
|
|
42
|
+
if (!apiToken) {
|
|
43
|
+
warnings.push(
|
|
44
|
+
"No CLOUDFLARE_API_TOKEN or CF_API_TOKEN found. Wrangler will prompt for authentication or use cached credentials."
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
const accountId = adapterConfig.options.accountId || this.config.accountId || adapterConfig.env.CF_ACCOUNT_ID;
|
|
48
|
+
if (!accountId) {
|
|
49
|
+
warnings.push(
|
|
50
|
+
"No account ID specified. Wrangler will attempt to auto-detect or prompt for selection."
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
valid: errors.length === 0,
|
|
55
|
+
errors,
|
|
56
|
+
warnings
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Execute deployment to Cloudflare Pages
|
|
61
|
+
*
|
|
62
|
+
* Uses wrangler pages deploy command to upload and deploy the site.
|
|
63
|
+
*/
|
|
64
|
+
async deploy(context) {
|
|
65
|
+
const { outDir, adapterConfig, dryRun, logger } = context;
|
|
66
|
+
const project = adapterConfig.project || this.config.project;
|
|
67
|
+
const branch = adapterConfig.branch || this.config.branch;
|
|
68
|
+
const commitMessage = adapterConfig.commitMessage || this.config.commitMessage || "Deploy from org-press";
|
|
69
|
+
const accountId = adapterConfig.accountId || this.config.accountId || process.env.CF_ACCOUNT_ID;
|
|
70
|
+
logger.info(`Deploying to Cloudflare Pages: ${project}`);
|
|
71
|
+
if (branch) {
|
|
72
|
+
logger.info(`Branch deployment: ${branch}`);
|
|
73
|
+
} else {
|
|
74
|
+
logger.info("Production deployment");
|
|
75
|
+
}
|
|
76
|
+
if (dryRun) {
|
|
77
|
+
logger.info("Dry run mode - skipping actual deployment");
|
|
78
|
+
const previewUrl = branch ? `https://${branch}.${project}.pages.dev` : `https://${project}.pages.dev`;
|
|
79
|
+
return {
|
|
80
|
+
success: true,
|
|
81
|
+
deploymentId: `dry-run-${Date.now()}`,
|
|
82
|
+
url: previewUrl,
|
|
83
|
+
logs: ["Dry run completed successfully"]
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const args = ["wrangler", "pages", "deploy", outDir];
|
|
88
|
+
args.push("--project-name", project);
|
|
89
|
+
if (branch) {
|
|
90
|
+
args.push("--branch", branch);
|
|
91
|
+
}
|
|
92
|
+
if (commitMessage) {
|
|
93
|
+
args.push("--commit-message", commitMessage);
|
|
94
|
+
}
|
|
95
|
+
const env = { ...process.env };
|
|
96
|
+
if (accountId) {
|
|
97
|
+
env.CLOUDFLARE_ACCOUNT_ID = accountId;
|
|
98
|
+
}
|
|
99
|
+
logger.info(`Running: npx ${args.join(" ")}`);
|
|
100
|
+
const result = spawnSync("npx", args, {
|
|
101
|
+
encoding: "utf-8",
|
|
102
|
+
timeout: 3e5,
|
|
103
|
+
// 5 minute timeout for uploads
|
|
104
|
+
env
|
|
105
|
+
});
|
|
106
|
+
if (result.status !== 0) {
|
|
107
|
+
const errorOutput = result.stderr || result.stdout || "Unknown error";
|
|
108
|
+
logger.error(`Wrangler failed: ${errorOutput}`);
|
|
109
|
+
return {
|
|
110
|
+
success: false,
|
|
111
|
+
error: `Wrangler deployment failed: ${errorOutput}`,
|
|
112
|
+
logs: result.stdout ? [result.stdout] : void 0
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const output = result.stdout || "";
|
|
116
|
+
const url = this.parseDeploymentUrl(output, project, branch);
|
|
117
|
+
logger.info("Successfully deployed to Cloudflare Pages!");
|
|
118
|
+
if (url) {
|
|
119
|
+
logger.info(`Deployment URL: ${url}`);
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
success: true,
|
|
123
|
+
deploymentId: this.parseDeploymentId(output) || `cf-${Date.now()}`,
|
|
124
|
+
url,
|
|
125
|
+
logs: output ? [output] : void 0
|
|
126
|
+
};
|
|
127
|
+
} catch (err) {
|
|
128
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
129
|
+
logger.error(`Deployment failed: ${error}`);
|
|
130
|
+
return {
|
|
131
|
+
success: false,
|
|
132
|
+
error: `Deployment failed: ${error}`
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Parse deployment URL from wrangler output
|
|
138
|
+
*
|
|
139
|
+
* Wrangler outputs the URL in various formats:
|
|
140
|
+
* - "Published to https://xxx.project.pages.dev"
|
|
141
|
+
* - "Deployment complete! https://xxx.project.pages.dev"
|
|
142
|
+
*/
|
|
143
|
+
parseDeploymentUrl(output, project, branch) {
|
|
144
|
+
const urlMatch = output.match(
|
|
145
|
+
/https:\/\/[a-z0-9-]+(?:\.[a-z0-9-]+)*\.pages\.dev/i
|
|
146
|
+
);
|
|
147
|
+
if (urlMatch) {
|
|
148
|
+
return urlMatch[0];
|
|
149
|
+
}
|
|
150
|
+
if (branch) {
|
|
151
|
+
return `https://${branch}.${project}.pages.dev`;
|
|
152
|
+
}
|
|
153
|
+
return `https://${project}.pages.dev`;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Parse deployment ID from wrangler output
|
|
157
|
+
*/
|
|
158
|
+
parseDeploymentId(output) {
|
|
159
|
+
const idMatch = output.match(/deployment[:\s]+([a-f0-9-]{36})/i);
|
|
160
|
+
return idMatch ? idMatch[1] : void 0;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
function cloudflareAdapter(config) {
|
|
164
|
+
return new CloudflareAdapter(config);
|
|
165
|
+
}
|
|
166
|
+
export {
|
|
167
|
+
CloudflareAdapter,
|
|
168
|
+
cloudflareAdapter
|
|
169
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@org-press/deploy-cloudflare",
|
|
3
|
+
"version": "0.9.12",
|
|
4
|
+
"description": "Cloudflare Pages deploy adapter for org-press",
|
|
5
|
+
"license": "GPL-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"src",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"@org-press/deploy": ">=0.9.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"tsup": "^8.0.0",
|
|
25
|
+
"typescript": "~5.7.3",
|
|
26
|
+
"@types/node": "^20.0.0",
|
|
27
|
+
"vitest": "^3.1.4",
|
|
28
|
+
"@org-press/deploy": "0.9.12"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://orgp.dev",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/org-press/org-press.git"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/org-press/org-press/issues"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsup",
|
|
43
|
+
"dev": "tsup --watch",
|
|
44
|
+
"test": "vitest",
|
|
45
|
+
"test:ci": "vitest --run",
|
|
46
|
+
"clean": "rm -rf dist"
|
|
47
|
+
}
|
|
48
|
+
}
|