@netlify/agent-runner-cli 1.29.1 → 1.30.1-broken.0
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/README.md +6 -5
- package/dist/bin-local.d.ts +1 -0
- package/dist/bin-local.js +301 -0
- package/dist/bin.js +225 -78
- package/dist/index.d.ts +64 -12
- package/dist/index.js +211 -77
- package/dist/scripts/scaffold.d.ts +2 -0
- package/dist/scripts/scaffold.js +13 -0
- package/dist/skills/frontend-design/SKILL.md +86 -0
- package/dist/skills/general-database/SKILL.md +45 -0
- package/dist/skills/netlify-ai-gateway/SKILL.md +411 -0
- package/dist/skills/netlify-blobs/SKILL.md +421 -0
- package/dist/skills/netlify-database/SKILL.md +423 -0
- package/dist/skills/netlify-edge-functions/SKILL.md +478 -0
- package/dist/skills/netlify-forms/SKILL.md +358 -0
- package/dist/skills/netlify-forms/scripts/enable.cjs +12 -0
- package/dist/skills/netlify-functions/SKILL.md +478 -0
- package/dist/skills/netlify-identity/SKILL.md +632 -0
- package/dist/skills/netlify-identity/scripts/enable.cjs +12 -0
- package/dist/skills/netlify-image-cdn/SKILL.md +244 -0
- package/package.json +40 -28
- package/scripts/scenarios/install-skills.mjs +57 -0
- package/scripts/scenarios/netlify-deploy.mjs +311 -0
- package/scripts/scenarios/prewarm-template-mirror.mjs +69 -0
- package/patches/@google+gemini-cli+0.1.17.patch +0 -87
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: netlify-forms
|
|
3
|
+
description: Build and configure Netlify Forms for serverless form handling. Use when implementing contact forms, feedback forms, file uploads, or any form that collects user submissions without backend code. Make sure to always run `node scripts/enable.cjs` located in this skill's directory after implementing Netlify Forms.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Netlify Forms
|
|
7
|
+
|
|
8
|
+
Netlify Forms is a serverless form-handling service. The build system parses HTML at deploy time to detect forms and
|
|
9
|
+
automatically creates submission endpoints.
|
|
10
|
+
|
|
11
|
+
## Enable Feature
|
|
12
|
+
|
|
13
|
+
After implementing Netlify Forms, you **must** run the enable script to activate the feature on the deploy. The script
|
|
14
|
+
is located in this skill's `scripts/` directory:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
node scripts/enable.cjs
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
Add `data-netlify="true"` or `netlify` attribute to any `<form>` tag:
|
|
23
|
+
|
|
24
|
+
```html
|
|
25
|
+
<form name="contact" method="POST" data-netlify="true">
|
|
26
|
+
<input type="text" name="name" required />
|
|
27
|
+
<input type="email" name="email" required />
|
|
28
|
+
<textarea name="message"></textarea>
|
|
29
|
+
<button type="submit">Send</button>
|
|
30
|
+
</form>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Key requirements:**
|
|
34
|
+
|
|
35
|
+
- The `name` attribute identifies the form in Netlify UI (must be unique per site)
|
|
36
|
+
- Method must be `POST`
|
|
37
|
+
- Netlify injects a hidden `form-name` field during build
|
|
38
|
+
|
|
39
|
+
## AJAX / JavaScript Submission
|
|
40
|
+
|
|
41
|
+
Submit forms without page reload:
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
const form = document.getElementById('contact-form')
|
|
45
|
+
|
|
46
|
+
form.addEventListener('submit', async (e) => {
|
|
47
|
+
e.preventDefault()
|
|
48
|
+
|
|
49
|
+
const response = await fetch('/', {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
52
|
+
body: new URLSearchParams(new FormData(form)).toString(),
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
if (response.ok) {
|
|
56
|
+
// Handle success
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
> **SSR/SPA apps (React, Vue, TanStack Start, Next.js, SvelteKit, Remix, Nuxt):** You MUST create a static HTML skeleton
|
|
62
|
+
> file for build-time form detection. Without it, Netlify cannot detect the form during build and submissions will
|
|
63
|
+
> silently fail. Additionally, the `fetch` URL must target the skeleton file path (e.g. `/__forms.html`), **not** `/` —
|
|
64
|
+
> in SSR apps, `fetch('/')` is intercepted by the SSR function and never reaches Netlify's form processing middleware.
|
|
65
|
+
> See [JavaScript Frameworks](#javascript-frameworks-ssr--client-rendered-apps) below.
|
|
66
|
+
|
|
67
|
+
**Critical AJAX requirements:**
|
|
68
|
+
|
|
69
|
+
1. Content-Type MUST be `application/x-www-form-urlencoded`
|
|
70
|
+
2. Include hidden `form-name` field in your HTML:
|
|
71
|
+
```html
|
|
72
|
+
<input type="hidden" name="form-name" value="contact" />
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## JavaScript Frameworks (SSR & Client-Rendered Apps)
|
|
76
|
+
|
|
77
|
+
Netlify's build bot cannot detect forms rendered client-side. For any SSR or SPA framework (React, Vue, TanStack Start,
|
|
78
|
+
Next.js, SvelteKit, Remix, Nuxt), you MUST:
|
|
79
|
+
|
|
80
|
+
1. **Create a static HTML skeleton** in `public/` for build-time form detection — this is the critical step. Without it,
|
|
81
|
+
Netlify never registers the form and submissions will silently fail or 404.
|
|
82
|
+
2. Submit via AJAX with `e.preventDefault()` — full-page POST will not work in SPAs
|
|
83
|
+
3. Include a hidden `form-name` field matching the form's `name` attribute
|
|
84
|
+
|
|
85
|
+
### Static Form Skeleton
|
|
86
|
+
|
|
87
|
+
Create a static HTML file (e.g. `public/__forms.html`) containing every form your app uses. The file name does not
|
|
88
|
+
matter — Netlify scans all HTML files in the build output:
|
|
89
|
+
|
|
90
|
+
```html
|
|
91
|
+
<!-- public/__forms.html — only for Netlify's build bot detection -->
|
|
92
|
+
<html>
|
|
93
|
+
<body>
|
|
94
|
+
<form name="contact" data-netlify="true" netlify-honeypot="bot-field" hidden>
|
|
95
|
+
<input type="hidden" name="form-name" value="contact" />
|
|
96
|
+
<input name="name" />
|
|
97
|
+
<input name="email" />
|
|
98
|
+
<textarea name="message"></textarea>
|
|
99
|
+
<input name="bot-field" />
|
|
100
|
+
</form>
|
|
101
|
+
</body>
|
|
102
|
+
</html>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Rules:**
|
|
106
|
+
|
|
107
|
+
- The form `name` attribute must exactly match the `form-name` value in your React/Vue component
|
|
108
|
+
- Include every field your component submits — Netlify validates field names against the registered form
|
|
109
|
+
- Add `netlify-honeypot="bot-field"` and a `bot-field` input for spam protection
|
|
110
|
+
|
|
111
|
+
### React Example
|
|
112
|
+
|
|
113
|
+
```jsx
|
|
114
|
+
function ContactForm() {
|
|
115
|
+
const handleSubmit = async (e) => {
|
|
116
|
+
e.preventDefault()
|
|
117
|
+
const formData = new FormData(e.target)
|
|
118
|
+
|
|
119
|
+
// URL must point to the static skeleton file, not '/' — SSR catch-all intercepts '/'
|
|
120
|
+
await fetch('/__forms.html', {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
123
|
+
body: new URLSearchParams(formData).toString(),
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<form name="contact" method="POST" data-netlify="true" netlify-honeypot="bot-field" onSubmit={handleSubmit}>
|
|
129
|
+
{/* Hidden field required for AJAX */}
|
|
130
|
+
<input type="hidden" name="form-name" value="contact" />
|
|
131
|
+
<p style={{ display: 'none' }}>
|
|
132
|
+
<label>
|
|
133
|
+
Don't fill this out: <input name="bot-field" />
|
|
134
|
+
</label>
|
|
135
|
+
</p>
|
|
136
|
+
<input name="name" required />
|
|
137
|
+
<input name="email" type="email" required />
|
|
138
|
+
<textarea name="message" />
|
|
139
|
+
<button type="submit">Send</button>
|
|
140
|
+
</form>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## File Uploads
|
|
146
|
+
|
|
147
|
+
```html
|
|
148
|
+
<form name="upload" method="POST" data-netlify="true" enctype="multipart/form-data">
|
|
149
|
+
<input type="file" name="attachment" />
|
|
150
|
+
<button type="submit">Upload</button>
|
|
151
|
+
</form>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Limits:**
|
|
155
|
+
|
|
156
|
+
- Maximum request size: **8 MB**
|
|
157
|
+
- Timeout: **30 seconds**
|
|
158
|
+
- One file per input field (use multiple inputs for multiple files)
|
|
159
|
+
|
|
160
|
+
**Security:** For file uploads containing PII (personally identifiable information), use the
|
|
161
|
+
[Very Good Security (VGS)](https://www.netlify.com/integrations/very-good-security/) integration for additional
|
|
162
|
+
protection.
|
|
163
|
+
|
|
164
|
+
**AJAX file uploads:** Do NOT set Content-Type header - let browser set `multipart/form-data` with boundary:
|
|
165
|
+
|
|
166
|
+
```javascript
|
|
167
|
+
// Correct - no Content-Type header
|
|
168
|
+
fetch('/', {
|
|
169
|
+
method: 'POST',
|
|
170
|
+
body: new FormData(form), // Browser sets correct Content-Type
|
|
171
|
+
})
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Spam Protection
|
|
175
|
+
|
|
176
|
+
### Honeypot Field (Recommended)
|
|
177
|
+
|
|
178
|
+
```html
|
|
179
|
+
<form name="contact" method="POST" data-netlify="true" netlify-honeypot="bot-field">
|
|
180
|
+
<!-- Hidden from humans, filled by bots -->
|
|
181
|
+
<p style="display:none">
|
|
182
|
+
<label>Don't fill this: <input name="bot-field" /></label>
|
|
183
|
+
</p>
|
|
184
|
+
|
|
185
|
+
<!-- Visible fields -->
|
|
186
|
+
<input name="name" required />
|
|
187
|
+
<button type="submit">Send</button>
|
|
188
|
+
</form>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### reCAPTCHA v2 (Netlify-provided)
|
|
192
|
+
|
|
193
|
+
```html
|
|
194
|
+
<form name="contact" method="POST" data-netlify="true" data-netlify-recaptcha="true">
|
|
195
|
+
<input name="name" required />
|
|
196
|
+
|
|
197
|
+
<!-- Netlify injects reCAPTCHA here -->
|
|
198
|
+
<div data-netlify-recaptcha="true"></div>
|
|
199
|
+
|
|
200
|
+
<button type="submit">Send</button>
|
|
201
|
+
</form>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Only one Netlify-provided reCAPTCHA per page. For multiple CAPTCHAs on one page, use custom reCAPTCHA.
|
|
205
|
+
|
|
206
|
+
### Custom reCAPTCHA v2
|
|
207
|
+
|
|
208
|
+
Use your own reCAPTCHA 2 code with Netlify validation:
|
|
209
|
+
|
|
210
|
+
1. Sign up for a [reCAPTCHA API key pair](http://www.google.com/recaptcha/admin) and add the reCAPTCHA script/widget to
|
|
211
|
+
your form.
|
|
212
|
+
2. Set environment variables in Netlify (UI, CLI, or API):
|
|
213
|
+
- `SITE_RECAPTCHA_KEY` — your reCAPTCHA site key (scopes: Builds + Runtime)
|
|
214
|
+
- `SITE_RECAPTCHA_SECRET` — your reCAPTCHA secret key (scope: Runtime)
|
|
215
|
+
3. Add `data-netlify-recaptcha="true"` to your `<form>` tag.
|
|
216
|
+
|
|
217
|
+
Netlify validates the `g-recaptcha-response` server-side on each submission.
|
|
218
|
+
|
|
219
|
+
**For AJAX with reCAPTCHA:** Include `g-recaptcha-response` in POST body (automatic if using `FormData()`).
|
|
220
|
+
|
|
221
|
+
## Success Redirects
|
|
222
|
+
|
|
223
|
+
```html
|
|
224
|
+
<!-- Redirect to custom thank-you page -->
|
|
225
|
+
<form name="contact" method="POST" data-netlify="true" action="/thank-you"></form>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The `action` path must:
|
|
229
|
+
|
|
230
|
+
- Start with `/`
|
|
231
|
+
- Be relative to site root
|
|
232
|
+
- Point to an existing page
|
|
233
|
+
|
|
234
|
+
> **Pretty URLs:** Netlify serves `thank-you.html` at `/thank-you` by default. Use `action="/thank-you"`, not
|
|
235
|
+
> `action="/thank-you.html"` — the `.html` path returns 404.
|
|
236
|
+
|
|
237
|
+
## Notifications
|
|
238
|
+
|
|
239
|
+
### Email Notifications
|
|
240
|
+
|
|
241
|
+
Configure in Netlify UI: **Project configuration > Notifications > Emails and webhooks > Form submission notifications**
|
|
242
|
+
|
|
243
|
+
- Include `<input name="email">` to set reply-to address automatically
|
|
244
|
+
- Custom subject line:
|
|
245
|
+
```html
|
|
246
|
+
<input type="hidden" name="subject" value="New inquiry from %{formName}" />
|
|
247
|
+
```
|
|
248
|
+
- Available variables: `%{formName}`, `%{siteName}`, `%{submissionId}`
|
|
249
|
+
- For forms created before May 5, 2023: remove `[Netlify]` prefix from subject by adding `data-remove-prefix`:
|
|
250
|
+
```html
|
|
251
|
+
<input type="hidden" name="subject" data-remove-prefix value="Sales inquiry" />
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Webhooks
|
|
255
|
+
|
|
256
|
+
Configure in Netlify UI: **Project configuration > Notifications > Emails and webhooks > Form submission notifications**
|
|
257
|
+
|
|
258
|
+
Sends JSON payload on each verified submission.
|
|
259
|
+
|
|
260
|
+
## Function Triggers
|
|
261
|
+
|
|
262
|
+
Trigger serverless functions on submissions:
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
// netlify/functions/submission-created.mts
|
|
266
|
+
import type { Context } from '@netlify/functions'
|
|
267
|
+
|
|
268
|
+
interface FormPayload {
|
|
269
|
+
form_name: string
|
|
270
|
+
data: Record<string, string>
|
|
271
|
+
created_at: string
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export default async (req: Request, context: Context) => {
|
|
275
|
+
const { payload } = (await req.json()) as { payload: FormPayload }
|
|
276
|
+
|
|
277
|
+
console.log('Form:', payload.form_name)
|
|
278
|
+
console.log('Data:', payload.data)
|
|
279
|
+
|
|
280
|
+
// Process submission (send to CRM, Slack, etc.)
|
|
281
|
+
|
|
282
|
+
return new Response('OK')
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**Event name:** `submission-created` (filename must match)
|
|
287
|
+
|
|
288
|
+
## Limits
|
|
289
|
+
|
|
290
|
+
| Feature | Free Tier | Paid Tier |
|
|
291
|
+
| ------------ | ----------- | ---------- |
|
|
292
|
+
| Submissions | 100/month | Metered |
|
|
293
|
+
| File Storage | 10 MB/month | Scalable |
|
|
294
|
+
| Request Size | 8 MB | 8 MB |
|
|
295
|
+
| Timeout | 30 seconds | 30 seconds |
|
|
296
|
+
|
|
297
|
+
## Common Errors & Solutions
|
|
298
|
+
|
|
299
|
+
### "404 on submit"
|
|
300
|
+
|
|
301
|
+
**Cause:** Build bot didn't detect the form. **Fix:**
|
|
302
|
+
|
|
303
|
+
1. Ensure static HTML version exists for JS frameworks
|
|
304
|
+
2. Verify `form-name` hidden input is present
|
|
305
|
+
3. Check form has `name` attribute
|
|
306
|
+
|
|
307
|
+
### Submissions not appearing
|
|
308
|
+
|
|
309
|
+
**Check:**
|
|
310
|
+
|
|
311
|
+
1. Look in **Spam** folder in Netlify UI (Akismet filtering)
|
|
312
|
+
2. Avoid test values like "test@test.com" or "asdf" — use real email and full sentences
|
|
313
|
+
3. Verify form was included in the latest deploy
|
|
314
|
+
|
|
315
|
+
### AJAX submission fails silently
|
|
316
|
+
|
|
317
|
+
**Ensure:**
|
|
318
|
+
|
|
319
|
+
1. Content-Type is `application/x-www-form-urlencoded` (not JSON)
|
|
320
|
+
2. `form-name` field is included in body
|
|
321
|
+
3. Check browser Network tab for actual response
|
|
322
|
+
|
|
323
|
+
### Form succeeds but no submissions appear (SSR apps)
|
|
324
|
+
|
|
325
|
+
**Cause:** Two things must be true for SSR form submissions to work:
|
|
326
|
+
|
|
327
|
+
1. A static HTML skeleton file must exist in `public/` so Netlify registers the form at build time.
|
|
328
|
+
2. The `fetch` URL must target the skeleton file path (e.g. `/__forms.html`), **not** `/`.
|
|
329
|
+
|
|
330
|
+
Without (1), Netlify doesn't know the form exists. Without (2), the POST to `/` is intercepted by the SSR catch-all
|
|
331
|
+
function (TanStack Start, Next.js, SvelteKit, Remix, Nuxt) — the SSR handler renders a 200 HTML page, so `fetch()`
|
|
332
|
+
reports success, but the request never reaches Netlify's form processing middleware.
|
|
333
|
+
|
|
334
|
+
**Fix:**
|
|
335
|
+
|
|
336
|
+
1. Create a static HTML skeleton in `public/` (see
|
|
337
|
+
[JavaScript Frameworks](#javascript-frameworks-ssr--client-rendered-apps) section).
|
|
338
|
+
2. Change `fetch('/')` to `fetch('/__forms.html')` (or whatever path your skeleton file is at) so the request routes
|
|
339
|
+
through the CDN origin where Netlify's `formsHandler` runs.
|
|
340
|
+
|
|
341
|
+
### File upload fails
|
|
342
|
+
|
|
343
|
+
**Check:**
|
|
344
|
+
|
|
345
|
+
1. Total request size under 8 MB
|
|
346
|
+
2. Not setting Content-Type header manually
|
|
347
|
+
3. Using `enctype="multipart/form-data"` on form
|
|
348
|
+
|
|
349
|
+
## API Access
|
|
350
|
+
|
|
351
|
+
List submissions programmatically:
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
curl -H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
|
|
355
|
+
https://api.netlify.com/api/v1/forms/{form_id}/submissions
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Export available as CSV from Netlify UI.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const { execFileSync } = require('child_process')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const path = require('path')
|
|
4
|
+
|
|
5
|
+
const repoRoot = execFileSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf-8' }).trim()
|
|
6
|
+
const featuresDir = path.join(repoRoot, '.netlify', 'features')
|
|
7
|
+
const markerFile = path.join(featuresDir, 'netlify-forms')
|
|
8
|
+
|
|
9
|
+
fs.mkdirSync(featuresDir, { recursive: true })
|
|
10
|
+
fs.writeFileSync(markerFile, '')
|
|
11
|
+
|
|
12
|
+
console.log('Netlify Forms feature enabled for this site.')
|