Skip to content

Commit 43aeca5

Browse files
committed
feat: add netlify push for git-based deploys via Netlify-hosted git
Introduce a new deploy workflow using Netlify-hosted git repositories: - `netlify init --git`: configures a site to use Netlify's git provider, sets up the `netlify` remote, and wires up a credential helper in `./.git/config` for seamless git operations - `netlify push`: stages, commits, and pushes to the Netlify remote (which will trigger a build/deploy) - `netlify git-credentials` (hidden): git credential helper that provides the Netlify auth token for push authentication Adds `@clack/prompts` for pretty CLI UX. We've been meaning to do this for years and I'd rather not keep spreading our current patterns. The dependency weight is very small.
1 parent a761635 commit 43aeca5

File tree

14 files changed

+346
-0
lines changed

14 files changed

+346
-0
lines changed

docs/commands/init.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ netlify init
2020

2121
- `filter` (*string*) - For monorepos, specify the name of the application to run the command in
2222
- `force` (*boolean*) - Reinitialize CI hooks if the linked project is already configured to use CI
23+
- `git` (*boolean*) - Use Netlify-hosted git for deploys (no external provider needed)
2324
- `git-remote-name` (*string*) - Name of Git remote to use. e.g. "origin"
2425
- `manual` (*boolean*) - Manually configure a git remote for CI
2526
- `debug` (*boolean*) - Print debugging information

docs/commands/push.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
title: Netlify CLI push command
3+
sidebar:
4+
label: push
5+
description: Push code to Netlify via git, triggering a build
6+
---
7+
8+
# `push`
9+
10+
<!-- AUTO-GENERATED-CONTENT:START (GENERATE_COMMANDS_DOCS) -->
11+
Push code to Netlify via git, triggering a build
12+
13+
**Usage**
14+
15+
```bash
16+
netlify push
17+
```
18+
19+
**Flags**
20+
21+
- `filter` (*string*) - For monorepos, specify the name of the application to run the command in
22+
- `message` (*string*) - Commit message
23+
- `debug` (*boolean*) - Print debugging information
24+
- `auth` (*string*) - Netlify auth token - can be used to run this command without logging in
25+
26+
**Examples**
27+
28+
```bash
29+
netlify push
30+
netlify push -m "Add contact form"
31+
```
32+
33+
34+
<!-- AUTO-GENERATED-CONTENT:END -->

