netlify-cli 8.14.1 → 8.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/npm-shrinkwrap.json +20 -61
- package/package.json +6 -5
- package/src/commands/functions/functions-invoke.js +17 -2
- package/src/lib/functions/netlify-function.js +9 -1
- package/src/lib/functions/runtimes/js/builders/zisi.js +28 -6
- package/src/lib/functions/runtimes/js/index.js +2 -1
- package/src/lib/functions/scheduled.js +98 -0
- package/src/lib/functions/server.js +10 -0
- package/src/utils/functions/constants.js +5 -0
- package/src/utils/functions/get-functions.js +5 -3
- package/src/utils/functions/index.js +2 -0
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "netlify-cli",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.15.0",
|
|
4
4
|
"lockfileVersion": 2,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "netlify-cli",
|
|
9
|
-
"version": "8.
|
|
9
|
+
"version": "8.15.0",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"dependencies": {
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"@sindresorhus/slugify": "^1.1.0",
|
|
23
23
|
"ansi-escapes": "^5.0.0",
|
|
24
24
|
"ansi-styles": "^5.0.0",
|
|
25
|
+
"ansi2html": "^0.0.1",
|
|
25
26
|
"ascii-table": "0.0.9",
|
|
26
27
|
"backoff": "^2.5.0",
|
|
27
28
|
"better-opn": "^3.0.0",
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
"content-type": "^1.0.4",
|
|
37
38
|
"cookie": "^0.4.0",
|
|
38
39
|
"copy-template-dir": "^1.4.0",
|
|
40
|
+
"cron-parser": "^4.2.1",
|
|
39
41
|
"debug": "^4.1.1",
|
|
40
42
|
"decache": "^4.6.0",
|
|
41
43
|
"del": "^6.0.0",
|
|
@@ -134,7 +136,6 @@
|
|
|
134
136
|
"ini": "^2.0.0",
|
|
135
137
|
"jsonwebtoken": "^8.5.1",
|
|
136
138
|
"mock-fs": "^5.1.2",
|
|
137
|
-
"mock-require": "^3.0.3",
|
|
138
139
|
"p-timeout": "^4.0.0",
|
|
139
140
|
"proxyquire": "^2.1.3",
|
|
140
141
|
"seedrandom": "^3.0.5",
|
|
@@ -4426,6 +4427,17 @@
|
|
|
4426
4427
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
|
4427
4428
|
}
|
|
4428
4429
|
},
|
|
4430
|
+
"node_modules/ansi2html": {
|
|
4431
|
+
"version": "0.0.1",
|
|
4432
|
+
"resolved": "https://registry.npmjs.org/ansi2html/-/ansi2html-0.0.1.tgz",
|
|
4433
|
+
"integrity": "sha1-u4gARhtECvALkb89c2al4LhHO6g=",
|
|
4434
|
+
"bin": {
|
|
4435
|
+
"ansi2html": "bin/ansi2html"
|
|
4436
|
+
},
|
|
4437
|
+
"engines": {
|
|
4438
|
+
"node": ">0.4"
|
|
4439
|
+
}
|
|
4440
|
+
},
|
|
4429
4441
|
"node_modules/any-observable": {
|
|
4430
4442
|
"version": "0.3.0",
|
|
4431
4443
|
"resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz",
|
|
@@ -10887,12 +10899,6 @@
|
|
|
10887
10899
|
"node": ">=6.0"
|
|
10888
10900
|
}
|
|
10889
10901
|
},
|
|
10890
|
-
"node_modules/get-caller-file": {
|
|
10891
|
-
"version": "1.0.3",
|
|
10892
|
-
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
|
|
10893
|
-
"integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
|
|
10894
|
-
"dev": true
|
|
10895
|
-
},
|
|
10896
10902
|
"node_modules/get-intrinsic": {
|
|
10897
10903
|
"version": "1.1.1",
|
|
10898
10904
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
|
|
@@ -14770,31 +14776,6 @@
|
|
|
14770
14776
|
"node": ">=12.0.0"
|
|
14771
14777
|
}
|
|
14772
14778
|
},
|
|
14773
|
-
"node_modules/mock-require": {
|
|
14774
|
-
"version": "3.0.3",
|
|
14775
|
-
"resolved": "https://registry.npmjs.org/mock-require/-/mock-require-3.0.3.tgz",
|
|
14776
|
-
"integrity": "sha512-lLzfLHcyc10MKQnNUCv7dMcoY/2Qxd6wJfbqCcVk3LDb8An4hF6ohk5AztrvgKhJCqj36uyzi/p5se+tvyD+Wg==",
|
|
14777
|
-
"dev": true,
|
|
14778
|
-
"dependencies": {
|
|
14779
|
-
"get-caller-file": "^1.0.2",
|
|
14780
|
-
"normalize-path": "^2.1.1"
|
|
14781
|
-
},
|
|
14782
|
-
"engines": {
|
|
14783
|
-
"node": ">=4.3.0"
|
|
14784
|
-
}
|
|
14785
|
-
},
|
|
14786
|
-
"node_modules/mock-require/node_modules/normalize-path": {
|
|
14787
|
-
"version": "2.1.1",
|
|
14788
|
-
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
|
|
14789
|
-
"integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
|
|
14790
|
-
"dev": true,
|
|
14791
|
-
"dependencies": {
|
|
14792
|
-
"remove-trailing-separator": "^1.0.1"
|
|
14793
|
-
},
|
|
14794
|
-
"engines": {
|
|
14795
|
-
"node": ">=0.10.0"
|
|
14796
|
-
}
|
|
14797
|
-
},
|
|
14798
14779
|
"node_modules/module-definition": {
|
|
14799
14780
|
"version": "3.3.1",
|
|
14800
14781
|
"resolved": "https://registry.npmjs.org/module-definition/-/module-definition-3.3.1.tgz",
|
|
@@ -24813,6 +24794,11 @@
|
|
|
24813
24794
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
|
24814
24795
|
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="
|
|
24815
24796
|
},
|
|
24797
|
+
"ansi2html": {
|
|
24798
|
+
"version": "0.0.1",
|
|
24799
|
+
"resolved": "https://registry.npmjs.org/ansi2html/-/ansi2html-0.0.1.tgz",
|
|
24800
|
+
"integrity": "sha1-u4gARhtECvALkb89c2al4LhHO6g="
|
|
24801
|
+
},
|
|
24816
24802
|
"any-observable": {
|
|
24817
24803
|
"version": "0.3.0",
|
|
24818
24804
|
"resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz",
|
|
@@ -29735,12 +29721,6 @@
|
|
|
29735
29721
|
"node-source-walk": "^4.0.0"
|
|
29736
29722
|
}
|
|
29737
29723
|
},
|
|
29738
|
-
"get-caller-file": {
|
|
29739
|
-
"version": "1.0.3",
|
|
29740
|
-
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
|
|
29741
|
-
"integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
|
|
29742
|
-
"dev": true
|
|
29743
|
-
},
|
|
29744
29724
|
"get-intrinsic": {
|
|
29745
29725
|
"version": "1.1.1",
|
|
29746
29726
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
|
|
@@ -32621,27 +32601,6 @@
|
|
|
32621
32601
|
"integrity": "sha512-YkjQkdLulFrz0vD4BfNQdQRVmgycXTV7ykuHMlyv+C8WCHazpkiQRDthwa02kSyo8wKnY9wRptHfQLgmf0eR+A==",
|
|
32622
32602
|
"dev": true
|
|
32623
32603
|
},
|
|
32624
|
-
"mock-require": {
|
|
32625
|
-
"version": "3.0.3",
|
|
32626
|
-
"resolved": "https://registry.npmjs.org/mock-require/-/mock-require-3.0.3.tgz",
|
|
32627
|
-
"integrity": "sha512-lLzfLHcyc10MKQnNUCv7dMcoY/2Qxd6wJfbqCcVk3LDb8An4hF6ohk5AztrvgKhJCqj36uyzi/p5se+tvyD+Wg==",
|
|
32628
|
-
"dev": true,
|
|
32629
|
-
"requires": {
|
|
32630
|
-
"get-caller-file": "^1.0.2",
|
|
32631
|
-
"normalize-path": "^2.1.1"
|
|
32632
|
-
},
|
|
32633
|
-
"dependencies": {
|
|
32634
|
-
"normalize-path": {
|
|
32635
|
-
"version": "2.1.1",
|
|
32636
|
-
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
|
|
32637
|
-
"integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
|
|
32638
|
-
"dev": true,
|
|
32639
|
-
"requires": {
|
|
32640
|
-
"remove-trailing-separator": "^1.0.1"
|
|
32641
|
-
}
|
|
32642
|
-
}
|
|
32643
|
-
}
|
|
32644
|
-
},
|
|
32645
32604
|
"module-definition": {
|
|
32646
32605
|
"version": "3.3.1",
|
|
32647
32606
|
"resolved": "https://registry.npmjs.org/module-definition/-/module-definition-3.3.1.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": "8.
|
|
4
|
+
"version": "8.15.0",
|
|
5
5
|
"author": "Netlify Inc.",
|
|
6
6
|
"contributors": [
|
|
7
7
|
"Mathias Biilmann <matt@netlify.com> (https://twitter.com/biilmann)",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"test:dev:ava": "ava --verbose",
|
|
65
65
|
"test:ci:ava": "c8 -r json ava",
|
|
66
66
|
"test:affected": "node ./tools/affected-test.js",
|
|
67
|
-
"e2e": "node ./tools/e2e/run.
|
|
67
|
+
"e2e": "node ./tools/e2e/run.mjs",
|
|
68
68
|
"docs": "node ./site/scripts/docs.js",
|
|
69
69
|
"watch": "c8 --reporter=lcov ava --watch",
|
|
70
70
|
"site:build": "run-s site:build:*",
|
|
@@ -73,8 +73,8 @@
|
|
|
73
73
|
"postinstall": "node ./scripts/postinstall.js"
|
|
74
74
|
},
|
|
75
75
|
"config": {
|
|
76
|
-
"eslint": "--ignore-path .gitignore --cache --format=codeframe --max-warnings=0 \"{src,tools,scripts,site,tests,.github}/**/*.{js,md,html}\" \"*.{js,md,html}\" \".*.{js,md,html}\"",
|
|
77
|
-
"prettier": "--ignore-path .gitignore --loglevel=warn \"{src,tools,scripts,site,tests,.github}/**/*.{js,md,yml,json,html}\" \"*.{js,yml,json,html}\" \".*.{js,yml,json,html}\" \"!CHANGELOG.md\" \"!npm-shrinkwrap.json\" \"!.github/**/*.md\""
|
|
76
|
+
"eslint": "--ignore-path .gitignore --cache --format=codeframe --max-warnings=0 \"{src,tools,scripts,site,tests,.github}/**/*.{mjs,cjs,js,md,html}\" \"*.{mjs,cjs,js,md,html}\" \".*.{mjs,cjs,js,md,html}\"",
|
|
77
|
+
"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\""
|
|
78
78
|
},
|
|
79
79
|
"dependencies": {
|
|
80
80
|
"@netlify/build": "^26.2.0",
|
|
@@ -89,6 +89,7 @@
|
|
|
89
89
|
"@sindresorhus/slugify": "^1.1.0",
|
|
90
90
|
"ansi-escapes": "^5.0.0",
|
|
91
91
|
"ansi-styles": "^5.0.0",
|
|
92
|
+
"ansi2html": "^0.0.1",
|
|
92
93
|
"ascii-table": "0.0.9",
|
|
93
94
|
"backoff": "^2.5.0",
|
|
94
95
|
"better-opn": "^3.0.0",
|
|
@@ -103,6 +104,7 @@
|
|
|
103
104
|
"content-type": "^1.0.4",
|
|
104
105
|
"cookie": "^0.4.0",
|
|
105
106
|
"copy-template-dir": "^1.4.0",
|
|
107
|
+
"cron-parser": "^4.2.1",
|
|
106
108
|
"debug": "^4.1.1",
|
|
107
109
|
"decache": "^4.6.0",
|
|
108
110
|
"del": "^6.0.0",
|
|
@@ -197,7 +199,6 @@
|
|
|
197
199
|
"ini": "^2.0.0",
|
|
198
200
|
"jsonwebtoken": "^8.5.1",
|
|
199
201
|
"mock-fs": "^5.1.2",
|
|
200
|
-
"mock-require": "^3.0.3",
|
|
201
202
|
"p-timeout": "^4.0.0",
|
|
202
203
|
"proxyquire": "^2.1.3",
|
|
203
204
|
"seedrandom": "^3.0.5",
|
|
@@ -3,10 +3,11 @@ const fs = require('fs')
|
|
|
3
3
|
const path = require('path')
|
|
4
4
|
const process = require('process')
|
|
5
5
|
|
|
6
|
+
const CronParser = require('cron-parser')
|
|
6
7
|
const inquirer = require('inquirer')
|
|
7
8
|
const fetch = require('node-fetch')
|
|
8
9
|
|
|
9
|
-
const { BACKGROUND, NETLIFYDEVWARN, chalk, error, exit, getFunctions } = require('../../utils')
|
|
10
|
+
const { BACKGROUND, CLOCKWORK_USERAGENT, NETLIFYDEVWARN, chalk, error, exit, getFunctions } = require('../../utils')
|
|
10
11
|
|
|
11
12
|
// https://www.netlify.com/docs/functions/#event-triggered-functions
|
|
12
13
|
const events = [
|
|
@@ -130,6 +131,13 @@ const getFunctionToTrigger = function (options, argumentName) {
|
|
|
130
131
|
return argumentName
|
|
131
132
|
}
|
|
132
133
|
|
|
134
|
+
const getNextRun = function (schedule) {
|
|
135
|
+
const cron = CronParser.parseExpression(schedule, {
|
|
136
|
+
tz: 'Etc/UTC',
|
|
137
|
+
})
|
|
138
|
+
return cron.next().toDate()
|
|
139
|
+
}
|
|
140
|
+
|
|
133
141
|
/**
|
|
134
142
|
* The functions:invoke command
|
|
135
143
|
* @param {string} nameArgument
|
|
@@ -150,11 +158,18 @@ const functionsInvoke = async (nameArgument, options, command) => {
|
|
|
150
158
|
|
|
151
159
|
const functions = await getFunctions(functionsDir)
|
|
152
160
|
const functionToTrigger = await getNameFromArgs(functions, options, nameArgument)
|
|
161
|
+
const functionObj = functions.find((func) => func.name === functionToTrigger)
|
|
153
162
|
|
|
154
163
|
let headers = {}
|
|
155
164
|
let body = {}
|
|
156
165
|
|
|
157
|
-
if (
|
|
166
|
+
if (functionObj.schedule) {
|
|
167
|
+
body.next_run = getNextRun(functionObj.schedule)
|
|
168
|
+
headers = {
|
|
169
|
+
'user-agent': CLOCKWORK_USERAGENT,
|
|
170
|
+
'X-NF-Event': 'schedule',
|
|
171
|
+
}
|
|
172
|
+
} else if (eventTriggeredFunctions.has(functionToTrigger)) {
|
|
158
173
|
/** handle event triggered fns */
|
|
159
174
|
// https://www.netlify.com/docs/functions/#event-triggered-functions
|
|
160
175
|
const [name, event] = functionToTrigger.split('-')
|
|
@@ -32,6 +32,7 @@ class NetlifyFunction {
|
|
|
32
32
|
// Determines whether this is a background function based on the function
|
|
33
33
|
// name.
|
|
34
34
|
this.isBackground = name.endsWith(BACKGROUND_SUFFIX)
|
|
35
|
+
this.schedule = null
|
|
35
36
|
|
|
36
37
|
// List of the function's source files. This starts out as an empty set
|
|
37
38
|
// and will get populated on every build.
|
|
@@ -44,6 +45,12 @@ class NetlifyFunction {
|
|
|
44
45
|
return /^[A-Za-z0-9_-]+$/.test(this.name)
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
async isScheduled() {
|
|
49
|
+
await this.buildQueue
|
|
50
|
+
|
|
51
|
+
return Boolean(this.schedule)
|
|
52
|
+
}
|
|
53
|
+
|
|
47
54
|
// The `build` method transforms source files into invocable functions. Its
|
|
48
55
|
// return value is an object with:
|
|
49
56
|
//
|
|
@@ -61,12 +68,13 @@ class NetlifyFunction {
|
|
|
61
68
|
this.buildQueue = buildFunction({ cache })
|
|
62
69
|
|
|
63
70
|
try {
|
|
64
|
-
const { srcFiles, ...buildData } = await this.buildQueue
|
|
71
|
+
const { schedule, srcFiles, ...buildData } = await this.buildQueue
|
|
65
72
|
const srcFilesSet = new Set(srcFiles)
|
|
66
73
|
const srcFilesDiff = this.getSrcFilesDiff(srcFilesSet)
|
|
67
74
|
|
|
68
75
|
this.buildData = buildData
|
|
69
76
|
this.srcFiles = srcFilesSet
|
|
77
|
+
this.schedule = schedule
|
|
70
78
|
|
|
71
79
|
return { srcFilesDiff }
|
|
72
80
|
} catch (error) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { mkdir, writeFile } = require('fs').promises
|
|
2
2
|
const path = require('path')
|
|
3
3
|
|
|
4
|
-
const { zipFunction } = require('@netlify/zip-it-and-ship-it')
|
|
4
|
+
const { listFunction, zipFunction } = require('@netlify/zip-it-and-ship-it')
|
|
5
5
|
const decache = require('decache')
|
|
6
6
|
const readPkgUp = require('read-pkg-up')
|
|
7
7
|
const sourceMapSupport = require('source-map-support')
|
|
@@ -35,7 +35,11 @@ const buildFunction = async ({ cache, config, directory, func, hasTypeModule, pr
|
|
|
35
35
|
// root of the functions directory (e.g. `functions/my-func.js`). In
|
|
36
36
|
// this case, we use `mainFile` as the function path of `zipFunction`.
|
|
37
37
|
const entryPath = functionDirectory === directory ? func.mainFile : functionDirectory
|
|
38
|
-
const {
|
|
38
|
+
const {
|
|
39
|
+
inputs,
|
|
40
|
+
path: functionPath,
|
|
41
|
+
schedule,
|
|
42
|
+
} = await memoizedBuild({
|
|
39
43
|
cache,
|
|
40
44
|
cacheKey: `zisi-${entryPath}`,
|
|
41
45
|
command: () => zipFunction(entryPath, targetDirectory, zipOptions),
|
|
@@ -56,7 +60,22 @@ const buildFunction = async ({ cache, config, directory, func, hasTypeModule, pr
|
|
|
56
60
|
|
|
57
61
|
clearFunctionsCache(targetDirectory)
|
|
58
62
|
|
|
59
|
-
return { buildPath, srcFiles }
|
|
63
|
+
return { buildPath, srcFiles, schedule }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {object} params
|
|
68
|
+
* @param {unknown} params.config
|
|
69
|
+
* @param {string} params.mainFile
|
|
70
|
+
* @param {string} params.projectRoot
|
|
71
|
+
*/
|
|
72
|
+
const parseForSchedule = async ({ config, mainFile, projectRoot }) => {
|
|
73
|
+
const listedFunction = await listFunction(mainFile, {
|
|
74
|
+
config: netlifyConfigToZisiConfig({ config, projectRoot }),
|
|
75
|
+
parseISC: true,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
return listedFunction && listedFunction.schedule
|
|
60
79
|
}
|
|
61
80
|
|
|
62
81
|
// Clears the cache for any files inside the directory from which functions are
|
|
@@ -79,10 +98,11 @@ const getTargetDirectory = async ({ errorExit }) => {
|
|
|
79
98
|
return targetDirectory
|
|
80
99
|
}
|
|
81
100
|
|
|
101
|
+
const netlifyConfigToZisiConfig = ({ config, projectRoot }) =>
|
|
102
|
+
addFunctionsConfigDefaults(normalizeFunctionsConfig({ functionsConfig: config.functions, projectRoot }))
|
|
103
|
+
|
|
82
104
|
module.exports = async ({ config, directory, errorExit, func, projectRoot }) => {
|
|
83
|
-
const functionsConfig =
|
|
84
|
-
normalizeFunctionsConfig({ functionsConfig: config.functions, projectRoot }),
|
|
85
|
-
)
|
|
105
|
+
const functionsConfig = netlifyConfigToZisiConfig({ config, projectRoot })
|
|
86
106
|
|
|
87
107
|
const packageJson = await readPkgUp(func.mainFile)
|
|
88
108
|
const hasTypeModule = packageJson && packageJson.packageJson.type === 'module'
|
|
@@ -115,3 +135,5 @@ module.exports = async ({ config, directory, errorExit, func, projectRoot }) =>
|
|
|
115
135
|
target: targetDirectory,
|
|
116
136
|
}
|
|
117
137
|
}
|
|
138
|
+
|
|
139
|
+
module.exports.parseForSchedule = parseForSchedule
|
|
@@ -46,8 +46,9 @@ const getBuildFunction = async ({ config, directory, errorExit, func, projectRoo
|
|
|
46
46
|
// main file otherwise.
|
|
47
47
|
const functionDirectory = dirname(func.mainFile)
|
|
48
48
|
const srcFiles = functionDirectory === directory ? [func.mainFile] : [functionDirectory]
|
|
49
|
+
const schedule = await detectZisiBuilder.parseForSchedule({ mainFile: func.mainFile, config, projectRoot })
|
|
49
50
|
|
|
50
|
-
return () => ({ srcFiles })
|
|
51
|
+
return () => ({ schedule, srcFiles })
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
const invokeFunction = async ({ context, event, func, timeout }) => {
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const ansi2html = require('ansi2html')
|
|
2
|
+
|
|
3
|
+
const { CLOCKWORK_USERAGENT } = require('../../utils')
|
|
4
|
+
|
|
5
|
+
const { formatLambdaError } = require('./utils')
|
|
6
|
+
|
|
7
|
+
const buildHelpResponse = ({ error, headers, path, result }) => {
|
|
8
|
+
const acceptsHtml = headers.accept && headers.accept.includes('text/html')
|
|
9
|
+
|
|
10
|
+
const paragraph = (text) => {
|
|
11
|
+
text = text.trim()
|
|
12
|
+
|
|
13
|
+
if (acceptsHtml) {
|
|
14
|
+
return ansi2html(`<p>${text}</p>`)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
text = text
|
|
18
|
+
.replace(/<pre><code>/gm, '```\n')
|
|
19
|
+
.replace(/<\/code><\/pre>/gm, '\n```')
|
|
20
|
+
.replace(/<code>/gm, '`')
|
|
21
|
+
.replace(/<\/code>/gm, '`')
|
|
22
|
+
|
|
23
|
+
return `${text}\n\n`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const isSimulatedRequest = headers['user-agent'] === CLOCKWORK_USERAGENT
|
|
27
|
+
|
|
28
|
+
let message = ''
|
|
29
|
+
|
|
30
|
+
if (!isSimulatedRequest) {
|
|
31
|
+
message += paragraph(`
|
|
32
|
+
You performed an HTTP request to <code>${path}</code>, which is a scheduled function.
|
|
33
|
+
You can do this to test your functions locally, but it won't work in production.
|
|
34
|
+
`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (error) {
|
|
38
|
+
message += paragraph(`
|
|
39
|
+
There was an error during execution of your scheduled function:
|
|
40
|
+
|
|
41
|
+
<pre><code>${formatLambdaError(error)}</code></pre>`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (result) {
|
|
45
|
+
// lambda emulator adds level field, which isn't user-provided
|
|
46
|
+
const returnValue = { ...result }
|
|
47
|
+
delete returnValue.level
|
|
48
|
+
|
|
49
|
+
const { statusCode } = returnValue
|
|
50
|
+
if (statusCode >= 500) {
|
|
51
|
+
message += paragraph(`
|
|
52
|
+
Your function returned a status code of <code>${statusCode}</code>.
|
|
53
|
+
At the moment, Netlify does nothing about that. In the future, there might be a retry mechanism based on this.
|
|
54
|
+
`)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const allowedKeys = new Set(['statusCode'])
|
|
58
|
+
const returnedKeys = Object.keys(returnValue)
|
|
59
|
+
const ignoredKeys = returnedKeys.filter((key) => !allowedKeys.has(key))
|
|
60
|
+
|
|
61
|
+
if (ignoredKeys.length !== 0) {
|
|
62
|
+
message += paragraph(
|
|
63
|
+
`Your function returned ${ignoredKeys
|
|
64
|
+
.map((key) => `<code>${key}</code>`)
|
|
65
|
+
.join(', ')}. Is this an accident? It won't be interpreted by Netlify.`,
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const statusCode = error ? 500 : 200
|
|
71
|
+
return acceptsHtml
|
|
72
|
+
? {
|
|
73
|
+
statusCode,
|
|
74
|
+
contentType: 'text/html',
|
|
75
|
+
message: `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">\n
|
|
76
|
+
${message}`,
|
|
77
|
+
}
|
|
78
|
+
: {
|
|
79
|
+
statusCode,
|
|
80
|
+
contentType: 'text/plain',
|
|
81
|
+
message,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const handleScheduledFunction = ({ error, request, response, result }) => {
|
|
86
|
+
const { contentType, message, statusCode } = buildHelpResponse({
|
|
87
|
+
error,
|
|
88
|
+
headers: request.headers,
|
|
89
|
+
path: request.path,
|
|
90
|
+
result,
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
response.status(statusCode)
|
|
94
|
+
response.set('Content-Type', contentType)
|
|
95
|
+
response.send(message)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = { handleScheduledFunction, buildHelpResponse }
|
|
@@ -6,6 +6,7 @@ const { NETLIFYDEVERR, NETLIFYDEVLOG, error: errorExit, getInternalFunctionsDir,
|
|
|
6
6
|
const { handleBackgroundFunction, handleBackgroundFunctionResult } = require('./background')
|
|
7
7
|
const { createFormSubmissionHandler } = require('./form-submissions-handler')
|
|
8
8
|
const { FunctionsRegistry } = require('./registry')
|
|
9
|
+
const { handleScheduledFunction } = require('./scheduled')
|
|
9
10
|
const { handleSynchronousFunction } = require('./synchronous')
|
|
10
11
|
const { shouldBase64Encode } = require('./utils')
|
|
11
12
|
|
|
@@ -105,6 +106,15 @@ const createHandler = function ({ functionsRegistry }) {
|
|
|
105
106
|
const { error } = await func.invoke(event, clientContext)
|
|
106
107
|
|
|
107
108
|
handleBackgroundFunctionResult(functionName, error)
|
|
109
|
+
} else if (await func.isScheduled()) {
|
|
110
|
+
const { error, result } = await func.invoke(event, clientContext)
|
|
111
|
+
|
|
112
|
+
handleScheduledFunction({
|
|
113
|
+
error,
|
|
114
|
+
result,
|
|
115
|
+
request,
|
|
116
|
+
response,
|
|
117
|
+
})
|
|
108
118
|
} else {
|
|
109
119
|
const { error, result } = await func.invoke(event, clientContext)
|
|
110
120
|
|
|
@@ -5,10 +5,10 @@ const getUrlPath = (functionName) => `/.netlify/functions/${functionName}`
|
|
|
5
5
|
|
|
6
6
|
const BACKGROUND = '-background'
|
|
7
7
|
|
|
8
|
-
const addFunctionProps = ({ mainFile, name, runtime }) => {
|
|
8
|
+
const addFunctionProps = ({ mainFile, name, runtime, schedule }) => {
|
|
9
9
|
const urlPath = getUrlPath(name)
|
|
10
10
|
const isBackground = name.endsWith(BACKGROUND)
|
|
11
|
-
return { mainFile, name, runtime, urlPath, isBackground }
|
|
11
|
+
return { mainFile, name, runtime, urlPath, isBackground, schedule }
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const JS = 'js'
|
|
@@ -21,7 +21,9 @@ const getFunctions = async (functionsSrcDir) => {
|
|
|
21
21
|
// performance optimization, load '@netlify/zip-it-and-ship-it' on demand
|
|
22
22
|
// eslint-disable-next-line node/global-require
|
|
23
23
|
const { listFunctions } = require('@netlify/zip-it-and-ship-it')
|
|
24
|
-
const functions = await listFunctions(functionsSrcDir
|
|
24
|
+
const functions = await listFunctions(functionsSrcDir, {
|
|
25
|
+
parseISC: true,
|
|
26
|
+
})
|
|
25
27
|
const functionsWithProps = functions.filter(({ runtime }) => runtime === JS).map((func) => addFunctionProps(func))
|
|
26
28
|
return functionsWithProps
|
|
27
29
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
const constants = require('./constants')
|
|
1
2
|
const edgeHandlers = require('./edge-handlers')
|
|
2
3
|
const functions = require('./functions')
|
|
3
4
|
const getFunctions = require('./get-functions')
|
|
4
5
|
|
|
5
6
|
module.exports = {
|
|
7
|
+
...constants,
|
|
6
8
|
...functions,
|
|
7
9
|
...edgeHandlers,
|
|
8
10
|
...getFunctions,
|