Skip to content

Commit

Permalink
feat: stream function and build logs (#6139)
Browse files Browse the repository at this point in the history
* feat: streaming of deploy and function logs

* chore: fixes

* chore: more fixes

* chore: silly

* chore: use netlify api client method

* feat: stop stream of build logs on finish

* chore: command docs

* chore: light tests

* fix: remove mock require

* feat: types

* feat: moar types

* chore: make argument optional

* chore: feedbacks

* chore: borked alias

* chore: feedback, docs, things

* chore: update snapshots

* chore: ts thing

* feat: sort order for commands

* chore: tests for alphabet order

* chore: types tidy

* chore: feedback

* chore: build thing

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
estephinson and kodiakhq[bot] authored Nov 16, 2023
1 parent 2428b9c commit f1b4f05
Show file tree
Hide file tree
Showing 14 changed files with 601 additions and 61 deletions.
80 changes: 80 additions & 0 deletions docs/commands/logs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
title: Netlify CLI logs command
---

# `logs`

<!-- AUTO-GENERATED-CONTENT:START (GENERATE_COMMANDS_DOCS) -->
Stream logs from your site

**Usage**

```bash
netlify logs
```

**Flags**

- `filter` (*string*) - For monorepos, specify the name of the application to run the command in
- `debug` (*boolean*) - Print debugging information

| Subcommand | description |
|:--------------------------- |:-----|
| [`logs:deploy`](/commands/logs#logsdeploy) | (Beta) Stream the logs of deploys currently being built to the console |
| [`logs:function`](/commands/logs#logsfunction) | (Beta) Stream netlify function logs to the console |


**Examples**

```bash
netlify logs:deploy
netlify logs:function
netlify logs:function my-function
```

---
## `logs:deploy`

(Beta) Stream the logs of deploys currently being built to the console

**Usage**

```bash
netlify logs:deploy
```

**Flags**

- `filter` (*string*) - For monorepos, specify the name of the application to run the command in
- `debug` (*boolean*) - Print debugging information

---
## `logs:function`

(Beta) Stream netlify function logs to the console

**Usage**

```bash
netlify logs:function
```

**Arguments**

- functionName - Name of the function to stream logs for

**Flags**

- `filter` (*string*) - For monorepos, specify the name of the application to run the command in
- `debug` (*boolean*) - Print debugging information

**Examples**

```bash
netlify logs:function my-function
netlify logs:function
```

---

<!-- AUTO-GENERATED-CONTENT:END -->
10 changes: 10 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,16 @@ Link a local repo or project folder to an existing site on Netlify

Login to your Netlify account

### [logs](/commands/logs)

Stream logs from your site

| Subcommand | description |
|:--------------------------- |:-----|
| [`logs:deploy`](/commands/logs#logsdeploy) | (Beta) Stream the logs of deploys currently being built to the console |
| [`logs:function`](/commands/logs#logsfunction) | (Beta) Stream netlify function logs to the console |


### [open](/commands/open)

Open settings for the site linked to the current folder
Expand Down
46 changes: 46 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@
"uuid": "9.0.0",
"wait-port": "1.0.4",
"write-file-atomic": "5.0.1",
"ws": "^8.14.2",
"zod": "3.22.4"
},
"devDependencies": {
Expand All @@ -199,6 +200,7 @@
"@types/node": "20.9.0",
"@types/prettyjson": "0.0.30",
"@types/semver": "7.5.0",
"@types/ws": "^8.5.9",
"@types/uuid": "9.0.7",
"@vitest/coverage-v8": "1.0.0-beta.4",
"c8": "7.14.0",
Expand Down
1 change: 1 addition & 0 deletions site/src/_app.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const navOrder = [
'lm',
'login',
'logout',
'logs',
'open',
'recipes',
'serve',
Expand Down
83 changes: 25 additions & 58 deletions src/commands/base-command.mts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@

import { existsSync } from 'fs'
import { join, relative, resolve } from 'path'
import process from 'process'
import { format } from 'util'

import { DefaultLogger, Project } from '@netlify/build-info'
// eslint-disable-next-line import/extensions, n/no-missing-import
import { NodeFS, NoopLogger } from '@netlify/build-info/node'
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module '@net... Remove this comment to see the full error message
import { resolveConfig } from '@netlify/config'
import { Command, Option } from 'commander'
import { Command, Help, Option } from 'commander'
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'debu... Remove this comment to see the full error message
import debug from 'debug'
import { findUp } from 'find-up'
Expand Down Expand Up @@ -131,6 +129,13 @@ async function selectWorkspace(project, filter) {
return selected.path
}

async function getRepositoryRoot(cwd?: string): Promise<string | undefined> {
const res = await findUp('.git', { cwd, type: 'directory' })
if (res) {
return join(res, '..')
}
}

/** Base command class that provides tracking and config initialization */
export default class BaseCommand extends Command {
/**
Expand Down Expand Up @@ -267,33 +272,26 @@ export default class BaseCommand extends Command {
return padLeft(term, HELP_INDENT_WIDTH)
}

/**
* @param {BaseCommand} command
*/
// @ts-expect-error TS(7006) FIXME: Parameter 'command' implicitly has an 'any' type.
const getCommands = (command) => {
// eslint-disable-next-line unicorn/consistent-function-scoping
const getCommands = (command: BaseCommand) => {
const parentCommand = this.name() === 'netlify' ? command : command.parent
return (
// @ts-expect-error TS(7006) FIXME: Parameter 'cmd' implicitly has an 'any' type.
parentCommand?.commands.filter((cmd) => {
if (cmd._hidden) return false
// the root command
if (this.name() === 'netlify') {
// don't include subcommands on the main page
return !cmd.name().includes(':')
}
return cmd.name().startsWith(`${command.name()}:`)
}) || []
parentCommand?.commands
.filter((cmd) => {
if ((cmd as any)._hidden) return false
// the root command
if (this.name() === 'netlify') {
// don't include subcommands on the main page
return !cmd.name().includes(':')
}
return cmd.name().startsWith(`${command.name()}:`)
})
// eslint-disable-next-line id-length
.sort((a, b) => a.name().localeCompare(b.name())) || []
)
}

/**
* override the longestSubcommandTermLength
* @param {BaseCommand} command
* @returns {number}
*/
help.longestSubcommandTermLength = (command) =>
// @ts-expect-error TS(7006) FIXME: Parameter 'max' implicitly has an 'any' type.
help.longestSubcommandTermLength = (command: BaseCommand): number =>
getCommands(command).reduce((max, cmd) => Math.max(max, cmd.name().length), 0)

/**
Expand All @@ -308,13 +306,7 @@ export default class BaseCommand extends Command {
helper.visibleOptions(command).reduce((max, option) => Math.max(max, helper.optionTerm(option).length), 0)) ||
0

/**
* override the format help function to style it correctly
* @param {BaseCommand} command
* @param {import('commander').Help} helper
* @returns {string}
*/
help.formatHelp = (command, helper) => {
help.formatHelp = (command: BaseCommand, helper: Help): string => {
const parentCommand = this.name() === 'netlify' ? command : command.parent
const termWidth = helper.padWidth(command, helper)
const helpWidth = helper.helpWidth || FALLBACK_HELP_CMD_WIDTH
Expand Down Expand Up @@ -367,7 +359,6 @@ export default class BaseCommand extends Command {
output = [...output, chalk.bold('ARGUMENTS'), formatHelpList(argumentList), '']
}

// @ts-expect-error TS(2551) FIXME: Property 'noBaseOptions' does not exist on type 'C... Remove this comment to see the full error message
if (command.noBaseOptions === false) {
// Options
const optionList = helper
Expand All @@ -393,18 +384,15 @@ export default class BaseCommand extends Command {
output = [...output, chalk.bold('ALIASES'), formatHelpList(aliases), '']
}

// @ts-expect-error TS(2339) FIXME: Property 'examples' does not exist on type 'Comman... Remove this comment to see the full error message
if (command.examples.length !== 0) {
output = [
...output,
chalk.bold('EXAMPLES'),
// @ts-expect-error TS(2339) FIXME: Property 'examples' does not exist on type 'Comman... Remove this comment to see the full error message
formatHelpList(command.examples.map((example) => `${HELP_$} ${example}`)),
'',
]
}

// @ts-expect-error TS(7006) FIXME: Parameter 'cmd' implicitly has an 'any' type.
const commandList = getCommands(command).map((cmd) =>
formatItem(cmd.name(), helper.subcommandDescription(cmd).split('\n')[0], true),
)
Expand Down Expand Up @@ -448,13 +436,7 @@ export default class BaseCommand extends Command {
}
}

/**
*
* @param {string|undefined} tokenFromFlag
* @returns
*/
// @ts-expect-error TS(7006) FIXME: Parameter 'tokenFromFlag' implicitly has an 'any' ... Remove this comment to see the full error message
async authenticate(tokenFromFlag) {
async authenticate(tokenFromFlag?: string) {
const [token] = await getToken(tokenFromFlag)
if (token) {
return token
Expand Down Expand Up @@ -553,7 +535,6 @@ export default class BaseCommand extends Command {
// ==================================================

// retrieve the repository root
// @ts-expect-error TS(2554) FIXME: Expected 1 arguments, but got 0.
const rootDir = await getRepositoryRoot()
// Get framework, add to analytics payload for every command, if a framework is set
const fs = new NodeFS()
Expand Down Expand Up @@ -780,17 +761,3 @@ export default class BaseCommand extends Command {
return this.name() === 'serve' ? 'production' : 'dev'
}
}

/**
* Retrieves the repository root through a git command.
* Returns undefined if not a git project.
* @param {string} [cwd] The optional current working directory
* @returns {Promise<string|undefined>}
*/
// @ts-expect-error TS(7006) FIXME: Parameter 'cwd' implicitly has an 'any' type.
async function getRepositoryRoot(cwd) {
const res = await findUp('.git', { cwd, type: 'directory' })
if (res) {
return join(res, '..')
}
}
Loading

2 comments on commit f1b4f05

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📊 Benchmark results

  • Dependency count: 1,396
  • Package size: 404 MB

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📊 Benchmark results

  • Dependency count: 1,396
  • Package size: 404 MB

Please sign in to comment.