docs/index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ Open settings for the project linked to the current folder
145145
| [`open:site`](/commands/open#opensite) | Opens current project url in browser |
146146

147147

148+
### [push](/commands/push)
149+
150+
Push code to Netlify via git, triggering a build
151+
148152
### [recipes](/commands/recipes)
149153

150154
Create and modify files in a project using pre-defined recipes

package-lock.json

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"typecheck:watch": "tsc --watch"
5858
},
5959
"dependencies": {
60+
"@clack/prompts": "^1.0.0",
6061
"@fastify/static": "9.0.0",
6162
"@netlify/ai": "0.3.4",
6263
"@netlify/api": "14.0.13",
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import process from 'process'
2+
3+
import type BaseCommand from '../base-command.js'
4+
5+
const readStdin = (): Promise<string> =>
6+
new Promise((resolve) => {
7+
let data = ''
8+
process.stdin.setEncoding('utf8')
9+
process.stdin.on('data', (chunk: string) => {
10+
data += chunk
11+
})
12+
process.stdin.on('end', () => {
13+
resolve(data)
14+
})
15+
// If stdin isn't being piped, resolve after a short timeout
16+
if (process.stdin.isTTY) {
17+
resolve(data)
18+
}
19+
})
20+
21+
export const gitCredentials = async (command: BaseCommand) => {
22+
const input = await readStdin()
23+
24+
// Only respond to "get" requests from the git credential protocol
25+
if (!input.includes('protocol=') && !input.startsWith('get')) {
26+
return
27+
}
28+
29+
const token = command.netlify.api.accessToken
30+
if (!token) {
31+
throw new Error('No access token found. Please run `netlify login` first.')
32+
}
33+
34+
// Output in git credential helper format
35+
process.stdout.write(`username=x-access-token\npassword=${token}\n`)
36+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import BaseCommand from '../base-command.js'
2+
3+
export const createGitCredentialsCommand = (program: BaseCommand) =>
4+
program
5+
.command('git-credentials', { hidden: true })
6+
.description('Git credential helper for Netlify-hosted repos')
7+
.action(async (_options, command: BaseCommand) => {
8+
const { gitCredentials } = await import('./git-credentials.js')
9+
await gitCredentials(command)
10+
})

src/commands/init/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const createInitCommand = (program: BaseCommand) =>
1010
'Configure continuous deployment for a new or existing project. To create a new project without continuous deployment, use `netlify sites:create`',
1111
)
1212
.option('-m, --manual', 'Manually configure a git remote for CI')
13+
.option('--git', 'Use Netlify-hosted git for deploys (no external provider needed)')
1314
.option('--git-remote-name <name>', 'Name of Git remote to use. e.g. "origin"')
1415
.addHelpText('after', () => {
1516
const docsUrl = 'https://docs.netlify.com/cli/get-started/'

src/commands/init/init.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { chalk, exit, log, netlifyCommand } from '../../utils/command-helpers.js
66
import getRepoData from '../../utils/get-repo-data.js'
77
import { ensureNetlifyIgnore } from '../../utils/gitignore.js'
88
import { configureRepo } from '../../utils/init/config.js'
9+
import { configNetlifyGit } from '../../utils/init/config-netlify-git.js'
910
import { track } from '../../utils/telemetry/index.js'
1011
import type BaseCommand from '../base-command.js'
1112
import { link } from '../link/link.js'
@@ -241,6 +242,14 @@ export const init = async (
241242
// Add .netlify to .gitignore file
242243
await ensureNetlifyIgnore(repositoryRoot)
243244

245+
// Handle --git flag: use Netlify-hosted git
246+
if (options.git) {
247+
const siteInfo = isEmpty(existingSiteInfo) ? await createOrLinkSiteToRepo(command) : existingSiteInfo
248+
persistState({ state, siteInfo })
249+
await configNetlifyGit({ command, siteId: siteInfo.id })
250+
return siteInfo
251+
}
252+
244253
const repoUrl = getRepoUrl(existingSiteInfo)
245254
if (repoUrl && !options.force) {
246255
logExistingAndExit({ siteInfo: existingSiteInfo })

src/commands/main.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@ import { createDevCommand } from './dev/index.js'
3535
import { createDevExecCommand } from './dev-exec/index.js'
3636
import { createEnvCommand } from './env/index.js'
3737
import { createFunctionsCommand } from './functions/index.js'
38+
import { createGitCredentialsCommand } from './git-credentials/index.js'
3839
import { createInitCommand } from './init/index.js'
3940
import { createLinkCommand } from './link/index.js'
4041
import { createLoginCommand } from './login/index.js'
4142
import { createLogoutCommand } from './logout/index.js'
4243
import { createLogsCommand } from './logs/index.js'
4344
import { createOpenCommand } from './open/index.js'
45+
import { createPushCommand } from './push/index.js'
4446
import { createRecipesCommand } from './recipes/index.js'
4547
import { createServeCommand } from './serve/index.js'
4648
import { createSitesCommand } from './sites/index.js'
@@ -225,13 +227,15 @@ export const createMainCommand = (): BaseCommand => {
225227
createDevCommand(program)
226228
createEnvCommand(program)
227229
createFunctionsCommand(program)
230+
createGitCredentialsCommand(program)
228231
createRecipesCommand(program)
229232
createInitCommand(program)
230233
createCloneCommand(program)
231234
createLinkCommand(program)
232235
createLoginCommand(program)
233236
createLogoutCommand(program)
234237
createOpenCommand(program)
238+
createPushCommand(program)
235239
createServeCommand(program)
236240
createSitesCommand(program)
237241
createStatusCommand(program)

0 commit comments

Comments
 (0)