wrangler 2.0.18 → 2.0.22
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/bin/wrangler.js +36 -5
- package/package.json +2 -2
- package/src/__tests__/configuration.test.ts +11 -7
- package/src/__tests__/dev.test.tsx +44 -0
- package/src/__tests__/helpers/run-in-tmp.ts +2 -2
- package/src/__tests__/metrics.test.ts +445 -0
- package/src/__tests__/test-old-node-version.js +31 -0
- package/src/config/config.ts +8 -0
- package/src/config/validation.ts +9 -0
- package/src/config-cache.ts +2 -1
- package/src/dev/dev.tsx +28 -18
- package/src/dev/local.tsx +7 -4
- package/src/dev/remote.tsx +5 -1
- package/src/dev.tsx +7 -1
- package/src/index.tsx +73 -0
- package/src/metrics/index.ts +4 -0
- package/src/metrics/is-ci.ts +14 -0
- package/src/metrics/metrics-config.ts +239 -0
- package/src/metrics/metrics-dispatcher.ts +95 -0
- package/src/metrics/send-event.ts +92 -0
- package/src/pages/build.tsx +2 -0
- package/src/pages/deployments.tsx +6 -1
- package/src/pages/dev.tsx +4 -0
- package/src/pages/projects.tsx +7 -1
- package/src/pages/publish.tsx +3 -0
- package/src/pages/upload.tsx +2 -2
- package/src/pubsub/pubsub-commands.tsx +46 -0
- package/src/user/user.tsx +2 -1
- package/src/whoami.tsx +2 -1
- package/src/worker-namespace.ts +16 -0
- package/wrangler-dist/cli.js +1619 -1002
package/bin/wrangler.js
CHANGED
|
@@ -3,7 +3,6 @@ const { spawn } = require("child_process");
|
|
|
3
3
|
const path = require("path");
|
|
4
4
|
const fs = require("fs");
|
|
5
5
|
const os = require("os");
|
|
6
|
-
const semiver = require("semiver");
|
|
7
6
|
|
|
8
7
|
const MIN_NODE_VERSION = "16.7.0";
|
|
9
8
|
const debug =
|
|
@@ -21,8 +20,8 @@ function runWrangler() {
|
|
|
21
20
|
// Note Volta and nvm are also recommended in the official docs:
|
|
22
21
|
// https://developers.cloudflare.com/workers/get-started/guide#2-install-the-workers-cli
|
|
23
22
|
console.error(
|
|
24
|
-
`Wrangler requires at least
|
|
25
|
-
|
|
23
|
+
`Wrangler requires at least node.js v${MIN_NODE_VERSION}. You are using v${process.versions.node}. Please update your version of node.js.
|
|
24
|
+
|
|
26
25
|
Consider using a Node.js version manager such as https://volta.sh/ or https://github.com/nvm-sh/nvm.`
|
|
27
26
|
);
|
|
28
27
|
process.exitCode = 1;
|
|
@@ -128,10 +127,42 @@ async function main() {
|
|
|
128
127
|
}
|
|
129
128
|
|
|
130
129
|
process.on("SIGINT", () => {
|
|
131
|
-
wranglerProcess
|
|
130
|
+
wranglerProcess && wranglerProcess.kill();
|
|
132
131
|
});
|
|
133
132
|
process.on("SIGTERM", () => {
|
|
134
|
-
wranglerProcess
|
|
133
|
+
wranglerProcess && wranglerProcess.kill();
|
|
135
134
|
});
|
|
136
135
|
|
|
136
|
+
// semiver implementation via https://github.com/lukeed/semiver/blob/ae7eebe6053c96be63032b14fb0b68e2553fcac4/src/index.js
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
MIT License
|
|
140
|
+
|
|
141
|
+
Copyright (c) Luke Edwards <luke.edwards05@gmail.com> (lukeed.com)
|
|
142
|
+
|
|
143
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
144
|
+
|
|
145
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
146
|
+
|
|
147
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
148
|
+
|
|
149
|
+
*/
|
|
150
|
+
|
|
151
|
+
var fn = new Intl.Collator(0, { numeric: 1 }).compare;
|
|
152
|
+
|
|
153
|
+
function semiver(a, b, bool) {
|
|
154
|
+
a = a.split(".");
|
|
155
|
+
b = b.split(".");
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
fn(a[0], b[0]) ||
|
|
159
|
+
fn(a[1], b[1]) ||
|
|
160
|
+
((b[2] = b.slice(2).join(".")),
|
|
161
|
+
(bool = /[.-]/.test((a[2] = a.slice(2).join(".")))),
|
|
162
|
+
bool == /[.-]/.test(b[2]) ? fn(a[2], b[2]) : bool ? -1 : 1)
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// end semiver implementation
|
|
167
|
+
|
|
137
168
|
void main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wrangler",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.22",
|
|
4
4
|
"description": "Command-line interface for all things Cloudflare Workers",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"wrangler",
|
|
@@ -93,7 +93,6 @@
|
|
|
93
93
|
"nanoid": "^3.3.3",
|
|
94
94
|
"path-to-regexp": "^6.2.0",
|
|
95
95
|
"selfsigned": "^2.0.1",
|
|
96
|
-
"semiver": "^1.1.0",
|
|
97
96
|
"xxhash-wasm": "^1.0.1"
|
|
98
97
|
},
|
|
99
98
|
"devDependencies": {
|
|
@@ -131,6 +130,7 @@
|
|
|
131
130
|
"ink-table": "^3.0.0",
|
|
132
131
|
"ink-testing-library": "^2.1.0",
|
|
133
132
|
"ink-text-input": "^4.0.3",
|
|
133
|
+
"is-ci": "^3.0.1",
|
|
134
134
|
"jest-fetch-mock": "^3.0.3",
|
|
135
135
|
"jest-websocket-mock": "^2.3.0",
|
|
136
136
|
"mime": "^3.0.0",
|
|
@@ -40,6 +40,7 @@ describe("normalizeAndValidateConfig()", () => {
|
|
|
40
40
|
tsconfig: undefined,
|
|
41
41
|
kv_namespaces: [],
|
|
42
42
|
legacy_env: true,
|
|
43
|
+
send_metrics: undefined,
|
|
43
44
|
main: undefined,
|
|
44
45
|
migrations: [],
|
|
45
46
|
name: undefined,
|
|
@@ -76,6 +77,7 @@ describe("normalizeAndValidateConfig()", () => {
|
|
|
76
77
|
it("should override config defaults with provided values", () => {
|
|
77
78
|
const expectedConfig: Partial<ConfigFields<RawDevConfig>> = {
|
|
78
79
|
legacy_env: true,
|
|
80
|
+
send_metrics: false,
|
|
79
81
|
dev: {
|
|
80
82
|
ip: "255.255.255.255",
|
|
81
83
|
port: 9999,
|
|
@@ -98,6 +100,7 @@ describe("normalizeAndValidateConfig()", () => {
|
|
|
98
100
|
it("should error on invalid top level fields", () => {
|
|
99
101
|
const expectedConfig = {
|
|
100
102
|
legacy_env: "FOO",
|
|
103
|
+
send_metrics: "BAD",
|
|
101
104
|
dev: {
|
|
102
105
|
ip: 222,
|
|
103
106
|
port: "FOO",
|
|
@@ -117,13 +120,14 @@ describe("normalizeAndValidateConfig()", () => {
|
|
|
117
120
|
);
|
|
118
121
|
expect(diagnostics.hasWarnings()).toBe(false);
|
|
119
122
|
expect(diagnostics.renderErrors()).toMatchInlineSnapshot(`
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
123
|
+
"Processing wrangler configuration:
|
|
124
|
+
- Expected \\"legacy_env\\" to be of type boolean but got \\"FOO\\".
|
|
125
|
+
- Expected \\"send_metrics\\" to be of type boolean but got \\"BAD\\".
|
|
126
|
+
- Expected \\"dev.ip\\" to be of type string but got 222.
|
|
127
|
+
- Expected \\"dev.port\\" to be of type number but got \\"FOO\\".
|
|
128
|
+
- Expected \\"dev.local_protocol\\" field to be one of [\\"http\\",\\"https\\"] but got \\"wss\\".
|
|
129
|
+
- Expected \\"dev.upstream_protocol\\" field to be one of [\\"http\\",\\"https\\"] but got \\"ws\\"."
|
|
130
|
+
`);
|
|
127
131
|
});
|
|
128
132
|
|
|
129
133
|
it("should warn on and remove unexpected top level fields", () => {
|
|
@@ -1049,6 +1049,50 @@ describe("wrangler dev", () => {
|
|
|
1049
1049
|
}
|
|
1050
1050
|
`);
|
|
1051
1051
|
});
|
|
1052
|
+
|
|
1053
|
+
it("should default to true, without a warning", async () => {
|
|
1054
|
+
fs.writeFileSync("index.js", `export default {};`);
|
|
1055
|
+
await runWrangler("dev index.js");
|
|
1056
|
+
expect((Dev as jest.Mock).mock.calls[0][0].inspect).toEqual(true);
|
|
1057
|
+
expect(std).toMatchInlineSnapshot(`
|
|
1058
|
+
Object {
|
|
1059
|
+
"debug": "",
|
|
1060
|
+
"err": "",
|
|
1061
|
+
"out": "",
|
|
1062
|
+
"warn": "",
|
|
1063
|
+
}
|
|
1064
|
+
`);
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
it("should pass true, with a warning", async () => {
|
|
1068
|
+
fs.writeFileSync("index.js", `export default {};`);
|
|
1069
|
+
await runWrangler("dev index.js --inspect");
|
|
1070
|
+
expect((Dev as jest.Mock).mock.calls[0][0].inspect).toEqual(true);
|
|
1071
|
+
expect(std).toMatchInlineSnapshot(`
|
|
1072
|
+
Object {
|
|
1073
|
+
"debug": "",
|
|
1074
|
+
"err": "",
|
|
1075
|
+
"out": "",
|
|
1076
|
+
"warn": "[33m▲ [43;33m[[43;30mWARNING[43;33m][0m [1mPassing --inspect is unnecessary, now you can always connect to devtools.[0m
|
|
1077
|
+
|
|
1078
|
+
",
|
|
1079
|
+
}
|
|
1080
|
+
`);
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
it("should pass false, without a warning", async () => {
|
|
1084
|
+
fs.writeFileSync("index.js", `export default {};`);
|
|
1085
|
+
await runWrangler("dev index.js --inspect false");
|
|
1086
|
+
expect((Dev as jest.Mock).mock.calls[0][0].inspect).toEqual(false);
|
|
1087
|
+
expect(std).toMatchInlineSnapshot(`
|
|
1088
|
+
Object {
|
|
1089
|
+
"debug": "",
|
|
1090
|
+
"err": "",
|
|
1091
|
+
"out": "",
|
|
1092
|
+
"warn": "",
|
|
1093
|
+
}
|
|
1094
|
+
`);
|
|
1095
|
+
});
|
|
1052
1096
|
});
|
|
1053
1097
|
|
|
1054
1098
|
describe("service bindings", () => {
|
|
@@ -6,7 +6,7 @@ import { reinitialiseAuthTokens } from "../../user";
|
|
|
6
6
|
const originalCwd = process.cwd();
|
|
7
7
|
|
|
8
8
|
export function runInTempDir({ homedir } = { homedir: "./home" }) {
|
|
9
|
-
let tmpDir: string
|
|
9
|
+
let tmpDir: string;
|
|
10
10
|
|
|
11
11
|
beforeEach(() => {
|
|
12
12
|
// Use realpath because the temporary path can point to a symlink rather than the actual path.
|
|
@@ -30,7 +30,7 @@ export function runInTempDir({ homedir } = { homedir: "./home" }) {
|
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
afterEach(() => {
|
|
33
|
-
if (
|
|
33
|
+
if (fs.existsSync(tmpDir)) {
|
|
34
34
|
process.chdir(originalCwd);
|
|
35
35
|
fs.rmSync(tmpDir, { recursive: true });
|
|
36
36
|
}
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import fetchMock from "jest-fetch-mock";
|
|
3
|
+
import { version as wranglerVersion } from "../../package.json";
|
|
4
|
+
import { purgeConfigCaches, saveToConfigCache } from "../config-cache";
|
|
5
|
+
import { logger } from "../logger";
|
|
6
|
+
import { getMetricsDispatcher, getMetricsConfig } from "../metrics";
|
|
7
|
+
import { CI } from "../metrics/is-ci";
|
|
8
|
+
import {
|
|
9
|
+
CURRENT_METRICS_DATE,
|
|
10
|
+
readMetricsConfig,
|
|
11
|
+
USER_ID_CACHE_PATH,
|
|
12
|
+
writeMetricsConfig,
|
|
13
|
+
} from "../metrics/metrics-config";
|
|
14
|
+
import { writeAuthConfigFile } from "../user";
|
|
15
|
+
import { setMockResponse, unsetAllMocks } from "./helpers/mock-cfetch";
|
|
16
|
+
import { mockConsoleMethods } from "./helpers/mock-console";
|
|
17
|
+
import { mockConfirm } from "./helpers/mock-dialogs";
|
|
18
|
+
import { useMockIsTTY } from "./helpers/mock-istty";
|
|
19
|
+
import { runInTempDir } from "./helpers/run-in-tmp";
|
|
20
|
+
|
|
21
|
+
declare const global: { SPARROW_SOURCE_KEY: string | undefined };
|
|
22
|
+
|
|
23
|
+
describe("metrics", () => {
|
|
24
|
+
const ORIGINAL_SPARROW_SOURCE_KEY = global.SPARROW_SOURCE_KEY;
|
|
25
|
+
const std = mockConsoleMethods();
|
|
26
|
+
runInTempDir();
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
global.SPARROW_SOURCE_KEY = "MOCK_KEY";
|
|
30
|
+
logger.loggerLevel = "debug";
|
|
31
|
+
// Create a node_modules directory to store config-cache files
|
|
32
|
+
mkdirSync("node_modules");
|
|
33
|
+
});
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
global.SPARROW_SOURCE_KEY = ORIGINAL_SPARROW_SOURCE_KEY;
|
|
36
|
+
fetchMock.resetMocks();
|
|
37
|
+
unsetAllMocks();
|
|
38
|
+
purgeConfigCaches();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("getMetricsDispatcher()", () => {
|
|
42
|
+
const MOCK_DISPATCHER_OPTIONS = {
|
|
43
|
+
// By setting this to true we avoid the `getMetricsConfig()` logic in these tests.
|
|
44
|
+
sendMetrics: true,
|
|
45
|
+
offline: false,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// These tests should never hit the `/user` API endpoint.
|
|
49
|
+
const userRequests = mockUserRequest();
|
|
50
|
+
afterEach(() => {
|
|
51
|
+
expect(userRequests.count).toBe(0);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("identify()", () => {
|
|
55
|
+
it("should send a request to the default URL", async () => {
|
|
56
|
+
const dispatcher = await getMetricsDispatcher(MOCK_DISPATCHER_OPTIONS);
|
|
57
|
+
fetchMock.mockResponse("");
|
|
58
|
+
await dispatcher.identify({ a: 1, b: 2 });
|
|
59
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
60
|
+
const [url, { body, headers }] = fetchMock.mock.calls[0];
|
|
61
|
+
expect(url).toEqual("https://sparrow.cloudflare.com/api/v1/identify");
|
|
62
|
+
expect(JSON.parse(body)).toEqual(
|
|
63
|
+
expect.objectContaining({
|
|
64
|
+
event: "identify",
|
|
65
|
+
properties: {
|
|
66
|
+
category: "Workers",
|
|
67
|
+
wranglerVersion,
|
|
68
|
+
a: 1,
|
|
69
|
+
b: 2,
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
expect(headers).toEqual(
|
|
74
|
+
expect.objectContaining({
|
|
75
|
+
"Sparrow-Source-Key": "MOCK_KEY",
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
expect(std.debug).toMatchInlineSnapshot(
|
|
79
|
+
`"Metrics dispatcher: Posting data {\\"type\\":\\"identify\\",\\"name\\":\\"identify\\",\\"properties\\":{\\"a\\":1,\\"b\\":2}}"`
|
|
80
|
+
);
|
|
81
|
+
expect(std.out).toMatchInlineSnapshot(`""`);
|
|
82
|
+
expect(std.warn).toMatchInlineSnapshot(`""`);
|
|
83
|
+
expect(std.err).toMatchInlineSnapshot(`""`);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should write a debug log if the dispatcher is disabled", async () => {
|
|
87
|
+
const dispatcher = await getMetricsDispatcher({
|
|
88
|
+
...MOCK_DISPATCHER_OPTIONS,
|
|
89
|
+
sendMetrics: false,
|
|
90
|
+
});
|
|
91
|
+
await dispatcher.identify({ a: 1, b: 2 });
|
|
92
|
+
expect(fetchMock).toHaveBeenCalledTimes(0);
|
|
93
|
+
expect(std.debug).toMatchInlineSnapshot(
|
|
94
|
+
`"Metrics dispatcher: Dispatching disabled - would have sent {\\"type\\":\\"identify\\",\\"name\\":\\"identify\\",\\"properties\\":{\\"a\\":1,\\"b\\":2}}."`
|
|
95
|
+
);
|
|
96
|
+
expect(std.out).toMatchInlineSnapshot(`""`);
|
|
97
|
+
expect(std.warn).toMatchInlineSnapshot(`""`);
|
|
98
|
+
expect(std.err).toMatchInlineSnapshot(`""`);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should write a debug log if the request fails", async () => {
|
|
102
|
+
const dispatcher = await getMetricsDispatcher(MOCK_DISPATCHER_OPTIONS);
|
|
103
|
+
fetchMock.mockReject(new Error("BAD REQUEST"));
|
|
104
|
+
await dispatcher.identify({ a: 1, b: 2 });
|
|
105
|
+
expect(std.debug).toMatchInlineSnapshot(`
|
|
106
|
+
"Metrics dispatcher: Posting data {\\"type\\":\\"identify\\",\\"name\\":\\"identify\\",\\"properties\\":{\\"a\\":1,\\"b\\":2}}
|
|
107
|
+
Metrics dispatcher: Failed to send request: BAD REQUEST"
|
|
108
|
+
`);
|
|
109
|
+
expect(std.out).toMatchInlineSnapshot(`""`);
|
|
110
|
+
expect(std.warn).toMatchInlineSnapshot(`""`);
|
|
111
|
+
expect(std.err).toMatchInlineSnapshot(`""`);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should write a warning log if no source key has been provided", async () => {
|
|
115
|
+
global.SPARROW_SOURCE_KEY = undefined;
|
|
116
|
+
const dispatcher = await getMetricsDispatcher(MOCK_DISPATCHER_OPTIONS);
|
|
117
|
+
await dispatcher.identify({ a: 1, b: 2 });
|
|
118
|
+
expect(fetchMock).toHaveBeenCalledTimes(0);
|
|
119
|
+
expect(std.debug).toMatchInlineSnapshot(
|
|
120
|
+
`"Metrics dispatcher: Source Key not provided. Be sure to initialize before sending events. { type: 'identify', name: 'identify', properties: { a: 1, b: 2 } }"`
|
|
121
|
+
);
|
|
122
|
+
expect(std.out).toMatchInlineSnapshot(`""`);
|
|
123
|
+
expect(std.warn).toMatchInlineSnapshot(`""`);
|
|
124
|
+
expect(std.err).toMatchInlineSnapshot(`""`);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("sendEvent()", () => {
|
|
129
|
+
it("should send a request to the default URL", async () => {
|
|
130
|
+
const dispatcher = await getMetricsDispatcher(MOCK_DISPATCHER_OPTIONS);
|
|
131
|
+
fetchMock.mockResponse("");
|
|
132
|
+
await dispatcher.sendEvent("some-event", { a: 1, b: 2 });
|
|
133
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
134
|
+
const [url, { body, headers }] = fetchMock.mock.calls[0];
|
|
135
|
+
expect(url).toEqual("https://sparrow.cloudflare.com/api/v1/event");
|
|
136
|
+
expect(JSON.parse(body)).toEqual(
|
|
137
|
+
expect.objectContaining({
|
|
138
|
+
event: "some-event",
|
|
139
|
+
properties: {
|
|
140
|
+
category: "Workers",
|
|
141
|
+
wranglerVersion,
|
|
142
|
+
a: 1,
|
|
143
|
+
b: 2,
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
);
|
|
147
|
+
expect(headers).toEqual(
|
|
148
|
+
expect.objectContaining({
|
|
149
|
+
"Sparrow-Source-Key": "MOCK_KEY",
|
|
150
|
+
})
|
|
151
|
+
);
|
|
152
|
+
expect(std.debug).toMatchInlineSnapshot(
|
|
153
|
+
`"Metrics dispatcher: Posting data {\\"type\\":\\"event\\",\\"name\\":\\"some-event\\",\\"properties\\":{\\"a\\":1,\\"b\\":2}}"`
|
|
154
|
+
);
|
|
155
|
+
expect(std.out).toMatchInlineSnapshot(`""`);
|
|
156
|
+
expect(std.warn).toMatchInlineSnapshot(`""`);
|
|
157
|
+
expect(std.err).toMatchInlineSnapshot(`""`);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("should write a debug log if the dispatcher is disabled", async () => {
|
|
161
|
+
const dispatcher = await getMetricsDispatcher({
|
|
162
|
+
...MOCK_DISPATCHER_OPTIONS,
|
|
163
|
+
sendMetrics: false,
|
|
164
|
+
});
|
|
165
|
+
await dispatcher.sendEvent("some-event", { a: 1, b: 2 });
|
|
166
|
+
expect(fetchMock).toHaveBeenCalledTimes(0);
|
|
167
|
+
expect(std.debug).toMatchInlineSnapshot(
|
|
168
|
+
`"Metrics dispatcher: Dispatching disabled - would have sent {\\"type\\":\\"event\\",\\"name\\":\\"some-event\\",\\"properties\\":{\\"a\\":1,\\"b\\":2}}."`
|
|
169
|
+
);
|
|
170
|
+
expect(std.out).toMatchInlineSnapshot(`""`);
|
|
171
|
+
expect(std.warn).toMatchInlineSnapshot(`""`);
|
|
172
|
+
expect(std.err).toMatchInlineSnapshot(`""`);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("should write a debug log if the request fails", async () => {
|
|
176
|
+
const dispatcher = await getMetricsDispatcher(MOCK_DISPATCHER_OPTIONS);
|
|
177
|
+
fetchMock.mockReject(new Error("BAD REQUEST"));
|
|
178
|
+
await dispatcher.sendEvent("some-event", { a: 1, b: 2 });
|
|
179
|
+
expect(std.debug).toMatchInlineSnapshot(`
|
|
180
|
+
"Metrics dispatcher: Posting data {\\"type\\":\\"event\\",\\"name\\":\\"some-event\\",\\"properties\\":{\\"a\\":1,\\"b\\":2}}
|
|
181
|
+
Metrics dispatcher: Failed to send request: BAD REQUEST"
|
|
182
|
+
`);
|
|
183
|
+
expect(std.out).toMatchInlineSnapshot(`""`);
|
|
184
|
+
expect(std.warn).toMatchInlineSnapshot(`""`);
|
|
185
|
+
expect(std.err).toMatchInlineSnapshot(`""`);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("should write a warning log if no source key has been provided", async () => {
|
|
189
|
+
global.SPARROW_SOURCE_KEY = undefined;
|
|
190
|
+
const dispatcher = await getMetricsDispatcher(MOCK_DISPATCHER_OPTIONS);
|
|
191
|
+
await dispatcher.sendEvent("some-event", { a: 1, b: 2 });
|
|
192
|
+
expect(fetchMock).toHaveBeenCalledTimes(0);
|
|
193
|
+
expect(std.debug).toMatchInlineSnapshot(
|
|
194
|
+
`"Metrics dispatcher: Source Key not provided. Be sure to initialize before sending events. { type: 'event', name: 'some-event', properties: { a: 1, b: 2 } }"`
|
|
195
|
+
);
|
|
196
|
+
expect(std.out).toMatchInlineSnapshot(`""`);
|
|
197
|
+
expect(std.warn).toMatchInlineSnapshot(`""`);
|
|
198
|
+
expect(std.err).toMatchInlineSnapshot(`""`);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe("getMetricsConfig()", () => {
|
|
204
|
+
let isCISpy: jest.SpyInstance;
|
|
205
|
+
|
|
206
|
+
const { setIsTTY } = useMockIsTTY();
|
|
207
|
+
beforeEach(() => {
|
|
208
|
+
// Default the mock TTY to interactive for all these tests.
|
|
209
|
+
setIsTTY(true);
|
|
210
|
+
isCISpy = jest.spyOn(CI, "isCI").mockReturnValue(false);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe("enabled", () => {
|
|
214
|
+
const ORIGINAL_ENV = process.env;
|
|
215
|
+
beforeEach(() => {
|
|
216
|
+
process.env = { ...ORIGINAL_ENV };
|
|
217
|
+
});
|
|
218
|
+
afterEach(() => {
|
|
219
|
+
process.env = ORIGINAL_ENV;
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("should return the WRANGLER_SEND_METRICS environment variable for enabled if it is defined", async () => {
|
|
223
|
+
process.env.WRANGLER_SEND_METRICS = "false";
|
|
224
|
+
expect(await getMetricsConfig({})).toMatchObject({
|
|
225
|
+
enabled: false,
|
|
226
|
+
});
|
|
227
|
+
process.env.WRANGLER_SEND_METRICS = "true";
|
|
228
|
+
expect(await getMetricsConfig({})).toMatchObject({
|
|
229
|
+
enabled: true,
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("should return false if running in a CI environment", async () => {
|
|
234
|
+
isCISpy.mockReturnValue(true);
|
|
235
|
+
expect(await getMetricsConfig({})).toMatchObject({
|
|
236
|
+
enabled: false,
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("should return the sendMetrics argument for enabled if it is defined", async () => {
|
|
241
|
+
expect(
|
|
242
|
+
await getMetricsConfig({ sendMetrics: false, offline: false })
|
|
243
|
+
).toMatchObject({
|
|
244
|
+
enabled: false,
|
|
245
|
+
});
|
|
246
|
+
expect(
|
|
247
|
+
await getMetricsConfig({ sendMetrics: true, offline: false })
|
|
248
|
+
).toMatchObject({
|
|
249
|
+
enabled: true,
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("should return enabled false if the process is not interactive", async () => {
|
|
254
|
+
setIsTTY(false);
|
|
255
|
+
expect(
|
|
256
|
+
await getMetricsConfig({
|
|
257
|
+
sendMetrics: undefined,
|
|
258
|
+
offline: false,
|
|
259
|
+
})
|
|
260
|
+
).toMatchObject({
|
|
261
|
+
enabled: false,
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("should return enabled true if the user on this device previously agreed to send metrics", async () => {
|
|
266
|
+
await writeMetricsConfig({
|
|
267
|
+
permission: {
|
|
268
|
+
enabled: true,
|
|
269
|
+
date: new Date(2022, 6, 4),
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
expect(
|
|
273
|
+
await getMetricsConfig({
|
|
274
|
+
sendMetrics: undefined,
|
|
275
|
+
offline: false,
|
|
276
|
+
})
|
|
277
|
+
).toMatchObject({
|
|
278
|
+
enabled: true,
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it("should return enabled false if the user on this device previously refused to send metrics", async () => {
|
|
283
|
+
await writeMetricsConfig({
|
|
284
|
+
permission: {
|
|
285
|
+
enabled: false,
|
|
286
|
+
date: new Date(2022, 6, 4),
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
expect(
|
|
290
|
+
await getMetricsConfig({
|
|
291
|
+
sendMetrics: undefined,
|
|
292
|
+
offline: false,
|
|
293
|
+
})
|
|
294
|
+
).toMatchObject({
|
|
295
|
+
enabled: false,
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("should accept and store permission granting to send metrics if the user agrees", async () => {
|
|
300
|
+
const checkConfirmations = mockConfirm({
|
|
301
|
+
text: "Would you like to help improve Wrangler by sending usage metrics to Cloudflare?",
|
|
302
|
+
result: true,
|
|
303
|
+
});
|
|
304
|
+
expect(
|
|
305
|
+
await getMetricsConfig({
|
|
306
|
+
sendMetrics: undefined,
|
|
307
|
+
offline: false,
|
|
308
|
+
})
|
|
309
|
+
).toMatchObject({
|
|
310
|
+
enabled: true,
|
|
311
|
+
});
|
|
312
|
+
checkConfirmations();
|
|
313
|
+
expect((await readMetricsConfig()).permission).toMatchObject({
|
|
314
|
+
enabled: true,
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it("should accept and store permission declining to send metrics if the user declines", async () => {
|
|
319
|
+
const checkConfirmations = mockConfirm({
|
|
320
|
+
text: "Would you like to help improve Wrangler by sending usage metrics to Cloudflare?",
|
|
321
|
+
result: false,
|
|
322
|
+
});
|
|
323
|
+
expect(
|
|
324
|
+
await getMetricsConfig({
|
|
325
|
+
sendMetrics: undefined,
|
|
326
|
+
offline: false,
|
|
327
|
+
})
|
|
328
|
+
).toMatchObject({
|
|
329
|
+
enabled: false,
|
|
330
|
+
});
|
|
331
|
+
checkConfirmations();
|
|
332
|
+
expect((await readMetricsConfig()).permission).toMatchObject({
|
|
333
|
+
enabled: false,
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it("should ignore the config if the permission date is older than the current metrics date", async () => {
|
|
338
|
+
const checkConfirmations = mockConfirm({
|
|
339
|
+
text: "Would you like to help improve Wrangler by sending usage metrics to Cloudflare?",
|
|
340
|
+
result: false,
|
|
341
|
+
});
|
|
342
|
+
const OLD_DATE = new Date(2000);
|
|
343
|
+
await writeMetricsConfig({
|
|
344
|
+
permission: { enabled: true, date: OLD_DATE },
|
|
345
|
+
});
|
|
346
|
+
expect(
|
|
347
|
+
await getMetricsConfig({
|
|
348
|
+
sendMetrics: undefined,
|
|
349
|
+
offline: false,
|
|
350
|
+
})
|
|
351
|
+
).toMatchObject({
|
|
352
|
+
enabled: false,
|
|
353
|
+
});
|
|
354
|
+
checkConfirmations();
|
|
355
|
+
const { permission } = await readMetricsConfig();
|
|
356
|
+
expect(permission?.enabled).toBe(false);
|
|
357
|
+
// The date should be updated to today's date
|
|
358
|
+
expect(permission?.date).toEqual(CURRENT_METRICS_DATE);
|
|
359
|
+
|
|
360
|
+
expect(std.out).toMatchInlineSnapshot(`
|
|
361
|
+
"Usage metrics tracking has changed since you last granted permission.
|
|
362
|
+
Your choice has been saved in the following file: home/.wrangler/config/metrics.json.
|
|
363
|
+
|
|
364
|
+
You can override the user level setting for a project in \`wrangler.toml\`:
|
|
365
|
+
|
|
366
|
+
- to disable sending metrics for a project: \`send_metrics = false\`
|
|
367
|
+
- to enable sending metrics for a project: \`send_metrics = true\`"
|
|
368
|
+
`);
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
describe("deviceId", () => {
|
|
373
|
+
it("should return a deviceId found in the config file", async () => {
|
|
374
|
+
await writeMetricsConfig({ deviceId: "XXXX-YYYY-ZZZZ" });
|
|
375
|
+
const { deviceId } = await getMetricsConfig({
|
|
376
|
+
sendMetrics: true,
|
|
377
|
+
offline: false,
|
|
378
|
+
});
|
|
379
|
+
expect(deviceId).toEqual("XXXX-YYYY-ZZZZ");
|
|
380
|
+
expect((await readMetricsConfig()).deviceId).toEqual(deviceId);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("should create and store a new deviceId if none is found in the config file", async () => {
|
|
384
|
+
await writeMetricsConfig({});
|
|
385
|
+
const { deviceId } = await getMetricsConfig({
|
|
386
|
+
sendMetrics: true,
|
|
387
|
+
offline: false,
|
|
388
|
+
});
|
|
389
|
+
expect(deviceId).toMatch(
|
|
390
|
+
/[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}/
|
|
391
|
+
);
|
|
392
|
+
expect((await readMetricsConfig()).deviceId).toEqual(deviceId);
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
describe("userId", () => {
|
|
397
|
+
const userRequests = mockUserRequest();
|
|
398
|
+
it("should return a userId found in a cache file", async () => {
|
|
399
|
+
await saveToConfigCache(USER_ID_CACHE_PATH, {
|
|
400
|
+
userId: "CACHED_USER_ID",
|
|
401
|
+
});
|
|
402
|
+
const { userId } = await getMetricsConfig({
|
|
403
|
+
sendMetrics: true,
|
|
404
|
+
offline: false,
|
|
405
|
+
});
|
|
406
|
+
expect(userId).toEqual("CACHED_USER_ID");
|
|
407
|
+
expect(userRequests.count).toBe(0);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it("should fetch the userId from Cloudflare and store it in a cache file", async () => {
|
|
411
|
+
writeAuthConfigFile({ oauth_token: "DUMMY_TOKEN" });
|
|
412
|
+
const { userId } = await getMetricsConfig({
|
|
413
|
+
sendMetrics: true,
|
|
414
|
+
offline: false,
|
|
415
|
+
});
|
|
416
|
+
expect(userId).toEqual("MOCK_USER_ID");
|
|
417
|
+
expect(userRequests.count).toBe(1);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it("should not fetch the userId from Cloudflare if running in `offline` mode", async () => {
|
|
421
|
+
writeAuthConfigFile({ oauth_token: "DUMMY_TOKEN" });
|
|
422
|
+
const { userId } = await getMetricsConfig({
|
|
423
|
+
sendMetrics: true,
|
|
424
|
+
offline: true,
|
|
425
|
+
});
|
|
426
|
+
expect(userId).toBe(undefined);
|
|
427
|
+
expect(userRequests.count).toBe(0);
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
function mockUserRequest() {
|
|
434
|
+
const requests = { count: 0 };
|
|
435
|
+
beforeEach(() => {
|
|
436
|
+
setMockResponse("/user", () => {
|
|
437
|
+
requests.count++;
|
|
438
|
+
return { id: "MOCK_USER_ID" };
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
afterEach(() => {
|
|
442
|
+
requests.count = 0;
|
|
443
|
+
});
|
|
444
|
+
return requests;
|
|
445
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// this test has to be run with a version of node.js older than 16.7 to pass
|
|
2
|
+
|
|
3
|
+
const { spawn } = require("child_process");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const assert = require("assert");
|
|
7
|
+
|
|
8
|
+
const wranglerProcess = spawn(
|
|
9
|
+
"node",
|
|
10
|
+
[path.join(__dirname, "../../bin/wrangler.js")],
|
|
11
|
+
{ stdio: "pipe" }
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const messageToMatch = "Wrangler requires at least node.js v16.7.0";
|
|
15
|
+
|
|
16
|
+
wranglerProcess.once("exit", (code) => {
|
|
17
|
+
try {
|
|
18
|
+
const errorMessage = wranglerProcess.stderr.read().toString();
|
|
19
|
+
|
|
20
|
+
assert(code === 1, "Expected exit code 1");
|
|
21
|
+
assert(
|
|
22
|
+
errorMessage.includes(messageToMatch),
|
|
23
|
+
`Expected error message to include "${messageToMatch}"`
|
|
24
|
+
);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.error("Error:", err);
|
|
27
|
+
throw new Error(
|
|
28
|
+
"This test has to be run with a version of node.js under 16.7 to pass"
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
});
|
package/src/config/config.ts
CHANGED
|
@@ -38,6 +38,14 @@ export interface ConfigFields<Dev extends RawDevConfig> {
|
|
|
38
38
|
*/
|
|
39
39
|
legacy_env: boolean;
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Whether Wrangler should send usage metrics to Cloudflare for this project.
|
|
43
|
+
*
|
|
44
|
+
* When defined this will override any user settings.
|
|
45
|
+
* Otherwise, Wrangler will use the user's preference.
|
|
46
|
+
*/
|
|
47
|
+
send_metrics: boolean | undefined;
|
|
48
|
+
|
|
41
49
|
/**
|
|
42
50
|
* Options to configure the development server that your worker will use.
|
|
43
51
|
*/
|