netlify-cli 10.14.0 → 10.15.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 CHANGED
@@ -165,7 +165,7 @@ Manage netlify functions
165
165
  | [`graph:init`](/docs/commands/graph.md#graphinit) | Initialize all the resources for Netlify Graph |
166
166
  | [`graph:library`](/docs/commands/graph.md#graphlibrary) | Generate the Graph function library |
167
167
  | [`graph:operations`](/docs/commands/graph.md#graphoperations) | List all of the locally available operations |
168
- | [`graph:pull`](/docs/commands/graph.md#graphpull) | Pull down your local Netlify Graph schema, and process pending Graph edit events |
168
+ | [`graph:pull`](/docs/commands/graph.md#graphpull) | Pull your remote Netlify Graph schema locally, and process pending Graph edit events |
169
169
 
170
170
 
171
171
  ### [init](/docs/commands/init.md)
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "netlify-cli",
3
- "version": "10.14.0",
3
+ "version": "10.15.0",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "netlify-cli",
9
- "version": "10.14.0",
9
+ "version": "10.15.0",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT",
12
12
  "dependencies": {
@@ -81,7 +81,7 @@
81
81
  "multiparty": "^4.2.1",
82
82
  "netlify": "^12.0.0",
83
83
  "netlify-headers-parser": "^6.0.2",
84
- "netlify-onegraph-internal": "0.4.1",
84
+ "netlify-onegraph-internal": "0.4.2",
85
85
  "netlify-redirect-parser": "^13.0.5",
86
86
  "netlify-redirector": "^0.2.1",
87
87
  "node-fetch": "^2.6.0",
@@ -8194,7 +8194,7 @@
8194
8194
  "node_modules/cpy/node_modules/glob-parent": {
8195
8195
  "version": "3.1.0",
8196
8196
  "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
8197
- "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
8197
+ "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==",
8198
8198
  "dependencies": {
8199
8199
  "is-glob": "^3.1.0",
8200
8200
  "path-dirname": "^1.0.0"
@@ -11962,9 +11962,9 @@
11962
11962
  }
11963
11963
  },
11964
11964
  "node_modules/follow-redirects": {
11965
- "version": "1.14.7",
11966
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
11967
- "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==",
11965
+ "version": "1.15.1",
11966
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
11967
+ "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
11968
11968
  "funding": [
11969
11969
  {
11970
11970
  "type": "individual",
@@ -12507,9 +12507,9 @@
12507
12507
  }
12508
12508
  },
12509
12509
  "node_modules/got": {
12510
- "version": "11.8.3",
12511
- "resolved": "https://registry.npmjs.org/got/-/got-11.8.3.tgz",
12512
- "integrity": "sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg==",
12510
+ "version": "11.8.5",
12511
+ "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz",
12512
+ "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==",
12513
12513
  "dev": true,
12514
12514
  "dependencies": {
12515
12515
  "@sindresorhus/is": "^4.0.0",
@@ -13312,9 +13312,9 @@
13312
13312
  }
13313
13313
  },
13314
13314
  "node_modules/inquirer/node_modules/ansi-regex": {
13315
- "version": "4.1.0",
13316
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
13317
- "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
13315
+ "version": "4.1.1",
13316
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
13317
+ "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
13318
13318
  "engines": {
13319
13319
  "node": ">=6"
13320
13320
  }
@@ -13391,9 +13391,9 @@
13391
13391
  }
13392
13392
  },
13393
13393
  "node_modules/inquirer/node_modules/string-width/node_modules/ansi-regex": {
13394
- "version": "3.0.0",
13395
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
13396
- "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
13394
+ "version": "3.0.1",
13395
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
13396
+ "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
13397
13397
  "engines": {
13398
13398
  "node": ">=4"
13399
13399
  }
@@ -14842,9 +14842,9 @@
14842
14842
  }
14843
14843
  },
14844
14844
  "node_modules/listr-update-renderer/node_modules/wrap-ansi/node_modules/ansi-regex": {
14845
- "version": "3.0.0",
14846
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
14847
- "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
14845
+ "version": "3.0.1",
14846
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
14847
+ "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
14848
14848
  "engines": {
14849
14849
  "node": ">=4"
14850
14850
  }
@@ -16384,12 +16384,13 @@
16384
16384
  }
16385
16385
  },
16386
16386
  "node_modules/netlify-onegraph-internal": {
16387
- "version": "0.4.1",
16388
- "resolved": "https://registry.npmjs.org/netlify-onegraph-internal/-/netlify-onegraph-internal-0.4.1.tgz",
16389
- "integrity": "sha512-b+9gNOJHUeFZ9DoUDYuOLxMhtfIibEO6PZKSj3Xf5TtV9k536uX2c963kldb3VQ2HfIv9S5H5iirgkXcJfzXHw==",
16387
+ "version": "0.4.2",
16388
+ "resolved": "https://registry.npmjs.org/netlify-onegraph-internal/-/netlify-onegraph-internal-0.4.2.tgz",
16389
+ "integrity": "sha512-3TGD/s2FGjx9NcOcMPMMUxamy3IVY9O0ZZ176FuHVDmLAc5l8mj0ZNoUbhdrXfZjes62Gt6/YynCJxGShE6oUA==",
16390
16390
  "dependencies": {
16391
16391
  "graphql": "16.0.0",
16392
16392
  "node-fetch": "^2.6.0",
16393
+ "rusha": "^0.8.14",
16393
16394
  "uuid": "^8.3.2"
16394
16395
  }
16395
16396
  },
@@ -19977,6 +19978,11 @@
19977
19978
  "queue-microtask": "^1.2.2"
19978
19979
  }
19979
19980
  },
19981
+ "node_modules/rusha": {
19982
+ "version": "0.8.14",
19983
+ "resolved": "https://registry.npmjs.org/rusha/-/rusha-0.8.14.tgz",
19984
+ "integrity": "sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA=="
19985
+ },
19980
19986
  "node_modules/rxjs": {
19981
19987
  "version": "6.6.7",
19982
19988
  "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
@@ -29028,7 +29034,7 @@
29028
29034
  "glob-parent": {
29029
29035
  "version": "3.1.0",
29030
29036
  "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
29031
- "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
29037
+ "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==",
29032
29038
  "requires": {
29033
29039
  "is-glob": "^3.1.0",
29034
29040
  "path-dirname": "^1.0.0"
@@ -31954,9 +31960,9 @@
31954
31960
  }
31955
31961
  },
31956
31962
  "follow-redirects": {
31957
- "version": "1.14.7",
31958
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
31959
- "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ=="
31963
+ "version": "1.15.1",
31964
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
31965
+ "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
31960
31966
  },
31961
31967
  "for-in": {
31962
31968
  "version": "1.0.2",
@@ -32362,9 +32368,9 @@
32362
32368
  }
32363
32369
  },
32364
32370
  "got": {
32365
- "version": "11.8.3",
32366
- "resolved": "https://registry.npmjs.org/got/-/got-11.8.3.tgz",
32367
- "integrity": "sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg==",
32371
+ "version": "11.8.5",
32372
+ "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz",
32373
+ "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==",
32368
32374
  "dev": true,
32369
32375
  "requires": {
32370
32376
  "@sindresorhus/is": "^4.0.0",
@@ -32911,9 +32917,9 @@
32911
32917
  "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ=="
32912
32918
  },
32913
32919
  "ansi-regex": {
32914
- "version": "4.1.0",
32915
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
32916
- "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
32920
+ "version": "4.1.1",
32921
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
32922
+ "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="
32917
32923
  },
32918
32924
  "ansi-styles": {
32919
32925
  "version": "3.2.1",
@@ -32966,9 +32972,9 @@
32966
32972
  },
32967
32973
  "dependencies": {
32968
32974
  "ansi-regex": {
32969
- "version": "3.0.0",
32970
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
32971
- "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
32975
+ "version": "3.0.1",
32976
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
32977
+ "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw=="
32972
32978
  },
32973
32979
  "strip-ansi": {
32974
32980
  "version": "4.0.0",
@@ -34075,9 +34081,9 @@
34075
34081
  },
34076
34082
  "dependencies": {
34077
34083
  "ansi-regex": {
34078
- "version": "3.0.0",
34079
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
34080
- "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
34084
+ "version": "3.0.1",
34085
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
34086
+ "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw=="
34081
34087
  },
34082
34088
  "is-fullwidth-code-point": {
34083
34089
  "version": "2.0.0",
@@ -35257,12 +35263,13 @@
35257
35263
  }
35258
35264
  },
35259
35265
  "netlify-onegraph-internal": {
35260
- "version": "0.4.1",
35261
- "resolved": "https://registry.npmjs.org/netlify-onegraph-internal/-/netlify-onegraph-internal-0.4.1.tgz",
35262
- "integrity": "sha512-b+9gNOJHUeFZ9DoUDYuOLxMhtfIibEO6PZKSj3Xf5TtV9k536uX2c963kldb3VQ2HfIv9S5H5iirgkXcJfzXHw==",
35266
+ "version": "0.4.2",
35267
+ "resolved": "https://registry.npmjs.org/netlify-onegraph-internal/-/netlify-onegraph-internal-0.4.2.tgz",
35268
+ "integrity": "sha512-3TGD/s2FGjx9NcOcMPMMUxamy3IVY9O0ZZ176FuHVDmLAc5l8mj0ZNoUbhdrXfZjes62Gt6/YynCJxGShE6oUA==",
35263
35269
  "requires": {
35264
35270
  "graphql": "16.0.0",
35265
35271
  "node-fetch": "^2.6.0",
35272
+ "rusha": "^0.8.14",
35266
35273
  "uuid": "^8.3.2"
35267
35274
  },
35268
35275
  "dependencies": {
@@ -37945,6 +37952,11 @@
37945
37952
  "queue-microtask": "^1.2.2"
37946
37953
  }
37947
37954
  },
37955
+ "rusha": {
37956
+ "version": "0.8.14",
37957
+ "resolved": "https://registry.npmjs.org/rusha/-/rusha-0.8.14.tgz",
37958
+ "integrity": "sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA=="
37959
+ },
37948
37960
  "rxjs": {
37949
37961
  "version": "6.6.7",
37950
37962
  "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "netlify-cli",
3
3
  "description": "Netlify command line tool",
4
- "version": "10.14.0",
4
+ "version": "10.15.0",
5
5
  "author": "Netlify Inc.",
6
6
  "contributors": [
7
7
  "Abraham Schilling <AbrahamSchilling@gmail.com> (https://gitlab.com/n4bb12)",
@@ -291,7 +291,7 @@
291
291
  "multiparty": "^4.2.1",
292
292
  "netlify": "^12.0.0",
293
293
  "netlify-headers-parser": "^6.0.2",
294
- "netlify-onegraph-internal": "0.4.1",
294
+ "netlify-onegraph-internal": "0.4.2",
295
295
  "netlify-redirect-parser": "^13.0.5",
296
296
  "netlify-redirector": "^0.2.1",
297
297
  "node-fetch": "^2.6.0",
@@ -48,6 +48,7 @@ const graphEdit = async (options, command) => {
48
48
  netlifyToken,
49
49
  site,
50
50
  state,
51
+ netlifyGraphConfig,
51
52
  })
52
53
 
53
54
  const { branch } = gitRepoInfo()
@@ -5,6 +5,7 @@ const { OneGraphClient } = require('netlify-onegraph-internal')
5
5
  const { v4: uuidv4 } = require('uuid')
6
6
 
7
7
  const { OneGraphCliClient, ensureCLISession } = require('../../lib/one-graph/cli-client')
8
+ const { getNetlifyGraphConfig } = require('../../lib/one-graph/cli-netlify-graph')
8
9
  const { NETLIFYDEVERR, chalk, error, exit, getToken, log } = require('../../utils')
9
10
  const { msg } = require('../login/login')
10
11
 
@@ -63,11 +64,13 @@ const graphInit = async (options, command) => {
63
64
 
64
65
  await ensureAppForSite(netlifyToken, siteId)
65
66
 
67
+ const netlifyGraphConfig = await getNetlifyGraphConfig({ command, options })
66
68
  await ensureCLISession({
67
69
  metadata: {},
68
70
  netlifyToken,
69
71
  site,
70
72
  state,
73
+ netlifyGraphConfig,
71
74
  })
72
75
 
73
76
  let envChanged = false
@@ -1,4 +1,5 @@
1
1
  // @ts-check
2
+ const { readLockfile } = require('../../lib/one-graph/cli-client')
2
3
  const {
3
4
  buildSchema,
4
5
  defaultExampleOperationsDoc,
@@ -9,7 +10,7 @@ const {
9
10
  readGraphQLOperationsSourceFile,
10
11
  readGraphQLSchemaFile,
11
12
  } = require('../../lib/one-graph/cli-netlify-graph')
12
- const { error, log } = require('../../utils')
13
+ const { NETLIFYDEVERR, chalk, error, log } = require('../../utils')
13
14
 
14
15
  /**
15
16
  * Creates the `netlify graph:library` command
@@ -42,10 +43,23 @@ const graphLibrary = async (options, command) => {
42
43
  const parsedDoc = parse(currentOperationsDoc)
43
44
  const { fragments, functions } = extractFunctionsFromOperationDoc(parsedDoc)
44
45
 
46
+ const lockfile = readLockfile({ siteRoot: command.netlify.site.root })
47
+
48
+ if (lockfile == null) {
49
+ error(
50
+ `${NETLIFYDEVERR} Error: no lockfile found, unable to run \`netlify graph:library\`. To pull a remote schema (and create a lockfile), run ${chalk.yellow(
51
+ 'netlify graph:pull',
52
+ )} `,
53
+ )
54
+ }
55
+
56
+ const schemaId = lockfile && lockfile.locked.schemaId
57
+
45
58
  generateFunctionsFile({
46
59
  logger: log,
47
60
  netlifyGraphConfig,
48
61
  schema,
62
+ schemaId,
49
63
  operationsDoc: currentOperationsDoc,
50
64
  functions,
51
65
  fragments,
@@ -34,6 +34,13 @@ const graphPull = async (options, command) => {
34
34
  const { jwt } = await OneGraphCliClient.getGraphJwtForSite({ siteId, nfToken: netlifyToken })
35
35
 
36
36
  const oneGraphSessionId = loadCLISession(state)
37
+ if (!oneGraphSessionId) {
38
+ warn(
39
+ 'No local Netlify Graph session found, skipping command queue drain. Create a new session by running `netlify graph:edit`.',
40
+ )
41
+ return
42
+ }
43
+
37
44
  await refetchAndGenerateFromOneGraph({
38
45
  logger: log,
39
46
  netlifyGraphConfig,
@@ -43,13 +50,6 @@ const graphPull = async (options, command) => {
43
50
  sessionId: oneGraphSessionId,
44
51
  })
45
52
 
46
- if (!oneGraphSessionId) {
47
- warn(
48
- 'No local Netlify Graph session found, skipping command queue drain. Create a new session by running `netlify graph:edit`.',
49
- )
50
- return
51
- }
52
-
53
53
  const schemaString = readGraphQLSchemaFile(netlifyGraphConfig)
54
54
 
55
55
  let schema
@@ -106,7 +106,7 @@ const graphPull = async (options, command) => {
106
106
  const createGraphPullCommand = (program) =>
107
107
  program
108
108
  .command('graph:pull')
109
- .description('Pull down your local Netlify Graph schema, and process pending Graph edit events')
109
+ .description('Pull your remote Netlify Graph schema locally, and process pending Graph edit events')
110
110
  .action(async (options, command) => {
111
111
  await graphPull(options, command)
112
112
  })
@@ -2,12 +2,13 @@
2
2
  /* eslint-disable eslint-comments/disable-enable-pair */
3
3
  /* eslint-disable fp/no-loops */
4
4
  const crypto = require('crypto')
5
+ const { readFileSync, writeFileSync } = require('fs')
5
6
  const os = require('os')
6
7
  const path = require('path')
7
8
 
8
9
  const gitRepoInfo = require('git-repo-info')
9
10
  const { GraphQL, InternalConsole, OneGraphClient } = require('netlify-onegraph-internal')
10
- const { NetlifyGraph } = require('netlify-onegraph-internal')
11
+ const { NetlifyGraph, NetlifyGraphLockfile } = require('netlify-onegraph-internal')
11
12
 
12
13
  // eslint-disable-next-line no-unused-vars
13
14
  const { StateConfig, USER_AGENT, chalk, error, execa, log, warn, watchDebounced } = require('../../utils')
@@ -273,6 +274,33 @@ const regenerateFunctionsFileFromOperationsFile = (input) => {
273
274
  generateFunctionsFile({ netlifyGraphConfig, schema, operationsDoc: appOperationsDoc, functions, fragments })
274
275
  }
275
276
 
277
+ /**
278
+ * Lockfile Operations
279
+ */
280
+
281
+ /**
282
+ * Persist the Netlify Graph lockfile on disk
283
+ * @param {object} input
284
+ * @param {string} input.siteRoot The GraphQL schema to use when generating code
285
+ * @param {NetlifyGraphLockfile.V0_format} input.lockfile
286
+ */
287
+ const writeLockfile = ({ lockfile, siteRoot }) => {
288
+ writeFileSync(path.join(siteRoot, NetlifyGraphLockfile.defaultLockFileName), JSON.stringify(lockfile, null, 2))
289
+ }
290
+
291
+ /**
292
+ * Read the Netlify Graph lockfile from disk, if it exists
293
+ * @param {object} input
294
+ * @param {string} input.siteRoot The GraphQL schema to use when generating code
295
+ * @return {NetlifyGraphLockfile.V0_format | undefined}
296
+ */
297
+ const readLockfile = ({ siteRoot }) => {
298
+ try {
299
+ const buf = readFileSync(path.join(siteRoot, NetlifyGraphLockfile.defaultLockFileName))
300
+ return JSON.parse(buf.toString('utf8'))
301
+ } catch {}
302
+ }
303
+
276
304
  /**
277
305
  * Compute a md5 hash of a string
278
306
  * @param {string} input String to compute a quick md5 hash for
@@ -285,7 +313,8 @@ const quickHash = (input) => {
285
313
  }
286
314
 
287
315
  /**
288
- * Fetch a persisted operations doc by its id, write it to the system, and regenerate the library
316
+ * Fetch a persisted operations doc by its id, normalize it for Netlify Graph
317
+ * and return its contents as a string
289
318
  * @param {object} input
290
319
  * @param {string} input.siteId The site id to query against
291
320
  * @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
@@ -293,11 +322,11 @@ const quickHash = (input) => {
293
322
  * @param {(message: string) => void=} input.logger A function that if provided will be used to log messages
294
323
  * @param {GraphQL.GraphQLSchema} input.schema The GraphQL schema to use when generating code
295
324
  * @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
296
- * @returns
325
+ * @returns {Promise<string | undefined>}
297
326
  */
298
- const updateGraphQLOperationsFileFromPersistedDoc = async (input) => {
327
+ const fetchGraphQLOperationsLibraryFromPersistedDoc = async (input) => {
299
328
  try {
300
- const { docId, logger, netlifyGraphConfig, netlifyToken, schema, siteId } = input
329
+ const { docId, netlifyToken, siteId } = input
301
330
  const { jwt } = await OneGraphClient.getGraphJwtForSite({ siteId, nfToken: netlifyToken })
302
331
  const persistedDoc = await OneGraphClient.fetchPersistedQuery(jwt, siteId, docId)
303
332
  if (!persistedDoc) {
@@ -308,21 +337,63 @@ const updateGraphQLOperationsFileFromPersistedDoc = async (input) => {
308
337
  // Sorts the operations stably, prepends the @netlify directive, etc.
309
338
  const operationsDocString = normalizeOperationsDoc(persistedDoc.query)
310
339
 
311
- writeGraphQLOperationsSourceFile({ logger, netlifyGraphConfig, operationsDocString })
312
- regenerateFunctionsFileFromOperationsFile({ netlifyGraphConfig, schema })
340
+ return operationsDocString
341
+ } catch {
342
+ warn(`Unable to reach Netlify Graph servers in order to update Graph operations file`)
343
+ }
344
+ }
345
+
346
+ /**
347
+ * Fetch a persisted operations doc by its id, write it to the system, and regenerate the library
348
+ * @param {object} input
349
+ * @param {string} input.operationsDocString The contents of the GraphQL operations document
350
+ * @param {(message: string) => void=} input.logger A function that if provided will be used to log messages
351
+ * @param {GraphQL.GraphQLSchema} input.schema The GraphQL schema to use when generating code
352
+ * @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
353
+ * @returns
354
+ */
355
+ const updateGraphQLOperationsFileFromPersistedDoc = (input) => {
356
+ const { logger, netlifyGraphConfig, operationsDocString, schema } = input
313
357
 
314
- const hash = quickHash(operationsDocString)
358
+ writeGraphQLOperationsSourceFile({ logger, netlifyGraphConfig, operationsDocString })
359
+ regenerateFunctionsFileFromOperationsFile({ netlifyGraphConfig, schema })
315
360
 
316
- const relevantHasLength = 10
361
+ const hash = quickHash(operationsDocString)
317
362
 
318
- if (witnessedIncomingDocumentHashes.length > relevantHasLength) {
319
- witnessedIncomingDocumentHashes.shift()
320
- }
363
+ const relevantHasLength = 10
321
364
 
322
- witnessedIncomingDocumentHashes.push(hash)
323
- } catch {
324
- warn(`Unable to reach Netlify Graph servers in order to update Graph operations file`)
365
+ if (witnessedIncomingDocumentHashes.length > relevantHasLength) {
366
+ witnessedIncomingDocumentHashes.shift()
325
367
  }
368
+
369
+ witnessedIncomingDocumentHashes.push(hash)
370
+ }
371
+
372
+ /**
373
+ * Fetch a persisted operations doc by its id, write it to the system, and regenerate the library
374
+ * @param {object} input
375
+ * @param {string} input.siteId The site id to query against
376
+ * @param {string} input.schemaId The schema ID to query against
377
+ * @param {string} input.siteRoot Path to the root of the project
378
+ * @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
379
+ * @param {string} input.docId The GraphQL operations document id to fetch
380
+ * @param {(message: string) => void=} input.logger A function that if provided will be used to log messages
381
+ * @param {GraphQL.GraphQLSchema} input.schema The GraphQL schema to use when generating code
382
+ * @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
383
+ * @returns {Promise<string | undefined>}
384
+ */
385
+ const handleOperationsLibraryPersistedEvent = async (input) => {
386
+ const { schemaId, siteRoot } = input
387
+ const operationsFileContents = await fetchGraphQLOperationsLibraryFromPersistedDoc(input)
388
+
389
+ if (!operationsFileContents) {
390
+ // `fetch` already warned
391
+ return
392
+ }
393
+
394
+ const lockfile = NetlifyGraphLockfile.createLockfile({ operationsFileContents, schemaId })
395
+ writeLockfile({ siteRoot, lockfile })
396
+ updateGraphQLOperationsFileFromPersistedDoc({ ...input, operationsDocString: operationsFileContents })
326
397
  }
327
398
 
328
399
  const handleCliSessionEvent = async ({
@@ -412,12 +483,14 @@ const handleCliSessionEvent = async ({
412
483
  break
413
484
  }
414
485
  case 'OneGraphNetlifyCliSessionPersistedLibraryUpdatedEvent':
415
- await updateGraphQLOperationsFileFromPersistedDoc({
486
+ await handleOperationsLibraryPersistedEvent({
416
487
  netlifyToken,
417
488
  docId: payload.docId,
489
+ schemaId: payload.schemaId,
418
490
  netlifyGraphConfig,
419
491
  schema,
420
492
  siteId,
493
+ siteRoot,
421
494
  })
422
495
  break
423
496
  default: {
@@ -538,6 +611,20 @@ const persistNewOperationsDocForSession = async ({
538
611
  return
539
612
  }
540
613
 
614
+ const lockfile = readLockfile({ siteRoot })
615
+
616
+ if (!lockfile) {
617
+ warn(
618
+ `can't find a lockfile for the project while running trying to persist operations for session. To pull a remote schema (and create a lockfile), run ${chalk.yellow(
619
+ 'netlify graph:pull',
620
+ )} `,
621
+ )
622
+ }
623
+
624
+ // NOTE(anmonteiro): We still persist a new operations document because we
625
+ // might be checking out someone else's branch whose session we don't have
626
+ // access to.
627
+
541
628
  const { branch } = gitRepoInfo()
542
629
  const { jwt } = await OneGraphClient.getGraphJwtForSite({ siteId, nfToken: netlifyToken })
543
630
  const persistedResult = await OneGraphClient.executeCreatePersistedQueryMutation(
@@ -575,6 +662,15 @@ const persistNewOperationsDocForSession = async ({
575
662
 
576
663
  if (result.errors) {
577
664
  warn(`Unable to update session metadata with updated operations doc ${JSON.stringify(result.errors, null, 2)}`)
665
+ } else if (lockfile != null) {
666
+ // Now that we've persisted the document, lock it in the lockfile
667
+ const currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
668
+
669
+ const newLockfile = NetlifyGraphLockfile.createLockfile({
670
+ schemaId: lockfile.locked.schemaId,
671
+ operationsFileContents: currentOperationsDoc,
672
+ })
673
+ writeLockfile({ siteRoot, lockfile: newLockfile })
578
674
  }
579
675
  }
580
676
 
@@ -611,6 +707,7 @@ const startOneGraphCLISession = async (input) => {
611
707
  site,
612
708
  state,
613
709
  oneGraphSessionId: input.oneGraphSessionId,
710
+ netlifyGraphConfig,
614
711
  })
615
712
 
616
713
  const enabledServices = []
@@ -718,29 +815,81 @@ const generateSessionName = () => {
718
815
  return sessionName
719
816
  }
720
817
 
818
+ /**
819
+ * Mark a session as inactive so it doesn't show up in any UI lists, and potentially becomes available to GC later
820
+ * @param {object} input
821
+ * @param {{metadata: {schemaId:string}; id: string; appId: string; name?: string}} input.session The current session
822
+ * @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
823
+ * @param {NetlifyGraphLockfile.V0_format | undefined} input.lockfile A function to call to set/get the current state of the local Netlify project
824
+ */
825
+ const idempotentlyUpdateSessionSchemaIdFromLockfile = async (input) => {
826
+ const { lockfile, netlifyToken, session } = input
827
+ const sessionSchemaId = session.metadata && session.metadata.schemaId
828
+ const lockfileSchemaId = lockfile && lockfile.locked.schemaId
829
+
830
+ if (lockfileSchemaId != null && sessionSchemaId !== lockfileSchemaId) {
831
+ // Local schema always wins, update the session metadata to reflect that
832
+ const siteId = session.appId
833
+ const { jwt } = await OneGraphClient.getGraphJwtForSite({ siteId, nfToken: netlifyToken })
834
+
835
+ log(`Found new lockfile, overwriting session ${session.name || session.id}`)
836
+ return OneGraphClient.updateCLISessionMetadata(jwt, siteId, session.id, {
837
+ ...session.metadata,
838
+ schemaId: lockfileSchemaId,
839
+ })
840
+ }
841
+ }
842
+
721
843
  /**
722
844
  * Ensures a cli session exists for the current checkout, or errors out if it doesn't and cannot create one.
845
+ * @param {object} input
846
+ * @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
847
+ * @param {object} input.metadata
848
+ * @param {string} input.netlifyToken
849
+ * @param {StateConfig} input.state
850
+ * @param {string} [input.oneGraphSessionId]
851
+ * @param {any} input.site The site object
723
852
  */
724
853
  const ensureCLISession = async (input) => {
725
- const { metadata, netlifyToken, site, state } = input
854
+ const { metadata, netlifyGraphConfig, netlifyToken, site, state } = input
726
855
  let oneGraphSessionId = input.oneGraphSessionId ? input.oneGraphSessionId : loadCLISession(state)
727
856
  let parentCliSessionId = null
728
857
  const { jwt } = await OneGraphClient.getGraphJwtForSite({ siteId: site.id, nfToken: netlifyToken })
729
858
 
859
+ const lockfile = readLockfile({ siteRoot: site.root })
860
+
730
861
  // Validate that session still exists and we can access it
731
862
  try {
732
863
  if (oneGraphSessionId) {
733
- const sessionEvents = await OneGraphClient.fetchCliSessionEvents({
864
+ const { errors, session } = await OneGraphClient.fetchCliSession({
734
865
  appId: site.id,
735
866
  jwt,
736
867
  sessionId: oneGraphSessionId,
868
+ desiredEventCount: 0,
737
869
  })
738
- if (sessionEvents.errors) {
739
- warn(`Unable to fetch cli session: ${JSON.stringify(sessionEvents.errors, null, 2)}`)
870
+ if (errors) {
871
+ warn(`Unable to fetch cli session: ${JSON.stringify(errors, null, 2)}`)
740
872
  log(`Creating new cli session`)
741
873
  parentCliSessionId = oneGraphSessionId
742
874
  oneGraphSessionId = null
743
875
  }
876
+
877
+ // During the transition to lockfiles, write a lockfile if one isn't
878
+ // found. Later, only handling a 'OneGraphNetlifyCliSessionPersistedLibraryUpdatedEvent'
879
+ // will create or update the lockfile
880
+ // TODO(anmonteiro): remove this in the future?
881
+ if (lockfile == null && session.metadata.schemaId) {
882
+ const currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
883
+ log(`Generating Netlify Graph lockfile at ${NetlifyGraphLockfile.defaultLockFileName}`)
884
+
885
+ const newLockfile = NetlifyGraphLockfile.createLockfile({
886
+ schemaId: session.metadata.schemaId,
887
+ operationsFileContents: currentOperationsDoc,
888
+ })
889
+ writeLockfile({ siteRoot: site.root, lockfile: newLockfile })
890
+ }
891
+
892
+ await idempotentlyUpdateSessionSchemaIdFromLockfile({ session, lockfile, netlifyToken })
744
893
  }
745
894
  } catch (fetchSessionError) {
746
895
  warn(`Unable to fetch cli session events: ${JSON.stringify(fetchSessionError, null, 2)}`)
@@ -752,17 +901,25 @@ const ensureCLISession = async (input) => {
752
901
  const sessionName = generateSessionName()
753
902
  const detectedMetadata = detectLocalCLISessionMetadata({ siteRoot: site.root })
754
903
  const newSessionMetadata = parentCliSessionId ? { parentCliSessionId } : {}
904
+
755
905
  const sessionMetadata = {
756
906
  ...detectedMetadata,
757
907
  ...newSessionMetadata,
758
908
  ...metadata,
759
909
  }
910
+
911
+ if (lockfile != null) {
912
+ log(`Creating new session "${sessionName}" from lockfile`)
913
+ sessionMetadata.schemaId = lockfile.locked.schemaId
914
+ }
915
+
760
916
  const oneGraphSession = await createCLISession({
761
917
  netlifyToken,
762
918
  siteId: site.id,
763
919
  sessionName,
764
920
  metadata: sessionMetadata,
765
921
  })
922
+
766
923
  oneGraphSessionId = oneGraphSession.id
767
924
  }
768
925
 
@@ -808,4 +965,5 @@ module.exports = {
808
965
  refetchAndGenerateFromOneGraph,
809
966
  startOneGraphCLISession,
810
967
  upsertMergeCLISessionMetadata,
968
+ readLockfile,
811
969
  }