Skip to content

Commit

Permalink
feat(deploy): print edge function logs url (#6851)
Browse files Browse the repository at this point in the history
* chore(types): add missing types to deploy `printResults`

* fix(deploy): remove `name`, fix `site_id` in `--json` output

`name` was always `nil` so it effectively never really existed

* feat(deploy): print edge function logs URL

* feat(deploy): add function_logs, edge_function_logs to --json output

This could be useful, but it also makes the feature easier to test. :)

* fix(deploy): use standard casing for product name
  • Loading branch information
serhalp authored Oct 8, 2024
1 parent 01810d5 commit b996f92
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 27 deletions.
64 changes: 38 additions & 26 deletions src/commands/deploy/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Stats } from 'fs'
import { type Stats } from 'fs'
import { stat } from 'fs/promises'
import { basename, resolve } from 'path'

import { runCoreSteps } from '@netlify/build'
import { OptionValues } from 'commander'
import { type NetlifyConfig, runCoreSteps } from '@netlify/build'
import { type OptionValues } from 'commander'
import inquirer from 'inquirer'
import isEmpty from 'lodash/isEmpty.js'
import isObject from 'lodash/isObject.js'
Expand Down Expand Up @@ -34,7 +34,6 @@ import {
import { DEFAULT_DEPLOY_TIMEOUT } from '../../utils/deploy/constants.js'
import { deploySite } from '../../utils/deploy/deploy-site.js'
import { getEnvelopeEnv } from '../../utils/env/index.js'
import { getFrameworksAPIPaths } from '../../utils/frameworks-api.js'
import { getFunctionsManifestPath, getInternalFunctionsDir } from '../../utils/functions/index.js'
import openBrowser from '../../utils/open-browser.js'
import BaseCommand from '../base-command.js'
Expand Down Expand Up @@ -446,6 +445,7 @@ const runDeploy = async ({
deployUrl: string
logsUrl: string
functionLogsUrl: string
edgeFunctionLogsUrl: string
}> => {
let results
let deployId
Expand Down Expand Up @@ -532,9 +532,11 @@ const runDeploy = async ({
const logsUrl = `${results.deploy.admin_url}/deploys/${results.deploy.id}`

let functionLogsUrl = `${results.deploy.admin_url}/logs/functions`
let edgeFunctionLogsUrl = `${results.deploy.admin_url}/logs/edge-functions`

if (!deployToProduction) {
functionLogsUrl += `?scope=deploy:${deployId}`
edgeFunctionLogsUrl += `?scope=deploy:${deployId}`
}

return {
Expand All @@ -545,6 +547,7 @@ const runDeploy = async ({
deployUrl,
logsUrl,
functionLogsUrl,
edgeFunctionLogsUrl,
}
}

Expand Down Expand Up @@ -624,30 +627,40 @@ const bundleEdgeFunctions = async (options, command: BaseCommand) => {
})
}

/**
*
* @param {object} config
* @param {boolean} config.deployToProduction
* @param {boolean} config.isIntegrationDeploy If the user ran netlify integration:deploy instead of just netlify deploy
* @param {boolean} config.json If the result should be printed as json message
* @param {boolean} config.runBuildCommand If the build command should be run
* @param {object} config.results
* @returns {void}
*/
// @ts-expect-error TS(7031) FIXME: Binding element 'deployToProduction' implicitly ha... Remove this comment to see the full error message
const printResults = ({ deployToProduction, isIntegrationDeploy, json, results, runBuildCommand }) => {
const msgData = {
interface JsonData {
site_id: string
site_name: string
deploy_id: string
deploy_url: string
logs: string
function_logs: string
edge_function_logs: string
url?: string
}

const printResults = ({
deployToProduction,
isIntegrationDeploy,
json,
results,
runBuildCommand,
}: {
deployToProduction: boolean
isIntegrationDeploy: boolean
json: boolean
results: Awaited<ReturnType<typeof prepAndRunDeploy>>
runBuildCommand: boolean
}): void => {
const msgData: Record<string, string> = {
'Build logs': results.logsUrl,
'Function logs': results.functionLogsUrl,
'Edge function Logs': results.edgeFunctionLogsUrl,
}

if (deployToProduction) {
// @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
msgData['Unique deploy URL'] = results.deployUrl
// @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
msgData['Website URL'] = results.siteUrl
} else {
// @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
msgData['Website draft URL'] = results.deployUrl
}

Expand All @@ -656,16 +669,16 @@ const printResults = ({ deployToProduction, isIntegrationDeploy, json, results,

// Json response for piping commands
if (json) {
const jsonData = {
name: results.name,
site_id: results.site_id,
const jsonData: JsonData = {
site_id: results.siteId,
site_name: results.siteName,
deploy_id: results.deployId,
deploy_url: results.deployUrl,
logs: results.logsUrl,
function_logs: results.functionLogsUrl,
edge_function_logs: results.edgeFunctionLogsUrl,
}
if (deployToProduction) {
// @ts-expect-error TS(2339) FIXME: Property 'url' does not exist on type '{ name: any... Remove this comment to see the full error message
jsonData.url = results.siteUrl
}

Expand Down Expand Up @@ -853,8 +866,7 @@ export const deploy = async (options: OptionValues, command: BaseCommand) => {
defaultConfig: getDefaultConfig(settings),
currentDir: command.workingDir,
options,
// @ts-expect-error TS(7031) FIXME: Binding element 'netlifyConfig' implicitly has an ... Remove this comment to see the full error message
deployHandler: async ({ netlifyConfig }) => {
deployHandler: async ({ netlifyConfig }: { netlifyConfig: NetlifyConfig }) => {
results = await prepAndRunDeploy({
command,
options,
Expand Down
64 changes: 63 additions & 1 deletion tests/integration/commands/deploy/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,23 @@ const validateDeploy = async ({
contentMessage?: string
siteName: string
content?: string
deploy: { site_name: string; deploy_url: string; deploy_id: string; logs: string }
deploy: {
site_id: string
site_name: string
deploy_url: string
deploy_id: string
logs: string
function_logs: string
edge_function_logs: string
}
}) => {
expect(deploy.site_id).toBeTruthy()
expect(deploy.site_name).toBeTruthy()
expect(deploy.deploy_url).toBeTruthy()
expect(deploy.deploy_id).toBeTruthy()
expect(deploy.logs).toBeTruthy()
expect(deploy.function_logs).toBeTruthy()
expect(deploy.edge_function_logs).toBeTruthy()
expect(deploy.site_name, contentMessage).toEqual(siteName)

await validateContent({ siteUrl: deploy.deploy_url, path: '', content })
Expand Down Expand Up @@ -263,6 +274,57 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co
})
})

test('should print deploy-scoped URLs for build logs, function logs, and edge function logs', async (t) => {
await withSiteBuilder(t, async (builder) => {
const content = '<h1>Why Next.js is perfect, an essay</h1>'
builder.withContentFile({
path: 'public/index.html',
content,
})
await builder.build()

const deploy = await callCli(['deploy', '--json', '--dir', 'public'], {
cwd: builder.directory,
env: { NETLIFY_SITE_ID: context.siteId },
}).then((output) => JSON.parse(output))

await validateDeploy({ deploy, siteName: SITE_NAME, content })
expect(deploy).toHaveProperty('logs', `https://app.netlify.com/sites/${SITE_NAME}/deploys/${deploy.deploy_id}`)
expect(deploy).toHaveProperty(
'function_logs',
`https://app.netlify.com/sites/${SITE_NAME}/logs/functions?scope=deploy:${deploy.deploy_id}`,
)
expect(deploy).toHaveProperty(
'edge_function_logs',
`https://app.netlify.com/sites/${SITE_NAME}/logs/edge-functions?scope=deploy:${deploy.deploy_id}`,
)
})
})

test('should print production URLs for build logs, function logs, and edge function logs when --prod is passed', async (t) => {
await withSiteBuilder(t, async (builder) => {
const content = '<h1>Why Next.js is perfect, a novella</h1>'
builder.withContentFile({
path: 'public/index.html',
content,
})
await builder.build()

const deploy = await callCli(['deploy', '--json', '--dir', 'public', '--prod'], {
cwd: builder.directory,
env: { NETLIFY_SITE_ID: context.siteId },
}).then((output) => JSON.parse(output))

await validateDeploy({ deploy, siteName: SITE_NAME, content })
expect(deploy).toHaveProperty('logs', `https://app.netlify.com/sites/${SITE_NAME}/deploys/${deploy.deploy_id}`)
expect(deploy).toHaveProperty('function_logs', `https://app.netlify.com/sites/${SITE_NAME}/logs/functions`)
expect(deploy).toHaveProperty(
'edge_function_logs',
`https://app.netlify.com/sites/${SITE_NAME}/logs/edge-functions`,
)
})
})

test('should return valid json when both --build and --json are passed', async (t) => {
await withSiteBuilder(t, async (builder) => {
const content = '<h1>⊂◉‿◉つ</h1>'
Expand Down

0 comments on commit b996f92

Please sign in to comment.