netlify-cli 9.0.4 → 9.2.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "netlify-cli",
3
3
  "description": "Netlify command line tool",
4
- "version": "9.0.4",
4
+ "version": "9.2.0",
5
5
  "author": "Netlify Inc.",
6
6
  "contributors": [
7
7
  "Abraham Schilling <AbrahamSchilling@gmail.com> (https://gitlab.com/n4bb12)",
@@ -164,6 +164,7 @@
164
164
  "url": "https://github.com/netlify/cli/issues"
165
165
  },
166
166
  "scripts": {
167
+ "snap": "ava --verbose -u tests/integration/**/*graph*.test.js",
167
168
  "prepare": "husky install node_modules/@netlify/eslint-config-node/.husky/",
168
169
  "start": "node ./bin/run",
169
170
  "test": "run-s format test:dev",
@@ -195,7 +196,7 @@
195
196
  },
196
197
  "config": {
197
198
  "eslint": "--ignore-path .gitignore --cache --format=codeframe --max-warnings=0 \"{src,scripts,site,tests,.github}/**/*.{mjs,cjs,js,md,html}\" \"*.{mjs,cjs,js,md,html}\" \".*.{mjs,cjs,js,md,html}\"",
198
- "prettier": "--ignore-path .gitignore --loglevel=warn \"{src,tools,scripts,site,tests,.github}/**/*.{mjs,cjs,js,md,yml,json,html}\" \"*.{mjs,cjs,js,yml,json,html}\" \".*.{mjs,cjs,js,yml,json,html}\" \"!CHANGELOG.md\" \"!npm-shrinkwrap.json\" \"!.github/**/*.md\""
199
+ "prettier": "--ignore-path .gitignore --loglevel=warn \"{src,tools,scripts,site,tests,.github}/**/*.{mjs,cjs,js,md,yml,json,html}\" \"*.{mjs,cjs,js,yml,json,html}\" \".*.{mjs,cjs,js,yml,json,html}\" \"!CHANGELOG.md\" \"!npm-shrinkwrap.json\" \"!site/package-lock.json\" \"!.github/**/*.md\""
199
200
  },
200
201
  "dependencies": {
201
202
  "@netlify/build": "^26.2.6",
@@ -203,7 +204,7 @@
203
204
  "@netlify/framework-info": "^9.0.0",
204
205
  "@netlify/local-functions-proxy": "^1.1.1",
205
206
  "@netlify/plugin-edge-handlers": "^3.0.6",
206
- "@netlify/plugins-list": "^6.9.0",
207
+ "@netlify/plugins-list": "^6.10.1",
207
208
  "@netlify/routing-local-proxy": "^0.34.1",
208
209
  "@netlify/zip-it-and-ship-it": "^5.7.4",
209
210
  "@octokit/rest": "^18.0.0",
@@ -270,7 +271,7 @@
270
271
  "multiparty": "^4.2.1",
271
272
  "netlify": "^11.0.0",
272
273
  "netlify-headers-parser": "^6.0.1",
273
- "netlify-onegraph-internal": "0.0.32",
274
+ "netlify-onegraph-internal": "0.0.39",
274
275
  "netlify-redirect-parser": "^13.0.2",
275
276
  "netlify-redirector": "^0.2.1",
276
277
  "node-fetch": "^2.6.0",
@@ -300,13 +300,14 @@ class BaseCommand extends Command {
300
300
  const duration = getDuration(startTime)
301
301
  const status = error_ === undefined ? 'success' : 'error'
302
302
 
303
- debug(`${this.name()}:onEnd`)(`Status: ${status}`)
304
- debug(`${this.name()}:onEnd`)(`Duration: ${duration}ms`)
303
+ const command = Array.isArray(this.args) ? this.args[0] : this.name()
304
+
305
+ debug(`${this.name()}:onEnd`)(`Command: ${command}. Status: ${status}. Duration: ${duration}ms`)
305
306
 
306
307
  try {
307
308
  await track('command', {
308
309
  ...payload,
309
- command: this.name(),
310
+ command,
310
311
  duration,
311
312
  status,
312
313
  })
@@ -32,7 +32,6 @@ const {
32
32
  detectServerSettings,
33
33
  error,
34
34
  exit,
35
- generateAuthlifyJWT,
36
35
  getSiteInformation,
37
36
  injectEnvVariables,
38
37
  log,
@@ -265,27 +264,7 @@ const dev = async (options, command) => {
265
264
  }
266
265
 
267
266
  const startNetlifyGraphWatcher = Boolean(options.graph)
268
- let authlifyJWT
269
-
270
- if (startNetlifyGraphWatcher) {
271
- const netlifyToken = await command.authenticate()
272
- authlifyJWT = generateAuthlifyJWT(netlifyToken, siteInfo.authlify_token_id, site.id)
273
- }
274
-
275
- await injectEnvVariables({
276
- env: Object.assign(
277
- command.netlify.cachedConfig.env,
278
- authlifyJWT == null
279
- ? {}
280
- : {
281
- ONEGRAPH_AUTHLIFY_TOKEN: {
282
- sources: ['general'],
283
- value: authlifyJWT,
284
- },
285
- },
286
- ),
287
- site,
288
- })
267
+ await injectEnvVariables({ env: command.netlify.cachedConfig.env, site })
289
268
 
290
269
  const { addonsUrls, capabilities, siteUrl, timeouts } = await getSiteInformation({
291
270
  // inherited from base command --offline
@@ -306,28 +285,14 @@ const dev = async (options, command) => {
306
285
 
307
286
  command.setAnalyticsPayload({ projectType: settings.framework || 'custom', live: options.live })
308
287
 
309
- let configWithAuthlify
310
-
311
- if (siteInfo.authlify_token_id) {
312
- const netlifyToken = command.authenticate()
313
- // Only inject the authlify config if a token ID exists. This prevents
314
- // calling command.authenticate() (which opens a browser window) if the
315
- // user hasn't enabled API Authentication
316
- configWithAuthlify = Object.assign(config, {
317
- authlify: {
318
- netlifyToken,
319
- authlifyTokenId: siteInfo.authlify_token_id,
320
- siteId: site.id,
321
- },
322
- })
323
- } else {
324
- configWithAuthlify = config
325
- }
326
-
327
288
  await startFunctionsServer({
328
- config: configWithAuthlify,
289
+ api,
290
+ command,
291
+ config,
292
+ isGraphEnabled: startNetlifyGraphWatcher,
329
293
  settings,
330
294
  site,
295
+ siteInfo,
331
296
  siteUrl,
332
297
  capabilities,
333
298
  timeouts,
@@ -34,8 +34,10 @@ const functionsServe = async (options, command) => {
34
34
 
35
35
  await startFunctionsServer({
36
36
  config,
37
+ api,
37
38
  settings: { functions: functionsDir, functionsPort },
38
39
  site,
40
+ siteInfo,
39
41
  siteUrl,
40
42
  capabilities,
41
43
  timeouts,
@@ -0,0 +1,55 @@
1
+ // @ts-check
2
+ const fs = require('fs')
3
+ const path = require('path')
4
+
5
+ const { getNetlifyGraphConfig } = require('../../lib/one-graph/cli-netlify-graph')
6
+ const { NETLIFYDEVERR, chalk, error } = require('../../utils')
7
+
8
+ /**
9
+ * Creates the `netlify graph:config:write` command
10
+ * @param {import('commander').OptionValues} options
11
+ * @param {import('../base-command').BaseCommand} command
12
+ * @returns
13
+ */
14
+ const graphConfigWrite = async (options, command) => {
15
+ const { site } = command.netlify
16
+
17
+ if (!site.id) {
18
+ error(
19
+ `${NETLIFYDEVERR} Warning: no siteId defined, unable to start Netlify Graph. To enable, run ${chalk.yellow(
20
+ 'netlify init',
21
+ )} or ${chalk.yellow('netlify link')}`,
22
+ )
23
+ }
24
+
25
+ const netlifyGraphConfig = await getNetlifyGraphConfig({ command, options })
26
+
27
+ const schemaPath = netlifyGraphConfig.graphQLSchemaFilename.join('/')
28
+
29
+ // Support tools that looks for the schema under different keys
30
+ const graphQLConfig = {
31
+ schema: [schemaPath],
32
+ schemaPath: [schemaPath],
33
+ }
34
+
35
+ const filePath = path.resolve(...netlifyGraphConfig.graphQLConfigJsonFilename)
36
+
37
+ fs.writeFileSync(filePath, JSON.stringify(graphQLConfig, null, 2))
38
+ }
39
+
40
+ /**
41
+ * Creates the `netlify graph:config:write` command
42
+ * @param {import('../base-command').BaseCommand} program
43
+ * @returns
44
+ */
45
+ const createGraphConfigWriteCommand = (program) =>
46
+ program
47
+ .command('graph:config:write')
48
+ .description(
49
+ 'Write a .graphqlrc.json file to the current directory for use with local tooling (e.g. the graphql extension for vscode)',
50
+ )
51
+ .action(async (options, command) => {
52
+ await graphConfigWrite(options, command)
53
+ })
54
+
55
+ module.exports = { createGraphConfigWriteCommand }
@@ -1,4 +1,5 @@
1
1
  // @ts-check
2
+ const { createGraphConfigWriteCommand } = require('./graph-config-write')
2
3
  const { createGraphEditCommand } = require('./graph-edit')
3
4
  const { createGraphHandlerCommand } = require('./graph-handler')
4
5
  const { createGraphLibraryCommand } = require('./graph-library')
@@ -20,6 +21,7 @@ const graph = (options, command) => {
20
21
  * @returns
21
22
  */
22
23
  const createGraphCommand = (program) => {
24
+ createGraphConfigWriteCommand(program)
23
25
  createGraphEditCommand(program)
24
26
  createGraphHandlerCommand(program)
25
27
  createGraphLibraryCommand(program)
@@ -1,4 +1,6 @@
1
1
  // @ts-check
2
+ const process = require('process')
3
+
2
4
  const jwtDecode = require('jwt-decode')
3
5
 
4
6
  const {
@@ -6,7 +8,7 @@ const {
6
8
  NETLIFYDEVERR,
7
9
  NETLIFYDEVLOG,
8
10
  error: errorExit,
9
- generateAuthlifyJWT,
11
+ generateNetlifyGraphJWT,
10
12
  getInternalFunctionsDir,
11
13
  log,
12
14
  } = require('../../utils')
@@ -45,7 +47,51 @@ const buildClientContext = function (headers) {
45
47
  }
46
48
  }
47
49
 
48
- const createHandler = function ({ config, functionsRegistry }) {
50
+ const startPollingForAPIAuthentication = async function (options) {
51
+ const { api, command, config, site, siteInfo } = options
52
+ const frequency = 5000
53
+
54
+ const helper = async (maybeSiteData) => {
55
+ const siteData = await (maybeSiteData || api.getSite({ siteId: site.id }))
56
+ const authlifyTokenId = siteData && siteData.authlify_token_id
57
+
58
+ const existingAuthlifyTokenId = config && config.netlifyGraphConfig && config.netlifyGraphConfig.authlifyTokenId
59
+ if (authlifyTokenId && authlifyTokenId !== existingAuthlifyTokenId) {
60
+ const netlifyToken = await command.authenticate()
61
+ // Only inject the authlify config if a token ID exists. This prevents
62
+ // calling command.authenticate() (which opens a browser window) if the
63
+ // user hasn't enabled API Authentication
64
+ const netlifyGraphConfig = {
65
+ netlifyToken,
66
+ authlifyTokenId: siteData.authlify_token_id,
67
+ siteId: site.id,
68
+ }
69
+ config.netlifyGraphConfig = netlifyGraphConfig
70
+
71
+ const netlifyGraphJWT = generateNetlifyGraphJWT(netlifyGraphConfig)
72
+
73
+ if (netlifyGraphJWT != null) {
74
+ // XXX(anmonteiro): this name is deprecated. Delete after 3/31/2022
75
+ process.env.ONEGRAPH_AUTHLIFY_TOKEN = netlifyGraphJWT
76
+ process.env.NETLIFY_GRAPH_TOKEN = netlifyGraphJWT
77
+ }
78
+ } else {
79
+ delete config.authlify
80
+ }
81
+
82
+ setTimeout(helper, frequency)
83
+ }
84
+
85
+ await helper(siteInfo)
86
+ }
87
+
88
+ const createHandler = function (options) {
89
+ const { config, functionsRegistry } = options
90
+
91
+ if (options.isGraphEnabled) {
92
+ startPollingForAPIAuthentication(options)
93
+ }
94
+
49
95
  return async function handler(request, response) {
50
96
  // handle proxies without path re-writes (http-servr)
51
97
  const cleanPath = request.path.replace(/^\/.netlify\/(functions|builders)/, '')
@@ -107,8 +153,10 @@ const createHandler = function ({ config, functionsRegistry }) {
107
153
  }
108
154
 
109
155
  if (config && config.authlify && config.authlify.authlifyTokenId != null) {
110
- const { authlifyTokenId, netlifyToken, siteId } = config.authlify
111
- event.authlifyToken = generateAuthlifyJWT(netlifyToken, authlifyTokenId, siteId)
156
+ // XXX(anmonteiro): this name is deprecated. Delete after 3/31/2022
157
+ const jwt = generateNetlifyGraphJWT(config.netlifyGraphConfig)
158
+ event.authlifyToken = jwt
159
+ event.netlifyGraphToken = jwt
112
160
  }
113
161
 
114
162
  const clientContext = buildClientContext(request.headers) || {}
@@ -160,14 +208,15 @@ const createHandler = function ({ config, functionsRegistry }) {
160
208
  }
161
209
  }
162
210
 
163
- const getFunctionsServer = function ({ buildersPrefix, config, functionsPrefix, functionsRegistry, siteUrl }) {
211
+ const getFunctionsServer = function (options) {
212
+ const { buildersPrefix = '', functionsPrefix = '', functionsRegistry, siteUrl } = options
164
213
  // performance optimization, load express on demand
165
214
  // eslint-disable-next-line node/global-require
166
215
  const express = require('express')
167
216
  // eslint-disable-next-line node/global-require
168
217
  const expressLogging = require('express-logging')
169
218
  const app = express()
170
- const functionHandler = createHandler({ config, functionsRegistry })
219
+ const functionHandler = createHandler(options)
171
220
 
172
221
  app.set('query parser', 'simple')
173
222
 
@@ -195,16 +244,8 @@ const getFunctionsServer = function ({ buildersPrefix, config, functionsPrefix,
195
244
  return app
196
245
  }
197
246
 
198
- const startFunctionsServer = async ({
199
- buildersPrefix = '',
200
- capabilities,
201
- config,
202
- functionsPrefix = '',
203
- settings,
204
- site,
205
- siteUrl,
206
- timeouts,
207
- }) => {
247
+ const startFunctionsServer = async (options) => {
248
+ const { capabilities, config, settings, site, siteUrl, timeouts } = options
208
249
  const internalFunctionsDir = await getInternalFunctionsDir({ base: site.root })
209
250
 
210
251
  // The order of the function directories matters. Leftmost directories take
@@ -223,13 +264,7 @@ const startFunctionsServer = async ({
223
264
 
224
265
  await functionsRegistry.scan(functionsDirectories)
225
266
 
226
- const server = getFunctionsServer({
227
- config,
228
- functionsRegistry,
229
- siteUrl,
230
- functionsPrefix,
231
- buildersPrefix,
232
- })
267
+ const server = getFunctionsServer(Object.assign(options, { functionsRegistry }))
233
268
 
234
269
  await startWebServer({ server, settings })
235
270
  }
@@ -16,6 +16,7 @@ const { watchDebounced } = require('../functions/watcher')
16
16
  const {
17
17
  generateFunctionsFile,
18
18
  generateHandlerByOperationId,
19
+ normalizeOperationsDoc,
19
20
  readGraphQLOperationsSourceFile,
20
21
  writeGraphQLOperationsSourceFile,
21
22
  writeGraphQLSchemaFile,
@@ -231,7 +232,8 @@ const updateGraphQLOperationsFileFromPersistedDoc = async (input) => {
231
232
  return
232
233
  }
233
234
 
234
- const doc = persistedDoc.query
235
+ // Sorts the operations stably, prepends the @netlify directive, etc.
236
+ const doc = normalizeOperationsDoc(persistedDoc.query)
235
237
 
236
238
  writeGraphQLOperationsSourceFile(netlifyGraphConfig, doc)
237
239
  regenerateFunctionsFileFromOperationsFile({ netlifyGraphConfig, schema })
@@ -3,7 +3,7 @@ const fs = require('fs')
3
3
  const path = require('path')
4
4
  const process = require('process')
5
5
 
6
- const { GraphQL, InternalConsole, NetlifyGraph } = require('netlify-onegraph-internal')
6
+ const { GraphQL, GraphQLHelpers, InternalConsole, NetlifyGraph } = require('netlify-onegraph-internal')
7
7
 
8
8
  const { detectServerSettings, error, execa, getFunctionsDir, log, warn } = require('../../utils')
9
9
 
@@ -202,6 +202,10 @@ const getNetlifyGraphConfig = async ({ command, options, settings }) => {
202
202
  (userSpecifiedConfig.graphQLOperationsSourceFilename &&
203
203
  userSpecifiedConfig.graphQLOperationsSourceFilename.split(path.sep)) ||
204
204
  defaultFrameworkConfig.graphQLOperationsSourceFilename
205
+ const graphQLConfigJsonFilename =
206
+ (userSpecifiedConfig.graphQLConfigJsonFilename && userSpecifiedConfig.graphQLConfigJsonFilename.split(path.sep)) ||
207
+ defaultFrameworkConfig.graphQLConfigJsonFilename ||
208
+ baseConfig.graphQLConfigJsonFilename
205
209
  const graphQLSchemaFilename =
206
210
  (userSpecifiedConfig.graphQLSchemaFilename && userSpecifiedConfig.graphQLSchemaFilename.split(path.sep)) ||
207
211
  defaultFrameworkConfig.graphQLSchemaFilename
@@ -229,6 +233,7 @@ const getNetlifyGraphConfig = async ({ command, options, settings }) => {
229
233
  netlifyGraphTypeDefinitionsFilename,
230
234
  graphQLOperationsSourceFilename,
231
235
  graphQLSchemaFilename,
236
+ graphQLConfigJsonFilename,
232
237
  netlifyGraphRequirePath,
233
238
  framework,
234
239
  language,
@@ -473,7 +478,7 @@ const { buildSchema, parse } = GraphQL
473
478
  const getGraphEditUrlBySiteName = ({ oneGraphSessionId, siteName }) => {
474
479
  const host = process.env.NETLIFY_APP_HOST || 'app.netlify.com'
475
480
  // http because app.netlify.com will redirect to https, and localhost will still work for development
476
- const url = `http://${host}/sites/${siteName}/graph/explorer?cliSessionId=${oneGraphSessionId}`
481
+ const url = `http://${host}/sites/app/${siteName}/graph/explorer/${oneGraphSessionId}`
477
482
 
478
483
  return url
479
484
  }
@@ -488,7 +493,7 @@ const getGraphEditUrlBySiteName = ({ oneGraphSessionId, siteName }) => {
488
493
  const getGraphEditUrlBySiteId = ({ oneGraphSessionId, siteId }) => {
489
494
  const host = process.env.NETLIFY_APP_HOST || 'app.netlify.com'
490
495
  // http because app.netlify.com will redirect to https, and localhost will still work for development
491
- const url = `http://${host}/site-redirect/${siteId}/graph/explorer?cliSessionId=${oneGraphSessionId}`
496
+ const url = `http://${host}/site-redirect/${siteId}/graph/explorer/${oneGraphSessionId}`
492
497
 
493
498
  return url
494
499
  }
@@ -505,6 +510,7 @@ module.exports = {
505
510
  getGraphEditUrlBySiteId,
506
511
  getGraphEditUrlBySiteName,
507
512
  getNetlifyGraphConfig,
513
+ normalizeOperationsDoc: GraphQLHelpers.normalizeOperationsDoc,
508
514
  parse,
509
515
  readGraphQLOperationsSourceFile,
510
516
  readGraphQLSchemaFile,
package/src/utils/dev.js CHANGED
@@ -194,12 +194,12 @@ const acquirePort = async ({ configuredPort, defaultPort, errorMessage }) => {
194
194
  return acquiredPort
195
195
  }
196
196
 
197
- // Generates an Authlify JWT with the following claims:
197
+ // Generates a Netlify Graph JWT with the following claims:
198
198
  // - site_id
199
199
  // - netlify_token -- the bearer token for the Netlify API
200
200
  // - authlify_token_id -- the authlify token ID stored for the site after
201
201
  // enabling API Authentication.
202
- const generateAuthlifyJWT = (netlifyToken, authlifyTokenId, siteId) => {
202
+ const generateNetlifyGraphJWT = ({ authlifyTokenId, netlifyToken, siteId }) => {
203
203
  const claims = {
204
204
  netlify_token: netlifyToken,
205
205
  authlify_token_id: authlifyTokenId,
@@ -219,5 +219,5 @@ module.exports = {
219
219
  getSiteInformation,
220
220
  injectEnvVariables,
221
221
  acquirePort,
222
- generateAuthlifyJWT,
222
+ generateNetlifyGraphJWT,
223
223
  }